chore: audit getGasLimitForTx retail execute fallbacks (prevent BASE_GAS_LIMIT OOG) #475

Closed
opened 2026-07-12 07:09:48 +00:00 by PlasticDigits · 22 comments
PlasticDigits commented 2026-07-12 07:09:48 +00:00 (Migrated from gitlab.com)

Summary

Harden the static Terra Classic gas envelope table so new or overlooked execute messages do not silently fall through to BASE_GAS_LIMIT (200_000) and fail with retail out of gas copy. Bundle related hygiene (inventory, gap fixes, guardrails) into this single issue.

Triggered by mainnet Mint bug report (“Automatic gas sometimes fails” / “all the time”) — root cause for Mint is missing drip mapping (tracked separately as the P0 Mint fix). This issue covers recurrence prevention and any other retail gaps found in the same audit.

Precedent: #384 (register → 200k → OOG → REGISTER_FEE_DISCOUNT_GAS_LIMIT).


Current codebase

Architecture

  • Broadcast path: executeTerraContract* → broadcastTerraExecuteContracts → estimateTerraClassicFeeForEntries → getGasLimitForTx → buildTerraClassicFee.
  • No LCD simulate / estimateFee for execute envelopes (terraClassicFeeEstimate.ts, docs/frontend.md).
  • Unknown / unmapped messages return BASE_GAS_LIMIT = 200000.
  • Swap / hybrid / limit-order / wrap / fee-discount register paths have dedicated constants and buffers; many CW20 send inners are partially unwrapped in getGasLimitForTx.

Known / suspected gaps (audit starting points)

