From 88e33268050417cc9ff5e677d16e7e3f839d42a5 Mon Sep 17 00:00:00 2001 From: Charlie Lye Date: Mon, 1 Apr 2024 17:10:42 +0000 Subject: [PATCH] e2e token contract persistence --- yarn-project/aztec.js/src/index.ts | 2 +- yarn-project/circuit-types/src/index.ts | 2 +- .../src/types/grumpkin_private_key.ts | 1 + yarn-project/end-to-end/jest.config.json | 16 + yarn-project/end-to-end/jest.config.ts | 12 - yarn-project/end-to-end/package.json | 2 +- .../end-to-end/src/e2e_token_contract.test.ts | 2204 +++++++++-------- .../src/fixtures/get_acvm_config.ts | 48 + yarn-project/end-to-end/src/fixtures/setup.ts | 232 ++ .../src/fixtures/setup_l1_contracts.ts | 87 + yarn-project/end-to-end/src/fixtures/utils.ts | 3 + .../foundation/src/aztec-address/index.ts | 11 + .../foundation/src/eth-address/index.ts | 11 + yarn-project/foundation/src/fields/fields.ts | 14 + .../foundation/src/serialize/index.ts | 1 + .../foundation/src/serialize/type_registry.ts | 35 + 16 files changed, 1581 insertions(+), 1100 deletions(-) create mode 100644 yarn-project/end-to-end/jest.config.json delete mode 100644 yarn-project/end-to-end/jest.config.ts create mode 100644 yarn-project/end-to-end/src/fixtures/get_acvm_config.ts create mode 100644 yarn-project/end-to-end/src/fixtures/setup.ts create mode 100644 yarn-project/end-to-end/src/fixtures/setup_l1_contracts.ts create mode 100644 yarn-project/foundation/src/serialize/type_registry.ts diff --git a/yarn-project/aztec.js/src/index.ts b/yarn-project/aztec.js/src/index.ts index 646c5488bf9..18636735898 100644 --- a/yarn-project/aztec.js/src/index.ts +++ b/yarn-project/aztec.js/src/index.ts @@ -83,7 +83,7 @@ export { Body, CompleteAddress, ExtendedNote, - FunctionCall, + type FunctionCall, GrumpkinPrivateKey, L1ToL2Message, L1Actor, diff --git a/yarn-project/circuit-types/src/index.ts b/yarn-project/circuit-types/src/index.ts index 31213c9cb9b..cb966630034 100644 --- a/yarn-project/circuit-types/src/index.ts +++ b/yarn-project/circuit-types/src/index.ts @@ -20,4 +20,4 @@ export * from './packed_arguments.js'; export * from './interfaces/index.js'; export * from './auth_witness.js'; export * from './aztec_node/rpc/index.js'; -export { CompleteAddress, PublicKey, PartialAddress, GrumpkinPrivateKey } from '@aztec/circuits.js'; +export { CompleteAddress, type PublicKey, type PartialAddress, GrumpkinPrivateKey } from '@aztec/circuits.js'; diff --git a/yarn-project/circuits.js/src/types/grumpkin_private_key.ts b/yarn-project/circuits.js/src/types/grumpkin_private_key.ts index cb96ce1cd8c..e147ea53d14 100644 --- a/yarn-project/circuits.js/src/types/grumpkin_private_key.ts +++ b/yarn-project/circuits.js/src/types/grumpkin_private_key.ts @@ -2,3 +2,4 @@ import { type GrumpkinScalar } from '@aztec/foundation/fields'; /** A type alias for private key which belongs to the scalar field of Grumpkin curve. */ export type GrumpkinPrivateKey = GrumpkinScalar; +export const GrumpkinPrivateKey = GrumpkinScalar; diff --git a/yarn-project/end-to-end/jest.config.json b/yarn-project/end-to-end/jest.config.json new file mode 100644 index 00000000000..6e667a6f88f --- /dev/null +++ b/yarn-project/end-to-end/jest.config.json @@ -0,0 +1,16 @@ +{ + "preset": "ts-jest/presets/default-esm", + "transform": { + "^.+\\.ts$": [ + "ts-jest", + { + "useESM": true + } + ] + }, + "moduleNameMapper": { + "^(\\.{1,2}/.*)\\.[cm]?js$": "$1" + }, + "testRegex": "./src/.*\\.test\\.ts$", + "rootDir": "./src" +} diff --git a/yarn-project/end-to-end/jest.config.ts b/yarn-project/end-to-end/jest.config.ts deleted file mode 100644 index 83d85d85f9b..00000000000 --- a/yarn-project/end-to-end/jest.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Config } from 'jest'; - -const config: Config = { - preset: 'ts-jest/presets/default-esm', - moduleNameMapper: { - '^(\\.{1,2}/.*)\\.[cm]?js$': '$1', - }, - testRegex: './src/.*\\.test\\.(js|mjs|ts)$', - rootDir: './src', -}; - -export default config; diff --git a/yarn-project/end-to-end/package.json b/yarn-project/end-to-end/package.json index c6e7616b123..bf49a0c8b38 100644 --- a/yarn-project/end-to-end/package.json +++ b/yarn-project/end-to-end/package.json @@ -11,7 +11,7 @@ "clean": "rm -rf ./dest .tsbuildinfo", "formatting": "run -T prettier --check ./src \"!src/web/main.js\" && run -T eslint ./src", "formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src", - "test": "DEBUG='aztec:*' NODE_NO_WARNINGS=1 node --experimental-vm-modules $(yarn bin jest) --runInBand --testTimeout=60000 --forceExit", + "test": "DEBUG=${DEBUG:-'aztec:*'} NODE_NO_WARNINGS=1 node --experimental-vm-modules $(yarn bin jest) --runInBand --testTimeout=60000 --forceExit", "test:integration": "concurrently -k -s first -c reset,dim -n test,anvil \"yarn test:integration:run\" \"anvil\"", "test:integration:run": "NODE_NO_WARNINGS=1 node --experimental-vm-modules $(yarn bin jest) --no-cache --runInBand --config jest.integration.config.json" }, diff --git a/yarn-project/end-to-end/src/e2e_token_contract.test.ts b/yarn-project/end-to-end/src/e2e_token_contract.test.ts index 91f534a68eb..9eb9677dcd1 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract.test.ts @@ -14,9 +14,11 @@ import { decodeFunctionSignature } from '@aztec/foundation/abi'; import { DocsExampleContract, ReaderContract, TokenContract } from '@aztec/noir-contracts.js'; import { jest } from '@jest/globals'; +import { existsSync } from 'fs'; import { BITSIZE_TOO_BIG_ERROR, U128_OVERFLOW_ERROR, U128_UNDERFLOW_ERROR } from './fixtures/fixtures.js'; -import { publicDeployAccounts, setup } from './fixtures/utils.js'; +import { publicDeployAccounts, setup, setupFromState } from './fixtures/setup.js'; +// import { publicDeployAccounts, setup } from './fixtures/utils.js'; import { TokenSimulator } from './simulators/token_simulator.js'; const TIMEOUT = 100_000; @@ -63,19 +65,40 @@ describe('e2e_token_contract', () => { return str; }; - beforeAll(async () => { - ({ teardown, logger, wallets, accounts } = await setup(3)); + async function thisSetup(statePath: string) { + const setupResult = await setup(3, statePath, 'e2e_token_contract'); + ({ teardown, logger, wallets, accounts } = setupResult); + const { customData, snapshot } = setupResult; + + logger(`Public deploy accounts...`); await publicDeployAccounts(wallets[0], accounts.slice(0, 2)); + logger(`Deploying TokenContract...`); + asset = await TokenContract.deploy(wallets[0], accounts[0], TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS) + .send() + .deployed(); + logger(`Token deployed to ${asset.address}`); + + customData.tokenContractAddress = asset.address; + + await snapshot(); + } + + async function thisSetupFromState(statePath: string) { + const setupData = await setupFromState(statePath, 'e2e_token_contract'); + ({ teardown, logger, wallets, accounts } = setupData); + asset = await TokenContract.at(setupData.customData.tokenContractAddress, wallets[0]); + } + + beforeAll(async () => { + const { STATE_PATH: statePath = './e2e_token_contract_state' } = process.env; + existsSync(statePath) ? await thisSetupFromState(statePath) : await thisSetup(statePath); + TokenContract.artifact.functions.forEach(fn => { const sig = decodeFunctionSignature(fn.name, fn.parameters); logger(`Function ${sig} and the selector: ${FunctionSelector.fromNameAndParameters(fn.name, fn.parameters)}`); }); - asset = await TokenContract.deploy(wallets[0], accounts[0], TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS) - .send() - .deployed(); - logger(`Token deployed to ${asset.address}`); tokenSim = new TokenSimulator( asset, logger, @@ -84,7 +107,7 @@ describe('e2e_token_contract', () => { expect(await asset.methods.admin().view()).toBe(accounts[0].address.toBigInt()); - badAccount = await DocsExampleContract.deploy(wallets[0]).send().deployed(); + // badAccount = await DocsExampleContract.deploy(wallets[0]).send().deployed(); }, 100_000); afterAll(() => teardown()); @@ -93,1100 +116,1111 @@ describe('e2e_token_contract', () => { await tokenSim.check(); }, TIMEOUT); + // it('noop', () => {}); + describe('Reading constants', () => { let reader: ReaderContract; + beforeAll(async () => { + logger('Deploying ReaderContract...'); reader = await ReaderContract.deploy(wallets[0]).send().deployed(); + logger(`Deployed ReaderContract to ${reader.address}.`); }); describe('name', () => { - it.each([ - ['private', 'check_name_private' as const, "Cannot satisfy constraint 'name.is_eq(_what)'"], - [ - 'public', - 'check_name_public' as const, - "Failed to solve brillig function, reason: explicit trap hit in brillig 'name.is_eq(_what)'", - ], - ])('name - %s', async (_type, method, errorMessage) => { + it('check name private', async () => { const t = toString(await asset.methods.un_get_name().view()); expect(t).toBe(TOKEN_NAME); - await reader.methods[method](asset.address, TOKEN_NAME).send().wait(); - await expect(reader.methods[method](asset.address, 'WRONG_NAME').simulate()).rejects.toThrow(errorMessage); - }); - }); - - describe('symbol', () => { - it('private', async () => { - const t = toString(await asset.methods.un_get_symbol().view()); - expect(t).toBe(TOKEN_SYMBOL); - - await reader.methods.check_symbol_private(asset.address, TOKEN_SYMBOL).send().wait(); - - await expect(reader.methods.check_symbol_private(asset.address, 'WRONG_SYMBOL').simulate()).rejects.toThrow( - "Cannot satisfy constraint 'symbol.is_eq(_what)'", + await reader.methods.check_name_private(asset.address, TOKEN_NAME).send().wait(); + await expect(reader.methods.check_name_private(asset.address, 'WRONG_NAME').simulate()).rejects.toThrow( + 'name.is_eq(_what)', ); }); - it('public', async () => { - const t = toString(await asset.methods.un_get_symbol().view()); - expect(t).toBe(TOKEN_SYMBOL); - - await reader.methods.check_symbol_public(asset.address, TOKEN_SYMBOL).send().wait(); - - await expect(reader.methods.check_symbol_public(asset.address, 'WRONG_SYMBOL').simulate()).rejects.toThrow( - "Failed to solve brillig function, reason: explicit trap hit in brillig 'symbol.is_eq(_what)'", - ); - }); - }); - - describe('decimals', () => { - it('private', async () => { - const t = await asset.methods.un_get_decimals().view(); - expect(t).toBe(TOKEN_DECIMALS); - - await reader.methods.check_decimals_private(asset.address, TOKEN_DECIMALS).send().wait(); - - await expect(reader.methods.check_decimals_private(asset.address, 99).simulate()).rejects.toThrow( - "Cannot satisfy constraint 'ret[0] as u8 == what'", - ); - }); - - it('public', async () => { - const t = await asset.methods.un_get_decimals().view(); - expect(t).toBe(TOKEN_DECIMALS); - - await reader.methods.check_decimals_public(asset.address, TOKEN_DECIMALS).send().wait(); - - await expect(reader.methods.check_decimals_public(asset.address, 99).simulate()).rejects.toThrow( - "Failed to solve brillig function, reason: explicit trap hit in brillig 'ret[0] as u8 == what'", - ); - }); - }); - }); - - describe('Access controlled functions', () => { - it('Set admin', async () => { - await asset.methods.set_admin(accounts[1].address).send().wait(); - expect(await asset.methods.admin().view()).toBe(accounts[1].address.toBigInt()); - }); - - it('Add minter as admin', async () => { - await asset.withWallet(wallets[1]).methods.set_minter(accounts[1].address, true).send().wait(); - expect(await asset.methods.is_minter(accounts[1].address).view()).toBe(true); - }); - - it('Revoke minter as admin', async () => { - await asset.withWallet(wallets[1]).methods.set_minter(accounts[1].address, false).send().wait(); - expect(await asset.methods.is_minter(accounts[1].address).view()).toBe(false); - }); - - describe('failure cases', () => { - it('Set admin (not admin)', async () => { - await expect(asset.methods.set_admin(accounts[0].address).simulate()).rejects.toThrow( - 'Assertion failed: caller is not admin', - ); - }); - it('Revoke minter not as admin', async () => { - await expect(asset.methods.set_minter(accounts[0].address, false).simulate()).rejects.toThrow( - 'Assertion failed: caller is not admin', - ); - }); - }); - }); - - describe('Minting', () => { - describe('Public', () => { - it('as minter', async () => { - const amount = 10000n; - await asset.methods.mint_public(accounts[0].address, amount).send().wait(); - - tokenSim.mintPublic(accounts[0].address, amount); - expect(await asset.methods.balance_of_public(accounts[0].address).view()).toEqual( - tokenSim.balanceOfPublic(accounts[0].address), - ); - expect(await asset.methods.total_supply().view()).toEqual(tokenSim.totalSupply); - }); - - describe('failure cases', () => { - it('as non-minter', async () => { - const amount = 10000n; - await expect( - asset.withWallet(wallets[1]).methods.mint_public(accounts[0].address, amount).simulate(), - ).rejects.toThrow('Assertion failed: caller is not minter'); - }); - - it('mint >u128 tokens to overflow', async () => { - const amount = 2n ** 128n; // U128::max() + 1; - await expect(asset.methods.mint_public(accounts[0].address, amount).simulate()).rejects.toThrow( - BITSIZE_TOO_BIG_ERROR, - ); - }); - - it('mint u128', async () => { - const amount = 2n ** 128n - tokenSim.balanceOfPublic(accounts[0].address); - await expect(asset.methods.mint_public(accounts[0].address, amount).simulate()).rejects.toThrow( - U128_OVERFLOW_ERROR, - ); - }); - - it('mint u128', async () => { - const amount = 2n ** 128n - tokenSim.balanceOfPublic(accounts[0].address); - await expect(asset.methods.mint_public(accounts[1].address, amount).simulate()).rejects.toThrow( - U128_OVERFLOW_ERROR, - ); - }); - }); - }); - - describe('Private', () => { - const secret = Fr.random(); - const amount = 10000n; - let secretHash: Fr; - let txHash: TxHash; - - beforeAll(() => { - secretHash = computeMessageSecretHash(secret); - }); - - describe('Mint flow', () => { - it('mint_private as minter', async () => { - const receipt = await asset.methods.mint_private(amount, secretHash).send().wait(); - tokenSim.mintPrivate(amount); - txHash = receipt.txHash; - }); - - it('redeem as recipient', async () => { - await addPendingShieldNoteToPXE(0, amount, secretHash, txHash); - const txClaim = asset.methods.redeem_shield(accounts[0].address, amount, secret).send(); - // docs:start:debug - const receiptClaim = await txClaim.wait({ debug: true }); - // docs:end:debug - tokenSim.redeemShield(accounts[0].address, amount); - // 1 note should be created containing `amount` of tokens - const { visibleNotes } = receiptClaim.debugInfo!; - expect(visibleNotes.length).toBe(1); - expect(visibleNotes[0].note.items[0].toBigInt()).toBe(amount); - }); - }); - - describe('failure cases', () => { - it('try to redeem as recipient (double-spend) [REVERTS]', async () => { - await expect(addPendingShieldNoteToPXE(0, amount, secretHash, txHash)).rejects.toThrow( - 'The note has been destroyed.', - ); - await expect(asset.methods.redeem_shield(accounts[0].address, amount, secret).simulate()).rejects.toThrow( - `Assertion failed: Cannot return zero notes`, - ); - }); - - it('mint_private as non-minter', async () => { - await expect( - asset.withWallet(wallets[1]).methods.mint_private(amount, secretHash).simulate(), - ).rejects.toThrow('Assertion failed: caller is not minter'); - }); - - it('mint >u128 tokens to overflow', async () => { - const amount = 2n ** 128n; // U128::max() + 1; - await expect(asset.methods.mint_private(amount, secretHash).simulate()).rejects.toThrow( - BITSIZE_TOO_BIG_ERROR, - ); - }); - - it('mint u128', async () => { - const amount = 2n ** 128n - tokenSim.balanceOfPrivate(accounts[0].address); - expect(amount).toBeLessThan(2n ** 128n); - await expect(asset.methods.mint_private(amount, secretHash).simulate()).rejects.toThrow(U128_OVERFLOW_ERROR); - }); - - it('mint u128', async () => { - const amount = 2n ** 128n - tokenSim.totalSupply; - await expect(asset.methods.mint_private(amount, secretHash).simulate()).rejects.toThrow(U128_OVERFLOW_ERROR); - }); - }); - }); - }); - - describe('Transfer', () => { - describe('public', () => { - it('transfer less than balance', async () => { - const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balance0 / 2n; - expect(amount).toBeGreaterThan(0n); - await asset.methods.transfer_public(accounts[0].address, accounts[1].address, amount, 0).send().wait(); - - tokenSim.transferPublic(accounts[0].address, accounts[1].address, amount); - }); - - it('transfer to self', async () => { - const balance = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balance / 2n; - expect(amount).toBeGreaterThan(0n); - await asset.methods.transfer_public(accounts[0].address, accounts[0].address, amount, 0).send().wait(); - - tokenSim.transferPublic(accounts[0].address, accounts[0].address, amount); - }); - - it('transfer on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balance0 / 2n; - expect(amount).toBeGreaterThan(0n); - const nonce = Fr.random(); - - // docs:start:authwit_public_transfer_example - const action = asset - .withWallet(wallets[1]) - .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce); - - await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); - // docs:end:authwit_public_transfer_example - - // Perform the transfer - await action.send().wait(); - - tokenSim.transferPublic(accounts[0].address, accounts[1].address, amount); - - // Check that the message hash is no longer valid. Need to try to send since nullifiers are handled by sequencer. - const txReplay = asset - .withWallet(wallets[1]) - .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce) - .send(); - await expect(txReplay.wait()).rejects.toThrow('Transaction '); - }); - - describe('failure cases', () => { - it('transfer more than balance', async () => { - const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balance0 + 1n; - const nonce = 0; - await expect( - asset.methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce).simulate(), - ).rejects.toThrow(U128_UNDERFLOW_ERROR); - }); - - it('transfer on behalf of self with non-zero nonce', async () => { - const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balance0 - 1n; - const nonce = 1; - await expect( - asset.methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce).simulate(), - ).rejects.toThrow('Assertion failed: invalid nonce'); - }); - - it('transfer on behalf of other without "approval"', async () => { - const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balance0 + 1n; - const nonce = Fr.random(); - await expect( - asset - .withWallet(wallets[1]) - .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce) - .simulate(), - ).rejects.toThrow('Assertion failed: Message not authorized by account'); - }); - - it('transfer more than balance on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); - const balance1 = await asset.methods.balance_of_public(accounts[1].address).view(); - const amount = balance0 + 1n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - const action = asset - .withWallet(wallets[1]) - .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce); - - expect( - await wallets[0].lookupValidity(wallets[0].getAddress(), { caller: accounts[1].address, action }), - ).toEqual({ - isValidInPrivate: false, - isValidInPublic: false, - }); - - // We need to compute the message we want to sign and add it to the wallet as approved - await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); - - expect( - await wallets[0].lookupValidity(wallets[0].getAddress(), { caller: accounts[1].address, action }), - ).toEqual({ - isValidInPrivate: false, - isValidInPublic: true, - }); - - // Perform the transfer - await expect(action.simulate()).rejects.toThrow(U128_UNDERFLOW_ERROR); - - expect(await asset.methods.balance_of_public(accounts[0].address).view()).toEqual(balance0); - expect(await asset.methods.balance_of_public(accounts[1].address).view()).toEqual(balance1); - }); - - it('transfer on behalf of other, wrong designated caller', async () => { - const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); - const balance1 = await asset.methods.balance_of_public(accounts[1].address).view(); - const amount = balance0 + 2n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset - .withWallet(wallets[1]) - .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce); - - await wallets[0].setPublicAuthWit({ caller: accounts[0].address, action }, true).send().wait(); - - // Perform the transfer - await expect(action.simulate()).rejects.toThrow('Assertion failed: Message not authorized by account'); - - expect(await asset.methods.balance_of_public(accounts[0].address).view()).toEqual(balance0); - expect(await asset.methods.balance_of_public(accounts[1].address).view()).toEqual(balance1); - }); - - it('transfer on behalf of other, wrong designated caller', async () => { - const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); - const balance1 = await asset.methods.balance_of_public(accounts[1].address).view(); - const amount = balance0 + 2n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset - .withWallet(wallets[1]) - .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce); - await wallets[0].setPublicAuthWit({ caller: accounts[0].address, action }, true).send().wait(); - - // Perform the transfer - await expect(action.simulate()).rejects.toThrow('Assertion failed: Message not authorized by account'); - - expect(await asset.methods.balance_of_public(accounts[0].address).view()).toEqual(balance0); - expect(await asset.methods.balance_of_public(accounts[1].address).view()).toEqual(balance1); - }); - - it('transfer on behalf of other, cancelled authwit', async () => { - const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balance0 / 2n; - expect(amount).toBeGreaterThan(0n); - const nonce = Fr.random(); - - const action = asset - .withWallet(wallets[1]) - .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce); - - await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); - - await wallets[0].cancelAuthWit({ caller: accounts[1].address, action }).send().wait(); - - // Check that the authwit is no longer valid. Need to try to send since nullifiers are handled by sequencer. - const txCancelledAuthwit = asset - .withWallet(wallets[1]) - .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce) - .send(); - await expect(txCancelledAuthwit.wait()).rejects.toThrowError('Transaction '); - }); - - it('transfer on behalf of other, cancelled authwit, flow 2', async () => { - const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balance0 / 2n; - expect(amount).toBeGreaterThan(0n); - const nonce = Fr.random(); - - const action = asset - .withWallet(wallets[1]) - .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce); - - await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); - - await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, false).send().wait(); - - // Check that the authwit is no longer valid. Need to try to send since nullifiers are handled by sequencer. - const txCancelledAuthwit = asset - .withWallet(wallets[1]) - .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce) - .send(); - await expect(txCancelledAuthwit.wait()).rejects.toThrowError('Transaction '); - }); - - it('transfer on behalf of other, cancelled authwit, flow 3', async () => { - const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balance0 / 2n; - expect(amount).toBeGreaterThan(0n); - const nonce = Fr.random(); - - const action = asset - .withWallet(wallets[1]) - .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce); - const messageHash = computeAuthWitMessageHash( - accounts[1].address, - wallets[0].getChainId(), - wallets[0].getVersion(), - action.request(), - ); - - await wallets[0].setPublicAuthWit(messageHash, true).send().wait(); - - await wallets[0].cancelAuthWit(messageHash).send().wait(); - - // Check that the message hash is no longer valid. Need to try to send since nullifiers are handled by sequencer. - const txCancelledAuthwit = asset - .withWallet(wallets[1]) - .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce) - .send(); - await expect(txCancelledAuthwit.wait()).rejects.toThrow('Transaction '); - }); - - it('transfer on behalf of other, invalid spend_public_authwit on "from"', async () => { - const nonce = Fr.random(); - - // Should fail as the returned value from the badAccount is malformed - const txCancelledAuthwit = asset - .withWallet(wallets[1]) - .methods.transfer_public(badAccount.address, accounts[1].address, 0, nonce) - .send(); - await expect(txCancelledAuthwit.wait()).rejects.toThrow( - "Assertion failed: Message not authorized by account 'result == IS_VALID_SELECTOR'", - ); - }); - - it.skip('transfer into account to overflow', () => { - // This should already be covered by the mint case earlier. e.g., since we cannot mint to overflow, there is not - // a way to get funds enough to overflow. - // Require direct storage manipulation for us to perform a nice explicit case though. - // See https://github.com/AztecProtocol/aztec-packages/issues/1259 - }); - }); - }); - - describe('private', () => { - it('transfer less than balance', async () => { - const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balance0 / 2n; - expect(amount).toBeGreaterThan(0n); - await asset.methods.transfer(accounts[0].address, accounts[1].address, amount, 0).send().wait(); - tokenSim.transferPrivate(accounts[0].address, accounts[1].address, amount); - }); - - it('transfer to self', async () => { - const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balance0 / 2n; - expect(amount).toBeGreaterThan(0n); - await asset.methods.transfer(accounts[0].address, accounts[0].address, amount, 0).send().wait(); - tokenSim.transferPrivate(accounts[0].address, accounts[0].address, amount); - }); - it('transfer on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balance0 / 2n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - // docs:start:authwit_transfer_example - const action = asset - .withWallet(wallets[1]) - .methods.transfer(accounts[0].address, accounts[1].address, amount, nonce); - - const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); - await wallets[1].addAuthWitness(witness); - expect( - await wallets[0].lookupValidity(wallets[0].getAddress(), { caller: accounts[1].address, action }), - ).toEqual({ - isValidInPrivate: true, - isValidInPublic: false, - }); - // docs:end:authwit_transfer_example - - // Perform the transfer - await action.send().wait(); - tokenSim.transferPrivate(accounts[0].address, accounts[1].address, amount); - - // Perform the transfer again, should fail - const txReplay = asset - .withWallet(wallets[1]) - .methods.transfer(accounts[0].address, accounts[1].address, amount, nonce) - .send(); - await expect(txReplay.wait()).rejects.toThrow('Transaction '); - }); - - describe('failure cases', () => { - it('transfer more than balance', async () => { - const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balance0 + 1n; - expect(amount).toBeGreaterThan(0n); - await expect( - asset.methods.transfer(accounts[0].address, accounts[1].address, amount, 0).simulate(), - ).rejects.toThrow('Assertion failed: Balance too low'); - }); - - it('transfer on behalf of self with non-zero nonce', async () => { - const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balance0 - 1n; - expect(amount).toBeGreaterThan(0n); - await expect( - asset.methods.transfer(accounts[0].address, accounts[1].address, amount, 1).simulate(), - ).rejects.toThrow('Assertion failed: invalid nonce'); - }); - - it('transfer more than balance on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); - const balance1 = await asset.methods.balance_of_private(accounts[1].address).view(); - const amount = balance0 + 1n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset - .withWallet(wallets[1]) - .methods.transfer(accounts[0].address, accounts[1].address, amount, nonce); - - // Both wallets are connected to same node and PXE so we could just insert directly using - // await wallet.signAndAddAuthWitness(messageHash, ); - // But doing it in two actions to show the flow. - const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); - await wallets[1].addAuthWitness(witness); - - // Perform the transfer - await expect(action.simulate()).rejects.toThrow('Assertion failed: Balance too low'); - expect(await asset.methods.balance_of_private(accounts[0].address).view()).toEqual(balance0); - expect(await asset.methods.balance_of_private(accounts[1].address).view()).toEqual(balance1); - }); - - it.skip('transfer into account to overflow', () => { - // This should already be covered by the mint case earlier. e.g., since we cannot mint to overflow, there is not - // a way to get funds enough to overflow. - // Require direct storage manipulation for us to perform a nice explicit case though. - // See https://github.com/AztecProtocol/aztec-packages/issues/1259 - }); - - it('transfer on behalf of other without approval', async () => { - const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balance0 / 2n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset - .withWallet(wallets[1]) - .methods.transfer(accounts[0].address, accounts[1].address, amount, nonce); - const messageHash = computeAuthWitMessageHash( - accounts[1].address, - wallets[0].getChainId(), - wallets[0].getVersion(), - action.request(), - ); - - await expect(action.simulate()).rejects.toThrow( - `Unknown auth witness for message hash ${messageHash.toString()}`, - ); - }); - - it('transfer on behalf of other, wrong designated caller', async () => { - const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balance0 / 2n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset - .withWallet(wallets[2]) - .methods.transfer(accounts[0].address, accounts[1].address, amount, nonce); - const expectedMessageHash = computeAuthWitMessageHash( - accounts[2].address, - wallets[0].getChainId(), - wallets[0].getVersion(), - action.request(), - ); - - const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); - await wallets[2].addAuthWitness(witness); - - await expect(action.simulate()).rejects.toThrow( - `Unknown auth witness for message hash ${expectedMessageHash.toString()}`, - ); - expect(await asset.methods.balance_of_private(accounts[0].address).view()).toEqual(balance0); - }); - - it('transfer on behalf of other, cancelled authwit', async () => { - const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balance0 / 2n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset - .withWallet(wallets[1]) - .methods.transfer(accounts[0].address, accounts[1].address, amount, nonce); - - const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); - await wallets[1].addAuthWitness(witness); - - await wallets[0].cancelAuthWit(witness.requestHash).send().wait(); - - // Perform the transfer, should fail because nullifier already emitted - const txCancelledAuthwit = asset - .withWallet(wallets[1]) - .methods.transfer(accounts[0].address, accounts[1].address, amount, nonce) - .send(); - await expect(txCancelledAuthwit.wait()).rejects.toThrowError('Transaction '); - }); - - it('transfer on behalf of other, cancelled authwit, flow 2', async () => { - const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balance0 / 2n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset - .withWallet(wallets[1]) - .methods.transfer(accounts[0].address, accounts[1].address, amount, nonce); - - const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); - await wallets[1].addAuthWitness(witness); - - await wallets[0].cancelAuthWit({ caller: accounts[1].address, action }).send().wait(); - - // Perform the transfer, should fail because nullifier already emitted - const txCancelledAuthwit = asset - .withWallet(wallets[1]) - .methods.transfer(accounts[0].address, accounts[1].address, amount, nonce) - .send(); - await expect(txCancelledAuthwit.wait()).rejects.toThrow('Transaction '); - }); - - it('transfer on behalf of other, invalid spend_private_authwit on "from"', async () => { - const nonce = Fr.random(); - - // Should fail as the returned value from the badAccount is malformed - const txCancelledAuthwit = asset - .withWallet(wallets[1]) - .methods.transfer(badAccount.address, accounts[1].address, 0, nonce) - .send(); - await expect(txCancelledAuthwit.wait()).rejects.toThrow( - "Assertion failed: Message not authorized by account 'result == IS_VALID_SELECTOR'", - ); - }); - }); - }); - }); - - describe('Shielding (shield + redeem_shield)', () => { - const secret = Fr.random(); - let secretHash: Fr; - - beforeAll(() => { - secretHash = computeMessageSecretHash(secret); - }); - - it('on behalf of self', async () => { - const balancePub = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balancePub / 2n; - expect(amount).toBeGreaterThan(0n); - - const receipt = await asset.methods.shield(accounts[0].address, amount, secretHash, 0).send().wait(); - - tokenSim.shield(accounts[0].address, amount); - await tokenSim.check(); - - // Redeem it - await addPendingShieldNoteToPXE(0, amount, secretHash, receipt.txHash); - await asset.methods.redeem_shield(accounts[0].address, amount, secret).send().wait(); - - tokenSim.redeemShield(accounts[0].address, amount); - }); - - it('on behalf of other', async () => { - const balancePub = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balancePub / 2n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset.withWallet(wallets[1]).methods.shield(accounts[0].address, amount, secretHash, nonce); - await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); - - const receipt = await action.send().wait(); - - tokenSim.shield(accounts[0].address, amount); - await tokenSim.check(); - - // Check that replaying the shield should fail! - const txReplay = asset - .withWallet(wallets[1]) - .methods.shield(accounts[0].address, amount, secretHash, nonce) - .send(); - await expect(txReplay.wait()).rejects.toThrow('Transaction '); - - // Redeem it - await addPendingShieldNoteToPXE(0, amount, secretHash, receipt.txHash); - await asset.methods.redeem_shield(accounts[0].address, amount, secret).send().wait(); - - tokenSim.redeemShield(accounts[0].address, amount); - }); - - describe('failure cases', () => { - it('on behalf of self (more than balance)', async () => { - const balancePub = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balancePub + 1n; - expect(amount).toBeGreaterThan(0n); - - await expect(asset.methods.shield(accounts[0].address, amount, secretHash, 0).simulate()).rejects.toThrow( - U128_UNDERFLOW_ERROR, - ); - }); - - it('on behalf of self (invalid nonce)', async () => { - const balancePub = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balancePub + 1n; - expect(amount).toBeGreaterThan(0n); - - await expect(asset.methods.shield(accounts[0].address, amount, secretHash, 1).simulate()).rejects.toThrow( - 'Assertion failed: invalid nonce', - ); - }); - - it('on behalf of other (more than balance)', async () => { - const balancePub = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balancePub + 1n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset.withWallet(wallets[1]).methods.shield(accounts[0].address, amount, secretHash, nonce); - await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); - - await expect(action.simulate()).rejects.toThrow(U128_UNDERFLOW_ERROR); - }); - - it('on behalf of other (wrong designated caller)', async () => { - const balancePub = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balancePub + 1n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset.withWallet(wallets[2]).methods.shield(accounts[0].address, amount, secretHash, nonce); - await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); - - await expect(action.simulate()).rejects.toThrow('Assertion failed: Message not authorized by account'); - }); - - it('on behalf of other (without approval)', async () => { - const balance = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balance / 2n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - await expect( - asset.withWallet(wallets[1]).methods.shield(accounts[0].address, amount, secretHash, nonce).simulate(), - ).rejects.toThrow(`Assertion failed: Message not authorized by account`); - }); - }); - }); - - describe('Unshielding', () => { - it('on behalf of self', async () => { - const balancePriv = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balancePriv / 2n; - expect(amount).toBeGreaterThan(0n); - - await asset.methods.unshield(accounts[0].address, accounts[0].address, amount, 0).send().wait(); - - tokenSim.unshield(accounts[0].address, accounts[0].address, amount); - }); - - it('on behalf of other', async () => { - const balancePriv0 = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balancePriv0 / 2n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset - .withWallet(wallets[1]) - .methods.unshield(accounts[0].address, accounts[1].address, amount, nonce); - - // Both wallets are connected to same node and PXE so we could just insert directly - // But doing it in two actions to show the flow. - const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); - await wallets[1].addAuthWitness(witness); - - await action.send().wait(); - tokenSim.unshield(accounts[0].address, accounts[1].address, amount); - - // Perform the transfer again, should fail - const txReplay = asset - .withWallet(wallets[1]) - .methods.unshield(accounts[0].address, accounts[1].address, amount, nonce) - .send(); - await expect(txReplay.wait()).rejects.toThrow('Transaction '); - }); - - describe('failure cases', () => { - it('on behalf of self (more than balance)', async () => { - const balancePriv = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balancePriv + 1n; - expect(amount).toBeGreaterThan(0n); - - await expect( - asset.methods.unshield(accounts[0].address, accounts[0].address, amount, 0).simulate(), - ).rejects.toThrow('Assertion failed: Balance too low'); - }); - - it('on behalf of self (invalid nonce)', async () => { - const balancePriv = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balancePriv + 1n; - expect(amount).toBeGreaterThan(0n); - - await expect( - asset.methods.unshield(accounts[0].address, accounts[0].address, amount, 1).simulate(), - ).rejects.toThrow('Assertion failed: invalid nonce'); - }); - - it('on behalf of other (more than balance)', async () => { - const balancePriv0 = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balancePriv0 + 2n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset - .withWallet(wallets[1]) - .methods.unshield(accounts[0].address, accounts[1].address, amount, nonce); - - // Both wallets are connected to same node and PXE so we could just insert directly - // But doing it in two actions to show the flow. - const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); - await wallets[1].addAuthWitness(witness); - - await expect(action.simulate()).rejects.toThrow('Assertion failed: Balance too low'); - }); - - it('on behalf of other (invalid designated caller)', async () => { - const balancePriv0 = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balancePriv0 + 2n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset - .withWallet(wallets[2]) - .methods.unshield(accounts[0].address, accounts[1].address, amount, nonce); - const expectedMessageHash = computeAuthWitMessageHash( - accounts[2].address, - wallets[0].getChainId(), - wallets[0].getVersion(), - action.request(), - ); - - // Both wallets are connected to same node and PXE so we could just insert directly - // But doing it in two actions to show the flow. - const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); - await wallets[2].addAuthWitness(witness); + it('check name public', async () => { + const t = toString(await asset.methods.un_get_name().view()); + expect(t).toBe(TOKEN_NAME); - await expect(action.simulate()).rejects.toThrow( - `Unknown auth witness for message hash ${expectedMessageHash.toString()}`, + await reader.methods.check_name_public(asset.address, TOKEN_NAME).send().wait(); + await expect(reader.methods.check_name_public(asset.address, 'WRONG_NAME').simulate()).rejects.toThrow( + 'name.is_eq(_what)', ); }); }); }); - describe('Burn', () => { - describe('public', () => { - it('burn less than balance', async () => { - const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balance0 / 2n; - expect(amount).toBeGreaterThan(0n); - await asset.methods.burn_public(accounts[0].address, amount, 0).send().wait(); - - tokenSim.burnPublic(accounts[0].address, amount); - }); - - it('burn on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balance0 / 2n; - expect(amount).toBeGreaterThan(0n); - const nonce = Fr.random(); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset.withWallet(wallets[1]).methods.burn_public(accounts[0].address, amount, nonce); - await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); - - await action.send().wait(); - - tokenSim.burnPublic(accounts[0].address, amount); - - // Check that the message hash is no longer valid. Need to try to send since nullifiers are handled by sequencer. - const txReplay = asset.withWallet(wallets[1]).methods.burn_public(accounts[0].address, amount, nonce).send(); - await expect(txReplay.wait()).rejects.toThrow('Transaction '); - }); - - describe('failure cases', () => { - it('burn more than balance', async () => { - const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balance0 + 1n; - const nonce = 0; - await expect(asset.methods.burn_public(accounts[0].address, amount, nonce).simulate()).rejects.toThrow( - U128_UNDERFLOW_ERROR, - ); - }); - - it('burn on behalf of self with non-zero nonce', async () => { - const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balance0 - 1n; - expect(amount).toBeGreaterThan(0n); - const nonce = 1; - await expect(asset.methods.burn_public(accounts[0].address, amount, nonce).simulate()).rejects.toThrow( - 'Assertion failed: invalid nonce', - ); - }); - - it('burn on behalf of other without "approval"', async () => { - const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balance0 + 1n; - const nonce = Fr.random(); - await expect( - asset.withWallet(wallets[1]).methods.burn_public(accounts[0].address, amount, nonce).simulate(), - ).rejects.toThrow('Assertion failed: Message not authorized by account'); - }); - - it('burn more than balance on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balance0 + 1n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset.withWallet(wallets[1]).methods.burn_public(accounts[0].address, amount, nonce); - await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); - - await expect(action.simulate()).rejects.toThrow(U128_UNDERFLOW_ERROR); - }); - - it('burn on behalf of other, wrong designated caller', async () => { - const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); - const amount = balance0 + 2n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset.withWallet(wallets[1]).methods.burn_public(accounts[0].address, amount, nonce); - await wallets[0].setPublicAuthWit({ caller: accounts[0].address, action }, true).send().wait(); - - await expect( - asset.withWallet(wallets[1]).methods.burn_public(accounts[0].address, amount, nonce).simulate(), - ).rejects.toThrow('Assertion failed: Message not authorized by account'); - }); - }); - }); - - describe('private', () => { - it('burn less than balance', async () => { - const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balance0 / 2n; - expect(amount).toBeGreaterThan(0n); - await asset.methods.burn(accounts[0].address, amount, 0).send().wait(); - tokenSim.burnPrivate(accounts[0].address, amount); - }); - - it('burn on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balance0 / 2n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset.withWallet(wallets[1]).methods.burn(accounts[0].address, amount, nonce); - - // Both wallets are connected to same node and PXE so we could just insert directly - // But doing it in two actions to show the flow. - const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); - await wallets[1].addAuthWitness(witness); - - await asset.withWallet(wallets[1]).methods.burn(accounts[0].address, amount, nonce).send().wait(); - tokenSim.burnPrivate(accounts[0].address, amount); - - // Perform the transfer again, should fail - const txReplay = asset.withWallet(wallets[1]).methods.burn(accounts[0].address, amount, nonce).send(); - await expect(txReplay.wait()).rejects.toThrow('Transaction '); - }); - - describe('failure cases', () => { - it('burn more than balance', async () => { - const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balance0 + 1n; - expect(amount).toBeGreaterThan(0n); - await expect(asset.methods.burn(accounts[0].address, amount, 0).simulate()).rejects.toThrow( - 'Assertion failed: Balance too low', - ); - }); - - it('burn on behalf of self with non-zero nonce', async () => { - const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balance0 - 1n; - expect(amount).toBeGreaterThan(0n); - await expect(asset.methods.burn(accounts[0].address, amount, 1).simulate()).rejects.toThrow( - 'Assertion failed: invalid nonce', - ); - }); - - it('burn more than balance on behalf of other', async () => { - const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balance0 + 1n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset.withWallet(wallets[1]).methods.burn(accounts[0].address, amount, nonce); - - // Both wallets are connected to same node and PXE so we could just insert directly - // But doing it in two actions to show the flow. - const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); - await wallets[1].addAuthWitness(witness); - - await expect(action.simulate()).rejects.toThrow('Assertion failed: Balance too low'); - }); - - it('burn on behalf of other without approval', async () => { - const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balance0 / 2n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset.withWallet(wallets[1]).methods.burn(accounts[0].address, amount, nonce); - const messageHash = computeAuthWitMessageHash( - accounts[1].address, - wallets[0].getChainId(), - wallets[0].getVersion(), - action.request(), - ); - - await expect(action.simulate()).rejects.toThrow( - `Unknown auth witness for message hash ${messageHash.toString()}`, - ); - }); - - it('on behalf of other (invalid designated caller)', async () => { - const balancePriv0 = await asset.methods.balance_of_private(accounts[0].address).view(); - const amount = balancePriv0 + 2n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset.withWallet(wallets[2]).methods.burn(accounts[0].address, amount, nonce); - const expectedMessageHash = computeAuthWitMessageHash( - accounts[2].address, - wallets[0].getChainId(), - wallets[0].getVersion(), - action.request(), - ); - - const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); - await wallets[2].addAuthWitness(witness); - - await expect(action.simulate()).rejects.toThrow( - `Unknown auth witness for message hash ${expectedMessageHash.toString()}`, - ); - }); - }); - }); - }); + // describe('symbol', () => { + // it('private', async () => { + // const t = toString(await asset.methods.un_get_symbol().view()); + // expect(t).toBe(TOKEN_SYMBOL); + + // await reader.methods.check_symbol_private(asset.address, TOKEN_SYMBOL).send().wait(); + + // await expect(reader.methods.check_symbol_private(asset.address, 'WRONG_SYMBOL').simulate()).rejects.toThrow( + // "Cannot satisfy constraint 'symbol.is_eq(_what)'", + // ); + // }); + // it('public', async () => { + // const t = toString(await asset.methods.un_get_symbol().view()); + // expect(t).toBe(TOKEN_SYMBOL); + + // await reader.methods.check_symbol_public(asset.address, TOKEN_SYMBOL).send().wait(); + + // await expect(reader.methods.check_symbol_public(asset.address, 'WRONG_SYMBOL').simulate()).rejects.toThrow( + // "Failed to solve brillig function, reason: explicit trap hit in brillig 'symbol.is_eq(_what)'", + // ); + // }); + // }); + + // describe('decimals', () => { + // it('private', async () => { + // const t = await asset.methods.un_get_decimals().view(); + // expect(t).toBe(TOKEN_DECIMALS); + + // await reader.methods.check_decimals_private(asset.address, TOKEN_DECIMALS).send().wait(); + + // await expect(reader.methods.check_decimals_private(asset.address, 99).simulate()).rejects.toThrow( + // "Cannot satisfy constraint 'ret[0] as u8 == what'", + // ); + // }); + + // it('public', async () => { + // const t = await asset.methods.un_get_decimals().view(); + // expect(t).toBe(TOKEN_DECIMALS); + + // await reader.methods.check_decimals_public(asset.address, TOKEN_DECIMALS).send().wait(); + + // await expect(reader.methods.check_decimals_public(asset.address, 99).simulate()).rejects.toThrow( + // "Failed to solve brillig function, reason: explicit trap hit in brillig 'ret[0] as u8 == what'", + // ); + // }); + // }); + // }); + + // describe('Access controlled functions', () => { + // it('Set admin', async () => { + // await asset.methods.set_admin(accounts[1].address).send().wait(); + // expect(await asset.methods.admin().view()).toBe(accounts[1].address.toBigInt()); + // }); + + // it('Add minter as admin', async () => { + // await asset.withWallet(wallets[1]).methods.set_minter(accounts[1].address, true).send().wait(); + // expect(await asset.methods.is_minter(accounts[1].address).view()).toBe(true); + // }); + + // it('Revoke minter as admin', async () => { + // await asset.withWallet(wallets[1]).methods.set_minter(accounts[1].address, false).send().wait(); + // expect(await asset.methods.is_minter(accounts[1].address).view()).toBe(false); + // }); + + // describe('failure cases', () => { + // it('Set admin (not admin)', async () => { + // await expect(asset.methods.set_admin(accounts[0].address).simulate()).rejects.toThrow( + // 'Assertion failed: caller is not admin', + // ); + // }); + // it('Revoke minter not as admin', async () => { + // await expect(asset.methods.set_minter(accounts[0].address, false).simulate()).rejects.toThrow( + // 'Assertion failed: caller is not admin', + // ); + // }); + // }); + // }); + + // describe('Minting', () => { + // describe('Public', () => { + // it('as minter', async () => { + // const amount = 10000n; + // await asset.methods.mint_public(accounts[0].address, amount).send().wait(); + + // tokenSim.mintPublic(accounts[0].address, amount); + // expect(await asset.methods.balance_of_public(accounts[0].address).view()).toEqual( + // tokenSim.balanceOfPublic(accounts[0].address), + // ); + // expect(await asset.methods.total_supply().view()).toEqual(tokenSim.totalSupply); + // }); + + // describe('failure cases', () => { + // it('as non-minter', async () => { + // const amount = 10000n; + // await expect( + // asset.withWallet(wallets[1]).methods.mint_public(accounts[0].address, amount).simulate(), + // ).rejects.toThrow('Assertion failed: caller is not minter'); + // }); + + // it('mint >u128 tokens to overflow', async () => { + // const amount = 2n ** 128n; // U128::max() + 1; + // await expect(asset.methods.mint_public(accounts[0].address, amount).simulate()).rejects.toThrow( + // BITSIZE_TOO_BIG_ERROR, + // ); + // }); + + // it('mint u128', async () => { + // const amount = 2n ** 128n - tokenSim.balanceOfPublic(accounts[0].address); + // await expect(asset.methods.mint_public(accounts[0].address, amount).simulate()).rejects.toThrow( + // U128_OVERFLOW_ERROR, + // ); + // }); + + // it('mint u128', async () => { + // const amount = 2n ** 128n - tokenSim.balanceOfPublic(accounts[0].address); + // await expect(asset.methods.mint_public(accounts[1].address, amount).simulate()).rejects.toThrow( + // U128_OVERFLOW_ERROR, + // ); + // }); + // }); + // }); + + // describe('Private', () => { + // const secret = Fr.random(); + // const amount = 10000n; + // let secretHash: Fr; + // let txHash: TxHash; + + // beforeAll(() => { + // secretHash = computeMessageSecretHash(secret); + // }); + + // describe('Mint flow', () => { + // it('mint_private as minter', async () => { + // const receipt = await asset.methods.mint_private(amount, secretHash).send().wait(); + // tokenSim.mintPrivate(amount); + // txHash = receipt.txHash; + // }); + + // it('redeem as recipient', async () => { + // await addPendingShieldNoteToPXE(0, amount, secretHash, txHash); + // const txClaim = asset.methods.redeem_shield(accounts[0].address, amount, secret).send(); + // // docs:start:debug + // const receiptClaim = await txClaim.wait({ debug: true }); + // // docs:end:debug + // tokenSim.redeemShield(accounts[0].address, amount); + // // 1 note should be created containing `amount` of tokens + // const { visibleNotes } = receiptClaim.debugInfo!; + // expect(visibleNotes.length).toBe(1); + // expect(visibleNotes[0].note.items[0].toBigInt()).toBe(amount); + // }); + // }); + + // describe('failure cases', () => { + // it('try to redeem as recipient (double-spend) [REVERTS]', async () => { + // await expect(addPendingShieldNoteToPXE(0, amount, secretHash, txHash)).rejects.toThrow( + // 'The note has been destroyed.', + // ); + // await expect(asset.methods.redeem_shield(accounts[0].address, amount, secret).simulate()).rejects.toThrow( + // `Assertion failed: Cannot return zero notes`, + // ); + // }); + + // it('mint_private as non-minter', async () => { + // await expect( + // asset.withWallet(wallets[1]).methods.mint_private(amount, secretHash).simulate(), + // ).rejects.toThrow('Assertion failed: caller is not minter'); + // }); + + // it('mint >u128 tokens to overflow', async () => { + // const amount = 2n ** 128n; // U128::max() + 1; + // await expect(asset.methods.mint_private(amount, secretHash).simulate()).rejects.toThrow( + // BITSIZE_TOO_BIG_ERROR, + // ); + // }); + + // it('mint u128', async () => { + // const amount = 2n ** 128n - tokenSim.balanceOfPrivate(accounts[0].address); + // expect(amount).toBeLessThan(2n ** 128n); + // await expect(asset.methods.mint_private(amount, secretHash).simulate()).rejects.toThrow(U128_OVERFLOW_ERROR); + // }); + + // it('mint u128', async () => { + // const amount = 2n ** 128n - tokenSim.totalSupply; + // await expect(asset.methods.mint_private(amount, secretHash).simulate()).rejects.toThrow(U128_OVERFLOW_ERROR); + // }); + // }); + // }); + // }); + + // describe('Transfer', () => { + // describe('public', () => { + // it('transfer less than balance', async () => { + // const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balance0 / 2n; + // expect(amount).toBeGreaterThan(0n); + // await asset.methods.transfer_public(accounts[0].address, accounts[1].address, amount, 0).send().wait(); + + // tokenSim.transferPublic(accounts[0].address, accounts[1].address, amount); + // }); + + // it('transfer to self', async () => { + // const balance = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balance / 2n; + // expect(amount).toBeGreaterThan(0n); + // await asset.methods.transfer_public(accounts[0].address, accounts[0].address, amount, 0).send().wait(); + + // tokenSim.transferPublic(accounts[0].address, accounts[0].address, amount); + // }); + + // it('transfer on behalf of other', async () => { + // const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balance0 / 2n; + // expect(amount).toBeGreaterThan(0n); + // const nonce = Fr.random(); + + // // docs:start:authwit_public_transfer_example + // const action = asset + // .withWallet(wallets[1]) + // .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce); + + // await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); + // // docs:end:authwit_public_transfer_example + + // // Perform the transfer + // await action.send().wait(); + + // tokenSim.transferPublic(accounts[0].address, accounts[1].address, amount); + + // // Check that the message hash is no longer valid. Need to try to send since nullifiers are handled by sequencer. + // const txReplay = asset + // .withWallet(wallets[1]) + // .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce) + // .send(); + // await expect(txReplay.wait()).rejects.toThrow('Transaction '); + // }); + + // describe('failure cases', () => { + // it('transfer more than balance', async () => { + // const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balance0 + 1n; + // const nonce = 0; + // await expect( + // asset.methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce).simulate(), + // ).rejects.toThrow(U128_UNDERFLOW_ERROR); + // }); + + // it('transfer on behalf of self with non-zero nonce', async () => { + // const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balance0 - 1n; + // const nonce = 1; + // await expect( + // asset.methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce).simulate(), + // ).rejects.toThrow('Assertion failed: invalid nonce'); + // }); + + // it('transfer on behalf of other without "approval"', async () => { + // const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balance0 + 1n; + // const nonce = Fr.random(); + // await expect( + // asset + // .withWallet(wallets[1]) + // .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce) + // .simulate(), + // ).rejects.toThrow('Assertion failed: Message not authorized by account'); + // }); + + // it('transfer more than balance on behalf of other', async () => { + // const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); + // const balance1 = await asset.methods.balance_of_public(accounts[1].address).view(); + // const amount = balance0 + 1n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // const action = asset + // .withWallet(wallets[1]) + // .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce); + + // expect( + // await wallets[0].lookupValidity(wallets[0].getAddress(), { caller: accounts[1].address, action }), + // ).toEqual({ + // isValidInPrivate: false, + // isValidInPublic: false, + // }); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); + + // expect( + // await wallets[0].lookupValidity(wallets[0].getAddress(), { caller: accounts[1].address, action }), + // ).toEqual({ + // isValidInPrivate: false, + // isValidInPublic: true, + // }); + + // // Perform the transfer + // await expect(action.simulate()).rejects.toThrow(U128_UNDERFLOW_ERROR); + + // expect(await asset.methods.balance_of_public(accounts[0].address).view()).toEqual(balance0); + // expect(await asset.methods.balance_of_public(accounts[1].address).view()).toEqual(balance1); + // }); + + // it('transfer on behalf of other, wrong designated caller', async () => { + // const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); + // const balance1 = await asset.methods.balance_of_public(accounts[1].address).view(); + // const amount = balance0 + 2n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // const action = asset + // .withWallet(wallets[1]) + // .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce); + + // await wallets[0].setPublicAuthWit({ caller: accounts[0].address, action }, true).send().wait(); + + // // Perform the transfer + // await expect(action.simulate()).rejects.toThrow('Assertion failed: Message not authorized by account'); + + // expect(await asset.methods.balance_of_public(accounts[0].address).view()).toEqual(balance0); + // expect(await asset.methods.balance_of_public(accounts[1].address).view()).toEqual(balance1); + // }); + + // it('transfer on behalf of other, wrong designated caller', async () => { + // const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); + // const balance1 = await asset.methods.balance_of_public(accounts[1].address).view(); + // const amount = balance0 + 2n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // const action = asset + // .withWallet(wallets[1]) + // .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce); + // await wallets[0].setPublicAuthWit({ caller: accounts[0].address, action }, true).send().wait(); + + // // Perform the transfer + // await expect(action.simulate()).rejects.toThrow('Assertion failed: Message not authorized by account'); + + // expect(await asset.methods.balance_of_public(accounts[0].address).view()).toEqual(balance0); + // expect(await asset.methods.balance_of_public(accounts[1].address).view()).toEqual(balance1); + // }); + + // it('transfer on behalf of other, cancelled authwit', async () => { + // const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balance0 / 2n; + // expect(amount).toBeGreaterThan(0n); + // const nonce = Fr.random(); + + // const action = asset + // .withWallet(wallets[1]) + // .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce); + + // await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); + + // await wallets[0].cancelAuthWit({ caller: accounts[1].address, action }).send().wait(); + + // // Check that the authwit is no longer valid. Need to try to send since nullifiers are handled by sequencer. + // const txCancelledAuthwit = asset + // .withWallet(wallets[1]) + // .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce) + // .send(); + // await expect(txCancelledAuthwit.wait()).rejects.toThrowError('Transaction '); + // }); + + // it('transfer on behalf of other, cancelled authwit, flow 2', async () => { + // const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balance0 / 2n; + // expect(amount).toBeGreaterThan(0n); + // const nonce = Fr.random(); + + // const action = asset + // .withWallet(wallets[1]) + // .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce); + + // await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); + + // await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, false).send().wait(); + + // // Check that the authwit is no longer valid. Need to try to send since nullifiers are handled by sequencer. + // const txCancelledAuthwit = asset + // .withWallet(wallets[1]) + // .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce) + // .send(); + // await expect(txCancelledAuthwit.wait()).rejects.toThrowError('Transaction '); + // }); + + // it('transfer on behalf of other, cancelled authwit, flow 3', async () => { + // const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balance0 / 2n; + // expect(amount).toBeGreaterThan(0n); + // const nonce = Fr.random(); + + // const action = asset + // .withWallet(wallets[1]) + // .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce); + // const messageHash = computeAuthWitMessageHash( + // accounts[1].address, + // wallets[0].getChainId(), + // wallets[0].getVersion(), + // action.request(), + // ); + + // await wallets[0].setPublicAuthWit(messageHash, true).send().wait(); + + // await wallets[0].cancelAuthWit(messageHash).send().wait(); + + // // Check that the message hash is no longer valid. Need to try to send since nullifiers are handled by sequencer. + // const txCancelledAuthwit = asset + // .withWallet(wallets[1]) + // .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce) + // .send(); + // await expect(txCancelledAuthwit.wait()).rejects.toThrow('Transaction '); + // }); + + // it('transfer on behalf of other, invalid spend_public_authwit on "from"', async () => { + // const nonce = Fr.random(); + + // // Should fail as the returned value from the badAccount is malformed + // const txCancelledAuthwit = asset + // .withWallet(wallets[1]) + // .methods.transfer_public(badAccount.address, accounts[1].address, 0, nonce) + // .send(); + // await expect(txCancelledAuthwit.wait()).rejects.toThrow( + // "Assertion failed: Message not authorized by account 'result == IS_VALID_SELECTOR'", + // ); + // }); + + // it.skip('transfer into account to overflow', () => { + // // This should already be covered by the mint case earlier. e.g., since we cannot mint to overflow, there is not + // // a way to get funds enough to overflow. + // // Require direct storage manipulation for us to perform a nice explicit case though. + // // See https://github.com/AztecProtocol/aztec-packages/issues/1259 + // }); + // }); + // }); + + // describe('private', () => { + // it('transfer less than balance', async () => { + // const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balance0 / 2n; + // expect(amount).toBeGreaterThan(0n); + // await asset.methods.transfer(accounts[0].address, accounts[1].address, amount, 0).send().wait(); + // tokenSim.transferPrivate(accounts[0].address, accounts[1].address, amount); + // }); + + // it('transfer to self', async () => { + // const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balance0 / 2n; + // expect(amount).toBeGreaterThan(0n); + // await asset.methods.transfer(accounts[0].address, accounts[0].address, amount, 0).send().wait(); + // tokenSim.transferPrivate(accounts[0].address, accounts[0].address, amount); + // }); + + // it('transfer on behalf of other', async () => { + // const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balance0 / 2n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // // docs:start:authwit_transfer_example + // const action = asset + // .withWallet(wallets[1]) + // .methods.transfer(accounts[0].address, accounts[1].address, amount, nonce); + + // const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); + // await wallets[1].addAuthWitness(witness); + // expect( + // await wallets[0].lookupValidity(wallets[0].getAddress(), { caller: accounts[1].address, action }), + // ).toEqual({ + // isValidInPrivate: true, + // isValidInPublic: false, + // }); + // // docs:end:authwit_transfer_example + + // // Perform the transfer + // await action.send().wait(); + // tokenSim.transferPrivate(accounts[0].address, accounts[1].address, amount); + + // // Perform the transfer again, should fail + // const txReplay = asset + // .withWallet(wallets[1]) + // .methods.transfer(accounts[0].address, accounts[1].address, amount, nonce) + // .send(); + // await expect(txReplay.wait()).rejects.toThrow('Transaction '); + // }); + + // describe('failure cases', () => { + // it('transfer more than balance', async () => { + // const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balance0 + 1n; + // expect(amount).toBeGreaterThan(0n); + // await expect( + // asset.methods.transfer(accounts[0].address, accounts[1].address, amount, 0).simulate(), + // ).rejects.toThrow('Assertion failed: Balance too low'); + // }); + + // it('transfer on behalf of self with non-zero nonce', async () => { + // const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balance0 - 1n; + // expect(amount).toBeGreaterThan(0n); + // await expect( + // asset.methods.transfer(accounts[0].address, accounts[1].address, amount, 1).simulate(), + // ).rejects.toThrow('Assertion failed: invalid nonce'); + // }); + + // it('transfer more than balance on behalf of other', async () => { + // const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); + // const balance1 = await asset.methods.balance_of_private(accounts[1].address).view(); + // const amount = balance0 + 1n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // const action = asset + // .withWallet(wallets[1]) + // .methods.transfer(accounts[0].address, accounts[1].address, amount, nonce); + + // // Both wallets are connected to same node and PXE so we could just insert directly using + // // await wallet.signAndAddAuthWitness(messageHash, ); + // // But doing it in two actions to show the flow. + // const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); + // await wallets[1].addAuthWitness(witness); + + // // Perform the transfer + // await expect(action.simulate()).rejects.toThrow('Assertion failed: Balance too low'); + // expect(await asset.methods.balance_of_private(accounts[0].address).view()).toEqual(balance0); + // expect(await asset.methods.balance_of_private(accounts[1].address).view()).toEqual(balance1); + // }); + + // it.skip('transfer into account to overflow', () => { + // // This should already be covered by the mint case earlier. e.g., since we cannot mint to overflow, there is not + // // a way to get funds enough to overflow. + // // Require direct storage manipulation for us to perform a nice explicit case though. + // // See https://github.com/AztecProtocol/aztec-packages/issues/1259 + // }); + + // it('transfer on behalf of other without approval', async () => { + // const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balance0 / 2n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // const action = asset + // .withWallet(wallets[1]) + // .methods.transfer(accounts[0].address, accounts[1].address, amount, nonce); + // const messageHash = computeAuthWitMessageHash( + // accounts[1].address, + // wallets[0].getChainId(), + // wallets[0].getVersion(), + // action.request(), + // ); + + // await expect(action.simulate()).rejects.toThrow( + // `Unknown auth witness for message hash ${messageHash.toString()}`, + // ); + // }); + + // it('transfer on behalf of other, wrong designated caller', async () => { + // const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balance0 / 2n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // const action = asset + // .withWallet(wallets[2]) + // .methods.transfer(accounts[0].address, accounts[1].address, amount, nonce); + // const expectedMessageHash = computeAuthWitMessageHash( + // accounts[2].address, + // wallets[0].getChainId(), + // wallets[0].getVersion(), + // action.request(), + // ); + + // const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); + // await wallets[2].addAuthWitness(witness); + + // await expect(action.simulate()).rejects.toThrow( + // `Unknown auth witness for message hash ${expectedMessageHash.toString()}`, + // ); + // expect(await asset.methods.balance_of_private(accounts[0].address).view()).toEqual(balance0); + // }); + + // it('transfer on behalf of other, cancelled authwit', async () => { + // const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balance0 / 2n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // const action = asset + // .withWallet(wallets[1]) + // .methods.transfer(accounts[0].address, accounts[1].address, amount, nonce); + + // const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); + // await wallets[1].addAuthWitness(witness); + + // await wallets[0].cancelAuthWit(witness.requestHash).send().wait(); + + // // Perform the transfer, should fail because nullifier already emitted + // const txCancelledAuthwit = asset + // .withWallet(wallets[1]) + // .methods.transfer(accounts[0].address, accounts[1].address, amount, nonce) + // .send(); + // await expect(txCancelledAuthwit.wait()).rejects.toThrowError('Transaction '); + // }); + + // it('transfer on behalf of other, cancelled authwit, flow 2', async () => { + // const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balance0 / 2n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // const action = asset + // .withWallet(wallets[1]) + // .methods.transfer(accounts[0].address, accounts[1].address, amount, nonce); + + // const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); + // await wallets[1].addAuthWitness(witness); + + // await wallets[0].cancelAuthWit({ caller: accounts[1].address, action }).send().wait(); + + // // Perform the transfer, should fail because nullifier already emitted + // const txCancelledAuthwit = asset + // .withWallet(wallets[1]) + // .methods.transfer(accounts[0].address, accounts[1].address, amount, nonce) + // .send(); + // await expect(txCancelledAuthwit.wait()).rejects.toThrow('Transaction '); + // }); + + // it('transfer on behalf of other, invalid spend_private_authwit on "from"', async () => { + // const nonce = Fr.random(); + + // // Should fail as the returned value from the badAccount is malformed + // const txCancelledAuthwit = asset + // .withWallet(wallets[1]) + // .methods.transfer(badAccount.address, accounts[1].address, 0, nonce) + // .send(); + // await expect(txCancelledAuthwit.wait()).rejects.toThrow( + // "Assertion failed: Message not authorized by account 'result == IS_VALID_SELECTOR'", + // ); + // }); + // }); + // }); + // }); + + // describe('Shielding (shield + redeem_shield)', () => { + // const secret = Fr.random(); + // let secretHash: Fr; + + // beforeAll(() => { + // secretHash = computeMessageSecretHash(secret); + // }); + + // it('on behalf of self', async () => { + // const balancePub = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balancePub / 2n; + // expect(amount).toBeGreaterThan(0n); + + // const receipt = await asset.methods.shield(accounts[0].address, amount, secretHash, 0).send().wait(); + + // tokenSim.shield(accounts[0].address, amount); + // await tokenSim.check(); + + // // Redeem it + // await addPendingShieldNoteToPXE(0, amount, secretHash, receipt.txHash); + // await asset.methods.redeem_shield(accounts[0].address, amount, secret).send().wait(); + + // tokenSim.redeemShield(accounts[0].address, amount); + // }); + + // it('on behalf of other', async () => { + // const balancePub = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balancePub / 2n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // const action = asset.withWallet(wallets[1]).methods.shield(accounts[0].address, amount, secretHash, nonce); + // await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); + + // const receipt = await action.send().wait(); + + // tokenSim.shield(accounts[0].address, amount); + // await tokenSim.check(); + + // // Check that replaying the shield should fail! + // const txReplay = asset + // .withWallet(wallets[1]) + // .methods.shield(accounts[0].address, amount, secretHash, nonce) + // .send(); + // await expect(txReplay.wait()).rejects.toThrow('Transaction '); + + // // Redeem it + // await addPendingShieldNoteToPXE(0, amount, secretHash, receipt.txHash); + // await asset.methods.redeem_shield(accounts[0].address, amount, secret).send().wait(); + + // tokenSim.redeemShield(accounts[0].address, amount); + // }); + + // describe('failure cases', () => { + // it('on behalf of self (more than balance)', async () => { + // const balancePub = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balancePub + 1n; + // expect(amount).toBeGreaterThan(0n); + + // await expect(asset.methods.shield(accounts[0].address, amount, secretHash, 0).simulate()).rejects.toThrow( + // U128_UNDERFLOW_ERROR, + // ); + // }); + + // it('on behalf of self (invalid nonce)', async () => { + // const balancePub = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balancePub + 1n; + // expect(amount).toBeGreaterThan(0n); + + // await expect(asset.methods.shield(accounts[0].address, amount, secretHash, 1).simulate()).rejects.toThrow( + // 'Assertion failed: invalid nonce', + // ); + // }); + + // it('on behalf of other (more than balance)', async () => { + // const balancePub = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balancePub + 1n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // const action = asset.withWallet(wallets[1]).methods.shield(accounts[0].address, amount, secretHash, nonce); + // await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); + + // await expect(action.simulate()).rejects.toThrow(U128_UNDERFLOW_ERROR); + // }); + + // it('on behalf of other (wrong designated caller)', async () => { + // const balancePub = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balancePub + 1n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // const action = asset.withWallet(wallets[2]).methods.shield(accounts[0].address, amount, secretHash, nonce); + // await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); + + // await expect(action.simulate()).rejects.toThrow('Assertion failed: Message not authorized by account'); + // }); + + // it('on behalf of other (without approval)', async () => { + // const balance = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balance / 2n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // await expect( + // asset.withWallet(wallets[1]).methods.shield(accounts[0].address, amount, secretHash, nonce).simulate(), + // ).rejects.toThrow(`Assertion failed: Message not authorized by account`); + // }); + // }); + // }); + + // describe('Unshielding', () => { + // it('on behalf of self', async () => { + // const balancePriv = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balancePriv / 2n; + // expect(amount).toBeGreaterThan(0n); + + // await asset.methods.unshield(accounts[0].address, accounts[0].address, amount, 0).send().wait(); + + // tokenSim.unshield(accounts[0].address, accounts[0].address, amount); + // }); + + // it('on behalf of other', async () => { + // const balancePriv0 = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balancePriv0 / 2n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // const action = asset + // .withWallet(wallets[1]) + // .methods.unshield(accounts[0].address, accounts[1].address, amount, nonce); + + // // Both wallets are connected to same node and PXE so we could just insert directly + // // But doing it in two actions to show the flow. + // const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); + // await wallets[1].addAuthWitness(witness); + + // await action.send().wait(); + // tokenSim.unshield(accounts[0].address, accounts[1].address, amount); + + // // Perform the transfer again, should fail + // const txReplay = asset + // .withWallet(wallets[1]) + // .methods.unshield(accounts[0].address, accounts[1].address, amount, nonce) + // .send(); + // await expect(txReplay.wait()).rejects.toThrow('Transaction '); + // }); + + // describe('failure cases', () => { + // it('on behalf of self (more than balance)', async () => { + // const balancePriv = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balancePriv + 1n; + // expect(amount).toBeGreaterThan(0n); + + // await expect( + // asset.methods.unshield(accounts[0].address, accounts[0].address, amount, 0).simulate(), + // ).rejects.toThrow('Assertion failed: Balance too low'); + // }); + + // it('on behalf of self (invalid nonce)', async () => { + // const balancePriv = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balancePriv + 1n; + // expect(amount).toBeGreaterThan(0n); + + // await expect( + // asset.methods.unshield(accounts[0].address, accounts[0].address, amount, 1).simulate(), + // ).rejects.toThrow('Assertion failed: invalid nonce'); + // }); + + // it('on behalf of other (more than balance)', async () => { + // const balancePriv0 = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balancePriv0 + 2n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // const action = asset + // .withWallet(wallets[1]) + // .methods.unshield(accounts[0].address, accounts[1].address, amount, nonce); + + // // Both wallets are connected to same node and PXE so we could just insert directly + // // But doing it in two actions to show the flow. + // const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); + // await wallets[1].addAuthWitness(witness); + + // await expect(action.simulate()).rejects.toThrow('Assertion failed: Balance too low'); + // }); + + // it('on behalf of other (invalid designated caller)', async () => { + // const balancePriv0 = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balancePriv0 + 2n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // const action = asset + // .withWallet(wallets[2]) + // .methods.unshield(accounts[0].address, accounts[1].address, amount, nonce); + // const expectedMessageHash = computeAuthWitMessageHash( + // accounts[2].address, + // wallets[0].getChainId(), + // wallets[0].getVersion(), + // action.request(), + // ); + + // // Both wallets are connected to same node and PXE so we could just insert directly + // // But doing it in two actions to show the flow. + // const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); + // await wallets[2].addAuthWitness(witness); + + // await expect(action.simulate()).rejects.toThrow( + // `Unknown auth witness for message hash ${expectedMessageHash.toString()}`, + // ); + // }); + // }); + // }); + + // describe('Burn', () => { + // describe('public', () => { + // it('burn less than balance', async () => { + // const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balance0 / 2n; + // expect(amount).toBeGreaterThan(0n); + // await asset.methods.burn_public(accounts[0].address, amount, 0).send().wait(); + + // tokenSim.burnPublic(accounts[0].address, amount); + // }); + + // it('burn on behalf of other', async () => { + // const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balance0 / 2n; + // expect(amount).toBeGreaterThan(0n); + // const nonce = Fr.random(); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // const action = asset.withWallet(wallets[1]).methods.burn_public(accounts[0].address, amount, nonce); + // await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); + + // await action.send().wait(); + + // tokenSim.burnPublic(accounts[0].address, amount); + + // // Check that the message hash is no longer valid. Need to try to send since nullifiers are handled by sequencer. + // const txReplay = asset.withWallet(wallets[1]).methods.burn_public(accounts[0].address, amount, nonce).send(); + // await expect(txReplay.wait()).rejects.toThrow('Transaction '); + // }); + + // describe('failure cases', () => { + // it('burn more than balance', async () => { + // const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balance0 + 1n; + // const nonce = 0; + // await expect(asset.methods.burn_public(accounts[0].address, amount, nonce).simulate()).rejects.toThrow( + // U128_UNDERFLOW_ERROR, + // ); + // }); + + // it('burn on behalf of self with non-zero nonce', async () => { + // const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balance0 - 1n; + // expect(amount).toBeGreaterThan(0n); + // const nonce = 1; + // await expect(asset.methods.burn_public(accounts[0].address, amount, nonce).simulate()).rejects.toThrow( + // 'Assertion failed: invalid nonce', + // ); + // }); + + // it('burn on behalf of other without "approval"', async () => { + // const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balance0 + 1n; + // const nonce = Fr.random(); + // await expect( + // asset.withWallet(wallets[1]).methods.burn_public(accounts[0].address, amount, nonce).simulate(), + // ).rejects.toThrow('Assertion failed: Message not authorized by account'); + // }); + + // it('burn more than balance on behalf of other', async () => { + // const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balance0 + 1n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // const action = asset.withWallet(wallets[1]).methods.burn_public(accounts[0].address, amount, nonce); + // await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); + + // await expect(action.simulate()).rejects.toThrow(U128_UNDERFLOW_ERROR); + // }); + + // it('burn on behalf of other, wrong designated caller', async () => { + // const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); + // const amount = balance0 + 2n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // const action = asset.withWallet(wallets[1]).methods.burn_public(accounts[0].address, amount, nonce); + // await wallets[0].setPublicAuthWit({ caller: accounts[0].address, action }, true).send().wait(); + + // await expect( + // asset.withWallet(wallets[1]).methods.burn_public(accounts[0].address, amount, nonce).simulate(), + // ).rejects.toThrow('Assertion failed: Message not authorized by account'); + // }); + // }); + // }); + + // describe('private', () => { + // it('burn less than balance', async () => { + // const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balance0 / 2n; + // expect(amount).toBeGreaterThan(0n); + // await asset.methods.burn(accounts[0].address, amount, 0).send().wait(); + // tokenSim.burnPrivate(accounts[0].address, amount); + // }); + + // it('burn on behalf of other', async () => { + // const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balance0 / 2n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // const action = asset.withWallet(wallets[1]).methods.burn(accounts[0].address, amount, nonce); + + // // Both wallets are connected to same node and PXE so we could just insert directly + // // But doing it in two actions to show the flow. + // const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); + // await wallets[1].addAuthWitness(witness); + + // await asset.withWallet(wallets[1]).methods.burn(accounts[0].address, amount, nonce).send().wait(); + // tokenSim.burnPrivate(accounts[0].address, amount); + + // // Perform the transfer again, should fail + // const txReplay = asset.withWallet(wallets[1]).methods.burn(accounts[0].address, amount, nonce).send(); + // await expect(txReplay.wait()).rejects.toThrow('Transaction '); + // }); + + // describe('failure cases', () => { + // it('burn more than balance', async () => { + // const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balance0 + 1n; + // expect(amount).toBeGreaterThan(0n); + // await expect(asset.methods.burn(accounts[0].address, amount, 0).simulate()).rejects.toThrow( + // 'Assertion failed: Balance too low', + // ); + // }); + + // it('burn on behalf of self with non-zero nonce', async () => { + // const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balance0 - 1n; + // expect(amount).toBeGreaterThan(0n); + // await expect(asset.methods.burn(accounts[0].address, amount, 1).simulate()).rejects.toThrow( + // 'Assertion failed: invalid nonce', + // ); + // }); + + // it('burn more than balance on behalf of other', async () => { + // const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balance0 + 1n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // const action = asset.withWallet(wallets[1]).methods.burn(accounts[0].address, amount, nonce); + + // // Both wallets are connected to same node and PXE so we could just insert directly + // // But doing it in two actions to show the flow. + // const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); + // await wallets[1].addAuthWitness(witness); + + // await expect(action.simulate()).rejects.toThrow('Assertion failed: Balance too low'); + // }); + + // it('burn on behalf of other without approval', async () => { + // const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balance0 / 2n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // const action = asset.withWallet(wallets[1]).methods.burn(accounts[0].address, amount, nonce); + // const messageHash = computeAuthWitMessageHash( + // accounts[1].address, + // wallets[0].getChainId(), + // wallets[0].getVersion(), + // action.request(), + // ); + + // await expect(action.simulate()).rejects.toThrow( + // `Unknown auth witness for message hash ${messageHash.toString()}`, + // ); + // }); + + // it('on behalf of other (invalid designated caller)', async () => { + // const balancePriv0 = await asset.methods.balance_of_private(accounts[0].address).view(); + // const amount = balancePriv0 + 2n; + // const nonce = Fr.random(); + // expect(amount).toBeGreaterThan(0n); + + // // We need to compute the message we want to sign and add it to the wallet as approved + // const action = asset.withWallet(wallets[2]).methods.burn(accounts[0].address, amount, nonce); + // const expectedMessageHash = computeAuthWitMessageHash( + // accounts[2].address, + // wallets[0].getChainId(), + // wallets[0].getVersion(), + // action.request(), + // ); + + // const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); + // await wallets[2].addAuthWitness(witness); + + // await expect(action.simulate()).rejects.toThrow( + // `Unknown auth witness for message hash ${expectedMessageHash.toString()}`, + // ); + // }); + // }); + // }); + // }); }); diff --git a/yarn-project/end-to-end/src/fixtures/get_acvm_config.ts b/yarn-project/end-to-end/src/fixtures/get_acvm_config.ts new file mode 100644 index 00000000000..f2ee106043a --- /dev/null +++ b/yarn-project/end-to-end/src/fixtures/get_acvm_config.ts @@ -0,0 +1,48 @@ +import { type DebugLogger, fileURLToPath } from '@aztec/aztec.js'; +import { randomBytes } from '@aztec/foundation/crypto'; + +import * as fs from 'fs/promises'; +import * as path from 'path'; + +export { deployAndInitializeTokenAndBridgeContracts } from '../shared/cross_chain_test_harness.js'; + +const { + // PXE_URL = '', + NOIR_RELEASE_DIR = 'noir-repo/target/release', + TEMP_DIR = '/tmp', + ACVM_BINARY_PATH = '', + ACVM_WORKING_DIRECTORY = '', + // ENABLE_GAS = '', +} = process.env; + +// Determines if we have access to the acvm binary and a tmp folder for temp files +export async function getACVMConfig(logger: DebugLogger) { + try { + const expectedAcvmPath = ACVM_BINARY_PATH + ? ACVM_BINARY_PATH + : `${path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../noir/', NOIR_RELEASE_DIR)}/acvm`; + await fs.access(expectedAcvmPath, fs.constants.R_OK); + const tempWorkingDirectory = `${TEMP_DIR}/${randomBytes(4).toString('hex')}`; + const acvmWorkingDirectory = ACVM_WORKING_DIRECTORY ? ACVM_WORKING_DIRECTORY : `${tempWorkingDirectory}/acvm`; + await fs.mkdir(acvmWorkingDirectory, { recursive: true }); + logger(`Using native ACVM binary at ${expectedAcvmPath} with working directory ${acvmWorkingDirectory}`); + + const directoryToCleanup = ACVM_WORKING_DIRECTORY ? undefined : tempWorkingDirectory; + + const cleanup = async () => { + if (directoryToCleanup) { + logger(`Cleaning up ACVM temp directory ${directoryToCleanup}`); + await fs.rm(directoryToCleanup, { recursive: true, force: true }); + } + }; + + return { + acvmWorkingDirectory, + expectedAcvmPath, + cleanup, + }; + } catch (err) { + logger(`Native ACVM not available, error: ${err}`); + return undefined; + } +} diff --git a/yarn-project/end-to-end/src/fixtures/setup.ts b/yarn-project/end-to-end/src/fixtures/setup.ts new file mode 100644 index 00000000000..66f29944150 --- /dev/null +++ b/yarn-project/end-to-end/src/fixtures/setup.ts @@ -0,0 +1,232 @@ +import { SchnorrAccountContractArtifact, getSchnorrAccount } from '@aztec/accounts/schnorr'; +import { type AztecNodeConfig, AztecNodeService, getConfigEnvVars } from '@aztec/aztec-node'; +import { + AztecAddress, + BatchCall, + CompleteAddress, + EthCheatCodes, + GrumpkinPrivateKey, + Wallet, + createDebugLogger, +} from '@aztec/aztec.js'; +import { deployInstance, registerContractClass } from '@aztec/aztec.js/deployment'; +import { asyncMap } from '@aztec/foundation/async-map'; +import { reviver } from '@aztec/foundation/serialize'; +import { createPXEService, getPXEServiceConfig } from '@aztec/pxe'; + +import { createAnvil } from '@viem/anvil'; +import { mkdirSync, readFileSync, writeFileSync } from 'fs'; +import getPort from 'get-port'; +import { mnemonicToAccount } from 'viem/accounts'; + +import { MNEMONIC } from './fixtures.js'; +import { getACVMConfig } from './get_acvm_config.js'; +import { setupL1Contracts } from './setup_l1_contracts.js'; + +export { deployAndInitializeTokenAndBridgeContracts } from '../shared/cross_chain_test_harness.js'; + +interface EndToEndSnapshotState { + nodeConfig: AztecNodeConfig; + // pxeConfig: PXEServiceConfig; + accountKeys: [`0x${string}`, `0x${string}`][]; + customData: { [key: string]: any }; +} + +export async function setupFromState(statePath: string, testName: string) { + const logger = createDebugLogger('aztec:' + testName); + logger(`Initializing with saved state at ${statePath}...`); + + // Load config. + const { nodeConfig, accountKeys, customData }: EndToEndSnapshotState = JSON.parse( + readFileSync(`${statePath}/config.json`, 'utf-8'), + reviver, + ); + + // Start anvil. We go via a wrapper script to ensure if the parent dies, anvil dies. + const ethereumHostPort = await getPort(); + nodeConfig.rpcUrl = `http://localhost:${ethereumHostPort}`; + const anvil = createAnvil({ anvilBinary: './scripts/anvil_kill_wrapper.sh', port: ethereumHostPort }); + await anvil.start(); + // Load anvil state. + const anvilStateFile = `${statePath}/anvil.dat`; + const ethCheatCodes = new EthCheatCodes(nodeConfig.rpcUrl); + await ethCheatCodes.loadChainState(anvilStateFile); + + // TODO: Encapsulate this in a NativeAcvm impl. + const acvmConfig = await getACVMConfig(logger); + if (acvmConfig) { + nodeConfig.acvmWorkingDirectory = acvmConfig.acvmWorkingDirectory; + nodeConfig.acvmBinaryPath = acvmConfig.expectedAcvmPath; + } + + logger('Creating aztec node...'); + const aztecNode = await AztecNodeService.createAndSync(nodeConfig); + // const sequencer = aztecNode.getSequencer(); + logger('Creating pxe...'); + const pxeConfig = getPXEServiceConfig(); + pxeConfig.dataDirectory = statePath; + const pxe = await createPXEService(aztecNode, pxeConfig); + + const accountManagers = accountKeys.map(a => + getSchnorrAccount(pxe, GrumpkinPrivateKey.fromString(a[0]), GrumpkinPrivateKey.fromString(a[1])), + ); + const wallets = await Promise.all(accountManagers.map(a => a.getWallet())); + + const teardown = async () => { + await aztecNode.stop(); + await pxe.stop(); + await acvmConfig?.cleanup(); + await anvil.stop(); + }; + + return { + customData, + // aztecNode, + // pxe, + // deployL1ContractsValues: config.deployL1ContractsValues, + accounts: await pxe.getRegisteredAccounts(), + // config: nodeConfig, + // wallet: wallets[0], + wallets, + logger, + // cheatCodes, + // sequencer, + teardown, + }; +} + +/** + * Sets up the environment for the end-to-end tests. + */ +export async function setup(numberOfAccounts = 1, statePath: string, testName: string) { + const logger = createDebugLogger('aztec:' + testName); + logger(`Initializing state...`); + + // Fetch the AztecNode config. + // TODO: For some reason this is currently the union of a bunch of subsystems. That needs fixing. + const config: AztecNodeConfig = getConfigEnvVars(); + config.dataDirectory = statePath; + + // Start anvil. We go via a wrapper script to ensure if the parent dies, anvil dies. + logger('Starting anvil...'); + const ethereumHostPort = await getPort(); + config.rpcUrl = `http://localhost:${ethereumHostPort}`; + const anvil = createAnvil({ anvilBinary: './scripts/anvil_kill_wrapper.sh', port: ethereumHostPort }); + await anvil.start(); + + // Deploy our L1 contracts. + logger('Deploying L1 contracts...'); + const hdAccount = mnemonicToAccount(MNEMONIC); + const privKeyRaw = hdAccount.getHdKey().privateKey; + const publisherPrivKey = privKeyRaw === null ? null : Buffer.from(privKeyRaw); + const deployL1ContractsValues = await setupL1Contracts(config.rpcUrl, hdAccount, logger); + config.publisherPrivateKey = `0x${publisherPrivKey!.toString('hex')}`; + config.l1Contracts = deployL1ContractsValues.l1ContractAddresses; + config.l1BlockPublishRetryIntervalMS = 100; + + const acvmConfig = await getACVMConfig(logger); + if (acvmConfig) { + config.acvmWorkingDirectory = acvmConfig.acvmWorkingDirectory; + config.acvmBinaryPath = acvmConfig.expectedAcvmPath; + } + + logger('Creating and synching an aztec node...'); + const aztecNode = await AztecNodeService.createAndSync(config); + // const sequencer = aztecNode.getSequencer(); + + logger('Creating pxe...'); + const pxeConfig = getPXEServiceConfig(); + pxeConfig.dataDirectory = statePath; + const pxe = await createPXEService(aztecNode, pxeConfig); + + // Generate account keys. + const accountKeys = Array.from({ length: numberOfAccounts }).map(_ => [ + GrumpkinPrivateKey.random(), + GrumpkinPrivateKey.random(), + ]); + + logger('Simulating account deployment...'); + const accounts = await asyncMap(accountKeys, async ([encPk, signPk]) => { + const account = getSchnorrAccount(pxe, encPk, signPk); + // Unfortunately the function below is not stateless and we call it here because it takes a long time to run and + // the results get stored within the account object. By calling it here we increase the probability of all the + // accounts being deployed in the same block because it makes the deploy() method basically instant. + await account.getDeployMethod().then(d => + d.simulate({ + contractAddressSalt: account.salt, + skipClassRegistration: true, + skipPublicDeployment: true, + universalDeploy: true, + }), + ); + return account; + }); + + logger('Deploying accounts...'); + const txs = await Promise.all(accounts.map(account => account.deploy())); + await Promise.all(txs.map(tx => tx.wait({ interval: 0.1 }))); + const wallets = await Promise.all(accounts.map(account => account.getWallet())); + + // const cheatCodes = CheatCodes.create(config.rpcUrl, pxe!); + + const customData: { [key: string]: any } = {}; + + const teardown = async () => { + await aztecNode.stop(); + await pxe.stop(); + await acvmConfig?.cleanup(); + await anvil.stop(); + }; + + const snapshot = async () => { + logger(`Saving setup state to ${statePath}...`); + mkdirSync(statePath, { recursive: true }); + + const ethCheatCodes = new EthCheatCodes(config.rpcUrl); + const anvilStateFile = `${statePath}/anvil.dat`; + await ethCheatCodes.dumpChainState(anvilStateFile); + + const state: EndToEndSnapshotState = { + nodeConfig: config, + // pxeConfig: {}, + accountKeys: accountKeys.map(a => [a[0].toString(), a[1].toString()]), + customData, + }; + + writeFileSync(`${statePath}/config.json`, JSON.stringify(state)); + + // TODO: Copy lmdb state. + }; + + return { + customData, + // aztecNode, + // pxe, + // deployL1ContractsValues, + accounts: await pxe.getRegisteredAccounts(), + // config, + // wallet: wallets[0], + wallets, + logger, + // cheatCodes, + // sequencer, + teardown, + snapshot, + }; +} + +/** + * Registers the contract class used for test accounts and publicly deploys the instances requested. + * Use this when you need to make a public call to an account contract, such as for requesting a public authwit. + * @param sender - Wallet to send the deployment tx. + * @param accountsToDeploy - Which accounts to publicly deploy. + */ +export async function publicDeployAccounts(sender: Wallet, accountsToDeploy: (CompleteAddress | AztecAddress)[]) { + const accountAddressesToDeploy = accountsToDeploy.map(a => ('address' in a ? a.address : a)); + const instances = await Promise.all(accountAddressesToDeploy.map(account => sender.getContractInstance(account))); + const batch = new BatchCall(sender, [ + (await registerContractClass(sender, SchnorrAccountContractArtifact)).request(), + ...instances.map(instance => deployInstance(sender, instance!).request()), + ]); + await batch.send().wait(); +} diff --git a/yarn-project/end-to-end/src/fixtures/setup_l1_contracts.ts b/yarn-project/end-to-end/src/fixtures/setup_l1_contracts.ts new file mode 100644 index 00000000000..1a3d3b35128 --- /dev/null +++ b/yarn-project/end-to-end/src/fixtures/setup_l1_contracts.ts @@ -0,0 +1,87 @@ +import { + type DebugLogger, + type DeployL1Contracts, + type L1ContractArtifactsForDeployment, + deployL1Contracts, +} from '@aztec/aztec.js'; +import { + AvailabilityOracleAbi, + AvailabilityOracleBytecode, + GasPortalAbi, + GasPortalBytecode, + InboxAbi, + InboxBytecode, + OutboxAbi, + OutboxBytecode, + PortalERC20Abi, + PortalERC20Bytecode, + RegistryAbi, + RegistryBytecode, + RollupAbi, + RollupBytecode, +} from '@aztec/l1-artifacts'; +import { getCanonicalGasTokenAddress } from '@aztec/protocol-contracts/gas-token'; + +import { type HDAccount, type PrivateKeyAccount, getContract } from 'viem'; +import { foundry } from 'viem/chains'; + +export { deployAndInitializeTokenAndBridgeContracts } from '../shared/cross_chain_test_harness.js'; + +export const setupL1Contracts = async ( + l1RpcUrl: string, + account: HDAccount | PrivateKeyAccount, + logger: DebugLogger, +) => { + const l1Artifacts: L1ContractArtifactsForDeployment = { + registry: { + contractAbi: RegistryAbi, + contractBytecode: RegistryBytecode, + }, + inbox: { + contractAbi: InboxAbi, + contractBytecode: InboxBytecode, + }, + outbox: { + contractAbi: OutboxAbi, + contractBytecode: OutboxBytecode, + }, + availabilityOracle: { + contractAbi: AvailabilityOracleAbi, + contractBytecode: AvailabilityOracleBytecode, + }, + rollup: { + contractAbi: RollupAbi, + contractBytecode: RollupBytecode, + }, + gasToken: { + contractAbi: PortalERC20Abi, + contractBytecode: PortalERC20Bytecode, + }, + gasPortal: { + contractAbi: GasPortalAbi, + contractBytecode: GasPortalBytecode, + }, + }; + + const l1Data = await deployL1Contracts(l1RpcUrl, account, foundry, logger, l1Artifacts); + await initGasBridge(l1Data); + + return l1Data; +}; + +async function initGasBridge({ walletClient, l1ContractAddresses }: DeployL1Contracts) { + const gasPortal = getContract({ + address: l1ContractAddresses.gasPortalAddress.toString(), + abi: GasPortalAbi, + client: walletClient, + }); + + await gasPortal.write.initialize( + [ + l1ContractAddresses.registryAddress.toString(), + l1ContractAddresses.gasTokenAddress.toString(), + getCanonicalGasTokenAddress(l1ContractAddresses.gasPortalAddress).toString(), + ], + {} as any, + ); +} diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index 4be491ac04e..fb851f8e023 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -382,10 +382,13 @@ export async function setup( const aztecNode = await AztecNodeService.createAndSync(config); const sequencer = aztecNode.getSequencer(); + logger('Creating a pxe...'); const { pxe, accounts, wallets } = await setupPXEService(numberOfAccounts, aztecNode!, pxeOpts, logger); if (['1', 'true'].includes(ENABLE_GAS)) { + logger(`Deploying canonical gas token...`); await deployCanonicalGasToken(new SignerlessWallet(pxe, new DefaultMultiCallEntrypoint())); + logger(`Done.`); } const cheatCodes = CheatCodes.create(config.rpcUrl, pxe!); diff --git a/yarn-project/foundation/src/aztec-address/index.ts b/yarn-project/foundation/src/aztec-address/index.ts index c932c10cdaa..e97fb1f1794 100644 --- a/yarn-project/foundation/src/aztec-address/index.ts +++ b/yarn-project/foundation/src/aztec-address/index.ts @@ -2,6 +2,7 @@ import { inspect } from 'util'; import { Fr, fromBuffer } from '../fields/index.js'; import { type BufferReader, FieldReader } from '../serialize/index.js'; +import { TypeRegistry } from '../serialize/type_registry.js'; /** * AztecAddress represents a 32-byte address in the Aztec Protocol. @@ -53,4 +54,14 @@ export class AztecAddress extends Fr { static random() { return new AztecAddress(super.random().toBuffer()); } + + toJSON() { + return { + type: 'AztecAddress', + value: this.toString(), + }; + } } + +// For deserializing JSON. +TypeRegistry.register('AztecAddress', AztecAddress); diff --git a/yarn-project/foundation/src/eth-address/index.ts b/yarn-project/foundation/src/eth-address/index.ts index f61c1f75b4f..5f28e59f54a 100644 --- a/yarn-project/foundation/src/eth-address/index.ts +++ b/yarn-project/foundation/src/eth-address/index.ts @@ -4,6 +4,7 @@ import { keccak256String } from '../crypto/keccak/index.js'; import { randomBytes } from '../crypto/random/index.js'; import { Fr } from '../fields/index.js'; import { BufferReader, FieldReader } from '../serialize/index.js'; +import { TypeRegistry } from '../serialize/type_registry.js'; /** * Represents an Ethereum address as a 20-byte buffer and provides various utility methods @@ -236,4 +237,14 @@ export class EthAddress { toFriendlyJSON() { return this.toString(); } + + toJSON() { + return { + type: 'EthAddress', + value: this.toString(), + }; + } } + +// For deserializing JSON. +TypeRegistry.register('EthAddress', EthAddress); diff --git a/yarn-project/foundation/src/fields/fields.ts b/yarn-project/foundation/src/fields/fields.ts index a28018638f4..9b2a67b55b0 100644 --- a/yarn-project/foundation/src/fields/fields.ts +++ b/yarn-project/foundation/src/fields/fields.ts @@ -257,6 +257,13 @@ export class Fr extends BaseField { return new Fr(this.toBigInt() / rhs.toBigInt()); } + + toJSON() { + return { + type: 'Fr', + value: this.toString(), + }; + } } /** @@ -319,6 +326,13 @@ export class Fq extends BaseField { static fromHighLow(high: Fr, low: Fr): Fq { return new Fq((high.toBigInt() << Fq.HIGH_SHIFT) + low.toBigInt()); } + + toJSON() { + return { + type: 'Fq', + value: this.toString(), + }; + } } // Beware: Performance bottleneck below diff --git a/yarn-project/foundation/src/serialize/index.ts b/yarn-project/foundation/src/serialize/index.ts index 875d37f4410..33cb9a5bb4d 100644 --- a/yarn-project/foundation/src/serialize/index.ts +++ b/yarn-project/foundation/src/serialize/index.ts @@ -3,3 +3,4 @@ export * from './buffer_reader.js'; export * from './field_reader.js'; export * from './types.js'; export * from './serialize.js'; +export * from './type_registry.js'; diff --git a/yarn-project/foundation/src/serialize/type_registry.ts b/yarn-project/foundation/src/serialize/type_registry.ts new file mode 100644 index 00000000000..bc48c60f0fd --- /dev/null +++ b/yarn-project/foundation/src/serialize/type_registry.ts @@ -0,0 +1,35 @@ +type Deserializable = { fromString(str: string): object }; + +/** + * Register a class here that has a toJSON method that returns: + * ``` + * { + * "type": "ExampleClassName", + * "value": + * } + * ``` + * and has an e.g. ExampleClassName.fromString(string) method. + * This means you can then easily serialize/deserialize the type using JSON.stringify and JSON.parse. + */ +export class TypeRegistry { + private static registry: Map = new Map(); + + public static register(typeName: string, constructor: Deserializable): void { + this.registry.set(typeName, constructor); + } + + public static getConstructor(typeName: string): Deserializable | undefined { + return this.registry.get(typeName); + } +} + +// Reviver function that uses TypeRegistry to instantiate objects. +export function reviver(key: string, value: any) { + if (value && typeof value === 'object' && 'type' in value && 'value' in value) { + const Constructor = TypeRegistry.getConstructor(value.type); + if (Constructor) { + return Constructor.fromString(value.value); + } + } + return value; +}