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

feat: precompilegen Solidity interface -> stateful precompile #72

Draft
wants to merge 23 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d24e254
feat: `precompilegen` binary
ARR4N Nov 11, 2024
8534530
test: explicit (i.e. non-fallback) `precompilegen` contract methods
ARR4N Nov 11, 2024
45c4c0c
test: `precompilgen` fallback function
ARR4N Nov 12, 2024
85669ed
test: `precompilegen` tuple arguments
ARR4N Nov 12, 2024
268790e
refactor: move `vm.Selector` etc. to `abi` package
ARR4N Nov 13, 2024
e841e39
chore: remove unnecessary `require` argument
ARR4N Nov 14, 2024
fab5066
Merge branch 'main' into arr4n/precompilegen
ARR4N Dec 19, 2024
4fc6bcc
chore: file renaming and deletion
ARR4N Dec 19, 2024
9f392ae
chore: `go:generate` workflow in `ethereum/solc` container
ARR4N Dec 19, 2024
31e9a9a
chore: fight with GH Actions and Docker... en garde!
ARR4N Dec 19, 2024
1442e0f
chore: install `solc` via `apt` instead
ARR4N Dec 19, 2024
34f134b
fix: `apt` `solc` version
ARR4N Dec 19, 2024
85dc089
chore: `apt list` in workflow; I hate GH Actions
ARR4N Dec 19, 2024
49f2312
feat: `view` and `pure` mutability limitations
ARR4N Dec 19, 2024
2200b8e
test: payable vs non-payable
ARR4N Dec 20, 2024
08e4985
refactor: split `IPrecompile` and `TestSuite` contracts + move tests
ARR4N Dec 20, 2024
495011a
test: assert reason for revert when paying non-payable
ARR4N Dec 20, 2024
e494e60
refactor!: fine-grained mutability instead of boolean `ReadOnly()`
ARR4N Dec 24, 2024
f33c339
fix: comment typo
ARR4N Dec 24, 2024
7a5c68f
doc: make `godoc` place `CallType` consts with the type
ARR4N Dec 24, 2024
ecac678
test: `PrecompileEnvironment` mutability limitation
ARR4N Dec 24, 2024
7194e1a
refactor: reduce nesting of last commit
ARR4N Dec 24, 2024
2780b74
fix: `view` and `pure` functions revert when paid
ARR4N Dec 24, 2024
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
7 changes: 7 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ jobs:
EXCLUDE_REGEX: 'ava-labs/libevm/(accounts/usbwallet/trezor)$'
runs-on: ubuntu-latest
steps:
- name: Install solc
run: |
sudo add-apt-repository ppa:ethereum/ethereum
sudo apt update
sudo apt list -a solc
sudo apt install solc=1:0.8.28-0ubuntu1~noble

- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
Expand Down
75 changes: 75 additions & 0 deletions accounts/abi/selector.libevm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright 2024 the libevm authors.
//
// The libevm additions to go-ethereum are free software: you can redistribute
// them and/or modify them 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 libevm additions are distributed in the hope that they 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 abi

import (
"encoding/binary"
"fmt"
)

// SelectorByteLen is the number of bytes in an ABI function selector.
const SelectorByteLen = 4

// A Selector is an ABI function selector. It is a uint32 instead of a [4]byte
// to allow for simpler hex literals.
type Selector uint32

// String returns a hex encoding of `s`.
func (s Selector) String() string {
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, uint32(s))
return fmt.Sprintf("%#x", b)
}

// ExtractSelector returns the first 4 bytes of the slice as a Selector. It
// assumes that its input is of sufficient length.
func ExtractSelector(data []byte) Selector {
return Selector(binary.BigEndian.Uint32(data[:4]))
}

// Selector returns the Method's ID as a Selector.
func (m Method) Selector() Selector {
return ExtractSelector(m.ID)
}

// MethodsBySelector maps 4-byte ABI selectors to the corresponding method
// representation. The key MUST be equivalent to the value's [Method.Selector].
type MethodsBySelector map[Selector]Method

// MethodsBySelector returns the [Method]s keyed by their Selectors.
func (a *ABI) MethodsBySelector() MethodsBySelector {
ms := make(MethodsBySelector)
for _, m := range a.Methods {
ms[m.Selector()] = m
}
return ms
}

