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

Codespace organic space funicular 97796qwpgg4vcgq #3

Draft
wants to merge 3 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
138 changes: 138 additions & 0 deletions core/vm/call_stack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser Genercs Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser Genercs Public License for more details.
//
// You should have received a copy of the GNU Lesser Genercs Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package vm

import (
"sync"

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

var callStackPool = sync.Pool{
New: func() interface{} {
return &callStack{calls: make([]*csCall, 0, 16)}
},
}

// callStack keeps track of the calls.
type callStack struct {
calls []*csCall
}

type csCall struct {
Op OpCode
Address common.Address
Selector []byte
}

// newCallStack creates a new call stack.
func newCallStack() *callStack {
return callStackPool.Get().(*callStack)
}

// Push pushes given call to the stack.
func (cs *callStack) Push(op OpCode, addr common.Address, input []byte) {
var selector []byte
if len(input) >= 4 {
selector = input[:4]
}
cs.calls = append(cs.calls, &csCall{
Op: op,
Address: addr,
Selector: selector,
})
}

// Pop pops the latest call from the stack and returns the stack back to the
// pool if no calls are left after the final pop.
func (cs *callStack) Pop() {
cs.calls = cs.calls[:len(cs.calls)-1]
if len(cs.calls) == 0 {
callStackPool.Put(cs)
}
}

func isAddrIn(checkAddr common.Address, addrs []common.Address) bool {
for _, addr := range addrs {
if addr.Cmp(checkAddr) == 0 {
return true
}
}
return false
}

// RequiredGas implements the precompiled contract interface.
func (cs *callStack) RequiredGas(input []byte) uint64 {
// Assume 100 gas base cost
// ORIGIN, ADDRESS and CALLER opcodes spend 2 gas
// Assume 2 gas per called address and 2 gas per created and called address
// TODO: Move these constants to params/protocol_params.go?
const baseGasCost = 0 // TBD
return baseGasCost + uint64(2*len(cs.calls))
}

// Run runs the precompiled contract.
func (cs *callStack) Run(input []byte) ([]byte, error) {
return newCallStackEncoder(cs.calls).Encode()
}

var (
callStackEncodingArrayOffset = uint256.NewInt(32)
)

type callStackEncoder struct {
calls []*csCall

i int
result []byte
}

func newCallStackEncoder(calls []*csCall) *callStackEncoder {
return &callStackEncoder{
calls: calls,
// 1 for offset, 1 for list length
// 3 x list length for the elements
// rest for the actucs elements in both lists
result: make([]byte, (2+3*len(calls))*32),
}
}

func (enc *callStackEncoder) Encode() ([]byte, error) {
// add the array offset and the call list length
enc.appendNum(callStackEncodingArrayOffset)
enc.appendNum(uint256.NewInt(uint64(len(enc.calls))))

// add call info from each call
for _, call := range enc.calls {
enc.appendNum(uint256.NewInt(uint64(call.Op)))
enc.appendAddr(call.Address)
enc.appendNum(uint256.NewInt(0).SetBytes(call.Selector))
}

return enc.result, nil
}

func (enc *callStackEncoder) appendNum(n *uint256.Int) {
copy(enc.result[enc.i*32:(enc.i+1)*32], n.PaddedBytes(32))
enc.i++
}

func (enc *callStackEncoder) appendAddr(addr common.Address) {
copy(enc.result[enc.i*32+12:(enc.i+1)*32], addr.Bytes())
enc.i++
}
84 changes: 84 additions & 0 deletions core/vm/call_stack_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package vm

import (
"encoding/hex"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)

func BenchmarkCallStackPrecompile1(b *testing.B) {
benchCallStackPrecompileN(b, 1)
}

func BenchmarkCallStackPrecompile10(b *testing.B) {
benchCallStackPrecompileN(b, 10)
}

func BenchmarkCallStackPrecompile100(b *testing.B) {
benchCallStackPrecompileN(b, 100)
}

func benchCallStackPrecompileN(b *testing.B, n int) {
var calls []*csCall
for i := 0; i < n; i++ {
calls = append(calls, &csCall{
Op: OpCode(i),
Address: common.HexToAddress("0xCdA8dcaEe60ce9d63165Ef025fD98CDA2B99B5B2"),
Selector: []byte{0xde, 0xad, 0xbe, 0xef},
})
}
callStack := newCallStack()
callStack.calls = calls
for i := 0; i < b.N; i++ {
callStack.Run(nil)
}
}

func TestCallStackPrecompile(t *testing.T) {
r := require.New(t)
callStack := newCallStack()
callStack.calls = []*csCall{
{
Op: OpCode(0x10),
Address: common.HexToAddress("0xCdA8dcaEe60ce9d63165Ef025fD98CDA2B99B5B2"),
Selector: []byte{0xab, 0xcd, 0xef, 0x12},
},
{
Op: OpCode(0x20),
Address: common.HexToAddress("0xCdA8dcaEe60ce9d63165Ef025fD98CDA2B99B5B2"),
Selector: []byte{0xde, 0xad, 0xbe, 0xef},
},
}
b, err := callStack.Run(nil)
r.NoError(err)
expectedB, err := hex.DecodeString(
"0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000010" +
"000000000000000000000000cda8dcaee60ce9d63165ef025fd98cda2b99b5b2" +
"00000000000000000000000000000000000000000000000000000000abcdef12" +
"0000000000000000000000000000000000000000000000000000000000000020" +
"000000000000000000000000cda8dcaee60ce9d63165ef025fd98cda2b99b5b2" +
"00000000000000000000000000000000000000000000000000000000deadbeef",
)
r.NoError(err)
r.Equal(expectedB, b)
}
3 changes: 3 additions & 0 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ type PrecompiledContract interface {
Run(input []byte) ([]byte, error) // Run runs the precompiled contract
}

// CallStackPrecompileAddress is the default address for this precompiled contract.
var CallStackPrecompileAddress = common.BytesToAddress([]byte{0x20})

// PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum
// contracts used in the Frontier and Homestead releases.
var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{
Expand Down
27 changes: 26 additions & 1 deletion core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,13 @@ func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
precompiles = PrecompiledContractsHomestead
}
p, ok := precompiles[addr]
return p, ok
if ok {
return p, ok
}
if addr.Cmp(CallStackPrecompileAddress) == 0 {
return evm.callStack, true
}
return nil, false
}

