Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Add aztec-nr private functions for initialization nullifier #4807

Merged
merged 9 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions noir-projects/aztec-nr/aztec/src/initializer.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use dep::protocol_types::hash::silo_nullifier;
use crate::context::PrivateContext;

pub fn mark_as_initialized(context: &mut PrivateContext) {
let init_nullifier = compute_unsiloed_contract_initialization_nullifier(context);
context.push_new_nullifier(init_nullifier, 0);

// We push a commitment as well and use this value to check initialization,
// since we cannot yet read a nullifier from the same tx in which it was emitted.
// Eventually, when that's supported, we should delete this note_hash and
// have all checks rely on reading the nullifier directly.
// TODO(@spalladino) Remove when possible.
context.push_new_note_hash(init_nullifier);
}

// TODO(@spalladino): Add a variant using PublicContext once we can read nullifiers or note hashes from public-land.
pub fn assert_is_initialized(context: &mut PrivateContext) {
let init_nullifier = compute_contract_initialization_nullifier(context);
context.push_read_request(init_nullifier);
}

pub fn compute_contract_initialization_nullifier(context: &mut PrivateContext) -> Field {
let address = context.this_address();
silo_nullifier(address, compute_unsiloed_contract_initialization_nullifier(context))
}

pub fn compute_unsiloed_contract_initialization_nullifier(context: &mut PrivateContext) -> Field {
context.this_address().to_field()
}
1 change: 1 addition & 0 deletions noir-projects/aztec-nr/aztec/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod deploy;
mod hash;
mod hasher;
mod history;
mod initializer;
mod key;
mod log;
mod messaging;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// A contract used for testing a random hodgepodge of small features from simulator and end-to-end tests.
contract StatefulTest {
use dep::aztec::protocol_types::{address::AztecAddress, abis::function_selector::FunctionSelector};
use dep::std::option::Option;
use dep::value_note::{balance_utils, utils::{increment, decrement}, value_note::{VALUE_NOTE_LEN, ValueNote}};
use dep::aztec::{
deploy::{deploy_contract as aztec_deploy_contract},
context::{PrivateContext, PublicContext, Context},
note::{note_header::NoteHeader, utils as note_utils},
state_vars::{Map, PublicMutable, PrivateSet},
oracle::get_contract_instance::get_contract_instance
oracle::get_contract_instance::get_contract_instance,
initializer::{mark_as_initialized, assert_is_initialized},
protocol_types::{address::AztecAddress, abis::function_selector::FunctionSelector},
};

struct Storage {
Expand All @@ -18,12 +19,22 @@ contract StatefulTest {

#[aztec(private)]
fn constructor(owner: AztecAddress, value: Field) {
let selector = FunctionSelector::from_signature("create_note((Field),Field)");
let selector = FunctionSelector::from_signature("internal_create_note((Field),Field)");
let _res = context.call_private_function(context.this_address(), selector, [owner.to_field(), value]);
mark_as_initialized(&mut context);
}

#[aztec(private)]
fn create_note(owner: AztecAddress, value: Field) {
assert_is_initialized(&mut context);
if (value != 0) {
let loc = storage.notes.at(owner);
increment(loc, value, owner);
}
}

#[aztec(private)]
internal fn internal_create_note(owner: AztecAddress, value: Field) {
if (value != 0) {
let loc = storage.notes.at(owner);
increment(loc, value, owner);
Expand All @@ -32,6 +43,7 @@ contract StatefulTest {

#[aztec(private)]
fn destroy_and_create(recipient: AztecAddress, amount: Field) {
assert_is_initialized(&mut context);
let sender = context.msg_sender();

let sender_notes = storage.notes.at(sender);
Expand Down
6 changes: 5 additions & 1 deletion yarn-project/accounts/src/testing/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ export async function deployInitialTestAccounts(pxe: PXE) {
const deployMethods = await Promise.all(
accounts.map(async x => {
const deployMethod = await x.account.getDeployMethod();
await deployMethod.create({ contractAddressSalt: x.account.salt });
await deployMethod.create({
contractAddressSalt: x.account.salt,
skipClassRegistration: true,
skipPublicDeployment: true,
});
await deployMethod.simulate({});
return deployMethod;
}),
Expand Down
8 changes: 7 additions & 1 deletion yarn-project/accounts/src/testing/create_account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ export async function createAccounts(pxe: PXE, numberOfAccounts = 1): Promise<Ac
// Unfortunately the function below is not stateless and we call it here because it takes a long time to run and
// the results get stored within the account object. By calling it here we increase the probability of all the
// accounts being deployed in the same block because it makes the deploy() method basically instant.
await account.getDeployMethod().then(d => d.simulate({ contractAddressSalt: account.salt }));
await account.getDeployMethod().then(d =>
d.simulate({
contractAddressSalt: account.salt,
skipClassRegistration: true,
skipPublicDeployment: true,
}),
);
accounts.push(account);
}

Expand Down
25 changes: 17 additions & 8 deletions yarn-project/aztec.js/src/account_manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ 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, WaitOpts } from '../contract/index.js';
import { LegacyContractDeployer } from '../deployment/legacy/legacy_contract_deployer.js';
import { LegacyDeployMethod } from '../deployment/legacy/legacy_deploy_method.js';
import { DeployMethod } from '../contract/deploy_method.js';
import { DefaultWaitOpts, WaitOpts } from '../contract/sent_tx.js';
import { ContractDeployer } from '../deployment/contract_deployer.js';
import { waitForAccountSynch } from '../utils/account.js';
import { generatePublicKey } from '../utils/index.js';
import { AccountWalletWithPrivateKey } from '../wallet/index.js';
import { AccountWalletWithPrivateKey, SignerlessWallet } from '../wallet/index.js';
import { DeployAccountSentTx } from './deploy_account_sent_tx.js';

/**
Expand All @@ -27,7 +27,7 @@ export class AccountManager {
private instance?: ContractInstanceWithAddress;
private encryptionPublicKey?: PublicKey;
// TODO(@spalladino): Update to the new deploy method and kill the legacy one.
private deployMethod?: LegacyDeployMethod;
private deployMethod?: DeployMethod;

constructor(
private pxe: PXE,
Expand Down Expand Up @@ -132,9 +132,13 @@ export class AccountManager {
}
await this.#register();
const encryptionPublicKey = this.getEncryptionPublicKey();
const deployer = new LegacyContractDeployer(
// We use a signerless wallet so we hit the account contract directly and it deploys itself.
// If we used getWallet, the deployment would get routed via the account contract entrypoint
// instead of directly hitting the initializer.
const deployWallet = new SignerlessWallet(this.pxe);
const deployer = new ContractDeployer(
this.accountContract.getContractArtifact(),
this.pxe,
deployWallet,
encryptionPublicKey,
);
const args = this.accountContract.getDeploymentArgs();
Expand All @@ -145,6 +149,7 @@ export class AccountManager {

/**
* Deploys the account contract that backs this account.
* Does not register the associated class nor publicly deploy the instance.
* Uses the salt provided in the constructor or a randomly generated one.
* Note that if the Account is constructed with an explicit complete address
* it is assumed that the account contract has already been deployed and this method will throw.
Expand All @@ -154,7 +159,11 @@ export class AccountManager {
public async deploy(): Promise<DeployAccountSentTx> {
const deployMethod = await this.getDeployMethod();
const wallet = await this.getWallet();
const sentTx = deployMethod.send({ contractAddressSalt: this.salt });
const sentTx = deployMethod.send({
contractAddressSalt: this.salt,
skipClassRegistration: true,
skipPublicDeployment: true,
});
return new DeployAccountSentTx(wallet, sentTx.getTxHash());
}

Expand Down
22 changes: 12 additions & 10 deletions yarn-project/aztec.js/src/contract/deploy_method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import {
import { ContractArtifact, FunctionArtifact } from '@aztec/foundation/abi';
import { EthAddress } from '@aztec/foundation/eth-address';
import { Fr } from '@aztec/foundation/fields';
import { createDebugLogger } from '@aztec/foundation/log';
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';
Expand Down Expand Up @@ -94,6 +94,17 @@ export class DeployMethod<TContract extends ContractBase = Contract> extends Bas
* it returns a promise for an array instead of a function call directly.
*/
public async request(options: DeployOptions = {}): Promise<FunctionCall[]> {
const { address } = this.getInstance(options);
const constructorCall = new ContractFunctionInteraction(this.wallet, address, this.constructorArtifact, this.args);
return [...(await this.getDeploymentFunctionCalls(options)), constructorCall.request()];
}

/**
* Returns calls for registration of the class and deployment of the instance, depending on the provided options.
* @param options - Deployment options.
* @returns A function call array with potentially requests to the class registerer and instance deployer.
*/
protected async getDeploymentFunctionCalls(options: DeployOptions = {}): Promise<FunctionCall[]> {
const calls: FunctionCall[] = [];

// Set contract instance object so it's available for populating the DeploySendTx object
Expand Down Expand Up @@ -127,11 +138,6 @@ export class DeployMethod<TContract extends ContractBase = Contract> extends Bas
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(),
);

return calls;
}

Expand All @@ -145,10 +151,6 @@ export class DeployMethod<TContract extends ContractBase = Contract> extends Bas
*/
public send(options: DeployOptions = {}): DeploySentTx<TContract> {
const txHashPromise = super.send(options).getTxHash();
// 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));
}

Expand Down

This file was deleted.

Loading
Loading