Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
createPXEClient,
getContractClassFromArtifact,
makeFetch,
sleep,
} from '@aztec/aztec.js';
import { CounterContract } from '@aztec/noir-contracts.js/Counter';
import { DocsExampleContract } from '@aztec/noir-contracts.js/DocsExample';
Expand Down Expand Up @@ -140,9 +141,34 @@ describe('e2e_deploy_contract deploy method', () => {
await new BatchCall(wallet, [...deploy.calls, init]).send().wait();
}, 300_000);

it.skip('publicly deploys and calls a public function in a tx in the same block', async () => {
// TODO(@spalladino): Requires being able to read a nullifier on the same block it was emitted.
});
it.skip('publicly deploys a contract in one tx and calls a public function on it later in the same block', async () => {
await t.aztecNode.setConfig({ minTxsPerBlock: 2 });

const owner = wallet.getAddress();
logger.debug('Initializing deploy method');
const deployMethod = StatefulTestContract.deploy(wallet, owner, owner, 42);
logger.debug('Creating request/calls to register and deploy contract');
const deploy = await deployMethod.request();
const deployTx = new BatchCall(wallet, deploy.calls);
logger.debug('Getting an instance of the not-yet-deployed contract to batch calls to');
const contract = await StatefulTestContract.at((await deployMethod.getInstance()).address, wallet);

logger.debug('Creating public call to run in same block as deployment');
const publicCall = contract.methods.increment_public_value(owner, 84);

// First send the deploy transaction
const deployTxPromise = deployTx.send({ skipPublicSimulation: true }).wait({ timeout: 600 });

// Wait a bit to ensure the deployment transaction gets processed first
await sleep(5000);

// Then send the public call transaction
const publicCallTxPromise = publicCall.send({ skipPublicSimulation: true }).wait({ timeout: 600 });

logger.debug('Deploying a contract and calling a public function in the same block');
const [deployTxReceipt, publicCallTxReceipt] = await Promise.all([deployTxPromise, publicCallTxPromise]);
expect(deployTxReceipt.blockNumber).toEqual(publicCallTxReceipt.blockNumber);
}, 300_000);

