package statedb import ( "fmt" "math/big" "sort" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "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" ) // 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 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 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) {} // 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.GetAccount(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 sdkerrors.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.SetAccount(s.ctx, obj.Address(), obj.account); err != nil { return sdkerrors.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 }