Skip to content

Commit

Permalink
feat: Canonical instance deployer contract (AztecProtocol#4436)
Browse files Browse the repository at this point in the history
Implementation for the initial contract instance deployer contract.
Tracks instance deployed events in nodes and stores them locally.

Skips validations for class id and eth address for now.

Fixes AztecProtocol#4071
Fixes AztecProtocol#4072
  • Loading branch information
spalladino committed Feb 7, 2024
1 parent 34109eb commit b4acc8c
Show file tree
Hide file tree
Showing 21 changed files with 394 additions and 17 deletions.
2 changes: 2 additions & 0 deletions l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ library Constants {
0x1b70e95fde0b70adc30496b90a327af6a5e383e028e7a43211a07bcd;
uint256 internal constant REGISTERER_UNCONSTRAINED_FUNCTION_BROADCASTED_MAGIC_VALUE =
0xe7af816635466f128568edb04c9fa024f6c87fb9010fdbffa68b3d99;
uint256 internal constant DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE =
0x85864497636cf755ae7bde03f267ce01a520981c21c3682aaf82a631;
uint256 internal constant L1_TO_L2_MESSAGE_LENGTH = 8;
uint256 internal constant L1_TO_L2_MESSAGE_ORACLE_CALL_LENGTH = 25;
uint256 internal constant MAX_NOTE_FIELDS_LENGTH = 20;
Expand Down
77 changes: 73 additions & 4 deletions yarn-project/archiver/src/archiver/archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE,
} from '@aztec/circuits.js';
import { ContractInstanceDeployedEvent, computeSaltedInitializationHash } from '@aztec/circuits.js/contract';
import { createEthereumChain } from '@aztec/ethereum';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { toBigIntBE } from '@aztec/foundation/bigint-buffer';
Expand Down Expand Up @@ -73,12 +74,19 @@ export class Archiver implements ArchiveSource {
// TODO(@spalladino): Calculate this on the fly somewhere else.
// Today this is printed in the logs for end-to-end test at
// end-to-end/src/e2e_deploy_contract.test.ts -t 'registering a new contract class'
// as "Added contract ContractClassRegisterer ADDRESS"
// "Added contract ContractClassRegisterer ADDRESS"
// "Added contract ContractInstanceDeployer ADDRESS"

/** Address of the ClassRegisterer contract with a salt=1 */
private classRegistererAddress = AztecAddress.fromString(
'0x29c0cd0000951bba8af520ad5513cc53d9f0413c5a24a72a4ba8c17894c0bef9',
);

/** Address of the InstanceDeployer contract with a salt=1 */
private instanceDeployerAddress = AztecAddress.fromString(
'0x1799c61aa10430bf6fec46679c4cb76c3ed12cd8b6e73ed7389d5ae296ad1b97',
);

/**
* Creates a new instance of the Archiver.
* @param publicClient - A client for interacting with the Ethereum node.
Expand Down Expand Up @@ -288,13 +296,14 @@ export class Archiver implements ArchiveSource {
),
);

// Unroll all logs emitted during the retrieved blocks and extract any contract classes from them
// Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
await Promise.all(
retrievedBlocks.retrievedData.map(async block => {
const blockLogs = (block.newUnencryptedLogs?.txLogs ?? [])
.flatMap(txLog => txLog.unrollLogs())
.map(log => UnencryptedL2Log.fromBuffer(log));
await this.storeRegisteredContractClasses(blockLogs, block.number);
await this.storeDeployedContractInstances(blockLogs, block.number);
}),
);

Expand Down Expand Up @@ -355,6 +364,33 @@ export class Archiver implements ArchiveSource {
}
}

/**
* Extracts and stores contract instances out of ContractInstanceDeployed events emitted by the canonical deployer contract.
* @param allLogs - All logs emitted in a bunch of blocks.
*/
private async storeDeployedContractInstances(allLogs: UnencryptedL2Log[], blockNum: number) {
const contractInstances: ContractInstanceWithAddress[] = [];
for (const log of allLogs) {
try {
if (
!log.contractAddress.equals(this.instanceDeployerAddress) ||
!ContractInstanceDeployedEvent.isContractInstanceDeployedEvent(log.data)
) {
continue;
}
const event = ContractInstanceDeployedEvent.fromLogData(log.data);
contractInstances.push(event.toContractInstance());
} catch (err) {
this.log.warn(`Error processing log ${log.toHumanReadable()}: ${err}`);
}
}

if (contractInstances.length > 0) {
contractInstances.forEach(c => this.log(`Storing contract instance at ${c.address.toString()}`));
await this.store.addContractInstances(contractInstances, blockNum);
}
}

/**
* Stores extended contract data as classes and instances.
* Temporary solution until we source this data from the contract class registerer and instance deployer.
Expand Down Expand Up @@ -426,8 +462,37 @@ export class Archiver implements ArchiveSource {
* @param contractAddress - The contract data address.
* @returns The extended contract data or undefined if not found.
*/
getExtendedContractData(contractAddress: AztecAddress): Promise<ExtendedContractData | undefined> {
return this.store.getExtendedContractData(contractAddress);
public async getExtendedContractData(contractAddress: AztecAddress): Promise<ExtendedContractData | undefined> {
return (
(await this.store.getExtendedContractData(contractAddress)) ?? this.makeExtendedContractDataFor(contractAddress)
);
}

/**
* Temporary method for creating a fake extended contract data out of classes and instances registered in the node.
* Used as a fallback if the extended contract data is not found.
*/
private async makeExtendedContractDataFor(address: AztecAddress): Promise<ExtendedContractData | undefined> {
const instance = await this.store.getContractInstance(address);
if (!instance) {
return undefined;
}

const contractClass = await this.store.getContractClass(instance.contractClassId);
if (!contractClass) {
this.log.warn(
`Contract class ${instance.contractClassId.toString()} for address ${address.toString()} not found`,
);
return undefined;
}

return new ExtendedContractData(
new ContractData(address, instance.portalContractAddress),
contractClass.publicFunctions.map(f => new EncodedContractFunction(f.selector, f.isInternal, f.bytecode)),
contractClass.id,
computeSaltedInitializationHash(instance),
instance.publicKeysHash,
);
}

/**
Expand Down Expand Up @@ -505,6 +570,10 @@ export class Archiver implements ArchiveSource {
return this.store.getContractClass(id);
}

public getContract(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
return this.store.getContractInstance(address);
}

/**
* Gets up to `limit` amount of pending L1 to L2 messages.
* @param limit - The number of messages to return.
Expand Down
6 changes: 5 additions & 1 deletion yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import {
SequencerClient,
getGlobalVariableBuilder,
} from '@aztec/sequencer-client';
import { ContractClassPublic } from '@aztec/types/contracts';
import { ContractClassPublic, ContractInstanceWithAddress } from '@aztec/types/contracts';
import {
MerkleTrees,
ServerWorldStateSynchronizer,
Expand Down Expand Up @@ -243,6 +243,10 @@ export class AztecNodeService implements AztecNode {
return this.contractDataSource.getContractClass(id);
}

public getContract(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
return this.contractDataSource.getContract(address);
}

/**
* Gets up to `limit` amount of logs starting from `from`.
* @param from - Number of the L2 block to which corresponds the first logs to be returned.
Expand Down
8 changes: 7 additions & 1 deletion yarn-project/circuit-types/src/contract_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
serializeBufferArrayToVector,
serializeToBuffer,
} from '@aztec/foundation/serialize';
import { ContractClassPublic } from '@aztec/types/contracts';
import { ContractClassPublic, ContractInstanceWithAddress } from '@aztec/types/contracts';

/**
* Used for retrieval of contract data (A3 address, portal contract address, bytecode).
Expand Down Expand Up @@ -62,6 +62,12 @@ export interface ContractDataSource {
* @param id - Contract class id.
*/
getContractClass(id: Fr): Promise<ContractClassPublic | undefined>;

/**
* Returns a publicly deployed contract instance given its address.
* @param address - Address of the deployed contract.
*/
getContract(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined>;
}

/**
Expand Down
8 changes: 7 additions & 1 deletion yarn-project/circuit-types/src/interfaces/aztec-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { L1ContractAddresses } from '@aztec/ethereum';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { Fr } from '@aztec/foundation/fields';
import { ContractClassPublic } from '@aztec/types/contracts';
import { ContractClassPublic, ContractInstanceWithAddress } from '@aztec/types/contracts';

import { ContractData, ExtendedContractData } from '../contract_data.js';
import { L1ToL2MessageAndIndex } from '../l1_to_l2_message.js';
Expand Down Expand Up @@ -286,4 +286,10 @@ export interface AztecNode {
* @param id - Id of the contract class.
*/
getContractClass(id: Fr): Promise<ContractClassPublic | undefined>;

/**
* Returns a publicly deployed contract instance given its address.
* @param address - Address of the deployed contract.
*/
getContract(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined>;
}
2 changes: 2 additions & 0 deletions yarn-project/circuits.js/src/constants.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ export const REGISTERER_PRIVATE_FUNCTION_BROADCASTED_MAGIC_VALUE =
0x1b70e95fde0b70adc30496b90a327af6a5e383e028e7a43211a07bcdn;
export const REGISTERER_UNCONSTRAINED_FUNCTION_BROADCASTED_MAGIC_VALUE =
0xe7af816635466f128568edb04c9fa024f6c87fb9010fdbffa68b3d99n;
export const DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE =
0x85864497636cf755ae7bde03f267ce01a520981c21c3682aaf82a631n;
export const L1_TO_L2_MESSAGE_LENGTH = 8;
export const L1_TO_L2_MESSAGE_ORACLE_CALL_LENGTH = 25;
export const MAX_NOTE_FIELDS_LENGTH = 20;
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/circuits.js/src/contract/contract_class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type ContractArtifactWithHash = ContractArtifact & { artifactHash: Fr };
export function getContractClassFromArtifact(
artifact: ContractArtifact | ContractArtifactWithHash,
): ContractClassWithId {
const artifactHash = (artifact as ContractArtifactWithHash).artifactHash ?? computeArtifactHash(artifact);
const artifactHash = 'artifactHash' in artifact ? artifact.artifactHash : computeArtifactHash(artifact);
const publicFunctions: ContractClass['publicFunctions'] = artifact.functions
.filter(f => f.functionType === FunctionType.OPEN)
.map(f => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { toBigIntBE } from '@aztec/foundation/bigint-buffer';
import { Fr } from '@aztec/foundation/fields';
import { BufferReader } from '@aztec/foundation/serialize';
import { ContractInstanceWithAddress } from '@aztec/types/contracts';

import { DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE } from '../constants.gen.js';
import { AztecAddress, EthAddress } from '../index.js';

/** Event emitted from the ContractInstanceDeployer. */
export class ContractInstanceDeployedEvent {
constructor(
public readonly address: AztecAddress,
public readonly version: number,
public readonly salt: Fr,
public readonly contractClassId: Fr,
public readonly initializationHash: Fr,
public readonly portalContractAddress: EthAddress,
public readonly publicKeysHash: Fr,
public readonly universalDeploy: boolean,
) {}

static isContractInstanceDeployedEvent(log: Buffer) {
return toBigIntBE(log.subarray(0, 32)) == DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE;
}

static fromLogData(log: Buffer) {
if (!this.isContractInstanceDeployedEvent(log)) {
const magicValue = DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE.toString(16);
throw new Error(`Log data for ContractInstanceDeployedEvent is not prefixed with magic value 0x${magicValue}`);
}
const reader = new BufferReader(log.subarray(32));
const address = reader.readObject(AztecAddress);
const version = reader.readObject(Fr).toNumber();
const salt = reader.readObject(Fr);
const contractClassId = reader.readObject(Fr);
const initializationHash = reader.readObject(Fr);
const portalContractAddress = reader.readObject(EthAddress);
const publicKeysHash = reader.readObject(Fr);
const universalDeploy = reader.readObject(Fr).toBool();

return new ContractInstanceDeployedEvent(
address,
version,
salt,
contractClassId,
initializationHash,
portalContractAddress,
publicKeysHash,
universalDeploy,
);
}

toContractInstance(): ContractInstanceWithAddress {
if (this.version !== 1) {
throw new Error(`Unexpected contract instance version ${this.version}`);
}

return {
address: this.address,
version: this.version,
contractClassId: this.contractClassId,
initializationHash: this.initializationHash,
portalContractAddress: this.portalContractAddress,
publicKeysHash: this.publicKeysHash,
salt: this.salt,
};
}
}
1 change: 1 addition & 0 deletions yarn-project/circuits.js/src/contract/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './contract_address.js';
export * from './private_function.js';
export * from './public_bytecode.js';
export * from './contract_class_registered_event.js';
export * from './contract_instance_deployed_event.js';
1 change: 1 addition & 0 deletions yarn-project/end-to-end/src/cli_docs_sandbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ BenchmarkingContractArtifact
CardGameContractArtifact
ChildContractArtifact
ContractClassRegistererContractArtifact
ContractInstanceDeployerContractArtifact
CounterContractArtifact
DocsExampleContractArtifact
EasyPrivateTokenContractArtifact
Expand Down
Loading

0 comments on commit b4acc8c

Please sign in to comment.