Skip to content
Open
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ log/
codegenCache.json
.env
.cursor
pxe-test/
pxe-test/
tags
150 changes: 144 additions & 6 deletions benchmarks/counter.benchmark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,27 @@ import {
Benchmark,
type BenchmarkContext,
} from "@defi-wonderland/aztec-benchmark";
import { deriveKeys, generatePublicKey } from "@aztec/aztec.js/keys";
import { Fr } from "@aztec/aztec.js/fields";
import { computePartialAddress } from "@aztec/stdlib/contract";
import { GrumpkinScalar } from "@aztec/foundation/curves/grumpkin";
import { Capsule } from "@aztec/stdlib/tx";
import { poseidon2Hash } from "@aztec/foundation/crypto/poseidon";
import { getContractClassFromArtifact } from "@aztec/aztec.js/contracts";
import { computeContractAddressFromInstance } from "@aztec/stdlib/contract";
import { PublicKeys } from "@aztec/stdlib/keys";

import { CounterContract } from "../src/artifacts/Counter.js";
import { ConstantContractContract } from "../src/artifacts/ConstantContract.js";

// Extend the BenchmarkContext from the new package
interface CounterBenchmarkContext extends BenchmarkContext {
wallet: Wallet;
deployer: AztecAddress;
accounts: AztecAddress[];
counterContract: CounterContract;
counterContract: ConstantContractContract;
nonInitializedContract: ConstantContractContract;
constantsSlot: Fr;
constantsData: Fr[];
}

// Use export default class extending Benchmark
Expand All @@ -37,11 +49,102 @@ export default class CounterContractBenchmark extends Benchmark {

const [deployer] = accounts;

const counterContract = await CounterContract.deploy(wallet, deployer)
// Generate a secret key for the contract to derive its viewing keys
const contractSecretKey = Fr.random();

// Derive public keys (including incoming viewing public key) from the secret
const contractPublicKeys = (await deriveKeys(contractSecretKey)).publicKeys;

// Generate a valid signing key pair
const signingPrivateKey = GrumpkinScalar.random();
const signingPublicKey = await generatePublicKey(signingPrivateKey);

// Create the deployment with the public keys
const counterDeployment = ConstantContractContract.deployWithPublicKeys(
contractPublicKeys,
wallet,
signingPublicKey.x, // signing_pub_key_x
signingPublicKey.y, // signing_pub_key_y
);

// Get the instance to compute the partial address
const counterInstance = await counterDeployment.getInstance();

// Register the contract's keys with PXE so it can decrypt notes sent during deployment
const pxe = (wallet as any).pxe;
await pxe.registerAccount(
contractSecretKey,
await computePartialAddress(counterInstance),
);

// Now deploy the contract
const counterContract = await counterDeployment
.send({ from: deployer })
.deployed();

return { wallet, deployer, accounts, counterContract };
// Create capsule for fetch_from_constants benchmark
// The CONSTANTS_SLOT value from the contract
const CONSTANTS_SLOT = new Fr(0x31415926535n);

// Create Constants struct: { signing_public_key: PublicKeyNote { x, y } }
// When serialized, this is just [x, y]
const constantsData = [
new Fr(signingPublicKey.x),
new Fr(signingPublicKey.y),
];

// Deploy a non-initialized contract with proper initialization_hash
// Compute the constants_commitment which should match initialization_hash
const constantsCommitment = await poseidon2Hash(constantsData);

// Get the contract class from the artifact
const contractClass = await getContractClassFromArtifact(
ConstantContractContract.artifact,
);

// Create contract instance with initialization_hash = constants_commitment
const nonInitializedInstance = {
version: 1 as const,
salt: Fr.random(),
deployer: AztecAddress.ZERO,
currentContractClassId: contractClass.id,
originalContractClassId: contractClass.id,
initializationHash: constantsCommitment,
publicKeys: contractPublicKeys,
};

// Compute the address for this instance
const nonInitializedAddress = await computeContractAddressFromInstance(
nonInitializedInstance,
);

// Create the full instance with address
const nonInitializedInstanceWithAddress = {
...nonInitializedInstance,
address: nonInitializedAddress,
};

// Register the non-initialized contract with the wallet
await wallet.registerContract(
nonInitializedInstanceWithAddress,
ConstantContractContract.artifact,
);

// Create a ConstantContractContract instance for the non-initialized contract
const nonInitializedContract = await ConstantContractContract.at(
nonInitializedAddress,
wallet,
);

return {
wallet,
deployer,
accounts,
counterContract,
nonInitializedContract,
constantsSlot: CONSTANTS_SLOT,
constantsData,
};
}

