core: implement auth and authcall

This commit is contained in:
lightclient 2023-11-27 08:16:02 -07:00
parent 5b3e3cd2be
commit 2c133df35b
No known key found for this signature in database
GPG key ID: 75C916AFEE20183E
14 changed files with 451 additions and 16 deletions

View file

@ -17,6 +17,7 @@
package core package core
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
@ -4221,3 +4222,131 @@ func TestEIP3651(t *testing.T) {
t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual) 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))
}
}

View file

@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -39,6 +40,7 @@ var activators = map[int]func(*JumpTable){
1884: enable1884, 1884: enable1884,
1344: enable1344, 1344: enable1344,
1153: enable1153, 1153: enable1153,
3074: enable3074,
4762: enable4762, 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
}

View file

@ -38,6 +38,7 @@ var (
ErrGasUintOverflow = errors.New("gas uint64 overflow") ErrGasUintOverflow = errors.New("gas uint64 overflow")
ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") ErrInvalidCode = errors.New("invalid code: must not begin with 0xef")
ErrNonceUintOverflow = errors.New("nonce uint64 overflow") ErrNonceUintOverflow = errors.New("nonce uint64 overflow")
ErrAuthorizedNotSet = errors.New("authcall without setting authorized")
// errStopToken is an internal token indicating interpreter loop termination, // errStopToken is an internal token indicating interpreter loop termination,
// never returned to outside callers. // never returned to outside callers.

View file

@ -420,6 +420,73 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
return ret, gas, err 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 { type codeAndHash struct {
code []byte code []byte
hash common.Hash hash common.Hash

View file

@ -51,3 +51,18 @@ func callGas(isEip150 bool, availableGas, base uint64, callCost *uint256.Int) (u
return callCost.Uint64(), nil 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
}

View file

@ -480,6 +480,41 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
return gas, nil 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) { func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var gas uint64 var gas uint64
// EIP150 homestead gas reprice fork: // EIP150 homestead gas reprice fork:

View file

@ -117,7 +117,7 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu
expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected)) expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected))
stack.push(x) stack.push(x)
stack.push(y) stack.push(y)
opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, nil})
if len(stack.data) != 1 { if len(stack.data) != 1 {
t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data)) 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(z)
stack.push(y) stack.push(y)
stack.push(x) stack.push(x)
opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, nil})
actual := stack.pop() actual := stack.pop()
if actual.Cmp(expected) != 0 { if actual.Cmp(expected) != 0 {
t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual) 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)) y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y))
stack.push(x) stack.push(x)
stack.push(y) stack.push(y)
opFn(&pc, interpreter, &ScopeContext{nil, stack, nil}) opFn(&pc, interpreter, &ScopeContext{nil, stack, nil, nil})
actual := stack.pop() actual := stack.pop()
result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)} 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 ( var (
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
scope = &ScopeContext{nil, stack, nil} scope = &ScopeContext{nil, stack, nil, nil}
evmInterpreter = NewEVMInterpreter(env) evmInterpreter = NewEVMInterpreter(env)
) )
@ -546,13 +546,13 @@ func TestOpMstore(t *testing.T) {
v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700"
stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v))) stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v)))
stack.push(new(uint256.Int)) 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 { if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v {
t.Fatalf("Mstore fail, got %v, expected %v", got, v) t.Fatalf("Mstore fail, got %v, expected %v", got, v)
} }
stack.push(new(uint256.Int).SetUint64(0x1)) stack.push(new(uint256.Int).SetUint64(0x1))
stack.push(new(uint256.Int)) 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" { if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" {
t.Fatalf("Mstore failed to overwrite previous value") t.Fatalf("Mstore failed to overwrite previous value")
} }
@ -576,7 +576,7 @@ func BenchmarkOpMstore(bench *testing.B) {
for i := 0; i < bench.N; i++ { for i := 0; i < bench.N; i++ {
stack.push(value) stack.push(value)
stack.push(memStart) 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} to = common.Address{1}
contractRef = contractRef{caller} contractRef = contractRef{caller}
contract = NewContract(contractRef, AccountRef(to), new(uint256.Int), 0) contract = NewContract(contractRef, AccountRef(to), new(uint256.Int), 0)
scopeContext = ScopeContext{mem, stack, contract} scopeContext = ScopeContext{mem, stack, contract, nil}
value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700") value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700")
) )
@ -639,7 +639,7 @@ func BenchmarkOpKeccak256(bench *testing.B) {
for i := 0; i < bench.N; i++ { for i := 0; i < bench.N; i++ {
stack.push(uint256.NewInt(32)) stack.push(uint256.NewInt(32))
stack.push(start) 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) pc = uint64(0)
evmInterpreter = env.interpreter evmInterpreter = env.interpreter
) )
opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, nil})
if len(stack.data) != 1 { if len(stack.data) != 1 {
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) 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 evmInterpreter = env.interpreter
) )
stack.push(uint256.NewInt(tt.idx)) 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 { if len(stack.data) != 1 {
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) 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) mem.Resize(memorySize)
} }
// Do the copy // 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, " ", "")) want := common.FromHex(strings.ReplaceAll(tc.want, " ", ""))
if have := mem.store; !bytes.Equal(want, have) { if have := mem.store; !bytes.Equal(want, have) {
t.Errorf("case %d: \nwant: %#x\nhave: %#x\n", i, 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)
}
}

