Skip to content

Commit 63cc7e6

Browse files
authored
feat: deploy method refactor 2 (#23033)
## Summary Splits `DeployMethod` into an abstract umbrella type plus three concrete flavors that encode the deployer-lock state at the type level instead of branching on a nullable field. - `DeployMethod<T>` — abstract base, the type consumers use generically (`let d: DeployMethod<MyContract> = ...`). - `BoundDeployMethod` — locked to a concrete `deployer`. - `UniversalDeployMethod` — locked to `AztecAddress.ZERO` (any sender). - `PendingDeployMethod` — promotes into a `Bound`/`Universal` sibling on the first `send` / `simulate` / `profile` call. The three flavor-specific decisions (`getDeployerAddress`, `lockOrAssertDeployer`, `cloneInstantiation`) are now abstract methods rather than `if (this.deployer === undefined / equals(ZERO) / else)` branches in the base. ## Compile-time guarantees Each subclass takes a narrowed instantiation type: - `BoundInstantiationOptions` — `deployer` required, `universalDeploy: never`. - `UniversalInstantiationOptions` — `universalDeploy: true` required, `deployer: never`. - `PendingInstantiationOptions` — both `never`. `new BoundDeployMethod(..., { universalDeploy: true })` is now a TypeScript error. The runtime mutual-exclusion checks in subclass constructors are gone; the only runtime guard left is `BoundDeployMethod` rejecting `AztecAddress.ZERO` (a value-level invariant the type system can't model). ## Constructor shape Subclass constructors take named bundles instead of 9 positionals: ```ts new BoundDeployMethod(wallet, contract, instantiation, payload?) // ^ ^ ^ // { artifact, postDeployCtor, args, constructorNameOrArtifact } // { salt, publicKeys, deployer } // { authWitnesses, capsules, extraHashedArgs } ``` `DeployMethod.create` follows the same shape. `Contract.deploy(wallet, artifact, args, constructorName, instantiation)` and `MyContract.deploy(wallet, ...args, instantiation?)` (codegen) are **unchanged** — the bundle reshape stops at the `create` boundary. ## Other changes - `DeployAccountMethod` now extends `UniversalDeployMethod` (account contracts are always universal).
1 parent d8aa9fe commit 63cc7e6

8 files changed

Lines changed: 417 additions & 157 deletions

File tree

playground/src/components/contract/components/CreateContractDialog.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,16 @@ export function CreateContractDialog({
115115
if (publiclyDeploy) {
116116
const postDeployCtor = (instance: ContractInstanceWithAddress, wallet: Wallet) =>
117117
Contract.at(instance.address, contractArtifact, wallet);
118-
deployMethod = new DeployMethod(wallet, contractArtifact, postDeployCtor, parameters, initializer?.name, {
119-
publicKeys: contract.publicKeys,
120-
salt: contract.salt,
121-
deployer: from,
122-
});
118+
deployMethod = DeployMethod.create(
119+
wallet,
120+
{
121+
artifact: contractArtifact,
122+
postDeployCtor,
123+
args: parameters,
124+
constructorNameOrArtifact: initializer?.name,
125+
},
126+
{ publicKeys: contract.publicKeys, salt: contract.salt, deployer: from },
127+
);
123128
opts = {
124129
from,
125130
fee: { paymentMethod: feePaymentMethod },

yarn-project/aztec.js/src/api/contract.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export {
7171
DeployMethod,
7272
type RequestDeployOptions,
7373
type SimulateDeployOptions,
74+
UniversalDeployMethod,
7475
} from '../contract/deploy_method.js';
7576
export { waitForProven, type WaitForProvenOpts, DefaultWaitForProvenOpts } from '../contract/wait_for_proven.js';
7677
export { getGasLimits } from '../contract/get_gas_limits.js';

yarn-project/aztec.js/src/contract/contract.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ export class Contract extends ContractBase {
4343
) {
4444
const postDeployCtor = (instance: ContractInstanceWithAddress, wallet: Wallet) =>
4545
Contract.at(instance.address, artifact, wallet);
46-
return new DeployMethod(wallet, artifact, postDeployCtor, args, constructorName, instantiation);
46+
return DeployMethod.create(
47+
wallet,
48+
{ artifact, postDeployCtor, args, constructorNameOrArtifact: constructorName },
49+
instantiation,
50+
);
4751
}
4852
}

yarn-project/aztec.js/src/contract/deploy_method.test.ts

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import { NO_FROM } from './interaction_options.js';
1414

1515
describe('DeployMethod', () => {
1616
let wallet: MockProxy<Wallet>;
17-
1817
beforeEach(() => {
1918
wallet = mock<Wallet>();
2019
wallet.registerContract.mockResolvedValue({} as ContractInstanceWithAddress);
@@ -27,14 +26,10 @@ describe('DeployMethod', () => {
2726
const txSimResult = mock<TxSimulationResultWithAppOffset>();
2827
Object.defineProperty(txSimResult, 'offchainEffects', { value: offchainEffects });
2928
Object.defineProperty(txSimResult, 'publicInputs', {
30-
value: {
31-
constants: { anchorBlockHeader: { globalVariables: { timestamp: anchorBlockTimestamp } } },
32-
},
29+
value: { constants: { anchorBlockHeader: { globalVariables: { timestamp: anchorBlockTimestamp } } } },
3330
});
3431
Object.defineProperty(txSimResult, 'stats', { value: {} });
35-
Object.defineProperty(txSimResult, 'gasUsed', {
36-
value: { totalGas: Gas.empty(), teardownGas: Gas.empty() },
37-
});
32+
Object.defineProperty(txSimResult, 'gasUsed', { value: { totalGas: Gas.empty(), teardownGas: Gas.empty() } });
3833
return txSimResult;
3934
};
4035

@@ -43,19 +38,12 @@ describe('DeployMethod', () => {
4338
const contractAddress = await AztecAddress.random();
4439
const msgPayload = [Fr.random(), Fr.random()];
4540
const anchorBlockTimestamp = 42000n;
46-
4741
const offchainEffects: OffchainEffect[] = [
48-
{
49-
data: [OFFCHAIN_MESSAGE_IDENTIFIER, recipient.toField(), ...msgPayload],
50-
contractAddress,
51-
},
42+
{ data: [OFFCHAIN_MESSAGE_IDENTIFIER, recipient.toField(), ...msgPayload], contractAddress },
5243
];
53-
5444
wallet.simulateTx.mockResolvedValue(makeSimulateResult(offchainEffects, anchorBlockTimestamp));
55-
5645
const deploy = Contract.deploy(wallet, testContractArtifact, []);
5746
const result = await deploy.simulate({ from: await AztecAddress.random() });
58-
5947
expect(result.offchainMessages).toHaveLength(1);
6048
expect(result.offchainMessages[0]).toEqual({
6149
recipient,
@@ -112,15 +100,11 @@ describe('DeployMethod', () => {
112100

113101
it('keeps the address stable across simulate then send when lazy-locking from `from`', async () => {
114102
const deploy = Contract.deploy(wallet, testContractArtifact, [], undefined, { salt });
115-
116103
const expected = await expectedAddress(alice);
117-
118104
await deploy.simulate({ from: alice });
119105
const afterSimulate = await deploy.getAddress();
120-
121106
await deploy.send({ from: alice });
122107
const afterSend = await deploy.getAddress();
123-
124108
expect(afterSimulate).toEqual(afterSend);
125109
expect(afterSimulate).toEqual(expected);
126110
});
@@ -147,7 +131,6 @@ describe('DeployMethod', () => {
147131
it('allows any sender when locked to universal at construction, and the address is stable', async () => {
148132
const deploy = Contract.deploy(wallet, testContractArtifact, [], undefined, { salt, universalDeploy: true });
149133
const expected = await expectedAddress(AztecAddress.ZERO);
150-
151134
await expect(deploy.send({ from: alice })).resolves.toBeDefined();
152135
expect(await deploy.getAddress()).toEqual(expected);
153136
await expect(deploy.send({ from: bob })).resolves.toBeDefined();

0 commit comments

Comments
 (0)