diff --git a/yarn-project/acir-simulator/src/client/private_execution.test.ts b/yarn-project/acir-simulator/src/client/private_execution.test.ts index 02cfa0707d9..0a4202791b1 100644 --- a/yarn-project/acir-simulator/src/client/private_execution.test.ts +++ b/yarn-project/acir-simulator/src/client/private_execution.test.ts @@ -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[]; @@ -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 }), @@ -113,6 +113,7 @@ describe('Private Execution test suite', () => { abi, functionData.isConstructor ? AztecAddress.ZERO : contractAddress, portalContractAddress, + msgSender, ); }; @@ -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; @@ -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 () => { @@ -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)); @@ -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; @@ -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 () => { @@ -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)); @@ -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]); @@ -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( @@ -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]); @@ -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)); @@ -751,7 +755,6 @@ describe('Private Execution test suite', () => { }); const result = await runSimulator({ - origin: contractAddress, abi, args: [amount, secret, recipient], }); @@ -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, @@ -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); @@ -918,7 +920,6 @@ describe('Private Execution test suite', () => { const result = await runSimulator({ args: args, abi: abi, - origin: contractAddress, contractAddress: contractAddress, }); @@ -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); @@ -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]); }); }); @@ -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); }); @@ -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); }); @@ -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); }); }); diff --git a/yarn-project/acir-simulator/src/client/simulator.ts b/yarn-project/acir-simulator/src/client/simulator.ts index 59ded1cc1b9..d5e6567e2a9 100644 --- a/yarn-project/acir-simulator/src/client/simulator.ts +++ b/yarn-project/acir-simulator/src/client/simulator.ts @@ -53,9 +53,7 @@ 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( @@ -63,6 +61,7 @@ export class AcirSimulator { entryPointABI: FunctionAbiWithDebugMetadata, contractAddress: AztecAddress, portalContractAddress: EthAddress, + msgSender = AztecAddress.ZERO, ): Promise { if (entryPointABI.functionType !== FunctionType.SECRET) { throw new Error(`Cannot run ${entryPointABI.functionType} function as secret`); @@ -76,7 +75,7 @@ export class AcirSimulator { const historicBlockData = await this.db.getHistoricBlockData(); const callContext = new CallContext( - AztecAddress.ZERO, + msgSender, contractAddress, portalContractAddress, false, diff --git a/yarn-project/aztec-sandbox/src/examples/private_token_contract.ts b/yarn-project/aztec-sandbox/src/examples/private_token_contract.ts index 32888a67486..91e1dae1a42 100644 --- a/yarn-project/aztec-sandbox/src/examples/private_token_contract.ts +++ b/yarn-project/aztec-sandbox/src/examples/private_token_contract.ts @@ -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); diff --git a/yarn-project/aztec-sandbox/src/examples/uniswap_trade_on_l1_from_l2.ts b/yarn-project/aztec-sandbox/src/examples/uniswap_trade_on_l1_from_l2.ts index 2a7907a836b..09bcbd2028c 100644 --- a/yarn-project/aztec-sandbox/src/examples/uniswap_trade_on_l1_from_l2.ts +++ b/yarn-project/aztec-sandbox/src/examples/uniswap_trade_on_l1_from_l2.ts @@ -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); diff --git a/yarn-project/canary/src/uniswap_trade_on_l1_from_l2.test.ts b/yarn-project/canary/src/uniswap_trade_on_l1_from_l2.test.ts index 15748b722bf..c9b2691158e 100644 --- a/yarn-project/canary/src/uniswap_trade_on_l1_from_l2.test.ts +++ b/yarn-project/canary/src/uniswap_trade_on_l1_from_l2.test.ts @@ -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); diff --git a/yarn-project/end-to-end/src/e2e_2_rpc_servers.test.ts b/yarn-project/end-to-end/src/e2e_2_rpc_servers.test.ts index 64347fc67d0..3e114605d3a 100644 --- a/yarn-project/end-to-end/src/e2e_2_rpc_servers.test.ts +++ b/yarn-project/end-to-end/src/e2e_2_rpc_servers.test.ts @@ -111,9 +111,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(); @@ -127,9 +125,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(); diff --git a/yarn-project/end-to-end/src/e2e_aztec_js_browser.test.ts b/yarn-project/end-to-end/src/e2e_aztec_js_browser.test.ts index c9427ae15fd..7f322c8a373 100644 --- a/yarn-project/end-to-end/src/e2e_aztec_js_browser.test.ts +++ b/yarn-project/end-to-end/src/e2e_aztec_js_browser.test.ts @@ -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 }); }, diff --git a/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts b/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts index f3567538948..1456efa0ab3 100644 --- a/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts @@ -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'; @@ -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)); @@ -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(), ]; diff --git a/yarn-project/end-to-end/src/e2e_multi_transfer.test.ts b/yarn-project/end-to-end/src/e2e_multi_transfer.test.ts index 76af3b09c4d..ed48e142d0d 100644 --- a/yarn-project/end-to-end/src/e2e_multi_transfer.test.ts +++ b/yarn-project/end-to-end/src/e2e_multi_transfer.test.ts @@ -20,7 +20,7 @@ describe('multi-transfer payments', () => { let wallet: Wallet; let logger: DebugLogger; let ownerAddress: AztecAddress; - const recipients: AztecAddress[] = []; + let recipients: AztecAddress[]; let initialBalance: bigint; let zkTokenContract: PrivateTokenAirdropContract; @@ -30,11 +30,7 @@ describe('multi-transfer payments', () => { let accounts: CompleteAddress[]; ({ aztecNode, aztecRpcServer, accounts, logger, wallet } = await setup(numberOfAccounts + 1)); // 1st being the `owner` ownerAddress = accounts[0].address; - - for (let i = 1; i < accounts.length; i++) { - const account = accounts[i].address; - recipients.push(account); - } + recipients = accounts.slice(1).map(a => a.address); logger(`Deploying zk token contract...`); initialBalance = 1000n; @@ -166,29 +162,55 @@ describe('multi-transfer payments', () => { * * End state: * sender: [50, 50, 50, 50, 50, 50, 50, 50, 50, 400, 50, 50, 50] + * + * Transaction 2: Spend more notes than it's allowed in a single call, to transfer some amount to a recipient. + * It will destroy the largest note (400n) plus 8 small notes (50n * 8n). + * 4 notes will be destroyed first: [400n, 50n, 50n, 50n] + * And another 4 + 1 notes will be burnt in two function calls: [50n, 50n, 50n, 50n] and [50n] + * One change note (10n) will be created for the sender. + * One note will be created for the recipient. */ - it('create 12 small notes out of 1 large note', async () => { + it('create 12 small notes out of 1 large note and transfer to a recipient', async () => { // Transaction 1 - const amounts: bigint[] = [50n, 50n, 50n, 50n, 50n, 50n, 50n, 50n, 50n, 50n, 50n, 50n]; - const noteOffsets: bigint[] = [0n, 0n, 3n, 6n]; - const repeatedSelfAdddress: AztecAddress[] = Array(12).fill(ownerAddress); - logger(`split multiTransfer()...`); - const multiTransferTx = multiTransferContract.methods - .multiTransfer( - zkTokenContract.address.toField(), - repeatedSelfAdddress, - amounts, - ownerAddress, - zkTokenContract.methods.batchTransfer.selector.toField(), - noteOffsets, - ) - .send({ origin: ownerAddress }); - await multiTransferTx.isMined({ timeout: 100 }); // mining timeout ≥ time needed for the test to finish. - const multiTransferTxReceipt = await multiTransferTx.getReceipt(); - logger(`Consumption Receipt status: ${multiTransferTxReceipt.status}`); + { + const amounts: bigint[] = [50n, 50n, 50n, 50n, 50n, 50n, 50n, 50n, 50n, 50n, 50n, 50n]; + const noteOffsets: bigint[] = [0n, 0n, 3n, 6n]; + const repeatedSelfAdddress: AztecAddress[] = Array(12).fill(ownerAddress); + + const multiTransferTx = multiTransferContract.methods + .multiTransfer( + zkTokenContract.address.toField(), + repeatedSelfAdddress, + amounts, + ownerAddress, + zkTokenContract.methods.batchTransfer.selector.toField(), + noteOffsets, + ) + .send({ origin: ownerAddress }); + await multiTransferTx.isMined({ timeout: 100 }); // mining timeout ≥ time needed for the test to finish. + const multiTransferTxReceipt = await multiTransferTx.getReceipt(); + logger(`Consumption Receipt status: ${multiTransferTxReceipt.status}`); + + await expectBalance(zkTokenContract, ownerAddress, initialBalance); + await expectsNumOfEncryptedLogsInTheLastBlockToBe(aztecNode, 16); + } - await expectBalance(zkTokenContract, ownerAddress, initialBalance); - await expectsNumOfEncryptedLogsInTheLastBlockToBe(aztecNode, 16); + // Transaction 2 + logger(`transfer()`); + { + const transferAmount = 400n + 50n * 7n + 40n; + const recipient = recipients[0]; + await expectBalance(zkTokenContract, recipient, 0n); + + const transferTx = zkTokenContract.methods.transfer(transferAmount, recipient).send({ origin: ownerAddress }); + await transferTx.isMined(); + const txReceipt = await transferTx.getReceipt(); + logger(`consumption Receipt status: ${txReceipt.status}`); + + await expectBalance(zkTokenContract, ownerAddress, initialBalance - transferAmount); + await expectBalance(zkTokenContract, recipient, transferAmount); + await expectsNumOfEncryptedLogsInTheLastBlockToBe(aztecNode, 2); + } }, 100_000); }); diff --git a/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts b/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts index 475dc1263e8..8e6a1177f1c 100644 --- a/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts +++ b/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts @@ -81,7 +81,7 @@ describe('e2e_multiple_accounts_1_enc_key', () => { const contractWithWallet = await PrivateTokenContract.at(privateTokenAddress, wallets[senderIndex]); - const tx = contractWithWallet.methods.transfer(transferAmount, sender, receiver).send({ origin: sender }); + const tx = contractWithWallet.methods.transfer(transferAmount, receiver).send({ origin: sender }); await tx.isMined({ interval: 0.1 }); const receipt = await tx.getReceipt(); diff --git a/yarn-project/end-to-end/src/e2e_private_token_contract.test.ts b/yarn-project/end-to-end/src/e2e_private_token_contract.test.ts index 72ffa0da02b..84d839908e0 100644 --- a/yarn-project/end-to-end/src/e2e_private_token_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_private_token_contract.test.ts @@ -92,7 +92,7 @@ describe('e2e_private_token_contract', () => { await expectsNumOfEncryptedLogsInTheLastBlockToBe(aztecNode, 1); - const tx = contract.methods.transfer(transferAmount, owner, receiver).send({ origin: owner }); + const tx = contract.methods.transfer(transferAmount, receiver).send({ origin: owner }); await tx.isMined({ interval: 0.1 }); const receipt = await tx.getReceipt(); diff --git a/yarn-project/end-to-end/src/e2e_sandbox_example.test.ts b/yarn-project/end-to-end/src/e2e_sandbox_example.test.ts index 6513204a532..afec182fe91 100644 --- a/yarn-project/end-to-end/src/e2e_sandbox_example.test.ts +++ b/yarn-project/end-to-end/src/e2e_sandbox_example.test.ts @@ -145,7 +145,7 @@ describe('e2e_sandbox_example', () => { // We will now transfer tokens from ALice to Bob const transferQuantity = 543n; logger(`Transferring ${transferQuantity} tokens from Alice to Bob...`); - await tokenContractAlice.methods.transfer(transferQuantity, alice, bob).send().wait(); + await tokenContractAlice.methods.transfer(transferQuantity, bob).send({ origin: alice }).wait(); // Check the new balances aliceBalance = await tokenContractAlice.methods.getBalance(alice).view(); diff --git a/yarn-project/end-to-end/src/fixtures/cross_chain_test_harness.ts b/yarn-project/end-to-end/src/fixtures/cross_chain_test_harness.ts index 5c00589d6dc..6e6e41ee0b8 100644 --- a/yarn-project/end-to-end/src/fixtures/cross_chain_test_harness.ts +++ b/yarn-project/end-to-end/src/fixtures/cross_chain_test_harness.ts @@ -157,8 +157,8 @@ export class CrossChainTestHarness { async performL2Transfer(transferAmount: bigint) { // send a transfer tx to force through rollup with the message included const transferTx = this.l2Contract.methods - .transfer(transferAmount, this.ownerAddress, this.receiver) - .send({ origin: this.accounts[0].address }); + .transfer(transferAmount, this.receiver) + .send({ origin: this.ownerAddress }); await transferTx.isMined({ interval: 0.1 }); const transferReceipt = await transferTx.getReceipt(); @@ -270,7 +270,7 @@ export class CrossChainTestHarness { async unshieldTokensOnL2(unshieldAmount: bigint) { this.logger('Unshielding tokens'); const unshieldTx = this.l2Contract.methods - .unshieldTokens(unshieldAmount, this.ownerAddress, this.ownerAddress) + .unshieldTokens(unshieldAmount, this.ownerAddress) .send({ origin: this.ownerAddress }); await unshieldTx.isMined(); const unshieldReceipt = await unshieldTx.getReceipt(); diff --git a/yarn-project/end-to-end/src/integration_archiver_l1_to_l2.test.ts b/yarn-project/end-to-end/src/integration_archiver_l1_to_l2.test.ts index 60b903fd0da..07a30489ead 100644 --- a/yarn-project/end-to-end/src/integration_archiver_l1_to_l2.test.ts +++ b/yarn-project/end-to-end/src/integration_archiver_l1_to_l2.test.ts @@ -123,7 +123,7 @@ describe('archiver integration with l1 to l2 messages', () => { it('archiver handles l1 to l2 message correctly even when l2block has no such messages', async () => { // send a transfer tx to force through rollup with the message included const transferAmount = 1n; - l2Contract.methods.transfer(transferAmount, owner, receiver).send({ origin: owner }); + l2Contract.methods.transfer(transferAmount, receiver).send({ origin: owner }); expect((await archiver.getPendingL1ToL2Messages(10)).length).toEqual(0); expect(() => archiver.getConfirmedL1ToL2Message(Fr.ZERO)).toThrow(); diff --git a/yarn-project/noir-contracts/src/contracts/escrow_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/escrow_contract/src/main.nr index 08f2de87eea..a89853f5416 100644 --- a/yarn-project/noir-contracts/src/contracts/escrow_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/escrow_contract/src/main.nr @@ -1,5 +1,6 @@ mod address_note; mod storage; +mod private_token_contract_interface; // Sample escrow contract that stores a balance of a private token on behalf of an owner. contract Escrow { @@ -26,6 +27,8 @@ contract Escrow { use crate::storage::Storage; + use crate::private_token_contract_interface::PrivateTokenContractInterface; + // Creates a new instance fn constructor( inputs: pub PrivateContextInputs, @@ -69,8 +72,7 @@ contract Escrow { assert(note.owner == this); // TODO: Can we dynamically get this selector? - let transfer_selector = 0xdcd4c318; - let _callStackItem = context.call_private_function(token, transfer_selector, [amount, this, recipient]); + let _callStackItem = PrivateTokenContractInterface::at(token).transfer(&mut context, amount, recipient); context.finish() } diff --git a/yarn-project/noir-contracts/src/contracts/escrow_contract/src/private_token_contract_interface.nr b/yarn-project/noir-contracts/src/contracts/escrow_contract/src/private_token_contract_interface.nr new file mode 120000 index 00000000000..0f8207f8b98 --- /dev/null +++ b/yarn-project/noir-contracts/src/contracts/escrow_contract/src/private_token_contract_interface.nr @@ -0,0 +1 @@ +../../private_token_contract/src/interface.nr \ No newline at end of file diff --git a/yarn-project/noir-contracts/src/contracts/import_test_contract/src/test_contract_interface.nr b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/test_contract_interface.nr index 412fbaacd2d..1113fbe816e 120000 --- a/yarn-project/noir-contracts/src/contracts/import_test_contract/src/test_contract_interface.nr +++ b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/test_contract_interface.nr @@ -1 +1 @@ -../../test_contract/src/test_contract_interface.nr \ No newline at end of file +../../test_contract/src/interface.nr \ No newline at end of file diff --git a/yarn-project/noir-contracts/src/contracts/native_token_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/native_token_contract/src/main.nr index 229815b5392..4bab89bc072 100644 --- a/yarn-project/noir-contracts/src/contracts/native_token_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/native_token_contract/src/main.nr @@ -9,7 +9,7 @@ contract NativeToken { // Libs use dep::value_note::{ balance_utils, - utils::{send_note, spend_notes}, + utils::{increment, decrement}, value_note::{VALUE_NOTE_LEN, ValueNoteMethods}, }; @@ -55,7 +55,7 @@ contract NativeToken { let mut context = PrivateContext::new(inputs, abi::hash_args([initial_supply, owner])); let balance = storage.balances.at(owner); - send_note(&mut context, balance, initial_supply, owner); + increment(&mut context, balance, initial_supply, owner); context.finish() } @@ -123,7 +123,7 @@ contract NativeToken { context.consume_l1_to_l2_message(inputs, msg_key, content_hash, secret); let balance = storage.balances.at(owner); - send_note(&mut context, balance, amount, owner); + increment(&mut context, balance, amount, owner); // Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel. context.finish() @@ -149,7 +149,7 @@ contract NativeToken { ])); let sender_balance = storage.balances.at(sender); - spend_notes(&mut context, sender_balance, amount, sender); + decrement(&mut context, sender_balance, amount, sender); let content = get_withdraw_content_hash(amount, recipient, callerOnL1); context.message_portal(content); @@ -329,10 +329,10 @@ contract NativeToken { // Gets the set of sender's notes and picks 2 of those. let sender_balance = storage.balances.at(from); - spend_notes(&mut context, sender_balance, amount, from); + decrement(&mut context, sender_balance, amount, from); let balance = storage.balances.at(to); - send_note(&mut context, balance, amount, to); + increment(&mut context, balance, amount, to); context.finish() } @@ -392,7 +392,7 @@ contract NativeToken { // Mint the tokens let balance = storage.balances.at(owner); - send_note(&mut context, balance, amount, owner); + increment(&mut context, balance, amount, owner); context.finish() } @@ -411,7 +411,7 @@ contract NativeToken { // Remove user balance let sender_balance = storage.balances.at(from); - spend_notes(&mut context, sender_balance, amount, from); + decrement(&mut context, sender_balance, amount, from); // enqueue a public function to perform the public state update. let thisAddress = context.this_address(); diff --git a/yarn-project/noir-contracts/src/contracts/non_native_token_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/non_native_token_contract/src/main.nr index 45bd2acee9c..c45af6d3ead 100644 --- a/yarn-project/noir-contracts/src/contracts/non_native_token_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/non_native_token_contract/src/main.nr @@ -20,7 +20,7 @@ contract NonNativeToken { // Libs use dep::value_note::{ balance_utils, - utils::{send_note, spend_notes}, + utils::{increment, decrement}, value_note::{VALUE_NOTE_LEN, ValueNoteMethods}, }; @@ -65,7 +65,7 @@ contract NonNativeToken { let mut context = PrivateContext::new(inputs, abi::hash_args([initial_supply, owner])); let balance = storage.balances.at(owner); - send_note(&mut context, balance, initial_supply, owner); + increment(&mut context, balance, initial_supply, owner); // Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel. context.finish() @@ -98,7 +98,7 @@ contract NonNativeToken { context.consume_l1_to_l2_message(inputs, msg_key, content_hash, secret); let balance = storage.balances.at(owner); - send_note(&mut context, balance, amount, owner); + increment(&mut context, balance, amount, owner); // Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel. context.finish() @@ -113,7 +113,7 @@ contract NonNativeToken { inputs: PrivateContextInputs, //*********************************/ amount: Field, - sender: Field, + sender: Field, // TODO: Should verify sender. recipient: Field, // ethereum address in the field callerOnL1: Field, // ethereum address that can call this function on the L1 portal (0x0 if anyone can call) ) -> distinct pub abi::PrivateCircuitPublicInputs { @@ -123,7 +123,7 @@ contract NonNativeToken { ])); let sender_balance = storage.balances.at(sender); - spend_notes(&mut context, sender_balance, amount, sender); + decrement(&mut context, sender_balance, amount, sender); let content = get_withdraw_content_hash(amount, recipient, callerOnL1); context.message_portal(content); @@ -212,20 +212,18 @@ contract NonNativeToken { inputs: PrivateContextInputs, //*********************************/ amount: Field, - sender: Field, recipient: Field, ) -> distinct pub abi::PrivateCircuitPublicInputs { let storage = Storage::init(); - let mut context = PrivateContext::new(inputs, abi::hash_args([ - amount, sender, recipient - ])); + let mut context = PrivateContext::new(inputs, abi::hash_args([amount, recipient])); + let sender = context.msg_sender(); // Gets the set of sender's notes and picks 2 of those. let sender_balance = storage.balances.at(sender); - spend_notes(&mut context, sender_balance, amount, sender); + decrement(&mut context, sender_balance, amount, sender); let balance = storage.balances.at(recipient); - send_note(&mut context, balance, amount, recipient); + increment(&mut context, balance, amount, recipient); // Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel. context.finish() @@ -284,7 +282,7 @@ contract NonNativeToken { // Mint the tokens let balance = storage.balances.at(owner); - send_note(&mut context, balance, amount, owner); + increment(&mut context, balance, amount, owner); context.finish() } @@ -292,17 +290,15 @@ contract NonNativeToken { fn unshieldTokens( inputs: PrivateContextInputs, amount: Field, - owner: Field, recipient: Field, ) -> distinct pub abi::PrivateCircuitPublicInputs { let storage = Storage::init(); - let mut context = PrivateContext::new(inputs, abi::hash_args([ - amount, owner, recipient - ])); + let mut context = PrivateContext::new(inputs, abi::hash_args([amount, recipient])); + let owner = context.msg_sender(); // Remove user balance let sender_balance = storage.balances.at(owner); - spend_notes(&mut context, sender_balance, amount, owner); + decrement(&mut context, sender_balance, amount, owner); // enqueue a public function to perform the public state update. let thisAddress = context.this_address(); diff --git a/yarn-project/noir-contracts/src/contracts/pokeable_token_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/pokeable_token_contract/src/main.nr index 757172d569c..30f0a41ba27 100644 --- a/yarn-project/noir-contracts/src/contracts/pokeable_token_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/pokeable_token_contract/src/main.nr @@ -5,7 +5,7 @@ contract PokeableToken { // Libs use dep::value_note::{ balance_utils, - utils::{send_note, spend_notes}, + utils::increment, value_note::{VALUE_NOTE_LEN, ValueNoteMethods, ValueNote}, }; use dep::aztec::abi; @@ -43,7 +43,7 @@ contract PokeableToken { // Insert new note to a set of user notes and emit the newly created encrypted note preimage via oracle call. let sender_balance = storage.balances.at(sender); - send_note(&mut context, sender_balance, initial_supply, sender); + increment(&mut context, sender_balance, initial_supply, sender); // Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel. context.finish() @@ -90,7 +90,7 @@ contract PokeableToken { // Create new note for the recipient. let recipient_balance = storage.balances.at(recipient); - send_note(&mut context, recipient_balance, note_sum, recipient); + increment(&mut context, recipient_balance, note_sum, recipient); // Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel. context.finish() diff --git a/yarn-project/noir-contracts/src/contracts/private_token_airdrop_contract/src/interface.nr b/yarn-project/noir-contracts/src/contracts/private_token_airdrop_contract/src/interface.nr new file mode 100644 index 00000000000..52b59aa68fa --- /dev/null +++ b/yarn-project/noir-contracts/src/contracts/private_token_airdrop_contract/src/interface.nr @@ -0,0 +1,115 @@ +/* Autogenerated file, do not edit! */ + +use dep::std; +use dep::aztec::context::PrivateContext; +use dep::aztec::constants_gen::RETURN_VALUES_LENGTH; + + +struct PrivateTokenAirdropContractInterface { + address: Field, +} + +impl PrivateTokenAirdropContractInterface { + fn at(address: Field) -> Self { + Self { + address, + } + } + + fn batchTransfer( + self, + context: &mut PrivateContext, + sender: Field, + amounts: [Field;3], + recipients: [Field;3], + spend_note_offset: u32 + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialised_args = [0; 8]; + serialised_args[0] = sender; + serialised_args[1] = amounts[0]; + serialised_args[2] = amounts[1]; + serialised_args[3] = amounts[2]; + serialised_args[4] = recipients[0]; + serialised_args[5] = recipients[1]; + serialised_args[6] = recipients[2]; + serialised_args[7] = spend_note_offset as Field; + + context.call_private_function(self.address, 0x88bd156f, serialised_args) + } + + + fn burn( + self, + context: &mut PrivateContext, + amount: Field, + owner: Field + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialised_args = [0; 2]; + serialised_args[0] = amount; + serialised_args[1] = owner; + + context.call_private_function(self.address, 0x7ecb218a, serialised_args) + } + + + fn claim( + self, + context: &mut PrivateContext, + amount: Field, + secret: Field, + owner: Field + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialised_args = [0; 3]; + serialised_args[0] = amount; + serialised_args[1] = secret; + serialised_args[2] = owner; + + context.call_private_function(self.address, 0x9f7bacc8, serialised_args) + } + + + fn createClaims( + self, + context: &mut PrivateContext, + amounts: [Field;2], + secrets: [Field;2] + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialised_args = [0; 4]; + serialised_args[0] = amounts[0]; + serialised_args[1] = amounts[1]; + serialised_args[2] = secrets[0]; + serialised_args[3] = secrets[1]; + + context.call_private_function(self.address, 0xcaf1f505, serialised_args) + } + + + fn mint( + self, + context: &mut PrivateContext, + amount: Field, + owner: Field + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialised_args = [0; 2]; + serialised_args[0] = amount; + serialised_args[1] = owner; + + context.call_private_function(self.address, 0x1dc9c3c0, serialised_args) + } + + + fn transfer( + self, + context: &mut PrivateContext, + amount: Field, + recipient: Field + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialised_args = [0; 2]; + serialised_args[0] = amount; + serialised_args[1] = recipient; + + context.call_private_function(self.address, 0x61dd7032, serialised_args) + } + +} + diff --git a/yarn-project/noir-contracts/src/contracts/private_token_airdrop_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/private_token_airdrop_contract/src/main.nr index 32459643ebe..7060b0bc20c 100644 --- a/yarn-project/noir-contracts/src/contracts/private_token_airdrop_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/private_token_airdrop_contract/src/main.nr @@ -1,11 +1,12 @@ mod claim_note; +mod interface; mod storage; contract PrivateTokenAirdrop { // Libs use dep::value_note::{ balance_utils, - utils::{send_note, spend_notes, send_notes, spend_one_note}, + utils::{increment, decrement, decrement_by_at_most}, value_note::{VALUE_NOTE_LEN, ValueNoteMethods}, }; @@ -13,6 +14,7 @@ contract PrivateTokenAirdrop { use dep::aztec::abi::PrivateContextInputs; use dep::aztec::context::PrivateContext; use dep::aztec::note::{ + note_getter_options::NoteGetterOptions, note_header::NoteHeader, utils as note_utils, }; @@ -20,6 +22,7 @@ contract PrivateTokenAirdrop { use crate::storage::Storage; use crate::claim_note::{ClaimNote, ClaimNoteMethods}; + use crate::interface::PrivateTokenAirdropContractInterface; // Constructs the contract and sets `initial_supply` which is fully owned by `owner`. @@ -37,7 +40,7 @@ contract PrivateTokenAirdrop { // Insert new note to a set of user notes and emit the newly created encrypted note preimage via oracle call. let owner_balance = storage.balances.at(owner); if (initial_supply != 0) { - send_note(&mut context, owner_balance, initial_supply, owner); + increment(&mut context, owner_balance, initial_supply, owner); } // Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel. @@ -58,12 +61,50 @@ contract PrivateTokenAirdrop { // Insert new note to a set of user notes and emit the newly created encrypted note preimage via oracle call. let owner_balance = storage.balances.at(owner); - send_note(&mut context, owner_balance, amount, owner); + increment(&mut context, owner_balance, amount, owner); // Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel.. context.finish() } + // Burn `amount` of tokens from `owner`'s balance. + // This is an example of a recursive function. It calls itself until enough notes have been burned to burn the `amount`. + fn burn( + //*********************************/ + // Should eventually be hidden: + inputs: PrivateContextInputs, + //*********************************/ + amount: Field, + owner: Field, + ) -> distinct pub abi::PrivateCircuitPublicInputs { + let storage = Storage::init(); + let mut context = PrivateContext::new(inputs, abi::hash_args([amount, owner])); + + let msg_sender = context.msg_sender(); + let this_address = context.this_address(); + + // If this function is not called by another function in the same contract, the owner must be msg_sender. + if msg_sender != this_address { + assert(owner == msg_sender); + } + + let balance = storage.balances.at(owner); + let sum = decrement_by_at_most(&mut context, balance, amount, owner); + + // If sum is 0, there are no notes to be burned. + assert(sum != 0); + + if sum != amount { + // The destroyed notes' sum is not enough. Keep burning. + let amount_to_burn = amount - sum; + let this = PrivateTokenAirdropContractInterface::at(this_address); + let _res = this.burn(&mut context, amount_to_burn, owner); + } + + // Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel.. + context.finish() + } + // Transfers `amount` of tokens from `sender` to a `recipient`. fn transfer( //*********************************/ @@ -71,20 +112,33 @@ contract PrivateTokenAirdrop { inputs: PrivateContextInputs, //*********************************/ amount: Field, - sender: Field, recipient: Field, ) -> distinct pub abi::PrivateCircuitPublicInputs { let storage = Storage::init(); - let mut context = PrivateContext::new(inputs, abi::hash_args([amount, sender, recipient])); + let mut context = PrivateContext::new(inputs, abi::hash_args([amount, recipient])); - // Pick from the set of sender's notes to spend amount. + let sender = context.msg_sender(); let sender_balance = storage.balances.at(sender); - spend_notes(&mut context, sender_balance, amount, sender); - // Creates new note for the recipient. - let recipient_balance = storage.balances.at(recipient); - send_note(&mut context, recipient_balance, amount, recipient); + let sum = decrement_by_at_most(&mut context, sender_balance, amount, sender); + + // If sum is 0, there are no notes to be used. + assert(sum != 0); + + if sum != amount { + // The destroyed notes' sum is not enough for the transfer. + // Burn the remaining amount. + // We only call burn() when decrement_by_at_most() didn't destroy enough notes. + let amount_to_burn = amount - sum; + let this_address = context.this_address(); + let this = PrivateTokenAirdropContractInterface::at(this_address); + let _res = this.burn(&mut context, amount_to_burn, sender); + } + // Create a new note for the recipient. + let recipient_balance = storage.balances.at(recipient); + increment(&mut context, recipient_balance, amount, recipient); + // Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel.. context.finish() } @@ -96,15 +150,15 @@ contract PrivateTokenAirdrop { //*********************************/ amounts: [Field; 2], secrets: [Field; 2], - sender: Field ) -> distinct pub abi::PrivateCircuitPublicInputs { let storage = Storage::init(); - let mut context = PrivateContext::new(inputs, abi::hash_args([amounts[0], amounts[1], secrets[0], secrets[1], sender])); + let mut context = PrivateContext::new(inputs, abi::hash_args([amounts[0], amounts[1], secrets[0], secrets[1]])); + let sender = context.msg_sender(); // Pick from the set of sender's notes to spend amount. let sender_balance = storage.balances.at(sender); let total = amounts[0] + amounts[1]; - spend_notes(&mut context, sender_balance, total, sender); + decrement(&mut context, sender_balance, total, sender); // Create claim notes. let claims = storage.claims; @@ -137,7 +191,7 @@ contract PrivateTokenAirdrop { // Send the value note. let balance = storage.balances.at(owner); - send_note(&mut context, balance, amount, owner); + increment(&mut context, balance, amount, owner); // Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel.. context.finish() @@ -172,14 +226,29 @@ contract PrivateTokenAirdrop { // Spends the first of those 4 notes. let sender_balance = storage.balances.at(sender); let total = amounts[0] + amounts[1] + amounts[2]; - spend_one_note(&mut context, sender_balance, total, sender, spend_note_offset); - // Creates new note for the recipient. - let recipient1_balance = storage.balances.at(recipients[0]); - let recipient2_balance = storage.balances.at(recipients[1]); - let recipient3_balance = storage.balances.at(recipients[2]); + let options = NoteGetterOptions::new().set_limit(1).set_offset(spend_note_offset); + let opt_notes = sender_balance.get_notes(&mut context, options); + + // The note should always exist. + let note = opt_notes[0].unwrap_unchecked(); + + assert(note.owner == sender); - send_notes(&mut context, [recipient1_balance, recipient2_balance, recipient3_balance], amounts, recipients); + sender_balance.remove(&mut context, note); + + // Assert that the note chosen to spend has enough funds. + assert(note.value as u64 >= total as u64); + + // Add the change value back to the owner's balance. + let change_value = note.value - total; + increment(&mut context, sender_balance, change_value, sender); + + // Creates new note for the recipient. + for i in 0..3 { + let recipient_balance = storage.balances.at(recipients[i]); + increment(&mut context, recipient_balance, amounts[i], recipients[i]); + } // Also emit an unencrypted log, eg. "Coins transferred" // In this example, we emit the first output note's commitment to ensure that the unencrypted log diff --git a/yarn-project/noir-contracts/src/contracts/private_token_contract/src/interface.nr b/yarn-project/noir-contracts/src/contracts/private_token_contract/src/interface.nr new file mode 100644 index 00000000000..f35be0af102 --- /dev/null +++ b/yarn-project/noir-contracts/src/contracts/private_token_contract/src/interface.nr @@ -0,0 +1,47 @@ +/* Autogenerated file, do not edit! */ + +use dep::std; +use dep::aztec::context::PrivateContext; +use dep::aztec::constants_gen::RETURN_VALUES_LENGTH; + + +struct PrivateTokenContractInterface { + address: Field, +} + +impl PrivateTokenContractInterface { + fn at(address: Field) -> Self { + Self { + address, + } + } + + fn mint( + self, + context: &mut PrivateContext, + amount: Field, + owner: Field + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialised_args = [0; 2]; + serialised_args[0] = amount; + serialised_args[1] = owner; + + context.call_private_function(self.address, 0x1dc9c3c0, serialised_args) + } + + + fn transfer( + self, + context: &mut PrivateContext, + amount: Field, + recipient: Field + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialised_args = [0; 2]; + serialised_args[0] = amount; + serialised_args[1] = recipient; + + context.call_private_function(self.address, 0x61dd7032, serialised_args) + } + +} + diff --git a/yarn-project/noir-contracts/src/contracts/private_token_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/private_token_contract/src/main.nr index 81be50e4925..edbb738a9a8 100644 --- a/yarn-project/noir-contracts/src/contracts/private_token_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/private_token_contract/src/main.nr @@ -4,7 +4,7 @@ contract PrivateToken { // Libs use dep::value_note::{ balance_utils, - utils::{send_note, spend_notes}, + utils::{increment, decrement}, value_note::{VALUE_NOTE_LEN, ValueNoteMethods}, }; @@ -36,7 +36,7 @@ contract PrivateToken { // Insert new note to a set of user notes and emit the newly created encrypted note preimage via oracle call. let owner_balance = storage.balances.at(owner); if (initial_supply != 0) { - send_note(&mut context, owner_balance, initial_supply, owner); + increment(&mut context, owner_balance, initial_supply, owner); } // Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel. @@ -59,7 +59,7 @@ contract PrivateToken { // Insert new note to a set of user notes and emit the newly created encrypted note preimage via oracle call. let owner_balance = storage.balances.at(owner); - send_note(&mut context, owner_balance, amount, owner); + increment(&mut context, owner_balance, amount, owner); // Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel.. context.finish() @@ -67,27 +67,27 @@ contract PrivateToken { // docs:end:mint // docs:start:transfer - // Transfers `amount` of tokens from `sender` to a `recipient`. + // Transfers `amount` of tokens from msg_sender to a `recipient`. fn transfer( //*********************************/ // Should eventually be hidden: inputs: PrivateContextInputs, //*********************************/ amount: Field, - sender: Field, recipient: Field, ) -> distinct pub abi::PrivateCircuitPublicInputs { let storage = Storage::init(); - let mut context = PrivateContext::new(inputs, abi::hash_args([amount, sender, recipient])); + let mut context = PrivateContext::new(inputs, abi::hash_args([amount, recipient])); + let sender = context.msg_sender(); // Pick from the set of sender's notes to spend amount. let sender_balance = storage.balances.at(sender); - spend_notes(&mut context, sender_balance, amount, sender); + decrement(&mut context, sender_balance, amount, sender); // Creates new note for the recipient. let recipient_balance = storage.balances.at(recipient); - send_note(&mut context, recipient_balance, amount, recipient); - + increment(&mut context, recipient_balance, amount, recipient); + // Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel.. context.finish() } diff --git a/yarn-project/noir-contracts/src/contracts/test_contract/src/test_contract_interface.nr b/yarn-project/noir-contracts/src/contracts/test_contract/src/interface.nr similarity index 100% rename from yarn-project/noir-contracts/src/contracts/test_contract/src/test_contract_interface.nr rename to yarn-project/noir-contracts/src/contracts/test_contract/src/interface.nr diff --git a/yarn-project/noir-contracts/src/scripts/copy_output.ts b/yarn-project/noir-contracts/src/scripts/copy_output.ts index 3ef72f4c061..605ffd04ec2 100644 --- a/yarn-project/noir-contracts/src/scripts/copy_output.ts +++ b/yarn-project/noir-contracts/src/scripts/copy_output.ts @@ -22,7 +22,7 @@ const PROJECT_CONTRACTS = [ { name: 'EcdsaAccount', target: '../aztec.js/src/abis/', exclude: [] }, ]; -const INTERFACE_CONTRACTS = ['test']; +const INTERFACE_CONTRACTS = ['private_token', 'private_token_airdrop', 'test']; /** * Writes the contract to a specific project folder, if needed. @@ -90,7 +90,7 @@ const main = () => { // Write a .nr contract interface, for consumption by other Noir Contracts if (INTERFACE_CONTRACTS.includes(name)) { - const noirInterfaceDestFilePath = `${projectDirPath}/src/${projectName}_interface.nr`; + const noirInterfaceDestFilePath = `${projectDirPath}/src/interface.nr`; try { writeFileSync(noirInterfaceDestFilePath, generateNoirContractInterface(artifactJson)); log(`Written ${noirInterfaceDestFilePath}`); diff --git a/yarn-project/noir-libs/easy-private-state/src/easy_private_state.nr b/yarn-project/noir-libs/easy-private-state/src/easy_private_state.nr index a7f5e3b1848..915b441db51 100644 --- a/yarn-project/noir-libs/easy-private-state/src/easy_private_state.nr +++ b/yarn-project/noir-libs/easy-private-state/src/easy_private_state.nr @@ -33,7 +33,7 @@ impl EasyPrivateUint { } } - // Very similar to `send_note`. + // Very similar to `value_note::utils::increment`. fn add( self, context: &mut PrivateContext, @@ -57,7 +57,7 @@ impl EasyPrivateUint { ); } - // Very similar to `spend_note`. + // Very similar to `value_note::utils::decrement`. fn sub( self, context: &mut PrivateContext, diff --git a/yarn-project/noir-libs/value-note/src/utils.nr b/yarn-project/noir-libs/value-note/src/utils.nr index e231a71bf3b..1afbfb4154c 100644 --- a/yarn-project/noir-libs/value-note/src/utils.nr +++ b/yarn-project/noir-libs/value-note/src/utils.nr @@ -1,184 +1,132 @@ +use dep::std::option::Option; use dep::aztec::context::PrivateContext; // docs:start:encrypted_import use dep::aztec::log::emit_encrypted_log; // docs:end:encrypted_import -use dep::aztec::note::note_getter_options::NoteGetterOptions; +use dep::aztec::note::note_getter_options::{NoteGetterOptions, SortOrder}; use dep::aztec::oracle::get_public_key::get_public_key; use dep::aztec::state_vars::set::Set; -use dep::aztec::types::point::Point; use crate::{ filter::filter_notes_min_sum, value_note::{ValueNote, VALUE_NOTE_LEN}, }; -fn spend_notes( +// Sort the note values (0th field) in descending order. +// Pick the fewest notes whose sum is equal to or greater than `amount`. +fn create_note_getter_options_for_decreasing_balance(amount: Field) -> NoteGetterOptions { + NoteGetterOptions::with_filter(filter_notes_min_sum, amount).sort(0, SortOrder.DESC) +} + +// Creates a new note for the recipient. +// Inserts it to the recipient's set of notes. +fn increment( context: &mut PrivateContext, balance: Set, amount: Field, - owner: Field, + recipient: Field, ) { - let options = NoteGetterOptions::with_filter(filter_notes_min_sum, amount); - let opt_notes = balance.get_notes(context, options); - - let mut sum = 0; - for i in 0..opt_notes.len() { - if opt_notes[i].is_some() { - let note = opt_notes[i].unwrap_unchecked(); - - // Ensure the notes are actually owned by the owner (to prevent user from generating a valid proof while - // spending someone else's notes). - assert(note.owner == owner); - - // Removes the note from the owner's set of notes. - balance.remove(context, note); - - sum += note.value; - } - } - - assert(sum as u120 >= amount as u120); - - // Creates change note for the owner. - let change_value = sum - amount; - let mut change_note = ValueNote::new(change_value, owner); - balance.insert(context, &mut change_note); - - // Emit the newly created encrypted note preimages via oracle calls. - let mut encrypted_data = [0; VALUE_NOTE_LEN]; - if change_value != 0 { - encrypted_data = change_note.serialise(); - }; + let mut note = ValueNote::new(amount, recipient); + create_note(context, balance, recipient, &mut note); - let encryption_pub_key = get_public_key(owner); - emit_encrypted_log( - context, - (*context).this_address(), - balance.storage_slot, - encryption_pub_key, - encrypted_data, - ); + // It won't compile if Set.insert() is in an if statement :( + // if amount as u120 > 0 { + // create_note(context, balance, recipient, &mut note); + // } } -/* - Spends one note from a set of 4 notes. - Details: Reads 4 notes from the user's `balance`: [n_{o}, n_{o+1}, n_{o+2}, n_{o+3}] - where "o" is the note_offset. Then, spends the note: [n_{o + i}] - where "i" is the spend_note_index. This gives more control to the user on which - note is to be spent. -*/ -fn spend_one_note( +// Find some of the `owner`'s notes whose values add up to the `amount`. +// Remove those notes. +// If the value of the removed notes exceeds the requested `amount`, create a new note containing the excess value, so that exactly `amount` is removed. +// Fail if the sum of the selected notes is less than the amount. +fn decrement( context: &mut PrivateContext, balance: Set, amount: Field, owner: Field, - spend_note_offset: u32, ) { - let options = NoteGetterOptions::new().set_limit(1).set_offset(spend_note_offset); - let opt_notes = balance.get_notes(context, options); - - // The note should always exist. - let note = opt_notes[0].unwrap(); - - // Ensure the notes are actually owned by the owner (to prevent user from generating a valid proof while - // spending someone else's notes). - assert(owner == note.owner); - - // Removes the note from the owner's set of notes. - balance.remove(context, note); - - let note_value = note.value; - - // Assert that the note chosen to spend has enough funds. - assert(note_value as u64 >= amount as u64); - - // Creates change note for the owner. - let change_value = note_value - amount; - let mut change_note = ValueNote::new(change_value, owner); - - // Insert the change note to the owner's sets of notes. - balance.insert(context, &mut change_note); - - // Emit the newly created encrypted note preimages via oracle calls. - let mut encrypted_data = [0; VALUE_NOTE_LEN]; - if change_value != 0 { - encrypted_data = change_note.serialise(); - }; - - let encryption_pub_key = get_public_key(owner); - emit_encrypted_log( - context, - context.inputs.call_context.storage_contract_address, - balance.storage_slot, - encryption_pub_key, - encrypted_data, - ); + let sum = decrement_by_at_most(context, balance, amount, owner); + assert(sum == amount); } -fn send_note( +// Similar to `decrement`, except that it doesn't fail if the decremented amount is less than max_amount. +// The motivation behind this function is that there is an upper-bound on the number of notes a function may +// read and nullify. The requested decrementation `amount` might be spread across too many of the `owner`'s +// notes to 'fit' within this upper-bound, so we might have to remove an amount less than `amount`. A common +// pattern is to repeatedly call this function across many function calls, until enough notes have been nullified to +// equal `amount`. +// +// It returns the decremented amount, which should be less than or equal to max_amount. +fn decrement_by_at_most( context: &mut PrivateContext, balance: Set, - amount: Field, - recipient: Field, -) { - // Creates new note for the recipient. - let mut note = ValueNote::new(amount, recipient); - - // Insert the new note to the recipient's set of notes. - balance.insert(context, &mut note); - - // Emit the newly created encrypted note preimages via oracle calls. - // docs:start:encrypted + max_amount: Field, + owner: Field, +) -> Field { + let options = create_note_getter_options_for_decreasing_balance(max_amount); + let opt_notes = balance.get_notes(context, options); - let application_contract_address = (*context).this_address(); - let note_storage_slot = balance.storage_slot; - let encryption_pub_key = get_public_key(recipient); - let encrypted_data = note.serialise(); + let mut decremented = 0; + for i in 0..opt_notes.len() { + if opt_notes[i].is_some() { + decremented += destroy_note(context, balance, owner, opt_notes[i].unwrap_unchecked()); + } + } - emit_encrypted_log( - context, - application_contract_address, - note_storage_slot, - encryption_pub_key, - encrypted_data, - ); + // Add the change value back to the owner's balance. + let mut change_value = 0; + if decremented as u120 > max_amount as u120 { + change_value = decremented - max_amount; + decremented -= change_value; + } + increment(context, balance, change_value, owner); - // docs:end:encrypted + decremented } -/* - Sends three amounts to three recipients. - Why three? Because one private call currently allows `MAX_NEW_COMMITMENTS_PER_CALL = 4` output commitments. - So we split the output notes as: 3 to recipients + 1 to the owner (the change note). -*/ -fn send_notes( +fn create_note( context: &mut PrivateContext, - recipient_balances: [Set; NUM_RECIPIENTS], - amounts: [Field; NUM_RECIPIENTS], - recipients: [Field; NUM_RECIPIENTS], + balance: Set, + owner: Field, + note: &mut ValueNote, ) { - for i in 0..recipients.len() { - // Creates a new note for the i-th recipients - let mut recipient_note = ValueNote::new(amounts[i], recipients[i]); - - // Insert the new notes to the i-th recipient's sets of notes. - recipient_balances[i].insert(context, &mut recipient_note); - - // Get recipient encryption keys. - let recipient_encryption_pub_key = get_public_key(recipients[i]); + // Insert the new note to the owner's set of notes. + balance.insert(context, note); + // Remove this if statement if we can wrap this create_note function in an if statement. + if note.value != 0 { // Emit the newly created encrypted note preimages via oracle calls. - let mut recipient_encrypted_data = [0; VALUE_NOTE_LEN]; - if recipient_note.value != 0 { - recipient_encrypted_data = recipient_note.serialise(); - }; + // docs:start:encrypted + let application_contract_address = (*context).this_address(); + let note_storage_slot = balance.storage_slot; + let encryption_pub_key = get_public_key(owner); + let encrypted_data = (*note).serialise(); + emit_encrypted_log( context, - context.inputs.call_context.storage_contract_address, - recipient_balances[i].storage_slot, - recipient_encryption_pub_key, - recipient_encrypted_data, + application_contract_address, + note_storage_slot, + encryption_pub_key, + encrypted_data, ); + // docs:end:encrypted } -} \ No newline at end of file +} + +// Removes the note from the owner's set of notes. +// Returns the value of the destroyed note. +fn destroy_note( + context: &mut PrivateContext, + balance: Set, + owner: Field, + note: ValueNote, +) -> Field { + // Ensure the note is actually owned by the owner (to prevent user from generating a valid proof while + // spending someone else's notes). + assert(note.owner == owner); + + balance.remove(context, note); + + note.value +}