Our synthetic sale accepts a direct payment asset or a second asset that first passes through a converter. Both paths deliver 73 settlement units to the sale. The buyer should receive the same allocation from either path because the same received asset quantity must reach the same settlement rule.
The independent JavaScript example separates conversion from settlement and keeps all quantities as integers. Its converter is a fixed synthetic fixture. There are no live rates or token addresses and the model does not represent a commercial sale.
Measure the quantity entering settlement
The direct path requests a transfer and measures the sale's balance change. The converted path records the same settlement asset balance before and after conversion. Both pass the measured increase into one function so purchase limits and allocation calculations apply to the same unit.
A requested input amount is not automatically the received settlement amount. Conversion fees or token transfer behavior can change it. Applying a purchase cap to the input asset on one path and to settlement units on another path gives the two entry points different economic limits.
function settle(book, received) {
if (received <= 0n || received > 100n) throw new Error("settlement cap");
const allocation = received * 3n;
const burned = (received * 1500n) / 10000n;
book.issued += allocation;
book.burned += burned;
book.retained += received - burned;
return allocation;
}Our fixture sets a maximum of 100 received settlement units and issues three allocation units per received unit. It burns 1500 basis points of the payment and retains the remainder. For a received quantity of 73 the integer burn is ten units and the retained payment is 63.
Name every destination in the equation
Received payment equals burned quantity plus retained quantity in this example. Allocation tokens are measured in a different unit and belong in a separate equation. Adding token inventory and payment inventory together because both are integers produces a number without an accounting meaning.
Rounding is part of the burn policy. We floor the burn calculation so the remainder stays in retained payment. Another policy can round differently but every received unit still needs a destination. Repeated small payments can produce different aggregate burns from one large payment when each operation rounds independently.
We route both entry points into the same settlement calculation because duplicated code tends to preserve the obvious token transfer while allowing a less visible rule such as a purchase cap or a burn destination to drift until two buyers supplying the same received value acquire different entitlements through different payment assets.
That shared function does not remove the need to validate each entry path. The converter remains an external dependency and the direct transfer still has token semantics. The common function reduces the number of places that define the economic rule.
Keep conversion and distribution separate
A converter can leave a residual input balance or return an output below the buyer's minimum. Those outcomes need explicit handling before the purchase is considered complete. The user should choose a meaningful minimum output or deadline when the conversion path is exposed to price movement.
Later distribution of retained payment is another state transition. A counter named distributed should increase when distribution occurs. Receiving funds changes a separate quantity. Separate counters make reconciliation possible between received payment and payment that remains available for distribution.
73 received -> 219 allocation units; 10 burned; 63 retained
8 assertions passedThe fixture verifies equal allocations for equivalent received quantities and checks conservation of payment units. It also rejects a payment above the settlement cap without changing the ledger. Its state records issued allocations separately from burned and retained payment quantities.
Put the external calls back into the contract tests
This pure model does not simulate EVM rollback or token callbacks. In a Solidity implementation a failed converter call must not leave a completed allocation and a failed output transfer must not leave payment recorded as successfully settled. An uncaught revert supplies transaction rollback but tests should verify the actual failure path.
We also test a converter that returns a plausible number without delivering the matching balance increase. Measuring received assets helps expose that discrepancy but support for rebasing or callback capable tokens needs a separate policy. A reentrancy guard and checks around state updates should match the entire reachable settlement path.
Split one synthetic payment into several smaller purchases and compare its aggregate allocation and burn against a single purchase. Any rounding difference should follow the declared policy and remain identical across equivalent direct and converted payment paths.