From c03c654631d6e70bb3f2c6abcd9fa046ce8554b8 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 6 Oct 2023 10:10:45 -0300 Subject: [PATCH] feat(aztec.js): Remove attach method (#2715) Removes the attach method in favor of supplying the portal contract address at construction time in the Contract class. This allows us to implement the DeployedContract interface, which is useful for directly registering Contract instances in a pxe. The attach method used to register the contract with the portal address in the pxe, though this is already handled during deployment. What we may need is a new method for registering a contract on the pxe via aztecjs without having to manually call addContract, but that can go in another PR. --- .../aztec-sandbox/src/examples/util.ts | 10 ++---- .../aztec.js/src/contract/contract.test.ts | 35 +----------------- .../aztec.js/src/contract/contract.ts | 8 ++++- .../aztec.js/src/contract/contract_base.ts | 36 ++++--------------- .../src/contract_deployer/deploy_sent_tx.ts | 2 +- .../boxes/blank-react/src/artifacts/blank.ts | 24 ++++--------- .../boxes/blank/src/artifacts/blank.ts | 24 ++++--------- .../src/artifacts/private_token.ts | 24 ++++--------- .../src/uniswap_trade_on_l1_from_l2.test.ts | 1 - yarn-project/canary/src/utils.ts | 15 +++----- yarn-project/end-to-end/src/fixtures/utils.ts | 29 +++++---------- .../src/uniswap_trade_on_l1_from_l2.test.ts | 1 - .../src/contract-interface-gen/typescript.ts | 15 +++----- 13 files changed, 54 insertions(+), 170 deletions(-) diff --git a/yarn-project/aztec-sandbox/src/examples/util.ts b/yarn-project/aztec-sandbox/src/examples/util.ts index d3462673cce..581e16514fd 100644 --- a/yarn-project/aztec-sandbox/src/examples/util.ts +++ b/yarn-project/aztec-sandbox/src/examples/util.ts @@ -46,13 +46,9 @@ export async function deployAndInitializeNonNativeL2TokenContracts( }); // deploy l2 contract and attach to portal - const tx = NonNativeTokenContract.deploy(wallet, initialBalance, owner).send({ - portalContract: tokenPortalAddress, - }); - await tx.isMined({ interval: 0.1 }); - const receipt = await tx.getReceipt(); - const l2Contract = await NonNativeTokenContract.at(receipt.contractAddress!, wallet); - await l2Contract.attach(tokenPortalAddress); + const l2Contract = await NonNativeTokenContract.deploy(wallet, initialBalance, owner) + .send({ portalContract: tokenPortalAddress }) + .deployed(); const l2TokenAddress = l2Contract.address.toString() as `0x${string}`; // initialize portal diff --git a/yarn-project/aztec.js/src/contract/contract.test.ts b/yarn-project/aztec.js/src/contract/contract.test.ts index 3faef893055..541a9c588b7 100644 --- a/yarn-project/aztec.js/src/contract/contract.test.ts +++ b/yarn-project/aztec.js/src/contract/contract.test.ts @@ -1,17 +1,7 @@ import { AztecAddress, CompleteAddress, EthAddress } from '@aztec/circuits.js'; import { L1ContractAddresses } from '@aztec/ethereum'; import { ABIParameterVisibility, ContractAbi, FunctionType } from '@aztec/foundation/abi'; -import { - DeployedContract, - ExtendedContractData, - NodeInfo, - Tx, - TxExecutionRequest, - TxHash, - TxReceipt, - randomContractAbi, - randomDeployedContract, -} from '@aztec/types'; +import { ExtendedContractData, NodeInfo, Tx, TxExecutionRequest, TxHash, TxReceipt } from '@aztec/types'; import { MockProxy, mock } from 'jest-mock-extended'; @@ -155,27 +145,4 @@ describe('Contract Class', () => { expect(() => fooContract.methods.bar().view()).toThrow(); expect(() => fooContract.methods.baz().view()).toThrow(); }); - - it('should add contract and dependencies to PXE', async () => { - const entry: DeployedContract = { - abi: randomContractAbi(), - completeAddress: resolvedExtendedContractData.getCompleteAddress(), - portalContract: EthAddress.random(), - }; - const contract = await Contract.at(entry.completeAddress.address, entry.abi, wallet); - - { - await contract.attach(entry.portalContract); - expect(wallet.addContracts).toHaveBeenCalledTimes(1); - expect(wallet.addContracts).toHaveBeenCalledWith([entry]); - wallet.addContracts.mockClear(); - } - - { - const dependencies = [await randomDeployedContract(), await randomDeployedContract()]; - await contract.attach(entry.portalContract, dependencies); - expect(wallet.addContracts).toHaveBeenCalledTimes(1); - expect(wallet.addContracts).toHaveBeenCalledWith([entry, ...dependencies]); - } - }); }); diff --git a/yarn-project/aztec.js/src/contract/contract.ts b/yarn-project/aztec.js/src/contract/contract.ts index 4fa1a4f9ff7..9249184c491 100644 --- a/yarn-project/aztec.js/src/contract/contract.ts +++ b/yarn-project/aztec.js/src/contract/contract.ts @@ -19,6 +19,7 @@ export class Contract extends ContractBase { * @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. + * @param portalContract - The portal contract address on L1, if any. * @returns A promise that resolves to a new Contract instance. */ public static async at(address: AztecAddress, abi: ContractAbi, wallet: Wallet): Promise { @@ -26,7 +27,12 @@ export class Contract extends ContractBase { if (extendedContractData === undefined) { throw new Error('Contract ' + address.toString() + ' is not deployed'); } - return new Contract(extendedContractData.getCompleteAddress(), abi, wallet); + return new Contract( + extendedContractData.getCompleteAddress(), + abi, + wallet, + extendedContractData.contractData.portalContractAddress, + ); } /** diff --git a/yarn-project/aztec.js/src/contract/contract_base.ts b/yarn-project/aztec.js/src/contract/contract_base.ts index b408d386bf5..4ac143fc47a 100644 --- a/yarn-project/aztec.js/src/contract/contract_base.ts +++ b/yarn-project/aztec.js/src/contract/contract_base.ts @@ -19,25 +19,21 @@ export type ContractMethod = ((...args: any[]) => ContractFunctionInteraction) & /** * Abstract implementation of a contract extended by the Contract class and generated contract types. */ -export class ContractBase { +export class ContractBase implements DeployedContract { /** * An object containing contract methods mapped to their respective names. */ public methods: { [name: string]: ContractMethod } = {}; protected constructor( - /** - * The deployed contract's complete address. - */ + /** The deployed contract's complete address. */ public readonly completeAddress: CompleteAddress, - /** - * The Application Binary Interface for the contract. - */ + /** The Application Binary Interface for the contract. */ public readonly abi: ContractAbi, - /** - * The wallet. - */ + /** The wallet used for interacting with this contract. */ protected wallet: Wallet, + /** The portal contract address on L1, if any. */ + public readonly portalContract: EthAddress, ) { abi.functions.forEach((f: FunctionAbi) => { const interactionFunction = (...args: any[]) => { @@ -69,24 +65,6 @@ export class ContractBase { * @returns A new contract instance. */ public withWallet(wallet: Wallet): this { - return new ContractBase(this.completeAddress, this.abi, wallet) as this; - } - - /** - * 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 PXE. - * 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 PXE. - */ - public attach(portalContract: EthAddress, dependencies: DeployedContract[] = []) { - const deployedContract: DeployedContract = { - abi: this.abi, - completeAddress: this.completeAddress, - portalContract, - }; - return this.wallet.addContracts([deployedContract, ...dependencies]); + return new ContractBase(this.completeAddress, this.abi, wallet, this.portalContract) as this; } } diff --git a/yarn-project/aztec.js/src/contract_deployer/deploy_sent_tx.ts b/yarn-project/aztec.js/src/contract_deployer/deploy_sent_tx.ts index a442f3bee3c..8b0fc0a2a0f 100644 --- a/yarn-project/aztec.js/src/contract_deployer/deploy_sent_tx.ts +++ b/yarn-project/aztec.js/src/contract_deployer/deploy_sent_tx.ts @@ -19,7 +19,7 @@ export type DeployTxReceipt = FieldsO /** * A contract deployment transaction sent to the network, extending SentTx with methods to create a contract instance. */ -export class DeploySentTx extends SentTx { +export class DeploySentTx extends SentTx { constructor(private abi: ContractAbi, wallet: PXE | Wallet, txHashPromise: Promise) { super(wallet, txHashPromise); } diff --git a/yarn-project/boxes/blank-react/src/artifacts/blank.ts b/yarn-project/boxes/blank-react/src/artifacts/blank.ts index 75cba5b04dd..672d4ba566a 100644 --- a/yarn-project/boxes/blank-react/src/artifacts/blank.ts +++ b/yarn-project/boxes/blank-react/src/artifacts/blank.ts @@ -4,10 +4,12 @@ import { AztecAddress, CompleteAddress, + Contract, ContractBase, ContractFunctionInteraction, ContractMethod, DeployMethod, + EthAddress, FieldLike, AztecAddressLike, EthAddressLike, @@ -23,13 +25,8 @@ export const BlankContractAbi = BlankContractAbiJson as ContractAbi; * Type-safe interface for contract Blank; */ export class BlankContract extends ContractBase { - private constructor( - /** The deployed contract's complete address. */ - completeAddress: CompleteAddress, - /** The wallet. */ - wallet: Wallet, - ) { - super(completeAddress, BlankContractAbi, wallet); + private constructor(completeAddress: CompleteAddress, wallet: Wallet, portalContract = EthAddress.ZERO) { + super(completeAddress, BlankContractAbi, wallet, portalContract); } /** @@ -38,17 +35,8 @@ export class BlankContract extends ContractBase { * @param wallet - The wallet to use when interacting with the contract. * @returns A promise that resolves to a new Contract instance. */ - public static async at( - /** The deployed contract's address. */ - address: AztecAddress, - /** The wallet. */ - wallet: Wallet, - ) { - const extendedContractData = await wallet.getExtendedContractData(address); - if (extendedContractData === undefined) { - throw new Error('Contract ' + address.toString() + ' is not deployed'); - } - return new BlankContract(extendedContractData.getCompleteAddress(), wallet); + public static async at(address: AztecAddress, wallet: Wallet) { + return Contract.at(address, BlankContract.abi, wallet) as Promise; } /** diff --git a/yarn-project/boxes/blank/src/artifacts/blank.ts b/yarn-project/boxes/blank/src/artifacts/blank.ts index 75cba5b04dd..672d4ba566a 100644 --- a/yarn-project/boxes/blank/src/artifacts/blank.ts +++ b/yarn-project/boxes/blank/src/artifacts/blank.ts @@ -4,10 +4,12 @@ import { AztecAddress, CompleteAddress, + Contract, ContractBase, ContractFunctionInteraction, ContractMethod, DeployMethod, + EthAddress, FieldLike, AztecAddressLike, EthAddressLike, @@ -23,13 +25,8 @@ export const BlankContractAbi = BlankContractAbiJson as ContractAbi; * Type-safe interface for contract Blank; */ export class BlankContract extends ContractBase { - private constructor( - /** The deployed contract's complete address. */ - completeAddress: CompleteAddress, - /** The wallet. */ - wallet: Wallet, - ) { - super(completeAddress, BlankContractAbi, wallet); + private constructor(completeAddress: CompleteAddress, wallet: Wallet, portalContract = EthAddress.ZERO) { + super(completeAddress, BlankContractAbi, wallet, portalContract); } /** @@ -38,17 +35,8 @@ export class BlankContract extends ContractBase { * @param wallet - The wallet to use when interacting with the contract. * @returns A promise that resolves to a new Contract instance. */ - public static async at( - /** The deployed contract's address. */ - address: AztecAddress, - /** The wallet. */ - wallet: Wallet, - ) { - const extendedContractData = await wallet.getExtendedContractData(address); - if (extendedContractData === undefined) { - throw new Error('Contract ' + address.toString() + ' is not deployed'); - } - return new BlankContract(extendedContractData.getCompleteAddress(), wallet); + public static async at(address: AztecAddress, wallet: Wallet) { + return Contract.at(address, BlankContract.abi, wallet) as Promise; } /** diff --git a/yarn-project/boxes/private-token/src/artifacts/private_token.ts b/yarn-project/boxes/private-token/src/artifacts/private_token.ts index 1d2e2900220..4812265609d 100644 --- a/yarn-project/boxes/private-token/src/artifacts/private_token.ts +++ b/yarn-project/boxes/private-token/src/artifacts/private_token.ts @@ -4,10 +4,12 @@ import { AztecAddress, CompleteAddress, + Contract, ContractBase, ContractFunctionInteraction, ContractMethod, DeployMethod, + EthAddress, FieldLike, AztecAddressLike, EthAddressLike, @@ -23,13 +25,8 @@ export const PrivateTokenContractAbi = PrivateTokenContractAbiJson as ContractAb * Type-safe interface for contract PrivateToken; */ export class PrivateTokenContract extends ContractBase { - private constructor( - /** The deployed contract's complete address. */ - completeAddress: CompleteAddress, - /** The wallet. */ - wallet: Wallet, - ) { - super(completeAddress, PrivateTokenContractAbi, wallet); + private constructor(completeAddress: CompleteAddress, wallet: Wallet, portalContract = EthAddress.ZERO) { + super(completeAddress, PrivateTokenContractAbi, wallet, portalContract); } /** @@ -38,17 +35,8 @@ export class PrivateTokenContract extends ContractBase { * @param wallet - The wallet to use when interacting with the contract. * @returns A promise that resolves to a new Contract instance. */ - public static async at( - /** The deployed contract's address. */ - address: AztecAddress, - /** The wallet. */ - wallet: Wallet, - ) { - const extendedContractData = await wallet.getExtendedContractData(address); - if (extendedContractData === undefined) { - throw new Error('Contract ' + address.toString() + ' is not deployed'); - } - return new PrivateTokenContract(extendedContractData.getCompleteAddress(), wallet); + public static async at(address: AztecAddress, wallet: Wallet) { + return Contract.at(address, PrivateTokenContract.abi, wallet) as Promise; } /** diff --git a/yarn-project/canary/src/uniswap_trade_on_l1_from_l2.test.ts b/yarn-project/canary/src/uniswap_trade_on_l1_from_l2.test.ts index 1b6c4fcb6a1..a478d20a908 100644 --- a/yarn-project/canary/src/uniswap_trade_on_l1_from_l2.test.ts +++ b/yarn-project/canary/src/uniswap_trade_on_l1_from_l2.test.ts @@ -115,7 +115,6 @@ async function deployAllContracts( const uniswapL2Contract = await UniswapContract.deploy(ownerWallet) .send({ portalContract: uniswapPortalAddress }) .deployed(); - await uniswapL2Contract.attach(uniswapPortalAddress); await uniswapPortal.write.initialize( [l1ContractsAddresses!.registryAddress.toString(), uniswapL2Contract.address.toString()], diff --git a/yarn-project/canary/src/utils.ts b/yarn-project/canary/src/utils.ts index 0bcc6926632..4a05c6713f5 100644 --- a/yarn-project/canary/src/utils.ts +++ b/yarn-project/canary/src/utils.ts @@ -1,4 +1,4 @@ -import { AztecAddress, EthAddress, Fr, TxStatus, Wallet } from '@aztec/aztec.js'; +import { AztecAddress, EthAddress, TxStatus, Wallet } from '@aztec/aztec.js'; import { PortalERC20Abi, PortalERC20Bytecode, TokenPortalAbi, TokenPortalBytecode } from '@aztec/l1-artifacts'; import { TokenBridgeContract, TokenContract } from '@aztec/noir-contracts/types'; @@ -72,15 +72,10 @@ export async function deployAndInitializeTokenAndBridgeContracts( const token = await TokenContract.at(deployReceipt.contractAddress!, wallet); // deploy l2 token bridge and attach to the portal - const bridgeTx = TokenBridgeContract.deploy(wallet, token.address).send({ - portalContract: tokenPortalAddress, - contractAddressSalt: Fr.random(), - }); - - const bridgeReceipt = await bridgeTx.wait(); - const bridge = await TokenBridgeContract.at(bridgeReceipt.contractAddress!, wallet); - await bridge.attach(tokenPortalAddress); - const bridgeAddress = bridge.address.toString() as `0x${string}`; + const bridge = await TokenBridgeContract.deploy(wallet, token.address) + .send({ portalContract: tokenPortalAddress }) + .deployed(); + const bridgeAddress = bridge.address.toString(); // now we wait for the txs to be mined. This way we send all tx in the same rollup. if ((await token.methods.admin().view()) !== owner.toBigInt()) throw new Error(`Token admin is not ${owner}`); diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index 35b5693b385..ea37f3056c5 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -17,7 +17,6 @@ import { deployL1Contract, deployL1Contracts, } from '@aztec/ethereum'; -import { Fr } from '@aztec/foundation/fields'; import { DebugLogger, createDebugLogger } from '@aztec/foundation/log'; import { retryUntil } from '@aztec/foundation/retry'; import { @@ -412,15 +411,9 @@ export async function deployAndInitializeTokenAndBridgeContracts( const token = await TokenContract.at(deployReceipt.contractAddress!, wallet); // deploy l2 token bridge and attach to the portal - const bridgeTx = TokenBridgeContract.deploy(wallet, token.address).send({ - portalContract: tokenPortalAddress, - contractAddressSalt: Fr.random(), - }); - const bridgeReceipt = await bridgeTx.wait(); - if (bridgeReceipt.status !== TxStatus.MINED) throw new Error(`Deploy bridge tx status is ${bridgeReceipt.status}`); - const bridge = await TokenBridgeContract.at(bridgeReceipt.contractAddress!, wallet); - await bridge.attach(tokenPortalAddress); - const bridgeAddress = bridge.address.toString() as `0x${string}`; + const bridge = await TokenBridgeContract.deploy(wallet, token.address) + .send({ portalContract: tokenPortalAddress }) + .deployed(); if ((await token.methods.admin().view()) !== owner.toBigInt()) throw new Error(`Token admin is not ${owner}`); @@ -436,7 +429,7 @@ export async function deployAndInitializeTokenAndBridgeContracts( // initialize portal await tokenPortal.write.initialize( - [rollupRegistryAddress.toString(), underlyingERC20Address.toString(), bridgeAddress], + [rollupRegistryAddress.toString(), underlyingERC20Address.toString(), bridge.address.toString()], {} as any, ); @@ -485,16 +478,10 @@ export async function deployAndInitializeNonNativeL2TokenContracts( }); // deploy l2 contract and attach to portal - const tx = NonNativeTokenContract.deploy(wallet, initialBalance, owner).send({ - portalContract: tokenPortalAddress, - contractAddressSalt: Fr.random(), - }); - await tx.isMined({ interval: 0.1 }); - const receipt = await tx.getReceipt(); - if (receipt.status !== TxStatus.MINED) throw new Error(`Tx status is ${receipt.status}`); - const l2Contract = await NonNativeTokenContract.at(receipt.contractAddress!, wallet); - await l2Contract.attach(tokenPortalAddress); - const l2TokenAddress = l2Contract.address.toString() as `0x${string}`; + const l2Contract = await NonNativeTokenContract.deploy(wallet, initialBalance, owner) + .send({ portalContract: tokenPortalAddress }) + .deployed(); + const l2TokenAddress = l2Contract.address.toString(); // initialize portal await tokenPortal.write.initialize( 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 b5e8481d265..8e26fb3a817 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 @@ -124,7 +124,6 @@ describe('uniswap_trade_on_l1_from_l2', () => { uniswapL2Contract = await UniswapContract.deploy(ownerWallet) .send({ portalContract: uniswapPortalAddress }) .deployed(); - await uniswapL2Contract.attach(uniswapPortalAddress); await uniswapPortal.write.initialize( [deployL1ContractsValues!.l1ContractAddresses.registryAddress!.toString(), uniswapL2Contract.address.toString()], diff --git a/yarn-project/noir-compiler/src/contract-interface-gen/typescript.ts b/yarn-project/noir-compiler/src/contract-interface-gen/typescript.ts index 31ddb4ad10b..210776234ba 100644 --- a/yarn-project/noir-compiler/src/contract-interface-gen/typescript.ts +++ b/yarn-project/noir-compiler/src/contract-interface-gen/typescript.ts @@ -91,12 +91,11 @@ function generateDeploy(input: ContractAbi) { function generateConstructor(name: string) { return ` private constructor( - /** The deployed contract's complete address. */ completeAddress: CompleteAddress, - /** The wallet. */ wallet: Wallet, + portalContract = EthAddress.ZERO ) { - super(completeAddress, ${name}ContractAbi, wallet); + super(completeAddress, ${name}ContractAbi, wallet, portalContract); } `; } @@ -116,16 +115,10 @@ function generateAt(name: string) { * @returns A promise that resolves to a new Contract instance. */ public static async at( - /** The deployed contract's address. */ address: AztecAddress, - /** The wallet. */ wallet: Wallet, ) { - const extendedContractData = await wallet.getExtendedContractData(address); - if (extendedContractData === undefined) { - throw new Error('Contract ' + address.toString() + ' is not deployed'); - } - return new ${name}Contract(extendedContractData.getCompleteAddress(), wallet); + return Contract.at(address, ${name}Contract.abi, wallet) as Promise<${name}Contract>; }`; } @@ -178,7 +171,7 @@ export function generateTypescriptContractInterface(input: ContractAbi, abiImpor /* Autogenerated file, do not edit! */ /* eslint-disable */ -import { AztecAddress, CompleteAddress, ContractBase, ContractFunctionInteraction, ContractMethod, DeployMethod, FieldLike, AztecAddressLike, EthAddressLike, Wallet } from '@aztec/aztec.js'; +import { AztecAddress, CompleteAddress, Contract, ContractBase, ContractFunctionInteraction, ContractMethod, DeployMethod, EthAddress, FieldLike, AztecAddressLike, EthAddressLike, Wallet } from '@aztec/aztec.js'; import { Fr, Point } from '@aztec/foundation/fields'; import { PXE, PublicKey } from '@aztec/types'; import { ContractAbi } from '@aztec/foundation/abi';