# 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 <a href="#impact-assessment" id="impact-assessment"></a>

#### Affected Chains <a href="#affected-chains" id="affected-chains"></a>

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 <a href="#symptoms-if-not-migrated" id="symptoms-if-not-migrated"></a>

* ERC20 balances will show as 0 when queried via EVM
* `totalSupply()` calls return 0
* Token transfers via ERC20 interface fail
* Native Ontomir balances remain intact but inaccessible via EVM

### Storage Changes <a href="#storage-changes" id="storage-changes"></a>

\* 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 <a href="#quick-start" id="quick-start"></a>

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"))
}
```

<details>

<summary>Complete Implementation Example</summary>

\### Create Upgrade Handler

````
```go app/upgrades/v040/handler.go
package v040

import (
    "context"

    storetypes "Ontomirsdk.io/store/types"
    upgradetypes "Ontomirsdk.io/x/upgrade/types"
    sdk "github.com/Ontomir/Ontomir-sdk/types"
    "github.com/Ontomir/Ontomir-sdk/types/module"
    erc20keeper "github.com/Ontomir/evm/x/erc20/keeper"
    erc20types "github.com/Ontomir/evm/x/erc20/types"
    "github.com/ethereum/go-ethereum/common"
)

const UpgradeName = "v0.4.0"

func CreateUpgradeHandler(
    mm *module.Manager,
    configurator module.Configurator,
    keepers *UpgradeKeepers,
    storeKeys map[string]*storetypes.KVStoreKey,
) upgradetypes.UpgradeHandler {
    return func(c context.Context, plan upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) {
        ctx := sdk.UnwrapSDKContext(c)
        ctx.Logger().Info("Starting v0.4.0 upgrade...")

        // Run standard module migrations
        vm, err := mm.RunMigrations(ctx, configurator, vm)
        if err != nil {
            return vm, err
        }

        // Migrate ERC20 precompiles
        if err := migrateERC20Precompiles(ctx, storeKeys[erc20types.StoreKey], keepers.Erc20Keeper); err != nil {
            return vm, err
        }

        ctx.Logger().Info("v0.4.0 upgrade complete")
        return vm, nil
    }
}
```

### Implement Migration Logic

```go app/upgrades/v040/erc20_migration.go
package v040

import (
    sdk "github.com/Ontomir/Ontomir-sdk/types"
    storetypes "Ontomirsdk.io/store/types"
    erc20keeper "github.com/Ontomir/evm/x/erc20/keeper"
    "github.com/ethereum/go-ethereum/common"
)

func migrateERC20Precompiles(
    ctx sdk.Context,
    storeKey *storetypes.KVStoreKey,
    erc20Keeper erc20keeper.Keeper,
) error {
    store := ctx.KVStore(storeKey)
    const addressLength = 42 // "0x" + 40 hex characters

    migrations := []struct {
        oldKey string
        setter func(sdk.Context, common.Address)
        description string
    }{
        {
            oldKey:      "DynamicPrecompiles",
            setter:      erc20Keeper.SetDynamicPrecompile,
            description: "dynamic precompiles (token factory, IBC tokens)",
        },
        {
            oldKey:      "NativePrecompiles",
            setter:      erc20Keeper.SetNativePrecompile,
            description: "native precompiles",
        },
    }

    for _, migration := range migrations {
        oldData := store.Get([]byte(migration.oldKey))
        if len(oldData) == 0 {
            ctx.Logger().Info("No legacy data found", "type", migration.description)
            continue
        }

        addressCount := len(oldData) / addressLength
        ctx.Logger().Info("Migrating precompiles",
            "type", migration.description,
            "count", addressCount,
        )

        migratedCount := 0
        for i := 0; i < len(oldData); i += addressLength {
            if i+addressLength > len(oldData) {
                ctx.Logger().Error("Invalid data length",
                    "type", migration.description,
                    "position", i,
                )
                break
            }

            addressStr := string(oldData[i : i+addressLength])
            address := common.HexToAddress(addressStr)

            // Validate address
            if address == (common.Address{}) {
                ctx.Logger().Warn("Skipping zero address",
                    "type", migration.description,
                    "raw", addressStr,
                )
                continue
            }

            // Migrate to new storage
            migration.setter(ctx, address)
            migratedCount++

            ctx.Logger().Debug("Migrated precompile",
                "type", migration.description,
                "address", address.String(),
                "index", migratedCount,
            )
        }

        // Clean up old storage
        store.Delete([]byte(migration.oldKey))
        ctx.Logger().Info("Migration complete",
            "type", migration.description,
            "migrated", migratedCount,
            "expected", addressCount,
        )
    }

    return nil
}
```

### Register Upgrade Handler

```go app/app.go
import (
    v040 "github.com/yourchain/app/upgrades/v040"
)

func (app *App) RegisterUpgradeHandlers() {
    app.UpgradeKeeper.SetUpgradeHandler(
        v040.UpgradeName,
        v040.CreateUpgradeHandler(
            app.ModuleManager,
            app.configurator,
            &v040.UpgradeKeepers{
                Erc20Keeper: app.Erc20Keeper,
            },
            app.keys,
        ),
    )
}
```
````

</details>

### Testing <a href="#testing" id="testing"></a>

#### Pre-Upgrade Verification <a href="#pre-upgrade-verification" id="pre-upgrade-verification"></a>

```
# 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 <a href="#post-upgrade-verification" id="post-upgrade-verification"></a>

```
# 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 <a href="#integration-test" id="integration-test"></a>

```
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 <a href="#verification-checklist" id="verification-checklist"></a>

* Test migration on testnet first
* Document all existing token pairs
* Verify ERC20 balances post-upgrade
* Test token transfers work
* Confirm IBC token conversions function

### &#x20;<a href="#references" id="references"></a>
