Skip to content

Commit

Permalink
Merge pull request #40 from quilt/eip3074-update
Browse files Browse the repository at this point in the history
EIP-3074 updates
  • Loading branch information
adietrichs authored Mar 15, 2021
2 parents ebaefcc + 9a04463 commit 1d05a1c
Show file tree
Hide file tree
Showing 15 changed files with 641 additions and 90 deletions.
2 changes: 1 addition & 1 deletion core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
} else {
// Increment the nonce for the next transaction
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
ret, st.gas, vmerr = st.evm.Call(sender, sender.Address(), st.to(), st.data, st.gas, st.value)
ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value)
}
st.refundGas()
st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
Expand Down
24 changes: 14 additions & 10 deletions core/vm/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,14 @@ func (ar AccountRef) Address() common.Address { return (common.Address)(ar) }
// Contract represents an ethereum contract in the state database. It contains
// the contract code, calling arguments. Contract implements ContractRef
type Contract struct {
// SenderAddress is the result of the sender which initialised this
// contract. However when the "call method" is delegated this value
// needs to be initialised to that of the caller's sender.
SenderAddress common.Address
// CallerAddress is the result of the caller which initialised this
// 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 All @@ -63,8 +67,8 @@ type Contract struct {
}

// NewContract returns a new contract environment for the execution of EVM.
func NewContract(caller ContractRef, sender common.Address, object ContractRef, value *big.Int, gas uint64) *Contract {
c := &Contract{SenderAddress: sender, caller: caller, self: object}
func NewContract(caller ContractRef, object ContractRef, value *big.Int, gas uint64) *Contract {
c := &Contract{CallerAddress: caller.Address(), caller: caller, self: object}

if parent, ok := caller.(*Contract); ok {
// Reuse JUMPDEST analysis from parent context if available.
Expand Down Expand Up @@ -135,7 +139,7 @@ func (c *Contract) AsDelegate() *Contract {
// NOTE: caller must, at all times be a contract. It should never happen
// that caller is something other than a Contract.
parent := c.caller.(*Contract)
c.SenderAddress = parent.SenderAddress
c.CallerAddress = parent.CallerAddress
c.value = parent.value

return c
Expand All @@ -155,12 +159,12 @@ func (c *Contract) GetByte(n uint64) byte {
return 0
}

// Caller returns the sender of the contract.
// Caller returns the caller of the contract.
//
// Caller will recursively call caller when the contract is a delegate
// call, including that of caller's sender.
// call, including that of caller's caller.
func (c *Contract) Caller() common.Address {
return c.SenderAddress
return c.CallerAddress
}

// UseGas attempts the use gas and subtracts it and returns true on success
Expand Down
70 changes: 38 additions & 32 deletions core/vm/eips.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ package vm

import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"sort"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
Expand Down Expand Up @@ -169,48 +169,57 @@ func enable3074(jt *JumpTable) {

func opAuth(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
stack := callContext.stack
commit, sigYParity, sigR, sigS := stack.pop(), stack.pop(), stack.pop(), stack.pop()
commit, v, r, s := stack.pop(), stack.pop(), stack.pop(), stack.pop()

r := sigR.ToBig()
s := sigS.ToBig()
yParity := uint8(sigYParity[0])
// Zero out the current authorized account. Only update it if an address
// is successfully recovered from the signature.
callContext.authorized = nil

callContext.authorizedAccount = nil
if v.BitLen() < 8 && crypto.ValidateSignatureValues(byte(v.Uint64()), r.ToBig(), s.ToBig(), true) {
msg := make([]byte, 65)

if val, of := sigYParity.Uint64WithOverflow(); !of && val <= 0xff && crypto.ValidateSignatureValues(yParity, r, s, true) {
input := make([]byte, 65)
input[0] = 0x03
copy(input[13:33], callContext.contract.Address().Bytes())
commit.WriteToSlice(input[33:65])
hash := crypto.Keccak256(input)
// EIP-3074 messages are of the form
// keccak256(type ++ invoker ++ commit)
msg[0] = 0x03
copy(msg[13:33], callContext.contract.Address().Bytes())
commit.WriteToSlice(msg[33:65])
hash := crypto.Keccak256(msg)

sig := make([]byte, 65)
sigR.WriteToSlice(sig[0:32])
sigS.WriteToSlice(sig[32:64])
sig[64] = yParity
// v needs to be at the end for libsecp256k1
pubKey, err := crypto.Ecrecover(hash[:], sig)
// make sure the public key is a valid one
// the first byte of pubkey is bitcoin heritage
r.WriteToSlice(sig[0:32])
s.WriteToSlice(sig[32:64])
sig[64] = byte(v.Uint64())

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

if err == nil {
from := common.BytesToAddress(crypto.Keccak256(pubKey[1:])[12:])
if from != interpreter.evm.Origin {
callContext.authorizedAccount = &from
var addr common.Address
copy(addr[:], crypto.Keccak256(pub[1:])[12:])

// Transaction origin is not allowed to authorize itself.
// This is to prevent reentering contracts that expect
// caller == origin only in the first frame of a transaction.
if addr != interpreter.evm.Origin {
callContext.authorized = &addr
}
}
}

if callContext.authorizedAccount != nil {
commit.SetBytes20(callContext.authorizedAccount.Bytes())
// reuse commit to push the result
temp := commit
if callContext.authorized != nil {
temp.SetBytes20(callContext.authorized.Bytes())
} else {
commit.Clear()
temp.Clear()
}
stack.push(&commit)

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

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

Expand All @@ -226,15 +235,12 @@ func opAuthCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) (
args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))

var bigVal = big0
//TODO: use uint256.Int instead of converting with toBig()
// By using big0 here, we save an alloc for the most common case (non-ether-transferring contract calls),
// but it would make more sense to extend the usage of uint256.Int
if !value.IsZero() {
gas += params.CallStipend
bigVal = value.ToBig()
}

ret, returnGas, err := interpreter.evm.Call(callContext.contract, *callContext.authorizedAccount, toAddr, args, gas, bigVal)
ret, returnGas, err := interpreter.evm.AuthCall(callContext.contract, *callContext.authorized, toAddr, args, gas, bigVal)

if err != nil {
temp.Clear()
Expand Down
85 changes: 77 additions & 8 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ func (evm *EVM) Interpreter() Interpreter {
// parameters. It also handles any necessary value transfer required and takes
// the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer.
func (evm *EVM) Call(caller ContractRef, sender common.Address, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
if evm.vmConfig.NoRecursion && evm.depth > 0 {
return nil, gas, nil
}
Expand All @@ -239,7 +239,7 @@ func (evm *EVM) Call(caller ContractRef, sender common.Address, addr common.Addr
if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
// Calling a non existing account, don't do anything, but ping the tracer
if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(sender, addr, false, input, gas, value)
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil)
}
return nil, gas, nil
Expand All @@ -250,7 +250,7 @@ func (evm *EVM) Call(caller ContractRef, sender common.Address, addr common.Addr

// Capture the tracer start/end events in debug mode
if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(sender, addr, false, input, gas, value)
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters
evm.vmConfig.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err)
}(gas, time.Now())
Expand All @@ -268,7 +268,7 @@ func (evm *EVM) Call(caller ContractRef, sender common.Address, addr common.Addr
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, sender, AccountRef(addrCopy), value, gas)
contract := NewContract(caller, AccountRef(addrCopy), value, gas)
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code)
ret, err = run(evm, contract, input, false)
gas = contract.Gas
Expand Down Expand Up @@ -320,7 +320,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
addrCopy := addr
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
contract := NewContract(caller, caller.Address(), AccountRef(caller.Address()), value, gas)
contract := NewContract(caller, AccountRef(caller.Address()), value, gas)
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
ret, err = run(evm, contract, input, false)
gas = contract.Gas
Expand Down Expand Up @@ -355,7 +355,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
} else {
addrCopy := addr
// Initialise a new contract and make initialise the delegate values
contract := NewContract(caller, caller.Address(), AccountRef(caller.Address()), nil, gas).AsDelegate()
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
ret, err = run(evm, contract, input, false)
gas = contract.Gas
Expand Down Expand Up @@ -403,7 +403,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
addrCopy := addr
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
contract := NewContract(caller, caller.Address(), AccountRef(addrCopy), new(big.Int), gas)
contract := NewContract(caller, AccountRef(addrCopy), new(big.Int), gas)
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
// 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
Expand All @@ -420,6 +420,75 @@ 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 *big.Int) (ret []byte, leftOverGas uint64, err error) {
if evm.vmConfig.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, gas, ErrInsufficientBalance
}
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.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(from, addr, false, input, gas, value)
evm.vmConfig.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.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(from, addr, false, input, gas, value)
defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters
evm.vmConfig.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 = run(evm, 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 Expand Up @@ -464,7 +533,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,

// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
contract := NewContract(caller, caller.Address(), AccountRef(address), value, gas)
contract := NewContract(caller, AccountRef(address), value, gas)
contract.SetCodeOptionalHash(&address, codeAndHash)

if evm.vmConfig.NoRecursion && evm.depth > 0 {
Expand Down
2 changes: 1 addition & 1 deletion core/vm/gas_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func TestEIP2200(t *testing.T) {
}
vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})

_, gas, err := vmenv.Call(AccountRef(common.Address{}), common.Address{}, address, nil, tt.gaspool, new(big.Int))
_, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, tt.gaspool, new(big.Int))
if err != tt.failure {
t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure)
}
Expand Down
2 changes: 1 addition & 1 deletion core/vm/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]by
bigVal = value.ToBig()
}

ret, returnGas, err := interpreter.evm.Call(callContext.contract, callContext.contract.Address(), toAddr, args, gas, bigVal)
ret, returnGas, err := interpreter.evm.Call(callContext.contract, toAddr, args, gas, bigVal)

if err != nil {
temp.Clear()
Expand Down
Loading

0 comments on commit 1d05a1c

Please sign in to comment.