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:^"