Skip to content

fix: better DeployMethod#22985

Merged
Thunkar merged 10 commits intomerge-train/fairiesfrom
gj/deploy_method_improvements
May 7, 2026
Merged

fix: better DeployMethod#22985
Thunkar merged 10 commits intomerge-train/fairiesfrom
gj/deploy_method_improvements

Conversation

@Thunkar
Copy link
Copy Markdown
Contributor

@Thunkar Thunkar commented May 6, 2026

Refactor to avoid multiple footguns with DeployMethod. The idea is that all address-affecting parameters have to be "locked in" in the constructor, so any subsequent invocation yields a deterministic address. Any other path throws and exception.

The "convenient" flow of just saying await Contract.deploy(wallet, ...args).send({ from: acc }) is still supported, but generating an incompatible DeployMethod instance (for example, using a different deployer) is not allowed anymore. Furthermore, it is not possible to set the salt on send to avoid the same issue, and the object has to be regenerated. Read migration notes for more details

@Thunkar Thunkar self-assigned this May 6, 2026
@Thunkar Thunkar added ci-no-fail-fast Sets NO_FAIL_FAST in the CI so the run is not aborted on the first failure ci-draft Run CI on draft PRs. labels May 6, 2026
@Thunkar Thunkar added the ci-full Run all master checks. label May 6, 2026
@Thunkar Thunkar marked this pull request as ready for review May 6, 2026 13:21
@Thunkar Thunkar requested a review from a team as a code owner May 6, 2026 13:21
@Thunkar Thunkar added backport-to-v4-next and removed ci-full Run all master checks. ci-draft Run CI on draft PRs. labels May 6, 2026
@Thunkar Thunkar requested review from mverzilli and nchamo May 6, 2026 13:34
Copy link
Copy Markdown
Contributor

@nchamo nchamo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a really good change, but I feel what it would be a good opportunity to use obj params for constructor args. Feels off to have different versions do different things

Would also like @mverzilli 's opinion on it

- await cd.deploy(...ctorArgs).send({ from: alice, contractAddressSalt: salt });
+ const cd = new ContractDeployer(artifact, wallet);
+ await cd.deploy({ salt }, ...ctorArgs).send({ from: alice });
```
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels weird that in other cases we are doing params at the end:

const deploy = MyContract.deploy(wallet, ...args, { publicKeys });

But here, we are doing params at the start

await cd.deploy({ salt }, ...ctorArgs).send({ from: alice });

I know they are different things, but it felt weird when I saw this:

const { receipt: txReceipt } = await deployer
        .deploy({ salt: new Fr(BigInt(1)) }, ownerAddress, 1)
        .send({ from: ownerAddress });

Maybe could add the constructor args as part of the object?

const { receipt: txReceipt } = await deployer
        .deploy({ ctorArgs: [ownerAddress, 1], salt: new Fr(BigInt(1)) })
        .send({ from: ownerAddress });

Update:
Now, that I think about it, I think this could also look clearer if we used object args:

const deployMethod = StatefulTestContract.deploy(wallet, defaultAccountAddress, 0, {
        salt: Fr.random(),
        deployer: defaultAccountAddress,
      });

vs

const deployMethod = StatefulTestContract.deploy({
  wallet, 
  ctorArgs: [defaultAccountAddress, 0],
  salt: Fr.random(),
  deployer: defaultAccountAddress,
});

I do love object params 😅 But I really feel like it helps understand what each param actually is in this case

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH I didn't pay much attention to the ContractDeployer since it's on its way out, unified so now looks like:

const deployMethod = StatefulTestContract.deploy(wallet, defaultAccountAddress, 0, {
        salt: Fr.random(),
        deployer: defaultAccountAddress,
      });

const { receipt: txReceipt } = await deployer
        .deploy([ownerAddress, 1], { salt: new Fr(BigInt(1)) })
        .send({ from: ownerAddress });

I quite like keeping the args separate because the options object is often omitted. But it's true the order should be preserved, done!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, and was about to make the same observation, but I guess here the intention is to reduce api breakage (you can always add params and things more or less keep working).

@Thunkar Thunkar requested a review from LeilaWang as a code owner May 7, 2026 07:24
@Thunkar Thunkar removed the request for review from LeilaWang May 7, 2026 07:27
Universal deploys can be sent by any account, since the universal address does not depend on `from`:

```typescript
const deploy = MyContract.deploy(wallet, ...args, { universalDeploy: true });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unpacking deploy from send makes me realize having deploy be a verb is a bit misleading, maybe deployment would be more apt (a bit offtopic maybe)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's actually a great idea, but I'd do it on a different PR (or even version, this change is bigger, but the other can just be a massive search and replace)