describe('regressions', () => {
it('fails properly when trying to deploy a contract with a failing constructor with a pxe client with retries', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class PhasesTxValidator implements TxValidator<Tx> {

return { result: 'valid' };
} finally {
await this.contractDataSource.removeNewContracts(tx);
this.contractDataSource.clearContractsForTx();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { DEPLOYER_CONTRACT_ADDRESS } from '@aztec/constants';
import { Fr } from '@aztec/foundation/fields';
import { createLogger } from '@aztec/foundation/log';
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
import { computeFeePayerBalanceStorageSlot } from '@aztec/protocol-contracts/fee-juice';
import { computeFeePayerBalanceStorageSlot, getCanonicalFeeJuice } from '@aztec/protocol-contracts/fee-juice';
import type { ContractArtifact } from '@aztec/stdlib/abi';
import { PublicDataWrite } from '@aztec/stdlib/avm';
import { AztecAddress } from '@aztec/stdlib/aztec-address';
Expand All @@ -11,6 +11,7 @@ import { computePublicDataTreeLeafSlot, siloNullifier } from '@aztec/stdlib/hash
import type { MerkleTreeWriteOperations } from '@aztec/stdlib/interfaces/server';
import { MerkleTreeId } from '@aztec/stdlib/trees';

import { createContractClassAndInstance } from './index.js';
import type { SimpleContractDataSource } from './simple_contract_data_source.js';

/**
Expand Down Expand Up @@ -58,25 +59,31 @@ export abstract class BaseAvmSimulationTester {
seed = 0,
originalContractClassId?: Fr, // if previously upgraded
): Promise<ContractInstanceWithAddress> {
const contractInstance = await this.contractDataSource.registerAndDeployContract(
const { contractClass, contractInstance } = await createContractClassAndInstance(
constructorArgs,
deployer,
contractArtifact,
seed,
originalContractClassId,
);

await this.contractDataSource.addNewContract(contractArtifact, contractClass, contractInstance);

if (!skipNullifierInsertion) {
await this.insertContractAddressNullifier(contractInstance.address);
}
return contractInstance;
}

async registerFeeJuiceContract(): Promise<ContractInstanceWithAddress> {
return await this.contractDataSource.registerFeeJuiceContract();
}

getFirstContractInstance(): ContractInstanceWithAddress {
return this.contractDataSource.getFirstContractInstance();
const feeJuice = await getCanonicalFeeJuice();
const feeJuiceContractClassPublic = {
...feeJuice.contractClass,
privateFunctions: [],
unconstrainedFunctions: [],
};
await this.contractDataSource.addNewContract(feeJuice.artifact, feeJuiceContractClassPublic, feeJuice.instance);
return feeJuice.instance;
}

addContractClass(contractClass: ContractClassPublic, contractArtifact: ContractArtifact): Promise<void> {
Expand Down
62 changes: 61 additions & 1 deletion yarn-project/simulator/src/public/avm/fixtures/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import { MAX_L2_GAS_PER_TX_PUBLIC_PORTION } from '@aztec/constants';
import {
DEPLOYER_CONTRACT_ADDRESS,
MAX_L2_GAS_PER_TX_PUBLIC_PORTION,
PUBLIC_DISPATCH_SELECTOR,
} from '@aztec/constants';
import { EthAddress } from '@aztec/foundation/eth-address';
import { Fr } from '@aztec/foundation/fields';
import { AvmGadgetsTestContractArtifact } from '@aztec/noir-contracts.js/AvmGadgetsTest';
import { AvmTestContractArtifact } from '@aztec/noir-contracts.js/AvmTest';
import { type ContractArtifact, type FunctionArtifact, FunctionSelector } from '@aztec/stdlib/abi';
import { AztecAddress } from '@aztec/stdlib/aztec-address';
import {
type ContractClassPublic,
type ContractInstanceWithAddress,
computeInitializationHash,
} from '@aztec/stdlib/contract';
import { isNoirCallStackUnresolved } from '@aztec/stdlib/errors';
import { GasFees } from '@aztec/stdlib/gas';
import { siloNullifier } from '@aztec/stdlib/hash';
import type { MerkleTreeWriteOperations } from '@aztec/stdlib/interfaces/server';
import { makeContractClassPublic, makeContractInstanceFromClassId } from '@aztec/stdlib/testing';
import { GlobalVariables } from '@aztec/stdlib/tx';

import { strict as assert } from 'assert';
Expand Down Expand Up @@ -234,3 +245,52 @@ export function resolveAvmGadgetsTestContractAssertionMessage(

return resolveAssertionMessageFromRevertData(output, functionArtifact);
}

/**
* Create a contract class and instance given constructor args, artifact, etc.
* NOTE: This is useful for testing real-ish contract class registration and instance deployment TXs (via logs)
* @param constructorArgs - The constructor arguments for the contract.
* @param deployer - The deployer of the contract.
* @param contractArtifact - The contract artifact for the contract.
* @param seed - The seed for the contract.
* @param originalContractClassId - The original contract class ID (if upgraded)
* @returns The contract class, instance, and contract address nullifier.
*/
export async function createContractClassAndInstance(
constructorArgs: any[],
deployer: AztecAddress,
contractArtifact: ContractArtifact,
seed = 0,
originalContractClassId?: Fr, // if previously upgraded
): Promise<{
contractClass: ContractClassPublic;
contractInstance: ContractInstanceWithAddress;
contractAddressNullifier: Fr;
}> {
const bytecode = getContractFunctionArtifact(PUBLIC_DISPATCH_FN_NAME, contractArtifact)!.bytecode;
const contractClass = await makeContractClassPublic(
seed,
/*publicDispatchFunction=*/ { bytecode, selector: new FunctionSelector(PUBLIC_DISPATCH_SELECTOR) },
);

const constructorAbi = getContractFunctionArtifact('constructor', contractArtifact);
const initializationHash = await computeInitializationHash(constructorAbi, constructorArgs);
const contractInstance =
originalContractClassId === undefined
? await makeContractInstanceFromClassId(contractClass.id, seed, {
deployer,
initializationHash,
})
: await makeContractInstanceFromClassId(originalContractClassId, seed, {
deployer,
initializationHash,
currentClassId: contractClass.id,
});

const contractAddressNullifier = await siloNullifier(
AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS),
contractInstance.address.toField(),
);

return { contractClass, contractInstance, contractAddressNullifier };
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import { PUBLIC_DISPATCH_SELECTOR } from '@aztec/constants';
import type { Fr } from '@aztec/foundation/fields';
import { createLogger } from '@aztec/foundation/log';
import { getCanonicalFeeJuice } from '@aztec/protocol-contracts/fee-juice';
import { type ContractArtifact, FunctionSelector } from '@aztec/stdlib/abi';
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
import {
type ContractClassPublic,
type ContractDataSource,
type ContractInstanceWithAddress,
type PublicFunction,
computeInitializationHash,
computePublicBytecodeCommitment,
import type {
ContractClassPublic,
ContractDataSource,
ContractInstanceWithAddress,
PublicFunction,
} from '@aztec/stdlib/contract';
import { makeContractClassPublic, makeContractInstanceFromClassId } from '@aztec/stdlib/testing';

import { PUBLIC_DISPATCH_FN_NAME, getContractFunctionArtifact } from './index.js';
import { PUBLIC_DISPATCH_FN_NAME } from './index.js';

/**
* This class is used during public/avm testing to function as a database of
Expand All @@ -39,64 +34,22 @@ export class SimpleContractDataSource implements ContractDataSource {
* Derive the contract class and instance with some seed.
* Add both to the contract data source along with the contract artifact.
*/
async registerAndDeployContract(
constructorArgs: any[],
deployer: AztecAddress,
async addNewContract(
contractArtifact: ContractArtifact,
seed = 0,
originalContractClassId?: Fr, // if previously upgraded
): Promise<ContractInstanceWithAddress> {
const bytecode = getContractFunctionArtifact(PUBLIC_DISPATCH_FN_NAME, contractArtifact)!.bytecode;
const contractClass = await makeContractClassPublic(
seed,
/*publicDispatchFunction=*/ { bytecode, selector: new FunctionSelector(PUBLIC_DISPATCH_SELECTOR) },
);

const constructorAbi = getContractFunctionArtifact('constructor', contractArtifact);
const initializationHash = await computeInitializationHash(constructorAbi, constructorArgs);
this.logger.trace(`Initialization hash for contract class ${contractClass.id}: ${initializationHash.toString()}`);
const contractInstance =
originalContractClassId === undefined
? await makeContractInstanceFromClassId(contractClass.id, seed, {
deployer,
initializationHash,
})
: await makeContractInstanceFromClassId(originalContractClassId, seed, {
deployer,
initializationHash,
currentClassId: contractClass.id,
});

contractClass: ContractClassPublic,
contractInstance: ContractInstanceWithAddress,
) {
this.addContractArtifact(contractClass.id, contractArtifact);
await this.addContractClass(contractClass);
await this.addContractInstance(contractInstance);
return contractInstance;
}

async registerFeeJuiceContract(): Promise<ContractInstanceWithAddress> {
const feeJuice = await getCanonicalFeeJuice();
const feeJuiceContractClassPublic = {
...feeJuice.contractClass,
privateFunctions: [],
unconstrainedFunctions: [],
};

this.addContractArtifact(feeJuiceContractClassPublic.id, feeJuice.artifact);
await this.addContractClass(feeJuiceContractClassPublic);
await this.addContractInstance(feeJuice.instance);
return feeJuice.instance;
}

getFirstContractInstance(): ContractInstanceWithAddress {
return this.contractInstances.values().next().value;
}

addContractArtifact(classId: Fr, artifact: ContractArtifact): void {
this.contractArtifacts.set(classId.toString(), artifact);
}

/////////////////////////////////////////////////////////////
// ContractDataSource function impelementations
// ContractDataSource function implementations
getPublicFunction(_address: AztecAddress, _selector: FunctionSelector): Promise<PublicFunction> {
throw new Error('Method not implemented.');
}
Expand All @@ -109,9 +62,8 @@ export class SimpleContractDataSource implements ContractDataSource {
return Promise.resolve(this.contractClasses.get(id.toString()));
}

async getBytecodeCommitment(id: Fr): Promise<Fr | undefined> {
const contractClass = await this.getContractClass(id);
return Promise.resolve(computePublicBytecodeCommitment(contractClass!.packedBytecode));
getBytecodeCommitment(_id: Fr): Promise<Fr | undefined> {
return Promise.resolve(undefined);
}

getContract(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
Expand Down
6 changes: 2 additions & 4 deletions yarn-project/simulator/src/public/avm/journal/journal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,17 +193,15 @@ export class AvmPersistableStateManager {
`Value mismatch when performing public data write (got value: ${value}, value in tree: ${newLeafPreimage.value})`,
);
} else {
this.log.debug(`insertion witness data length: ${result.insertionWitnessData.length}`);
// The new leaf preimage should have the new value and slot
newLeafPreimage.slot = leafSlot;
newLeafPreimage.value = value;
// TODO: is this necessary?! Why doesn't sequentialInsert return the newLeafPreimage via
// result.insertionWitnessData[0].leafPreimage?

this.log.debug(
`newLeafPreimage.slot: ${newLeafPreimage.slot}, newLeafPreimage.value: ${newLeafPreimage.value}`,
this.log.trace(
`newLeafPreimage.slot: ${newLeafPreimage.slot}, newLeafPreimage.value: ${newLeafPreimage.value}, insertionIndex: ${result.insertionWitnessData[0].index}`,
);
this.log.debug(`insertion index: ${result.insertionWitnessData[0].index}`);
insertionPath = result.insertionWitnessData[0].siblingPath.toFields();
}

Expand Down
Loading
Loading