Item Notes
Faucet drip Confirmed missing — fix in linked P0 Mint issue; verify covered after that lands
CW20 send → inner unwrap / wrap-adjacent msgs UNWRAP_GAS_LIMIT applied for router unwrap_output; confirm send hook / wrapMapper paths map correctly vs legacy SWAP_GAS_LIMIT (600k)
Any executeTerraContract* call site whose top-level or inner key is absent from getGasLimitForTx Silent 200k fallback
Allowance-only txs Intentionally 200k — confirm measured gas_used still below ceiling on columbus-5
Multi-msg txs (executeTerraContractMulti) Sum via totalGasLimitForExecuteMsgs — each entry must map
  • Mainnet wallet fee rewrite / extension guard (#429)
  • Swap buffer tuning (#115 / #134 / #249 / #260) — already measured; do not retune unless audit finds under-envelope

Why a new implementation is needed

Static envelopes are correct for Classic fee reliability (#127) but fragile: every new CosmWasm execute shape must be registered or users hit deterministic OOG. Soft-launch Mint shipped without a gas constant (#473). Without inventory + CI guardrails, the next feature repeats #384 / Mint.


Constraints / guardrails

  • Do not replace static envelopes with LCD simulate as the default broadcast path (architectural choice; separate RFC if desired).
  • Prefer measured gas_used + margin over arbitrary large ceilings; document measurements in tests/comments.
  • Over-allocation costs user LUNC only; under-allocation burns fees on failed txs — bias to safe margin.
  • Keep effectiveGasPriceUluna() floor (28.325).
  • Dev-only warnings / tests must not break production builds or E2E flakiness.
  • Bundle related hygiene here; keep Mint drip constant landing in the P0 issue if not already merged.

Relevant files

File Role
frontend-dapp/src/services/terraclassic/terraGas.ts Envelope table + fallback
frontend-dapp/src/services/terraclassic/terraClassicFeeEstimate.ts Fee estimate API
frontend-dapp/src/services/terraclassic/terraBroadcast.ts Broadcast
frontend-dapp/src/services/terraclassic/transactions.ts executeTerraContract*
frontend-dapp/src/services/terraclassic/router.ts Wrap / router / multi
frontend-dapp/src/services/terraclassic/faucet.ts Drip (P0)
frontend-dapp/src/services/terraclassic/hybridSwapGas.ts Hybrid envelopes
frontend-dapp/src/utils/constants.ts Swap / wrap / gas price constants
frontend-dapp/src/utils/humanizeTerraTxError.ts OOG humanization
docs/frontend.md Gas docs
skills/AGENTS_TERRACLASSIC_GAS.md Agent rules
frontend-dapp/src/services/terraclassic/__tests__/transactions.test.ts Fallback behavior
frontend-dapp/src/services/terraclassic/__tests__/terraGas.feeDiscount.test.ts #384 pattern

  1. Inventory all retail executeTerraContract / executeTerraContractMulti / CW20 send inner msg shapes vs getGasLimitForTx branches.
  2. For each gap: measure gas_used (LocalTerra optimized and/or columbus-5), add named constant + branch, unit test.
  3. Guardrail options (pick one or combine):
    • Unit test table: every known retail msg key must map above BASE_GAS_LIMIT unless explicitly allowlisted (e.g. allowance).
    • Dev-only console.warn when fallback hits for unrecognized keys.
    • Checklist in AGENTS_TERRACLASSIC_GAS.md / PR template: “new execute msg → gas constant + test.”
  4. Update docs/frontend.md gas table for any new constants.
  5. Optional verify script listing expected mappings.

Acceptance criteria

  • Written inventory of retail execute msg shapes vs getGasLimitForTx (issue comment or docs table).
  • All identified under-envelopes fixed with measured constants + unit tests.
  • Explicit allowlist for intentional BASE_GAS_LIMIT uses (e.g. increase/decrease allowance) with rationale.
  • Guardrail landed so a new unmapped retail msg fails CI or loudly warns in dev.
  • Docs / AGENTS_TERRACLASSIC_GAS.md updated.
  • No regression on swap, hybrid, limits, pool, tiers register/deregister, wrap/unwrap, mint drip (after P0).

Test plan (all paths)

Path Expectation
Each inventoried execute shape Unit: mapped limit; optional live: gas_used < gas_wanted
Intentional 200k msgs (allowance) Still map to BASE_GAS_LIMIT; live smoke if feasible
Multi-msg wrap+swap / wrap+router Summed limits sufficient
CW20 send with each known inner Correct inner mapping (not silent wrong constant)
Unknown synthetic msg in unit test Fallback behavior documented; guardrail triggers as designed
Existing suites transactions, terraGas.feeDiscount, hybrid/swap gas tests green

Test plan (attack / hack / abuse vectors)

Vector Expectation
Crafted oversized execute payload Chain/contract rejects; envelope does not grant extra privileges
Intentionally huge gas constants User self-grief only; no shared treasury drain
Omitting a new msg from the table CI/dev guardrail fails before mainnet OOG
Wallet fee undershoot after correct envelope Still #429; audit notes if observed
Spamming failed txs with low gas Pre-existing; fixing envelopes reduces accidental fee burn

Verification criteria

  • Inventory comment/docs merged with issue.
  • cd frontend-dapp && npm run test:run -- src/services/terraclassic/__tests__/ covers new mapping tests.
  • Spot-check columbus-5 or LocalTerra txs for any newly fixed shapes: gas_used < gas_wanted.
  • Mint drip (P0) and fee-discount register (#384) remain mapped above measured usage.
## Summary Harden the static Terra Classic gas envelope table so **new or overlooked execute messages** do not silently fall through to `BASE_GAS_LIMIT` (200_000) and fail with retail **out of gas** copy. Bundle related hygiene (inventory, gap fixes, guardrails) into this single issue. Triggered by mainnet Mint bug report (“Automatic gas sometimes fails” / “all the time”) — root cause for Mint is missing `drip` mapping (tracked separately as the P0 Mint fix). This issue covers **recurrence prevention** and any other retail gaps found in the same audit. Precedent: **#384** (`register` → 200k → OOG → `REGISTER_FEE_DISCOUNT_GAS_LIMIT`). --- ## Current codebase ### Architecture - Broadcast path: `executeTerraContract*` → `broadcastTerraExecuteContracts` → `estimateTerraClassicFeeForEntries` → **`getGasLimitForTx`** → `buildTerraClassicFee`. - No LCD `simulate` / `estimateFee` for execute envelopes (`terraClassicFeeEstimate.ts`, `docs/frontend.md`). - Unknown / unmapped messages return **`BASE_GAS_LIMIT = 200000`**. - Swap / hybrid / limit-order / wrap / fee-discount register paths have dedicated constants and buffers; many CW20 `send` inners are partially unwrapped in `getGasLimitForTx`. ### Known / suspected gaps (audit starting points) | Item | Notes | |------|--------| | Faucet `drip` | Confirmed missing — **fix in linked P0 Mint issue**; verify covered after that lands | | CW20 `send` → inner `unwrap` / wrap-adjacent msgs | `UNWRAP_GAS_LIMIT` applied for router `unwrap_output`; confirm `send` hook / `wrapMapper` paths map correctly vs legacy `SWAP_GAS_LIMIT` (600k) | | Any `executeTerraContract*` call site whose top-level or inner key is absent from `getGasLimitForTx` | Silent 200k fallback | | Allowance-only txs | Intentionally 200k — confirm measured `gas_used` still below ceiling on columbus-5 | | Multi-msg txs (`executeTerraContractMulti`) | Sum via `totalGasLimitForExecuteMsgs` — each entry must map | ### Related (out of primary scope unless audit proves coupling) - Mainnet wallet fee rewrite / extension guard (#429) - Swap buffer tuning (#115 / #134 / #249 / #260) — already measured; do not retune unless audit finds under-envelope --- ## Why a new implementation is needed Static envelopes are correct for Classic fee reliability (#127) but **fragile**: every new CosmWasm execute shape must be registered or users hit deterministic OOG. Soft-launch Mint shipped without a gas constant (#473). Without inventory + CI guardrails, the next feature repeats #384 / Mint. --- ## Constraints / guardrails - Do **not** replace static envelopes with LCD simulate as the default broadcast path (architectural choice; separate RFC if desired). - Prefer **measured `gas_used` + margin** over arbitrary large ceilings; document measurements in tests/comments. - Over-allocation costs user LUNC only; under-allocation burns fees on failed txs — bias to safe margin. - Keep `effectiveGasPriceUluna()` floor (28.325). - Dev-only warnings / tests must not break production builds or E2E flakiness. - Bundle related hygiene here; keep Mint `drip` constant landing in the P0 issue if not already merged. --- ## Relevant files | File | Role | |------|------| | `frontend-dapp/src/services/terraclassic/terraGas.ts` | Envelope table + fallback | | `frontend-dapp/src/services/terraclassic/terraClassicFeeEstimate.ts` | Fee estimate API | | `frontend-dapp/src/services/terraclassic/terraBroadcast.ts` | Broadcast | | `frontend-dapp/src/services/terraclassic/transactions.ts` | `executeTerraContract*` | | `frontend-dapp/src/services/terraclassic/router.ts` | Wrap / router / multi | | `frontend-dapp/src/services/terraclassic/faucet.ts` | Drip (P0) | | `frontend-dapp/src/services/terraclassic/hybridSwapGas.ts` | Hybrid envelopes | | `frontend-dapp/src/utils/constants.ts` | Swap / wrap / gas price constants | | `frontend-dapp/src/utils/humanizeTerraTxError.ts` | OOG humanization | | `docs/frontend.md` | Gas docs | | `skills/AGENTS_TERRACLASSIC_GAS.md` | Agent rules | | `frontend-dapp/src/services/terraclassic/__tests__/transactions.test.ts` | Fallback behavior | | `frontend-dapp/src/services/terraclassic/__tests__/terraGas.feeDiscount.test.ts` | #384 pattern | --- ## Recommended direction 1. **Inventory** all retail `executeTerraContract` / `executeTerraContractMulti` / CW20 `send` inner msg shapes vs `getGasLimitForTx` branches. 2. For each gap: measure `gas_used` (LocalTerra optimized and/or columbus-5), add named constant + branch, unit test. 3. **Guardrail options** (pick one or combine): - Unit test table: every known retail msg key must map above `BASE_GAS_LIMIT` unless explicitly allowlisted (e.g. allowance). - Dev-only `console.warn` when fallback hits for unrecognized keys. - Checklist in `AGENTS_TERRACLASSIC_GAS.md` / PR template: “new execute msg → gas constant + test.” 4. Update `docs/frontend.md` gas table for any new constants. 5. Optional verify script listing expected mappings. --- ## Acceptance criteria - [ ] Written inventory of retail execute msg shapes vs `getGasLimitForTx` (issue comment or docs table). - [ ] All identified under-envelopes fixed with measured constants + unit tests. - [ ] Explicit allowlist for intentional `BASE_GAS_LIMIT` uses (e.g. increase/decrease allowance) with rationale. - [ ] Guardrail landed so a new unmapped retail msg fails CI or loudly warns in dev. - [ ] Docs / `AGENTS_TERRACLASSIC_GAS.md` updated. - [ ] No regression on swap, hybrid, limits, pool, tiers register/deregister, wrap/unwrap, mint drip (after P0). --- ## Test plan (all paths) | Path | Expectation | |------|-------------| | Each inventoried execute shape | Unit: mapped limit; optional live: `gas_used < gas_wanted` | | Intentional 200k msgs (allowance) | Still map to `BASE_GAS_LIMIT`; live smoke if feasible | | Multi-msg wrap+swap / wrap+router | Summed limits sufficient | | CW20 `send` with each known inner | Correct inner mapping (not silent wrong constant) | | Unknown synthetic msg in unit test | Fallback behavior documented; guardrail triggers as designed | | Existing suites | `transactions`, `terraGas.feeDiscount`, hybrid/swap gas tests green | --- ## Test plan (attack / hack / abuse vectors) | Vector | Expectation | |--------|-------------| | Crafted oversized execute payload | Chain/contract rejects; envelope does not grant extra privileges | | Intentionally huge gas constants | User self-grief only; no shared treasury drain | | Omitting a new msg from the table | CI/dev guardrail fails before mainnet OOG | | Wallet fee undershoot after correct envelope | Still #429; audit notes if observed | | Spamming failed txs with low gas | Pre-existing; fixing envelopes reduces accidental fee burn | --- ## Verification criteria - Inventory comment/docs merged with issue. - `cd frontend-dapp && npm run test:run -- src/services/terraclassic/__tests__/` covers new mapping tests. - Spot-check columbus-5 or LocalTerra txs for any newly fixed shapes: `gas_used < gas_wanted`. - Mint drip (P0) and fee-discount register (#384) remain mapped above measured usage.
PlasticDigits commented 2026-07-12 07:09:49 +00:00 (Migrated from gitlab.com)

marked as related to #384

marked as related to #384
PlasticDigits commented 2026-07-12 07:09:54 +00:00 (Migrated from gitlab.com)

marked as related to #474

marked as related to #474
PlasticDigits commented 2026-07-12 09:10:36 +00:00 (Migrated from gitlab.com)

mentioned in commit d3e10ddb7b

mentioned in commit d3e10ddb7bbd2e8ffe0d8d6fbe56cc49906ba7b3
PlasticDigits commented 2026-07-12 09:10:38 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1015

mentioned in merge request !1015
PlasticDigits commented 2026-07-12 09:11:59 +00:00 (Migrated from gitlab.com)

Implemented on MR !1015 (fix/475-gas-limit-audit).

Inventory: frontend-dapp/src/services/terraclassic/terraGasRetailInventory.ts (RETAIL_GAS_SHAPE_FIXTURES) + unit guardrail terraGas.retailShapes.test.ts.

Gaps fixed:

  • { drip } → FAUCET_DRIP_GAS_LIMIT (400k) — also closes #474
  • CW20 send → unwrap → UNWRAP_GAS_LIMIT (400k)

Allowlist (intentional BASE 200k): increase_allowance, decrease_allowance

Guardrail: fixtures must map above BASE unless allowlisted; DEV console.warn on unmapped fallback; make verify-issue-475

Docs: docs/frontend.md, skills/AGENTS_TERRACLASSIC_GAS.md, skills/AGENTS_SOFT_LAUNCH_FAUCET.md, runbook F13

Implemented on MR !1015 (`fix/475-gas-limit-audit`). **Inventory:** `frontend-dapp/src/services/terraclassic/terraGasRetailInventory.ts` (`RETAIL_GAS_SHAPE_FIXTURES`) + unit guardrail `terraGas.retailShapes.test.ts`. **Gaps fixed:** - `{ drip }` → `FAUCET_DRIP_GAS_LIMIT` (400k) — also closes #474 - CW20 `send` → `unwrap` → `UNWRAP_GAS_LIMIT` (400k) **Allowlist (intentional BASE 200k):** `increase_allowance`, `decrease_allowance` **Guardrail:** fixtures must map above BASE unless allowlisted; DEV `console.warn` on unmapped fallback; `make verify-issue-475` **Docs:** `docs/frontend.md`, `skills/AGENTS_TERRACLASSIC_GAS.md`, `skills/AGENTS_SOFT_LAUNCH_FAUCET.md`, runbook F13
PlasticDigits commented 2026-07-12 09:22:48 +00:00 (Migrated from gitlab.com)

mentioned in commit b598b37758

mentioned in commit b598b3775898defea541ddc8379977cc6cd10a3e
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-07-12 09:22:49 +00:00
PlasticDigits commented 2026-08-09 06:51:36 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1045

mentioned in merge request !1045
PlasticDigits commented 2026-08-18 00:43:31 +00:00 (Migrated from gitlab.com)

mentioned in issue #559

mentioned in issue #559
PlasticDigits commented 2026-08-21 11:29:49 +00:00 (Migrated from gitlab.com)

mentioned in issue #587

mentioned in issue #587
PlasticDigits commented 2026-08-21 11:29:51 +00:00 (Migrated from gitlab.com)

marked as related to #587

marked as related to #587
PlasticDigits commented 2026-08-22 11:02:34 +00:00 (Migrated from gitlab.com)

mentioned in issue #595

mentioned in issue #595
PlasticDigits commented 2026-08-23 03:05:41 +00:00 (Migrated from gitlab.com)

mentioned in issue #599

mentioned in issue #599
PlasticDigits commented 2026-08-23 03:31:22 +00:00 (Migrated from gitlab.com)

mentioned in issue #600

mentioned in issue #600
PlasticDigits commented 2026-08-24 03:15:46 +00:00 (Migrated from gitlab.com)

mentioned in issue #619

mentioned in issue #619
PlasticDigits commented 2026-08-27 00:20:45 +00:00 (Migrated from gitlab.com)

mentioned in issue #679

mentioned in issue #679
PlasticDigits commented 2026-08-27 00:20:48 +00:00 (Migrated from gitlab.com)

marked as related to #679

marked as related to #679
PlasticDigits commented 2026-08-27 00:20:55 +00:00 (Migrated from gitlab.com)

mentioned in issue #681

mentioned in issue #681
PlasticDigits commented 2026-08-27 00:20:59 +00:00 (Migrated from gitlab.com)

marked as related to #681

marked as related to #681
PlasticDigits commented 2026-08-27 00:37:16 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1182

mentioned in merge request !1182
PlasticDigits commented 2026-08-27 11:52:21 +00:00 (Migrated from gitlab.com)

mentioned in issue #690

mentioned in issue #690
PlasticDigits commented 2026-08-30 05:24:17 +00:00 (Migrated from gitlab.com)

mentioned in issue #708

mentioned in issue #708
PlasticDigits commented 2026-08-31 04:17:47 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1198

mentioned in merge request !1198
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#475
No description provided.