From b78be8f5f19d050e2b3b003da6a7c949226df8d1 Mon Sep 17 00:00:00 2001 From: Hamish Peebles Date: Wed, 7 Sep 2022 23:43:07 +0100 Subject: [PATCH 1/6] fix: optional fields not populated if wire type has additional fields --- packages/candid/src/idl.test.ts | 17 +++++++++++++++++ packages/candid/src/idl.ts | 25 +++++++++++++------------ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/packages/candid/src/idl.test.ts b/packages/candid/src/idl.test.ts index c25a55354..330d761e7 100644 --- a/packages/candid/src/idl.test.ts +++ b/packages/candid/src/idl.test.ts @@ -660,3 +660,20 @@ test('should correctly decode expected optional fields with lower hash than requ upgrade: [true], }); }); + +test('should decode matching optional fields if input type contains more fields than output type', () => { + const InputType = IDL.Record({ + latest_message: IDL.Opt(IDL.Text), + name: IDL.Text, + }); + const OutputType = IDL.Record({ + latest_message: IDL.Opt(IDL.Text), + }); + + const encoded = IDL.encode([InputType], [{ name: 'abc', latest_message: ['123'] }]); + const decoded = IDL.decode([OutputType], encoded)[0]; + + expect(decoded).toEqual({ + latest_message: ['123'], + }); +}); diff --git a/packages/candid/src/idl.ts b/packages/candid/src/idl.ts index 08766f828..c2de9b8b9 100644 --- a/packages/candid/src/idl.ts +++ b/packages/candid/src/idl.ts @@ -991,25 +991,26 @@ export class RecordClass extends ConstructType> { } const [expectKey, expectType] = this._fields[expectedRecordIdx]; - if (idlLabelToId(this._fields[expectedRecordIdx][0]) !== idlLabelToId(hash)) { - // the current field on the wire does not match the expected field - - // skip expected optional fields that are not present on the wire + const expectedId = idlLabelToId(this._fields[expectedRecordIdx][0]); + const actualId = idlLabelToId(hash); + if (expectedId === actualId) { + // the current field on the wire matches the expected field + x[expectKey] = expectType.decodeValue(b, type); + expectedRecordIdx++; + actualRecordIdx++; + } else if (actualId > expectedId) { + // The expected field does not exist on the wire if (expectType instanceof OptClass || expectType instanceof ReservedClass) { x[expectKey] = []; expectedRecordIdx++; - continue; + } else { + throw new Error('Cannot find required field ' + expectKey); } - - // skip unexpected interspersed fields present on the wire + } else { + // The field on the wire does not exist in the output type, so we can skip it type.decodeValue(b, type); actualRecordIdx++; - continue; } - - x[expectKey] = expectType.decodeValue(b, type); - expectedRecordIdx++; - actualRecordIdx++; } // initialize left over expected optional fields From e4e0dbfc723b65c0f04f0d92ae884918448c48f6 Mon Sep 17 00:00:00 2001 From: Hamish Peebles Date: Thu, 8 Sep 2022 00:19:50 +0100 Subject: [PATCH 2/6] update changelog --- docs/generated/changelog.html | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/generated/changelog.html b/docs/generated/changelog.html index 0be4a7707..a1d9b115e 100644 --- a/docs/generated/changelog.html +++ b/docs/generated/changelog.html @@ -18,8 +18,14 @@

Version 0.13.3

the _key non-nullable. This fixes a regression with async window.open behavior in Safari
  • - HttpAgent now offers a method to sync time with the replica, provided a specific canister. - This can be used to set proper Expiry times when a device has fallen out of sync with the replica. + HttpAgent now offers a method to sync time with the replica, provided a specific canister. + This can be used to set proper Expiry times when a device has fallen out of sync with the + replica. +
  • +
  • + Fixes a candid bug where when decoding, optional fields could be skipped if the data on + the wire contains additional fields. +
  • Version 0.13.2

      From 55a79b2324bda23f92015188063c54074a5cf72a Mon Sep 17 00:00:00 2001 From: Hamish Peebles Date: Thu, 8 Sep 2022 00:24:16 +0100 Subject: [PATCH 3/6] Undo automatic formatting changes --- docs/generated/changelog.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/generated/changelog.html b/docs/generated/changelog.html index a1d9b115e..ed692a001 100644 --- a/docs/generated/changelog.html +++ b/docs/generated/changelog.html @@ -18,9 +18,8 @@

      Version 0.13.3

      the _key non-nullable. This fixes a regression with async window.open behavior in Safari
    • - HttpAgent now offers a method to sync time with the replica, provided a specific canister. - This can be used to set proper Expiry times when a device has fallen out of sync with the - replica. + HttpAgent now offers a method to sync time with the replica, provided a specific canister. + This can be used to set proper Expiry times when a device has fallen out of sync with the replica.
    • Fixes a candid bug where when decoding, optional fields could be skipped if the data on From 3ceb8ad0cbe6c63ce55f6a7fbb9e84bcee12c4f7 Mon Sep 17 00:00:00 2001 From: Hamish Peebles Date: Thu, 8 Sep 2022 00:26:20 +0100 Subject: [PATCH 4/6] improve test description --- packages/candid/src/idl.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/candid/src/idl.test.ts b/packages/candid/src/idl.test.ts index 330d761e7..fa9a2aeea 100644 --- a/packages/candid/src/idl.test.ts +++ b/packages/candid/src/idl.test.ts @@ -180,7 +180,9 @@ test('IDL encoding (arraybuffer)', () => { IDL.encode([IDL.Vec(IDL.Nat8)], [new Uint8Array()]); IDL.encode([IDL.Vec(IDL.Nat8)], [new Uint8Array(100).fill(42)]); IDL.encode([IDL.Vec(IDL.Nat16)], [new Uint16Array(200).fill(42)]); - expect(() => IDL.encode([IDL.Vec(IDL.Int8)], [new Uint16Array(10).fill(420)])).toThrow(/Invalid vec int8 argument/); + expect(() => IDL.encode([IDL.Vec(IDL.Int8)], [new Uint16Array(10).fill(420)])).toThrow( + /Invalid vec int8 argument/, + ); }); test('IDL encoding (array)', () => { @@ -661,7 +663,7 @@ test('should correctly decode expected optional fields with lower hash than requ }); }); -test('should decode matching optional fields if input type contains more fields than output type', () => { +test('should decode matching optional fields if wire type contains additional fields', () => { const InputType = IDL.Record({ latest_message: IDL.Opt(IDL.Text), name: IDL.Text, From bb5fbd2cd29a98d639958c4aaa3fd0a50f1b78ee Mon Sep 17 00:00:00 2001 From: Hamish Peebles Date: Thu, 8 Sep 2022 00:27:09 +0100 Subject: [PATCH 5/6] arghh undo auto formatting again! --- packages/candid/src/idl.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/candid/src/idl.test.ts b/packages/candid/src/idl.test.ts index fa9a2aeea..73833b3ee 100644 --- a/packages/candid/src/idl.test.ts +++ b/packages/candid/src/idl.test.ts @@ -180,9 +180,7 @@ test('IDL encoding (arraybuffer)', () => { IDL.encode([IDL.Vec(IDL.Nat8)], [new Uint8Array()]); IDL.encode([IDL.Vec(IDL.Nat8)], [new Uint8Array(100).fill(42)]); IDL.encode([IDL.Vec(IDL.Nat16)], [new Uint16Array(200).fill(42)]); - expect(() => IDL.encode([IDL.Vec(IDL.Int8)], [new Uint16Array(10).fill(420)])).toThrow( - /Invalid vec int8 argument/, - ); + expect(() => IDL.encode([IDL.Vec(IDL.Int8)], [new Uint16Array(10).fill(420)])).toThrow(/Invalid vec int8 argument/); }); test('IDL encoding (array)', () => { From 4516ae2bf77d18c7089203f022b85a495cfaa52d Mon Sep 17 00:00:00 2001 From: Hamish Peebles Date: Thu, 8 Sep 2022 08:57:53 +0100 Subject: [PATCH 6/6] Simplify field names in test --- packages/candid/src/idl.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/candid/src/idl.test.ts b/packages/candid/src/idl.test.ts index 73833b3ee..7ab98b6b0 100644 --- a/packages/candid/src/idl.test.ts +++ b/packages/candid/src/idl.test.ts @@ -663,17 +663,17 @@ test('should correctly decode expected optional fields with lower hash than requ test('should decode matching optional fields if wire type contains additional fields', () => { const InputType = IDL.Record({ - latest_message: IDL.Opt(IDL.Text), - name: IDL.Text, + a: IDL.Text, + b: IDL.Opt(IDL.Text), }); const OutputType = IDL.Record({ - latest_message: IDL.Opt(IDL.Text), + b: IDL.Opt(IDL.Text), }); - const encoded = IDL.encode([InputType], [{ name: 'abc', latest_message: ['123'] }]); + const encoded = IDL.encode([InputType], [{ a: 'abc', b: ['123'] }]); const decoded = IDL.decode([OutputType], encoded)[0]; expect(decoded).toEqual({ - latest_message: ['123'], + b: ['123'], }); });