Skip to content

Commit

Permalink
feat!: Use new deployment flow in ContractDeployer (#4497)
Browse files Browse the repository at this point in the history
Updates the `ContractDeployer` to use the new deployment flow. Instead
of creating txs flagged as `isDeployment`, the deployer now creates a
batch call that registers the contract class (if needed), publicly
deploys the contract via the canonical `InstanceDeployer`, and then
initializes it by calling the constructor. This batch call is then sent
via an account contract entrypoint and executed in a single tx. Check
out `e2e_deploy` _publicly deploys and initializes a contract_ to see it
in action.

However, since this requires sending txs through an account entrypoint,
it breaks for deploying accounts. So we still keep the old deployment
flow around for those scenarios, under the `LegacyDeployMethod` name.
We'll need to tweak account contracts constructors so they can be
initialized and optionally deployed in the same single direct call, and
they also pay fees for it. Alternatively, we can also add a canonical
`MultiCall` contract, which could take care of this, though fee payment
is unclear.

This PR also changes the `ClassRegisterer` API so that bytecode is
provided via a capsule instead of an arg, since having args of size over
~2k elements was causing a loop in the Hasher that massively increased
build times (see noir-lang/noir#4395).

**Breaking changes:** Since deployments are now done from an account,
deploying a contract via `Contract.deploy` now requires a `Wallet`
instance instead of a `PXE`, and deploy a contract via CLI now requires
a `--private-key`.

---------

Co-authored-by: PhilWindle <60546371+PhilWindle@users.noreply.github.com>
  • Loading branch information
spalladino and PhilWindle committed Feb 27, 2024
1 parent d3ae287 commit 0702dc6
Show file tree
Hide file tree
Showing 67 changed files with 1,075 additions and 768 deletions.
6 changes: 3 additions & 3 deletions boxes/react/src/hooks/useContract.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
});
Expand Down
12 changes: 5 additions & 7 deletions boxes/react/src/index.tsx
Original file line number Diff line number Diff line change
@@ -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(
<>
<Home />
Expand Down
25 changes: 8 additions & 17 deletions boxes/react/src/pages/contract.tsx
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -15,7 +15,7 @@ export function ContractComponent({ contract }: { contract: Contract }) {
<select name="viewFunctions" id="viewFunctions">
{filteredInterface.map(
(fn, index) =>
fn.functionType === "unconstrained" && (
fn.functionType === 'unconstrained' && (
<option key={index} value={index}>
{fn.name}
</option>
Expand All @@ -29,26 +29,17 @@ export function ContractComponent({ contract }: { contract: Contract }) {

<form onSubmit={setNumber}>
<label htmlFor="functions">Functions:</label>
<select
name="functions"
id="functions"
onChange={() => setShowInput(true)}
>
<select name="functions" id="functions" onChange={() => setShowInput(true)}>
{filteredInterface.map(
(fn, index) =>
fn.functionType !== "unconstrained" && (
fn.functionType !== 'unconstrained' && (
<option key={index} value={index}>
{fn.name}
</option>
),
)}
</select>
<input
type="number"
name="numberToSet"
id="numberToSet"
hidden={!showInput}
/>
<input type="number" name="numberToSet" id="numberToSet" hidden={!showInput} />
<button type="submit" disabled={wait}>
Write
</button>
Expand Down
4 changes: 2 additions & 2 deletions boxes/react/src/pages/home.tsx
Original file line number Diff line number Diff line change
@@ -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();
Expand Down
6 changes: 2 additions & 4 deletions boxes/react/tests/blank.contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
Expand Down
6 changes: 3 additions & 3 deletions boxes/vanilla-js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);

Expand Down
2 changes: 1 addition & 1 deletion l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
Original file line number Diff line number Diff line change
@@ -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<N>() -> [Field; N] {}

// A capsule is a "blob" of data that is passed to the contract through an oracle.
unconstrained pub fn pop_capsule<N>() -> [Field; N] {
pop_capsule_oracle()
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod events;
mod capsule;

contract ContractClassRegisterer {
use dep::std::option::Option;
Expand All @@ -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,
Expand All @@ -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)]
Expand All @@ -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)]
Expand All @@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
81 changes: 28 additions & 53 deletions yarn-project/archiver/src/archiver/archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}

/**
Expand All @@ -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<ContractData | undefined> {
return this.store.getContractData(contractAddress);
public async getContractData(contractAddress: AztecAddress): Promise<ContractData | undefined> {
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<ContractData | undefined> {
const instance = await this.store.getContractInstance(address);
if (!instance) {
return undefined;
}

return new ContractData(address, instance.portalContractAddress);
}

/**
Expand Down Expand Up @@ -591,6 +562,10 @@ export class Archiver implements ArchiveSource {
getConfirmedL1ToL2Message(messageKey: Fr): Promise<L1ToL2Message> {
return this.store.getConfirmedL1ToL2Message(messageKey);
}

getContractClassIds(): Promise<Fr[]> {
return this.store.getContractClassIds();
}
}

/**
Expand Down
3 changes: 3 additions & 0 deletions yarn-project/archiver/src/archiver/archiver_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,7 @@ export interface ArchiverDataStore {
* @param address - Address of the contract.
*/
getContractInstance(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined>;

/** Returns the list of all class ids known by the archiver. */
getContractClassIds(): Promise<Fr[]>;
}
Loading

0 comments on commit 0702dc6

Please sign in to comment.