Skip to content

Keep contract burn_tokens working after the tokenfactory removal - #4549

Merged
alexander-sei merged 5 commits into
mainfrom
alex/legacy-burn-tokens-alias
Oct 16, 2026
Merged

alexander-sei merged 5 commits into
mainfrom
alex/legacy-burn-tokens-alias

Conversation

@alexander-sei

@alexander-sei alexander-sei commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

#4536 removed tokenfactory behind v6.8, including the burn_tokens custom Wasm message, and contracts that issued factory/ denoms redeem them by sending exactly that message. From the upgrade height those redemptions fail with "Unknown Sei Wasm Message". The Silo and Eris liquid staking hubs on pacific-1 burn the queued iSEI and ampSEI in submit_batch, so no unbonding batch could be submitted and the queued tokens would stay locked in the hubs; iSEI alone is backed by about 3.6M SEI. The Wormhole token translator, which has no admin and so can never be migrated, issues 90 live factory/ denoms through the same bindings.

The removed module had no burn logic of its own beyond an admin check. CustomEncoder turned burn_tokens into a MsgBurn sent by the contract (EncodeTokenFactoryBurn), msgServer.Burn required that sender to be the admin stored for the denom (Burn), and burnFrom moved the coins into the tokenfactory module account with SendCoinsFromAccountToModule and destroyed them with BankKeeper.BurnCoins (burnFrom). MsgBurn had no way to name another address, so a burn only ever took from the sender's own balance.

LegacyBurnTokensHandler makes the same two bank calls through the Wasm module account, which already holds Burner permission because BankMsg::Burn burns through it the same way, and it refuses missing, zero or invalid amounts. The admin check becomes the creator embedded in the denom: only factory/<contract>/<subdenom> denoms whose <contract> is the caller are accepted, so no tokenfactory store or keeper is needed, and because any contract can already burn coins it holds with BankMsg::Burn, the handler grants no new capability. It runs first in CustomMessageHandler's chain because CustomEncoder refuses burn_tokens with an error that ends the chain. Creating, minting, admin changes, metadata, queries and allowlists stay removed and direct MsgBurn transactions are still refused at CheckTx, so the hubs stop issuing and wind down instead of breaking. Asking each issuer to switch to BankMsg::Burn would cover the two hubs but not immutable contracts such as the translator.

The handler cannot change how other contracts execute. Every message that is not burn_tokens, including bank, staking, Wasm, stargate and call_evm, gets ErrUnknownMsg without touching state and continues down the chain exactly as before. The burned balance is always the calling contract's own, because wasmd supplies the contract address rather than the message, and the prefix ends in /, so no contract can burn another contract's or a user's tokens, including factory/ tokens a pool merely holds. The transfer and the burn run inside the sub-message's cache context in DispatchSubmessages, so a failure reverts both, and the bank operations are gas-metered. The remaining edges are deliberate: a hand-built message carrying both burn_tokens and call_evm is treated as a burn, the same precedence the removed encoder had; a burn lowers the denom's bank supply, which readers such as the ERC20 pointer's totalSupply see, as they did before v6.8; two burns of the same denom in one block write the same supply and module balance keys, so OCC re-executes one, while burns of different denoms share no keys; and every other custom message gets one extra JSON decode that is not gas-metered, next to the one CustomEncoder already does.

This has to ship in the same release as #4536: a v6.8 cut from main without it breaks those redemptions at the upgrade height. Two differences from the removed module are visible: the bank burn event names the Wasm module account as the burner and has no burn_from_address, and authority follows the creator rather than the stored admin, so a contract that handed admin rights away can burn its own holdings again, as it already could with BankMsg::Burn, while an admin that did not create a denom cannot, which is already the case after #4536. Validated with the wasmbinding tests and by running the pacific-1 Silo and Eris hub bytecode from v6.7 to v6.8, both in the offline upgrade harness and on a four-validator docker cluster upgraded through governance: with this change both hubs burn, unbond and pay out the queued SEI with every validator agreeing on the app hash, and without it both submit_batch calls fail. That cross-version test is not included because it needs the two hub binaries (about 1.3 MB) checked in as test data.

Contracts that issued factory denoms redeem by sending the retired
tokenfactory burn_tokens custom message: the Silo and Eris liquid staking
hubs burn iSEI and ampSEI when they submit an unbonding batch, and the
Wormhole token translator burns when bridging out. Without the module that
message ends the whole execution with "Unknown Sei Wasm Message".

LegacyBurnTokensHandler serves it ahead of the SDK message handler by
burning from the calling contract's own balance through the Wasm module
account, the same operation BankMsg::Burn already performs, and only for
denoms of the form factory/<contract>/<subdenom>. Creating, minting,
admin changes and metadata stay removed, and direct MsgBurn transactions
are still refused.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedOct 16, 2026, 11:00 AM

@codecov

codecov Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 31.25000% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 57.54%. Comparing base (c07cde4) to head (ae97af9).

Files with missing lines Patch % Lines
wasmbinding/burn_tokens.go 26.66% 11 Missing ⚠️

❌ Your patch check has failed because the patch coverage (31.25%) is below the target coverage (50.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4549      +/-   ##
==========================================
- Coverage   57.56%   57.54%   -0.02%     
==========================================
  Files        2132     2133       +1     
  Lines      167272   167288      +16     
==========================================
- Hits        96286    96263      -23     
- Misses      70981    71020      +39     
  Partials        5        5              
Flag Coverage Δ
sei-chain 55.59% <31.25%> (-0.03%) ⬇️
sei-db 75.41% <ø> (+0.23%) ⬆️
sei-db-state-db 80.13% <ø> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
wasmbinding/message_plugin.go 62.50% <100.00%> (+2.50%) ⬆️
wasmbinding/burn_tokens.go 26.66% <26.66%> (ø)

... and 30 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@alexander-sei

Copy link
Copy Markdown
Contributor Author

@seidroid review

@seidroid seidroid Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds a message handler that keeps the retired tokenfactory burn_tokens Wasm message working: a contract can burn its own balance of factory/\<contract>/\<subdenom> denoms through the Wasm module account, and the handler runs first in CustomMessageHandler's chain. The burn uses the same transfer-then-burn path as wasmd's BankMsg::Burn handler, the Wasm module account has Burner permission, the prefix check ends in / so one address cannot match a longer one, and other messages still fall through as ErrUnknownMsg, so nothing blocks; the one note is a panic on malformed input (tree checked at bf78b7c; tests not run because no Go toolchain was available; codex's reading reported nothing, which agrees except for that note).

Non-blocking

1 finding on the changed lines, as inline comments.

seidroid review · decision approve · session f772c3ba10f444a09031f34f84dff0ca · turn resp_claude_1bc698d91a0a75202387082019e6700a · item e3a055812c3558b896ff0cc1ac415bfb

Findings: 0 blocking | 1 non-blocking | 1 posted inline

Comment thread wasmbinding/burn_tokens.go
alexander-sei and others added 2 commits October 15, 2026 16:48
Co-authored-by: Cursor <cursoragent@cursor.com>
Coin.Validate dereferences the amount, so a burn_tokens message whose
amount has a denom but no amount panicked instead of returning
ErrInvalidCoins, and Wasm dispatch re-raises that panic past any
reply_on_error submessage.

Co-authored-by: Cursor <cursoragent@cursor.com>
@alexander-sei

Copy link
Copy Markdown
Contributor Author

@seidroid review

@seidroid seidroid Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since my last review the handler checks amount.Amount.IsNil() before Validate(), a test now covers a missing amount, and a CHANGELOG entry was added; this fixes the panic I raised, and printing the nil amount in the error message is also safe. No other findings: codex's reading also reported nothing, which agrees, and the tree I checked is at merge commit 5653a44.

seidroid review · decision approve · session f772c3ba10f444a09031f34f84dff0ca · turn resp_claude_847e9e036376bec1b83def354679dc74 · item f5f5fddb871c504d8c9846bab3d16dea

Findings: 0 blocking | 0 non-blocking | 0 posted inline

@alexander-sei
alexander-sei requested a review from masih October 15, 2026 18:41
@alexander-sei
alexander-sei marked this pull request as ready for review October 15, 2026 18:41
@cursor

cursor Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes live Wasm message handling and bank supply for factory/ denoms on mainnet contracts; scope is narrow but must ship with the tokenfactory removal upgrade.

Overview
Restores the retired tokenfactory burn_tokens CosmWasm custom message after v6.8 removes the module, so contracts that redeem by burning their own factory/<contract>/… denoms (e.g. Silo/Eris liquid staking hubs and the Wormhole token translator) keep working instead of hitting “Unknown Sei Wasm Message”.

Adds LegacyBurnTokensHandler, registered first in CustomMessageHandler before the SDK path (whose CustomEncoder no longer encodes burn_tokens). It only accepts positive amounts for denoms whose creator prefix matches the calling contract, moves coins from the contract to the wasm module account, and BurnCoins—same bank effect as before, without tokenfactory state. Minting and every other tokenfactory custom message stay removed. Includes wasmbinding tests and an Unreleased changelog entry.

Reviewed by Cursor Bugbot for commit ae97af9. Bugbot is set up for automated code reviews on this repo. Configure here.

@alexander-sei
alexander-sei added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit 1e58601 Oct 16, 2026
60 checks passed
@alexander-sei
alexander-sei deleted the alex/legacy-burn-tokens-alias branch October 16, 2026 11:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants