From 0702dc6988149258124184b85d38db930effe0e7 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Tue, 27 Feb 2024 07:56:07 -0300 Subject: [PATCH] feat!: Use new deployment flow in ContractDeployer (#4497) Updates the `ContractDeployer` to use the new deployment flow. Instead of creating txs flagged as `isDeployment`, the deployer now creates a batch call that registers the contract class (if needed), publicly deploys the contract via the canonical `InstanceDeployer`, and then initializes it by calling the constructor. This batch call is then sent via an account contract entrypoint and executed in a single tx. Check out `e2e_deploy` _publicly deploys and initializes a contract_ to see it in action. However, since this requires sending txs through an account entrypoint, it breaks for deploying accounts. So we still keep the old deployment flow around for those scenarios, under the `LegacyDeployMethod` name. We'll need to tweak account contracts constructors so they can be initialized and optionally deployed in the same single direct call, and they also pay fees for it. Alternatively, we can also add a canonical `MultiCall` contract, which could take care of this, though fee payment is unclear. This PR also changes the `ClassRegisterer` API so that bytecode is provided via a capsule instead of an arg, since having args of size over ~2k elements was causing a loop in the Hasher that massively increased build times (see https://github.com/noir-lang/noir/issues/4395). **Breaking changes:** Since deployments are now done from an account, deploying a contract via `Contract.deploy` now requires a `Wallet` instance instead of a `PXE`, and deploy a contract via CLI now requires a `--private-key`. --------- Co-authored-by: PhilWindle <60546371+PhilWindle@users.noreply.github.com> --- boxes/react/src/hooks/useContract.tsx | 6 +- boxes/react/src/index.tsx | 12 +- boxes/react/src/pages/contract.tsx | 25 +- boxes/react/src/pages/home.tsx | 4 +- boxes/react/tests/blank.contract.test.ts | 6 +- boxes/vanilla-js/src/index.ts | 6 +- .../src/core/libraries/ConstantsGen.sol | 2 +- .../src/capsule.nr | 10 + .../src/main.nr | 54 ++- .../src/crates/types/src/constants.nr | 6 +- .../archiver/src/archiver/archiver.ts | 81 ++-- .../archiver/src/archiver/archiver_store.ts | 3 + .../src/archiver/archiver_store_test_suite.ts | 2 +- .../kv_archiver_store/contract_class_store.ts | 4 + .../kv_archiver_store/kv_archiver_store.ts | 4 + .../memory_archiver_store.ts | 4 + .../aztec.js/src/account_manager/index.ts | 14 +- .../aztec.js/src/contract/deploy_method.ts | 160 ++++--- .../aztec.js/src/contract/deploy_sent_tx.ts | 11 +- .../src/deployment/contract_deployer.test.ts | 58 --- .../src/deployment/contract_deployer.ts | 6 +- .../legacy/legacy_contract_deployer.ts | 30 ++ .../deployment/legacy/legacy_deploy_method.ts | 151 +++++++ .../aztec.js/src/deployment/register_class.ts | 8 +- yarn-project/aztec.js/src/index.ts | 1 + .../aztec.js/src/utils/l2_contracts.ts | 2 +- .../aztec.js/src/wallet/base_wallet.ts | 8 +- .../circuit-types/src/contract_data.ts | 21 +- .../circuit-types/src/interfaces/pxe.ts | 23 +- yarn-project/circuit-types/src/l2_block.ts | 2 +- .../circuit-types/src/logs/tx_l2_logs.ts | 5 + yarn-project/circuit-types/src/mocks.ts | 2 +- .../circuit-types/src/tx/tx_receipt.ts | 9 +- yarn-project/circuits.js/package.json | 2 +- yarn-project/circuits.js/src/constants.gen.ts | 2 +- .../contract_class_registered_event.ts | 13 +- .../contract_instance_deployed_event.ts | 13 +- yarn-project/circuits.js/src/hash/hash.ts | 9 +- yarn-project/circuits.js/src/tests/index.ts | 2 + yarn-project/cli/src/cmds/deploy.ts | 7 +- yarn-project/cli/src/cmds/inspect_contract.ts | 38 +- yarn-project/cli/src/index.ts | 4 +- .../end-to-end/src/cli_docs_sandbox.test.ts | 8 +- .../end-to-end/src/e2e_block_building.test.ts | 16 +- .../src/e2e_deploy_contract.test.ts | 409 +++++++++--------- .../src/e2e_lending_contract.test.ts | 62 +-- .../end-to-end/src/e2e_p2p_network.test.ts | 7 +- .../end-to-end/src/guides/up_quick_start.sh | 2 +- .../src/integration_l1_publisher.test.ts | 2 +- yarn-project/end-to-end/src/shared/browser.ts | 8 +- yarn-project/end-to-end/src/shared/cli.ts | 5 +- .../src/type_conversion.test.ts | 2 +- .../__snapshots__/index.test.ts.snap | 372 ++++++++-------- .../src/class-registerer/index.ts | 2 +- .../src/database/pxe_database_test_suite.ts | 2 +- .../src/kernel_prover/kernel_prover.test.ts | 2 +- .../pxe/src/pxe_service/pxe_service.ts | 24 +- .../pxe/src/synchronizer/synchronizer.test.ts | 2 +- yarn-project/sequencer-client/package.json | 1 + .../block_builder/solo_block_builder.test.ts | 2 +- .../src/sequencer/app_logic_phase_manager.ts | 3 + .../src/sequencer/public_processor.test.ts | 2 +- .../src/simulator/public_executor.ts | 72 ++- yarn-project/sequencer-client/tsconfig.json | 3 + .../src/client/private_execution.test.ts | 2 +- .../simulator/src/public/index.test.ts | 2 +- yarn-project/yarn.lock | 1 + 67 files changed, 1075 insertions(+), 768 deletions(-) create mode 100644 noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/capsule.nr delete mode 100644 yarn-project/aztec.js/src/deployment/contract_deployer.test.ts create mode 100644 yarn-project/aztec.js/src/deployment/legacy/legacy_contract_deployer.ts create mode 100644 yarn-project/aztec.js/src/deployment/legacy/legacy_deploy_method.ts create mode 100644 yarn-project/circuits.js/src/tests/index.ts diff --git a/boxes/react/src/hooks/useContract.tsx b/boxes/react/src/hooks/useContract.tsx index 0174c205d39..8082005867c 100644 --- a/boxes/react/src/hooks/useContract.tsx +++ b/boxes/react/src/hooks/useContract.tsx @@ -14,17 +14,17 @@ export function useContract() { e.preventDefault(); setWait(true); - const contractDeployer = new ContractDeployer(artifact, deployerEnv.pxe); const wallet = await deployerEnv.getWallet(); + const contractDeployer = new ContractDeployer(artifact, wallet); const salt = Fr.random(); const tx = contractDeployer .deploy(Fr.random(), wallet.getCompleteAddress().address) .send({ contractAddressSalt: salt }); - const { contractAddress } = await toast.promise(tx.wait(), { + const { address: contractAddress } = await toast.promise(tx.deployed(), { pending: 'Deploying contract...', success: { - render: ({ data }) => `Address: ${data.contractAddress}`, + render: ({ data }) => `Address: ${data.address}`, }, error: 'Error deploying contract', }); diff --git a/boxes/react/src/index.tsx b/boxes/react/src/index.tsx index e71d6ce7dcf..3c657332834 100644 --- a/boxes/react/src/index.tsx +++ b/boxes/react/src/index.tsx @@ -1,11 +1,9 @@ -import { Home } from "./pages/home"; -import "react-toastify/dist/ReactToastify.css"; -import * as ReactDOM from "react-dom/client"; -import { ToastContainer } from "react-toastify"; +import { Home } from './pages/home'; +import 'react-toastify/dist/ReactToastify.css'; +import * as ReactDOM from 'react-dom/client'; +import { ToastContainer } from 'react-toastify'; -const root = ReactDOM.createRoot( - document.getElementById("root") as HTMLElement, -); +const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); root.render( <> diff --git a/boxes/react/src/pages/contract.tsx b/boxes/react/src/pages/contract.tsx index 1310eae0c48..93d398ef1f4 100644 --- a/boxes/react/src/pages/contract.tsx +++ b/boxes/react/src/pages/contract.tsx @@ -1,7 +1,7 @@ -import { useState } from "react"; -import { Contract } from "@aztec/aztec.js"; -import { useNumber } from "../hooks/useNumber"; -import { filteredInterface } from "../config"; +import { useState } from 'react'; +import { Contract } from '@aztec/aztec.js'; +import { useNumber } from '../hooks/useNumber'; +import { filteredInterface } from '../config'; export function ContractComponent({ contract }: { contract: Contract }) { const [showInput, setShowInput] = useState(true); @@ -15,7 +15,7 @@ export function ContractComponent({ contract }: { contract: Contract }) { setShowInput(true)} - > + - + diff --git a/boxes/react/src/pages/home.tsx b/boxes/react/src/pages/home.tsx index 8c0fb123667..ba41f15cebf 100644 --- a/boxes/react/src/pages/home.tsx +++ b/boxes/react/src/pages/home.tsx @@ -1,5 +1,5 @@ -import { ContractComponent } from "./contract"; -import { useContract } from "../hooks/useContract"; +import { ContractComponent } from './contract'; +import { useContract } from '../hooks/useContract'; export function Home() { const { contract, deploy, wait } = useContract(); diff --git a/boxes/react/tests/blank.contract.test.ts b/boxes/react/tests/blank.contract.test.ts index 9887ccc7f0b..40c43ef8acc 100644 --- a/boxes/react/tests/blank.contract.test.ts +++ b/boxes/react/tests/blank.contract.test.ts @@ -13,11 +13,9 @@ describe('BoxReact Contract Tests', () => { beforeAll(async () => { wallet = await deployerEnv.getWallet(); const pxe = deployerEnv.pxe; - const deployer = new ContractDeployer(artifact, pxe); + const deployer = new ContractDeployer(artifact, wallet); const salt = Fr.random(); - const tx = deployer.deploy(Fr.random(), wallet.getCompleteAddress().address).send({ contractAddressSalt: salt }); - await tx.wait(); - const { contractAddress } = await tx.getReceipt(); + const { address: contractAddress } = await deployer.deploy(Fr.random(), wallet.getCompleteAddress().address).send({ contractAddressSalt: salt }).deployed(); contract = await BoxReactContract.at(contractAddress!, wallet); logger(`L2 contract deployed at ${contractAddress}`); diff --git a/boxes/vanilla-js/src/index.ts b/boxes/vanilla-js/src/index.ts index de17f116857..53e0f92099f 100644 --- a/boxes/vanilla-js/src/index.ts +++ b/boxes/vanilla-js/src/index.ts @@ -27,11 +27,11 @@ document.querySelector('#deploy').addEventListener('click', async ({ target }: a wallet = await account.register(); const { artifact, at } = VanillaContract; - const contractDeployer = new ContractDeployer(artifact, pxe); - const { contractAddress } = await contractDeployer + const contractDeployer = new ContractDeployer(artifact, wallet); + const { address: contractAddress } = await contractDeployer .deploy(Fr.random(), wallet.getCompleteAddress().address) .send({ contractAddressSalt: Fr.random() }) - .wait(); + .deployed(); contract = await at(contractAddress, wallet); alert(`Contract deployed at ${contractAddress}`); diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol index 1595e0cea21..3975a87c7d8 100644 --- a/l1-contracts/src/core/libraries/ConstantsGen.sol +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -72,7 +72,7 @@ library Constants { uint256 internal constant NUM_FIELDS_PER_SHA256 = 2; uint256 internal constant ARGS_HASH_CHUNK_LENGTH = 32; uint256 internal constant ARGS_HASH_CHUNK_COUNT = 32; - uint256 internal constant MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS = 1000; + uint256 internal constant MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS = 8000; uint256 internal constant MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS = 500; uint256 internal constant MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS = 500; uint256 internal constant REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE = diff --git a/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/capsule.nr b/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/capsule.nr new file mode 100644 index 00000000000..d77649e6b28 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/capsule.nr @@ -0,0 +1,10 @@ +// Copied from noir-contracts/contracts/slow_tree_contract/src/capsule.nr +// We should extract this to a shared lib in aztec-nr once we settle on a design for capsules + +#[oracle(popCapsule)] +fn pop_capsule_oracle() -> [Field; N] {} + +// A capsule is a "blob" of data that is passed to the contract through an oracle. +unconstrained pub fn pop_capsule() -> [Field; N] { + pop_capsule_oracle() +} diff --git a/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr b/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr index d54616558f1..760b4e5ff64 100644 --- a/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr @@ -1,4 +1,5 @@ mod events; +mod capsule; contract ContractClassRegisterer { use dep::std::option::Option; @@ -19,19 +20,18 @@ contract ContractClassRegisterer { unconstrained_function_broadcasted::{ClassUnconstrainedFunctionBroadcasted, UnconstrainedFunction} }; + use crate::capsule::pop_capsule; + #[aztec(private)] fn constructor() {} #[aztec(private)] - fn register( - artifact_hash: Field, - private_functions_root: Field, - public_bytecode_commitment: Field, - packed_public_bytecode: [Field; MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS] - ) { + fn register(artifact_hash: Field, private_functions_root: Field, public_bytecode_commitment: Field) { // TODO: Validate public_bytecode_commitment is the correct commitment of packed_public_bytecode // TODO: Validate packed_public_bytecode is legit public bytecode + let packed_public_bytecode: [Field; MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS] = pop_capsule(); + // Compute contract class id from preimage let contract_class_id = ContractClassId::compute( artifact_hash, @@ -44,9 +44,16 @@ contract ContractClassRegisterer { context.push_new_nullifier(contract_class_id.to_field(), 0); // Broadcast class info including public bytecode - let event_payload = event.serialize(); - dep::aztec::oracle::debug_log::debug_log_array_with_prefix("ContractClassRegistered", event_payload); - emit_unencrypted_log_from_private(&mut context, event_payload); + dep::aztec::oracle::debug_log::debug_log_array_with_prefix( + "ContractClassRegistered", + [ + contract_class_id.to_field(), + artifact_hash, + private_functions_root, + public_bytecode_commitment + ] + ); + emit_unencrypted_log_from_private(&mut context, event.serialize()); } #[aztec(private)] @@ -66,9 +73,18 @@ contract ContractClassRegisterer { artifact_function_tree_sibling_path, function: function_data }; - let event_payload = event.serialize(); - dep::aztec::oracle::debug_log::debug_log_array_with_prefix("ClassPrivateFunctionBroadcasted", event_payload); - emit_unencrypted_log_from_private(&mut context, event_payload); + dep::aztec::oracle::debug_log::debug_log_array_with_prefix( + "ClassPrivateFunctionBroadcasted", + [ + contract_class_id.to_field(), + artifact_metadata_hash, + unconstrained_functions_artifact_tree_root, + function_data.selector.to_field(), + function_data.vk_hash, + function_data.metadata_hash + ] + ); + emit_unencrypted_log_from_private(&mut context, event.serialize()); } #[aztec(private)] @@ -86,8 +102,16 @@ contract ContractClassRegisterer { artifact_function_tree_sibling_path, function: function_data }; - let event_payload = event.serialize(); - dep::aztec::oracle::debug_log::debug_log_array_with_prefix("ClassUnconstrainedFunctionBroadcasted", event_payload); - emit_unencrypted_log_from_private(&mut context, event_payload); + dep::aztec::oracle::debug_log::debug_log_array_with_prefix( + "ClassUnconstrainedFunctionBroadcasted", + [ + contract_class_id.to_field(), + artifact_metadata_hash, + private_functions_artifact_tree_root, + function_data.selector.to_field(), + function_data.metadata_hash + ] + ); + emit_unencrypted_log_from_private(&mut context, event.serialize()); } } diff --git a/noir-projects/noir-protocol-circuits/src/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/src/crates/types/src/constants.nr index c5938e87500..ac6e623d309 100644 --- a/noir-projects/noir-protocol-circuits/src/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/src/crates/types/src/constants.nr @@ -104,11 +104,7 @@ global ARGS_HASH_CHUNK_COUNT: u32 = 32; global INITIALIZATION_SLOT_SEPARATOR: Field = 1000_000_000; // CONTRACT CLASS CONSTANTS -// This should be around 8192 (assuming 2**15 instructions packed at 8 bytes each), -// but it's reduced to speed up build times, otherwise the ClassRegisterer takes over 5 mins to compile. -// We are not using 1024 so we can squeeze in a few more args to methods that consume packed public bytecode, -// such as the ClassRegisterer.register, and still land below the 32 * 32 max args limit for hashing. -global MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS: Field = 1000; +global MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS: Field = 8000; // Bytecode size for private functions is per function, not for the entire contract. // Note that private functions bytecode includes a mix of acir and brillig. global MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS: Field = 500; diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index 1af414b0fb5..dc5ccbe9fd0 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -20,12 +20,10 @@ import { ContractClassRegisteredEvent, FunctionSelector, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, - REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE, } from '@aztec/circuits.js'; -import { ContractInstanceDeployedEvent, computeSaltedInitializationHash } from '@aztec/circuits.js/contract'; +import { ContractInstanceDeployedEvent } from '@aztec/circuits.js/contract'; import { createEthereumChain } from '@aztec/ethereum'; import { AztecAddress } from '@aztec/foundation/aztec-address'; -import { toBigIntBE } from '@aztec/foundation/bigint-buffer'; import { padArrayEnd } from '@aztec/foundation/collection'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; @@ -72,12 +70,6 @@ export class Archiver implements ArchiveSource { */ private lastLoggedL1BlockNumber = 0n; - /** Address of the ClassRegisterer contract with a salt=1 */ - private classRegistererAddress = ClassRegistererAddress; - - /** Address of the InstanceDeployer contract with a salt=1 */ - private instanceDeployerAddress = InstanceDeployerAddress; - /** * Creates a new instance of the Archiver. * @param publicClient - A client for interacting with the Ethereum node. @@ -342,22 +334,9 @@ export class Archiver implements ArchiveSource { * @param allLogs - All logs emitted in a bunch of blocks. */ private async storeRegisteredContractClasses(allLogs: UnencryptedL2Log[], blockNum: number) { - const contractClasses: ContractClassPublic[] = []; - for (const log of allLogs) { - try { - if ( - !log.contractAddress.equals(this.classRegistererAddress) || - toBigIntBE(log.data.subarray(0, 32)) !== REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE - ) { - continue; - } - const event = ContractClassRegisteredEvent.fromLogData(log.data); - contractClasses.push(event.toContractClassPublic()); - } catch (err) { - this.log.warn(`Error processing log ${log.toHumanReadable()}: ${err}`); - } - } - + const contractClasses = ContractClassRegisteredEvent.fromLogs(allLogs, ClassRegistererAddress).map(e => + e.toContractClassPublic(), + ); if (contractClasses.length > 0) { contractClasses.forEach(c => this.log(`Registering contract class ${c.id.toString()}`)); await this.store.addContractClasses(contractClasses, blockNum); @@ -369,22 +348,9 @@ export class Archiver implements ArchiveSource { * @param allLogs - All logs emitted in a bunch of blocks. */ private async storeDeployedContractInstances(allLogs: UnencryptedL2Log[], blockNum: number) { - const contractInstances: ContractInstanceWithAddress[] = []; - for (const log of allLogs) { - try { - if ( - !log.contractAddress.equals(this.instanceDeployerAddress) || - !ContractInstanceDeployedEvent.isContractInstanceDeployedEvent(log.data) - ) { - continue; - } - const event = ContractInstanceDeployedEvent.fromLogData(log.data); - contractInstances.push(event.toContractInstance()); - } catch (err) { - this.log.warn(`Error processing log ${log.toHumanReadable()}: ${err}`); - } - } - + const contractInstances = ContractInstanceDeployedEvent.fromLogs(allLogs, InstanceDeployerAddress).map(e => + e.toContractInstance(), + ); if (contractInstances.length > 0) { contractInstances.forEach(c => this.log(`Storing contract instance at ${c.address.toString()}`)); await this.store.addContractInstances(contractInstances, blockNum); @@ -480,19 +446,11 @@ export class Archiver implements ArchiveSource { const contractClass = await this.store.getContractClass(instance.contractClassId); if (!contractClass) { - this.log.warn( - `Contract class ${instance.contractClassId.toString()} for address ${address.toString()} not found`, - ); + this.log.warn(`Class ${instance.contractClassId.toString()} for address ${address.toString()} not found`); return undefined; } - return new ExtendedContractData( - new ContractData(address, instance.portalContractAddress), - contractClass.publicFunctions.map(f => new EncodedContractFunction(f.selector, f.isInternal, f.bytecode)), - contractClass.id, - computeSaltedInitializationHash(instance), - instance.publicKeysHash, - ); + return ExtendedContractData.fromClassAndInstance(contractClass, instance); } /** @@ -510,8 +468,21 @@ export class Archiver implements ArchiveSource { * @param contractAddress - The contract data address. * @returns ContractData with the portal address (if we didn't throw an error). */ - public getContractData(contractAddress: AztecAddress): Promise { - return this.store.getContractData(contractAddress); + public async getContractData(contractAddress: AztecAddress): Promise { + return (await this.store.getContractData(contractAddress)) ?? this.makeContractDataFor(contractAddress); + } + + /** + * Temporary method for creating a fake contract data out of classes and instances registered in the node. + * Used as a fallback if the extended contract data is not found. + */ + private async makeContractDataFor(address: AztecAddress): Promise { + const instance = await this.store.getContractInstance(address); + if (!instance) { + return undefined; + } + + return new ContractData(address, instance.portalContractAddress); } /** @@ -591,6 +562,10 @@ export class Archiver implements ArchiveSource { getConfirmedL1ToL2Message(messageKey: Fr): Promise { return this.store.getConfirmedL1ToL2Message(messageKey); } + + getContractClassIds(): Promise { + return this.store.getContractClassIds(); + } } /** diff --git a/yarn-project/archiver/src/archiver/archiver_store.ts b/yarn-project/archiver/src/archiver/archiver_store.ts index 06bc1ed5301..6052323ad42 100644 --- a/yarn-project/archiver/src/archiver/archiver_store.ts +++ b/yarn-project/archiver/src/archiver/archiver_store.ts @@ -196,4 +196,7 @@ export interface ArchiverDataStore { * @param address - Address of the contract. */ getContractInstance(address: AztecAddress): Promise; + + /** Returns the list of all class ids known by the archiver. */ + getContractClassIds(): Promise; } diff --git a/yarn-project/archiver/src/archiver/archiver_store_test_suite.ts b/yarn-project/archiver/src/archiver/archiver_store_test_suite.ts index 5562b919cd4..012c7576691 100644 --- a/yarn-project/archiver/src/archiver/archiver_store_test_suite.ts +++ b/yarn-project/archiver/src/archiver/archiver_store_test_suite.ts @@ -11,7 +11,7 @@ import { } from '@aztec/circuit-types'; import '@aztec/circuit-types/jest'; import { AztecAddress, Fr } from '@aztec/circuits.js'; -import { makeContractClassPublic } from '@aztec/circuits.js/factories'; +import { makeContractClassPublic } from '@aztec/circuits.js/testing'; import { randomBytes } from '@aztec/foundation/crypto'; import { ContractClassPublic, ContractInstanceWithAddress, SerializableContractInstance } from '@aztec/types/contracts'; diff --git a/yarn-project/archiver/src/archiver/kv_archiver_store/contract_class_store.ts b/yarn-project/archiver/src/archiver/kv_archiver_store/contract_class_store.ts index 79ededfb106..29d18d5a13b 100644 --- a/yarn-project/archiver/src/archiver/kv_archiver_store/contract_class_store.ts +++ b/yarn-project/archiver/src/archiver/kv_archiver_store/contract_class_store.ts @@ -21,6 +21,10 @@ export class ContractClassStore { const contractClass = this.#contractClasses.get(id.toString()); return contractClass && { ...deserializeContractClassPublic(contractClass), id }; } + + getContractClassIds(): Fr[] { + return Array.from(this.#contractClasses.keys()).map(key => Fr.fromString(key)); + } } export function serializeContractClassPublic(contractClass: ContractClassPublic): Buffer { diff --git a/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts b/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts index 6b8339d09fc..e5600544617 100644 --- a/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts +++ b/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts @@ -50,6 +50,10 @@ export class KVArchiverDataStore implements ArchiverDataStore { return Promise.resolve(this.#contractClassStore.getContractClass(id)); } + getContractClassIds(): Promise { + return Promise.resolve(this.#contractClassStore.getContractClassIds()); + } + getContractInstance(address: AztecAddress): Promise { return Promise.resolve(this.#contractInstanceStore.getContractInstance(address)); } diff --git a/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts b/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts index 27a6a2b55b0..c8db5c84a86 100644 --- a/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts +++ b/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts @@ -85,6 +85,10 @@ export class MemoryArchiverStore implements ArchiverDataStore { return Promise.resolve(this.contractClasses.get(id.toString())); } + public getContractClassIds(): Promise { + return Promise.resolve(Array.from(this.contractClasses.keys()).map(key => Fr.fromString(key))); + } + public getContractInstance(address: AztecAddress): Promise { return Promise.resolve(this.contractInstances.get(address.toString())); } diff --git a/yarn-project/aztec.js/src/account_manager/index.ts b/yarn-project/aztec.js/src/account_manager/index.ts index 954ab75846a..7ebd7d5c306 100644 --- a/yarn-project/aztec.js/src/account_manager/index.ts +++ b/yarn-project/aztec.js/src/account_manager/index.ts @@ -6,8 +6,9 @@ import { ContractInstanceWithAddress } from '@aztec/types/contracts'; import { AccountContract } from '../account/contract.js'; import { Salt } from '../account/index.js'; import { AccountInterface } from '../account/interface.js'; -import { DefaultWaitOpts, DeployMethod, WaitOpts } from '../contract/index.js'; -import { ContractDeployer } from '../deployment/index.js'; +import { DefaultWaitOpts, WaitOpts } from '../contract/index.js'; +import { LegacyContractDeployer } from '../deployment/legacy/legacy_contract_deployer.js'; +import { LegacyDeployMethod } from '../deployment/legacy/legacy_deploy_method.js'; import { waitForAccountSynch } from '../utils/account.js'; import { generatePublicKey } from '../utils/index.js'; import { AccountWalletWithPrivateKey } from '../wallet/index.js'; @@ -25,7 +26,8 @@ export class AccountManager { private completeAddress?: CompleteAddress; private instance?: ContractInstanceWithAddress; private encryptionPublicKey?: PublicKey; - private deployMethod?: DeployMethod; + // TODO(@spalladino): Update to the new deploy method and kill the legacy one. + private deployMethod?: LegacyDeployMethod; constructor( private pxe: PXE, @@ -130,7 +132,11 @@ export class AccountManager { } await this.#register(); const encryptionPublicKey = this.getEncryptionPublicKey(); - const deployer = new ContractDeployer(this.accountContract.getContractArtifact(), this.pxe, encryptionPublicKey); + const deployer = new LegacyContractDeployer( + this.accountContract.getContractArtifact(), + this.pxe, + encryptionPublicKey, + ); const args = this.accountContract.getDeploymentArgs(); this.deployMethod = deployer.deploy(...args); } diff --git a/yarn-project/aztec.js/src/contract/deploy_method.ts b/yarn-project/aztec.js/src/contract/deploy_method.ts index 32cbd28c7c6..0d9a9756125 100644 --- a/yarn-project/aztec.js/src/contract/deploy_method.ts +++ b/yarn-project/aztec.js/src/contract/deploy_method.ts @@ -1,21 +1,23 @@ -import { PXE, PackedArguments, PublicKey, Tx, TxExecutionRequest } from '@aztec/circuit-types'; +import { FunctionCall, PublicKey, Tx, TxExecutionRequest } from '@aztec/circuit-types'; import { AztecAddress, - ContractDeploymentData, - FunctionData, - TxContext, computePartialAddress, + getContractClassFromArtifact, getContractInstanceFromDeployParams, } from '@aztec/circuits.js'; -import { ContractArtifact, FunctionArtifact, encodeArguments } from '@aztec/foundation/abi'; +import { ContractArtifact, FunctionArtifact } from '@aztec/foundation/abi'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; import { ContractInstanceWithAddress } from '@aztec/types/contracts'; import { Wallet } from '../account/index.js'; +import { deployInstance } from '../deployment/deploy_instance.js'; +import { registerContractClass } from '../deployment/register_class.js'; +import { createDebugLogger } from '../index.js'; import { BaseContractInteraction, SendMethodOptions } from './base_contract_interaction.js'; import { type Contract } from './contract.js'; import { ContractBase } from './contract_base.js'; +import { ContractFunctionInteraction } from './contract_function_interaction.js'; import { DeploySentTx } from './deploy_sent_tx.js'; /** @@ -23,35 +25,41 @@ import { DeploySentTx } from './deploy_sent_tx.js'; * Allows specifying a portal contract, contract address salt, and additional send method options. */ export type DeployOptions = { - /** - * The Ethereum address of the Portal contract. - */ + /** The Ethereum address of the Portal contract. */ portalContract?: EthAddress; - /** - * An optional salt value used to deterministically calculate the contract address. - */ + /** An optional salt value used to deterministically calculate the contract address. */ contractAddressSalt?: Fr; + /** Set to true to *not* include the sender in the address computation. */ + universalDeploy?: boolean; + /** Skip contract class registration. */ + skipClassRegistration?: boolean; + /** Skip public deployment and only initialize the contract. */ + skipPublicDeployment?: boolean; } & SendMethodOptions; +// TODO(@spalladino): Add unit tests for this class! + /** - * Creates a TxRequest from a contract ABI, for contract deployment. - * Extends the ContractFunctionInteraction class. + * Contract interaction for deployment. Handles class registration, public instance deployment, + * and initialization of the contract. Extends the BaseContractInteraction class. */ export class DeployMethod extends BaseContractInteraction { /** The contract instance to be deployed. */ - public instance?: ContractInstanceWithAddress = undefined; + private instance?: ContractInstanceWithAddress = undefined; /** Constructor function to call. */ private constructorArtifact: FunctionArtifact; + private log = createDebugLogger('aztec:js:deploy_method'); + constructor( private publicKey: PublicKey, - protected pxe: PXE, + protected wallet: Wallet, private artifact: ContractArtifact, private postDeployCtor: (address: AztecAddress, wallet: Wallet) => Promise, private args: any[] = [], ) { - super(pxe); + super(wallet); const constructorArtifact = artifact.functions.find(f => f.name === 'constructor'); if (!constructorArtifact) { throw new Error('Cannot find constructor in the artifact.'); @@ -68,53 +76,63 @@ export class DeployMethod extends Bas * @param options - An object containing optional deployment settings, including portalContract, contractAddressSalt, and from. * @returns A Promise resolving to an object containing the signed transaction data and other relevant information. */ - public async create(options: DeployOptions = {}) { - const portalContract = options.portalContract ?? EthAddress.ZERO; - const contractAddressSalt = options.contractAddressSalt ?? Fr.random(); - - const { chainId, protocolVersion } = await this.pxe.getNodeInfo(); - - const deployParams = [this.artifact, this.args, contractAddressSalt, this.publicKey, portalContract] as const; - const instance = getContractInstanceFromDeployParams(...deployParams); - const address = instance.address; - - const contractDeploymentData = new ContractDeploymentData( - this.publicKey, - instance.initializationHash, - instance.contractClassId, - contractAddressSalt, - portalContract, - ); + public async create(options: DeployOptions = {}): Promise { + if (!this.txRequest) { + this.txRequest = await this.wallet.createTxExecutionRequest(await this.request(options)); + // TODO: Should we add the contracts to the DB here, or once the tx has been sent or mined? + await this.pxe.addContracts([{ artifact: this.artifact, instance: this.instance! }]); + } + return this.txRequest; + } + + /** + * Returns an array of function calls that represent this operation. Useful as a building + * block for constructing batch requests. + * @param options - Deployment options. + * @returns An array of function calls. + * @remarks This method does not have the same return type as the `request` in the ContractInteraction object, + * it returns a promise for an array instead of a function call directly. + */ + public async request(options: DeployOptions = {}): Promise { + const calls: FunctionCall[] = []; + + // Set contract instance object so it's available for populating the DeploySendTx object + const instance = this.getInstance(options); + + // Obtain contract class from artifact and check it matches the reported one by the instance. + // TODO(@spalladino): We're unnecessarily calculating the contract class multiple times here. + const contractClass = getContractClassFromArtifact(this.artifact); + if (!instance.contractClassId.equals(contractClass.id)) { + throw new Error( + `Contract class mismatch when deploying contract: got ${instance.contractClassId.toString()} from instance and ${contractClass.id.toString()} from artifact`, + ); + } + + // Register the contract class if it hasn't been published already. + if (!options.skipClassRegistration) { + if (await this.pxe.isContractClassPubliclyRegistered(contractClass.id)) { + this.log( + `Skipping registration of already registered contract class ${contractClass.id.toString()} for ${instance.address.toString()}`, + ); + } else { + this.log( + `Creating request for registering contract class ${contractClass.id.toString()} as part of deployment for ${instance.address.toString()}`, + ); + calls.push((await registerContractClass(this.wallet, this.artifact)).request()); + } + } - const txContext = new TxContext( - false, - false, - true, - contractDeploymentData, - new Fr(chainId), - new Fr(protocolVersion), + // Deploy the contract via the instance deployer. + if (!options.skipPublicDeployment) { + calls.push(deployInstance(this.wallet, instance, { universalDeploy: options.universalDeploy }).request()); + } + + // Call the constructor. + calls.push( + new ContractFunctionInteraction(this.wallet, instance.address, this.constructorArtifact, this.args).request(), ); - const args = encodeArguments(this.constructorArtifact, this.args); - const functionData = FunctionData.fromAbi(this.constructorArtifact); - const execution = { args, functionData, to: address }; - const packedArguments = PackedArguments.fromArgs(execution.args); - - const txRequest = TxExecutionRequest.from({ - origin: execution.to, - functionData: execution.functionData, - argsHash: packedArguments.hash, - txContext, - packedArguments: [packedArguments], - authWitnesses: [], - }); - - this.txRequest = txRequest; - this.instance = instance; - - // TODO: Should we add the contracts to the DB here, or once the tx has been sent or mined? - await this.pxe.addContracts([{ artifact: this.artifact, instance }]); - return this.txRequest; + return calls; } /** @@ -127,7 +145,27 @@ export class DeployMethod extends Bas */ public send(options: DeployOptions = {}): DeploySentTx { const txHashPromise = super.send(options).getTxHash(); - return new DeploySentTx(this.pxe, txHashPromise, this.postDeployCtor, this.instance!); + // Note the bang on this.instance is brittle: it depends on super.send setting the contract instance + // before any `await` operation, otherwise it'll be undefined by the time we get here. Tests should + // catch it easily though, but if you start seeing instance.address being undefined in DeploySentTx, + // this is probably the culprit. + return new DeploySentTx(this.pxe, txHashPromise, this.postDeployCtor, this.getInstance(options)); + } + + /** + * Builds the contract instance to be deployed and returns it. + * + * @param options - An object containing various deployment options. + * @returns An instance object. + */ + public getInstance(options: DeployOptions = {}): ContractInstanceWithAddress { + if (!this.instance) { + const portalContract = options.portalContract ?? EthAddress.ZERO; + const contractAddressSalt = options.contractAddressSalt ?? Fr.random(); + const deployParams = [this.artifact, this.args, contractAddressSalt, this.publicKey, portalContract] as const; + this.instance = getContractInstanceFromDeployParams(...deployParams); + } + return this.instance; } /** diff --git a/yarn-project/aztec.js/src/contract/deploy_sent_tx.ts b/yarn-project/aztec.js/src/contract/deploy_sent_tx.ts index a88be16ddf1..a9898e71267 100644 --- a/yarn-project/aztec.js/src/contract/deploy_sent_tx.ts +++ b/yarn-project/aztec.js/src/contract/deploy_sent_tx.ts @@ -29,7 +29,7 @@ export class DeploySentTx extends SentTx txHashPromise: Promise, private postDeployCtor: (address: AztecAddress, wallet: Wallet) => Promise, /** The deployed contract instance */ - public instance?: ContractInstanceWithAddress, + public instance: ContractInstanceWithAddress, ) { super(wallet, txHashPromise); } @@ -51,19 +51,16 @@ export class DeploySentTx extends SentTx */ public async wait(opts?: DeployedWaitOpts): Promise> { const receipt = await super.wait(opts); - const contract = await this.getContractObject(opts?.wallet, receipt.contractAddress); + const contract = await this.getContractObject(opts?.wallet); return { ...receipt, contract }; } - protected getContractObject(wallet?: Wallet, address?: AztecAddress): Promise { + protected getContractObject(wallet?: Wallet): Promise { const isWallet = (pxe: PXE | Wallet): pxe is Wallet => !!(pxe as Wallet).createTxExecutionRequest; const contractWallet = wallet ?? (isWallet(this.pxe) && this.pxe); if (!contractWallet) { throw new Error(`A wallet is required for creating a contract instance`); } - if (!address) { - throw new Error(`Contract address is missing from transaction receipt`); - } - return this.postDeployCtor(address, contractWallet) as Promise; + return this.postDeployCtor(this.instance.address, contractWallet) as Promise; } } diff --git a/yarn-project/aztec.js/src/deployment/contract_deployer.test.ts b/yarn-project/aztec.js/src/deployment/contract_deployer.test.ts deleted file mode 100644 index 6fabd46e2ac..00000000000 --- a/yarn-project/aztec.js/src/deployment/contract_deployer.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { PXE, PublicKey, Tx, TxHash, TxReceipt } from '@aztec/circuit-types'; -import { EthAddress, Fr, Point } from '@aztec/circuits.js'; -import { ContractArtifact, FunctionType } from '@aztec/foundation/abi'; - -import { MockProxy, mock } from 'jest-mock-extended'; - -import { ContractDeployer } from './contract_deployer.js'; - -describe.skip('Contract Deployer', () => { - let pxe: MockProxy; - - const artifact: ContractArtifact = { - name: 'MyContract', - functions: [ - { - name: 'constructor', - functionType: FunctionType.SECRET, - isInternal: false, - parameters: [], - returnTypes: [], - bytecode: '0af', - debugSymbols: '', - }, - ], - events: [], - fileMap: {}, - }; - - const publicKey: PublicKey = Point.random(); - const portalContract = EthAddress.random(); - const contractAddressSalt = Fr.random(); - const args = [12, 345n]; - - const mockTx = { type: 'Tx' } as any as Tx; - const mockTxHash = { type: 'TxHash' } as any as TxHash; - const mockTxReceipt = { type: 'TxReceipt' } as any as TxReceipt; - - beforeEach(() => { - pxe = mock(); - pxe.sendTx.mockResolvedValue(mockTxHash); - pxe.getTxReceipt.mockResolvedValue(mockTxReceipt); - }); - - it('should create and send a contract deployment tx', async () => { - const deployer = new ContractDeployer(artifact, pxe, publicKey); - const sentTx = deployer.deploy(args[0], args[1]).send({ - portalContract, - contractAddressSalt, - }); - const txHash = await sentTx.getTxHash(); - const receipt = await sentTx.getReceipt(); - - expect(txHash).toBe(mockTxHash); - expect(receipt).toBe(mockTxReceipt); - expect(pxe.sendTx).toHaveBeenCalledTimes(1); - expect(pxe.sendTx).toHaveBeenCalledWith(mockTx); - }); -}); diff --git a/yarn-project/aztec.js/src/deployment/contract_deployer.ts b/yarn-project/aztec.js/src/deployment/contract_deployer.ts index 8c6aaf2a0a3..9c674b30691 100644 --- a/yarn-project/aztec.js/src/deployment/contract_deployer.ts +++ b/yarn-project/aztec.js/src/deployment/contract_deployer.ts @@ -1,4 +1,4 @@ -import { PXE, PublicKey } from '@aztec/circuit-types'; +import { PublicKey } from '@aztec/circuit-types'; import { AztecAddress } from '@aztec/circuits.js'; import { ContractArtifact } from '@aztec/foundation/abi'; import { Point } from '@aztec/foundation/fields'; @@ -12,7 +12,7 @@ import { Contract } from '../contract/index.js'; * @remarks Keeping this around even though we have Aztec.nr contract types because it can be useful for non-TS users. */ export class ContractDeployer { - constructor(private artifact: ContractArtifact, private pxe: PXE, private publicKey?: PublicKey) {} + constructor(private artifact: ContractArtifact, private wallet: Wallet, private publicKey?: PublicKey) {} /** * Deploy a contract using the provided ABI and constructor arguments. @@ -25,6 +25,6 @@ export class ContractDeployer { */ public deploy(...args: any[]) { const postDeployCtor = (address: AztecAddress, wallet: Wallet) => Contract.at(address, this.artifact, wallet); - return new DeployMethod(this.publicKey ?? Point.ZERO, this.pxe, this.artifact, postDeployCtor, args); + return new DeployMethod(this.publicKey ?? Point.ZERO, this.wallet, this.artifact, postDeployCtor, args); } } diff --git a/yarn-project/aztec.js/src/deployment/legacy/legacy_contract_deployer.ts b/yarn-project/aztec.js/src/deployment/legacy/legacy_contract_deployer.ts new file mode 100644 index 00000000000..4e84fa7e2ee --- /dev/null +++ b/yarn-project/aztec.js/src/deployment/legacy/legacy_contract_deployer.ts @@ -0,0 +1,30 @@ +import { PXE, PublicKey } from '@aztec/circuit-types'; +import { AztecAddress } from '@aztec/circuits.js'; +import { ContractArtifact } from '@aztec/foundation/abi'; +import { Point } from '@aztec/foundation/fields'; + +import { Wallet } from '../../account/wallet.js'; +import { Contract } from '../../contract/index.js'; +import { LegacyDeployMethod } from './legacy_deploy_method.js'; + +/** + * A class for deploying contract using the legacy deployment workflow. Used for account deployment. To be removed soon. + * @remarks Keeping this around even though we have Aztec.nr contract types because it can be useful for non-TS users. + */ +export class LegacyContractDeployer { + constructor(private artifact: ContractArtifact, private pxe: PXE, private publicKey?: PublicKey) {} + + /** + * Deploy a contract using the provided ABI and constructor arguments. + * This function creates a new DeployMethod instance that can be used to send deployment transactions + * and query deployment status. The method accepts any number of constructor arguments, which will + * be passed to the contract's constructor during deployment. + * + * @param args - The constructor arguments for the contract being deployed. + * @returns A DeployMethod instance configured with the ABI, PXE, and constructor arguments. + */ + public deploy(...args: any[]) { + const postDeployCtor = (address: AztecAddress, wallet: Wallet) => Contract.at(address, this.artifact, wallet); + return new LegacyDeployMethod(this.publicKey ?? Point.ZERO, this.pxe, this.artifact, postDeployCtor, args); + } +} diff --git a/yarn-project/aztec.js/src/deployment/legacy/legacy_deploy_method.ts b/yarn-project/aztec.js/src/deployment/legacy/legacy_deploy_method.ts new file mode 100644 index 00000000000..3461c2d3b8a --- /dev/null +++ b/yarn-project/aztec.js/src/deployment/legacy/legacy_deploy_method.ts @@ -0,0 +1,151 @@ +import { PXE, PackedArguments, PublicKey, Tx, TxExecutionRequest } from '@aztec/circuit-types'; +import { + AztecAddress, + ContractDeploymentData, + FunctionData, + TxContext, + computePartialAddress, + getContractInstanceFromDeployParams, +} from '@aztec/circuits.js'; +import { ContractArtifact, FunctionArtifact, encodeArguments } from '@aztec/foundation/abi'; +import { EthAddress } from '@aztec/foundation/eth-address'; +import { Fr } from '@aztec/foundation/fields'; +import { ContractInstanceWithAddress } from '@aztec/types/contracts'; + +import { Wallet } from '../../account/index.js'; +import { BaseContractInteraction, SendMethodOptions } from '../../contract/base_contract_interaction.js'; +import { type Contract } from '../../contract/contract.js'; +import { ContractBase } from '../../contract/contract_base.js'; +import { DeploySentTx } from '../../contract/deploy_sent_tx.js'; + +/** + * Options for deploying a contract on the Aztec network. + * Allows specifying a portal contract, contract address salt, and additional send method options. + */ +export type DeployOptions = { + /** + * The Ethereum address of the Portal contract. + */ + portalContract?: EthAddress; + /** + * An optional salt value used to deterministically calculate the contract address. + */ + contractAddressSalt?: Fr; +} & SendMethodOptions; + +/** + * Creates a TxRequest from a contract ABI, for contract deployment. + * Extends the ContractFunctionInteraction class. + */ +export class LegacyDeployMethod extends BaseContractInteraction { + /** The contract instance to be deployed. */ + public instance?: ContractInstanceWithAddress = undefined; + + /** Constructor function to call. */ + private constructorArtifact: FunctionArtifact; + + constructor( + private publicKey: PublicKey, + protected pxe: PXE, + private artifact: ContractArtifact, + private postDeployCtor: (address: AztecAddress, wallet: Wallet) => Promise, + private args: any[] = [], + ) { + super(pxe); + const constructorArtifact = artifact.functions.find(f => f.name === 'constructor'); + if (!constructorArtifact) { + throw new Error('Cannot find constructor in the artifact.'); + } + this.constructorArtifact = constructorArtifact; + } + + /** + * Create a contract deployment transaction, given the deployment options. + * This function internally calls `request()` and `sign()` methods to prepare + * the transaction for deployment. The resulting signed transaction can be + * later sent using the `send()` method. + * + * @param options - An object containing optional deployment settings, including portalContract, contractAddressSalt, and from. + * @returns A Promise resolving to an object containing the signed transaction data and other relevant information. + */ + public async create(options: DeployOptions = {}) { + const portalContract = options.portalContract ?? EthAddress.ZERO; + const contractAddressSalt = options.contractAddressSalt ?? Fr.random(); + + const { chainId, protocolVersion } = await this.pxe.getNodeInfo(); + + const deployParams = [this.artifact, this.args, contractAddressSalt, this.publicKey, portalContract] as const; + const instance = getContractInstanceFromDeployParams(...deployParams); + const address = instance.address; + + const contractDeploymentData = new ContractDeploymentData( + this.publicKey, + instance.initializationHash, + instance.contractClassId, + contractAddressSalt, + portalContract, + ); + + const txContext = new TxContext( + false, + false, + true, + contractDeploymentData, + new Fr(chainId), + new Fr(protocolVersion), + ); + const args = encodeArguments(this.constructorArtifact, this.args); + const functionData = FunctionData.fromAbi(this.constructorArtifact); + const execution = { args, functionData, to: address }; + const packedArguments = PackedArguments.fromArgs(execution.args); + + const txRequest = TxExecutionRequest.from({ + origin: execution.to, + functionData: execution.functionData, + argsHash: packedArguments.hash, + txContext, + packedArguments: [packedArguments], + authWitnesses: [], + }); + + this.txRequest = txRequest; + this.instance = instance; + + // TODO: Should we add the contracts to the DB here, or once the tx has been sent or mined? + await this.pxe.addContracts([{ artifact: this.artifact, instance }]); + + return this.txRequest; + } + + /** + * Send the contract deployment transaction using the provided options. + * This function extends the 'send' method from the ContractFunctionInteraction class, + * allowing us to send a transaction specifically for contract deployment. + * + * @param options - An object containing various deployment options such as portalContract, contractAddressSalt, and from. + * @returns A SentTx object that returns the receipt and the deployed contract instance. + */ + public send(options: DeployOptions = {}): DeploySentTx { + const txHashPromise = super.send(options).getTxHash(); + return new DeploySentTx(this.pxe, txHashPromise, this.postDeployCtor, this.instance!); + } + + /** + * Simulate the request. + * @param options - Deployment options. + * @returns The simulated tx. + */ + public simulate(options: DeployOptions): Promise { + return super.simulate(options); + } + + /** Return this deployment address. */ + public get address() { + return this.instance?.address; + } + + /** Returns the partial address for this deployment. */ + public get partialAddress() { + return this.instance && computePartialAddress(this.instance); + } +} diff --git a/yarn-project/aztec.js/src/deployment/register_class.ts b/yarn-project/aztec.js/src/deployment/register_class.ts index 4ac0532bef5..8d5efd0107f 100644 --- a/yarn-project/aztec.js/src/deployment/register_class.ts +++ b/yarn-project/aztec.js/src/deployment/register_class.ts @@ -6,10 +6,14 @@ import { Wallet } from '../wallet/index.js'; import { getRegistererContract } from './protocol_contracts.js'; /** Sets up a call to register a contract class given its artifact. */ -export function registerContractClass(wallet: Wallet, artifact: ContractArtifact): ContractFunctionInteraction { +export async function registerContractClass( + wallet: Wallet, + artifact: ContractArtifact, +): Promise { const { artifactHash, privateFunctionsRoot, publicBytecodeCommitment, packedBytecode } = getContractClassFromArtifact(artifact); const encodedBytecode = bufferAsFields(packedBytecode, MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS); const registerer = getRegistererContract(wallet); - return registerer.methods.register(artifactHash, privateFunctionsRoot, publicBytecodeCommitment, encodedBytecode); + await wallet.addCapsule(encodedBytecode); + return registerer.methods.register(artifactHash, privateFunctionsRoot, publicBytecodeCommitment); } diff --git a/yarn-project/aztec.js/src/index.ts b/yarn-project/aztec.js/src/index.ts index c4b950f624d..c20a90ed4e4 100644 --- a/yarn-project/aztec.js/src/index.ts +++ b/yarn-project/aztec.js/src/index.ts @@ -32,6 +32,7 @@ export { } from './contract/index.js'; export { ContractDeployer } from './deployment/index.js'; +export { LegacyContractDeployer } from './deployment/legacy/legacy_contract_deployer.js'; export { generatePublicKey, diff --git a/yarn-project/aztec.js/src/utils/l2_contracts.ts b/yarn-project/aztec.js/src/utils/l2_contracts.ts index f734f26b364..a694049be80 100644 --- a/yarn-project/aztec.js/src/utils/l2_contracts.ts +++ b/yarn-project/aztec.js/src/utils/l2_contracts.ts @@ -8,5 +8,5 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; * @returns A flag indicating whether the contract is deployed. */ export async function isContractDeployed(pxe: PXE, contractAddress: AztecAddress): Promise { - return !!(await pxe.getContractData(contractAddress)); + return !!(await pxe.getContractData(contractAddress)) || !!(await pxe.getContractInstance(contractAddress)); } diff --git a/yarn-project/aztec.js/src/wallet/base_wallet.ts b/yarn-project/aztec.js/src/wallet/base_wallet.ts index 8f6519bc0fe..63b732c62e6 100644 --- a/yarn-project/aztec.js/src/wallet/base_wallet.ts +++ b/yarn-project/aztec.js/src/wallet/base_wallet.ts @@ -18,7 +18,7 @@ import { TxReceipt, } from '@aztec/circuit-types'; import { AztecAddress, CompleteAddress, Fr, GrumpkinPrivateKey, PartialAddress } from '@aztec/circuits.js'; -import { ContractInstanceWithAddress } from '@aztec/types/contracts'; +import { ContractClassWithId, ContractInstanceWithAddress } from '@aztec/types/contracts'; import { NodeInfo } from '@aztec/types/interfaces'; import { FeeOptions } from '../account/interface.js'; @@ -39,6 +39,9 @@ export abstract class BaseWallet implements Wallet { getContractInstance(address: AztecAddress): Promise { return this.pxe.getContractInstance(address); } + getContractClass(id: Fr): Promise { + return this.pxe.getContractClass(id); + } addCapsule(capsule: Fr[]): Promise { return this.pxe.addCapsule(capsule); } @@ -120,4 +123,7 @@ export abstract class BaseWallet implements Wallet { addAuthWitness(authWitness: AuthWitness) { return this.pxe.addAuthWitness(authWitness); } + isContractClassPubliclyRegistered(id: Fr): Promise { + return this.pxe.isContractClassPubliclyRegistered(id); + } } diff --git a/yarn-project/circuit-types/src/contract_data.ts b/yarn-project/circuit-types/src/contract_data.ts index 7770cd81fd9..c6d4f6a2b5f 100644 --- a/yarn-project/circuit-types/src/contract_data.ts +++ b/yarn-project/circuit-types/src/contract_data.ts @@ -1,4 +1,4 @@ -import { FUNCTION_SELECTOR_NUM_BYTES, Fr, FunctionSelector } from '@aztec/circuits.js'; +import { FUNCTION_SELECTOR_NUM_BYTES, Fr, FunctionSelector, computeSaltedInitializationHash } from '@aztec/circuits.js'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { randomBytes } from '@aztec/foundation/crypto'; import { EthAddress } from '@aztec/foundation/eth-address'; @@ -8,7 +8,7 @@ import { serializeBufferArrayToVector, serializeToBuffer, } from '@aztec/foundation/serialize'; -import { ContractClassPublic, ContractInstanceWithAddress } from '@aztec/types/contracts'; +import { ContractClass, ContractClassPublic, ContractInstanceWithAddress } from '@aztec/types/contracts'; /** * Used for retrieval of contract data (A3 address, portal contract address, bytecode). @@ -68,6 +68,9 @@ export interface ContractDataSource { * @param address - Address of the deployed contract. */ getContract(address: AztecAddress): Promise; + + /** Returns the list of all class ids known. */ + getContractClassIds(): Promise; } /** @@ -249,6 +252,20 @@ export class ExtendedContractData { static empty(): ExtendedContractData { return new ExtendedContractData(ContractData.empty(), [], Fr.ZERO, Fr.ZERO, Fr.ZERO); } + + /** Temporary method for creating extended contract data out of classes and instances */ + static fromClassAndInstance( + contractClass: Pick, + instance: ContractInstanceWithAddress, + ) { + return new ExtendedContractData( + new ContractData(instance.address, instance.portalContractAddress), + contractClass.publicFunctions.map(f => new EncodedContractFunction(f.selector, f.isInternal, f.bytecode)), + instance.contractClassId, + computeSaltedInitializationHash(instance), + instance.publicKeysHash, + ); + } } /** diff --git a/yarn-project/circuit-types/src/interfaces/pxe.ts b/yarn-project/circuit-types/src/interfaces/pxe.ts index eb3671bbf92..eaa4fcc63df 100644 --- a/yarn-project/circuit-types/src/interfaces/pxe.ts +++ b/yarn-project/circuit-types/src/interfaces/pxe.ts @@ -1,5 +1,5 @@ import { AztecAddress, CompleteAddress, Fr, GrumpkinPrivateKey, PartialAddress } from '@aztec/circuits.js'; -import { ContractInstanceWithAddress } from '@aztec/types/contracts'; +import { ContractClassWithId, ContractInstanceWithAddress } from '@aztec/types/contracts'; import { NodeInfo } from '@aztec/types/interfaces'; import { AuthWitness } from '../auth_witness.js'; @@ -266,9 +266,26 @@ export interface PXE { /** * Returns a Contact Instance given its address, which includes the contract class identifier, portal address, * initialization hash, deployment salt, and public keys hash. - * TOOD(@spalladino): Should we return the public keys in plain as well here? - * @param address + * TODO(@spalladino): Should we return the public keys in plain as well here? + * @param address - Deployment address of the contract. */ getContractInstance(address: AztecAddress): Promise; + + /** + * Returns a Contact Class given its identifier. + * TODO(@spalladino): The PXE actually holds artifacts and not classes, what should we return? Also, + * should the pxe query the node for contract public info, and merge it with its own definitions? + * @param id - Identifier of the class. + */ + getContractClass(id: Fr): Promise; + + /** + * Queries the node to check whether the contract class with the given id has been publicly registered. + * TODO(@spalladino): This method is strictly needed to decide whether to publicly register a class or not + * during a public deployment. We probably want a nicer and more general API for this, but it'll have to + * do for the time being. + * @param id - Identifier of the class. + */ + isContractClassPubliclyRegistered(id: Fr): Promise; } // docs:end:pxe-interface diff --git a/yarn-project/circuit-types/src/l2_block.ts b/yarn-project/circuit-types/src/l2_block.ts index c9efbb09ab8..c2da9e92ece 100644 --- a/yarn-project/circuit-types/src/l2_block.ts +++ b/yarn-project/circuit-types/src/l2_block.ts @@ -10,7 +10,7 @@ import { NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, STRING_ENCODING, } from '@aztec/circuits.js'; -import { makeAppendOnlyTreeSnapshot, makeHeader } from '@aztec/circuits.js/factories'; +import { makeAppendOnlyTreeSnapshot, makeHeader } from '@aztec/circuits.js/testing'; import { makeTuple } from '@aztec/foundation/array'; import { times } from '@aztec/foundation/collection'; import { sha256 } from '@aztec/foundation/crypto'; diff --git a/yarn-project/circuit-types/src/logs/tx_l2_logs.ts b/yarn-project/circuit-types/src/logs/tx_l2_logs.ts index e7d6be8eb43..e6257ed02f0 100644 --- a/yarn-project/circuit-types/src/logs/tx_l2_logs.ts +++ b/yarn-project/circuit-types/src/logs/tx_l2_logs.ts @@ -139,4 +139,9 @@ export class TxL2Logs { return kernelPublicInputsLogsHash; } + + /** Creates an empty instance. */ + public static empty() { + return new TxL2Logs([]); + } } diff --git a/yarn-project/circuit-types/src/mocks.ts b/yarn-project/circuit-types/src/mocks.ts index 0cb2baaea77..e82e59428b0 100644 --- a/yarn-project/circuit-types/src/mocks.ts +++ b/yarn-project/circuit-types/src/mocks.ts @@ -7,7 +7,7 @@ import { MAX_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, Proof, } from '@aztec/circuits.js'; -import { makePrivateKernelTailCircuitPublicInputs, makePublicCallRequest } from '@aztec/circuits.js/factories'; +import { makePrivateKernelTailCircuitPublicInputs, makePublicCallRequest } from '@aztec/circuits.js/testing'; import { ContractArtifact } from '@aztec/foundation/abi'; import { makeTuple } from '@aztec/foundation/array'; import { times } from '@aztec/foundation/collection'; diff --git a/yarn-project/circuit-types/src/tx/tx_receipt.ts b/yarn-project/circuit-types/src/tx/tx_receipt.ts index 0bcd900acc7..0136ab07026 100644 --- a/yarn-project/circuit-types/src/tx/tx_receipt.ts +++ b/yarn-project/circuit-types/src/tx/tx_receipt.ts @@ -1,4 +1,3 @@ -import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; import { ContractData } from '../contract_data.js'; @@ -41,10 +40,6 @@ export class TxReceipt { * The block number in which the transaction was included. */ public blockNumber?: number, - /** - * The deployed contract's address. - */ - public contractAddress?: AztecAddress, /** * Information useful for testing/debugging, set when test flag is set to true in `waitOpts`. */ @@ -62,7 +57,6 @@ export class TxReceipt { error: this.error, blockHash: this.blockHash?.toString('hex'), blockNumber: this.blockNumber, - contractAddress: this.contractAddress?.toString(), }; } @@ -77,8 +71,7 @@ export class TxReceipt { const error = obj.error; const blockHash = obj.blockHash ? Buffer.from(obj.blockHash, 'hex') : undefined; const blockNumber = obj.blockNumber ? Number(obj.blockNumber) : undefined; - const contractAddress = obj.contractAddress ? AztecAddress.fromString(obj.contractAddress) : undefined; - return new TxReceipt(txHash, status, error, blockHash, blockNumber, contractAddress); + return new TxReceipt(txHash, status, error, blockHash, blockNumber); } } diff --git a/yarn-project/circuits.js/package.json b/yarn-project/circuits.js/package.json index 715e844ad06..344a8f220f1 100644 --- a/yarn-project/circuits.js/package.json +++ b/yarn-project/circuits.js/package.json @@ -6,7 +6,7 @@ ".": "./dest/index.js", "./hash": "./dest/hash/index.js", "./barretenberg": "./dest/barretenberg/index.js", - "./factories": "./dest/tests/factories.js", + "./testing": "./dest/tests/index.js", "./utils": "./dest/utils/index.js", "./types": "./dest/types/index.js", "./constants": "./dest/constants.gen.js", diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index 776afeed222..8b3295b2af8 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -58,7 +58,7 @@ export const FUNCTION_SELECTOR_NUM_BYTES = 4; export const NUM_FIELDS_PER_SHA256 = 2; export const ARGS_HASH_CHUNK_LENGTH = 32; export const ARGS_HASH_CHUNK_COUNT = 32; -export const MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS = 1000; +export const MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS = 8000; export const MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS = 500; export const MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS = 500; export const REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE = diff --git a/yarn-project/circuits.js/src/contract/contract_class_registered_event.ts b/yarn-project/circuits.js/src/contract/contract_class_registered_event.ts index d0acde29dac..1c028930a4f 100644 --- a/yarn-project/circuits.js/src/contract/contract_class_registered_event.ts +++ b/yarn-project/circuits.js/src/contract/contract_class_registered_event.ts @@ -1,4 +1,5 @@ import { bufferFromFields } from '@aztec/foundation/abi'; +import { AztecAddress } from '@aztec/foundation/aztec-address'; import { toBigIntBE } from '@aztec/foundation/bigint-buffer'; import { Fr } from '@aztec/foundation/fields'; import { BufferReader } from '@aztec/foundation/serialize'; @@ -24,10 +25,18 @@ export class ContractClassRegisteredEvent { return toBigIntBE(log.subarray(0, 32)) == REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE; } + static fromLogs(logs: { contractAddress: AztecAddress; data: Buffer }[], registererContractAddress: AztecAddress) { + return logs + .filter(log => ContractClassRegisteredEvent.isContractClassRegisteredEvent(log.data)) + .filter(log => log.contractAddress.equals(registererContractAddress)) + .map(log => this.fromLogData(log.data)); + } + static fromLogData(log: Buffer) { if (!this.isContractClassRegisteredEvent(log)) { - const magicValue = REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE.toString(16); - throw new Error(`Log data for ContractClassRegisteredEvent is not prefixed with magic value 0x${magicValue}`); + throw new Error( + `Log data for ContractClassRegisteredEvent is not prefixed with magic value 0x${REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE}`, + ); } const reader = new BufferReader(log.subarray(32)); const contractClassId = reader.readObject(Fr); diff --git a/yarn-project/circuits.js/src/contract/contract_instance_deployed_event.ts b/yarn-project/circuits.js/src/contract/contract_instance_deployed_event.ts index 45af7ba5187..a2fdd4c6704 100644 --- a/yarn-project/circuits.js/src/contract/contract_instance_deployed_event.ts +++ b/yarn-project/circuits.js/src/contract/contract_instance_deployed_event.ts @@ -1,10 +1,11 @@ +import { AztecAddress } from '@aztec/foundation/aztec-address'; import { toBigIntBE } from '@aztec/foundation/bigint-buffer'; +import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; import { BufferReader } from '@aztec/foundation/serialize'; import { ContractInstanceWithAddress } from '@aztec/types/contracts'; import { DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE } from '../constants.gen.js'; -import { AztecAddress, EthAddress } from '../index.js'; /** Event emitted from the ContractInstanceDeployer. */ export class ContractInstanceDeployedEvent { @@ -23,6 +24,16 @@ export class ContractInstanceDeployedEvent { return toBigIntBE(log.subarray(0, 32)) == DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE; } + // TODO(@spalladino) We should be loading the singleton address from protocol-contracts, + // but the protocol-contracts package depends on this one, and we cannot have circular dependencies, + // hence we require it as an argument for now. + static fromLogs(logs: { contractAddress: AztecAddress; data: Buffer }[], instanceDeployerAddress: AztecAddress) { + return logs + .filter(log => ContractInstanceDeployedEvent.isContractInstanceDeployedEvent(log.data)) + .filter(log => log.contractAddress.equals(instanceDeployerAddress)) + .map(log => ContractInstanceDeployedEvent.fromLogData(log.data)); + } + static fromLogData(log: Buffer) { if (!this.isContractInstanceDeployedEvent(log)) { const magicValue = DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE.toString(16); diff --git a/yarn-project/circuits.js/src/hash/hash.ts b/yarn-project/circuits.js/src/hash/hash.ts index c545af04dfc..44cc9ec964f 100644 --- a/yarn-project/circuits.js/src/hash/hash.ts +++ b/yarn-project/circuits.js/src/hash/hash.ts @@ -2,6 +2,7 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; import { padArrayEnd } from '@aztec/foundation/collection'; import { pedersenHash, pedersenHashBuffer } from '@aztec/foundation/crypto'; import { Fr } from '@aztec/foundation/fields'; +import { createDebugLogger } from '@aztec/foundation/log'; import { numToUInt8, numToUInt16BE, numToUInt32BE } from '@aztec/foundation/serialize'; import { Buffer } from 'buffer'; @@ -180,8 +181,12 @@ export function computeVarArgsHash(args: Fr[]) { if (args.length === 0) { return Fr.ZERO; } - if (args.length > ARGS_HASH_CHUNK_LENGTH * ARGS_HASH_CHUNK_COUNT) { - throw new Error(`Cannot hash more than ${ARGS_HASH_CHUNK_LENGTH * ARGS_HASH_CHUNK_COUNT} arguments`); + const maxLen = ARGS_HASH_CHUNK_LENGTH * ARGS_HASH_CHUNK_COUNT; + if (args.length > maxLen) { + // TODO(@spalladino): This should throw instead of warning. And we should implement + // the same check on the Noir side, which is currently missing. + args = args.slice(0, maxLen); + createDebugLogger('aztec:circuits:abis').warn(`Hashing ${args.length} args exceeds max of ${maxLen}`); } let chunksHashes = chunk(args, ARGS_HASH_CHUNK_LENGTH).map(c => { diff --git a/yarn-project/circuits.js/src/tests/index.ts b/yarn-project/circuits.js/src/tests/index.ts new file mode 100644 index 00000000000..4fa91dd0835 --- /dev/null +++ b/yarn-project/circuits.js/src/tests/index.ts @@ -0,0 +1,2 @@ +export * from './fixtures.js'; +export * from './factories.js'; diff --git a/yarn-project/cli/src/cmds/deploy.ts b/yarn-project/cli/src/cmds/deploy.ts index 1687d275829..faaa1c33127 100644 --- a/yarn-project/cli/src/cmds/deploy.ts +++ b/yarn-project/cli/src/cmds/deploy.ts @@ -1,4 +1,5 @@ -import { ContractDeployer, EthAddress, Fr, Point } from '@aztec/aztec.js'; +import { getSchnorrAccount } from '@aztec/accounts/schnorr'; +import { ContractDeployer, EthAddress, Fq, Fr, Point } from '@aztec/aztec.js'; import { DebugLogger, LogFn } from '@aztec/foundation/log'; import { createCompatibleClient } from '../client.js'; @@ -14,6 +15,7 @@ export async function deploy( rawArgs: any[], portalAddress: EthAddress, salt: Fr, + privateKey: Fq, wait: boolean, debugLogger: DebugLogger, log: LogFn, @@ -31,7 +33,8 @@ export async function deploy( ); } - const deployer = new ContractDeployer(contractArtifact, client, publicKey); + const wallet = await getSchnorrAccount(client, privateKey, privateKey, Fr.ZERO).getWallet(); + const deployer = new ContractDeployer(contractArtifact, wallet, publicKey); const constructor = getFunctionArtifact(contractArtifact, 'constructor'); if (!constructor) { diff --git a/yarn-project/cli/src/cmds/inspect_contract.ts b/yarn-project/cli/src/cmds/inspect_contract.ts index ad8aafa871d..8f102631152 100644 --- a/yarn-project/cli/src/cmds/inspect_contract.ts +++ b/yarn-project/cli/src/cmds/inspect_contract.ts @@ -1,4 +1,6 @@ +import { getContractClassFromArtifact } from '@aztec/circuits.js'; import { + FunctionArtifact, FunctionSelector, decodeFunctionSignature, decodeFunctionSignatureWithParameterNames, @@ -9,18 +11,30 @@ import { getContractArtifact } from '../utils.js'; export async function inspectContract(contractArtifactFile: string, debugLogger: DebugLogger, log: LogFn) { const contractArtifact = await getContractArtifact(contractArtifactFile, debugLogger); - const contractFns = contractArtifact.functions.filter( - f => !f.isInternal && f.name !== 'compute_note_hash_and_nullifier', - ); + const contractFns = contractArtifact.functions.filter(f => f.name !== 'compute_note_hash_and_nullifier'); if (contractFns.length === 0) { - log(`No external functions found for contract ${contractArtifact.name}`); - } - for (const fn of contractFns) { - const signatureWithParameterNames = decodeFunctionSignatureWithParameterNames(fn.name, fn.parameters); - const signature = decodeFunctionSignature(fn.name, fn.parameters); - const selector = FunctionSelector.fromSignature(signature); - log( - `${fn.functionType} ${signatureWithParameterNames} \n\tfunction signature: ${signature}\n\tselector: ${selector}`, - ); + 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); + log( + `${fn.functionType} ${signatureWithParameterNames} \n\tfunction signature: ${signature}\n\tselector: ${selector}`, + ); } diff --git a/yarn-project/cli/src/index.ts b/yarn-project/cli/src/index.ts index f5ccd6165c1..c895abb416b 100644 --- a/yarn-project/cli/src/index.ts +++ b/yarn-project/cli/src/index.ts @@ -173,11 +173,12 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command { 'Optional deployment salt as a hex string for generating the deployment address.', parseFieldFromHexString, ) + .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') - .action(async (artifactPath, { json, rpcUrl, publicKey, args: rawArgs, portalAddress, salt, wait }) => { + .action(async (artifactPath, { json, rpcUrl, publicKey, args: rawArgs, portalAddress, salt, wait, privateKey }) => { const { deploy } = await import('./cmds/deploy.js'); await deploy( artifactPath, @@ -187,6 +188,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command { rawArgs, portalAddress, salt, + privateKey, wait, debugLogger, log, diff --git a/yarn-project/end-to-end/src/cli_docs_sandbox.test.ts b/yarn-project/end-to-end/src/cli_docs_sandbox.test.ts index 89285cd2776..a3cf6e66db3 100644 --- a/yarn-project/end-to-end/src/cli_docs_sandbox.test.ts +++ b/yarn-project/end-to-end/src/cli_docs_sandbox.test.ts @@ -262,13 +262,17 @@ Accounts found: // Test deploy docs = ` // docs:start:deploy -% aztec-cli deploy TokenContractArtifact --args $ADDRESS TokenName TKN 18 +% aztec-cli deploy TokenContractArtifact --private-key $PRIVATE_KEY --args $ADDRESS TokenName TKN 18 Contract deployed at 0x1ae8eea0dc265fb7f160dae62cc8912686d8a9ed78e821fbdd8bcedc54c06d0f // docs:end:deploy `; - command = docs.split('\n')[2].split('aztec-cli ')[1].replace('$ADDRESS', newAddress.toString()); + command = docs + .split('\n')[2] + .split('aztec-cli ')[1] + .replace('$ADDRESS', newAddress.toString()) + .replace('$PRIVATE_KEY', foundPrivateKey!); await run(command); let foundContractAddress = findInLogs(/Contract\sdeployed\sat\s(?
0x[a-fA-F0-9]+)/)?.groups?.address; diff --git a/yarn-project/end-to-end/src/e2e_block_building.test.ts b/yarn-project/end-to-end/src/e2e_block_building.test.ts index 20fc0d83567..2a982b4c8ef 100644 --- a/yarn-project/end-to-end/src/e2e_block_building.test.ts +++ b/yarn-project/end-to-end/src/e2e_block_building.test.ts @@ -50,9 +50,12 @@ describe('e2e_block_building', () => { await aztecNode.setConfig({ minTxsPerBlock: TX_COUNT }); const deployer = new ContractDeployer(artifact, owner); const methods = times(TX_COUNT, () => deployer.deploy()); - for (let i = 0; i < TX_COUNT; i++) { - await methods[i].create({ contractAddressSalt: new Fr(BigInt(i + 1)) }); + await methods[i].create({ + contractAddressSalt: new Fr(BigInt(i + 1)), + skipClassRegistration: true, + skipPublicDeployment: true, + }); await methods[i].simulate({}); } @@ -69,7 +72,7 @@ describe('e2e_block_building', () => { expect(receipts.map(r => r.blockNumber)).toEqual(times(TX_COUNT, () => receipts[0].blockNumber)); // Assert all contracts got deployed - const areDeployed = await Promise.all(receipts.map(r => isContractDeployed(pxe, r.contractAddress!))); + const areDeployed = await Promise.all(receipts.map(r => isContractDeployed(pxe, r.contract.address))); expect(areDeployed).toEqual(times(TX_COUNT, () => true)); }, 60_000); @@ -86,7 +89,7 @@ describe('e2e_block_building', () => { // but we are in the same block as the deployment transaction const callInteraction = new ContractFunctionInteraction( owner, - deployer.instance!.address, + deployer.getInstance().address, TokenContract.artifact.functions.find(x => x.name === 'set_minter')!, [minter.getCompleteAddress(), true], ); @@ -115,6 +118,7 @@ describe('e2e_block_building', () => { beforeAll(async () => { ({ teardown, pxe, logger, wallet: owner } = await setup(1)); contract = await TestContract.deploy(owner).send().deployed(); + logger(`Test contract deployed at ${contract.address}`); }, 100_000); afterAll(() => teardown()); @@ -143,7 +147,7 @@ describe('e2e_block_building', () => { const nullifier = Fr.random(); const calls = times(2, () => contract.methods.emit_nullifier(nullifier).request()); await expect(new BatchCall(owner, calls).send().wait()).rejects.toThrowError(/dropped/); - }); + }, 30_000); it('drops tx with private nullifier already emitted from public on the same block', async () => { const secret = Fr.random(); @@ -160,7 +164,7 @@ describe('e2e_block_building', () => { } const [tx1, tx2] = calls.map(call => call.send()); await expectXorTx(tx1, tx2); - }); + }, 30_000); }); }); diff --git a/yarn-project/end-to-end/src/e2e_deploy_contract.test.ts b/yarn-project/end-to-end/src/e2e_deploy_contract.test.ts index 50d77af15e9..b26b5d757e0 100644 --- a/yarn-project/end-to-end/src/e2e_deploy_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_deploy_contract.test.ts @@ -3,7 +3,6 @@ import { AztecNode, BatchCall, CompleteAddress, - Contract, ContractArtifact, ContractBase, ContractClassWithId, @@ -14,8 +13,6 @@ import { Fr, PXE, SignerlessWallet, - TxHash, - TxStatus, Wallet, getContractClassFromArtifact, getContractInstanceFromDeployParams, @@ -30,9 +27,9 @@ import { import { ContractClassIdPreimage, Point, PublicKey } from '@aztec/circuits.js'; import { siloNullifier } from '@aztec/circuits.js/hash'; import { FunctionSelector, FunctionType } from '@aztec/foundation/abi'; -import { ContractInstanceDeployerContract, StatefulTestContract } from '@aztec/noir-contracts.js'; +import { StatefulTestContract } from '@aztec/noir-contracts.js'; import { TestContract, TestContractArtifact } from '@aztec/noir-contracts.js/Test'; -import { TokenContractArtifact } from '@aztec/noir-contracts.js/Token'; +import { TokenContract, TokenContractArtifact } from '@aztec/noir-contracts.js/Token'; import { SequencerClient } from '@aztec/sequencer-client'; import { setup } from './fixtures/utils.js'; @@ -46,226 +43,190 @@ describe('e2e_deploy_contract', () => { let aztecNode: AztecNode; let teardown: () => Promise; - beforeAll(async () => { - ({ teardown, pxe, accounts, logger, wallet, sequencer, aztecNode } = await setup()); - }, 100_000); - - afterAll(() => teardown()); - - /** - * Milestone 1.1. - * https://hackmd.io/ouVCnacHQRq2o1oRc5ksNA#Interfaces-and-Responsibilities - */ - it('should deploy a contract', async () => { - const publicKey = accounts[0].publicKey; - const salt = Fr.random(); - const deploymentData = getContractInstanceFromDeployParams( - TestContractArtifact, - [], - salt, - publicKey, - EthAddress.ZERO, - ); - const deployer = new ContractDeployer(TestContractArtifact, pxe, publicKey); - const tx = deployer.deploy().send({ contractAddressSalt: salt }); - logger(`Tx sent with hash ${await tx.getTxHash()}`); - const receipt = await tx.getReceipt(); - expect(receipt).toEqual( - expect.objectContaining({ - status: TxStatus.PENDING, - error: '', - }), - ); - logger(`Receipt received and expecting contract deployment at ${receipt.contractAddress}`); - // we pass in wallet to wait(...) because wallet is necessary to create a TS contract instance - const receiptAfterMined = await tx.wait({ wallet }); - - expect(receiptAfterMined).toEqual( - expect.objectContaining({ - status: TxStatus.MINED, - error: '', - contractAddress: deploymentData.address, - }), - ); - const contractAddress = receiptAfterMined.contractAddress!; - expect(await isContractDeployed(pxe, contractAddress)).toBe(true); - expect(await isContractDeployed(pxe, AztecAddress.random())).toBe(false); - }, 60_000); - - /** - * Verify that we can produce multiple rollups. - */ - it('should deploy one contract after another in consecutive rollups', async () => { - const deployer = new ContractDeployer(TestContractArtifact, pxe); - - for (let index = 0; index < 2; index++) { - logger(`Deploying contract ${index + 1}...`); - // we pass in wallet to wait(...) because wallet is necessary to create a TS contract instance - const receipt = await deployer.deploy().send({ contractAddressSalt: Fr.random() }).wait({ wallet }); - expect(receipt.status).toBe(TxStatus.MINED); - } - }, 60_000); - - /** - * Verify that we can deploy multiple contracts and interact with all of them. - */ - it('should deploy multiple contracts and interact with them', async () => { - const deployer = new ContractDeployer(TestContractArtifact, pxe); - - for (let index = 0; index < 2; index++) { - logger(`Deploying contract ${index + 1}...`); - const receipt = await deployer.deploy().send({ contractAddressSalt: Fr.random() }).wait({ wallet }); - - const contract = await Contract.at(receipt.contractAddress!, TestContractArtifact, wallet); - logger(`Sending TX to contract ${index + 1}...`); - await contract.methods.get_public_key(accounts[0].address).send().wait(); - } - }, 60_000); - - /** - * Milestone 1.2. - * https://hackmd.io/-a5DjEfHTLaMBR49qy6QkA - */ - it('should not deploy a contract with the same salt twice', async () => { - const contractAddressSalt = Fr.random(); - const deployer = new ContractDeployer(TestContractArtifact, pxe); - - { - // we pass in wallet to wait(...) because wallet is necessary to create a TS contract instance - const receipt = await deployer.deploy().send({ contractAddressSalt }).wait({ wallet }); - - expect(receipt.status).toBe(TxStatus.MINED); - expect(receipt.error).toBe(''); - } - - { - await expect(deployer.deploy().send({ contractAddressSalt }).wait()).rejects.toThrowError( - /A settled tx with equal hash/, + describe('legacy tests', () => { + beforeAll(async () => { + ({ teardown, pxe, accounts, logger, wallet, sequencer, aztecNode } = await setup()); + }, 100_000); + + afterAll(() => teardown()); + + /** + * Milestone 1.1. + * https://hackmd.io/ouVCnacHQRq2o1oRc5ksNA#Interfaces-and-Responsibilities + */ + it('should deploy a test contract', async () => { + const publicKey = accounts[0].publicKey; + const salt = Fr.random(); + const deploymentData = getContractInstanceFromDeployParams( + TestContractArtifact, + [], + salt, + publicKey, + EthAddress.ZERO, ); - } - }, 60_000); + const deployer = new ContractDeployer(TestContractArtifact, wallet, publicKey); + const receipt = await deployer.deploy().send({ contractAddressSalt: salt }).wait({ wallet }); + expect(receipt.contract.address).toEqual(deploymentData.address); + expect(await isContractDeployed(pxe, deploymentData.address)).toBe(true); + expect(await isContractDeployed(pxe, AztecAddress.random())).toBe(false); + }, 60_000); - it('should deploy a contract connected to a portal contract', async () => { - const deployer = new ContractDeployer(TestContractArtifact, wallet); - const portalContract = EthAddress.random(); + /** + * Verify that we can produce multiple rollups. + */ + it('should deploy one contract after another in consecutive rollups', async () => { + const deployer = new ContractDeployer(TestContractArtifact, wallet); - // ContractDeployer was instantiated with wallet so we don't have to pass it to wait(...) - const txReceipt = await deployer.deploy().send({ portalContract }).wait(); + for (let index = 0; index < 2; index++) { + logger(`Deploying contract ${index + 1}...`); + await deployer.deploy().send({ contractAddressSalt: Fr.random() }).wait({ wallet }); + } + }, 60_000); - expect(txReceipt.status).toBe(TxStatus.MINED); - const contractAddress = txReceipt.contractAddress!; + /** + * Verify that we can deploy multiple contracts and interact with all of them. + */ + it('should deploy multiple contracts and interact with them', async () => { + const deployer = new ContractDeployer(TestContractArtifact, wallet); + + for (let index = 0; index < 2; index++) { + logger(`Deploying contract ${index + 1}...`); + const receipt = await deployer.deploy().send({ contractAddressSalt: Fr.random() }).wait({ wallet }); + logger(`Sending TX to contract ${index + 1}...`); + await receipt.contract.methods.get_public_key(accounts[0].address).send().wait(); + } + }, 90_000); + + /** + * Milestone 1.2. + * https://hackmd.io/-a5DjEfHTLaMBR49qy6QkA + */ + it('should not deploy a contract with the same salt twice', async () => { + const contractAddressSalt = Fr.random(); + const deployer = new ContractDeployer(TestContractArtifact, wallet); + + await deployer.deploy().send({ contractAddressSalt }).wait({ wallet }); + await expect(deployer.deploy().send({ contractAddressSalt }).wait()).rejects.toThrow(/dropped/); + }, 60_000); - expect((await pxe.getContractData(contractAddress))?.portalContractAddress.toString()).toEqual( - portalContract.toString(), - ); - expect((await pxe.getExtendedContractData(contractAddress))?.contractData.portalContractAddress.toString()).toEqual( - portalContract.toString(), - ); - }, 60_000); + it('should deploy a contract connected to a portal contract', async () => { + const deployer = new ContractDeployer(TestContractArtifact, wallet); + const portalContract = EthAddress.random(); - it('it should not deploy a contract which failed the public part of the execution', async () => { - sequencer?.updateSequencerConfig({ - minTxsPerBlock: 2, - }); + // ContractDeployer was instantiated with wallet so we don't have to pass it to wait(...) + const receipt = await deployer.deploy().send({ portalContract }).wait(); + const address = receipt.contract.address; - try { - // This test requires at least another good transaction to go through in the same block as the bad one. - // I deployed the same contract again but it could really be any valid transaction here. - const goodDeploy = new ContractDeployer(TokenContractArtifact, wallet).deploy( - AztecAddress.random(), - 'TokenName', - 'TKN', - 18, - ); - const badDeploy = new ContractDeployer(TokenContractArtifact, wallet).deploy( - AztecAddress.ZERO, - 'TokenName', - 'TKN', - 18, + expect((await pxe.getContractData(address))?.portalContractAddress.toString()).toEqual(portalContract.toString()); + expect((await pxe.getExtendedContractData(address))?.contractData.portalContractAddress.toString()).toEqual( + portalContract.toString(), ); + }, 60_000); + + // TODO(@spalladino): Review this test, it's showing an unexpected 'Bytecode not found' error in logs. + // It's possible it is failing for the wrong reason, and the getContractData checks are returning wrong data. + it('should not deploy a contract which failed the public part of the execution', async () => { + sequencer?.updateSequencerConfig({ + minTxsPerBlock: 2, + }); - await Promise.all([ - goodDeploy.simulate({ skipPublicSimulation: true }), - badDeploy.simulate({ skipPublicSimulation: true }), - ]); + try { + // This test requires at least another good transaction to go through in the same block as the bad one. + // I deployed the same contract again but it could really be any valid transaction here. + const artifact = TokenContractArtifact; + const initArgs = ['TokenName', 'TKN', 18] as const; + const goodDeploy = new ContractDeployer(artifact, wallet).deploy(AztecAddress.random(), ...initArgs); + const badDeploy = new ContractDeployer(artifact, wallet).deploy(AztecAddress.ZERO, ...initArgs); - const [goodTx, badTx] = [ - goodDeploy.send({ skipPublicSimulation: true }), - badDeploy.send({ skipPublicSimulation: true }), - ]; + const firstOpts = { skipPublicSimulation: true }; + const secondOpts = { skipPublicSimulation: true, skipClassRegistration: true }; - const [goodTxPromiseResult, badTxReceiptResult] = await Promise.allSettled([goodTx.wait(), badTx.wait()]); + await Promise.all([goodDeploy.simulate(firstOpts), badDeploy.simulate(secondOpts)]); + const [goodTx, badTx] = [goodDeploy.send(firstOpts), badDeploy.send(secondOpts)]; + const [goodTxPromiseResult, badTxReceiptResult] = await Promise.allSettled([goodTx.wait(), badTx.wait()]); - expect(goodTxPromiseResult.status).toBe('fulfilled'); - expect(badTxReceiptResult.status).toBe('rejected'); + expect(goodTxPromiseResult.status).toBe('fulfilled'); + expect(badTxReceiptResult.status).toBe('rejected'); - const [goodTxReceipt, badTxReceipt] = await Promise.all([goodTx.getReceipt(), badTx.getReceipt()]); + const [goodTxReceipt, badTxReceipt] = await Promise.all([goodTx.getReceipt(), badTx.getReceipt()]); - expect(goodTxReceipt.blockNumber).toEqual(expect.any(Number)); - expect(badTxReceipt.blockNumber).toBeUndefined(); + expect(goodTxReceipt.blockNumber).toEqual(expect.any(Number)); + expect(badTxReceipt.blockNumber).toBeUndefined(); - await expect(pxe.getExtendedContractData(goodDeploy.instance!.address)).resolves.toBeDefined(); - await expect(pxe.getExtendedContractData(goodDeploy.instance!.address)).resolves.toBeDefined(); + await expect(pxe.getContractData(goodDeploy.getInstance().address)).resolves.toBeDefined(); + await expect(pxe.getExtendedContractData(goodDeploy.getInstance().address)).resolves.toBeDefined(); - await expect(pxe.getContractData(badDeploy.instance!.address)).resolves.toBeUndefined(); - await expect(pxe.getExtendedContractData(badDeploy.instance!.address)).resolves.toBeUndefined(); - } finally { - sequencer?.updateSequencerConfig({ - minTxsPerBlock: 1, - }); - } - }, 60_000); - - // Tests calling a private function in an uninitialized and undeployed contract. Note that - // it still requires registering the contract artifact and instance locally in the pxe. - test.each(['as entrypoint', 'from an account contract'] as const)( - 'executes a function in an undeployed contract %s', - async kind => { - const testWallet = kind === 'as entrypoint' ? new SignerlessWallet(pxe) : wallet; - const contract = await registerContract(testWallet, TestContract); - const receipt = await contract.methods.emit_nullifier(10).send().wait({ debug: true }); - const expected = siloNullifier(contract.address, new Fr(10)); - expect(receipt.debugInfo?.newNullifiers[1]).toEqual(expected); - }, - ); - - // Tests privately initializing an undeployed contract. Also requires pxe registration in advance. - test.each(['as entrypoint', 'from an account contract'] as const)( - 'privately initializes an undeployed contract contract %s', - async kind => { - const testWallet = kind === 'as entrypoint' ? new SignerlessWallet(pxe) : wallet; + await expect(pxe.getContractData(badDeploy.getInstance().address)).resolves.toBeUndefined(); + await expect(pxe.getExtendedContractData(badDeploy.getInstance().address)).resolves.toBeUndefined(); + } finally { + sequencer?.updateSequencerConfig({ + minTxsPerBlock: 1, + }); + } + }, 90_000); + }); + + describe('private initialization', () => { + beforeAll(async () => { + ({ teardown, pxe, accounts, logger, wallet, sequencer, aztecNode } = await setup()); + }, 100_000); + afterAll(() => teardown()); + + // Tests calling a private function in an uninitialized and undeployed contract. Note that + // it still requires registering the contract artifact and instance locally in the pxe. + test.each(['as entrypoint', 'from an account contract'] as const)( + 'executes a function in an undeployed contract %s', + async kind => { + const testWallet = kind === 'as entrypoint' ? new SignerlessWallet(pxe) : wallet; + const contract = await registerContract(testWallet, TestContract); + const receipt = await contract.methods.emit_nullifier(10).send().wait({ debug: true }); + const expected = siloNullifier(contract.address, new Fr(10)); + expect(receipt.debugInfo?.newNullifiers[1]).toEqual(expected); + }, + 30_000, + ); + + // Tests privately initializing an undeployed contract. Also requires pxe registration in advance. + test.each(['as entrypoint', 'from an account contract'] as const)( + 'privately initializes an undeployed contract contract %s', + async kind => { + const testWallet = kind === 'as entrypoint' ? new SignerlessWallet(pxe) : wallet; + const owner = await registerRandomAccount(pxe); + const initArgs: StatefulContractCtorArgs = [owner, 42]; + const contract = await registerContract(testWallet, StatefulTestContract, initArgs); + await contract.methods + .constructor(...initArgs) + .send() + .wait(); + expect(await contract.methods.summed_values(owner).view()).toEqual(42n); + }, + 30_000, + ); + + // Tests privately initializing multiple undeployed contracts on the same tx through an account contract. + it('initializes multiple undeployed contracts in a single tx', async () => { const owner = await registerRandomAccount(pxe); - const initArgs: StatefulContractCtorArgs = [owner, 42]; - const contract = await registerContract(testWallet, StatefulTestContract, initArgs); - await contract.methods - .constructor(...initArgs) - .send() - .wait(); - expect(await contract.methods.summed_values(owner).view()).toEqual(42n); - }, - ); - - // Tests privately initializing multiple undeployed contracts on the same tx through an account contract. - it('initializes multiple undeployed contracts in a single tx', async () => { - const owner = await registerRandomAccount(pxe); - const initArgs: StatefulContractCtorArgs[] = [42, 52].map(value => [owner, value]); - const contracts = await Promise.all(initArgs.map(args => registerContract(wallet, StatefulTestContract, args))); - const calls = contracts.map((c, i) => c.methods.constructor(...initArgs[i]).request()); - await new BatchCall(wallet, calls).send().wait(); - expect(await contracts[0].methods.summed_values(owner).view()).toEqual(42n); - expect(await contracts[1].methods.summed_values(owner).view()).toEqual(52n); + const initArgs: StatefulContractCtorArgs[] = [42, 52].map(value => [owner, value]); + const contracts = await Promise.all(initArgs.map(args => registerContract(wallet, StatefulTestContract, args))); + const calls = contracts.map((c, i) => c.methods.constructor(...initArgs[i]).request()); + await new BatchCall(wallet, calls).send().wait(); + expect(await contracts[0].methods.summed_values(owner).view()).toEqual(42n); + expect(await contracts[1].methods.summed_values(owner).view()).toEqual(52n); + }, 30_000); }); - // Tests registering a new contract class on a node and then deploying an instance. - // These tests look scary, but don't fret: all this hodgepodge of calls will be hidden - // behind a much nicer API in the near future as part of #4080. describe('registering a contract class', () => { + beforeAll(async () => { + ({ teardown, pxe, accounts, logger, wallet, sequencer, aztecNode } = await setup()); + }, 100_000); + afterAll(() => teardown()); + let artifact: ContractArtifact; let contractClass: ContractClassWithId & ContractClassIdPreimage; beforeAll(async () => { artifact = StatefulTestContract.artifact; - await registerContractClass(wallet, artifact).send().wait(); + await registerContractClass(wallet, artifact).then(c => c.send().wait()); contractClass = getContractClassFromArtifact(artifact); }, 60_000); @@ -296,15 +257,11 @@ describe('e2e_deploy_contract', () => { describe('deploying a contract instance', () => { let instance: ContractInstanceWithAddress; - let deployer: ContractInstanceDeployerContract; - let deployTxHash: TxHash; let initArgs: StatefulContractCtorArgs; let publicKey: PublicKey; beforeAll(async () => { initArgs = [accounts[0].address, 42]; - deployer = await registerContract(wallet, ContractInstanceDeployerContract, [], { salt: new Fr(1) }); - const salt = Fr.random(); const portalAddress = EthAddress.random(); publicKey = Point.random(); @@ -313,15 +270,8 @@ describe('e2e_deploy_contract', () => { const { address, contractClassId } = instance; logger(`Deploying contract instance at ${address.toString()} class id ${contractClassId.toString()}`); - const tx = await deployInstance(wallet, instance).send().wait(); - deployTxHash = tx.txHash; - }); - - it('emits deployment log', async () => { - const logs = await pxe.getUnencryptedLogs({ txHash: deployTxHash }); - const deployedLog = logs.logs[0].log; - expect(deployedLog.contractAddress).toEqual(deployer.address); - }); + await deployInstance(wallet, instance).send().wait(); + }, 60_000); it('stores contract instance in the aztec node', async () => { const deployed = await aztecNode.getContract(instance.address); @@ -355,7 +305,38 @@ describe('e2e_deploy_contract', () => { await contract.methods.increment_public_value(whom, 10).send({ skipPublicSimulation: true }).wait(); const stored = await contract.methods.get_public_value(whom).view(); expect(stored).toEqual(10n); - }); + }, 30_000); + }); + }); + + describe('using the contract deploy method', () => { + // We use a beforeEach hook so we get a fresh pxe and node, so class registrations + // from one test don't influence the others. + beforeEach(async () => { + ({ teardown, pxe, accounts, logger, wallet, sequencer, aztecNode } = await setup()); + }, 100_000); + afterEach(() => teardown()); + + it('publicly deploys and initializes a contract', async () => { + const owner = accounts[0]; + const contract = await StatefulTestContract.deploy(wallet, owner, 42).send().deployed(); + expect(await contract.methods.summed_values(owner).view()).toEqual(42n); + await contract.methods.increment_public_value(owner, 84).send().wait(); + expect(await contract.methods.get_public_value(owner).view()).toEqual(84n); + }, 60_000); + + it('publicly deploys and calls a public function from the constructor', async () => { + const owner = accounts[0]; + const token = await TokenContract.deploy(wallet, owner, 'TOKEN', 'TKN', 18).send().deployed(); + expect(await token.methods.is_minter(owner).view()).toEqual(true); + }, 60_000); + + it.skip('publicly deploys and calls a public function in the same batched call', async () => { + // TODO(@spalladino) + }); + + it.skip('publicly deploys and calls a public function in a tx in the same block', async () => { + // TODO(@spalladino) }); }); }); diff --git a/yarn-project/end-to-end/src/e2e_lending_contract.test.ts b/yarn-project/end-to-end/src/e2e_lending_contract.test.ts index 10c6cbae525..fc016b5e423 100644 --- a/yarn-project/end-to-end/src/e2e_lending_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_lending_contract.test.ts @@ -43,46 +43,28 @@ describe('e2e_lending_contract', () => { }; const deployContracts = async () => { - let lendingContract: LendingContract; - let priceFeedContract: PriceFeedContract; - - let collateralAsset: TokenContract; - let stableCoin: TokenContract; - - { - logger(`Deploying price feed contract...`); - const receipt = await waitForSuccess(PriceFeedContract.deploy(wallet).send()); - logger(`Price feed deployed to ${receipt.contractAddress}`); - priceFeedContract = await PriceFeedContract.at(receipt.contractAddress!, wallet); - } - - { - logger(`Deploying collateral asset feed contract...`); - const receipt = await waitForSuccess( - TokenContract.deploy(wallet, accounts[0], 'TokenName', 'TokenSymbol', 18).send(), - ); - logger(`Collateral asset deployed to ${receipt.contractAddress}`); - collateralAsset = await TokenContract.at(receipt.contractAddress!, wallet); - } - - { - logger(`Deploying stable coin contract...`); - const receipt = await waitForSuccess( - TokenContract.deploy(wallet, accounts[0], 'TokenName', 'TokenSymbol', 18).send(), - ); - logger(`Stable coin asset deployed to ${receipt.contractAddress}`); - stableCoin = await TokenContract.at(receipt.contractAddress!, wallet); - } - - { - logger(`Deploying L2 public contract...`); - const receipt = await waitForSuccess(LendingContract.deploy(wallet).send()); - logger(`CDP deployed at ${receipt.contractAddress}`); - lendingContract = await LendingContract.at(receipt.contractAddress!, wallet); - } - - await waitForSuccess(collateralAsset.methods.set_minter(lendingContract.address, true).send()); - await waitForSuccess(stableCoin.methods.set_minter(lendingContract.address, true).send()); + logger(`Deploying price feed contract...`); + const priceFeedContract = await PriceFeedContract.deploy(wallet).send().deployed(); + logger(`Price feed deployed to ${priceFeedContract.address}`); + + logger(`Deploying collateral asset feed contract...`); + const collateralAsset = await TokenContract.deploy(wallet, accounts[0], 'TokenName', 'TokenSymbol', 18) + .send() + .deployed(); + logger(`Collateral asset deployed to ${collateralAsset.address}`); + + logger(`Deploying stable coin contract...`); + const stableCoin = await TokenContract.deploy(wallet, accounts[0], 'TokenName', 'TokenSymbol', 18) + .send() + .deployed(); + logger(`Stable coin asset deployed to ${stableCoin.address}`); + + logger(`Deploying L2 public contract...`); + const lendingContract = await LendingContract.deploy(wallet).send().deployed(); + logger(`CDP deployed at ${lendingContract.address}`); + + await collateralAsset.methods.set_minter(lendingContract.address, true).send().wait(); + await stableCoin.methods.set_minter(lendingContract.address, true).send().wait(); return { priceFeedContract, lendingContract, collateralAsset, stableCoin }; }; diff --git a/yarn-project/end-to-end/src/e2e_p2p_network.test.ts b/yarn-project/end-to-end/src/e2e_p2p_network.test.ts index 07506db1be2..531f687f809 100644 --- a/yarn-project/end-to-end/src/e2e_p2p_network.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p_network.test.ts @@ -2,12 +2,12 @@ import { AztecNodeConfig, AztecNodeService } from '@aztec/aztec-node'; import { AztecAddress, CompleteAddress, - ContractDeployer, DebugLogger, DeploySentTx, EthAddress, Fr, Grumpkin, + LegacyContractDeployer, PublicKey, TxStatus, Wallet, @@ -72,7 +72,7 @@ describe('e2e_p2p_network', () => { const receipt = await tx.wait({ wallet }); expect(receipt.status).toBe(TxStatus.MINED); - const contractAddress = receipt.contractAddress!; + const contractAddress = receipt.contract.address; expect(await isContractDeployed(context.pxeService, contractAddress)).toBeTruthy(); expect(await isContractDeployed(context.pxeService, AztecAddress.random())).toBeFalsy(); } @@ -145,7 +145,8 @@ describe('e2e_p2p_network', () => { publicKey, EthAddress.ZERO, ).address; - const deployer = new ContractDeployer(TestContractArtifact, pxe, publicKey); + // TODO(@spalladino): Remove usage of LegacyContractDeployer. + const deployer = new LegacyContractDeployer(TestContractArtifact, pxe, publicKey); const tx = deployer.deploy().send({ contractAddressSalt: salt }); logger(`Tx sent with hash ${await tx.getTxHash()}`); const receipt = await tx.getReceipt(); diff --git a/yarn-project/end-to-end/src/guides/up_quick_start.sh b/yarn-project/end-to-end/src/guides/up_quick_start.sh index cf87493a1b2..c1776e355d9 100755 --- a/yarn-project/end-to-end/src/guides/up_quick_start.sh +++ b/yarn-project/end-to-end/src/guides/up_quick_start.sh @@ -11,7 +11,7 @@ ALICE_PRIVATE_KEY="0x2153536ff6628eee01cf4024889ff977a18d9fa61d0e414422f7681cf08 # docs:end:declare-accounts # docs:start:deploy -CONTRACT=$(aztec-cli deploy TokenContractArtifact --salt 0 --args $ALICE "TokenName" "TKN" 18 --json | jq -r '.address') +CONTRACT=$(aztec-cli deploy TokenContractArtifact --private-key $ALICE_PRIVATE_KEY --salt 0 --args $ALICE "TokenName" "TKN" 18 --json | jq -r '.address') echo "Deployed contract at $CONTRACT" aztec-cli check-deploy --contract-address $CONTRACT # docs:end:deploy diff --git a/yarn-project/end-to-end/src/integration_l1_publisher.test.ts b/yarn-project/end-to-end/src/integration_l1_publisher.test.ts index 49fe9f2705a..e28bda064ad 100644 --- a/yarn-project/end-to-end/src/integration_l1_publisher.test.ts +++ b/yarn-project/end-to-end/src/integration_l1_publisher.test.ts @@ -28,7 +28,7 @@ import { makeNewSideEffect, makeNewSideEffectLinkedToNoteHash, makeProof, -} from '@aztec/circuits.js/factories'; +} from '@aztec/circuits.js/testing'; import { createEthereumChain } from '@aztec/ethereum'; import { makeTuple, range } from '@aztec/foundation/array'; import { openTmpStore } from '@aztec/kv-store/utils'; diff --git a/yarn-project/end-to-end/src/shared/browser.ts b/yarn-project/end-to-end/src/shared/browser.ts index d8805c22244..94b57e45c77 100644 --- a/yarn-project/end-to-end/src/shared/browser.ts +++ b/yarn-project/end-to-end/src/shared/browser.ts @@ -206,7 +206,7 @@ export const browserTestSuite = ( }, 60_000); const deployTokenContract = async () => { - const txHash = await page.evaluate( + const [txHash, tokenAddress] = await page.evaluate( async (rpcUrl, initialBalance, TokenContractArtifact) => { const { DeployMethod, @@ -239,7 +239,7 @@ export const browserTestSuite = ( const ownerAddress = owner.getAddress(); const tx = new DeployMethod( owner.getCompleteAddress().publicKey, - pxe, + owner, TokenContractArtifact, (a: AztecJs.AztecAddress) => Contract.at(a, TokenContractArtifact, owner), [owner.getCompleteAddress(), 'TokenName', 'TKN', 18], @@ -267,7 +267,7 @@ export const browserTestSuite = ( await token.methods.redeem_shield(ownerAddress, initialBalance, secret).send().wait(); - return txHash.toString(); + return [txHash.toString(), token.address.toString()]; }, pxeURL, initialBalance, @@ -276,7 +276,7 @@ export const browserTestSuite = ( const txResult = await testClient.getTxReceipt(AztecJs.TxHash.fromString(txHash)); expect(txResult.status).toEqual(AztecJs.TxStatus.MINED); - contractAddress = txResult.contractAddress!; + contractAddress = AztecJs.AztecAddress.fromString(tokenAddress); }; const getTokenAddress = async () => { diff --git a/yarn-project/end-to-end/src/shared/cli.ts b/yarn-project/end-to-end/src/shared/cli.ts index 64aaf20b98e..234c3e21e96 100644 --- a/yarn-project/end-to-end/src/shared/cli.ts +++ b/yarn-project/end-to-end/src/shared/cli.ts @@ -60,6 +60,7 @@ export const cliTestSuite = ( if (addRpcUrl) { args.push('--rpc-url', rpcURL); } + debug(`Running command ${args.join(' ')}`); const res = cli.parseAsync(args); resetCli(); return res; @@ -135,7 +136,9 @@ export const cliTestSuite = ( const ownerAddress = AztecAddress.fromString(foundAddress!); debug('Deploy Token Contract using created account.'); - await run(`deploy ${artifact} --salt ${salt} --args ${ownerAddress} 'TokenName' 'TKN' 18`); + await run( + `deploy ${artifact} --private-key ${privKey} --salt ${salt} --args ${ownerAddress} 'TokenName' 'TKN' 18`, + ); const loggedAddress = findInLogs(/Contract\sdeployed\sat\s+(?
0x[a-fA-F0-9]+)/)?.groups?.address; expect(loggedAddress).toBeDefined(); contractAddress = AztecAddress.fromString(loggedAddress!); diff --git a/yarn-project/noir-protocol-circuits-types/src/type_conversion.test.ts b/yarn-project/noir-protocol-circuits-types/src/type_conversion.test.ts index 562d9aa09e0..92285ce4c0f 100644 --- a/yarn-project/noir-protocol-circuits-types/src/type_conversion.test.ts +++ b/yarn-project/noir-protocol-circuits-types/src/type_conversion.test.ts @@ -8,7 +8,7 @@ import { Point, TxContext, } from '@aztec/circuits.js'; -import { makeHeader } from '@aztec/circuits.js/factories'; +import { makeHeader } from '@aztec/circuits.js/testing'; import { mapAztecAddressFromNoir, diff --git a/yarn-project/protocol-contracts/src/class-registerer/__snapshots__/index.test.ts.snap b/yarn-project/protocol-contracts/src/class-registerer/__snapshots__/index.test.ts.snap index 763c59c36e7..007772174fc 100644 --- a/yarn-project/protocol-contracts/src/class-registerer/__snapshots__/index.test.ts.snap +++ b/yarn-project/protocol-contracts/src/class-registerer/__snapshots__/index.test.ts.snap @@ -3,122 +3,122 @@ exports[`ClassRegisterer returns canonical protocol contract 1`] = ` { "address": AztecAddress { - "asBigInt": 12165572618278205561135319266893715614363974000709547171863995507972204751528n, + "asBigInt": 16782756145759928719149283510239227487276026664652666301289472823408773532318n, "asBuffer": { "data": [ + 37, 26, - 229, - 120, - 87, - 210, - 210, - 52, - 118, - 43, - 74, - 102, - 9, - 134, - 217, - 77, - 250, + 180, + 15, 54, - 27, - 6, - 163, - 9, - 77, - 124, - 119, - 221, - 13, - 19, - 48, + 161, + 72, + 165, + 172, + 136, + 254, + 60, + 35, + 152, + 236, + 25, + 104, + 213, + 103, + 107, + 0, + 102, + 41, + 146, 120, - 81, - 106, - 168, + 97, + 11, + 135, + 13, + 115, + 138, + 158, ], "type": "Buffer", }, }, "contractClass": { "artifactHash": Fr { - "asBigInt": 1364372310007692800585626333523730889923819928339145276751574119320046781646n, + "asBigInt": 16849511505047847203273396629509951427400035309439142008111973067225945181374n, "asBuffer": { "data": [ - 3, - 4, - 53, - 21, - 164, - 29, - 233, - 4, - 193, - 147, - 128, - 246, - 235, - 216, - 242, - 66, - 55, - 97, - 55, - 80, - 84, - 212, - 24, - 60, - 24, - 13, - 127, - 108, + 37, + 64, + 124, 77, + 27, + 36, + 23, + 73, + 81, + 17, + 116, + 88, + 222, + 208, + 217, + 214, + 218, 239, - 196, - 206, + 54, + 118, + 248, + 164, + 248, + 200, + 193, + 92, + 204, + 111, + 120, + 101, + 140, + 190, ], "type": "Buffer", }, }, "id": Fr { - "asBigInt": 15823458103336768291861774407076060748007261259174556009564711604871953507490n, + "asBigInt": 6459203616861001844074101389807539136298812127324721680929212139138484648106n, "asBuffer": { "data": [ - 34, - 251, - 194, - 137, - 56, - 109, - 23, - 89, - 23, - 38, - 147, - 215, - 101, - 203, - 138, - 228, - 237, - 145, - 243, - 61, - 16, - 54, + 14, 71, - 253, + 199, + 153, + 215, + 143, + 115, + 227, + 153, + 133, + 163, + 8, + 166, + 65, 142, - 222, - 138, - 190, - 21, - 41, - 228, - 162, + 137, + 249, + 189, + 105, + 134, + 85, + 180, + 166, + 60, + 24, + 82, + 12, + 219, + 196, + 57, + 184, + 170, ], "type": "Buffer", }, @@ -228,7 +228,7 @@ exports[`ClassRegisterer returns canonical protocol contract 1`] = ` { "isInternal": false, "selector": FunctionSelector { - "value": 1669488881, + "value": 2432309179, }, "vkHash": Fr { "asBigInt": 0n, @@ -274,7 +274,7 @@ exports[`ClassRegisterer returns canonical protocol contract 1`] = ` { "isInternal": false, "selector": FunctionSelector { - "value": 2432309179, + "value": 2562483603, }, "vkHash": Fr { "asBigInt": 0n, @@ -319,41 +319,41 @@ exports[`ClassRegisterer returns canonical protocol contract 1`] = ` }, ], "privateFunctionsRoot": Fr { - "asBigInt": 14149643440615160691253002398502794418486578915412752764304101313202744681431n, + "asBigInt": 3517286851632816228452217354955431312081086184223594011972279331675045466476n, "asBuffer": { "data": [ - 31, - 72, - 106, - 20, - 204, - 180, - 255, - 137, - 103, - 217, - 28, - 197, - 42, - 211, - 3, - 8, - 107, - 19, + 7, + 198, + 182, + 188, + 60, + 243, + 25, 4, - 29, - 137, - 6, - 131, + 208, + 224, + 141, + 46, + 193, + 171, + 222, + 237, + 79, + 232, + 128, + 10, + 174, + 175, + 173, + 88, + 133, 49, - 212, - 129, - 225, - 242, - 112, - 231, - 99, - 215, + 210, + 133, + 124, + 41, + 145, + 108, ], "type": "Buffer", }, @@ -403,81 +403,81 @@ exports[`ClassRegisterer returns canonical protocol contract 1`] = ` }, "instance": { "address": AztecAddress { - "asBigInt": 12165572618278205561135319266893715614363974000709547171863995507972204751528n, + "asBigInt": 16782756145759928719149283510239227487276026664652666301289472823408773532318n, "asBuffer": { "data": [ + 37, 26, - 229, - 120, - 87, - 210, - 210, - 52, - 118, - 43, - 74, - 102, - 9, - 134, - 217, - 77, - 250, + 180, + 15, 54, - 27, - 6, - 163, - 9, - 77, - 124, - 119, - 221, - 13, - 19, - 48, + 161, + 72, + 165, + 172, + 136, + 254, + 60, + 35, + 152, + 236, + 25, + 104, + 213, + 103, + 107, + 0, + 102, + 41, + 146, 120, - 81, - 106, - 168, + 97, + 11, + 135, + 13, + 115, + 138, + 158, ], "type": "Buffer", }, }, "contractClassId": Fr { - "asBigInt": 15823458103336768291861774407076060748007261259174556009564711604871953507490n, + "asBigInt": 6459203616861001844074101389807539136298812127324721680929212139138484648106n, "asBuffer": { "data": [ - 34, - 251, - 194, - 137, - 56, - 109, - 23, - 89, - 23, - 38, - 147, - 215, - 101, - 203, - 138, - 228, - 237, - 145, - 243, - 61, - 16, - 54, + 14, 71, - 253, + 199, + 153, + 215, + 143, + 115, + 227, + 153, + 133, + 163, + 8, + 166, + 65, 142, - 222, - 138, - 190, - 21, - 41, - 228, - 162, + 137, + 249, + 189, + 105, + 134, + 85, + 180, + 166, + 60, + 24, + 82, + 12, + 219, + 196, + 57, + 184, + 170, ], "type": "Buffer", }, diff --git a/yarn-project/protocol-contracts/src/class-registerer/index.ts b/yarn-project/protocol-contracts/src/class-registerer/index.ts index 09d3cda26fc..11ad7901b9f 100644 --- a/yarn-project/protocol-contracts/src/class-registerer/index.ts +++ b/yarn-project/protocol-contracts/src/class-registerer/index.ts @@ -13,5 +13,5 @@ export function getCanonicalClassRegisterer(): ProtocolContract { * @remarks This should not change often, hence we hardcode it to save from having to recompute it every time. */ export const ClassRegistererAddress = AztecAddress.fromString( - '0x1ae57857d2d234762b4a660986d94dfa361b06a3094d7c77dd0d133078516aa8', + '0x251ab40f36a148a5ac88fe3c2398ec1968d5676b0066299278610b870d738a9e', ); diff --git a/yarn-project/pxe/src/database/pxe_database_test_suite.ts b/yarn-project/pxe/src/database/pxe_database_test_suite.ts index 8af50b909e0..4cc6a6a5497 100644 --- a/yarn-project/pxe/src/database/pxe_database_test_suite.ts +++ b/yarn-project/pxe/src/database/pxe_database_test_suite.ts @@ -1,6 +1,6 @@ import { INITIAL_L2_BLOCK_NUM, NoteFilter, NoteStatus, randomTxHash } from '@aztec/circuit-types'; import { AztecAddress, CompleteAddress } from '@aztec/circuits.js'; -import { makeHeader } from '@aztec/circuits.js/factories'; +import { makeHeader } from '@aztec/circuits.js/testing'; import { Fr, Point } from '@aztec/foundation/fields'; import { BenchmarkingContractArtifact } from '@aztec/noir-contracts.js/Benchmarking'; import { SerializableContractInstance } from '@aztec/types/contracts'; diff --git a/yarn-project/pxe/src/kernel_prover/kernel_prover.test.ts b/yarn-project/pxe/src/kernel_prover/kernel_prover.test.ts index fd3049681f3..6ae5a8561ba 100644 --- a/yarn-project/pxe/src/kernel_prover/kernel_prover.test.ts +++ b/yarn-project/pxe/src/kernel_prover/kernel_prover.test.ts @@ -18,7 +18,7 @@ import { VerificationKey, makeEmptyProof, } from '@aztec/circuits.js'; -import { makeTxRequest } from '@aztec/circuits.js/factories'; +import { makeTxRequest } from '@aztec/circuits.js/testing'; import { makeTuple } from '@aztec/foundation/array'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index 34619026f70..90515343744 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -57,7 +57,7 @@ import { collectUnencryptedLogs, resolveOpcodeLocations, } from '@aztec/simulator'; -import { ContractInstanceWithAddress } from '@aztec/types/contracts'; +import { ContractClassWithId, ContractInstanceWithAddress } from '@aztec/types/contracts'; import { NodeInfo } from '@aztec/types/interfaces'; import { PXEServiceConfig, getPackageInfo } from '../config/index.js'; @@ -163,6 +163,11 @@ export class PXEService implements PXE { return this.db.getContractInstance(address); } + public async getContractClass(id: Fr): Promise { + const artifact = await this.db.getContractArtifact(id); + return artifact && getContractClassFromArtifact(artifact); + } + public async registerAccount(privKey: GrumpkinPrivateKey, partialAddress: PartialAddress): Promise { const completeAddress = CompleteAddress.fromPrivateKeyAndPartialAddress(privKey, partialAddress); const wasAdded = await this.db.addCompleteAddress(completeAddress); @@ -446,18 +451,7 @@ export class PXEService implements PXE { const settledTx = await this.node.getTx(txHash); if (settledTx) { - const deployedContractAddress = settledTx.newContractData.find( - c => !c.contractAddress.equals(AztecAddress.ZERO), - )?.contractAddress; - - txReceipt = new TxReceipt( - txHash, - TxStatus.MINED, - '', - settledTx.blockHash.toBuffer(), - settledTx.blockNumber, - deployedContractAddress, - ); + txReceipt = new TxReceipt(txHash, TxStatus.MINED, '', settledTx.blockHash.toBuffer(), settledTx.blockNumber); } return txReceipt; @@ -784,4 +778,8 @@ export class PXEService implements PXE { public getKeyStore() { return this.keyStore; } + + public async isContractClassPubliclyRegistered(id: Fr): Promise { + return !!(await this.node.getContractClass(id)); + } } diff --git a/yarn-project/pxe/src/synchronizer/synchronizer.test.ts b/yarn-project/pxe/src/synchronizer/synchronizer.test.ts index 33f3ce60cf2..7b0677f98d8 100644 --- a/yarn-project/pxe/src/synchronizer/synchronizer.test.ts +++ b/yarn-project/pxe/src/synchronizer/synchronizer.test.ts @@ -1,7 +1,7 @@ import { AztecNode, INITIAL_L2_BLOCK_NUM, L2Block } from '@aztec/circuit-types'; import { CompleteAddress, Fr, GrumpkinScalar, Header } from '@aztec/circuits.js'; import { Grumpkin } from '@aztec/circuits.js/barretenberg'; -import { makeHeader } from '@aztec/circuits.js/factories'; +import { makeHeader } from '@aztec/circuits.js/testing'; import { SerialQueue } from '@aztec/foundation/fifo'; import { TestKeyStore } from '@aztec/key-store'; import { openTmpStore } from '@aztec/kv-store/utils'; diff --git a/yarn-project/sequencer-client/package.json b/yarn-project/sequencer-client/package.json index 5b1a7403a6e..a203affec0c 100644 --- a/yarn-project/sequencer-client/package.json +++ b/yarn-project/sequencer-client/package.json @@ -29,6 +29,7 @@ "@aztec/merkle-tree": "workspace:^", "@aztec/noir-protocol-circuits-types": "workspace:^", "@aztec/p2p": "workspace:^", + "@aztec/protocol-contracts": "workspace:^", "@aztec/simulator": "workspace:^", "@aztec/types": "workspace:^", "@aztec/world-state": "workspace:^", diff --git a/yarn-project/sequencer-client/src/block_builder/solo_block_builder.test.ts b/yarn-project/sequencer-client/src/block_builder/solo_block_builder.test.ts index 8b52b4a3fd3..89e95faa8e4 100644 --- a/yarn-project/sequencer-client/src/block_builder/solo_block_builder.test.ts +++ b/yarn-project/sequencer-client/src/block_builder/solo_block_builder.test.ts @@ -54,7 +54,7 @@ import { makeProof, makePublicCallRequest, makeRootRollupPublicInputs, -} from '@aztec/circuits.js/factories'; +} from '@aztec/circuits.js/testing'; import { makeTuple, range } from '@aztec/foundation/array'; import { toBufferBE } from '@aztec/foundation/bigint-buffer'; import { times } from '@aztec/foundation/collection'; diff --git a/yarn-project/sequencer-client/src/sequencer/app_logic_phase_manager.ts b/yarn-project/sequencer-client/src/sequencer/app_logic_phase_manager.ts index 01fee546818..653aed17ff6 100644 --- a/yarn-project/sequencer-client/src/sequencer/app_logic_phase_manager.ts +++ b/yarn-project/sequencer-client/src/sequencer/app_logic_phase_manager.ts @@ -42,6 +42,9 @@ export class AppLogicPhaseManager extends AbstractPhaseManager { publicKernelProof: Proof; }> { // add new contracts to the contracts db so that their functions may be found and called + // TODO(#4073): This is catching only private deployments, when we add public ones, we'll + // have to capture contracts emitted in that phase as well. + // TODO(@spalladino): Should we allow emitting contracts in the fee preparation phase? this.log(`Processing tx ${tx.getTxHash()}`); await this.publicContractsDB.addNewContracts(tx); this.log(`Executing enqueued public calls for tx ${tx.getTxHash()}`); diff --git a/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts b/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts index 284da77cecf..866559e5182 100644 --- a/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts @@ -34,7 +34,7 @@ import { makePrivateKernelTailCircuitPublicInputs, makePublicCallRequest, makeSelector, -} from '@aztec/circuits.js/factories'; +} from '@aztec/circuits.js/testing'; import { makeTuple } from '@aztec/foundation/array'; import { padArrayEnd, times } from '@aztec/foundation/collection'; import { PublicExecution, PublicExecutionResult, PublicExecutor } from '@aztec/simulator'; diff --git a/yarn-project/sequencer-client/src/simulator/public_executor.ts b/yarn-project/sequencer-client/src/simulator/public_executor.ts index 4bf333a7ef3..2d7f539f0e2 100644 --- a/yarn-project/sequencer-client/src/simulator/public_executor.ts +++ b/yarn-project/sequencer-client/src/simulator/public_executor.ts @@ -1,6 +1,15 @@ -import { ContractDataSource, ExtendedContractData, L1ToL2MessageSource, MerkleTreeId, Tx } from '@aztec/circuit-types'; +import { + ContractDataSource, + ExtendedContractData, + L1ToL2MessageSource, + MerkleTreeId, + Tx, + UnencryptedL2Log, +} from '@aztec/circuit-types'; import { AztecAddress, + ContractClassRegisteredEvent, + ContractInstanceDeployedEvent, EthAddress, Fr, FunctionSelector, @@ -8,7 +17,11 @@ import { PublicDataTreeLeafPreimage, } from '@aztec/circuits.js'; import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/hash'; +import { createDebugLogger } from '@aztec/foundation/log'; +import { ClassRegistererAddress } from '@aztec/protocol-contracts/class-registerer'; +import { InstanceDeployerAddress } from '@aztec/protocol-contracts/instance-deployer'; import { CommitmentsDB, MessageLoadOracleInputs, PublicContractsDB, PublicStateDB } from '@aztec/simulator'; +import { ContractClassPublic, ContractInstanceWithAddress } from '@aztec/types/contracts'; import { MerkleTreeOperations } from '@aztec/world-state'; /** @@ -16,7 +29,11 @@ import { MerkleTreeOperations } from '@aztec/world-state'; * Progressively records contracts in transaction as they are processed in a block. */ export class ContractsDataSourcePublicDB implements PublicContractsDB { - cache = new Map(); + private cache = new Map(); + private instanceCache = new Map(); + private classCache = new Map(); + + private log = createDebugLogger('aztec:sequencer:contracts-data-source'); constructor(private db: ContractDataSource) {} @@ -35,6 +52,19 @@ export class ContractsDataSourcePublicDB implements PublicContractsDB { this.cache.set(contractAddress.toString(), contract); } + // Extract contract class and instance data from logs and add to cache for this block + const logs = tx.unencryptedLogs.unrollLogs().map(UnencryptedL2Log.fromBuffer); + ContractClassRegisteredEvent.fromLogs(logs, ClassRegistererAddress).forEach(e => { + this.log(`Adding class ${e.contractClassId.toString()} to public execution contract cache`); + this.classCache.set(e.contractClassId.toString(), e.toContractClassPublic()); + }); + ContractInstanceDeployedEvent.fromLogs(logs, InstanceDeployerAddress).forEach(e => { + this.log( + `Adding instance ${e.address.toString()} with class ${e.contractClassId.toString()} to public execution contract cache`, + ); + this.instanceCache.set(e.address.toString(), e.toContractInstance()); + }); + return Promise.resolve(); } @@ -52,6 +82,17 @@ export class ContractsDataSourcePublicDB implements PublicContractsDB { this.cache.delete(contractAddress.toString()); } + + // TODO(@spalladino): Can this inadvertently delete a valid contract added by another tx? + // Let's say we have two txs adding the same contract on the same block. If the 2nd one reverts, + // wouldn't that accidentally remove the contract added on the first one? + const logs = tx.unencryptedLogs.unrollLogs().map(UnencryptedL2Log.fromBuffer); + ContractClassRegisteredEvent.fromLogs(logs, ClassRegistererAddress).forEach(e => + this.classCache.delete(e.contractClassId.toString()), + ); + ContractInstanceDeployedEvent.fromLogs(logs, InstanceDeployerAddress).forEach(e => + this.instanceCache.delete(e.address.toString()), + ); return Promise.resolve(); } @@ -59,17 +100,42 @@ export class ContractsDataSourcePublicDB implements PublicContractsDB { const contract = await this.#getContract(address); return contract?.getPublicFunction(selector)?.bytecode; } + async getIsInternal(address: AztecAddress, selector: FunctionSelector): Promise { const contract = await this.#getContract(address); return contract?.getPublicFunction(selector)?.isInternal; } + async getPortalContractAddress(address: AztecAddress): Promise { const contract = await this.#getContract(address); return contract?.contractData.portalContractAddress; } async #getContract(address: AztecAddress): Promise { - return this.cache.get(address.toString()) ?? (await this.db.getExtendedContractData(address)); + return ( + this.cache.get(address.toString()) ?? + (await this.#makeExtendedContractDataFor(address)) ?? + (await this.db.getExtendedContractData(address)) + ); + } + + async #makeExtendedContractDataFor(address: AztecAddress): Promise { + const instance = this.instanceCache.get(address.toString()); + if (!instance) { + return undefined; + } + + const contractClass = + this.classCache.get(instance.contractClassId.toString()) ?? + (await this.db.getContractClass(instance.contractClassId)); + if (!contractClass) { + this.log.warn( + `Contract class ${instance.contractClassId.toString()} for address ${address.toString()} not found`, + ); + return undefined; + } + + return ExtendedContractData.fromClassAndInstance(contractClass, instance); } } diff --git a/yarn-project/sequencer-client/tsconfig.json b/yarn-project/sequencer-client/tsconfig.json index 2d59c600fd7..501b195fd41 100644 --- a/yarn-project/sequencer-client/tsconfig.json +++ b/yarn-project/sequencer-client/tsconfig.json @@ -30,6 +30,9 @@ { "path": "../p2p" }, + { + "path": "../protocol-contracts" + }, { "path": "../simulator" }, diff --git a/yarn-project/simulator/src/client/private_execution.test.ts b/yarn-project/simulator/src/client/private_execution.test.ts index c51dd7673aa..2bd6f735c57 100644 --- a/yarn-project/simulator/src/client/private_execution.test.ts +++ b/yarn-project/simulator/src/client/private_execution.test.ts @@ -19,13 +19,13 @@ import { nonEmptySideEffects, sideEffectArrayToValueArray, } from '@aztec/circuits.js'; -import { makeContractDeploymentData, makeHeader } from '@aztec/circuits.js/factories'; import { computeCommitmentNonce, computeMessageSecretHash, computeVarArgsHash, siloNoteHash, } from '@aztec/circuits.js/hash'; +import { makeContractDeploymentData, makeHeader } from '@aztec/circuits.js/testing'; import { FunctionArtifact, FunctionSelector, diff --git a/yarn-project/simulator/src/public/index.test.ts b/yarn-project/simulator/src/public/index.test.ts index 9070b0e1938..e8b368ea9ed 100644 --- a/yarn-project/simulator/src/public/index.test.ts +++ b/yarn-project/simulator/src/public/index.test.ts @@ -8,7 +8,7 @@ import { L1_TO_L2_MSG_TREE_HEIGHT, L2ToL1Message, } from '@aztec/circuits.js'; -import { makeHeader } from '@aztec/circuits.js/factories'; +import { makeHeader } from '@aztec/circuits.js/testing'; import { FunctionArtifact, FunctionSelector, encodeArguments } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { pedersenHash } from '@aztec/foundation/crypto'; diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 2c901f66be1..6ac00fb5105 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -841,6 +841,7 @@ __metadata: "@aztec/merkle-tree": "workspace:^" "@aztec/noir-protocol-circuits-types": "workspace:^" "@aztec/p2p": "workspace:^" + "@aztec/protocol-contracts": "workspace:^" "@aztec/simulator": "workspace:^" "@aztec/types": "workspace:^" "@aztec/world-state": "workspace:^"