forked from ethereum/go-ethereum
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from EscanBE/feat/stateful-precompile-contracts
feat(precompiled): add custom precompiled contracts, support stateful precompiled contracts
- Loading branch information
Showing
6 changed files
with
199 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package vm | ||
|
||
import ( | ||
"bytes" | ||
"encoding/hex" | ||
"fmt" | ||
"github.com/ethereum/go-ethereum/common" | ||
) | ||
|
||
var _ PrecompiledContract = &CustomPrecompiledContract{} | ||
|
||
// CustomPrecompiledContract is a precompiled contract that externally provided. | ||
type CustomPrecompiledContract struct { | ||
name string // name of the contract | ||
address common.Address // address of the contract | ||
methods []CustomPrecompiledContractMethod // methods of the contract | ||
disabled bool // disabled flag indicate the contract is disabled | ||
} | ||
|
||
func NewCustomPrecompiledContract( | ||
address common.Address, methods []CustomPrecompiledContractMethod, name string, | ||
) PrecompiledContract { | ||
if address == (common.Address{}) { | ||
panic("invalid address") | ||
} | ||
|
||
if len(methods) == 0 { | ||
panic("no methods registered") | ||
} | ||
|
||
uniqueSig := make(map[string]struct{}) | ||
for _, m := range methods { | ||
hexSig := hex.EncodeToString(m.Method4BytesSignatures) | ||
if err := m.Validate(); err != nil { | ||
panic(fmt.Sprintf("invalid method %s: %s", hexSig, err)) | ||
} | ||
if _, exists := uniqueSig[hexSig]; exists { | ||
panic(fmt.Sprintf("duplicate method %s", hexSig)) | ||
} | ||
} | ||
|
||
return &CustomPrecompiledContract{ | ||
address: address, | ||
methods: methods, | ||
name: name, | ||
} | ||
} | ||
|
||
func (s CustomPrecompiledContract) RequiredGas(input []byte) uint64 { | ||
sig := input[:4] | ||
|
||
for _, method := range s.methods { | ||
if bytes.Equal(method.Method4BytesSignatures, sig) { | ||
return method.RequireGas | ||
} | ||
} | ||
|
||
return 0 | ||
} | ||
|
||
// Run runs the precompiled contract. | ||
func (s CustomPrecompiledContract) Run(_ []byte) ([]byte, error) { | ||
panic("use RunCustom instead") | ||
} | ||
|
||
// RunCustom runs the custom precompiled contract with extra arguments supports state-modifying contracts. | ||
func (s CustomPrecompiledContract) RunCustom(caller ContractRef, input []byte, readOnly bool, evm *EVM) ([]byte, error) { | ||
sig := input[:4] | ||
|
||
for _, method := range s.methods { | ||
if bytes.Equal(method.Method4BytesSignatures, sig) { | ||
if readOnly && !method.ReadOnly { | ||
return nil, ErrWriteProtection | ||
} | ||
|
||
return method.Executor.Execute(caller, s.address, input, evm) | ||
} | ||
} | ||
|
||
return nil, ErrExecutionReverted | ||
} | ||
|
||
func (s CustomPrecompiledContract) Name() string { | ||
return s.name | ||
} | ||
|
||
func (s CustomPrecompiledContract) Address() common.Address { | ||
return s.address | ||
} | ||
|
||
func (s *CustomPrecompiledContract) WithDisabled(disabled bool) *CustomPrecompiledContract { | ||
s.disabled = disabled | ||
return s | ||
} | ||
|
||
type CustomPrecompiledContractMethod struct { | ||
Method4BytesSignatures []byte | ||
RequireGas uint64 | ||
ReadOnly bool | ||
Executor CustomPrecompiledContractMethodExecutorI | ||
} | ||
|
||
func (m CustomPrecompiledContractMethod) Validate() error { | ||
if len(m.Method4BytesSignatures) != 4 { | ||
return fmt.Errorf("invalid method signature, expected 4 bytes, got %d", len(m.Method4BytesSignatures)) | ||
} | ||
|
||
if m.ReadOnly { | ||
// allow any gas requirement for read-only methods | ||
} else { | ||
if m.RequireGas == 0 { | ||
return fmt.Errorf("invalid gas requirement, expected non-zero value") | ||
} | ||
} | ||
|
||
if m.Executor == nil { | ||
return fmt.Errorf("missing executor") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
type CustomPrecompiledContractMethodExecutorI interface { | ||
// Execute executes the method with the given input and returns the output. | ||
Execute(caller ContractRef, contractAddress common.Address, input []byte, evm *EVM) ([]byte, error) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package vm | ||
|
||
import "github.com/ethereum/go-ethereum/common" | ||
|
||
func (evm *EVM) WithCustomPrecompiledContract(precompiles ...PrecompiledContract) *EVM { | ||
evm.customPrecompiledContracts = make(map[common.Address]PrecompiledContract, len(precompiles)) | ||
for _, precompile := range precompiles { | ||
evm.customPrecompiledContracts[precompile.(*CustomPrecompiledContract).Address()] = precompile | ||
} | ||
return evm | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package vm | ||
|
||
import ( | ||
"errors" | ||
) | ||
|
||
var ( | ||
ErrDisabledPrecompile = errors.New("the precompile contract is disabled") | ||
) | ||
|
||
// RunPrecompiledContract runs a precompiled contract with the given input and supplied gas. | ||
func (in *EVMInterpreter) RunPrecompiledContract(caller ContractRef, p PrecompiledContract, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { | ||
cpc, isCustomPrecompiledContract := p.(*CustomPrecompiledContract) | ||
if !isCustomPrecompiledContract { | ||
return RunPrecompiledContract(p, input, suppliedGas) | ||
} | ||
|
||
if cpc.disabled { | ||
return nil, suppliedGas, ErrDisabledPrecompile | ||
} | ||
|
||
if len(input) < 4 { | ||
return nil, 0, ErrExecutionReverted | ||
} | ||
|
||
gasCost := cpc.RequiredGas(input) | ||
if suppliedGas < gasCost { | ||
return nil, 0, ErrOutOfGas | ||
} | ||
suppliedGas -= gasCost | ||
|
||
{ | ||
// We do not increase the call depth for precompiled contracts. | ||
} | ||
|
||
// Make sure the readOnly is only set if we aren't in readOnly yet. | ||
// This also makes sure that the readOnly flag isn't removed for child calls. | ||
if readOnly && !in.readOnly { | ||
in.readOnly = true | ||
defer func() { in.readOnly = false }() | ||
} | ||
|
||
output, err := cpc.RunCustom(caller, input, readOnly, in.evm) | ||
return output, suppliedGas, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.