diff --git a/yarn-project/aztec/src/deploy/deploy.test.ts b/yarn-project/aztec/src/deploy/deploy.test.ts index 8dfa8096e5d5..664d91eab925 100644 --- a/yarn-project/aztec/src/deploy/deploy.test.ts +++ b/yarn-project/aztec/src/deploy/deploy.test.ts @@ -66,6 +66,23 @@ describe('runDeployment', () => { }, } satisfies ActionStep; + // Five more same-account actions: together they exceed one batch, exercising the fee-call + // slot reservation in action chunking. They mint to `operator` (declared below), never to + // admin — defToken's deferred name reads the admin balance, and it must not depend on where + // these land relative to its resolution. + const extraMints = Object.fromEntries( + Array.from({ length: 5 }, (_, i) => [ + `mintExtra${i}`, + { + kind: 'action', + from: r => r.account('admin'), + dependsOn: ['token'], + call: ctx => ctx.instance('token').methods.mint_to_public(operator, 1n), + done: async ctx => !(await ctx.ran('token')), + } satisfies ActionStep, + ]), + ); + // Deferred: its name reads runtime state (the token supply `mint` creates), so the address can // only resolve once `mint` has run. On a re-run the state is already there, so the framework // resolves it at inventory time and discovers it published — nothing to send. @@ -145,7 +162,7 @@ describe('runDeployment', () => { accounts: { admin: { secret, salt } }, // Admin is genesis-funded well above this threshold ⇒ "funded" ⇒ pays from balance (no bridge). fees: { kind: 'fee-juice', threshold: 1n, fundAmount: 0n } as const, - steps: { ...contracts, mint, defToken, mintDef, fundOperator }, + steps: { ...contracts, mint, ...extraMints, defToken, mintDef, fundOperator }, output: capture, }; diff --git a/yarn-project/aztec/src/deploy/runner.ts b/yarn-project/aztec/src/deploy/runner.ts index d132f86043a6..bb9172cadcc5 100644 --- a/yarn-project/aztec/src/deploy/runner.ts +++ b/yarn-project/aztec/src/deploy/runner.ts @@ -14,7 +14,8 @@ * AT EXECUTION TIME, once their `dependsOn` has run. * - Steps execute in topological layers over the single graph, so an action can precede a contract * it sets up. Within a layer, contract publishes are individual txs and same-account actions batch - * into ≤{@link APP_MAX_CALLS}-call BatchCalls. The one-time fee-juice claim per account is + * into BatchCalls of {@link APP_MAX_CALLS} - 1 (the entrypoint's limit counts the fee payment + * call, so a full batch leaves no room for it). The one-time fee-juice claim per account is * consumed + mined by that account's first tx before the rest fan out. * - Fund steps provision arbitrary addresses (contracts or accounts that never send) with bridged * Fee Juice: bridge at execution time, then an L2 claim tx from the step's `from` account. Their @@ -837,7 +838,11 @@ class DeploymentRun { return units; } - /** A layer's actions, batching independent same-account actions into ≤{@link APP_MAX_CALLS}-call BatchCalls. */ + /** + * A layer's actions, batching independent same-account actions into BatchCalls of + * {@link APP_MAX_CALLS} - 1 — the entrypoint's call limit counts the fee payment call, so a full + * batch leaves no room for it. + */ private actionUnits(layer: string[]): ExecutionUnit[] { const actionsByAccount = new Map(); for (const alias of layer.filter(a => this.actionSteps.has(a))) { @@ -851,7 +856,7 @@ class DeploymentRun { } const units: ExecutionUnit[] = []; for (const { account, aliases } of actionsByAccount.values()) { - for (const batch of chunk(aliases, APP_MAX_CALLS)) { + for (const batch of chunk(aliases, APP_MAX_CALLS - 1)) { units.push({ label: batch.length === 1 ? `action ${batch[0]}` : `batch [${batch.join(', ')}]`, kind: 'action',