Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: throw when creating an instance of non-existent contract #1300

Merged
merged 10 commits into from
Aug 1, 2023
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