precompile_test.go 4.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
package script

import (
	"errors"
	"math/big"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/ethereum/go-ethereum/accounts/abi"
12
	"github.com/ethereum/go-ethereum/common"
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
	"github.com/ethereum/go-ethereum/core/vm"
	"github.com/ethereum/go-ethereum/crypto"
)

type EmbeddedExample struct {
	Foo uint64
}

func (e *EmbeddedExample) TwoFoo() uint64 {
	e.Foo *= 2
	return e.Foo
}

type ExamplePrecompile struct {
	EmbeddedExample

	Bar       *big.Int
	hello     string
	helloFrom string
}

var testErr = errors.New("test err")

func (e *ExamplePrecompile) Greet(name string) (string, error) {
	if name == "mallory" {
		return "", testErr
	}
	e.helloFrom = name
	return e.hello + " " + name + "!", nil
}

func (e *ExamplePrecompile) Things() (bar *big.Int, hello string, seen string) {
	return e.Bar, e.hello, e.helloFrom
}

func (e *ExamplePrecompile) AddAndMul(a, b, c uint64, x uint8) uint64 {
	return (a + b + c) * uint64(x)
}

func TestPrecompile(t *testing.T) {
	e := &ExamplePrecompile{hello: "Hola", EmbeddedExample: EmbeddedExample{Foo: 42}, Bar: big.NewInt(123)}
	p, err := NewPrecompile[*ExamplePrecompile](e)
	require.NoError(t, err)

	for k, v := range p.abiMethods {
		t.Logf("4byte: %x  ABI: %s  Go: %s", k, v.abiSignature, v.goName)
	}

	// input/output
	input := crypto.Keccak256([]byte("greet(string)"))[:4]
	input = append(input, b32(0x20)...)                 // offset
	input = append(input, b32(uint64(len("alice")))...) // length
	input = append(input, "alice"...)
	out, err := p.Run(input)
	require.NoError(t, err)
	require.Equal(t, e.helloFrom, "alice")
	require.Equal(t, out[:32], b32(0x20))
	require.Equal(t, out[32:32*2], b32(uint64(len("Hola alice!"))))
71
	require.Equal(t, out[32*2:32*3], rightPad32([]byte("Hola alice!")))
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105

	// error handling
	input = crypto.Keccak256([]byte("greet(string)"))[:4]
	input = append(input, b32(0x20)...)                   // offset
	input = append(input, b32(uint64(len("mallory")))...) // length
	input = append(input, "mallory"...)
	out, err = p.Run(input)
	require.Equal(t, err, vm.ErrExecutionReverted)
	msg, err := abi.UnpackRevert(out)
	require.NoError(t, err, "must unpack revert data")
	require.True(t, strings.HasSuffix(msg, testErr.Error()), "revert data must end with the inner error")

	// field reads
	input = crypto.Keccak256([]byte("foo()"))[:4]
	out, err = p.Run(input)
	require.NoError(t, err)
	require.Equal(t, out, b32(42))

	input = crypto.Keccak256([]byte("twoFoo()"))[:4]
	out, err = p.Run(input)
	require.NoError(t, err)
	require.Equal(t, out, b32(42*2))

	// persistent state changes
	input = crypto.Keccak256([]byte("twoFoo()"))[:4]
	out, err = p.Run(input)
	require.NoError(t, err)
	require.Equal(t, out, b32(42*2*2))

	// multi-output
	input = crypto.Keccak256([]byte("things()"))[:4]
	out, err = p.Run(input)
	require.NoError(t, err)
	require.Equal(t, b32(123), out[:32])
106 107 108 109 110 111
	require.Equal(t, b32(32*3), out[32*1:32*2])                   // offset of hello
	require.Equal(t, b32(32*5), out[32*2:32*3])                   // offset of seen
	require.Equal(t, b32(uint64(len("Hola"))), out[32*3:32*4])    // length of hello
	require.Equal(t, rightPad32([]byte("Hola")), out[32*4:32*5])  // hello content
	require.Equal(t, b32(uint64(len("alice"))), out[32*5:32*6])   // length of seen
	require.Equal(t, rightPad32([]byte("alice")), out[32*6:32*7]) // seen content
112 113 114 115 116 117 118 119 120 121 122

	// multi-input
	input = crypto.Keccak256([]byte("addAndMul(uint64,uint64,uint64,uint8)"))[:4]
	input = append(input, b32(42)...)
	input = append(input, b32(100)...)
	input = append(input, b32(7)...)
	input = append(input, b32(3)...)
	out, err = p.Run(input)
	require.NoError(t, err)
	require.Equal(t, b32((42+100+7)*3), out)
}
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143

type DeploymentExample struct {
	FooBar common.Address
}

func TestDeploymentOutputPrecompile(t *testing.T) {
	e := &DeploymentExample{}
	p, err := NewPrecompile[*DeploymentExample](e, WithFieldSetter[*DeploymentExample])
	require.NoError(t, err)

	addr := common.Address{0: 0x42, 19: 0xaa}
	fooBarSelector := bytes4("fooBar()")
	var input []byte
	input = append(input, setterFnBytes4[:]...)
	input = append(input, rightPad32(fooBarSelector[:])...)
	input = append(input, leftPad32(addr[:])...)
	out, err := p.Run(input)
	require.NoError(t, err)
	require.Empty(t, out)
	require.Equal(t, addr, e.FooBar)
}