diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index 318aa222a3..7f691ba532 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -203,10 +203,10 @@ func runCmd(ctx *cli.Context) error { var leftOverGas uint64 if ctx.GlobalBool(CreateFlag.Name) { input := append(code, common.Hex2Bytes(ctx.GlobalString(InputFlag.Name))...) - ret, _, leftOverGas, err = runtime.Create(input, &runtimeConfig) + ret, _, leftOverGas, err = runtime.Create(input, &runtimeConfig) // The default code version is 0. } else { if len(code) > 0 { - statedb.SetCode(receiver, code) + statedb.SetCode(receiver, code, 0) } ret, leftOverGas, err = runtime.Call(receiver, common.Hex2Bytes(ctx.GlobalString(InputFlag.Name)), &runtimeConfig) } diff --git a/core/evm.go b/core/evm.go index b654bbd479..69a7e71bf3 100644 --- a/core/evm.go +++ b/core/evm.go @@ -45,16 +45,17 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author beneficiary = *author } return vm.Context{ - CanTransfer: CanTransfer, - Transfer: Transfer, - GetHash: GetHashFn(header, chain), - Origin: msg.From(), - Coinbase: beneficiary, - BlockNumber: new(big.Int).Set(header.Number), - Time: new(big.Int).SetUint64(header.Time), - Difficulty: new(big.Int).Set(header.Difficulty), - GasLimit: header.GasLimit, - GasPrice: new(big.Int).Set(msg.GasPrice()), + CanTransfer: CanTransfer, + Transfer: Transfer, + GetHash: GetHashFn(header, chain), + ValidateCode: ValidateCode, + Origin: msg.From(), + Coinbase: beneficiary, + BlockNumber: new(big.Int).Set(header.Number), + Time: new(big.Int).SetUint64(header.Time), + Difficulty: new(big.Int).Set(header.Difficulty), + GasLimit: header.GasLimit, + GasPrice: new(big.Int).Set(msg.GasPrice()), } } @@ -95,3 +96,10 @@ func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) db.SubBalance(sender, amount) db.AddBalance(recipient, amount) } + +// ValidateCode returns an indicator whether the given code is valid. +func ValidateCode(version uint64, code []byte) bool { + // When code version is 0, the validation does nothing and always succeeds. + // Future VM versions can define additional validation that has to be passed. + return true +} diff --git a/core/genesis.go b/core/genesis.go index 87bab25201..c9afdf3351 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -248,7 +248,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { statedb, _ := state.New(common.Hash{}, state.NewDatabase(db)) for addr, account := range g.Alloc { statedb.AddBalance(addr, account.Balance) - statedb.SetCode(addr, account.Code) + statedb.SetCode(addr, account.Code, 0) // Should all genesis accounts' code version being 0? statedb.SetNonce(addr, account.Nonce) for key, value := range account.Storage { statedb.SetState(addr, key, value) diff --git a/core/state/dump.go b/core/state/dump.go index 51d3e5554f..f38e5481cb 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -29,14 +29,15 @@ import ( // DumpAccount represents an account in the state type DumpAccount struct { - Balance string `json:"balance"` - Nonce uint64 `json:"nonce"` - Root string `json:"root"` - CodeHash string `json:"codeHash"` - Code string `json:"code,omitempty"` - Storage map[common.Hash]string `json:"storage,omitempty"` - Address *common.Address `json:"address,omitempty"` // Address only present in iterative (line-by-line) mode - SecureKey hexutil.Bytes `json:"key,omitempty"` // If we don't have address, we can output the key + Balance string `json:"balance"` + Nonce uint64 `json:"nonce"` + Root string `json:"root"` + CodeHash string `json:"codeHash"` + Code string `json:"code,omitempty"` + CodeVersion uint64 `json:"codeVersion"` + Storage map[common.Hash]string `json:"storage,omitempty"` + Address *common.Address `json:"address,omitempty"` // Address only present in iterative (line-by-line) mode + SecureKey hexutil.Bytes `json:"key,omitempty"` // If we don't have address, we can output the key } @@ -65,14 +66,15 @@ func (self *Dump) onAccount(addr common.Address, account DumpAccount) { func (self iterativeDump) onAccount(addr common.Address, account DumpAccount) { dumpAccount := &DumpAccount{ - Balance: account.Balance, - Nonce: account.Nonce, - Root: account.Root, - CodeHash: account.CodeHash, - Code: account.Code, - Storage: account.Storage, - SecureKey: account.SecureKey, - Address: nil, + Balance: account.Balance, + Nonce: account.Nonce, + Root: account.Root, + CodeHash: account.CodeHash, + Code: account.Code, + CodeVersion: account.CodeVersion, + Storage: account.Storage, + SecureKey: account.SecureKey, + Address: nil, } if addr != (common.Address{}) { dumpAccount.Address = &addr @@ -86,7 +88,7 @@ func (self iterativeDump) onRoot(root common.Hash) { } func (self *StateDB) dump(c collector, excludeCode, excludeStorage, excludeMissingPreimages bool) { - emptyAddress := (common.Address{}) + emptyAddress := common.Address{} missingPreimages := 0 c.onRoot(self.trie.Hash()) it := trie.NewIterator(self.trie.NodeIterator(nil)) @@ -98,10 +100,11 @@ func (self *StateDB) dump(c collector, excludeCode, excludeStorage, excludeMissi addr := common.BytesToAddress(self.trie.GetKey(it.Key)) obj := newObject(nil, addr, data) account := DumpAccount{ - Balance: data.Balance.String(), - Nonce: data.Nonce, - Root: common.Bytes2Hex(data.Root[:]), - CodeHash: common.Bytes2Hex(data.CodeHash), + Balance: data.Balance.String(), + Nonce: data.Nonce, + Root: common.Bytes2Hex(data.Root[:]), + CodeHash: common.Bytes2Hex(data.CodeHash), + CodeVersion: data.CodeVersion, } if emptyAddress == addr { // Preimage missing diff --git a/core/state/journal.go b/core/state/journal.go index a03ca57dbc..4272df48c0 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -114,6 +114,7 @@ type ( codeChange struct { account *common.Address prevcode, prevhash []byte + version uint64 } // Changes to other state values. @@ -188,7 +189,7 @@ func (ch nonceChange) dirtied() *common.Address { } func (ch codeChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode) + s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode, ch.version) } func (ch codeChange) dirtied() *common.Address { diff --git a/core/state/state_object.go b/core/state/state_object.go index 852c340b82..2d0b2cd8f1 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -43,7 +43,6 @@ func (s Storage) String() (str string) { for key, value := range s { str += fmt.Sprintf("%X : %X\n", key, value) } - return } @@ -52,7 +51,6 @@ func (s Storage) Copy() Storage { for key, value := range s { cpy[key] = value } - return cpy } @@ -95,13 +93,75 @@ func (s *stateObject) empty() bool { return s.data.Nonce == 0 && s.data.Balance.Sign() == 0 && bytes.Equal(s.data.CodeHash, emptyCodeHash) } -// Account is the Ethereum consensus representation of accounts. -// These objects are stored in the main account trie. +// legacyStoredAccount is the legacy storage encoding of a state object used in +// database. +type legacyStoredAccount struct { + Nonce uint64 // The nonce of account. + Balance *big.Int // The balance of account. + Root common.Hash // Merkle root of the storage trie + CodeHash []byte // The code hash of account. +} + +// storedAccount is the storage encoding of a state object used in database which +// includes code version field introduced by EIP1702. +type storedAccount Account + +// Account is ethereum consensus representation of accounts. These objects are +// stored in the main account trie. type Account struct { - Nonce uint64 - Balance *big.Int - Root common.Hash // merkle root of the storage trie - CodeHash []byte + Nonce uint64 // The nonce of account. + Balance *big.Int // The balance of account. + Root common.Hash // Merkle root of the storage trie. + CodeHash []byte // The code hash of account. + CodeVersion uint64 // The version of account code. +} + +// EncodeRLP implements rlp.Encoder. +func (a *Account) EncodeRLP(w io.Writer) error { + if a.CodeVersion == 0 { + return rlp.Encode(w, legacyStoredAccount{a.Nonce, a.Balance, a.Root, a.CodeHash}) + } + return rlp.Encode(w, storedAccount(*a)) +} + +// EncodeRLP implements rlp.Decoder. +func (a *Account) DecodeRLP(r *rlp.Stream) error { + blob, err := r.Raw() + if err != nil { + return err + } + elems, _, err := rlp.SplitList(blob) + if err != nil { + return err + } + switch c, _ := rlp.CountValues(elems); c { + case 4: + return decodeLegacyAccount(a, blob) + case 5: + return decodeAccount(a, blob) + default: + return fmt.Errorf("invalid number of list elements: %v", c) + } +} + +// decodeLegacyAccount decodes the account information from legacy format blob. +func decodeLegacyAccount(a *Account, blob []byte) error { + var dec legacyStoredAccount + if err := rlp.DecodeBytes(blob, &dec); err != nil { + return err + } + a.Nonce, a.Balance, a.Root, a.CodeHash = dec.Nonce, dec.Balance, dec.Root, dec.CodeHash + return nil +} + +// decodeAccount decodes the account information from current format blob. +func decodeAccount(a *Account, blob []byte) error { + var dec storedAccount + if err := rlp.DecodeBytes(blob, &dec); err != nil { + return err + } + *a = Account(dec) + return nil } // newObject creates a state object. @@ -124,7 +184,7 @@ func newObject(db *StateDB, address common.Address, data Account) *stateObject { // EncodeRLP implements rlp.Encoder. func (s *stateObject) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, s.data) + return rlp.Encode(w, &s.data) } // setError remembers the first non-nil error it is called with. @@ -355,19 +415,21 @@ func (s *stateObject) Code(db Database) []byte { return code } -func (s *stateObject) SetCode(codeHash common.Hash, code []byte) { +func (s *stateObject) SetCode(codeHash common.Hash, code []byte, version uint64) { prevcode := s.Code(s.db.db) s.db.journal.append(codeChange{ account: &s.address, prevhash: s.CodeHash(), prevcode: prevcode, + version: s.data.CodeVersion, }) - s.setCode(codeHash, code) + s.setCode(codeHash, code, version) } -func (s *stateObject) setCode(codeHash common.Hash, code []byte) { +func (s *stateObject) setCode(codeHash common.Hash, code []byte, version uint64) { s.code = code s.data.CodeHash = codeHash[:] + s.data.CodeVersion = version s.dirtyCode = true } @@ -395,6 +457,10 @@ func (s *stateObject) Nonce() uint64 { return s.data.Nonce } +func (s *stateObject) CodeVersion() uint64 { + return s.data.CodeVersion +} + // Never called, but must be present to allow stateObject to be used // as a vm.Account interface that also satisfies the vm.ContractRef // interface. Interfaces are awesome. diff --git a/core/state/state_test.go b/core/state/state_test.go index d6ff714ee4..3a1e3f13a8 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -42,7 +42,7 @@ func (s *StateSuite) TestDump(c *checker.C) { obj1 := s.state.GetOrNewStateObject(toAddr([]byte{0x01})) obj1.AddBalance(big.NewInt(22)) obj2 := s.state.GetOrNewStateObject(toAddr([]byte{0x01, 0x02})) - obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3}) + obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3}, 0) obj3 := s.state.GetOrNewStateObject(toAddr([]byte{0x02})) obj3.SetBalance(big.NewInt(44)) @@ -60,20 +60,23 @@ func (s *StateSuite) TestDump(c *checker.C) { "balance": "22", "nonce": 0, "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + "codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "codeVersion": 0 }, "0x0000000000000000000000000000000000000002": { "balance": "44", "nonce": 0, "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + "codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "codeVersion": 0 }, "0x0000000000000000000000000000000000000102": { "balance": "0", "nonce": 0, "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "codeHash": "87874902497a5bb968da31a2998d8f22e949d1ef6214bcdedd8bae24cca4b9e3", - "code": "03030303030303" + "code": "03030303030303", + "codeVersion": 0 } } }` @@ -153,7 +156,7 @@ func TestSnapshot2(t *testing.T) { so0 := state.getStateObject(stateobjaddr0) so0.SetBalance(big.NewInt(42)) so0.SetNonce(43) - so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'}) + so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'}, 0) so0.suicided = false so0.deleted = false state.setStateObject(so0) @@ -165,7 +168,7 @@ func TestSnapshot2(t *testing.T) { so1 := state.getStateObject(stateobjaddr1) so1.SetBalance(big.NewInt(52)) so1.SetNonce(53) - so1.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e', '2'}), []byte{'c', 'a', 'f', 'e', '2'}) + so1.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e', '2'}), []byte{'c', 'a', 'f', 'e', '2'}, 0) so1.suicided = true so1.deleted = true state.setStateObject(so1) diff --git a/core/state/statedb.go b/core/state/statedb.go index 3bb9862edd..0435b90969 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -278,6 +278,14 @@ func (self *StateDB) GetCodeHash(addr common.Address) common.Hash { return common.BytesToHash(stateObject.CodeHash()) } +func (self *StateDB) GetCodeVersion(addr common.Address) uint64 { + stateObject := self.getStateObject(addr) + if stateObject == nil { + return 0 + } + return stateObject.CodeVersion() +} + // GetState retrieves a value from the given account's storage trie. func (self *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { stateObject := self.getStateObject(addr) @@ -372,10 +380,10 @@ func (self *StateDB) SetNonce(addr common.Address, nonce uint64) { } } -func (self *StateDB) SetCode(addr common.Address, code []byte) { +func (self *StateDB) SetCode(addr common.Address, code []byte, version uint64) { stateObject := self.GetOrNewStateObject(addr) if stateObject != nil { - stateObject.SetCode(crypto.Keccak256Hash(code), code) + stateObject.SetCode(crypto.Keccak256Hash(code), code, version) } } @@ -488,7 +496,6 @@ func (self *StateDB) GetOrNewStateObject(addr common.Address) *stateObject { func (self *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) { prev = self.getStateObject(addr) newobj = newObject(self, addr, Account{}) - newobj.setNonce(0) // sets the object to dirty if prev == nil { self.journal.append(createObjectChange{account: &addr}) } else { diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index bf073bc948..dcb14131aa 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" ) // Tests that updating a state trie does not leak any database writes prior to @@ -51,7 +52,7 @@ func TestUpdateLeaks(t *testing.T) { state.SetState(addr, common.BytesToHash([]byte{i, i, i}), common.BytesToHash([]byte{i, i, i, i})) } if i%3 == 0 { - state.SetCode(addr, []byte{i, i, i, i, i}) + state.SetCode(addr, []byte{i, i, i, i, i}, 0) } state.IntermediateRoot(false) } @@ -80,7 +81,7 @@ func TestIntermediateLeaks(t *testing.T) { state.SetState(addr, common.Hash{i, i, i, tweak}, common.Hash{i, i, i, i, tweak}) } if i%3 == 0 { - state.SetCode(addr, []byte{i, i, i, i, i, tweak}) + state.SetCode(addr, []byte{i, i, i, i, i, tweak}, 0) } } @@ -247,10 +248,10 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction { fn: func(a testAction, s *StateDB) { code := make([]byte, 16) binary.BigEndian.PutUint64(code, uint64(a.args[0])) - binary.BigEndian.PutUint64(code[8:], uint64(a.args[1])) - s.SetCode(addr, code) + binary.BigEndian.PutUint64(code[8:16], uint64(a.args[1])) + s.SetCode(addr, code, uint64(a.args[2])) }, - args: make([]int64, 2), + args: make([]int64, 3), }, { name: "CreateAccount", @@ -449,3 +450,49 @@ func TestCopyOfCopy(t *testing.T) { t.Fatalf("2nd copy fail, expected 42, got %v", got) } } + +func TestStateObjectDecoding(t *testing.T) { + var accounts = []Account{ + { + Nonce: 100, + Balance: big.NewInt(100), + Root: common.HexToHash("deadbeef"), + CodeHash: []byte{0x01, 0x02, 0x03}, + }, + { + Nonce: 100, + Balance: big.NewInt(100), + Root: common.HexToHash("deadbeef"), + CodeHash: []byte{0x01, 0x02, 0x03}, + CodeVersion: 0, + }, + { + Nonce: 100, + Balance: big.NewInt(100), + Root: common.HexToHash("deadbeef"), + CodeHash: []byte{0x01, 0x02, 0x03}, + CodeVersion: 1, + }, + { + Nonce: 100, + Balance: big.NewInt(100), + Root: common.HexToHash("deadbeef"), + CodeHash: []byte{0x01, 0x02, 0x03}, + CodeVersion: math.MaxUint64, + }, + } + for _, acct := range accounts { + blob, err := rlp.EncodeToBytes(&acct) + if err != nil { + t.Fatalf("Failed to encode account %v", err) + } + var dec Account + err = rlp.DecodeBytes(blob, &dec) + if err != nil { + t.Fatalf("Failed to decode account %v", err) + } + if !reflect.DeepEqual(acct, dec) { + t.Fatalf("Mismatch after encoding/decoding, want %v, has %v", acct, dec) + } + } +} diff --git a/core/state/sync_test.go b/core/state/sync_test.go index de098dce0f..1db7dc96bc 100644 --- a/core/state/sync_test.go +++ b/core/state/sync_test.go @@ -56,7 +56,7 @@ func makeTestState() (Database, common.Hash, []*testAccount) { acc.nonce = uint64(42 * i) if i%3 == 0 { - obj.SetCode(crypto.Keccak256Hash([]byte{i, i, i, i, i}), []byte{i, i, i, i, i}) + obj.SetCode(crypto.Keccak256Hash([]byte{i, i, i, i, i}), []byte{i, i, i, i, i}, 0) acc.code = []byte{i, i, i, i, i} } state.updateStateObject(obj) diff --git a/core/state_transition.go b/core/state_transition.go index fda081b7d1..36a99db793 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -27,9 +27,7 @@ import ( "github.com/ethereum/go-ethereum/params" ) -var ( - errInsufficientBalanceForGas = errors.New("insufficient balance to pay for gas") -) +var errInsufficientBalanceForGas = errors.New("insufficient balance to pay for gas") /* The State Transitioning Model @@ -206,7 +204,7 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo vmerr error ) if contractCreation { - ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value) + ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value, st.evm.ChainConfig().LatestCodeVersion) } else { // Increment the nonce for the next transaction st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1) diff --git a/core/vm/contract.go b/core/vm/contract.go index 20baa6e751..b1b1bf4b71 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -57,8 +57,9 @@ type Contract struct { CodeAddr *common.Address Input []byte - Gas uint64 - value *big.Int + CodeVersion uint64 + Gas uint64 + value *big.Int } // NewContract returns a new contract environment for the execution of EVM. @@ -169,16 +170,18 @@ func (c *Contract) Value() *big.Int { // SetCallCode sets the code of the contract and address of the backing data // object -func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) { +func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte, version uint64) { c.Code = code c.CodeHash = hash c.CodeAddr = addr + c.CodeVersion = version } // SetCodeOptionalHash can be used to provide code, but it's optional to provide hash. // In case hash is not provided, the jumpdest analysis will not be saved to the parent context -func (c *Contract) SetCodeOptionalHash(addr *common.Address, codeAndHash *codeAndHash) { +func (c *Contract) SetCodeOptionalHash(addr *common.Address, codeAndHash *codeAndHash, version uint64) { c.Code = codeAndHash.code c.CodeHash = codeAndHash.hash c.CodeAddr = addr + c.CodeVersion = version } diff --git a/core/vm/evm.go b/core/vm/evm.go index 98d9e31fbb..b80ef19bf0 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -33,11 +33,18 @@ var emptyCodeHash = crypto.Keccak256Hash(nil) type ( // CanTransferFunc is the signature of a transfer guard function CanTransferFunc func(StateDB, common.Address, *big.Int) bool + // TransferFunc is the signature of a transfer function TransferFunc func(StateDB, common.Address, common.Address, *big.Int) + // GetHashFunc returns the nth block hash in the blockchain // and is used by the BLOCKHASH EVM op code. GetHashFunc func(uint64) common.Hash + + // ValidateCodeFunc returns an indicator whether the given contract + // code is valid or not. If false is returned, the transaction is failed + // and returns out-of-gas. + ValidateCodeFunc func(uint64, []byte) bool ) // run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter. @@ -54,7 +61,8 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err return RunPrecompiledContract(p, input, contract) } } - for _, interpreter := range evm.interpreters { + interpreters := evm.interpreters[contract.CodeVersion] + for _, interpreter := range interpreters { if interpreter.CanRun(contract.Code) { if evm.interpreter != interpreter { // Ensure that the interpreter pointer is set back @@ -76,11 +84,16 @@ type Context struct { // CanTransfer returns whether the account contains // sufficient ether to transfer the value CanTransfer CanTransferFunc + // Transfer transfers ether from one account to the other Transfer TransferFunc + // GetHash returns the hash corresponding to n GetHash GetHashFunc + // ValidateCode returns whether the contract code is valid. + ValidateCode ValidateCodeFunc + // Message information Origin common.Address // Provides information for ORIGIN GasPrice *big.Int // Provides information for GASPRICE @@ -119,7 +132,7 @@ type EVM struct { vmConfig Config // global (to this context) ethereum virtual machine // used throughout the execution of the tx. - interpreters []Interpreter + interpreters map[uint64][]Interpreter interpreter Interpreter // abort is used to abort the EVM calling operations // NOTE: must be set atomically @@ -139,7 +152,7 @@ func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmCon vmConfig: vmConfig, chainConfig: chainConfig, chainRules: chainConfig.Rules(ctx.BlockNumber), - interpreters: make([]Interpreter, 0, 1), + interpreters: make(map[uint64][]Interpreter), } if chainConfig.IsEWASM(ctx.BlockNumber) { @@ -160,8 +173,8 @@ func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmCon // vmConfig.EVMInterpreter will be used by EVM-C, it won't be checked here // as we always want to have the built-in EVM as the failover option. - evm.interpreters = append(evm.interpreters, NewEVMInterpreter(evm, vmConfig)) - evm.interpreter = evm.interpreters[0] + evm.interpreters[0] = []Interpreter{NewEVMInterpreter(evm, vmConfig)} + evm.interpreter = evm.interpreters[0][0] return evm } @@ -226,7 +239,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. contract := NewContract(caller, to, value, gas) - contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) + contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr), evm.StateDB.GetCodeVersion(addr)) // Even if the account has no code, we need to continue because it might be a precompile start := time.Now() @@ -281,7 +294,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. contract := NewContract(caller, to, value, gas) - contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) + contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr), evm.StateDB.GetCodeVersion(addr)) ret, err = run(evm, contract, input, false) if err != nil { @@ -314,7 +327,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by // Initialise a new contract and make initialise the delegate values contract := NewContract(caller, to, nil, gas).AsDelegate() - contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) + contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr), evm.StateDB.GetCodeVersion(addr)) ret, err = run(evm, contract, input, false) if err != nil { @@ -346,7 +359,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. contract := NewContract(caller, to, new(big.Int), gas) - contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) + contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr), evm.StateDB.GetCodeVersion(addr)) // We do an AddBalance of zero here, just in order to trigger a touch. // This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium, @@ -380,7 +393,7 @@ func (c *codeAndHash) Hash() common.Hash { } // create creates a new contract using code as deployment code. -func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address) ([]byte, common.Address, uint64, error) { +func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, version uint64) ([]byte, common.Address, uint64, error) { // Depth check execution. Fail if we're trying to execute above the // limit. if evm.depth > int(params.CallCreateDepth) { @@ -389,6 +402,12 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if !evm.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, common.Address{}, gas, ErrInsufficientBalance } + if !evm.ValidateCode(version, codeAndHash.code) { + // According to EIP1702, if the validation phrase fails, + // contract creation does not proceed, return out-of-gas + // and consume all remaining gas. + return nil, common.Address{}, 0, ErrOutOfGas + } nonce := evm.StateDB.GetNonce(caller.Address()) evm.StateDB.SetNonce(caller.Address(), nonce+1) @@ -408,7 +427,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(address), value, gas) - contract.SetCodeOptionalHash(&address, codeAndHash) + contract.SetCodeOptionalHash(&address, codeAndHash, version) if evm.vmConfig.NoRecursion && evm.depth > 0 { return nil, address, gas, nil @@ -430,7 +449,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if err == nil && !maxCodeSizeExceeded { createDataGas := uint64(len(ret)) * params.CreateDataGas if contract.UseGas(createDataGas) { - evm.StateDB.SetCode(address, ret) + evm.StateDB.SetCode(address, ret, version) } else { err = ErrCodeStoreOutOfGas } @@ -457,19 +476,19 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } // Create creates a new contract using code as deployment code. -func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { +func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.Int, version uint64) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address())) - return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr) + return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr, version) } // Create2 creates a new contract using code as deployment code. // // The different between Create2 with Create is Create2 uses sha3(0xff ++ msg.sender ++ salt ++ sha3(init_code))[12:] // instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. -func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { +func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *big.Int, version uint64) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { codeAndHash := &codeAndHash{code: code} contractAddr = crypto.CreateAddress2(caller.Address(), common.BigToHash(salt), codeAndHash.Hash().Bytes()) - return evm.create(caller, codeAndHash, gas, endowment, contractAddr) + return evm.create(caller, codeAndHash, gas, endowment, contractAddr, version) } // ChainConfig returns the environment's chain configuration diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 7b6909c927..f2a189b656 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -699,7 +699,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memor } contract.UseGas(gas) - res, addr, returnGas, suberr := interpreter.evm.Create(contract, input, gas, value) + res, addr, returnGas, suberr := interpreter.evm.Create(contract, input, gas, value, contract.CodeVersion) // Push item on the stack based on the returned error. If the ruleset is // homestead we must check for CodeStoreOutOfGasError (homestead only // rule) and treat as an error, if the ruleset is frontier we must @@ -732,7 +732,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memo // Apply EIP150 gas -= gas / 64 contract.UseGas(gas) - res, addr, returnGas, suberr := interpreter.evm.Create2(contract, input, gas, endowment, salt) + res, addr, returnGas, suberr := interpreter.evm.Create2(contract, input, gas, endowment, salt, contract.CodeVersion) // Push item on the stack based on the returned error. if suberr != nil { stack.push(interpreter.intPool.getZero()) diff --git a/core/vm/interface.go b/core/vm/interface.go index dd401466ad..bb3b37f4d5 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -36,8 +36,9 @@ type StateDB interface { GetCodeHash(common.Address) common.Hash GetCode(common.Address) []byte - SetCode(common.Address, []byte) + SetCode(common.Address, []byte, uint64) GetCodeSize(common.Address) int + GetCodeVersion(address common.Address) uint64 AddRefund(uint64) SubRefund(uint64) @@ -65,16 +66,3 @@ type StateDB interface { ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error } - -// CallContext provides a basic interface for the EVM calling conventions. The EVM -// depends on this context being implemented for doing subcalls and initialising new EVM contracts. -type CallContext interface { - // Call another contract - Call(env *EVM, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) - // Take another's contract code and execute within our own context - CallCode(env *EVM, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) - // Same as CallCode except sender and value is propagated from parent to child scope - DelegateCall(env *EVM, me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) - // Create a new contract - Create(env *EVM, me ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) -} diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index 31c9b9cf9d..f374a6410d 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -24,17 +24,17 @@ import ( func NewEnv(cfg *Config) *vm.EVM { context := vm.Context{ - CanTransfer: core.CanTransfer, - Transfer: core.Transfer, - GetHash: func(uint64) common.Hash { return common.Hash{} }, - - Origin: cfg.Origin, - Coinbase: cfg.Coinbase, - BlockNumber: cfg.BlockNumber, - Time: cfg.Time, - Difficulty: cfg.Difficulty, - GasLimit: cfg.GasLimit, - GasPrice: cfg.GasPrice, + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + GetHash: func(uint64) common.Hash { return common.Hash{} }, + ValidateCode: core.ValidateCode, + Origin: cfg.Origin, + Coinbase: cfg.Coinbase, + BlockNumber: cfg.BlockNumber, + Time: cfg.Time, + Difficulty: cfg.Difficulty, + GasLimit: cfg.GasLimit, + GasPrice: cfg.GasPrice, } return vm.NewEVM(context, cfg.State, cfg.ChainConfig, cfg.EVMConfig) diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index db1f6f3822..79ae25732b 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -41,6 +41,7 @@ type Config struct { GasLimit uint64 GasPrice *big.Int Value *big.Int + Version uint64 Debug bool EVMConfig vm.Config @@ -108,7 +109,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { ) cfg.State.CreateAccount(address) // set the receiver's (the executing contract) code for execution. - cfg.State.SetCode(address, code) + cfg.State.SetCode(address, code, cfg.Version) // Call the code with the given configuration. ret, _, err := vmenv.Call( sender, @@ -142,6 +143,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { input, cfg.GasLimit, cfg.Value, + cfg.Version, ) return code, address, leftOverGas, err } diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 15f545ddca..5f68d140d4 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -104,7 +104,7 @@ func TestCall(t *testing.T) { byte(vm.PUSH1), 32, byte(vm.PUSH1), 0, byte(vm.RETURN), - }) + }, 0) ret, _, err := Call(address, nil, &Config{State: state}) if err != nil { @@ -157,7 +157,7 @@ func benchmarkEVM_Create(bench *testing.B, code string) { ) statedb.CreateAccount(sender) - statedb.SetCode(receiver, common.FromHex(code)) + statedb.SetCode(receiver, common.FromHex(code), 0) runtimeConfig := Config{ Origin: sender, State: statedb, diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 69eb80a5c5..f16f7126d3 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -144,15 +144,16 @@ func TestPrestateTracerCreate2(t *testing.T) { */ origin, _ := signer.Sender(tx) context := vm.Context{ - CanTransfer: core.CanTransfer, - Transfer: core.Transfer, - Origin: origin, - Coinbase: common.Address{}, - BlockNumber: new(big.Int).SetUint64(8000000), - Time: new(big.Int).SetUint64(5), - Difficulty: big.NewInt(0x30000), - GasLimit: uint64(6000000), - GasPrice: big.NewInt(1), + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + ValidateCode: core.ValidateCode, + Origin: origin, + Coinbase: common.Address{}, + BlockNumber: new(big.Int).SetUint64(8000000), + Time: new(big.Int).SetUint64(5), + Difficulty: big.NewInt(0x30000), + GasLimit: uint64(6000000), + GasPrice: big.NewInt(1), } alloc := core.GenesisAlloc{} @@ -232,15 +233,16 @@ func TestCallTracer(t *testing.T) { origin, _ := signer.Sender(tx) context := vm.Context{ - CanTransfer: core.CanTransfer, - Transfer: core.Transfer, - Origin: origin, - Coinbase: test.Context.Miner, - BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), - Time: new(big.Int).SetUint64(uint64(test.Context.Time)), - Difficulty: (*big.Int)(test.Context.Difficulty), - GasLimit: uint64(test.Context.GasLimit), - GasPrice: tx.GasPrice(), + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + ValidateCode: core.ValidateCode, + Origin: origin, + Coinbase: test.Context.Miner, + BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), + Time: new(big.Int).SetUint64(uint64(test.Context.Time)), + Difficulty: (*big.Int)(test.Context.Difficulty), + GasLimit: uint64(test.Context.GasLimit), + GasPrice: tx.GasPrice(), } statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc) diff --git a/params/config.go b/params/config.go index 2144aa22c1..820a615352 100644 --- a/params/config.go +++ b/params/config.go @@ -58,6 +58,7 @@ var ( HomesteadBlock: big.NewInt(1150000), DAOForkBlock: big.NewInt(1920000), DAOForkSupport: true, + LatestCodeVersion: 0, EIP150Block: big.NewInt(2463000), EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), EIP155Block: big.NewInt(2675000), @@ -96,6 +97,7 @@ var ( HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, DAOForkSupport: true, + LatestCodeVersion: 0, EIP150Block: big.NewInt(0), EIP150Hash: common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"), EIP155Block: big.NewInt(10), @@ -134,6 +136,7 @@ var ( HomesteadBlock: big.NewInt(1), DAOForkBlock: nil, DAOForkSupport: true, + LatestCodeVersion: 0, EIP150Block: big.NewInt(2), EIP150Hash: common.HexToHash("0x9b095b36c15eaf13044373aef8ee0bd3a382a5abb92e402afa44b8249c3a90e9"), EIP155Block: big.NewInt(3), @@ -174,6 +177,7 @@ var ( HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, DAOForkSupport: true, + LatestCodeVersion: 0, EIP150Block: big.NewInt(0), EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), @@ -213,16 +217,16 @@ var ( // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil} + AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, 0, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil} // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers into the Clique consensus. // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} + AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, 0, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} - TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil} + TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, 0, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil} TestRules = TestChainConfig.Rules(new(big.Int)) ) @@ -281,6 +285,9 @@ type ChainConfig struct { DAOForkBlock *big.Int `json:"daoForkBlock,omitempty"` // TheDAO hard-fork switch block (nil = no fork) DAOForkSupport bool `json:"daoForkSupport,omitempty"` // Whether the nodes supports or opposes the DAO hard-fork + // EIP1702 introduces the code version change (https://eips.ethereum.org/EIPS/eip-1702) + LatestCodeVersion uint64 `json:"latestCodeVersion,omitempty"` // The latest account code version indicator, 0 as default. + // EIP150 implements the Gas price changes (https://github.com/ethereum/EIPs/issues/150) EIP150Block *big.Int `json:"eip150Block,omitempty"` // EIP150 HF block (nil = no fork) EIP150Hash common.Hash `json:"eip150Hash,omitempty"` // EIP150 HF hash (needed for header only clients as only gas pricing changed) @@ -293,7 +300,6 @@ type ChainConfig struct { PetersburgBlock *big.Int `json:"petersburgBlock,omitempty"` // Petersburg switch block (nil = same as Constantinople) IstanbulBlock *big.Int `json:"istanbulBlock,omitempty"` // Istanbul switch block (nil = no fork, 0 = already on istanbul) EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) - // Various consensus engines Ethash *EthashConfig `json:"ethash,omitempty"` Clique *CliqueConfig `json:"clique,omitempty"` diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 0b78f26ed1..968a3cc318 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -173,7 +173,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB sdb := state.NewDatabase(db) statedb, _ := state.New(common.Hash{}, sdb) for addr, a := range accounts { - statedb.SetCode(addr, a.Code) + statedb.SetCode(addr, a.Code, 0) // Default code version is 0. statedb.SetNonce(addr, a.Nonce) statedb.SetBalance(addr, a.Balance) for k, v := range a.Storage {