Hybrid swap gas: aggregate CW20 transfers across book + pool legs #248
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:implement
agent:implement
agent:open_issues
agent:ready
agent:research
agent:security_audit
agent:verify
architecture
backend
blocker:hybrid
blocker:launch
blocker:limit-orders
blocker:v2
block:log_only
block:security
bug
ci
contracts
correctness
deploy
dev
devops
docs
documentation
duplicate
e2e
enhancement
epic
feature
frontend
functional-completion
gas
good first issue
governance
help wanted
high-risk
hooks
hybrid
indexer
infra
infrastructure
integrators
invalid
launch-blocker
limit-orders
localnet
localterra
low priority
missing-implementation
needs-design
ops
performance
priority
high
priority
medium
product
qa
QA
question
ready
ready
research
scripts
security
security-hardening
smartcontracts
tech-debt
testing
ux
UX
v2
verification
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-dex-terraclassic#248
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Restructure
match_bids/match_asksto accumulate payout amounts in memory instead of emitting one CW20Transfersubmessage per maker fill. Build aggregated transfer messages once inexecute_swapso net-to-taker and treasury commission are single transfers per token, and maker payouts are deduped by owner.Current codebase
execute_swap(smartcontracts/contracts/pair/src/contract.rs~754–1038) runs book leg then pool leg, concatenating messages:book_messagesfromorderbook::match_bids/match_askspool_messages(treasury commission + taker return on ask token)orderbook.rs(~711–739 bids, ~889–917 asks):limit_placement.rs(~190–199).HybridSwapParams/ router / dApp unchanged.Why this is needed
Hybrid swaps are the default execution path (
TradeMarketOrderPanel,SwapPage). Each CW20 transfer costs ~80–130k gas (two balance maps + event). A swap crossing 5 makers on a ladder can emit 17 transfers; aggregating to M + 2 (worst case M distinct makers) is the largest contract-side gas lever with no integrator breakage.Constraints / guardrails
book_return_net + pool_return; treasury receivesbook_commission + pool_commission.limit_order_fillwasm events for indexer (orderbook.rslimit_order_fill_event) — aggregation is message-layer only.AfterSwap.commission_amountmust remainpool_commission + book_commission(L7).simulate_match_bids/simulate_match_asksalready accumulate in memory; execute path should mirror that structure.Relevant files
smartcontracts/contracts/pair/src/orderbook.rsmatch_bids,match_asks,BookMatchResultsmartcontracts/contracts/pair/src/contract.rsexecute_swap, message assemblysmartcontracts/tests/src/limit_order_tests.rssmartcontracts/packages/dex-common/src/pair.rsdocs/limit-orders.md,docs/contracts-security-audit.mdRecommended solution direction
BookMatchResultto include accumulation maps:maker_payouts: BTreeMap<Addr, Uint128>(offer token units per maker)net_to_taker: Uint128,commission_total: Uint128(ask token)fill_events, drop per-fillmessagesvec or build minimal msgs at end.execute_swap, after book + pool legs:total_net = book_return_net + pool_return→ one ask-token transfer to receiver.total_commission = book_commission + pool_commission→ one ask-token transfer to treasury.maker_payouts→ one offer-token transfer (collapses ladder self-cross to one payout per owner).Acceptance criteria
commission_amountattr and hook payload unchanged in value.Test plan — functional paths
Test plan — attack / abuse vectors
book_commission_amount,pool_return_amountstill accurate.Verification criteria
make test-contractsgreen including new aggregation property tests.HybridSimulationquery results (sim path already aggregated).mentioned in commit
4f26d11457Implementation complete (pushed to
main@4f26d11)Restructured hybrid swap CW20 emission per #248:
What changed
orderbook::match_bids/match_asks: accumulatemaker_payouts: BTreeMap<Addr, Uint128>(offer token) plus in-memoryreturn_net/commission_total(ask token). Per-filllimit_order_fillevents unchanged.execute_swap: after book + pool legs, emit aggregated submessages in order: maker payouts → one ask-token transfer to receiver (book + poolnet) → one ask-token transfer to treasury (book + poolcommission). Worst case M + 2 CW20 executes (M distinct maker owners).docs/contracts-security-audit.md; execution-order section indocs/limit-orders.md; crosslinks inskills/AGENTS_TERRACLASSIC_GAS.mdandskills/AGENTS_HYBRID_QUOTING.md.Tests added
orderbook::aggregation_tests::*— maker dedup + commission/return conservationlimit_order_tests::hybrid_swap_two_makers_emits_two_fill_events— same-owner two fills → one aggregated maker payout (balance check)limit_order_tests::hybrid_aggregated_maker_payouts_multi_maker— three distinct makersmake test-contractsgreen locally.Verification checklist (QA)
HybridSimulationfor book-only, pool-only, and split legsbook_return + pool_return; treasury receivesbook_commission + pool_commissionAfterSwap.commission_amount= pool + book commission;return_asset.amount= total netlimit_order_fillcount unchanged; swap attrsbook_commission_amount,pool_return_amountaccuratebook_input = 0gas_usedmaterially lower vs pre-4f26d11 (target ≥30% — record actual)bash scripts/with-node.sh --cwd frontend-dapp -- npx playwright test e2e/hybrid-swap.spec.ts --project=e2e-txFollow-ups (optional)
SWAP_GAS_PER_HOP/ hybrid hop estimates if margin allows (AGENTS_TERRACLASSIC_GAS.md).make verify-issue-248) if deploy QA wants a repeatable LCD path — not added in this PR.@qa-agent-team — please run the checklist above on LocalTerra (or QA stack) and confirm economics + gas before closing. Leaving issue open until QA sign-off.
mentioned in issue #252
marked as related to #252
Follow-up filed: #252 — benchmark post-aggregation
gas_usedon LocalTerra (cold + warm swarm load) and recalibrate dApp +localnet-trading-swarmgas constants sogas_usednever exceedsgas_wantedunder active bot trading. Complements quote-driven limits in #249.mentioned in commit
0be09de77cmentioned in issue #255
qa verified on the QA stack @PlasticDigits — 20/22 confirmed, contract correctness + live aggregation hold. only the gas benchmark is left, and that's #252.
pulled main to
ff7a680, redeployed, restarted indexer.contract + indexer:
source review:
live on
ff7a680:ticked 20/22: acceptance 1-5, all functional paths, all attack/abuse, verification 1/3/4.
open (2 -> #252): acceptance 6 + verification 2, the M=1,3,5 / 5-maker >=30% gas benchmark. no pre-4f26d11 baseline exists anywhere and the >=30% needs one, so the cold+warm-swarm benchmark stays in #252. first live data point: 1-maker hybrid = 615208 gas_used vs 522050 pool-only (same pair/reserves). left a bench rig at scripts/qa/bench-issue-248.sh (parametrized book+pool swap, reads gas_used + transfer/fill counts) as a #252 starting point.
correctness + aggregation proven live; the 2 open boxes are pure benchmark.
marked the checklist item Single-maker fill: same transfers as before (count may drop from 3+2 to 3). as completed
marked the checklist item M distinct makers: ≤ M + 2 CW20 transfer submessages total (plus hooks/discount msgs). as completed
marked the checklist item Same owner, M fills on ladder: 1 maker payout transfer for that owner. as completed
marked the checklist item commission_amount attr and hook payload unchanged in value. as completed
marked the checklist item All existing hybrid swap integration tests pass without changing expected economics. as completed
marked the checklist item Book-only hybrid
pool\_input\=0: aggregated taker + treasury transfers correct. as completedmarked the checklist item Pool-only
book\_input\=0: unchanged (no regression). as completedmarked the checklist item Split hybrid: book + pool legs; single taker + single treasury transfer on ask token. as completed
marked the checklist item Partial fill leaves order on book; maker receives partial aggregated payout. as completed
marked the checklist item Full fill unlinks order; events still emitted per fill. as completed
marked the checklist item Bid side
token0 offerand ask sidetoken1 offerboth tested. as completedmarked the checklist item Property test: aggregated transfer sums == legacy per-fill sums for random fill sequences. as completed
marked the checklist item No change to HybridSimulation query results (sim path already aggregated). as completed
marked the checklist item Rounding / dust: Commission floor per fill; aggregated treasury amount must not underpay vs per-fill sum (no protocol loss). as completed
marked the checklist item Maker map collision: Two makers same address
impossible on Addr— N/A; same owner multiple orders → single payout must equal sum of fills. as completedmarked the checklist item Overflow: Aggregating large Uint128 sums → checked_add throughout. as completed
marked the checklist item Reentrancy: CW20 transfer order change must not enable double-spend (CosmWasm atomicity; verify no duplicate net-to-taker). as completed
marked the checklist item Indexer: Fill events unchanged; swap attrs book_commission_amount, pool_return_amount still accurate. as completed
marked the checklist item make test-contracts green including new aggregation property tests. as completed
marked the checklist item Manual swap on localterra with ladder on book; LCD balances match spreadsheet. as completed
Closing as benchmarks should be in #252
mentioned in issue #262
marked as related to #262
mentioned in issue #546
mentioned in issue #617