Skip to content

Commit

Permalink
feat: pay fee when deploying schnorr account
Browse files Browse the repository at this point in the history
  • Loading branch information
alexghr committed Apr 3, 2024
1 parent 02524e1 commit e951798
Show file tree
Hide file tree
Showing 16 changed files with 367 additions and 166 deletions.
17 changes: 15 additions & 2 deletions noir-projects/aztec-nr/authwit/src/account.nr
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use dep::aztec::protocol_types::{address::AztecAddress, abis::function_selector:
use crate::entrypoint::{app::AppPayload, fee::FeePayload};
use crate::auth::{IS_VALID_SELECTOR, compute_outer_authwit_hash};

use crate::capsule::pop_capsule;

struct AccountActions {
context: Context,
is_valid_impl: fn(&mut PrivateContext, Field) -> bool,
Expand Down Expand Up @@ -54,6 +56,17 @@ impl AccountActions {
)
}

pub fn initialize_account_contract(self) {
let valid_fn = self.is_valid_impl;
let mut private_context = self.context.private.unwrap();

let fee_payload = FeePayload::deserialize(pop_capsule());
let fee_hash = fee_payload.hash();
assert(valid_fn(private_context, fee_hash));
fee_payload.execute_calls(private_context);
private_context.capture_min_revertible_side_effect_counter();
}

// docs:start:entrypoint
pub fn entrypoint(self, app_payload: AppPayload, fee_payload: FeePayload) {
let valid_fn = self.is_valid_impl;
Expand All @@ -73,7 +86,7 @@ impl AccountActions {
// docs:start:spend_private_authwit
pub fn spend_private_authwit(self, inner_hash: Field) -> Field {
let context = self.context.private.unwrap();
// The `inner_hash` is "siloed" with the `msg_sender` to ensure that only it can
// The `inner_hash` is "siloed" with the `msg_sender` to ensure that only it can
// consume the message.
// This ensures that contracts cannot consume messages that are not intended for them.
let message_hash = compute_outer_authwit_hash(
Expand All @@ -92,7 +105,7 @@ impl AccountActions {
// docs:start:spend_public_authwit
pub fn spend_public_authwit(self, inner_hash: Field) -> Field {
let context = self.context.public.unwrap();
// The `inner_hash` is "siloed" with the `msg_sender` to ensure that only it can
// The `inner_hash` is "siloed" with the `msg_sender` to ensure that only it can
// consume the message.
// This ensures that contracts cannot consume messages that are not intended for them.
let message_hash = compute_outer_authwit_hash(
Expand Down
10 changes: 10 additions & 0 deletions noir-projects/aztec-nr/authwit/src/capsule.nr
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()
}
16 changes: 15 additions & 1 deletion noir-projects/aztec-nr/authwit/src/entrypoint/fee.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use dep::aztec::prelude::PrivateContext;
use dep::aztec::protocol_types::{constants::GENERATOR_INDEX__FEE_PAYLOAD, hash::pedersen_hash, traits::{Hash, Serialize}};
use dep::aztec::protocol_types::utils::reader::Reader;
use dep::aztec::protocol_types::{constants::GENERATOR_INDEX__FEE_PAYLOAD, hash::pedersen_hash, traits::{Hash, Serialize, Deserialize}};
use crate::entrypoint::function_call::FunctionCall;

// 2 * 4 (function call) + 1
Expand Down Expand Up @@ -29,6 +30,19 @@ impl Serialize<FEE_PAYLOAD_SIZE> for FeePayload {
}
}

impl Deserialize<FEE_PAYLOAD_SIZE> for FeePayload {
// Deserializes the entrypoint struct
fn deserialize(fields: [Field; FEE_PAYLOAD_SIZE]) -> Self {
let mut reader = Reader::new(fields);
let payload = FeePayload {
function_calls: reader.read_struct_array(FunctionCall::deserialize, [FunctionCall::empty(); MAX_FEE_FUNCTION_CALLS]),
nonce: reader.read(),
};
reader.finish();
payload
}
}

impl Hash for FeePayload {
fn hash(self) -> Field {
pedersen_hash(
Expand Down
27 changes: 26 additions & 1 deletion noir-projects/aztec-nr/authwit/src/entrypoint/function_call.nr
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use dep::aztec::protocol_types::{abis::function_selector::FunctionSelector, address::AztecAddress, traits::Serialize};
use dep::aztec::protocol_types::{
abis::function_selector::FunctionSelector, address::AztecAddress,
traits::{Serialize, Deserialize, Empty}
};

// 1 (ARGS_HASH) + 1 (FUNCTION_SELECTOR) + 1 (TARGET_ADDRESS) + 1 (IS_PUBLIC)
global FUNCTION_CALL_SIZE: Field = 4;
Expand All @@ -18,6 +21,28 @@ impl Serialize<FUNCTION_CALL_SIZE> for FunctionCall {
}
}

impl Deserialize<FUNCTION_CALL_SIZE> for FunctionCall {
fn deserialize(data: [Field; FUNCTION_CALL_SIZE]) -> Self {
FunctionCall {
args_hash: data[0],
function_selector: FunctionSelector::from_field(data[1]),
target_address: AztecAddress::from_field(data[2]),
is_public: data[3] as bool,
}
}
}

impl Empty for FunctionCall {
fn empty() -> Self {
FunctionCall {
args_hash: 0,
function_selector: FunctionSelector::zero(),
target_address: AztecAddress::empty(),
is_public: false,
}
}
}

impl FunctionCall {
fn to_be_bytes(self) -> [u8; FUNCTION_CALL_SIZE_IN_BYTES] {
let mut bytes: [u8; FUNCTION_CALL_SIZE_IN_BYTES] = [0; FUNCTION_CALL_SIZE_IN_BYTES];
Expand Down
1 change: 1 addition & 0 deletions noir-projects/aztec-nr/authwit/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ mod account;
mod auth_witness;
mod auth;
mod entrypoint;
mod capsule;
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ contract SchnorrAccount {
let mut pub_key_note = PublicKeyNote::new(signing_pub_key_x, signing_pub_key_y, this);
storage.signing_public_key.initialize(&mut pub_key_note, true);
// docs:end:initialize

let actions = AccountActions::private(
&mut context,
storage.approved_actions.storage_slot,
is_valid_impl
);
actions.initialize_account_contract();
}

// Note: If you globally change the entrypoint signature don't forget to update default_entrypoint.ts file
Expand Down Expand Up @@ -117,9 +124,9 @@ contract SchnorrAccount {
* @param block_number The block number to check the nullifier against
* @param check_private Whether to check the validity of the authwitness in private state or not
* @param message_hash The message hash of the message to check the validity
* @return An array of two booleans, the first is the validity of the authwitness in the private state,
* @return An array of two booleans, the first is the validity of the authwitness in the private state,
* the second is the validity of the authwitness in the public state
* Both values will be `false` if the nullifier is spent
* Both values will be `false` if the nullifier is spent
*/
unconstrained fn lookup_validity(
myself: AztecAddress,
Expand Down Expand Up @@ -147,7 +154,7 @@ contract SchnorrAccount {
let valid_in_public = storage.approved_actions.at(message_hash).read();

// Compute the nullifier and check if it is spent
// This will BLINDLY TRUST the oracle, but the oracle is us, and
// This will BLINDLY TRUST the oracle, but the oracle is us, and
// it is not as part of execution of the contract, so we are good.
let siloed_nullifier = compute_siloed_nullifier(myself, message_hash);
let lower_wit = get_low_nullifier_membership_witness(block_number, siloed_nullifier);
Expand Down
22 changes: 15 additions & 7 deletions yarn-project/aztec.js/src/account_manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import { type ContractInstanceWithAddress } from '@aztec/types/contracts';
import { type AccountContract } from '../account/contract.js';
import { type Salt } from '../account/index.js';
import { type AccountInterface } from '../account/interface.js';
import { DeployAccountMethod } from '../contract/deploy_account_method.js';
import { type DeployMethod } from '../contract/deploy_method.js';
import { DefaultWaitOpts, type WaitOpts } from '../contract/sent_tx.js';
import { ContractDeployer } from '../deployment/contract_deployer.js';
import { type FeeOptions } from '../entrypoint/entrypoint.js';
import { Contract } from '../index.js';
import { waitForAccountSynch } from '../utils/account.js';
import { generatePublicKey } from '../utils/index.js';
import { AccountWalletWithPrivateKey, SignerlessWallet } from '../wallet/index.js';
Expand Down Expand Up @@ -132,13 +134,17 @@ export class AccountManager {
// 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(),
deployWallet,
const args = this.accountContract.getDeploymentArgs() ?? [];
this.deployMethod = new DeployAccountMethod(
await this.getAccount(),
encryptionPublicKey,
deployWallet,
this.accountContract.getContractArtifact(),
(address, wallet) => {
return Contract.at(address, this.accountContract.getContractArtifact(), wallet);
},
args,
);
const args = this.accountContract.getDeploymentArgs() ?? [];
this.deployMethod = deployer.deploy(...args);
}
return this.deployMethod;
}
Expand All @@ -148,16 +154,18 @@ export class AccountManager {
* Does not register the associated class nor publicly deploy the instance by default.
* Uses the salt provided in the constructor or a randomly generated one.
* Registers the account in the PXE Service before deploying the contract.
* @param fee - Fee to be paid for the deployment.
* @returns A SentTx object that can be waited to get the associated Wallet.
*/
public async deploy(): Promise<DeployAccountSentTx> {
public async deploy(fee?: FeeOptions): Promise<DeployAccountSentTx> {
const deployMethod = await this.getDeployMethod();
const wallet = await this.getWallet();
const sentTx = deployMethod.send({
contractAddressSalt: this.salt,
skipClassRegistration: true,
skipPublicDeployment: true,
universalDeploy: true,
fee,
});
return new DeployAccountSentTx(wallet, sentTx.getTxHash());
}
Expand Down
65 changes: 65 additions & 0 deletions yarn-project/aztec.js/src/contract/deploy_account_method.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { PackedArguments, TxExecutionRequest } from '@aztec/circuit-types';
import { type AztecAddress, type PublicKey, TxContext } from '@aztec/circuits.js';

import { type AuthWitnessProvider } from '../account/interface.js';
import { type Wallet } from '../account/wallet.js';
import { type ContractArtifact, type FunctionArtifact } from '../api/abi.js';
import { EntrypointPayload } from '../entrypoint/payload.js';
import { type Contract } from './contract.js';
import { type ContractBase } from './contract_base.js';
import { DeployMethod, type DeployOptions } from './deploy_method.js';

/**
* Contract interaction for deployment. Handles class registration, public instance deployment,
* and initialization of the contract. Extends the BaseContractInteraction class.
*/
export class DeployAccountMethod<TContract extends ContractBase = Contract> extends DeployMethod<TContract> {
constructor(
private authWitnessProvider: AuthWitnessProvider,
publicKey: PublicKey,
wallet: Wallet,
artifact: ContractArtifact,
postDeployCtor: (address: AztecAddress, wallet: Wallet) => Promise<TContract>,
args: any[] = [],
constructorNameOrArtifact?: string | FunctionArtifact,
) {
super(publicKey, wallet, artifact, postDeployCtor, args, constructorNameOrArtifact);
}

/**
* Creates a TxExecutionRequest for deploying the contract.
* @param options - Prepares the contract for deployment by calling the request method and creating a TxExecutionRequest.
* @returns The TxExecutionRequest for deploying the contract.
*/
public async create(options: DeployOptions = {}): Promise<TxExecutionRequest> {
if (this.txRequest) {
return this.txRequest;
}

const calls = await this.request(options);
if (calls.length === 0) {
throw new Error(`No function calls needed to deploy contract`);
}
const feePayload = await EntrypointPayload.fromFeeOptions(options.fee);
const feeAuthWit = await this.authWitnessProvider.createAuthWit(feePayload.hash());
await this.pxe.addCapsule(feePayload.toFields());

const execution = calls[0];
const packedArguments = PackedArguments.fromArgs(execution.args);

const { chainId, protocolVersion } = await this.wallet.getNodeInfo();
const txContext = TxContext.empty(chainId, protocolVersion);
this.txRequest = new TxExecutionRequest(
execution.to,
execution.functionData,
packedArguments.hash,
txContext,
[packedArguments, ...feePayload.packedArguments],
[feeAuthWit],
);

await this.pxe.registerContract({ artifact: this.artifact, instance: this.getInstance() });

return this.txRequest;
}
}
2 changes: 1 addition & 1 deletion yarn-project/aztec.js/src/contract/deploy_method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export class DeployMethod<TContract extends ContractBase = Contract> extends Bas
constructor(
private publicKey: PublicKey,
protected wallet: Wallet,
private artifact: ContractArtifact,
protected artifact: ContractArtifact,
private postDeployCtor: (address: AztecAddress, wallet: Wallet) => Promise<TContract>,
private args: any[] = [],
constructorNameOrArtifact?: string | FunctionArtifact,
Expand Down
13 changes: 2 additions & 11 deletions yarn-project/aztec.js/src/entrypoint/entrypoint.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import { type FunctionCall, type TxExecutionRequest } from '@aztec/circuit-types';
import { type Fr } from '@aztec/foundation/fields';

import { type FeePaymentMethod } from '../fee/fee_payment_method.js';
import { EntrypointPayload, type FeeOptions } from './payload.js';

/**
* Fee payment options for a transaction.
*/
export type FeeOptions = {
/** The fee payment method to use */
paymentMethod: FeePaymentMethod;
/** The fee limit to pay */
maxFee: bigint | number | Fr;
};
export { EntrypointPayload, FeeOptions };

/** Creates transaction execution requests out of a set of function calls. */
export interface EntrypointInterface {
Expand Down
Loading

0 comments on commit e951798

Please sign in to comment.