Skip to content

Commit

Permalink
Create fake contract data in node from contract classes and instances
Browse files Browse the repository at this point in the history
  • Loading branch information
spalladino committed Feb 6, 2024
1 parent 079368a commit 021b30f
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 35 deletions.
35 changes: 32 additions & 3 deletions yarn-project/archiver/src/archiver/archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE,
} from '@aztec/circuits.js';
import { ContractInstanceDeployedEvent } from '@aztec/circuits.js/contract';
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 @@ -462,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
103 changes: 71 additions & 32 deletions yarn-project/end-to-end/src/e2e_deploy_contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ContractBase,
ContractClassWithId,
ContractDeployer,
ContractInstanceWithAddress,
DebugLogger,
EthAddress,
Fr,
Expand All @@ -25,6 +26,7 @@ import {
MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS,
MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS,
Point,
PublicKey,
computeArtifactFunctionTree,
computeArtifactFunctionTreeRoot,
computeArtifactMetadataHash,
Expand All @@ -40,7 +42,6 @@ import { padArrayEnd } from '@aztec/foundation/collection';
import {
ContractClassRegistererContract,
ContractInstanceDeployerContract,
ReaderContractArtifact,
StatefulTestContract,
} from '@aztec/noir-contracts';
import { TestContract, TestContractArtifact } from '@aztec/noir-contracts/Test';
Expand Down Expand Up @@ -268,9 +269,10 @@ describe('e2e_deploy_contract', () => {
expect(await contracts[1].methods.summed_values(owner).view()).toEqual(52n);
});

// Tests registering a new contract class on a node
// All this dance will be hidden behind a nicer API in the near future!
describe('public registration and deployment', () => {
// Tests registering a new contract class on a node and then deploying an instance.
// These tests look scary, but don't fret: all this hodgepodge of calls will be hidden
// behind a much nicer API in the near future as part of #4080.
describe('registering a contract class', () => {
let registerer: ContractClassRegistererContract;
let artifact: ContractArtifact;
let contractClass: ContractClassWithId;
Expand All @@ -279,11 +281,11 @@ describe('e2e_deploy_contract', () => {
let publicBytecodeCommitment: Fr;

beforeAll(async () => {
artifact = ReaderContractArtifact;
artifact = StatefulTestContract.artifact;
contractClass = getContractClassFromArtifact(artifact);
privateFunctionsRoot = computePrivateFunctionsRoot(contractClass.privateFunctions);
publicBytecodeCommitment = computePublicBytecodeCommitment(contractClass.packedBytecode);
registerer = await registerContract(wallet, ContractClassRegistererContract, [], new Fr(1));
registerer = await registerContract(wallet, ContractClassRegistererContract, [], { salt: new Fr(1) });

logger(`contractClass.id: ${contractClass.id}`);
logger(`contractClass.artifactHash: ${contractClass.artifactHash}`);
Expand Down Expand Up @@ -388,33 +390,69 @@ describe('e2e_deploy_contract', () => {
.wait();
}, 60_000);

it('deploys a new instance of the registered class via the deployer', async () => {
const deployer = await registerContract(wallet, ContractInstanceDeployerContract, [], new Fr(1));

const salt = Fr.random();
const portalAddress = EthAddress.random();
const publicKey = Point.random();
const publicKeysHash = computePublicKeysHash(publicKey);
describe('deploying a contract instance', () => {
let instance: ContractInstanceWithAddress;
let deployer: ContractInstanceDeployerContract;
let deployTxHash: TxHash;
let initArgs: StatefulContractCtorArgs;
let publicKey: PublicKey;

beforeAll(async () => {
initArgs = [accounts[0].address, 42];
deployer = await registerContract(wallet, ContractInstanceDeployerContract, [], { salt: new Fr(1) });

const salt = Fr.random();
const portalAddress = EthAddress.random();
publicKey = Point.random();
const publicKeysHash = computePublicKeysHash(publicKey);

instance = getContractInstanceFromDeployParams(artifact, initArgs, salt, publicKey, portalAddress);
const tx = await deployer.methods
.deploy(salt, contractClass.id, instance.initializationHash, portalAddress, publicKeysHash, false)
.send()
.wait();
deployTxHash = tx.txHash;
});

const expected = getContractInstanceFromDeployParams(artifact, [], salt, publicKey, portalAddress);
it('emits deployment log', async () => {
const logs = await pxe.getUnencryptedLogs({ txHash: deployTxHash });
const deployedLog = logs.logs[0].log;
expect(deployedLog.contractAddress).toEqual(deployer.address);
});

const tx = await deployer.methods
.deploy(salt, contractClass.id, expected.initializationHash, portalAddress, publicKeysHash, false)
.send()
.wait();
it('stores contract instance in the aztec node', async () => {
const deployed = await aztecNode.getContract(instance.address);
expect(deployed).toBeDefined();
expect(deployed!.address).toEqual(instance.address);
expect(deployed!.contractClassId).toEqual(contractClass.id);
expect(deployed!.initializationHash).toEqual(instance.initializationHash);
expect(deployed!.portalContractAddress).toEqual(instance.portalContractAddress);
expect(deployed!.publicKeysHash).toEqual(instance.publicKeysHash);
expect(deployed!.salt).toEqual(instance.salt);
});

const logs = await pxe.getUnencryptedLogs({ txHash: tx.txHash });
const deployedLog = logs.logs[0].log;
expect(deployedLog.contractAddress).toEqual(deployer.address);

const instance = await aztecNode.getContract(expected.address);
expect(instance).toBeDefined();
expect(instance!.address).toEqual(expected.address);
expect(instance!.contractClassId).toEqual(contractClass.id);
expect(instance!.initializationHash).toEqual(expected.initializationHash);
expect(instance!.portalContractAddress).toEqual(portalAddress);
expect(instance!.publicKeysHash).toEqual(publicKeysHash);
expect(instance!.salt).toEqual(salt);
it('calls a public function on the deployed instance', async () => {
// TODO(@spalladino) We should **not** need the whole instance, including initArgs and salt,
// in order to interact with a public function for the contract. We may even not need
// all of it for running a private function. Consider removing `instance` as a required
// field in the aztec.js `Contract` class, maybe we can replace it with just the partialAddress.
// Not just that, but this instance has been broadcasted, so the pxe should be able to get
// its information from the node directly, excluding private functions, but it's ok because
// we are not going to run those - but this may require registering "partial" contracts in the pxe.
// Anyway, when we implement that, we should be able to replace this `registerContract` with
// a simpler `Contract.at(instance.address, wallet)`.
const registered = await registerContract(wallet, StatefulTestContract, initArgs, {
salt: instance.salt,
portalAddress: instance.portalContractAddress,
publicKey,
});
expect(registered.address).toEqual(instance.address);
const contract = await StatefulTestContract.at(instance.address, wallet);
const whom = AztecAddress.random();
await contract.methods.increment_public_value(whom, 10).send({ skipPublicSimulation: true }).wait();
const stored = await contract.methods.get_public_value(whom).view();
expect(stored).toEqual(10n);
});
});
});
});
Expand All @@ -436,9 +474,10 @@ async function registerContract<T extends ContractBase>(
wallet: Wallet,
contractArtifact: ContractArtifactClass<T>,
args: any[] = [],
salt?: Fr,
opts: { salt?: Fr; publicKey?: Point; portalAddress?: EthAddress } = {},
): Promise<T> {
const instance = getContractInstanceFromDeployParams(contractArtifact.artifact, args, salt);
const { salt, publicKey, portalAddress } = opts;
const instance = getContractInstanceFromDeployParams(contractArtifact.artifact, args, salt, publicKey, portalAddress);
await wallet.addContracts([{ artifact: contractArtifact.artifact, instance }]);
return contractArtifact.at(instance.address, wallet);
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,23 @@ contract StatefulTest {
increment(recipient_notes, amount, recipient);
}

#[aztec(public)]
fn increment_public_value(owner: AztecAddress, value: Field) {
let loc = storage.public_values.at(owner);
loc.write(loc.read() + value);
}

unconstrained fn summed_values(owner: AztecAddress) -> pub Field {
let owner_balance = storage.notes.at(owner);

// Return the sum of all notes in the set.
balance_utils::get_balance(owner_balance)
}

unconstrained fn get_public_value(owner: AztecAddress) -> pub Field {
storage.public_values.at(owner).read()
}

unconstrained fn compute_note_hash_and_nullifier(
contract_address: AztecAddress,
nonce: Field,
Expand Down

0 comments on commit 021b30f

Please sign in to comment.