Skip to content

Commit

Permalink
feat(aztec.js): Remove attach method (#2715)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
spalladino authored Oct 6, 2023
1 parent e92154b commit c03c654
Show file tree
Hide file tree
Showing 13 changed files with 54 additions and 170 deletions.
10 changes: 3 additions & 7 deletions yarn-project/aztec-sandbox/src/examples/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 1 addition & 34 deletions yarn-project/aztec.js/src/contract/contract.test.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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]);
}
});
});
8 changes: 7 additions & 1 deletion yarn-project/aztec.js/src/contract/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,20 @@ 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<Contract> {
const extendedContractData = await wallet.getExtendedContractData(address);
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,
);
}

/**
Expand Down
36 changes: 7 additions & 29 deletions yarn-project/aztec.js/src/contract/contract_base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[]) => {
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export type DeployTxReceipt<TContract extends ContractBase = Contract> = FieldsO
/**
* A contract deployment transaction sent to the network, extending SentTx with methods to create a contract instance.
*/
export class DeploySentTx<TContract extends ContractBase = Contract> extends SentTx {
export class DeploySentTx<TContract extends Contract = Contract> extends SentTx {
constructor(private abi: ContractAbi, wallet: PXE | Wallet, txHashPromise: Promise<TxHash>) {
super(wallet, txHashPromise);
}
Expand Down
24 changes: 6 additions & 18 deletions yarn-project/boxes/blank-react/src/artifacts/blank.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import {
AztecAddress,
CompleteAddress,
Contract,
ContractBase,
ContractFunctionInteraction,
ContractMethod,
DeployMethod,
EthAddress,
FieldLike,
AztecAddressLike,
EthAddressLike,
Expand All @@ -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);
}

/**
Expand All @@ -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<BlankContract>;
}

/**
Expand Down
24 changes: 6 additions & 18 deletions yarn-project/boxes/blank/src/artifacts/blank.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import {
AztecAddress,
CompleteAddress,
Contract,
ContractBase,
ContractFunctionInteraction,
ContractMethod,
DeployMethod,
EthAddress,
FieldLike,
AztecAddressLike,
EthAddressLike,
Expand All @@ -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);
}

/**
Expand All @@ -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<BlankContract>;
}

/**
Expand Down
24 changes: 6 additions & 18 deletions yarn-project/boxes/private-token/src/artifacts/private_token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import {
AztecAddress,
CompleteAddress,
Contract,
ContractBase,
ContractFunctionInteraction,
ContractMethod,
DeployMethod,
EthAddress,
FieldLike,
AztecAddressLike,
EthAddressLike,
Expand All @@ -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);
}

/**
Expand All @@ -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<PrivateTokenContract>;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()],
Expand Down
15 changes: 5 additions & 10 deletions yarn-project/canary/src/utils.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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}`);
Expand Down
29 changes: 8 additions & 21 deletions yarn-project/end-to-end/src/fixtures/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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}`);

Expand All @@ -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,
);

Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()],
Expand Down
Loading

0 comments on commit c03c654

Please sign in to comment.