Skip to content

Commit

Permalink
Reduced footprint of exported symbols (#8)
Browse files Browse the repository at this point in the history
* chore: gitignore coverage artifacts and `experimental/` directory

* chore: reduce exported-symbol footprint

* chore: rename test to be more precise
  • Loading branch information
aschlosberg authored Mar 1, 2024
1 parent 2a33450 commit ad051a8
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 70 deletions.
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# IDEs
workspace.code-workspace

workspace.code-workspace
# Scratchpad
experimental/**

# Coverage
cover.out
lcov.info
8 changes: 5 additions & 3 deletions compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"

"github.com/ethereum/go-ethereum/core/vm"

"github.com/solidifylabs/specialops/types"
)

// A splice is a (possibly empty) buffer of bytecode, followed by either a
Expand All @@ -13,7 +15,7 @@ import (
// determine. A splice allows for lazy determination of locations.
type splice struct {
buf bytes.Buffer
op Bytecoder // either JUMPDEST or PUSHJUMPDEST
op types.Bytecoder // either JUMPDEST or PUSHJUMPDEST
// If op is a JUMPDEST
offset *int // Current estimate of offset in the bytecode, or nil if not yet estimated
// If op is a PUSHJUMPDEST
Expand Down Expand Up @@ -43,7 +45,7 @@ func (s *spliceConcat) curr() *splice {
// to the previous splice.
func newSpliceBuffer[T interface{ JUMPDEST | PUSHJUMPDEST }](s *spliceConcat, op T) *bytes.Buffer {
curr := s.curr()
curr.op = Bytecoder(op)
curr.op = types.Bytecoder(op)
if j, ok := any(op).(JUMPDEST); ok {
s.dests[j] = curr
}
Expand All @@ -59,7 +61,7 @@ func (c Code) flatten() Code {
out := make(Code, 0, len(c))
for _, bc := range c {
switch bc := bc.(type) {
case BytecodeHolder:
case types.BytecodeHolder:
out = append(out, Code(bc.Bytecoders()).flatten()...)
default:
out = append(out, bc)
Expand Down
74 changes: 11 additions & 63 deletions specialops.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ import (
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/holiman/uint256"

"github.com/solidifylabs/specialops/types"
)

// Code is a slice of Bytecoders; it is itself a Bytecoder, allowing for
// nesting.
type Code []Bytecoder
type Code []types.Bytecoder

// Bytecode always returns an error; use Code.Compile instead(), which flattens
// nested Code instances.
Expand All @@ -31,21 +33,8 @@ func (c Code) Bytecode() ([]byte, error) {
}

// Bytecoders returns the Code as a slice of Bytecoders.
func (c Code) Bytecoders() []Bytecoder {
return []Bytecoder(c)
}

// A Bytecoder returns raw EVM bytecode. If the returned bytecode is the
// concatenation of multiple Bytecoder outputs, the type MUST also implement
// BytecodeHolder.
type Bytecoder interface {
Bytecode() ([]byte, error)
}

// A BytecodeHolder is a concatenation of Bytecoders.
type BytecodeHolder interface {
Bytecoder
Bytecoders() []Bytecoder
func (c Code) Bytecoders() []types.Bytecoder {
return []types.Bytecoder(c)
}

// Fn returns a Bytecoder that returns the concatenation of the *reverse* of
Expand All @@ -55,7 +44,7 @@ type BytecodeHolder interface {
//
// Although the returned BytecodeHolder can contain JUMPDESTs, they're hard to
// reason about so should be used with care.
func Fn(bcs ...Bytecoder) BytecodeHolder {
func Fn(bcs ...types.Bytecoder) types.BytecodeHolder {
c := Code(bcs)
for i, n := 0, len(c); i < n/2; i++ {
j := n - i - 1
Expand Down Expand Up @@ -96,58 +85,17 @@ func (p PUSHJUMPDEST) Bytecode() ([]byte, error) {
return nil, fmt.Errorf("direct call to %T.Bytecode()", p)
}

// A StackPusher returns [1,32] bytes to be pushed to the stack.
type StackPusher interface {
ToPush() []byte
}

// BytecoderFromStackPusher returns a Bytecoder that calls s.ToPush() and
// prepends the appropriate PUSH<N> opcode to the returned bytecode.
func BytecoderFromStackPusher(s StackPusher) Bytecoder {
return pusher{s}
}

type pusher struct {
StackPusher
}

func (p pusher) Bytecode() ([]byte, error) {
buf := p.ToPush()
n := len(buf)
if n == 0 || n > 32 {
return nil, fmt.Errorf("len(%T.ToPush()) == %d must be in [1,32]", p.StackPusher, n)
}

size := n
for _, b := range buf {
if b == 0 {
size--
} else {
break
}
}
if size == 0 {
return []byte{byte(vm.PUSH0)}, nil
}

return append(
// PUSH0 to PUSH32 are contiguous, so we can perform arithmetic on them.
[]byte{byte(vm.PUSH0 + vm.OpCode(size))},
buf[n-size:]...,
), nil
}

// PUSHSelector returns a PUSH4 Bytecoder that pushes the selector of the
// signature, i.e. `sha3(sig)[:4]`.
func PUSHSelector(sig string) Bytecoder {
func PUSHSelector(sig string) types.Bytecoder {
return PUSH(crypto.Keccak256([]byte(sig))[:4])
}

// PUSHBytes accepts [1,32] bytes, returning a PUSH<x> Bytecoder where x is the
// smallest number of bytes (possibly zero) that can represent the concatenated
// values; i.e. x = len(bs) - leadingZeros(bs).
func PUSHBytes(bs ...byte) Bytecoder {
return BytecoderFromStackPusher(bytesPusher(bs))
func PUSHBytes(bs ...byte) types.Bytecoder {
return types.BytecoderFromStackPusher(bytesPusher(bs))
}

type bytesPusher []byte
Expand All @@ -159,8 +107,8 @@ func (p bytesPusher) ToPush() []byte { return []byte(p) }
func PUSH[P interface {
int | uint64 | common.Address | uint256.Int | byte | []byte | JUMPDEST | string
}](v P,
) Bytecoder {
pToB := BytecoderFromStackPusher
) types.Bytecoder {
pToB := types.BytecoderFromStackPusher

switch v := any(v).(type) {
case int:
Expand Down
7 changes: 4 additions & 3 deletions specialops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/google/go-cmp/cmp"
"github.com/holiman/uint256"
"github.com/solidifylabs/specialops/types"
)

// mustRunByteCode propagates arguments to runBytecode, calling log.Fatal() on
Expand Down Expand Up @@ -203,7 +204,7 @@ func TestRunCompiled(t *testing.T) {
}
}

func bytecode(t *testing.T, b Bytecoder) []byte {
func bytecode(t *testing.T, b types.Bytecoder) []byte {
t.Helper()
buf, err := b.Bytecode()
if err != nil {
Expand All @@ -212,7 +213,7 @@ func bytecode(t *testing.T, b Bytecoder) []byte {
return buf
}

func TestPUSHBytesZeroes(t *testing.T) {
func TestPUSHZeroes(t *testing.T) {
push0 := []byte{byte(vm.PUSH0)}

t.Run("all-zero bytes", func(t *testing.T) {
Expand All @@ -225,7 +226,7 @@ func TestPUSHBytesZeroes(t *testing.T) {
})

t.Run("various types zero", func(t *testing.T) {
for _, b := range []Bytecoder{
for _, b := range []types.Bytecoder{
PUSH(int(0)),
PUSH(uint64(0)),
PUSH(common.Address{}),
Expand Down
63 changes: 63 additions & 0 deletions types/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Package types defines types used by the specialops package, which is intended
// to be dot-imported so requires a minimal footprint of exported symbols.
package types

import (
"fmt"

"github.com/ethereum/go-ethereum/core/vm"
)

// A Bytecoder returns raw EVM bytecode. If the returned bytecode is the
// concatenation of multiple Bytecoder outputs, the type MUST also implement
// BytecodeHolder.
type Bytecoder interface {
Bytecode() ([]byte, error)
}

// A BytecodeHolder is a concatenation of Bytecoders.
type BytecodeHolder interface {
Bytecoder
Bytecoders() []Bytecoder
}

// A StackPusher returns [1,32] bytes to be pushed to the stack.
type StackPusher interface {
ToPush() []byte
}

// BytecoderFromStackPusher returns a Bytecoder that calls s.ToPush() and
// prepends the appropriate PUSH<N> opcode to the returned bytecode.
func BytecoderFromStackPusher(s StackPusher) Bytecoder {
return pusher{s}
}

type pusher struct {
StackPusher
}

func (p pusher) Bytecode() ([]byte, error) {
buf := p.ToPush()
n := len(buf)
if n == 0 || n > 32 {
return nil, fmt.Errorf("len(%T.ToPush()) == %d must be in [1,32]", p.StackPusher, n)
}

size := n
for _, b := range buf {
if b == 0 {
size--
} else {
break
}
}
if size == 0 {
return []byte{byte(vm.PUSH0)}, nil
}

return append(
// PUSH0 to PUSH32 are contiguous, so we can perform arithmetic on them.
[]byte{byte(vm.PUSH0 + vm.OpCode(size))},
buf[n-size:]...,
), nil
}

0 comments on commit ad051a8

Please sign in to comment.