Skip to content

Commit

Permalink
feat: throw when creating an instance of non-existent contract (#1300)
Browse files Browse the repository at this point in the history
# Description

Fixes #1225
  • Loading branch information
benesjan authored Aug 1, 2023
1 parent cb2d210 commit 5353ed0
Show file tree
Hide file tree
Showing 26 changed files with 176 additions and 113 deletions.
4 changes: 2 additions & 2 deletions circuits/cpp/src/aztec3/circuits/abis/c_bind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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!**
*
Expand All @@ -78,7 +78,7 @@ template <size_t TREE_HEIGHT> void rightfill_with_zeroleaves(std::vector<NT::fr>
} // 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);
Expand Down
3 changes: 3 additions & 0 deletions yarn-project/archiver/src/archiver/archiver_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ContractData | undefined> {
if (contractAddress.isZero()) {
return Promise.resolve(undefined);
}
for (const block of this.l2Blocks) {
for (const contractData of block.newContractData) {
if (contractData.contractAddress.equals(contractAddress)) {
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/aztec-cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -330,7 +330,7 @@ async function main() {

main()
.then(() => {
logger('Finished running successfuly.');
logger('Finished running successfully.');
process.exit(0);
})
.catch(err => {
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/aztec-sandbox/src/examples/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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}`;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/aztec.js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand All @@ -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 });
Expand Down
15 changes: 8 additions & 7 deletions yarn-project/aztec.js/src/contract/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ describe('Contract Class', () => {
beforeEach(() => {
wallet = mock<Wallet>();
wallet.createAuthenticatedTxRequest.mockResolvedValue(mockTxRequest);
wallet.isContractDeployed.mockResolvedValue(true);
wallet.sendTx.mockResolvedValue(mockTxHash);
wallet.viewTx.mockResolvedValue(mockViewResultValue);
wallet.getTxReceipt.mockResolvedValue(mockTxReceipt);
Expand All @@ -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({
Expand All @@ -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,
});
Expand All @@ -128,24 +129,24 @@ 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,
}),
).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);
Expand Down
79 changes: 13 additions & 66 deletions yarn-project/aztec.js/src/contract/contract.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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<Contract> {
if (!(await wallet.isContractDeployed(address))) {
throw new Error('Contract ' + address.toString() + ' is not deployed');
}
return new Contract(address, abi, wallet);
}
}
77 changes: 77 additions & 0 deletions yarn-project/aztec.js/src/contract/contract_base.ts
Original file line number Diff line number Diff line change
@@ -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]);
}
}
1 change: 1 addition & 0 deletions yarn-project/aztec.js/src/contract/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './contract.js';
export * from './contract_function_interaction.js';
export * from './sent_tx.js';
export * from './contract_base.js';
4 changes: 2 additions & 2 deletions yarn-project/aztec.js/src/contract_deployer/deploy_method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
8 changes: 4 additions & 4 deletions yarn-project/end-to-end/src/e2e_2_rpc_servers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);

Expand Down
4 changes: 2 additions & 2 deletions yarn-project/end-to-end/src/e2e_account_contracts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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/,
);
Expand Down
Loading

0 comments on commit 5353ed0

Please sign in to comment.