View file

@ -38,9 +38,10 @@ type Config struct {
// ScopeContext contains the things that are per-call, such as stack and memory, // ScopeContext contains the things that are per-call, such as stack and memory,
// but not transients like pc and gas // but not transients like pc and gas
type ScopeContext struct { type ScopeContext struct {
Memory *Memory Memory *Memory
Stack *Stack Stack *Stack
Contract *Contract Contract *Contract
Authorized *common.Address
} }
// MemoryData returns the underlying memory slice. Callers must not modify the contents // 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: case evm.chainRules.IsVerkle:
// TODO replace with proper instruction set when fork is specified // TODO replace with proper instruction set when fork is specified
table = &verkleInstructionSet table = &verkleInstructionSet
case evm.chainRules.IsPrague:
table = &pragueInstructionSet
case evm.chainRules.IsCancun: case evm.chainRules.IsCancun:
table = &cancunInstructionSet table = &cancunInstructionSet
case evm.chainRules.IsShanghai: case evm.chainRules.IsShanghai:

View file

@ -57,6 +57,7 @@ var (
mergeInstructionSet = newMergeInstructionSet() mergeInstructionSet = newMergeInstructionSet()
shanghaiInstructionSet = newShanghaiInstructionSet() shanghaiInstructionSet = newShanghaiInstructionSet()
cancunInstructionSet = newCancunInstructionSet() cancunInstructionSet = newCancunInstructionSet()
pragueInstructionSet = newPraugeInstructionSet()
verkleInstructionSet = newVerkleInstructionSet() verkleInstructionSet = newVerkleInstructionSet()
) )
@ -87,6 +88,12 @@ func newVerkleInstructionSet() JumpTable {
return validate(instructionSet) return validate(instructionSet)
} }
func newPraugeInstructionSet() JumpTable {
instructionSet := newCancunInstructionSet()
enable3074(&instructionSet) // EIP-3074 AUTH & AUTHCALL
return validate(instructionSet)
}
func newCancunInstructionSet() JumpTable { func newCancunInstructionSet() JumpTable {
instructionSet := newShanghaiInstructionSet() instructionSet := newShanghaiInstructionSet()
enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode) enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode)

View file

@ -56,6 +56,10 @@ func memoryMcopy(stack *Stack) (uint64, bool) {
return calcMemSize64(mStart, stack.Back(2)) // stack[2]: length 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) { func memoryCreate(stack *Stack) (uint64, bool) {
return calcMemSize64(stack.Back(1), stack.Back(2)) return calcMemSize64(stack.Back(1), stack.Back(2))
} }

