Skip to content

Commit

Permalink
fix(erc20): Register code hash also for native precompiles (evmos#2962)
Browse files Browse the repository at this point in the history
* add code hash also for native precompiles not only dynamic precompiles

* address review comments

* add changelog entry

* add required build inputs for nix build on mac os
  • Loading branch information
MalteHerrmann authored Nov 7, 2024
1 parent 4ad93fe commit 4208de3
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 48 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
- (app) [#2878](https://github.com/evmos/evmos/pull/2878) Changed `x/feemarket` BaseFee to Decimal. `x/evm` uses `FeeMarketWrapper` to manage EVM coin with different decimals.
- (precompiles) [#2929](https://github.com/evmos/evmos/pull/2929) Distribution: scale balance change entries to the statedb journal to support different EVM denom precision.
- (precompiles) [#2927](https://github.com/evmos/evmos/pull/2927) Erc20: scale balance change entries to the statedb journal to support different EVM denom precision.
- (erc20) [#2962](https://github.com/evmos/evmos/pull/2962) Register ERC-20 code hash also for native ERC-20 extensions.

### Improvements

Expand Down
6 changes: 1 addition & 5 deletions app/upgrades/v19_2/upgrades_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,6 @@ func TestAddCodeToERC20Extensions(t *testing.T) {

ctx := network.GetContext()

// check code does not exist
code := network.App.EvmKeeper.GetCode(ctx, expCodeHash)
require.Len(t, code, 0)

// seed the token pairs
for _, p := range tokenPairsSeed {
network.App.Erc20Keeper.SetToken(ctx, p)
Expand All @@ -159,7 +155,7 @@ func TestAddCodeToERC20Extensions(t *testing.T) {
err := v192.AddCodeToERC20Extensions(ctx, logger, network.App.Erc20Keeper)
require.NoError(t, err)

code = network.App.EvmKeeper.GetCode(ctx, expCodeHash)
code := network.App.EvmKeeper.GetCode(ctx, expCodeHash)
require.True(t, len(code) > 0)

pairs := network.App.Erc20Keeper.GetTokenPairs(ctx)
Expand Down
1 change: 1 addition & 0 deletions default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
, buildPackages
, darwin
, fetchFromGitHub
, pkgs
, stdenv
, rev ? "dirty"
, rocksdb
Expand Down
13 changes: 6 additions & 7 deletions x/erc20/keeper/dynamic_precompiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (k Keeper) RegisterERC20Extension(ctx sdk.Context, denom string) (*types.To

// RegisterERC20CodeHash sets the codehash for the erc20 precompile account
// if the bytecode for the erc20 codehash does not exists, it stores it.
func (k Keeper) RegisterERC20CodeHash(ctx sdk.Context, address common.Address) error {
func (k Keeper) RegisterERC20CodeHash(ctx sdk.Context, erc20Addr common.Address) error {
var (
// bytecode and codeHash is the same for all IBC coins
// cause they're all using the same contract
Expand All @@ -55,34 +55,33 @@ func (k Keeper) RegisterERC20CodeHash(ctx sdk.Context, address common.Address) e
balance = common.Big0
)
// keep balance and nonce if account exists
if acc := k.evmKeeper.GetAccount(ctx, address); acc != nil {
if acc := k.evmKeeper.GetAccount(ctx, erc20Addr); acc != nil {
nonce = acc.Nonce
balance = acc.Balance
}

return k.evmKeeper.SetAccount(ctx, address, statedb.Account{
return k.evmKeeper.SetAccount(ctx, erc20Addr, statedb.Account{
CodeHash: codeHash,
Nonce: nonce,
Balance: balance,
})
}

// UnRegisterERC20CodeHash sets the codehash for the account to an empty one
func (k Keeper) UnRegisterERC20CodeHash(ctx sdk.Context, erc20Address string) error {
func (k Keeper) UnRegisterERC20CodeHash(ctx sdk.Context, erc20Addr common.Address) error {
emptyCodeHash := crypto.Keccak256(nil)
contractAddr := common.HexToAddress(erc20Address)

var (
nonce uint64
balance = common.Big0
)
// keep balance and nonce if account exists
if acc := k.evmKeeper.GetAccount(ctx, contractAddr); acc != nil {
if acc := k.evmKeeper.GetAccount(ctx, erc20Addr); acc != nil {
nonce = acc.Nonce
balance = acc.Balance
}

return k.evmKeeper.SetAccount(ctx, contractAddr, statedb.Account{
return k.evmKeeper.SetAccount(ctx, erc20Addr, statedb.Account{
CodeHash: emptyCodeHash,
Nonce: nonce,
Balance: balance,
Expand Down
2 changes: 1 addition & 1 deletion x/erc20/keeper/dynamic_precompiles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func (suite *KeeperTestSuite) TestRegisterERC20CodeHash() {
suite.Require().Equal(uint64(0), acc.Nonce)
}

err = suite.network.App.Erc20Keeper.UnRegisterERC20CodeHash(ctx, account.Hex())
err = suite.network.App.Erc20Keeper.UnRegisterERC20CodeHash(ctx, account)
suite.Require().NoError(err)

acc = suite.network.App.EvmKeeper.GetAccount(ctx, account)
Expand Down
62 changes: 45 additions & 17 deletions x/erc20/keeper/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,43 +23,71 @@ func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) {
return types.NewParams(enableErc20, nativePrecompiles, dynamicPrecompiles)
}

func (k Keeper) UpdateCodeHash(ctx sdk.Context, updatedDynamicPrecompiles []string) error {
// if a precompile is disabled or deleted in the params, we should remove the codehash
// UpdateCodeHash takes in the updated parameters and
// compares the new set of native and dynamic precompiles to the current
// parameter set.
//
// If there is a diff, the ERC-20 code hash for all precompiles that are removed from the list
// will be removed from the store. Meanwhile, for all newly added precompiles the code hash will be
// registered.
func (k Keeper) UpdateCodeHash(ctx sdk.Context, newParams types.Params) error {
oldNativePrecompiles := k.getNativePrecompiles(ctx)
oldDynamicPrecompiles := k.getDynamicPrecompiles(ctx)
disabledPrecompiles, enabledPrecompiles := types.GetDisabledAndEnabledPrecompiles(oldDynamicPrecompiles, updatedDynamicPrecompiles)
for _, precompile := range disabledPrecompiles {
if err := k.UnRegisterERC20CodeHash(ctx, precompile); err != nil {

if err := k.RegisterOrUnregisterERC20CodeHashes(ctx, oldDynamicPrecompiles, newParams.DynamicPrecompiles); err != nil {
return err
}

return k.RegisterOrUnregisterERC20CodeHashes(ctx, oldNativePrecompiles, newParams.NativePrecompiles)
}

// RegisterOrUnregisterERC20CodeHashes takes two arrays of precompiles as its argument:
// - previously registered precompiles
// - new set of precompiles to be registered
//
// It then compares the two arrays and registers the code hash for all precompiles that are newly added
// and unregisters the code hash for all precompiles that are removed from the list.
func (k Keeper) RegisterOrUnregisterERC20CodeHashes(ctx sdk.Context, oldPrecompiles, newPrecompiles []string) error {
for _, precompile := range oldPrecompiles {
if slices.Contains(newPrecompiles, precompile) {
continue
}

if err := k.UnRegisterERC20CodeHash(ctx, common.HexToAddress(precompile)); err != nil {
return err
}
}

// if a precompile is added we should register the account with the erc20 codehash
for _, precompile := range enabledPrecompiles {
for _, precompile := range newPrecompiles {
if slices.Contains(oldPrecompiles, precompile) {
continue
}

if err := k.RegisterERC20CodeHash(ctx, common.HexToAddress(precompile)); err != nil {
return err
}
}

return nil
}

// SetParams sets the erc20 parameters to the param space.
func (k Keeper) SetParams(ctx sdk.Context, params types.Params) error {
// and keep params equal between different executions
slices.Sort(params.DynamicPrecompiles)
slices.Sort(params.NativePrecompiles)
func (k Keeper) SetParams(ctx sdk.Context, newParams types.Params) error {
// sort to keep params equal between different executions
slices.Sort(newParams.DynamicPrecompiles)
slices.Sort(newParams.NativePrecompiles)

if err := params.Validate(); err != nil {
if err := newParams.Validate(); err != nil {
return err
}

// update the codehash for enabled or disabled dynamic precompiles
if err := k.UpdateCodeHash(ctx, params.DynamicPrecompiles); err != nil {
if err := k.UpdateCodeHash(ctx, newParams); err != nil {
return err
}

k.setERC20Enabled(ctx, params.EnableErc20)
k.setDynamicPrecompiles(ctx, params.DynamicPrecompiles)
k.setNativePrecompiles(ctx, params.NativePrecompiles)
k.setERC20Enabled(ctx, newParams.EnableErc20)
k.setDynamicPrecompiles(ctx, newParams.DynamicPrecompiles)
k.setNativePrecompiles(ctx, newParams.NativePrecompiles)
return nil
}

Expand Down
17 changes: 0 additions & 17 deletions x/erc20/types/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package types
import (
"fmt"
"regexp"
"slices"
"strings"

sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -92,19 +91,3 @@ func IsModuleAccount(acc sdk.AccountI) bool {
_, isModuleAccount := acc.(sdk.ModuleAccountI)
return isModuleAccount
}

func GetDisabledAndEnabledPrecompiles(oldDynamicPrecompiles, newDynamicPrecompiles []string) (disabledPrecompiles, enabledPrecompiles []string) {
for _, precompile := range oldDynamicPrecompiles {
if !slices.Contains(newDynamicPrecompiles, precompile) {
disabledPrecompiles = append(disabledPrecompiles, precompile)
}
}

for _, precompile := range newDynamicPrecompiles {
if !slices.Contains(oldDynamicPrecompiles, precompile) {
enabledPrecompiles = append(enabledPrecompiles, precompile)
}
}

return disabledPrecompiles, enabledPrecompiles
}
4 changes: 3 additions & 1 deletion x/evm/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/evmos/evmos/v20/contracts"
"github.com/evmos/evmos/v20/crypto/ethsecp256k1"
"github.com/evmos/evmos/v20/precompiles/erc20"
testfactory "github.com/evmos/evmos/v20/testutil/integration/evmos/factory"
testhandler "github.com/evmos/evmos/v20/testutil/integration/evmos/grpc"
testkeyring "github.com/evmos/evmos/v20/testutil/integration/evmos/keyring"
Expand Down Expand Up @@ -234,12 +235,13 @@ func TestExportGenesis(t *testing.T) {
require.NoError(t, ts.network.NextBlock(), "failed to advance block")

genState := evm.ExportGenesis(ts.network.GetContext(), ts.network.App.EvmKeeper)
require.Len(t, genState.Accounts, 2, "expected only one smart contract in the exported genesis")
require.Len(t, genState.Accounts, 3, "expected 3 smart contracts in the exported genesis") // NOTE: 2 deployed above + 1 for the aevmos denomination ERC-20 pair

genAddresses := make([]string, 0, len(genState.Accounts))
for _, acc := range genState.Accounts {
genAddresses = append(genAddresses, acc.Address)
}
require.Contains(t, genAddresses, contractAddr.Hex(), "expected contract 1 address in exported genesis")
require.Contains(t, genAddresses, contractAddr2.Hex(), "expected contract 2 address in exported genesis")
require.Contains(t, genAddresses, erc20.WEVMOSContractMainnet, "expected mainnet aevmos contract address in exported genesis")
}
7 changes: 7 additions & 0 deletions x/evm/keeper/statedb_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keeper_test

import (
"bytes"
"fmt"
"math/big"
"testing"
Expand All @@ -18,6 +19,7 @@ import (
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/evmos/evmos/v20/contracts"
"github.com/evmos/evmos/v20/precompiles/erc20"
testfactory "github.com/evmos/evmos/v20/testutil/integration/evmos/factory"
testhandler "github.com/evmos/evmos/v20/testutil/integration/evmos/grpc"
testkeyring "github.com/evmos/evmos/v20/testutil/integration/evmos/keyring"
Expand Down Expand Up @@ -402,6 +404,11 @@ func TestIterateContracts(t *testing.T) {
)

network.App.EvmKeeper.IterateContracts(network.GetContext(), func(addr common.Address, codeHash common.Hash) bool {
// NOTE: we only care about the 2 contracts deployed above, not the ERC20 native precompile for the aevmos denomination
if bytes.Equal(addr.Bytes(), common.HexToAddress(erc20.WEVMOSContractMainnet).Bytes()) {
return false
}

foundAddrs = append(foundAddrs, addr)
foundHashes = append(foundHashes, codeHash)
return false
Expand Down

0 comments on commit 4208de3

Please sign in to comment.