Skip to content

Commit

Permalink
feat: pay fee when initializing account contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
alexghr committed Apr 8, 2024
1 parent 8b30b95 commit 6191313
Show file tree
Hide file tree
Showing 33 changed files with 832 additions and 258 deletions.
12 changes: 12 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,16 @@ jobs:
aztec_manifest_key: end-to-end
<<: *defaults_e2e_test

e2e-account-init-fees:
steps:
- *checkout
- *setup_env
- run:
name: "Test"
command: cond_spot_run_compose end-to-end 4 ./scripts/docker-compose.yml TEST=e2e_account_init_fees.test.ts ENABLE_GAS=1
aztec_manifest_key: end-to-end
<<: *defaults_e2e_test

e2e-dapp-subscription:
steps:
- *checkout
Expand Down Expand Up @@ -1480,6 +1490,7 @@ workflows:
- e2e-card-game: *e2e_test
- e2e-avm-simulator: *e2e_test
- e2e-fees: *e2e_test
- e2e-account-init-fees: *e2e_test
- e2e-dapp-subscription: *e2e_test
- pxe: *e2e_test
- cli-docs-sandbox: *e2e_test
Expand Down Expand Up @@ -1546,6 +1557,7 @@ workflows:
- e2e-card-game
- e2e-avm-simulator
- e2e-fees
- e2e-account-init-fees
- e2e-dapp-subscription
- pxe
- boxes-vanilla
Expand Down
10 changes: 10 additions & 0 deletions noir-projects/aztec-nr/authwit/src/account.nr
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ impl AccountActions {
}
// docs:end:entrypoint

