core/vm: implement contract pool

This commit is contained in:
Matus Kysel 2025-06-11 11:25:29 +02:00
parent 0983cd789e
commit 72b800501c
No known key found for this signature in database
GPG key ID: 960806D4163CE769
6 changed files with 79 additions and 13 deletions

48
core/vm/contract_pool.go Normal file
View file

@ -0,0 +1,48 @@
package vm
import (
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/holiman/uint256"
)
var contractPool = sync.Pool{
New: func() any {
return &Contract{}
},
}
// GetContract returns a contract from the pool or creates a new one
func GetContract(caller common.Address, address common.Address, value *uint256.Int, gas uint64, jumpDests map[common.Hash]bitvec) *Contract {
contract := contractPool.Get().(*Contract)
// Reset the contract with new values
contract.caller = caller
contract.address = address
contract.jumpdests = jumpDests
if contract.jumpdests == nil {
// Initialize the jump analysis map if it's nil, mostly for tests
contract.jumpdests = make(map[common.Hash]bitvec)
}
contract.Gas = gas
contract.value = value
contract.Code = nil
contract.CodeHash = common.Hash{}
contract.Input = nil
contract.IsDeployment = false
contract.IsSystemCall = false
contract.analysis = nil
return contract
}
// ReturnContract returns a contract to the pool
func ReturnContract(contract *Contract) {
if contract == nil {
return
}
contractPool.Put(contract)
}

View file

@ -238,7 +238,9 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
ret, err = nil, nil // gas is unchanged
} else {
// The contract is a scoped environment for this execution context only.
contract := NewContract(caller, addr, value, gas, evm.jumpDests)
contract := GetContract(caller, addr, value, gas, evm.jumpDests)
defer ReturnContract(contract)
contract.IsSystemCall = isSystemCall(caller)
contract.SetCallCode(evm.resolveCodeHash(addr), code)
ret, err = evm.interpreter.Run(contract, input, false)
@ -298,7 +300,9 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
} 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.
contract := NewContract(caller, caller, value, gas, evm.jumpDests)
contract := GetContract(caller, caller, value, gas, evm.jumpDests)
defer ReturnContract(contract)
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
ret, err = evm.interpreter.Run(contract, input, false)
gas = contract.Gas
@ -342,7 +346,9 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
// Initialise a new contract and make initialise the delegate values
//
// Note: The value refers to the original value from the parent call.
contract := NewContract(originCaller, caller, value, gas, evm.jumpDests)
contract := GetContract(originCaller, caller, value, gas, evm.jumpDests)
defer ReturnContract(contract)
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
ret, err = evm.interpreter.Run(contract, input, false)
gas = contract.Gas
@ -393,7 +399,9 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
} 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.
contract := NewContract(caller, addr, new(uint256.Int), gas, evm.jumpDests)
contract := GetContract(caller, addr, new(uint256.Int), gas, evm.jumpDests)
defer ReturnContract(contract)
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
// When an error was returned by the EVM or when setting the creation code
@ -500,7 +508,8 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui
// 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, address, value, gas, evm.jumpDests)
contract := GetContract(caller, address, value, gas, evm.jumpDests)
defer ReturnContract(contract)
// Explicitly set the code to a null hash to prevent caching of jump analysis
// for the initialization code.
@ -514,7 +523,8 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui
contract.UseGas(contract.Gas, evm.Config.Tracer, tracing.GasChangeCallFailedExecution)
}
}
return ret, address, contract.Gas, err
leftOverGas = contract.Gas
return ret, address, leftOverGas, err
}
// initNewContract runs a new contract's creation code, performs checks on the

View file

@ -567,10 +567,11 @@ func TestOpTstore(t *testing.T) {
mem = NewMemory()
caller = common.Address{}
to = common.Address{1}
contract = NewContract(caller, to, new(uint256.Int), 0, nil)
contract = GetContract(caller, to, new(uint256.Int), 0, nil)
scopeContext = ScopeContext{mem, stack, contract}
value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700")
)
defer ReturnContract(contract)
// Add a stateObject for the caller and the contract being called
statedb.CreateAccount(caller)

View file

@ -55,8 +55,10 @@ func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCo
gasLimit uint64 = 31000
startGas uint64 = 10000
value = uint256.NewInt(0)
contract = vm.NewContract(common.Address{}, common.Address{}, value, startGas, nil)
contract = vm.GetContract(common.Address{}, common.Address{}, value, startGas, nil)
)
defer vm.ReturnContract(contract)
evm.SetTxContext(vmctx.txCtx)
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0}
if contractCode != nil {
@ -183,8 +185,10 @@ func TestHaltBetweenSteps(t *testing.T) {
t.Fatal(err)
}
scope := &vm.ScopeContext{
Contract: vm.NewContract(common.Address{}, common.Address{}, uint256.NewInt(0), 0, nil),
Contract: vm.GetContract(common.Address{}, common.Address{}, uint256.NewInt(0), 0, nil),
}
defer vm.ReturnContract(scope.Contract)
evm := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, chainConfig, vm.Config{Tracer: tracer.Hooks})
evm.SetTxContext(vm.TxContext{GasPrice: big.NewInt(1)})
tracer.OnTxStart(evm.GetVMContext(), types.NewTx(&types.LegacyTx{}), common.Address{})
@ -281,8 +285,9 @@ func TestEnterExit(t *testing.T) {
t.Fatal(err)
}
scope := &vm.ScopeContext{
Contract: vm.NewContract(common.Address{}, common.Address{}, uint256.NewInt(0), 0, nil),
Contract: vm.GetContract(common.Address{}, common.Address{}, uint256.NewInt(0), 0, nil),
}
defer vm.ReturnContract(scope.Contract)
tracer.OnEnter(1, byte(vm.CALL), scope.Contract.Caller(), scope.Contract.Address(), []byte{}, 1000, new(big.Int))
tracer.OnExit(1, []byte{}, 400, nil, false)

View file

@ -43,8 +43,10 @@ func TestStoreCapture(t *testing.T) {
var (
logger = NewStructLogger(nil)
evm = vm.NewEVM(vm.BlockContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: logger.Hooks()})
contract = vm.NewContract(common.Address{}, common.Address{}, new(uint256.Int), 100000, nil)
contract = vm.GetContract(common.Address{}, common.Address{}, new(uint256.Int), 100000, nil)
)
defer vm.ReturnContract(contract)
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)}
var index common.Hash
logger.OnTxStart(evm.GetVMContext(), nil, common.Address{})

View file

@ -301,8 +301,8 @@ func runBenchmark(b *testing.B, t *StateTest) {
evm.SetTxContext(txContext)
// Create "contract" for sender to cache code analysis.
sender := vm.NewContract(msg.From, msg.From, nil, 0, nil)
sender := vm.GetContract(msg.From, msg.From, nil, 0, nil)
defer vm.ReturnContract(sender)
var (
gasUsed uint64
elapsed uint64