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(noir): autogenerate contract interface for calling from external contracts #1487

Merged
merged 16 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion yarn-project/acir-simulator/src/client/private_execution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
computeContractAddressFromPartial,
computeSecretMessageHash,
computeUniqueCommitment,
computeVarArgsHash,
siloCommitment,
} from '@aztec/circuits.js/abis';
import { pedersenPlookupCommitInputs } from '@aztec/circuits.js/barretenberg';
Expand All @@ -32,6 +33,7 @@ import { DebugLogger, createDebugLogger } from '@aztec/foundation/log';
import { AppendOnlyTree, Pedersen, StandardTree, newTree } from '@aztec/merkle-tree';
import {
ChildContractAbi,
ImportTestContractAbi,
NonNativeTokenContractAbi,
ParentContractAbi,
PendingCommitmentsContractAbi,
Expand Down Expand Up @@ -641,7 +643,50 @@ describe('Private Execution test suite', () => {
});
});

describe('consuming Messages', () => {
describe('nested calls through autogenerated interface', () => {
let args: any[];
let argsHash: Fr;
let testCodeGenAbi: FunctionAbi;

beforeAll(async () => {
// These args should match the ones hardcoded in importer contract
const dummyNote = { amount: 1, secretHash: 2 };
const deepStruct = { aField: 1, aBool: true, aNote: dummyNote, manyNotes: [dummyNote, dummyNote, dummyNote] };
args = [1, true, 1, [1, 2], dummyNote, deepStruct];
testCodeGenAbi = TestContractAbi.functions.find(f => f.name === 'testCodeGen')!;
const serialisedArgs = encodeArguments(testCodeGenAbi, args);
argsHash = await computeVarArgsHash(await CircuitsWasm.get(), serialisedArgs);
});

it('test function should be directly callable', async () => {
logger(`Calling testCodeGen function`);
const result = await runSimulator({ args, abi: testCodeGenAbi });

expect(result.callStackItem.publicInputs.returnValues[0]).toEqual(argsHash);
});

it('test function should be callable through autogenerated interface', async () => {
const importerAddress = AztecAddress.random();
const testAddress = AztecAddress.random();
const parentAbi = ImportTestContractAbi.functions.find(f => f.name === 'main')!;
const testCodeGenSelector = generateFunctionSelector(testCodeGenAbi.name, testCodeGenAbi.parameters);

oracle.getFunctionABI.mockResolvedValue(testCodeGenAbi);
oracle.getPortalContractAddress.mockResolvedValue(EthAddress.ZERO);

logger(`Calling importer main function`);
const args = [testAddress];
const result = await runSimulator({ args, abi: parentAbi, origin: importerAddress });

expect(result.callStackItem.publicInputs.returnValues[0]).toEqual(argsHash);
expect(oracle.getFunctionABI.mock.calls[0]).toEqual([testAddress, testCodeGenSelector]);
expect(oracle.getPortalContractAddress.mock.calls[0]).toEqual([testAddress]);
expect(result.nestedExecutions).toHaveLength(1);
expect(result.nestedExecutions[0].callStackItem.publicInputs.returnValues[0]).toEqual(argsHash);
});
});

describe('consuming messages', () => {
const contractAddress = defaultContractAddress;
const recipientPk = PrivateKey.fromString('0c9ed344548e8f9ba8aa3c9f8651eaa2853130f6c1e9c050ccf198f7ea18a7ec');

Expand Down
2 changes: 1 addition & 1 deletion yarn-project/aztec.js/src/abis/ecdsa_account_contract.json

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

293 changes: 162 additions & 131 deletions yarn-project/end-to-end/src/e2e_nested_contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ContractAbi } from '@aztec/foundation/abi';
import { DebugLogger } from '@aztec/foundation/log';
import { toBigInt } from '@aztec/foundation/serialize';
import { ChildContractAbi, ParentContractAbi } from '@aztec/noir-contracts/artifacts';
import { ChildContract, ParentContract } from '@aztec/noir-contracts/types';
import { ChildContract, ImportTestContract, ParentContract, TestContract } from '@aztec/noir-contracts/types';
import { AztecRPC, TxStatus } from '@aztec/types';

import { setup } from './fixtures/utils.js';
Expand All @@ -17,14 +17,8 @@ describe('e2e_nested_contract', () => {
let accounts: AztecAddress[];
let logger: DebugLogger;

let parentContract: ParentContract;
let childContract: ChildContract;

beforeEach(async () => {
({ aztecNode, aztecRpcServer, accounts, wallet, logger } = await setup());

parentContract = (await deployContract(ParentContractAbi)) as ParentContract;
childContract = (await deployContract(ChildContractAbi)) as ChildContract;
}, 100_000);

afterEach(async () => {
Expand All @@ -34,129 +28,166 @@ describe('e2e_nested_contract', () => {
}
});

const deployContract = async (abi: ContractAbi) => {
logger(`Deploying L2 contract ${abi.name}...`);
const deployer = new ContractDeployer(abi, aztecRpcServer);
const tx = deployer.deploy().send();

await tx.isMined({ interval: 0.1 });

const receipt = await tx.getReceipt();
const contract = await Contract.create(receipt.contractAddress!, abi, wallet);
logger(`L2 contract ${abi.name} deployed at ${contract.address}`);
return contract;
};

const addressToField = (address: AztecAddress): bigint => Fr.fromBuffer(address.toBuffer()).value;

const getChildStoredValue = (child: { address: AztecAddress }) =>
aztecRpcServer.getPublicStorageAt(child.address, new Fr(1)).then(x => toBigInt(x!));

/**
* Milestone 3.
*/
it('performs nested calls', async () => {
const tx = parentContract.methods
.entryPoint(childContract.address, Fr.fromBuffer(childContract.methods.value.selector))
.send({ origin: accounts[0] });

await tx.isMined({ interval: 0.1 });
const receipt = await tx.getReceipt();

expect(receipt.status).toBe(TxStatus.MINED);
}, 100_000);

it('performs public nested calls', async () => {
const tx = parentContract.methods
.pubEntryPoint(childContract.address, Fr.fromBuffer(childContract.methods.pubValue.selector), 42n)
.send({ origin: accounts[0] });

await tx.isMined({ interval: 0.1 });
const receipt = await tx.getReceipt();

expect(receipt.status).toBe(TxStatus.MINED);
}, 100_000);

it('enqueues a single public call', async () => {
const tx = parentContract.methods
.enqueueCallToChild(childContract.address, Fr.fromBuffer(childContract.methods.pubStoreValue.selector), 42n)
.send({ origin: accounts[0] });

await tx.isMined({ interval: 0.1 });
const receipt = await tx.getReceipt();
expect(receipt.status).toBe(TxStatus.MINED);

expect(await getChildStoredValue(childContract)).toEqual(42n);
}, 100_000);

// Fails with "solver opcode resolution error: cannot solve opcode: expression has too many unknowns %EXPR [ 0 ]%"
// See https://github.com/noir-lang/noir/issues/1347
it.skip('enqueues multiple public calls', async () => {
const tx = parentContract.methods
.enqueueCallToChildTwice(
addressToField(childContract.address),
Fr.fromBuffer(childContract.methods.pubStoreValue.selector).value,
42n,
)
.send({ origin: accounts[0] });

await tx.isMined({ interval: 0.1 });
const receipt = await tx.getReceipt();
expect(receipt.status).toBe(TxStatus.MINED);

expect(await getChildStoredValue(childContract)).toEqual(85n);
}, 100_000);

it('enqueues a public call with nested public calls', async () => {
const tx = parentContract.methods
.enqueueCallToPubEntryPoint(
childContract.address,
Fr.fromBuffer(childContract.methods.pubStoreValue.selector),
42n,
)
.send({ origin: accounts[0] });

await tx.isMined({ interval: 0.1 });
const receipt = await tx.getReceipt();
expect(receipt.status).toBe(TxStatus.MINED);

expect(await getChildStoredValue(childContract)).toEqual(42n);
}, 100_000);

// Fails with "solver opcode resolution error: cannot solve opcode: expression has too many unknowns %EXPR [ 0 ]%"
// See https://github.com/noir-lang/noir/issues/1347
it.skip('enqueues multiple public calls with nested public calls', async () => {
const tx = parentContract.methods
.enqueueCallsToPubEntryPoint(
childContract.address,
Fr.fromBuffer(childContract.methods.pubStoreValue.selector),
42n,
)
.send({ origin: accounts[0] });

await tx.isMined({ interval: 0.1 });
const receipt = await tx.getReceipt();
expect(receipt.status).toBe(TxStatus.MINED);

expect(await getChildStoredValue(childContract)).toEqual(84n);
}, 100_000);
describe('parent manually calls child', () => {
let parentContract: ParentContract;
let childContract: ChildContract;

beforeEach(async () => {
parentContract = (await deployContract(ParentContractAbi)) as ParentContract;
childContract = (await deployContract(ChildContractAbi)) as ChildContract;
}, 100_000);

const deployContract = async (abi: ContractAbi) => {
logger(`Deploying L2 contract ${abi.name}...`);
const deployer = new ContractDeployer(abi, aztecRpcServer);
const tx = deployer.deploy().send();

await tx.isMined({ interval: 0.1 });

const receipt = await tx.getReceipt();
const contract = await Contract.create(receipt.contractAddress!, abi, wallet);
logger(`L2 contract ${abi.name} deployed at ${contract.address}`);
return contract;
};

const addressToField = (address: AztecAddress): bigint => Fr.fromBuffer(address.toBuffer()).value;

const getChildStoredValue = (child: { address: AztecAddress }) =>
aztecRpcServer.getPublicStorageAt(child.address, new Fr(1)).then(x => toBigInt(x!));

/**
* Milestone 3.
*/
it('performs nested calls', async () => {
const tx = parentContract.methods
.entryPoint(childContract.address, Fr.fromBuffer(childContract.methods.value.selector))
.send({ origin: accounts[0] });

await tx.isMined({ interval: 0.1 });
const receipt = await tx.getReceipt();

expect(receipt.status).toBe(TxStatus.MINED);
}, 100_000);

it('performs public nested calls', async () => {
const tx = parentContract.methods
.pubEntryPoint(childContract.address, Fr.fromBuffer(childContract.methods.pubValue.selector), 42n)
.send({ origin: accounts[0] });

await tx.isMined({ interval: 0.1 });
const receipt = await tx.getReceipt();

expect(receipt.status).toBe(TxStatus.MINED);
}, 100_000);

it('enqueues a single public call', async () => {
const tx = parentContract.methods
.enqueueCallToChild(childContract.address, Fr.fromBuffer(childContract.methods.pubStoreValue.selector), 42n)
.send({ origin: accounts[0] });

await tx.isMined({ interval: 0.1 });
const receipt = await tx.getReceipt();
expect(receipt.status).toBe(TxStatus.MINED);

expect(await getChildStoredValue(childContract)).toEqual(42n);
}, 100_000);

// Fails with "solver opcode resolution error: cannot solve opcode: expression has too many unknowns %EXPR [ 0 ]%"
// See https://github.com/noir-lang/noir/issues/1347
it.skip('enqueues multiple public calls', async () => {
const tx = parentContract.methods
.enqueueCallToChildTwice(
addressToField(childContract.address),
Fr.fromBuffer(childContract.methods.pubStoreValue.selector).value,
42n,
)
.send({ origin: accounts[0] });

await tx.isMined({ interval: 0.1 });
const receipt = await tx.getReceipt();
expect(receipt.status).toBe(TxStatus.MINED);

expect(await getChildStoredValue(childContract)).toEqual(85n);
}, 100_000);

it('enqueues a public call with nested public calls', async () => {
const tx = parentContract.methods
.enqueueCallToPubEntryPoint(
childContract.address,
Fr.fromBuffer(childContract.methods.pubStoreValue.selector),
42n,
)
.send({ origin: accounts[0] });

await tx.isMined({ interval: 0.1 });
const receipt = await tx.getReceipt();
expect(receipt.status).toBe(TxStatus.MINED);

expect(await getChildStoredValue(childContract)).toEqual(42n);
}, 100_000);

// Fails with "solver opcode resolution error: cannot solve opcode: expression has too many unknowns %EXPR [ 0 ]%"
// See https://github.com/noir-lang/noir/issues/1347
it.skip('enqueues multiple public calls with nested public calls', async () => {
const tx = parentContract.methods
.enqueueCallsToPubEntryPoint(
childContract.address,
Fr.fromBuffer(childContract.methods.pubStoreValue.selector),
42n,
)
.send({ origin: accounts[0] });

await tx.isMined({ interval: 0.1 });
const receipt = await tx.getReceipt();
expect(receipt.status).toBe(TxStatus.MINED);

expect(await getChildStoredValue(childContract)).toEqual(84n);
}, 100_000);

// Regression for https://github.com/AztecProtocol/aztec-packages/issues/640
// Fails with "solver opcode resolution error: cannot solve opcode: expression has too many unknowns %EXPR [ 0 ]%"
// See https://github.com/noir-lang/noir/issues/1347
it.skip('reads fresh value after write within the same tx', async () => {
const tx = parentContract.methods
.pubEntryPointTwice(
addressToField(childContract.address),
Fr.fromBuffer(childContract.methods.pubStoreValue.selector).value,
42n,
)
.send({ origin: accounts[0] });

await tx.isMined({ interval: 0.1 });
const receipt = await tx.getReceipt();

expect(receipt.status).toBe(TxStatus.MINED);
expect(await getChildStoredValue(childContract)).toEqual(85n);
}, 100_000);
});

// Regression for https://github.com/AztecProtocol/aztec-packages/issues/640
// Fails with "solver opcode resolution error: cannot solve opcode: expression has too many unknowns %EXPR [ 0 ]%"
// See https://github.com/noir-lang/noir/issues/1347
it.skip('reads fresh value after write within the same tx', async () => {
const tx = parentContract.methods
.pubEntryPointTwice(
addressToField(childContract.address),
Fr.fromBuffer(childContract.methods.pubStoreValue.selector).value,
42n,
)
.send({ origin: accounts[0] });

await tx.isMined({ interval: 0.1 });
const receipt = await tx.getReceipt();

expect(receipt.status).toBe(TxStatus.MINED);
expect(await getChildStoredValue(childContract)).toEqual(85n);
}, 100_000);
describe('importer uses autogenerated test contract interface', () => {
let importerContract: ImportTestContract;
let testContract: TestContract;

beforeEach(async () => {
logger(`Deploying importer test contract`);
importerContract = await ImportTestContract.deploy(wallet).send().deployed();
logger(`Deploying test contract`);
testContract = await TestContract.deploy(wallet).send().deployed();
}, 30_000);

it('calls a method with multiple arguments', async () => {
logger(`Calling main on importer contract`);
await importerContract.methods.main(testContract.address).send().wait();
}, 30_000);

it('calls a method no arguments', async () => {
logger(`Calling noargs on importer contract`);
await importerContract.methods.callNoArgs(testContract.address).send().wait();
}, 30_000);

it('calls an open function', async () => {
logger(`Calling openfn on importer contract`);
await importerContract.methods.callOpenFn(testContract.address).send().wait();
}, 30_000);
});
});
2 changes: 1 addition & 1 deletion yarn-project/foundation/src/abi/abi_coder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function computeFunctionSelector(signature: string, size: number) {
*/
export function generateFunctionSelector(name: string, parameters: ABIParameter[]) {
const signature = computeFunctionSignature(name, parameters);
return keccak(Buffer.from(signature)).slice(0, 4);
return computeFunctionSelector(signature, 4);
}

/**
Expand Down
Loading