Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: cli type check arguments and options #2571

Merged
merged 3 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 83 additions & 91 deletions yarn-project/cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import {
AztecAddress,
Contract,
ContractDeployer,
Fr,
GrumpkinScalar,
Point,
generatePublicKey,
getSchnorrAccount,
isContractDeployed,
Expand All @@ -14,10 +12,10 @@ import { JsonStringify } from '@aztec/foundation/json-rpc';
import { DebugLogger, LogFn } from '@aztec/foundation/log';
import { fileURLToPath } from '@aztec/foundation/url';
import { compileContract, generateNoirInterface, generateTypescriptInterface } from '@aztec/noir-compiler/cli';
import { CompleteAddress, ContractData, L2BlockL2Logs, TxHash } from '@aztec/types';
import { CompleteAddress, ContractData, L2BlockL2Logs } from '@aztec/types';

import { createSecp256k1PeerId } from '@libp2p/peer-id-factory';
import { Command } from 'commander';
import { Command, Option } from 'commander';
import { readFileSync } from 'fs';
import { dirname, resolve } from 'path';
import { mnemonicToAccount } from 'viem/accounts';
Expand All @@ -30,20 +28,19 @@ import {
getAbiFunction,
getContractAbi,
getExampleContractArtifacts,
getSaltFromHexString,
getTxSender,
parseAztecAddress,
parsePartialAddress,
parsePrivateKey,
parsePublicKey,
parseSaltFromHexString,
parseTxHash,
prepTx,
stripLeadingHex,
} from './utils.js';

const accountCreationSalt = Fr.ZERO;

const {
ETHEREUM_HOST = 'http://localhost:8545',
PXE_HOST = 'http://localhost:8080',
PRIVATE_KEY,
API_KEY,
} = process.env;
const { ETHEREUM_HOST = 'http://localhost:8545', PRIVATE_KEY, API_KEY } = process.env;

/**
* Returns commander program that defines the CLI.
Expand All @@ -59,6 +56,17 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {

program.name('aztec-cli').description('CLI for interacting with Aztec.').version(version);

const pxeOption = new Option('-u, --rpc-url <string>', 'URL of the PXE')
.env('PXE_HOST')
.default('http://localhost:8080')
.makeOptionMandatory(true);

const createPrivateKeyOption = (description: string, mandatory: boolean) =>
new Option('-k, --private-key <string>', description)
.env('PRIVATE_KEY')
.argParser(parsePrivateKey)
.makeOptionMandatory(mandatory);

program
.command('deploy-l1-contracts')
.description('Deploys all necessary Ethereum contracts for Aztec.')
Expand Down Expand Up @@ -134,17 +142,13 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
'Creates an aztec account that can be used for sending transactions. Registers the account on the PXE and deploys an account contract. Uses a Schnorr single-key account which uses the same key for encryption and authentication (not secure for production usage).',
)
.summary('Creates an aztec account that can be used for sending transactions.')
.option(
'-k, --private-key <string>',
'Private key for note encryption and transaction signing. Uses random by default.',
PRIVATE_KEY,
.addOption(
createPrivateKeyOption('Private key for note encryption and transaction signing. Uses random by default.', false),
)
.requiredOption('-u, --rpc-url <string>', 'URL of the PXE', PXE_HOST)
.addOption(pxeOption)
.action(async options => {
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
const privateKey = options.privateKey
? GrumpkinScalar.fromString(stripLeadingHex(options.privateKey))
: GrumpkinScalar.random();
const privateKey = options.privateKey ?? GrumpkinScalar.random();

const account = getSchnorrAccount(client, privateKey, privateKey, accountCreationSalt);
const wallet = await account.waitDeploy();
Expand All @@ -165,38 +169,38 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
"A compiled Aztec.nr contract's ABI in JSON format or name of a contract ABI exported by @aztec/noir-contracts",
)
.option('-a, --args <constructorArgs...>', 'Contract constructor arguments', [])
.requiredOption('-u, --rpc-url <string>', 'URL of the PXE', PXE_HOST)
.addOption(pxeOption)
.option(
'-k, --public-key <string>',
'Optional encryption public key for this address. Set this value only if this contract is expected to receive private notes, which will be encrypted using this public key.',
parsePublicKey,
)
.option(
'-s, --salt <hex string>',
'Optional deployment salt as a hex string for generating the deployment address.',
getSaltFromHexString,
parseSaltFromHexString,
)
// `options.wait` is default true. Passing `--no-wait` will set it to false.
// https://github.com/tj/commander.js#other-option-types-negatable-boolean-and-booleanvalue
.option('--no-wait', 'Skip waiting for the contract to be deployed. Print the hash of deployment transaction')
.action(async (abiPath, options: any) => {
.action(async (abiPath, { rpcUrl, publicKey, args: rawArgs, salt, wait }) => {
const contractAbi = await getContractAbi(abiPath, log);
const constructorAbi = contractAbi.functions.find(({ name }) => name === 'constructor');

const client = await createCompatibleClient(options.rpcUrl, debugLogger);
const publicKey = options.publicKey ? Point.fromString(options.publicKey) : undefined;

const client = await createCompatibleClient(rpcUrl, debugLogger);
const deployer = new ContractDeployer(contractAbi, client, publicKey);

const constructor = getAbiFunction(contractAbi, 'constructor');
if (!constructor) throw new Error(`Constructor not found in contract ABI`);

debugLogger(`Input arguments: ${options.args.map((x: any) => `"${x}"`).join(', ')}`);
const args = encodeArgs(options.args, constructorAbi!.parameters);
debugLogger(`Input arguments: ${rawArgs.map((x: any) => `"${x}"`).join(', ')}`);
const args = encodeArgs(rawArgs, constructorAbi!.parameters);
debugLogger(`Encoded arguments: ${args.join(', ')}`);
const tx = deployer.deploy(...args).send({ contractAddressSalt: options.salt });

const tx = deployer.deploy(...args).send({ contractAddressSalt: salt });
const txHash = await tx.getTxHash();
debugLogger(`Deploy tx sent with hash ${txHash}`);
if (options.wait) {
if (wait) {
const deployed = await tx.wait();
log(`\nContract deployed at ${deployed.contractAddress!.toString()}\n`);
} else {
Expand All @@ -207,11 +211,15 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
program
.command('check-deploy')
.description('Checks if a contract is deployed to the specified Aztec address.')
.requiredOption('-ca, --contract-address <address>', 'An Aztec address to check if contract has been deployed to.')
.requiredOption('-u, --rpc-url <url>', 'URL of the PXE', PXE_HOST)
.requiredOption(
'-ca, --contract-address <address>',
'An Aztec address to check if contract has been deployed to.',
parseAztecAddress,
)
.addOption(pxeOption)
.action(async options => {
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
const address = AztecAddress.fromString(options.contractAddress);
const address = options.contractAddress;
const isDeployed = await isContractDeployed(client, address);
if (isDeployed) log(`\nContract found at ${address.toString()}\n`);
else log(`\nNo contract found at ${address.toString()}\n`);
Expand All @@ -220,14 +228,13 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
program
.command('get-tx-receipt')
.description('Gets the receipt for the specified transaction hash.')
.argument('<txHash>', 'A transaction hash to get the receipt for.')
.requiredOption('-u, --rpc-url <string>', 'URL of the PXE', PXE_HOST)
.action(async (_txHash, options) => {
.argument('<txHash>', 'A transaction hash to get the receipt for.', parseTxHash)
.addOption(pxeOption)
.action(async (txHash, options) => {
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
const txHash = TxHash.fromString(_txHash);
const receipt = await client.getTxReceipt(txHash);
if (!receipt) {
log(`No receipt found for transaction hash ${_txHash}`);
log(`No receipt found for transaction hash ${txHash.toString()}`);
} else {
log(`\nTransaction receipt: \n${JsonStringify(receipt, true)}\n`);
}
Expand All @@ -236,15 +243,14 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
program
.command('get-contract-data')
.description('Gets information about the Aztec contract deployed at the specified address.')
.argument('<contractAddress>', 'Aztec address of the contract.')
.requiredOption('-u, --rpc-url <string>', 'URL of the PXE', PXE_HOST)
.argument('<contractAddress>', 'Aztec address of the contract.', parseAztecAddress)
.addOption(pxeOption)
.option('-b, --include-bytecode <boolean>', "Include the contract's public function bytecode, if any.", false)
.action(async (contractAddress, options) => {
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
const address = AztecAddress.fromString(contractAddress);
const contractDataWithOrWithoutBytecode = options.includeBytecode
? await client.getExtendedContractData(address)
: await client.getContractData(address);
? await client.getExtendedContractData(contractAddress)
: await client.getContractData(contractAddress);

if (!contractDataWithOrWithoutBytecode) {
log(`No contract data found at ${contractAddress}`);
Expand All @@ -270,7 +276,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
.description('Gets all the unencrypted logs from L2 blocks in the range specified.')
.option('-f, --from <blockNum>', 'Initial block number for getting logs (defaults to 1).')
.option('-l, --limit <blockCount>', 'How many blocks to fetch (defaults to 100).')
.requiredOption('-u, --rpc-url <string>', 'URL of the PXE', PXE_HOST)
.addOption(pxeOption)
.action(async options => {
const { from, limit } = options;
const fromBlock = from ? parseInt(from) : 1;
Expand All @@ -289,24 +295,24 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
program
.command('register-recipient')
.description('Register a recipient in the PXE.')
.requiredOption('-a, --address <aztecAddress>', "The account's Aztec address.")
.requiredOption('-p, --public-key <publicKey>', 'The account public key.')
.requiredOption('-pa, --partial-address <partialAddress', 'The partially computed address of the account contract.')
.requiredOption('-u, --rpc-url <string>', 'URL of the PXE', PXE_HOST)
.action(async options => {
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
const address = AztecAddress.fromString(options.address);
const publicKey = Point.fromString(options.publicKey);
const partialAddress = Fr.fromString(options.partialAddress);

.requiredOption('-a, --address <aztecAddress>', "The account's Aztec address.", parseAztecAddress)
.requiredOption('-p, --public-key <publicKey>', 'The account public key.', parsePublicKey)
.requiredOption(
'-pa, --partial-address <partialAddress>',
'The partially computed address of the account contract.',
parsePartialAddress,
)
.addOption(pxeOption)
.action(async ({ address, publicKey, partialAddress, rpcUrl }) => {
const client = await createCompatibleClient(rpcUrl, debugLogger);
await client.registerRecipient(await CompleteAddress.create(address, publicKey, partialAddress));
log(`\nRegistered details for account with address: ${options.address}\n`);
log(`\nRegistered details for account with address: ${address}\n`);
});

program
.command('get-accounts')
.description('Gets all the Aztec accounts stored in the PXE.')
.requiredOption('-u, --rpc-url <string>', 'URL of the PXE', PXE_HOST)
.addOption(pxeOption)
.action(async (options: any) => {
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
const accounts = await client.getRegisteredAccounts();
Expand All @@ -323,15 +329,14 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
program
.command('get-account')
.description('Gets an account given its Aztec address.')
.argument('<address>', 'The Aztec address to get account for')
.requiredOption('-u, --rpc-url <string>', 'URL of the PXE', PXE_HOST)
.action(async (_address, options) => {
.argument('<address>', 'The Aztec address to get account for', parseAztecAddress)
.addOption(pxeOption)
.action(async (address, options) => {
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
const address = AztecAddress.fromString(_address);
const account = await client.getRegisteredAccount(address);

if (!account) {
log(`Unknown account ${_address}`);
log(`Unknown account ${address.toString()}`);
} else {
log(account.toReadableString());
}
Expand All @@ -340,7 +345,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
program
.command('get-recipients')
.description('Gets all the recipients stored in the PXE.')
.requiredOption('-u, --rpc-url <string>', 'URL of the PXE', PXE_HOST)
.addOption(pxeOption)
.action(async (options: any) => {
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
const recipients = await client.getRecipients();
Expand All @@ -357,15 +362,14 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
program
.command('get-recipient')
.description('Gets a recipient given its Aztec address.')
.argument('<address>', 'The Aztec address to get recipient for')
.requiredOption('-u, --rpc-url <string>', 'URL of the PXE', PXE_HOST)
.action(async (_address, options) => {
.argument('<address>', 'The Aztec address to get recipient for', parseAztecAddress)
.addOption(pxeOption)
.action(async (address, options) => {
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
const address = AztecAddress.fromString(_address);
const recipient = await client.getRecipient(address);

if (!recipient) {
log(`Unknown recipient ${_address}`);
log(`Unknown recipient ${address.toString()}`);
} else {
log(recipient.toReadableString());
}
Expand All @@ -380,20 +384,13 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
'-c, --contract-abi <fileLocation>',
"A compiled Aztec.nr contract's ABI in JSON format or name of a contract ABI exported by @aztec/noir-contracts",
)
.requiredOption('-ca, --contract-address <address>', 'Aztec address of the contract.')
.requiredOption('-k, --private-key <string>', "The sender's private key.", PRIVATE_KEY)
.requiredOption('-u, --rpc-url <string>', 'URL of the PXE', PXE_HOST)
.requiredOption('-ca, --contract-address <address>', 'Aztec address of the contract.', parseAztecAddress)
.addOption(createPrivateKeyOption("The sender's private key.", true))
.addOption(pxeOption)
.option('--no-wait', 'Print transaction hash without waiting for it to be mined')
.action(async (functionName, options) => {
const { contractAddress, functionArgs, contractAbi } = await prepTx(
options.contractAbi,
options.contractAddress,
functionName,
options.args,
log,
);

const privateKey = GrumpkinScalar.fromString(stripLeadingHex(options.privateKey));
const { functionArgs, contractAbi } = await prepTx(options.contractAbi, functionName, options.args, log);
const { contractAddress, privateKey } = options;

const client = await createCompatibleClient(options.rpcUrl, debugLogger);
const wallet = await getSchnorrAccount(client, privateKey, privateKey, accountCreationSalt).getWallet();
Expand Down Expand Up @@ -425,17 +422,12 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
'-c, --contract-abi <fileLocation>',
"A compiled Aztec.nr contract's ABI in JSON format or name of a contract ABI exported by @aztec/noir-contracts",
)
.requiredOption('-ca, --contract-address <address>', 'Aztec address of the contract.')
.requiredOption('-ca, --contract-address <address>', 'Aztec address of the contract.', parseAztecAddress)
.option('-f, --from <string>', 'Aztec address of the caller. If empty, will use the first account from RPC.')
.requiredOption('-u, --rpc-url <string>', 'URL of the PXE', PXE_HOST)
.addOption(pxeOption)
.action(async (functionName, options) => {
const { contractAddress, functionArgs, contractAbi } = await prepTx(
options.contractAbi,
options.contractAddress,
functionName,
options.args,
log,
);
const { functionArgs, contractAbi } = await prepTx(options.contractAbi, functionName, options.args, log);

const fnAbi = getAbiFunction(contractAbi, functionName);
if (fnAbi.parameters.length !== options.args.length) {
throw Error(
Expand All @@ -444,7 +436,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
}
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
const from = await getTxSender(client, options.from);
const result = await client.viewTx(functionName, functionArgs, contractAddress, from);
const result = await client.viewTx(functionName, functionArgs, options.contractAddress, from);
log('\nView result: ', result, '\n');
});

Expand Down Expand Up @@ -475,7 +467,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
program
.command('block-number')
.description('Gets the current Aztec L2 block number.')
.requiredOption('-u, --rpc-url <string>', 'URL of the PXE', PXE_HOST)
.addOption(pxeOption)
.action(async (options: any) => {
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
const num = await client.getBlockNumber();
Expand Down Expand Up @@ -506,7 +498,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
program
.command('get-node-info')
.description('Gets the information of an aztec node at a URL.')
.requiredOption('-u, --rpc-url <string>', 'URL of the PXE', PXE_HOST)
.addOption(pxeOption)
.action(async options => {
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
const info = await client.getNodeInfo();
Expand Down
Loading