Skip to content

AssetFactory.create sends malformed calldata: deploy routine args are not spread when encoded #130

Description

@sarvalabs-adithya

AssetFactory.create sends malformed calldata: deploy routine args are not spread when encoded

Summary

AssetFactory.create accepts deploy-routine arguments as rest params (...calldata), but when it hands them off to ManifestCoder.encodeArguments, it passes the entire calldata array in a single argument slot instead of spreading. ManifestCoder.encodeArguments(routine, ...args) then reads args[field.slot] per declared field — slot 0 gets the whole array, every other slot gets undefined. The resulting calldata is malformed: arg 0 gets serialized as garbage and the remaining args are silently truncated.

Every MASX deploy whose Init routine accepts at least one argument reverts on-chain with:

builtin.CallFailure: invalid inputs: malformed data for '<first_arg>': data does not decode to an identifier: incompatible wire: mismatched data length for byte array

(<first_arg> is whatever the first declared field is — typically treasury_addr, owner, etc.) This affects the canonical AssetFactory.create example in the docs verbatim.

Version

Reproduced on every published 0.7.0 release candidate: rc9, rc10, rc11, rc13, rc14, rc15. The line has not changed across these.

Location

Sourcejs-moi-asset/src.ts/asset-factory.ts (around line 55):

payload.logic_payload.calldata = manifestCoder.encodeArguments(callsite, calldata) as Hex;  // ← missing spread

Build outputjs-moi-asset/lib.cjs/asset-factory.js:40 and lib.esm/asset-factory.js:37:

if (argsLen > 0) {
    const manifestCoder = new ManifestCoder(manifest);
    payload.logic_payload.calldata = manifestCoder.encodeArguments(callsite, calldata);  // ← missing spread
}

For contrast, AssetDriver (used by MASX invokes) gets this right:

// js-moi-asset/lib.cjs/asset-driver.js
payload.calldata = this.manifestCoder.encodeArguments(ixObject.routine.name, ...ixObject.arguments);

— which is why MASX invokes work and only MASX deploys with args are broken.

Root cause

ManifestCoder.encodeArguments uses rest params:

encodeArguments(routine, ...args) {
    const element = this.elementDescriptor.getRoutineElement(routine).data;
    const schema = this.schema.parseFields(element.accepts ?? []);
    const calldata = Object.values(element.accepts).reduce((acc, field) => {
        acc[field.label] = this.parseCalldata(schema.fields[field.label], args[field.slot]);
        return acc;
    }, {});
    ...
}

When AssetFactory.create calls encodeArguments(callsite, calldata) with the whole array, args becomes [calldata]. So args[0] is the entire [arg1, arg2, ...] array, and args[1] is undefined. The schema then tries to encode that array as a bytes (or whatever the first declared type is) field and produces a malformed blob; the runtime can't decode it back into the declared type.

Reproduction

A minimal cocolang asset whose Init takes any argument is enough. Using a (Identifier, U64) Init:

import 'dotenv/config'
import { VoyageProvider, Wallet, AssetFactory } from 'js-moi-sdk'
import manifest from './taxtoken.json' with { type: 'json' }

const provider = new VoyageProvider('devnet')
const wallet = await Wallet.fromMnemonic(process.env.MOI_MNEMONIC, "m/44'/6174'/7020'/0/0")
wallet.connect(provider)

const identifier = await wallet.getIdentifier()

const ctx = AssetFactory.create(
  wallet, 'TAXED', 1_000_000, identifier.toString(), true, manifest, 'Init',
  identifier.toBytes(), 500,
)
const response = await ctx.send()
const receipt = await response.wait()

console.log(receipt.ix_operations[0].data.error)
// → "builtin.CallFailure invalid inputs: malformed data for 'treasury_addr':
//    data does not decode to an identifier: incompatible wire:
//    mismatched data length for byte array"

Fix

Add the spread on src.ts/asset-factory.ts:55 (and matching build outputs):

payload.logic_payload.calldata = manifestCoder.encodeArguments(callsite, ...calldata) as Hex;

That is the entire patch — three characters. Workaround for downstream users until release: patch-package the spread into lib.cjs/asset-factory.js and lib.esm/asset-factory.js.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions