diff --git a/yarn-project/aztec.js/src/account/index.ts b/yarn-project/aztec.js/src/account/index.ts index 1e17feb770a..0f5dfffef3c 100644 --- a/yarn-project/aztec.js/src/account/index.ts +++ b/yarn-project/aztec.js/src/account/index.ts @@ -8,8 +8,8 @@ */ import { type Fr } from '@aztec/circuits.js'; -export { AccountContract } from './contract.js'; -export { AccountInterface, AuthWitnessProvider } from './interface.js'; +export { type AccountContract } from './contract.js'; +export { type AccountInterface, type AuthWitnessProvider } from './interface.js'; export * from './wallet.js'; /** A contract deployment salt. */ diff --git a/yarn-project/aztec.js/src/account_manager/index.ts b/yarn-project/aztec.js/src/account_manager/index.ts index 9d947435caf..002f98169aa 100644 --- a/yarn-project/aztec.js/src/account_manager/index.ts +++ b/yarn-project/aztec.js/src/account_manager/index.ts @@ -17,7 +17,10 @@ import { DeployAccountSentTx } from './deploy_account_sent_tx.js'; /** * Options to deploy an account contract. */ -export type DeployAccountOptions = Pick; +export type DeployAccountOptions = Pick< + DeployOptions, + 'fee' | 'skipClassRegistration' | 'skipPublicDeployment' | 'estimateGas' +>; /** * Manages a user account. Provides methods for calculating the account's address, deploying the account contract, @@ -163,6 +166,7 @@ export class AccountManager { skipInitialization: false, universalDeploy: true, fee: opts?.fee, + estimateGas: opts?.estimateGas, }), ) .then(tx => tx.getTxHash()); diff --git a/yarn-project/aztec.js/src/api/abi.ts b/yarn-project/aztec.js/src/api/abi.ts index 03a5b41d4f2..69f8b095aee 100644 --- a/yarn-project/aztec.js/src/api/abi.ts +++ b/yarn-project/aztec.js/src/api/abi.ts @@ -1,3 +1,3 @@ -export { ContractArtifact, FunctionArtifact, FunctionSelector } from '@aztec/foundation/abi'; +export { type ContractArtifact, type FunctionArtifact, FunctionSelector } from '@aztec/foundation/abi'; export { loadContractArtifact, contractArtifactToBuffer, contractArtifactFromBuffer } from '@aztec/types/abi'; -export { NoirCompiledContract } from '@aztec/types/noir'; +export { type NoirCompiledContract } from '@aztec/types/noir'; diff --git a/yarn-project/aztec.js/src/api/fee.ts b/yarn-project/aztec.js/src/api/fee.ts index c25727eb4d5..13dfffad38d 100644 --- a/yarn-project/aztec.js/src/api/fee.ts +++ b/yarn-project/aztec.js/src/api/fee.ts @@ -3,3 +3,4 @@ export { NativeFeePaymentMethod } from '../fee/native_fee_payment_method.js'; export { PrivateFeePaymentMethod } from '../fee/private_fee_payment_method.js'; export { PublicFeePaymentMethod } from '../fee/public_fee_payment_method.js'; export { NativeFeePaymentMethodWithClaim } from '../fee/native_fee_payment_method_with_claim.js'; +export { NoFeePaymentMethod } from '../fee/no_fee_payment_method.js'; diff --git a/yarn-project/aztec.js/src/contract/base_contract_interaction.ts b/yarn-project/aztec.js/src/contract/base_contract_interaction.ts index 20af8dce86d..1cdbaa00b4d 100644 --- a/yarn-project/aztec.js/src/contract/base_contract_interaction.ts +++ b/yarn-project/aztec.js/src/contract/base_contract_interaction.ts @@ -1,5 +1,6 @@ import { type Tx, type TxExecutionRequest } from '@aztec/circuit-types'; import { GasSettings } from '@aztec/circuits.js'; +import { createDebugLogger } from '@aztec/foundation/log'; import { type Wallet } from '../account/wallet.js'; import { type ExecutionRequestInit, type FeeOptions } from '../entrypoint/entrypoint.js'; @@ -27,6 +28,8 @@ export abstract class BaseContractInteraction { protected tx?: Tx; protected txRequest?: TxExecutionRequest; + protected log = createDebugLogger('aztec:js:contract_interaction'); + constructor(protected wallet: Wallet) {} /** @@ -104,6 +107,9 @@ export abstract class BaseContractInteraction { simulationResult, fee.gasSettings.teardownGasLimits, ); + this.log.debug( + `Estimated gas limits for tx: DA=${gasLimits.daGas} L2=${gasLimits.l2Gas} teardownDA=${teardownGasLimits.daGas} teardownL2=${teardownGasLimits.l2Gas}`, + ); const gasSettings = GasSettings.default({ ...fee.gasSettings, gasLimits, teardownGasLimits }); return { ...fee, gasSettings }; } diff --git a/yarn-project/aztec.js/src/contract/deploy_method.ts b/yarn-project/aztec.js/src/contract/deploy_method.ts index d3f67b865b8..c572d526f44 100644 --- a/yarn-project/aztec.js/src/contract/deploy_method.ts +++ b/yarn-project/aztec.js/src/contract/deploy_method.ts @@ -7,7 +7,6 @@ import { } from '@aztec/circuits.js'; import { type ContractArtifact, type FunctionArtifact, getInitializer } from '@aztec/foundation/abi'; import { type Fr } from '@aztec/foundation/fields'; -import { createDebugLogger } from '@aztec/foundation/log'; import { type ContractInstanceWithAddress } from '@aztec/types/contracts'; import { type Wallet } from '../account/index.js'; @@ -53,8 +52,6 @@ export class DeployMethod extends Bas /** Cached call to request() */ private functionCalls?: ExecutionRequestInit; - private log = createDebugLogger('aztec:js:deploy_method'); - constructor( private publicKeysHash: Fr, wallet: Wallet, @@ -119,6 +116,12 @@ export class DeployMethod extends Bas }; if (options.estimateGas) { + // Why do we call this seemingly idempotent getter method here, without using its return value? + // This call pushes a capsule required for contract class registration under the hood. And since + // capsules are a stack, when we run the simulation for estimating gas, we consume the capsule + // that was meant for the actual call. So we need to push it again here. Hopefully this design + // will go away soon. + await this.getDeploymentFunctionCalls(options); request.fee = await this.getFeeOptionsFromEstimatedGas(request); } diff --git a/yarn-project/aztec.js/src/fee/no_fee_payment_method.ts b/yarn-project/aztec.js/src/fee/no_fee_payment_method.ts new file mode 100644 index 00000000000..e9ed5809bc7 --- /dev/null +++ b/yarn-project/aztec.js/src/fee/no_fee_payment_method.ts @@ -0,0 +1,23 @@ +import { type FunctionCall } from '@aztec/circuit-types'; +import { AztecAddress } from '@aztec/circuits.js'; + +import { type FeePaymentMethod } from './fee_payment_method.js'; + +/** + * Does not pay fees. Will work until we enforce fee payment for all txs. + */ +export class NoFeePaymentMethod implements FeePaymentMethod { + constructor() {} + + getAsset() { + return AztecAddress.ZERO; + } + + getFunctionCalls(): Promise { + return Promise.resolve([]); + } + + getFeePayer(): Promise { + return Promise.resolve(AztecAddress.ZERO); + } +} diff --git a/yarn-project/aztec.js/src/index.ts b/yarn-project/aztec.js/src/index.ts index fd58326f366..920e20d51bf 100644 --- a/yarn-project/aztec.js/src/index.ts +++ b/yarn-project/aztec.js/src/index.ts @@ -24,13 +24,14 @@ export { Contract, ContractBase, ContractFunctionInteraction, - ContractMethod, - ContractNotes, - ContractStorageLayout, + type ContractMethod, + type ContractNotes, + type ContractStorageLayout, DeployMethod, DeploySentTx, + type SendMethodOptions, SentTx, - WaitOpts, + type WaitOpts, } from './contract/index.js'; export { ContractDeployer } from './deployment/index.js'; diff --git a/yarn-project/aztec/src/bin/index.ts b/yarn-project/aztec/src/bin/index.ts index 70a34456c74..4cdc27c1798 100644 --- a/yarn-project/aztec/src/bin/index.ts +++ b/yarn-project/aztec/src/bin/index.ts @@ -63,6 +63,6 @@ async function main() { main().catch(err => { debugLogger.error(`Error in command execution`); - debugLogger.error(err); + debugLogger.error(err + '\n' + err.stack); process.exit(1); }); diff --git a/yarn-project/aztec/src/cli/cmds/start_prover.ts b/yarn-project/aztec/src/cli/cmds/start_prover.ts index 7c39fe6e16a..d5bd721961a 100644 --- a/yarn-project/aztec/src/cli/cmds/start_prover.ts +++ b/yarn-project/aztec/src/cli/cmds/start_prover.ts @@ -1,20 +1,26 @@ import { type ProvingJobSource } from '@aztec/circuit-types'; +import { type ProverClientConfig, getProverEnvVars } from '@aztec/prover-client'; import { ProverPool, createProvingJobSourceClient } from '@aztec/prover-client/prover-pool'; import { tmpdir } from 'node:os'; import { type ServiceStarter, parseModuleOptions } from '../util.js'; -type ProverOptions = Partial<{ - proverUrl: string; - agents: string; - acvmBinaryPath?: string; - bbBinaryPath?: string; - simulate?: string; -}>; +type ProverOptions = ProverClientConfig & + Partial<{ + proverUrl: string; + agents: string; + acvmBinaryPath?: string; + bbBinaryPath?: string; + simulate?: string; + }>; export const startProver: ServiceStarter = async (options, signalHandlers, logger) => { - const proverOptions: ProverOptions = parseModuleOptions(options.prover); + const proverOptions: ProverOptions = { + proverUrl: process.env.PROVER_URL, + ...getProverEnvVars(), + ...parseModuleOptions(options.prover), + }; let source: ProvingJobSource; if (typeof proverOptions.proverUrl === 'string') { @@ -24,7 +30,7 @@ export const startProver: ServiceStarter = async (options, signalHandlers, logge throw new Error('Starting prover without an orchestrator is not supported'); } - const agentCount = proverOptions.agents ? parseInt(proverOptions.agents, 10) : 1; + const agentCount = proverOptions.agents ? parseInt(proverOptions.agents, 10) : proverOptions.proverAgents; if (agentCount === 0 || !Number.isSafeInteger(agentCount)) { throw new Error('Cannot start prover without agents'); } diff --git a/yarn-project/aztec/src/cli/texts.ts b/yarn-project/aztec/src/cli/texts.ts index 7d1edcb3970..f4e4dd81eb0 100644 --- a/yarn-project/aztec/src/cli/texts.ts +++ b/yarn-project/aztec/src/cli/texts.ts @@ -4,7 +4,9 @@ const contractAddresses = 'registryAddress:REGISTRY_CONTRACT_ADDRESS - string - The deployed L1 registry contract address.\n' + 'inboxAddress:INBOX_CONTRACT_ADDRESS - string - The deployed L1 inbox contract address.\n' + 'outboxAddress:OUTBOX_CONTRACT_ADDRESS - string - The deployed L1 outbox contract address.\n' + - 'availabilityOracleAddress:AVAILABILITY_ORACLE_CONTRACT_ADDRESS - string - The deployed L1 availability oracle contract address.\n'; + 'availabilityOracleAddress:AVAILABILITY_ORACLE_CONTRACT_ADDRESS - string - The deployed L1 availability oracle contract address.\n' + + 'gasTokenAddress:GAS_TOKEN_CONTRACT_ADDRESS - string - The deployed L1 gas token contract address.\n' + + 'gasPortalAddress:GAS_PORTAL_CONTRACT_ADDRESS - string - The deployed L1 gas portal contract address.\n'; const p2pOptions = 'p2pBlockCheckIntervalMS:P2P_BLOCK_CHECK_INTERVAL_MS - number - The frequency in which to check for blocks. Default: 100\n' + 'p2pPeerCheckIntervalMS:P2P_PEER_CHECK_INTERVAL_MS - number - The frequency in which to check for peers. Default: 1000\n' + diff --git a/yarn-project/aztec-node/terraform/main.tf b/yarn-project/aztec/terraform/node/main.tf similarity index 51% rename from yarn-project/aztec-node/terraform/main.tf rename to yarn-project/aztec/terraform/node/main.tf index aa0f09624ed..5e1c6cc006b 100644 --- a/yarn-project/aztec-node/terraform/main.tf +++ b/yarn-project/aztec/terraform/node/main.tf @@ -55,14 +55,11 @@ data "terraform_remote_state" "l1_contracts" { # Compute local variables locals { publisher_private_keys = [var.SEQ_1_PUBLISHER_PRIVATE_KEY, var.SEQ_2_PUBLISHER_PRIVATE_KEY] - bootnode_ids = [var.BOOTNODE_1_PEER_ID, var.BOOTNODE_2_PEER_ID] node_p2p_private_keys = [var.NODE_1_PRIVATE_KEY, var.NODE_2_PRIVATE_KEY] node_count = length(local.publisher_private_keys) - bootnodes = [for i in range(0, local.node_count) : - "/dns4/${var.DEPLOY_TAG}-p2p-bootstrap-${i + 1}.local/tcp/${var.BOOTNODE_LISTEN_PORT + i}/p2p/${local.bootnode_ids[i]}" - ] - combined_bootnodes = join(",", local.bootnodes) - data_dir = "/usr/src/yarn-project/aztec/data" + data_dir = "/usr/src/yarn-project/aztec/data" + agents_per_sequencer = var.AGENTS_PER_SEQUENCER + total_agents = local.node_count * local.agents_per_sequencer } resource "aws_cloudwatch_log_group" "aztec-node-log-group" { @@ -163,7 +160,12 @@ resource "aws_ecs_task_definition" "aztec-node" { "containerPort": 80 }, { - "containerPort": ${var.NODE_TCP_PORT + count.index} + "containerPort": ${var.NODE_P2P_TCP_PORT + count.index}, + "protocol": "tcp" + }, + { + "containerPort": ${var.NODE_P2P_UDP_PORT + count.index}, + "protocol": "udp" } ], "environment": [ @@ -185,7 +187,7 @@ resource "aws_ecs_task_definition" "aztec-node" { }, { "name": "DEBUG", - "value": "aztec:*" + "value": "aztec:*,-json-rpc:json_proxy:*,-aztec:avm_simulator:*,discv5:*,libp2p:*" }, { "name": "ETHEREUM_HOST", @@ -218,31 +220,31 @@ resource "aws_ecs_task_definition" "aztec-node" { { "name": "ROLLUP_CONTRACT_ADDRESS", - "value": "${data.terraform_remote_state.l1_contracts.outputs.rollup_contract_address}" + "value": "${var.ROLLUP_CONTRACT_ADDRESS}" }, { "name": "INBOX_CONTRACT_ADDRESS", - "value": "${data.terraform_remote_state.l1_contracts.outputs.inbox_contract_address}" + "value": "${var.INBOX_CONTRACT_ADDRESS}" }, { "name": "OUTBOX_CONTRACT_ADDRESS", - "value": "${data.terraform_remote_state.l1_contracts.outputs.outbox_contract_address}" + "value": "${var.OUTBOX_CONTRACT_ADDRESS}" }, { "name": "REGISTRY_CONTRACT_ADDRESS", - "value": "${data.terraform_remote_state.l1_contracts.outputs.registry_contract_address}" + "value": "${var.REGISTRY_CONTRACT_ADDRESS}" }, { "name": "AVAILABILITY_ORACLE_CONTRACT_ADDRESS", - "value": "${data.terraform_remote_state.l1_contracts.outputs.availability_oracle_contract_address}" + "value": "${var.AVAILABILITY_ORACLE_CONTRACT_ADDRESS}" }, { "name": "GAS_TOKEN_CONTRACT_ADDRESS", - "value": "${data.terraform_remote_state.l1_contracts.outputs.gas_token_contract_address}" + "value": "${var.GAS_TOKEN_CONTRACT_ADDRESS}" }, { "name": "GAS_PORTAL_CONTRACT_ADDRESS", - "value": "${data.terraform_remote_state.l1_contracts.outputs.gas_portal_contract_address}" + "value": "${var.GAS_PORTAL_CONTRACT_ADDRESS}" }, { "name": "API_KEY", @@ -254,7 +256,11 @@ resource "aws_ecs_task_definition" "aztec-node" { }, { "name": "P2P_TCP_LISTEN_PORT", - "value": "${var.NODE_TCP_PORT + count.index}" + "value": "${var.NODE_P2P_TCP_PORT + count.index}" + }, + { + "name": "P2P_UDP_LISTEN_PORT", + "value": "${var.NODE_P2P_UDP_PORT + count.index}" }, { "name": "P2P_TCP_LISTEN_IP", @@ -262,19 +268,19 @@ resource "aws_ecs_task_definition" "aztec-node" { }, { "name": "P2P_ANNOUNCE_HOSTNAME", - "value": "/dns4/${data.terraform_remote_state.aztec-network_iac.outputs.nlb_dns}" + "value": "/ip4/${data.terraform_remote_state.aztec-network_iac.outputs.p2p_eip}" }, { "name": "P2P_ANNOUNCE_PORT", - "value": "${var.NODE_TCP_PORT + count.index}" + "value": "${var.NODE_P2P_TCP_PORT + count.index}" }, { "name": "BOOTSTRAP_NODES", - "value": "${local.combined_bootnodes}" + "value": "enr:-JO4QNvVz7yYHQ4nzZQ7JCng9LOQkDnFqeLntDEfrAAGOS_eMFWOE4ZlyjYKb3J-yCGu8xoXXEUnUqI8iTJj1K43KH0EjWF6dGVjX25ldHdvcmsBgmlkgnY0gmlwhA0pYm6Jc2VjcDI1NmsxoQLzGvsxdzM9VhPjrMnxLmMxvrEcvSg-QZq7PWXDnnIy1YN1ZHCCnjQ" }, { "name": "P2P_ENABLED", - "value": "true" + "value": "${var.P2P_ENABLED}" }, { "name": "CHAIN_ID", @@ -300,6 +306,30 @@ resource "aws_ecs_task_definition" "aztec-node" { "name": "P2P_PEER_CHECK_INTERVAL_MS", "value": "2000" }, + { + "name": "ACVM_WORKING_DIRECTORY", + "value": "/usr/src/acvm" + }, + { + "name": "BB_WORKING_DIRECTORY", + "value": "/usr/src/bb" + }, + { + "name": "ACVM_BINARY_PATH", + "value": "/usr/src/noir/noir-repo/target/release/acvm" + }, + { + "name": "BB_BINARY_PATH", + "value": "/usr/src/barretenberg/cpp/build/bin/bb" + }, + { + "name": "PROVER_AGENTS", + "value": "0" + }, + { + "name": "PROVER_REAL_PROOFS", + "value": "${var.PROVING_ENABLED}" + } ], "mountPoints": [ { @@ -333,24 +363,29 @@ resource "aws_ecs_service" "aztec-node" { network_configuration { subnets = [ - data.terraform_remote_state.setup_iac.outputs.subnet_az1_private_id, - data.terraform_remote_state.setup_iac.outputs.subnet_az2_private_id + data.terraform_remote_state.setup_iac.outputs.subnet_az1_private_id ] security_groups = [data.terraform_remote_state.aztec-network_iac.outputs.p2p_security_group_id, data.terraform_remote_state.setup_iac.outputs.security_group_private_id] } load_balancer { - target_group_arn = aws_alb_target_group.aztec-node[count.index].arn + target_group_arn = aws_alb_target_group.aztec-node-http[count.index].arn container_name = "${var.DEPLOY_TAG}-aztec-node-${count.index + 1}" container_port = 80 } - load_balancer { - target_group_arn = aws_lb_target_group.aztec-node-target-group[count.index].arn - container_name = "${var.DEPLOY_TAG}-aztec-node-${count.index + 1}" - container_port = var.NODE_TCP_PORT + count.index - } + # load_balancer { + # target_group_arn = aws_lb_target_group.aztec-node-tcp[count.index].arn + # container_name = "${var.DEPLOY_TAG}-aztec-node-${count.index + 1}" + # container_port = var.NODE_P2P_TCP_PORT + count.index + # } + + # load_balancer { + # target_group_arn = aws_lb_target_group.aztec-node-udp[count.index].arn + # container_name = "${var.DEPLOY_TAG}-aztec-node-${count.index + 1}" + # container_port = var.NODE_P2P_UDP_PORT + count.index + # } service_registries { registry_arn = aws_service_discovery_service.aztec-node[count.index].arn @@ -362,7 +397,7 @@ resource "aws_ecs_service" "aztec-node" { } # Configure ALB to route /aztec-node to server. -resource "aws_alb_target_group" "aztec-node" { +resource "aws_alb_target_group" "aztec-node-http" { count = local.node_count name = "${var.DEPLOY_TAG}-node-${count.index + 1}-http-target" port = 80 @@ -392,7 +427,7 @@ resource "aws_lb_listener_rule" "api" { action { type = "forward" - target_group_arn = aws_alb_target_group.aztec-node[count.index].arn + target_group_arn = aws_alb_target_group.aztec-node-http[count.index].arn } condition { @@ -402,10 +437,10 @@ resource "aws_lb_listener_rule" "api" { } } -resource "aws_lb_target_group" "aztec-node-target-group" { +resource "aws_lb_target_group" "aztec-node-tcp" { count = local.node_count - name = "${var.DEPLOY_TAG}-node-${count.index + 1}-p2p-target" - port = var.NODE_TCP_PORT + count.index + name = "${var.DEPLOY_TAG}-node-${count.index + 1}-p2p-tcp-target" + port = var.NODE_P2P_TCP_PORT + count.index protocol = "TCP" target_type = "ip" vpc_id = data.terraform_remote_state.setup_iac.outputs.vpc_id @@ -415,24 +450,34 @@ resource "aws_lb_target_group" "aztec-node-target-group" { interval = 10 healthy_threshold = 2 unhealthy_threshold = 2 - port = var.NODE_TCP_PORT + count.index + port = var.NODE_P2P_TCP_PORT + count.index } } -resource "aws_security_group_rule" "allow-node-tcp" { +resource "aws_security_group_rule" "allow-node-tcp-in" { count = local.node_count type = "ingress" - from_port = var.NODE_TCP_PORT + count.index - to_port = var.NODE_TCP_PORT + count.index + from_port = var.NODE_P2P_TCP_PORT + count.index + to_port = var.NODE_P2P_TCP_PORT + count.index protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] security_group_id = data.terraform_remote_state.aztec-network_iac.outputs.p2p_security_group_id } +# resource "aws_security_group_rule" "allow-node-tcp-out" { +# count = local.node_count +# type = "egress" +# from_port = var.NODE_P2P_TCP_PORT + count.index +# to_port = var.NODE_P2P_TCP_PORT + count.index +# protocol = "tcp" +# cidr_blocks = ["0.0.0.0/0"] +# security_group_id = data.terraform_remote_state.aztec-network_iac.outputs.p2p_security_group_id +# } + resource "aws_lb_listener" "aztec-node-tcp-listener" { count = local.node_count load_balancer_arn = data.terraform_remote_state.aztec-network_iac.outputs.nlb_arn - port = var.NODE_TCP_PORT + count.index + port = var.NODE_P2P_TCP_PORT + count.index protocol = "TCP" tags = { @@ -441,6 +486,209 @@ resource "aws_lb_listener" "aztec-node-tcp-listener" { default_action { type = "forward" - target_group_arn = aws_lb_target_group.aztec-node-target-group[count.index].arn + target_group_arn = aws_lb_target_group.aztec-node-tcp[count.index].arn + } +} + + +resource "aws_lb_target_group" "aztec-node-udp" { + count = local.node_count + name = "${var.DEPLOY_TAG}-node-${count.index + 1}-p2p-udp-target" + port = var.NODE_P2P_UDP_PORT + count.index + protocol = "UDP" + target_type = "ip" + vpc_id = data.terraform_remote_state.setup_iac.outputs.vpc_id + + health_check { + protocol = "TCP" + interval = 10 + healthy_threshold = 2 + unhealthy_threshold = 2 + port = var.NODE_P2P_TCP_PORT + count.index + } +} + +resource "aws_security_group_rule" "allow-node-udp-in" { + type = "ingress" + from_port = var.NODE_P2P_UDP_PORT + to_port = var.NODE_P2P_UDP_PORT + 100 + protocol = "udp" + cidr_blocks = ["0.0.0.0/0"] + security_group_id = data.terraform_remote_state.aztec-network_iac.outputs.p2p_security_group_id +} + +resource "aws_security_group_rule" "allow-node-udp-out" { + type = "egress" + from_port = var.NODE_P2P_UDP_PORT + to_port = var.NODE_P2P_UDP_PORT + 100 + protocol = "udp" + cidr_blocks = ["0.0.0.0/0"] + security_group_id = data.terraform_remote_state.aztec-network_iac.outputs.p2p_security_group_id +} + +resource "aws_lb_listener" "aztec-node-udp-listener" { + count = local.node_count + load_balancer_arn = data.terraform_remote_state.aztec-network_iac.outputs.nlb_arn + port = var.NODE_P2P_UDP_PORT + count.index + protocol = "UDP" + + tags = { + name = "aztec-node-${count.index}-udp-listener" + } + + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.aztec-node-udp[count.index].arn + } +} + + + + + + +// Configuration for proving agents + +resource "aws_cloudwatch_log_group" "aztec-proving-agent-log-group" { + count = local.total_agents + name = "/fargate/service/${var.DEPLOY_TAG}/aztec-proving-agent-${floor(count.index / local.agents_per_sequencer) + 1}-${(count.index % local.agents_per_sequencer) + 1}" + retention_in_days = 14 +} + +resource "aws_service_discovery_service" "aztec-proving-agent" { + count = local.total_agents + name = "${var.DEPLOY_TAG}-aztec-proving-agent-${floor(count.index / local.agents_per_sequencer) + 1}-${(count.index % local.agents_per_sequencer) + 1}" + + health_check_custom_config { + failure_threshold = 1 + } + + dns_config { + namespace_id = data.terraform_remote_state.setup_iac.outputs.local_service_discovery_id + + dns_records { + ttl = 60 + type = "A" + } + + dns_records { + ttl = 60 + type = "SRV" + } + + routing_policy = "MULTIVALUE" + } + + # Terraform just fails if this resource changes and you have registered instances. + provisioner "local-exec" { + when = destroy + command = "${path.module}/servicediscovery-drain.sh ${self.id}" + } +} + +# Define task definitions for each node. +resource "aws_ecs_task_definition" "aztec-proving-agent" { + count = local.total_agents + family = "${var.DEPLOY_TAG}-aztec-proving-agent-${floor(count.index / local.agents_per_sequencer) + 1}-${(count.index % local.agents_per_sequencer) + 1}" + requires_compatibilities = ["FARGATE"] + network_mode = "awsvpc" + cpu = "16384" + memory = "65536" + execution_role_arn = data.terraform_remote_state.setup_iac.outputs.ecs_task_execution_role_arn + task_role_arn = data.terraform_remote_state.aztec2_iac.outputs.cloudwatch_logging_ecs_role_arn + + container_definitions = <>(), private logger: DebugLogger, ) {} public static async new( - config: BBProverConfig, + config: BBConfig, initialCircuits: ProtocolArtifact[] = [], logger = createDebugLogger('aztec:bb-verifier'), ) { diff --git a/yarn-project/circuits.js/src/structs/parity/root_parity_input.ts b/yarn-project/circuits.js/src/structs/parity/root_parity_input.ts index bcdafe5aa2b..1496d19b179 100644 --- a/yarn-project/circuits.js/src/structs/parity/root_parity_input.ts +++ b/yarn-project/circuits.js/src/structs/parity/root_parity_input.ts @@ -19,6 +19,10 @@ export class RootParityInput { return serializeToBuffer(...RootParityInput.getFields(this)); } + toString() { + return this.toBuffer().toString('hex'); + } + static from( fields: FieldsOf>, ): RootParityInput { @@ -29,12 +33,22 @@ export class RootParityInput { return [fields.proof, fields.verificationKey, fields.publicInputs] as const; } - static fromBuffer(buffer: Buffer | BufferReader, size: PROOF_LENGTH) { + static fromBuffer( + buffer: Buffer | BufferReader, + expectedSize?: PROOF_LENGTH, + ): RootParityInput { const reader = BufferReader.asReader(buffer); return new RootParityInput( - RecursiveProof.fromBuffer(reader, size), + RecursiveProof.fromBuffer(reader, expectedSize), reader.readObject(VerificationKeyAsFields), reader.readObject(ParityPublicInputs), ); } + + static fromString( + str: string, + expectedSize?: PROOF_LENGTH, + ): RootParityInput { + return RootParityInput.fromBuffer(Buffer.from(str, 'hex'), expectedSize); + } } diff --git a/yarn-project/circuits.js/src/structs/recursive_proof.ts b/yarn-project/circuits.js/src/structs/recursive_proof.ts index 12358a67f52..4f94adbe212 100644 --- a/yarn-project/circuits.js/src/structs/recursive_proof.ts +++ b/yarn-project/circuits.js/src/structs/recursive_proof.ts @@ -38,9 +38,16 @@ export class RecursiveProof { * @param buffer - A Buffer or BufferReader containing the length-encoded proof data. * @returns A Proof instance containing the decoded proof data. */ - static fromBuffer(buffer: Buffer | BufferReader, size: N): RecursiveProof { + static fromBuffer( + buffer: Buffer | BufferReader, + expectedSize?: N, + ): RecursiveProof { const reader = BufferReader.asReader(buffer); - return new RecursiveProof(reader.readArray(size, Fr), Proof.fromBuffer(reader), reader.readBoolean()); + const size = reader.readNumber(); + if (typeof expectedSize === 'number' && expectedSize !== size) { + throw new Error(`Expected proof length ${expectedSize}, got ${size}`); + } + return new RecursiveProof(reader.readArray(size, Fr) as any, Proof.fromBuffer(reader), reader.readBoolean()); } /** @@ -50,7 +57,7 @@ export class RecursiveProof { * @returns A Buffer containing the serialized proof data in custom format. */ public toBuffer() { - return serializeToBuffer(this.proof, this.binaryProof, this.fieldsValid); + return serializeToBuffer(this.proof.length, this.proof, this.binaryProof, this.fieldsValid); } /** @@ -66,8 +73,11 @@ export class RecursiveProof { * @param str - A hex string to deserialize from. * @returns - A new Proof instance. */ - static fromString(str: string, size: N) { - return RecursiveProof.fromBuffer(Buffer.from(str, 'hex'), size); + static fromString( + str: string, + expectedSize?: N, + ): RecursiveProof { + return RecursiveProof.fromBuffer(Buffer.from(str, 'hex'), expectedSize); } } diff --git a/yarn-project/circuits.js/src/structs/verification_key.ts b/yarn-project/circuits.js/src/structs/verification_key.ts index e469a7aee33..37f723be33d 100644 --- a/yarn-project/circuits.js/src/structs/verification_key.ts +++ b/yarn-project/circuits.js/src/structs/verification_key.ts @@ -235,9 +235,10 @@ export class VerificationKeyData { return serializeToBuffer(this.keyAsFields, this.keyAsBytes.length, this.keyAsBytes); } - /** - @@ -126,28 +97,14 @@ export class VerificationKeyAsFields { - */ + toString() { + return this.toBuffer().toString('hex'); + } + static fromBuffer(buffer: Buffer | BufferReader): VerificationKeyData { const reader = BufferReader.asReader(buffer); const verificationKeyAsFields = reader.readObject(VerificationKeyAsFields); @@ -246,6 +247,10 @@ export class VerificationKeyData { return new VerificationKeyData(verificationKeyAsFields, bytes); } + static fromString(str: string): VerificationKeyData { + return VerificationKeyData.fromBuffer(Buffer.from(str, 'hex')); + } + public clone() { return VerificationKeyData.fromBuffer(this.toBuffer()); } diff --git a/yarn-project/circuits.js/src/types/public_keys.ts b/yarn-project/circuits.js/src/types/public_keys.ts index babb3c8ddbc..4086261b2e6 100644 --- a/yarn-project/circuits.js/src/types/public_keys.ts +++ b/yarn-project/circuits.js/src/types/public_keys.ts @@ -117,4 +117,12 @@ export class PublicKeys { reader.readObject(Point), ); } + + toString() { + return this.toBuffer().toString('hex'); + } + + static fromString(keys: string) { + return PublicKeys.fromBuffer(Buffer.from(keys, 'hex')); + } } diff --git a/yarn-project/cli/.eslintrc.cjs b/yarn-project/cli/.eslintrc.cjs new file mode 100644 index 00000000000..e659927475c --- /dev/null +++ b/yarn-project/cli/.eslintrc.cjs @@ -0,0 +1 @@ +module.exports = require('@aztec/foundation/eslint'); diff --git a/yarn-project/cli/CHANGELOG.md b/yarn-project/cli/CHANGELOG.md new file mode 100644 index 00000000000..f9ef9369f54 --- /dev/null +++ b/yarn-project/cli/CHANGELOG.md @@ -0,0 +1,171 @@ +# Changelog + +## [0.32.0](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.31.0...aztec-cli-v0.32.0) (2024-03-27) + + +### Miscellaneous + +* **aztec-cli:** Synchronize aztec-packages versions + +## [0.31.0](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.30.1...aztec-cli-v0.31.0) (2024-03-26) + + +### Features + +* Capture broadcasted functions in node ([#5353](https://github.com/AztecProtocol/aztec-packages/issues/5353)) ([bc05db2](https://github.com/AztecProtocol/aztec-packages/commit/bc05db26c864c9a9dae43f149814e082cdcfd7df)) + + +### Bug Fixes + +* **cli:** Support initializers not named constructor in cli ([#5397](https://github.com/AztecProtocol/aztec-packages/issues/5397)) ([85f14c5](https://github.com/AztecProtocol/aztec-packages/commit/85f14c5dc84c46910b8de498472959fa561d593c)) + +## [0.30.1](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.30.0...aztec-cli-v0.30.1) (2024-03-20) + + +### Miscellaneous + +* **aztec-cli:** Synchronize aztec-packages versions + +## [0.30.0](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.29.0...aztec-cli-v0.30.0) (2024-03-19) + + +### Features + +* Allow registering contract classes in PXE ([#5291](https://github.com/AztecProtocol/aztec-packages/issues/5291)) ([b811207](https://github.com/AztecProtocol/aztec-packages/commit/b811207bad691f519b31a6391967b9215a9e17d3)), closes [#4055](https://github.com/AztecProtocol/aztec-packages/issues/4055) + + +### Miscellaneous + +* Add gas portal to l1 contract addresses ([#5265](https://github.com/AztecProtocol/aztec-packages/issues/5265)) ([640c89a](https://github.com/AztecProtocol/aztec-packages/commit/640c89a04d7b780795d40e239be3b3db73a16923)), closes [#5022](https://github.com/AztecProtocol/aztec-packages/issues/5022) + +## [0.29.0](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.28.1...aztec-cli-v0.29.0) (2024-03-18) + + +### Features + +* Use deployer in address computation ([#5201](https://github.com/AztecProtocol/aztec-packages/issues/5201)) ([258ff4a](https://github.com/AztecProtocol/aztec-packages/commit/258ff4a00208be8695e2e59aecc14d6a92eaac1c)) + + +### Miscellaneous + +* Delete ContractData ([#5258](https://github.com/AztecProtocol/aztec-packages/issues/5258)) ([e516f9b](https://github.com/AztecProtocol/aztec-packages/commit/e516f9b94d1fbdc126a9d0d7d79c571d61914980)) +* Delete ExtendedContractData struct ([#5248](https://github.com/AztecProtocol/aztec-packages/issues/5248)) ([8ae0c13](https://github.com/AztecProtocol/aztec-packages/commit/8ae0c13ceaf8a1f3db09d0e61f0a3781c8926ca6)) +* Removing redundant receipts check ([#5271](https://github.com/AztecProtocol/aztec-packages/issues/5271)) ([5ab07fb](https://github.com/AztecProtocol/aztec-packages/commit/5ab07fb8b395b6edbda6167845c7ea864e9395a3)) + +## [0.28.1](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.28.0...aztec-cli-v0.28.1) (2024-03-14) + + +### Miscellaneous + +* **aztec-cli:** Synchronize aztec-packages versions + +## [0.28.0](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.27.2...aztec-cli-v0.28.0) (2024-03-14) + + +### ⚠ BREAKING CHANGES + +* Support contracts with no constructor ([#5175](https://github.com/AztecProtocol/aztec-packages/issues/5175)) + +### Features + +* Support contracts with no constructor ([#5175](https://github.com/AztecProtocol/aztec-packages/issues/5175)) ([df7fa32](https://github.com/AztecProtocol/aztec-packages/commit/df7fa32f34e790231e091c38a4a6e84be5407763)) + +## [0.27.2](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.27.1...aztec-cli-v0.27.2) (2024-03-13) + + +### Miscellaneous + +* **aztec-cli:** Synchronize aztec-packages versions + +## [0.27.1](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.27.0...aztec-cli-v0.27.1) (2024-03-12) + + +### Miscellaneous + +* **aztec-cli:** Synchronize aztec-packages versions + +## [0.27.0](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.26.6...aztec-cli-v0.27.0) (2024-03-12) + + +### Miscellaneous + +* Remove old contract deployment flow ([#4970](https://github.com/AztecProtocol/aztec-packages/issues/4970)) ([6d15947](https://github.com/AztecProtocol/aztec-packages/commit/6d1594736e96cd744ea691a239fcd3a46bdade60)) + +## [0.26.6](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.26.5...aztec-cli-v0.26.6) (2024-03-08) + + +### Features + +* Show bytecode size per function in CLI inspect-contract ([#5059](https://github.com/AztecProtocol/aztec-packages/issues/5059)) ([cb9fdc6](https://github.com/AztecProtocol/aztec-packages/commit/cb9fdc6b5069ee2ab8fb1f68f369e360039fa18b)) + +## [0.26.5](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.26.4...aztec-cli-v0.26.5) (2024-03-07) + + +### Miscellaneous + +* **aztec-cli:** Synchronize aztec-packages versions + +## [0.26.4](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.26.3...aztec-cli-v0.26.4) (2024-03-06) + + +### Miscellaneous + +* **aztec-cli:** Synchronize aztec-packages versions + +## [0.26.3](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.26.2...aztec-cli-v0.26.3) (2024-03-06) + + +### Miscellaneous + +* **aztec-cli:** Synchronize aztec-packages versions + +## [0.26.2](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.26.1...aztec-cli-v0.26.2) (2024-03-06) + + +### Miscellaneous + +* **aztec-cli:** Synchronize aztec-packages versions + +## [0.26.1](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.26.0...aztec-cli-v0.26.1) (2024-03-06) + + +### Miscellaneous + +* **aztec-cli:** Synchronize aztec-packages versions + +## [0.26.0](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.25.0...aztec-cli-v0.26.0) (2024-03-05) + + +### ⚠ BREAKING CHANGES + +* Use new deployment flow in ContractDeployer ([#4497](https://github.com/AztecProtocol/aztec-packages/issues/4497)) +* move noir out of yarn-project ([#4479](https://github.com/AztecProtocol/aztec-packages/issues/4479)) +* note type ids ([#4500](https://github.com/AztecProtocol/aztec-packages/issues/4500)) +* Include contract class id in deployment info ([#4223](https://github.com/AztecProtocol/aztec-packages/issues/4223)) +* aztec binary ([#3927](https://github.com/AztecProtocol/aztec-packages/issues/3927)) + +### Features + +* **avm-transpiler:** Brillig to AVM transpiler ([#4227](https://github.com/AztecProtocol/aztec-packages/issues/4227)) ([c366c6e](https://github.com/AztecProtocol/aztec-packages/commit/c366c6e6d5c9f28a5dc92a303dcab4a23fb2d84e)) +* Aztec binary ([#3927](https://github.com/AztecProtocol/aztec-packages/issues/3927)) ([12356d9](https://github.com/AztecProtocol/aztec-packages/commit/12356d9e34994a239d5612798c1bc82fa3d26562)) +* Aztec.js API for registering a contract class ([#4469](https://github.com/AztecProtocol/aztec-packages/issues/4469)) ([d566c74](https://github.com/AztecProtocol/aztec-packages/commit/d566c74786a1ea960e9beee4599c1fdedc7ae6eb)) +* Include contract class id in deployment info ([#4223](https://github.com/AztecProtocol/aztec-packages/issues/4223)) ([0ed4126](https://github.com/AztecProtocol/aztec-packages/commit/0ed41261ae43e21f695c35ad753e07adfaaa55f9)), closes [#4054](https://github.com/AztecProtocol/aztec-packages/issues/4054) +* Moving the unbox option to npx command ([#4718](https://github.com/AztecProtocol/aztec-packages/issues/4718)) ([4c3bb92](https://github.com/AztecProtocol/aztec-packages/commit/4c3bb9294fc10ff4663275c952e277eaa7ecd647)) +* Note type ids ([#4500](https://github.com/AztecProtocol/aztec-packages/issues/4500)) ([e1da2fd](https://github.com/AztecProtocol/aztec-packages/commit/e1da2fd509c75d7886b95655d233165e087cf2ed)) +* Parallel native/wasm bb builds. Better messaging around using ci cache. ([#4766](https://github.com/AztecProtocol/aztec-packages/issues/4766)) ([a924e55](https://github.com/AztecProtocol/aztec-packages/commit/a924e55393daa89fbba3a87cf019977286104b59)) +* Use new deployment flow in ContractDeployer ([#4497](https://github.com/AztecProtocol/aztec-packages/issues/4497)) ([0702dc6](https://github.com/AztecProtocol/aztec-packages/commit/0702dc6988149258124184b85d38db930effe0e7)) + + +### Bug Fixes + +* Add new oracle contract to devnet in CI ([#4687](https://github.com/AztecProtocol/aztec-packages/issues/4687)) ([920fa10](https://github.com/AztecProtocol/aztec-packages/commit/920fa10d4d5fb476cd6d868439310452f6e8dcc5)) +* Load contract artifact from json ([#4352](https://github.com/AztecProtocol/aztec-packages/issues/4352)) ([47a0a79](https://github.com/AztecProtocol/aztec-packages/commit/47a0a79f6beaa241eafc94fcae84103488a9dcef)) + + +### Miscellaneous + +* **docs:** Fix a few links to docs ([#4260](https://github.com/AztecProtocol/aztec-packages/issues/4260)) ([1c8ea49](https://github.com/AztecProtocol/aztec-packages/commit/1c8ea497fb1d64da64cb240917a60d57bd1efef8)) +* Move noir out of yarn-project ([#4479](https://github.com/AztecProtocol/aztec-packages/issues/4479)) ([1fe674b](https://github.com/AztecProtocol/aztec-packages/commit/1fe674b046c694e1cbbbb2edaf5a855828bb5340)), closes [#4107](https://github.com/AztecProtocol/aztec-packages/issues/4107) +* Remove stubbed docs ([#4196](https://github.com/AztecProtocol/aztec-packages/issues/4196)) ([25a4bc4](https://github.com/AztecProtocol/aztec-packages/commit/25a4bc490a53304110e7e1f79e99f4c8b7639164)) +* Squash yp ypb + other build improvements. ([#4901](https://github.com/AztecProtocol/aztec-packages/issues/4901)) ([be5855c](https://github.com/AztecProtocol/aztec-packages/commit/be5855cdbd1993155bd228afbeafee2c447b46a5)) +* Updating viem ([#4783](https://github.com/AztecProtocol/aztec-packages/issues/4783)) ([23bc26a](https://github.com/AztecProtocol/aztec-packages/commit/23bc26a4859d9777c3e6dd49e351a4e6b13a989a)) diff --git a/yarn-project/cli/Dockerfile b/yarn-project/cli/Dockerfile new file mode 100644 index 00000000000..c69606b278c --- /dev/null +++ b/yarn-project/cli/Dockerfile @@ -0,0 +1,10 @@ +FROM aztecprotocol/yarn-project AS yarn-project +ENTRYPOINT ["node", "--no-warnings", "/usr/src/yarn-project/cli/dest/bin/index.js"] + +# The version has been updated in yarn-project. +# Adding COMMIT_TAG here to rebuild versioned image. +ARG COMMIT_TAG="" + +RUN mkdir /cache && chmod 777 /cache +ENV XDG_CACHE_HOME /cache +VOLUME "/cache" diff --git a/yarn-project/cli/README.md b/yarn-project/cli/README.md new file mode 100644 index 00000000000..56bf674e842 --- /dev/null +++ b/yarn-project/cli/README.md @@ -0,0 +1,450 @@ +# Aztec CLI Documentation + +The Aztec CLI `aztec-cli` 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 + +1. In your terminal, download the sandbox by running + +``` +bash -i <(curl -s install.aztec.network) +``` + +2. Verify the installation: After the installation is complete, run the following command to verify that `aztec-cli` is installed correctly: + + ```shell + aztec-cli --version + ``` + + This command will display the version number of `aztec-cli` if the installation was successful. + +## Usage + +To use `aztec-cli`, open a terminal or command prompt and run the `aztec-cli` command followed by the desired command and its options. + +Here's the basic syntax for running a command: + +```shell +aztec-cli [options] +``` + +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 `aztec-cli.` +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. +- `PXE_URL` -> `-u, --rpc-url` for commands that require a PXE +- `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 Private eXecution Environment (PXE) remotely you can do: + +```shell +export PXE_URL=http://external.site/rpc:8080 +aztec-cli deploy my_contract.json +``` + +And this will send the request to `http://external.site/rpc:8080`. + +**NOTE**: Entering an option value will override the environment variable. + +## Available Commands + +`aztec-cli` provides the following commands for interacting with Aztec: + +### deploy-l1-contracts + +Deploys all necessary Ethereum contracts for Aztec. + +Syntax: + +```shell +aztec-cli deploy-l1-contracts [rpcUrl] [options] +``` + +- `rpcUrl` (optional): URL of the Ethereum host. Chain identifiers `localhost` and `testnet` can be used. Default: `http://localhost:8545`. + +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`. + +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 +aztec-cli deploy-l1-contracts +``` + +### create-private-key + +Generates a 32-byte private key. + +Syntax: + +```shell +aztec-cli 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 +aztec-cli create-private-key +``` + +### create-account + +Creates an Aztec account that can be used for transactions. + +Syntax: + +```shell +aztec-cli 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 PXE Service. 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 +aztec-cli create-account +``` + +### deploy + +Deploys a compiled Aztec.nr contract to Aztec. + +Syntax: + +```shell +aztec-cli deploy [options] +``` + +Options: + +- `-c, --contract-artifact `: Path to the compiled Aztec.nr contract's artifact file in JSON format. You can also use one of Aztec's example contracts found in [@aztec/noir-contracts](https://www.npmjs.com/package/@aztec/noir-contracts), e.g. PrivateTokenContractArtifact. You can get a full ist of the available contracts with `aztec-cli example-contracts` +- `-a, --args ` (optional): Contract constructor arguments Default: []. +- `-u, --rpc-url `: URL of PXE Service. Default: `http://localhost:8080`. +- `-k, --public-key `: Public key of the deployer. If not provided, it will check the RPC for existing ones. + +This command deploys a compiled Aztec.nr contract to Aztec. It requires the path to the contract's artifact 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 +aztec-cli deploy -c path/to/contract.artifact.json -a ...args +``` + +With an Aztec example contract: + +```shell +aztec-cli deploy -c PrivateTokenContractArtifact -a 333 0x134567890abcdef +``` + +### check-deploy + +Checks if a contract is deployed to the specified Aztec address. + +Syntax: + +```shell +aztec-cli check-deploy [options] +``` + +Options: + +- `-ca, --contract-address
`: An Aztec address to check if the contract has been deployed to. +- `-u, --rpc-url `: URL of PXE Service. 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 +aztec-cli check-deploy -ca 0x123456789abcdef123456789abcdef12345678 +``` + +### get-tx-receipt + +Gets the receipt for the specified transaction hash. + +Syntax: + +```shell +aztec-cli get-tx-receipt [options] +``` + +- `txHash`: A transaction hash to get the receipt for. + +Options: + +- `-u, --rpc-url `: URL of PXE Service. 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 +aztec-cli get-tx-receipt 0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef12345678 +``` + +### get-contract-data + +Gets information about the Aztec contract deployed at the specified address. + +Syntax: + +```shell +aztec-cli get-contract-data [options] +``` + +- `contractAddress`: Aztec address of the contract. + +Options: + +- `-u, --rpc-url `: URL of PXE Service. Default: `http://localhost:8080`. +- `-b, --include-bytecode`: Include the contract's public function bytecode, if any. + +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. + +Example usage: + +```shell +aztec-cli get-contract-data 0x123456789abcdef123456789abcdef12345678 +``` + +### register-recipient + +Register a recipient account on the PXE (called recipient because we can only send notes to this account and not receive them via this PXE). +To read about how keys are generated and used, head to our docs [here](https://github.com/AztecProtocol/aztec-packages/blob/master/docs/docs/aztec/developer/wallet-providers/keys.md#addresses-partial-addresses-and-public-keys). + +Syntax: + +```shell +aztec-cli register-recipient [options] +``` + +Options: + +- `-a, --address `: The account's Aztec address. +- `-p, --public-key `: 'The account public key.' +- `-pa, --partial-address `: URL of PXE Service. Default: `http://localhost:8080`. + +Example usage: + +```shell +aztec-cli register-recipient -p 0x20d9d93c4a9eb2b4bdb70ead07d28d1edb74bfd78443a8c36b098b024cd26f0e0647f5dbe3619453f42eb788c2beed0294c84676425047aadac23294605c4af9 -a 0x111fdc0f6bf831ca59f05863199762d643b782699d7ce6feaae40a923baf60af -pa 0x72bf7c9537875b0af267b4a8c497927e251f5988af6e30527feb16299042ed +``` + +### get-accounts + +Gets all the Aztec accounts stored in a PXE. + +Syntax: + +```shell +aztec-cli get-accounts [options] +``` + +Options: + +- `-u, --rpc-url `: URL of PXE Service. Default: `http://localhost:8080`. + +This command retrieves and displays all the Aztec accounts available in the system. + +Example usage: + +```shell +aztec-cli get-accounts +``` + +### get-account + +Gets an account given its Aztec address. + +Syntax: + +```shell +aztec-cli get-account
[options] +``` + +- `address`: The Aztec address to get the public key for. + +Options: + +- `-u, --rpc-url `: URL of PXE Service. Default: `http://localhost:8080`. + +This command retrieves and displays the public key of an account given its Aztec address. + +Example usage: + +```shell +aztec-cli get-account 0x123456789abcdef123456789abcdef12345678 +``` + +### send + +Sends a transaction invoking a function on an Aztec contract. + +Syntax: + +```shell +aztec-cli send --args [functionArgs...] --contract-artifact --contract-address --private-key +``` + +- `functionName`: Name of the function to call. + +Options: + +- `'-a, --args [functionArgs...]` (optional): Function arguments. Default: []. +- `-c, --contract-artifact `: The compiled contract's artifact in JSON format. You can also use one of Aztec's example contracts found in (@aztec/noir-contracts)[https://www.npmjs.com/package/@aztec/noir-contracts], e.g. PrivateTokenContractArtifact. +- `-ca, --contract-address
`: Address of the contract. +- `-k, --private-key `: The sender's private key. +- `-u, --rpc-url `: URL of PXE Service. Default: `http://localhost:8080`. + +This command calls a function on an Aztec contract. It requires the contract's artifact, address, function name, and optionally, function arguments. The command executes the function call and displays the transaction details. + +Example usage: + +```shell +aztec-cli send transfer -ca 0x123456789abcdef123456789abcdef12345678 -a 100 -c path/to/artifact.json +``` + +### call + +Calls a view (read-only) function on a deployed contract. +Unlike transactions, view calls do not modify the state of the contract. + +Syntax: + +```shell +aztec-cli call -a [functionArgs...] -c -ca -f +``` + +- `functionName`: Name of the function to view. + +Options: + +- `'-a, --args [functionArgs...]` (optional): Function arguments. Default: []. +- `-c, --contract-artifact `: The compiled contract's artifact in JSON format. You can also use one of Aztec's example contracts found in (@aztec/noir-contracts)[https://www.npmjs.com/package/@aztec/noir-contracts], e.g. PrivateTokenContractArtifact. +- `-ca, --contract-address
`: Address of the contract. +- `-f, --from `: Address of the caller. If empty, first account in the Private eXecution Environment (PXE) will be used. +- `-u, --rpc-url `: URL of PXE Service. 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 artifact, address, function name, and optionally, function arguments. The command displays the result of the view function. + +Example usage: + +```shell +aztec-cli call balanceOf -c path/to/contract.artifact.json -ca 0x123456789abcdef123456789abcdef12345678 -a balanceOf 0xabcdef1234567890abcdef1234567890abcdef12 +``` + +### parse-parameter-struct + +Helper for parsing an encoded string into a contract's parameter struct. + +Syntax: + +```shell +aztec-cli parse-parameter-struct +``` + +- `encodedString`: The encoded hex string. +- `contractArtifact`: The compiled contract's artifact 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 artifact, and the name of the struct parameter. The command decodes the string and displays the struct data. + +Example usage: + +```shell +aztec-cli parse-parameter-struct 0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890 path/to/contract.artifact.json paramName +``` + +### get-logs + +Applies filter and returns the resulting unencrypted logs. +The filter is applied by doing an intersection of all its params. + +Syntax: + +```shell +aztec-cli get-logs --fromBlock +``` + +Options: + +- `-u, --rpc-url `: URL of PXE Service. Default: `http://localhost:8080`. + +This command retrieves and displays all the unencrypted logs from L2 blocks in the specified range or from a specific transaction. +Example usage: + +```shell +aztec-cli get-logs --txHash 21fef567e01f8508e30843ebcef9c5f6ff27b29d66783cfcdbd070c3a9174234 +aztec-cli get-logs --fromBlock 4 --toBlock 5 --contractAddress 0x1db5f68861c5960c37205d3d5b23466240359c115c49e45982865ea7ace69a02 +aztec-cli get-logs --fromBlock 4 --toBlock 5 --contractAddress 0x1db5f68861c5960c37205d3d5b23466240359c115c49e45982865ea7ace69a02 --selector 00000005 +``` + +Run `aztec-cli get-logs --help` for more information on the filtering options. + +### block-number + +Gets the current Aztec L2 block number. + +Syntax: + +```shell +aztec-cli block-number +``` + +Options: + +- `-u, --rpc-url `: URL of PXE Service. Default: `http://localhost:8080`. + +This command retrieves and displays the current Aztec L2 block number. + +### example-contracts + +Lists the contracts available in [@aztec/noir-contracts](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-contracts) + +Syntax: + +```shell +aztec-cli example-contracts +``` + +### get-node-info + +Gets information of an Aztec node at the specified URL. + +Syntax: + +```shell +aztec-cli get-node-info +``` + +Options: + +- `-u, --rpc-url `: URL of PXE Service. Default: `http://localhost:8080`. + +## 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/cli/aztec-cli-dest b/yarn-project/cli/aztec-cli-dest new file mode 100755 index 00000000000..d2fc2aa3694 --- /dev/null +++ b/yarn-project/cli/aztec-cli-dest @@ -0,0 +1,3 @@ +#!/bin/sh +SCRIPT_PATH=$(dirname $(realpath $0)) +node --no-warnings $SCRIPT_PATH/dest/bin/index.js $@ diff --git a/yarn-project/cli/package.json b/yarn-project/cli/package.json new file mode 100644 index 00000000000..f1b0a6a81b0 --- /dev/null +++ b/yarn-project/cli/package.json @@ -0,0 +1,99 @@ +{ + "name": "@aztec/cli", + "version": "0.32.0", + "type": "module", + "main": "./dest/index.js", + "bin": { + "aztec-cli": "./dest/bin/index.js" + }, + "typedocOptions": { + "entryPoints": [ + "./src/index.ts" + ], + "name": "Aztec CLI", + "tsconfig": "./tsconfig.json" + }, + "scripts": { + "build": "yarn clean && tsc -b", + "build:dev": "tsc -b --watch", + "clean": "rm -rf ./dest .tsbuildinfo", + "formatting": "run -T prettier --check ./src && run -T eslint ./src", + "formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src", + "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests", + "start": "yarn build && node --no-warnings ./dest/bin/index.js" + }, + "inherits": [ + "../package.common.json" + ], + "jest": { + "preset": "ts-jest/presets/default-esm", + "moduleNameMapper": { + "^(\\.{1,2}/.*)\\.[cm]?js$": "$1" + }, + "testRegex": "./src/.*\\.test\\.(js|mjs|ts)$", + "rootDir": "./src", + "extensionsToTreatAsEsm": [ + ".ts" + ], + "transform": { + "^.+\\.tsx?$": [ + "@swc/jest" + ] + }, + "reporters": [ + [ + "default", + { + "summaryThreshold": 9999 + } + ] + ] + }, + "dependencies": { + "@aztec/accounts": "workspace:^", + "@aztec/aztec.js": "workspace:^", + "@aztec/bb-prover": "workspace:^", + "@aztec/circuit-types": "workspace:^", + "@aztec/circuits.js": "workspace:^", + "@aztec/ethereum": "workspace:^", + "@aztec/foundation": "workspace:^", + "@aztec/l1-artifacts": "workspace:^", + "@aztec/noir-contracts.js": "workspace:^", + "@aztec/protocol-contracts": "workspace:^", + "@aztec/simulator": "workspace:^", + "@aztec/types": "workspace:^", + "@iarna/toml": "^2.2.5", + "@libp2p/peer-id-factory": "^3.0.4", + "commander": "^9.0.0", + "jszip": "^3.10.1", + "lodash.startcase": "^4.4.0", + "node-fetch": "^3.3.2", + "semver": "^7.5.4", + "solc": "^0.8.26", + "source-map-support": "^0.5.21", + "tslib": "^2.4.0", + "viem": "^2.7.15" + }, + "devDependencies": { + "@jest/globals": "^29.5.0", + "@types/jest": "^29.5.0", + "@types/lodash.startcase": "^4.4.7", + "@types/node": "^18.7.23", + "@types/semver": "^7.5.2", + "@types/source-map-support": "^0.5.10", + "jest": "^29.5.0", + "jest-mock-extended": "^3.0.5", + "ts-jest": "^29.1.0", + "ts-node": "^10.9.1", + "typescript": "^5.0.4" + }, + "files": [ + "dest", + "src", + "!*.test.*" + ], + "types": "./dest/index.d.ts", + "engines": { + "node": ">=18" + } +} diff --git a/yarn-project/cli/src/bin/index.ts b/yarn-project/cli/src/bin/index.ts new file mode 100644 index 00000000000..0d79e796d50 --- /dev/null +++ b/yarn-project/cli/src/bin/index.ts @@ -0,0 +1,24 @@ +#!/usr/bin/env -S node --no-warnings +import { createConsoleLogger, createDebugLogger } from '@aztec/foundation/log'; + +import 'source-map-support/register.js'; + +import { getProgram } from '../index.js'; + +const debugLogger = createDebugLogger('aztec:cli-client'); +const log = createConsoleLogger(); + +/** CLI main entrypoint */ +async function main() { + process.once('SIGINT', () => process.exit(0)); + process.once('SIGTERM', () => process.exit(0)); + + const program = getProgram(log, debugLogger); + await program.parseAsync(process.argv); +} + +main().catch(err => { + log(`Error in command execution`); + log(err); + process.exit(1); +}); diff --git a/yarn-project/cli/src/client.test.ts b/yarn-project/cli/src/client.test.ts new file mode 100644 index 00000000000..ad21f399256 --- /dev/null +++ b/yarn-project/cli/src/client.test.ts @@ -0,0 +1,31 @@ +import { type NodeInfo } from '@aztec/aztec.js'; +import { type PXE } from '@aztec/circuit-types'; + +import { type MockProxy, mock } from 'jest-mock-extended'; + +import { checkServerVersion } from './client.js'; + +describe('client', () => { + describe('checkServerVersion', () => { + let pxe: MockProxy; + + beforeEach(() => { + pxe = mock(); + }); + + it('checks versions match', async () => { + pxe.getNodeInfo.mockResolvedValue({ nodeVersion: '0.1.0-alpha47' } as NodeInfo); + await checkServerVersion(pxe, '0.1.0-alpha47'); + }); + + it('reports mismatch on older pxe version', async () => { + pxe.getNodeInfo.mockResolvedValue({ nodeVersion: '0.1.0-alpha47' } as NodeInfo); + await expect(checkServerVersion(pxe, '0.1.0-alpha48')).rejects.toThrow(/is older than the expected by this CLI/); + }); + + it('reports mismatch on newer pxe version', async () => { + pxe.getNodeInfo.mockResolvedValue({ nodeVersion: '0.1.0-alpha48' } as NodeInfo); + await expect(checkServerVersion(pxe, '0.1.0-alpha47')).rejects.toThrow(/is newer than the expected by this CLI/); + }); + }); +}); diff --git a/yarn-project/cli/src/client.ts b/yarn-project/cli/src/client.ts new file mode 100644 index 00000000000..8b3d55c37d9 --- /dev/null +++ b/yarn-project/cli/src/client.ts @@ -0,0 +1,68 @@ +import { type PXE, createPXEClient } from '@aztec/aztec.js'; +import { type DebugLogger } from '@aztec/foundation/log'; +import { fileURLToPath } from '@aztec/foundation/url'; + +import { readFileSync } from 'fs'; +import { dirname, resolve } from 'path'; +import { gtr, ltr, satisfies, valid } from 'semver'; + +/** + * Creates a PXE client with a given set of retries on non-server errors. + * Checks that PXE matches the expected version, and warns if not. + * @param rpcUrl - URL of the RPC server wrapping the PXE. + * @param logger - Debug logger to warn version incompatibilities. + * @returns A PXE client. + */ +export async function createCompatibleClient(rpcUrl: string, logger: DebugLogger) { + const pxe = createPXEClient(rpcUrl); + const packageJsonPath = resolve(dirname(fileURLToPath(import.meta.url)), '../package.json'); + const packageJsonContents = JSON.parse(readFileSync(packageJsonPath).toString()); + const expectedVersionRange = packageJsonContents.version; + + try { + await checkServerVersion(pxe, expectedVersionRange); + } catch (err) { + if (err instanceof VersionMismatchError) { + logger.warn(err.message); + } else { + throw err; + } + } + + return pxe; +} + +/** Mismatch between server and client versions. */ +class VersionMismatchError extends Error {} + +/** + * Checks that Private eXecution Environment (PXE) version matches the expected one by this CLI. Throws if not. + * @param pxe - PXE client. + * @param expectedVersionRange - Expected version by CLI. + */ +export async function checkServerVersion(pxe: PXE, expectedVersionRange: string) { + const serverName = 'Aztec Node'; + const { nodeVersion } = await pxe.getNodeInfo(); + if (!nodeVersion) { + throw new VersionMismatchError(`Couldn't determine ${serverName} version. You may run into issues.`); + } + if (!nodeVersion || !valid(nodeVersion)) { + throw new VersionMismatchError( + `Missing or invalid version identifier for ${serverName} (${nodeVersion ?? 'empty'}).`, + ); + } else if (!satisfies(nodeVersion, expectedVersionRange)) { + if (gtr(nodeVersion, expectedVersionRange)) { + throw new VersionMismatchError( + `${serverName} is running version ${nodeVersion} which is newer than the expected by this CLI (${expectedVersionRange}). Consider upgrading your CLI to a newer version.`, + ); + } else if (ltr(nodeVersion, expectedVersionRange)) { + throw new VersionMismatchError( + `${serverName} is running version ${nodeVersion} which is older than the expected by this CLI (${expectedVersionRange}). Consider upgrading your ${serverName} to a newer version.`, + ); + } else { + throw new VersionMismatchError( + `${serverName} is running version ${nodeVersion} which does not match the expected by this CLI (${expectedVersionRange}).`, + ); + } + } +} diff --git a/yarn-project/cli/src/cmds/add_contract.ts b/yarn-project/cli/src/cmds/add_contract.ts new file mode 100644 index 00000000000..5f13f5044a3 --- /dev/null +++ b/yarn-project/cli/src/cmds/add_contract.ts @@ -0,0 +1,39 @@ +import { AztecAddress, type ContractInstanceWithAddress, Fr, getContractClassFromArtifact } from '@aztec/aztec.js'; +import { type PublicKeys } from '@aztec/circuits.js'; +import { computeContractAddressFromInstance } from '@aztec/circuits.js/contract'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +import { createCompatibleClient } from '../client.js'; +import { getContractArtifact } from '../utils.js'; + +export async function addContract( + rpcUrl: string, + contractArtifactPath: string, + address: AztecAddress, + initializationHash: Fr, + salt: Fr, + publicKeys: PublicKeys, + deployer: AztecAddress | undefined, + debugLogger: DebugLogger, + log: LogFn, +) { + const artifact = await getContractArtifact(contractArtifactPath, log); + const instance: ContractInstanceWithAddress = { + version: 1, + salt, + initializationHash, + contractClassId: getContractClassFromArtifact(artifact).id, + publicKeysHash: publicKeys?.hash() ?? Fr.ZERO, + address, + deployer: deployer ?? AztecAddress.ZERO, + }; + const computed = computeContractAddressFromInstance(instance); + if (!computed.equals(address)) { + throw new Error(`Contract address ${address.toString()} does not match computed address ${computed.toString()}`); + } + + const client = await createCompatibleClient(rpcUrl, debugLogger); + + await client.registerContract({ artifact, instance }); + log(`\nContract added to PXE at ${address.toString()} with class ${instance.contractClassId.toString()}\n`); +} diff --git a/yarn-project/cli/src/cmds/add_note.ts b/yarn-project/cli/src/cmds/add_note.ts new file mode 100644 index 00000000000..f6359bd5c1c --- /dev/null +++ b/yarn-project/cli/src/cmds/add_note.ts @@ -0,0 +1,22 @@ +import { type AztecAddress, type Fr } from '@aztec/aztec.js'; +import { ExtendedNote, Note, type TxHash } from '@aztec/circuit-types'; +import { type DebugLogger } from '@aztec/foundation/log'; + +import { createCompatibleClient } from '../client.js'; +import { parseFields } from '../parse_args.js'; + +export async function addNote( + address: AztecAddress, + contractAddress: AztecAddress, + storageSlot: Fr, + noteTypeId: Fr, + txHash: TxHash, + noteFields: string[], + rpcUrl: string, + debugLogger: DebugLogger, +) { + const note = new Note(parseFields(noteFields)); + const extendedNote = new ExtendedNote(note, address, contractAddress, storageSlot, noteTypeId, txHash); + const client = await createCompatibleClient(rpcUrl, debugLogger); + await client.addNote(extendedNote); +} diff --git a/yarn-project/cli/src/cmds/add_pending_shield.ts b/yarn-project/cli/src/cmds/add_pending_shield.ts new file mode 100644 index 00000000000..1ae6f4f9da1 --- /dev/null +++ b/yarn-project/cli/src/cmds/add_pending_shield.ts @@ -0,0 +1,33 @@ +import { type AztecAddress, Fr, computeSecretHash } from '@aztec/aztec.js'; +import { ExtendedNote, Note, type TxHash } from '@aztec/circuit-types'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; +import { TokenContract } from '@aztec/noir-contracts.js'; + +import { createCompatibleClient } from '../client.js'; + +export async function addPendingShield( + ownerAddress: AztecAddress, + tokenAddress: AztecAddress, + amount: bigint, + secret: Fr, + txHash: TxHash, + rpcUrl: string, + debugLogger: DebugLogger, + log: LogFn, +) { + const secretHash = computeSecretHash(secret); + const note = new Note([new Fr(amount), secretHash]); + const extendedNote = new ExtendedNote( + note, + ownerAddress, + tokenAddress, + TokenContract.storage.pending_shields.slot, + TokenContract.notes.TransparentNote.id, + txHash, + ); + const client = await createCompatibleClient(rpcUrl, debugLogger); + await client.addNote(extendedNote); + log(`Added pending shield note owned by ${ownerAddress.toString()} for ${amount}`); +} + +// await t.addPendingShieldNoteToPXE(bobsAddress, maxFee - actualFee, computeSecretHash(rebateSecret), tx.txHash); diff --git a/yarn-project/cli/src/cmds/block_number.ts b/yarn-project/cli/src/cmds/block_number.ts new file mode 100644 index 00000000000..5b6ca472d6b --- /dev/null +++ b/yarn-project/cli/src/cmds/block_number.ts @@ -0,0 +1,9 @@ +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +import { createCompatibleClient } from '../client.js'; + +export async function blockNumber(rpcUrl: string, debugLogger: DebugLogger, log: LogFn) { + const client = await createCompatibleClient(rpcUrl, debugLogger); + const num = await client.getBlockNumber(); + log(`${num}\n`); +} diff --git a/yarn-project/cli/src/cmds/bootstrap.ts b/yarn-project/cli/src/cmds/bootstrap.ts new file mode 100644 index 00000000000..0dc833a1c83 --- /dev/null +++ b/yarn-project/cli/src/cmds/bootstrap.ts @@ -0,0 +1,53 @@ +import { AztecAddress, SignerlessWallet, createPXEClient } from '@aztec/aztec.js'; +import { DefaultMultiCallEntrypoint } from '@aztec/aztec.js/entrypoint'; +import { CANONICAL_KEY_REGISTRY_ADDRESS } from '@aztec/circuits.js'; +import { type LogFn } from '@aztec/foundation/log'; +import { GasTokenContract, KeyRegistryContract } from '@aztec/noir-contracts.js'; +import { getCanonicalGasToken } from '@aztec/protocol-contracts/gas-token'; +import { getCanonicalKeyRegistry } from '@aztec/protocol-contracts/key-registry'; + +export async function bootstrap(rpcUrl: string, log: LogFn) { + const pxe = createPXEClient(rpcUrl); + const canonicalKeyRegistry = getCanonicalKeyRegistry(); + const deployer = new SignerlessWallet(pxe, new DefaultMultiCallEntrypoint(31337, 1)); + + if ( + (await deployer.getContractInstance(canonicalKeyRegistry.address))?.contractClassId.equals( + canonicalKeyRegistry.contractClass.id, + ) && + (await deployer.isContractClassPubliclyRegistered(canonicalKeyRegistry.contractClass.id)) + ) { + log('Key Registry already deployed'); + return; + } + + const keyRegistry = await KeyRegistryContract.deploy(deployer) + .send({ contractAddressSalt: canonicalKeyRegistry.instance.salt, universalDeploy: true }) + .deployed(); + + if ( + !keyRegistry.address.equals(canonicalKeyRegistry.address) || + !keyRegistry.address.equals(AztecAddress.fromBigInt(CANONICAL_KEY_REGISTRY_ADDRESS)) + ) { + throw new Error( + `Deployed Key Registry address ${keyRegistry.address} does not match expected address ${canonicalKeyRegistry.address}, or they both do not equal CANONICAL_KEY_REGISTRY_ADDRESS`, + ); + } + + log(`Key Registry deployed at canonical address ${keyRegistry.address.toString()}`); + + const gasPortalAddress = (await deployer.getNodeInfo()).l1ContractAddresses.gasPortalAddress; + const canonicalGasToken = getCanonicalGasToken(); + + if (await deployer.isContractClassPubliclyRegistered(canonicalGasToken.contractClass.id)) { + log('Gas token already deployed'); + return; + } + + const gasToken = await GasTokenContract.deploy(deployer) + .send({ contractAddressSalt: canonicalGasToken.instance.salt, universalDeploy: true }) + .deployed(); + await gasToken.methods.set_portal(gasPortalAddress).send().wait(); + + log(`Gas token deployed at canonical address ${gasToken.address.toString()}`); +} diff --git a/yarn-project/cli/src/cmds/bridge_l1_gas.ts b/yarn-project/cli/src/cmds/bridge_l1_gas.ts new file mode 100644 index 00000000000..08428f979bb --- /dev/null +++ b/yarn-project/cli/src/cmds/bridge_l1_gas.ts @@ -0,0 +1,38 @@ +import { type AztecAddress } from '@aztec/circuits.js'; +import { createEthereumChain, createL1Clients } from '@aztec/ethereum'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +import { createCompatibleClient } from '../client.js'; +import { GasPortalManagerFactory } from '../gas_portal.js'; + +export async function bridgeL1Gas( + amount: bigint, + recipient: AztecAddress, + rpcUrl: string, + l1RpcUrl: string, + apiKey: string, + mnemonic: string, + log: LogFn, + debugLogger: DebugLogger, +) { + // Prepare L1 client + const chain = createEthereumChain(l1RpcUrl, apiKey); + const { publicClient, walletClient } = createL1Clients(chain.rpcUrl, mnemonic, chain.chainInfo); + + // Prepare L2 client + const client = await createCompatibleClient(rpcUrl, debugLogger); + + // Setup portal manager + const manager = await GasPortalManagerFactory.create({ + pxeService: client, + publicClient: publicClient, + walletClient: walletClient, + logger: debugLogger, + }); + + const { secret } = await manager.prepareTokensOnL1(amount, amount, recipient); + + log(`Minted ${amount} gas tokens on L1 and pushed to L2 portal`); + log(`claimAmount=${amount},claimSecret=${secret}\n`); + log(`Note: You need to wait for two L2 blocks before pulling them from the L2 side`); +} diff --git a/yarn-project/cli/src/cmds/call.ts b/yarn-project/cli/src/cmds/call.ts new file mode 100644 index 00000000000..63e19db0cb4 --- /dev/null +++ b/yarn-project/cli/src/cmds/call.ts @@ -0,0 +1,33 @@ +import { type AztecAddress, ContractFunctionInteraction, SignerlessWallet } from '@aztec/aztec.js'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +import { format } from 'util'; + +import { createCompatibleClient } from '../client.js'; +import { getFunctionArtifact, getTxSender, prepTx } from '../utils.js'; + +export async function call( + functionName: string, + functionArgsIn: any[], + contractArtifactPath: string, + contractAddress: AztecAddress, + fromAddress: string | undefined, + rpcUrl: string, + debugLogger: DebugLogger, + log: LogFn, +) { + const { functionArgs, contractArtifact } = await prepTx(contractArtifactPath, functionName, functionArgsIn, log); + + const fnArtifact = getFunctionArtifact(contractArtifact, functionName); + if (fnArtifact.parameters.length !== functionArgs.length) { + throw Error( + `Invalid number of args passed. Expected ${fnArtifact.parameters.length}; Received: ${functionArgs.length}`, + ); + } + + const client = await createCompatibleClient(rpcUrl, debugLogger); + const call = new ContractFunctionInteraction(new SignerlessWallet(client), contractAddress, fnArtifact, functionArgs); + const from = await getTxSender(client, fromAddress); + const result = await call.simulate({ from }); + log(format('\nView result: ', result, '\n')); +} diff --git a/yarn-project/cli/src/cmds/check_deploy.ts b/yarn-project/cli/src/cmds/check_deploy.ts new file mode 100644 index 00000000000..4a00c72a518 --- /dev/null +++ b/yarn-project/cli/src/cmds/check_deploy.ts @@ -0,0 +1,19 @@ +import { type AztecAddress } from '@aztec/aztec.js'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +import { createCompatibleClient } from '../client.js'; + +export async function checkDeploy(rpcUrl: string, contractAddress: AztecAddress, debugLogger: DebugLogger, log: LogFn) { + const client = await createCompatibleClient(rpcUrl, debugLogger); + const isPrivatelyDeployed = await client.getContractInstance(contractAddress); + const isPubliclyDeployed = await client.isContractPubliclyDeployed(contractAddress); + if (isPubliclyDeployed && isPrivatelyDeployed) { + log(`\nContract is publicly deployed at ${contractAddress.toString()}\n`); + } else if (isPrivatelyDeployed) { + log(`\nContract is registered in the local pxe at ${contractAddress.toString()} but not publicly deployed\n`); + } else if (isPubliclyDeployed) { + log(`\nContract is publicly deployed at ${contractAddress.toString()} but not registered in the local pxe\n`); + } else { + log(`\nNo contract found at ${contractAddress.toString()}\n`); + } +} diff --git a/yarn-project/cli/src/cmds/compute_selector.ts b/yarn-project/cli/src/cmds/compute_selector.ts new file mode 100644 index 00000000000..9d299a64eff --- /dev/null +++ b/yarn-project/cli/src/cmds/compute_selector.ts @@ -0,0 +1,7 @@ +import { FunctionSelector } from '@aztec/foundation/abi'; +import { type LogFn } from '@aztec/foundation/log'; + +export function computeSelector(functionSignature: string, log: LogFn) { + const selector = FunctionSelector.fromSignature(functionSignature); + log(`${selector}`); +} diff --git a/yarn-project/cli/src/cmds/create_account.ts b/yarn-project/cli/src/cmds/create_account.ts new file mode 100644 index 00000000000..e526682a779 --- /dev/null +++ b/yarn-project/cli/src/cmds/create_account.ts @@ -0,0 +1,63 @@ +import { getSchnorrAccount } from '@aztec/accounts/schnorr'; +import { deriveSigningKey } from '@aztec/circuits.js'; +import { Fr } from '@aztec/foundation/fields'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +import { createCompatibleClient } from '../client.js'; +import { type IFeeOpts, printGasEstimates } from '../fees.js'; + +export async function createAccount( + rpcUrl: string, + privateKey: Fr | undefined, + registerOnly: boolean, + wait: boolean, + feeOpts: IFeeOpts, + debugLogger: DebugLogger, + log: LogFn, +) { + const client = await createCompatibleClient(rpcUrl, debugLogger); + const printPK = typeof privateKey === 'undefined'; + privateKey ??= Fr.random(); + const salt = Fr.ZERO; + + const account = getSchnorrAccount(client, privateKey, deriveSigningKey(privateKey), salt); + const { address, publicKeys, partialAddress } = account.getCompleteAddress(); + + let tx; + let txReceipt; + if (registerOnly) { + await account.register(); + } else { + const wallet = await account.getWallet(); + const sendOpts = feeOpts.toSendOpts(wallet); + if (feeOpts.estimateOnly) { + const gas = await (await account.getDeployMethod()).estimateGas({ ...sendOpts }); + printGasEstimates(feeOpts, gas, log); + } else { + tx = account.deploy({ ...sendOpts }); + const txHash = await tx.getTxHash(); + debugLogger.debug(`Account contract tx sent with hash ${txHash}`); + if (wait) { + log(`\nWaiting for account contract deployment...`); + txReceipt = await tx.wait(); + } + } + } + + log(`\nNew account:\n`); + log(`Address: ${address.toString()}`); + log(`Public key: 0x${publicKeys.toString()}`); + if (printPK) { + log(`Private key: ${privateKey.toString()}`); + } + log(`Partial address: ${partialAddress.toString()}`); + log(`Salt: ${salt.toString()}`); + log(`Init hash: ${account.getInstance().initializationHash.toString()}`); + log(`Deployer: ${account.getInstance().deployer.toString()}`); + if (tx) { + log(`Deploy tx hash: ${await tx.getTxHash()}`); + } + if (txReceipt) { + log(`Deploy tx fee: ${txReceipt.transactionFee}`); + } +} diff --git a/yarn-project/cli/src/cmds/deploy.ts b/yarn-project/cli/src/cmds/deploy.ts new file mode 100644 index 00000000000..c80c45dda4e --- /dev/null +++ b/yarn-project/cli/src/cmds/deploy.ts @@ -0,0 +1,119 @@ +import { getSchnorrAccount } from '@aztec/accounts/schnorr'; +import { ContractDeployer, type DeployMethod, Fr } from '@aztec/aztec.js'; +import { type PublicKeys, deriveSigningKey } from '@aztec/circuits.js'; +import { getInitializer } from '@aztec/foundation/abi'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +import { createCompatibleClient } from '../client.js'; +import { encodeArgs } from '../encoding.js'; +import { type IFeeOpts, printGasEstimates } from '../fees.js'; +import { GITHUB_TAG_PREFIX } from '../github.js'; +import { getContractArtifact } from '../utils.js'; + +export async function deploy( + artifactPath: string, + json: boolean, + rpcUrl: string, + publicKeys: PublicKeys | undefined, + rawArgs: any[], + salt: Fr | undefined, + privateKey: Fr, + initializer: string | undefined, + skipPublicDeployment: boolean, + skipClassRegistration: boolean, + skipInitialization: boolean | undefined, + universalDeploy: boolean | undefined, + wait: boolean, + feeOpts: IFeeOpts, + debugLogger: DebugLogger, + log: LogFn, + logJson: (output: any) => void, +) { + salt ??= Fr.random(); + const contractArtifact = await getContractArtifact(artifactPath, log); + const constructorArtifact = getInitializer(contractArtifact, initializer); + + const client = await createCompatibleClient(rpcUrl, debugLogger); + const nodeInfo = await client.getNodeInfo(); + const expectedAztecNrVersion = `${GITHUB_TAG_PREFIX}-v${nodeInfo.nodeVersion}`; + if (contractArtifact.aztecNrVersion && contractArtifact.aztecNrVersion !== expectedAztecNrVersion) { + log( + `\nWarning: Contract was compiled with a different version of Aztec.nr: ${contractArtifact.aztecNrVersion}. Consider updating Aztec.nr to ${expectedAztecNrVersion}\n`, + ); + } + + const wallet = await getSchnorrAccount(client, privateKey, deriveSigningKey(privateKey), Fr.ZERO).getWallet(); + const deployer = new ContractDeployer(contractArtifact, wallet, publicKeys?.hash() ?? Fr.ZERO, initializer); + + let args = []; + if (rawArgs.length > 0) { + if (!constructorArtifact) { + throw new Error(`Cannot process constructor arguments as no constructor was found`); + } + debugLogger.debug(`Input arguments: ${rawArgs.map((x: any) => `"${x}"`).join(', ')}`); + args = encodeArgs(rawArgs, constructorArtifact!.parameters); + debugLogger.debug(`Encoded arguments: ${args.join(', ')}`); + } + + const deploy = deployer.deploy(...args); + const deployOpts: Parameters[0] = { + ...feeOpts.toSendOpts(wallet), + contractAddressSalt: salt, + universalDeploy, + skipClassRegistration, + skipInitialization, + skipPublicDeployment, + }; + + if (feeOpts.estimateOnly) { + const gas = await deploy.estimateGas(deployOpts); + printGasEstimates(feeOpts, gas, log); + return; + } + + await deploy.create(deployOpts); + const tx = deploy.send(deployOpts); + + const txHash = await tx.getTxHash(); + debugLogger.debug(`Deploy tx sent with hash ${txHash}`); + if (wait) { + const deployed = await tx.wait(); + const { address, partialAddress, instance } = deployed.contract; + if (json) { + logJson({ + address: address.toString(), + partialAddress: partialAddress.toString(), + initializationHash: instance.initializationHash.toString(), + salt: salt.toString(), + transactionFee: deployed.transactionFee, + }); + } else { + log(`Contract deployed at ${address.toString()}`); + log(`Contract partial address ${partialAddress.toString()}`); + log(`Contract init hash ${instance.initializationHash.toString()}`); + log(`Deployment tx hash: ${txHash.toString()}`); + log(`Deployment salt: ${salt.toString()}`); + log(`Deployment fee: ${deployed.transactionFee}`); + } + } else { + const { address, partialAddress } = deploy; + const instance = deploy.getInstance(); + if (json) { + logJson({ + address: address?.toString() ?? 'N/A', + partialAddress: partialAddress?.toString() ?? 'N/A', + txHash: txHash.toString(), + initializationHash: instance.initializationHash.toString(), + salt: salt.toString(), + deployer: instance.deployer.toString(), + }); + } else { + log(`Contract deployed at ${address?.toString()}`); + log(`Contract partial address ${partialAddress?.toString()}`); + log(`Contract init hash ${instance.initializationHash.toString()}`); + log(`Deployment tx hash: ${txHash.toString()}`); + log(`Deployment salt: ${salt.toString()}`); + log(`Deployer: ${instance.deployer.toString()}`); + } + } +} diff --git a/yarn-project/cli/src/cmds/deploy_l1_contracts.ts b/yarn-project/cli/src/cmds/deploy_l1_contracts.ts new file mode 100644 index 00000000000..629dcaec1c7 --- /dev/null +++ b/yarn-project/cli/src/cmds/deploy_l1_contracts.ts @@ -0,0 +1,24 @@ +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +import { deployAztecContracts } from '../utils.js'; + +export async function deployL1Contracts( + rpcUrl: string, + apiKey: string, + privateKey: string, + mnemonic: string, + log: LogFn, + debugLogger: DebugLogger, +) { + const { l1ContractAddresses } = await deployAztecContracts(rpcUrl, apiKey, privateKey, mnemonic, debugLogger); + + log('\n'); + log(`Rollup Address: ${l1ContractAddresses.rollupAddress.toString()}`); + log(`Registry Address: ${l1ContractAddresses.registryAddress.toString()}`); + log(`L1 -> L2 Inbox Address: ${l1ContractAddresses.inboxAddress.toString()}`); + log(`L2 -> L1 Outbox Address: ${l1ContractAddresses.outboxAddress.toString()}`); + log(`Availability Oracle Address: ${l1ContractAddresses.availabilityOracleAddress.toString()}`); + log(`Gas Token Address: ${l1ContractAddresses.gasTokenAddress.toString()}`); + log(`Gas Portal Address: ${l1ContractAddresses.gasPortalAddress.toString()}`); + log('\n'); +} diff --git a/yarn-project/cli/src/cmds/deploy_l1_verifier.ts b/yarn-project/cli/src/cmds/deploy_l1_verifier.ts new file mode 100644 index 00000000000..5ca89e94837 --- /dev/null +++ b/yarn-project/cli/src/cmds/deploy_l1_verifier.ts @@ -0,0 +1,102 @@ +import { BBCircuitVerifier } from '@aztec/bb-prover'; +import { createL1Clients, deployL1Contract } from '@aztec/ethereum'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; +import { MockVerifierAbi, MockVerifierBytecode, RollupAbi } from '@aztec/l1-artifacts'; + +// @ts-expect-error solc-js doesn't publish its types https://github.com/ethereum/solc-js/issues/689 +import solc from 'solc'; +import { getContract } from 'viem'; +import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts'; + +import { createCompatibleClient } from '../client.js'; + +export async function deployUltraVerifier( + ethRpcUrl: string, + privateKey: string, + mnemonic: string, + pxeRpcUrl: string, + bbBinaryPath: string, + bbWorkingDirectory: string, + log: LogFn, + debugLogger: DebugLogger, +) { + const circuitVerifier = await BBCircuitVerifier.new({ bbBinaryPath, bbWorkingDirectory }); + const contractSrc = await circuitVerifier.generateSolidityContract('RootRollupArtifact', 'UltraVerifier.sol'); + log('Generated UltraVerifier contract'); + + const input = { + language: 'Solidity', + sources: { + 'UltraVerifier.sol': { + content: contractSrc, + }, + }, + settings: { + // we require the optimizer + optimizer: { + enabled: true, + runs: 200, + }, + outputSelection: { + '*': { + '*': ['evm.bytecode.object', 'abi'], + }, + }, + }, + }; + + const output = JSON.parse(solc.compile(JSON.stringify(input))); + log('Compiled UltraVerifier'); + + const abi = output.contracts['UltraVerifier.sol']['UltraVerifier'].abi; + const bytecode: string = output.contracts['UltraVerifier.sol']['UltraVerifier'].evm.bytecode.object; + + const account = !privateKey + ? mnemonicToAccount(mnemonic!) + : privateKeyToAccount(`${privateKey.startsWith('0x') ? '' : '0x'}${privateKey}` as `0x${string}`); + const { publicClient, walletClient } = createL1Clients(ethRpcUrl, account); + + const verifierAddress = await deployL1Contract(walletClient, publicClient, abi, `0x${bytecode}`); + log(`Deployed UltraVerifier at ${verifierAddress.toString()}`); + + const pxe = await createCompatibleClient(pxeRpcUrl, debugLogger); + const { l1ContractAddresses } = await pxe.getNodeInfo(); + + const rollup = getContract({ + abi: RollupAbi, + address: l1ContractAddresses.rollupAddress.toString(), + client: walletClient, + }); + + await rollup.write.setVerifier([verifierAddress.toString()]); + log(`Rollup accepts only real proofs now`); +} + +export async function deployMockVerifier( + ethRpcUrl: string, + privateKey: string, + mnemonic: string, + pxeRpcUrl: string, + log: LogFn, + debugLogger: DebugLogger, +) { + const account = !privateKey + ? mnemonicToAccount(mnemonic!) + : privateKeyToAccount(`${privateKey.startsWith('0x') ? '' : '0x'}${privateKey}` as `0x${string}`); + const { publicClient, walletClient } = createL1Clients(ethRpcUrl, account); + + const mockVerifierAddress = await deployL1Contract(walletClient, publicClient, MockVerifierAbi, MockVerifierBytecode); + log(`Deployed MockVerifier at ${mockVerifierAddress.toString()}`); + + const pxe = await createCompatibleClient(pxeRpcUrl, debugLogger); + const { l1ContractAddresses } = await pxe.getNodeInfo(); + + const rollup = getContract({ + abi: RollupAbi, + address: l1ContractAddresses.rollupAddress.toString(), + client: walletClient, + }); + + await rollup.write.setVerifier([mockVerifierAddress.toString()]); + log(`Rollup accepts only fake proofs now`); +} diff --git a/yarn-project/cli/src/cmds/example_contracts.ts b/yarn-project/cli/src/cmds/example_contracts.ts new file mode 100644 index 00000000000..94c7437262b --- /dev/null +++ b/yarn-project/cli/src/cmds/example_contracts.ts @@ -0,0 +1,9 @@ +import { type LogFn } from '@aztec/foundation/log'; + +import { getExampleContractArtifacts } from '../utils.js'; + +export async function exampleContracts(log: LogFn) { + const abisList = await getExampleContractArtifacts(); + const names = Object.keys(abisList).filter(name => name !== 'AvmTestContractArtifact'); + names.forEach(name => log(name)); +} diff --git a/yarn-project/cli/src/cmds/generate_p2p_private_key.ts b/yarn-project/cli/src/cmds/generate_p2p_private_key.ts new file mode 100644 index 00000000000..f62617ae6ea --- /dev/null +++ b/yarn-project/cli/src/cmds/generate_p2p_private_key.ts @@ -0,0 +1,10 @@ +import { type LogFn } from '@aztec/foundation/log'; + +import { createSecp256k1PeerId } from '@libp2p/peer-id-factory'; + +export async function generateP2PPrivateKey(log: LogFn) { + const peerId = await createSecp256k1PeerId(); + const exportedPeerId = Buffer.from(peerId.privateKey!).toString('hex'); + log(`Private key: ${exportedPeerId}`); + log(`Peer Id: ${peerId}`); +} diff --git a/yarn-project/cli/src/cmds/generate_private_key.ts b/yarn-project/cli/src/cmds/generate_private_key.ts new file mode 100644 index 00000000000..5744658b383 --- /dev/null +++ b/yarn-project/cli/src/cmds/generate_private_key.ts @@ -0,0 +1,11 @@ +import { Fr } from '@aztec/aztec.js'; +import { deriveSigningKey } from '@aztec/circuits.js'; + +export function generateKeys() { + const privateKey = Fr.random(); + const signingKey = deriveSigningKey(privateKey); + return { + privateEncryptionKey: privateKey, + privateSigningKey: signingKey, + }; +} diff --git a/yarn-project/cli/src/cmds/get_account.ts b/yarn-project/cli/src/cmds/get_account.ts new file mode 100644 index 00000000000..c672c85fb85 --- /dev/null +++ b/yarn-project/cli/src/cmds/get_account.ts @@ -0,0 +1,15 @@ +import { type AztecAddress } from '@aztec/aztec.js'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +import { createCompatibleClient } from '../client.js'; + +export async function getAccount(aztecAddress: AztecAddress, rpcUrl: string, debugLogger: DebugLogger, log: LogFn) { + const client = await createCompatibleClient(rpcUrl, debugLogger); + const account = await client.getRegisteredAccount(aztecAddress); + + if (!account) { + log(`Unknown account ${aztecAddress.toString()}`); + } else { + log(account.toReadableString()); + } +} diff --git a/yarn-project/cli/src/cmds/get_accounts.ts b/yarn-project/cli/src/cmds/get_accounts.ts new file mode 100644 index 00000000000..204842f4387 --- /dev/null +++ b/yarn-project/cli/src/cmds/get_accounts.ts @@ -0,0 +1,36 @@ +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +import { createCompatibleClient } from '../client.js'; + +export async function getAccounts( + rpcUrl: string, + json: boolean, + debugLogger: DebugLogger, + log: LogFn, + logJson: (output: any) => void, +) { + const client = await createCompatibleClient(rpcUrl, debugLogger); + const accounts = await client.getRegisteredAccounts(); + if (!accounts.length) { + if (json) { + logJson([]); + } else { + log('No accounts found.'); + } + return; + } + if (json) { + logJson( + accounts.map(a => ({ + address: a.address.toString(), + publicKeys: a.publicKeys.toString(), + partialAddress: a.partialAddress.toString(), + })), + ); + } else { + log(`Accounts found: \n`); + for (const account of accounts) { + log(account.toReadableString()); + } + } +} diff --git a/yarn-project/cli/src/cmds/get_balance.ts b/yarn-project/cli/src/cmds/get_balance.ts new file mode 100644 index 00000000000..4fa7d7f5cc3 --- /dev/null +++ b/yarn-project/cli/src/cmds/get_balance.ts @@ -0,0 +1,32 @@ +import { AztecAddress } from '@aztec/aztec.js'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; +import { TokenContractArtifact } from '@aztec/noir-contracts.js'; +import { GasTokenAddress, GasTokenArtifact } from '@aztec/protocol-contracts/gas-token'; +import { computeSlotForMapping } from '@aztec/simulator'; + +import { createCompatibleClient } from '../client.js'; + +export async function getBalance( + address: AztecAddress, + maybeTokenAddress: string | undefined, + rpcUrl: string, + debugLogger: DebugLogger, + log: LogFn, +) { + const client = await createCompatibleClient(rpcUrl, debugLogger); + const tokenAddress = maybeTokenAddress ? AztecAddress.fromString(maybeTokenAddress) : GasTokenAddress; + + // Get private balance + if (!tokenAddress.equals(GasTokenAddress)) { + const result = await client.simulateUnconstrained(`balance_of_private`, [address], tokenAddress); + log(`\nPrivate balance: ${result.toString()}`); + } + + // TODO(#6707): For public balance, we cannot directly simulate a public function call, so we read directly from storage as a workaround + const balancesStorageSlot = tokenAddress.equals(GasTokenAddress) + ? GasTokenArtifact.storageLayout.balances.slot + : TokenContractArtifact.storageLayout.public_balances.slot; + const slot = computeSlotForMapping(balancesStorageSlot, address); + const result = await client.getPublicStorageAt(tokenAddress, slot); + log(`Public balance: ${result.toBigInt()}`); +} diff --git a/yarn-project/cli/src/cmds/get_contract_data.ts b/yarn-project/cli/src/cmds/get_contract_data.ts new file mode 100644 index 00000000000..592358a9fd3 --- /dev/null +++ b/yarn-project/cli/src/cmds/get_contract_data.ts @@ -0,0 +1,32 @@ +import { type AztecAddress } from '@aztec/aztec.js'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +import { createCompatibleClient } from '../client.js'; + +export async function getContractData( + rpcUrl: string, + contractAddress: AztecAddress, + includeBytecode: boolean, + debugLogger: DebugLogger, + log: LogFn, +) { + const client = await createCompatibleClient(rpcUrl, debugLogger); + const instance = await client.getContractInstance(contractAddress); + const contractClass = includeBytecode && instance && (await client.getContractClass(instance?.contractClassId)); + + if (!instance) { + log(`No contract found at ${contractAddress}`); + return; + } + + log(`\nContract Data:`); + Object.entries(instance).forEach(([key, value]) => { + const capitalized = key.charAt(0).toUpperCase() + key.slice(1); + log(`${capitalized}: ${value.toString()}`); + }); + + if (contractClass) { + log(`Bytecode: ${contractClass.packedBytecode.toString('base64')}`); + } + log('\n'); +} diff --git a/yarn-project/cli/src/cmds/get_l1_balance.ts b/yarn-project/cli/src/cmds/get_l1_balance.ts new file mode 100644 index 00000000000..26163191c42 --- /dev/null +++ b/yarn-project/cli/src/cmds/get_l1_balance.ts @@ -0,0 +1,33 @@ +import { type EthAddress } from '@aztec/circuits.js'; +import { createEthereumChain } from '@aztec/ethereum'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; +import { PortalERC20Abi } from '@aztec/l1-artifacts'; + +import { createPublicClient, getContract, http } from 'viem'; + +import { createCompatibleClient } from '../client.js'; + +export async function getL1Balance( + who: EthAddress, + rpcUrl: string, + l1RpcUrl: string, + apiKey: string, + log: LogFn, + debugLogger: DebugLogger, +) { + const client = await createCompatibleClient(rpcUrl, debugLogger); + const { l1ContractAddresses } = await client.getNodeInfo(); + + const chain = createEthereumChain(l1RpcUrl, apiKey); + const publicClient = createPublicClient({ chain: chain.chainInfo, transport: http(chain.rpcUrl) }); + + const gasL1 = getContract({ + address: l1ContractAddresses.gasTokenAddress.toString(), + abi: PortalERC20Abi, + client: publicClient, + }); + + const balance = await gasL1.read.balanceOf([who.toString()]); + + log(`L1 gas token balance of ${who.toString()} is ${balance.toString()}`); +} diff --git a/yarn-project/cli/src/cmds/get_logs.ts b/yarn-project/cli/src/cmds/get_logs.ts new file mode 100644 index 00000000000..29afb7bbc6f --- /dev/null +++ b/yarn-project/cli/src/cmds/get_logs.ts @@ -0,0 +1,69 @@ +import { type AztecAddress, type LogFilter, type LogId, type TxHash } from '@aztec/aztec.js'; +import { type EventSelector } from '@aztec/foundation/abi'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; +import { sleep } from '@aztec/foundation/sleep'; + +import { createCompatibleClient } from '../client.js'; + +export async function getLogs( + txHash: TxHash, + fromBlock: number, + toBlock: number, + afterLog: LogId, + contractAddress: AztecAddress, + selector: EventSelector, + rpcUrl: string, + follow: boolean, + debugLogger: DebugLogger, + log: LogFn, +) { + const pxe = await createCompatibleClient(rpcUrl, debugLogger); + + if (follow) { + if (txHash) { + throw Error('Cannot use --follow with --tx-hash'); + } + if (toBlock) { + throw Error('Cannot use --follow with --to-block'); + } + } + + const filter: LogFilter = { txHash, fromBlock, toBlock, afterLog, contractAddress, selector }; + + const fetchLogs = async () => { + const response = await pxe.getUnencryptedLogs(filter); + const logs = response.logs; + + if (!logs.length) { + const filterOptions = Object.entries(filter) + .filter(([, value]) => value !== undefined) + .map(([key, value]) => `${key}: ${value}`) + .join(', '); + if (!follow) { + log(`No logs found for filter: {${filterOptions}}`); + } + } else { + if (!follow && !filter.afterLog) { + log('Logs found: \n'); + } + logs.forEach(unencryptedLog => log(unencryptedLog.toHumanReadable())); + // Set the continuation parameter for the following requests + filter.afterLog = logs[logs.length - 1].id; + } + return response.maxLogsHit; + }; + + if (follow) { + log('Fetching logs...'); + while (true) { + const maxLogsHit = await fetchLogs(); + if (!maxLogsHit) { + await sleep(1000); + } + } + } else { + while (await fetchLogs()) { + // Keep fetching logs until we reach the end. + } + } +} diff --git a/yarn-project/cli/src/cmds/get_node_info.ts b/yarn-project/cli/src/cmds/get_node_info.ts new file mode 100644 index 00000000000..b66df822532 --- /dev/null +++ b/yarn-project/cli/src/cmds/get_node_info.ts @@ -0,0 +1,13 @@ +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +import { createCompatibleClient } from '../client.js'; + +export async function getNodeInfo(rpcUrl: string, debugLogger: DebugLogger, log: LogFn) { + const client = await createCompatibleClient(rpcUrl, debugLogger); + const info = await client.getNodeInfo(); + log(`\nNode Info:\n`); + log(`Node Version: ${info.nodeVersion}\n`); + log(`Chain Id: ${info.chainId}\n`); + log(`Protocol Version: ${info.protocolVersion}\n`); + log(`Rollup Address: ${info.l1ContractAddresses.rollupAddress.toString()}`); +} diff --git a/yarn-project/cli/src/cmds/get_recipient.ts b/yarn-project/cli/src/cmds/get_recipient.ts new file mode 100644 index 00000000000..4ae6baac896 --- /dev/null +++ b/yarn-project/cli/src/cmds/get_recipient.ts @@ -0,0 +1,15 @@ +import { type AztecAddress } from '@aztec/aztec.js'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +import { createCompatibleClient } from '../client.js'; + +export async function getRecipient(aztecAddress: AztecAddress, rpcUrl: string, debugLogger: DebugLogger, log: LogFn) { + const client = await createCompatibleClient(rpcUrl, debugLogger); + const recipient = await client.getRecipient(aztecAddress); + + if (!recipient) { + log(`Unknown recipient ${aztecAddress.toString()}`); + } else { + log(recipient.toReadableString()); + } +} diff --git a/yarn-project/cli/src/cmds/get_recipients.ts b/yarn-project/cli/src/cmds/get_recipients.ts new file mode 100644 index 00000000000..bc091bc5dd6 --- /dev/null +++ b/yarn-project/cli/src/cmds/get_recipients.ts @@ -0,0 +1,16 @@ +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +import { createCompatibleClient } from '../client.js'; + +export async function getRecipients(rpcUrl: string, debugLogger: DebugLogger, log: LogFn) { + const client = await createCompatibleClient(rpcUrl, debugLogger); + const recipients = await client.getRecipients(); + if (!recipients.length) { + log('No recipients found.'); + } else { + log(`Recipients found: \n`); + for (const recipient of recipients) { + log(recipient.toReadableString()); + } + } +} diff --git a/yarn-project/cli/src/cmds/get_tx_receipt.ts b/yarn-project/cli/src/cmds/get_tx_receipt.ts new file mode 100644 index 00000000000..6119f035aac --- /dev/null +++ b/yarn-project/cli/src/cmds/get_tx_receipt.ts @@ -0,0 +1,15 @@ +import { type TxHash } from '@aztec/aztec.js'; +import { JsonStringify } from '@aztec/foundation/json-rpc'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +import { createCompatibleClient } from '../client.js'; + +export async function getTxReceipt(rpcUrl: string, txHash: TxHash, debugLogger: DebugLogger, log: LogFn) { + const client = await createCompatibleClient(rpcUrl, debugLogger); + const receipt = await client.getTxReceipt(txHash); + if (!receipt) { + log(`No receipt found for transaction hash ${txHash.toString()}`); + } else { + log(`\nTransaction receipt: \n${JsonStringify(receipt, true)}\n`); + } +} diff --git a/yarn-project/cli/src/cmds/inspect_contract.ts b/yarn-project/cli/src/cmds/inspect_contract.ts new file mode 100644 index 00000000000..49967561084 --- /dev/null +++ b/yarn-project/cli/src/cmds/inspect_contract.ts @@ -0,0 +1,43 @@ +import { getContractClassFromArtifact } from '@aztec/circuits.js'; +import { + type FunctionArtifact, + FunctionSelector, + decodeFunctionSignature, + decodeFunctionSignatureWithParameterNames, +} from '@aztec/foundation/abi'; +import { sha256 } from '@aztec/foundation/crypto'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +import { getContractArtifact } from '../utils.js'; + +export async function inspectContract(contractArtifactFile: string, debugLogger: DebugLogger, log: LogFn) { + const contractArtifact = await getContractArtifact(contractArtifactFile, log); + const contractFns = contractArtifact.functions.filter(f => f.name !== 'compute_note_hash_and_nullifier'); + if (contractFns.length === 0) { + log(`No functions found for contract ${contractArtifact.name}`); + } + const contractClass = getContractClassFromArtifact(contractArtifact); + const bytecodeLengthInFields = 1 + Math.ceil(contractClass.packedBytecode.length / 31); + + log(`Contract class details:`); + log(`\tidentifier: ${contractClass.id.toString()}`); + log(`\tartifact hash: ${contractClass.artifactHash.toString()}`); + log(`\tprivate function tree root: ${contractClass.privateFunctionsRoot.toString()}`); + log(`\tpublic bytecode commitment: ${contractClass.publicBytecodeCommitment.toString()}`); + log(`\tpublic bytecode length: ${contractClass.packedBytecode.length} bytes (${bytecodeLengthInFields} fields)`); + log(`\nExternal functions:`); + contractFns.filter(f => !f.isInternal).forEach(f => logFunction(f, log)); + log(`\nInternal functions:`); + contractFns.filter(f => f.isInternal).forEach(f => logFunction(f, log)); +} + +function logFunction(fn: FunctionArtifact, log: LogFn) { + const signatureWithParameterNames = decodeFunctionSignatureWithParameterNames(fn.name, fn.parameters); + const signature = decodeFunctionSignature(fn.name, fn.parameters); + const selector = FunctionSelector.fromSignature(signature); + const bytecodeSize = fn.bytecode.length; + const bytecodeHash = sha256(fn.bytecode).toString('hex'); + log( + `${fn.functionType} ${signatureWithParameterNames} \n\tfunction signature: ${signature}\n\tselector: ${selector}\n\tbytecode: ${bytecodeSize} bytes (sha256 ${bytecodeHash})`, + ); +} diff --git a/yarn-project/cli/src/cmds/parse_parameter_struct.ts b/yarn-project/cli/src/cmds/parse_parameter_struct.ts new file mode 100644 index 00000000000..15bc057ede9 --- /dev/null +++ b/yarn-project/cli/src/cmds/parse_parameter_struct.ts @@ -0,0 +1,27 @@ +import { type StructType } from '@aztec/foundation/abi'; +import { JsonStringify } from '@aztec/foundation/json-rpc'; +import { type LogFn } from '@aztec/foundation/log'; + +import { parseStructString } from '../encoding.js'; +import { getContractArtifact } from '../utils.js'; + +export async function parseParameterStruct( + encodedString: string, + contractArtifactPath: string, + parameterName: string, + log: LogFn, +) { + const contractArtifact = await getContractArtifact(contractArtifactPath, log); + const parameterAbitype = contractArtifact.functions + .map(({ parameters }) => parameters) + .flat() + .find(({ name, type }) => name === parameterName && type.kind === 'struct'); + + if (!parameterAbitype) { + log(`No struct parameter found with name ${parameterName}`); + return; + } + + const data = parseStructString(encodedString, parameterAbitype.type as StructType); + log(`\nStruct Data: \n${JsonStringify(data, true)}\n`); +} diff --git a/yarn-project/cli/src/cmds/register_account.ts b/yarn-project/cli/src/cmds/register_account.ts new file mode 100644 index 00000000000..86853523708 --- /dev/null +++ b/yarn-project/cli/src/cmds/register_account.ts @@ -0,0 +1,21 @@ +import { type Fr } from '@aztec/foundation/fields'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +import { createCompatibleClient } from '../client.js'; + +export async function registerAccount( + rpcUrl: string, + privateKey: Fr, + partialAddress: Fr, + debugLogger: DebugLogger, + log: LogFn, +) { + const client = await createCompatibleClient(rpcUrl, debugLogger); + + const { address, publicKeys } = await client.registerAccount(privateKey, partialAddress); + + log(`\nRegistered account:\n`); + log(`Address: ${address.toString()}`); + log(`Public key: ${publicKeys.toString()}`); + log(`Partial address: ${partialAddress.toString()}`); +} diff --git a/yarn-project/cli/src/cmds/register_recipient.ts b/yarn-project/cli/src/cmds/register_recipient.ts new file mode 100644 index 00000000000..57b2e186317 --- /dev/null +++ b/yarn-project/cli/src/cmds/register_recipient.ts @@ -0,0 +1,19 @@ +import { type AztecAddress, type Fr } from '@aztec/aztec.js'; +import { CompleteAddress } from '@aztec/circuit-types'; +import { type PublicKeys } from '@aztec/circuits.js'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +import { createCompatibleClient } from '../client.js'; + +export async function registerRecipient( + aztecAddress: AztecAddress, + publicKeys: PublicKeys, + partialAddress: Fr, + rpcUrl: string, + debugLogger: DebugLogger, + log: LogFn, +) { + const client = await createCompatibleClient(rpcUrl, debugLogger); + await client.registerRecipient(new CompleteAddress(aztecAddress, publicKeys, partialAddress)); + log(`\nRegistered details for account with address: ${aztecAddress}\n`); +} diff --git a/yarn-project/cli/src/cmds/send.ts b/yarn-project/cli/src/cmds/send.ts new file mode 100644 index 00000000000..87e41d64698 --- /dev/null +++ b/yarn-project/cli/src/cmds/send.ts @@ -0,0 +1,55 @@ +import { getSchnorrAccount } from '@aztec/accounts/schnorr'; +import { type AztecAddress, Contract, Fr } from '@aztec/aztec.js'; +import { deriveSigningKey } from '@aztec/circuits.js'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +import { createCompatibleClient } from '../client.js'; +import { type IFeeOpts, printGasEstimates } from '../fees.js'; +import { prepTx } from '../utils.js'; + +export async function send( + functionName: string, + functionArgsIn: any[], + contractArtifactPath: string, + contractAddress: AztecAddress, + encryptionPrivateKey: Fr, + rpcUrl: string, + wait: boolean, + feeOpts: IFeeOpts, + debugLogger: DebugLogger, + log: LogFn, +) { + const { functionArgs, contractArtifact } = await prepTx(contractArtifactPath, functionName, functionArgsIn, log); + + const client = await createCompatibleClient(rpcUrl, debugLogger); + const wallet = await getSchnorrAccount( + client, + encryptionPrivateKey, + deriveSigningKey(encryptionPrivateKey), + Fr.ZERO, + ).getWallet(); + const contract = await Contract.at(contractAddress, contractArtifact, wallet); + const call = contract.methods[functionName](...functionArgs); + + if (feeOpts.estimateOnly) { + const gas = await call.estimateGas({ ...feeOpts.toSendOpts(wallet) }); + printGasEstimates(feeOpts, gas, log); + return; + } + + const tx = call.send({ ...feeOpts.toSendOpts(wallet) }); + log(`\nTransaction hash: ${(await tx.getTxHash()).toString()}`); + if (wait) { + await tx.wait(); + + log('Transaction has been mined'); + + const receipt = await tx.getReceipt(); + log(` Tx fee: ${receipt.transactionFee}`); + log(` Status: ${receipt.status}`); + log(` Block number: ${receipt.blockNumber}`); + log(` Block hash: ${receipt.blockHash?.toString('hex')}`); + } else { + log('Transaction pending. Check status with get-tx-receipt'); + } +} diff --git a/yarn-project/cli/src/encoding.ts b/yarn-project/cli/src/encoding.ts new file mode 100644 index 00000000000..e10f843814d --- /dev/null +++ b/yarn-project/cli/src/encoding.ts @@ -0,0 +1,117 @@ +import { type ABIParameter, type AbiType, type StructType } from '@aztec/foundation/abi'; +import { Fr } from '@aztec/foundation/fields'; + +/** + * Parses a hex string into an ABI struct type. + * @param str - The encoded hex string. + * @param abiType - The ABI Struct type. + * @returns An object in the ABI struct type's format. + */ +export function parseStructString(str: string, abiType: StructType) { + // Assign string bytes to struct fields. + const buf = Buffer.from(str.replace(/^0x/i, ''), 'hex'); + const struct: any = {}; + let byteIndex = 0; + let argIndex = 0; + while (byteIndex < buf.length) { + const { name } = abiType.fields[argIndex]; + struct[name] = Fr.fromBuffer(buf.subarray(byteIndex, byteIndex + 32)); + byteIndex += 32; + argIndex += 1; + } + + return struct; +} + +/** + * Helper function to encode CLI string args to an appropriate JS type. + * @param arg - The CLI argument. + * @param abiType - The type as described by the contract's ABI. + * @returns The encoded argument. + */ +function encodeArg(arg: string, abiType: AbiType, name: string): any { + const { kind } = abiType; + if (kind === 'field' || kind === 'integer') { + let res: bigint; + try { + res = BigInt(arg); + } catch (err) { + throw new Error( + `Invalid value passed for ${name}. Could not parse ${arg} as a${kind === 'integer' ? 'n' : ''} ${kind}.`, + ); + } + return res; + } else if (kind === 'boolean') { + if (arg === 'true') { + return true; + } + if (arg === 'false') { + return false; + } else { + throw Error(`Invalid boolean value passed for ${name}: ${arg}.`); + } + } else if (kind === 'string') { + return arg; + } else if (kind === 'array') { + let arr; + const res = []; + try { + arr = JSON.parse(arg); + } catch { + throw new Error(`Unable to parse arg ${arg} as array for ${name} parameter`); + } + if (!Array.isArray(arr)) { + throw Error(`Invalid argument ${arg} passed for array parameter ${name}.`); + } + if (arr.length !== abiType.length) { + throw Error(`Invalid array length passed for ${name}. Expected ${abiType.length}, received ${arr.length}.`); + } + for (let i = 0; i < abiType.length; i += 1) { + res.push(encodeArg(arr[i], abiType.type, name)); + } + return res; + } else if (kind === 'struct') { + // check if input is encoded long string + if (arg.startsWith('0x')) { + return parseStructString(arg, abiType); + } + let obj; + try { + obj = JSON.parse(arg); + } catch { + throw new Error(`Unable to parse arg ${arg} as struct`); + } + if (Array.isArray(obj)) { + throw Error(`Array passed for arg ${name}. Expected a struct.`); + } + const res: any = {}; + for (const field of abiType.fields) { + // Remove field name from list as it's present + const arg = obj[field.name]; + if (!arg) { + throw Error(`Expected field ${field.name} not found in struct ${name}.`); + } + res[field.name] = encodeArg(obj[field.name], field.type, field.name); + } + return res; + } +} + +/** + * Tries to encode function args to their equivalent TS type. + * @param args - An array of function's / constructor's args. + * @returns The encoded array. + */ +export function encodeArgs(args: any[], params: ABIParameter[]) { + if (args.length !== params.length) { + throw new Error( + `Invalid args provided.\nExpected args: [${params + .map(param => param.name + ': ' + param.type.kind) + .join(', ')}]\nReceived args: ${args.join(', ')}`, + ); + } + return args.map((arg: any, index) => { + const { type, name } = params[index]; + return encodeArg(arg, type, name); + }); +} diff --git a/yarn-project/cli/src/fees.ts b/yarn-project/cli/src/fees.ts new file mode 100644 index 00000000000..4b0a347197b --- /dev/null +++ b/yarn-project/cli/src/fees.ts @@ -0,0 +1,191 @@ +import { + type AccountWallet, + type FeePaymentMethod, + NativeFeePaymentMethod, + NativeFeePaymentMethodWithClaim, + NoFeePaymentMethod, + PrivateFeePaymentMethod, + PublicFeePaymentMethod, + type SendMethodOptions, +} from '@aztec/aztec.js'; +import { AztecAddress, Fr, Gas, GasFees, GasSettings } from '@aztec/circuits.js'; +import { type LogFn } from '@aztec/foundation/log'; + +import { Option } from 'commander'; + +import { parseBigint } from './parse_args.js'; + +export type CliFeeArgs = { + estimateGasOnly: boolean; + inclusionFee?: bigint; + gasLimits?: string; + payment?: string; + estimateGas?: boolean; +}; + +export interface IFeeOpts { + estimateOnly: boolean; + gasSettings: GasSettings; + toSendOpts(sender: AccountWallet): SendMethodOptions; +} + +export function printGasEstimates( + feeOpts: IFeeOpts, + gasEstimates: Pick, + log: LogFn, +) { + const inclusionFee = feeOpts.gasSettings.inclusionFee; + log(`Maximum total tx fee: ${getEstimatedCost(gasEstimates, inclusionFee, GasSettings.default().maxFeesPerGas)}`); + log(`Estimated total tx fee: ${getEstimatedCost(gasEstimates, inclusionFee, GasFees.default())}`); + log(`Estimated gas usage: ${formatGasEstimate(gasEstimates)}`); +} + +function formatGasEstimate(estimate: Pick) { + return `da=${estimate.gasLimits.daGas},l2=${estimate.gasLimits.l2Gas},teardownDA=${estimate.teardownGasLimits.daGas},teardownL2=${estimate.teardownGasLimits.l2Gas}`; +} + +function getEstimatedCost( + estimate: Pick, + inclusionFee: Fr, + fees: GasFees, +) { + return GasSettings.from({ ...GasSettings.default(), ...estimate, inclusionFee, maxFeesPerGas: fees }) + .getFeeLimit() + .toBigInt(); +} + +export class FeeOpts implements IFeeOpts { + constructor( + public estimateOnly: boolean, + public gasSettings: GasSettings, + private paymentMethodFactory: (sender: AccountWallet) => FeePaymentMethod, + private estimateGas: boolean, + ) {} + + toSendOpts(sender: AccountWallet): SendMethodOptions { + return { + estimateGas: this.estimateGas, + fee: { gasSettings: this.gasSettings ?? GasSettings.default(), paymentMethod: this.paymentMethodFactory(sender) }, + }; + } + + static getOptions() { + return [ + new Option('--inclusion-fee ', 'Inclusion fee to pay for the tx.').argParser(parseBigint), + new Option('--gas-limits ', 'Gas limits for the tx.'), + new Option( + '--payment ', + 'Fee payment method and arguments. Valid methods are: none, native, fpc-public, fpc-private.', + ), + new Option('--no-estimate-gas', 'Whether to automatically estimate gas limits for the tx.'), + new Option('--estimate-gas-only', 'Only report gas estimation for the tx, do not send it.'), + ]; + } + + static fromCli(args: CliFeeArgs, log: LogFn) { + const estimateOnly = args.estimateGasOnly; + if (!args.inclusionFee && !args.gasLimits && !args.payment) { + return new NoFeeOpts(estimateOnly); + } + const gasSettings = GasSettings.from({ + ...GasSettings.default(), + ...(args.gasLimits ? parseGasLimits(args.gasLimits) : {}), + ...(args.inclusionFee ? { inclusionFee: new Fr(args.inclusionFee) } : {}), + maxFeesPerGas: GasFees.default(), + }); + return new FeeOpts( + estimateOnly, + gasSettings, + args.payment ? parsePaymentMethod(args.payment, log) : () => new NoFeePaymentMethod(), + !!args.estimateGas, + ); + } +} + +class NoFeeOpts implements IFeeOpts { + constructor(public estimateOnly: boolean) {} + + get gasSettings(): GasSettings { + return GasSettings.default(); + } + + toSendOpts(): SendMethodOptions { + return {}; + } +} + +function parsePaymentMethod(payment: string, log: LogFn): (sender: AccountWallet) => FeePaymentMethod { + const parsed = payment.split(',').reduce((acc, item) => { + const [dimension, value] = item.split('='); + acc[dimension] = value; + return acc; + }, {} as Record); + + const getFpcOpts = (parsed: Record) => { + if (!parsed.fpc) { + throw new Error('Missing "fpc" in payment option'); + } + if (!parsed.asset) { + throw new Error('Missing "asset" in payment option'); + } + + return [AztecAddress.fromString(parsed.asset), AztecAddress.fromString(parsed.fpc)]; + }; + + return (sender: AccountWallet) => { + switch (parsed.method) { + case 'none': + log('Using no fee payment'); + return new NoFeePaymentMethod(); + case 'native': + if (parsed.claimSecret && parsed.claimAmount) { + log(`Using native fee payment method with claim for ${parsed.claimAmount} tokens`); + return new NativeFeePaymentMethodWithClaim( + sender.getAddress(), + BigInt(parsed.claimAmount), + Fr.fromString(parsed.claimSecret), + ); + } else { + log(`Using native fee payment`); + return new NativeFeePaymentMethod(sender.getAddress()); + } + case 'fpc-public': { + const [asset, fpc] = getFpcOpts(parsed); + log(`Using public fee payment with asset ${asset} via paymaster ${fpc}`); + return new PublicFeePaymentMethod(asset, fpc, sender); + } + case 'fpc-private': { + const [asset, fpc] = getFpcOpts(parsed); + const rebateSecret = parsed.rebateSecret ? Fr.fromString(parsed.rebateSecret) : Fr.random(); + log( + `Using private fee payment with asset ${asset} via paymaster ${fpc} with rebate secret ${rebateSecret.toString()}`, + ); + return new PrivateFeePaymentMethod(asset, fpc, sender, rebateSecret); + } + case undefined: + throw new Error('Missing "method" in payment option'); + default: + throw new Error(`Invalid fee payment method: ${payment}`); + } + }; +} + +function parseGasLimits(gasLimits: string): { gasLimits: Gas; teardownGasLimits: Gas } { + const parsed = gasLimits.split(',').reduce((acc, limit) => { + const [dimension, value] = limit.split('='); + acc[dimension] = parseInt(value, 10); + return acc; + }, {} as Record); + + const expected = ['da', 'l2', 'teardownDA', 'teardownL2']; + for (const dimension of expected) { + if (!(dimension in parsed)) { + throw new Error(`Missing gas limit for ${dimension}`); + } + } + + return { + gasLimits: new Gas(parsed.da, parsed.l2), + teardownGasLimits: new Gas(parsed.teardownDA, parsed.teardownL2), + }; +} diff --git a/yarn-project/cli/src/gas_portal.ts b/yarn-project/cli/src/gas_portal.ts new file mode 100644 index 00000000000..aa74367c5e1 --- /dev/null +++ b/yarn-project/cli/src/gas_portal.ts @@ -0,0 +1,154 @@ +// REFACTOR: This file has been shamelessly copied from yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts +// We should make this a shared utility in the aztec.js package. +import { type AztecAddress, type DebugLogger, EthAddress, Fr, type PXE, computeSecretHash } from '@aztec/aztec.js'; +import { GasPortalAbi, OutboxAbi, PortalERC20Abi } from '@aztec/l1-artifacts'; + +import { + type Account, + type Chain, + type GetContractReturnType, + type HttpTransport, + type PublicClient, + type WalletClient, + getContract, +} from 'viem'; + +export interface GasPortalManagerFactoryConfig { + pxeService: PXE; + publicClient: PublicClient; + walletClient: WalletClient; + logger: DebugLogger; +} + +export class GasPortalManagerFactory { + private constructor(private config: GasPortalManagerFactoryConfig) {} + + private async createReal() { + const { pxeService, publicClient, walletClient, logger } = this.config; + + const ethAccount = EthAddress.fromString((await walletClient.getAddresses())[0]); + const l1ContractAddresses = (await pxeService.getNodeInfo()).l1ContractAddresses; + + const gasTokenAddress = l1ContractAddresses.gasTokenAddress; + const gasPortalAddress = l1ContractAddresses.gasPortalAddress; + + if (gasTokenAddress.isZero() || gasPortalAddress.isZero()) { + throw new Error('Gas portal not deployed on L1'); + } + + const outbox = getContract({ + address: l1ContractAddresses.outboxAddress.toString(), + abi: OutboxAbi, + client: walletClient, + }); + + const gasL1 = getContract({ + address: gasTokenAddress.toString(), + abi: PortalERC20Abi, + client: walletClient, + }); + + const gasPortal = getContract({ + address: gasPortalAddress.toString(), + abi: GasPortalAbi, + client: walletClient, + }); + + return new GasPortalManager( + pxeService, + logger, + ethAccount, + gasPortalAddress, + gasPortal, + gasL1, + outbox, + publicClient, + walletClient, + ); + } + + static create(config: GasPortalManagerFactoryConfig): Promise { + const factory = new GasPortalManagerFactory(config); + return factory.createReal(); + } +} + +/** + * A Class for testing cross chain interactions, contains common interactions + * shared between cross chain tests. + */ +class GasPortalManager { + constructor( + /** Private eXecution Environment (PXE). */ + public pxeService: PXE, + /** Logger. */ + public logger: DebugLogger, + /** Eth account to interact with. */ + public ethAccount: EthAddress, + /** Portal address. */ + public tokenPortalAddress: EthAddress, + /** Token portal instance. */ + public tokenPortal: GetContractReturnType>, + /** Underlying token for portal tests. */ + public underlyingERC20: GetContractReturnType>, + /** Message Bridge Outbox. */ + public outbox: GetContractReturnType>, + /** Viem Public client instance. */ + public publicClient: PublicClient, + /** Viem Wallet Client instance. */ + public walletClient: WalletClient, + ) {} + + get l1GasTokenAddress() { + return EthAddress.fromString(this.underlyingERC20.address); + } + + generateClaimSecret(): [Fr, Fr] { + this.logger.debug("Generating a claim secret using pedersen's hash function"); + const secret = Fr.random(); + const secretHash = computeSecretHash(secret); + this.logger.info('Generated claim secret: ' + secretHash.toString()); + return [secret, secretHash]; + } + + async mintTokensOnL1(amount: bigint) { + this.logger.info('Minting tokens on L1'); + await this.publicClient.waitForTransactionReceipt({ + hash: await this.underlyingERC20.write.mint([this.ethAccount.toString(), amount]), + }); + } + + async getL1GasTokenBalance(address: EthAddress) { + return await this.underlyingERC20.read.balanceOf([address.toString()]); + } + + async sendTokensToPortalPublic(bridgeAmount: bigint, l2Address: AztecAddress, secretHash: Fr) { + await this.publicClient.waitForTransactionReceipt({ + hash: await this.underlyingERC20.write.approve([this.tokenPortalAddress.toString(), bridgeAmount]), + }); + + // Deposit tokens to the TokenPortal + this.logger.info('Sending messages to L1 portal to be consumed publicly'); + const args = [l2Address.toString(), bridgeAmount, secretHash.toString()] as const; + const { result: messageHash } = await this.tokenPortal.simulate.depositToAztecPublic(args, { + account: this.ethAccount.toString(), + } as any); + await this.publicClient.waitForTransactionReceipt({ + hash: await this.tokenPortal.write.depositToAztecPublic(args), + }); + + return Fr.fromString(messageHash); + } + + async prepareTokensOnL1(l1TokenBalance: bigint, bridgeAmount: bigint, owner: AztecAddress) { + const [secret, secretHash] = this.generateClaimSecret(); + + // Mint tokens on L1 + await this.mintTokensOnL1(l1TokenBalance); + + // Deposit tokens to the TokenPortal + const msgHash = await this.sendTokensToPortalPublic(bridgeAmount, owner, secretHash); + + return { secret, msgHash, secretHash }; + } +} diff --git a/yarn-project/cli/src/github.ts b/yarn-project/cli/src/github.ts new file mode 100644 index 00000000000..0486c382369 --- /dev/null +++ b/yarn-project/cli/src/github.ts @@ -0,0 +1,3 @@ +export const GITHUB_OWNER = 'AztecProtocol'; +export const GITHUB_REPO = 'aztec-packages'; +export const GITHUB_TAG_PREFIX = 'aztec-packages'; diff --git a/yarn-project/cli/src/index.ts b/yarn-project/cli/src/index.ts new file mode 100644 index 00000000000..489ee5c1c23 --- /dev/null +++ b/yarn-project/cli/src/index.ts @@ -0,0 +1,654 @@ +import { Fr, PublicKeys } from '@aztec/circuits.js'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; +import { fileURLToPath } from '@aztec/foundation/url'; + +import { Command as CommanderCommand, Option } from 'commander'; +import { lookup } from 'dns/promises'; +import { readFileSync } from 'fs'; +import { dirname, resolve } from 'path'; + +import { FeeOpts } from './fees.js'; +import { + parseAztecAddress, + parseBigint, + parseEthereumAddress, + parseField, + parseFieldFromHexString, + parseOptionalAztecAddress, + parseOptionalInteger, + parseOptionalLogId, + parseOptionalSelector, + parseOptionalTxHash, + parsePartialAddress, + parsePrivateKey, + parsePublicKey, + parseTxHash, +} from './parse_args.js'; + +/** + * If we can successfully resolve 'host.docker.internal', then we are running in a container, and we should treat + * localhost as being host.docker.internal. + */ +const getLocalhost = () => + lookup('host.docker.internal') + .then(() => 'host.docker.internal') + .catch(() => 'localhost'); + +const LOCALHOST = await getLocalhost(); +const { ETHEREUM_HOST = `http://${LOCALHOST}:8545`, PRIVATE_KEY, API_KEY, CLI_VERSION } = process.env; + +class Command extends CommanderCommand { + addOptions(options: Option[]) { + options.forEach(option => this.addOption(option)); + return this; + } + + override createCommand(name?: string): Command { + return new Command(name); + } +} + +/** + * Returns commander program that defines the CLI. + * @param log - Console logger. + * @param debugLogger - Debug logger. + * @returns The CLI. + */ +export function getProgram(log: LogFn, debugLogger: DebugLogger): Command { + const program = new Command(); + + const packageJsonPath = resolve(dirname(fileURLToPath(import.meta.url)), '../package.json'); + const cliVersion: string = CLI_VERSION || JSON.parse(readFileSync(packageJsonPath).toString()).version; + const logJson = (obj: object) => log(JSON.stringify(obj, null, 2)); + + program.name('aztec-cli').description('CLI for interacting with Aztec.').version(cliVersion); + + const pxeOption = new Option('-u, --rpc-url ', 'URL of the PXE') + .env('PXE_URL') + .default(`http://${LOCALHOST}:8080`) + .makeOptionMandatory(true); + + const createPrivateKeyOption = (description: string, mandatory: boolean) => + new Option('-e, --private-key ', description) + .env('PRIVATE_KEY') + .argParser(parsePrivateKey) + .makeOptionMandatory(mandatory); + + program + .command('deploy-l1-contracts') + .description('Deploys all necessary Ethereum contracts for Aztec.') + .requiredOption( + '-u, --rpc-url ', + 'Url of the ethereum host. Chain identifiers localhost and testnet can be used', + ETHEREUM_HOST, + ) + .option('-a, --api-key ', 'Api key for the ethereum host', API_KEY) + .requiredOption('-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 options => { + const { deployL1Contracts } = await import('./cmds/deploy_l1_contracts.js'); + await deployL1Contracts( + options.rpcUrl, + options.apiKey ?? '', + options.privateKey, + options.mnemonic, + log, + debugLogger, + ); + }); + + program + .command('deploy-l1-verifier') + .description('Deploys the rollup verifier contract') + .requiredOption( + '--eth-rpc-url ', + 'Url of the ethereum host. Chain identifiers localhost and testnet can be used', + ETHEREUM_HOST, + ) + .addOption(pxeOption) + .requiredOption('-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', + ) + .requiredOption('--verifier ', 'Either mock or real', 'real') + .option('--bb ', 'Path to bb binary') + .option('--bb-working-dir ', 'Path to bb working directory') + .action(async options => { + const { deployMockVerifier, deployUltraVerifier } = await import('./cmds/deploy_l1_verifier.js'); + if (options.verifier === 'mock') { + await deployMockVerifier( + options.ethRpcUrl, + options.privateKey, + options.mnemonic, + options.rpcUrl, + log, + debugLogger, + ); + } else { + await deployUltraVerifier( + options.ethRpcUrl, + options.privateKey, + options.mnemonic, + options.rpcUrl, + options.bb, + options.bbWorkingDir, + log, + debugLogger, + ); + } + }); + + program + .command('bridge-l1-gas') + .description('Mints L1 gas tokens and pushes them to L2.') + .argument('', 'The amount of gas tokens to mint and bridge.', parseBigint) + .argument('', 'Aztec address of the recipient.', parseAztecAddress) + .requiredOption( + '--l1-rpc-url ', + 'Url of the ethereum host. Chain identifiers localhost and testnet can be used', + ETHEREUM_HOST, + ) + .option('-a, --api-key ', 'Api key for the ethereum host', API_KEY) + .option( + '-m, --mnemonic ', + 'The mnemonic to use for deriving the Ethereum address that will mint and bridge', + 'test test test test test test test test test test test junk', + ) + .addOption(pxeOption) + .action(async (amount, recipient, options) => { + const { bridgeL1Gas } = await import('./cmds/bridge_l1_gas.js'); + await bridgeL1Gas( + amount, + recipient, + options.rpcUrl, + options.l1RpcUrl, + options.apiKey ?? '', + options.mnemonic, + log, + debugLogger, + ); + }); + + program + .command('get-l1-balance') + .description('Gets the balance of gas tokens in L1 for the given Ethereum address.') + .argument('', 'Ethereum address to check.', parseEthereumAddress) + .requiredOption( + '--l1-rpc-url ', + 'Url of the ethereum host. Chain identifiers localhost and testnet can be used', + ETHEREUM_HOST, + ) + .option('-a, --api-key ', 'Api key for the ethereum host', API_KEY) + .addOption(pxeOption) + .action(async (who, options) => { + const { getL1Balance } = await import('./cmds/get_l1_balance.js'); + await getL1Balance(who, options.rpcUrl, options.l1RpcUrl, options.apiKey ?? '', log, debugLogger); + }); + + program + .command('generate-keys') + .summary('Generates encryption and signing private keys.') + .description('Generates and encryption and signing private key pair.') + .option( + '-m, --mnemonic', + 'An optional mnemonic string used for the private key generation. If not provided, random private key will be generated.', + ) + .action(async _options => { + const { generateKeys } = await import('./cmds/generate_private_key.js'); + const { privateEncryptionKey, privateSigningKey } = generateKeys(); + log(`Encryption Private Key: ${privateEncryptionKey}\nSigning Private key: ${privateSigningKey}\n`); + }); + + program + .command('generate-p2p-private-key') + .summary('Generates a LibP2P peer private key.') + .description('Generates a private key that can be used for running a node on a LibP2P network.') + .action(async () => { + const { generateP2PPrivateKey } = await import('./cmds/generate_p2p_private_key.js'); + await generateP2PPrivateKey(log); + }); + + program + .command('create-account') + .description( + '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.') + .addOption(createPrivateKeyOption('Private key for account. Uses random by default.', false)) + .addOption(pxeOption) + .addOptions(FeeOpts.getOptions()) + .option( + '--register-only', + 'Just register the account on the PXE. Do not deploy or initialize the account contract.', + ) + // `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 args => { + const { createAccount } = await import('./cmds/create_account.js'); + const { rpcUrl, privateKey, wait, registerOnly } = args; + await createAccount(rpcUrl, privateKey, registerOnly, wait, FeeOpts.fromCli(args, log), debugLogger, log); + }); + + program + .command('register-account') + .description( + 'Registers an aztec account that can be used for sending transactions. Registers the account on the PXE. Uses a Schnorr single-key account which uses the same key for encryption and authentication (not secure for production usage).', + ) + .summary('Registers an aztec account that can be used for sending transactions.') + .addOption(createPrivateKeyOption('Private key for account.', true)) + .requiredOption( + '-pa, --partial-address ', + 'The partially computed address of the account contract.', + parsePartialAddress, + ) + .addOption(pxeOption) + .action(async ({ rpcUrl, privateKey, partialAddress }) => { + const { registerAccount } = await import('./cmds/register_account.js'); + await registerAccount(rpcUrl, privateKey, partialAddress, debugLogger, log); + }); + + program + .command('bootstrap') + .description('Bootstrap the blockchain') + .addOption(pxeOption) + .action(async options => { + const { bootstrap } = await import('./cmds/bootstrap.js'); + await bootstrap(options.rpcUrl, log); + }); + + program + .command('deploy') + .description('Deploys a compiled Aztec.nr contract to Aztec.') + .argument( + '', + "A compiled Aztec.nr contract's artifact in JSON format or name of a contract artifact exported by @aztec/noir-contracts.js", + ) + .option('--initialize ', 'The contract initializer function to call', 'constructor') + .option('--no-initialize') + .option('-a, --args ', 'Contract constructor arguments', []) + .addOption(pxeOption) + .option( + '-k, --public-key ', + '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 ', + 'Optional deployment salt as a hex string for generating the deployment address.', + parseFieldFromHexString, + ) + .option('--universal', 'Do not mix the sender address into the deployment.') + .addOption(createPrivateKeyOption("The sender's private key.", true)) + .option('--json', 'Emit output as json') + // `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') + .option('--class-registration', 'Register the contract class. Only has to be done once') + .option('--no-class-registration', 'Skip registering the contract class') + .option('--public-deployment', 'Deploy the public bytecode of contract') + .option('--no-public-deployment', "Skip deploying the contract's public bytecode") + .addOptions(FeeOpts.getOptions()) + .action(async (artifactPath, opts) => { + const { deploy } = await import('./cmds/deploy.js'); + const { + json, + rpcUrl, + publicKey, + args: rawArgs, + salt, + wait, + privateKey, + classRegistration, + initialize, + publicDeployment, + universal, + } = opts; + await deploy( + artifactPath, + json, + rpcUrl, + publicKey ? PublicKeys.fromString(publicKey) : undefined, + rawArgs, + salt, + privateKey, + typeof initialize === 'string' ? initialize : undefined, + !publicDeployment, + !classRegistration, + typeof initialize === 'string' ? false : initialize, + universal, + wait, + FeeOpts.fromCli(opts, log), + debugLogger, + log, + logJson, + ); + }); + + program + .command('check-deploy') + .description('Checks if a contract is deployed to the specified Aztec address.') + .requiredOption( + '-ca, --contract-address
', + 'An Aztec address to check if contract has been deployed to.', + parseAztecAddress, + ) + .addOption(pxeOption) + .action(async options => { + const { checkDeploy } = await import('./cmds/check_deploy.js'); + await checkDeploy(options.rpcUrl, options.contractAddress, debugLogger, log); + }); + + program + .command('add-contract') + .description( + 'Adds an existing contract to the PXE. This is useful if you have deployed a contract outside of the PXE and want to use it with the PXE.', + ) + .requiredOption( + '-c, --contract-artifact ', + "A compiled Aztec.nr contract's ABI in JSON format or name of a contract ABI exported by @aztec/noir-contracts.js", + ) + .requiredOption('-ca, --contract-address
', 'Aztec address of the contract.', parseAztecAddress) + .requiredOption('--init-hash ', 'Initialization hash', parseFieldFromHexString) + .option('--salt ', 'Optional deployment salt', parseFieldFromHexString) + .option('-p, --public-key ', 'Optional public key for this contract', parsePublicKey) + .option('--portal-address
', 'Optional address to a portal contract on L1', parseEthereumAddress) + .option('--deployer-address
', 'Optional address of the contract deployer', parseAztecAddress) + .addOption(pxeOption) + .action(async options => { + const { addContract } = await import('./cmds/add_contract.js'); + await addContract( + options.rpcUrl, + options.contractArtifact, + options.contractAddress, + options.initHash, + options.salt ?? Fr.ZERO, + options.publicKey, + options.deployerAddress, + debugLogger, + log, + ); + }); + + program + .command('get-tx-receipt') + .description('Gets the receipt for the specified transaction hash.') + .argument('', 'A transaction hash to get the receipt for.', parseTxHash) + .addOption(pxeOption) + .action(async (txHash, options) => { + const { getTxReceipt } = await import('./cmds/get_tx_receipt.js'); + await getTxReceipt(options.rpcUrl, txHash, debugLogger, log); + }); + + program + .command('get-contract-data') + .description('Gets information about the Aztec contract deployed at the specified address.') + .argument('', 'Aztec address of the contract.', parseAztecAddress) + .addOption(pxeOption) + .option('-b, --include-bytecode ', "Include the contract's public function bytecode, if any.", false) + .action(async (contractAddress, options) => { + const { getContractData } = await import('./cmds/get_contract_data.js'); + await getContractData(options.rpcUrl, contractAddress, options.includeBytecode, debugLogger, log); + }); + + program + .command('get-logs') + .description('Gets all the unencrypted logs from an intersection of all the filter params.') + .option('-tx, --tx-hash ', 'A transaction hash to get the receipt for.', parseOptionalTxHash) + .option( + '-fb, --from-block ', + 'Initial block number for getting logs (defaults to 1).', + parseOptionalInteger, + ) + .option('-tb, --to-block ', 'Up to which block to fetch logs (defaults to latest).', parseOptionalInteger) + .option('-al --after-log ', 'ID of a log after which to fetch the logs.', parseOptionalLogId) + .option('-ca, --contract-address
', 'Contract address to filter logs by.', parseOptionalAztecAddress) + .option('-s, --selector ', 'Event selector to filter logs by.', parseOptionalSelector) + .addOption(pxeOption) + .option('--follow', 'If set, will keep polling for new logs until interrupted.') + .action(async ({ txHash, fromBlock, toBlock, afterLog, contractAddress, selector, rpcUrl, follow }) => { + const { getLogs } = await import('./cmds/get_logs.js'); + await getLogs(txHash, fromBlock, toBlock, afterLog, contractAddress, selector, rpcUrl, follow, debugLogger, log); + }); + + program + .command('register-recipient') + .description('Register a recipient in the PXE.') + .requiredOption('-a, --address ', "The account's Aztec address.", parseAztecAddress) + .requiredOption('-p, --public-key ', 'The account public key.', parsePublicKey) + .requiredOption( + '-pa, --partial-address ', + 'The partially computed address of the account contract.', + parsePartialAddress, + ) + .addOption(pxeOption) + .action(async ({ address, publicKey, partialAddress, rpcUrl }) => { + const { registerRecipient } = await import('./cmds/register_recipient.js'); + await registerRecipient(address, publicKey, partialAddress, rpcUrl, debugLogger, log); + }); + + program + .command('get-accounts') + .description('Gets all the Aztec accounts stored in the PXE.') + .addOption(pxeOption) + .option('--json', 'Emit output as json') + .action(async (options: any) => { + const { getAccounts } = await import('./cmds/get_accounts.js'); + await getAccounts(options.rpcUrl, options.json, debugLogger, log, logJson); + }); + + program + .command('get-account') + .description('Gets an account given its Aztec address.') + .argument('
', 'The Aztec address to get account for', parseAztecAddress) + .addOption(pxeOption) + .action(async (address, options) => { + const { getAccount } = await import('./cmds/get_account.js'); + await getAccount(address, options.rpcUrl, debugLogger, log); + }); + + program + .command('get-recipients') + .description('Gets all the recipients stored in the PXE.') + .addOption(pxeOption) + .action(async (options: any) => { + const { getRecipients } = await import('./cmds/get_recipients.js'); + await getRecipients(options.rpcUrl, debugLogger, log); + }); + + program + .command('get-recipient') + .description('Gets a recipient given its Aztec address.') + .argument('
', 'The Aztec address to get recipient for', parseAztecAddress) + .addOption(pxeOption) + .action(async (address, options) => { + const { getRecipient } = await import('./cmds/get_recipient.js'); + await getRecipient(address, options.rpcUrl, debugLogger, log); + }); + + program + .command('get-balance') + .description('Gets the token balance for an account. Does NOT format according to decimals.') + .argument('
', 'Aztec address to query balance for.', parseAztecAddress) + .option('-t, --token-address
', 'Token address to query balance for (defaults to gas token).') + .addOption(pxeOption) + .action(async (address, options) => { + const { getBalance } = await import('./cmds/get_balance.js'); + await getBalance(address, options.tokenAddress, options.rpcUrl, debugLogger, log); + }); + + program + .command('send') + .description('Calls a function on an Aztec contract.') + .argument('', 'Name of function to execute') + .option('-a, --args [functionArgs...]', 'Function arguments', []) + .requiredOption( + '-c, --contract-artifact ', + "A compiled Aztec.nr contract's ABI in JSON format or name of a contract ABI exported by @aztec/noir-contracts.js", + ) + .requiredOption('-ca, --contract-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') + .addOptions(FeeOpts.getOptions()) + .action(async (functionName, options) => { + const { send } = await import('./cmds/send.js'); + await send( + functionName, + options.args, + options.contractArtifact, + options.contractAddress, + options.privateKey, + options.rpcUrl, + !options.noWait, + FeeOpts.fromCli(options, log), + debugLogger, + log, + ); + }); + + program + .command('call') + .description( + 'Simulates the execution of a view (read-only) function on a deployed contract, without modifying state.', + ) + .argument('', 'Name of function to call') + .option('-a, --args [functionArgs...]', 'Function arguments', []) + .requiredOption( + '-c, --contract-artifact ', + "A compiled Aztec.nr contract's ABI in JSON format or name of a contract ABI exported by @aztec/noir-contracts.js", + ) + .requiredOption('-ca, --contract-address
', 'Aztec address of the contract.', parseAztecAddress) + .option('-f, --from ', 'Aztec address of the caller. If empty, will use the first account from RPC.') + .addOption(pxeOption) + .action(async (functionName, options) => { + const { call } = await import('./cmds/call.js'); + await call( + functionName, + options.args, + options.contractArtifact, + options.contractAddress, + options.from, + options.rpcUrl, + debugLogger, + log, + ); + }); + + program + .command('add-note') + .description('Adds a note to the database in the PXE.') + .argument('
', 'The Aztec address of the note owner.', parseAztecAddress) + .argument('', 'Aztec address of the contract.', parseAztecAddress) + .argument('', 'The storage slot of the note.', parseField) + .argument('', 'The type ID of the note.', parseField) + .argument('', 'The tx hash of the tx containing the note.', parseTxHash) + .requiredOption('-n, --note [note...]', 'The members of a Note serialized as hex strings.', []) + .addOption(pxeOption) + .action(async (address, contractAddress, storageSlot, noteTypeId, txHash, options) => { + const { addNote } = await import('./cmds/add_note.js'); + await addNote( + address, + contractAddress, + storageSlot, + noteTypeId, + txHash, + options.note, + options.rpcUrl, + debugLogger, + ); + }); + + program + .command('add-pending-shield') + .description('Adds a pending shield note to the database in the PXE.') + .argument('
', 'Aztec address of the note owner.', parseAztecAddress) + .argument('', 'Amount of the pending shield note.', parseBigint) + .requiredOption('-ca, --contract-address
', 'Aztec address of the token contract.', parseAztecAddress) + .requiredOption('-tx, --tx-hash ', 'Tx hash in which the note was created.', parseOptionalTxHash) + .requiredOption('--secret ', 'Secret used for shielding the note.', parseField) + .addOption(pxeOption) + .action(async (address, amount, options) => { + const { addPendingShield } = await import('./cmds/add_pending_shield.js'); + await addPendingShield( + address, + options.contractAddress, + amount, + options.secret, + options.txHash, + options.rpcUrl, + debugLogger, + log, + ); + }); + + // 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') + .requiredOption( + '-c, --contract-artifact ', + "A compiled Aztec.nr contract's ABI in JSON format or name of a contract ABI exported by @aztec/noir-contracts.js", + ) + .requiredOption('-p, --parameter ', 'The name of the struct parameter to decode into') + .action(async (encodedString, options) => { + const { parseParameterStruct } = await import('./cmds/parse_parameter_struct.js'); + await parseParameterStruct(encodedString, options.contractArtifact, options.parameter, log); + }); + + program + .command('block-number') + .description('Gets the current Aztec L2 block number.') + .addOption(pxeOption) + .action(async (options: any) => { + const { blockNumber } = await import('./cmds/block_number.js'); + await blockNumber(options.rpcUrl, debugLogger, log); + }); + + program + .command('example-contracts') + .description('Lists the example contracts available to deploy from @aztec/noir-contracts.js') + .action(async () => { + const { exampleContracts } = await import('./cmds/example_contracts.js'); + await exampleContracts(log); + }); + + program + .command('get-node-info') + .description('Gets the information of an aztec node at a URL.') + .addOption(pxeOption) + .action(async options => { + const { getNodeInfo } = await import('./cmds/get_node_info.js'); + await getNodeInfo(options.rpcUrl, debugLogger, log); + }); + + program + .command('inspect-contract') + .description('Shows list of external callable functions for a contract') + .argument( + '', + `A compiled Noir contract's artifact in JSON format or name of a contract artifact exported by @aztec/noir-contracts.js`, + ) + .action(async (contractArtifactFile: string) => { + const { inspectContract } = await import('./cmds/inspect_contract.js'); + await inspectContract(contractArtifactFile, debugLogger, log); + }); + + program + .command('compute-selector') + .description('Given a function signature, it computes a selector') + .argument('', 'Function signature to compute selector for e.g. foo(Field)') + .action(async (functionSignature: string) => { + const { computeSelector } = await import('./cmds/compute_selector.js'); + computeSelector(functionSignature, log); + }); + + return program; +} diff --git a/yarn-project/cli/src/parse_args.ts b/yarn-project/cli/src/parse_args.ts new file mode 100644 index 00000000000..d4b49c8cc16 --- /dev/null +++ b/yarn-project/cli/src/parse_args.ts @@ -0,0 +1,250 @@ +import { FunctionSelector } from '@aztec/aztec.js/abi'; +import { AztecAddress } from '@aztec/aztec.js/aztec_address'; +import { EthAddress } from '@aztec/aztec.js/eth_address'; +import { Fr } from '@aztec/aztec.js/fields'; +import { LogId } from '@aztec/aztec.js/log_id'; +import { TxHash } from '@aztec/aztec.js/tx_hash'; +import { PublicKeys } from '@aztec/circuits.js'; + +import { InvalidArgumentError } from 'commander'; + +/** + * Removes the leading 0x from a hex string. If no leading 0x is found the string is returned unchanged. + * @param hex - A hex string + * @returns A new string with leading 0x removed + */ +const stripLeadingHex = (hex: string) => { + if (hex.length > 2 && hex.startsWith('0x')) { + return hex.substring(2); + } + return hex; +}; + +export function parseBigint(bigint: string): bigint | undefined { + return bigint ? BigInt(bigint) : undefined; +} + +/** + * Parses a hex encoded string to an Fr integer + * @param str - Hex encoded string + * @returns A integer + */ +export function parseFieldFromHexString(str: string): Fr { + const hex = stripLeadingHex(str); + + // ensure it's a hex string + if (!hex.match(/^[0-9a-f]+$/i)) { + throw new InvalidArgumentError('Invalid hex string'); + } + + // pad it so that we may read it as a buffer. + // Buffer needs _exactly_ two hex characters per byte + const padded = hex.length % 2 === 1 ? '0' + hex : hex; + + // finally, turn it into an integer + return Fr.fromBuffer(Buffer.from(padded, 'hex')); +} + +/** + * Parses an AztecAddress from a string. + * @param address - A serialized Aztec address + * @returns An Aztec address + * @throws InvalidArgumentError if the input string is not valid. + */ +export function parseAztecAddress(address: string): AztecAddress { + try { + return AztecAddress.fromString(address); + } catch { + throw new InvalidArgumentError(`Invalid address: ${address}`); + } +} + +/** + * Parses an Ethereum address from a string. + * @param address - A serialized Ethereum address + * @returns An Ethereum address + * @throws InvalidArgumentError if the input string is not valid. + */ +export function parseEthereumAddress(address: string): EthAddress { + try { + return EthAddress.fromString(address); + } catch { + throw new InvalidArgumentError(`Invalid address: ${address}`); + } +} + +/** + * Parses an AztecAddress from a string. + * @param address - A serialized Aztec address + * @returns An Aztec address + * @throws InvalidArgumentError if the input string is not valid. + */ +export function parseOptionalAztecAddress(address: string): AztecAddress | undefined { + if (!address) { + return undefined; + } + return parseAztecAddress(address); +} + +/** + * Parses an optional log ID string into a LogId object. + * + * @param logId - The log ID string to parse. + * @returns The parsed LogId object, or undefined if the log ID is missing or empty. + */ +export function parseOptionalLogId(logId: string): LogId | undefined { + if (!logId) { + return undefined; + } + return LogId.fromString(logId); +} + +/** + * Parses a selector from a string. + * @param selector - A serialized selector. + * @returns A selector. + * @throws InvalidArgumentError if the input string is not valid. + */ +export function parseOptionalSelector(selector: string): FunctionSelector | undefined { + if (!selector) { + return undefined; + } + try { + return FunctionSelector.fromString(selector); + } catch { + throw new InvalidArgumentError(`Invalid selector: ${selector}`); + } +} + +/** + * Parses a string into an integer or returns undefined if the input is falsy. + * + * @param value - The string to parse into an integer. + * @returns The parsed integer, or undefined if the input string is falsy. + * @throws If the input is not a valid integer. + */ +export function parseOptionalInteger(value: string): number | undefined { + if (!value) { + return undefined; + } + const parsed = Number(value); + if (!Number.isInteger(parsed)) { + throw new InvalidArgumentError('Invalid integer.'); + } + return parsed; +} + +/** + * Parses a TxHash from a string. + * @param txHash - A transaction hash + * @returns A TxHash instance + * @throws InvalidArgumentError if the input string is not valid. + */ +export function parseTxHash(txHash: string): TxHash { + try { + return TxHash.fromString(txHash); + } catch { + throw new InvalidArgumentError(`Invalid transaction hash: ${txHash}`); + } +} + +/** + * Parses an optional TxHash from a string. + * Calls parseTxHash internally. + * @param txHash - A transaction hash + * @returns A TxHash instance, or undefined if the input string is falsy. + * @throws InvalidArgumentError if the input string is not valid. + */ +export function parseOptionalTxHash(txHash: string): TxHash | undefined { + if (!txHash) { + return undefined; + } + return parseTxHash(txHash); +} + +/** + * Parses a public key from a string. + * @param publicKey - A public keys object serialised as a string + * @returns A PublicKeys instance + * @throws InvalidArgumentError if the input string is not valid. + */ +export function parsePublicKey(publicKey: string): PublicKeys | undefined { + if (!publicKey) { + return undefined; + } + try { + return PublicKeys.fromString(publicKey); + } catch (err) { + throw new InvalidArgumentError(`Invalid public key: ${publicKey}`); + } +} + +/** + * Parses a partial address from a string. + * @param address - A partial address + * @returns A Fr instance + * @throws InvalidArgumentError if the input string is not valid. + */ +export function parsePartialAddress(address: string): Fr { + try { + return Fr.fromString(address); + } catch (err) { + throw new InvalidArgumentError(`Invalid partial address: ${address}`); + } +} + +/** + * Parses a private key from a string. + * @param privateKey - A string + * @returns A private key + * @throws InvalidArgumentError if the input string is not valid. + */ +export function parsePrivateKey(privateKey: string): Fr { + try { + return Fr.fromString(privateKey); + } catch (err) { + throw new InvalidArgumentError(`Invalid encryption private key: ${privateKey}`); + } +} + +/** + * Parses a field from a string. + * @param field - A string representing the field. + * @returns A field. + * @throws InvalidArgumentError if the input string is not valid. + */ +export function parseField(field: string): Fr { + try { + const isHex = field.startsWith('0x') || field.match(new RegExp(`^[0-9a-f]{${Fr.SIZE_IN_BYTES * 2}}$`, 'i')); + if (isHex) { + return Fr.fromString(field); + } + + if (['true', 'false'].includes(field)) { + return new Fr(field === 'true'); + } + + const isNumber = +field || field === '0'; + if (isNumber) { + return new Fr(BigInt(field)); + } + + const isBigInt = field.endsWith('n'); + if (isBigInt) { + return new Fr(BigInt(field.replace(/n$/, ''))); + } + + return new Fr(BigInt(field)); + } catch (err) { + throw new InvalidArgumentError(`Invalid field: ${field}`); + } +} + +/** + * Parses an array of strings to Frs. + * @param fields - An array of strings representing the fields. + * @returns An array of Frs. + */ +export function parseFields(fields: string[]): Fr[] { + return fields.map(parseField); +} diff --git a/yarn-project/cli/src/utils.ts b/yarn-project/cli/src/utils.ts new file mode 100644 index 00000000000..7917d77156f --- /dev/null +++ b/yarn-project/cli/src/utils.ts @@ -0,0 +1,241 @@ +import { type ContractArtifact, type FunctionArtifact, loadContractArtifact } from '@aztec/aztec.js/abi'; +import { AztecAddress } from '@aztec/aztec.js/aztec_address'; +import { type L1ContractArtifactsForDeployment } from '@aztec/aztec.js/ethereum'; +import { type PXE } from '@aztec/aztec.js/interfaces/pxe'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; +import { type NoirPackageConfig } from '@aztec/foundation/noir'; +import { + AvailabilityOracleAbi, + AvailabilityOracleBytecode, + GasPortalAbi, + GasPortalBytecode, + PortalERC20Abi, + PortalERC20Bytecode, +} from '@aztec/l1-artifacts'; + +import TOML from '@iarna/toml'; +import { CommanderError, InvalidArgumentError } from 'commander'; +import { readFile, rename, writeFile } from 'fs/promises'; + +import { encodeArgs } from './encoding.js'; + +/** + * Helper type to dynamically import contracts. + */ +interface ArtifactsType { + [key: string]: ContractArtifact; +} + +/** + * Helper to get an ABI function or throw error if it doesn't exist. + * @param artifact - Contract's build artifact in JSON format. + * @param fnName - Function name to be found. + * @returns The function's ABI. + */ +export function getFunctionArtifact(artifact: ContractArtifact, fnName: string): FunctionArtifact { + const fn = artifact.functions.find(({ name }) => name === fnName); + if (!fn) { + throw Error(`Function ${fnName} not found in contract ABI.`); + } + return fn; +} + +/** + * Function to execute the 'deployRollupContracts' command. + * @param rpcUrl - The RPC URL of the ethereum node. + * @param apiKey - The api key of the ethereum node endpoint. + * @param privateKey - The private key to be used in contract deployment. + * @param mnemonic - The mnemonic to be used in contract deployment. + */ +export async function deployAztecContracts( + rpcUrl: string, + apiKey: string, + privateKey: string, + mnemonic: string, + debugLogger: DebugLogger, +) { + const { + InboxAbi, + InboxBytecode, + OutboxAbi, + OutboxBytecode, + RegistryAbi, + RegistryBytecode, + RollupAbi, + RollupBytecode, + } = await import('@aztec/l1-artifacts'); + const { createEthereumChain, deployL1Contracts } = await import('@aztec/ethereum'); + const { mnemonicToAccount, privateKeyToAccount } = await import('viem/accounts'); + + const account = !privateKey + ? mnemonicToAccount(mnemonic!) + : privateKeyToAccount(`${privateKey.startsWith('0x') ? '' : '0x'}${privateKey}` as `0x${string}`); + const chain = createEthereumChain(rpcUrl, apiKey); + 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, + }, + }; + return await deployL1Contracts(chain.rpcUrl, account, chain.chainInfo, debugLogger, l1Artifacts); +} + +/** + * Gets all contracts available in \@aztec/noir-contracts.js. + * @returns The contract ABIs. + */ +export async function getExampleContractArtifacts(): Promise { + const imports = await import('@aztec/noir-contracts.js'); + return Object.fromEntries(Object.entries(imports).filter(([key]) => key.endsWith('Artifact'))) as any; +} + +/** + * Reads a file and converts it to an Aztec Contract ABI. + * @param fileDir - The directory of the compiled contract ABI. + * @returns The parsed contract artifact. + */ +export async function getContractArtifact(fileDir: string, log: LogFn) { + // first check if it's a noir-contracts example + const artifacts = await getExampleContractArtifacts(); + for (const key of [fileDir, fileDir + 'Artifact', fileDir + 'ContractArtifact']) { + if (artifacts[key]) { + return artifacts[key] as ContractArtifact; + } + } + + let contents: string; + try { + contents = await readFile(fileDir, 'utf8'); + } catch { + throw Error(`Contract ${fileDir} not found`); + } + + try { + return loadContractArtifact(JSON.parse(contents)); + } catch (err) { + log('Invalid file used. Please try again.'); + throw err; + } +} + +/** + * Utility to select a TX sender either from user input + * or from the first account that is found in a PXE instance. + * @param pxe - The PXE 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(pxe: PXE, _from?: string) { + let from: AztecAddress; + if (_from) { + try { + from = AztecAddress.fromString(_from); + } catch { + throw new InvalidArgumentError(`Invalid option 'from' passed: ${_from}`); + } + } else { + const accounts = await pxe.getRegisteredAccounts(); + if (!accounts.length) { + throw new Error('No accounts found in PXE instance.'); + } + from = accounts[0].address; + } + return from; +} + +/** + * Performs necessary checks, conversions & operations to call a contract fn from the CLI. + * @param contractFile - Directory of the compiled contract ABI. + * @param functionName - Name of the function to be called. + * @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. + */ +export async function prepTx(contractFile: string, functionName: string, _functionArgs: string[], log: LogFn) { + const contractArtifact = await getContractArtifact(contractFile, log); + const functionArtifact = getFunctionArtifact(contractArtifact, functionName); + const functionArgs = encodeArgs(_functionArgs, functionArtifact.parameters); + + return { functionArgs, contractArtifact }; +} + +/** + * Removes the leading 0x from a hex string. If no leading 0x is found the string is returned unchanged. + * @param hex - A hex string + * @returns A new string with leading 0x removed + */ +export const stripLeadingHex = (hex: string) => { + if (hex.length > 2 && hex.startsWith('0x')) { + return hex.substring(2); + } + return hex; +}; + +/** + * Updates a file in place atomically. + * @param filePath - Path to file + * @param contents - New contents to write + */ +export async function atomicUpdateFile(filePath: string, contents: string) { + const tmpFilepath = filePath + '.tmp'; + try { + await writeFile(tmpFilepath, contents, { + // let's crash if the tmp file already exists + flag: 'wx', + }); + await rename(tmpFilepath, filePath); + } catch (e) { + if (e instanceof Error && 'code' in e && e.code === 'EEXIST') { + const commanderError = new CommanderError( + 1, + e.code, + `Temporary file already exists: ${tmpFilepath}. Delete this file and try again.`, + ); + commanderError.nestedError = e.message; + throw commanderError; + } else { + throw e; + } + } +} + +/** + * Pretty prints Nargo.toml contents to a string + * @param config - Nargo.toml contents + * @returns The Nargo.toml contents as a string + */ +export function prettyPrintNargoToml(config: NoirPackageConfig): string { + const withoutDependencies = Object.fromEntries(Object.entries(config).filter(([key]) => key !== 'dependencies')); + + const partialToml = TOML.stringify(withoutDependencies); + const dependenciesToml = Object.entries(config.dependencies).map(([name, dep]) => { + const depToml = TOML.stringify.value(dep); + return `${name} = ${depToml}`; + }); + + return partialToml + '\n[dependencies]\n' + dependenciesToml.join('\n') + '\n'; +} diff --git a/yarn-project/cli/tsconfig.json b/yarn-project/cli/tsconfig.json new file mode 100644 index 00000000000..80b20c28830 --- /dev/null +++ b/yarn-project/cli/tsconfig.json @@ -0,0 +1,48 @@ +{ + "extends": "..", + "compilerOptions": { + "outDir": "dest", + "rootDir": "src", + "tsBuildInfoFile": ".tsbuildinfo" + }, + "references": [ + { + "path": "../accounts" + }, + { + "path": "../aztec.js" + }, + { + "path": "../bb-prover" + }, + { + "path": "../circuit-types" + }, + { + "path": "../circuits.js" + }, + { + "path": "../ethereum" + }, + { + "path": "../foundation" + }, + { + "path": "../l1-artifacts" + }, + { + "path": "../noir-contracts.js" + }, + { + "path": "../protocol-contracts" + }, + { + "path": "../simulator" + }, + { + "path": "../types" + } + ], + "include": ["src"], + "exclude": ["contracts"] +} diff --git a/yarn-project/end-to-end/src/composed/integration_proof_verification.test.ts b/yarn-project/end-to-end/src/composed/integration_proof_verification.test.ts index 62e1f5553e9..9b9e178c717 100644 --- a/yarn-project/end-to-end/src/composed/integration_proof_verification.test.ts +++ b/yarn-project/end-to-end/src/composed/integration_proof_verification.test.ts @@ -63,8 +63,6 @@ describe('proof_verification', () => { const acvm = await getACVMConfig(logger); circuitVerifier = await BBCircuitVerifier.new({ - acvmBinaryPath: acvm!.acvmBinaryPath, - acvmWorkingDirectory: acvm!.acvmWorkingDirectory, bbBinaryPath: bb!.bbBinaryPath, bbWorkingDirectory: bb!.bbWorkingDirectory, }); diff --git a/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts b/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts index ed421629e07..27b90f29185 100644 --- a/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts +++ b/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts @@ -151,10 +151,7 @@ export class FullProverTest { throw new Error(`Test must be run with BB native configuration`); } - this.circuitProofVerifier = await BBCircuitVerifier.new({ - ...acvmConfig, - ...bbConfig, - }); + this.circuitProofVerifier = await BBCircuitVerifier.new(bbConfig); this.proverPool = ProverPool.nativePool( { diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index 8bb1f092b06..c842f6a6c5c 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -95,7 +95,7 @@ export interface L1ContractArtifactsForDeployment { */ export function createL1Clients( rpcUrl: string, - mnemonicOrHdAccount: string | HDAccount, + mnemonicOrHdAccount: string | HDAccount | PrivateKeyAccount, chain: Chain = foundry, ): { publicClient: PublicClient; walletClient: WalletClient } { const hdAccount = diff --git a/yarn-project/foundation/src/abi/encoder.test.ts b/yarn-project/foundation/src/abi/encoder.test.ts index 82c2d569ac7..b9a54c993fa 100644 --- a/yarn-project/foundation/src/abi/encoder.test.ts +++ b/yarn-project/foundation/src/abi/encoder.test.ts @@ -167,7 +167,7 @@ describe('abi/encoder', () => { }; const args = ['garbage']; - expect(() => encodeArguments(testFunctionAbi, args)).toThrow('Invalid argument "garbage" of type field'); + expect(() => encodeArguments(testFunctionAbi, args)).toThrow('Invalid hex-encoded string: "garbage"'); }); it('throws when passing string argument as integer', () => { @@ -191,9 +191,7 @@ describe('abi/encoder', () => { returnTypes: [], }; const args = ['garbage']; - expect(() => encodeArguments(testFunctionAbi, args)).toThrow( - `Type 'string' with value 'garbage' passed to BaseField ctor.`, - ); + expect(() => encodeArguments(testFunctionAbi, args)).toThrow(`Cannot convert garbage to a BigInt`); }); it('throws when passing object argument as field', () => { diff --git a/yarn-project/foundation/src/abi/encoder.ts b/yarn-project/foundation/src/abi/encoder.ts index 119554935af..22b31c63144 100644 --- a/yarn-project/foundation/src/abi/encoder.ts +++ b/yarn-project/foundation/src/abi/encoder.ts @@ -46,6 +46,8 @@ class ArgumentEncoder { this.flattened.push(new Fr(BigInt(arg))); } else if (typeof arg === 'bigint') { this.flattened.push(new Fr(arg)); + } else if (typeof arg === 'string') { + this.flattened.push(Fr.fromString(arg)); } else if (typeof arg === 'boolean') { this.flattened.push(new Fr(arg ? 1n : 0n)); } else if (typeof arg === 'object') { @@ -103,7 +105,12 @@ class ArgumentEncoder { break; } case 'integer': - this.flattened.push(new Fr(arg)); + if (typeof arg === 'string') { + const value = BigInt(arg); + this.flattened.push(new Fr(value)); + } else { + this.flattened.push(new Fr(arg)); + } break; default: throw new Error(`Unsupported type: ${abiType}`); diff --git a/yarn-project/foundation/src/crypto/random/randomness_singleton.ts b/yarn-project/foundation/src/crypto/random/randomness_singleton.ts index 9e51f6da8ef..667db265df1 100644 --- a/yarn-project/foundation/src/crypto/random/randomness_singleton.ts +++ b/yarn-project/foundation/src/crypto/random/randomness_singleton.ts @@ -18,10 +18,10 @@ export class RandomnessSingleton { private readonly log = createDebugLogger('aztec:randomness_singleton'), ) { if (seed !== undefined) { - this.log.info(`Using pseudo-randomness with seed: ${seed}`); + this.log.verbose(`Using pseudo-randomness with seed: ${seed}`); this.counter = seed; } else { - this.log.info('Using true randomness'); + this.log.verbose('Using true randomness'); } } diff --git a/yarn-project/foundation/src/fields/fields.ts b/yarn-project/foundation/src/fields/fields.ts index f8d31429f58..9c6874ff648 100644 --- a/yarn-project/foundation/src/fields/fields.ts +++ b/yarn-project/foundation/src/fields/fields.ts @@ -174,8 +174,14 @@ function random(f: DerivedField): T { /** * Constructs a field from a 0x prefixed hex string. */ -function fromString(buf: string, f: DerivedField) { - const buffer = Buffer.from(buf.replace(/^0x/i, ''), 'hex'); +function fromHexString(buf: string, f: DerivedField) { + const withoutPrefix = buf.replace(/^0x/i, ''); + const buffer = Buffer.from(withoutPrefix, 'hex'); + + if (buffer.length === 0 && withoutPrefix.length > 0) { + throw new Error(`Invalid hex-encoded string: "${buf}"`); + } + return new f(buffer); } @@ -228,7 +234,7 @@ export class Fr extends BaseField { } static fromString(buf: string) { - return fromString(buf, Fr); + return fromHexString(buf, Fr); } /** Arithmetic */ @@ -329,7 +335,7 @@ export class Fq extends BaseField { } static fromString(buf: string) { - return fromString(buf, Fq); + return fromHexString(buf, Fq); } static fromHighLow(high: Fr, low: Fr): Fq { diff --git a/yarn-project/l1-artifacts/scripts/generate-artifacts.sh b/yarn-project/l1-artifacts/scripts/generate-artifacts.sh index 67ba59a51a1..af6dec6c058 100755 --- a/yarn-project/l1-artifacts/scripts/generate-artifacts.sh +++ b/yarn-project/l1-artifacts/scripts/generate-artifacts.sh @@ -20,6 +20,7 @@ CONTRACTS=( "l1-contracts:UniswapPortal" "l1-contracts:IERC20" "l1-contracts:GasPortal" + "l1-contracts:MockVerifier" ) diff --git a/yarn-project/p2p-bootstrap/terraform/main.tf b/yarn-project/p2p-bootstrap/terraform/main.tf index aab81f1594f..ef4cd2e7e23 100644 --- a/yarn-project/p2p-bootstrap/terraform/main.tf +++ b/yarn-project/p2p-bootstrap/terraform/main.tf @@ -105,7 +105,7 @@ resource "aws_ecs_task_definition" "p2p-bootstrap" { [ { "name": "${var.DEPLOY_TAG}-p2p-bootstrap-${count.index + 1}", - "image": "${var.DOCKERHUB_ACCOUNT}/aztec:devnet", + "image": "${var.DOCKERHUB_ACCOUNT}/aztec:${var.DEPLOY_TAG}", "command": ["start", "--p2p-bootstrap"], "essential": true, "memoryReservation": 3776, @@ -145,7 +145,7 @@ resource "aws_ecs_task_definition" "p2p-bootstrap" { }, { "name": "DEBUG", - "value": "aztec:*" + "value": "aztec:*,discv5:*" }, { "name": "P2P_MIN_PEERS", @@ -251,9 +251,10 @@ resource "aws_security_group_rule" "allow-bootstrap-udp" { # Egress Rule for UDP Traffic resource "aws_security_group_rule" "allow-bootstrap-udp-egress" { + count = local.bootnode_count type = "egress" - from_port = var.BOOTNODE_LISTEN_PORT - to_port = var.BOOTNODE_LISTEN_PORT + from_port = var.BOOTNODE_LISTEN_PORT + count.index + to_port = var.BOOTNODE_LISTEN_PORT + count.index protocol = "udp" security_group_id = data.terraform_remote_state.aztec-network_iac.outputs.p2p_security_group_id cidr_blocks = ["0.0.0.0/0"] diff --git a/yarn-project/p2p/src/bootstrap/bootstrap.ts b/yarn-project/p2p/src/bootstrap/bootstrap.ts index d73a24937bf..7eab3aed482 100644 --- a/yarn-project/p2p/src/bootstrap/bootstrap.ts +++ b/yarn-project/p2p/src/bootstrap/bootstrap.ts @@ -49,6 +49,7 @@ export class BootstrapNode { bindAddrs: { ip4: listenAddrUdp }, config: { lookupTimeout: 2000, + allowUnverifiedSessions: true, }, }); diff --git a/yarn-project/p2p/src/service/discV5_service.ts b/yarn-project/p2p/src/service/discV5_service.ts index f86dc8f4c7c..05f81016cb4 100644 --- a/yarn-project/p2p/src/service/discV5_service.ts +++ b/yarn-project/p2p/src/service/discV5_service.ts @@ -61,6 +61,7 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService bindAddrs: { ip4: listenMultiAddrUdp }, config: { lookupTimeout: 2000, + allowUnverifiedSessions: true, }, }); diff --git a/yarn-project/prover-client/src/prover-pool/rpc.ts b/yarn-project/prover-client/src/prover-pool/rpc.ts index a7debf7bff2..7e506658131 100644 --- a/yarn-project/prover-client/src/prover-pool/rpc.ts +++ b/yarn-project/prover-client/src/prover-pool/rpc.ts @@ -10,9 +10,12 @@ import { PublicKernelCircuitPrivateInputs, PublicKernelCircuitPublicInputs, PublicKernelTailCircuitPrivateInputs, + RecursiveProof, + RootParityInput, RootParityInputs, RootRollupInputs, RootRollupPublicInputs, + VerificationKeyData, } from '@aztec/circuits.js'; import { createJsonRpcClient, makeFetch } from '@aztec/foundation/json-rpc/client'; import { JsonRpcServer } from '@aztec/foundation/json-rpc/server'; @@ -29,6 +32,7 @@ export function createProvingJobSourceServer(queue: ProvingJobSource): JsonRpcSe MergeRollupInputs, ParityPublicInputs, Proof, + RootParityInput, RootParityInputs, RootRollupInputs, RootRollupPublicInputs, @@ -37,6 +41,8 @@ export function createProvingJobSourceServer(queue: ProvingJobSource): JsonRpcSe PublicKernelTailCircuitPrivateInputs, KernelCircuitPublicInputs, ProvingError, + RecursiveProof, + VerificationKeyData, }, {}, ); @@ -56,6 +62,7 @@ export function createProvingJobSourceClient( MergeRollupInputs, ParityPublicInputs, Proof, + RootParityInput, RootParityInputs, RootRollupInputs, RootRollupPublicInputs, @@ -64,6 +71,8 @@ export function createProvingJobSourceClient( PublicKernelTailCircuitPrivateInputs, KernelCircuitPublicInputs, ProvingError, + RecursiveProof, + VerificationKeyData, }, {}, false, diff --git a/yarn-project/pxe/src/config/index.ts b/yarn-project/pxe/src/config/index.ts index acd6cdae938..b9caa044d73 100644 --- a/yarn-project/pxe/src/config/index.ts +++ b/yarn-project/pxe/src/config/index.ts @@ -37,12 +37,22 @@ export type PXEServiceConfig = PXEConfig & KernelProverConfig & BBProverConfig; * Creates an instance of PXEServiceConfig out of environment variables using sensible defaults for integration testing if not set. */ export function getPXEServiceConfig(): PXEServiceConfig { - const { PXE_BLOCK_POLLING_INTERVAL_MS, PXE_L2_STARTING_BLOCK, PXE_DATA_DIRECTORY } = process.env; + const { + PXE_BLOCK_POLLING_INTERVAL_MS, + PXE_L2_STARTING_BLOCK, + PXE_DATA_DIRECTORY, + BB_BINARY_PATH, + BB_WORKING_DIRECTORY, + PXE_PROVER_ENABLED, + } = process.env; return { l2BlockPollingIntervalMS: PXE_BLOCK_POLLING_INTERVAL_MS ? +PXE_BLOCK_POLLING_INTERVAL_MS : 1000, l2StartingBlock: PXE_L2_STARTING_BLOCK ? +PXE_L2_STARTING_BLOCK : INITIAL_L2_BLOCK_NUM, dataDirectory: PXE_DATA_DIRECTORY, + bbBinaryPath: BB_BINARY_PATH, + bbWorkingDirectory: BB_WORKING_DIRECTORY, + proverEnabled: ['1', 'true'].includes(PXE_PROVER_ENABLED!), }; } diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 2d068660e7f..1fe547e127a 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -212,7 +212,8 @@ export class Sequencer { // We must initialise the block to be a power of 2 in size const numRealTxs = validTxs.length; const pow2 = Math.log2(numRealTxs); - const totalTxs = 2 ** Math.ceil(pow2); + // TODO turn this back into a Math.ceil once we can pad blocks to the next-power-of-2 with empty txs + const totalTxs = 2 ** Math.floor(pow2); const blockSize = Math.max(2, totalTxs); const blockTicket = await this.prover.startNewBlock(blockSize, newGlobalVariables, l1ToL2Messages, emptyTx); diff --git a/yarn-project/simulator/src/index.ts b/yarn-project/simulator/src/index.ts index 22bfc120b57..9688f1fc383 100644 --- a/yarn-project/simulator/src/index.ts +++ b/yarn-project/simulator/src/index.ts @@ -6,3 +6,4 @@ export * from './public/index.js'; export * from './simulator/index.js'; export * from './mocks/index.js'; export * from './stats/index.js'; +export * from './utils.js'; diff --git a/yarn-project/tsconfig.json b/yarn-project/tsconfig.json index 8fa1304b81d..96d001f5ded 100644 --- a/yarn-project/tsconfig.json +++ b/yarn-project/tsconfig.json @@ -46,7 +46,8 @@ { "path": "types/tsconfig.json" }, { "path": "world-state/tsconfig.json" }, { "path": "scripts/tsconfig.json" }, - { "path": "entrypoints/tsconfig.json" } + { "path": "entrypoints/tsconfig.json" }, + { "path": "cli/tsconfig.json" } ], "files": ["./@types/jest/index.d.ts"] } diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 3da39b2f7d9..1f675e04c7c 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -343,6 +343,49 @@ __metadata: languageName: unknown linkType: soft +"@aztec/cli@workspace:cli": + version: 0.0.0-use.local + resolution: "@aztec/cli@workspace:cli" + dependencies: + "@aztec/accounts": "workspace:^" + "@aztec/aztec.js": "workspace:^" + "@aztec/bb-prover": "workspace:^" + "@aztec/circuit-types": "workspace:^" + "@aztec/circuits.js": "workspace:^" + "@aztec/ethereum": "workspace:^" + "@aztec/foundation": "workspace:^" + "@aztec/l1-artifacts": "workspace:^" + "@aztec/noir-contracts.js": "workspace:^" + "@aztec/protocol-contracts": "workspace:^" + "@aztec/simulator": "workspace:^" + "@aztec/types": "workspace:^" + "@iarna/toml": ^2.2.5 + "@jest/globals": ^29.5.0 + "@libp2p/peer-id-factory": ^3.0.4 + "@types/jest": ^29.5.0 + "@types/lodash.startcase": ^4.4.7 + "@types/node": ^18.7.23 + "@types/semver": ^7.5.2 + "@types/source-map-support": ^0.5.10 + commander: ^9.0.0 + jest: ^29.5.0 + jest-mock-extended: ^3.0.5 + jszip: ^3.10.1 + lodash.startcase: ^4.4.0 + node-fetch: ^3.3.2 + semver: ^7.5.4 + solc: ^0.8.26 + source-map-support: ^0.5.21 + ts-jest: ^29.1.0 + ts-node: ^10.9.1 + tslib: ^2.4.0 + typescript: ^5.0.4 + viem: ^2.7.15 + bin: + aztec-cli: ./dest/bin/index.js + languageName: unknown + linkType: soft + "@aztec/docs@workspace:docs": version: 0.0.0-use.local resolution: "@aztec/docs@workspace:docs" @@ -2199,7 +2242,7 @@ __metadata: languageName: node linkType: hard -"@libp2p/crypto@npm:^2.0.3": +"@libp2p/crypto@npm:^2.0.3, @libp2p/crypto@npm:^2.0.8": version: 2.0.8 resolution: "@libp2p/crypto@npm:2.0.8" dependencies: @@ -2441,6 +2484,21 @@ __metadata: languageName: node linkType: hard +"@libp2p/peer-id-factory@npm:^3.0.4": + version: 3.0.11 + resolution: "@libp2p/peer-id-factory@npm:3.0.11" + dependencies: + "@libp2p/crypto": ^2.0.8 + "@libp2p/interface": ^0.1.6 + "@libp2p/peer-id": ^3.0.6 + multiformats: ^12.0.1 + protons-runtime: ^5.0.0 + uint8arraylist: ^2.4.3 + uint8arrays: ^4.0.6 + checksum: bdcee4fef7f8aace6a8316e523e8c82753986a42c58b51f59d04534a1095c9c1eec8193e859614aa2589a7f5e43e64e529bb0b475e7bad7150b2034b2ebc0aa2 + languageName: node + linkType: hard + "@libp2p/peer-id@npm:4.0.7": version: 4.0.7 resolution: "@libp2p/peer-id@npm:4.0.7" @@ -3753,6 +3811,15 @@ __metadata: languageName: node linkType: hard +"@types/lodash.startcase@npm:^4.4.7": + version: 4.4.9 + resolution: "@types/lodash.startcase@npm:4.4.9" + dependencies: + "@types/lodash": "*" + checksum: 448203f0b6d31c1af9fe8292d5417af670bee560bb0af0cac3a6047b90c2d60ba03197367c2defae21e3982c665763197343863ce7d97131efa8e13e6431fe9f + languageName: node + linkType: hard + "@types/lodash.times@npm:^4.3.7": version: 4.3.9 resolution: "@types/lodash.times@npm:4.3.9" @@ -3859,7 +3926,7 @@ __metadata: languageName: node linkType: hard -"@types/semver@npm:^7.5.0, @types/semver@npm:^7.5.4": +"@types/semver@npm:^7.5.0, @types/semver@npm:^7.5.2, @types/semver@npm:^7.5.4": version: 7.5.8 resolution: "@types/semver@npm:7.5.8" checksum: ea6f5276f5b84c55921785a3a27a3cd37afee0111dfe2bcb3e03c31819c197c782598f17f0b150a69d453c9584cd14c4c4d7b9a55d2c5e6cacd4d66fdb3b3663 @@ -5171,6 +5238,15 @@ __metadata: languageName: node linkType: hard +"bs-logger@npm:0.x": + version: 0.2.6 + resolution: "bs-logger@npm:0.2.6" + dependencies: + fast-json-stable-stringify: 2.x + checksum: d34bdaf68c64bd099ab97c3ea608c9ae7d3f5faa1178b3f3f345acd94e852e608b2d4f9103fb2e503f5e69780e98293df41691b84be909b41cf5045374d54606 + languageName: node + linkType: hard + "bser@npm:2.1.1": version: 2.1.1 resolution: "bser@npm:2.1.1" @@ -5911,6 +5987,13 @@ __metadata: languageName: node linkType: hard +"data-uri-to-buffer@npm:^4.0.0": + version: 4.0.1 + resolution: "data-uri-to-buffer@npm:4.0.1" + checksum: 0d0790b67ffec5302f204c2ccca4494f70b4e2d940fea3d36b09f0bb2b8539c2e86690429eb1f1dc4bcc9e4df0644193073e63d9ee48ac9fce79ec1506e4aa4c + languageName: node + linkType: hard + "data-uri-to-buffer@npm:^6.0.2": version: 6.0.2 resolution: "data-uri-to-buffer@npm:6.0.2" @@ -7296,7 +7379,7 @@ __metadata: languageName: node linkType: hard -"fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": +"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" checksum: b191531e36c607977e5b1c47811158733c34ccb3bfde92c44798929e9b4154884378536d26ad90dfecd32e1ffc09c545d23535ad91b3161a27ddbb8ebe0cbecb @@ -7358,6 +7441,16 @@ __metadata: languageName: node linkType: hard +"fetch-blob@npm:^3.1.2, fetch-blob@npm:^3.1.4": + version: 3.2.0 + resolution: "fetch-blob@npm:3.2.0" + dependencies: + node-domexception: ^1.0.0 + web-streams-polyfill: ^3.0.3 + checksum: f19bc28a2a0b9626e69fd7cf3a05798706db7f6c7548da657cbf5026a570945f5eeaedff52007ea35c8bcd3d237c58a20bf1543bc568ab2422411d762dd3d5bf + languageName: node + linkType: hard + "file-entry-cache@npm:^6.0.1": version: 6.0.1 resolution: "file-entry-cache@npm:6.0.1" @@ -7525,6 +7618,15 @@ __metadata: languageName: node linkType: hard +"formdata-polyfill@npm:^4.0.10": + version: 4.0.10 + resolution: "formdata-polyfill@npm:4.0.10" + dependencies: + fetch-blob: ^3.1.2 + checksum: 82a34df292afadd82b43d4a740ce387bc08541e0a534358425193017bf9fb3567875dc5f69564984b1da979979b70703aa73dee715a17b6c229752ae736dd9db + languageName: node + linkType: hard + "formidable@npm:^2.1.2": version: 2.1.2 resolution: "formidable@npm:2.1.2" @@ -8182,6 +8284,13 @@ __metadata: languageName: node linkType: hard +"immediate@npm:~3.0.5": + version: 3.0.6 + resolution: "immediate@npm:3.0.6" + checksum: f9b3486477555997657f70318cc8d3416159f208bec4cca3ff3442fd266bc23f50f0c9bd8547e1371a6b5e82b821ec9a7044a4f7b944798b25aa3cc6d5e63e62 + languageName: node + linkType: hard + "import-fresh@npm:^3.2.1, import-fresh@npm:^3.3.0": version: 3.3.0 resolution: "import-fresh@npm:3.3.0" @@ -9388,7 +9497,7 @@ __metadata: languageName: node linkType: hard -"jest-util@npm:^29.7.0": +"jest-util@npm:^29.0.0, jest-util@npm:^29.7.0": version: 29.7.0 resolution: "jest-util@npm:29.7.0" dependencies: @@ -9609,6 +9718,18 @@ __metadata: languageName: node linkType: hard +"jszip@npm:^3.10.1": + version: 3.10.1 + resolution: "jszip@npm:3.10.1" + dependencies: + lie: ~3.3.0 + pako: ~1.0.2 + readable-stream: ~2.3.6 + setimmediate: ^1.0.5 + checksum: abc77bfbe33e691d4d1ac9c74c8851b5761fba6a6986630864f98d876f3fcc2d36817dfc183779f32c00157b5d53a016796677298272a714ae096dfe6b1c8b60 + languageName: node + linkType: hard + "keygrip@npm:~1.1.0": version: 1.1.0 resolution: "keygrip@npm:1.1.0" @@ -9875,6 +9996,15 @@ __metadata: languageName: node linkType: hard +"lie@npm:~3.3.0": + version: 3.3.0 + resolution: "lie@npm:3.3.0" + dependencies: + immediate: ~3.0.5 + checksum: 33102302cf19766f97919a6a98d481e01393288b17a6aa1f030a3542031df42736edde8dab29ffdbf90bebeffc48c761eb1d064dc77592ca3ba3556f9fe6d2a8 + languageName: node + linkType: hard + "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4" @@ -10024,6 +10154,13 @@ __metadata: languageName: node linkType: hard +"lodash.memoize@npm:4.x": + version: 4.1.2 + resolution: "lodash.memoize@npm:4.1.2" + checksum: 9ff3942feeccffa4f1fafa88d32f0d24fdc62fd15ded5a74a5f950ff5f0c6f61916157246744c620173dddf38d37095a92327d5fd3861e2063e736a5c207d089 + languageName: node + linkType: hard + "lodash.merge@npm:^4.6.2": version: 4.6.2 resolution: "lodash.merge@npm:4.6.2" @@ -10045,6 +10182,13 @@ __metadata: languageName: node linkType: hard +"lodash.startcase@npm:^4.4.0": + version: 4.4.0 + resolution: "lodash.startcase@npm:4.4.0" + checksum: c03a4a784aca653845fe09d0ef67c902b6e49288dc45f542a4ab345a9c406a6dc194c774423fa313ee7b06283950301c1221dd2a1d8ecb2dac8dfbb9ed5606b5 + languageName: node + linkType: hard + "lodash.times@npm:^4.3.2": version: 4.3.2 resolution: "lodash.times@npm:4.3.2" @@ -10175,7 +10319,7 @@ __metadata: languageName: node linkType: hard -"make-error@npm:^1.1.1": +"make-error@npm:1.x, make-error@npm:^1.1.1": version: 1.3.6 resolution: "make-error@npm:1.3.6" checksum: b86e5e0e25f7f777b77fabd8e2cbf15737972869d852a22b7e73c17623928fccb826d8e46b9951501d3f20e51ad74ba8c59ed584f610526a48f8ccf88aaec402 @@ -10762,6 +10906,24 @@ __metadata: languageName: node linkType: hard +"node-domexception@npm:^1.0.0": + version: 1.0.0 + resolution: "node-domexception@npm:1.0.0" + checksum: ee1d37dd2a4eb26a8a92cd6b64dfc29caec72bff5e1ed9aba80c294f57a31ba4895a60fd48347cf17dd6e766da0ae87d75657dfd1f384ebfa60462c2283f5c7f + languageName: node + linkType: hard + +"node-fetch@npm:^3.3.2": + version: 3.3.2 + resolution: "node-fetch@npm:3.3.2" + dependencies: + data-uri-to-buffer: ^4.0.0 + fetch-blob: ^3.1.4 + formdata-polyfill: ^4.0.10 + checksum: 06a04095a2ddf05b0830a0d5302699704d59bda3102894ea64c7b9d4c865ecdff2d90fd042df7f5bc40337266961cb6183dcc808ea4f3000d024f422b462da92 + languageName: node + linkType: hard + "node-forge@npm:^1.1.0": version: 1.3.1 resolution: "node-forge@npm:1.3.1" @@ -11210,6 +11372,13 @@ __metadata: languageName: node linkType: hard +"pako@npm:~1.0.2": + version: 1.0.11 + resolution: "pako@npm:1.0.11" + checksum: 1be2bfa1f807608c7538afa15d6f25baa523c30ec870a3228a89579e474a4d992f4293859524e46d5d87fd30fa17c5edf34dbef0671251d9749820b488660b16 + languageName: node + linkType: hard + "parent-module@npm:^1.0.0": version: 1.0.1 resolution: "parent-module@npm:1.0.1" @@ -11833,7 +12002,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.3.8": +"readable-stream@npm:^2.3.8, readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" dependencies: @@ -12308,6 +12477,13 @@ __metadata: languageName: node linkType: hard +"setimmediate@npm:^1.0.5": + version: 1.0.5 + resolution: "setimmediate@npm:1.0.5" + checksum: c9a6f2c5b51a2dabdc0247db9c46460152ffc62ee139f3157440bd48e7c59425093f42719ac1d7931f054f153e2d26cf37dfeb8da17a794a58198a2705e527fd + languageName: node + linkType: hard + "setprototypeof@npm:1.1.0": version: 1.1.0 resolution: "setprototypeof@npm:1.1.0" @@ -12491,6 +12667,23 @@ __metadata: languageName: node linkType: hard +"solc@npm:^0.8.26": + version: 0.8.26 + resolution: "solc@npm:0.8.26" + dependencies: + command-exists: ^1.2.8 + commander: ^8.1.0 + follow-redirects: ^1.12.1 + js-sha3: 0.8.0 + memorystream: ^0.3.1 + semver: ^5.5.0 + tmp: 0.0.33 + bin: + solcjs: solc.js + checksum: e3eaeac76e60676377b357af8f3919d4c8c6a74b74112b49279fe8c74a3dfa1de8afe4788689fc307453bde336edc8572988d2cf9e909f84d870420eb640400c + languageName: node + linkType: hard + "sonic-forest@npm:^1.0.0": version: 1.0.3 resolution: "sonic-forest@npm:1.0.3" @@ -13214,6 +13407,42 @@ __metadata: languageName: node linkType: hard +"ts-jest@npm:^29.1.0": + version: 29.1.3 + resolution: "ts-jest@npm:29.1.3" + dependencies: + bs-logger: 0.x + fast-json-stable-stringify: 2.x + jest-util: ^29.0.0 + json5: ^2.2.3 + lodash.memoize: 4.x + make-error: 1.x + semver: ^7.5.3 + yargs-parser: ^21.0.1 + peerDependencies: + "@babel/core": ">=7.0.0-beta.0 <8" + "@jest/transform": ^29.0.0 + "@jest/types": ^29.0.0 + babel-jest: ^29.0.0 + jest: ^29.0.0 + typescript: ">=4.3 <6" + peerDependenciesMeta: + "@babel/core": + optional: true + "@jest/transform": + optional: true + "@jest/types": + optional: true + babel-jest: + optional: true + esbuild: + optional: true + bin: + ts-jest: cli.js + checksum: c5b2c0501680a9056c50541a3315de7b3b85a611056b978062b4defc96fb0066d12bf1e15715021799a3779723343fb98a9a4ba01dc01709f274899b6c28453d + languageName: node + linkType: hard + "ts-loader@npm:^9.4.4": version: 9.5.1 resolution: "ts-loader@npm:9.5.1" @@ -13875,6 +14104,13 @@ __metadata: languageName: node linkType: hard +"web-streams-polyfill@npm:^3.0.3": + version: 3.3.3 + resolution: "web-streams-polyfill@npm:3.3.3" + checksum: 21ab5ea08a730a2ef8023736afe16713b4f2023ec1c7085c16c8e293ee17ed085dff63a0ad8722da30c99c4ccbd4ccd1b2e79c861829f7ef2963d7de7004c2cb + languageName: node + linkType: hard + "webpack-cli@npm:^5.1.4": version: 5.1.4 resolution: "webpack-cli@npm:5.1.4" @@ -14196,7 +14432,7 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:^21.1.1": +"yargs-parser@npm:^21.0.1, yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1" checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c