From 5353ed0ae5ecfd227fac36b8f2305c3d91d1c855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bene=C5=A1?= Date: Tue, 1 Aug 2023 17:20:21 +0200 Subject: [PATCH] feat: throw when creating an instance of non-existent contract (#1300) # Description Fixes #1225 --- .../cpp/src/aztec3/circuits/abis/c_bind.cpp | 4 +- .../archiver/src/archiver/archiver_store.ts | 3 + yarn-project/aztec-cli/src/index.ts | 2 +- .../examples/uniswap_trade_on_l1_from_l2.ts | 4 +- .../aztec-sandbox/src/examples/util.ts | 4 +- .../src/examples/zk_token_contract.ts | 2 +- yarn-project/aztec.js/README.md | 4 +- .../aztec.js/src/contract/contract.test.ts | 15 ++-- .../aztec.js/src/contract/contract.ts | 79 +++---------------- .../aztec.js/src/contract/contract_base.ts | 77 ++++++++++++++++++ yarn-project/aztec.js/src/contract/index.ts | 1 + .../src/contract_deployer/deploy_method.ts | 4 +- .../end-to-end/src/e2e_2_rpc_servers.test.ts | 8 +- .../src/e2e_account_contracts.test.ts | 4 +- .../src/e2e_escrow_contract.test.ts | 4 +- .../src/e2e_lending_contract.test.ts | 2 +- .../e2e_multiple_accounts_1_enc_key.test.ts | 4 +- .../src/e2e_nested_contract.test.ts | 2 +- .../src/e2e_non_contract_account.test.ts | 5 +- .../e2e_pending_commitments_contract.test.ts | 2 +- .../src/e2e_public_token_contract.test.ts | 2 +- .../src/e2e_zk_token_contract.test.ts | 4 +- .../src/uniswap_trade_on_l1_from_l2.test.ts | 2 +- yarn-project/end-to-end/src/utils.ts | 10 ++- yarn-project/ethereum/src/index.ts | 4 +- .../noir-compiler/src/typegen/index.ts | 37 ++++++++- 26 files changed, 176 insertions(+), 113 deletions(-) create mode 100644 yarn-project/aztec.js/src/contract/contract_base.ts diff --git a/circuits/cpp/src/aztec3/circuits/abis/c_bind.cpp b/circuits/cpp/src/aztec3/circuits/abis/c_bind.cpp index 5e0a2574cb1..c79440219fe 100644 --- a/circuits/cpp/src/aztec3/circuits/abis/c_bind.cpp +++ b/circuits/cpp/src/aztec3/circuits/abis/c_bind.cpp @@ -55,7 +55,7 @@ using aztec3::circuits::abis::PublicTypes; * @brief Fill in zero-leaves to get a full tree's bottom layer. * * @details Given the a vector of nonzero leaves starting at the left, - * append zeroleaves to that list until it represents a FULL set of leaves + * append zero leaves to that list until it represents a FULL set of leaves * for a tree of the given height. * **MODIFIES THE INPUT `leaves` REFERENCE!** * @@ -78,7 +78,7 @@ template void rightfill_with_zeroleaves(std::vector } // namespace // Note: We don't have a simple way of calling the barretenberg c-bind. -// Mimick bbmalloc behaviour. +// Mimic bbmalloc behaviour. static void* bbmalloc(size_t size) { auto* ptr = aligned_alloc(64, size); diff --git a/yarn-project/archiver/src/archiver/archiver_store.ts b/yarn-project/archiver/src/archiver/archiver_store.ts index c2bf44ee54d..942387ecd33 100644 --- a/yarn-project/archiver/src/archiver/archiver_store.ts +++ b/yarn-project/archiver/src/archiver/archiver_store.ts @@ -346,6 +346,9 @@ export class MemoryArchiverStore implements ArchiverDataStore { * @returns ContractData with the portal address (if we didn't throw an error). */ public getL2ContractInfo(contractAddress: AztecAddress): Promise { + if (contractAddress.isZero()) { + return Promise.resolve(undefined); + } for (const block of this.l2Blocks) { for (const contractData of block.newContractData) { if (contractData.contractAddress.equals(contractAddress)) { diff --git a/yarn-project/aztec-cli/src/index.ts b/yarn-project/aztec-cli/src/index.ts index 8c24944ce3e..eca51331f69 100644 --- a/yarn-project/aztec-cli/src/index.ts +++ b/yarn-project/aztec-cli/src/index.ts @@ -300,7 +300,7 @@ async function main() { PrivateKey.fromString(options.privateKey), accountCreationSalt, ); - const contract = new Contract(contractAddress, contractAbi, wallet); + const contract = await Contract.create(contractAddress, contractAbi, wallet); const origin = (await wallet.getAccounts()).find(addr => addr.equals(wallet.getAddress())); const tx = contract.methods[functionName](...functionArgs).send({ origin, diff --git a/yarn-project/aztec-sandbox/src/examples/uniswap_trade_on_l1_from_l2.ts b/yarn-project/aztec-sandbox/src/examples/uniswap_trade_on_l1_from_l2.ts index 6b12f21df4a..b8b90c07fcd 100644 --- a/yarn-project/aztec-sandbox/src/examples/uniswap_trade_on_l1_from_l2.ts +++ b/yarn-project/aztec-sandbox/src/examples/uniswap_trade_on_l1_from_l2.ts @@ -111,7 +111,7 @@ async function deployAllContracts(owner: AztecAddress) { const tx = UniswapContract.deploy(aztecRpcClient).send({ portalContract: uniswapPortalAddress }); await tx.isMined(0, 0.5); const receipt = await tx.getReceipt(); - const uniswapL2Contract = new UniswapContract(receipt.contractAddress!, wallet); + const uniswapL2Contract = await UniswapContract.create(receipt.contractAddress!, wallet); await uniswapL2Contract.attach(uniswapPortalAddress); await uniswapPortal.write.initialize( @@ -330,7 +330,7 @@ async function main() { main() .then(() => { - logger('Finished running successfuly.'); + logger('Finished running successfully.'); process.exit(0); }) .catch(err => { diff --git a/yarn-project/aztec-sandbox/src/examples/util.ts b/yarn-project/aztec-sandbox/src/examples/util.ts index 33233305c3e..5f2cfe35333 100644 --- a/yarn-project/aztec-sandbox/src/examples/util.ts +++ b/yarn-project/aztec-sandbox/src/examples/util.ts @@ -13,7 +13,7 @@ import { Account, Chain, Hex, HttpTransport, PublicClient, WalletClient, getCont * @param rollupRegistryAddress - address of rollup registry to pass to initialize the token portal * @param initialBalance - initial balance of the owner of the L2 contract * @param owner - owner of the L2 contract - * @param underlyingERC20Address - address of the underlying ERC20 contract to use (if noone supplied, it deploys one) + * @param underlyingERC20Address - address of the underlying ERC20 contract to use (if none supplied, it deploys one) * @returns l2 contract instance, token portal instance, token portal address and the underlying ERC20 instance */ export async function deployAndInitializeNonNativeL2TokenContracts( @@ -51,7 +51,7 @@ export async function deployAndInitializeNonNativeL2TokenContracts( }); await tx.isMined(0, 0.1); const receipt = await tx.getReceipt(); - const l2Contract = new NonNativeTokenContract(receipt.contractAddress!, wallet); + const l2Contract = await NonNativeTokenContract.create(receipt.contractAddress!, wallet); await l2Contract.attach(tokenPortalAddress); const l2TokenAddress = l2Contract.address.toString() as `0x${string}`; diff --git a/yarn-project/aztec-sandbox/src/examples/zk_token_contract.ts b/yarn-project/aztec-sandbox/src/examples/zk_token_contract.ts index 6d5e4588513..b59bb0bc132 100644 --- a/yarn-project/aztec-sandbox/src/examples/zk_token_contract.ts +++ b/yarn-project/aztec-sandbox/src/examples/zk_token_contract.ts @@ -25,7 +25,7 @@ async function deployZKContract(owner: AztecAddress) { logger('Deploying L2 contract...'); const tx = ZkTokenContract.deploy(aztecRpcClient, INITIAL_BALANCE, owner).send(); const receipt = await tx.getReceipt(); - const contract = new ZkTokenContract(receipt.contractAddress!, wallet); + const contract = await ZkTokenContract.create(receipt.contractAddress!, wallet); await tx.isMined(); await tx.getReceipt(); logger('L2 contract deployed'); diff --git a/yarn-project/aztec.js/README.md b/yarn-project/aztec.js/README.md index 0ba9cacb23c..bdb68520d85 100644 --- a/yarn-project/aztec.js/README.md +++ b/yarn-project/aztec.js/README.md @@ -23,7 +23,7 @@ console.log(`Contract address: ${receipt.contractAddress}`); ```typescript import { Contract } from '@aztec/aztec.js'; -const contract = new Contract(contractAddress, contractAbi, aztecRpcServer); +const contract = await Contract.create(contractAddress, contractAbi, aztecRpcServer); const tx = contract.methods .transfer(amount, recipientAddress)) .send({ origin: senderAddress }); @@ -36,7 +36,7 @@ console.log(`Transferred ${amount} to ${recipientAddress}!`); ```typescript import { Contract } from '@aztec/aztec.js'; -const contract = new Contract(contractAddress, contractAbi, aztecRpcServer); +const contract = await Contract.create(contractAddress, contractAbi, aztecRpcServer); const [balance] = contract.methods .getBalance(accountPublicKey)) .view({ from: accountAddress }); diff --git a/yarn-project/aztec.js/src/contract/contract.test.ts b/yarn-project/aztec.js/src/contract/contract.test.ts index c7bc28e9ab8..c4100b3e192 100644 --- a/yarn-project/aztec.js/src/contract/contract.test.ts +++ b/yarn-project/aztec.js/src/contract/contract.test.ts @@ -94,6 +94,7 @@ describe('Contract Class', () => { beforeEach(() => { wallet = mock(); wallet.createAuthenticatedTxRequest.mockResolvedValue(mockTxRequest); + wallet.isContractDeployed.mockResolvedValue(true); wallet.sendTx.mockResolvedValue(mockTxHash); wallet.viewTx.mockResolvedValue(mockViewResultValue); wallet.getTxReceipt.mockResolvedValue(mockTxReceipt); @@ -102,7 +103,7 @@ describe('Contract Class', () => { }); it('should create and send a contract method tx', async () => { - const fooContract = new Contract(contractAddress, defaultAbi, wallet); + const fooContract = await Contract.create(contractAddress, defaultAbi, wallet); const param0 = 12; const param1 = 345n; const sentTx = fooContract.methods.bar(param0, param1).send({ @@ -119,7 +120,7 @@ describe('Contract Class', () => { }); it('should call view on an unconstrained function', async () => { - const fooContract = new Contract(contractAddress, defaultAbi, wallet); + const fooContract = await Contract.create(contractAddress, defaultAbi, wallet); const result = await fooContract.methods.qux(123n).view({ from: account, }); @@ -128,8 +129,8 @@ describe('Contract Class', () => { expect(result).toBe(mockViewResultValue); }); - it('should not call send on an unconstrained function', () => { - const fooContract = new Contract(contractAddress, defaultAbi, wallet); + it('should not call send on an unconstrained function', async () => { + const fooContract = await Contract.create(contractAddress, defaultAbi, wallet); expect(() => fooContract.methods.qux().send({ origin: account, @@ -137,15 +138,15 @@ describe('Contract Class', () => { ).toThrow(); }); - it('should not call view on a secret or open function', () => { - const fooContract = new Contract(contractAddress, defaultAbi, wallet); + it('should not call view on a secret or open function', async () => { + const fooContract = await Contract.create(contractAddress, defaultAbi, wallet); expect(() => fooContract.methods.bar().view()).toThrow(); expect(() => fooContract.methods.baz().view()).toThrow(); }); it('should add contract and dependencies to aztec rpc', async () => { const entry = randomDeployContract(); - const contract = new Contract(entry.address, entry.abi, wallet); + const contract = await Contract.create(entry.address, entry.abi, wallet); { await contract.attach(entry.portalContract); diff --git a/yarn-project/aztec.js/src/contract/contract.ts b/yarn-project/aztec.js/src/contract/contract.ts index 0a4f1d209f1..454768027c1 100644 --- a/yarn-project/aztec.js/src/contract/contract.ts +++ b/yarn-project/aztec.js/src/contract/contract.ts @@ -1,21 +1,8 @@ -import { ContractAbi, FunctionAbi, generateFunctionSelector } from '@aztec/foundation/abi'; +import { ContractAbi } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; -import { EthAddress } from '@aztec/foundation/eth-address'; -import { DeployedContract } from '@aztec/types'; import { Wallet } from '../aztec_rpc_client/wallet.js'; -import { ContractFunctionInteraction } from './contract_function_interaction.js'; - -/** - * Type representing a contract method that returns a ContractFunctionInteraction instance - * and has a readonly 'selector' property of type Buffer. Takes any number of arguments. - */ -export type ContractMethod = ((...args: any[]) => ContractFunctionInteraction) & { - /** - * The unique identifier for a contract function in bytecode. - */ - readonly selector: Buffer; -}; +import { ContractBase } from './contract_base.js'; /** * The Contract class represents a contract and provides utility methods for interacting with it. @@ -24,58 +11,18 @@ export type ContractMethod = ((...args: any[]) => ContractFunctionInteraction) & * to attach the contract instance to a deployed contract on-chain through the AztecRPCClient, which facilitates * interaction with Aztec's privacy protocol. */ -export class Contract { - /** - * An object containing contract methods mapped to their respective names. - */ - public methods: { [name: string]: ContractMethod } = {}; - - constructor( - /** - * The deployed contract's address. - */ - public readonly address: AztecAddress, - /** - * The Application Binary Interface for the contract. - */ - public readonly abi: ContractAbi, - /** - * The wallet. - */ - protected wallet: Wallet, - ) { - abi.functions.forEach((f: FunctionAbi) => { - const interactionFunction = (...args: any[]) => { - return new ContractFunctionInteraction(this.wallet, this.address!, f, args); - }; - - this.methods[f.name] = Object.assign(interactionFunction, { - /** - * A getter for users to fetch the function selector. - * @returns Selector of the function. - */ - get selector() { - return generateFunctionSelector(f.name, f.parameters); - }, - }); - }); - } - +export class Contract extends ContractBase { /** - * Attach the current contract instance to a portal contract and optionally add its dependencies. - * The function will return a promise that resolves when all contracts have been added to the AztecRPCClient. - * This is useful when you need to interact with a deployed contract that has multiple nested contracts. - * - * @param portalContract - The Ethereum address of the portal contract. - * @param dependencies - An optional array of additional DeployedContract instances to be attached. - * @returns A promise that resolves when all contracts are successfully added to the AztecRPCClient. + * Creates a contract instance. + * @param address - The deployed contract's address. + * @param abi - The Application Binary Interface for the contract. + * @param wallet - The wallet to use when interacting with the contract. + * @returns A promise that resolves to a new Contract instance. */ - attach(portalContract: EthAddress, dependencies: DeployedContract[] = []) { - const deployedContract: DeployedContract = { - abi: this.abi, - address: this.address, - portalContract, - }; - return this.wallet.addContracts([deployedContract, ...dependencies]); + public static async create(address: AztecAddress, abi: ContractAbi, wallet: Wallet): Promise { + if (!(await wallet.isContractDeployed(address))) { + throw new Error('Contract ' + address.toString() + ' is not deployed'); + } + return new Contract(address, abi, wallet); } } diff --git a/yarn-project/aztec.js/src/contract/contract_base.ts b/yarn-project/aztec.js/src/contract/contract_base.ts new file mode 100644 index 00000000000..3c2d6653c41 --- /dev/null +++ b/yarn-project/aztec.js/src/contract/contract_base.ts @@ -0,0 +1,77 @@ +import { ContractAbi, FunctionAbi, generateFunctionSelector } from '@aztec/foundation/abi'; +import { AztecAddress } from '@aztec/foundation/aztec-address'; +import { EthAddress } from '@aztec/foundation/eth-address'; +import { DeployedContract } from '@aztec/types'; + +import { Wallet } from '../aztec_rpc_client/wallet.js'; +import { ContractFunctionInteraction } from './contract_function_interaction.js'; + +/** + * Type representing a contract method that returns a ContractFunctionInteraction instance + * and has a readonly 'selector' property of type Buffer. Takes any number of arguments. + */ +export type ContractMethod = ((...args: any[]) => ContractFunctionInteraction) & { + /** + * The unique identifier for a contract function in bytecode. + */ + readonly selector: Buffer; +}; + +/** + * Abstract implementation of a contract extended by the Contract class and generated contract types. + */ +export abstract class ContractBase { + /** + * An object containing contract methods mapped to their respective names. + */ + public methods: { [name: string]: ContractMethod } = {}; + + protected constructor( + /** + * The deployed contract's address. + */ + public readonly address: AztecAddress, + /** + * The Application Binary Interface for the contract. + */ + public readonly abi: ContractAbi, + /** + * The wallet. + */ + protected wallet: Wallet, + ) { + abi.functions.forEach((f: FunctionAbi) => { + const interactionFunction = (...args: any[]) => { + return new ContractFunctionInteraction(this.wallet, this.address!, f, args); + }; + + this.methods[f.name] = Object.assign(interactionFunction, { + /** + * A getter for users to fetch the function selector. + * @returns Selector of the function. + */ + get selector() { + return generateFunctionSelector(f.name, f.parameters); + }, + }); + }); + } + + /** + * Attach the current contract instance to a portal contract and optionally add its dependencies. + * The function will return a promise that resolves when all contracts have been added to the AztecRPCClient. + * This is useful when you need to interact with a deployed contract that has multiple nested contracts. + * + * @param portalContract - The Ethereum address of the portal contract. + * @param dependencies - An optional array of additional DeployedContract instances to be attached. + * @returns A promise that resolves when all contracts are successfully added to the AztecRPCClient. + */ + public attach(portalContract: EthAddress, dependencies: DeployedContract[] = []) { + const deployedContract: DeployedContract = { + abi: this.abi, + address: this.address, + portalContract, + }; + return this.wallet.addContracts([deployedContract, ...dependencies]); + } +} diff --git a/yarn-project/aztec.js/src/contract/index.ts b/yarn-project/aztec.js/src/contract/index.ts index c4a9ef3f415..8cda1ef43e2 100644 --- a/yarn-project/aztec.js/src/contract/index.ts +++ b/yarn-project/aztec.js/src/contract/index.ts @@ -1,3 +1,4 @@ export * from './contract.js'; export * from './contract_function_interaction.js'; export * from './sent_tx.js'; +export * from './contract_base.js'; diff --git a/yarn-project/aztec.js/src/contract_deployer/deploy_method.ts b/yarn-project/aztec.js/src/contract_deployer/deploy_method.ts index da4209eba7b..d48e48e1442 100644 --- a/yarn-project/aztec.js/src/contract_deployer/deploy_method.ts +++ b/yarn-project/aztec.js/src/contract_deployer/deploy_method.ts @@ -149,10 +149,10 @@ export class DeployMethod extends ContractFunctionInteraction { * @param withWallet - The wallet to provide to the contract abstraction * @returns - The generated contract abstraction. */ - public getContract(withWallet: Wallet) { + public async getContract(withWallet: Wallet) { if (!this.completeContractAddress) { throw new Error(`Cannot get a contract instance for a contract not yet deployed`); } - return new Contract(this.completeContractAddress, this.abi, withWallet); + return await Contract.create(this.completeContractAddress, this.abi, withWallet); } } diff --git a/yarn-project/end-to-end/src/e2e_2_rpc_servers.test.ts b/yarn-project/end-to-end/src/e2e_2_rpc_servers.test.ts index 442bd924af3..c65322ce6d3 100644 --- a/yarn-project/end-to-end/src/e2e_2_rpc_servers.test.ts +++ b/yarn-project/end-to-end/src/e2e_2_rpc_servers.test.ts @@ -70,7 +70,7 @@ describe('e2e_2_rpc_servers', () => { await awaitUserSynchronised(wallet, owner); // Then check the balance - const contractWithWallet = new ZkTokenContract(tokenAddress, wallet); + const contractWithWallet = await ZkTokenContract.create(tokenAddress, wallet); const [balance] = await contractWithWallet.methods.getBalance(owner).view({ from: owner }); logger(`Account ${owner} balance: ${balance}`); expect(balance).toBe(expectedBalance); @@ -118,7 +118,7 @@ describe('e2e_2_rpc_servers', () => { await expectUnencryptedLogsFromLastBlockToBe(aztecNode, ['Balance set in constructor']); // Transfer funds from A to B via RPC server A - const contractWithWalletA = new ZkTokenContract(tokenAddress, walletA); + const contractWithWalletA = await ZkTokenContract.create(tokenAddress, walletA); const txAToB = contractWithWalletA.methods.transfer(transferAmount1, userA, userB).send({ origin: userA }); await txAToB.isMined(0, 0.1); @@ -133,7 +133,7 @@ describe('e2e_2_rpc_servers', () => { await expectUnencryptedLogsFromLastBlockToBe(aztecNode, ['Coins transferred']); // Transfer funds from B to A via RPC server B - const contractWithWalletB = new ZkTokenContract(tokenAddress, walletB); + const contractWithWalletB = await ZkTokenContract.create(tokenAddress, walletB); const txBToA = contractWithWalletB.methods.transfer(transferAmount2, userB, userA).send({ origin: userB }); await txBToA.isMined(0, 0.1); @@ -186,7 +186,7 @@ describe('e2e_2_rpc_servers', () => { const newValueToSet = 256n; - const childContractWithWalletB = new ChildContract(childAddress, walletB); + const childContractWithWalletB = await ChildContract.create(childAddress, walletB); const tx = childContractWithWalletB.methods.pubStoreValue(newValueToSet).send({ origin: userB }); await tx.isMined(0, 0.1); diff --git a/yarn-project/end-to-end/src/e2e_account_contracts.test.ts b/yarn-project/end-to-end/src/e2e_account_contracts.test.ts index a58a254a72e..2f27be77557 100644 --- a/yarn-project/end-to-end/src/e2e_account_contracts.test.ts +++ b/yarn-project/end-to-end/src/e2e_account_contracts.test.ts @@ -40,7 +40,7 @@ function itShouldBehaveLikeAnAccountContract( )); const { address: childAddress } = await deployContract(aztecRpcServer, Point.random(), ChildContract.abi, []); - child = new ChildContract(childAddress, wallet); + child = await ChildContract.create(childAddress, wallet); }, 60_000); afterEach(async () => { @@ -70,7 +70,7 @@ function itShouldBehaveLikeAnAccountContract( context.aztecRpcServer, await createAccountImpl(address, false, partialAddress, encryptionPrivateKey), ); - const childWithInvalidWallet = new ChildContract(child.address, invalidWallet); + const childWithInvalidWallet = await ChildContract.create(child.address, invalidWallet); await expect(childWithInvalidWallet.methods.value(42).simulate()).rejects.toThrowError( /could not satisfy all constraints/, ); diff --git a/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts b/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts index 5db617f0267..51c93103f64 100644 --- a/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts @@ -49,14 +49,14 @@ describe('e2e_escrow_contract', () => { await aztecRpcServer.addAccount(escrowPrivateKey, deployInfo.address, deployInfo.partialAddress); const escrowDeployTx = EscrowContract.deployWithPublicKey(aztecRpcServer, escrowPublicKey, owner); await escrowDeployTx.send({ contractAddressSalt: salt }).wait(); - escrowContract = new EscrowContract(escrowDeployTx.completeContractAddress!, wallet); + escrowContract = await EscrowContract.create(escrowDeployTx.completeContractAddress!, wallet); logger(`Escrow contract deployed at ${escrowContract.address}`); // Deploy ZK token contract and mint funds for the escrow contract zkTokenContract = await ZkTokenContract.deploy(aztecRpcServer, 100n, escrowContract.address) .send() .wait() - .then(r => new ZkTokenContract(r.contractAddress!, wallet)); + .then(async r => await ZkTokenContract.create(r.contractAddress!, wallet)); logger(`Token contract deployed at ${zkTokenContract.address}`); }, 100_000); diff --git a/yarn-project/end-to-end/src/e2e_lending_contract.test.ts b/yarn-project/end-to-end/src/e2e_lending_contract.test.ts index f692f7d7896..7520d1387a4 100644 --- a/yarn-project/end-to-end/src/e2e_lending_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_lending_contract.test.ts @@ -24,10 +24,10 @@ describe('e2e_lending_contract', () => { logger(`Tx sent with hash ${await tx.getTxHash()}`); const receipt = await tx.getReceipt(); - contract = new LendingContract(receipt.contractAddress!, wallet); await tx.isMined(0, 0.1); const txReceipt = await tx.getReceipt(); logger(`L2 contract deployed at ${receipt.contractAddress}`); + contract = await LendingContract.create(receipt.contractAddress!, wallet); return { contract, tx, txReceipt }; }; diff --git a/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts b/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts index 4fa62560426..768d8b0833c 100644 --- a/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts +++ b/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts @@ -87,7 +87,7 @@ describe('e2e_multiple_accounts_1_enc_key', () => { const owner = accounts[userIndex]; // Then check the balance - const contractWithWallet = new ZkTokenContract(zkTokenAddress, wallet); + const contractWithWallet = await ZkTokenContract.create(zkTokenAddress, wallet); const [balance] = await contractWithWallet.methods.getBalance(owner).view({ from: owner }); logger(`Account ${owner} balance: ${balance}`); expect(balance).toBe(expectedBalance); @@ -104,7 +104,7 @@ describe('e2e_multiple_accounts_1_enc_key', () => { const sender = accounts[senderIndex]; const receiver = accounts[receiverIndex]; - const contractWithWallet = new ZkTokenContract(zkTokenAddress, wallets[senderIndex]); + const contractWithWallet = await ZkTokenContract.create(zkTokenAddress, wallets[senderIndex]); const tx = contractWithWallet.methods.transfer(transferAmount, sender, receiver).send({ origin: sender }); await tx.isMined(0, 0.1); diff --git a/yarn-project/end-to-end/src/e2e_nested_contract.test.ts b/yarn-project/end-to-end/src/e2e_nested_contract.test.ts index cb494b18eec..b6066237190 100644 --- a/yarn-project/end-to-end/src/e2e_nested_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_nested_contract.test.ts @@ -42,7 +42,7 @@ describe('e2e_nested_contract', () => { await tx.isMined(0, 0.1); const receipt = await tx.getReceipt(); - const contract = new Contract(receipt.contractAddress!, abi, wallet); + const contract = await Contract.create(receipt.contractAddress!, abi, wallet); logger(`L2 contract ${abi.name} deployed at ${contract.address}`); return contract; }; diff --git a/yarn-project/end-to-end/src/e2e_non_contract_account.test.ts b/yarn-project/end-to-end/src/e2e_non_contract_account.test.ts index d1234a33d0d..027511f83e0 100644 --- a/yarn-project/end-to-end/src/e2e_non_contract_account.test.ts +++ b/yarn-project/end-to-end/src/e2e_non_contract_account.test.ts @@ -63,12 +63,11 @@ describe('e2e_non_contract_account', () => { logger(`Deploying L2 contract...`); const tx = PokeableTokenContract.deploy(aztecRpcServer, initialBalance, sender, recipient, poker).send(); const receipt = await tx.getReceipt(); - contract = new PokeableTokenContract(receipt.contractAddress!, wallet); await tx.isMined(0, 0.1); const minedReceipt = await tx.getReceipt(); expect(minedReceipt.status).toEqual(TxStatus.MINED); logger('L2 contract deployed'); - return contract; + contract = await PokeableTokenContract.create(receipt.contractAddress!, wallet); }, 100_000); afterEach(async () => { @@ -89,7 +88,7 @@ describe('e2e_non_contract_account', () => { await expectBalance(recipient, 0n); await expectsNumOfEncryptedLogsInTheLastBlockToBe(aztecNode, 3); - const contractWithNoContractWallet = new PokeableTokenContract(contract.address, pokerWallet); + const contractWithNoContractWallet = await PokeableTokenContract.create(contract.address, pokerWallet); const isUserSynchronised = async () => { return await wallet.isAccountSynchronised(poker); diff --git a/yarn-project/end-to-end/src/e2e_pending_commitments_contract.test.ts b/yarn-project/end-to-end/src/e2e_pending_commitments_contract.test.ts index 4416122ec63..056b518b577 100644 --- a/yarn-project/end-to-end/src/e2e_pending_commitments_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_pending_commitments_contract.test.ts @@ -31,10 +31,10 @@ describe('e2e_pending_commitments_contract', () => { logger(`Deploying L2 contract...`); const tx = PendingCommitmentsContract.deploy(aztecRpcServer).send(); const receipt = await tx.getReceipt(); - contract = new PendingCommitmentsContract(receipt.contractAddress!, wallet); await tx.isMined(0, 0.1); await tx.getReceipt(); logger('L2 contract deployed'); + contract = await PendingCommitmentsContract.create(receipt.contractAddress!, wallet); return contract; }; diff --git a/yarn-project/end-to-end/src/e2e_public_token_contract.test.ts b/yarn-project/end-to-end/src/e2e_public_token_contract.test.ts index 8e1ee877b66..b8698f1b7ed 100644 --- a/yarn-project/end-to-end/src/e2e_public_token_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_public_token_contract.test.ts @@ -25,11 +25,11 @@ describe('e2e_public_token_contract', () => { logger(`Tx sent with hash ${await tx.getTxHash()}`); const receipt = await tx.getReceipt(); - contract = new PublicTokenContract(receipt.contractAddress!, wallet); await tx.isMined(0, 0.1); const txReceipt = await tx.getReceipt(); expect(txReceipt.status).toEqual(TxStatus.MINED); logger(`L2 contract deployed at ${receipt.contractAddress}`); + contract = await PublicTokenContract.create(receipt.contractAddress!, wallet); return { contract, tx, txReceipt }; }; diff --git a/yarn-project/end-to-end/src/e2e_zk_token_contract.test.ts b/yarn-project/end-to-end/src/e2e_zk_token_contract.test.ts index dc518695a03..8a6fcc3856e 100644 --- a/yarn-project/end-to-end/src/e2e_zk_token_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_zk_token_contract.test.ts @@ -35,13 +35,13 @@ describe('e2e_zk_token_contract', () => { const deployContract = async (initialBalance: bigint, owner: AztecAddress) => { logger(`Deploying L2 contract...`); - const tx = ZkTokenContract.deploy(aztecRpcServer, initialBalance, owner).send(); + const tx = ZkTokenContract.deploy(wallet, initialBalance, owner).send(); const receipt = await tx.getReceipt(); - contract = new ZkTokenContract(receipt.contractAddress!, wallet); await tx.isMined(0, 0.1); const minedReceipt = await tx.getReceipt(); expect(minedReceipt.status).toEqual(TxStatus.MINED); logger('L2 contract deployed'); + contract = await ZkTokenContract.create(receipt.contractAddress!, wallet); return contract; }; diff --git a/yarn-project/end-to-end/src/uniswap_trade_on_l1_from_l2.test.ts b/yarn-project/end-to-end/src/uniswap_trade_on_l1_from_l2.test.ts index 250c4347c72..f96b6fdeaa5 100644 --- a/yarn-project/end-to-end/src/uniswap_trade_on_l1_from_l2.test.ts +++ b/yarn-project/end-to-end/src/uniswap_trade_on_l1_from_l2.test.ts @@ -129,7 +129,7 @@ describe('uniswap_trade_on_l1_from_l2', () => { await tx.isMined(0, 0.1); const receipt = await tx.getReceipt(); expect(receipt.status).toEqual(TxStatus.MINED); - uniswapL2Contract = new UniswapContract(receipt.contractAddress!, wallet); + uniswapL2Contract = await UniswapContract.create(receipt.contractAddress!, wallet); await uniswapL2Contract.attach(uniswapPortalAddress); await uniswapPortal.write.initialize( diff --git a/yarn-project/end-to-end/src/utils.ts b/yarn-project/end-to-end/src/utils.ts index b04c53a291a..0d69d0dc9bf 100644 --- a/yarn-project/end-to-end/src/utils.ts +++ b/yarn-project/end-to-end/src/utils.ts @@ -425,8 +425,12 @@ export async function deployL2Contracts(wallet: Wallet, abis: ContractAbi[]) { const txs = await Promise.all(calls.map(c => c.send())); expect(every(await Promise.all(txs.map(tx => tx.isMined(0, 0.1))))).toBeTruthy(); const receipts = await Promise.all(txs.map(tx => tx.getReceipt())); - const contracts = zipWith(abis, receipts, (abi, receipt) => new Contract(receipt!.contractAddress!, abi!, wallet)); - contracts.forEach(c => logger(`L2 contract ${c.abi.name} deployed at ${c.address}`)); + const contracts = zipWith( + abis, + receipts, + async (abi, receipt) => await Contract.create(receipt!.contractAddress!, abi!, wallet), + ); + contracts.forEach(async c => logger(`L2 contract ${(await c).abi.name} deployed at ${(await c).address}`)); return contracts; } @@ -487,7 +491,7 @@ export async function deployAndInitializeNonNativeL2TokenContracts( await tx.isMined(0, 0.1); const receipt = await tx.getReceipt(); if (receipt.status !== TxStatus.MINED) throw new Error(`Tx status is ${receipt.status}`); - const l2Contract = new NonNativeTokenContract(receipt.contractAddress!, wallet); + const l2Contract = await NonNativeTokenContract.create(receipt.contractAddress!, wallet); await l2Contract.attach(tokenPortalAddress); const l2TokenAddress = l2Contract.address.toString() as `0x${string}`; diff --git a/yarn-project/ethereum/src/index.ts b/yarn-project/ethereum/src/index.ts index e3ba499d6bc..6279349059c 100644 --- a/yarn-project/ethereum/src/index.ts +++ b/yarn-project/ethereum/src/index.ts @@ -8,8 +8,8 @@ export * from './deploy_l1_contracts.js'; /** * Helper function to create an instance of Aztec Chain from an rpc url and api key. - * @param rpcUrl - The rpc url of the chain or a chain identifer (e.g. 'testnet') - * @param apiKey - An optional apikey for the chain client. + * @param rpcUrl - The rpc url of the chain or a chain identifier (e.g. 'testnet') + * @param apiKey - An optional API key for the chain client. */ export function createEthereumChain(rpcUrl: string, apiKey?: string) { if (rpcUrl === 'testnet') { diff --git a/yarn-project/noir-compiler/src/typegen/index.ts b/yarn-project/noir-compiler/src/typegen/index.ts index c3825eabcb9..6472c349f17 100644 --- a/yarn-project/noir-compiler/src/typegen/index.ts +++ b/yarn-project/noir-compiler/src/typegen/index.ts @@ -51,10 +51,11 @@ function generateMethod(entry: FunctionAbi) { * Generates the constructor by supplying the ABI to the parent class so the user doesn't have to. * @param name - Name of the contract to derive the ABI name from. * @returns A constructor method. + * @remarks The constructor is private because we want to force the user to use the create method. */ function generateConstructor(name: string) { return ` - constructor( + private constructor( /** The deployed contract's address. */ address: AztecAddress, /** The wallet. */ @@ -65,6 +66,33 @@ function generateConstructor(name: string) { `; } +/** + * Generates the create method for this contract. + * @param name - Name of the contract to derive the ABI name from. + * @returns A create method. + * @remarks We don't use constructor directly because of the async `wallet.isContractDeployed` check. + */ +function generateCreate(name: string) { + return ` + /** + * Creates a contract instance. + * @param address - The deployed contract's address. + * @param wallet - The wallet to use when interacting with the contract. + * @returns A promise that resolves to a new Contract instance. + */ + public static async create( + /** The deployed contract's address. */ + address: AztecAddress, + /** The wallet. */ + wallet: Wallet, + ) { + if (!(await wallet.isContractDeployed(address))) { + throw new Error('Contract ' + address.toString() + ' is not deployed'); + } + return new ${name}Contract(address, wallet); + }`; +} + /** * Generates a deploy method for this contract. * @param input - ABI of the contract. @@ -118,6 +146,7 @@ export function generateType(input: ContractAbi, abiImportPath?: string) { const methods = compact(input.functions.filter(f => f.name !== 'constructor').map(generateMethod)); const deploy = abiImportPath && generateDeploy(input); const ctor = abiImportPath && generateConstructor(input.name); + const create = abiImportPath && generateCreate(input.name); const abiImportStatement = abiImportPath && `import { ${input.name}ContractAbi } from '${abiImportPath}';`; const abiGetter = abiImportPath && generateAbiGetter(input.name); @@ -125,7 +154,7 @@ export function generateType(input: ContractAbi, abiImportPath?: string) { /* Autogenerated file, do not edit! */ /* eslint-disable */ -import { AztecAddress, Contract, ContractFunctionInteraction, ContractMethod, DeployMethod, Wallet } from '@aztec/aztec.js'; +import { AztecAddress, ContractBase, ContractFunctionInteraction, ContractMethod, DeployMethod, Wallet } from '@aztec/aztec.js'; import { Fr, Point } from '@aztec/foundation/fields'; import { AztecRPC, PublicKey } from '@aztec/types'; import { ContractAbi } from '@aztec/foundation/abi'; @@ -134,9 +163,11 @@ ${abiImportStatement} /** * Type-safe interface for contract ${input.name}; */ -export class ${input.name}Contract extends Contract { +export class ${input.name}Contract extends ContractBase { ${ctor} + ${create} + ${deploy} ${abiGetter}