FA-62076 / Currency rounding and FX conversion / Open access
A fee equal to the transfer amount yields a zero payout · case 01
A transfer entirely consumed by the fee is accepted and pays out 0.
ROOT CAUSE
The guard rejects only fee > amount.
VERIFIED REPAIR
Reject when the fee is greater than or equal to the amount.
Unsuccessful approach: Capping the fee at the amount still accepts the transfer with a zero payout.
Case contract
solve(amount, rate, src_exp, dst_exp, fee_bps, min_fee): amount and min_fee are source minor units, fee_bps a non-negative integer. fee = max(min_fee, ceil(amount*fee_bps/10000)) in source minor units, deducted before conversion. A fee greater than or equal to the amount returns 'ERR:amount-below-fee'; a non-positive amount or negative bps returns 'ERR:input'. The net amount is converted exactly (net * rate * 10**(dst_exp - src_exp)) and rounded half-even. Return [fee, converted].
Why this case matters
Currency amounts must be rounded at the right stage and in the right unit, or ledgers, quotes and settlements drift by minor units.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
from decimal import Decimal, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_FLOOR, ROUND_CEILING, ROUND_DOWN, ROUND_UP, ROUND_HALF_DOWN
N = 1
observations = []
def solve(amount, rate, src_exp, dst_exp, fee_bps, min_fee):
if fee_bps < 0 or amount <= 0: return 'ERR:input'
conv = lambda m: Decimal(m) * Decimal(rate) * Decimal(10) ** (dst_exp - src_exp)
rq = lambda d: int(d.quantize(Decimal(1), rounding=ROUND_HALF_EVEN))
pct = -(-amount * fee_bps // 10000)
fee = max(min_fee, pct)
if fee > amount: return 'ERR:amount-below-fee'
return [fee, rq(conv(amount - fee))]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression fee-exhausts-amount 1', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
('partial repair guard 2', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
('control: yen payout', (10000, '151.37', 2, 0, 150, 50), [150, 14910]),
('control: fee rounds up', (1001, '1', 2, 2, 100, 0), [11, 990]),
('control: minimum fee applies', (20000, '0.3071', 2, 3, 25, 100), [100, 61113]),
('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842])],
[('regression fee-exhausts-amount 1', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
('partial repair guard 2', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
('control: minimum fee applies', (20000, '0.3071', 2, 3, 25, 100), [100, 61113]),
('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842]),
('control: large percentage fee', (123456, '0.92', 2, 2, 250, 199), [3087, 110739]),
('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input')],
[('regression fee-exhausts-amount 1', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
('partial repair guard 2', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input'),
('control: yen payout', (10000, '151.37', 2, 0, 150, 50), [150, 14910]),
('control: fee rounds up', (1001, '1', 2, 2, 100, 0), [11, 990]),
('control: minimum fee applies', (20000, '0.3071', 2, 3, 25, 100), [100, 61113])],
[('regression fee-exhausts-amount 1', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
('partial repair guard 2', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
('control: fee rounds up', (1001, '1', 2, 2, 100, 0), [11, 990]),
('control: minimum fee applies', (20000, '0.3071', 2, 3, 25, 100), [100, 61113]),
('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842]),
('control: large percentage fee', (123456, '0.92', 2, 2, 250, 199), [3087, 110739])],
[('regression fee-exhausts-amount 1', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
('partial repair guard 2', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842]),
('control: large percentage fee', (123456, '0.92', 2, 2, 250, 199), [3087, 110739]),
('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input'),
('control: yen payout', (10000, '151.37', 2, 0, 150, 50), [150, 14910])]]
for label, args, expected in cases[N - 1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression fee-exhausts-amount 1 | [500, 0] | ERR:amount-below-fee | Failed |
| partial repair guard 2 | ERR:amount-below-fee | ERR:amount-below-fee | Passed |
| control: yen payout | [150, 14910] | [150, 14910] | Passed |
| control: fee rounds up | [11, 990] | [11, 990] | Passed |
| control: minimum fee applies | [100, 61113] | [100, 61113] | Passed |
| control: zero bps | [0, 842] | [0, 842] | Passed |
SHA-256 / e8c8b914ebb993d08fd503709de7b3b25930667cf6f34dc388522b65f466e267
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
from decimal import Decimal, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_FLOOR, ROUND_CEILING, ROUND_DOWN, ROUND_UP, ROUND_HALF_DOWN
N = 1
observations = []
def solve(amount, rate, src_exp, dst_exp, fee_bps, min_fee):
if fee_bps < 0 or amount <= 0: return 'ERR:input'
conv = lambda m: Decimal(m) * Decimal(rate) * Decimal(10) ** (dst_exp - src_exp)
rq = lambda d: int(d.quantize(Decimal(1), rounding=ROUND_HALF_EVEN))
pct = -(-amount * fee_bps // 10000)
fee = max(min_fee, pct)
if fee > amount: fee = amount
return [fee, rq(conv(amount - fee))]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression fee-exhausts-amount 1', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
('partial repair guard 2', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
('control: yen payout', (10000, '151.37', 2, 0, 150, 50), [150, 14910]),
('control: fee rounds up', (1001, '1', 2, 2, 100, 0), [11, 990]),
('control: minimum fee applies', (20000, '0.3071', 2, 3, 25, 100), [100, 61113]),
('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842])],
[('regression fee-exhausts-amount 1', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
('partial repair guard 2', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
('control: minimum fee applies', (20000, '0.3071', 2, 3, 25, 100), [100, 61113]),
('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842]),
('control: large percentage fee', (123456, '0.92', 2, 2, 250, 199), [3087, 110739]),
('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input')],
[('regression fee-exhausts-amount 1', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
('partial repair guard 2', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input'),
('control: yen payout', (10000, '151.37', 2, 0, 150, 50), [150, 14910]),
('control: fee rounds up', (1001, '1', 2, 2, 100, 0), [11, 990]),
('control: minimum fee applies', (20000, '0.3071', 2, 3, 25, 100), [100, 61113])],
[('regression fee-exhausts-amount 1', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
('partial repair guard 2', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
('control: fee rounds up', (1001, '1', 2, 2, 100, 0), [11, 990]),
('control: minimum fee applies', (20000, '0.3071', 2, 3, 25, 100), [100, 61113]),
('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842]),
('control: large percentage fee', (123456, '0.92', 2, 2, 250, 199), [3087, 110739])],
[('regression fee-exhausts-amount 1', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
('partial repair guard 2', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842]),
('control: large percentage fee', (123456, '0.92', 2, 2, 250, 199), [3087, 110739]),
('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input'),
('control: yen payout', (10000, '151.37', 2, 0, 150, 50), [150, 14910])]]
for label, args, expected in cases[N - 1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression fee-exhausts-amount 1 | [500, 0] | ERR:amount-below-fee | Failed |
| partial repair guard 2 | [99, 0] | ERR:amount-below-fee | Failed |
| control: yen payout | [150, 14910] | [150, 14910] | Passed |
| control: fee rounds up | [11, 990] | [11, 990] | Passed |
| control: minimum fee applies | [100, 61113] | [100, 61113] | Passed |
| control: zero bps | [0, 842] | [0, 842] | Passed |
SHA-256 / b065c71b54e939794526cc90f2b8e5f389b70ed0cf6f2cd2895124f1c4ef4194
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
from decimal import Decimal, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_FLOOR, ROUND_CEILING, ROUND_DOWN, ROUND_UP, ROUND_HALF_DOWN
N = 1
observations = []
def solve(amount, rate, src_exp, dst_exp, fee_bps, min_fee):
if fee_bps < 0 or amount <= 0: return 'ERR:input'
conv = lambda m: Decimal(m) * Decimal(rate) * Decimal(10) ** (dst_exp - src_exp)
rq = lambda d: int(d.quantize(Decimal(1), rounding=ROUND_HALF_EVEN))
pct = -(-amount * fee_bps // 10000)
fee = max(min_fee, pct)
if fee >= amount: return 'ERR:amount-below-fee'
return [fee, rq(conv(amount - fee))]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression fee-exhausts-amount 1', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
('partial repair guard 2', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
('control: yen payout', (10000, '151.37', 2, 0, 150, 50), [150, 14910]),
('control: fee rounds up', (1001, '1', 2, 2, 100, 0), [11, 990]),
('control: minimum fee applies', (20000, '0.3071', 2, 3, 25, 100), [100, 61113]),
('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842])],
[('regression fee-exhausts-amount 1', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
('partial repair guard 2', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
('control: minimum fee applies', (20000, '0.3071', 2, 3, 25, 100), [100, 61113]),
('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842]),
('control: large percentage fee', (123456, '0.92', 2, 2, 250, 199), [3087, 110739]),
('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input')],
[('regression fee-exhausts-amount 1', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
('partial repair guard 2', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input'),
('control: yen payout', (10000, '151.37', 2, 0, 150, 50), [150, 14910]),
('control: fee rounds up', (1001, '1', 2, 2, 100, 0), [11, 990]),
('control: minimum fee applies', (20000, '0.3071', 2, 3, 25, 100), [100, 61113])],
[('regression fee-exhausts-amount 1', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
('partial repair guard 2', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
('control: fee rounds up', (1001, '1', 2, 2, 100, 0), [11, 990]),
('control: minimum fee applies', (20000, '0.3071', 2, 3, 25, 100), [100, 61113]),
('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842]),
('control: large percentage fee', (123456, '0.92', 2, 2, 250, 199), [3087, 110739])],
[('regression fee-exhausts-amount 1', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
('partial repair guard 2', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842]),
('control: large percentage fee', (123456, '0.92', 2, 2, 250, 199), [3087, 110739]),
('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input'),
('control: yen payout', (10000, '151.37', 2, 0, 150, 50), [150, 14910])]]
for label, args, expected in cases[N - 1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression fee-exhausts-amount 1 | ERR:amount-below-fee | ERR:amount-below-fee | Passed |
| partial repair guard 2 | ERR:amount-below-fee | ERR:amount-below-fee | Passed |
| control: yen payout | [150, 14910] | [150, 14910] | Passed |
| control: fee rounds up | [11, 990] | [11, 990] | Passed |
| control: minimum fee applies | [100, 61113] | [100, 61113] | Passed |
| control: zero bps | [0, 842] | [0, 842] | Passed |
SHA-256 / a3846581ac836a9d9bd235a738934395d84d67776ce1f3cba5c633746fa8a50a
Verification & scope
A deterministic, bounded teaching model with a stipulated toy contract; it makes no claim of conformance to any real regulation, standard, or institution's rules. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:47:01.338123+00:00.
Case digest / 40845d0450b70793b10e15ada3caee0b2472737fbae4bceccd2fa6103a120cfa