For conceptual understanding of predeployed contracts and how they differ from precompiles, see the Predeployed Contracts concept page.

Activation Methods

There are three primary methods to deploy preinstalled contracts on a Cosmos EVM chain:

1. Genesis Configuration

The most straightforward method for new chains or testnets. Contracts are deployed when the chain is first initialized.

Default Configuration

Using the evmd example application automatically includes default preinstalls:
// evmd/genesis.go
func NewEVMGenesisState() *evmtypes.GenesisState {
    evmGenState := evmtypes.DefaultGenesisState()
    evmGenState.Params.ActiveStaticPrecompiles = evmtypes.AvailableStaticPrecompiles
    evmGenState.Preinstalls = evmtypes.DefaultPreinstalls
    return evmGenState
}

Custom Genesis Configuration

To customize which contracts are deployed:
// genesis.json
{
  "app_state": {
    "evm": {
      "preinstalls": [
        {
          "name": "Create2",
          "address": "0x4e59b44847b379578588920ca78fbf26c0b4956c",
          "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"
        },
        {
          "name": "Multicall3",
          "address": "0xcA11bde05977b3631167028862bE2a173976CA11",
          "code": "0x6080604052..." // Full bytecode
        }
      ]
    }
  }
}

Local Development

The local_node.sh script automatically configures default preinstalls:
./local_node.sh
# Automatically deploys all default preinstalls

2. Governance Proposal

For chains already in production, use the MsgRegisterPreinstalls governance proposal:

Proposal Structure

The authority field must be set to the governance module account address, which is typically derived from the gov module name. This is usually something like cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn for the standard gov module.
{
  "messages": [
    {
      "@type": "/cosmos.evm.vm.v1.MsgRegisterPreinstalls",
      "authority": "[gov module account address]",
      "preinstalls": [
        {
          "name": "Multicall3",
          "address": "0xcA11bde05977b3631167028862bE2a173976CA11",
          "code": "0x6080604052..."
        }
      ]
    }
  ],
  "metadata": "ipfs://CID",
  "deposit": "10000000stake",
  "title": "Deploy Multicall3 Contract",
  "summary": "Deploy the Multicall3 contract to enable batched calls"
}

Submission Process

# Submit proposal
evmd tx gov submit-proposal proposal.json \
  --from mykey \
  --chain-id cosmosevm_9001-1 \
  --gas auto

# Vote on proposal
evmd tx gov vote 1 yes --from mykey

3. Chain Upgrade Handler

Include predeployed contracts as part of a coordinated chain upgrade:
func CreateUpgradeHandler(
    mm *module.Manager,
    configurator module.Configurator,
    evmKeeper *evmkeeper.Keeper,
) upgradetypes.UpgradeHandler {
    return func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) {
        // Add preinstalls during upgrade
        if err := evmKeeper.AddPreinstalls(ctx, evmtypes.DefaultPreinstalls); err != nil {
            return nil, err
        }

        return mm.RunMigrations(ctx, configurator, fromVM)
    }
}

Implementation Details

Validation Process

All preinstall deployments undergo strict validation:
  1. Address Validation
    • Must be valid Ethereum address format (40 hex characters)
    • Cannot conflict with existing contracts
    • Should not overlap with precompile reserved addresses (typically 0x1-0x9FF)
  2. Code Validation
    • Must be valid EVM bytecode (hex encoded)
    • Cannot have empty code hash
    • Must pass bytecode verification
  3. Conflict Prevention
    • Checks for existing contracts at target address
    • Validates against account keeper for existing accounts
    • Ensures no code hash conflicts with different bytecode

Storage and State

