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

refactor: replacing use of capsules 1.0 with pxe_db + nuking capsules 1.0 #11885

Merged
merged 5 commits into from
Feb 11, 2025
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
1 change: 1 addition & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ yarn-error.log*

/docs/developers/reference/aztecjs
/docs/developers/reference/smart_contract_reference/aztec-nr
/docs/docs/protocol-specs/public-vm/gen
test-results
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,15 @@ sidebar_position: 5
tags: [functions, oracles]
---

`popCapsule` is used for passing artbitrary data. We have not yet included this in Aztec.nr, so it is a bit more complex than the other oracles. You can follow this how-to:
`popCapsule` is used for passing arbitrary data. We have not yet included this in Aztec.nr, so it is a bit more complex than the other oracles. You can follow this how-to:

### 1. Define the pop_capsule function

In a new file on the same level as your `main.nr`, implement an unconstrained function that calls the pop_capsule oracle:

#include_code pop_capsule noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/capsule.nr rust

### 2. Import this into your smart contract
### 1. Import capsules into your smart contract

If it lies in the same directory as your smart contract, you can import it like this:

#include_code import_pop_capsule noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr rust

### 3. Use it as any other oracle
### 2. Use it as any other oracle

Now it becomes a regular oracle you can call like this:

Expand Down
1 change: 1 addition & 0 deletions l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ library Constants {
uint256 internal constant MULTI_CALL_ENTRYPOINT_ADDRESS = 4;
uint256 internal constant FEE_JUICE_ADDRESS = 5;
uint256 internal constant ROUTER_ADDRESS = 6;
uint256 internal constant REGISTERER_CONTRACT_BYTECODE_CAPSULE_SLOT = 79025834455612;
uint256 internal constant FEE_JUICE_BALANCES_SLOT = 1;
uint256 internal constant DEFAULT_NPK_M_X =
582240093077765400562621227108555700500271598878376310175765873770292988861;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
mod events;
mod capsule;

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

Expand All @@ -11,7 +10,7 @@ pub contract ContractClassRegisterer {
ARTIFACT_FUNCTION_TREE_MAX_HEIGHT, FUNCTION_TREE_HEIGHT,
MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS,
MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS,
MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS,
MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS, REGISTERER_CONTRACT_BYTECODE_CAPSULE_SLOT,
},
contract_class_id::ContractClassId,
};
Expand All @@ -33,8 +32,7 @@ pub contract ContractClassRegisterer {
};

// docs:start:import_pop_capsule
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs need to be updated. I plan on doing that in a follow-up PR once I rename pxe_db to capsules.

use crate::capsule::pop_capsule;

use dep::aztec::oracle::pxe_db;
// docs:end:import_pop_capsule

#[private]
Expand All @@ -47,8 +45,13 @@ pub contract ContractClassRegisterer {
// TODO: Validate public_bytecode_commitment is the correct commitment of packed_public_bytecode
// TODO: We should be able to remove public_bytecode_commitment from the input if it's calculated in this function
// docs:start:pop_capsule
let packed_public_bytecode: [Field; MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS] =
unsafe { pop_capsule() };
let packed_public_bytecode: [Field; MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS] = unsafe {
pxe_db::load(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know before we only had one logical storage slot for pub / priv / unconstrained due to capsule design, but does it make sense still given the new design ?

Copy link
Contributor Author

@benesjan benesjan Feb 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It worked before with the same storage slot so I didn't bother with changing it. Would change it if it for whatever reason becomes an issue.

context.this_address(),
REGISTERER_CONTRACT_BYTECODE_CAPSULE_SLOT,
)
.unwrap()
};
// docs:end:pop_capsule
// First field element contains the length of the bytecode
let bytecode_length_in_bytes: u32 = packed_public_bytecode[0] as u32;
Expand Down Expand Up @@ -121,8 +124,13 @@ pub contract ContractClassRegisterer {
artifact_function_tree_leaf_index: Field,
function_data: InnerPrivateFunction,
) {
let private_bytecode: [Field; MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS] =
unsafe { pop_capsule() };
let private_bytecode: [Field; MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS] = unsafe {
pxe_db::load(
context.this_address(),
REGISTERER_CONTRACT_BYTECODE_CAPSULE_SLOT,
)
.unwrap()
};

let event = ClassPrivateFunctionBroadcasted {
contract_class_id,
Expand Down Expand Up @@ -162,8 +170,13 @@ pub contract ContractClassRegisterer {
artifact_function_tree_leaf_index: Field,
function_data: InnerUnconstrainedFunction,
) {
let unconstrained_bytecode: [Field; MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS] =
unsafe { pop_capsule() };
let unconstrained_bytecode: [Field; MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS] = unsafe {
pxe_db::load(
context.this_address(),
REGISTERER_CONTRACT_BYTECODE_CAPSULE_SLOT,
)
.unwrap()
};
let event = ClassUnconstrainedFunctionBroadcasted {
contract_class_id,
artifact_metadata_hash,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ pub global MULTI_CALL_ENTRYPOINT_ADDRESS: AztecAddress = AztecAddress::from_fiel
pub global FEE_JUICE_ADDRESS: AztecAddress = AztecAddress::from_field(5);
pub global ROUTER_ADDRESS: AztecAddress = AztecAddress::from_field(6);

// Randomly chosen slot for the bytecode capsule.
pub global REGISTERER_CONTRACT_BYTECODE_CAPSULE_SLOT: Field = 79025834455612;

// Slot of the balances map to be hashed with an AztecAddress (map key) to get an actual storage slot.
pub global FEE_JUICE_BALANCES_SLOT: u32 = 1;

Expand Down
15 changes: 13 additions & 2 deletions yarn-project/aztec.js/src/deployment/broadcast_function.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import {
ARTIFACT_FUNCTION_TREE_MAX_HEIGHT,
AztecAddress,
MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS,
REGISTERER_CONTRACT_ADDRESS,
REGISTERER_CONTRACT_BYTECODE_CAPSULE_SLOT,
computeVerificationKeyHash,
createPrivateFunctionMembershipProof,
createUnconstrainedFunctionMembershipProof,
Expand Down Expand Up @@ -57,7 +60,11 @@ export async function broadcastPrivateFunction(
MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS,
);

await wallet.addCapsule(bytecode);
await wallet.addCapsule(
AztecAddress.fromNumber(REGISTERER_CONTRACT_ADDRESS),
new Fr(REGISTERER_CONTRACT_BYTECODE_CAPSULE_SLOT),
bytecode,
);

const registerer = await getRegistererContract(wallet);
return Promise.resolve(
Expand Down Expand Up @@ -115,7 +122,11 @@ export async function broadcastUnconstrainedFunction(
MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS,
);

await wallet.addCapsule(bytecode);
await wallet.addCapsule(
AztecAddress.fromNumber(REGISTERER_CONTRACT_ADDRESS),
new Fr(REGISTERER_CONTRACT_BYTECODE_CAPSULE_SLOT),
bytecode,
);

const registerer = await getRegistererContract(wallet);
return registerer.methods.broadcast_unconstrained_function(
Expand Down
15 changes: 13 additions & 2 deletions yarn-project/aztec.js/src/deployment/register_class.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS, getContractClassFromArtifact } from '@aztec/circuits.js';
import {
AztecAddress,
Fr,
MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS,
REGISTERER_CONTRACT_ADDRESS,
REGISTERER_CONTRACT_BYTECODE_CAPSULE_SLOT,
getContractClassFromArtifact,
} from '@aztec/circuits.js';
import { type ContractArtifact, bufferAsFields } from '@aztec/foundation/abi';

import { type ContractFunctionInteraction } from '../contract/contract_function_interaction.js';
Expand All @@ -21,6 +28,10 @@ export async function registerContractClass(
await getContractClassFromArtifact(artifact);
const encodedBytecode = bufferAsFields(packedBytecode, MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS);
const registerer = await getRegistererContract(wallet);
await wallet.addCapsule(encodedBytecode);
await wallet.addCapsule(
AztecAddress.fromNumber(REGISTERER_CONTRACT_ADDRESS),
new Fr(REGISTERER_CONTRACT_BYTECODE_CAPSULE_SLOT),
encodedBytecode,
);
return registerer.methods.register(artifactHash, privateFunctionsRoot, publicBytecodeCommitment, emitPublicBytecode);
}
4 changes: 2 additions & 2 deletions yarn-project/aztec.js/src/wallet/base_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ export abstract class BaseWallet implements Wallet {
getAddress() {
return this.getCompleteAddress().address;
}
addCapsule(capsule: Fr[]): Promise<void> {
return this.pxe.addCapsule(capsule);
addCapsule(contract: AztecAddress, storageSlot: Fr, capsule: Fr[]): Promise<void> {
return this.pxe.addCapsule(contract, storageSlot, capsule);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kept the capsules name here as I will rename pxe_db to it in a followup PR.

}
registerAccount(secretKey: Fr, partialAddress: PartialAddress): Promise<CompleteAddress> {
return this.pxe.registerAccount(secretKey, partialAddress);
Expand Down
6 changes: 4 additions & 2 deletions yarn-project/circuit-types/src/interfaces/pxe.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ describe('PXESchema', () => {
});

it('addCapsule', async () => {
await context.client.addCapsule(times(3, Fr.random));
await context.client.addCapsule(address, Fr.random(), times(3, Fr.random));
});

it('registerAccount', async () => {
Expand Down Expand Up @@ -344,7 +344,9 @@ class MockPXE implements PXE {
expect(messageHash).toBeInstanceOf(Fr);
return Promise.resolve([Fr.random()]);
}
addCapsule(capsule: Fr[]): Promise<void> {
addCapsule(contract: AztecAddress, storageSlot: Fr, capsule: Fr[]): Promise<void> {
expect(contract).toBeInstanceOf(AztecAddress);
expect(storageSlot).toBeInstanceOf(Fr);
expect(capsule.every(c => c instanceof Fr)).toBeTruthy();
return Promise.resolve();
}
Expand Down
12 changes: 8 additions & 4 deletions yarn-project/circuit-types/src/interfaces/pxe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,15 @@ export interface PXE {
getAuthWitness(messageHash: Fr): Promise<Fr[] | undefined>;

/**
* Adding a capsule to the capsule dispenser.
* Adds a capsule.
* @param contract - The address of the contract to add the capsule to.
* @param storageSlot - The storage slot to add the capsule to.
* @param capsule - An array of field elements representing the capsule.
* @remarks A capsule is a "blob" of data that is passed to the contract through an oracle.
* @remarks A capsule is a "blob" of data that is passed to the contract through an oracle. It works similarly
* to public contract storage in that it's indexed by the contract address and storage slot but instead of the global
* network state it's backed by local PXE db.
*/
addCapsule(capsule: Fr[]): Promise<void>;
addCapsule(contract: AztecAddress, storageSlot: Fr, capsule: Fr[]): Promise<void>;

/**
* Registers a user account in PXE given its master encryption private key.
Expand Down Expand Up @@ -464,7 +468,7 @@ export const PXESchema: ApiSchemaFor<PXE> = {
.function()
.args(schemas.Fr)
.returns(z.union([z.undefined(), z.array(schemas.Fr)])),
addCapsule: z.function().args(z.array(schemas.Fr)).returns(z.void()),
addCapsule: z.function().args(schemas.AztecAddress, schemas.Fr, z.array(schemas.Fr)).returns(z.void()),
registerAccount: z.function().args(schemas.Fr, schemas.Fr).returns(CompleteAddress.schema),
getRegisteredAccounts: z.function().returns(z.array(CompleteAddress.schema)),
registerSender: z.function().args(schemas.AztecAddress).returns(schemas.AztecAddress),
Expand Down
1 change: 1 addition & 0 deletions yarn-project/circuits.js/src/constants.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export const REGISTERER_CONTRACT_ADDRESS = 3;
export const MULTI_CALL_ENTRYPOINT_ADDRESS = 4;
export const FEE_JUICE_ADDRESS = 5;
export const ROUTER_ADDRESS = 6;
export const REGISTERER_CONTRACT_BYTECODE_CAPSULE_SLOT = 79025834455612;
export const FEE_JUICE_BALANCES_SLOT = 1;
export const DEFAULT_NPK_M_X = 582240093077765400562621227108555700500271598878376310175765873770292988861n;
export const DEFAULT_NPK_M_Y = 10422444662424639723529825114205836958711284159673861467999592572974769103684n;
Expand Down
13 changes: 1 addition & 12 deletions yarn-project/pxe/src/database/kv_pxe_database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export class KVPxeDatabase implements PxeDatabase {
#completeAddressIndex: AztecAsyncMap<string, number>;
#addressBook: AztecAsyncSet<string>;
#authWitnesses: AztecAsyncMap<string, Buffer[]>;
#capsules: AztecAsyncArray<Buffer[]>;
#notes: AztecAsyncMap<string, Buffer>;
#nullifiedNotes: AztecAsyncMap<string, Buffer>;
#nullifierToNoteId: AztecAsyncMap<string, string>;
Expand Down Expand Up @@ -78,7 +77,6 @@ export class KVPxeDatabase implements PxeDatabase {
this.#addressBook = db.openSet('address_book');

this.#authWitnesses = db.openMap('auth_witnesses');
this.#capsules = db.openArray('capsules');

this.#contractArtifacts = db.openMap('contract_artifacts');
this.#contractInstances = db.openMap('contracts_instances');
Expand Down Expand Up @@ -189,15 +187,6 @@ export class KVPxeDatabase implements PxeDatabase {
return Promise.resolve(witness?.map(w => Fr.fromBuffer(w)));
}

async addCapsule(capsule: Fr[]): Promise<void> {
await this.#capsules.push(capsule.map(c => c.toBuffer()));
}

async popCapsule(): Promise<Fr[] | undefined> {
const val = await this.#capsules.pop();
return val?.map(b => Fr.fromBuffer(b));
}

async addNote(note: NoteDao, scope?: AztecAddress): Promise<void> {
await this.addNotes([note], scope);
}
Expand Down Expand Up @@ -652,7 +641,7 @@ export class KVPxeDatabase implements PxeDatabase {
}

async dbCopy(contractAddress: AztecAddress, srcSlot: Fr, dstSlot: Fr, numEntries: number): Promise<void> {
// In order to support overlaping source and destination regions we need to check the relative positions of source
// In order to support overlapping source and destination regions, we need to check the relative positions of source
// and destination. If destination is ahead of source, then by the time we overwrite source elements using forward
// indexes we'll have already read those. On the contrary, if source is ahead of destination we need to use backward
// indexes to avoid reading elements that've been overwritten.
Expand Down
16 changes: 1 addition & 15 deletions yarn-project/pxe/src/database/pxe_database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,6 @@ export interface PxeDatabase extends ContractArtifactDatabase, ContractInstanceD
*/
getAuthWitness(messageHash: Fr): Promise<Fr[] | undefined>;

/**
* Adding a capsule to the capsule dispenser.
* @remarks A capsule is a "blob" of data that is passed to the contract through an oracle.
* @param capsule - An array of field elements representing the capsule.
*/
addCapsule(capsule: Fr[]): Promise<void>;

/**
* Get the next capsule from the capsule dispenser.
* @remarks A capsule is a "blob" of data that is passed to the contract through an oracle.
* @returns A promise that resolves to an array of field elements representing the capsule.
*/
popCapsule(): Promise<Fr[] | undefined>;

/**
* Gets notes based on the provided filter.
* @param filter - The filter to apply to the notes.
Expand Down Expand Up @@ -216,7 +202,7 @@ export interface PxeDatabase extends ContractArtifactDatabase, ContractInstanceD

/**
* Stores arbitrary information in a per-contract non-volatile database, which can later be retrieved with `dbLoad`.
* If data was already stored at this slot, it is overwrriten.
* If data was already stored at this slot, it is overwritten.
* @param contractAddress - The contract address to scope the data under.
* @param slot - The slot in the database in which to store the value. Slots need not be contiguous.
* @param values - The data to store.
Expand Down
23 changes: 0 additions & 23 deletions yarn-project/pxe/src/database/pxe_database_test_suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,29 +54,6 @@ export function describePxeDatabase(getDatabase: () => PxeDatabase) {
});
});

describe('capsules', () => {
it('stores and retrieves capsules', async () => {
const capsule = [Fr.random(), Fr.random()];

await database.addCapsule(capsule);
await expect(database.popCapsule()).resolves.toEqual(capsule);
});

it("returns undefined if it doesn't have capsules", async () => {
await expect(database.popCapsule()).resolves.toBeUndefined();
});

it('behaves like a stack when storing capsules', async () => {
const capsule1 = [Fr.random(), Fr.random()];
const capsule2 = [Fr.random(), Fr.random()];

await database.addCapsule(capsule1);
await database.addCapsule(capsule2);
await expect(database.popCapsule()).resolves.toEqual(capsule2);
await expect(database.popCapsule()).resolves.toEqual(capsule1);
});
});

describe('incoming notes', () => {
let owners: CompleteAddress[];
let contractAddresses: AztecAddress[];
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/pxe/src/pxe_service/pxe_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ export class PXEService implements PXE {
return this.db.getAuthWitness(messageHash);
}

public addCapsule(capsule: Fr[]) {
return this.db.addCapsule(capsule);
public addCapsule(contract: AztecAddress, storageSlot: Fr, capsule: Fr[]) {
return this.db.dbStore(contract, storageSlot, capsule);
}

public getContractInstance(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
Expand Down
8 changes: 0 additions & 8 deletions yarn-project/pxe/src/simulator_oracle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,6 @@ export class SimulatorOracle implements DBOracle {
return witness;
}

async popCapsule(): Promise<Fr[]> {
const capsule = await this.db.popCapsule();
if (!capsule) {
throw new Error(`No capsules available`);
}
return capsule;
}

async getNotes(contractAddress: AztecAddress, storageSlot: Fr, status: NoteStatus, scopes?: AztecAddress[]) {
const noteDaos = await this.db.getNotes({
contractAddress,
Expand Down
Loading