Summary
ContractFunctionInteraction.simulate() returns a result typed any:
export type SimulationResult = {
/** Return value of the function */
result: any;
...
} & OffchainOutput;
so every read is a hand-written cast:
const balance = (
await token.methods.balance_of(owner).simulate({ from })
).result as bigint;
const { storage, len } = (
await registry.methods.entries().simulate({ from: NO_FROM })
).result as { storage: AztecAddress[]; len: bigint };
The return type is in the artifact — FunctionAbi.returnTypes — and aztec codegen already reads
it. It simply isn't carried into the TypeScript types.
The asymmetry
Codegen already maps Noir ABI types to TypeScript for parameters:
// generated
burn: ((from: AztecAddressLike, amount: (bigint | number), _nonce: FieldLike)
=> ContractFunctionInteraction) & Pick<ContractMethod, 'selector'>;
AztecAddressLike, FieldLike, bigint | number — the mapping exists and is applied. The same
mapping is not applied to the return value, so the typed wrapper hands back an untyped result.
Why any specifically is a problem
any does not merely fail to help, it actively removes checking:
- A wrong cast is not caught.
as bigint on a function returning a struct compiles, and fails
later as a runtime type error far from the call.
- A missing cast is not caught either.
const x = (await ...).simulate()).result; type-checks
and silently infects everything downstream with any.
- Renaming or re-typing a Noir return value cannot break the build. The compiler has nothing to
compare against, so drift surfaces at runtime.
unknown would at least force the cast to be deliberate, but the artifact makes the real type
available, so neither is necessary.
Request
Make the interaction generic in its return type, so the generated wrapper can supply it:
token.methods.balance_of(owner) // ContractFunctionInteraction<bigint>
.simulate({ from }) // Promise<SimulationResult<bigint>>
.then(r => r.result); // bigint — no cast
with SimulationResult<T> = { result: T; ... } and codegen deriving T from returnTypes using
the mapping it already applies to parameters. Functions returning nothing become
ContractFunctionInteraction<void>.
Backwards compatibility can default the parameter to the current behaviour
(ContractFunctionInteraction<T = any>), so untyped construction paths keep working while
generated wrappers become precise.
Who benefits
Every consumer of a generated contract wrapper. In one mid-sized codebase we have 67 simulate
call sites, 48 of which carry a hand-written cast — each one a place where the ABI is restated by
hand and nothing checks it.
Environment
aztec 5.0.1.
Summary
ContractFunctionInteraction.simulate()returns a result typedany:so every read is a hand-written cast:
The return type is in the artifact —
FunctionAbi.returnTypes— andaztec codegenalready readsit. It simply isn't carried into the TypeScript types.
The asymmetry
Codegen already maps Noir ABI types to TypeScript for parameters:
AztecAddressLike,FieldLike,bigint | number— the mapping exists and is applied. The samemapping is not applied to the return value, so the typed wrapper hands back an untyped result.
Why
anyspecifically is a problemanydoes not merely fail to help, it actively removes checking:as biginton a function returning a struct compiles, and failslater as a runtime type error far from the call.
const x = (await ...).simulate()).result;type-checksand silently infects everything downstream with
any.compare against, so drift surfaces at runtime.
unknownwould at least force the cast to be deliberate, but the artifact makes the real typeavailable, so neither is necessary.
Request
Make the interaction generic in its return type, so the generated wrapper can supply it:
with
SimulationResult<T> = { result: T; ... }and codegen derivingTfromreturnTypesusingthe mapping it already applies to parameters. Functions returning nothing become
ContractFunctionInteraction<void>.Backwards compatibility can default the parameter to the current behaviour
(
ContractFunctionInteraction<T = any>), so untyped construction paths keep working whilegenerated wrappers become precise.
Who benefits
Every consumer of a generated contract wrapper. In one mid-sized codebase we have 67
simulatecall sites, 48 of which carry a hand-written cast — each one a place where the ABI is restated by
hand and nothing checks it.
Environment
aztec 5.0.1.