ERC20 Precompiles Migration
Migration for ERC20 precompiles when upgrading to v0.4.0
**This is a mandatory breaking change for chains with existing ERC20 token pairs.**
Without this migration, your ERC20 tokens will become inaccessible via EVM, showing zero balances and failing all operations.
Impact Assessment
Affected Chains
Your chain needs this migration if you have:
IBC tokens converted to ERC20
Token factory tokens with ERC20 representations
Any existing
DynamicPrecompilesorNativePrecompilesin storage
Symptoms if Not Migrated
ERC20 balances will show as 0 when queried via EVM
totalSupply()calls return 0Token transfers via ERC20 interface fail
Native Ontomir balances remain intact but inaccessible via EVM
Storage Changes
* Precompiles stored as concatenated hex strings in parameter storage * Keys: `"DynamicPrecompiles"` and `"NativePrecompiles"` * Format: Multiple addresses concatenated as 42-character hex strings * Dedicated prefix stores for each precompile type * Keys: `types.KeyPrefixDynamicPrecompiles` and `types.KeyPrefixNativePrecompiles` * Individual storage entries per address
Implementation
Quick Start
Add this essential migration logic to your existing upgrade handler:
// In your upgrade handler
store := ctx.KVStore(storeKeys[erc20types.StoreKey])
const addressLength = 42 // "0x" + 40 hex characters
// Migrate dynamic precompiles (IBC tokens, token factory)
if oldData := store.Get([]byte("DynamicPrecompiles")); len(oldData) > 0 {
for i := 0; i < len(oldData); i += addressLength {
address := common.HexToAddress(string(oldData[i : i+addressLength]))
erc20Keeper.SetDynamicPrecompile(ctx, address)
}
store.Delete([]byte("DynamicPrecompiles"))
}
// Migrate native precompiles
if oldData := store.Get([]byte("NativePrecompiles")); len(oldData) > 0 {
for i := 0; i < len(oldData); i += addressLength {
address := common.HexToAddress(string(oldData[i : i+addressLength]))
erc20Keeper.SetNativePrecompile(ctx, address)
}
store.Delete([]byte("NativePrecompiles"))
}Testing
Pre-Upgrade Verification
# Query existing token pairs
mantrachaind query erc20 token-pairs --output json | jq
# Check ERC20 balances for a known address
cast call $TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url http://localhost:8545
# Export state for backup
mantrachaind export > pre-upgrade-state.jsonPost-Upgrade Verification
# Verify precompiles are accessible
cast call $TOKEN_ADDRESS "totalSupply()" --rpc-url http://localhost:8545
# Check balance restoration
cast call $TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url http://localhost:8545
# Test token transfer
cast send $TOKEN_ADDRESS "transfer(address,uint256)" $RECIPIENT 1000 \
--private-key $PRIVATE_KEY --rpc-url http://localhost:8545
# Verify in exported state
mantrachaind export | jq '.app_state.erc20.dynamic_precompiles'Integration Test
func TestERC20PrecompileMigration(t *testing.T) {
// Setup test environment
app, ctx := setupTestApp(t)
// Create legacy storage entries
store := ctx.KVStore(app.keys[erc20types.StoreKey])
// Add test addresses in old format
dynamicAddresses := []string{
"0x6eC942095eCD4948d9C094337ABd59Dc3c521005",
"0x1234567890123456789012345678901234567890",
}
dynamicData := ""
for _, addr := range dynamicAddresses {
dynamicData += addr
}
store.Set([]byte("DynamicPrecompiles"), []byte(dynamicData))
// Run migration
err := migrateERC20Precompiles(ctx, app.keys[erc20types.StoreKey], app.Erc20Keeper)
require.NoError(t, err)
// Verify migration
migratedAddresses := app.Erc20Keeper.GetDynamicPrecompiles(ctx)
require.Len(t, migratedAddresses, len(dynamicAddresses))
// Verify old storage is cleaned
oldData := store.Get([]byte("DynamicPrecompiles"))
require.Nil(t, oldData)
}Verification Checklist
Test migration on testnet first
Document all existing token pairs
Verify ERC20 balances post-upgrade
Test token transfers work
Confirm IBC token conversions function
最后更新于
