对于已存在 ERC20 代币对的 pre-v0.4.x 链,这是一个必须执行的破坏性变更。如果忽略此迁移,现有 ERC20 代币将无法访问,余额查询会返回 0,并且所有操作都会失败。
影响评估
受影响的链
如果你的链存在以下任一情况,就需要执行这次迁移:
- 已转换为 ERC20 的 IBC 代币
- 带有 ERC20 表示形式的 Token Factory 代币
- 存储中存在任何已有的
DynamicPrecompiles 或 NativePrecompiles
未迁移时的症状
- 通过 EVM 查询时,ERC20 余额会显示为 0
totalSupply() 调用会返回 0
- 通过 ERC20 接口进行的代币转账会失败
- 原生 Cosmos 余额仍然存在,但无法通过 EVM 访问
存储变更
实现方式
快速开始
将以下逻辑添加到你现有的升级处理器中:
// 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"))
}
升级前验证
# 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.json
升级后验证
# 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'
集成测试
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)
}
验证清单
参考资料
This is a mandatory breaking change for pre-v0.4.x chains with existing ERC20 token pairs.If neglected, existing ERC20 tokens will become inaccessible, return zero balances and fail 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
DynamicPrecompiles or NativePrecompiles in storage
Symptoms if Not Migrated
- ERC20 balances will show as 0 when queried via EVM
totalSupply() calls return 0
- Token transfers via ERC20 interface fail
- Native Cosmos balances remain intact but inaccessible via EVM
Storage Changes
Implementation
Quick Start
Add 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.json
Post-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
References