// FindSelector extracts the Selector from `data` and, if it exists in `m`,
// returns it. The returned boolean functions as for regular map lookups. Unlike
// [ExtractSelector], FindSelector confirms that `data` has at least 4 bytes,
// treating invalid inputs as not found.
func (m MethodsBySelector) FindSelector(data []byte) (Selector, bool) {
if len(data) < SelectorByteLen {
return 0, false
}
sel := ExtractSelector(data)
if _, ok := m[sel]; !ok {
return 0, false
}
return sel, true
}
74 changes: 66 additions & 8 deletions core/vm/contracts.libevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ type evmCallArgs struct {
type CallType OpCode

const (
Call = CallType(CALL)
CallCode = CallType(CALLCODE)
DelegateCall = CallType(DELEGATECALL)
StaticCall = CallType(STATICCALL)
Call CallType = CallType(CALL)
CallCode CallType = CallType(CALLCODE)
DelegateCall CallType = CallType(DELEGATECALL)
StaticCall CallType = CallType(STATICCALL)
)

func (t CallType) isValid() bool {
Expand Down Expand Up @@ -88,6 +88,32 @@ func (t CallType) OpCode() OpCode {
return INVALID
}

// StateMutability describes the available state access.
type StateMutability uint

const (
// Pure is a Solidity concept disallowing all access, read or write, to
// state.
Pure StateMutability = iota + 1
// ReadOnlyState is equivalent to Solidity's "view".
ReadOnlyState
// MutableState can be both read from and written to.
MutableState
)

// String returns a human-readable representation of the StateMutability.
func (m StateMutability) String() string {
switch m {
case MutableState:
return "mutable"
case ReadOnlyState:
return "read-only"
case Pure:
return "no state access"
}
return fmt.Sprintf("unknown %T(%[1]d)", m)
}

// run runs the [PrecompiledContract], differentiating between stateful and
// regular types, updating `args.gasRemaining` in the stateful case.
func (args *evmCallArgs) run(p PrecompiledContract, input []byte) (ret []byte, err error) {
Expand All @@ -96,7 +122,7 @@ func (args *evmCallArgs) run(p PrecompiledContract, input []byte) (ret []byte, e
return p.Run(input)
case statefulPrecompile:
env := args.env()
ret, err := p(env, input)
ret, err := p(env, common.CopyBytes(input))
args.gasRemaining = env.Gas()
return ret, err
}
Expand Down Expand Up @@ -141,14 +167,28 @@ func (p statefulPrecompile) Run([]byte) ([]byte, error) {
type PrecompileEnvironment interface {
ChainConfig() *params.ChainConfig
Rules() params.Rules
// StateDB will be non-nil i.f.f !ReadOnly().
// StateDB will be non-nil i.f.f StateMutability() returns [MutableState].
StateDB() StateDB
// ReadOnlyState will always be non-nil.
// ReadOnlyState will be non-nil i.f.f. StateMutability() does not return
// [Pure].
ReadOnlyState() libevm.StateReader

// StateMutability can infer [MutableState] vs [ReadOnlyState] based on EVM
// context, but [Pure] is a Solidity concept that is enforced by user code.
StateMutability() StateMutability
// AsReadOnly returns a copy of the current environment for which
// StateMutability() is at most [ReadOnlyState]; i.e. if mutability is
// already limited to [Pure], AsReadOnly() will not expand access. It can be
// used as a guard against accidental writes when a read-only function is
// invoked with EVM call() instead of staticcall().
AsReadOnly() PrecompileEnvironment
// AsPure returns a copy of the current environment that has no access to
// state; i.e. StateMutability() returns [Pure]. All calls to both StateDB()
// and ReadOnlyState() will return nil.
AsPure() PrecompileEnvironment

IncomingCallType() CallType
Addresses() *libevm.AddressContext
ReadOnly() bool
// Equivalent to respective methods on [Contract].
Gas() uint64
UseGas(uint64) (hasEnoughGas bool)
Expand Down Expand Up @@ -210,3 +250,21 @@ var (
(*EVM)(nil).StaticCall,
}
)

// A RevertError is an error that couples [ErrExecutionReverted] with the EVM
// return buffer. Although not used in vanilla geth, it can be returned by a
// libevm `precompilegen` method implementation to circumvent regular argument
// packing.
type RevertError []byte

// Error is equivalent to the respective method on [ErrExecutionReverted].
func (e RevertError) Error() string { return ErrExecutionReverted.Error() }

// Bytes returns the return buffer with which an EVM context reverted.
func (e RevertError) Bytes() []byte { return []byte(e) }

// Is returns true if `err` is directly == to `e` or if `err` is
// [ErrExecutionReverted].
func (e RevertError) Is(err error) bool {
return error(e) == err || err == ErrExecutionReverted
}
Loading
Loading