diff --git a/yarn-project/aztec-cli/README.md b/yarn-project/aztec-cli/README.md index 2b50a6fb1f7..968e281ef2f 100644 --- a/yarn-project/aztec-cli/README.md +++ b/yarn-project/aztec-cli/README.md @@ -1,36 +1,70 @@ # Aztec CLI Documentation -The Aztec CLI is a command-line interface for interacting with Aztec. It provides various commands to perform different tasks related to Aztec contracts and accounts. This documentation provides an overview of the available commands and their usage. +The Aztec CLI `azti` is a command-line interface (CLI) tool for interacting with Aztec. It provides various commands for deploying contracts, creating accounts, interacting with contracts, and retrieving blockchain data. ## Installation -To use the Aztec CLI, you need to have Node.js installed on your system. You can install it from the official Node.js website: [https://nodejs.org](https://nodejs.org) +To use `azti`, you need to have Node.js installed on your system. Follow these steps to install and set up the CLI tool: -After installing Node.js, you can install the Aztec CLI globally using the following command: +1. Install Node.js: Visit the official Node.js website (https://nodejs.org) and download the installer for your operating system. Follow the installation instructions to install Node.js. -```shell -npm install -g @aztec/azti -``` +2. Install `azti` package: Open a terminal or command prompt and run the following command to install `azti` globally on your system: + + ```shell + npm install -g @aztec/cli + ``` + + This will install the `azti` globally, making it accessible from any location in your terminal. + +3. Verify the installation: After the installation is complete, run the following command to verify that `azti` is installed correctly: + + ```shell + azti --version + ``` + + This command will display the version number of `azti` if the installation was successful. ## Usage -Once the Aztec CLI is installed, you can run it using the `azti` command followed by the desired command and its arguments. Here's the basic syntax: +To use `azti`, open a terminal or command prompt and run the `azti` command followed by the desired command and its options. + +Here's the basic syntax for running a command: ```shell -azti [command] [arguments] [options] +azti [options] ``` -To get help about the available commands and their usage, you can use the `--help` option: +Replace `` with the actual command you want to execute and `[options]` with any optional flags or parameters required by the command. + +### Environment Variables + +Some options can be set globally as environment variables to avoid having to re-enter them every time you call `azti.` +These options are: + +- `PRIVATE_KEY` -> `-k, --private-key` for all commands that require a private key. +- `PUBLIC_KEY` -> `-k, --public-key` for all commands that require a public key. +- `AZTEC_RPC_HOST` -> `-u, --rpc-url` for commands that require an Aztec RPC URL. +- `API_KEY` -> `a, --api-key` for `deploy-l1-contracts`. +- `ETHEREUM_RPC_HOST` -> `-u, --rpc-url` for `deploy-l1-contracts`. + +So if for example you are running your Aztec RPC server remotely you can do: ```shell -azti --help +export AZTEC_RPC_HOST=http://external.site/rpc:8080 +azti deploy my_contract.json ``` -## Commands +And this will send the request to `http://external.site/rpc:8080`. + +**NOTE**: Entering an option value will override the environment variable. + +## Available Commands + +`azti` provides the following commands for interacting with Aztec: ### deploy-l1-contracts -Deploy Aztec contracts on Layer 1. +Deploys all necessary Ethereum contracts for Aztec. Syntax: @@ -44,11 +78,64 @@ Options: - `-a, --api-key `: API key for the Ethereum host. - `-p, --private-key `: The private key to use for deployment. -- `-m, --mnemonic `: The mnemonic to use in deployment. Default: "test test test test test test test test test test test junk". +- `-m, --mnemonic `: The mnemonic to use in deployment. Default: `test test test test test test test test test test test junk`. + +This command deploys all the necessary Ethereum contracts required for Aztec. It creates the rollup contract, registry contract, inbox contract, outbox contract, and contract deployment emitter. The command displays the addresses of the deployed contracts. + +Example usage: + +```shell +azti deploy-l1-contracts +``` + +### create-private-key + +Generates a 32-byte private key. + +Syntax: + +```shell +azti create-private-key [options] +``` + +Options: + +- `-m, --mnemonic`: A mnemonic string that can be used for the private key generation. + +This command generates a random 32-byte private key or derives one from the provided mnemonic string. It displays the generated private key. + +Example usage: + +```shell +azti create-private-key +``` + +### create-account + +Creates an Aztec account that can be used for transactions. + +Syntax: + +```shell +azti create-account [options] +``` + +Options: + +- `-k, --private-key`: Private key to use for the account generation. Uses a random key by default. +- `-u, --rpc-url `: URL of the Aztec RPC. Default: `http://localhost:8080`. + +This command creates an Aztec account that can be used for transactions. It generates a new account with a private key or uses the provided private key. The command displays the account's address and public key. + +Example usage: + +```shell +azti create-account +``` ### deploy -Deploy an Aztec contract. +Deploys a compiled Noir contract to Aztec. Syntax: @@ -57,16 +144,24 @@ azti deploy [options] ``` - `contractAbi`: Path to the compiled Noir contract's ABI file in JSON format. +- `constructorArgs` (optional): Contract constructor arguments. Options: - `-u, --rpc-url `: URL of the Aztec RPC. Default: `http://localhost:8080`. -- `-k, --public-key `: Public key to use for deployment. -- `-a, --constructor-args [args...]`: Constructor arguments for the contract. +- `-k, --public-key `: Public key of the deployer. If not provided, it will check the RPC for existing ones. + +This command deploys a compiled Noir contract to Aztec. It requires the path to the contract's ABI file in JSON format. Optionally, you can specify the public key of the deployer and provide constructor arguments for the contract. The command displays the address of the deployed contract. + +Example usage: + +```shell +azti deploy path/to/contract.abi.json ...args +``` ### check-deploy -Check if a contract has been deployed to an Aztec address. +Checks if a contract is deployed to the specified Aztec address. Syntax: @@ -74,15 +169,23 @@ Syntax: azti check-deploy [options] ``` -- `contractAddress`: Aztec address to check if the contract has been deployed to. +- `contractAddress`: An Aztec address to check if the contract has been deployed to. Options: - `-u, --rpc-url `: URL of the Aztec RPC. Default: `http://localhost:8080`. +This command checks if a contract is deployed to the specified Aztec address. It verifies if the contract is present at the given address and displays the result. + +Example usage: + +```shell +azti check-deploy 0x123456789abcdef123456789abcdef12345678 +``` + ### get-tx-receipt -Get the receipt for a transaction hash. +Gets the receipt for the specified transaction hash. Syntax: @@ -90,15 +193,23 @@ Syntax: azti get-tx-receipt [options] ``` -- `txHash`: Transaction hash to get the receipt for. +- `txHash`: A transaction hash to get the receipt for. Options: - `-u, --rpc-url `: URL of the Aztec RPC. Default: `http://localhost:8080`. +This command retrieves and displays the receipt for the specified transaction hash. It shows details such as the transaction status, block number, and block hash. + +Example usage: + +```shell +azti get-tx-receipt 0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef12345678 +``` + ### get-contract-data -Get data about an Aztec contract. +Gets information about the Aztec contract deployed at the specified address. Syntax: @@ -113,25 +224,17 @@ Options: - `-u, --rpc-url `: URL of the Aztec RPC. Default: `http://localhost:8080`. - `-b, --include-bytecode`: Include the contract's public function bytecode, if any. -### create-account +This command retrieves and displays information about the Aztec contract deployed at the specified address. It shows the contract address, portal contract address, and optionally, the bytecode of the contract's public functions. -Create a new Aztec account. - -Syntax: +Example usage: ```shell -azti create-account [options] +azti get-contract-data 0x123456789abcdef123456789abcdef12345678 ``` -Options: - -- `-k, --private-key`: Private Key to use for the 1st account generation. -- `-u, --rpc-url `: URL of the Aztec RPC. Default: `http://localhost:8080`. -- `-n, --num-addresses `: Number of accounts to create. Default: 1. - ### get-accounts -Get a list of Aztec accounts. +Gets all the Aztec accounts. Syntax: @@ -143,9 +246,17 @@ Options: - `-u, --rpc-url `: URL of the Aztec RPC. Default: `http://localhost:8080`. +This command retrieves and displays all the Aztec accounts available in the system. + +Example usage: + +```shell +azti get-accounts +``` + ### get-account-public-key -Get the public key for an Aztec account. +Gets an account's public key, given its Aztec address. Syntax: @@ -153,55 +264,101 @@ Syntax: azti get-account-public-key
[options] ``` -- `address`: Aztec address to get the public key for. +- `address`: The Aztec address to get the public key for. Options: - `-u, --rpc-url `: URL of the Aztec RPC. Default: `http://localhost:8080`. +This command retrieves and displays the public key of an account given its Aztec address. + +Example usage: + +```shell +azti get-account-public-key 0x123456789abcdef123456789abcdef12345678 +``` + ### call-fn -Call a function on an Aztec contract. +Calls a function on an Aztec contract. Syntax: ```shell -azti call-fn [from] [functionArgs...] [options] +azti call-fn [functionArgs...] [options] ``` -- `contractAbi`: Path to the compiled contract's ABI file in JSON format. +- `contractAbi`: The compiled contract's ABI in JSON format. - `contractAddress`: Address of the contract. - `functionName`: Name of the function to call. -- `from` (optional): Caller of the transaction. - `functionArgs` (optional): Function arguments. Options: +- `-k, --private-key `: The sender's private key. - `-u, --rpcUrl `: URL of the Aztec RPC. Default: `http://localhost:8080`. -### view-tx +This command calls a function on an Aztec contract. It requires the contract's ABI, address, function name, and optionally, function arguments. The command executes the function call and displays the transaction details. + +Example usage: + +```shell +azti call-fn path/to/contract.abi.json 0x123456789abcdef123456789abcdef12345678 transfer 100 +``` -Simulate the execution of a view (read-only) function on a deployed contract without actually modifying state. +### view-fn + +Simulates the execution of a view (read-only) function on a deployed contract, without modifying state. Syntax: ```shell -azti view-tx [from] [functionArgs...] [options] +azti view-fn [functionArgs...] [options] ``` -- `contractAbi`: Path to the compiled contract's ABI file in JSON format. +- `contractAbi`: The compiled contract's ABI in JSON format. - `contractAddress`: Address of the contract. -- `functionName`: Name of the function to call. -- `from` (optional): Caller of the transaction. +- `functionName`: Name of the function to view. - `functionArgs` (optional): Function arguments. Options: +- `-f, --from `: Public key of the transaction viewer. If empty, it will try to find an account in the RPC. - `-u, --rpcUrl `: URL of the Aztec RPC. Default: `http://localhost:8080`. +This command simulates the execution of a view function on a deployed contract without modifying the state. It requires the contract's ABI, address, function name, and optionally, function arguments. The command displays the result of the view function. + +Example usage: + +```shell +azti view-fn path/to/contract.abi.json 0x123456789abcdef123456789abcdef12345678 balanceOf 0xabcdef1234567890abcdef1234567890abcdef12 +``` + +### parse-parameter-struct + +Helper for parsing an encoded string into a contract's parameter struct. + +Syntax: + +```shell +azti parse-parameter-struct +``` + +- `encodedString`: The encoded hex string. +- `contractAbi`: The compiled contract's ABI in JSON format. +- `parameterName`: The name of the struct parameter to decode into. + +This command is a helper for parsing an encoded hex string into a contract's parameter struct. It requires the encoded string, the contract's ABI, and the name of the struct parameter. The command decodes the string and displays the struct data. + +Example usage: + +```shell +azti parse-parameter-struct 0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890 path/to/contract.abi.json paramName +``` + ### get-logs -Get logs from Aztec blocks. +Gets all the unencrypted logs from L2 blocks in the specified range. Syntax: @@ -209,12 +366,43 @@ Syntax: azti get-logs [options] ``` -- ` +- `from`: Block number to start fetching logs from. +- `take`: Number of block logs to fetch. -from`: Block number to start fetching logs from. +Options: -- `take`: Number of block logs to fetch. +- `-u, --rpc-url `: URL of the Aztec RPC. Default: `http://localhost:8080`. + +This command retrieves and displays all the unencrypted logs from L2 blocks in the specified range. It shows the logs found in the blocks and unrolls them for readability. + +Example usage: + +```shell +azti get-logs 1000 10 +``` + +### block-num + +Gets the current Aztec L2 block number. + +Syntax: + +```shell +azti block-num [options] +``` Options: - `-u, --rpc-url `: URL of the Aztec RPC. Default: `http://localhost:8080`. + +This command retrieves and displays the current Aztec L2 block number. + +Example usage: + +```shell +azti block-num +``` + +## Conclusion + +That covers the available commands and their usage in the `aztec-cli`. You can now use these commands to interact with Aztec and perform various actions such as deploying contracts, creating accounts, executing functions, and retrieving blockchain data. diff --git a/yarn-project/aztec-cli/src/index.ts b/yarn-project/aztec-cli/src/index.ts index 34d86fe60ea..775e5faaa47 100644 --- a/yarn-project/aztec-cli/src/index.ts +++ b/yarn-project/aztec-cli/src/index.ts @@ -1,24 +1,29 @@ #!/usr/bin/env -S node --no-warnings import { Command } from 'commander'; +import { mnemonicToAccount } from 'viem/accounts'; import { createLogger } from '@aztec/foundation/log'; import { createDebugLogger } from '@aztec/foundation/log'; import { AztecAddress, Contract, ContractDeployer, + Fr, Point, TxHash, createAccounts, createAztecRpcClient, - pointToPublicKey, + getAccountWallet, } from '@aztec/aztec.js'; - -import { encodeArgs, parseStructString } from './cli_encoder.js'; -import { deployAztecContracts, getContractAbi, prepTx } from './utils.js'; import { JsonStringify } from '@aztec/foundation/json-rpc'; import { StructType } from '@aztec/foundation/abi'; +import { randomBytes } from '@aztec/foundation/crypto'; import { ContractData, L2BlockL2Logs } from '@aztec/types'; +import { encodeArgs, parseStructString } from './cli_encoder.js'; +import { deployAztecContracts, getContractAbi, getTxSender, prepTx } from './utils.js'; + +const accountCreationSalt = Fr.ZERO; + const debugLogger = createDebugLogger('aztec:cli'); const log = createLogger(); @@ -26,65 +31,127 @@ const program = new Command(); program.name('azti').description('CLI for interacting with Aztec.').version('0.1.0'); +const { ETHEREUM_HOST, AZTEC_RPC_HOST, PRIVATE_KEY, PUBLIC_KEY, API_KEY } = process.env; + /** - * A placeholder for the Aztec-cli. + * Main function for the Aztec CLI. */ async function main() { program .command('deploy-l1-contracts') + .description('Deploys all necessary Ethereum contracts for Aztec.') .argument( '[rpcUrl]', 'Url of the ethereum host. Chain identifiers localhost and testnet can be used', - 'http://localhost:8545', + ETHEREUM_HOST || 'http://localhost:8545', ) - .option('-a, --api-key ', 'Api key for the ethereum host', undefined) - .option('-p, --private-key ', 'The private key to use for deployment') + .option('-a, --api-key ', 'Api key for the ethereum host', API_KEY) + .option('-p, --private-key ', 'The private key to use for deployment', PRIVATE_KEY) .option( '-m, --mnemonic ', 'The mnemonic to use in deployment', 'test test test test test test test test test test test junk', ) .action(async (rpcUrl: string, options) => { - await deployAztecContracts(rpcUrl, options.apiKey ?? '', options.privateKey, options.mnemonic, debugLogger); + const { rollupAddress, registryAddress, inboxAddress, outboxAddress, contractDeploymentEmitterAddress } = + await deployAztecContracts(rpcUrl, options.apiKey ?? '', options.privateKey, options.mnemonic, debugLogger); + log('\n'); + log(`Rollup Address: ${rollupAddress.toString()}`); + log(`Registry Address: ${registryAddress.toString()}`); + log(`L1 -> L2 Inbox Address: ${inboxAddress.toString()}`); + log(`L2 -> L1 Outbox address: ${outboxAddress.toString()}`); + log(`Contract Deployment Emitter Address: ${contractDeploymentEmitterAddress.toString()}`); + log('\n'); + }); + + program + .command('create-private-key') + .description('Generates a 32-byte private key.') + .option('-m, --mnemonic', 'A mnemonic string that can be used for the private key generation.') + .action(options => { + let privKey; + if (options.mnemonic) { + const acc = mnemonicToAccount(options.mnemonic); + privKey = Buffer.from(acc.getHdKey().privateKey!).toString('hex'); + } else { + privKey = randomBytes(32).toString('hex'); + } + log(`\n${privKey}\n`); + }); + + program + .command('create-account') + .description('Creates an aztec account that can be used for transactions.') + .option( + '-k, --private-key ', + 'Private Key to use for the 1st account generation. Uses random by default.', + PRIVATE_KEY, + ) + .option('-u, --rpc-url ', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080') + .action(async options => { + const client = createAztecRpcClient(options.rpcUrl); + const privateKey = options.privateKey && Buffer.from(options.privateKey.replace(/^0x/i, ''), 'hex'); + const wallet = await createAccounts(client, privateKey, accountCreationSalt, 1); + const accounts = await wallet.getAccounts(); + const pubKeys = await Promise.all(accounts.map(acc => wallet.getAccountPublicKey(acc))); + log(`\nCreated account(s).`); + accounts.map((acc, i) => log(`\nAddress: ${acc.toString()}\nPublic Key: ${pubKeys[i].toString()}\n`)); }); program .command('deploy') + .description('Deploys a compiled Noir contract to Aztec.') .argument('', "A compiled Noir contract's ABI in JSON format", undefined) - .option('-u, --rpc-url ', 'URL of the Aztec RPC', 'http://localhost:8080') - .option('-k, --public-key ') - .option('-a, --constructor-args [args...]', 'Contract constructor arguments', []) - .action(async (contractFile: string, options: any) => { + .argument('[constructorArgs...]', 'Contract constructor arguments', []) + .option('-u, --rpc-url ', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080') + .option( + '-k, --public-key ', + 'Public key of the deployer. If not provided, it will check the RPC for existing ones.', + PUBLIC_KEY, + ) + .action(async (contractFile: string, args: string[], options: any) => { const contractAbi = getContractAbi(contractFile, log); const constructorAbi = contractAbi.functions.find(({ name }) => name === 'constructor'); - const publicKey = Point.fromString(options.publicKey); const client = createAztecRpcClient(options.rpcUrl); + let publicKey; + if (options.publicKey) { + publicKey = Point.fromString(options.publicKey); + } else { + const accounts = await client.getAccounts(); + if (!accounts) { + throw new Error('No public key provided or found in Aztec RPC.'); + } + publicKey = await client.getAccountPublicKey(accounts[0]); + } + + log(`Using Public Key: ${publicKey.toString()}`); + const deployer = new ContractDeployer(contractAbi, client); - const tx = deployer - .deploy(...encodeArgs(options.constructorArgs, constructorAbi!.parameters), pointToPublicKey(publicKey)) - .send(); + const tx = deployer.deploy(...encodeArgs(args, constructorAbi!.parameters), publicKey.toBigInts()).send(); await tx.isMined(); const receipt = await tx.getReceipt(); - log(`Contract deployed at ${receipt.contractAddress?.toString()}`); + log(`\nAztec Contract deployed at ${receipt.contractAddress?.toString()}\n`); }); program .command('check-deploy') + .description('Checks if a contract is deployed to the specified Aztec address.') .argument('', 'An Aztec address to check if contract has been deployed to.') - .option('-u, --rpc-url ', 'URL of the Aztec RPC', 'http://localhost:8080') + .option('-u, --rpc-url ', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080') .action(async (_contractAddress, options) => { const client = createAztecRpcClient(options.rpcUrl); const address = AztecAddress.fromString(_contractAddress); const isDeployed = await client.isContractDeployed(address); - log(isDeployed.toString()); + log(`\n${isDeployed.toString()}\n`); }); program .command('get-tx-receipt') + .description('Gets the receipt for the specified transaction hash.') .argument('', 'A TX hash to get the receipt for.') - .option('-u, --rpc-url ', 'URL of the Aztec RPC', 'http://localhost:8080') + .option('-u, --rpc-url ', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080') .action(async (_txHash, options) => { const client = createAztecRpcClient(options.rpcUrl); const txHash = TxHash.fromString(_txHash); @@ -92,14 +159,15 @@ async function main() { if (!receipt) { log(`No receipt found for tx hash ${_txHash}`); } else { - log(`TX Receipt: \n${JsonStringify(receipt, true)}`); + log(`\nTX Receipt: \n${JsonStringify(receipt, true)}\n`); } }); program .command('get-contract-data') + .description('Gets information about the Aztec contract deployed at the specified address.') .argument('', 'Aztec address of the contract.') - .option('-u, --rpc-url ', 'URL of the Aztec RPC', 'http://localhost:8080') + .option('-u, --rpc-url ', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080') .option('-b, --include-bytecode', "Include the contract's public function bytecode, if any.") .action(async (_contractAddress, options) => { const client = createAztecRpcClient(options.rpcUrl); @@ -119,18 +187,20 @@ async function main() { } else { contractData = contractDataOrInfo; } - log(`Contract Data: \nAddress: ${contractData.contractAddress.toString()}`); + log(`\nContract Data: \nAddress: ${contractData.contractAddress.toString()}`); log(`Portal: ${contractData.portalContractAddress.toString()}`); if ('bytecode' in contractDataOrInfo) { log(`Bytecode: ${contractDataOrInfo.bytecode}`); } + log('\n'); }); program .command('get-logs') + .description('Gets all the unencrypted logs from L2 blocks in the range specified.') .argument('', 'Block num start for getting logs.') .argument('', 'How many block logs to fetch.') - .option('-u, --rpc-url ', 'URL of the Aztec RPC', 'http://localhost:8080') + .option('-u, --rpc-url ', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080') .action(async (_from, _take, options) => { let from: number; let take: number; @@ -151,26 +221,9 @@ async function main() { } }); - // NOTE: This implementation should change soon but keeping it here for quick account creation. - program - .command('create-account') - .option('-k, --private-key', 'Private Key to use for the 1st account generation.') - .option('-n, --num-addresses ', 'Number of addresses the account can control') - .option('-u, --rpc-url ', 'URL of the Aztec RPC', 'http://localhost:8080') - .action(async options => { - const client = createAztecRpcClient(options.rpcUrl); - const privateKey = options.privateKey && Buffer.from(options.privateKeystr.replace(/^0x/i, ''), 'hex'); - const numAccounts = options.numAddresses ? parseInt(options.numAddresses) : 1; - const wallet = await createAccounts(client, privateKey, numAccounts); - const accounts = await wallet.getAccounts(); - const pubKeys = await Promise.all(accounts.map(acc => wallet.getAccountPublicKey(acc))); - log(`Created account(s).`); - accounts.map((acc, i) => log(`\nAddress: ${acc.toString()}\nPublic Key: ${pubKeys[i].toString()}\n`)); - }); - program .command('get-accounts') - .option('-u, --rpc-url ', 'URL of the Aztec RPC', 'http://localhost:8080') + .option('-u, --rpc-url ', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080') .action(async (options: any) => { const client = createAztecRpcClient(options.rpcUrl); const accounts = await client.getAccounts(); @@ -178,14 +231,15 @@ async function main() { log('No accounts found.'); } else { log(`Accounts found: \n`); - accounts.forEach(acc => log(`${acc}\n`)); + accounts.forEach(async acc => log(`Address: ${acc}\nPublic Key: ${await client.getAccountPublicKey(acc)}\n`)); } }); program .command('get-account-public-key') + .description("Gets an account's public key, given its Aztec address.") .argument('
', 'The Aztec address to get the public key for') - .option('-u, --rpc-url ', 'URL of the Aztec RPC', 'http://localhost:8080') + .option('-u, --rpc-url ', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080') .action(async (_address, options) => { const client = createAztecRpcClient(options.rpcUrl); const address = AztecAddress.fromString(_address); @@ -199,59 +253,68 @@ async function main() { program .command('call-fn') + .description('Calls a function on an Aztec contract.') .argument('', "The compiled contract's ABI in JSON format", undefined) .argument('', 'Address of the contract') .argument('', 'Name of Function to view') - .argument('[from]', 'The caller of the transaction', undefined) .argument('[functionArgs...]', 'Function arguments', []) - .option('-u, --rpcUrl ', 'URL of the Aztec RPC', 'http://localhost:8080') - .action(async (contractFile, _contractAddress, functionName, _from, _functionArgs, options) => { - const { contractAddress, functionArgs, from, contractAbi } = prepTx( + .option('-k, --private-key ', "The sender's private key.", PRIVATE_KEY) + .option('-u, --rpcUrl ', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080') + + .action(async (contractFile, _contractAddress, functionName, _functionArgs, options) => { + const { contractAddress, functionArgs, contractAbi } = prepTx( contractFile, _contractAddress, functionName, - _from, _functionArgs, log, ); + const client = createAztecRpcClient(options.rpcUrl); - const wallet = await createAccounts(client); + const wallet = await getAccountWallet(client, Buffer.from(options.privateKey, 'hex'), accountCreationSalt); const contract = new Contract(contractAddress, contractAbi, wallet); - const tx = contract.methods[functionName](...functionArgs).send({ from }); + const from = (await wallet.getAccounts()).find(addr => addr.equals(wallet.getAddress())); + const tx = contract.methods[functionName](...functionArgs).send({ + from, + }); await tx.isMined(); - log('TX has been mined'); + log('\nTX has been mined'); const receipt = await tx.getReceipt(); log(`TX Hash: ${(await tx.getTxHash()).toString()}`); log(`Block Num: ${receipt.blockNumber}`); log(`Block Hash: ${receipt.blockHash?.toString('hex')}`); - log(`TX Status: ${receipt.status}`); + log(`TX Status: ${receipt.status}\n`); }); program - .command('view-tx') + .command('view-fn') + .description( + 'Simulates the execution of a view (read-only) function on a deployed contract, without modifying state.', + ) .argument('', "The compiled contract's ABI in JSON format", undefined) .argument('', 'Address of the contract') .argument('', 'Name of Function to view') - .argument('[from]', 'The caller of the transaction', undefined) .argument('[functionArgs...]', 'Function arguments', []) - .option('-u, --rpcUrl ', 'URL of the Aztec RPC', 'http://localhost:8080') - .action(async (contractFile, _contractAddress, functionName, _from, _functionArgs, options) => { - const { contractAddress, functionArgs, from } = prepTx( + .option('-f, --from ', 'Public key of the TX viewer. If empty, will try to find account in RPC.') + .option('-u, --rpcUrl ', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080') + .action(async (contractFile, _contractAddress, functionName, _functionArgs, options) => { + const { contractAddress, functionArgs } = prepTx( contractFile, _contractAddress, functionName, - _from, _functionArgs, log, ); const client = createAztecRpcClient(options.rpcUrl); + const from = await getTxSender(client, options.from); const result = await client.viewTx(functionName, functionArgs, contractAddress, from); - log('View TX returned result: ', JsonStringify(result, true)); + log('\nView TX result: ', JsonStringify(result, true), '\n'); }); // Helper for users to decode hex strings into structs if needed program .command('parse-parameter-struct') + .description("Helper for parsing an encoded string into a contract's parameter struct.") .argument('', 'The encoded hex string') .argument('', "The compiled contract's ABI in JSON format") .argument('', 'The name of the struct parameter to decode into') @@ -266,16 +329,17 @@ async function main() { return; } const data = parseStructString(encodedString, parameterAbitype.type as StructType); - log(`Struct Data: \n${JsonStringify(data, true)}`); + log(`\nStruct Data: \n${JsonStringify(data, true)}\n`); }); program .command('block-num') - .option('-u, --rpcUrl ', 'URL of the Aztec RPC', 'http://localhost:8080') + .description('Gets the current Aztec L2 number.') + .option('-u, --rpcUrl ', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080') .action(async (options: any) => { const client = createAztecRpcClient(options.rpcUrl); const num = await client.getBlockNum(); - log(num); + log(`${num}\n`); }); await program.parseAsync(process.argv); diff --git a/yarn-project/aztec-cli/src/utils.ts b/yarn-project/aztec-cli/src/utils.ts index 9396e41b941..b74c88a125f 100644 --- a/yarn-project/aztec-cli/src/utils.ts +++ b/yarn-project/aztec-cli/src/utils.ts @@ -3,7 +3,7 @@ import fs from 'fs'; import { createEthereumChain, deployL1Contracts } from '@aztec/ethereum'; import { DebugLogger, Logger } from '@aztec/foundation/log'; import { ContractAbi } from '@aztec/foundation/abi'; -import { AztecAddress } from '@aztec/aztec.js'; +import { AztecAddress, AztecRPC } from '@aztec/aztec.js'; import { encodeArgs } from './cli_encoder.js'; /** @@ -22,7 +22,7 @@ export async function deployAztecContracts( ) { const account = !privateKey ? mnemonicToAccount(mnemonic!) : privateKeyToAccount(`0x${privateKey}`); const chain = createEthereumChain(rpcUrl, apiKey); - await deployL1Contracts(chain.rpcUrl, account, chain.chainInfo, debugLogger); + return await deployL1Contracts(chain.rpcUrl, account, chain.chainInfo, debugLogger); } /** @@ -42,12 +42,36 @@ export function getContractAbi(fileDir: string, log: Logger) { return contractAbi; } +/** + * Utility to select a TX sender either from user input + * or from the first account that is found in an Aztec RPC instance. + * @param client - The Aztec RPC instance that will be checked for an account. + * @param _from - The user input. + * @returns An Aztec address. Will throw if one can't be found in either options. + */ +export async function getTxSender(client: AztecRPC, _from?: string) { + let from: AztecAddress; + if (_from) { + try { + from = AztecAddress.fromString(_from); + } catch { + throw new Error(`Invalid option 'from' passed: ${_from}`); + } + } else { + const accounts = await client.getAccounts(); + if (!accounts.length) { + throw new Error('No accounts found in Aztec RPC insance.'); + } + from = accounts[0]; + } + return from; +} + /** * Performs necessary checks, conversions & operations to call a contract fn from the CLI. * @param contractFile - Directory of the compiled contract ABI. * @param _contractAddress - Aztec Address of the contract. * @param functionName - Name of the function to be called. - * @param _from - The caller's address. * @param _functionArgs - Arguments to call the function with. * @param log - Logger instance that will output to the CLI * @returns Formatted contract address, function arguments and caller's aztec address. @@ -56,7 +80,6 @@ export function prepTx( contractFile: string, _contractAddress: string, functionName: string, - _from: string, _functionArgs: string[], log: Logger, ) { @@ -73,14 +96,6 @@ export function prepTx( } const functionArgs = encodeArgs(_functionArgs, functionAbi.parameters); - let from; - if (_from) { - try { - from = AztecAddress.fromString(_from); - } catch { - throw new Error(`Unable to parse caller address ${_from}.`); - } - } - return { contractAddress, functionArgs, from, contractAbi }; + return { contractAddress, functionArgs, contractAbi }; } diff --git a/yarn-project/aztec-rpc/src/aztec_rpc/aztec_rpc.ts b/yarn-project/aztec-rpc/src/aztec_rpc/aztec_rpc.ts index 7afec43bf81..00ab7804fd2 100644 --- a/yarn-project/aztec-rpc/src/aztec_rpc/aztec_rpc.ts +++ b/yarn-project/aztec-rpc/src/aztec_rpc/aztec_rpc.ts @@ -87,6 +87,7 @@ export interface AztecRPC { ): Promise; getAccounts(): Promise; getAccountPublicKey(address: AztecAddress): Promise; + getAccountAddress(publicKey: Point): Promise; addContracts(contracts: DeployedContract[]): Promise; /** * Is an L2 contract deployed at this address? diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.ts b/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.ts index 2236d9e111d..3e346b7884a 100644 --- a/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.ts +++ b/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.ts @@ -129,10 +129,23 @@ export class AztecRPCServer implements AztecRPC { * @returns A Promise resolving to the Point instance representing the public key. */ public getAccountPublicKey(address: AztecAddress): Promise { - const account = this.#ensureAccount(address); + const account = this.#ensureAccountAddress(address); return Promise.resolve(account.getPublicKey()); } + /** + * Retrieve the address associated with a public key. + * Throws an error if the account is not found in the key store. + * + * @param publicKey - The Point instance representing the account public key. + * @returns A Promise resolving to the Aztec Address. + */ + public getAccountAddress(publicKey: Point): Promise { + // const account = this.#ensureAccount(address); + const account = this.#ensureAccountPublicKey(publicKey); + return Promise.resolve(account.getAddress()); + } + /** * Retrieves the storage data at a specified contract address and storage slot. * The returned data is an array of note preimage items, with each item containing its value. @@ -382,7 +395,7 @@ export class AztecRPCServer implements AztecRPC { throw new Error('No accounts available in the key store.'); } - return this.#ensureAccount(address); + return this.#ensureAccountAddress(address); } /** @@ -393,7 +406,7 @@ export class AztecRPCServer implements AztecRPC { * @returns The account state associated with the given address. * @throws If the account is unknown or not found in the synchroniser. */ - #ensureAccount(account: AztecAddress) { + #ensureAccountAddress(account: AztecAddress) { const accountState = this.synchroniser.getAccount(account); if (!accountState) { throw new Error(`Unknown account: ${account.toShortString()}.`); @@ -402,6 +415,24 @@ export class AztecRPCServer implements AztecRPC { return accountState; } + /** + * Ensures the given account public key exists in the synchroniser. + * Retrieves the account state for the provided address and throws an error if the account is not found. + * + * @param account - The public key. + * @returns The account state associated with the given address. + * @throws If the account is unknown or not found in the synchroniser. + */ + #ensureAccountPublicKey(account: Point) { + const accountState = this.synchroniser.getAccountByPublicKey(account); + + if (!accountState) { + throw new Error(`Unknown account: ${account.toShortString()}.`); + } + + return accountState; + } + /** * Returns the information about the server's node * @returns - The node information. diff --git a/yarn-project/aztec-rpc/src/synchroniser/synchroniser.ts b/yarn-project/aztec-rpc/src/synchroniser/synchroniser.ts index eae4823ec35..b85f9142190 100644 --- a/yarn-project/aztec-rpc/src/synchroniser/synchroniser.ts +++ b/yarn-project/aztec-rpc/src/synchroniser/synchroniser.ts @@ -1,5 +1,5 @@ import { AztecNode } from '@aztec/aztec-node'; -import { Fr } from '@aztec/circuits.js'; +import { Fr, Point } from '@aztec/circuits.js'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { createDebugLogger } from '@aztec/foundation/log'; import { InterruptableSleep } from '@aztec/foundation/sleep'; @@ -174,6 +174,11 @@ export class Synchroniser { abi = SchnorrAccountContractAbi, keyStore: KeyStore, ) { + // check if account exists + const account = this.getAccount(address); + if (account) { + return account; + } const accountState = new AccountState( publicKey, keyStore, @@ -198,6 +203,17 @@ export class Synchroniser { return this.accountStates.find(as => as.getAddress().equals(account)); } + /** + * Retrieve an account state by its AztecAddress from the list of managed account states. + * If no account state with the given address is found, returns undefined. + * + * @param account - The AztecAddress instance representing the account to search for. + * @returns The AccountState instance associated with the provided AztecAddress or undefined if not found. + */ + public getAccountByPublicKey(account: Point) { + return this.accountStates.find(as => as.getPublicKey().equals(account)); + } + /** * Retrieve a shallow copy of the array containing all account states. * The returned array includes all AccountState instances added to the synchronizer. diff --git a/yarn-project/aztec-sandbox/src/examples/uniswap_trade_on_l1_from_l2.ts b/yarn-project/aztec-sandbox/src/examples/uniswap_trade_on_l1_from_l2.ts index 0284da28316..6cc6c999da6 100644 --- a/yarn-project/aztec-sandbox/src/examples/uniswap_trade_on_l1_from_l2.ts +++ b/yarn-project/aztec-sandbox/src/examples/uniswap_trade_on_l1_from_l2.ts @@ -6,7 +6,6 @@ import { createAccounts, createAztecRpcClient, getL1ContractAddresses, - pointToPublicKey, AztecAddress, EthAddress, Fr, @@ -24,7 +23,7 @@ import { UniswapPortalAbi, UniswapPortalBytecode } from '@aztec/l1-artifacts'; /** * Type representation of a Public key's coordinates. */ -type PublicKey = { +type BigIntPublicKey = { /** Public key X coord */ x: bigint; /** Public key Y coord */ @@ -72,7 +71,7 @@ let wallet: Wallet; * Deploys all l1 / l2 contracts * @param ownerPub - Public key of deployer. */ -async function deployAllContracts(ownerPub: PublicKey) { +async function deployAllContracts(ownerPub: BigIntPublicKey) { const l1ContractsAddresses = await getL1ContractAddresses(aztecRpcUrl); logger('Deploying DAI Portal, initializing and deploying l2 contract...'); const daiContracts = await deployAndInitializeNonNativeL2TokenContracts( @@ -146,7 +145,7 @@ async function deployAllContracts(ownerPub: PublicKey) { const getL2BalanceOf = async (aztecRpcClient: AztecRPC, owner: AztecAddress, l2Contract: any) => { const ownerPublicKey = await aztecRpcClient.getAccountPublicKey(owner); - const [balance] = await l2Contract.methods.getBalance(pointToPublicKey(ownerPublicKey)).view({ from: owner }); + const [balance] = await l2Contract.methods.getBalance(ownerPublicKey.toBigInts()).view({ from: owner }); return balance; }; @@ -170,8 +169,8 @@ const transferWethOnL2 = async ( const transferTx = wethL2Contract.methods .transfer( transferAmount, - pointToPublicKey(await aztecRpcClient.getAccountPublicKey(ownerAddress)), - pointToPublicKey(await aztecRpcClient.getAccountPublicKey(receiver)), + (await aztecRpcClient.getAccountPublicKey(ownerAddress)).toBigInts(), + (await aztecRpcClient.getAccountPublicKey(receiver)).toBigInts(), ) .send({ from: ownerAddress }); await transferTx.isMined(0, 0.5); @@ -186,10 +185,10 @@ const transferWethOnL2 = async ( async function main() { logger('Running L1/L2 messaging test on HTTP interface.'); - wallet = await createAccounts(aztecRpcClient, privateKey!, 2); + wallet = await createAccounts(aztecRpcClient, privateKey!, Fr.random(), 2); const accounts = await wallet.getAccounts(); const [owner, receiver] = accounts; - const ownerPub = pointToPublicKey(await aztecRpcClient.getAccountPublicKey(owner)); + const ownerPub = (await aztecRpcClient.getAccountPublicKey(owner)).toBigInts(); const result = await deployAllContracts(ownerPub); const { diff --git a/yarn-project/aztec-sandbox/src/examples/zk_token_contract.ts b/yarn-project/aztec-sandbox/src/examples/zk_token_contract.ts index 63e6d3fd59b..706867f0d31 100644 --- a/yarn-project/aztec-sandbox/src/examples/zk_token_contract.ts +++ b/yarn-project/aztec-sandbox/src/examples/zk_token_contract.ts @@ -1,12 +1,5 @@ -import { - Contract, - ContractDeployer, - Wallet, - createAccounts, - createAztecRpcClient, - pointToPublicKey, -} from '@aztec/aztec.js'; -import { AztecAddress, Point } from '@aztec/circuits.js'; +import { Contract, ContractDeployer, Wallet, createAccounts, createAztecRpcClient } from '@aztec/aztec.js'; +import { AztecAddress, Fr, Point } from '@aztec/circuits.js'; import { createDebugLogger } from '@aztec/foundation/log'; import { ZkTokenContractAbi } from '@aztec/noir-contracts/examples'; @@ -30,7 +23,7 @@ const SECONDARY_AMOUNT = 33n; async function deployZKContract(pubKeyPoint: Point) { logger('Deploying L2 contract...'); const deployer = new ContractDeployer(ZkTokenContractAbi, aztecRpcClient); - const tx = deployer.deploy(INITIAL_BALANCE, pointToPublicKey(pubKeyPoint)).send(); + const tx = deployer.deploy(INITIAL_BALANCE, pubKeyPoint.toBigInts()).send(); const receipt = await tx.getReceipt(); const contract = new Contract(receipt.contractAddress!, ZkTokenContractAbi, wallet); await tx.isMined(); @@ -47,7 +40,7 @@ async function deployZKContract(pubKeyPoint: Point) { * @returns The owner's current balance of the token. */ async function getBalance(contract: Contract, ownerKey: Point, ownerAddress: AztecAddress) { - const [balance] = await contract.methods.getBalance(pointToPublicKey(ownerKey)).view({ from: ownerAddress }); + const [balance] = await contract.methods.getBalance(ownerKey.toBigInts()).view({ from: ownerAddress }); return balance; } @@ -57,22 +50,22 @@ async function getBalance(contract: Contract, ownerKey: Point, ownerAddress: Azt async function main() { logger('Running ZK contract test on HTTP interface.'); - wallet = await createAccounts(aztecRpcClient, privateKey, 1); + wallet = await createAccounts(aztecRpcClient, privateKey, Fr.random(), 2); const accounts = await aztecRpcClient.getAccounts(); - logger(`Created ${accounts.length} accounts`); const [ownerAddress, address2] = accounts; - const ownerPubKeyPoint = await aztecRpcClient.getAccountPublicKey(ownerAddress); - const address2PubKeyPoint = await aztecRpcClient.getAccountPublicKey(address2); - logger(`Created account ${ownerAddress.toString()} with public key ${ownerPubKeyPoint.toString()}`); + logger(`Created ${accounts.length} accounts`); + + const ownerPubKeyPoint = await wallet.getAccountPublicKey(ownerAddress); + const address2PubKeyPoint = await wallet.getAccountPublicKey(address2); + logger(`Created Owner account ${ownerAddress.toString()} with public key ${ownerPubKeyPoint.toString()}`); + const zkContract = await deployZKContract(ownerPubKeyPoint); - const [balance1] = await zkContract.methods - .getBalance(pointToPublicKey(ownerPubKeyPoint)) - .view({ from: ownerAddress }); + const [balance1] = await zkContract.methods.getBalance(ownerPubKeyPoint.toBigInts()).view({ from: ownerAddress }); logger(`Initial owner balance: ${balance1}`); // Mint more tokens logger(`Minting ${SECONDARY_AMOUNT} more coins`); - const mintTx = zkContract.methods.mint(SECONDARY_AMOUNT, ownerPubKeyPoint).send({ from: ownerAddress }); + const mintTx = zkContract.methods.mint(SECONDARY_AMOUNT, ownerPubKeyPoint.toBigInts()).send({ from: ownerAddress }); await mintTx.isMined(0, 0.5); const balanceAfterMint = await getBalance(zkContract, ownerPubKeyPoint, ownerAddress); logger(`Owner's balance is now: ${balanceAfterMint}`); @@ -80,7 +73,7 @@ async function main() { // Perform a transfer logger(`Transferring ${SECONDARY_AMOUNT} tokens from owner to another account.`); const transferTx = zkContract.methods - .transfer(SECONDARY_AMOUNT, ownerPubKeyPoint, address2PubKeyPoint) + .transfer(SECONDARY_AMOUNT, ownerPubKeyPoint.toBigInts(), address2PubKeyPoint.toBigInts()) .send({ from: ownerAddress }); await transferTx.isMined(0, 0.5); const balanceAfterTransfer = await getBalance(zkContract, ownerPubKeyPoint, ownerAddress); diff --git a/yarn-project/aztec.js/src/account_impl/account_collection.ts b/yarn-project/aztec.js/src/account_impl/account_collection.ts index b1c64025c5d..90147b5cbe0 100644 --- a/yarn-project/aztec.js/src/account_impl/account_collection.ts +++ b/yarn-project/aztec.js/src/account_impl/account_collection.ts @@ -6,7 +6,7 @@ import { AccountImplementation } from './index.js'; * A concrete account implementation that manages multiple accounts. */ export class AccountCollection implements AccountImplementation { - private accounts: Map = new Map(); + private accounts: Map = new Map(); /** * Registers an account implementation against an aztec address @@ -14,12 +14,12 @@ export class AccountCollection implements AccountImplementation { * @param impl - The account implementation to be registered. */ public registerAccount(addr: AztecAddress, impl: AccountImplementation) { - this.accounts.set(addr, impl); + this.accounts.set(addr.toString(), impl); } getAddress(): AztecAddress { if (!this.accounts) throw new Error(`No accounts registered`); - return this.accounts.keys().next().value as AztecAddress; + return AztecAddress.fromString(this.accounts.keys().next().value as string); } /** @@ -34,7 +34,7 @@ export class AccountCollection implements AccountImplementation { ): Promise { // TODO: Check all executions have the same origin const sender = executions[0].from; - const impl = this.accounts.get(sender); + const impl = this.accounts.get(sender.toString()); if (!impl) throw new Error(`No account implementation registered for ${sender}`); return impl.createAuthenticatedTxRequest(executions, txContext); } diff --git a/yarn-project/aztec.js/src/auth/ecdsa.ts b/yarn-project/aztec.js/src/auth/ecdsa.ts index cf2928f6a48..261d187aa64 100644 --- a/yarn-project/aztec.js/src/auth/ecdsa.ts +++ b/yarn-project/aztec.js/src/auth/ecdsa.ts @@ -1,9 +1,9 @@ import { secp256k1 } from '@noble/curves/secp256k1'; +import { EcdsaSignature } from '@aztec/circuits.js/barretenberg'; + import { AztecAddress } from '../index.js'; import { AuthPayload } from './index.js'; - import { EntrypointPayload } from '../account_impl/account_contract.js'; -import { EcdsaSignature } from '@aztec/circuits.js/barretenberg'; /** * An ecdsa implementation of auth provider. diff --git a/yarn-project/aztec.js/src/auth/schnorr.ts b/yarn-project/aztec.js/src/auth/schnorr.ts index b714cb7ed17..7b2a4abbcd7 100644 --- a/yarn-project/aztec.js/src/auth/schnorr.ts +++ b/yarn-project/aztec.js/src/auth/schnorr.ts @@ -1,5 +1,5 @@ -import { AuthPayload, AztecAddress, EntrypointPayload } from '@aztec/aztec.js'; import { Schnorr } from '@aztec/circuits.js/barretenberg'; +import { AuthPayload, AztecAddress, EntrypointPayload } from '@aztec/aztec.js'; /** * Implementation of a schnorr signature provider diff --git a/yarn-project/aztec.js/src/aztec_rpc_client/aztec_rpc_client.ts b/yarn-project/aztec.js/src/aztec_rpc_client/aztec_rpc_client.ts index d82c7fe4da5..0f3c6179729 100644 --- a/yarn-project/aztec.js/src/aztec_rpc_client/aztec_rpc_client.ts +++ b/yarn-project/aztec.js/src/aztec_rpc_client/aztec_rpc_client.ts @@ -1,36 +1,6 @@ -import { AztecAddress, AztecRPC, EthAddress, Fr, Point, Tx, TxHash } from '@aztec/aztec-rpc'; +import { AztecAddress, AztecRPC, EthAddress, Fr, Point, Tx } from '@aztec/aztec-rpc'; import { createJsonRpcClient } from '@aztec/foundation/json-rpc'; -import { ContractData, ContractDeploymentTx, ContractPublicData, TxExecutionRequest } from '@aztec/types'; - -/** - * A dictionary of the Aztec-deployed L1 contracts. - */ -export type L1ContractAddresses = { - /** - * Address fo the main Aztec rollup contract. - */ - rollup: EthAddress; - /** - * Address of the contract that emits events on public contract deployment. - */ - contractDeploymentEmitter: EthAddress; - /** - * Address of the L1/L2 messaging inbox contract. - */ - inbox: EthAddress; - - /** - * Registry Address. - */ - registry: EthAddress; -}; - -/** - * string dictionary of aztec contract addresses that we receive over http. - */ -type L1ContractAddressesResp = { - [K in keyof L1ContractAddresses]: string; -}; +import { ContractData, ContractDeploymentTx, ContractPublicData, TxExecutionRequest, TxHash } from '@aztec/types'; export const createAztecRpcClient = (url: string): AztecRPC => createJsonRpcClient( @@ -48,12 +18,3 @@ export const createAztecRpcClient = (url: string): AztecRPC => { Tx, ContractDeploymentTx }, false, ); - -export const getL1ContractAddresses = async (url: string): Promise => { - const reqUrl = new URL(`${url}/api/l1-contract-addresses`); - const response = (await (await fetch(reqUrl.toString())).json()) as unknown as L1ContractAddressesResp; - const result = Object.fromEntries( - Object.entries(response).map(([key, value]) => [key, EthAddress.fromString(value)]), - ); - return result as L1ContractAddresses; -}; diff --git a/yarn-project/aztec.js/src/aztec_rpc_client/wallet.ts b/yarn-project/aztec.js/src/aztec_rpc_client/wallet.ts index ee04031ac01..8d79198bd12 100644 --- a/yarn-project/aztec.js/src/aztec_rpc_client/wallet.ts +++ b/yarn-project/aztec.js/src/aztec_rpc_client/wallet.ts @@ -33,6 +33,9 @@ export abstract class BaseWallet implements Wallet { getAccountPublicKey(address: AztecAddress): Promise { return this.rpc.getAccountPublicKey(address); } + getAccountAddress(publicKey: Point): Promise { + return this.rpc.getAccountAddress(publicKey); + } addContracts(contracts: DeployedContract[]): Promise { return this.rpc.addContracts(contracts); } diff --git a/yarn-project/aztec.js/src/utils/account.ts b/yarn-project/aztec.js/src/utils/account.ts index 2eaccaa5e59..d2eb2fd896d 100644 --- a/yarn-project/aztec.js/src/utils/account.ts +++ b/yarn-project/aztec.js/src/utils/account.ts @@ -1,16 +1,17 @@ import { AztecRPC, TxStatus, getContractDeploymentInfo } from '@aztec/aztec-rpc'; -import { AztecAddress, CircuitsWasm, Fr, Point } from '@aztec/circuits.js'; +import { CircuitsWasm, Fr } from '@aztec/circuits.js'; import { randomBytes } from '@aztec/foundation/crypto'; import { createDebugLogger } from '@aztec/foundation/log'; -import { EcdsaAccountContractAbi } from '@aztec/noir-contracts/examples'; +import { SchnorrAccountContractAbi } from '@aztec/noir-contracts/examples'; import { AccountWallet, Wallet } from '../aztec_rpc_client/wallet.js'; import { AccountCollection, AccountContract, ContractDeployer, - EcdsaAuthProvider, + SchnorrAuthProvider, generatePublicKey, } from '../index.js'; +import { Schnorr } from '@aztec/circuits.js/barretenberg'; /** * Creates an Aztec Account. @@ -19,41 +20,71 @@ import { export async function createAccounts( aztecRpcClient: AztecRPC, privateKey?: Buffer, + salt = Fr.random(), numberOfAccounts = 1, logger = createDebugLogger('aztec:aztec.js:accounts'), ): Promise { + const accountAbi = SchnorrAccountContractAbi; const accountImpls = new AccountCollection(); - const results: [AztecAddress, Point][] = []; const wasm = await CircuitsWasm.get(); for (let i = 0; i < numberOfAccounts; ++i) { // TODO(#662): Let the aztec rpc server generate the keypair rather than hardcoding the private key const privKey = i == 0 && privateKey ? privateKey : randomBytes(32); - const accountAbi = EcdsaAccountContractAbi; const publicKey = await generatePublicKey(privKey); - const salt = Fr.random(); const deploymentInfo = await getContractDeploymentInfo(accountAbi, [], salt, publicKey); await aztecRpcClient.addAccount(privKey, deploymentInfo.address, deploymentInfo.partialAddress, accountAbi); const contractDeployer = new ContractDeployer(accountAbi, aztecRpcClient, publicKey); const tx = contractDeployer.deploy().send({ contractAddressSalt: salt }); - await tx.isMined(0, 0.1); + await tx.isMined(0, 0.5); const receipt = await tx.getReceipt(); if (receipt.status !== TxStatus.MINED) { throw new Error(`Deployment tx not mined (status is ${receipt.status})`); } const address = receipt.contractAddress!; + if (!address.equals(deploymentInfo.address)) { + throw new Error( + `Deployment address does not match for account contract (expected ${deploymentInfo.address.toString()} got ${address.toString()})`, + ); + } logger(`Created account ${address.toString()} with public key ${publicKey.toString()}`); accountImpls.registerAccount( address, new AccountContract( address, publicKey, - new EcdsaAuthProvider(privKey), + new SchnorrAuthProvider(await Schnorr.new(), privKey), deploymentInfo.partialAddress, accountAbi, wasm, ), ); - results.push([address, publicKey]); } return new AccountWallet(aztecRpcClient, accountImpls); } + +/** + * Gets the Aztec accounts that are stored in an Aztec RPC instance. + * @param aztecRpcClient - An instance of the Aztec RPC interface. + * @param numberOfAccounts - The number of accounts to fetch. + * @returns An AccountWallet implementation that includes all the accounts found. + */ +export async function getAccountWallet(aztecRpcClient: AztecRPC, privateKey: Buffer, salt: Fr) { + const wasm = await CircuitsWasm.get(); + const accountCollection = new AccountCollection(); + const publicKey = await generatePublicKey(privateKey); + const address = await aztecRpcClient.getAccountAddress(publicKey); + const deploymentInfo = await getContractDeploymentInfo(SchnorrAccountContractAbi, [], salt, publicKey); + + accountCollection.registerAccount( + address, + new AccountContract( + address, + publicKey, + new SchnorrAuthProvider(await Schnorr.new(), privateKey), + deploymentInfo.partialAddress, + SchnorrAccountContractAbi, + wasm, + ), + ); + return new AccountWallet(aztecRpcClient, accountCollection); +} diff --git a/yarn-project/aztec.js/src/utils/index.ts b/yarn-project/aztec.js/src/utils/index.ts index 1edb3e480fb..1825d0d2902 100644 --- a/yarn-project/aztec.js/src/utils/index.ts +++ b/yarn-project/aztec.js/src/utils/index.ts @@ -1,3 +1,4 @@ export * from './secrets.js'; export * from './account.js'; export * from './pub_key.js'; +export * from './l1_contracts.js'; diff --git a/yarn-project/aztec.js/src/utils/l1_contracts.ts b/yarn-project/aztec.js/src/utils/l1_contracts.ts new file mode 100644 index 00000000000..9276f8a5817 --- /dev/null +++ b/yarn-project/aztec.js/src/utils/l1_contracts.ts @@ -0,0 +1,40 @@ +import { EthAddress } from '@aztec/circuits.js'; + +/** + * A dictionary of the Aztec-deployed L1 contracts. + */ +export type L1ContractAddresses = { + /** + * Address fo the main Aztec rollup contract. + */ + rollup: EthAddress; + /** + * Address of the contract that emits events on public contract deployment. + */ + contractDeploymentEmitter: EthAddress; + /** + * Address of the L1/L2 messaging inbox contract. + */ + inbox: EthAddress; + + /** + * Registry Address. + */ + registry: EthAddress; +}; + +/** + * string dictionary of aztec contract addresses that we receive over http. + */ +type L1ContractAddressesResp = { + [K in keyof L1ContractAddresses]: string; +}; + +export const getL1ContractAddresses = async (url: string): Promise => { + const reqUrl = new URL(`${url}/api/l1-contract-addresses`); + const response = (await (await fetch(reqUrl.toString())).json()) as unknown as L1ContractAddressesResp; + const result = Object.fromEntries( + Object.entries(response).map(([key, value]) => [key, EthAddress.fromString(value)]), + ); + return result as L1ContractAddresses; +}; diff --git a/yarn-project/aztec.js/src/utils/pub_key.ts b/yarn-project/aztec.js/src/utils/pub_key.ts index 10ec2ead2a0..c932c049013 100644 --- a/yarn-project/aztec.js/src/utils/pub_key.ts +++ b/yarn-project/aztec.js/src/utils/pub_key.ts @@ -1,20 +1,6 @@ import { Grumpkin } from '@aztec/circuits.js/barretenberg'; import { Point } from '../index.js'; -/** - * Converts a Point type to a public key represented by BigInt coordinates - * @param point - The Point to convert. - * @returns An object with x & y coordinates represented as bigints. - */ -export function pointToPublicKey(point: Point) { - const x = point.x.toBigInt(); - const y = point.y.toBigInt(); - return { - x, - y, - }; -} - /** * Method for generating a public grumpkin key from a private key. * @param privateKey - The private key. diff --git a/yarn-project/end-to-end/src/cross_chain/test_harness.ts b/yarn-project/end-to-end/src/cross_chain/test_harness.ts index 63c1354dcc1..edd1994c9bf 100644 --- a/yarn-project/end-to-end/src/cross_chain/test_harness.ts +++ b/yarn-project/end-to-end/src/cross_chain/test_harness.ts @@ -4,7 +4,7 @@ import { AztecAddress, EthAddress, Fr, Point } from '@aztec/circuits.js'; import { DeployL1Contracts } from '@aztec/ethereum'; import { DebugLogger } from '@aztec/foundation/log'; import { PublicClient, HttpTransport, Chain, getContract } from 'viem'; -import { deployAndInitializeNonNativeL2TokenContracts, expectAztecStorageSlot, pointToPublicKey } from '../utils.js'; +import { deployAndInitializeNonNativeL2TokenContracts, expectAztecStorageSlot } from '../utils.js'; import { OutboxAbi } from '@aztec/l1-artifacts'; import { sha256ToField } from '@aztec/foundation/crypto'; import { toBufferBE } from '@aztec/foundation/bigint-buffer'; @@ -44,7 +44,7 @@ export class CrossChainTestHarness { publicClient, deployL1ContractsValues!.registryAddress, initialBalance, - pointToPublicKey(ownerPub), + ownerPub.toBigInts(), ); const l2Contract = contracts.l2Contract; const underlyingERC20 = contracts.underlyingERC20; @@ -152,8 +152,8 @@ export class CrossChainTestHarness { const transferTx = this.l2Contract.methods .transfer( transferAmount, - pointToPublicKey(await this.aztecRpcServer.getAccountPublicKey(this.ownerAddress)), - pointToPublicKey(await this.aztecRpcServer.getAccountPublicKey(this.receiver)), + (await this.aztecRpcServer.getAccountPublicKey(this.ownerAddress)).toBigInts(), + (await this.aztecRpcServer.getAccountPublicKey(this.receiver)).toBigInts(), ) .send({ from: this.accounts[0] }); @@ -189,7 +189,7 @@ export class CrossChainTestHarness { async getL2BalanceOf(owner: AztecAddress) { const ownerPublicKey = await this.aztecRpcServer.getAccountPublicKey(owner); - const [balance] = await this.l2Contract.methods.getBalance(pointToPublicKey(ownerPublicKey)).view({ from: owner }); + const [balance] = await this.l2Contract.methods.getBalance(ownerPublicKey.toBigInts()).view({ from: owner }); return balance; } diff --git a/yarn-project/end-to-end/src/e2e_cross_chain_messaging.test.ts b/yarn-project/end-to-end/src/e2e_cross_chain_messaging.test.ts index da08cfa3fbe..35c6f5cab1f 100644 --- a/yarn-project/end-to-end/src/e2e_cross_chain_messaging.test.ts +++ b/yarn-project/end-to-end/src/e2e_cross_chain_messaging.test.ts @@ -4,7 +4,7 @@ import { EthAddress } from '@aztec/foundation/eth-address'; import { Point } from '@aztec/foundation/fields'; import { DebugLogger } from '@aztec/foundation/log'; -import { delay, pointToPublicKey, setup } from './utils.js'; +import { delay, setup } from './utils.js'; import { CrossChainTestHarness } from './cross_chain/test_harness.js'; describe('e2e_cross_chain_messaging', () => { @@ -62,7 +62,7 @@ describe('e2e_cross_chain_messaging', () => { }); const expectBalance = async (owner: AztecAddress, expectedBalance: bigint) => { - const [balance] = await l2Contract.methods.getBalance(pointToPublicKey(ownerPub)).view({ from: owner }); + const [balance] = await l2Contract.methods.getBalance(ownerPub.toBigInts()).view({ from: owner }); logger(`Account ${owner} balance: ${balance}`); expect(balance).toBe(expectedBalance); }; diff --git a/yarn-project/end-to-end/src/e2e_public_token_contract.test.ts b/yarn-project/end-to-end/src/e2e_public_token_contract.test.ts index e66e068393e..8fb0c5095b0 100644 --- a/yarn-project/end-to-end/src/e2e_public_token_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_public_token_contract.test.ts @@ -5,7 +5,7 @@ import { PublicTokenContractAbi } from '@aztec/noir-contracts/examples'; import { L2BlockL2Logs, LogType } from '@aztec/types'; import times from 'lodash.times'; -import { expectAztecStorageSlot, pointToPublicKey, setup } from './utils.js'; +import { expectAztecStorageSlot, setup } from './utils.js'; describe('e2e_public_token_contract', () => { let aztecNode: AztecNodeService; @@ -65,7 +65,7 @@ describe('e2e_public_token_contract', () => { const PK = await aztecRpcServer.getAccountPublicKey(recipient); - const tx = deployedContract.methods.mint(mintAmount, pointToPublicKey(PK)).send({ from: recipient }); + const tx = deployedContract.methods.mint(mintAmount, PK.toBigInts()).send({ from: recipient }); await tx.isMined(0, 0.1); const receipt = await tx.getReceipt(); @@ -85,7 +85,7 @@ describe('e2e_public_token_contract', () => { const { contract: deployedContract } = await deployContract(); // Assemble two mint txs sequentially (no parallel calls to circuits!) and send them simultaneously - const methods = times(3, () => deployedContract.methods.mint(mintAmount, pointToPublicKey(PK))); + const methods = times(3, () => deployedContract.methods.mint(mintAmount, PK.toBigInts())); for (const method of methods) await method.simulate({ from: recipient }); const txs = await Promise.all(methods.map(method => method.send())); diff --git a/yarn-project/end-to-end/src/e2e_zk_token_contract.test.ts b/yarn-project/end-to-end/src/e2e_zk_token_contract.test.ts index faa9ad88c8c..6f60c9b7c30 100644 --- a/yarn-project/end-to-end/src/e2e_zk_token_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_zk_token_contract.test.ts @@ -4,7 +4,7 @@ import { ZkTokenContractAbi } from '@aztec/noir-contracts/examples'; import { DebugLogger } from '@aztec/foundation/log'; import { L2BlockL2Logs, LogType } from '@aztec/types'; -import { pointToPublicKey, setup } from './utils.js'; +import { setup } from './utils.js'; describe('e2e_zk_token_contract', () => { let aztecNode: AztecNodeService; @@ -26,7 +26,7 @@ describe('e2e_zk_token_contract', () => { const expectBalance = async (owner: AztecAddress, expectedBalance: bigint) => { const ownerPublicKey = await aztecRpcServer.getAccountPublicKey(owner); - const [balance] = await contract.methods.getBalance(pointToPublicKey(ownerPublicKey)).view({ from: owner }); + const [balance] = await contract.methods.getBalance(ownerPublicKey.toBigInts()).view({ from: owner }); logger(`Account ${owner} balance: ${balance}`); expect(balance).toBe(expectedBalance); }; @@ -67,7 +67,7 @@ describe('e2e_zk_token_contract', () => { it('1.3 should deploy zk token contract with initial token minted to the account', async () => { const initialBalance = 987n; const owner = await aztecRpcServer.getAccountPublicKey(accounts[0]); - await deployContract(initialBalance, pointToPublicKey(owner)); + await deployContract(initialBalance, owner.toBigInts()); await expectBalance(accounts[0], initialBalance); await expectBalance(accounts[1], 0n); @@ -82,7 +82,7 @@ describe('e2e_zk_token_contract', () => { const mintAmount = 65n; const [owner] = accounts; - const ownerPublicKey = pointToPublicKey(await aztecRpcServer.getAccountPublicKey(owner)); + const ownerPublicKey = (await aztecRpcServer.getAccountPublicKey(owner)).toBigInts(); const deployedContract = await deployContract(0n, ownerPublicKey); await expectBalance(owner, 0n); @@ -110,7 +110,7 @@ describe('e2e_zk_token_contract', () => { const transferAmount = 654n; const [owner, receiver] = accounts; - await deployContract(initialBalance, pointToPublicKey(await aztecRpcServer.getAccountPublicKey(owner))); + await deployContract(initialBalance, (await aztecRpcServer.getAccountPublicKey(owner)).toBigInts()); await expectBalance(owner, initialBalance); await expectBalance(receiver, 0n); @@ -121,8 +121,8 @@ describe('e2e_zk_token_contract', () => { const tx = contract.methods .transfer( transferAmount, - pointToPublicKey(await aztecRpcServer.getAccountPublicKey(owner)), - pointToPublicKey(await aztecRpcServer.getAccountPublicKey(receiver)), + (await aztecRpcServer.getAccountPublicKey(owner)).toBigInts(), + (await aztecRpcServer.getAccountPublicKey(receiver)).toBigInts(), ) .send({ from: accounts[0] }); diff --git a/yarn-project/end-to-end/src/integration_archiver_l1_to_l2.test.ts b/yarn-project/end-to-end/src/integration_archiver_l1_to_l2.test.ts index 89fe6cb12ca..2dc44a4863e 100644 --- a/yarn-project/end-to-end/src/integration_archiver_l1_to_l2.test.ts +++ b/yarn-project/end-to-end/src/integration_archiver_l1_to_l2.test.ts @@ -6,13 +6,7 @@ import { DeployL1Contracts } from '@aztec/ethereum'; import { Fr } from '@aztec/foundation/fields'; import { DebugLogger } from '@aztec/foundation/log'; import { Chain, HttpTransport, PublicClient } from 'viem'; -import { - delay, - deployAndInitializeNonNativeL2TokenContracts, - pointToPublicKey, - setNextBlockTimestamp, - setup, -} from './utils.js'; +import { delay, deployAndInitializeNonNativeL2TokenContracts, setNextBlockTimestamp, setup } from './utils.js'; import { Archiver } from '@aztec/archiver'; describe('archiver integration with l1 to l2 messages', () => { @@ -46,7 +40,7 @@ describe('archiver integration with l1 to l2 messages', () => { ethAccount = EthAddress.fromString((await walletClient.getAddresses())[0]); [ownerAddress, receiver] = accounts; - const ownerPub = pointToPublicKey(await aztecRpcServer.getAccountPublicKey(ownerAddress)); + const ownerPub = (await aztecRpcServer.getAccountPublicKey(ownerAddress)).toBigInts(); // Deploy and initialize all required contracts logger('Deploying Portal, initializing and deploying l2 contract...'); @@ -74,7 +68,7 @@ describe('archiver integration with l1 to l2 messages', () => { const expectBalance = async (owner: AztecAddress, expectedBalance: bigint) => { const ownerPublicKey = await aztecRpcServer.getAccountPublicKey(owner); - const [balance] = await l2Contract.methods.getBalance(pointToPublicKey(ownerPublicKey)).view({ from: owner }); + const [balance] = await l2Contract.methods.getBalance(ownerPublicKey.toBigInts()).view({ from: owner }); logger(`Account ${owner} balance: ${balance}`); expect(balance).toBe(expectedBalance); }; @@ -128,8 +122,8 @@ describe('archiver integration with l1 to l2 messages', () => { l2Contract.methods .transfer( transferAmount, - pointToPublicKey(await aztecRpcServer.getAccountPublicKey(ownerAddress)), - pointToPublicKey(await aztecRpcServer.getAccountPublicKey(receiver)), + (await aztecRpcServer.getAccountPublicKey(ownerAddress)).toBigInts(), + (await aztecRpcServer.getAccountPublicKey(receiver)).toBigInts(), ) .send({ from: accounts[0] }); diff --git a/yarn-project/end-to-end/src/uniswap_trade_on_l1_from_l2.test.ts b/yarn-project/end-to-end/src/uniswap_trade_on_l1_from_l2.test.ts index 6d779426dba..df290902afc 100644 --- a/yarn-project/end-to-end/src/uniswap_trade_on_l1_from_l2.test.ts +++ b/yarn-project/end-to-end/src/uniswap_trade_on_l1_from_l2.test.ts @@ -3,7 +3,7 @@ import { AztecAddress, AztecRPCServer, Contract, ContractDeployer, Fr, TxStatus, import { deployL1Contract } from '@aztec/ethereum'; import { EthAddress } from '@aztec/foundation/eth-address'; -import { delay, deployAndInitializeNonNativeL2TokenContracts, pointToPublicKey, setup } from './utils.js'; +import { delay, deployAndInitializeNonNativeL2TokenContracts, setup } from './utils.js'; import { CrossChainTestHarness } from './cross_chain/test_harness.js'; import { DebugLogger } from '@aztec/foundation/log'; import { getContract, parseEther } from 'viem'; @@ -58,7 +58,7 @@ describe('uniswap_trade_on_l1_from_l2', () => { ethAccount = EthAddress.fromString((await walletClient.getAddresses())[0]); [ownerAddress, receiver] = accounts; const ownerPubPoint = await aztecRpcServer.getAccountPublicKey(ownerAddress); - ownerPub = pointToPublicKey(ownerPubPoint); + ownerPub = ownerPubPoint.toBigInts(); logger('Deploying DAI Portal, initializing and deploying l2 contract...'); const daiContracts = await deployAndInitializeNonNativeL2TokenContracts( diff --git a/yarn-project/end-to-end/src/utils.ts b/yarn-project/end-to-end/src/utils.ts index 994e0dbe73a..e71d7ade2f1 100644 --- a/yarn-project/end-to-end/src/utils.ts +++ b/yarn-project/end-to-end/src/utils.ts @@ -12,7 +12,6 @@ import { Contract, ContractDeployer, EthAddress, - Point, SchnorrAuthProvider, TxStatus, Wallet, @@ -183,20 +182,6 @@ export function getLogger() { return createDebugLogger('aztec:' + describeBlockName); } -/** - * Converts a point to a public key. - * @param point - the point to convert to - * @returns two big ints x,y representing the public key - */ -export function pointToPublicKey(point: Point) { - const x = point.x.toBigInt(); - const y = point.y.toBigInt(); - return { - x, - y, - }; -} - /** * Deploy L1 token and portal, initialize portal, deploy a non native l2 token contract and attach is to the portal. * @param aztecRpcServer - the aztec rpc server instance diff --git a/yarn-project/foundation/src/fields/point.ts b/yarn-project/foundation/src/fields/point.ts index 2bd264f7d42..e19a28d1151 100644 --- a/yarn-project/foundation/src/fields/point.ts +++ b/yarn-project/foundation/src/fields/point.ts @@ -86,6 +86,19 @@ export class Point { return this.x.toFields().concat(this.y.toFields()); } + /** + * Returns the contents of the point as BigInts. + * @returns The point as BigInts + */ + toBigInts() { + const x = this.x.toBigInt(); + const y = this.y.toBigInt(); + return { + x, + y, + }; + } + /** * Converts the Point instance to a Buffer representaion of the coordinates. * The outputs buffer length will be 64, the length of both coordinates not represented as fields. diff --git a/yarn-project/key-store/src/test_key_store.ts b/yarn-project/key-store/src/test_key_store.ts index 407b86aebdd..734c0aadd8f 100644 --- a/yarn-project/key-store/src/test_key_store.ts +++ b/yarn-project/key-store/src/test_key_store.ts @@ -19,6 +19,13 @@ export class TestKeyStore implements KeyStore { */ public addAccount(privKey: Buffer): PublicKey { const keyPair = ConstantKeyPair.fromPrivateKey(this.curve, privKey); + + // check if private key has already been used + const account = this.accounts.find(a => a.getPublicKey().equals(keyPair.getPublicKey())); + if (account) { + return account.getPublicKey(); + } + this.accounts.push(keyPair); return keyPair.getPublicKey(); }