Skip to content

Commit

Permalink
feat: Recursive fn calls to spend more notes. (#1779)
Browse files Browse the repository at this point in the history
Closes #1422 

Also fixed a bug where we allowed any sender to spend notes.
  • Loading branch information
LeilaWang authored Aug 25, 2023
1 parent ad16b22 commit 94053e4
Show file tree
Hide file tree
Showing 28 changed files with 481 additions and 300 deletions.
60 changes: 30 additions & 30 deletions yarn-project/acir-simulator/src/client/private_execution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,13 @@ describe('Private Execution test suite', () => {
const runSimulator = async ({
abi,
args = [],
origin = AztecAddress.random(),
msgSender = AztecAddress.ZERO,
contractAddress = defaultContractAddress,
portalContractAddress = EthAddress.ZERO,
txContext = {},
}: {
abi: FunctionAbi;
origin?: AztecAddress;
msgSender?: AztecAddress;
contractAddress?: AztecAddress;
portalContractAddress?: EthAddress;
args?: any[];
Expand All @@ -101,7 +101,7 @@ describe('Private Execution test suite', () => {
const packedArguments = await PackedArguments.fromArgs(encodeArguments(abi, args), circuitsWasm);
const functionData = FunctionData.fromAbi(abi);
const txRequest = TxExecutionRequest.from({
origin,
origin: contractAddress,
argsHash: packedArguments.hash,
functionData,
txContext: TxContext.from({ ...txContextFields, ...txContext }),
Expand All @@ -113,6 +113,7 @@ describe('Private Execution test suite', () => {
abi,
functionData.isConstructor ? AztecAddress.ZERO : contractAddress,
portalContractAddress,
msgSender,
);
};

Expand Down Expand Up @@ -301,12 +302,13 @@ describe('Private Execution test suite', () => {
);
await insertLeaves(consumedNotes.map(n => n.siloedNoteHash));

const args = [amountToTransfer, owner, recipient];
const result = await runSimulator({ args, abi });
const args = [amountToTransfer, recipient];
const result = await runSimulator({ args, abi, msgSender: owner });

// The two notes were nullified
const newNullifiers = result.callStackItem.publicInputs.newNullifiers.filter(field => !field.equals(Fr.ZERO));
expect(newNullifiers).toEqual(consumedNotes.map(n => n.innerNullifier));
expect(newNullifiers).toHaveLength(consumedNotes.length);
expect(newNullifiers).toEqual(expect.arrayContaining(consumedNotes.map(n => n.innerNullifier)));

expect(result.preimages.newNotes).toHaveLength(2);
const [changeNote, recipientNote] = result.preimages.newNotes;
Expand All @@ -327,7 +329,8 @@ describe('Private Execution test suite', () => {
expect(changeNote.preimage[0]).toEqual(new Fr(40n));

const readRequests = result.callStackItem.publicInputs.readRequests.filter(field => !field.equals(Fr.ZERO));
expect(readRequests).toEqual(consumedNotes.map(n => n.uniqueSiloedNoteHash));
expect(readRequests).toHaveLength(consumedNotes.length);
expect(readRequests).toEqual(expect.arrayContaining(consumedNotes.map(n => n.uniqueSiloedNoteHash)));
});

it('should be able to transfer with dummy notes', async () => {
Expand All @@ -345,8 +348,8 @@ describe('Private Execution test suite', () => {
);
await insertLeaves(consumedNotes.map(n => n.siloedNoteHash));

const args = [amountToTransfer, owner, recipient];
const result = await runSimulator({ args, abi });
const args = [amountToTransfer, recipient];
const result = await runSimulator({ args, abi, msgSender: owner });

const newNullifiers = result.callStackItem.publicInputs.newNullifiers.filter(field => !field.equals(Fr.ZERO));
expect(newNullifiers).toEqual(consumedNotes.map(n => n.innerNullifier));
Expand Down Expand Up @@ -531,12 +534,13 @@ describe('Private Execution test suite', () => {
);
await insertLeaves(consumedNotes.map(n => n.siloedNoteHash));

const args = [amountToTransfer, owner, recipient];
const result = await runSimulator({ args, abi });
const args = [amountToTransfer, recipient];
const result = await runSimulator({ args, abi, msgSender: owner });

// The two notes were nullified
const newNullifiers = result.callStackItem.publicInputs.newNullifiers.filter(field => !field.equals(Fr.ZERO));
expect(newNullifiers).toEqual(consumedNotes.map(n => n.innerNullifier));
expect(newNullifiers).toHaveLength(consumedNotes.length);
expect(newNullifiers).toEqual(expect.arrayContaining(consumedNotes.map(n => n.innerNullifier)));

expect(result.preimages.newNotes).toHaveLength(2);
const [changeNote, recipientNote] = result.preimages.newNotes;
Expand All @@ -557,7 +561,8 @@ describe('Private Execution test suite', () => {
expect(changeNote.preimage[0]).toEqual(new Fr(40n));

const readRequests = result.callStackItem.publicInputs.readRequests.filter(field => !field.equals(Fr.ZERO));
expect(readRequests).toEqual(consumedNotes.map(n => n.uniqueSiloedNoteHash));
expect(readRequests).toHaveLength(consumedNotes.length);
expect(readRequests).toEqual(expect.arrayContaining(consumedNotes.map(n => n.uniqueSiloedNoteHash)));
});

it('should be able to transfer with dummy notes', async () => {
Expand All @@ -575,8 +580,8 @@ describe('Private Execution test suite', () => {
);
await insertLeaves(consumedNotes.map(n => n.siloedNoteHash));

const args = [amountToTransfer, owner, recipient];
const result = await runSimulator({ args, abi });
const args = [amountToTransfer, recipient];
const result = await runSimulator({ args, abi, msgSender: owner });

const newNullifiers = result.callStackItem.publicInputs.newNullifiers.filter(field => !field.equals(Fr.ZERO));
expect(newNullifiers).toEqual(consumedNotes.map(n => n.innerNullifier));
Expand Down Expand Up @@ -613,7 +618,7 @@ describe('Private Execution test suite', () => {
logger(`Calling child function ${childSelector.toString()} at ${childAddress.toShortString()}`);

const args = [Fr.fromBuffer(childAddress.toBuffer()), Fr.fromBuffer(childSelector.toBuffer())];
const result = await runSimulator({ args, abi: parentAbi, origin: parentAddress });
const result = await runSimulator({ args, abi: parentAbi });

expect(result.callStackItem.publicInputs.returnValues[0]).toEqual(new Fr(privateIncrement));
expect(oracle.getFunctionABI.mock.calls[0]).toEqual([childAddress, childSelector]);
Expand Down Expand Up @@ -651,7 +656,6 @@ describe('Private Execution test suite', () => {
});

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 = FunctionSelector.fromNameAndParameters(
Expand All @@ -664,7 +668,7 @@ describe('Private Execution test suite', () => {

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

expect(result.callStackItem.publicInputs.returnValues[0]).toEqual(argsHash);
expect(oracle.getFunctionABI.mock.calls[0]).toEqual([testAddress, testCodeGenSelector]);
Expand Down Expand Up @@ -716,7 +720,7 @@ describe('Private Execution test suite', () => {
});

const args = [bridgedAmount, recipient, messageKey, secret, canceller.toField()];
const result = await runSimulator({ origin: contractAddress, contractAddress, abi, args });
const result = await runSimulator({ contractAddress, abi, args });

// Check a nullifier has been inserted
const newNullifiers = result.callStackItem.publicInputs.newNullifiers.filter(field => !field.equals(Fr.ZERO));
Expand Down Expand Up @@ -751,7 +755,6 @@ describe('Private Execution test suite', () => {
});

const result = await runSimulator({
origin: contractAddress,
abi,
args: [amount, secret, recipient],
});
Expand Down Expand Up @@ -781,7 +784,7 @@ describe('Private Execution test suite', () => {

const args = [Fr.fromBuffer(childAddress.toBuffer()), childSelector.toField(), 42n];
const result = await runSimulator({
origin: parentAddress,
msgSender: parentAddress,
contractAddress: parentAddress,
abi: parentAbi,
args,
Expand Down Expand Up @@ -855,8 +858,7 @@ describe('Private Execution test suite', () => {
const result = await runSimulator({
args: args,
abi: abi,
origin: contractAddress,
contractAddress: contractAddress,
contractAddress,
});

expect(result.preimages.newNotes).toHaveLength(1);
Expand Down Expand Up @@ -918,7 +920,6 @@ describe('Private Execution test suite', () => {
const result = await runSimulator({
args: args,
abi: abi,
origin: contractAddress,
contractAddress: contractAddress,
});

Expand Down Expand Up @@ -972,8 +973,7 @@ describe('Private Execution test suite', () => {
const result = await runSimulator({
args: args,
abi: abi,
origin: contractAddress,
contractAddress: contractAddress,
contractAddress,
});

expect(result.preimages.newNotes).toHaveLength(1);
Expand Down Expand Up @@ -1015,7 +1015,7 @@ describe('Private Execution test suite', () => {
const pubKey = completeAddress.publicKey;

oracle.getCompleteAddress.mockResolvedValue(completeAddress);
const result = await runSimulator({ origin: AztecAddress.random(), abi, args });
const result = await runSimulator({ abi, args });
expect(result.returnValues).toEqual([pubKey.x.value, pubKey.y.value]);
});
});
Expand All @@ -1033,7 +1033,7 @@ describe('Private Execution test suite', () => {

// Overwrite the oracle return value
oracle.getPortalContractAddress.mockResolvedValue(portalContractAddress);
const result = await runSimulator({ origin: AztecAddress.random(), abi, args });
const result = await runSimulator({ abi, args });
expect(result.returnValues).toEqual(portalContractAddress.toField().value);
});

Expand All @@ -1045,7 +1045,7 @@ describe('Private Execution test suite', () => {
abi.returnTypes = [{ kind: 'field' }];

// Overwrite the oracle return value
const result = await runSimulator({ origin: AztecAddress.random(), abi, args: [], contractAddress });
const result = await runSimulator({ abi, args: [], contractAddress });
expect(result.returnValues).toEqual(contractAddress.toField().value);
});

Expand All @@ -1057,7 +1057,7 @@ describe('Private Execution test suite', () => {
abi.returnTypes = [{ kind: 'field' }];

// Overwrite the oracle return value
const result = await runSimulator({ origin: AztecAddress.random(), abi, args: [], portalContractAddress });
const result = await runSimulator({ abi, args: [], portalContractAddress });
expect(result.returnValues).toEqual(portalContractAddress.toField().value);
});
});
Expand Down
7 changes: 3 additions & 4 deletions yarn-project/acir-simulator/src/client/simulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,15 @@ export class AcirSimulator {
* @param entryPointABI - The ABI of the entry point function.
* @param contractAddress - The address of the contract (should match request.origin)
* @param portalContractAddress - The address of the portal contract.
* @param historicBlockData - Data required to reconstruct the block hash, this also contains the historic tree roots.
* @param curve - The curve instance for elliptic curve operations.
* @param packedArguments - The entrypoint packed arguments
* @param msgSender - The address calling the function. This can be replaced to simulate a call from another contract or a specific account.
* @returns The result of the execution.
*/
public async run(
request: TxExecutionRequest,
entryPointABI: FunctionAbiWithDebugMetadata,
contractAddress: AztecAddress,
portalContractAddress: EthAddress,
msgSender = AztecAddress.ZERO,
): Promise<ExecutionResult> {
if (entryPointABI.functionType !== FunctionType.SECRET) {
throw new Error(`Cannot run ${entryPointABI.functionType} function as secret`);
Expand All @@ -76,7 +75,7 @@ export class AcirSimulator {

const historicBlockData = await this.db.getHistoricBlockData();
const callContext = new CallContext(
AztecAddress.ZERO,
msgSender,
contractAddress,
portalContractAddress,
false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ async function main() {

// Perform a transfer
logger(`Transferring ${SECONDARY_AMOUNT} tokens from owner to another account.`);
const transferTx = zkContract.methods
.transfer(SECONDARY_AMOUNT, owner.address, account2.address)
.send({ origin: owner.address });
const transferTx = zkContract.methods.transfer(SECONDARY_AMOUNT, account2.address).send({ origin: owner.address });
await transferTx.isMined({ interval: 0.5 });
const balanceAfterTransfer = await getBalance(zkContract, owner.address);
const receiverBalance = await getBalance(zkContract, account2.address);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,7 @@ const transferWethOnL2 = async (
receiver: AztecAddress,
transferAmount: bigint,
) => {
const transferTx = wethL2Contract.methods
.transfer(transferAmount, ownerAddress, receiver)
.send({ origin: ownerAddress });
const transferTx = wethL2Contract.methods.transfer(transferAmount, receiver).send({ origin: ownerAddress });
await transferTx.isMined({ interval: 0.5 });
const transferReceipt = await transferTx.getReceipt();
// expect(transferReceipt.status).toBe(TxStatus.MINED);
Expand Down
4 changes: 1 addition & 3 deletions yarn-project/canary/src/uniswap_trade_on_l1_from_l2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,7 @@ const transferWethOnL2 = async (
receiver: AztecAddress,
transferAmount: bigint,
) => {
const transferTx = wethL2Contract.methods
.transfer(transferAmount, ownerAddress, receiver)
.send({ origin: ownerAddress });
const transferTx = wethL2Contract.methods.transfer(transferAmount, receiver).send({ origin: ownerAddress });
await transferTx.isMined();
const transferReceipt = await transferTx.getReceipt();
expect(transferReceipt.status).toBe(TxStatus.MINED);
Expand Down
8 changes: 2 additions & 6 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 @@ -110,9 +110,7 @@ describe('e2e_2_rpc_servers', () => {

// Transfer funds from A to B via RPC server A
const contractWithWalletA = await PrivateTokenContract.at(tokenAddress, walletA);
const txAToB = contractWithWalletA.methods
.transfer(transferAmount1, userA.address, userB.address)
.send({ origin: userA.address });
const txAToB = contractWithWalletA.methods.transfer(transferAmount1, userB.address).send({ origin: userA.address });

await txAToB.isMined({ interval: 0.1 });
const receiptAToB = await txAToB.getReceipt();
Expand All @@ -126,9 +124,7 @@ describe('e2e_2_rpc_servers', () => {

// Transfer funds from B to A via RPC server B
const contractWithWalletB = await PrivateTokenContract.at(tokenAddress, walletB);
const txBToA = contractWithWalletB.methods
.transfer(transferAmount2, userB.address, userA.address)
.send({ origin: userB.address });
const txBToA = contractWithWalletB.methods.transfer(transferAmount2, userA.address).send({ origin: userB.address });

await txBToA.isMined({ interval: 0.1 });
const receiptBToA = await txBToA.getReceipt();
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/end-to-end/src/e2e_aztec_js_browser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ conditionalDescribe()('e2e_aztec.js_browser', () => {
const receiver = accounts[1].address;
const wallet = await AztecJs.getSandboxAccountsWallet(client);
const contract = await Contract.at(AztecAddress.fromString(contractAddress), PrivateTokenContractAbi, wallet);
await contract.methods.transfer(transferAmount, owner, receiver).send({ origin: owner }).wait();
await contract.methods.transfer(transferAmount, receiver).send({ origin: owner }).wait();
console.log(`Transferred ${transferAmount} tokens to new Account`);
return await contract.methods.getBalance(receiver).view({ from: receiver });
},
Expand Down
14 changes: 3 additions & 11 deletions yarn-project/end-to-end/src/e2e_escrow_contract.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { AztecNodeService } from '@aztec/aztec-node';
import { AztecRPCServer } from '@aztec/aztec-rpc';
import { AztecAddress, BatchCall, Wallet, generatePublicKey } from '@aztec/aztec.js';
import { CompleteAddress, Fr, FunctionSelector, PrivateKey, getContractDeploymentInfo } from '@aztec/circuits.js';
import { toBufferBE } from '@aztec/foundation/bigint-buffer';
import { CompleteAddress, Fr, PrivateKey, getContractDeploymentInfo } from '@aztec/circuits.js';
import { DebugLogger } from '@aztec/foundation/log';
import { EscrowContractAbi, PrivateTokenContractAbi } from '@aztec/noir-contracts/artifacts';
import { EscrowContractAbi } from '@aztec/noir-contracts/artifacts';
import { EscrowContract, PrivateTokenContract } from '@aztec/noir-contracts/types';
import { AztecRPC, PublicKey } from '@aztec/types';

Expand All @@ -25,13 +24,6 @@ describe('e2e_escrow_contract', () => {
let escrowPrivateKey: PrivateKey;
let escrowPublicKey: PublicKey;

beforeAll(() => {
// Validate transfer selector. If this fails, then make sure to change it in the escrow contract.
const transferAbi = PrivateTokenContractAbi.functions.find(f => f.name === 'transfer')!;
const transferSelector = FunctionSelector.fromNameAndParameters(transferAbi.name, transferAbi.parameters);
expect(transferSelector.toBuffer()).toEqual(toBufferBE(0xdcd4c318n, 4));
});

beforeEach(async () => {
// Setup environment
({ aztecNode, aztecRpcServer, accounts, wallet, logger } = await setup(2));
Expand Down Expand Up @@ -92,7 +84,7 @@ describe('e2e_escrow_contract', () => {
await expectBalance(owner, 50n);

const actions = [
privateTokenContract.methods.transfer(10, owner, recipient).request(),
privateTokenContract.methods.transfer(10, recipient).request(),
escrowContract.methods.withdraw(privateTokenContract.address, 20, recipient).request(),
];

Expand Down
Loading

0 comments on commit 94053e4

Please sign in to comment.