pub fn pay_init_fee(self, fee_payload: FeePayload) {
let valid_fn = self.is_valid_impl;
let mut private_context = self.context.private.unwrap();

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:spend_private_authwit
pub fn spend_private_authwit(self, inner_hash: Field) -> Field {
let context = self.context.private.unwrap();
Expand Down
15 changes: 15 additions & 0 deletions noir-projects/aztec-nr/aztec/src/context/private_context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,19 @@ impl PrivateContext {
assert_eq(item.public_inputs.start_side_effect_counter, self.side_effect_counter);
self.side_effect_counter = item.public_inputs.end_side_effect_counter + 1;

// TODO (fees) figure out why this crashes the prover and enable it
// we need this in order to pay fees inside child call contexts
// assert(
// (item.public_inputs.min_revertible_side_effect_counter == 0 as u32)
// | (item.public_inputs.min_revertible_side_effect_counter
// > self.min_revertible_side_effect_counter)
// );

// if item.public_inputs.min_revertible_side_effect_counter
// > self.min_revertible_side_effect_counter {
// self.min_revertible_side_effect_counter = item.public_inputs.min_revertible_side_effect_counter;
// }

assert(contract_address.eq(item.contract_address));
assert(function_selector.eq(item.function_data.selector));

Expand All @@ -370,6 +383,8 @@ impl PrivateContext {
);
}

// crate::oracle::debug_log::debug_log_array_with_prefix("Private call stack item", item.serialize());

self.private_call_stack_hashes.push(item.hash());

item.public_inputs.return_values
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ contract EcdsaAccount {
}

#[aztec(private)]
#[aztec(noinitcheck)]
fn pay_init_fee(fee_payload: pub FeePayload) {
let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl);
actions.pay_init_fee(fee_payload);
}

#[aztec(private)]
#[aztec(noinitcheck)]
fn spend_private_authwit(inner_hash: Field) -> Field {
let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl);
actions.spend_private_authwit(inner_hash)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ contract SchnorrAccount {

// Note: If you globally change the entrypoint signature don't forget to update default_entrypoint.ts file
#[aztec(private)]
#[aztec(noinitcheck)]
fn entrypoint(app_payload: pub AppPayload, fee_payload: pub FeePayload) {
let actions = AccountActions::private(
&mut context,
Expand All @@ -48,6 +49,18 @@ contract SchnorrAccount {
}

#[aztec(private)]
#[aztec(noinitcheck)]
fn pay_init_fee(fee_payload: pub FeePayload) {
let actions = AccountActions::private(
&mut context,
storage.approved_actions.storage_slot,
is_valid_impl
);
actions.pay_init_fee(fee_payload);
}

#[aztec(private)]
#[aztec(noinitcheck)]
fn spend_private_authwit(inner_hash: Field) -> Field {
let actions = AccountActions::private(
&mut context,
Expand All @@ -58,6 +71,7 @@ contract SchnorrAccount {
}

#[aztec(public)]
#[aztec(noinitcheck)]
fn spend_public_authwit(inner_hash: Field) -> Field {
let actions = AccountActions::public(
&mut context,
Expand All @@ -75,6 +89,7 @@ contract SchnorrAccount {

#[aztec(public)]
#[aztec(internal)]
#[aztec(noinitcheck)]
fn approve_public_authwit(outer_hash: Field) {
let actions = AccountActions::public(
&mut context,
Expand Down Expand Up @@ -118,9 +133,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 @@ -148,7 +163,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
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ contract SchnorrHardcodedAccount {
actions.entrypoint(app_payload, fee_payload);
}

#[aztec(private)]
fn pay_init_fee(fee_payload: pub FeePayload) {
let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl);
actions.pay_init_fee(fee_payload);
}

#[aztec(private)]
fn spend_private_authwit(inner_hash: Field) -> Field {
let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ contract SchnorrSingleKeyAccount {
actions.entrypoint(app_payload, fee_payload);
}

#[aztec(private)]
fn pay_init_fee(fee_payload: pub FeePayload) {
let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl);
actions.pay_init_fee(fee_payload);
}

#[aztec(private)]
fn spend_private_authwit(inner_hash: Field) -> Field {
let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl);
Expand Down
8 changes: 4 additions & 4 deletions yarn-project/accounts/src/defaults/account_interface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type AccountInterface, type AuthWitnessProvider } from '@aztec/aztec.js/account';
import { type EntrypointInterface, type FeeOptions } from '@aztec/aztec.js/entrypoint';
import { type AuthWitness, type FunctionCall, type TxExecutionRequest } from '@aztec/circuit-types';
import { type EntrypointInterface, type ExecutionRequestInit } from '@aztec/aztec.js/entrypoint';
import { type AuthWitness, type TxExecutionRequest } from '@aztec/circuit-types';
import { type AztecAddress, type CompleteAddress, Fr } from '@aztec/circuits.js';
import { DefaultAccountEntrypoint } from '@aztec/entrypoints/account';
import { type NodeInfo } from '@aztec/types/interfaces';
Expand Down Expand Up @@ -29,8 +29,8 @@ export class DefaultAccountInterface implements AccountInterface {
this.version = new Fr(nodeInfo.protocolVersion);
}

createTxExecutionRequest(executions: FunctionCall[], fee?: FeeOptions): Promise<TxExecutionRequest> {
return this.entrypoint.createTxExecutionRequest(executions, fee);
createTxExecutionRequest(execution: ExecutionRequestInit): Promise<TxExecutionRequest> {
return this.entrypoint.createTxExecutionRequest(execution);
}

createAuthWit(messageHash: Fr): Promise<AuthWitness> {
Expand Down
8 changes: 7 additions & 1 deletion yarn-project/aztec.js/src/account/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { type CompleteAddress } from '@aztec/circuit-types';
import { type ContractArtifact } from '@aztec/foundation/abi';
import { type NodeInfo } from '@aztec/types/interfaces';

import { type AccountInterface } from './interface.js';
import { type AccountInterface, type AuthWitnessProvider } from './interface.js';

// docs:start:account-contract-interface
/**
Expand All @@ -29,5 +29,11 @@ export interface AccountContract {
* @returns An account interface instance for creating tx requests and authorizing actions.
*/
getInterface(address: CompleteAddress, nodeInfo: NodeInfo): AccountInterface;

/**
* Returns the auth witness provider for the given address.
* @param address - Address for which to create auth witnesses.
*/
getAuthWitnessProvider(address: CompleteAddress): AuthWitnessProvider;
}
// docs:end:account-contract-interface
73 changes: 73 additions & 0 deletions yarn-project/aztec.js/src/account_manager/deploy_account_method.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { type PublicKey } from '@aztec/circuit-types';
import { FunctionData } from '@aztec/circuits.js';
import {
type ContractArtifact,
type FunctionArtifact,
encodeArguments,
getFunctionArtifact,
} from '@aztec/foundation/abi';

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

/**
* Contract interaction for deploying an account contract. Handles fee preparation and contract initialization.
*/
export class DeployAccountMethod extends DeployMethod {
#authWitnessProvider: AuthWitnessProvider;
#feePaymentArtifact: FunctionArtifact | undefined;

constructor(
authWitnessProvider: AuthWitnessProvider,
publicKey: PublicKey,
wallet: Wallet,
artifact: ContractArtifact,
args: any[] = [],
constructorNameOrArtifact?: string | FunctionArtifact,
feePaymentNameOrArtifact?: string | FunctionArtifact,
) {
super(
publicKey,
wallet,
artifact,
(address, wallet) => Contract.at(address, artifact, wallet),
args,
constructorNameOrArtifact,
);

this.#authWitnessProvider = authWitnessProvider;
this.#feePaymentArtifact =
typeof feePaymentNameOrArtifact === 'string'
? getFunctionArtifact(artifact, feePaymentNameOrArtifact)
: feePaymentNameOrArtifact;
}

protected async getInitializeFunctionCalls(options: DeployOptions): Promise<ExecutionRequestInit> {
const exec = await super.getInitializeFunctionCalls(options);

if (options.fee && this.#feePaymentArtifact) {
const { address } = this.getInstance();
const feePayload = await EntrypointPayload.fromFeeOptions(options?.fee);

exec.calls.push({
to: address,
args: encodeArguments(this.#feePaymentArtifact, [feePayload]),
functionData: FunctionData.fromAbi(this.#feePaymentArtifact),
});

exec.authWitnesses ??= [];
exec.packedArguments ??= [];

exec.authWitnesses.push(await this.#authWitnessProvider.createAuthWit(feePayload.hash()));
exec.packedArguments.push(...feePayload.packedArguments);

options.fee = undefined;
}

return exec;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type TxHash, type TxReceipt } from '@aztec/circuit-types';
import { type PXE, type TxHash, type TxReceipt } from '@aztec/circuit-types';
import { type FieldsOf } from '@aztec/foundation/types';

import { type Wallet } from '../account/index.js';
Expand All @@ -15,8 +15,8 @@ export type DeployAccountTxReceipt = FieldsOf<TxReceipt> & {
* A deployment transaction for an account contract sent to the network, extending SentTx with methods to get the resulting wallet.
*/
export class DeployAccountSentTx extends SentTx {
constructor(private wallet: Wallet, txHashPromise: Promise<TxHash>) {
super(wallet, txHashPromise);
constructor(pxe: PXE, txHashPromise: Promise<TxHash>, private getWalletPromise: Promise<Wallet>) {
super(pxe, txHashPromise);
}

/**
Expand All @@ -36,7 +36,8 @@ export class DeployAccountSentTx extends SentTx {
*/
public async wait(opts: WaitOpts = DefaultWaitOpts): Promise<DeployAccountTxReceipt> {
const receipt = await super.wait(opts);
await waitForAccountSynch(this.pxe, this.wallet.getCompleteAddress(), opts);
return { ...receipt, wallet: this.wallet };
const wallet = await this.getWalletPromise;
await waitForAccountSynch(this.pxe, wallet.getCompleteAddress(), opts);
return { ...receipt, wallet };
}
}
Loading

0 comments on commit 6191313

Please sign in to comment.