Skip to content

fix(at): waive AT step fees when the chain has no native asset - #187

Merged
QuickMythril merged 2 commits into
mainfrom
feat/at-no-native-asset-fee-waiver
Jul 30, 2026
Merged

fix(at): waive AT step fees when the chain has no native asset#187
QuickMythril merged 2 commits into
mainfrom
feat/at-no-native-asset-fee-waiver

Conversation

@QuickMythril

Copy link
Copy Markdown
Member

Problem

deployAtWorkingAssetHeight (60,000) let non-native-working ATs deploy on a chain without a native asset, but the runtime half was missing. Three sites still priced execution in NATIVE:

  • ChainATAPI.hasNativeFeeBalance() — gates willExecute(), so the AT never takes a round
  • getMaxStepsPerRound() — budgets steps against the native balance (0 balance → 0 steps)
  • calcStepFees() / calcFinalFees() — bills steps × feePerStep in NATIVE, debited by Block.processAtFeesAndStates

On Previewnet nothing can ever fund an AT's native balance, so the deployed SMPL faucet AT (AG9QWs1tEBTmXoH2rrQXwV4LdMAM99o5WD) sat idle through both confirmed claim MESSAGEs (blocks 73,473 / 73,477) while reporting non-fatal/not-finished. The DeployAtTransaction comment claiming the runtime already waived these fees described only the VM-facing getFeePerStep() hook and has been corrected.

Fix

New feature trigger atNoNativeAssetFeeWaiverHeight = 80,000 on Previewnet (~2026-08-05 at the observed ~1,040 blocks/day). From that height, while assetExists(Asset.NATIVE) is false — the same detection the deploy-side fix uses — the effective feePerStep is zero.

All step-fee sites price through one effectiveFeePerStep() helper keyed off the consensus-anchored gate height, so no site can be waived while another still charges. Zero fees no-op cleanly in block fee accounting (modifyAssetBalance returns early on 0). Chains whose native asset exists are unchanged even beyond the trigger; blocks below the trigger replay byte-for-byte. The pinned AT jar needs no change (getFeePerStep() already returns 0 for non-native ATs).

blockchain.json deliberately omits the trigger (missing key → disabled).

Rollout

featureTriggers are excluded from getChainConfigHash, so peering is unaffected — but every minting node must run this build before height 80,000. After activation the deployed SMPL AT self-recovers: its first round runs at the trigger block and the two queued MESSAGEs are processed in successive rounds, nothing to resend.

Tests

  • New ATNoNativeAssetFeeWaiverTests (new no-native chain config, trigger at 10): AT skipped every round below the trigger, executes fee-free from the trigger block (fees 0, working-asset funding untouched, native asset still absent); with no trigger configured it stays skipped forever (pins today's live behavior); on the default chain, fees still charged with the trigger active.
  • test-chain-v2.json now activates the trigger from 0 with NATIVE in genesis, so the entire existing AT suite continuously guards the native-chain-unchanged property.
  • Mutation-tested: waiver-never-fires fails the transition test; waiver-always-fires fails all three.
  • Full suite: 2,883 tests, 0 failures, 0 errors (65 pre-existing skips).

🤖 Generated with Claude Code

QuickMythril and others added 2 commits July 30, 2026 02:49
deployAtWorkingAssetHeight let non-native-working ATs deploy on a chain
without a native asset, but the runtime half was missing: willExecute()
still demanded a native fee balance, getMaxStepsPerRound() budgeted zero
steps, and calcStepFees() still billed steps x feePerStep in NATIVE - so
a deployed AT could never take its first round, while reporting
non-fatal/not-finished.

Add the atNoNativeAssetFeeWaiverHeight feature trigger (80,000 on
Previewnet): from that height, while the native asset does not exist,
the effective feePerStep is zero. All step-fee sites price through one
effectiveFeePerStep() helper keyed off the consensus-anchored gate
height, so updated nodes cannot disagree with each other on per-AT or
per-block fee totals. Chains whose native asset exists are unchanged
even beyond the trigger (guarded suite-wide by activating the trigger
in test-chain-v2.json, whose genesis has NATIVE).

ATs deployed while the bug was live start executing on their own at the
activation height and process their already-confirmed queued messages;
nothing needs resending.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codex consensus review of the waiver flagged that the native asset is
not permanently absent: IssueAssetTransaction allows a development-
group-approved runtime bootstrap of assetId 0. Cover the transition:
the AT runs fee-free up to the bootstrap, is starved once fees resume
(zero native balance, even when transactions arrive), and charges real
step fees again once native-funded. Mutation-verified: ignoring asset
existence in the waiver fails this test and the genesis-native guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@QuickMythril

Copy link
Copy Markdown
Member Author

Dual-model review record: an adversarial Codex consensus review of a520f4614 found no critical/high defects — it confirmed all step-fee sites price through the waived rate (the remaining direct feePerStep read in getFeePerStep() is VM-working-balance only, already 0 for non-native ATs), activation is deterministic across minting/sync/replay/orphan paths, and pre-trigger behavior is byte-identical.

It flagged one low-severity gap, addressed in 389d9f8eb: the native asset is not permanently absent — IssueAssetTransaction allows a dev-group-approved runtime bootstrap of assetId 0, so the waiver can deterministically switch off mid-chain. New test testFeesResumeOnceNativeAssetIsBootstrapped covers the transition: fee-free rounds up to the bootstrap block, starvation once fees resume (zero native balance), and real fees charged again once the AT is native-funded. Mutation-verified (ignoring asset existence fails this test plus the genesis-native guard).

@QuickMythril
QuickMythril merged commit 1e6872b into main Jul 30, 2026
9 checks passed
@QuickMythril
QuickMythril deleted the feat/at-no-native-asset-fee-waiver branch July 30, 2026 07:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant