Skip to content

Commit fd1347f

Browse files
jasonpaulosaldur
authored andcommitted
Support ABI reference types and other improvements (#482)
* Support ABI reference types * Use new `ABIContract.networks` field * Document `populateForeignArray` * Change `equal` to `equals` * Temporarily change algorand-sdk-testing branch * Fix firefox step * Implement testing support for multiple methods in one atomic group * Support ABI app creation & update * Only check last log for return value * Add description fields for Interface and Contract * Fix return bug * Set status to submitted during execution * Switch back to master branch of cucumber repo * Remove magic constants 15 and 14
1 parent a95a90b commit fd1347f

File tree

10 files changed

+693
-174
lines changed

10 files changed

+693
-174
lines changed

src/abi/abi_type.ts

Lines changed: 97 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export abstract class ABIType {
4343
// Converts a ABIType object to a string
4444
abstract toString(): string;
4545
// Checks if two ABIType objects are equal in value
46-
abstract equal(other: ABIType): boolean;
46+
abstract equals(other: ABIType): boolean;
4747
// Checks if the ABIType object (or any of its child types) have dynamic length
4848
abstract isDynamic(): boolean;
4949
// Returns the size of the ABIType object in bytes
@@ -139,10 +139,8 @@ export class ABIUintType extends ABIType {
139139
return `uint${this.bitSize}`;
140140
}
141141

142-
equal(other: ABIUintType) {
143-
return (
144-
this.constructor === other.constructor && this.bitSize === other.bitSize
145-
);
142+
equals(other: ABIType) {
143+
return other instanceof ABIUintType && this.bitSize === other.bitSize;
146144
}
147145

148146
isDynamic() {
@@ -153,12 +151,11 @@ export class ABIUintType extends ABIType {
153151
return this.bitSize / 8;
154152
}
155153

156-
encode(value: bigint | number) {
157-
if (
158-
(typeof value !== 'bigint' && typeof value !== 'number') ||
159-
value >= BigInt(2 ** this.bitSize) ||
160-
value < BigInt(0)
161-
) {
154+
encode(value: ABIValue) {
155+
if (typeof value !== 'bigint' && typeof value !== 'number') {
156+
throw new Error(`Cannot encode value as uint${this.bitSize}: ${value}`);
157+
}
158+
if (value >= BigInt(2 ** this.bitSize) || value < BigInt(0)) {
162159
throw new Error(
163160
`${value} is not a non-negative int or too big to fit in size uint${this.bitSize}`
164161
);
@@ -171,7 +168,7 @@ export class ABIUintType extends ABIType {
171168
return bigIntToBytes(value, this.bitSize / 8);
172169
}
173170

174-
decode(byteString: Uint8Array) {
171+
decode(byteString: Uint8Array): bigint {
175172
if (byteString.length !== this.bitSize / 8) {
176173
throw new Error(`byte string must correspond to a uint${this.bitSize}`);
177174
}
@@ -199,9 +196,9 @@ export class ABIUfixedType extends ABIType {
199196
return `ufixed${this.bitSize}x${this.precision}`;
200197
}
201198

202-
equal(other: ABIUfixedType) {
199+
equals(other: ABIType) {
203200
return (
204-
this.constructor === other.constructor &&
201+
other instanceof ABIUfixedType &&
205202
this.bitSize === other.bitSize &&
206203
this.precision === other.precision
207204
);
@@ -215,14 +212,13 @@ export class ABIUfixedType extends ABIType {
215212
return this.bitSize / 8;
216213
}
217214

218-
encode(value: bigint | number) {
219-
if (
220-
(typeof value !== 'bigint' && typeof value !== 'number') ||
221-
value >= BigInt(2 ** this.bitSize) ||
222-
value < BigInt(0)
223-
) {
215+
encode(value: ABIValue) {
216+
if (typeof value !== 'bigint' && typeof value !== 'number') {
217+
throw new Error(`Cannot encode value as ${this.toString()}: ${value}`);
218+
}
219+
if (value >= BigInt(2 ** this.bitSize) || value < BigInt(0)) {
224220
throw new Error(
225-
`${value} is not a non-negative int or too big to fit in size ufixed${this.bitSize}`
221+
`${value} is not a non-negative int or too big to fit in size ${this.toString()}`
226222
);
227223
}
228224
if (typeof value === 'number' && !Number.isSafeInteger(value)) {
@@ -233,9 +229,9 @@ export class ABIUfixedType extends ABIType {
233229
return bigIntToBytes(value, this.bitSize / 8);
234230
}
235231

236-
decode(byteString: Uint8Array) {
232+
decode(byteString: Uint8Array): bigint {
237233
if (byteString.length !== this.bitSize / 8) {
238-
throw new Error(`byte string must correspond to a ufixed${this.bitSize}`);
234+
throw new Error(`byte string must correspond to a ${this.toString()}`);
239235
}
240236
return bytesToBigInt(byteString);
241237
}
@@ -246,8 +242,8 @@ export class ABIAddressType extends ABIType {
246242
return 'address';
247243
}
248244

249-
equal(other: ABIAddressType) {
250-
return this.constructor === other.constructor;
245+
equals(other: ABIType) {
246+
return other instanceof ABIAddressType;
251247
}
252248

253249
isDynamic() {
@@ -258,20 +254,23 @@ export class ABIAddressType extends ABIType {
258254
return ADDR_BYTE_SIZE;
259255
}
260256

261-
encode(value: string | Uint8Array) {
257+
encode(value: ABIValue) {
258+
if (typeof value !== 'string' && !(value instanceof Uint8Array)) {
259+
throw new Error(`Cannot encode value as ${this.toString()}: ${value}`);
260+
}
262261
if (typeof value === 'string') {
263262
const decodedAddress = decodeAddress(value);
264263
return decodedAddress.publicKey;
265264
}
266265
// Return the address if it is already in bytes
267-
if (value.length !== 32) {
266+
if (value.byteLength !== 32) {
268267
throw new Error(`byte string must be 32 bytes long for an address`);
269268
}
270269
return value;
271270
}
272271

273-
decode(byteString: Uint8Array) {
274-
if (byteString.length !== 32) {
272+
decode(byteString: Uint8Array): string {
273+
if (byteString.byteLength !== 32) {
275274
throw new Error(`byte string must be 32 bytes long for an address`);
276275
}
277276
return encodeAddress(byteString);
@@ -283,8 +282,8 @@ export class ABIBoolType extends ABIType {
283282
return 'bool';
284283
}
285284

286-
equal(other: ABIBoolType) {
287-
return this.constructor === other.constructor;
285+
equals(other: ABIType) {
286+
return other instanceof ABIBoolType;
288287
}
289288

290289
isDynamic() {
@@ -295,15 +294,18 @@ export class ABIBoolType extends ABIType {
295294
return SINGLE_BOOL_SIZE;
296295
}
297296

298-
encode(value: boolean) {
297+
encode(value: ABIValue) {
298+
if (typeof value !== 'boolean') {
299+
throw new Error(`Cannot encode value as bool: ${value}`);
300+
}
299301
if (value) {
300302
return new Uint8Array([128]);
301303
}
302304
return new Uint8Array([0]);
303305
}
304306

305-
decode(byteString: Uint8Array) {
306-
if (byteString.length !== 1) {
307+
decode(byteString: Uint8Array): boolean {
308+
if (byteString.byteLength !== 1) {
307309
throw new Error(`bool string must be 1 byte long`);
308310
}
309311
const value = byteString[0];
@@ -322,8 +324,8 @@ export class ABIByteType extends ABIType {
322324
return 'byte';
323325
}
324326

325-
equal(other: ABIByteType) {
326-
return this.constructor === other.constructor;
327+
equals(other: ABIType) {
328+
return other instanceof ABIByteType;
327329
}
328330

329331
isDynamic() {
@@ -334,15 +336,22 @@ export class ABIByteType extends ABIType {
334336
return SINGLE_BYTE_SIZE;
335337
}
336338

337-
encode(value: number) {
339+
encode(value: ABIValue) {
340+
if (typeof value !== 'number' && typeof value !== 'bigint') {
341+
throw new Error(`Cannot encode value as byte: ${value}`);
342+
}
343+
if (typeof value === 'bigint') {
344+
// eslint-disable-next-line no-param-reassign
345+
value = Number(value);
346+
}
338347
if (value < 0 || value > 255) {
339348
throw new Error(`${value} cannot be encoded into a byte`);
340349
}
341350
return new Uint8Array([value]);
342351
}
343352

344-
decode(byteString: Uint8Array) {
345-
if (byteString.length !== 1) {
353+
decode(byteString: Uint8Array): number {
354+
if (byteString.byteLength !== 1) {
346355
throw new Error(`byte string must be 1 byte long`);
347356
}
348357
return byteString[0];
@@ -354,8 +363,8 @@ export class ABIStringType extends ABIType {
354363
return 'string';
355364
}
356365

357-
equal(other: ABIStringType) {
358-
return this.constructor === other.constructor;
366+
equals(other: ABIType) {
367+
return other instanceof ABIStringType;
359368
}
360369

361370
isDynamic() {
@@ -366,7 +375,10 @@ export class ABIStringType extends ABIType {
366375
throw new Error(`${this.toString()} is a dynamic type`);
367376
}
368377

369-
encode(value: string) {
378+
encode(value: ABIValue) {
379+
if (typeof value !== 'string' && !(value instanceof Uint8Array)) {
380+
throw new Error(`Cannot encode value as string: ${value}`);
381+
}
370382
const encodedBytes = Buffer.from(value);
371383
const encodedLength = bigIntToBytes(value.length, LENGTH_ENCODE_BYTE_SIZE);
372384
const mergedBytes = new Uint8Array(value.length + LENGTH_ENCODE_BYTE_SIZE);
@@ -375,9 +387,11 @@ export class ABIStringType extends ABIType {
375387
return mergedBytes;
376388
}
377389

378-
decode(byteString: Uint8Array) {
390+
decode(byteString: Uint8Array): string {
379391
if (byteString.length < LENGTH_ENCODE_BYTE_SIZE) {
380-
throw new Error(`byte string is too short to be decoded: ${byteString}`);
392+
throw new Error(
393+
`byte string is too short to be decoded. Actual length is ${byteString.length}, but expected at least ${LENGTH_ENCODE_BYTE_SIZE}`
394+
);
381395
}
382396
const buf = Buffer.from(byteString);
383397
const byteLength = buf.readUIntBE(0, LENGTH_ENCODE_BYTE_SIZE);
@@ -387,11 +401,10 @@ export class ABIStringType extends ABIType {
387401
);
388402
if (byteLength !== byteValue.length) {
389403
throw new Error(
390-
`string length bytes do not match the actual length of string: ${byteString}`
404+
`string length bytes do not match the actual length of string. Expected ${byteLength}, got ${byteValue.length}`
391405
);
392406
}
393-
const stringValue = Buffer.from(byteValue).toString('utf-8');
394-
return stringValue;
407+
return Buffer.from(byteValue).toString('utf-8');
395408
}
396409
}
397410

@@ -414,11 +427,11 @@ export class ABIArrayStaticType extends ABIType {
414427
return `${this.childType.toString()}[${this.staticLength}]`;
415428
}
416429

417-
equal(other: ABIArrayStaticType) {
430+
equals(other: ABIType) {
418431
return (
419-
this.constructor === other.constructor &&
420-
this.childType === other.childType &&
421-
this.staticLength === other.staticLength
432+
other instanceof ABIArrayStaticType &&
433+
this.staticLength === other.staticLength &&
434+
this.childType.equals(other.childType)
422435
);
423436
}
424437

@@ -433,15 +446,20 @@ export class ABIArrayStaticType extends ABIType {
433446
return this.staticLength * this.childType.byteLen();
434447
}
435448

436-
encode(values: ABIValue[]) {
437-
if (values.length !== this.staticLength) {
438-
throw new Error(`value array does not match static array length`);
449+
encode(value: ABIValue) {
450+
if (!Array.isArray(value) && !(value instanceof Uint8Array)) {
451+
throw new Error(`Cannot encode value as ${this.toString()}: ${value}`);
452+
}
453+
if (value.length !== this.staticLength) {
454+
throw new Error(
455+
`Value array does not match static array length. Expected ${this.staticLength}, got ${value.length}`
456+
);
439457
}
440458
const convertedTuple = this.toABITupleType();
441-
return convertedTuple.encode(values);
459+
return convertedTuple.encode(value);
442460
}
443461

444-
decode(byteString: Uint8Array) {
462+
decode(byteString: Uint8Array): ABIValue[] {
445463
const convertedTuple = this.toABITupleType();
446464
return convertedTuple.decode(byteString);
447465
}
@@ -463,10 +481,10 @@ export class ABIArrayDynamicType extends ABIType {
463481
return `${this.childType.toString()}[]`;
464482
}
465483

466-
equal(other: ABIArrayDynamicType) {
484+
equals(other: ABIType) {
467485
return (
468-
this.constructor === other.constructor &&
469-
this.childType === other.childType
486+
other instanceof ABIArrayDynamicType &&
487+
this.childType.equals(other.childType)
470488
);
471489
}
472490

@@ -478,9 +496,12 @@ export class ABIArrayDynamicType extends ABIType {
478496
throw new Error(`${this.toString()} is a dynamic type`);
479497
}
480498

481-
encode(values: ABIValue[]) {
482-
const convertedTuple = this.toABITupleType(values.length);
483-
const encodedTuple = convertedTuple.encode(values);
499+
encode(value: ABIValue) {
500+
if (!Array.isArray(value) && !(value instanceof Uint8Array)) {
501+
throw new Error(`Cannot encode value as ${this.toString()}: ${value}`);
502+
}
503+
const convertedTuple = this.toABITupleType(value.length);
504+
const encodedTuple = convertedTuple.encode(value);
484505
const encodedLength = bigIntToBytes(
485506
convertedTuple.childTypes.length,
486507
LENGTH_ENCODE_BYTE_SIZE
@@ -489,7 +510,7 @@ export class ABIArrayDynamicType extends ABIType {
489510
return mergedBytes;
490511
}
491512

492-
decode(byteString: Uint8Array) {
513+
decode(byteString: Uint8Array): ABIValue[] {
493514
const buf = Buffer.from(byteString);
494515
const byteLength = buf.readUIntBE(0, LENGTH_ENCODE_BYTE_SIZE);
495516
const convertedTuple = this.toABITupleType(byteLength);
@@ -524,10 +545,13 @@ export class ABITupleType extends ABIType {
524545
return `(${typeStrings.join(',')})`;
525546
}
526547

527-
equal(other: ABITupleType) {
548+
equals(other: ABIType) {
528549
return (
529-
this.constructor === other.constructor &&
530-
this.childTypes === other.childTypes
550+
other instanceof ABITupleType &&
551+
this.childTypes.length === other.childTypes.length &&
552+
this.childTypes.every((child, index) =>
553+
child.equals(other.childTypes[index])
554+
)
531555
);
532556
}
533557

@@ -552,8 +576,12 @@ export class ABITupleType extends ABIType {
552576
return size;
553577
}
554578

555-
encode(values: ABIValue[]) {
556-
if (values.length > MAX_LEN) {
579+
encode(value: ABIValue) {
580+
if (!Array.isArray(value) && !(value instanceof Uint8Array)) {
581+
throw new Error(`Cannot encode value as ${this.toString()}: ${value}`);
582+
}
583+
const values = Array.from(value);
584+
if (value.length > MAX_LEN) {
557585
throw new Error('length of tuple array should not exceed a uint16');
558586
}
559587
const tupleTypes = this.childTypes;
@@ -620,7 +648,7 @@ export class ABITupleType extends ABIType {
620648
return concatArrays(...heads, ...tails);
621649
}
622650

623-
decode(byteString: Uint8Array) {
651+
decode(byteString: Uint8Array): ABIValue[] {
624652
const tupleTypes = this.childTypes;
625653
const dynamicSegments: Segment[] = [];
626654
const valuePartition: Uint8Array[] = [];

0 commit comments

Comments
 (0)