diff --git a/.changeset/old-years-grab.md b/.changeset/old-years-grab.md new file mode 100644 index 00000000000..67f74a5c737 --- /dev/null +++ b/.changeset/old-years-grab.md @@ -0,0 +1,8 @@ +--- +"@fuel-ts/abi-coder": minor +"@fuel-ts/predicate": minor +"@fuel-ts/program": minor +"@fuel-ts/script": minor +--- + +Added improved Vector support diff --git a/.changeset/six-lamps-rhyme.md b/.changeset/six-lamps-rhyme.md new file mode 100644 index 00000000000..4532e788f32 --- /dev/null +++ b/.changeset/six-lamps-rhyme.md @@ -0,0 +1,5 @@ +--- +"fuels": patch +--- + +Added tests diff --git a/packages/abi-coder/src/abi-coder.test.ts b/packages/abi-coder/src/abi-coder.test.ts index 9aabed4aaeb..dd6ee73fd4a 100644 --- a/packages/abi-coder/src/abi-coder.test.ts +++ b/packages/abi-coder/src/abi-coder.test.ts @@ -3,6 +3,8 @@ import { bn, toHex } from '@fuel-ts/math'; import AbiCoder from './abi-coder'; import type { DecodedValue } from './coders/abstract-coder'; +import type { JsonAbiFragmentType, JsonFlatAbi } from './json-abi'; +import { ABI } from './json-abi'; const B256 = '0xd5579c46dfcc7f18207013e65b44e4cb4e2c2298f4ac457ba8f82743f31e930b'; @@ -22,6 +24,70 @@ describe('AbiCoder', () => { expect(decoded).toEqual([B256]); }); + it('encodes and decodes a u8 struct', () => { + const types = [ + { + name: 'MyStruct', + type: 'struct MyStruct', + components: [ + { + name: 'num', + type: 'u8', + }, + { + name: 'bar', + type: 'u8', + }, + ], + }, + ]; + const encoded = abiCoder.encode(types, [ + { + num: 7, + bar: 9, + }, + ]); + expect(encoded).toEqual(new Uint8Array([0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 9])); + const decoded = abiCoder.decode(types, encoded) as DecodedValue[]; + expect(decoded).toEqual([ + { + num: 7, + bar: 9, + }, + ]); + }); + + it('encodes and decodes a u8 enum', () => { + const types = [ + { + name: 'MyStruct', + type: 'enum MyEnum', + components: [ + { + name: 'num', + type: 'u8', + }, + { + name: 'bar', + type: 'u8', + }, + ], + }, + ]; + const encoded = abiCoder.encode(types, [ + { + bar: 9, + }, + ]); + expect(encoded).toEqual(new Uint8Array([0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 9])); + const decoded = abiCoder.decode(types, encoded) as DecodedValue[]; + expect(decoded).toEqual([ + { + bar: 9, + }, + ]); + }); + it('encodes and decodes multiple primitives', () => { const types = [ { @@ -185,7 +251,7 @@ describe('AbiCoder', () => { ); }); - it('encodes vectors', () => { + it('encodes vector [u8]', () => { const types = [ { name: 'vector', @@ -238,14 +304,14 @@ describe('AbiCoder', () => { const capacity = [0, 0, 0, 0, 0, 0, 0, input.length]; const length = [0, 0, 0, 0, 0, 0, 0, input.length]; const data = [0, 0, 0, 0, 0, 0, 0, input[0]]; - const vecData = concat([pointer, capacity, length, data]); + const expectedBytes = concat([pointer, capacity, length, data]); - const expected = hexlify(vecData); + const expected = hexlify(expectedBytes); expect(hexlify(encoded)).toBe(expected); }); - it('encodes vectors with multiple items', () => { + it('encodes vector [u8 multi]', () => { const types = [ { name: 'vector', @@ -269,7 +335,7 @@ describe('AbiCoder', () => { typeArguments: [ { name: '', - type: 'u64', + type: 'u8', isParamType: true, }, ], @@ -283,7 +349,7 @@ describe('AbiCoder', () => { typeArguments: [ { name: '', - type: 'u64', + type: 'u8', isParamType: true, }, ], @@ -291,7 +357,7 @@ describe('AbiCoder', () => { }, ]; - const input = [36, 42, 57]; + const input = [36, 23, 19]; const encoded = abiCoder.encode(types, [input]); const pointer = [0, 0, 0, 0, 0, 0, 0, 24]; @@ -300,14 +366,14 @@ describe('AbiCoder', () => { const data1 = [0, 0, 0, 0, 0, 0, 0, input[0]]; const data2 = [0, 0, 0, 0, 0, 0, 0, input[1]]; const data3 = [0, 0, 0, 0, 0, 0, 0, input[2]]; - const vecData = concat([pointer, capacity, length, data1, data2, data3]); + const expectedBytes = concat([pointer, capacity, length, data1, data2, data3]); - const expected = hexlify(vecData); + const expected = hexlify(expectedBytes); expect(hexlify(encoded)).toBe(expected); }); - it('encodes vectors with multiple items [with offset]', () => { + it('encodes vector [u8 multi large offset]', () => { const types = [ { name: 'vector', @@ -331,7 +397,7 @@ describe('AbiCoder', () => { typeArguments: [ { name: '', - type: 'u64', + type: 'u8', isParamType: true, }, ], @@ -345,7 +411,7 @@ describe('AbiCoder', () => { typeArguments: [ { name: '', - type: 'u64', + type: 'u8', isParamType: true, }, ], @@ -353,7 +419,7 @@ describe('AbiCoder', () => { }, ]; - const input = [36, 42, 57]; + const input = [36, 23, 19]; const encoded = abiCoder.encode(types, [input], 14440); const pointer = [0, 0, 0, 0, 0, 0, 56, 128]; @@ -362,14 +428,14 @@ describe('AbiCoder', () => { const data1 = [0, 0, 0, 0, 0, 0, 0, input[0]]; const data2 = [0, 0, 0, 0, 0, 0, 0, input[1]]; const data3 = [0, 0, 0, 0, 0, 0, 0, input[2]]; - const vecData = concat([pointer, capacity, length, data1, data2, data3]); + const expectedBytes = concat([pointer, capacity, length, data1, data2, data3]); - const expected = hexlify(vecData); + const expected = hexlify(expectedBytes); expect(hexlify(encoded)).toBe(expected); }); - it('encodes inputs with [mixed params + vector second param + with offset]', () => { + it('encodes vector [u8 with offset]', () => { const types = [ { name: 'vector', @@ -382,18 +448,22 @@ describe('AbiCoder', () => { { name: 'ptr', type: 'raw untyped ptr', + isParamType: true, }, { name: 'cap', type: 'u64', + isParamType: true, }, ], typeArguments: [ { name: '', - type: 'u64', + type: 'u8', + isParamType: true, }, ], + isParamType: true, }, { name: 'len', @@ -403,37 +473,32 @@ describe('AbiCoder', () => { typeArguments: [ { name: '', - type: 'u64', + type: 'u8', + isParamType: true, }, ], + isParamType: true, }, ]; - const vector = [450, 202, 1340]; - const encoded = abiCoder.encode(types, [vector], 14440); + const input = [19]; + const encoded = abiCoder.encode(types, [input], 16); - const pointer = [0, 0, 0, 0, 0, 0, 56, 128]; - const capacity = [0, 0, 0, 0, 0, 0, 0, vector.length]; - const length = [0, 0, 0, 0, 0, 0, 0, vector.length]; - const data1 = [0, 0, 0, 0, 0, 0, Math.floor(vector[0] / 256), vector[0] % 256]; - const data2 = [0, 0, 0, 0, 0, 0, 0, vector[1]]; - const data3 = [0, 0, 0, 0, 0, 0, Math.floor(vector[2] / 256), vector[2] % 256]; - const inputAndVecData = concat([pointer, capacity, length, data1, data2, data3]); + const pointer = [0, 0, 0, 0, 0, 0, 0, 16 + 24]; + const capacity = [0, 0, 0, 0, 0, 0, 0, input.length]; + const length = [0, 0, 0, 0, 0, 0, 0, input.length]; + const data = [0, 0, 0, 0, 0, 0, 0, input[0]]; + const expectedBytes = concat([pointer, capacity, length, data]); - const expected = hexlify(inputAndVecData); + const expected = hexlify(expectedBytes); - expect(encoded).toStrictEqual(inputAndVecData); expect(hexlify(encoded)).toBe(expected); }); - it('encodes inputs with [mixed params + vector second param + with offset]', () => { + it('encodes vector [two u32 multi]', () => { const types = [ { - type: 'u32', - name: 'arg1', - }, - { - name: 'vector', + name: 'vector A', type: 'struct Vec', components: [ { @@ -449,9 +514,33 @@ describe('AbiCoder', () => { type: 'u64', }, ], - typeArguments: [ + }, + { + name: 'len', + type: 'u64', + }, + ], + typeArguments: [ + { + name: '', + type: 'u32', + }, + ], + }, + { + name: 'vector B', + type: 'struct Vec', + components: [ + { + name: 'buf', + type: 'struct RawVec', + components: [ { - name: '', + name: 'ptr', + type: 'raw untyped ptr', + }, + { + name: 'cap', type: 'u64', }, ], @@ -464,28 +553,1293 @@ describe('AbiCoder', () => { typeArguments: [ { name: '', + type: 'u32', + }, + ], + }, + ]; + + const input = [ + [36, 23, 19], + [35, 6, 4], + ]; + const encoded = abiCoder.encode(types, input); + + const pointerA = [0, 0, 0, 0, 0, 0, 0, 48]; + const pointerB = [0, 0, 0, 0, 0, 0, 0, 72]; + const capacity = [0, 0, 0, 0, 0, 0, 0, input[0].length]; + const length = [0, 0, 0, 0, 0, 0, 0, input[0].length]; + const dataA1 = [0, 0, 0, 0, 0, 0, 0, input[0][0]]; + const dataA2 = [0, 0, 0, 0, 0, 0, 0, input[0][1]]; + const dataA3 = [0, 0, 0, 0, 0, 0, 0, input[0][2]]; + const dataB1 = [0, 0, 0, 0, 0, 0, 0, input[1][0]]; + const dataB2 = [0, 0, 0, 0, 0, 0, 0, input[1][1]]; + const dataB3 = [0, 0, 0, 0, 0, 0, 0, input[1][2]]; + const expectedBytes = concat([ + pointerA, + capacity, + length, + pointerB, + capacity, + length, + dataA1, + dataA2, + dataA3, + dataB1, + dataB2, + dataB3, + ]); + + const expected = hexlify(expectedBytes); + + expect(hexlify(encoded)).toBe(expected); + }); + + it('encodes vector inside vector [single u32]', () => { + const types = [ + { + name: 'vector in vector', + type: 'struct Vec', + components: [ + { + name: 'buf', + type: 'struct RawVec', + components: [ + { + name: 'ptr', + type: 'raw untyped ptr', + isParamType: true, + }, + { + name: 'cap', + type: 'u64', + isParamType: true, + }, + ], + typeArguments: [], + isParamType: true, + }, + { + name: 'len', type: 'u64', }, ], + typeArguments: [ + { + name: 'in vector', + type: 'struct Vec', + components: [ + { + name: 'buf', + type: 'struct RawVec', + components: [ + { + name: 'ptr', + type: 'raw untyped ptr', + isParamType: true, + }, + { + name: 'cap', + type: 'u64', + isParamType: true, + }, + ], + typeArguments: [ + { + name: '', + type: 'u32', + isParamType: true, + }, + ], + isParamType: true, + }, + { + name: 'len', + type: 'u64', + }, + ], + typeArguments: [ + { + name: '', + type: 'u32', + isParamType: true, + }, + ], + isParamType: true, + }, + ], + isParamType: true, }, ]; - const u32 = 72; - const vector = [450, 202, 1340]; - const encoded = abiCoder.encode(types, [u32, vector], 14440); + const input = [[5, 6]]; + const encoded = abiCoder.encode(types, [input]); - const encodedU32 = [0, 0, 0, 0, 0, 0, 0, u32]; - const pointer = [0, 0, 0, 0, 0, 0, 56, 136]; - const capacity = [0, 0, 0, 0, 0, 0, 0, vector.length]; - const length = [0, 0, 0, 0, 0, 0, 0, vector.length]; - const data1 = [0, 0, 0, 0, 0, 0, Math.floor(vector[0] / 256), vector[0] % 256]; - const data2 = [0, 0, 0, 0, 0, 0, 0, vector[1]]; - const data3 = [0, 0, 0, 0, 0, 0, Math.floor(vector[2] / 256), vector[2] % 256]; - const inputAndVecData = concat([encodedU32, pointer, capacity, length, data1, data2, data3]); + const pointerVec1 = [0, 0, 0, 0, 0, 0, 0, 24]; + const capacityVec1 = [0, 0, 0, 0, 0, 0, 0, input.length]; + const lengthVec1 = [0, 0, 0, 0, 0, 0, 0, input.length]; - const expected = hexlify(inputAndVecData); + const pointerVec2 = [0, 0, 0, 0, 0, 0, 0, 48]; + const capacityVec2 = [0, 0, 0, 0, 0, 0, 0, input[0].length]; + const lengthVec2 = [0, 0, 0, 0, 0, 0, 0, input[0].length]; + const data1Vec1 = [0, 0, 0, 0, 0, 0, 0, input[0][0]]; + const data2Vec1 = [0, 0, 0, 0, 0, 0, 0, input[0][1]]; + + const expectedBytes = concat([ + // top level vector + pointerVec1, + capacityVec1, + lengthVec1, + // top level vector, index 0 vector + pointerVec2, + capacityVec2, + lengthVec2, + // index 0 vector's data + data1Vec1, + data2Vec1, + ]); + + const expected = hexlify(expectedBytes); + + expect(hexlify(encoded)).toBe(expected); + }); + + it('encodes vector inside array [u8 with offset]', () => { + const types = [ + { + name: 'array', + type: '[_; 1]', + components: [ + { + name: '__array_element', + type: 'struct Vec', + components: [ + { + name: 'buf', + type: 'struct RawVec', + components: [ + { + name: 'ptr', + type: 'raw untyped ptr', + }, + { + name: 'cap', + type: 'u64', + }, + ], + typeArguments: [ + { + name: '', + type: 'u8', + }, + ], + }, + { + name: 'len', + type: 'u64', + }, + ], + typeArguments: [ + { + name: '', + type: 'u8', + }, + ], + }, + ], + }, + ]; + + const offset = 40; + const input = [[5, 6]]; + const encoded = abiCoder.encode(types, [input], offset); + + const pointer = [0, 0, 0, 0, 0, 0, 0, 24 + offset]; + const capacity = [0, 0, 0, 0, 0, 0, 0, input[0].length]; + const length = [0, 0, 0, 0, 0, 0, 0, input[0].length]; + + const data1 = [0, 0, 0, 0, 0, 0, 0, input[0][0]]; + const data2 = [0, 0, 0, 0, 0, 0, 0, input[0][1]]; + const expectedBytes = concat([pointer, capacity, length, data1, data2]); + + const expected = hexlify(expectedBytes); + + expect(hexlify(encoded)).toBe(expected); + }); + + it('encodes vector inside vector [u32]', () => { + const types = [ + { + name: 'vector in vector', + type: 'struct Vec', + components: [ + { + name: 'buf', + type: 'struct RawVec', + components: [ + { + name: 'ptr', + type: 'raw untyped ptr', + isParamType: true, + }, + { + name: 'cap', + type: 'u64', + isParamType: true, + }, + ], + typeArguments: [], + isParamType: true, + }, + { + name: 'len', + type: 'u64', + }, + ], + typeArguments: [ + { + name: 'in vector', + type: 'struct Vec', + components: [ + { + name: 'buf', + type: 'struct RawVec', + components: [ + { + name: 'ptr', + type: 'raw untyped ptr', + isParamType: true, + }, + { + name: 'cap', + type: 'u64', + isParamType: true, + }, + ], + typeArguments: [ + { + name: '', + type: 'u32', + isParamType: true, + }, + ], + isParamType: true, + }, + { + name: 'len', + type: 'u64', + }, + ], + typeArguments: [ + { + name: '', + type: 'u32', + isParamType: true, + }, + ], + isParamType: true, + }, + ], + isParamType: true, + }, + ]; + + const input = [ + [0, 1, 2], + [6, 7, 8], + ]; + const encoded = abiCoder.encode(types, [input]); + + const pointer = [0, 0, 0, 0, 0, 0, 0, 24]; + const capacity = [0, 0, 0, 0, 0, 0, 0, input.length]; + const length = [0, 0, 0, 0, 0, 0, 0, input.length]; + + const pointerVec1 = [0, 0, 0, 0, 0, 0, 0, 72]; + const capacityVec1 = [0, 0, 0, 0, 0, 0, 0, input[0].length]; + const lengthVec1 = [0, 0, 0, 0, 0, 0, 0, input[0].length]; + const data1Vec1 = [0, 0, 0, 0, 0, 0, 0, input[0][0]]; + const data2Vec1 = [0, 0, 0, 0, 0, 0, 0, input[0][1]]; + const data3Vec1 = [0, 0, 0, 0, 0, 0, 0, input[0][2]]; + const pointerVec2 = [0, 0, 0, 0, 0, 0, 0, 96]; + const capacityVec2 = [0, 0, 0, 0, 0, 0, 0, input[1].length]; + const lengthVec2 = [0, 0, 0, 0, 0, 0, 0, input[1].length]; + const data1Vec2 = [0, 0, 0, 0, 0, 0, 0, input[1][0]]; + const data2Vec2 = [0, 0, 0, 0, 0, 0, 0, input[1][1]]; + const data3Vec2 = [0, 0, 0, 0, 0, 0, 0, input[1][2]]; + const expectedBytes = concat([ + // top level vector + pointer, + capacity, + length, + // top level vector, index 0 vector + pointerVec1, + capacityVec1, + lengthVec1, + // top level vector, index 1 vector + pointerVec2, + capacityVec2, + lengthVec2, + // index 0 vector's data + data1Vec1, + data2Vec1, + data3Vec1, + // index 1 vector's data + data1Vec2, + data2Vec2, + data3Vec2, + ]); + + const expected = hexlify(expectedBytes); + + expect(hexlify(encoded)).toBe(expected); + }); + + it('encodes vector inside enum', () => { + const types = [ + { + name: 'MyEnum', + type: 'enum MyEnum', + components: [ + { + name: 'num', + type: 'u8', + }, + { + name: 'vec', + type: 'struct Vec', + components: [ + { + name: 'buf', + type: 'struct RawVec', + components: [ + { + name: 'ptr', + type: 'raw untyped ptr', + isParamType: true, + }, + { + name: 'cap', + type: 'u64', + isParamType: true, + }, + ], + typeArguments: [ + { + name: '', + type: 'u8', + isParamType: true, + }, + ], + isParamType: true, + }, + { + name: 'len', + type: 'u64', + }, + ], + typeArguments: [ + { + name: '', + type: 'u8', + isParamType: true, + }, + ], + isParamType: true, + }, + ], + typeParameters: null, + }, + ]; + + const input = { + vec: [3, 9, 6, 4], + }; + const encoded = abiCoder.encode(types, [input]); + + const enumCaseOne = [0, 0, 0, 0, 0, 0, 0, 1]; + const pointer = [0, 0, 0, 0, 0, 0, 0, 32]; + const capacity = [0, 0, 0, 0, 0, 0, 0, input.vec.length]; + const length = [0, 0, 0, 0, 0, 0, 0, input.vec.length]; + const data1 = [0, 0, 0, 0, 0, 0, 0, input.vec[0]]; + const data2 = [0, 0, 0, 0, 0, 0, 0, input.vec[1]]; + const data3 = [0, 0, 0, 0, 0, 0, 0, input.vec[2]]; + const data4 = [0, 0, 0, 0, 0, 0, 0, input.vec[3]]; + const expectedBytes = concat([ + enumCaseOne, + pointer, + capacity, + length, + data1, + data2, + data3, + data4, + ]); + + const expected = hexlify(expectedBytes); + + expect(hexlify(encoded)).toBe(expected); + }); + + it('encodes vector inside struct', () => { + const types = [ + { + name: 'MyStruct', + type: 'struct MyStruct', + components: [ + { + name: 'num', + type: 'u8', + }, + { + name: 'vec', + type: 'struct Vec', + components: [ + { + name: 'buf', + type: 'struct RawVec', + components: [ + { + name: 'ptr', + type: 'raw untyped ptr', + isParamType: true, + }, + { + name: 'cap', + type: 'u64', + isParamType: true, + }, + ], + typeArguments: [ + { + name: '', + type: 'u8', + isParamType: true, + }, + ], + isParamType: true, + }, + { + name: 'len', + type: 'u64', + }, + ], + typeArguments: [ + { + name: '', + type: 'u8', + isParamType: true, + }, + ], + isParamType: true, + }, + ], + typeParameters: null, + }, + ]; + + const input = { + num: 7, + vec: [3, 9, 6, 4], + }; + const encoded = abiCoder.encode(types, [input]); + + const u8 = [0, 0, 0, 0, 0, 0, 0, 7]; + const pointer = [0, 0, 0, 0, 0, 0, 0, 32]; + const capacity = [0, 0, 0, 0, 0, 0, 0, input.vec.length]; + const length = [0, 0, 0, 0, 0, 0, 0, input.vec.length]; + const data1 = [0, 0, 0, 0, 0, 0, 0, input.vec[0]]; + const data2 = [0, 0, 0, 0, 0, 0, 0, input.vec[1]]; + const data3 = [0, 0, 0, 0, 0, 0, 0, input.vec[2]]; + const data4 = [0, 0, 0, 0, 0, 0, 0, input.vec[3]]; + const expectedBytes = concat([u8, pointer, capacity, length, data1, data2, data3, data4]); + + const expected = hexlify(expectedBytes); + + expect(hexlify(encoded)).toBe(expected); + }); + + it('encodes vector inside struct [with offset]', () => { + const types = [ + { + name: 'MyStruct', + type: 'struct MyStruct', + components: [ + { + name: 'num', + type: 'u8', + }, + { + name: 'vec', + type: 'struct Vec', + components: [ + { + name: 'buf', + type: 'struct RawVec', + components: [ + { + name: 'ptr', + type: 'raw untyped ptr', + isParamType: true, + }, + { + name: 'cap', + type: 'u64', + isParamType: true, + }, + ], + typeArguments: [ + { + name: '', + type: 'u8', + isParamType: true, + }, + ], + isParamType: true, + }, + { + name: 'len', + type: 'u64', + }, + ], + typeArguments: [ + { + name: '', + type: 'u8', + isParamType: true, + }, + ], + isParamType: true, + }, + ], + typeParameters: null, + }, + ]; + + const input = { + num: 7, + vec: [7, 6, 3], + }; + const encoded = abiCoder.encode(types, [input], 16); + + const u8 = [0, 0, 0, 0, 0, 0, 0, 7]; + const pointer = [0, 0, 0, 0, 0, 0, 0, 48]; + const capacity = [0, 0, 0, 0, 0, 0, 0, input.vec.length]; + const length = [0, 0, 0, 0, 0, 0, 0, input.vec.length]; + const data1 = [0, 0, 0, 0, 0, 0, 0, input.vec[0]]; + const data2 = [0, 0, 0, 0, 0, 0, 0, input.vec[1]]; + const data3 = [0, 0, 0, 0, 0, 0, 0, input.vec[2]]; + const expectedBytes = concat([u8, pointer, capacity, length, data1, data2, data3]); + + const expected = hexlify(expectedBytes); + + expect(hexlify(encoded)).toBe(expected); + }); + + it('encodes vectors with multiple items', () => { + const types = [ + { + name: 'vector', + type: 'struct Vec', + components: [ + { + name: 'buf', + type: 'struct RawVec', + components: [ + { + name: 'ptr', + type: 'raw untyped ptr', + isParamType: true, + }, + { + name: 'cap', + type: 'u64', + isParamType: true, + }, + ], + typeArguments: [ + { + name: '', + type: 'u64', + isParamType: true, + }, + ], + isParamType: true, + }, + { + name: 'len', + type: 'u64', + }, + ], + typeArguments: [ + { + name: '', + type: 'u64', + isParamType: true, + }, + ], + isParamType: true, + }, + ]; + + const input = [36, 42, 57]; + const encoded = abiCoder.encode(types, [input]); + + const pointer = [0, 0, 0, 0, 0, 0, 0, 24]; + const capacity = [0, 0, 0, 0, 0, 0, 0, input.length]; + const length = [0, 0, 0, 0, 0, 0, 0, input.length]; + const data1 = [0, 0, 0, 0, 0, 0, 0, input[0]]; + const data2 = [0, 0, 0, 0, 0, 0, 0, input[1]]; + const data3 = [0, 0, 0, 0, 0, 0, 0, input[2]]; + const vecData = concat([pointer, capacity, length, data1, data2, data3]); + + const expected = hexlify(vecData); + + expect(hexlify(encoded)).toBe(expected); + }); + + it('encodes vectors with multiple items [with offset]', () => { + const types = [ + { + name: 'vector', + type: 'struct Vec', + components: [ + { + name: 'buf', + type: 'struct RawVec', + components: [ + { + name: 'ptr', + type: 'raw untyped ptr', + isParamType: true, + }, + { + name: 'cap', + type: 'u64', + isParamType: true, + }, + ], + typeArguments: [ + { + name: '', + type: 'u64', + isParamType: true, + }, + ], + isParamType: true, + }, + { + name: 'len', + type: 'u64', + }, + ], + typeArguments: [ + { + name: '', + type: 'u64', + isParamType: true, + }, + ], + isParamType: true, + }, + ]; + + const input = [36, 42, 57]; + const encoded = abiCoder.encode(types, [input], 14440); + + const pointer = [0, 0, 0, 0, 0, 0, 56, 128]; + const capacity = [0, 0, 0, 0, 0, 0, 0, input.length]; + const length = [0, 0, 0, 0, 0, 0, 0, input.length]; + const data1 = [0, 0, 0, 0, 0, 0, 0, input[0]]; + const data2 = [0, 0, 0, 0, 0, 0, 0, input[1]]; + const data3 = [0, 0, 0, 0, 0, 0, 0, input[2]]; + const vecData = concat([pointer, capacity, length, data1, data2, data3]); + + const expected = hexlify(vecData); + + expect(hexlify(encoded)).toBe(expected); + }); + + it('encodes inputs with [vector with offset]', () => { + const types = [ + { + name: 'vector', + type: 'struct Vec', + components: [ + { + name: 'buf', + type: 'struct RawVec', + components: [ + { + name: 'ptr', + type: 'raw untyped ptr', + }, + { + name: 'cap', + type: 'u64', + }, + ], + typeArguments: [ + { + name: '', + type: 'u64', + }, + ], + }, + { + name: 'len', + type: 'u64', + }, + ], + typeArguments: [ + { + name: '', + type: 'u64', + }, + ], + }, + ]; + + const vector = [450, 202, 1340]; + const encoded = abiCoder.encode(types, [vector], 14440); + + const pointer = [0, 0, 0, 0, 0, 0, 56, 128]; + const capacity = [0, 0, 0, 0, 0, 0, 0, vector.length]; + const length = [0, 0, 0, 0, 0, 0, 0, vector.length]; + const data1 = [0, 0, 0, 0, 0, 0, Math.floor(vector[0] / 256), vector[0] % 256]; + const data2 = [0, 0, 0, 0, 0, 0, 0, vector[1]]; + const data3 = [0, 0, 0, 0, 0, 0, Math.floor(vector[2] / 256), vector[2] % 256]; + const inputAndVecData = concat([pointer, capacity, length, data1, data2, data3]); + + const expected = hexlify(inputAndVecData); + + expect(encoded).toStrictEqual(inputAndVecData); + expect(hexlify(encoded)).toBe(expected); + }); + + it('encodes inputs with [mixed params + vector second param + with offset]', () => { + const types = [ + { + type: 'u32', + name: 'arg1', + }, + { + name: 'vector', + type: 'struct Vec', + components: [ + { + name: 'buf', + type: 'struct RawVec', + components: [ + { + name: 'ptr', + type: 'raw untyped ptr', + }, + { + name: 'cap', + type: 'u64', + }, + ], + typeArguments: [ + { + name: '', + type: 'u64', + }, + ], + }, + { + name: 'len', + type: 'u64', + }, + ], + typeArguments: [ + { + name: '', + type: 'u64', + }, + ], + }, + ]; + + const u32 = 72; + const vector = [450, 202, 1340]; + const encoded = abiCoder.encode(types, [u32, vector], 14440); + + const encodedU32 = [0, 0, 0, 0, 0, 0, 0, u32]; + const pointer = [0, 0, 0, 0, 0, 0, 56, 136]; + const capacity = [0, 0, 0, 0, 0, 0, 0, vector.length]; + const length = [0, 0, 0, 0, 0, 0, 0, vector.length]; + const data1 = [0, 0, 0, 0, 0, 0, Math.floor(vector[0] / 256), vector[0] % 256]; + const data2 = [0, 0, 0, 0, 0, 0, 0, vector[1]]; + const data3 = [0, 0, 0, 0, 0, 0, Math.floor(vector[2] / 256), vector[2] % 256]; + const inputAndVecData = concat([encodedU32, pointer, capacity, length, data1, data2, data3]); + + const expected = hexlify(inputAndVecData); + + expect(encoded).toStrictEqual(inputAndVecData); + expect(hexlify(encoded)).toBe(expected); + }); + + it.skip('encodes vectors [lots of types]', () => { + const abi = ABI.unflatten({ + types: [ + { + typeId: 0, + type: '(_, _)', + components: [ + { + name: '__tuple_element', + type: 18, + typeArguments: null, + }, + { + name: '__tuple_element', + type: 18, + typeArguments: null, + }, + ], + typeParameters: null, + }, + { + typeId: 1, + type: '(_, _)', + components: [ + { + name: '__tuple_element', + type: 17, + typeArguments: [ + { + name: '', + type: 18, + typeArguments: null, + }, + ], + }, + { + name: '__tuple_element', + type: 17, + typeArguments: [ + { + name: '', + type: 18, + typeArguments: null, + }, + ], + }, + ], + typeParameters: null, + }, + { + typeId: 2, + type: '[_; 2]', + components: [ + { + name: '__array_element', + type: 19, + typeArguments: null, + }, + ], + typeParameters: null, + }, + { + typeId: 3, + type: '[_; 2]', + components: [ + { + name: '__array_element', + type: 17, + typeArguments: [ + { + name: '', + type: 18, + typeArguments: null, + }, + ], + }, + ], + typeParameters: null, + }, + { + typeId: 4, + type: 'bool', + components: null, + typeParameters: null, + }, + { + typeId: 5, + type: 'enum SomeEnum', + components: [ + { + name: 'a', + type: 6, + typeArguments: null, + }, + ], + typeParameters: [6], + }, + { + typeId: 6, + type: 'generic T', + components: null, + typeParameters: null, + }, + { + typeId: 7, + type: 'raw untyped ptr', + components: null, + typeParameters: null, + }, + { + typeId: 8, + type: 'str[10]', + components: null, + typeParameters: null, + }, + { + typeId: 9, + type: 'str[13]', + components: null, + typeParameters: null, + }, + { + typeId: 10, + type: 'str[14]', + components: null, + typeParameters: null, + }, + { + typeId: 11, + type: 'str[15]', + components: null, + typeParameters: null, + }, + { + typeId: 12, + type: 'str[16]', + components: null, + typeParameters: null, + }, + { + typeId: 13, + type: 'str[17]', + components: null, + typeParameters: null, + }, + { + typeId: 14, + type: 'str[37]', + components: null, + typeParameters: null, + }, + { + typeId: 15, + type: 'struct RawVec', + components: [ + { + name: 'ptr', + type: 7, + typeArguments: null, + }, + { + name: 'cap', + type: 19, + typeArguments: null, + }, + ], + typeParameters: [6], + }, + { + typeId: 16, + type: 'struct SomeStruct', + components: [ + { + name: 'a', + type: 6, + typeArguments: null, + }, + ], + typeParameters: [6], + }, + { + typeId: 17, + type: 'struct Vec', + components: [ + { + name: 'buf', + type: 15, + typeArguments: [ + { + name: '', + type: 6, + typeArguments: null, + }, + ], + }, + { + name: 'len', + type: 19, + typeArguments: null, + }, + ], + typeParameters: [6], + }, + { + typeId: 18, + type: 'u32', + components: null, + typeParameters: null, + }, + { + typeId: 19, + type: 'u64', + components: null, + typeParameters: null, + }, + ], + functions: [ + { + inputs: [ + { + name: 'u32_vec', + type: 17, + typeArguments: [ + { + name: '', + type: 18, + typeArguments: null, + }, + ], + }, + { + name: 'vec_in_vec', + type: 17, + typeArguments: [ + { + name: '', + type: 17, + typeArguments: [ + { + name: '', + type: 18, + typeArguments: null, + }, + ], + }, + ], + }, + { + name: 'struct_in_vec', + type: 17, + typeArguments: [ + { + name: '', + type: 16, + typeArguments: [ + { + name: '', + type: 18, + typeArguments: null, + }, + ], + }, + ], + }, + { + name: 'vec_in_struct', + type: 16, + typeArguments: [ + { + name: '', + type: 17, + typeArguments: [ + { + name: '', + type: 18, + typeArguments: null, + }, + ], + }, + ], + }, + { + name: 'array_in_vec', + type: 17, + typeArguments: [ + { + name: '', + type: 2, + typeArguments: null, + }, + ], + }, + { + name: 'vec_in_array', + type: 3, + typeArguments: null, + }, + { + name: 'vec_in_enum', + type: 5, + typeArguments: [ + { + name: '', + type: 17, + typeArguments: [ + { + name: '', + type: 18, + typeArguments: null, + }, + ], + }, + ], + }, + { + name: 'enum_in_vec', + type: 17, + typeArguments: [ + { + name: '', + type: 5, + typeArguments: [ + { + name: '', + type: 18, + typeArguments: null, + }, + ], + }, + ], + }, + { + name: 'tuple_in_vec', + type: 17, + typeArguments: [ + { + name: '', + type: 0, + typeArguments: null, + }, + ], + }, + { + name: 'vec_in_tuple', + type: 1, + typeArguments: null, + }, + { + name: 'vec_in_a_vec_in_a_struct_in_a_vec', + type: 17, + typeArguments: [ + { + name: '', + type: 16, + typeArguments: [ + { + name: '', + type: 17, + typeArguments: [ + { + name: '', + type: 17, + typeArguments: [ + { + name: '', + type: 18, + typeArguments: null, + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + name: 'test_all', + output: { + name: '', + type: 4, + typeArguments: null, + }, + attributes: null, + }, + ], + loggedTypes: [], + messagesTypes: [], + configurables: [], + } as JsonFlatAbi); + + const U32_VEC = [0, 1, 2]; + const VEC_IN_VEC = [ + [0, 1, 2], + [0, 1, 2], + ]; + const STRUCT_IN_VEC = [{ a: 0 }, { a: 1 }]; + const VEC_IN_STRUCT = { a: [0, 1, 2] }; + const ARRAY_IN_VEC = [ + [0, 1], + [0, 1], + ]; + const VEC_IN_ARRAY = [ + [0, 1, 2], + [0, 1, 2], + ]; + const VEC_IN_ENUM = { a: [0, 1, 2] }; + const ENUM_IN_VEC = [{ a: 0 }, { a: 1 }]; + + const TUPLE_IN_VEC = [ + [0, 0], + [1, 1], + ]; + const VEC_IN_TUPLE = [ + [0, 1, 2], + [0, 1, 2], + ]; + const VEC_IN_A_VEC_IN_A_STRUCT_IN_A_VEC = [ + { + a: [ + [0, 1, 2], + [3, 4, 5], + ], + }, + { + a: [ + [6, 7, 8], + [9, 10, 11], + ], + }, + ]; + + const encoded = abiCoder.encode( + abi[0].inputs as JsonAbiFragmentType[], + [ + U32_VEC, + VEC_IN_VEC, + STRUCT_IN_VEC, + VEC_IN_STRUCT, + ARRAY_IN_VEC, + VEC_IN_ARRAY, + VEC_IN_ENUM, + ENUM_IN_VEC, + TUPLE_IN_VEC, + VEC_IN_TUPLE, + VEC_IN_A_VEC_IN_A_STRUCT_IN_A_VEC, + ], + 0 + ); + + const expectedBytes = concat([ + [0, 0, 0, 0, 0, 0, 0, 24], // U32_VEC: pointer + [0, 0, 0, 0, 0, 0, 0, U32_VEC.length], // U32_VEC: cap + [0, 0, 0, 0, 0, 0, 0, U32_VEC.length], // U32_VEC: length + [0, 0, 0, 0, 0, 0, 0, U32_VEC[0]], // U32_VEC: data + [0, 0, 0, 0, 0, 0, 0, U32_VEC[1]], // U32_VEC: data + [0, 0, 0, 0, 0, 0, 0, U32_VEC[2]], // U32_VEC: data + ]); + + const expected = hexlify(expectedBytes); - expect(encoded).toStrictEqual(inputAndVecData); expect(hexlify(encoded)).toBe(expected); }); }); diff --git a/packages/abi-coder/src/abi-coder.ts b/packages/abi-coder/src/abi-coder.ts index cc99882b4fb..3b34162f370 100644 --- a/packages/abi-coder/src/abi-coder.ts +++ b/packages/abi-coder/src/abi-coder.ts @@ -29,7 +29,8 @@ import { VEC_CODER_TYPE, } from './constants'; import type { JsonAbiFragmentType } from './json-abi'; -import { filterEmptyParams, getVectorAdjustments, hasOptionTypes } from './utilities'; +import type { Uint8ArrayWithDynamicData } from './utilities'; +import { unpackDynamicData, filterEmptyParams, hasOptionTypes } from './utilities'; const logger = new Logger(versions.FUELS); @@ -154,12 +155,11 @@ export default class AbiCoder { } const coders = nonEmptyTypes.map((type) => this.getCoder(type)); - const vectorData = getVectorAdjustments(coders, shallowCopyValues, offset); const coder = new TupleCoder(coders); - const results = coder.encode(shallowCopyValues); + const results: Uint8ArrayWithDynamicData = coder.encode(shallowCopyValues); - return concat([results, concat(vectorData)]); + return unpackDynamicData(results, offset, results.byteLength); } decode(types: ReadonlyArray, data: BytesLike): DecodedValue[] | undefined { diff --git a/packages/abi-coder/src/coders/abstract-coder.test.ts b/packages/abi-coder/src/coders/abstract-coder.test.ts index e694add0e61..5d41c4b16ec 100644 --- a/packages/abi-coder/src/coders/abstract-coder.test.ts +++ b/packages/abi-coder/src/coders/abstract-coder.test.ts @@ -30,11 +30,4 @@ describe('Coder', () => { it('should throw unreachable on throwError', () => { expect(() => coder.throwError('test', 'test')).toThrowError('unreachable'); }); - - it('should set offset', () => { - expect(coder.offset).toBeUndefined(); - - coder.setOffset(8); - expect(coder.offset).toBe(8); - }); }); diff --git a/packages/abi-coder/src/coders/abstract-coder.ts b/packages/abi-coder/src/coders/abstract-coder.ts index 801bf8f33c4..5bfc56dcf05 100644 --- a/packages/abi-coder/src/coders/abstract-coder.ts +++ b/packages/abi-coder/src/coders/abstract-coder.ts @@ -38,7 +38,6 @@ export default abstract class Coder { readonly name: string; readonly type: string; readonly encodedLength: number; - offset?: number; constructor(name: string, type: string, encodedLength: number) { this.name = name; @@ -53,10 +52,6 @@ export default abstract class Coder { throw new Error('unreachable'); } - setOffset(offset: number): void { - this.offset = offset; - } - abstract encode(value: TInput, length?: number): Uint8Array; abstract decode(data: Uint8Array, offset: number, length?: number): [TDecoded, number]; diff --git a/packages/abi-coder/src/coders/array.ts b/packages/abi-coder/src/coders/array.ts index 5ace0a12c06..79e29396294 100644 --- a/packages/abi-coder/src/coders/array.ts +++ b/packages/abi-coder/src/coders/array.ts @@ -1,4 +1,4 @@ -import { concat } from '@ethersproject/bytes'; +import { concatWithDynamicData } from '../utilities'; import type { TypesOfCoder } from './abstract-coder'; import Coder from './abstract-coder'; @@ -28,7 +28,7 @@ export default class ArrayCoder extends Coder< this.throwError('Types/values length mismatch', value); } - return concat(Array.from(value).map((v) => this.coder.encode(v))); + return concatWithDynamicData(Array.from(value).map((v) => this.coder.encode(v))); } decode(data: Uint8Array, offset: number): [DecodedValueOf, number] { diff --git a/packages/abi-coder/src/coders/enum.ts b/packages/abi-coder/src/coders/enum.ts index 1adfc8c8968..714df3a7e20 100644 --- a/packages/abi-coder/src/coders/enum.ts +++ b/packages/abi-coder/src/coders/enum.ts @@ -2,6 +2,8 @@ import { concat } from '@ethersproject/bytes'; import { toNumber } from '@fuel-ts/math'; import type { RequireExactlyOne } from 'type-fest'; +import { concatWithDynamicData } from '../utilities'; + import type { TypesOfCoder } from './abstract-coder'; import Coder from './abstract-coder'; import U64Coder from './u64'; @@ -67,7 +69,7 @@ export default class EnumCoder> extends Co const encodedValue = valueCoder.encode(value[caseKey]); const padding = new Uint8Array(this.#encodedValueSize - valueCoder.encodedLength); - return concat([this.#caseIndexCoder.encode(caseIndex), padding, encodedValue]); + return concatWithDynamicData([this.#caseIndexCoder.encode(caseIndex), padding, encodedValue]); } #decodeNativeEnum(caseKey: string, newOffset: number): [DecodedValueOf, number] { diff --git a/packages/abi-coder/src/coders/struct.ts b/packages/abi-coder/src/coders/struct.ts index 40cf08feac8..6c7c4465c97 100644 --- a/packages/abi-coder/src/coders/struct.ts +++ b/packages/abi-coder/src/coders/struct.ts @@ -1,4 +1,4 @@ -import { concat } from '@ethersproject/bytes'; +import { concatWithDynamicData } from '../utilities'; import type { TypesOfCoder } from './abstract-coder'; import Coder from './abstract-coder'; @@ -38,7 +38,8 @@ export default class StructCoder> extends const encoded = fieldCoder.encode(fieldValue); return encoded; }); - return concat(encodedFields); + + return concatWithDynamicData([concatWithDynamicData(encodedFields)]); } decode(data: Uint8Array, offset: number): [DecodedValueOf, number] { diff --git a/packages/abi-coder/src/coders/tuple.ts b/packages/abi-coder/src/coders/tuple.ts index c87040f18be..a978005f7b8 100644 --- a/packages/abi-coder/src/coders/tuple.ts +++ b/packages/abi-coder/src/coders/tuple.ts @@ -1,4 +1,4 @@ -import { concat } from '@ethersproject/bytes'; +import { concatWithDynamicData } from '../utilities'; import type { TypesOfCoder } from './abstract-coder'; import Coder from './abstract-coder'; @@ -27,7 +27,7 @@ export default class TupleCoder extends Coder< this.throwError('Types/values length mismatch', { value }); } - return concat(this.coders.map((coder, i) => coder.encode(value[i]))); + return concatWithDynamicData(this.coders.map((coder, i) => coder.encode(value[i]))); } decode(data: Uint8Array, offset: number): [DecodedValueOf, number] { diff --git a/packages/abi-coder/src/coders/vec.test.ts b/packages/abi-coder/src/coders/vec.test.ts index 0abe0ffbe72..d164f4d66c7 100644 --- a/packages/abi-coder/src/coders/vec.test.ts +++ b/packages/abi-coder/src/coders/vec.test.ts @@ -1,23 +1,21 @@ +import type { Uint8ArrayWithDynamicData } from '../utilities'; + import BooleanCoder from './boolean'; import VecCoder from './vec'; describe('VecCoder', () => { it('should encode a Vec of Booleans', () => { const coder = new VecCoder(new BooleanCoder()); - const expected = new Uint8Array([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, + const expected: Uint8ArrayWithDynamicData = new Uint8Array([ + 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, ]); - const actual = coder.encode([true, false]); - - expect(actual).toStrictEqual(expected); - }); + expected.dynamicData = { + 0: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]), + }; - it('should get encoded vector data', () => { - const coder = new VecCoder(new BooleanCoder()); - const expected = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]); - const actual = coder.getEncodedVectorData([true, false]); + const actual = coder.encode([true, false]); - expect(actual).toStrictEqual(expected); + expect(actual).toEqual(expected); }); it('should throw when encoding non array input', () => { @@ -28,21 +26,6 @@ describe('VecCoder', () => { }).toThrow('expected array value'); }); - it('should throw when getting encoded vector data with non array input', () => { - const coder = new VecCoder(new BooleanCoder()); - - expect(() => { - coder.getEncodedVectorData('Nope' as never); - }).toThrow('expected array value'); - }); - - it('should get base offset', () => { - const expected = 24; - const actual = VecCoder.getBaseOffset(); - - expect(actual).toBe(expected); - }); - it('should throw an error when decoding', () => { const coder = new VecCoder(new BooleanCoder()); const invalidInput = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]); diff --git a/packages/abi-coder/src/coders/vec.ts b/packages/abi-coder/src/coders/vec.ts index f3c6c9ec581..b30ab35fa33 100644 --- a/packages/abi-coder/src/coders/vec.ts +++ b/packages/abi-coder/src/coders/vec.ts @@ -1,13 +1,10 @@ -import { concat } from '@ethersproject/bytes'; - -import { WORD_SIZE } from '../constants'; +import type { Uint8ArrayWithDynamicData } from '../utilities'; +import { concatWithDynamicData, BASE_VECTOR_OFFSET } from '../utilities'; import type { TypesOfCoder } from './abstract-coder'; import Coder from './abstract-coder'; import U64Coder from './u64'; -const VEC_PROPERTY_SPACE = 3; // ptr + cap + length - type InputValueOf = Array['Input']>; type DecodedValueOf = Array['Decoded']>; @@ -18,38 +15,33 @@ export default class VecCoder extends Coder< coder: TCoder; constructor(coder: TCoder) { - super('struct', `struct Vec`, 0); + super('struct', `struct Vec`, coder.encodedLength + BASE_VECTOR_OFFSET); this.coder = coder; } - static getBaseOffset(): number { - return VEC_PROPERTY_SPACE * WORD_SIZE; - } - - getEncodedVectorData(value: InputValueOf): Uint8Array { - if (!Array.isArray(value)) { - this.throwError('expected array value', value); - } - - const encodedValues = Array.from(value).map((v) => this.coder.encode(v)); - return concat(encodedValues); - } - encode(value: InputValueOf): Uint8Array { if (!Array.isArray(value)) { this.throwError('expected array value', value); } const parts: Uint8Array[] = []; + // pointer (ptr) - const pointer = this.offset || 0; - parts.push(new U64Coder().encode(pointer)); + const pointer: Uint8ArrayWithDynamicData = new U64Coder().encode(BASE_VECTOR_OFFSET); + // pointer dynamicData, encode the vector now and attach to its pointer + pointer.dynamicData = { + 0: concatWithDynamicData(Array.from(value).map((v) => this.coder.encode(v))), + }; + + parts.push(pointer); + // capacity (cap) parts.push(new U64Coder().encode(value.length)); + // length (len) parts.push(new U64Coder().encode(value.length)); - return concat(parts); + return concatWithDynamicData(parts); } decode(_data: Uint8Array, _offset: number): [DecodedValueOf, number] { diff --git a/packages/abi-coder/src/constants.ts b/packages/abi-coder/src/constants.ts index eb02cac4d90..2541e321a0a 100644 --- a/packages/abi-coder/src/constants.ts +++ b/packages/abi-coder/src/constants.ts @@ -32,3 +32,30 @@ export const TRANSACTION_SCRIPT_FIXED_SIZE = WORD_SIZE + // Outputs size WORD_SIZE + // Witnesses size BYTES_32; // Receipts root + +// TRANSACTION_PREDICATE_COIN_FIXED_SIZE = 168 +export const TRANSACTION_PREDICATE_COIN_FIXED_SIZE = + WORD_SIZE + // Identifier + 40 + // Utxo Id Length + ASSET_ID_LEN + // Owner + WORD_SIZE + // Amount + ASSET_ID_LEN + // Asset id + WORD_SIZE * 2 + // Transaction pointer + WORD_SIZE + // Witnesses index + WORD_SIZE + // Maturity + WORD_SIZE + // Predicate size + WORD_SIZE; // Predicate data size + +// TRANSACTION_PREDICATE_MESSAGE_FIXED_SIZE = 160 +export const TRANSACTION_PREDICATE_MESSAGE_FIXED_SIZE = + WORD_SIZE + // Input type + WORD_SIZE + // Identifier + ASSET_ID_LEN + // message_id + ASSET_ID_LEN + // Sender + ASSET_ID_LEN + // recipient + WORD_SIZE + // Amount + WORD_SIZE + // Nonce + WORD_SIZE + // Witnesses index + WORD_SIZE + // Data size + WORD_SIZE + // Predicate size + WORD_SIZE; // Predicate data size diff --git a/packages/abi-coder/src/utilities.test.ts b/packages/abi-coder/src/utilities.test.ts index 101767db051..1a50bb4ac30 100644 --- a/packages/abi-coder/src/utilities.test.ts +++ b/packages/abi-coder/src/utilities.test.ts @@ -1,9 +1,13 @@ -import AbiCoder from './abi-coder'; -import VecCoder from './coders/vec'; -import { WORD_SIZE } from './constants'; +import { concat } from '@ethersproject/bytes'; + import { ParamType } from './fragments/param-type'; -import type { JsonAbiFragmentType } from './json-abi'; -import { filterEmptyParams, hasOptionTypes, getVectorAdjustments } from './utilities'; +import type { Uint8ArrayWithDynamicData } from './utilities'; +import { + unpackDynamicData, + filterEmptyParams, + hasOptionTypes, + concatWithDynamicData, +} from './utilities'; describe('Abi Coder Utilities', () => { it('can filterEmptyParams', () => { @@ -50,597 +54,279 @@ describe('Abi Coder Utilities', () => { expect(RESULT).toStrictEqual(false); }); - it('can getVectorAdjustments [no Vectors, offset = 0]', () => { - const abiCoder = new AbiCoder(); - const NON_EMPTY_TYPES: ReadonlyArray = [ - { - type: 'b256', - name: 'arg0', - }, - { - type: 'b256', - name: 'arg1', - }, - ]; - const CODERS = NON_EMPTY_TYPES.map((type) => abiCoder.getCoder(type)); - const VALUES = [43]; - const EXPECTED: Uint8Array[] = []; + it('can concatWithVectorData [no dynamicData, should match original concat]', () => { + const data1 = [0, 0, 0, 0, 0, 0, 0, 24]; + const data2 = [0, 0, 0, 0, 0, 0, 0, 4]; + const data3 = [0, 0, 0, 0, 0, 0, 0, 4]; + const data4 = [0, 0, 0, 0, 0, 0, 0, 16]; + const EXPECTED = concat([data1, data2, data3, data4]); - const RESULT = getVectorAdjustments(CODERS, VALUES, 0); - expect(RESULT).toStrictEqual(EXPECTED); + const RESULT = concatWithDynamicData([data1, data2, data3, data4]); + expect(RESULT).toEqual(EXPECTED); }); - it('can getVectorAdjustments [no Vectors, offset = 8]', () => { - const abiCoder = new AbiCoder(); - const NON_EMPTY_TYPES: ReadonlyArray = [ - { - type: 'b256', - name: 'arg0', - }, - { - type: 'b256', - name: 'arg1', - }, - ]; - const CODERS = NON_EMPTY_TYPES.map((type) => abiCoder.getCoder(type)); - const VALUES = [43]; - const EXPECTED: Uint8Array[] = []; + it('can concatWithVectorData [relocate single dynamicData]', () => { + const pointer: Uint8ArrayWithDynamicData = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 24]); + pointer.dynamicData = { 0: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 36]) }; + const capacity = [0, 0, 0, 0, 0, 0, 0, 4]; + const length = [0, 0, 0, 0, 0, 0, 0, 4]; + const someData = [0, 0, 0, 0, 0, 0, 0, 16]; + const EXPECTED: Uint8ArrayWithDynamicData = concat([pointer, capacity, length, someData]); + EXPECTED.dynamicData = { 0: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 36]) }; - const RESULT = getVectorAdjustments(CODERS, VALUES, 8); - expect(RESULT).toStrictEqual(EXPECTED); + const RESULT = concatWithDynamicData([pointer, capacity, length, someData]); + expect(RESULT).toEqual(EXPECTED); + + // is idempotent + const RESULT_NEW = concatWithDynamicData([RESULT]); + expect(RESULT_NEW).toEqual(EXPECTED); }); - it('can getVectorAdjustments [inputs=[Vector], offset = 0]', () => { - const abiCoder = new AbiCoder(); - const NON_EMPTY_TYPES: ReadonlyArray = [ - { - name: 'vector', - type: 'struct Vec', - components: [ - { - name: 'buf', - type: 'struct RawVec', - components: [ - { - name: 'ptr', - type: 'raw untyped ptr', - }, - { - name: 'cap', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u8', - }, - ], - }, - { - name: 'len', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u8', - }, - ], - }, - ]; - const CODERS = NON_EMPTY_TYPES.map((type) => abiCoder.getCoder(type)); - const VALUES = [[1, 2, 34]]; - const EXPECTED: Uint8Array[] = [ - new Uint8Array([0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 34]), - ]; + it('can concatWithVectorData [two distinct dynamicData]', () => { + const pointer = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 24]); + const capacity = [0, 0, 0, 0, 0, 0, 0, 4]; + const length = [0, 0, 0, 0, 0, 0, 0, 4]; + const EXPECTED: Uint8ArrayWithDynamicData = concat([ + pointer, + capacity, + length, + pointer, + capacity, + length, + ]); + EXPECTED.dynamicData = { + 0: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 36]), + 3: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 36]), + }; - const RESULT = getVectorAdjustments(CODERS, VALUES, 0); - expect(RESULT).toStrictEqual(EXPECTED); - expect(CODERS[0].offset).toStrictEqual(VecCoder.getBaseOffset()); + const arrayWithVectorData: Uint8ArrayWithDynamicData = concat([pointer, capacity, length]); + arrayWithVectorData.dynamicData = { 0: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 36]) }; + + const RESULT = concatWithDynamicData([arrayWithVectorData, arrayWithVectorData]); + + expect(RESULT).toEqual(EXPECTED); + + // is idempotent + const RESULT_NEW = concatWithDynamicData([RESULT]); + expect(RESULT_NEW).toEqual(EXPECTED); }); - it('can getVectorAdjustments [inputs=[Vector], offset = 32]', () => { - const abiCoder = new AbiCoder(); - const NON_EMPTY_TYPES: ReadonlyArray = [ - { - name: 'vector', - type: 'struct Vec', - components: [ - { - name: 'buf', - type: 'struct RawVec', - components: [ - { - name: 'ptr', - type: 'raw untyped ptr', - }, - { - name: 'cap', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u8', - }, - ], - }, - { - name: 'len', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u8', - }, - ], - }, - ]; - const CODERS = NON_EMPTY_TYPES.map((type) => abiCoder.getCoder(type)); - const VALUES = [[1, 2, 34]]; - const OFFSET = 32; - const EXPECTED: Uint8Array[] = [ - new Uint8Array([0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 34]), - ]; + it('can concatWithVectorData [three distinct dynamicData]', () => { + const pointer = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 24]); + const capacity = [0, 0, 0, 0, 0, 0, 0, 4]; + const length = [0, 0, 0, 0, 0, 0, 0, 4]; + const EXPECTED: Uint8ArrayWithDynamicData = concat([ + pointer, + capacity, + length, + pointer, + capacity, + length, + pointer, + capacity, + length, + ]); + EXPECTED.dynamicData = { + 0: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 33]), + 3: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 35]), + 6: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 37]), + }; - const RESULT = getVectorAdjustments(CODERS, VALUES, OFFSET); - expect(RESULT).toStrictEqual(EXPECTED); - expect(CODERS[0].offset).toStrictEqual(VecCoder.getBaseOffset() + OFFSET); + const arrayWithVectorData1: Uint8ArrayWithDynamicData = concat([pointer, capacity, length]); + const arrayWithVectorData2: Uint8ArrayWithDynamicData = concat([pointer, capacity, length]); + const arrayWithVectorData3: Uint8ArrayWithDynamicData = concat([pointer, capacity, length]); + arrayWithVectorData1.dynamicData = { 0: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 33]) }; + arrayWithVectorData2.dynamicData = { 0: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 35]) }; + arrayWithVectorData3.dynamicData = { 0: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 37]) }; + + const RESULT = concatWithDynamicData([ + arrayWithVectorData1, + arrayWithVectorData2, + arrayWithVectorData3, + ]); + + expect(RESULT).toEqual(EXPECTED); + + // is idempotent + const RESULT_NEW = concatWithDynamicData([RESULT]); + expect(RESULT_NEW).toEqual(EXPECTED); }); - it('can getVectorAdjustments [inputs=[Vector, Vector], offset = 0]', () => { - const abiCoder = new AbiCoder(); - const NON_EMPTY_TYPES: ReadonlyArray = [ - { - name: 'vector', - type: 'struct Vec', - components: [ - { - name: 'buf', - type: 'struct RawVec', - components: [ - { - name: 'ptr', - type: 'raw untyped ptr', - }, - { - name: 'cap', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u8', - }, - ], - }, - { - name: 'len', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u8', - }, - ], - }, - { - name: 'vector', - type: 'struct Vec', - components: [ - { - name: 'buf', - type: 'struct RawVec', - components: [ - { - name: 'ptr', - type: 'raw untyped ptr', - }, - { - name: 'cap', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u8', - }, - ], - }, - { - name: 'len', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u8', - }, - ], - }, - ]; - const CODERS = NON_EMPTY_TYPES.map((type) => abiCoder.getCoder(type)); - const VALUES = [ - [1, 2, 34], - [71, 72, 99], - ]; - const EXPECTED: Uint8Array[] = [ - new Uint8Array([0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 34]), - new Uint8Array([0, 0, 0, 0, 0, 0, 0, 71, 0, 0, 0, 0, 0, 0, 0, 72, 0, 0, 0, 0, 0, 0, 0, 99]), - ]; + it('can concatWithVectorData [relocate three dynamicData]', () => { + const pointerA: Uint8ArrayWithDynamicData = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 24]); + pointerA.dynamicData = { 0: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 33]) }; + const pointerB: Uint8ArrayWithDynamicData = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 24]); + pointerB.dynamicData = { 0: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 12]) }; + const pointerC: Uint8ArrayWithDynamicData = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 24]); + pointerC.dynamicData = { 0: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 4]) }; + const capacity = [0, 0, 0, 0, 0, 0, 0, 4]; + const length = [0, 0, 0, 0, 0, 0, 0, 4]; + const someData = [0, 0, 0, 0, 0, 0, 0, 16]; + const EXPECTED: Uint8ArrayWithDynamicData = concat([ + pointerA, + capacity, + length, + pointerB, + capacity, + length, + pointerC, + capacity, + length, + someData, + ]); + EXPECTED.dynamicData = { + 0: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 33]), + 3: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 12]), + 6: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 4]), + }; - const RESULT = getVectorAdjustments(CODERS, VALUES, 0); - expect(RESULT).toStrictEqual(EXPECTED); - // one base vec offset per each vector input - expect(CODERS[0].offset).toStrictEqual(VecCoder.getBaseOffset() + VecCoder.getBaseOffset()); - // one base vec offset per each vector input + the first vector's data - expect(CODERS[1].offset).toStrictEqual( - VecCoder.getBaseOffset() + VecCoder.getBaseOffset() + VALUES[0].length * WORD_SIZE - ); + const RESULT = concatWithDynamicData([ + pointerA, + capacity, + length, + pointerB, + capacity, + length, + pointerC, + capacity, + length, + someData, + ]); + + expect(RESULT).toEqual(EXPECTED); + + // is idempotent + const RESULT_NEW = concatWithDynamicData([RESULT]); + expect(RESULT_NEW).toEqual(EXPECTED); }); - it('can getVectorAdjustments [inputs=[Vector,Vector], offset = 32]', () => { - const abiCoder = new AbiCoder(); - const NON_EMPTY_TYPES: ReadonlyArray = [ - { - name: 'vector', - type: 'struct Vec', - components: [ - { - name: 'buf', - type: 'struct RawVec', - components: [ - { - name: 'ptr', - type: 'raw untyped ptr', - }, - { - name: 'cap', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u8', - }, - ], - }, - { - name: 'len', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u8', - }, - ], - }, - { - name: 'vector', - type: 'struct Vec', - components: [ - { - name: 'buf', - type: 'struct RawVec', - components: [ - { - name: 'ptr', - type: 'raw untyped ptr', - }, - { - name: 'cap', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u64', - }, - ], - }, - { - name: 'len', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u64', - }, - ], - }, - ]; - const CODERS = NON_EMPTY_TYPES.map((type) => abiCoder.getCoder(type)); - const VALUES = [ - [7, 2, 34], - [867, 5309, 1337], - ]; - const OFFSET = 32; - const EXPECTED: Uint8Array[] = [ - new Uint8Array([0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 34]), - new Uint8Array([0, 0, 0, 0, 0, 0, 3, 99, 0, 0, 0, 0, 0, 0, 20, 189, 0, 0, 0, 0, 0, 0, 5, 57]), - ]; + it('can concatWithVectorData [with dynamicData in middle, should relocate]', () => { + const otherData = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 9]); + const pointer: Uint8ArrayWithDynamicData = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 24]); + pointer.dynamicData = { 0: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 36]) }; + const capacity = [0, 0, 0, 0, 0, 0, 0, 4]; + const length = [0, 0, 0, 0, 0, 0, 0, 4]; + const data = [0, 0, 0, 0, 0, 0, 0, 16]; + const EXPECTED: Uint8ArrayWithDynamicData = concat([ + otherData, + pointer, + capacity, + length, + data, + ]); + EXPECTED.dynamicData = { 2: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 36]) }; - const RESULT = getVectorAdjustments(CODERS, VALUES, OFFSET); - expect(RESULT).toStrictEqual(EXPECTED); - // one base vec offset per each vector input + plus custom OFFSET - expect(CODERS[0].offset).toStrictEqual( - VecCoder.getBaseOffset() + VecCoder.getBaseOffset() + OFFSET - ); - // one base vec offset per each vector input + the first vector's data + plus custom OFFSET - expect(CODERS[1].offset).toStrictEqual( - VecCoder.getBaseOffset() + VecCoder.getBaseOffset() + VALUES[0].length * WORD_SIZE + OFFSET - ); + const RESULT = concatWithDynamicData([otherData, pointer, capacity, length, data]); + + expect(RESULT).toEqual(EXPECTED); + + // is idempotent + const RESULT_NEW = concatWithDynamicData([RESULT]); + expect(RESULT_NEW).toEqual(EXPECTED); }); - it('can getVectorAdjustments [inputs=[Vector,b256], offset = 8]', () => { - const abiCoder = new AbiCoder(); - const NON_EMPTY_TYPES: ReadonlyArray = [ - { - name: 'vector', - type: 'struct Vec', - components: [ - { - name: 'buf', - type: 'struct RawVec', - components: [ - { - name: 'ptr', - type: 'raw untyped ptr', - }, - { - name: 'cap', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u8', - }, - ], - }, - { - name: 'len', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u8', - }, - ], - }, - { - type: 'b256', - name: 'arg1', - }, - ]; - const CODERS = NON_EMPTY_TYPES.map((type) => abiCoder.getCoder(type)); - const VALUES = [[1, 3, 3, 7], 43]; - const OFFSET = 8; - const EXPECTED: Uint8Array[] = [ - new Uint8Array([ - 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, - 7, - ]), + it('can unpackDynamicData [with dynamicData]', () => { + const results: Uint8ArrayWithDynamicData = new Uint8Array([ + 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 24, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, + ]); + const DATA_1 = [ + 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 7, 228, 0, 0, 0, 0, 0, 0, + 0, 12, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 7, 227, ]; + const DATA_2 = [0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 7, 188]; + results.dynamicData = { + 0: new Uint8Array(DATA_1), + 3: new Uint8Array(DATA_2), + }; + const BASE_OFFSET = 0; + const DATA_OFFSET = 0; + // prettier-ignore + const EXPECTED = new Uint8Array([ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 48, + 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 1, + ...DATA_1, + ...DATA_2, + ]); - const RESULT = getVectorAdjustments(CODERS, VALUES, OFFSET); - expect(RESULT).toStrictEqual(EXPECTED); - // one base vec offset + plus b256 data + plus custom OFFSET - expect(CODERS[0].offset).toStrictEqual(VecCoder.getBaseOffset() + 4 * WORD_SIZE + OFFSET); + const RESULT = unpackDynamicData(results, BASE_OFFSET, DATA_OFFSET); + + expect(RESULT).toEqual(EXPECTED); }); - it('can getVectorAdjustments [inputs=[b256,Vector], offset = 14440]', () => { - const abiCoder = new AbiCoder(); - const NON_EMPTY_TYPES: ReadonlyArray = [ - { - type: 'u32', - name: 'arg1', - }, - { - name: 'vector', - type: 'struct Vec', - components: [ - { - name: 'buf', - type: 'struct RawVec', - components: [ - { - name: 'ptr', - type: 'raw untyped ptr', - }, - { - name: 'cap', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u64', - }, - ], - }, - { - name: 'len', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u64', - }, - ], - }, - ]; - const CODERS = NON_EMPTY_TYPES.map((type) => abiCoder.getCoder(type)); - const VALUES = [33, [450, 202, 340]]; - const OFFSET = 14440; - const EXPECTED: Uint8Array[] = [ - new Uint8Array([0, 0, 0, 0, 0, 0, 1, 194, 0, 0, 0, 0, 0, 0, 0, 202, 0, 0, 0, 0, 0, 0, 1, 84]), + it('can unpackDynamicData [with dynamicData before regular data]', () => { + const results: Uint8ArrayWithDynamicData = new Uint8Array([ + 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 2, + ]); + const DATA_1 = [ + 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 7, 228, 0, 0, 0, 0, 0, 0, + 0, 12, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 7, 227, ]; + const DATA_2 = [0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 7, 188]; + results.dynamicData = { + 0: new Uint8Array(DATA_1), + 4: new Uint8Array(DATA_2), + }; + const BASE_OFFSET = 0; + const DATA_OFFSET = 0; + // prettier-ignore + const EXPECTED = new Uint8Array([ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 48, + 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 2, + ...DATA_1, + ...DATA_2, + ]); - const RESULT = getVectorAdjustments(CODERS, VALUES, OFFSET); - expect(RESULT).toStrictEqual(EXPECTED); - // one base vec offset + plus u32 data + plus custom OFFSET - expect(CODERS[1].offset).toStrictEqual(VecCoder.getBaseOffset() + WORD_SIZE + OFFSET); + const RESULT = unpackDynamicData(results, BASE_OFFSET, DATA_OFFSET); + + expect(RESULT).toEqual(EXPECTED); }); - it('can getVectorAdjustments [inputs=[b256,Vector,Vector,Vector], offset = 24]', () => { - const abiCoder = new AbiCoder(); - const NON_EMPTY_TYPES: ReadonlyArray = [ - { - type: 'u32', - name: 'arg1', - }, - { - name: 'vector', - type: 'struct Vec', - components: [ - { - name: 'buf', - type: 'struct RawVec', - components: [ - { - name: 'ptr', - type: 'raw untyped ptr', - }, - { - name: 'cap', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u64', - }, - ], - }, - { - name: 'len', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u64', - }, - ], - }, - { - name: 'vector', - type: 'struct Vec', - components: [ - { - name: 'buf', - type: 'struct RawVec', - components: [ - { - name: 'ptr', - type: 'raw untyped ptr', - }, - { - name: 'cap', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u64', - }, - ], - }, - { - name: 'len', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u64', - }, - ], - }, - { - name: 'vector', - type: 'struct Vec', - components: [ - { - name: 'buf', - type: 'struct RawVec', - components: [ - { - name: 'ptr', - type: 'raw untyped ptr', - }, - { - name: 'cap', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u64', - }, - ], - }, - { - name: 'len', - type: 'u64', - }, - ], - typeArguments: [ - { - name: '', - type: 'u64', - }, - ], - }, - ]; - const CODERS = NON_EMPTY_TYPES.map((type) => abiCoder.getCoder(type)); - const VALUES = [33, [450, 202, 340], [12, 13, 14], [11, 9]]; - const OFFSET = 24; - const EXPECTED: Uint8Array[] = [ - new Uint8Array([0, 0, 0, 0, 0, 0, 1, 194, 0, 0, 0, 0, 0, 0, 0, 202, 0, 0, 0, 0, 0, 0, 1, 84]), - new Uint8Array([0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 14]), - new Uint8Array([0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 9]), + it('can unpackDynamicData [with dynamicData before regular data, with offset]', () => { + const results: Uint8ArrayWithDynamicData = new Uint8Array([ + 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 2, + ]); + const DATA_1 = [ + 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 7, 228, 0, 0, 0, 0, 0, 0, + 0, 12, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 7, 227, ]; + const DATA_2 = [0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 7, 188]; + results.dynamicData = { + 0: new Uint8Array(DATA_1), + 4: new Uint8Array(DATA_2), + }; + const BASE_OFFSET = 12584; + const DATA_OFFSET = 352; + // prettier-ignore + const EXPECTED = new Uint8Array([ + 0, 0, 0, 0, 0, 0, 50, 136, + 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 50, 184, + 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 2, + ...DATA_1, + ...DATA_2, + ]); - const OFFSET_PLUS_DATA_PLUS_3_OFFSETS = VecCoder.getBaseOffset() * 3 + WORD_SIZE + OFFSET; + const RESULT = unpackDynamicData(results, BASE_OFFSET, DATA_OFFSET); - const RESULT = getVectorAdjustments(CODERS, VALUES, OFFSET); - expect(RESULT).toStrictEqual(EXPECTED); - // three base vec offset + plus u32 data + plus custom OFFSET - expect(CODERS[1].offset).toStrictEqual(OFFSET_PLUS_DATA_PLUS_3_OFFSETS); - // three base vec offset + plus u32 data + the first vector's data + plus custom OFFSET - expect(CODERS[2].offset).toStrictEqual(OFFSET_PLUS_DATA_PLUS_3_OFFSETS + 3 * WORD_SIZE); - // three base vec offset + plus u32 data + the first vector's data + the second vector's data + plus custom OFFSET - expect(CODERS[3].offset).toStrictEqual( - OFFSET_PLUS_DATA_PLUS_3_OFFSETS + 3 * WORD_SIZE + 3 * WORD_SIZE - ); + expect(RESULT).toEqual(EXPECTED); }); }); diff --git a/packages/abi-coder/src/utilities.ts b/packages/abi-coder/src/utilities.ts index 5ed294e38b8..6cbe622481b 100644 --- a/packages/abi-coder/src/utilities.ts +++ b/packages/abi-coder/src/utilities.ts @@ -1,7 +1,8 @@ -import type { InputValue } from './coders/abstract-coder'; -import type Coder from './coders/abstract-coder'; -import VecCoder from './coders/vec'; -import { OPTION_CODER_TYPE } from './constants'; +import type { BytesLike } from '@ethersproject/bytes'; +import { concat, arrayify } from '@ethersproject/bytes'; + +import U64Coder from './coders/u64'; +import { OPTION_CODER_TYPE, WORD_SIZE } from './constants'; import type { ParamType } from './fragments/param-type'; export function filterEmptyParams(types: T): T; @@ -14,49 +15,118 @@ export function hasOptionTypes(types: ReadonlyArray) { return types.some((t) => (t as Readonly)?.type === OPTION_CODER_TYPE); } -type ByteInfo = { vecByteLength: number } | { byteLength: number }; -export function getVectorAdjustments( - coders: Coder[], - values: InputValue[], - offset = 0 -) { - const vectorData: Uint8Array[] = []; - const byteMap: ByteInfo[] = coders.map((encoder, i) => { - if (!(encoder instanceof VecCoder)) { - return { byteLength: encoder.encodedLength }; +export type DynamicData = { + [pointerIndex: number]: Uint8ArrayWithDynamicData; +}; + +export type Uint8ArrayWithDynamicData = Uint8Array & { + dynamicData?: DynamicData; +}; + +const VEC_PROPERTY_SPACE = 3; // ptr + cap + length +export const BASE_VECTOR_OFFSET = VEC_PROPERTY_SPACE * WORD_SIZE; + +// this is a fork of @ethersproject/bytes:concat +// this collects individual dynamicData data and relocates it to top level +export function concatWithDynamicData(items: ReadonlyArray): Uint8ArrayWithDynamicData { + const topLevelData: DynamicData = {}; + + let totalIndex = 0; + const objects = items.map((item) => { + const dynamicData = (item as Uint8ArrayWithDynamicData).dynamicData; + if (dynamicData) { + Object.entries(dynamicData).forEach(([pointerIndex, vData]) => { + topLevelData[parseInt(pointerIndex, 10) + totalIndex] = vData; + }); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const data = encoder.getEncodedVectorData(values[i] as any); - vectorData.push(data); - return { vecByteLength: data.byteLength }; + const byteArray = arrayify(item); + totalIndex += byteArray.byteLength / WORD_SIZE; + + return byteArray; }); - if (!vectorData.length) { - return vectorData; + const length = objects.reduce((accum, item) => accum + item.length, 0); + const result: Uint8ArrayWithDynamicData = new Uint8Array(length); + + objects.reduce((offset, object) => { + result.set(object, offset); + return offset + object.length; + }, 0); + + // store vector data and pointer indices, but only if data exist + if (Object.keys(topLevelData).length) { + result.dynamicData = topLevelData; } - const baseVectorOffset = vectorData.length * VecCoder.getBaseOffset() + offset; - const offsetMap = coders.map((encoder, paramIndex) => { - if (!(encoder instanceof VecCoder)) { - return 0; - } + return result; +} + +export function unpackDynamicData( + results: Uint8ArrayWithDynamicData, + baseOffset: number, + dataOffset: number +): Uint8Array { + if (!results.dynamicData) { + return concat([results]); + } - return byteMap.reduce((sum, byteInfo, byteIndex) => { - // non-vector data - if ('byteLength' in byteInfo) { - return sum + byteInfo.byteLength; - } + let cumulativeDynamicByteLength = 0; + let updatedResults = results; + Object.entries(results.dynamicData).forEach(([pointerIndex, vData]) => { + // update value of pointer + const pointerOffset = parseInt(pointerIndex, 10) * WORD_SIZE; + const adjustedValue = new U64Coder().encode( + dataOffset + baseOffset + cumulativeDynamicByteLength + ); + updatedResults.set(adjustedValue, pointerOffset); - // account for preceding vector data earlier in input list - if (byteIndex < paramIndex) { - return sum + byteInfo.vecByteLength; - } + // append dynamic data at the end + const dataToAppend = vData.dynamicData + ? // unpack child dynamic data + unpackDynamicData( + vData, + baseOffset, + dataOffset + vData.byteLength + cumulativeDynamicByteLength + ) + : vData; + updatedResults = concat([updatedResults, dataToAppend]); - return sum; - }, baseVectorOffset); + cumulativeDynamicByteLength += dataToAppend.byteLength; }); - coders.forEach((code, i) => code.setOffset(offsetMap[i])); - return vectorData; + return updatedResults; } + +/** useful for debugging + * Turns: + Uint8Array(24) [ + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 24 + ] + + Into: + Array [ + Uint8Array(8) [ + 0, 0, 0, 0, 0, 0, 0, 1 + ], + Uint8Array(8) [ + 0, 0, 0, 0, 0, 0, 0, 2 + ], + Uint8Array(8) [ + 0, 0, 0, 0, 0, 0, 0, 24 + ] + ] + * + */ +export const chunkByWord = (data: Uint8Array): Uint8Array[] => { + const chunks = []; + let offset = 0; + let chunk = data.slice(offset, offset + WORD_SIZE); + while (chunk.length) { + chunks.push(chunk); + offset += WORD_SIZE; + chunk = data.slice(offset, offset + WORD_SIZE); + } + + return chunks; +}; diff --git a/packages/fuel-gauge/src/coverage-contract.test.ts b/packages/fuel-gauge/src/coverage-contract.test.ts index c502ab5e141..8363a341da7 100644 --- a/packages/fuel-gauge/src/coverage-contract.test.ts +++ b/packages/fuel-gauge/src/coverage-contract.test.ts @@ -507,4 +507,24 @@ describe('Coverage Contract', () => { bn(340).toHex(), ]); }); + + it('should support vec in vec', async () => { + const INPUT = [ + [0, 1, 2], + [0, 1, 2], + ]; + await contractInstance.functions.vec_in_vec(INPUT).call(); + + expect(1).toEqual(1); + }); + + it('should support array in vec', async () => { + const INPUT = [ + [0, 1, 2], + [0, 1, 2], + ]; + await contractInstance.functions.vec_in_array(INPUT).call(); + + expect(1).toEqual(1); + }); }); diff --git a/packages/fuel-gauge/src/predicate.test.ts b/packages/fuel-gauge/src/predicate.test.ts index ef1b2950d7f..70fe2d14348 100644 --- a/packages/fuel-gauge/src/predicate.test.ts +++ b/packages/fuel-gauge/src/predicate.test.ts @@ -28,6 +28,8 @@ import testPredicateAddress from '../test-projects/predicate-address'; import testPredicateFalse from '../test-projects/predicate-false'; import testPredicateMainArgsStruct from '../test-projects/predicate-main-args-struct'; import predicateMainArgsStructAbi from '../test-projects/predicate-main-args-struct/out/debug/predicate-main-args-struct-abi.json'; +import testPredicateMainArgsVector from '../test-projects/predicate-main-args-vector'; +import testPredicateMainArgsVectorAbi from '../test-projects/predicate-main-args-vector/out/debug/predicate-main-args-vector-abi.json'; import testPredicateStruct from '../test-projects/predicate-struct'; import testPredicateTrue from '../test-projects/predicate-true'; import testPredicateU32 from '../test-projects/predicate-u32'; @@ -77,7 +79,6 @@ const assertResults = async ( ): Promise => { // Check there are UTXO locked with the predicate hash expect(toNumber(initialPredicateBalance)).toBeGreaterThanOrEqual(toNumber(amountToPredicate)); - // !isSkippingInitialReceiverBalance && expect(initialReceiverBalance.toHex()).toEqual(toHex(0)); expect(initialReceiverBalance.toHex()).toEqual(toHex(0)); // Check the balance of the receiver @@ -447,6 +448,33 @@ describe('Predicate', () => { ).rejects.toThrow('Invalid transaction'); }); + it.skip('can call a Coin predicate which returns true with valid predicate data [main args vector]', async () => { + const [wallet, receiver] = await setup(); + const amountToPredicate = 100; + const chainId = await wallet.provider.getChainId(); + const amountToReceiver = 50; + const predicate = new Predicate<[BigNumberish[]]>( + testPredicateMainArgsVector, + chainId, + testPredicateMainArgsVectorAbi + ); + + const initialPredicateBalance = await setupPredicate(wallet, predicate, amountToPredicate); + const initialReceiverBalance = await receiver.getBalance(); + + const tx = await predicate.setData([42]).transfer(receiver.address, amountToReceiver); + await tx.waitForResult(); + + await assertResults( + predicate, + receiver, + initialPredicateBalance, + initialReceiverBalance, + amountToPredicate, + amountToReceiver + ); + }); + it('should fail if inform gasLimit too low', async () => { const [wallet, receiver] = await setup(); const amountToPredicate = 100; diff --git a/packages/fuel-gauge/src/script-with-vectors.test.ts b/packages/fuel-gauge/src/script-with-vectors.test.ts new file mode 100644 index 00000000000..8049b293659 --- /dev/null +++ b/packages/fuel-gauge/src/script-with-vectors.test.ts @@ -0,0 +1,155 @@ +import { generateTestWallet } from '@fuel-ts/wallet/test-utils'; +import type { BigNumberish, BN } from 'fuels'; +import { NativeAssetId, Provider } from 'fuels'; + +import { getScript } from './utils'; + +const setup = async (balance = 5_000) => { + const provider = new Provider('http://127.0.0.1:4000/graphql'); + + // Create wallet + const wallet = await generateTestWallet(provider, [[balance, NativeAssetId]]); + + return wallet; +}; + +describe('Script With Vectors', () => { + it('can call script and use main argument [array]', async () => { + const wallet = await setup(); + const someArray = [1, 100]; + const scriptInstance = getScript<[BigNumberish[]], void>('script-with-array', wallet); + + const { logs } = await scriptInstance.functions.main(someArray).call(); + + expect(logs.map((n) => n.toNumber())).toEqual([1]); + }); + + it('can call script and use main argument [vec]', async () => { + const wallet = await setup(); + const someVec = [7, 2, 1, 5]; + const scriptInstance = getScript<[BigNumberish[]], void>('script-with-vector', wallet); + + const { logs } = await scriptInstance.functions.main(someVec).call(); + + const formattedLog = logs.map((l) => (typeof l === 'string' ? l : l.toNumber())); + + expect(formattedLog).toEqual([ + 7, + 'vector.buf.ptr', + 11256, + 'vector.buf.cap', + 4, + 'vector.len', + 4, + 'addr_of vector', + 11232, + ]); + }); + + it('can call script and use main argument [struct in vec in struct in vec in struct in vec]', async () => { + const wallet = await setup(); + + const importantDates = [ + { + dates: [ + { + day: 29, + month: 12, + year: 2020, + }, + { + day: 12, + month: 8, + year: 2019, + }, + ], + tag: 4, + lag: 7, + }, + { + dates: [ + { + day: 22, + month: 10, + year: 1980, + }, + ], + tag: 3, + lag: 9, + }, + ]; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const scriptInstance = getScript<[any], void>('script-with-vector-mixed', wallet); + + const { value } = await scriptInstance.functions.main(importantDates).call(); + expect((value as unknown as BN).toString()).toBe('1'); + }); + + it('can call script and use main argument [struct in vec in struct in vec in struct in vec]', async () => { + const wallet = await setup(); + + const scores = [24, 56, 43]; + + const importantDates = [ + { + dates: [ + { + day: 29, + month: 12, + year: 2020, + }, + { + day: 12, + month: 8, + year: 2019, + }, + ], + tag: 1, + lag: 7, + }, + { + dates: [ + { + day: 22, + month: 10, + year: 1980, + }, + ], + tag: 2, + lag: 9, + }, + ]; + + const errors = [ + { StateError: 'Void' }, + { StateError: 'Pending' }, + { StateError: 'Completed' }, + { UserError: 'InsufficientPermissions' }, + { UserError: 'Unauthorized' }, + { UserError: 'Unauthorized' }, + { UserError: 'Unauthorized' }, + { UserError: 'Unauthorized' }, + { UserError: 'Unauthorized' }, + ]; + + const vectorOfStructs = [ + { + scores, + important_dates: importantDates, + errors, + }, + { + scores, + important_dates: importantDates, + errors, + }, + ]; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const scriptInstance = getScript<[any[]], void>('script-with-vector-advanced', wallet); + + const { value } = await scriptInstance.functions.main(vectorOfStructs).call(); + expect((value as unknown as BN).toString()).toBe('1'); + }); +}); diff --git a/packages/fuel-gauge/src/vector-types.test.ts b/packages/fuel-gauge/src/vector-types.test.ts new file mode 100644 index 00000000000..7a42fc8bcb1 --- /dev/null +++ b/packages/fuel-gauge/src/vector-types.test.ts @@ -0,0 +1,180 @@ +import { generateTestWallet } from '@fuel-ts/wallet/test-utils'; +import type { BigNumberish } from 'fuels'; +import { bn, Predicate, Wallet, Address, NativeAssetId, Provider } from 'fuels'; + +import predicateVectorTypes from '../test-projects/predicate-vector-types'; +import predicateVectorTypesAbi from '../test-projects/predicate-vector-types/out/debug/predicate-vector-types-abi.json'; + +import { getScript, getSetupContract } from './utils'; + +const U32_VEC = [0, 1, 2]; +const VEC_IN_VEC = [ + [0, 1, 2], + [0, 1, 2], +]; +const STRUCT_IN_VEC = [{ a: 0 }, { a: 1 }]; +const VEC_IN_STRUCT = { a: [0, 1, 2] }; +const ARRAY_IN_VEC = [ + [0, 1], + [0, 1], +]; +const VEC_IN_ARRAY = [ + [0, 1, 2], + [0, 1, 2], +]; +const VEC_IN_ENUM = { a: [0, 1, 2] }; +const ENUM_IN_VEC = [{ a: 0 }, { a: 1 }]; +const TUPLE_IN_VEC = [ + [0, 0], + [1, 1], +]; +const VEC_IN_TUPLE = [ + [0, 1, 2], + [0, 1, 2], +]; +const VEC_IN_A_VEC_IN_A_STRUCT_IN_A_VEC = [ + { + a: [ + [0, 1, 2], + [3, 4, 5], + ], + }, + { + a: [ + [6, 7, 8], + [9, 10, 11], + ], + }, +]; + +type SomeStruct = { + a: number; +}; + +type SomeStructWithVec = { + a: number[]; +}; + +type VecInAStructInAVec = { + a: number[][]; +}[]; + +type TwoDimensionArray = number[][]; + +// these Type shapes are here to get the TypeScript inference, they aren't 100% accurate +type MainArgs = [ + number[], // U32_VEC + TwoDimensionArray, // VEC_IN_VEC + SomeStruct[], // STRUCT_IN_VEC + SomeStructWithVec, // VEC_IN_STRUCT + TwoDimensionArray, // ARRAY_IN_VEC + TwoDimensionArray, // VEC_IN_ARRAY + SomeStructWithVec, // VEC_IN_ENUM + SomeStruct[], // ENUM_IN_VEC + TwoDimensionArray, // TUPLE_IN_VEC + TwoDimensionArray, // VEC_IN_TUPLE + VecInAStructInAVec // VEC_IN_A_VEC_IN_A_STRUCT_IN_A_VEC +]; + +const setup = async (balance = 5_000) => { + const provider = new Provider('http://127.0.0.1:4000/graphql'); + + // Create wallet + const wallet = await generateTestWallet(provider, [[balance, NativeAssetId]]); + + return wallet; +}; + +describe('Vector Types Validation', () => { + it('can use supported vector types [vector-types-contract]', async () => { + const setupContract = getSetupContract('vector-types-contract'); + const contractInstance = await setupContract(); + + const { value } = await contractInstance.functions + .test_all( + U32_VEC, + VEC_IN_VEC, + STRUCT_IN_VEC, + VEC_IN_STRUCT, + ARRAY_IN_VEC, + VEC_IN_ARRAY, + VEC_IN_ENUM, + ENUM_IN_VEC, + TUPLE_IN_VEC, + VEC_IN_TUPLE, + VEC_IN_A_VEC_IN_A_STRUCT_IN_A_VEC + ) + .call(); + expect(value).toBe(true); + }); + + it('can use supported vector types [vector-types-script]', async () => { + const wallet = await setup(); + const scriptInstance = getScript('vector-types-script', wallet); + + const { value } = await scriptInstance.functions + .main( + U32_VEC, + VEC_IN_VEC, + STRUCT_IN_VEC, + VEC_IN_STRUCT, + ARRAY_IN_VEC, + VEC_IN_ARRAY, + VEC_IN_ENUM, + ENUM_IN_VEC, + TUPLE_IN_VEC, + VEC_IN_TUPLE, + VEC_IN_A_VEC_IN_A_STRUCT_IN_A_VEC + ) + .call(); + + expect(value.toString()).toBe('1'); + }); + + it.skip('can use supported vector types [predicate-vector-types]', async () => { + const wallet = await setup(); + const receiver = Wallet.fromAddress(Address.fromRandom()); + const chainId = await wallet.provider.getChainId(); + const amountToPredicate = 100; + const amountToReceiver = 50; + const predicate = new Predicate( + predicateVectorTypes, + chainId, + predicateVectorTypesAbi + ); + + // setup predicate + const setupTx = await wallet.transfer(predicate.address, amountToPredicate, NativeAssetId); + await setupTx.waitForResult(); + + const initialPredicateBalance = await predicate.getBalance(); + const initialReceiverBalance = await receiver.getBalance(); + + const tx = await predicate + .setData( + U32_VEC, + VEC_IN_VEC, + STRUCT_IN_VEC, + VEC_IN_STRUCT, + ARRAY_IN_VEC, + VEC_IN_ARRAY, + VEC_IN_ENUM, + ENUM_IN_VEC, + TUPLE_IN_VEC, + VEC_IN_TUPLE, + VEC_IN_A_VEC_IN_A_STRUCT_IN_A_VEC + ) + .transfer(receiver.address, amountToReceiver); + await tx.waitForResult(); + + // Check the balance of the receiver + const finalReceiverBalance = await receiver.getBalance(); + expect(bn(initialReceiverBalance).add(amountToReceiver).toHex()).toEqual( + finalReceiverBalance.toHex() + ); + + // Check we spent the entire predicate hash input + const finalPredicateBalance = await predicate.getBalance(); + expect(finalPredicateBalance.lte(initialPredicateBalance)).toBeTruthy(); + }); +}); diff --git a/packages/fuel-gauge/test-projects/Forc.toml b/packages/fuel-gauge/test-projects/Forc.toml index 2ae24e0eb32..1c07a1b886d 100644 --- a/packages/fuel-gauge/test-projects/Forc.toml +++ b/packages/fuel-gauge/test-projects/Forc.toml @@ -15,17 +15,25 @@ members = [ "predicate-address", "predicate-false", "predicate-main-args-struct", + "predicate-main-args-vector", "predicate-struct", "predicate-triple-sig", "predicate-true", "predicate-with-configurable", "predicate-u32", + "predicate-vector-types", "revert-error", "script-main-args", "script-main-return-struct", "script-main-two-args", "script-with-configurable", + "script-with-array", + "script-with-vector", + "script-with-vector-advanced", + "script-with-vector-mixed", "storage-test-contract", "token_abi", - "token_contract" + "token_contract", + "vector-types-contract", + "vector-types-script" ] diff --git a/packages/fuel-gauge/test-projects/coverage-contract/src/main.sw b/packages/fuel-gauge/test-projects/coverage-contract/src/main.sw index 49b35dfe436..34cc70d65b3 100644 --- a/packages/fuel-gauge/test-projects/coverage-contract/src/main.sw +++ b/packages/fuel-gauge/test-projects/coverage-contract/src/main.sw @@ -101,6 +101,61 @@ abi CoverageContract { fn color_enum(input: ColorEnum) -> ColorEnum; fn vec_as_only_param(input: Vec) -> (u64, Option, Option, Option); fn u32_and_vec_params(foo: u32, input: Vec) -> (u64, Option, Option, Option); + fn vec_in_vec(arg: Vec>); + fn vec_in_array(arg: [Vec; 2]); +} + +pub fn vec_from(vals: [u32; 3]) -> Vec { + let mut vec = Vec::new(); + vec.push(vals[0]); + vec.push(vals[1]); + vec.push(vals[2]); + vec +} + +impl Eq for Vec { + fn eq(self, other: Self) -> bool { + if self.len() != other.len() { + return false; + } + let mut i = 0; + while i < self.len() { + if self.get(i).unwrap() != other.get(i).unwrap() { + return false; + } + i += 1; + } + true + } +} + +impl Eq for Vec> { + fn eq(self, other: Self) -> bool { + if self.len() != other.len() { + return false; + } + let mut i = 0; + while i < self.len() { + if self.get(i).unwrap() != other.get(i).unwrap() { + return false; + } + i += 1; + } + true + } +} + +impl Eq for [Vec; 2] { + fn eq(self, other: Self) -> bool { + let mut i = 0; + while i < 2 { + if self[i] != other[i] { + return false; + } + i += 1; + } + true + } } impl CoverageContract for Contract { @@ -369,4 +424,18 @@ impl CoverageContract for Contract { input.get(2), ) } + + fn vec_in_vec(arg: Vec>) { + let mut expected = Vec::new(); + expected.push(vec_from([0, 1, 2])); + expected.push(vec_from([0, 1, 2])); + + assert(expected == arg); + } + + fn vec_in_array(arg: [Vec; 2]) { + let expected = [vec_from([0, 1, 2]), vec_from([0, 1, 2])]; + + assert(expected == arg); + } } diff --git a/packages/fuel-gauge/test-projects/predicate-main-args-vector/Forc.toml b/packages/fuel-gauge/test-projects/predicate-main-args-vector/Forc.toml new file mode 100644 index 00000000000..4e03ae03798 --- /dev/null +++ b/packages/fuel-gauge/test-projects/predicate-main-args-vector/Forc.toml @@ -0,0 +1,5 @@ +[project] +license = "Apache-2.0" +name = "predicate-main-args-vector" + +[dependencies] diff --git a/packages/fuel-gauge/test-projects/predicate-main-args-vector/src/main.sw b/packages/fuel-gauge/test-projects/predicate-main-args-vector/src/main.sw new file mode 100644 index 00000000000..8d207fa1a60 --- /dev/null +++ b/packages/fuel-gauge/test-projects/predicate-main-args-vector/src/main.sw @@ -0,0 +1,5 @@ +predicate; + +fn main( some_vec: Vec ) -> bool { + some_vec.get(0).unwrap() == 42 +} diff --git a/packages/fuel-gauge/test-projects/predicate-vector-types/Forc.toml b/packages/fuel-gauge/test-projects/predicate-vector-types/Forc.toml new file mode 100644 index 00000000000..819c006059a --- /dev/null +++ b/packages/fuel-gauge/test-projects/predicate-vector-types/Forc.toml @@ -0,0 +1,5 @@ +[project] +license = "Apache-2.0" +name = "predicate-vector-types" + +[dependencies] diff --git a/packages/fuel-gauge/test-projects/predicate-vector-types/src/main.sw b/packages/fuel-gauge/test-projects/predicate-vector-types/src/main.sw new file mode 100644 index 00000000000..0cbfa0b82ee --- /dev/null +++ b/packages/fuel-gauge/test-projects/predicate-vector-types/src/main.sw @@ -0,0 +1,60 @@ +predicate; + +pub struct SomeStruct { + a: T, +} + +pub enum SomeEnum { + a: T, +} + +fn main( + u32_vec: Vec, + vec_in_vec: Vec>, + struct_in_vec: Vec>, + vec_in_struct: SomeStruct>, + array_in_vec: Vec<[u64; 2]>, + vec_in_array: [Vec; 2], + vec_in_enum: SomeEnum>, + enum_in_vec: Vec>, + tuple_in_vec: Vec<(u32, u32)>, + vec_in_tuple: (Vec, Vec), + vec_in_a_vec_in_a_struct_in_a_vec: Vec>>>, +) -> bool { + let mut result = true; + + result = result && (u32_vec.get(1).unwrap() == 4u32); + + result = result && (vec_in_vec.get(0).unwrap().get(1).unwrap() == 2u32); + + result = result && (struct_in_vec.get(0).unwrap().a == 8u32); + + result = result && (vec_in_struct.a.get(1).unwrap() == 16u32); + + let array: [u64; 2] = array_in_vec.get(1).unwrap(); + result = result && (array[0] == 32u64); + + result = result && (vec_in_array[0].get(1).unwrap() == 64u32); + + if let SomeEnum::a(some_vec) = vec_in_enum { + result = result && (some_vec.get(2).unwrap() == 128u32); + } else { + result = false; + } + + let enum_a = enum_in_vec.get(1).unwrap(); + if let SomeEnum::a(a) = enum_a { + result = result && (a == 16u32) + } else { + result = false; + } + + result = result && (tuple_in_vec.get(1).unwrap().0 == 128u32); + + let (tuple_a, tuple_b) = vec_in_tuple; + result = result && (tuple_a.get(1).unwrap() == 64u32); + + result = result && (vec_in_a_vec_in_a_struct_in_a_vec.get(1).unwrap().a.get(1).unwrap().get(1).unwrap() == 32u32); + + result +} diff --git a/packages/fuel-gauge/test-projects/script-with-array/Forc.toml b/packages/fuel-gauge/test-projects/script-with-array/Forc.toml new file mode 100644 index 00000000000..56f7f33b02a --- /dev/null +++ b/packages/fuel-gauge/test-projects/script-with-array/Forc.toml @@ -0,0 +1,5 @@ +[project] +license = "Apache-2.0" +name = "script-with-array" + +[dependencies] diff --git a/packages/fuel-gauge/test-projects/script-with-array/src/main.sw b/packages/fuel-gauge/test-projects/script-with-array/src/main.sw new file mode 100644 index 00000000000..d112e83df3c --- /dev/null +++ b/packages/fuel-gauge/test-projects/script-with-array/src/main.sw @@ -0,0 +1,9 @@ +script; + +use std::logging::log; + +fn main( some_array: [u64; 2] ) { + log(some_array[0]); + + require(some_array[0] == 1, "array value is not as expected"); +} \ No newline at end of file diff --git a/packages/fuel-gauge/test-projects/script-with-vector-advanced/Forc.toml b/packages/fuel-gauge/test-projects/script-with-vector-advanced/Forc.toml new file mode 100644 index 00000000000..436a980f5ac --- /dev/null +++ b/packages/fuel-gauge/test-projects/script-with-vector-advanced/Forc.toml @@ -0,0 +1,5 @@ +[project] +license = "Apache-2.0" +name = "script-with-vector-advanced" + +[dependencies] diff --git a/packages/fuel-gauge/test-projects/script-with-vector-advanced/src/main.sw b/packages/fuel-gauge/test-projects/script-with-vector-advanced/src/main.sw new file mode 100644 index 00000000000..e0b337539f1 --- /dev/null +++ b/packages/fuel-gauge/test-projects/script-with-vector-advanced/src/main.sw @@ -0,0 +1,68 @@ +script; + +use std::logging::log; + +struct Date { + day: u8, + month: u8, + year: u64, +} + +struct ImportantDates { + dates: Vec, + tag: u8, + lag: u8, +} + +pub enum StateError { + Void: (), + Pending: (), + Completed: (), +} + +pub enum UserError { + InsufficientPermissions: (), + Unauthorized: (), +} + +pub enum Error { + StateError: StateError, + UserError: UserError, +} + +struct Struct { + scores: Vec, + important_dates: Vec, + errors: Vec +} + +fn main(inputs: Vec) -> bool { + let input_1 = inputs.get(0).unwrap(); + + let important_dates_1 = input_1.important_dates.get(0).unwrap(); + + + if important_dates_1.tag != 1 { + return false; + } + + let dates_1 = important_dates_1.dates.get(0).unwrap(); + + if dates_1.day != 29 { + return false; + } + + log(dates_1.month); + + if dates_1.month != 12 { + return false; + } + + log(dates_1.year); + + if dates_1.year != 2020 { + return false; + } + + true +} \ No newline at end of file diff --git a/packages/fuel-gauge/test-projects/script-with-vector-mixed/Forc.toml b/packages/fuel-gauge/test-projects/script-with-vector-mixed/Forc.toml new file mode 100644 index 00000000000..ea133cdf241 --- /dev/null +++ b/packages/fuel-gauge/test-projects/script-with-vector-mixed/Forc.toml @@ -0,0 +1,5 @@ +[project] +license = "Apache-2.0" +name = "script-with-vector-mixed" + +[dependencies] diff --git a/packages/fuel-gauge/test-projects/script-with-vector-mixed/src/main.sw b/packages/fuel-gauge/test-projects/script-with-vector-mixed/src/main.sw new file mode 100644 index 00000000000..9af42c03ea4 --- /dev/null +++ b/packages/fuel-gauge/test-projects/script-with-vector-mixed/src/main.sw @@ -0,0 +1,41 @@ +script; + +use std::logging::log; + +struct Date { + day: u8, + month: u8, + year: u64, +} + +struct ImportantDates { + dates: Vec, + tag: u8, + lag: u8, +} + +fn main(input: Vec) -> bool { + let important_dates = input.get(0).unwrap(); + + log(important_dates.tag); + log(important_dates.lag); + + let dates_1 = important_dates.dates.get(0).unwrap(); + if dates_1.day != 29 { + return false; + } + + log(dates_1.month); + + if dates_1.month != 12 { + return false; + } + + log(dates_1.year); + + if dates_1.year != 2020 { + return false; + } + + true +} \ No newline at end of file diff --git a/packages/fuel-gauge/test-projects/script-with-vector/Forc.toml b/packages/fuel-gauge/test-projects/script-with-vector/Forc.toml new file mode 100644 index 00000000000..df21763a6ab --- /dev/null +++ b/packages/fuel-gauge/test-projects/script-with-vector/Forc.toml @@ -0,0 +1,5 @@ +[project] +license = "Apache-2.0" +name = "script-with-vector" + +[dependencies] diff --git a/packages/fuel-gauge/test-projects/script-with-vector/src/main.sw b/packages/fuel-gauge/test-projects/script-with-vector/src/main.sw new file mode 100644 index 00000000000..e6064240580 --- /dev/null +++ b/packages/fuel-gauge/test-projects/script-with-vector/src/main.sw @@ -0,0 +1,27 @@ +script; + +use std::logging::log; + +fn main(vector: Vec ) { + log(vector.get(0).unwrap()); + + let _is_valid = match vector.len() { + 0 => false, + length => { + assert(length == 4); + assert(vector.capacity() == 4); + assert(vector.is_empty() == false); + log("vector.buf.ptr"); + log(vector.buf.ptr); + log("vector.buf.cap"); + log(vector.buf.cap); + log("vector.len"); + log(vector.len); + log("addr_of vector"); + log(__addr_of(vector)); + true + }, + }; + + require(vector.get(0).unwrap() == 7, "value is not as expected"); +} \ No newline at end of file diff --git a/packages/fuel-gauge/test-projects/vector-types-contract/Forc.toml b/packages/fuel-gauge/test-projects/vector-types-contract/Forc.toml new file mode 100644 index 00000000000..22acf34f41a --- /dev/null +++ b/packages/fuel-gauge/test-projects/vector-types-contract/Forc.toml @@ -0,0 +1,6 @@ +[project] +entry = "main.sw" +license = "Apache-2.0" +name = "vector-types-contract" + +[dependencies] diff --git a/packages/fuel-gauge/test-projects/vector-types-contract/src/main.sw b/packages/fuel-gauge/test-projects/vector-types-contract/src/main.sw new file mode 100644 index 00000000000..9d6add61e16 --- /dev/null +++ b/packages/fuel-gauge/test-projects/vector-types-contract/src/main.sw @@ -0,0 +1,335 @@ +contract; + +use core::ops::Eq; + +pub struct SomeStruct { + a: T, +} + +pub enum SomeEnum { + a: T, +} + +pub fn vec_from(vals: [u32; 3]) -> Vec { + let mut vec = Vec::new(); + vec.push(vals[0]); + vec.push(vals[1]); + vec.push(vals[2]); + vec +} + +impl Eq for (u32, u32) { + fn eq(self, other: Self) -> bool { + self.0 == other.0 && self.1 == other.1 + } +} + +impl Eq for SomeEnum { + fn eq(self, other: Self) -> bool { + match self { + SomeEnum::a(val) => { + match other { + SomeEnum::a(other_val) => { + val == other_val + } + } + } + } + } +} + +impl Eq for Vec { + fn eq(self, other: Self) -> bool { + if self.len() != other.len() { + return false; + } + let mut i = 0; + while i < self.len() { + if self.get(i).unwrap() != other.get(i).unwrap() { + return false; + } + i += 1; + } + true + } +} + +impl Eq for (Vec, Vec) { + fn eq(self, other: Self) -> bool { + self.0 == other.0 && self.1 == other.1 + } +} + +impl Eq for Vec> { + fn eq(self, other: Self) -> bool { + if self.len() != other.len() { + return false; + } + let mut i = 0; + while i < self.len() { + if self.get(i).unwrap() != other.get(i).unwrap() { + return false; + } + i += 1; + } + true + } +} + +impl Eq for SomeStruct { + fn eq(self, other: Self) -> bool { + self.a == other.a + } +} + +impl Eq for [Vec; 2] { + fn eq(self, other: Self) -> bool { + let mut i = 0; + while i < 2 { + if self[i] != other[i] { + return false; + } + i += 1; + } + true + } +} + +impl Eq for Vec> { + fn eq(self, other: Self) -> bool { + if self.len() != other.len() { + return false; + } + let mut i = 0; + while i < self.len() { + if self.get(i).unwrap() != other.get(i).unwrap() { + return false; + } + i += 1; + } + true + } +} + +impl Eq for [u64; 2] { + fn eq(self, other: Self) -> bool { + let mut i = 0; + while i < 2 { + if self[i] != other[i] { + return false; + } + i += 1; + } + true + } +} + +impl Eq for Vec<[u64; 2]> { + fn eq(self, other: Self) -> bool { + if self.len() != other.len() { + return false; + } + let mut i = 0; + while i < self.len() { + if self.get(i).unwrap() != other.get(i).unwrap() { + return false; + } + i += 1; + } + true + } +} + +impl Eq for SomeEnum> { + fn eq(self, other: Self) -> bool { + match self { + SomeEnum::a(val) => { + match other { + SomeEnum::a(other_val) => { + val == other_val + } + } + } + } + } +} + +impl Eq for Vec> { + fn eq(self, other: Self) -> bool { + if self.len() != other.len() { + return false; + } + + let mut i = 0; + while i < self.len() { + if self.get(i).unwrap() != other.get(i).unwrap() { + return false; + } + i += 1; + } + true + } +} + +impl Eq for Vec<(u32, u32)> { + fn eq(self, other: Self) -> bool { + if self.len() != other.len() { + return false; + } + + let mut i = 0; + while i < self.len() { + if self.get(i).unwrap() != other.get(i).unwrap() { + return false; + } + i += 1; + } + true + } +} + +impl Eq for SomeStruct>> { + fn eq(self, other: Self) -> bool { + self.a == other.a + } +} + +impl Eq for Vec>>> { + fn eq(self, other: Self) -> bool { + if self.len() != other.len() { + return false; + } + + let mut i = 0; + while i < self.len() { + if self.get(i).unwrap() != other.get(i).unwrap() { + return false; + } + i += 1; + } + true + } +} + +abi MyContract { + fn test_all( + u32_vec: Vec, + vec_in_vec: Vec>, + struct_in_vec: Vec>, + vec_in_struct: SomeStruct>, + array_in_vec: Vec<[u64; 2]>, + vec_in_array: [Vec; 2], + vec_in_enum: SomeEnum>, + enum_in_vec: Vec>, + tuple_in_vec: Vec<(u32, u32)>, + vec_in_tuple: (Vec, Vec), + vec_in_a_vec_in_a_struct_in_a_vec: Vec>>>, + ) -> bool; +} + +impl MyContract for Contract { + fn test_all( + u32_vec: Vec, + vec_in_vec: Vec>, + struct_in_vec: Vec>, + vec_in_struct: SomeStruct>, + array_in_vec: Vec<[u64; 2]>, + vec_in_array: [Vec; 2], + vec_in_enum: SomeEnum>, + enum_in_vec: Vec>, + tuple_in_vec: Vec<(u32, u32)>, + vec_in_tuple: (Vec, Vec), + vec_in_a_vec_in_a_struct_in_a_vec: Vec>>>, + ) -> bool { + { + let exp_u32_vec = vec_from([0, 1, 2]); + + require(u32_vec == exp_u32_vec, "u32_vec_error"); + } + { + let mut exp_vec_in_vec = Vec::new(); + exp_vec_in_vec.push(vec_from([0, 1, 2])); + exp_vec_in_vec.push(vec_from([0, 1, 2])); + + require(vec_in_vec == exp_vec_in_vec, "vec_in_vec err"); + } + { + let mut exp_struct_in_vec = Vec::new(); + exp_struct_in_vec.push(SomeStruct { a: 0u32 }); + exp_struct_in_vec.push(SomeStruct { a: 1u32 }); + + require(struct_in_vec == exp_struct_in_vec, "struct_in_vec err"); + } + { + let exp_vec_in_struct = SomeStruct { + a: vec_from([0, 1, 2]), + }; + + require(vec_in_struct.a == exp_vec_in_struct.a, "vec_in_struct err"); + } + { + let mut exp_array_in_vec = Vec::new(); + exp_array_in_vec.push([0, 1]); + exp_array_in_vec.push([0, 1]); + + require(array_in_vec == exp_array_in_vec, "array_in_vec err"); + } + { + let exp_vec_in_array = [vec_from([0, 1, 2]), vec_from([0, 1, 2])]; + + require(vec_in_array == exp_vec_in_array, "vec_in_array err"); + } + { + let exp_u32_vec = vec_from([0, 1, 2]); + let exp_vec_in_enum = SomeEnum::a(exp_u32_vec); + + require(vec_in_enum == exp_vec_in_enum, "vec_in_enum err"); + } + { + let mut exp_enum_in_vec = Vec::new(); + exp_enum_in_vec.push(SomeEnum::a(0)); + exp_enum_in_vec.push(SomeEnum::a(1)); + + require(enum_in_vec == exp_enum_in_vec, "enum_in_vec err"); + } + { + let mut exp_tuple_in_vec = Vec::new(); + exp_tuple_in_vec.push((0, 0)); + exp_tuple_in_vec.push((1, 1)); + + require(tuple_in_vec == exp_tuple_in_vec, "tuple_in_vec err"); + } + { + let exp_vec_in_tuple = (vec_from([0, 1, 2]), vec_from([0, 1, 2])); + + require(vec_in_tuple == exp_vec_in_tuple, "vec_in_tuple err"); + } + { + let mut exp_vec_in_a_vec_in_a_struct_in_a_vec = Vec::new(); + + let mut inner_vec_1 = Vec::new(); + + let inner_inner_vec_1 = vec_from([0, 1, 2]); + inner_vec_1.push(inner_inner_vec_1); + + let inner_inner_vec_2 = vec_from([3, 4, 5]); + inner_vec_1.push(inner_inner_vec_2); + + exp_vec_in_a_vec_in_a_struct_in_a_vec.push(SomeStruct { a: inner_vec_1 }); + + let mut inner_vec_2 = Vec::new(); + + let inner_inner_vec_3 = vec_from([6, 7, 8]); + inner_vec_2.push(inner_inner_vec_3); + + let inner_inner_vec_4 = vec_from([9, 10, 11]); + inner_vec_2.push(inner_inner_vec_4); + + exp_vec_in_a_vec_in_a_struct_in_a_vec.push(SomeStruct { a: inner_vec_2 }); + + require(vec_in_a_vec_in_a_struct_in_a_vec == exp_vec_in_a_vec_in_a_struct_in_a_vec, "vec_in_a_vec_in_a_struct_in_a_vec err"); + } + + true +} +} diff --git a/packages/fuel-gauge/test-projects/vector-types-script/Forc.toml b/packages/fuel-gauge/test-projects/vector-types-script/Forc.toml new file mode 100644 index 00000000000..9f34c6afdc2 --- /dev/null +++ b/packages/fuel-gauge/test-projects/vector-types-script/Forc.toml @@ -0,0 +1,5 @@ +[project] +license = "Apache-2.0" +name = "vector-types-script" + +[dependencies] diff --git a/packages/fuel-gauge/test-projects/vector-types-script/src/main.sw b/packages/fuel-gauge/test-projects/vector-types-script/src/main.sw new file mode 100644 index 00000000000..eecd02d513b --- /dev/null +++ b/packages/fuel-gauge/test-projects/vector-types-script/src/main.sw @@ -0,0 +1,318 @@ +script; + +use core::ops::Eq; + +pub struct SomeStruct { + a: T, +} + +pub enum SomeEnum { + a: T, +} + +pub fn vec_from(vals: [u32; 3]) -> Vec { + let mut vec = Vec::new(); + vec.push(vals[0]); + vec.push(vals[1]); + vec.push(vals[2]); + vec +} + +impl Eq for (u32, u32) { + fn eq(self, other: Self) -> bool { + self.0 == other.0 && self.1 == other.1 + } +} + +impl Eq for SomeEnum { + fn eq(self, other: Self) -> bool { + match self { + SomeEnum::a(val) => { + match other { + SomeEnum::a(other_val) => { + val == other_val + } + } + } + } + } +} + +impl Eq for Vec { + fn eq(self, other: Self) -> bool { + if self.len() != other.len() { + return false; + } + let mut i = 0; + while i < self.len() { + if self.get(i).unwrap() != other.get(i).unwrap() { + return false; + } + i += 1; + } + true + } +} + +impl Eq for (Vec, Vec) { + fn eq(self, other: Self) -> bool { + self.0 == other.0 && self.1 == other.1 + } +} + +impl Eq for Vec> { + fn eq(self, other: Self) -> bool { + if self.len() != other.len() { + return false; + } + let mut i = 0; + while i < self.len() { + if self.get(i).unwrap() != other.get(i).unwrap() { + return false; + } + i += 1; + } + true + } +} + +impl Eq for SomeStruct { + fn eq(self, other: Self) -> bool { + self.a == other.a + } +} + +impl Eq for [Vec; 2] { + fn eq(self, other: Self) -> bool { + let mut i = 0; + while i < 2 { + if self[i] != other[i] { + return false; + } + i += 1; + } + true + } +} + +impl Eq for Vec> { + fn eq(self, other: Self) -> bool { + if self.len() != other.len() { + return false; + } + let mut i = 0; + while i < self.len() { + if self.get(i).unwrap() != other.get(i).unwrap() { + return false; + } + i += 1; + } + true + } +} + +impl Eq for [u64; 2] { + fn eq(self, other: Self) -> bool { + let mut i = 0; + while i < 2 { + if self[i] != other[i] { + return false; + } + i += 1; + } + true + } +} + +impl Eq for Vec<[u64; 2]> { + fn eq(self, other: Self) -> bool { + if self.len() != other.len() { + return false; + } + let mut i = 0; + while i < self.len() { + if self.get(i).unwrap() != other.get(i).unwrap() { + return false; + } + i += 1; + } + true + } +} + +impl Eq for SomeEnum> { + fn eq(self, other: Self) -> bool { + match self { + SomeEnum::a(val) => { + match other { + SomeEnum::a(other_val) => { + val == other_val + } + } + } + } + } +} + +impl Eq for Vec> { + fn eq(self, other: Self) -> bool { + if self.len() != other.len() { + return false; + } + + let mut i = 0; + while i < self.len() { + if self.get(i).unwrap() != other.get(i).unwrap() { + return false; + } + i += 1; + } + true + } +} + +impl Eq for Vec<(u32, u32)> { + fn eq(self, other: Self) -> bool { + if self.len() != other.len() { + return false; + } + + let mut i = 0; + while i < self.len() { + if self.get(i).unwrap() != other.get(i).unwrap() { + return false; + } + i += 1; + } + true + } +} + +impl Eq for SomeStruct>> { + fn eq(self, other: Self) -> bool { + self.a == other.a + } +} + +impl Eq for Vec>>> { + fn eq(self, other: Self) -> bool { + if self.len() != other.len() { + return false; + } + + let mut i = 0; + while i < self.len() { + if self.get(i).unwrap() != other.get(i).unwrap() { + return false; + } + i += 1; + } + true + } +} + + +fn main( + u32_vec: Vec, + vec_in_vec: Vec>, + struct_in_vec: Vec>, + vec_in_struct: SomeStruct>, + array_in_vec: Vec<[u64; 2]>, + vec_in_array: [Vec; 2], + vec_in_enum: SomeEnum>, + enum_in_vec: Vec>, + tuple_in_vec: Vec<(u32, u32)>, + vec_in_tuple: (Vec, Vec), + vec_in_a_vec_in_a_struct_in_a_vec: Vec>>>, +) -> bool { + { + let exp_u32_vec = vec_from([0, 1, 2]); + + require(u32_vec == exp_u32_vec, "u32_vec_error"); + } + { + let mut exp_vec_in_vec = Vec::new(); + exp_vec_in_vec.push(vec_from([0, 1, 2])); + exp_vec_in_vec.push(vec_from([0, 1, 2])); + + require(vec_in_vec == exp_vec_in_vec, "vec_in_vec err"); + } + { + let mut exp_struct_in_vec = Vec::new(); + exp_struct_in_vec.push(SomeStruct { a: 0u32 }); + exp_struct_in_vec.push(SomeStruct { a: 1u32 }); + + require(struct_in_vec == exp_struct_in_vec, "struct_in_vec err"); + } + { + let exp_vec_in_struct = SomeStruct { + a: vec_from([0, 1, 2]), + }; + + require(vec_in_struct.a == exp_vec_in_struct.a, "vec_in_struct err"); + } + { + let mut exp_array_in_vec = Vec::new(); + exp_array_in_vec.push([0, 1]); + exp_array_in_vec.push([0, 1]); + + require(array_in_vec == exp_array_in_vec, "array_in_vec err"); + } + { + let exp_vec_in_array = [vec_from([0, 1, 2]), vec_from([0, 1, 2])]; + + require(vec_in_array == exp_vec_in_array, "vec_in_array err"); + } + { + let exp_u32_vec = vec_from([0, 1, 2]); + let exp_vec_in_enum = SomeEnum::a(exp_u32_vec); + + require(vec_in_enum == exp_vec_in_enum, "vec_in_enum err"); + } + { + let mut exp_enum_in_vec = Vec::new(); + exp_enum_in_vec.push(SomeEnum::a(0)); + exp_enum_in_vec.push(SomeEnum::a(1)); + + require(enum_in_vec == exp_enum_in_vec, "enum_in_vec err"); + } + { + let mut exp_tuple_in_vec = Vec::new(); + exp_tuple_in_vec.push((0, 0)); + exp_tuple_in_vec.push((1, 1)); + + require(tuple_in_vec == exp_tuple_in_vec, "tuple_in_vec err"); + } + { + let exp_vec_in_tuple = (vec_from([0, 1, 2]), vec_from([0, 1, 2])); + + require(vec_in_tuple == exp_vec_in_tuple, "vec_in_tuple err"); + } + { + let mut exp_vec_in_a_vec_in_a_struct_in_a_vec = Vec::new(); + + let mut inner_vec_1 = Vec::new(); + + let inner_inner_vec_1 = vec_from([0, 1, 2]); + inner_vec_1.push(inner_inner_vec_1); + + let inner_inner_vec_2 = vec_from([3, 4, 5]); + inner_vec_1.push(inner_inner_vec_2); + + exp_vec_in_a_vec_in_a_struct_in_a_vec.push(SomeStruct { a: inner_vec_1 }); + + let mut inner_vec_2 = Vec::new(); + + let inner_inner_vec_3 = vec_from([6, 7, 8]); + inner_vec_2.push(inner_inner_vec_3); + + let inner_inner_vec_4 = vec_from([9, 10, 11]); + inner_vec_2.push(inner_inner_vec_4); + + exp_vec_in_a_vec_in_a_struct_in_a_vec.push(SomeStruct { a: inner_vec_2 }); + + require(vec_in_a_vec_in_a_struct_in_a_vec == exp_vec_in_a_vec_in_a_struct_in_a_vec, "vec_in_a_vec_in_a_struct_in_a_vec err"); + } + + true +} \ No newline at end of file diff --git a/packages/predicate/src/predicate.ts b/packages/predicate/src/predicate.ts index efadf0e0d98..1b8591e136b 100644 --- a/packages/predicate/src/predicate.ts +++ b/packages/predicate/src/predicate.ts @@ -1,7 +1,13 @@ import type { BytesLike } from '@ethersproject/bytes'; import { hexlify, arrayify } from '@ethersproject/bytes'; import { Logger } from '@ethersproject/logger'; -import { AbiCoder, Interface } from '@fuel-ts/abi-coder'; +import { + AbiCoder, + Interface, + TRANSACTION_PREDICATE_COIN_FIXED_SIZE, + TRANSACTION_SCRIPT_FIXED_SIZE, + VM_TX_MEMORY, +} from '@fuel-ts/abi-coder'; import type { JsonAbiFragmentType, JsonAbi, InputValue } from '@fuel-ts/abi-coder'; import { Address } from '@fuel-ts/address'; import type { @@ -11,7 +17,7 @@ import type { TransactionResponse, } from '@fuel-ts/providers'; import { transactionRequestify } from '@fuel-ts/providers'; -import { InputType } from '@fuel-ts/transactions'; +import { ByteArrayCoder, InputType } from '@fuel-ts/transactions'; import { versions } from '@fuel-ts/versions'; import { Account } from '@fuel-ts/wallet'; @@ -73,8 +79,15 @@ export class Predicate extends Account { } setData(...args: T) { + const paddedCode = new ByteArrayCoder(this.bytes.length).encode(this.bytes); + const OFFSET = + VM_TX_MEMORY + + TRANSACTION_SCRIPT_FIXED_SIZE + + TRANSACTION_PREDICATE_COIN_FIXED_SIZE + + paddedCode.byteLength - + 17; const abiCoder = new AbiCoder(); - const encoded = abiCoder.encode(this.jsonAbi || [], args); + const encoded = abiCoder.encode(this.jsonAbi || [], args, OFFSET); this.predicateData = encoded; return this; } diff --git a/packages/program/src/script-request.ts b/packages/program/src/script-request.ts index 70333ed3bbe..22e03c99471 100644 --- a/packages/program/src/script-request.ts +++ b/packages/program/src/script-request.ts @@ -158,14 +158,16 @@ export class ScriptRequest { this.scriptResultDecoder = scriptResultDecoder; } - getScriptDataOffset() { + static getScriptDataOffsetWithBytes(bytes: Uint8Array): number { return ( - VM_TX_MEMORY + - TRANSACTION_SCRIPT_FIXED_SIZE + - new ByteArrayCoder(this.bytes.length).encodedLength + VM_TX_MEMORY + TRANSACTION_SCRIPT_FIXED_SIZE + new ByteArrayCoder(bytes.length).encodedLength ); } + getScriptDataOffset() { + return ScriptRequest.getScriptDataOffsetWithBytes(this.bytes); + } + /** * Returns the memory offset for the contract call argument * Used for struct inputs diff --git a/packages/script/src/script-invocation-scope.ts b/packages/script/src/script-invocation-scope.ts index df4f82f6757..d91158fd75e 100644 --- a/packages/script/src/script-invocation-scope.ts +++ b/packages/script/src/script-invocation-scope.ts @@ -23,9 +23,12 @@ export class ScriptInvocationScope< } private buildScriptRequest() { + const programBytes = (this.program as AbstractScript).bytes; + this.scriptRequest = new ScriptRequest( - (this.program as AbstractScript).bytes, - (args: TArgs) => this.func.encodeArguments(args), + programBytes, + (args: TArgs) => + this.func.encodeArguments(args, ScriptRequest.getScriptDataOffsetWithBytes(programBytes)), () => [] as unknown as TReturn ); }