1
2
3
4
5
6
7
8
9
10
11
12
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
71
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package script
import (
"errors"
"math/big"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"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!"))))
require.Equal(t, out[32*2:32*3], rightPad32([]byte("Hola alice!")))
// 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])
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
// 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)
}
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)
}