bug: Swap Station auto-gas ~4k LUNC on hybrid multi-hop; manual ~200 LUNC succeeds #679
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#679
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
Retail report on Swap (
/): a cLUNC → USTR quote showed Station auto gas ~4,157.699 LUNC (~$0.21) on the mobile Confirm Transaction screen. The trader set gas manually ~200 LUNC and the tx succeeded.This is not an out-of-gas miss (#587). The chain has enough headroom at ~7M gas. Station (and/or the dApp envelope Station ignores) is asking for tens of times more LUNC than
gas_usedrequires.Do not split “honor Station/WC fee,” “stop 15M-per-pool-hop on mixed hybrid router msgs,” and “align Swap Network fee with the submit envelope.” Those are one user journey: the wallet confirm fee for a CW20 hybrid multi-hop swap.
Related: #587 (wrap+≥2hop OOG + Network fee row — closed; native path stays pool-only), #596 (hybrid always-on), #249 (quote-driven hybrid gas), #127 / #208 (
preferNoSetFee, Station amino), #429 (mainnet fee-guard off), #235 (Station P0 on columbus-5 only), #519 / #566 (Station mobile WalletConnect), #475 (retail gas inventory), #353 (router hop floors).Evidence (columbus-5)
Successful follow-up (trader reduced size after the 100k auto-gas scare; same shape):
AB8BE4F75E051837BB01C364DEDE6611727E47F0F857AADF04B17C39F360446D30121174code010000000000raw) — screenshot of the rejected auto-gas prompt was 100,000 cLUNCterra1437qslye72t7qmmahn4t5chz50r8a62g45phwkquwpyu2l62u6ksqssgdgsend→ official routerterra1e7s0h9ftxakwca5gxspyt4haeuaqxds6swr08ul3tsepq7el924sprrsrwterra_swaphops: cLUNC → CL8Y (book-only hybrid) → cUSTC → UST1 → USTRpool_input: "0",book_input: "10000000000",max_maker_fills: 8,book_start_hint: 1426hybridfield (pool-only)gas_wantedgas_used6,785,500 × 28.325)Station auto-gas 4,157.699 LUNC at the same 28.325 uluna/gas is
gas_wanted≈ 146,785,000 — ~29× measuredgas_used, ~22× the successful manual limit.The reporter described the quote as “1 hop / 2 swaps.” The signed msg is 4 hops. Route chrome must show the real path; do not “fix” gas by turning hybrid off or forcing the native hub 2-hop (
cLUNC → UST1 → USTR) — that would violate H596.Current codebase
The dApp does not LCD-simulate CosmWasm execute. It builds an explicit
Feeand asks the wallet to honor it. Three layers currently disagree, and Station mobile often replaces the dApp fee with its own simulate/auto-gas.quoteCw20ViaRouteSolve(GET /route/solve, hybrid always-on #596).swapOpsRequireRouteris true whenops.length ≥ 2→executeMultiHopSwapCW20send→ routerexecute_swap_operationswith per-hophybridcopied from the indexer (serializeTerraSwap).estimateSwapNetworkFeeonSwapPage: for this pathcw20DirectPairis false,cw20Hybridis true only when the Advanced manual book field is filled (multi-hop ignores indexer hybrid).hopCount=indexerOperations.length(here 4) → pool-onlygasLimitForRouterExecuteSwapOperations(4)= 3,810,000 → ~107.9 LUNC. That is below livegas_used5,026,176 — the hint would OOG if a wallet used it asgas_wanted.broadcastTerraExecuteContracts→getGasLimitForTxon the decodedsend.msg. If any hop hashybrid,gasLimitForHybridRouterOperationssumsgasLimitForHybridParamsper hop. Missing hybrid →makersUsed === undefined→HYBRID_SWAP_GAS_LIMIT15,000,000. Live shape: hop1 book/8 makers = 1,785,500; hops 2–4 = 15M each → 46,785,500 → ~1,325 LUNC. Tests only cover hybrid on every hop (transactions.test.ts2-hop … hybrid on each hop= 3,058,600). Mixed hybrid + pool hops are untested.preferNoSetFee: true+ amino (StationController, #208). Post-sign undershoot guard is LocalTerra-only (#429 / SEC-E08).isAtomicWalletConnectPostforceswallet.broadcastTx(unsignedTx, fee)— no splitsignAmino. Station mobile Confirm Transaction (settings gear next to Tx Fee) is that app’s auto-gas / LCD simulate, not the dApp row. Classic CosmWasm simulate on a 4-hop hybrid book walk routinely returns near block gas (~100M); × adjustment ≈ 147M gas / 4,158 LUNC.H596-7/ #587). This report is already-wrapped cLUNC → indexer hybrid router.Numeric classes at
MIN_GAS_PRICE_ULUNA28.325:gas_used5.03MgetGasLimitForTxtodayHYBRID_SWAP_GAS_LIMIT× 4Why this is needed
Traders see thousands of LUNC on Station Approve for a routine hub swap. Many will cancel (or drain a large LUNC reserve). The same tx lands at ~192 LUNC when gas is set by hand, and
gas_usedis ~5.0M (~142 LUNC at 28.325). That is a trust / convert bug on the primary Swap CTA, not a “Classic is expensive” fact.Even if Station is patched or bypassed, the dApp today would still either OOG (Network fee / pool-only 3.81M) or over-charge ~1,325 LUNC (15M per pool hop). Hint, broadcast, and wallet must share one envelope that sits above measured
gas_usedwith margin and in the low hundreds of LUNC, not thousands.Constraints / guardrails
uluna. No USTC /uusd/ multi-denom fees. Floor remainseffectiveGasPriceUluna()/ 28.325.AGENTS_TERRACLASSIC_GAS). Station’s simulate is the 4k-LUNC bug. LCD sim may tune constants; broadcast must still use named hop/hybrid math that exceeds measuredgas_usedwith wasm/storage headroom.H596-1–H596-8). No pool-only checkbox. Do not strip indexerhybridfrom submit to “make gas cheap.”book_inputto native wrap+multihop (H596-7/ #587). This issue is CW20send→ router.gasLimitForHybridParams(undefined)as a per-hop addend inside a mixed router msg is the 46.8M bug. Unknown hybrid remains a tx-level last resort only when no hop has parseable params (keep the #249 fallback).gasUsedis known, new defaults must exceed it with margin. Live floor: 5,026,176 on this 4-hop book+pool shape. The successful 6,785,500 (~192 LUNC) is a proven ceiling class — do not regress belowgas_used.estimateSwapNetworkFee/evaluateSwapNativeGasGatemust use the same execute-msg shape as submit (indexer ops + per-hop hybrid), nothopCount+ pool-only.#213Max reserve tracks that envelope when pay is native LUNC (CW20 pay unchanged).terraGasRetailInventory.ts. Do not fall through toBASE_GAS_LIMIT/SWAP_GAS_LIMIT(600k) ifsend.msgcannot be decoded — throwSendHookGasDecodeError.packages/localnet-trading-swarm/src/gas.tsif hop/hybrid floors change.ulunaroute/solve. Forcing a shorter cLUNC→USTR path (hub 2-hop) unless a separate solver issue shows the 4-hop book path is not best execution. New factory pairs.Relevant files
frontend-dapp/src/services/terraclassic/terraGas.tsgasLimitForHybridRouterOperations— stop 15M on pool hopsfrontend-dapp/src/services/terraclassic/hybridSwapGas.tsgasLimitForHybridParams(undefined)= 15M fallbackfrontend-dapp/src/services/terraclassic/swapNetworkFee.tsfrontend-dapp/src/pages/SwapPage.tsxestimateSwapNetworkFeehints;swap-network-feefrontend-dapp/src/services/terraclassic/router.tsexecuteMultiHopSwap/serializeTerraSwapfrontend-dapp/src/utils/cw20RouteSolveQuote.tsfrontend-dapp/src/services/terraclassic/terraBroadcast.tsbroadcastTxvs split signfrontend-dapp/src/services/terraclassic/terraWalletSignTxRaw.tsisAtomicWalletConnectPost,preferNoSetFeefrontend-dapp/src/services/terraclassic/stationExtensionConfig.tsfrontend-dapp/patches/@goblinhunt+cosmes+0.0.71-ghunt.22.patchfrontend-dapp/src/services/terraclassic/terraGasRetailInventory.tsfrontend-dapp/src/services/terraclassic/__tests__/transactions.test.tsfrontend-dapp/src/services/terraclassic/__tests__/swapNetworkFee.test.tsfrontend-dapp/src/utils/swapNativeGasBalanceGate.tsfeeUlunafrontend-dapp/src/components/trade/TradeMarketOrderPanel.tsxdocs/frontend.mdskills/AGENTS_TERRACLASSIC_GAS.mdAGENTS.mdmake verify-issue-<iid>Recommended direction
gasLimitForHybridRouterOperations, hops withouthybrid(orbook_input = 0) use the pool-only router/pair floor for that hop — notHYBRID_SWAP_GAS_LIMIT. Hops withbook_input > 0keepgasLimitForHybridSwap. Takemax(router N-hop floor, sum of per-hop pieces)so a 4-hop path cannot sit at 3.81M when hop1 is a book walk. Target class for the live shape: above 5.03M used, well below 15M, ~150–250 LUNC at 28.325 (the manual 6.79M / 192 LUNC is the calibration).operations(or a fixture-equivalent execute payload) intoestimateSwapNetworkFee/estimateTerraClassicFeeForEntries. Stop usinghopCountalone for CW20 multi-hop when hybrid is on the wire. Network fee row, gas gate, andFee.gasLimitmust match.broadcastTx/ Stationpostreceives aminofee.gas+fee.amount. If Station still simulates, investigate: pass fee in the payload Station documents; prefer splitsignAmino+preferNoSetFeewhen the mobile session exposes it; do not rely on LCD simulate. Document residual Station-app-only auto-gas as a known wallet limitation with the compact Network fee number as the dApp source of truth.gas_used5,026,176 vs Station ~147M; mixed-hop rule; Station mobile auto-gas ≠ dApp envelope.Acceptance criteria
getGasLimitForTxon the live 4-hop send hook (hybrid book on hop 1, no hybrid on hops 2–4) is > 5,026,176 and < 15,000,000. Fee at 28.325 is < 400 LUNC (tens-to-low-hundreds class, not 1,325 / 4,158).buildTerraClassicFee(getGasLimitForTx(…)).amount(hint = broadcast).hybridnever addHYBRID_SWAP_GAS_LIMIT.gasLimitForHybridParams(undefined)is not summed per empty hop.swap+ hybrid withbook_input = 0stays pool-only 840k;book_input > 0staysgasLimitForHybridSwap(not 15M whenmax_maker_fillsis set).send_4hop_hybrid_first_pool_rest(or equivalent name) inRETAIL_COMBINED_ENVELOPE_FIXTURES/ shape fixtures.preferNoSetFeeis honored. Station WalletConnect / mobilepost: either the same match, or a documented residual + dApp row still shows the honest ~200 LUNC class (no silent 4k).make verify-issue-<iid>+ docs/skill row. Swarmgas.tslockstep if floors change.Test plan (all paths)
Unit (Vitest, no chain)
send→execute_swap_operationswith 4 ops, hop1{ pool_input: 0, book_input: >0, max_maker_fills: 8 }, hops 2–4 omithybrid. Assert gas ∈ (5_026_176, 15_000_000) and fee LUNC < 400. Assert not 46_785_500.execute_swap_operationswithhybrid: {}/ unparseable on all hops still hits the single 15M fallback (or documented equivalent) — do not silently grant 600k.estimateSwapNetworkFeewith indexer-equivalent ops matchesestimateTerraClassicFeeForEntriesof the submit send msg (4-hop mixed + 2-hop pool-only + wrap+2hop).indexerOperations.length === 4and hop1 hybrid:swap-network-feeLUNC matches the envelope (not ~108). AMM Fee row still bps.send.msgthrowsSendHookGasDecodeError.cw20Hybrid: truemust not jump wrap+2hop to 15M (existing test).estimateMarketPairSwapSequenceUlunaFeesTotal; multi-hop market (if reachable) shares the mixed-hop helper — no second formula.isAtomicWalletConnectPosttests still treat Station/LuncDash WC as atomic unless the implementation intentionally moves them to split sign (then update tests + #519/#566).gas.tsconstants change.LocalTerra (Keplr or simulated wallet — not Station)
gas_used < gas_wanted, fee LUNC in the accepted class, no OOG toast.e2e/wrap-swap.spec.tsE7/E8 as applicable).columbus-5 (Station P0)
gas_used < gas_wanted, fee paid ≈ envelope (or manual override no longer required).Test plan (attack, hack, and abuse)
route/solvecan attachhybridon one hop and 4+ pool hops to inflate wallet gas (grief / drain LUNC reserve) or display a short path while submitting a long one. Submit path must stay ops-aligned (reconcileSwapRouteIntermediateTokens). Gas must not jump to 15M×N from a single hybrid flag. Cap / monotonicity: more hops may cost more, but not 15M per extra hop.max_maker_fillsgrief. Hugemax_maker_fillson hop 1 must remain clamped bymaxMakerFillsForSubmit/ on-chainMAX_MAKER_FILLS_HARD_CAP(100) andHYBRID_SWAP_GAS_LIMITper book hop, not unbounded.send.msg. Must throw (no 600k fallback that OOGs, no 15M×N). Attacker-controlled inner JSON must not selectplace_limit_order/unwrap/ invoice hooks by accident — keep exact-key parse order ingetGasLimitForTx.post. Changing Station WC to split sign must keep#499sequence refresh and#208single-popup lock (withTerraWalletSignLock). No second silentbroadcastTxafter user approval.pool_only=trueon official Swap/Trade (H596-4) or drophybridfrom submit while the quote assumed a book fill (H596-6) — that is a price-integrity / sandwich-adjacent miss, not a fee tweak.executeNativeSwap. Keep wrap+N-hop on #587 constants so a crafted “needsWrap + cw20Hybrid” hint cannot request 15M×hops.amountas a gas multiplier.Verification criteria
Close when:
gas_used5,026,176 and below 15M, and hint equals broadcast.gas_used < gas_wantedwithout a manual Station gas override, or an operator comment records Station-app residual with the dApp row in the ~200 LUNC class and extension/Keplr matching the envelope.AGENTS_TERRACLASSIC_GASdescribe mixed hybrid router hops and Station auto-gas ≠ LCD-sim envelope.Notes
marked as related to #587
marked as related to #596
marked as related to #249
marked as related to #127
marked as related to #429
marked as related to #475
marked as related to #353
mentioned in merge request !1182
Implementation in !1182.
Completed (unit + docs;
make verify-issue-679passed + retest)estimateSwapNetworkFeewith indexer ops equals broadcastgetGasLimitForTxsend hook.hybriduse the 950k pool floor.gasLimitForHybridParams(undefined)is not summed per empty hop.make verify-issue-587).book_input = 0stays 840k;book_input > 0stays quote-driven.send_4hop_hybrid_first_pool_rest.make verify-issue-596).make verify-issue-679+ docs/skill + swarmgas.tslockstep.Not done (needs operator / wallet)
VERIFY_ISSUE_679_CHAIN=1) — optional; 4-hop anchor remains hashAB8BE4F7….mentioned in commit
1fbee5b4d5mentioned in issue #686
marked as related to #686
Merged via !1182 (
1fbee5b4) ontomain36d64528.make verify-issue-679PASS (9/9, including children #475 / #587 / #596). Mixed 4-hop envelope stays 6,785,500 gas (~192 LUNC). Residual: Station mobile / WalletConnect may still replace the dApp fee with auto-gas — the Swap Network fee (est.) row is the source of truth; do not turn hybrid off (#596). OptionalVERIFY_ISSUE_679_CHAIN=1not run (LocalTerra down). Duplicate ticket #681 should stay closed. Post-merge ops: #686.mentioned in issue #681
mentioned in issue #690