From 2c133df35b7c5885381ae36cbca8873572802b64 Mon Sep 17 00:00:00 2001 From: lightclient Date: Mon, 27 Nov 2023 08:16:02 -0700 Subject: [PATCH] core: implement auth and authcall --- core/blockchain_test.go | 129 +++++++++++++++++++++++++++++++++++ core/vm/eips.go | 109 +++++++++++++++++++++++++++++ core/vm/errors.go | 1 + core/vm/evm.go | 67 ++++++++++++++++++ core/vm/gas.go | 15 ++++ core/vm/gas_table.go | 35 ++++++++++ core/vm/instructions_test.go | 56 +++++++++++---- core/vm/interpreter.go | 9 ++- core/vm/jump_table.go | 7 ++ core/vm/memory_table.go | 4 ++ core/vm/opcodes.go | 7 ++ core/vm/operations_acl.go | 24 ++++++- params/config.go | 1 + params/protocol_params.go | 3 + 14 files changed, 451 insertions(+), 16 deletions(-) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index e4bc3e09a6..d470889a64 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -17,6 +17,7 @@ package core import ( + "bytes" "errors" "fmt" "math/big" @@ -4221,3 +4222,131 @@ func TestEIP3651(t *testing.T) { t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual) } } + +func TestEIP3074(t *testing.T) { + var ( + aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") + bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") + engine = beacon.NewFaker() + + // A sender who makes transactions, has some funds + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr = crypto.PubkeyToAddress(key.PublicKey) + funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) + config = *params.AllEthashProtocolChanges + gspec = &Genesis{ + Config: &config, + Alloc: GenesisAlloc{ + addr: {Balance: funds}, + // The address 0xAAAA sloads 0x00 and 0x01 + aa: { + Code: nil, // added below + Nonce: 0, + Balance: big.NewInt(0), + }, + // The address 0xBBBB calls 0xAAAA + bb: { + Code: []byte{ + byte(vm.CALLER), + byte(vm.PUSH0), + byte(vm.SSTORE), + byte(vm.STOP), + }, + Nonce: 0, + Balance: big.NewInt(0), + }, + }, + } + ) + + invoker := []byte{ + // copy sig to memory + byte(vm.CALLDATASIZE), + byte(vm.PUSH0), + byte(vm.PUSH0), + byte(vm.CALLDATACOPY), + + // set up auth + byte(vm.CALLDATASIZE), + byte(vm.PUSH0), + } + // push authority to stack + invoker = append(invoker, append([]byte{byte(vm.PUSH20)}, addr.Bytes()...)...) + invoker = append(invoker, []byte{ + + byte(vm.AUTH), + byte(vm.POP), + + // execute authcall + byte(vm.PUSH0), // out size + byte(vm.DUP1), // out offset + byte(vm.DUP1), // out insize + byte(vm.DUP1), // in offset + byte(vm.DUP1), // valueExt + byte(vm.DUP1), // value + byte(vm.PUSH2), // address + byte(0xbb), + byte(0xbb), + byte(vm.GAS), // gas + byte(vm.AUTHCALL), + byte(vm.STOP), + }..., + ) + + // Set the invoker's code. + if entry, _ := gspec.Alloc[aa]; true { + entry.Code = invoker + gspec.Alloc[aa] = entry + } + + gspec.Config.BerlinBlock = common.Big0 + gspec.Config.LondonBlock = common.Big0 + gspec.Config.TerminalTotalDifficulty = common.Big0 + gspec.Config.TerminalTotalDifficultyPassed = true + gspec.Config.ShanghaiTime = u64(0) + gspec.Config.CancunTime = u64(0) + gspec.Config.PragueTime = u64(0) + signer := types.LatestSigner(gspec.Config) + + _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { + commit := common.Hash{0x42} + msg := []byte{params.AuthMagic} + msg = append(msg, common.LeftPadBytes(gspec.Config.ChainID.Bytes(), 32)...) + msg = append(msg, common.LeftPadBytes(common.Big1.Bytes(), 32)...) + msg = append(msg, common.LeftPadBytes(aa.Bytes(), 32)...) + msg = append(msg, commit.Bytes()...) + msg = crypto.Keccak256(msg) + + sig, _ := crypto.Sign(msg, key) + sig = append([]byte{sig[len(sig)-1]}, sig[0:len(sig)-1]...) + txdata := &types.DynamicFeeTx{ + ChainID: gspec.Config.ChainID, + Nonce: 0, + To: &aa, + Gas: 500000, + GasFeeCap: newGwei(5), + GasTipCap: big.NewInt(2), + AccessList: nil, + Data: append(sig, commit.Bytes()...), + } + tx := types.NewTx(txdata) + tx, _ = types.SignTx(tx, signer, key) + + b.AddTx(tx) + }) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{}, os.Stderr).Hooks()}, nil, 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 authcall worked correctly. + state, _ := chain.State() + got := state.GetState(bb, common.Hash{}) + if want := common.LeftPadBytes(addr.Bytes(), 32); !bytes.Equal(got.Bytes(), want) { + t.Fatalf("incorrect sender in authcall: got %s, want %s", got.Hex(), common.Bytes2Hex(want)) + } +} diff --git a/core/vm/eips.go b/core/vm/eips.go index edd6ec8d0a..c1ba542a9c 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -39,6 +40,7 @@ var activators = map[int]func(*JumpTable){ 1884: enable1884, 1344: enable1344, 1153: enable1153, + 3074: enable3074, 4762: enable4762, } @@ -533,3 +535,110 @@ func enable4762(jt *JumpTable) { } } } + +func enable3074(jt *JumpTable) { + jt[AUTH] = &operation{ + execute: opAuth, + constantGas: 3100 + params.WarmStorageReadCostEIP2929, + dynamicGas: gasAuthEIP2929, + minStack: minStack(3, 1), + maxStack: maxStack(3, 1), + memorySize: memoryAuth, + } + jt[AUTHCALL] = &operation{ + execute: opAuthCall, + constantGas: params.WarmStorageReadCostEIP2929, + dynamicGas: gasAuthCallEIP2929, + minStack: minStack(8, 1), + maxStack: maxStack(8, 1), + memorySize: memoryCall, + } +} + +// opAuth implements the EIP-3074 AUTH instruction. +func opAuth(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + tmp = scope.Stack.pop() + authority = common.Address(tmp.Bytes20()) + offset = scope.Stack.pop() + length = scope.Stack.pop() + data = scope.Memory.GetPtr(int64(offset.Uint64()), int64(length.Uint64())) + sig = make([]byte, 65) + commit common.Hash + ) + copy(sig, data) + if len(data) > 65 { + copy(commit[:], data[65:]) + } + + // If the desired authority has code, the operation must be considered + // unsuccessful. + statedb := interpreter.evm.StateDB + if statedb.GetCodeSize(authority) != 0 { + scope.Authorized = nil + scope.Stack.push(uint256.NewInt(0)) + return nil, nil + } + + // Build original auth message. + msg := []byte{params.AuthMagic} + msg = append(msg, common.LeftPadBytes(interpreter.evm.chainConfig.ChainID.Bytes(), 32)...) + msg = append(msg, common.LeftPadBytes(uint256.NewInt(statedb.GetNonce(authority)).Bytes(), 32)...) + msg = append(msg, common.LeftPadBytes(scope.Contract.Address().Bytes(), 32)...) + msg = append(msg, commit.Bytes()...) + msg = crypto.Keccak256(msg) + + // Verify signature against provided address. + sig = append(sig[1:], sig[0]) // send y parity to back + pub, err := crypto.Ecrecover(msg, sig) + if err != nil { + scope.Authorized = nil + scope.Stack.push(uint256.NewInt(0)) + return nil, nil + } + + // Check recovered matches expected authority. + var recovered common.Address + copy(recovered[:], crypto.Keccak256(pub[1:])[12:]) + if recovered != authority { + scope.Authorized = nil + scope.Stack.push(uint256.NewInt(0)) + return nil, nil + } + + scope.Stack.push(uint256.NewInt(1)) + scope.Authorized = &authority + return nil, nil +} + +// opAuthCall implements the EIP-3074 AUTHCALL instruction. +func opAuthCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if scope.Authorized == nil { + return nil, ErrAuthorizedNotSet + } + var ( + stack = scope.Stack + temp = stack.pop() + gas = interpreter.evm.callGasTemp + addr, value, _, inOffset, inSize, retOffset, retSize = stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() + toAddr = common.Address(addr.Bytes20()) + args = scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + ) + if interpreter.readOnly && !value.IsZero() { + return nil, ErrWriteProtection + } + ret, returnGas, err := interpreter.evm.AuthCall(scope.Contract, *scope.Authorized, toAddr, args, gas, &value) + if err != nil { + temp.Clear() + } else { + temp.SetOne() + } + stack.push(&temp) + if err == nil || err == ErrExecutionReverted { + scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + } + scope.Contract.Gas += returnGas + + interpreter.returnData = ret + return ret, nil +} diff --git a/core/vm/errors.go b/core/vm/errors.go index e5efc952d4..ed6236fa06 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -38,6 +38,7 @@ var ( ErrGasUintOverflow = errors.New("gas uint64 overflow") ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") + ErrAuthorizedNotSet = errors.New("authcall without setting authorized") // errStopToken is an internal token indicating interpreter loop termination, // never returned to outside callers. diff --git a/core/vm/evm.go b/core/vm/evm.go index 26af0ea041..99583410dd 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -420,6 +420,73 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte return ret, gas, err } +// AuthCall mimic Call except it sets the caller to the Authorized addres$ in Scope. +func (evm *EVM) AuthCall(invoker ContractRef, caller, addr common.Address, input []byte, gas uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) { + // Capture the tracer start/end events in debug mode + if evm.Config.Tracer != nil { + evm.captureBegin(evm.depth, CALL, caller, addr, input, gas, value.ToBig()) + defer func(startGas uint64) { + evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) + }(gas) + } + // Fail if we're trying to execute above the call depth limit + if evm.depth > int(params.CallCreateDepth) { + return nil, gas, ErrDepth + } + // Fail if we're trying to transfer more than the available balance + if !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller, value) { + return nil, gas, ErrInsufficientBalance + } + snapshot := evm.StateDB.Snapshot() + p, isPrecompile := evm.precompile(addr) + + if !evm.StateDB.Exist(addr) { + if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() { + // Calling a non-existing account, don't do anything. + return nil, gas, nil + } + evm.StateDB.CreateAccount(addr) + } + evm.Context.Transfer(evm.StateDB, caller, addr, value) + + if isPrecompile { + ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) + } else { + // 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. + code := evm.StateDB.GetCode(addr) + if len(code) == 0 { + ret, err = nil, nil // gas is unchanged + } else { + addrCopy := addr + callerCopy := caller + // If the account has no code, we can abort here + // The depth-check is already done, and precompiles handled above + contract := NewContract(AccountRef(callerCopy), AccountRef(addrCopy), value, gas) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) + ret, err = evm.interpreter.Run(contract, input, false) + gas = contract.Gas + } + } + // When an error was returned by the EVM or when setting the creation code + // above we revert to the snapshot and consume any gas remaining. Additionally, + // when we're in homestead this also counts for code storage gas errors. + if err != nil { + evm.StateDB.RevertToSnapshot(snapshot) + if err != ErrExecutionReverted { + if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { + evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution) + } + + gas = 0 + } + // TODO: consider clearing up unused snapshots: + //} else { + // evm.StateDB.DiscardSnapshot(snapshot) + } + return ret, gas, err +} + type codeAndHash struct { code []byte hash common.Hash diff --git a/core/vm/gas.go b/core/vm/gas.go index 5cf1d852d2..76a38bc118 100644 --- a/core/vm/gas.go +++ b/core/vm/gas.go @@ -51,3 +51,18 @@ func callGas(isEip150 bool, availableGas, base uint64, callCost *uint256.Int) (u return callCost.Uint64(), nil } + +func authCallGas(availableGas, base uint64, callCost *uint256.Int) (uint64, error) { + availableGas = availableGas - base + gas := availableGas - availableGas/64 + // If the bit length exceeds 64 bit we know that the newly calculated "gas" for EIP150 + // is smaller than the requested amount. Therefore we return the new gas instead + // of returning an error. + if !callCost.IsUint64() || gas < callCost.Uint64() { + return gas, nil + } + if !callCost.IsUint64() { + return 0, ErrGasUintOverflow + } + return callCost.Uint64(), nil +} diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index d294324b08..7ca474407b 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -480,6 +480,41 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo return gas, nil } +func gasAuthCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var ( + gas uint64 + transfersValue = !stack.Back(2).IsZero() + address = common.Address(stack.Back(1).Bytes20()) + ) + if transfersValue { + if evm.StateDB.Empty(address) { + gas += params.CallNewAccountGas + } else { + gas += params.CallValueTransferGas - params.CallStipend + } + } + memoryGas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + var overflow bool + if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { + return 0, ErrGasUintOverflow + } + + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + if err != nil { + return 0, err + } + if gas == 0 { + evm.callGasTemp = contract.Gas + } + if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} + func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var gas uint64 // EIP150 homestead gas reprice fork: diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index e17e913aa3..4538b18f83 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -117,7 +117,7 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected)) stack.push(x) stack.push(y) - opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, nil}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data)) } @@ -232,7 +232,7 @@ func TestAddMod(t *testing.T) { stack.push(z) stack.push(y) stack.push(x) - opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, nil}) actual := stack.pop() if actual.Cmp(expected) != 0 { t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual) @@ -259,7 +259,7 @@ func TestWriteExpectedValues(t *testing.T) { y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y)) stack.push(x) stack.push(y) - opFn(&pc, interpreter, &ScopeContext{nil, stack, nil}) + opFn(&pc, interpreter, &ScopeContext{nil, stack, nil, nil}) actual := stack.pop() result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)} } @@ -295,7 +295,7 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) { var ( env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack = newstack() - scope = &ScopeContext{nil, stack, nil} + scope = &ScopeContext{nil, stack, nil, nil} evmInterpreter = NewEVMInterpreter(env) ) @@ -546,13 +546,13 @@ func TestOpMstore(t *testing.T) { v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v))) stack.push(new(uint256.Int)) - opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, nil}) if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v { t.Fatalf("Mstore fail, got %v, expected %v", got, v) } stack.push(new(uint256.Int).SetUint64(0x1)) stack.push(new(uint256.Int)) - opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, nil}) if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" { t.Fatalf("Mstore failed to overwrite previous value") } @@ -576,7 +576,7 @@ func BenchmarkOpMstore(bench *testing.B) { for i := 0; i < bench.N; i++ { stack.push(value) stack.push(memStart) - opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, nil}) } } @@ -591,7 +591,7 @@ func TestOpTstore(t *testing.T) { to = common.Address{1} contractRef = contractRef{caller} contract = NewContract(contractRef, AccountRef(to), new(uint256.Int), 0) - scopeContext = ScopeContext{mem, stack, contract} + scopeContext = ScopeContext{mem, stack, contract, nil} value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700") ) @@ -639,7 +639,7 @@ func BenchmarkOpKeccak256(bench *testing.B) { for i := 0; i < bench.N; i++ { stack.push(uint256.NewInt(32)) stack.push(start) - opKeccak256(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + opKeccak256(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, nil}) } } @@ -734,7 +734,7 @@ func TestRandom(t *testing.T) { pc = uint64(0) evmInterpreter = env.interpreter ) - opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, nil}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) } @@ -776,7 +776,7 @@ func TestBlobHash(t *testing.T) { evmInterpreter = env.interpreter ) stack.push(uint256.NewInt(tt.idx)) - opBlobHash(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + opBlobHash(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, nil}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) } @@ -917,7 +917,7 @@ func TestOpMCopy(t *testing.T) { mem.Resize(memorySize) } // Do the copy - opMcopy(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + opMcopy(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, nil}) want := common.FromHex(strings.ReplaceAll(tc.want, " ", "")) if have := mem.store; !bytes.Equal(want, have) { t.Errorf("case %d: \nwant: %#x\nhave: %#x\n", i, want, have) @@ -928,3 +928,35 @@ func TestOpMCopy(t *testing.T) { } } } + +func TestEIP3074(t *testing.T) { + var ( + statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + env = NewEVM(BlockContext{}, TxContext{}, statedb, params.TestChainConfig, Config{}) + stack = newstack() + pc = uint64(0) + evmInterpreter = env.interpreter + contractRef = contractRef{common.Address{42}} + contract = NewContract(contractRef, AccountRef(common.Address{13, 37}), new(uint256.Int), 0) + + data = make([]byte, 65) + ) + + // Set pre + mem := NewMemory() + mem.Resize(uint64(len(data))) + mem.Set(0, uint64(len(data)), data) + + // Push stack args + zero := new(uint256.Int) + len := uint256.NewInt(uint64(len(data))) + + stack.push(zero) + stack.push(zero) + stack.push(len) + + _, err := opAuth(&pc, evmInterpreter, &ScopeContext{mem, stack, contract, nil}) + if err != nil { + t.Fatalf("unexpected error in auth: %v", err) + } +} diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 66a20f434e..ad176d825d 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -38,9 +38,10 @@ type Config struct { // ScopeContext contains the things that are per-call, such as stack and memory, // but not transients like pc and gas type ScopeContext struct { - Memory *Memory - Stack *Stack - Contract *Contract + Memory *Memory + Stack *Stack + Contract *Contract + Authorized *common.Address } // MemoryData returns the underlying memory slice. Callers must not modify the contents @@ -102,6 +103,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.IsPrague: + table = &pragueInstructionSet case evm.chainRules.IsCancun: table = &cancunInstructionSet case evm.chainRules.IsShanghai: diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 5624f47ba7..54c9d28002 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -57,6 +57,7 @@ var ( mergeInstructionSet = newMergeInstructionSet() shanghaiInstructionSet = newShanghaiInstructionSet() cancunInstructionSet = newCancunInstructionSet() + pragueInstructionSet = newPraugeInstructionSet() verkleInstructionSet = newVerkleInstructionSet() ) @@ -87,6 +88,12 @@ func newVerkleInstructionSet() JumpTable { return validate(instructionSet) } +func newPraugeInstructionSet() JumpTable { + instructionSet := newCancunInstructionSet() + enable3074(&instructionSet) // EIP-3074 AUTH & AUTHCALL + return validate(instructionSet) +} + func newCancunInstructionSet() JumpTable { instructionSet := newShanghaiInstructionSet() enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode) diff --git a/core/vm/memory_table.go b/core/vm/memory_table.go index 61a910a03d..a0cb99e14b 100644 --- a/core/vm/memory_table.go +++ b/core/vm/memory_table.go @@ -56,6 +56,10 @@ func memoryMcopy(stack *Stack) (uint64, bool) { return calcMemSize64(mStart, stack.Back(2)) // stack[2]: length } +func memoryAuth(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(1), stack.Back(2)) +} + func memoryCreate(stack *Stack) (uint64, bool) { return calcMemSize64(stack.Back(1), stack.Back(2)) } diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 2b9231fe1a..aad4ad68f2 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -218,6 +218,9 @@ const ( DELEGATECALL OpCode = 0xf4 CREATE2 OpCode = 0xf5 + AUTH OpCode = 0xf6 + AUTHCALL OpCode = 0xf7 + STATICCALL OpCode = 0xfa REVERT OpCode = 0xfd INVALID OpCode = 0xfe @@ -391,6 +394,8 @@ var opCodeToString = [256]string{ CALLCODE: "CALLCODE", DELEGATECALL: "DELEGATECALL", CREATE2: "CREATE2", + AUTH: "AUTH", + AUTHCALL: "AUTHCALL", STATICCALL: "STATICCALL", REVERT: "REVERT", INVALID: "INVALID", @@ -548,6 +553,8 @@ var stringToOp = map[string]OpCode{ "LOG4": LOG4, "CREATE": CREATE, "CREATE2": CREATE2, + "AUTH": AUTH, + "AUTHCALL": AUTHCALL, "CALL": CALL, "RETURN": RETURN, "CALLCODE": CALLCODE, diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 289da44be3..8c9a950459 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" @@ -47,7 +48,8 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { // Once we're done with YOLOv2 and schedule this for mainnet, might // be good to remove this panic here, which is just really a // canary to have during testing - panic("impossible case: address was not present in access list during sstore op") + + panic(fmt.Sprintf("impossible case: address was not present in access list during sstore op %s", contract.Address())) } } value := common.Hash(y.Bytes32()) @@ -205,6 +207,7 @@ var ( gasSelfdestructEIP2929 = makeSelfdestructGasFn(true) // gasSelfdestructEIP3529 implements the changes in EIP-3529 (no refunds) gasSelfdestructEIP3529 = makeSelfdestructGasFn(false) + gasAuthCallEIP2929 = makeCallVariantGasCallEIP2929(gasAuthCall) // gasSStoreEIP2929 implements gas cost for SSTORE according to EIP-2929 // @@ -248,3 +251,22 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { } return gasFunc } + +func gasAuthEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(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 + } + return gas, nil + } + return gas, nil +} diff --git a/params/config.go b/params/config.go index 871782399d..75f42515be 100644 --- a/params/config.go +++ b/params/config.go @@ -184,6 +184,7 @@ var ( GrayGlacierBlock: big.NewInt(0), ShanghaiTime: newUint64(0), CancunTime: newUint64(0), + PragueTime: newUint64(0), TerminalTotalDifficulty: big.NewInt(0), TerminalTotalDifficultyPassed: true, } diff --git a/params/protocol_params.go b/params/protocol_params.go index d375a96642..baba13f9d1 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -189,4 +189,7 @@ var ( BeaconRootsAddress = common.HexToAddress("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") // SystemAddress is where the system-transaction is sent from as per EIP-4788 SystemAddress = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe") + + // Magic prefix for EIP-3074 AUTH messages. + AuthMagic = byte(0x04) )