Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EIP-3074 Implementation #38

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3275,3 +3275,73 @@ func TestEIP1559Transition(t *testing.T) {
t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual)
}
}

func TestEIP3074AuthCall(t *testing.T) {
var (
aa = common.HexToAddress("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb")

engine = ethash.NewFaker()
db = rawdb.NewMemoryDatabase()
diskdb = rawdb.NewMemoryDatabase()
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr = crypto.PubkeyToAddress(key.PublicKey)
gspec = &Genesis{
Config: params.AllEthashProtocolChanges,
Alloc: GenesisAlloc{
addr: {Balance: big.NewInt(100000000000000)},
// authcall into bb
aa: {
Code: common.FromHex("7f794dd7b68f540151c21953cc5322e6df1b809eec12e561353832a5d68e14809a7f7aa455a9f8b84965a8c2f32e29dbb8147a913fffa1b02375c6ab28161e6ebf2560007fbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbf6506000808080808073000000000000000000000000000000000000bbbb6000f700"),
Nonce: 0,
Balance: big.NewInt(0),
},
// store the caller in 0x00
bb: {
Code: []byte{
byte(vm.CALLER),
byte(vm.PUSH1),
byte(0x00),
byte(vm.SSTORE),
},
Nonce: 0,
Balance: big.NewInt(0),
},
},
}
)

gspec.Config.BerlinBlock = common.Big0
gspec.Config.LondonBlock = common.Big0
gspec.Config.PuxiBlock = common.Big0
genesis := gspec.MustCommit(db)

blocks, _ := GenerateChain(gspec.Config, genesis, engine, db, 1, func(i int, b *BlockGen) {
signer := types.LatestSigner(gspec.Config)
tx, _ := types.SignNewTx(key, signer, &types.LegacyTx{
Nonce: 0,
To: &aa,
Gas: 100000,
GasPrice: newGwei(1),
})
b.AddTx(tx)
})

gspec.MustCommit(diskdb)

chain, err := NewBlockChain(diskdb, nil, gspec.Config, engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("failed to create tester chain: %v", err)
}
if n, err := chain.InsertChain(blocks); err != nil {
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
}

state, _ := chain.State()
caller := common.BytesToAddress(state.GetState(bb, common.Hash{}).Bytes())
expected := "0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B"

if caller.Hex() != expected {
t.Fatalf("wrong caller, got: %s, expected: %s", caller.Hex(), expected)
}
}
8 changes: 6 additions & 2 deletions core/vm/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,12 @@ func (ar AccountRef) Address() common.Address { return (common.Address)(ar) }
// the contract code, calling arguments. Contract implements ContractRef
type Contract struct {
// CallerAddress is the result of the caller which initialised this
// contract. However when the "call method" is delegated this value
// needs to be initialised to that of the caller's caller.
// contract. There are two cases where the caller may be overridden:
//
// 1. A DELEGATECALL will initialise the value to the caller's
// caller.
// 2. An AUTHCALL will initialise the value to last address
// authorized by AUTH.
CallerAddress common.Address
caller ContractRef
self ContractRef
Expand Down
101 changes: 101 additions & 0 deletions core/vm/eips.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ import (
"fmt"
"sort"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)

var activators = map[int]func(*JumpTable){
3529: enable3529,
3198: enable3198,
3074: enable3074,
2929: enable2929,
2200: enable2200,
1884: enable1884,
Expand Down Expand Up @@ -174,3 +177,101 @@ func opBaseFee(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
scope.Stack.push(baseFee)
return nil, nil
}

func enable3074(jt *JumpTable) {
jt[AUTH] = &operation{
execute: opAuth,
constantGas: params.AuthGasEIP3074,
minStack: minStack(4, 1),
maxStack: maxStack(4, 1),
}

jt[AUTHCALL] = &operation{
execute: opAuthCall,
constantGas: params.WarmStorageReadCostEIP2929,
dynamicGas: gasAuthCallEIP2929,
minStack: minStack(8, 1),
maxStack: maxStack(8, 1),
memorySize: memoryAuthCall,
returns: true,
}
}

func opAuth(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
stack := scope.Stack
commit, v, r, s := stack.pop(), stack.pop(), stack.pop(), stack.pop()

// Zero out the current authorized account. Only update it if an address
// is successfully recovered from the signature.
scope.Authorized = nil

if v.BitLen() < 8 && crypto.ValidateSignatureValues(byte(v.Uint64()), r.ToBig(), s.ToBig(), true) {
lightclient marked this conversation as resolved.
Show resolved Hide resolved
msg := make([]byte, 65)

// EIP-3074 messages are of the form
// keccak256(type ++ invoker ++ commit)
msg[0] = 0x03
copy(msg[13:33], scope.Contract.Address().Bytes())
commit.WriteToSlice(msg[33:65])
hash := crypto.Keccak256(msg)

sig := make([]byte, 65)
r.WriteToSlice(sig[0:32])
s.WriteToSlice(sig[32:64])
sig[64] = byte(v.Uint64())

pub, err := crypto.Ecrecover(hash[:], sig)

if err == nil {
var addr common.Address
copy(addr[:], crypto.Keccak256(pub[1:])[12:])
scope.Authorized = &addr
}
}

// reuse commit to push the result
temp := commit
if scope.Authorized != nil {
temp.SetBytes20(scope.Authorized.Bytes())
} else {
temp.Clear()
}

stack.push(&temp)
return nil, nil
}

func opAuthCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
// If no authorized account is set, revert.
if scope.Authorized == nil {
return nil, ErrNoAuthorizedAccount
}

stack := scope.Stack
// Pop gas. The actual gas in interpreter.evm.callGasTemp.
// We can use this as a temporary value
temp := stack.pop()
gas := interpreter.evm.callGasTemp
// Pop other call parameters.
addr, value, extValue, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
toAddr := common.Address(addr.Bytes20())
// Get the arguments from the memory.
args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))

