diff --git a/src/impl/encoded-types/encoded-types.ts b/src/impl/encoded-types/encoded-types.ts index 6691789..90b680a 100644 --- a/src/impl/encoded-types/encoded-types.ts +++ b/src/impl/encoded-types/encoded-types.ts @@ -1314,7 +1314,21 @@ export const getArc4Encoded = (value: DeliberateAny, sourceTypeInfoString?: stri name: `Struct<${value.constructor.name}>`, genericArgs: Object.fromEntries(Object.keys(value).map((x, i) => [x, genericArgs[i]])), } - return new Struct(typeInfo, Object.fromEntries(Object.keys(value).map((x, i) => [x, result[i]]))) + let s = Object.fromEntries(Object.keys(typeInfo.genericArgs).map((x, i) => [x, result[i]])) + if (propTypeInfos) { + // if source type info is provided, reorder the struct properties to match the expected type schema + s = Object.fromEntries(Object.keys(propTypeInfos).map((x) => [x, s[x]])) + typeInfo.genericArgs = propTypeInfos + } else { + // if not, reorder the struct properties alphabetically + typeInfo.genericArgs = Object.fromEntries( + Object.keys(typeInfo.genericArgs) + .sort() + .map((key) => [key, typeInfo.genericArgs[key]]), + ) + s = Object.fromEntries(Object.keys(typeInfo.genericArgs).map((key) => [key, s[key]])) + } + return new Struct(typeInfo, s) } throw new CodeError(`Unsupported type for encoding: ${typeof value}`) diff --git a/tests/arc4/encode-decode-arc4.algo.spec.ts b/tests/arc4/encode-decode-arc4.algo.spec.ts index c4971f9..9ee3919 100644 --- a/tests/arc4/encode-decode-arc4.algo.spec.ts +++ b/tests/arc4/encode-decode-arc4.algo.spec.ts @@ -89,7 +89,7 @@ const testData = [ new Tuple<[Uint<512>, DynamicBytes, Swapped1]>( abiUint512, abiBytes, - new Swapped1({ b: abiUint64, c: abiBool, d: abiString, a: new Tuple<[Uint<64>, Bool, Bool]>(abiUint64, abiBool, abiBool) }), + new Swapped1({ a: new Tuple<[Uint<64>, Bool, Bool]>(abiUint64, abiBool, abiBool), b: abiUint64, c: abiBool, d: abiString }), ), ] as readonly [Tuple<[Bool, Tuple<[Str, Bool]>]>, Tuple<[Uint<64>, Uint<64>]>, Tuple<[Uint<512>, DynamicBytes, Swapped1]>] }, @@ -122,7 +122,7 @@ const testData = [ }, { nativeValues() { - return { b: nativeNumber, c: nativeBool, d: nativeString, a: [nativeNumber, nativeBool, nativeBool] } as { + return { a: [nativeNumber, nativeBool, nativeBool], b: nativeNumber, c: nativeBool, d: nativeString } as { b: uint64 c: boolean d: string @@ -130,7 +130,7 @@ const testData = [ } }, abiValues() { - return { b: abiUint64, c: abiBool, d: abiString, a: new Tuple<[Uint<64>, Bool, Bool]>(abiUint64, abiBool, abiBool) } + return { a: new Tuple<[Uint<64>, Bool, Bool]>(abiUint64, abiBool, abiBool), b: abiUint64, c: abiBool, d: abiString } }, arc4Value() { return new Swapped1(this.abiValues()) @@ -167,7 +167,7 @@ describe('decodeArc4', () => { ...encodingUtil.utf8ToUint8Array('hello world'), ]), ) - const e = { a: new arc4.Uint64(50n), b: new DynamicBytes(asBytes(new Uint8Array([1, 2, 3, 4, 5]))) } + const e = { b: new DynamicBytes(asBytes(new Uint8Array([1, 2, 3, 4, 5]))), a: new arc4.Uint64(50n) } const eBytes = asBytes(new Uint8Array([...encodingUtil.bigIntToUint8Array(50n, 8), 0, 10, 0, 5, 1, 2, 3, 4, 5])) const f = new Address(Bytes.fromHex(`${'00'.repeat(31)}ff`)) const fBytes = Bytes.fromHex(`${'00'.repeat(31)}ff`) diff --git a/tests/native-mutable-object.algo.spec.ts b/tests/native-mutable-object.algo.spec.ts index 3b0228a..0418c02 100644 --- a/tests/native-mutable-object.algo.spec.ts +++ b/tests/native-mutable-object.algo.spec.ts @@ -589,7 +589,7 @@ describe('native mutable object', () => { c: arc4.Str d: arc4.DynamicBytes }> {} - const obj: SimpleObj = { a: 1, b: true, c: 'hello', d: Bytes('world') } + const obj: SimpleObj = { a: 1, c: 'hello', b: true, d: Bytes('world') } const encoded = encodeArc4(obj) const interpreted = convertBytes(encoded, { strategy: 'unsafe-cast' }) const decoded = decodeArc4(encoded) @@ -789,4 +789,14 @@ describe('native mutable object', () => { assertMatch(decoded, obj) }) }) + + describe('clone', () => { + it('should work when property order is different', () => { + const obj1: NestedObj = { a: 1, b: true, c: 'hello', d: { x: 10, y: 'world', z: true } } + const obj2: NestedObj = { d: { x: 10, z: true, y: 'world' }, a: 1, c: 'hello', b: true } + + expect(clone(obj2)).toEqual(obj2) + expect(clone(obj1)).toEqual(obj1) + }) + }) }) diff --git a/tests/references/box.algo.spec.ts b/tests/references/box.algo.spec.ts index b79e480..3f4b87e 100644 --- a/tests/references/box.algo.spec.ts +++ b/tests/references/box.algo.spec.ts @@ -131,7 +131,7 @@ describe('Box', () => { }, }, { - value: { a: 'hello', b: Bytes('world'), c: true }, + value: { a: 'hello', c: true, b: Bytes('world') }, newValue: { a: 'world', b: Bytes('hello'), c: false }, emptyValue: {}, withBoxContext: (test: (boxMap: Box) => void) => { @@ -143,7 +143,7 @@ describe('Box', () => { }, { value: { a: 'hello', b: Bytes('world'), c: true }, - newValue: { a: 'world', b: Bytes('hello'), c: false }, + newValue: { a: 'world', c: false, b: Bytes('hello') }, emptyValue: {}, withBoxContext: (test: (boxMap: Box>) => void) => { ctx.txn.createScope([ctx.any.txn.applicationCall()]).execute(() => {