encode.gno
5.09 Kb · 278 lines
1package abi
2
3import (
4 "encoding/binary"
5
6 u256 "gno.land/p/onbloc/math/uint256"
7)
8
9// Encode ABI-encodes values according to schema using Solidity's params tuple form.
10func Encode(schema Schema, values []any) ([]byte, error) {
11 return encodeTuple(schema.Fields, values)
12}
13
14func encodeTuple(fields []Field, values []any) ([]byte, error) {
15 if len(fields) != len(values) {
16 return nil, ErrInvalidValue
17 }
18
19 dyn := make([]bool, len(fields))
20 headLen := 0
21
22 for i, field := range fields {
23 dyn[i] = isDynamic(field)
24 if dyn[i] {
25 headLen += wordSize
26 continue
27 }
28
29 size, err := staticHeadSize(field)
30 if err != nil {
31 return nil, err
32 }
33
34 headLen += size
35 }
36
37 head := make([]byte, 0, headLen)
38
39 var tail []byte
40
41 for i, field := range fields {
42 encoded, err := encodeValue(field, values[i])
43 if err != nil {
44 return nil, err
45 }
46
47 if dyn[i] {
48 head = append(head, encodeUint64Word(uint64(headLen+len(tail)))...)
49 tail = append(tail, encoded...)
50 } else {
51 head = append(head, encoded...)
52 }
53 }
54
55 return append(head, tail...), nil
56}
57
58func encodeValue(field Field, value any) ([]byte, error) {
59 switch field.Type {
60 case TypeUint8:
61 v, ok := value.(uint8)
62 if !ok {
63 return nil, ErrInvalidValue
64 }
65
66 return encodeUint64Word(uint64(v)), nil
67 case TypeUint32:
68 v, ok := value.(uint32)
69 if !ok {
70 return nil, ErrInvalidValue
71 }
72
73 return encodeUint64Word(uint64(v)), nil
74 case TypeUint64:
75 v, ok := value.(uint64)
76 if !ok {
77 return nil, ErrInvalidValue
78 }
79
80 return encodeUint64Word(v), nil
81 case TypeUint256:
82 v, ok := value.(*u256.Uint)
83 if !ok || v == nil {
84 return nil, ErrInvalidValue
85 }
86
87 return encodeUint256Word(v), nil
88 case TypeBytes32:
89 v, ok := value.([32]byte)
90 if !ok {
91 return nil, ErrInvalidValue
92 }
93
94 out := make([]byte, wordSize)
95 copy(out, v[:])
96
97 return out, nil
98 case TypeBytes:
99 v, ok := value.([]byte)
100 if !ok {
101 return nil, ErrInvalidValue
102 }
103
104 return encodeBytes(v), nil
105 case TypeString:
106 v, ok := value.(string)
107 if !ok {
108 return nil, ErrInvalidValue
109 }
110
111 return encodeBytes([]byte(v)), nil
112 case TypeBool:
113 v, ok := value.(bool)
114 if !ok {
115 return nil, ErrInvalidValue
116 }
117
118 if v {
119 return encodeUint64Word(1), nil
120 }
121
122 return encodeUint64Word(0), nil
123 case TypeAddress:
124 v, ok := value.([]byte)
125 if !ok || len(v) != 20 {
126 return nil, ErrInvalidValue
127 }
128
129 out := make([]byte, wordSize)
130 copy(out[12:], v)
131
132 return out, nil
133 case TypeStruct:
134 if field.Sub == nil {
135 return nil, ErrInvalidSchema
136 }
137
138 v, ok := value.([]any)
139 if !ok {
140 return nil, ErrInvalidValue
141 }
142
143 return encodeTuple(field.Sub.Fields, v)
144 case TypeArray:
145 if field.Elem == nil {
146 return nil, ErrInvalidSchema
147 }
148
149 v, ok := value.([]any)
150 if !ok {
151 return nil, ErrInvalidValue
152 }
153
154 return encodeArray(*field.Elem, v)
155 default:
156 return nil, ErrUnsupportedType
157 }
158}
159
160func encodeArray(elem Field, values []any) ([]byte, error) {
161 out := encodeUint64Word(uint64(len(values)))
162 if len(values) == 0 {
163 return out, nil
164 }
165
166 if isDynamic(elem) {
167 headLen := len(values) * wordSize
168 head := make([]byte, 0, headLen)
169
170 var tail []byte
171
172 for _, value := range values {
173 encoded, err := encodeValue(elem, value)
174 if err != nil {
175 return nil, err
176 }
177
178 head = append(head, encodeUint64Word(uint64(headLen+len(tail)))...)
179 tail = append(tail, encoded...)
180 }
181
182 out = append(out, head...)
183
184 return append(out, tail...), nil
185 }
186
187 for _, value := range values {
188 encoded, err := encodeValue(elem, value)
189 if err != nil {
190 return nil, err
191 }
192
193 out = append(out, encoded...)
194 }
195
196 return out, nil
197}
198
199func encodeBytes(v []byte) []byte {
200 pad := (wordSize - len(v)%wordSize) % wordSize
201 out := make([]byte, 0, wordSize+len(v)+pad)
202 out = append(out, encodeUint64Word(uint64(len(v)))...)
203
204 out = append(out, v...)
205 if pad > 0 {
206 out = append(out, make([]byte, pad)...)
207 }
208
209 return out
210}
211
212func encodeUint64Word(v uint64) []byte {
213 out := make([]byte, wordSize)
214 binary.BigEndian.PutUint64(out[24:], v)
215
216 return out
217}
218
219// NOTE: depends on gnoswap/uint256's internal [4]uint64 layout (mirror of
220// SetBytes32). Replace with the upstream Bytes32() method once it exists.
221// Same workaround lives in ibc/union/zkgm/salt.gno; consolidate when fixed.
222func encodeUint256Word(v *u256.Uint) []byte {
223 out := make([]byte, wordSize)
224 binary.BigEndian.PutUint64(out[0:8], v[3])
225 binary.BigEndian.PutUint64(out[8:16], v[2])
226 binary.BigEndian.PutUint64(out[16:24], v[1])
227 binary.BigEndian.PutUint64(out[24:32], v[0])
228
229 return out
230}
231
232func isDynamic(field Field) bool {
233 switch field.Type {
234 case TypeBytes, TypeString, TypeArray:
235 return true
236 case TypeStruct:
237 if field.Sub == nil {
238 return true
239 }
240
241 for _, sub := range field.Sub.Fields {
242 if isDynamic(sub) {
243 return true
244 }
245 }
246
247 return false
248 default:
249 return false
250 }
251}
252
253func staticHeadSize(field Field) (int, error) {
254 if isDynamic(field) {
255 return wordSize, nil
256 }
257
258 if field.Type == TypeStruct {
259 if field.Sub == nil {
260 return 0, ErrInvalidSchema
261 }
262
263 size := 0
264
265 for _, sub := range field.Sub.Fields {
266 subSize, err := staticHeadSize(sub)
267 if err != nil {
268 return 0, err
269 }
270
271 size += subSize
272 }
273
274 return size, nil
275 }
276
277 return wordSize, nil
278}