Predeployed contracts are stored in the chain state like regular contracts:
// Actual deployment process from x/vm/keeper/preinstalls.go
func (k *Keeper) AddPreinstalls(ctx sdk.Context, preinstalls []types.Preinstall) error {
    for _, preinstall := range preinstalls {
        address := common.HexToAddress(preinstall.Address)
        accAddress := sdk.AccAddress(address.Bytes())

        if len(preinstall.Code) == 0 {
            return errorsmod.Wrapf(types.ErrInvalidPreinstall,
                "preinstall %s has no code", preinstall.Address)
        }

        codeHash := crypto.Keccak256Hash(common.FromHex(preinstall.Code)).Bytes()
        if types.IsEmptyCodeHash(codeHash) {
            return errorsmod.Wrapf(types.ErrInvalidPreinstall,
                "preinstall %s has empty code hash", preinstall.Address)
        }

        // Check for existing code hash conflicts
        existingCodeHash := k.GetCodeHash(ctx, address)
        if !types.IsEmptyCodeHash(existingCodeHash.Bytes()) &&
           !bytes.Equal(existingCodeHash.Bytes(), codeHash) {
            return errorsmod.Wrapf(types.ErrInvalidPreinstall,
                "preinstall %s already has a different code hash", preinstall.Address)
        }

        // Check that the account is not already set
        if acc := k.accountKeeper.GetAccount(ctx, accAddress); acc != nil {
            return errorsmod.Wrapf(types.ErrInvalidPreinstall,
                "preinstall %s already has an account in account keeper", preinstall.Address)
        }

        // Create account and store code
        account := k.accountKeeper.NewAccountWithAddress(ctx, accAddress)
        k.accountKeeper.SetAccount(ctx, account)
        k.SetCodeHash(ctx, address.Bytes(), codeHash)
        k.SetCode(ctx, codeHash, common.FromHex(preinstall.Code))
    }
    return nil
}

Verification and Testing

Verify Deployment

After deployment, verify contracts are properly installed:
# Query contract code via CLI
evmd query evm code 0x4e59b44847b379578588920ca78fbf26c0b4956c

# Check account exists
evmd query evm account 0x4e59b44847b379578588920ca78fbf26c0b4956c
// Verify via Web3
const code = await provider.getCode("0x4e59b44847b379578588920ca78fbf26c0b4956c");
console.log("Deployed:", code !== "0x");

Testing Strategy

  1. Local Testing: Deploy on local node first
  2. Testnet Validation: Test governance proposal process
  3. Integration Testing: Verify interactions with other contracts
  4. Gas Analysis: Monitor gas consumption patterns
  5. Security Audit: Review bytecode before mainnet deployment

Best Practices

Security Considerations

  • Bytecode Verification: Always verify that bytecode matches official releases
  • Address Selection: Ensure addresses don’t conflict with future plans
  • Audit Requirements: Even well-known contracts should be reviewed
  • Immutability: Remember that predeployed contracts cannot be upgraded

Deployment Recommendations

  1. Start with Defaults: Use evmtypes.DefaultPreinstalls unless you have specific requirements
  2. Test Thoroughly: Validate on testnet before mainnet deployment
  3. Document Changes: Clearly communicate any non-standard deployments to developers
  4. Monitor Usage: Track contract interactions to understand adoption

Common Pitfalls to Avoid

  • Don’t deploy to addresses that could conflict with precompiles (typically 0x1-0x9FF)
  • Don’t assume contracts are deployed - always check first
  • Don’t modify standard contract addresses without strong justification
  • Don’t deploy untested or unaudited bytecode

Known Issues

The Safe Singleton Factory bytecode in the current DefaultPreinstalls may be incorrect (appears to be duplicate of Create2 bytecode). Verify the correct bytecode before deploying this contract in production.

Adding Custom Preinstalls

To add custom contracts beyond the defaults:
// Define custom preinstall
customPreinstall := types.Preinstall{
    Name:    "MyCustomContract",
    Address: "0xYourChosenAddress",
    Code:    "0xCompiledBytecode",
}

// Validate before deployment
if err := customPreinstall.Validate(); err != nil {
    return err
}

// Add via appropriate method (genesis, governance, or upgrade)
preinstalls := append(evmtypes.DefaultPreinstalls, customPreinstall)

Troubleshooting

Common Issues

IssueCauseSolution
”preinstall already has an account in account keeper”Address collisionChoose different address
”preinstall has empty code hash”Invalid or empty bytecodeVerify bytecode hex string is valid
”preinstall address is not a valid hex address”Malformed addressEnsure 0x prefix and 40 hex chars
”invalid authority”Wrong governance addressUse correct gov module account address
Contract not found after deploymentWrong networkVerify chain ID and RPC endpoint

Debugging Steps

  1. Check chain genesis configuration
  2. Verify proposal passed and executed
  3. Query contract code directly
  4. Test with simple contract interaction
  5. Review chain logs for errors

Further Resources