Skip to content

Commit 4d3fb37

Browse files
Fix Vector<Option<T>> simple args: auto-wrap BCS values in MoveOption
When BCS-encoded values (e.g. AccountAddress.ONE) are passed as elements of vector<Option<T>>, they are now automatically wrapped in MoveOption instead of throwing 'Type mismatch for argument N, expected MoveOption'. The fix detects when a BCS-encoded argument is provided for an Option<T> parameter but is not already a MoveOption, and wraps it after validating it matches the Option's inner type. Also adds explicit return type annotation to checkOrConvertArgument to fix TypeScript DTS build error from the new recursive call pattern. Co-authored-by: Greg Nazario <greg@gnazar.io>
1 parent 8c785f2 commit 4d3fb37

File tree

3 files changed

+75
-15
lines changed

3 files changed

+75
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ All notable changes to the Aptos TypeScript SDK will be captured in this file. T
66

77
## Fixed
88

9+
- Fix simple function arguments for `Vector<Option<T>>` types: BCS-encoded values (e.g. `AccountAddress.ONE`) passed as elements of `vector<Option<address>>` are now automatically wrapped in `MoveOption` instead of throwing a type mismatch error
910
- Resolve moderate security advisories in `confidential-assets` dev tooling by pinning transitive `file-type` and `yauzl` (via `@swc/cli``@xhmikosr/downloader`) to patched releases
1011
- Remove hardcoded `maxGasAmount: 2000` from e2e tests (Account Derivation APIs, WebAuthn submission) that caused `MAX_GAS_UNITS_BELOW_MIN_TRANSACTION_GAS_UNITS` failures after the on-chain minimum gas increase
1112
- Add troubleshooting section to CONTRIBUTING.md for `ERR_WORKER_OUT_OF_MEMORY` build failures on low-RAM systems

src/transactions/transactionBuilder/remoteAbi.ts

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -347,23 +347,19 @@ export function checkOrConvertArgument(
347347
genericTypeParams: Array<TypeTag>,
348348
moduleAbi?: MoveModule,
349349
options?: { allowUnknownStructs?: boolean },
350-
) {
350+
): EntryFunctionArgumentTypes {
351351
// If the argument is bcs encoded, we can just use it directly
352352
if (isEncodedEntryFunctionArgument(arg)) {
353-
// Ensure the type matches the ABI
353+
// If the expected type is Option but the arg is not already a MoveOption,
354+
// wrap it after validating it matches the Option's inner type.
355+
// This handles cases like Vector<Option<address>> where elements may be
356+
// passed as AccountAddress instead of MoveOption<AccountAddress>.
357+
if (param.isStruct() && param.isOption() && !(arg instanceof MoveOption)) {
358+
return new MoveOption(
359+
checkOrConvertArgument(arg, param.value.typeArgs[0], position, genericTypeParams, moduleAbi, options),
360+
);
361+
}
354362

355-
/**
356-
* Checks the type of the provided argument against the expected type.
357-
* This function helps validate that the argument conforms to the specified type requirements.
358-
*
359-
* @param typeArgs - The expected type arguments.
360-
* @param arg - The argument to be checked.
361-
* @param position - The position of the argument in the context of the check.
362-
* @param moduleAbi - The ABI of the module containing the function, used for type checking.
363-
* This will typically have information about structs, enums, and other types.
364-
* @group Implementation
365-
* @category Transactions
366-
*/
367363
checkType(param, arg, position);
368364
return arg;
369365
}

tests/unit/remoteAbi.test.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,20 +266,83 @@ describe("Remote ABI", () => {
266266
new MoveString("Hello"),
267267
);
268268

269-
// You cannot provide `new U8(0)` without MoveOption
269+
// BCS-encoded values are auto-wrapped in MoveOption when the expected type is Option
270270
expect(
271271
checkOrConvertArgument(new MoveOption(new U8(255)), parseTypeTag("0x1::option::Option<u8>"), 0, []),
272272
).toEqual(new MoveOption(new U8(255)));
273273
expect(checkOrConvertArgument(new MoveOption<U8>(), parseTypeTag("0x1::option::Option<u8>"), 0, [])).toEqual(
274274
new MoveOption<U8>(),
275275
);
276+
expect(checkOrConvertArgument(new U8(255), parseTypeTag("0x1::option::Option<u8>"), 0, [])).toEqual(
277+
new MoveOption(new U8(255)),
278+
);
279+
expect(checkOrConvertArgument(AccountAddress.ONE, parseTypeTag("0x1::option::Option<address>"), 0, [])).toEqual(
280+
new MoveOption(AccountAddress.ONE),
281+
);
276282

277283
// Objects are account addresses, and it doesn't matter about the type used
278284
expect(
279285
checkOrConvertArgument(AccountAddress.ONE, parseTypeTag("0x1::object::Object<0x1::string::String>"), 0, []),
280286
).toEqual(AccountAddress.ONE);
281287
});
282288