/**
Expand All @@ -50,12 +153,47 @@ export default class CounterContractBenchmark extends Benchmark {
getMethods(
context: CounterBenchmarkContext,
): ContractFunctionInteractionCallIntent[] {
const { counterContract, wallet, deployer } = context;
const {
counterContract,
nonInitializedContract,
wallet,
deployer,
constantsSlot,
constantsData,
} = context;

// Create capsule only for the non-initialized contract
const capsule = new Capsule(
nonInitializedContract.address,
constantsSlot,
constantsData,
);

const methods: ContractFunctionInteractionCallIntent[] = [
{
caller: deployer,
action: counterContract.withWallet(wallet).methods.increment(),
action: counterContract.withWallet(wallet).methods.do_nothing(),
},
{
caller: deployer,
action: counterContract.withWallet(wallet).methods.fetch_from_storage(),
},
{
caller: deployer,
action: nonInitializedContract
.withWallet(wallet)
.methods.fetch_from_constants()
.with({ capsules: [capsule] }),
},
{
caller: deployer,
action: nonInitializedContract
.withWallet(wallet)
.methods.assert_fetched_values_are_correct(
constantsData[0],
constantsData[1],
)
.with({ capsules: [capsule] }),
},
];

Expand Down
116 changes: 81 additions & 35 deletions src/nr/counter_contract/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,57 +1,103 @@
use aztec::macros::aztec;
pub mod test;
mod public_key_note;

use dep::aztec::macros::aztec;

#[aztec]
pub contract Counter {
pub contract ConstantContract {
// aztec library imports
use aztec::{
macros::{functions::{external, initializer, only_self, view}, storage::storage},
protocol_types::address::AztecAddress,
state_vars::{PublicImmutable, PublicMutable},
use dep::aztec::{
macros::{functions::{external, initializer, noinitcheck}, storage::storage},
messages::message_delivery::MessageDelivery,
oracle::{
capsules,
get_contract_instance::get_contract_instance,
notes::{get_sender_for_tags, set_sender_for_tags},
},
protocol_types::{
address::AztecAddress,
hash::poseidon2_hash,
traits::{Deserialize, Serialize},
},
state_vars::SinglePrivateImmutable,
};

use crate::public_key_note::PublicKeyNote;

// @param owner The address of the owner
// @param counter A numerical value
#[storage]
struct Storage<Context> {
owner: PublicImmutable<AztecAddress, Context>,
counter: PublicMutable<u128, Context>,
signing_public_key: SinglePrivateImmutable<PublicKeyNote, Context>,
}

/// @dev Initialize the contract
/// @param owner The address of the owner
#[external("public")]
#[initializer]
fn constructor(owner: AztecAddress) {
self.storage.owner.initialize(owner);
#[derive(Serialize, Eq, Deserialize)]
pub struct Constants {
signing_public_key: PublicKeyNote,
}

/// @dev Retrieves the owner of the contract
/// @return The owner of the contract
#[external("public")]
fn get_owner() -> AztecAddress {
self.storage.owner.read()
pub global CONSTANTS_SLOT: Field = 0x31415926535;

impl Constants {
fn init(address: AztecAddress) -> Constants {
// Safety: this is checked against the committed value in the initialization hash
let constants: Constants = unsafe { capsules::load(address, CONSTANTS_SLOT).unwrap() };

let instance = get_contract_instance(address);
let serialized_constants = constants.serialize();
let constants_commitment = poseidon2_hash(serialized_constants);

assert_eq(constants_commitment, instance.initialization_hash);

constants
}
}

/// @dev Increments the counter
// Constructs the contract
#[external("private")]
fn increment() {
self.enqueue_self.increment_internal();
#[initializer]
fn constructor(signing_pub_key_x: Field, signing_pub_key_y: Field) {
let pub_key_note = PublicKeyNote { x: signing_pub_key_x, y: signing_pub_key_y };

// Safety: The sender for tags is only used to compute unconstrained shared secrets for emitting logs.
// Since this value is only used for unconstrained tagging and not for any constrained logic,
// it is safe to load from an unconstrained context.
let original_sender =
unsafe { get_sender_for_tags().unwrap_or(self.context.msg_sender().unwrap()) };

// We set the sender for tags to msg.sender to ensure tags are associated with the actual caller.
// Safety: Comment from above applies here as well.
unsafe { set_sender_for_tags(self.context.msg_sender().unwrap()) };

self.storage.signing_public_key.initialize(pub_key_note).deliver(
MessageDelivery.CONSTRAINED_ONCHAIN,
);

// Safety: Comment from above applies here as well.
unsafe { set_sender_for_tags(original_sender) };
}

/// @dev Retrieves the counter value
/// @return The current counter value
#[external("public")]
#[view]
fn get_counter() -> u128 {
self.storage.counter.read()
#[external("private")]
fn do_nothing() {}

#[external("private")]
fn fetch_from_storage() {
let storage = Storage::init(self.context);
let public_key = storage.signing_public_key.get_note();
}

/// @dev Increments the counter
#[external("public")]
#[only_self]
fn increment_internal() {
let current_value = self.storage.counter.read();
self.storage.counter.write(current_value + 1);
#[external("private")]
#[noinitcheck]
fn fetch_from_constants() {
let constants = Constants::init(self.address);
let public_key = constants.signing_public_key;
}

#[external("private")]
#[noinitcheck]
fn assert_fetched_values_are_correct(x: Field, y: Field) {
let constants = Constants::init(self.address);
let fetched_public_key = constants.signing_public_key;
let actual_public_key = PublicKeyNote { x: x, y: y };
assert_eq(actual_public_key, fetched_public_key);
}
}
9 changes: 9 additions & 0 deletions src/nr/counter_contract/src/public_key_note.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use dep::aztec::{macros::notes::note, protocol_types::traits::{Deserialize, Packable, Serialize}};

// Stores a public key composed of two fields
#[derive(Eq, Packable, Serialize, Deserialize)]
#[note]
pub struct PublicKeyNote {
pub x: Field,
pub y: Field,
}
2 changes: 0 additions & 2 deletions src/nr/counter_contract/src/test.nr

This file was deleted.

28 changes: 0 additions & 28 deletions src/nr/counter_contract/src/test/increment.nr

This file was deleted.

Loading
Loading