Fix Trade/Secured swap accounting and limit settlement
Summary
Track actual Trade/Secured input consumption in the advanced swap queue and handle limit-order settlement when withdrawals are underfunded or affected by share rounding.
Changes
- Advance
State.Inby the amount actually consumed. - Settle limit orders when the next chunk lacks sufficient balance or rounds to zero redeemable account units.
- Allow nonzero rounded fills to continue within the configured successful-swap allowance.
- Settle once that allowance is reached, even if some input remains. Failed attempts do not consume the allowance.
- Include outbound fees when evaluating the final allowed success.
Settlement schedules accumulated swap proceeds. Unspent Trade/Secured balances remain in the account, and multiple orders can still share those balances without escrow.
Mainnet impact (observed 2026-09-24, height ~27,965,700)
This MR fixes the mechanism behind #3253, which is live on mainnet today.
Trade/Secured limit orders are not escrowed. The balance is only withdrawn when a chunk executes. The queue's pre-check (checkWithFeeSwap) looks at price only, never at balance. So an order whose balance is gone passes discovery, fails in execution, and is retried every block. Each failure appends an index and the full error string to State.FailedSwaps / State.FailedSwapReasons and rewrites the whole record.
Current mainnet limit-swap queue:
- Size: about 670 limit orders carrying about 57 MB of
failed_swap_reasonsin consensus state, 89% of the queue's payload. The largest single order has 14,066 recorded failures (about 1.7 MB). - One source: 578 of those orders come from one contract,
thor1n5a08r0zvmqca39ka2tgwlkjy9ugalutk7fjpzptfppqcccnat2ska5t4g, labelledrujira-thorchain-swap(code 195). It placed limit orders funded from secured-asset balances it doesn't have. For example, it holds zero ETH-USDC but has hundreds of open ETH-USDC orders. It accounted for 99.7% of new failures (5,678 of 5,694) over a 114-block window. - 62% of recorded failures are
fail to withdraw from secured asset: insufficient secured asset balance … available 0. - 38% of recorded failures read
emit asset X less than price limit Y. These are underfunded orders too, where the sender holds some of the balance but not enough. When the requested amount exceeds the balance,SecuredAssetMgr.Withdrawredeems the whole balance, so the chunk swaps a smaller amount and misses the price limit (median shortfall 10.6×). Example: an 80 USDT order against a 3.27 USDT balance emitted 3,914 sats against a 95,610-sat limit. - Rate: about 50 failed attempts per block, appending about 7.6 KB of reason text per block. About 42 records are rewritten per block, roughly 17 MB of values (estimated).
- API:
/thorchain/queue/limit_swapswithout parameters now fails withgrpc: received message larger than max (~22 MB vs 10485760).
With this MR, an order whose next chunk is not fully funded, or rounds to zero redeemable units, is settled on its next attempt instead of being retried. That removes both failure kinds above. Refunds are unaffected: refundTx already skips Trade/Secured coins, so settlement credits nothing that was not withdrawn.
Behaviour note for integrators: orders that share one unescrowed balance, as rujira-thorchain-swap does, are now closed the first time that balance falls short, rather than retried until TTL.
Related: #3253, #3291 (duplicate). !5020 separately bounds the failure history, which is still needed for other repeatedly-failing orders.
Validation
Added Trade/Secured test coverage for rounded fills, zero-redemption chunks, successful-swap limits, accumulated payouts, duplicate processing, and final-swap fees.