View file

@ -218,6 +218,9 @@ const (
DELEGATECALL OpCode = 0xf4 DELEGATECALL OpCode = 0xf4
CREATE2 OpCode = 0xf5 CREATE2 OpCode = 0xf5
AUTH OpCode = 0xf6
AUTHCALL OpCode = 0xf7
STATICCALL OpCode = 0xfa STATICCALL OpCode = 0xfa
REVERT OpCode = 0xfd REVERT OpCode = 0xfd
INVALID OpCode = 0xfe INVALID OpCode = 0xfe
@ -391,6 +394,8 @@ var opCodeToString = [256]string{
CALLCODE: "CALLCODE", CALLCODE: "CALLCODE",
DELEGATECALL: "DELEGATECALL", DELEGATECALL: "DELEGATECALL",
CREATE2: "CREATE2", CREATE2: "CREATE2",
AUTH: "AUTH",
AUTHCALL: "AUTHCALL",
STATICCALL: "STATICCALL", STATICCALL: "STATICCALL",
REVERT: "REVERT", REVERT: "REVERT",
INVALID: "INVALID", INVALID: "INVALID",
@ -548,6 +553,8 @@ var stringToOp = map[string]OpCode{
"LOG4": LOG4, "LOG4": LOG4,
"CREATE": CREATE, "CREATE": CREATE,
"CREATE2": CREATE2, "CREATE2": CREATE2,
"AUTH": AUTH,
"AUTHCALL": AUTHCALL,
"CALL": CALL, "CALL": CALL,
"RETURN": RETURN, "RETURN": RETURN,
"CALLCODE": CALLCODE, "CALLCODE": CALLCODE,

View file

@ -18,6 +18,7 @@ package vm
import ( import (
"errors" "errors"
"fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "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 // Once we're done with YOLOv2 and schedule this for mainnet, might
// be good to remove this panic here, which is just really a // be good to remove this panic here, which is just really a
// canary to have during testing // 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()) value := common.Hash(y.Bytes32())
@ -205,6 +207,7 @@ var (
gasSelfdestructEIP2929 = makeSelfdestructGasFn(true) gasSelfdestructEIP2929 = makeSelfdestructGasFn(true)
// gasSelfdestructEIP3529 implements the changes in EIP-3529 (no refunds) // gasSelfdestructEIP3529 implements the changes in EIP-3529 (no refunds)
gasSelfdestructEIP3529 = makeSelfdestructGasFn(false) gasSelfdestructEIP3529 = makeSelfdestructGasFn(false)
gasAuthCallEIP2929 = makeCallVariantGasCallEIP2929(gasAuthCall)
// gasSStoreEIP2929 implements gas cost for SSTORE according to EIP-2929 // gasSStoreEIP2929 implements gas cost for SSTORE according to EIP-2929
// //
@ -248,3 +251,22 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
} }
return 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
}

View file

@ -184,6 +184,7 @@ var (
GrayGlacierBlock: big.NewInt(0), GrayGlacierBlock: big.NewInt(0),
ShanghaiTime: newUint64(0), ShanghaiTime: newUint64(0),
CancunTime: newUint64(0), CancunTime: newUint64(0),
PragueTime: newUint64(0),
TerminalTotalDifficulty: big.NewInt(0), TerminalTotalDifficulty: big.NewInt(0),
TerminalTotalDifficultyPassed: true, TerminalTotalDifficultyPassed: true,
} }

View file

@ -189,4 +189,7 @@ var (
BeaconRootsAddress = common.HexToAddress("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") BeaconRootsAddress = common.HexToAddress("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02")
// SystemAddress is where the system-transaction is sent from as per EIP-4788 // SystemAddress is where the system-transaction is sent from as per EIP-4788
SystemAddress = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe") SystemAddress = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe")
// Magic prefix for EIP-3074 AUTH messages.
AuthMagic = byte(0x04)
) )