ret, returnGas, err := interpreter.evm.AuthCall(scope.Contract, *scope.Authorized, toAddr, args, gas, value.ToBig(), extValue.ToBig())

if err == ErrInsufficientBalance {
return nil, ErrInsufficientBalance
} else 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

return ret, nil
}
5 changes: 5 additions & 0 deletions core/vm/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ var (
ErrReturnDataOutOfBounds = errors.New("return data out of bounds")
ErrGasUintOverflow = errors.New("gas uint64 overflow")
ErrInvalidCode = errors.New("invalid code: must not begin with 0xef")
ErrInvalidRetsub = errors.New("invalid retsub")
ErrReturnStackExceeded = errors.New("return stack limit reached")
ErrNoAuthorizedAccount = errors.New("authorized account not set")
ErrInsufficientAuthCallGas = errors.New("insufficient remaining gas for authcall")
ErrNonZeroExtValue = errors.New("non-zero external value")
)

// ErrStackUnderflow wraps an evm error when the items on the stack less
Expand Down
73 changes: 73 additions & 0 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,79 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
return ret, gas, err
}

// AuthCall executes the contract associated with the addr with the given input
// as parameters. It reverses the state in case of an execution error.
func (evm *EVM) AuthCall(caller ContractRef, from, addr common.Address, input []byte, gas uint64, value, extValue *big.Int) (ret []byte, leftOverGas uint64, err error) {
if evm.Config.NoRecursion && evm.depth > 0 {
return nil, gas, nil
}
// 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.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, 0, ErrInsufficientBalance
}
// Fail if we're trying to transfer value external to the caller
if extValue.Sign() != 0 {
return nil, gas, ErrNonZeroExtValue
}
snapshot := evm.StateDB.Snapshot()
p, isPrecompile := evm.precompile(addr)

if !evm.StateDB.Exist(addr) {
if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
// Calling a non existing account, don't do anything, but ping the tracer
if evm.Config.Debug && evm.depth == 0 {
evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
evm.Config.Tracer.CaptureEnd(ret, 0, 0, nil)
}
return nil, gas, nil
}
evm.StateDB.CreateAccount(addr)
}
evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)

// Capture the tracer start/end events in debug mode
if evm.Config.Debug && evm.depth == 0 {
evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters
evm.Config.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err)
}(gas, time.Now())
}

if isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
} 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
// If the account has no code, we can abort here
// The depth-check is already done, and precompiles handled above
contract := NewContract(caller, AccountRef(addrCopy), value, gas)
contract.CallerAddress = from
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 {
gas = 0
}
}
return ret, gas, err
}

type codeAndHash struct {
code []byte
hash common.Hash
Expand Down
12 changes: 11 additions & 1 deletion core/vm/gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,24 @@ const (
//
// The cost of gas was changed during the homestead price change HF.
// As part of EIP 150 (TangerineWhistle), the returned gas is gas - base * 63 / 64.
func callGas(isEip150 bool, availableGas, base uint64, callCost *uint256.Int) (uint64, error) {
func callGas(isEip150, isAuthCall bool, availableGas, base uint64, callCost *uint256.Int) (uint64, error) {
if isEip150 {
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() {
// AuthCall behaves differently than other call-like ops. If more gas is
// requested than is available, it throws.
if !isAuthCall {
return gas, nil
} else {
return 0, ErrInsufficientAuthCallGas
}
} else if isAuthCall && callCost.IsZero() {
// AuthCall has special behavior for 0 requested gas, in which case it passes
// in all available gas.
return gas, nil
}
}
Expand Down
39 changes: 35 additions & 4 deletions core/vm/gas_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
return 0, ErrGasUintOverflow
}

evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, false, contract.Gas, gas, stack.Back(0))
if err != nil {
return 0, err
}
Expand All @@ -375,7 +375,7 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
return 0, ErrGasUintOverflow
}
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, false, contract.Gas, gas, stack.Back(0))
if err != nil {
return 0, err
}
Expand All @@ -390,7 +390,7 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
if err != nil {
return 0, err
}
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, false, contract.Gas, gas, stack.Back(0))
if err != nil {
return 0, err
}
Expand All @@ -406,7 +406,7 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
if err != nil {
return 0, err
}
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, false, contract.Gas, gas, stack.Back(0))
if err != nil {
return 0, err
}
Expand All @@ -417,6 +417,37 @@ 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 && evm.StateDB.Empty(address) {
gas += params.CallNewAccountGas
}
if transfersValue {
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(true, true, contract.Gas, gas, stack.Back(0))
if err != nil {
return 0, err
}
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:
Expand Down
Loading