diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go index 2bc4f73b606b..d91f0d88c04e 100644 --- a/cmd/evm/internal/t8ntool/transaction.go +++ b/cmd/evm/internal/t8ntool/transaction.go @@ -180,7 +180,7 @@ func Transaction(ctx *cli.Context) error { r.Error = errors.New("gas * maxFeePerGas exceeds 256 bits") } // Check whether the init code size has been exceeded. - if chainConfig.IsShanghai(new(big.Int), 0) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize { + if chainConfig.IsShanghai(new(big.Int), 0) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSizeEIP3860 { r.Error = errors.New("max initcode size exceeded") } results = append(results, r) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index f47e922e1870..77ddfe2a1522 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -43,6 +43,7 @@ import ( "github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb/pebble" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" "github.com/holiman/uint256" @@ -4407,3 +4408,76 @@ func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64, } } } + +func TestEIP7907(t *testing.T) { + glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false)) + glogger.Verbosity(3) + log.SetDefault(log.NewLogger(glogger)) + + junk := make([]byte, 1024*250) // 250kb + for i := range junk { + junk[i] = byte(i) + } + code := program.New().Op(vm.ADDRESS).Op(vm.POP).ReturnViaCodeCopy(junk).Bytes() + var ( + config = *params.MergedTestChainConfig + signer = types.LatestSigner(&config) + engine = beacon.New(ethash.NewFaker()) + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") + bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") + funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) + ) + gspec := &Genesis{ + Config: &config, + GasLimit: 70000000, + Alloc: types.GenesisAlloc{ + addr1: {Balance: funds}, + addr2: {Balance: funds}, + aa: { // The address 0xAAAA calls into addr2 + Code: code, + Nonce: 0, + Balance: big.NewInt(0), + }, + bb: { // The address 0xBBBB copies and deploys the contract. + Code: program.New().ExtcodeCopy(aa, 0, 0, len(code)).Push0().Push(len(code)).Push0().Push0().Op(vm.CREATE).Op(vm.EXTCODESIZE).Bytes(), + Nonce: 0, + Balance: big.NewInt(0), + }, + }, + } + + _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { + b.SetCoinbase(aa) + txdata := &types.DynamicFeeTx{ + ChainID: gspec.Config.ChainID, + Nonce: 0, + To: &bb, + Gas: 70000000, + GasFeeCap: newGwei(5), + GasTipCap: big.NewInt(2), + } + tx := types.MustSignNewTx(key1, signer, txdata) + b.AddTx(tx) + }) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{}, os.Stderr).Hooks()}, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + defer chain.Stop() + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + + // Verify delegation designations were deployed. + created := crypto.CreateAddress(bb, 0) + fmt.Println(created.Hex()) + state, _ := chain.State() + code, want := state.GetCode(created), junk + if !bytes.Equal(code, want) { + t.Fatalf("created code incorrect: got %d, want %d", len(code), len(want)) + } +} diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go index 41e15debe94d..97ec7057ba79 100644 --- a/core/rawdb/accessors_state.go +++ b/core/rawdb/accessors_state.go @@ -65,6 +65,16 @@ func ReadCodeWithPrefix(db ethdb.KeyValueReader, hash common.Hash) []byte { return data } +// ReadCodeWithPrefix retrieves the contract code of the provided code hash. +// Return -1 if not found for legacy db. +func ReadCodeSizeWithPrefix(db ethdb.KeyValueReader, hash common.Hash) int { + data, _ := db.Get(codeSizeKey(hash)) + if len(data) != 4 { + return -1 + } + return int(binary.BigEndian.Uint32(data)) +} + // HasCode checks if the contract code corresponding to the // provided code hash is present in the db. func HasCode(db ethdb.KeyValueReader, hash common.Hash) bool { @@ -90,12 +100,19 @@ func WriteCode(db ethdb.KeyValueWriter, hash common.Hash, code []byte) { if err := db.Put(codeKey(hash), code); err != nil { log.Crit("Failed to store contract code", "err", err) } + var sizeData [4]byte + binary.BigEndian.PutUint32(sizeData[:], uint32(len(code))) + if err := db.Put(codeSizeKey(hash), sizeData[:]); err != nil { + log.Crit("Failed to store contract code size", "err", err) + } } // DeleteCode deletes the specified contract code from the database. func DeleteCode(db ethdb.KeyValueWriter, hash common.Hash) { if err := db.Delete(codeKey(hash)); err != nil { log.Crit("Failed to delete contract code", "err", err) + // Ignore error since the legacy db may not contain the size. + db.Delete(codeSizeKey(hash)) } } diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index fa125cecc053..24185ad2f47e 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -110,6 +110,7 @@ var ( SnapshotAccountPrefix = []byte("a") // SnapshotAccountPrefix + account hash -> account trie value SnapshotStoragePrefix = []byte("o") // SnapshotStoragePrefix + account hash + storage hash -> storage trie value CodePrefix = []byte("c") // CodePrefix + code hash -> account code + CodeSizePrefix = []byte("s") /// CodeSizePrefx + code hash -> code size skeletonHeaderPrefix = []byte("S") // skeletonHeaderPrefix + num (uint64 big endian) -> header // Path-based storage scheme of merkle patricia trie. @@ -233,6 +234,11 @@ func codeKey(hash common.Hash) []byte { return append(CodePrefix, hash.Bytes()...) } +// codeSizeKey = CodeSizePrefix + hash +func codeSizeKey(hash common.Hash) []byte { + return append(CodeSizePrefix, hash.Bytes()...) +} + // IsCodeKey reports whether the given byte slice is the key of contract code, // if so return the raw code hash as well. func IsCodeKey(key []byte) (bool, []byte) { diff --git a/core/state/access_list.go b/core/state/access_list.go index a58c2b20ea96..3579bb75f7d2 100644 --- a/core/state/access_list.go +++ b/core/state/access_list.go @@ -26,8 +26,9 @@ import ( ) type accessList struct { - addresses map[common.Address]int - slots []map[common.Hash]struct{} + addresses map[common.Address]int + slots []map[common.Hash]struct{} + addressCodes map[common.Address]struct{} } // ContainsAddress returns true if the address is in the access list. @@ -36,6 +37,12 @@ func (al *accessList) ContainsAddress(address common.Address) bool { return ok } +// ContainsAddress returns true if the address is in the access list. +func (al *accessList) ContainsAddressCode(address common.Address) bool { + _, ok := al.addressCodes[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) { @@ -55,7 +62,8 @@ func (al *accessList) Contains(address common.Address, slot common.Hash) (addres // newAccessList creates a new accessList. func newAccessList() *accessList { return &accessList{ - addresses: make(map[common.Address]int), + addresses: make(map[common.Address]int), + addressCodes: make(map[common.Address]struct{}), } } @@ -67,6 +75,10 @@ func (al *accessList) Copy() *accessList { for i, slotMap := range al.slots { cp.slots[i] = maps.Clone(slotMap) } + cp.addressCodes = make(map[common.Address]struct{}, len(al.addressCodes)) + for addr := range al.addressCodes { + cp.addressCodes[addr] = struct{}{} + } return cp } @@ -80,6 +92,16 @@ func (al *accessList) AddAddress(address common.Address) bool { return true } +// AddAddressCode adds an address code to the access list, and returns 'true' if +// the operation caused a change (addr was not previously in the list). +func (al *accessList) AddAddressCode(address common.Address) bool { + if _, present := al.addressCodes[address]; present { + return false + } + al.addressCodes[address] = struct{}{} + return true +} + // AddSlot adds the specified (addr, slot) combo to the access list. // Return values are: // - address added @@ -142,6 +164,11 @@ func (al *accessList) Equal(other *accessList) bool { return slices.EqualFunc(al.slots, other.slots, maps.Equal) } +// DeleteAddressCode removes an address code from the access list. +func (al *accessList) DeleteAddressCode(address common.Address) { + delete(al.addressCodes, address) +} + // PrettyPrint prints the contents of the access list in a human-readable form func (al *accessList) PrettyPrint() string { out := new(strings.Builder) diff --git a/core/state/journal.go b/core/state/journal.go index f3f976f24fed..a0da51db13b3 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -221,6 +221,10 @@ func (j *journal) accessListAddAccount(addr common.Address) { j.append(accessListAddAccountChange{addr}) } +func (j *journal) accessListAddAccountCode(addr common.Address) { + j.append(accessListAddAccountCodeChange{addr}) +} + func (j *journal) accessListAddSlot(addr common.Address, slot common.Hash) { j.append(accessListAddSlotChange{ address: addr, @@ -282,6 +286,9 @@ type ( address common.Address slot common.Hash } + accessListAddAccountCodeChange struct { + address common.Address + } // Changes to transient storage transientStorageChange struct { @@ -485,6 +492,20 @@ func (ch accessListAddAccountChange) copy() journalEntry { } } +func (ch accessListAddAccountCodeChange) revert(s *StateDB) { + s.accessList.DeleteAddressCode(ch.address) +} + +func (ch accessListAddAccountCodeChange) dirtied() *common.Address { + return nil +} + +func (ch accessListAddAccountCodeChange) copy() journalEntry { + return accessListAddAccountCodeChange{ + address: ch.address, + } +} + func (ch accessListAddSlotChange) revert(s *StateDB) { s.accessList.DeleteSlot(ch.address, ch.slot) } diff --git a/core/state/reader.go b/core/state/reader.go index 4628f4d5dbad..64bea0c43231 100644 --- a/core/state/reader.go +++ b/core/state/reader.go @@ -140,6 +140,11 @@ func (r *cachingCodeReader) CodeSize(addr common.Address, codeHash common.Hash) if cached, ok := r.codeSizeCache.Get(codeHash); ok { return cached, nil } + codeSize := rawdb.ReadCodeSizeWithPrefix(r.db, codeHash) + if codeSize != -1 { + r.codeSizeCache.Add(codeHash, codeSize) + return codeSize, nil + } code, err := r.Code(addr, codeHash) if err != nil { return 0, err diff --git a/core/state/statedb.go b/core/state/statedb.go index 6b474ea0a4a4..1498e12d9789 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1361,6 +1361,10 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d al.AddAddress(sender) if dst != nil { al.AddAddress(*dst) + // TODO: add for devnet-3 + // if rules.IsOsaka { + // al.AddAddressCode(*dst) + // } // If it's a create-tx, the destination will be added inside evm.create } for _, addr := range precompiles { @@ -1402,11 +1406,23 @@ func (s *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) { } } +// AddAddressCodeToAccessList adds the given address to the access list +func (s *StateDB) AddAddressCodeToAccessList(addr common.Address) { + if s.accessList.AddAddressCode(addr) { + s.journal.accessListAddAccountCode(addr) + } +} + // 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) } +// AddressCodeInAccessList returns true if the given address code is in the access list. +func (s *StateDB) AddressCodeInAccessList(addr common.Address) bool { + return s.accessList.ContainsAddressCode(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) diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go index a2fdfe9a217a..bc5e035d1892 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -125,10 +125,18 @@ func (s *hookedStateDB) SlotInAccessList(addr common.Address, slot common.Hash) return s.inner.SlotInAccessList(addr, slot) } +func (s *hookedStateDB) AddressCodeInAccessList(addr common.Address) bool { + return s.inner.AddressCodeInAccessList(addr) +} + func (s *hookedStateDB) AddAddressToAccessList(addr common.Address) { s.inner.AddAddressToAccessList(addr) } +func (s *hookedStateDB) AddAddressCodeToAccessList(addr common.Address) { + s.inner.AddAddressCodeToAccessList(addr) +} + func (s *hookedStateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) { s.inner.AddSlotToAccessList(addr, slot) } diff --git a/core/state_processor_test.go b/core/state_processor_test.go index d175b5eac59a..47aa02ea7c3f 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -125,7 +125,7 @@ func TestStateProcessorErrors(t *testing.T) { }, } blockchain, _ = NewBlockChain(db, gspec, beacon.New(ethash.NewFaker()), nil) - tooBigInitCode = [params.MaxInitCodeSize + 1]byte{} + tooBigInitCode = [params.MaxInitCodeSizeEIP3860 + 1]byte{} ) defer blockchain.Stop() diff --git a/core/state_transition.go b/core/state_transition.go index 3ec718c52c32..ec7c333753d9 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -471,8 +471,11 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { } // Check whether the init code size has been exceeded. - if rules.IsShanghai && contractCreation && len(msg.Data) > params.MaxInitCodeSize { - return nil, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(msg.Data), params.MaxInitCodeSize) + if rules.IsOsaka && contractCreation && len(msg.Data) > params.MaxInitCodeSizeEIP7907 { + return nil, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(msg.Data), params.MaxInitCodeSizeEIP7907) + } + if !rules.IsOsaka && rules.IsShanghai && contractCreation && len(msg.Data) > params.MaxInitCodeSizeEIP3860 { + return nil, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(msg.Data), params.MaxInitCodeSizeEIP3860) } // Execute the preparatory steps for state transition which includes: diff --git a/core/txpool/validation.go b/core/txpool/validation.go index fef1a99f7ba1..49084de1f276 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -87,8 +87,8 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types return fmt.Errorf("%w: type %d rejected, pool not yet in Prague", core.ErrTxTypeNotSupported, tx.Type()) } // Check whether the init code size has been exceeded - if rules.IsShanghai && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize { - return fmt.Errorf("%w: code size %v, limit %v", core.ErrMaxInitCodeSizeExceeded, len(tx.Data()), params.MaxInitCodeSize) + if rules.IsShanghai && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSizeEIP7907 { + return fmt.Errorf("%w: code size %v, limit %v", core.ErrMaxInitCodeSizeExceeded, len(tx.Data()), params.MaxInitCodeSizeEIP7907) } // Transactions can't be negative. This may never happen using RLP decoded // transactions but may occur for transactions created using the RPC. diff --git a/core/vm/eips.go b/core/vm/eips.go index 79fd24d13eec..76c5d47211a3 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -41,6 +41,7 @@ var activators = map[int]func(*JumpTable){ 1153: enable1153, 4762: enable4762, 7702: enable7702, + 7907: enable7907, } // EnableEIP enables the given EIP on the config. @@ -538,3 +539,14 @@ func enable7702(jt *JumpTable) { jt[STATICCALL].dynamicGas = gasStaticCallEIP7702 jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP7702 } + +// enable7907 the EIP-7907 changes to support large contracts. +func enable7907(jt *JumpTable) { + jt[CALL].dynamicGas = gasCallEIP7907 + jt[CALLCODE].dynamicGas = gasCallCodeEIP7907 + jt[STATICCALL].dynamicGas = gasStaticCallEIP7907 + jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP7907 + jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP7907 + jt[CREATE].dynamicGas = gasCreateEip7907 + jt[CREATE2].dynamicGas = gasCreate2Eip7907 +} diff --git a/core/vm/evm.go b/core/vm/evm.go index b45a43454531..e25df1b9b5b8 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -496,6 +496,10 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui } gas = gas - consumed } + // The contract code is added to the access list _after_ the contract code is successfully deployed. + if evm.chainRules.IsOsaka { + evm.StateDB.AddAddressCodeToAccessList(address) + } evm.Context.Transfer(evm.StateDB, caller, address, value) // Initialise a new contract and set the code that is to be used by the EVM. @@ -526,7 +530,10 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b } // Check whether the max code size has been exceeded, assign err if the case. - if evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { + if evm.chainRules.IsOsaka && len(ret) > params.MaxCodeSizeEIP7907 { + return ret, ErrMaxCodeSizeExceeded + } + if !evm.chainRules.IsOsaka && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSizeEIP170 { return ret, ErrMaxCodeSizeExceeded } diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 58f039df9ff7..ca3a1a595ed4 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -314,7 +314,7 @@ func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m if overflow { return 0, ErrGasUintOverflow } - if size > params.MaxInitCodeSize { + if size > params.MaxInitCodeSizeEIP3860 { return 0, fmt.Errorf("%w: size %d", ErrMaxInitCodeSizeExceeded, size) } // Since size <= params.MaxInitCodeSize, these multiplication cannot overflow @@ -333,7 +333,46 @@ func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, if overflow { return 0, ErrGasUintOverflow } - if size > params.MaxInitCodeSize { + if size > params.MaxInitCodeSizeEIP3860 { + return 0, fmt.Errorf("%w: size %d", ErrMaxInitCodeSizeExceeded, size) + } + // Since size <= params.MaxInitCodeSize, these multiplication cannot overflow + moreGas := (params.InitCodeWordGas + params.Keccak256WordGas) * ((size + 31) / 32) + if gas, overflow = math.SafeAdd(gas, moreGas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} + +func gasCreateEip7907(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + size, overflow := stack.Back(2).Uint64WithOverflow() + if overflow { + return 0, ErrGasUintOverflow + } + if size > params.MaxInitCodeSizeEIP7907 { + return 0, fmt.Errorf("%w: size %d", ErrMaxInitCodeSizeExceeded, size) + } + // Since size <= params.MaxInitCodeSize, these multiplication cannot overflow + moreGas := params.InitCodeWordGas * ((size + 31) / 32) + if gas, overflow = math.SafeAdd(gas, moreGas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} +func gasCreate2Eip7907(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + size, overflow := stack.Back(2).Uint64WithOverflow() + if overflow { + return 0, ErrGasUintOverflow + } + if size > params.MaxInitCodeSizeEIP7907 { return 0, fmt.Errorf("%w: size %d", ErrMaxInitCodeSizeExceeded, size) } // Since size <= params.MaxInitCodeSize, these multiplication cannot overflow diff --git a/core/vm/interface.go b/core/vm/interface.go index 86e8c56ab0e3..7b2e5e596326 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -76,6 +76,7 @@ type StateDB interface { Empty(common.Address) bool AddressInAccessList(addr common.Address) bool + AddressCodeInAccessList(addr common.Address) bool SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) // AddAddressToAccessList adds the given address to the access list. This operation is safe to perform // even if the feature/fork is not active yet @@ -83,6 +84,9 @@ type StateDB interface { // AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform // even if the feature/fork is not active yet AddSlotToAccessList(addr common.Address, slot common.Hash) + // AddAddressCodeToAccessList adds the given address code to the access list. This operation is safe to perform + // even if the feature/fork is not active yet + AddAddressCodeToAccessList(addr common.Address) // PointCache returns the point cache used in computations PointCache() *utils.PointCache diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index d0e5967e6e77..bd499750eba3 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -109,6 +109,8 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { case evm.chainRules.IsVerkle: // TODO replace with proper instruction set when fork is specified table = &verkleInstructionSet + case evm.chainRules.IsOsaka: + table = &osakaInstructionSet case evm.chainRules.IsPrague: table = &pragueInstructionSet case evm.chainRules.IsCancun: diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 17ac738c9887..bb2e40d2b375 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -62,6 +62,7 @@ var ( cancunInstructionSet = newCancunInstructionSet() verkleInstructionSet = newVerkleInstructionSet() pragueInstructionSet = newPragueInstructionSet() + osakaInstructionSet = newOsakaInstructionSet() ) // JumpTable contains the EVM opcodes supported at a given fork. @@ -91,6 +92,12 @@ func newVerkleInstructionSet() JumpTable { return validate(instructionSet) } +func newOsakaInstructionSet() JumpTable { + instructionSet := newPragueInstructionSet() + enable7907(&instructionSet) + return validate(instructionSet) +} + func newPragueInstructionSet() JumpTable { instructionSet := newCancunInstructionSet() enable7702(&instructionSet) // EIP-7702 Setcode transaction type diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index ff3875868f56..070b651280bc 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -18,6 +18,7 @@ package vm import ( "errors" + "fmt" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" @@ -310,3 +311,142 @@ func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc { return total, nil } } + +var ( + gasCallEIP7907 = makeCallVariantGasCallEIP7907(gasCall) + gasDelegateCallEIP7907 = makeCallVariantGasCallEIP7907(gasDelegateCall) + gasStaticCallEIP7907 = makeCallVariantGasCallEIP7907(gasStaticCall) + gasCallCodeEIP7907 = makeCallVariantGasCallEIP7907(gasCallCode) +) + +// Rounds n up to the nearest multiple of 32. +func ceil32(n int) uint64 { + r := n % 32 + if r == 0 { + return uint64(n) + } else { + return uint64(n + 32 - r) + } +} + +func calcColdCodeAccessGasCost(evm *EVM, addr common.Address) uint64 { + size := evm.StateDB.GetCodeSize(addr) + // Only charge additional access cost for contracts larger than old limit. + if size <= params.MaxCodeSizeEIP170 { + return 0 + } + excess := ceil32(size - params.MaxCodeSizeEIP170) + fmt.Println("excess", excess, "cost", (excess*params.CodeReadPerWordGasEIP7907)/32) + return (excess * params.CodeReadPerWordGasEIP7907) / 32 +} + +func makeCallVariantGasCallEIP7907(oldCalculator gasFunc) gasFunc { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var ( + total uint64 // total dynamic gas used + addr = common.Address(stack.Back(1).Bytes20()) + ) + + // Check slot presence in the access list + if !evm.StateDB.AddressInAccessList(addr) { + evm.StateDB.AddAddressToAccessList(addr) + // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so + // the cost to charge for cold access, if any, is Cold - Warm + coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 + // Charge the remaining difference here already, to correctly calculate available + // gas for call + if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { + return 0, ErrOutOfGas + } + total += coldCost + } + + // Check code presence in the access list + if !evm.StateDB.AddressCodeInAccessList(addr) { + cost := calcColdCodeAccessGasCost(evm, addr) + evm.StateDB.AddAddressCodeToAccessList(addr) + if !contract.UseGas(cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { + return 0, ErrOutOfGas + } + total += cost + } + + // TODO: reading code here would defeat the purpose of separate charging, so + // we should first see if the code size is 23 bytes before parsing. + + // Check if code is a delegation and if so, charge for resolution. + if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok { + var cost uint64 + if evm.StateDB.AddressInAccessList(target) { + cost = params.WarmStorageReadCostEIP2929 + } else { + evm.StateDB.AddAddressToAccessList(target) + cost = params.ColdAccountAccessCostEIP2929 + } + if !contract.UseGas(cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { + return 0, ErrOutOfGas + } + total += cost + if !evm.StateDB.AddressCodeInAccessList(target) { + cost = calcColdCodeAccessGasCost(evm, target) + evm.StateDB.AddAddressCodeToAccessList(target) + if !contract.UseGas(cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { + return 0, ErrOutOfGas + } + total += cost + } + } + + // Now call the old calculator, which takes into account + // - create new account + // - transfer value + // - memory expansion + // - 63/64ths rule + old, err := oldCalculator(evm, contract, stack, mem, memorySize) + if err != nil { + return old, err + } + + // Temporarily add the gas charge back to the contract and return value. By + // adding it to the return, it will be charged outside of this function, as + // part of the dynamic gas. This will ensure it is correctly reported to + // tracers. + contract.Gas += total + + var overflow bool + if total, overflow = math.SafeAdd(old, total); overflow { + return 0, ErrGasUintOverflow + } + return total, nil + } +} + +func gasExtCodeCopyEIP7907(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // memory expansion first (dynamic part of pre-2929 implementation) + gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err + } + addr := common.Address(stack.peek().Bytes20()) + // Check slot presence in the access list + if !evm.StateDB.AddressInAccessList(addr) { + evm.StateDB.AddAddressToAccessList(addr) + var overflow bool + // We charge (cold-warm), since 'warm' is already charged as constantGas + if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929-params.WarmStorageReadCostEIP2929); overflow { + return 0, ErrGasUintOverflow + } + } + + // Check address code presence in the access list + if !evm.StateDB.AddressCodeInAccessList(addr) { + cost := calcColdCodeAccessGasCost(evm, addr) + evm.StateDB.AddAddressCodeToAccessList(addr) + var overflow bool + // We charge (cold-warm), since 'warm' is already charged as constantGas + if gas, overflow = math.SafeAdd(gas, cost); overflow { + return 0, ErrGasUintOverflow + } + } + return gas, nil +} diff --git a/params/protocol_params.go b/params/protocol_params.go index 6b06dadaef17..408c8801d477 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -132,8 +132,12 @@ const ( DefaultElasticityMultiplier = 2 // Bounds the maximum gas limit an EIP-1559 block may have. InitialBaseFee = 1000000000 // Initial base fee for EIP-1559 blocks. - MaxCodeSize = 24576 // Maximum bytecode to permit for a contract - MaxInitCodeSize = 2 * MaxCodeSize // Maximum initcode to permit in a creation transaction and create instructions + MaxCodeSizeEIP170 = 24576 // Maximum bytecode to permit for a contract + MaxInitCodeSizeEIP3860 = 2 * MaxCodeSizeEIP170 // Maximum initcode to permit in a creation transaction and create instructions + + MaxCodeSizeEIP7907 = 262144 // Maximum bytecode permitted per contract after EIP-7907 + MaxInitCodeSizeEIP7907 = 2 * MaxCodeSizeEIP7907 // Maximum initcode to permit in a creation transaction and create instructions + CodeReadPerWordGasEIP7907 = 2 // Cost per word to read code from disk. // Precompiled contract gas prices