Skip to content

Commit fc454d0

Browse files
committed
feat: support stateful precompile
1 parent 7368b34 commit fc454d0

File tree

4 files changed

+62
-35
lines changed

4 files changed

+62
-35
lines changed

core/vm/contract.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ type Contract struct {
4444

4545
Gas uint64
4646
value *uint256.Int
47+
48+
// Precompile and Code are mutually exclusive
49+
Precompile PrecompiledContract
4750
}
4851

4952
// NewContract returns a new contract environment for the execution of EVM.
@@ -163,3 +166,7 @@ func (c *Contract) SetCallCode(hash common.Hash, code []byte) {
163166
c.Code = code
164167
c.CodeHash = hash
165168
}
169+
170+
func (c *Contract) SetPrecompile(precompile PrecompiledContract) {
171+
c.Precompile = precompile
172+
}

core/vm/contracts.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ type PrecompiledContract interface {
5353
Name() string
5454
}
5555

56+
// StatefulPrecompiledContract has access to the EVM and Contract context when executed.
57+
type StatefulPrecompiledContract interface {
58+
PrecompiledContract
59+
60+
RunWithEVM(evm *EVM, contract *Contract) ([]byte, error)
61+
}
62+
5663
// PrecompiledContracts contains the precompiled contracts supported at the given fork.
5764
type PrecompiledContracts map[common.Address]PrecompiledContract
5865

core/vm/evm.go

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -282,22 +282,23 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
282282
}
283283
evm.Context.Transfer(evm.StateDB, caller, addr, value)
284284

285+
// The contract is a scoped environment for this execution context only.
286+
contract := NewContract(caller, addr, value, gas, evm.jumpDests)
287+
contract.IsSystemCall = isSystemCall(caller)
285288
if isPrecompile {
286-
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
289+
contract.SetPrecompile(p)
287290
} else {
288291
// Initialise a new contract and set the code that is to be used by the EVM.
289292
code := evm.resolveCode(addr)
290293
if len(code) == 0 {
291-
ret, err = nil, nil // gas is unchanged
292-
} else {
293-
// The contract is a scoped environment for this execution context only.
294-
contract := NewContract(caller, addr, value, gas, evm.jumpDests)
295-
contract.IsSystemCall = isSystemCall(caller)
296-
contract.SetCallCode(evm.resolveCodeHash(addr), code)
297-
ret, err = evm.Run(contract, input, false)
298-
gas = contract.Gas
294+
return nil, gas, nil // gas is unchanged
299295
}
296+
297+
contract.SetCallCode(evm.resolveCodeHash(addr), code)
300298
}
299+
300+
ret, err = evm.Run(contract, input, false)
301+
gas = contract.Gas
301302
// When an error was returned by the EVM or when setting the creation code
302303
// above we revert to the snapshot and consume any gas remaining. Additionally,
303304
// when we're in homestead this also counts for code storage gas errors.
@@ -345,17 +346,18 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
345346
}
346347
var snapshot = evm.StateDB.Snapshot()
347348

348-
// It is allowed to call precompiles, even via delegatecall
349+
// Initialise a new contract and set the code that is to be used by the EVM.
350+
// The contract is a scoped environment for this execution context only.
351+
contract := NewContract(caller, caller, value, gas, evm.jumpDests)
349352
if p, isPrecompile := evm.precompile(addr); isPrecompile {
350-
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
353+
// It is allowed to call precompiles, even via delegatecall
354+
contract.SetPrecompile(p)
351355
} else {
352-
// Initialise a new contract and set the code that is to be used by the EVM.
353-
// The contract is a scoped environment for this execution context only.
354-
contract := NewContract(caller, caller, value, gas, evm.jumpDests)
355356
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
356-
ret, err = evm.Run(contract, input, false)
357-
gas = contract.Gas
358357
}
358+
359+
ret, err = evm.Run(contract, input, false)
360+
gas = contract.Gas
359361
if err != nil {
360362
evm.StateDB.RevertToSnapshot(snapshot)
361363
if err != ErrExecutionReverted {
@@ -388,18 +390,19 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
388390
}
389391
var snapshot = evm.StateDB.Snapshot()
390392

391-
// It is allowed to call precompiles, even via delegatecall
393+
// Initialise a new contract and make initialise the delegate values
394+
//
395+
// Note: The value refers to the original value from the parent call.
396+
contract := NewContract(originCaller, caller, value, gas, evm.jumpDests)
392397
if p, isPrecompile := evm.precompile(addr); isPrecompile {
393-
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
398+
// It is allowed to call precompiles, even via delegatecall
399+
contract.SetPrecompile(p)
394400
} else {
395-
// Initialise a new contract and make initialise the delegate values
396-
//
397-
// Note: The value refers to the original value from the parent call.
398-
contract := NewContract(originCaller, caller, value, gas, evm.jumpDests)
399401
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
400-
ret, err = evm.Run(contract, input, false)
401-
gas = contract.Gas
402402
}
403+
404+
ret, err = evm.Run(contract, input, false)
405+
gas = contract.Gas
403406
if err != nil {
404407
evm.StateDB.RevertToSnapshot(snapshot)
405408
if err != ErrExecutionReverted {
@@ -441,20 +444,20 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
441444
// future scenarios
442445
evm.StateDB.AddBalance(addr, new(uint256.Int), tracing.BalanceChangeTouchAccount)
443446

447+
// Initialise a new contract and set the code that is to be used by the EVM.
448+
// The contract is a scoped environment for this execution context only.
449+
contract := NewContract(caller, addr, new(uint256.Int), gas, evm.jumpDests)
444450
if p, isPrecompile := evm.precompile(addr); isPrecompile {
445-
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
451+
contract.SetPrecompile(p)
446452
} else {
447-
// Initialise a new contract and set the code that is to be used by the EVM.
448-
// The contract is a scoped environment for this execution context only.
449-
contract := NewContract(caller, addr, new(uint256.Int), gas, evm.jumpDests)
450453
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
451-
452-
// When an error was returned by the EVM or when setting the creation code
453-
// above we revert to the snapshot and consume any gas remaining. Additionally
454-
// when we're in Homestead this also counts for code storage gas errors.
455-
ret, err = evm.Run(contract, input, true)
456-
gas = contract.Gas
457454
}
455+
456+
// When an error was returned by the EVM or when setting the creation code
457+
// above we revert to the snapshot and consume any gas remaining. Additionally
458+
// when we're in Homestead this also counts for code storage gas errors.
459+
ret, err = evm.Run(contract, input, true)
460+
gas = contract.Gas
458461
if err != nil {
459462
evm.StateDB.RevertToSnapshot(snapshot)
460463
if err != ErrExecutionReverted {

core/vm/interpreter.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,17 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
110110
// as every returning call will return new data anyway.
111111
evm.returnData = nil
112112

113+
contract.Input = input
114+
115+
if contract.Precompile != nil {
116+
if stateful, ok := contract.Precompile.(StatefulPrecompiledContract); ok {
117+
return stateful.RunWithEVM(evm, contract)
118+
}
119+
120+
ret, contract.Gas, err = RunPrecompiledContract(contract.Precompile, input, contract.Gas, evm.Config.Tracer)
121+
return ret, err
122+
}
123+
113124
// Don't bother with the execution if there's no code.
114125
if len(contract.Code) == 0 {
115126
return nil, nil
@@ -145,7 +156,6 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
145156
returnStack(stack)
146157
mem.Free()
147158
}()
148-
contract.Input = input
149159

150160
if debug {
151161
defer func() { // this deferred method handles exit-with-error

0 commit comments

Comments
 (0)