diff --git a/docs/generated/changelog.html b/docs/generated/changelog.html
index c50a75442..f9a9e99e4 100644
--- a/docs/generated/changelog.html
+++ b/docs/generated/changelog.html
@@ -39,6 +39,10 @@
Version 0.13.3
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
diff --git a/packages/candid/src/idl.test.ts b/packages/candid/src/idl.test.ts
index c25a55354..7ab98b6b0 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 wire type contains additional fields', () => {
+ const InputType = IDL.Record({
+ a: IDL.Text,
+ b: IDL.Opt(IDL.Text),
+ });
+ const OutputType = IDL.Record({
+ b: IDL.Opt(IDL.Text),
+ });
+
+ const encoded = IDL.encode([InputType], [{ a: 'abc', b: ['123'] }]);
+ const decoded = IDL.decode([OutputType], encoded)[0];
+
+ expect(decoded).toEqual({
+ b: ['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