// BlockContext provides the EVM with auxiliary information. Once provided
Expand Down Expand Up @@ -120,6 +126,8 @@ type EVM struct {
// available gas is calculated in gasCall* according to the 63/64 rule and later
// applied in opCall*.
callGasTemp uint64
// callStack keeps track of the calls
callStack *callStack
}

// NewEVM returns a new EVM. The returned EVM is not thread safe and should
Expand All @@ -132,6 +140,7 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig
Config: config,
chainConfig: chainConfig,
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time),
callStack: newCallStack(),
}
evm.interpreter = NewEVMInterpreter(evm)
return evm
Expand Down Expand Up @@ -222,6 +231,9 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
if isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
} else {
// Push the call to the call stack.
evm.callStack.Push(CALL, addr, input)
defer evm.callStack.Pop()
// 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)
Expand Down Expand Up @@ -285,6 +297,9 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
} else {
// Push the call to the call stack.
evm.callStack.Push(CALLCODE, addr, input)
defer evm.callStack.Pop()
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.
Expand Down Expand Up @@ -330,6 +345,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
} else {
// Push the call to the call stack.
evm.callStack.Push(DELEGATECALL, addr, input)
defer evm.callStack.Pop()
addrCopy := addr
// Initialise a new contract and make initialise the delegate values
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
Expand Down Expand Up @@ -379,6 +397,9 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
} else {
// Push the call to the call stack.
evm.callStack.Push(STATICCALL, addr, input)
defer evm.callStack.Pop()
// At this point, we use a copy of address. If we don't, the go compiler will
// leak the 'contract' to the outer scope, and make allocation for 'contract'
// even if the actual execution ends on RunPrecompiled above.
Expand Down Expand Up @@ -447,6 +468,10 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
}
evm.Context.Transfer(evm.StateDB, caller.Address(), address, value)

// Push the call to the call stack.
evm.callStack.Push(typ, address, nil)
defer evm.callStack.Pop()

// 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, AccountRef(address), value, gas)
Expand Down