From a38d5ded0b95ac95b04c45d16a388c052a37e271 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Sun, 5 May 2024 08:02:04 -0400 Subject: [PATCH] Temporary Commit --- x/evm/genesis.go | 80 +-------- x/evm/keeper/precompile.go | 116 +++++++++++++ x/evm/keeper/precompile_test.go | 277 ++++++++++++++++++++++++++++++++ 3 files changed, 401 insertions(+), 72 deletions(-) create mode 100644 x/evm/keeper/precompile.go create mode 100644 x/evm/keeper/precompile_test.go diff --git a/x/evm/genesis.go b/x/evm/genesis.go index 922313f641..e977f7ae86 100644 --- a/x/evm/genesis.go +++ b/x/evm/genesis.go @@ -18,8 +18,6 @@ package evm import ( "bytes" "fmt" - "github.com/evmos/ethermint/x/evm/statedb" - abci "github.com/cometbft/cometbft/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -31,68 +29,6 @@ import ( "github.com/evmos/ethermint/x/evm/types" ) -const PrecompileNonce = 1 - -var PrecompileCode = []byte{0x1} - -type StateDB interface { - SetNonce(common.Address, uint64) - SetCode(common.Address, []byte) -} - -func SetDifference(a []string, b []string) []string { - bMap := make(map[string]struct{}, len(b)) - for _, elem := range b { - bMap[elem] = struct{}{} - } - - diff := make([]string, 0) - for _, elem := range a { - if _, ok := bMap[elem]; !ok { - diff = append(diff, elem) - } - } - - return diff -} - -func SyncEnabledPrecompiles(old []string, new []string) { - validateInited := old - validateUninited := SetDifference(new, old) - init := SetDifference(new, old) - uninit := SetDifference(old, new) - - validateInitedPrecompiles(validateInited) - validateUninitedPrecompiles(validateUninited) - initPrecompiles(init) - uninitPrecompiles(uninit) -} - -func validateInitedPrecompiles(precompiles []string) {} - -func validateUninitedPrecompiles(precompiles []string) {} - -func initPrecompiles(stateDB StateDB, addrs []common.Address) { - for _, addr := range addrs { - initPrecompile(stateDB, addr) - } -} - -func initPrecompile(stateDB StateDB, addr common.Address) { - // Set the nonce of the precompile's address (as is done when a contract is created) to ensure - // that it is marked as non-empty and will not be cleaned up when the statedb is finalized. - stateDB.SetNonce(addr, PrecompileNonce) - // Set the code of the precompile's address to a non-zero length byte slice to ensure that the precompile - // can be called from within Solidity contracts. Solidity adds a check before invoking a contract to ensure - // that it does not attempt to invoke a non-existent contract. - stateDB.SetCode(addr, PrecompileCode) -} - -func uninitPrecompiles(stateDB StateDB, addr common.Address) { - stateDB.SetNonce(addr, 0) - stateDB.SetCode(addr, nil) // TODO(yevhenii): nil or empty slice -} - // InitGenesis initializes genesis state based on exported genesis func InitGenesis( ctx sdk.Context, @@ -100,15 +36,15 @@ func InitGenesis( accountKeeper types.AccountKeeper, data types.GenesisState, registeredModules []precompile_modules.Module, - evmKeeper statedb.Keeper, + // evmKeeper statedb.Keeper, ) []abci.ValidatorUpdate { - txConfig := statedb.TxConfig{ - BlockHash: common.Hash{}, - TxHash: common.Hash{}, - TxIndex: 0, - LogIndex: 0, - } - stateDB := statedb.New(ctx, evmKeeper, txConfig) + //txConfig := statedb.TxConfig{ + // BlockHash: common.Hash{}, + // TxHash: common.Hash{}, + // TxIndex: 0, + // LogIndex: 0, + //} + //stateDB := statedb.New(ctx, evmKeeper, txConfig) k.WithChainID(ctx) diff --git a/x/evm/keeper/precompile.go b/x/evm/keeper/precompile.go new file mode 100644 index 0000000000..81a885cbc5 --- /dev/null +++ b/x/evm/keeper/precompile.go @@ -0,0 +1,116 @@ +package keeper + +import ( + "bytes" + "fmt" + "github.com/ethereum/go-ethereum/common" +) + +const PrecompileNonce uint64 = 1 + +var PrecompileCode = []byte{0x1} + +type StateDB interface { + GetNonce(addr common.Address) uint64 + GetCode(addr common.Address) []byte + SetNonce(common.Address, uint64) + SetCode(common.Address, []byte) +} + +type InitializationConfig struct { + ValidateInitialized []common.Address + ValidateUninitialized []common.Address + Initialize []common.Address + Uninitialize []common.Address +} + +func SyncEnabledPrecompiles(stateDB StateDB, old []common.Address, new []common.Address) error { + cfg := DetermineInitializationConfig(old, new) + return ApplyInitializationConfig(stateDB, cfg) +} + +func DetermineInitializationConfig(old []common.Address, new []common.Address) *InitializationConfig { + return &InitializationConfig{ + ValidateInitialized: old, + ValidateUninitialized: SetDifference(new, old), + Initialize: SetDifference(new, old), + Uninitialize: SetDifference(old, new), + } +} + +func ApplyInitializationConfig(stateDB StateDB, cfg *InitializationConfig) error { + if err := ValidatePrecompilesInitialized(stateDB, cfg.ValidateInitialized); err != nil { + return err + } + if err := ValidatePrecompilesUninitialized(stateDB, cfg.ValidateUninitialized); err != nil { + return err + } + + InitializePrecompiles(stateDB, cfg.Initialize) + UninitializePrecompiles(stateDB, cfg.Uninitialize) + + return nil +} + +func SetDifference(a []common.Address, b []common.Address) []common.Address { + bMap := make(map[common.Address]struct{}, len(b)) + for _, elem := range b { + bMap[elem] = struct{}{} + } + + diff := make([]common.Address, 0) + for _, elem := range a { + if _, ok := bMap[elem]; !ok { + diff = append(diff, elem) + } + } + + return diff +} + +func ValidatePrecompilesInitialized(stateDB StateDB, addrs []common.Address) error { + for _, addr := range addrs { + nonce := stateDB.GetNonce(addr) + code := stateDB.GetCode(addr) + + ok := nonce == PrecompileNonce && bytes.Equal(code, PrecompileCode) + if !ok { + return fmt.Errorf("precompile %v is not initialized, nonce: %v, code: %v", addr, nonce, code) + } + } + + return nil +} + +func ValidatePrecompilesUninitialized(stateDB StateDB, addrs []common.Address) error { + for _, addr := range addrs { + nonce := stateDB.GetNonce(addr) + code := stateDB.GetCode(addr) + + ok := nonce == 0 && bytes.Equal(code, nil) + if !ok { + return fmt.Errorf("precompile %v is initialized, nonce: %v, code: %v", addr, nonce, code) + } + } + + return nil +} + +func InitializePrecompiles(stateDB StateDB, addrs []common.Address) { + for _, addr := range addrs { + // Set the nonce of the precompile's address (as is done when a contract is created) to ensure + // that it is marked as non-empty and will not be cleaned up when the statedb is finalized. + stateDB.SetNonce(addr, PrecompileNonce) + // Set the code of the precompile's address to a non-zero length byte slice to ensure that the precompile + // can be called from within Solidity contracts. Solidity adds a check before invoking a contract to ensure + // that it does not attempt to invoke a non-existent contract. + stateDB.SetCode(addr, PrecompileCode) + } +} + +func UninitializePrecompiles(stateDB StateDB, addrs []common.Address) { + for _, addr := range addrs { + stateDB.SetNonce(addr, 0) + stateDB.SetCode(addr, nil) + } +} diff --git a/x/evm/keeper/precompile_test.go b/x/evm/keeper/precompile_test.go new file mode 100644 index 0000000000..cb6b92f465 --- /dev/null +++ b/x/evm/keeper/precompile_test.go @@ -0,0 +1,277 @@ +package keeper_test + +import ( + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "testing" + + "github.com/evmos/ethermint/x/evm/keeper" +) + +var ( + addr1 = common.HexToAddress("0x1000000000000000000000000000000000000000") + addr2 = common.HexToAddress("0x2000000000000000000000000000000000000000") + addr3 = common.HexToAddress("0x3000000000000000000000000000000000000000") +) + +type account struct { + nonce uint64 + code []byte +} + +func newAccount() *account { + return &account{} +} + +type stateDB struct { + accounts map[common.Address]*account +} + +func newStateDB() *stateDB { + return &stateDB{ + accounts: make(map[common.Address]*account, 0), + } +} + +func (s *stateDB) GetNonce(addr common.Address) uint64 { + account := s.getOrNewAccount(addr) + return account.nonce +} + +func (s *stateDB) GetCode(addr common.Address) []byte { + account := s.getOrNewAccount(addr) + return account.code +} + +func (s *stateDB) SetNonce(addr common.Address, nonce uint64) { + account := s.getOrNewAccount(addr) + account.nonce = nonce +} + +func (s *stateDB) SetCode(addr common.Address, code []byte) { + account := s.getOrNewAccount(addr) + account.code = code +} + +func (s *stateDB) getOrNewAccount(addr common.Address) *account { + _, ok := s.accounts[addr] + if !ok { + s.accounts[addr] = newAccount() + } + + return s.accounts[addr] +} + +func TestSyncEnabledPrecompiles(t *testing.T) { + stateDB := newStateDB() + + testCases := []struct { + name string + old []common.Address + new []common.Address + uninitialized []common.Address + }{ + { + name: "enable addr1 and addr2", + old: []common.Address{}, + new: []common.Address{addr1, addr2}, + uninitialized: []common.Address{addr3}, + }, + { + name: "enable addr3, and disable the rest", + old: []common.Address{addr1, addr2}, + new: []common.Address{addr3}, + uninitialized: []common.Address{addr1, addr2}, + }, + { + name: "no changes", + old: []common.Address{addr3}, + new: []common.Address{addr3}, + uninitialized: []common.Address{addr1, addr2}, + }, + { + name: "enable all precompiles", + old: []common.Address{addr3}, + new: []common.Address{addr1, addr2, addr3}, + uninitialized: []common.Address{}, + }, + { + name: "disable all precompiles", + old: []common.Address{addr1, addr2, addr3}, + new: []common.Address{}, + uninitialized: []common.Address{addr1, addr2, addr3}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := keeper.SyncEnabledPrecompiles(stateDB, tc.old, tc.new) + require.NoError(t, err) + + err = keeper.ValidatePrecompilesInitialized(stateDB, tc.new) + require.NoError(t, err) + + err = keeper.ValidatePrecompilesUninitialized(stateDB, tc.uninitialized) + require.NoError(t, err) + }) + } +} + +func TestDetermineInitializationConfig(t *testing.T) { + testCases := []struct { + name string + old []common.Address + new []common.Address + cfg *keeper.InitializationConfig + }{ + { + name: "enable addr1 and addr2", + old: []common.Address{}, + new: []common.Address{addr1, addr2}, + cfg: &keeper.InitializationConfig{ + ValidateInitialized: []common.Address{}, + ValidateUninitialized: []common.Address{addr1, addr2}, + Initialize: []common.Address{addr1, addr2}, + Uninitialize: []common.Address{}, + }, + }, + { + name: "enable addr3, and disable the rest", + old: []common.Address{addr1, addr2}, + new: []common.Address{addr3}, + cfg: &keeper.InitializationConfig{ + ValidateInitialized: []common.Address{addr1, addr2}, + ValidateUninitialized: []common.Address{addr3}, + Initialize: []common.Address{addr3}, + Uninitialize: []common.Address{addr1, addr2}, + }, + }, + { + name: "no changes", + old: []common.Address{addr3}, + new: []common.Address{addr3}, + cfg: &keeper.InitializationConfig{ + ValidateInitialized: []common.Address{addr3}, + ValidateUninitialized: []common.Address{}, + Initialize: []common.Address{}, + Uninitialize: []common.Address{}, + }, + }, + { + name: "enable all precompiles", + old: []common.Address{addr3}, + new: []common.Address{addr1, addr2, addr3}, + cfg: &keeper.InitializationConfig{ + ValidateInitialized: []common.Address{addr3}, + ValidateUninitialized: []common.Address{addr1, addr2}, + Initialize: []common.Address{addr1, addr2}, + Uninitialize: []common.Address{}, + }, + }, + { + name: "disable all precompiles", + old: []common.Address{addr1, addr2, addr3}, + new: []common.Address{}, + cfg: &keeper.InitializationConfig{ + ValidateInitialized: []common.Address{addr1, addr2, addr3}, + ValidateUninitialized: []common.Address{}, + Initialize: []common.Address{}, + Uninitialize: []common.Address{addr1, addr2, addr3}, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + cfg := keeper.DetermineInitializationConfig(tc.old, tc.new) + require.Equal(t, tc.cfg, cfg) + fmt.Printf("%+v\n", cfg) + }) + } +} + +func TestSetDifference(t *testing.T) { + testCases := []struct { + name string + a []common.Address + b []common.Address + diff []common.Address + }{ + { + name: "A and B intersect, but diff isn't empty", + a: []common.Address{addr1, addr2}, + b: []common.Address{addr1, addr3}, + diff: []common.Address{addr2}, + }, + { + name: "A and B don't intersect, diff isn't empty", + a: []common.Address{addr1}, + b: []common.Address{addr2, addr3}, + diff: []common.Address{addr1}, + }, + { + name: "A is a subset of B, diff is empty", + a: []common.Address{addr1, addr2}, + b: []common.Address{addr1, addr2, addr3}, + diff: []common.Address{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + diff := keeper.SetDifference(tc.a, tc.b) + require.Equal(t, tc.diff, diff) + }) + } +} + +func TestSyncEnabledPrecompilesHelpers(t *testing.T) { + t.Run("initialize precompiles", func(t *testing.T) { + stateDB := newStateDB() + + require.Equal(t, uint64(0), stateDB.GetNonce(addr1)) + require.Equal(t, []byte(nil), stateDB.GetCode(addr1)) + + keeper.InitializePrecompiles(stateDB, []common.Address{addr1}) + + require.Equal(t, keeper.PrecompileNonce, stateDB.GetNonce(addr1)) + require.Equal(t, keeper.PrecompileCode, stateDB.GetCode(addr1)) + }) + + t.Run("uninitialize precompiles", func(t *testing.T) { + stateDB := newStateDB() + + keeper.InitializePrecompiles(stateDB, []common.Address{addr1}) + require.Equal(t, keeper.PrecompileNonce, stateDB.GetNonce(addr1)) + require.Equal(t, keeper.PrecompileCode, stateDB.GetCode(addr1)) + + keeper.UninitializePrecompiles(stateDB, []common.Address{addr1}) + require.Equal(t, uint64(0), stateDB.GetNonce(addr1)) + require.Equal(t, []byte(nil), stateDB.GetCode(addr1)) + }) + + t.Run("validate precompiles initialized", func(t *testing.T) { + stateDB := newStateDB() + + err := keeper.ValidatePrecompilesInitialized(stateDB, []common.Address{addr1}) + require.ErrorContains(t, err, "is not initialized") + + keeper.InitializePrecompiles(stateDB, []common.Address{addr1}) + + err = keeper.ValidatePrecompilesInitialized(stateDB, []common.Address{addr1}) + require.NoError(t, err) + }) + + t.Run("validate precompiles uninitialized", func(t *testing.T) { + stateDB := newStateDB() + + err := keeper.ValidatePrecompilesUninitialized(stateDB, []common.Address{addr1}) + require.NoError(t, err) + + keeper.InitializePrecompiles(stateDB, []common.Address{addr1}) + + err = keeper.ValidatePrecompilesUninitialized(stateDB, []common.Address{addr1}) + require.ErrorContains(t, err, "is initialized") + }) +}