diff --git a/x/evm/keeper/statedb.go b/x/evm/keeper/statedb.go index 8255056cdd..890e53e4ce 100644 --- a/x/evm/keeper/statedb.go +++ b/x/evm/keeper/statedb.go @@ -29,7 +29,6 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/ethereum/go-ethereum/common" ethermint "github.com/evmos/ethermint/types" - "github.com/evmos/ethermint/x/evm/legacystatedb" "github.com/evmos/ethermint/x/evm/statedb" "github.com/evmos/ethermint/x/evm/types" ) @@ -51,23 +50,6 @@ func (k *Keeper) GetAccount(ctx sdk.Context, addr common.Address) *statedb.Accou return acct } -func (k *Keeper) GetAccountLegacy(ctx sdk.Context, addr common.Address) *legacystatedb.Account { - acct := k.GetAccountWithoutBalance(ctx, addr) - if acct == nil { - return nil - } - - bal := k.GetBalance(ctx, addr) - - legacyAcct := &legacystatedb.Account{ - Nonce: acct.Nonce, - Balance: bal, - CodeHash: acct.CodeHash, - } - - return legacyAcct -} - // GetState loads contract state from database, implements `statedb.Keeper` interface. func (k *Keeper) GetState(ctx sdk.Context, addr common.Address, key common.Hash) common.Hash { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AddressStoragePrefix(addr)) @@ -138,17 +120,6 @@ func (k *Keeper) SetBalance(ctx sdk.Context, addr common.Address, amount *big.In return nil } -func (k *Keeper) SetAccountLegacy(ctx sdk.Context, addr common.Address, account legacystatedb.Account) error { - if err := k.SetAccount(ctx, addr, statedb.Account{ - Nonce: account.Nonce, - CodeHash: account.CodeHash, - }); err != nil { - return err - } - - return k.SetBalance(ctx, addr, account.Balance) -} - // SetAccount updates nonce/balance/codeHash together. func (k *Keeper) SetAccount(ctx sdk.Context, addr common.Address, account statedb.Account) error { // update account diff --git a/x/evm/legacystatedb/access_list.go b/x/evm/legacystatedb/access_list.go deleted file mode 100644 index 31e4458426..0000000000 --- a/x/evm/legacystatedb/access_list.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2020 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package legacystatedb - -import ( - "github.com/ethereum/go-ethereum/common" -) - -type accessList struct { - addresses map[common.Address]int - slots []map[common.Hash]struct{} -} - -// ContainsAddress returns true if the address is in the access list. -func (al *accessList) ContainsAddress(address common.Address) bool { - _, ok := al.addresses[address] - return ok -} - -// Contains checks if a slot within an account is present in the access list, returning -// separate flags for the presence of the account and the slot respectively. -func (al *accessList) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) { - idx, ok := al.addresses[address] - if !ok { - // no such address (and hence zero slots) - return false, false - } - if idx == -1 { - // address yes, but no slots - return true, false - } - _, slotPresent = al.slots[idx][slot] - return true, slotPresent -} - -// newAccessList creates a new accessList. -func newAccessList() *accessList { - return &accessList{ - addresses: make(map[common.Address]int), - } -} - -// AddAddress adds an address to the access list, and returns 'true' if the operation -// caused a change (addr was not previously in the list). -func (al *accessList) AddAddress(address common.Address) bool { - if _, present := al.addresses[address]; present { - return false - } - al.addresses[address] = -1 - return true -} - -// AddSlot adds the specified (addr, slot) combo to the access list. -// Return values are: -// - address added -// - slot added -// For any 'true' value returned, a corresponding journal entry must be made. -func (al *accessList) AddSlot(address common.Address, slot common.Hash) (addrChange bool, slotChange bool) { - idx, addrPresent := al.addresses[address] - if !addrPresent || idx == -1 { - // Address not present, or addr present but no slots there - al.addresses[address] = len(al.slots) - slotmap := map[common.Hash]struct{}{slot: {}} - al.slots = append(al.slots, slotmap) - return !addrPresent, true - } - // There is already an (address,slot) mapping - slotmap := al.slots[idx] - if _, ok := slotmap[slot]; !ok { - slotmap[slot] = struct{}{} - // Journal add slot change - return false, true - } - // No changes required - return false, false -} - -// DeleteSlot removes an (address, slot)-tuple from the access list. -// This operation needs to be performed in the same order as the addition happened. -// This method is meant to be used by the journal, which maintains ordering of -// operations. -func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) { - idx, addrOk := al.addresses[address] - if !addrOk { - panic("reverting slot change, address not present in list") - } - slotmap := al.slots[idx] - delete(slotmap, slot) - // If that was the last (first) slot, remove it - // Since additions and rollbacks are always performed in order, - // we can delete the item without worrying about screwing up later indices - if len(slotmap) == 0 { - al.slots = al.slots[:idx] - al.addresses[address] = -1 - } -} - -// DeleteAddress removes an address from the access list. This operation -// needs to be performed in the same order as the addition happened. -// This method is meant to be used by the journal, which maintains ordering of -// operations. -func (al *accessList) DeleteAddress(address common.Address) { - delete(al.addresses, address) -} diff --git a/x/evm/legacystatedb/config.go b/x/evm/legacystatedb/config.go deleted file mode 100644 index 1e3ca62af2..0000000000 --- a/x/evm/legacystatedb/config.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2021 Evmos Foundation -// This file is part of Evmos' Ethermint library. -// -// The Ethermint library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The Ethermint library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the Ethermint library. If not, see https://github.com/evmos/ethermint/blob/main/LICENSE -package legacystatedb - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/params" - "github.com/evmos/ethermint/x/evm/types" -) - -// TxConfig encapulates the readonly information of current tx for `StateDB`. -type TxConfig struct { - BlockHash common.Hash // hash of current block - TxHash common.Hash // hash of current tx - TxIndex uint // the index of current transaction - LogIndex uint // the index of next log within current block -} - -// NewTxConfig returns a TxConfig -func NewTxConfig(bhash, thash common.Hash, txIndex, logIndex uint) TxConfig { - return TxConfig{ - BlockHash: bhash, - TxHash: thash, - TxIndex: txIndex, - LogIndex: logIndex, - } -} - -// NewEmptyTxConfig construct an empty TxConfig, -// used in context where there's no transaction, e.g. `eth_call`/`eth_estimateGas`. -func NewEmptyTxConfig(bhash common.Hash) TxConfig { - return TxConfig{ - BlockHash: bhash, - TxHash: common.Hash{}, - TxIndex: 0, - LogIndex: 0, - } -} - -// EVMConfig encapsulates common parameters needed to create an EVM to execute a message -// It's mainly to reduce the number of method parameters -type EVMConfig struct { - Params types.Params - ChainConfig *params.ChainConfig - CoinBase common.Address - BaseFee *big.Int -} diff --git a/x/evm/legacystatedb/interfaces.go b/x/evm/legacystatedb/interfaces.go deleted file mode 100644 index c8b26dd806..0000000000 --- a/x/evm/legacystatedb/interfaces.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2021 Evmos Foundation -// This file is part of Evmos' Ethermint library. -// -// The Ethermint library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The Ethermint library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the Ethermint library. If not, see https://github.com/evmos/ethermint/blob/main/LICENSE -package legacystatedb - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm" -) - -// ExtStateDB defines an extension to the interface provided by the go-ethereum -// codebase to support additional state transition functionalities. In particular -// it supports appending a new entry to the state journal through -// AppendJournalEntry so that the state can be reverted after running -// stateful precompiled contracts. -type ExtStateDB interface { - vm.StateDB - AppendJournalEntry(JournalEntry) -} - -// Keeper provide underlying storage of StateDB -type Keeper interface { - // Read methods - GetAccountLegacy(ctx sdk.Context, addr common.Address) *Account - GetState(ctx sdk.Context, addr common.Address, key common.Hash) common.Hash - GetCode(ctx sdk.Context, codeHash common.Hash) []byte - // the callback returns false to break early - ForEachStorage(ctx sdk.Context, addr common.Address, cb func(key, value common.Hash) bool) - - // Write methods, only called by `StateDB.Commit()` - SetAccountLegacy(ctx sdk.Context, addr common.Address, account Account) error - SetState(ctx sdk.Context, addr common.Address, key common.Hash, value []byte) - SetCode(ctx sdk.Context, codeHash []byte, code []byte) - DeleteAccount(ctx sdk.Context, addr common.Address) error -} diff --git a/x/evm/legacystatedb/journal.go b/x/evm/legacystatedb/journal.go deleted file mode 100644 index f34b41625f..0000000000 --- a/x/evm/legacystatedb/journal.go +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package legacystatedb - -import ( - "bytes" - "math/big" - "sort" - - "github.com/ethereum/go-ethereum/common" -) - -// JournalEntry is a modification entry in the state change journal that can be -// Reverted on demand. -type JournalEntry interface { - // Revert undoes the changes introduced by this journal entry. - Revert(*StateDB) - - // Dirtied returns the Ethereum address modified by this journal entry. - Dirtied() *common.Address -} - -// journal contains the list of state modifications applied since the last state -// commit. These are tracked to be able to be reverted in the case of an execution -// exception or request for reversal. -type journal struct { - entries []JournalEntry // Current changes tracked by the journal - dirties map[common.Address]int // Dirty accounts and the number of changes -} - -// newJournal creates a new initialized journal. -func newJournal() *journal { - return &journal{ - dirties: make(map[common.Address]int), - } -} - -// sortedDirties sort the dirty addresses for deterministic iteration -func (j *journal) sortedDirties() []common.Address { - keys := make([]common.Address, len(j.dirties)) - i := 0 - for k := range j.dirties { - keys[i] = k - i++ - } - sort.Slice(keys, func(i, j int) bool { - return bytes.Compare(keys[i].Bytes(), keys[j].Bytes()) < 0 - }) - return keys -} - -// append inserts a new modification entry to the end of the change journal. -func (j *journal) append(entry JournalEntry) { - j.entries = append(j.entries, entry) - if addr := entry.Dirtied(); addr != nil { - j.dirties[*addr]++ - } -} - -// Revert undoes a batch of journalled modifications along with any Reverted -// dirty handling too. -func (j *journal) Revert(statedb *StateDB, snapshot int) { - for i := len(j.entries) - 1; i >= snapshot; i-- { - // Undo the changes made by the operation - j.entries[i].Revert(statedb) - - // Drop any dirty tracking induced by the change - if addr := j.entries[i].Dirtied(); addr != nil { - if j.dirties[*addr]--; j.dirties[*addr] == 0 { - delete(j.dirties, *addr) - } - } - } - j.entries = j.entries[:snapshot] -} - -// length returns the current number of entries in the journal. -func (j *journal) length() int { - return len(j.entries) -} - -type ( - // Changes to the account trie. - createObjectChange struct { - account *common.Address - } - resetObjectChange struct { - prev *stateObject - } - suicideChange struct { - account *common.Address - prev bool // whether account had already suicided - prevbalance *big.Int - } - - // Changes to individual accounts. - balanceChange struct { - account *common.Address - prev *big.Int - } - nonceChange struct { - account *common.Address - prev uint64 - } - storageChange struct { - account *common.Address - key, prevalue common.Hash - } - codeChange struct { - account *common.Address - prevcode, prevhash []byte - } - - // Changes to other state values. - refundChange struct { - prev uint64 - } - addLogChange struct{} - - // Changes to the access list - accessListAddAccountChange struct { - address *common.Address - } - accessListAddSlotChange struct { - address *common.Address - slot *common.Hash - } -) - -func (ch createObjectChange) Revert(s *StateDB) { - delete(s.stateObjects, *ch.account) -} - -func (ch createObjectChange) Dirtied() *common.Address { - return ch.account -} - -func (ch resetObjectChange) Revert(s *StateDB) { - s.setStateObject(ch.prev) -} - -func (ch resetObjectChange) Dirtied() *common.Address { - return nil -} - -func (ch suicideChange) Revert(s *StateDB) { - obj := s.getStateObject(*ch.account) - if obj != nil { - obj.suicided = ch.prev - obj.setBalance(ch.prevbalance) - } -} - -func (ch suicideChange) Dirtied() *common.Address { - return ch.account -} - -func (ch balanceChange) Revert(s *StateDB) { - s.getStateObject(*ch.account).setBalance(ch.prev) -} - -func (ch balanceChange) Dirtied() *common.Address { - return ch.account -} - -func (ch nonceChange) Revert(s *StateDB) { - s.getStateObject(*ch.account).setNonce(ch.prev) -} - -func (ch nonceChange) Dirtied() *common.Address { - return ch.account -} - -func (ch codeChange) Revert(s *StateDB) { - s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode) -} - -func (ch codeChange) Dirtied() *common.Address { - return ch.account -} - -func (ch storageChange) Revert(s *StateDB) { - s.getStateObject(*ch.account).setState(ch.key, ch.prevalue) -} - -func (ch storageChange) Dirtied() *common.Address { - return ch.account -} - -func (ch refundChange) Revert(s *StateDB) { - s.refund = ch.prev -} - -func (ch refundChange) Dirtied() *common.Address { - return nil -} - -func (ch addLogChange) Revert(s *StateDB) { - s.logs = s.logs[:len(s.logs)-1] -} - -func (ch addLogChange) Dirtied() *common.Address { - return nil -} - -func (ch accessListAddAccountChange) Revert(s *StateDB) { - /* - One important invariant here, is that whenever a (addr, slot) is added, if the - addr is not already present, the add causes two journal entries: - - one for the address, - - one for the (address,slot) - Therefore, when unrolling the change, we can always blindly delete the - (addr) at this point, since no storage adds can remain when come upon - a single (addr) change. - */ - s.accessList.DeleteAddress(*ch.address) -} - -func (ch accessListAddAccountChange) Dirtied() *common.Address { - return nil -} - -func (ch accessListAddSlotChange) Revert(s *StateDB) { - s.accessList.DeleteSlot(*ch.address, *ch.slot) -} - -func (ch accessListAddSlotChange) Dirtied() *common.Address { - return nil -} diff --git a/x/evm/legacystatedb/state_object.go b/x/evm/legacystatedb/state_object.go deleted file mode 100644 index bdd138d592..0000000000 --- a/x/evm/legacystatedb/state_object.go +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright 2021 Evmos Foundation -// This file is part of Evmos' Ethermint library. -// -// The Ethermint library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The Ethermint library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the Ethermint library. If not, see https://github.com/evmos/ethermint/blob/main/LICENSE -package legacystatedb - -import ( - "bytes" - "math/big" - "sort" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" -) - -var emptyCodeHash = crypto.Keccak256(nil) - -// Account is the Ethereum consensus representation of accounts. -// These objects are stored in the storage of auth module. -type Account = struct { - Balance *big.Int - Nonce uint64 - CodeHash []byte -} - -// NewEmptyAccount returns an empty account. -func NewEmptyAccount() *Account { - return &Account{ - Balance: new(big.Int), - CodeHash: emptyCodeHash, - } -} - -// Storage represents in-memory cache/buffer of contract storage. -type Storage map[common.Hash]common.Hash - -// SortedKeys sort the keys for deterministic iteration -func (s Storage) SortedKeys() []common.Hash { - keys := make([]common.Hash, len(s)) - i := 0 - for k := range s { - keys[i] = k - i++ - } - sort.Slice(keys, func(i, j int) bool { - return bytes.Compare(keys[i].Bytes(), keys[j].Bytes()) < 0 - }) - return keys -} - -// stateObject is the state of an acount -type stateObject struct { - db *StateDB - - account Account - code []byte - - // state storage - originStorage Storage - dirtyStorage Storage - - address common.Address - - // flags - dirtyCode bool - suicided bool -} - -// newObject creates a state object. -func newObject(db *StateDB, address common.Address, account Account) *stateObject { - if account.Balance == nil { - account.Balance = new(big.Int) - } - if account.CodeHash == nil { - account.CodeHash = emptyCodeHash - } - return &stateObject{ - db: db, - address: address, - account: account, - originStorage: make(Storage), - dirtyStorage: make(Storage), - } -} - -// empty returns whether the account is considered empty. -func (s *stateObject) empty() bool { - return s.account.Nonce == 0 && s.account.Balance.Sign() == 0 && bytes.Equal(s.account.CodeHash, emptyCodeHash) -} - -func (s *stateObject) markSuicided() { - s.suicided = true -} - -// AddBalance adds amount to s's balance. -// It is used to add funds to the destination account of a transfer. -func (s *stateObject) AddBalance(amount *big.Int) { - if amount.Sign() == 0 { - return - } - s.SetBalance(new(big.Int).Add(s.Balance(), amount)) -} - -// SubBalance removes amount from s's balance. -// It is used to remove funds from the origin account of a transfer. -func (s *stateObject) SubBalance(amount *big.Int) { - if amount.Sign() == 0 { - return - } - s.SetBalance(new(big.Int).Sub(s.Balance(), amount)) -} - -// SetBalance update account balance. -func (s *stateObject) SetBalance(amount *big.Int) { - s.db.journal.append(balanceChange{ - account: &s.address, - prev: new(big.Int).Set(s.account.Balance), - }) - s.setBalance(amount) -} - -func (s *stateObject) setBalance(amount *big.Int) { - s.account.Balance = amount -} - -// -// Attribute accessors -// - -// Returns the address of the contract/account -func (s *stateObject) Address() common.Address { - return s.address -} - -// Code returns the contract code associated with this object, if any. -func (s *stateObject) Code() []byte { - if s.code != nil { - return s.code - } - if bytes.Equal(s.CodeHash(), emptyCodeHash) { - return nil - } - code := s.db.keeper.GetCode(s.db.ctx, common.BytesToHash(s.CodeHash())) - s.code = code - return code -} - -// CodeSize returns the size of the contract code associated with this object, -// or zero if none. -func (s *stateObject) CodeSize() int { - return len(s.Code()) -} - -// SetCode set contract code to account -func (s *stateObject) SetCode(codeHash common.Hash, code []byte) { - prevcode := s.Code() - s.db.journal.append(codeChange{ - account: &s.address, - prevhash: s.CodeHash(), - prevcode: prevcode, - }) - s.setCode(codeHash, code) -} - -func (s *stateObject) setCode(codeHash common.Hash, code []byte) { - s.code = code - s.account.CodeHash = codeHash[:] - s.dirtyCode = true -} - -// SetCode set nonce to account -func (s *stateObject) SetNonce(nonce uint64) { - s.db.journal.append(nonceChange{ - account: &s.address, - prev: s.account.Nonce, - }) - s.setNonce(nonce) -} - -func (s *stateObject) setNonce(nonce uint64) { - s.account.Nonce = nonce -} - -// CodeHash returns the code hash of account -func (s *stateObject) CodeHash() []byte { - return s.account.CodeHash -} - -// Balance returns the balance of account -func (s *stateObject) Balance() *big.Int { - return s.account.Balance -} - -// Nonce returns the nonce of account -func (s *stateObject) Nonce() uint64 { - return s.account.Nonce -} - -// GetCommittedState query the committed state -func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { - if value, cached := s.originStorage[key]; cached { - return value - } - // If no live objects are available, load it from keeper - value := s.db.keeper.GetState(s.db.ctx, s.Address(), key) - s.originStorage[key] = value - return value -} - -// GetState query the current state (including dirty state) -func (s *stateObject) GetState(key common.Hash) common.Hash { - if value, dirty := s.dirtyStorage[key]; dirty { - return value - } - return s.GetCommittedState(key) -} - -// SetState sets the contract state -func (s *stateObject) SetState(key common.Hash, value common.Hash) { - // If the new value is the same as old, don't set - prev := s.GetState(key) - if prev == value { - return - } - // New value is different, update and journal the change - s.db.journal.append(storageChange{ - account: &s.address, - key: key, - prevalue: prev, - }) - s.setState(key, value) -} - -func (s *stateObject) setState(key, value common.Hash) { - s.dirtyStorage[key] = value -} diff --git a/x/evm/legacystatedb/statedb.go b/x/evm/legacystatedb/statedb.go deleted file mode 100644 index ac0e093b60..0000000000 --- a/x/evm/legacystatedb/statedb.go +++ /dev/null @@ -1,479 +0,0 @@ -// Copyright 2021 Evmos Foundation -// This file is part of Evmos' Ethermint library. -// -// The Ethermint library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The Ethermint library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the Ethermint library. If not, see https://github.com/evmos/ethermint/blob/main/LICENSE -package legacystatedb - -import ( - "fmt" - "math/big" - "sort" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" - ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/evmos/ethermint/x/evm/statedb" -) - -// revision is the identifier of a version of state. -// it consists of an auto-increment id and a journal index. -// it's safer to use than using journal index alone. -type revision struct { - id int - journalIndex int -} - -var _ vm.StateDB = &StateDB{} - -// StateDB structs within the ethereum protocol are used to store anything -// within the merkle trie. StateDBs take care of caching and storing -// nested states. It's the general query interface to retrieve: -// * Contracts -// * Accounts -type StateDB struct { - keeper Keeper - ctx sdk.Context - - // Journal of state modifications. This is the backbone of - // Snapshot and RevertToSnapshot. - journal *journal - validRevisions []revision - nextRevisionID int - - stateObjects map[common.Address]*stateObject - - txConfig statedb.TxConfig - - // The refund counter, also used by state transitioning. - refund uint64 - - // Per-transaction logs - logs []*ethtypes.Log - - // Per-transaction access list - accessList *accessList -} - -// New creates a new state from a given trie. -func New(ctx sdk.Context, keeper Keeper, txConfig statedb.TxConfig) *StateDB { - return &StateDB{ - keeper: keeper, - ctx: ctx, - stateObjects: make(map[common.Address]*stateObject), - journal: newJournal(), - accessList: newAccessList(), - - txConfig: txConfig, - } -} - -// Keeper returns the underlying `Keeper` -func (s *StateDB) Keeper() Keeper { - return s.keeper -} - -// AddLog adds a log, called by evm. -func (s *StateDB) AddLog(log *ethtypes.Log) { - s.journal.append(addLogChange{}) - - log.TxHash = s.txConfig.TxHash - log.BlockHash = s.txConfig.BlockHash - log.TxIndex = s.txConfig.TxIndex - log.Index = s.txConfig.LogIndex + uint(len(s.logs)) - s.logs = append(s.logs, log) -} - -// Logs returns the logs of current transaction. -func (s *StateDB) Logs() []*ethtypes.Log { - return s.logs -} - -// AddRefund adds gas to the refund counter -func (s *StateDB) AddRefund(gas uint64) { - s.journal.append(refundChange{prev: s.refund}) - s.refund += gas -} - -// SubRefund removes gas from the refund counter. -// This method will panic if the refund counter goes below zero -func (s *StateDB) SubRefund(gas uint64) { - s.journal.append(refundChange{prev: s.refund}) - if gas > s.refund { - panic(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, s.refund)) - } - s.refund -= gas -} - -// Exist reports whether the given account address exists in the state. -// Notably this also returns true for suicided accounts. -func (s *StateDB) Exist(addr common.Address) bool { - return s.getStateObject(addr) != nil -} - -// Empty returns whether the state object is either non-existent -// or empty according to the EIP161 specification (balance = nonce = code = 0) -func (s *StateDB) Empty(addr common.Address) bool { - so := s.getStateObject(addr) - return so == nil || so.empty() -} - -// GetBalance retrieves the balance from the given address or 0 if object not found -func (s *StateDB) GetBalance(addr common.Address) *big.Int { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.Balance() - } - return common.Big0 -} - -// GetNonce returns the nonce of account, 0 if not exists. -func (s *StateDB) GetNonce(addr common.Address) uint64 { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.Nonce() - } - - return 0 -} - -// GetCode returns the code of account, nil if not exists. -func (s *StateDB) GetCode(addr common.Address) []byte { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.Code() - } - return nil -} - -// GetCodeSize returns the code size of account. -func (s *StateDB) GetCodeSize(addr common.Address) int { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.CodeSize() - } - return 0 -} - -// GetCodeHash returns the code hash of account. -func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { - stateObject := s.getStateObject(addr) - if stateObject == nil { - return common.Hash{} - } - return common.BytesToHash(stateObject.CodeHash()) -} - -// GetState retrieves a value from the given account's storage trie. -func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.GetState(hash) - } - return common.Hash{} -} - -// GetCommittedState retrieves a value from the given account's committed storage trie. -func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.GetCommittedState(hash) - } - return common.Hash{} -} - -// GetRefund returns the current value of the refund counter. -func (s *StateDB) GetRefund() uint64 { - return s.refund -} - -// HasSuicided returns if the contract is suicided in current transaction. -func (s *StateDB) HasSuicided(addr common.Address) bool { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.suicided - } - return false -} - -// AddPreimage records a SHA3 preimage seen by the VM. -// AddPreimage performs a no-op since the EnablePreimageRecording flag is disabled -// on the vm.Config during state transitions. No store trie preimages are written -// to the database. -func (s *StateDB) AddPreimage(hash common.Hash, preimage []byte) {} //nolint: revive - -// getStateObject retrieves a state object given by the address, returning nil if -// the object is not found. -func (s *StateDB) getStateObject(addr common.Address) *stateObject { - // Prefer live objects if any is available - if obj := s.stateObjects[addr]; obj != nil { - return obj - } - // If no live objects are available, load it from keeper - account := s.keeper.GetAccountLegacy(s.ctx, addr) - if account == nil { - return nil - } - // Insert into the live set - obj := newObject(s, addr, *account) - s.setStateObject(obj) - return obj -} - -// getOrNewStateObject retrieves a state object or create a new state object if nil. -func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject { - stateObject := s.getStateObject(addr) - if stateObject == nil { - stateObject, _ = s.createObject(addr) - } - return stateObject -} - -// createObject creates a new state object. If there is an existing account with -// the given address, it is overwritten and returned as the second return value. -func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) { - prev = s.getStateObject(addr) - - newobj = newObject(s, addr, Account{}) - if prev == nil { - s.journal.append(createObjectChange{account: &addr}) - } else { - s.journal.append(resetObjectChange{prev: prev}) - } - s.setStateObject(newobj) - if prev != nil { - return newobj, prev - } - return newobj, nil -} - -// CreateAccount explicitly creates a state object. If a state object with the address -// already exists the balance is carried over to the new account. -// -// CreateAccount is called during the EVM CREATE operation. The situation might arise that -// a contract does the following: -// -// 1. sends funds to sha(account ++ (nonce + 1)) -// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1) -// -// Carrying over the balance ensures that Ether doesn't disappear. -func (s *StateDB) CreateAccount(addr common.Address) { - newObj, prev := s.createObject(addr) - if prev != nil { - newObj.setBalance(prev.account.Balance) - } -} - -// ForEachStorage iterate the contract storage, the iteration order is not defined. -func (s *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error { - so := s.getStateObject(addr) - if so == nil { - return nil - } - s.keeper.ForEachStorage(s.ctx, addr, func(key, value common.Hash) bool { - if value, dirty := so.dirtyStorage[key]; dirty { - return cb(key, value) - } - if len(value) > 0 { - return cb(key, value) - } - return true - }) - return nil -} - -func (s *StateDB) setStateObject(object *stateObject) { - s.stateObjects[object.Address()] = object -} - -/* - * SETTERS - */ - -// AddBalance adds amount to the account associated with addr. -func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) { - stateObject := s.getOrNewStateObject(addr) - if stateObject != nil { - stateObject.AddBalance(amount) - } -} - -// SubBalance subtracts amount from the account associated with addr. -func (s *StateDB) SubBalance(addr common.Address, amount *big.Int) { - stateObject := s.getOrNewStateObject(addr) - if stateObject != nil { - stateObject.SubBalance(amount) - } -} - -// SetNonce sets the nonce of account. -func (s *StateDB) SetNonce(addr common.Address, nonce uint64) { - stateObject := s.getOrNewStateObject(addr) - if stateObject != nil { - stateObject.SetNonce(nonce) - } -} - -// SetCode sets the code of account. -func (s *StateDB) SetCode(addr common.Address, code []byte) { - stateObject := s.getOrNewStateObject(addr) - if stateObject != nil { - stateObject.SetCode(crypto.Keccak256Hash(code), code) - } -} - -// SetState sets the contract state. -func (s *StateDB) SetState(addr common.Address, key, value common.Hash) { - stateObject := s.getOrNewStateObject(addr) - if stateObject != nil { - stateObject.SetState(key, value) - } -} - -// Suicide marks the given account as suicided. -// This clears the account balance. -// -// The account's state object is still available until the state is committed, -// getStateObject will return a non-nil account after Suicide. -func (s *StateDB) Suicide(addr common.Address) bool { - stateObject := s.getStateObject(addr) - if stateObject == nil { - return false - } - s.journal.append(suicideChange{ - account: &addr, - prev: stateObject.suicided, - prevbalance: new(big.Int).Set(stateObject.Balance()), - }) - stateObject.markSuicided() - stateObject.account.Balance = new(big.Int) - - return true -} - -// PrepareAccessList handles the preparatory steps for executing a state transition with -// regards to both EIP-2929 and EIP-2930: -// -// - Add sender to access list (2929) -// - Add destination to access list (2929) -// - Add precompiles to access list (2929) -// - Add the contents of the optional tx access list (2930) -// -// This method should only be called if Yolov3/Berlin/2929+2930 is applicable at the current number. -func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address, precompiles []common.Address, list ethtypes.AccessList) { - s.AddAddressToAccessList(sender) - if dst != nil { - s.AddAddressToAccessList(*dst) - // If it's a create-tx, the destination will be added inside evm.create - } - for _, addr := range precompiles { - s.AddAddressToAccessList(addr) - } - for _, el := range list { - s.AddAddressToAccessList(el.Address) - for _, key := range el.StorageKeys { - s.AddSlotToAccessList(el.Address, key) - } - } -} - -// AddAddressToAccessList adds the given address to the access list -func (s *StateDB) AddAddressToAccessList(addr common.Address) { - if s.accessList.AddAddress(addr) { - s.journal.append(accessListAddAccountChange{&addr}) - } -} - -// AddSlotToAccessList adds the given (address, slot)-tuple to the access list -func (s *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) { - addrMod, slotMod := s.accessList.AddSlot(addr, slot) - if addrMod { - // In practice, this should not happen, since there is no way to enter the - // scope of 'address' without having the 'address' become already added - // to the access list (via call-variant, create, etc). - // Better safe than sorry, though - s.journal.append(accessListAddAccountChange{&addr}) - } - if slotMod { - s.journal.append(accessListAddSlotChange{ - address: &addr, - slot: &slot, - }) - } -} - -// AddressInAccessList returns true if the given address is in the access list. -func (s *StateDB) AddressInAccessList(addr common.Address) bool { - return s.accessList.ContainsAddress(addr) -} - -// SlotInAccessList returns true if the given (address, slot)-tuple is in the access list. -func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) { - return s.accessList.Contains(addr, slot) -} - -// Snapshot returns an identifier for the current revision of the state. -func (s *StateDB) Snapshot() int { - id := s.nextRevisionID - s.nextRevisionID++ - s.validRevisions = append(s.validRevisions, revision{id, s.journal.length()}) - return id -} - -// RevertToSnapshot reverts all state changes made since the given revision. -func (s *StateDB) RevertToSnapshot(revid int) { - // Find the snapshot in the stack of valid snapshots. - idx := sort.Search(len(s.validRevisions), func(i int) bool { - return s.validRevisions[i].id >= revid - }) - if idx == len(s.validRevisions) || s.validRevisions[idx].id != revid { - panic(fmt.Errorf("revision id %v cannot be reverted", revid)) - } - snapshot := s.validRevisions[idx].journalIndex - - // Replay the journal to undo changes and remove invalidated snapshots - s.journal.Revert(s, snapshot) - s.validRevisions = s.validRevisions[:idx] -} - -// Commit writes the dirty states to keeper -// the StateDB object should be discarded after committed. -func (s *StateDB) Commit() error { - for _, addr := range s.journal.sortedDirties() { - obj := s.stateObjects[addr] - if obj.suicided { - if err := s.keeper.DeleteAccount(s.ctx, obj.Address()); err != nil { - return errorsmod.Wrap(err, "failed to delete account") - } - } else { - if obj.code != nil && obj.dirtyCode { - s.keeper.SetCode(s.ctx, obj.CodeHash(), obj.code) - } - if err := s.keeper.SetAccountLegacy(s.ctx, obj.Address(), obj.account); err != nil { - return errorsmod.Wrap(err, "failed to set account") - } - for _, key := range obj.dirtyStorage.SortedKeys() { - value := obj.dirtyStorage[key] - // Skip noop changes, persist actual changes - if value == obj.originStorage[key] { - continue - } - s.keeper.SetState(s.ctx, obj.Address(), key, value.Bytes()) - } - } - } - return nil -}