@Thunkar Thunkar requested a review from nventuro as a code owner May 7, 2026 07:57
@Thunkar Thunkar removed the request for review from nventuro May 7, 2026 07:58

```typescript
const deploy = MyContract.deploy(wallet, ...args, { deployer: alice });
await deploy.send({ from: bob }); // throws: deployer is locked to alice
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe not practical, feel free to dismiss: if we set a deployer we can make deploy's type different, such that this line becomes a compile time error.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mmm, but how would we detect deployer: alice and from: alice? Is that even possible using types? It's the one case we want to allow (to generate deterministic addresses, but send the tx at a later time)

Comment on lines 184 to +207
constructor(
protected publicKeys: PublicKeys,
wallet: Wallet,
protected artifact: ContractArtifact,
protected postDeployCtor: (instance: ContractInstanceWithAddress, wallet: Wallet) => TContract,
protected args: any[] = [],
constructorNameOrArtifact?: string | FunctionArtifact,
instantiation: DeployInstantiationOptions = {},
authWitnesses: AuthWitness[] = [],
capsules: Capsule[] = [],
protected extraHashedArgs: HashedValues[] = [],
) {
super(wallet, authWitnesses, capsules);
this.constructorArtifact = getInitializer(artifact, constructorNameOrArtifact);
this.salt = instantiation.salt ?? Fr.random();
this.publicKeys = instantiation.publicKeys ?? PublicKeys.default();
if (instantiation.deployer !== undefined && instantiation.universalDeploy) {
throw new Error('DeployInstantiationOptions: `deployer` and `universalDeploy` are mutually exclusive.');
}
if (instantiation.universalDeploy) {
this.deployer = AztecAddress.ZERO;
} else if (instantiation.deployer !== undefined) {
this.deployer = instantiation.deployer;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the constructor is getting logically dense in particular as regards deployers. may be a future enhancement idea could be to provide some factory/builder methods?

* @returns The execution payload for this operation
*/
public async request(options: RequestDeployOptions = {}): Promise<ExecutionPayload> {
if (this.deployer === undefined) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is starting to look a lot like it wants to be a DeployerLockedDeployMethod subclass

Comment on lines +569 to +577
protected cloneInstantiation(): DeployInstantiationOptions {
if (this.deployer === undefined) {
return { salt: this.salt, publicKeys: this.publicKeys };
}
if (this.deployer.equals(AztecAddress.ZERO)) {
return { salt: this.salt, publicKeys: this.publicKeys, universalDeploy: true };
}
return { salt: this.salt, publicKeys: this.publicKeys, deployer: this.deployer };
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another symptom that maybe we want to refactor into three different types: UnlockedDeployMethod, SpecificDeployerDeployMethod, UniversalDeployerDeployMethod (names suck ack)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exploring this for a next PR, I like the idea!

Copy link
Copy Markdown
Contributor

@mverzilli mverzilli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I do think DeployMethod could use some refactoring. Not sure what the best way is as I don't have a feel for the code. Maybe it could be split into specific classes to handle different deployer cases, or maybe we keep a single DeployMethod type but it holds a new Deployer union type or class.

@Thunkar Thunkar merged commit 01a6f54 into merge-train/fairies May 7, 2026
14 checks passed
@Thunkar Thunkar deleted the gj/deploy_method_improvements branch May 7, 2026 09:47
@AztecBot
Copy link
Copy Markdown
Collaborator

AztecBot commented May 7, 2026

❌ Failed to cherry-pick to v4-next due to conflicts. (🤖) View backport run.

AztecBot added a commit that referenced this pull request May 7, 2026
After rebasing onto backport-to-v4-next-staging (which now includes the
backport of #22889 'feat(txe): add tx private logs to tx side effects
oracle'), only three files still need cross-PR-drift handling:

- docs/docs-developers/docs/resources/migration_notes.md: keep just the
  two new TBD entries from #22968 (TXE `call_public_incognito` no
  longer takes `from`; `view_public_incognito` deprecated). The
  DeployMethod and aztec-up bundled-binary entries that the cherry-pick
  brought along belong to other PRs not yet backported (e.g. #22985).

- yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts:
  keep the v4-next-staging-specific tree-height imports
  (L1_TO_L2_MSG_TREE_HEIGHT, NOTE_HASH_TREE_HEIGHT,
  PUBLIC_DATA_TREE_HEIGHT) which are still in use here — #21577
  removed them on next but hasn't been backported. Drop only
  NULL_MSG_SENDER_CONTRACT_ADDRESS (replaced by AztecAddress.NULL_MSG_SENDER).

- yarn-project/stdlib/src/aztec-address/index.ts: keep both the existing
  eslint-disable comment and the new NULL_MSG_SENDER_CONTRACT_ADDRESS
  import.

- noir-projects/aztec-nr/aztec/src/test/helpers/txe_oracles.nr: with
  #22889 backported, the imports now match next exactly — just drop
  NULL_MSG_SENDER_CONTRACT_ADDRESS.
nchamo added a commit that referenced this pull request May 7, 2026
…2968) to v4-next (#23057)

Backport of #22968 ("feat: allow setting additional scopes in nr tests")
to `backport-to-v4-next-staging`.

Rebased on top of latest staging (which now includes the backport of
#22889 — `feat(txe): add tx private logs to tx side effects oracle`).
All conflicts handled in a single resolution commit.

## What

The PR adds `call_private_opts` / `view_private_opts` (with
`CallPrivateOptions` / `ViewPrivateOptions`) to `TestEnvironment`,
letting tests grant access to additional account scopes when calling
private functions. It also reshapes the TXE oracle ABI for
`private_call_new_flow` / `public_call_new_flow` to take an
`Option<AztecAddress>` for `from`, removes `call_public_incognito`'s
ignored `from` parameter, deprecates `view_public_incognito`, and
introduces `AztecAddress.NULL_MSG_SENDER` on the TS side.

## Conflicts handled (single commit)

After rebasing, four files still drifted vs `next`:

- **`noir-projects/aztec-nr/aztec/src/test/helpers/txe_oracles.nr`** —
with #22889 now on staging, the imports here match `next` exactly aside
from `NULL_MSG_SENDER_CONTRACT_ADDRESS`. Just drop that one symbol (the
PR removes its only use).
- **`docs/docs-developers/docs/resources/migration_notes.md`** — keep
only the two new TBD entries from #22968. The DeployMethod and
`aztec-up` bundled-binary entries that the cherry-pick brought along
belong to other PRs not yet backported (e.g. #22985); leave them out so
those backports add them in their own PRs.
-
**`yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts`**
— drop `NULL_MSG_SENDER_CONTRACT_ADDRESS` (replaced by
`AztecAddress.NULL_MSG_SENDER`). Keep `L1_TO_L2_MSG_TREE_HEIGHT`,
`NOTE_HASH_TREE_HEIGHT`, `PUBLIC_DATA_TREE_HEIGHT` — they're still in
use on this branch since #21577 hasn't been backported.
- **`yarn-project/stdlib/src/aztec-address/index.ts`** — keep both the
existing `eslint-disable
@typescript-eslint/no-unsafe-declaration-merging` comment and the new
`NULL_MSG_SENDER_CONTRACT_ADDRESS` import.

## Verification

- `nargo check` passes for both Noir contracts the PR touches
(`scope_test_contract`, `public_checks_contract`).
- No conflict markers remain.

Original PR: #22968
AztecBot added a commit that referenced this pull request May 7, 2026
Cherry-pick conflict resolution on commits 3befdc8/2ee3b06763 accidentally
reverted 7 files to an older API. Upstream PR #22985 only touched 43 files;
none of these 7 are among them. Restoring to origin/backport-to-v4-next-staging.

- yarn-project/foundation/src/collection/array.ts (re-add uniqueBy)
- yarn-project/foundation/src/collection/array.test.ts (re-add uniqueBy tests)
- yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts
- yarn-project/pxe/src/events/event_service.ts (batched validateAndStoreEvents)
- yarn-project/pxe/src/events/event_service.test.ts
- yarn-project/pxe/src/notes/note_service.ts (chunked nullifier batching)
- yarn-project/pxe/src/notes/note_service.test.ts
AztecBot pushed a commit that referenced this pull request May 8, 2026
Records the raw cherry-pick state (including conflict markers) so reviewers can see exactly what conflicted before resolution.

Original PR: #22985
AztecBot added a commit that referenced this pull request May 8, 2026
Resolve conflicts in #22985 cherry-pick onto v4-next-staging:
- migration_notes.md: keep only the new DeployMethod note. Drop unrelated entries
  (getBlock/getCheckpoint, feeAssetPriceModifier, Domain separators, aztec-up
  table, slasher renames, Unreleased v5 returnReceipt removal) from PRs not yet
  backported.
- txe_oracles.nr / constants*.nr: drop bleed-through constants
  (NULL_MSG_SENDER_CONTRACT_ADDRESS, DOM_SEP__HANDSHAKE_SECRET_HASH,
  DOM_SEP__MERKLE_HASH, DOM_SEP__NON_INTERACTIVE_HANDSHAKE_LOG_TAG,
  DOM_SEP__NULLIFIER_MERKLE, DOM_SEP__PARTIAL_NOTE_COMMITMENT,
  DOM_SEP__PUBLIC_DATA_MERKLE) introduced by other PRs.
- bot/factory.ts: adopt new DeployInstantiationOptions API; drop
  setupTokenWithOptionalEarlyRefuel / setupTokenContractWithOptionalEarlyRefuel
  / getTokenInstance helpers from an unbackported refuel PR; hoist instance
  variable so the post-if address read works on v4-next.
- e2e tests + cli-wallet/deploy.ts: adopt new DeployInstantiationOptions API
  (salt/deployer/universalDeploy/publicKeys move to construction); keep v4-next-only
  'wait: { returnReceipt: true }' callers compatible.
- aztec.js/api/contract.ts: keep DeployTxReceipt / DeployWaitOptions /
  DeployInteractionWaitOptions exports (v4-next-only).
- e2e_ha_full.test.ts: drop the keystore-reload test (introduced by an
  unbackported keystore-reload PR).
- token_bridge/index.ts: keep node.getProvenBlockNumber() (the new
  getBlockNumber('proven') ships with the unbackported getBlock RPC PR).
AztecBot pushed a commit that referenced this pull request May 8, 2026
Records the raw cherry-pick state (including conflict markers) so reviewers can see exactly what conflicted before resolution.

Original PR: #22985
AztecBot added a commit that referenced this pull request May 8, 2026
Resolve conflicts in #22985 cherry-pick onto v4-next-staging:
- migration_notes.md: keep only the new DeployMethod note. Drop unrelated entries
  (getBlock/getCheckpoint, feeAssetPriceModifier, Domain separators, aztec-up
  table, slasher renames, Unreleased v5 returnReceipt removal) from PRs not yet
  backported.
- txe_oracles.nr / constants*.nr: drop bleed-through constants
  (NULL_MSG_SENDER_CONTRACT_ADDRESS, DOM_SEP__HANDSHAKE_SECRET_HASH,
  DOM_SEP__MERKLE_HASH, DOM_SEP__NON_INTERACTIVE_HANDSHAKE_LOG_TAG,
  DOM_SEP__NULLIFIER_MERKLE, DOM_SEP__PARTIAL_NOTE_COMMITMENT,
  DOM_SEP__PUBLIC_DATA_MERKLE) introduced by other PRs.
- bot/factory.ts: adopt new DeployInstantiationOptions API; drop
  setupTokenWithOptionalEarlyRefuel / setupTokenContractWithOptionalEarlyRefuel
  / getTokenInstance helpers from an unbackported refuel PR; hoist instance
  variable so the post-if address read works on v4-next.
- e2e tests + cli-wallet/deploy.ts: adopt new DeployInstantiationOptions API
  (salt/deployer/universalDeploy/publicKeys move to construction); keep v4-next-only
  'wait: { returnReceipt: true }' callers compatible.
- aztec.js/api/contract.ts: keep DeployTxReceipt / DeployWaitOptions /
  DeployInteractionWaitOptions exports (v4-next-only).
- e2e_ha_full.test.ts: drop the keystore-reload test (introduced by an
  unbackported keystore-reload PR).
- token_bridge/index.ts: keep node.getProvenBlockNumber() (the new
  getBlockNumber('proven') ships with the unbackported getBlock RPC PR).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-to-v4-next ci-no-fail-fast Sets NO_FAIL_FAST in the CI so the run is not aborted on the first failure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants