-
Notifications
You must be signed in to change notification settings - Fork 590
Description
Problem Statement
Contract instance publishing is private-only, blocking the public factory pattern.
The publish_contract_instance_for_public_execution helper and ContractInstanceRegistry's publish_for_public_execution both use PrivateContext. As a result:
- Both initializers cannot be public – Factory + child cannot both have public initializers.
- Circular dependencies are hard – Two contracts needing each other's address (e.g. Governance ↔ Treasury) require a bootstrap.
- DX – Workaround (private init → enqueue child public init) is more complex.
Root cause: Registry's publish_for_public_execution is abi_private; aztec-nr helper uses get_contract_instance oracle (private-only).
Proposed Solution
Add a public path for publishing instances.
1. New public function on ContractInstanceRegistry
publish_for_public_execution_public (or similar) – abi_public, same args as private version. Uses PublicContext:
nullifier_exists_unsafefor class registration check (instead ofassert_nullifier_exists)deployer = universal_deploy ? zero : msg_senderpush_nullifier(address)(already supported in PublicContext)emit_public_logfor deployment event
The update function already shows public nullifier usage works.
2. aztec-nr helper
publish_contract_instance_for_public_execution_public (or similar) – takes PublicContext and instance params as arguments (no oracle).
3. Event format
Ensure public deployment events work with indexers/archiver (emit_public_log vs emit_private_log).
Example Use Case
Circular dependency – User deploys Governance; Governance's initializer spawns Treasury and passes self.address + config. (Or the other way around.) Class IDs known at deploy time:
// Treasury – spawned by Governance; needs governance address in its init
#[aztec]
pub contract Treasury {
#[storage]
struct Storage { governance: PublicMutable<AztecAddress>, fee_bps: PublicMutable<Field> }
#[external("public")]
#[initializer]
fn constructor(governance_address: AztecAddress, fee_bps: Field) {
self.governance.set(governance_address);
self.fee_bps.set(fee_bps);
}
}// Governance – user deploys this; its init spawns Treasury with actual constructor args
#[aztec]
pub contract Governance {
#[storage]
struct Storage { treasury: PublicMutable<AztecAddress> }
#[external("public")]
#[initializer]
fn constructor(
treasury_salt: Field, treasury_public_keys: PublicKeys,
fee_bps: Field, // actual arg for Treasury.constructor
) {
let registry = ContractInstanceRegistry::at(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS);
let deployer = self.address;
let treasury_class_id = ContractClassId::from_field(0x456);
// Derive init_hash from args for Treasury.constructor(governance_address, fee_bps); uses hash_args, compute_initialization_hash
let treasury_args_hash = hash_args([self.address.to_field(), fee_bps]);
let treasury_init_hash = compute_initialization_hash(
comptime { FunctionSelector::from_signature("constructor((Field),Field)") },
treasury_args_hash,
);
// 1. Publish Treasury
self.call_public(
registry.publish_for_public_execution_public(
treasury_salt,
treasury_class_id,
treasury_init_hash,
treasury_public_keys,
false
));
// 2. Compute address, then call Treasury with actual args
let treasury_address = AztecAddress::compute(
treasury_public_keys,
PartialAddress::compute(
treasury_class_id,
treasury_salt,
treasury_init_hash,
deployer
));
self.call_public(
Treasury::at(treasury_address).constructor(self.address, fee_bps)
);
self.treasury.set(treasury_address);
}
}Single public tx; Treasury gets Governance address + fee_bps from the deployment args.
Alternative Solutions
Workarounds that avoid implementing public publish:
| Alternative | Drawback |
|---|---|
| Private factory init + enqueue child init | Factory must be private; blocks public flows |
| Two-phase deploy (publish, then init) | Two txs, worse UX |
| Circular deps: 2 txs + setters | Pre-compute both addresses. Tx1: publish Gov, init with treasury=placeholder. Tx2: publish Treasury, init, call Gov.set_treasury. Works today. Drawback: 2 txs, setter required. |
| Status quo | Public factory impossible |
Additional Context
- Feasibility: PublicContext has
push_nullifierandnullifier_exists_unsafe; registry receives params as args (no oracle). Seepublic_context.nr, ContractInstanceRegistryupdatefn. - Related:
publish_contract_instance.nr,contract_instance_registry_contract/main.nr, e2e_amm.test.ts TODO(Make the AMM itself be a token #9480)