Skip to content

Commit

Permalink
feat: Gas token self deploys (#6956)
Browse files Browse the repository at this point in the history
Handles genesis by having gas token deploy itself by minting tokens to
itself in an entrypoint method.
  • Loading branch information
spalladino authored Jun 6, 2024
1 parent e671935 commit ecd7614
Show file tree
Hide file tree
Showing 16 changed files with 153 additions and 77 deletions.
4 changes: 3 additions & 1 deletion l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,10 @@ library Constants {
9735143693259978736521448915549382765209954358646272896519366195578572330622;
uint256 internal constant DEPLOYER_CONTRACT_ADDRESS =
1330791240588942273989478952163154931941860232471291360599950658792066893795;
uint256 internal constant REGISTERER_CONTRACT_ADDRESS =
12230492553436229472833564540666503591270810173190529382505862577652523721217;
uint256 internal constant GAS_TOKEN_ADDRESS =
15579792265603019607941255406546358293698695592162311788953655054527726682735;
21054354231481372816168706751151469079551620620213512837742215289221210616379;
uint256 internal constant AZTEC_ADDRESS_LENGTH = 1;
uint256 internal constant GAS_FEES_LENGTH = 2;
uint256 internal constant GAS_LENGTH = 2;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ type = "contract"
[dependencies]
aztec = { path = "../../../aztec-nr/aztec" }
authwit = { path = "../../../aztec-nr/authwit" }
deployer = { path = "../contract_instance_deployer_contract" }
registerer = { path = "../contract_class_registerer_contract" }
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
mod lib;

contract GasToken {
use dep::aztec::protocol_types::{abis::function_selector::FunctionSelector, address::{AztecAddress, EthAddress}};
use dep::aztec::state_vars::{SharedImmutable, PublicMutable, Map};
use dep::aztec::{
protocol_types::{
contract_class_id::ContractClassId, abis::function_selector::FunctionSelector,
address::{AztecAddress, EthAddress},
constants::{DEPLOYER_CONTRACT_ADDRESS, REGISTERER_CONTRACT_ADDRESS}
},
state_vars::{SharedImmutable, PublicMutable, Map},
oracle::get_contract_instance::get_contract_instance, deploy::deploy_contract
};

use dep::deployer::ContractInstanceDeployer;
use dep::registerer::ContractClassRegisterer;

use crate::lib::{calculate_fee, get_bridge_gas_msg_hash};

Expand All @@ -14,6 +24,51 @@ contract GasToken {
portal_address: SharedImmutable<EthAddress>,
}

// Not flagged as initializer to reduce cost of checking init nullifier in all functions.
// This function should be called as entrypoint to initialize the contract by minting itself funds.
#[aztec(private)]
fn deploy(
artifact_hash: Field,
private_functions_root: Field,
public_bytecode_commitment: Field,
portal_address: EthAddress
) {
// Validate contract class parameters are correct
let self = context.this_address();
let instance = get_contract_instance(self);
let contract_class_id = ContractClassId::compute(
artifact_hash,
private_functions_root,
public_bytecode_commitment
);
assert(
instance.contract_class_id == contract_class_id, "Invalid contract class id computed for gas token"
);

// Increase self balance and set as fee payer, and end setup
let deploy_fees = 20000000000;
GasToken::at(self)._increase_public_balance(self, deploy_fees).enqueue(&mut context);
context.set_as_fee_payer();
context.end_setup();

// Register class and publicly deploy contract
let _register = ContractClassRegisterer::at(AztecAddress::from_field(REGISTERER_CONTRACT_ADDRESS)).register(
artifact_hash,
private_functions_root,
public_bytecode_commitment
).call(&mut context);
let _deploy = ContractInstanceDeployer::at(AztecAddress::from_field(DEPLOYER_CONTRACT_ADDRESS)).deploy(
instance.salt,
instance.contract_class_id,
instance.initialization_hash,
instance.public_keys_hash,
true
).call(&mut context);

// Enqueue call to set the portal address
GasToken::at(self).set_portal(portal_address).enqueue(&mut context);
}

// We purposefully not set this function as an initializer so we do not bind
// the contract to a specific L1 portal address, since the gas token address
// is a hardcoded constant in the rollup circuits.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ global FIXED_DA_GAS: u32 = 512;
// CANONICAL CONTRACT ADDRESSES
global CANONICAL_KEY_REGISTRY_ADDRESS = 0x1585e564a60e6ec974bc151b62705292ebfc75c33341986a47fd9749cedb567e;
global DEPLOYER_CONTRACT_ADDRESS = 0x02f1337e8c79dd0247ccbde85241ad65ee991ae283a63479e095e51f0abbc7e3;
global GAS_TOKEN_ADDRESS = 0x2271d994fae5e4279485ca23d5c2408f408155676cd31d487d127bae206d026f;
global REGISTERER_CONTRACT_ADDRESS = 0x1b0a36a60d2a1a358a328feb38cb38244429d8f57d25510083795d0491e1e201;
global GAS_TOKEN_ADDRESS = 0x2e8c579a24417ffdfe9e28139023ed46748e64508e20d7d63163ef7a0732b23b;

// LENGTH OF STRUCTS SERIALIZED TO FIELDS
global AZTEC_ADDRESS_LENGTH = 1;
Expand Down
13 changes: 5 additions & 8 deletions yarn-project/archiver/src/archiver/archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { type EthAddress } from '@aztec/foundation/eth-address';
import { Fr } from '@aztec/foundation/fields';
import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log';
import { RunningPromise } from '@aztec/foundation/running-promise';
import { getCanonicalClassRegistererAddress } from '@aztec/protocol-contracts/class-registerer';
import { ClassRegistererAddress } from '@aztec/protocol-contracts/class-registerer';
import {
type ContractClassPublic,
type ContractDataSource,
Expand Down Expand Up @@ -292,8 +292,8 @@ export class Archiver implements ArchiveSource {
* @param allLogs - All logs emitted in a bunch of blocks.
*/
private async storeRegisteredContractClasses(allLogs: UnencryptedL2Log[], blockNum: number) {
const contractClasses = ContractClassRegisteredEvent.fromLogs(allLogs, getCanonicalClassRegistererAddress()).map(
e => e.toContractClassPublic(),
const contractClasses = ContractClassRegisteredEvent.fromLogs(allLogs, ClassRegistererAddress).map(e =>
e.toContractClassPublic(),
);
if (contractClasses.length > 0) {
contractClasses.forEach(c => this.log.verbose(`Registering contract class ${c.id.toString()}`));
Expand All @@ -315,11 +315,8 @@ export class Archiver implements ArchiveSource {

private async storeBroadcastedIndividualFunctions(allLogs: UnencryptedL2Log[], _blockNum: number) {
// Filter out private and unconstrained function broadcast events
const privateFnEvents = PrivateFunctionBroadcastedEvent.fromLogs(allLogs, getCanonicalClassRegistererAddress());
const unconstrainedFnEvents = UnconstrainedFunctionBroadcastedEvent.fromLogs(
allLogs,
getCanonicalClassRegistererAddress(),
);
const privateFnEvents = PrivateFunctionBroadcastedEvent.fromLogs(allLogs, ClassRegistererAddress);
const unconstrainedFnEvents = UnconstrainedFunctionBroadcastedEvent.fromLogs(allLogs, ClassRegistererAddress);

// Group all events by contract class id
for (const [classIdString, classEvents] of Object.entries(
Expand Down
4 changes: 3 additions & 1 deletion yarn-project/circuits.js/src/constants.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ export const FIXED_DA_GAS = 512;
export const CANONICAL_KEY_REGISTRY_ADDRESS =
9735143693259978736521448915549382765209954358646272896519366195578572330622n;
export const DEPLOYER_CONTRACT_ADDRESS = 1330791240588942273989478952163154931941860232471291360599950658792066893795n;
export const GAS_TOKEN_ADDRESS = 15579792265603019607941255406546358293698695592162311788953655054527726682735n;
export const REGISTERER_CONTRACT_ADDRESS =
12230492553436229472833564540666503591270810173190529382505862577652523721217n;
export const GAS_TOKEN_ADDRESS = 21054354231481372816168706751151469079551620620213512837742215289221210616379n;
export const AZTEC_ADDRESS_LENGTH = 1;
export const GAS_FEES_LENGTH = 2;
export const GAS_LENGTH = 2;
Expand Down
6 changes: 3 additions & 3 deletions yarn-project/circuits.js/src/contract/artifact_hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ export function computeArtifactMetadataHash(artifact: ContractArtifact) {
return sha256Fr(Buffer.from(JSON.stringify({ name: artifact.name }), 'utf-8'));
}

// TODO(palla) Minimize impact of contract instance deployer address changing, using the same
// trick as in the contracts above.
if (artifact.name === 'ContractInstanceDeployer') {
// TODO(palla) Minimize impact of contract instance deployer and class registerer addresses
// changing, using the same trick as in the contracts above.
if (artifact.name === 'ContractInstanceDeployer' || artifact.name === 'ContractClassRegisterer') {
return sha256Fr(Buffer.from(JSON.stringify({ name: artifact.name }), 'utf-8'));
}

Expand Down
5 changes: 5 additions & 0 deletions yarn-project/circuits.js/src/structs/gas_settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ export class GasSettings {
});
}

/** Default gas settings with no teardown */
static teardownless() {
return GasSettings.default({ teardownGasLimits: Gas.from({ l2Gas: 0, daGas: 0 }) });
}

/** Gas settings to use for simulating a contract call. */
static simulation() {
return GasSettings.default();
Expand Down
8 changes: 4 additions & 4 deletions yarn-project/cli/src/inspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { type AztecAddress, type Fr } from '@aztec/circuits.js';
import { siloNullifier } from '@aztec/circuits.js/hash';
import { type LogFn } from '@aztec/foundation/log';
import { toHumanReadable } from '@aztec/foundation/serialize';
import { getCanonicalClassRegistererAddress } from '@aztec/protocol-contracts/class-registerer';
import { getCanonicalInstanceDeployer } from '@aztec/protocol-contracts/instance-deployer';
import { ClassRegistererAddress } from '@aztec/protocol-contracts/class-registerer';
import { InstanceDeployerAddress } from '@aztec/protocol-contracts/instance-deployer';

export async function inspectBlock(pxe: PXE, blockNumber: number, log: LogFn, opts: { showTxs?: boolean } = {}) {
const block = await pxe.getBlock(blockNumber);
Expand Down Expand Up @@ -161,8 +161,8 @@ function toFriendlyAddress(address: AztecAddress, artifactMap: ArtifactMap) {

async function getKnownNullifiers(pxe: PXE, artifactMap: ArtifactMap) {
const knownContracts = await pxe.getContracts();
const deployerAddress = getCanonicalInstanceDeployer().address;
const registererAddress = getCanonicalClassRegistererAddress();
const deployerAddress = InstanceDeployerAddress;
const registererAddress = ClassRegistererAddress;
const initNullifiers: Record<string, AztecAddress> = {};
const deployNullifiers: Record<string, AztecAddress> = {};
const classNullifiers: Record<string, string> = {};
Expand Down
49 changes: 22 additions & 27 deletions yarn-project/end-to-end/src/e2e_fees/fees_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,17 +136,17 @@ export class FeesTest {
}

public async applyBaseSnapshots() {
await this.applyDeployGasTokenSnapshot();
await this.applyInitialAccountsSnapshot();
await this.applyPublicDeployAccountsSnapshot();
await this.applyDeployGasTokenSnapshot();
await this.applyDeployBananaTokenSnapshot();
}

private async applyInitialAccountsSnapshot() {
await this.snapshotManager.snapshot(
'initial_accounts',
addAccounts(3, this.logger),
async ({ accountKeys }, { pxe, aztecNode }) => {
async ({ accountKeys }, { pxe, aztecNode, aztecNodeConfig }) => {
this.pxe = pxe;
this.aztecNode = aztecNode;
const accountManagers = accountKeys.map(ak => getSchnorrAccount(pxe, ak[0], ak[1], 1));
Expand All @@ -155,7 +155,19 @@ export class FeesTest {
this.wallets.forEach((w, i) => this.logger.verbose(`Wallet ${i} address: ${w.getAddress()}`));
[this.aliceWallet, this.bobWallet] = this.wallets.slice(0, 2);
[this.aliceAddress, this.bobAddress, this.sequencerAddress] = this.wallets.map(w => w.getAddress());
this.gasTokenContract = await GasTokenContract.at(getCanonicalGasToken().address, this.aliceWallet);
this.coinbase = EthAddress.random();

const { publicClient, walletClient } = createL1Clients(aztecNodeConfig.rpcUrl, MNEMONIC);
this.gasBridgeTestHarness = await GasPortalTestingHarnessFactory.create({
aztecNode: aztecNode,
pxeService: pxe,
publicClient: publicClient,
walletClient: walletClient,
wallet: this.aliceWallet,
logger: this.logger,
mockL1: false,
});
},
);
}
Expand All @@ -167,31 +179,14 @@ export class FeesTest {
}

private async applyDeployGasTokenSnapshot() {
await this.snapshotManager.snapshot(
'deploy_gas_token',
async context => {
await deployCanonicalGasToken(
new SignerlessWallet(
context.pxe,
new DefaultMultiCallEntrypoint(context.aztecNodeConfig.chainId, context.aztecNodeConfig.version),
),
);
},
async (_data, context) => {
this.gasTokenContract = await GasTokenContract.at(getCanonicalGasToken().address, this.aliceWallet);

const { publicClient, walletClient } = createL1Clients(context.aztecNodeConfig.rpcUrl, MNEMONIC);
this.gasBridgeTestHarness = await GasPortalTestingHarnessFactory.create({
aztecNode: context.aztecNode,
pxeService: context.pxe,
publicClient: publicClient,
walletClient: walletClient,
wallet: this.aliceWallet,
logger: this.logger,
mockL1: false,
});
},
);
await this.snapshotManager.snapshot('deploy_gas_token', async context => {
await deployCanonicalGasToken(
new SignerlessWallet(
context.pxe,
new DefaultMultiCallEntrypoint(context.aztecNodeConfig.chainId, context.aztecNodeConfig.version),
),
);
});
}

private async applyDeployBananaTokenSnapshot() {
Expand Down
40 changes: 29 additions & 11 deletions yarn-project/end-to-end/src/fixtures/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
EthCheatCodes,
type L1ContractArtifactsForDeployment,
LogType,
NoFeePaymentMethod,
type PXE,
type SentTx,
SignerlessWallet,
Expand All @@ -30,9 +31,12 @@ import { DefaultMultiCallEntrypoint } from '@aztec/aztec.js/entrypoint';
import { type BBNativeProofCreator } from '@aztec/bb-prover';
import {
CANONICAL_KEY_REGISTRY_ADDRESS,
GasSettings,
MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS,
computeContractAddressFromInstance,
getContractClassFromArtifact,
} from '@aztec/circuits.js';
import { bufferAsFields } from '@aztec/foundation/abi';
import { makeBackoff, retry } from '@aztec/foundation/retry';
import {
AvailabilityOracleAbi,
Expand Down Expand Up @@ -591,27 +595,41 @@ export async function expectMappingDelta<K, V extends number | bigint>(
/**
* Deploy the protocol contracts to a running instance.
*/
export async function deployCanonicalGasToken(deployer: Wallet) {
export async function deployCanonicalGasToken(pxe: PXE) {
// "deploy" the Gas token as it contains public functions
const gasPortalAddress = (await deployer.getNodeInfo()).l1ContractAddresses.gasPortalAddress;
const gasPortalAddress = (await pxe.getNodeInfo()).l1ContractAddresses.gasPortalAddress;
const canonicalGasToken = getCanonicalGasToken();

if (await deployer.isContractClassPubliclyRegistered(canonicalGasToken.contractClass.id)) {
if (await pxe.isContractClassPubliclyRegistered(canonicalGasToken.contractClass.id)) {
getLogger().debug('Gas token already deployed');
await expect(deployer.isContractPubliclyDeployed(canonicalGasToken.address)).resolves.toBe(true);
await expect(pxe.isContractPubliclyDeployed(canonicalGasToken.address)).resolves.toBe(true);
return;
}

const gasToken = await GasTokenContract.deploy(deployer)
.send({ contractAddressSalt: canonicalGasToken.instance.salt, universalDeploy: true })
.deployed();
await gasToken.methods.set_portal(gasPortalAddress).send().wait();
// Capsules will die soon, patience!
const publicBytecode = canonicalGasToken.contractClass.packedBytecode;
const encodedBytecode = bufferAsFields(publicBytecode, MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS);
await pxe.addCapsule(encodedBytecode);

await pxe.registerContract(canonicalGasToken);
const wallet = new SignerlessWallet(pxe);
const gasToken = await GasTokenContract.at(canonicalGasToken.address, wallet);

await gasToken.methods
.deploy(
canonicalGasToken.contractClass.artifactHash,
canonicalGasToken.contractClass.privateFunctionsRoot,
canonicalGasToken.contractClass.publicBytecodeCommitment,
gasPortalAddress,
)
.send({ fee: { paymentMethod: new NoFeePaymentMethod(), gasSettings: GasSettings.teardownless() } })
.wait();

getLogger().info(`Gas token publicly deployed at ${gasToken.address}`);

await expect(deployer.isContractClassPubliclyRegistered(gasToken.instance.contractClassId)).resolves.toBe(true);
await expect(deployer.getContractInstance(gasToken.address)).resolves.toBeDefined();
await expect(deployer.isContractPubliclyDeployed(gasToken.address)).resolves.toBe(true);
await expect(pxe.isContractClassPubliclyRegistered(gasToken.instance.contractClassId)).resolves.toBe(true);
await expect(pxe.getContractInstance(gasToken.address)).resolves.toBeDefined();
await expect(pxe.isContractPubliclyDeployed(gasToken.address)).resolves.toBe(true);
}

export async function deployCanonicalKeyRegistry(deployer: Wallet) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { computeContractAddressFromInstance, getContractClassFromArtifact } from '@aztec/circuits.js';

import { getCanonicalClassRegisterer, getCanonicalClassRegistererAddress } from './index.js';
import { ClassRegistererAddress, getCanonicalClassRegisterer } from './index.js';

describe('ClassRegisterer', () => {
it('returns canonical protocol contract', () => {
const contract = getCanonicalClassRegisterer();
expect(computeContractAddressFromInstance(contract.instance)).toEqual(contract.address);
expect(getContractClassFromArtifact(contract.artifact).id).toEqual(contract.contractClass.id);
expect(contract.address.toString()).toEqual(getCanonicalClassRegistererAddress().toString());
expect(contract.address.toString()).toEqual(ClassRegistererAddress.toString());
});
});
20 changes: 9 additions & 11 deletions yarn-project/protocol-contracts/src/class-registerer/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { type AztecAddress } from '@aztec/circuits.js';
import { AztecAddress, REGISTERER_CONTRACT_ADDRESS } from '@aztec/circuits.js';

import { type ProtocolContract, getCanonicalProtocolContract } from '../protocol_contract.js';
import { ContractClassRegistererArtifact } from './artifact.js';

/** Returns the canonical deployment of the class registerer contract. */
export function getCanonicalClassRegisterer(): ProtocolContract {
return getCanonicalProtocolContract(ContractClassRegistererArtifact, 1);
}

let address: AztecAddress | undefined = undefined;

/** Returns the address for the canonical deployment of the class registerer */
export function getCanonicalClassRegistererAddress() {
if (!address) {
address = getCanonicalClassRegisterer().address;
const contract = getCanonicalProtocolContract(ContractClassRegistererArtifact, 1);
if (!contract.address.equals(ClassRegistererAddress)) {
throw new Error(
`Incorrect address for class registerer (got ${contract.address.toString()} but expected ${ClassRegistererAddress.toString()}).`,
);
}
return address;
return contract;
}

export const ClassRegistererAddress = AztecAddress.fromBigInt(REGISTERER_CONTRACT_ADDRESS);
Loading

0 comments on commit ecd7614

Please sign in to comment.