289+
it("should parse vector<Option<T>> arguments", () => {
290+
// vector<Option<address>> with BCS-encoded AccountAddress elements
291+
expect(
292+
checkOrConvertArgument([AccountAddress.ONE], parseTypeTag("vector<0x1::option::Option<address>>"), 0, []),
293+
).toEqual(new MoveVector([new MoveOption(AccountAddress.ONE)]));
294+
295+
// vector<Option<address>> with string elements
296+
expect(checkOrConvertArgument(["0x1"], parseTypeTag("vector<0x1::option::Option<address>>"), 0, [])).toEqual(
297+
new MoveVector([new MoveOption(AccountAddress.ONE)]),
298+
);
299+
300+
// vector<Option<address>> with null elements (empty options)
301+
expect(checkOrConvertArgument([null], parseTypeTag("vector<0x1::option::Option<address>>"), 0, [])).toEqual(
302+
new MoveVector([new MoveOption<AccountAddress>(null)]),
303+
);
304+
305+
expect(checkOrConvertArgument([undefined], parseTypeTag("vector<0x1::option::Option<address>>"), 0, [])).toEqual(
306+
new MoveVector([new MoveOption<AccountAddress>(null)]),
307+
);
308+
309+
// vector<Option<address>> with mixed present and null values
310+
expect(
311+
checkOrConvertArgument(
312+
[AccountAddress.ONE, null, "0x2"],
313+
parseTypeTag("vector<0x1::option::Option<address>>"),
314+
0,
315+
[],
316+
),
317+
).toEqual(
318+
new MoveVector([
319+
new MoveOption(AccountAddress.ONE),
320+
new MoveOption<AccountAddress>(null),
321+
new MoveOption(AccountAddress.TWO),
322+
]),
323+
);
324+
325+
// vector<Option<u8>> with number elements
326+
expect(checkOrConvertArgument([1, 2, 3], parseTypeTag("vector<0x1::option::Option<u8>>"), 0, [])).toEqual(
327+
new MoveVector([new MoveOption(new U8(1)), new MoveOption(new U8(2)), new MoveOption(new U8(3))]),
328+
);
329+
330+
// vector<Option<u64>> with mixed values
331+
expect(checkOrConvertArgument([100n, null], parseTypeTag("vector<0x1::option::Option<u64>>"), 0, [])).toEqual(
332+
new MoveVector([new MoveOption(new U64(100n)), new MoveOption<U64>(null)]),
333+
);
334+
335+
// vector<Option<u8>> with BCS-encoded elements
336+
expect(checkOrConvertArgument([new U8(42)], parseTypeTag("vector<0x1::option::Option<u8>>"), 0, [])).toEqual(
337+
new MoveVector([new MoveOption(new U8(42))]),
338+
);
339+
340+
// vector<Option<u8>> with already-wrapped MoveOption elements
341+
expect(
342+
checkOrConvertArgument([new MoveOption(new U8(42))], parseTypeTag("vector<0x1::option::Option<u8>>"), 0, []),
343+
).toEqual(new MoveVector([new MoveOption(new U8(42))]));
344+
});
345+
283346
it("should allow mixed arguments", () => {
284347
expect(checkOrConvertArgument([AccountAddress.ONE, "0x2"], parseTypeTag("vector<address>"), 0, [])).toEqual(
285348
new MoveVector<AccountAddress>([AccountAddress.ONE, AccountAddress.TWO]),

0 commit comments

Comments
 (0)