Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}