Skip to content

Commit

Permalink
feat(avm): introduce small e2e test (AztecProtocol#4470)
Browse files Browse the repository at this point in the history
  • Loading branch information
Maddiaa0 committed Feb 9, 2024
1 parent 47d66f9 commit 7b4c6e7
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 7 deletions.
13 changes: 13 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,17 @@ jobs:
name: "Test"
command: cond_spot_run_compose end-to-end 4 ./scripts/docker-compose.yml TEST=e2e_card_game.test.ts

e2e-avm-simulator:
docker:
- image: aztecprotocol/alpine-build-image
resource_class: small
steps:
- *checkout
- *setup_env
- run:
name: "Test"
command: AVM_ENABLED=1 cond_spot_run_compose end-to-end 4 ./scripts/docker-compose.yml TEST=e2e_avm_simulator.test.ts

pxe:
docker:
- image: aztecprotocol/alpine-build-image
Expand Down Expand Up @@ -1275,6 +1286,7 @@ workflows:
- e2e-persistence: *e2e_test
- e2e-browser: *e2e_test
- e2e-card-game: *e2e_test
- e2e-avm-simulator: *e2e_test
- pxe: *e2e_test
- cli-docs-sandbox: *e2e_test
- guides-writing-an-account-contract: *e2e_test
Expand Down Expand Up @@ -1313,6 +1325,7 @@ workflows:
- e2e-persistence
- e2e-browser
- e2e-card-game
- e2e-avm-simulator
- pxe
- boxes-blank
- boxes-blank-react
Expand Down
3 changes: 3 additions & 0 deletions build-system/scripts/remote_run_script
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,8 @@ ssh -A -F $SSH_CONFIG_PATH $IP "
export ECR_DEPLOY_URL=$ECR_DEPLOY_URL
export ECR_URL=$ECR_URL
export BUILD_SYSTEM_DEBUG=${BUILD_SYSTEM_DEBUG:-}
# temp while we transitioning to avm
export AVM_ENABLED=${AVM_ENABLED:-}
./remote_runner $@
"
1 change: 1 addition & 0 deletions yarn-project/end-to-end/scripts/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ services:
WS_BLOCK_CHECK_INTERVAL_MS: 50
PXE_BLOCK_POLLING_INTERVAL_MS: 50
ARCHIVER_VIEM_POLLING_INTERVAL_MS: 500
AVM_ENABLED: ${AVM_ENABLED:-}
ports:
- '8080:8080'

Expand Down
35 changes: 35 additions & 0 deletions yarn-project/end-to-end/src/e2e_avm_simulator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { DebugLogger, Fr, Wallet } from '@aztec/aztec.js';
import { AvmTestContract } from '@aztec/noir-contracts';

import { setup } from './fixtures/utils.js';

process.env.AVM_ENABLED = 'absofrigginlutely';

describe('e2e_nested_contract', () => {
let wallet: Wallet;
let logger: DebugLogger;
let teardown: () => Promise<void>;

beforeEach(async () => {
({ teardown, wallet, logger } = await setup());
}, 100_000);

afterEach(() => teardown());

describe('Call succeeds through AVM', () => {
let avmContact: AvmTestContract;

beforeEach(async () => {
avmContact = await AvmTestContract.deploy(wallet).send().deployed();
}, 50_000);

it('Calls an avm contract', async () => {
const a = new Fr(1);
const b = new Fr(2);

logger('Calling avm_addArgsReturn...');
await avmContact.methods.avm_addArgsReturn(a, b).send().wait();
logger('Success');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ import {
} from '@aztec/simulator';
import { MerkleTreeOperations } from '@aztec/world-state';

import { env } from 'process';

import { getVerificationKeys } from '../mocks/verification_keys.js';
import { PublicProver } from '../prover/index.js';
import { PublicKernelCircuitSimulator } from '../simulator/index.js';
Expand Down Expand Up @@ -157,7 +159,15 @@ export abstract class AbstractPhaseManager {
while (executionStack.length) {
const current = executionStack.pop()!;
const isExecutionRequest = !isPublicExecutionResult(current);
const result = isExecutionRequest ? await this.publicExecutor.simulate(current, this.globalVariables) : current;

// NOTE: temporary glue to incorporate avm execution calls
const simulator = (execution: PublicExecution, globalVariables: GlobalVariables) =>
env.AVM_ENABLED
? this.publicExecutor.simulateAvm(execution, globalVariables)
: this.publicExecutor.simulate(execution, globalVariables);

const result = isExecutionRequest ? await simulator(current, this.globalVariables) : current;

newUnencryptedFunctionLogs.push(result.unencryptedLogs);
const functionSelector = result.execution.functionData.selector.toString();
this.log(
Expand Down
7 changes: 6 additions & 1 deletion yarn-project/simulator/src/avm/avm_execution_environment.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GlobalVariables } from '@aztec/circuits.js';
import { FunctionSelector, GlobalVariables } from '@aztec/circuits.js';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { EthAddress } from '@aztec/foundation/eth-address';
import { Fr } from '@aztec/foundation/fields';
Expand Down Expand Up @@ -35,6 +35,8 @@ export class AvmExecutionEnvironment {
public readonly isDelegateCall: boolean,

public readonly calldata: Fr[],

public readonly temporaryFunctionSelector: FunctionSelector,
) {}

public deriveEnvironmentForNestedCall(address: AztecAddress, calldata: Fr[]): AvmExecutionEnvironment {
Expand All @@ -52,6 +54,7 @@ export class AvmExecutionEnvironment {
this.isStaticCall,
this.isDelegateCall,
/*calldata=*/ calldata,
this.temporaryFunctionSelector,
);
}

Expand All @@ -70,6 +73,7 @@ export class AvmExecutionEnvironment {
/*isStaticCall=*/ true,
this.isDelegateCall,
/*calldata=*/ calldata,
this.temporaryFunctionSelector,
);
}

Expand All @@ -88,6 +92,7 @@ export class AvmExecutionEnvironment {
this.isStaticCall,
/*isDelegateCall=*/ true,
/*calldata=*/ calldata,
this.temporaryFunctionSelector,
);
}
}
4 changes: 2 additions & 2 deletions yarn-project/simulator/src/avm/avm_simulator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { FunctionSelector } from '@aztec/circuits.js';
import { DebugLogger, createDebugLogger } from '@aztec/foundation/log';

import { strict as assert } from 'assert';
Expand Down Expand Up @@ -72,7 +71,8 @@ export class AvmSimulator {
*/
private async fetchAndDecodeBytecode(): Promise<Instruction[]> {
// NOTE: the following is mocked as getPublicBytecode does not exist yet
const selector = new FunctionSelector(0);

const selector = this.context.environment.temporaryFunctionSelector;
const bytecode = await this.context.worldState.hostStorage.contractsDb.getBytecode(
this.context.environment.address,
selector,
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/simulator/src/avm/fixtures/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { GlobalVariables } from '@aztec/circuits.js';
import { FunctionSelector } from '@aztec/foundation/abi';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { EthAddress } from '@aztec/foundation/eth-address';
import { Fr } from '@aztec/foundation/fields';
Expand Down Expand Up @@ -52,6 +53,7 @@ export function initExecutionEnvironment(overrides?: Partial<AvmExecutionEnviron
overrides?.isStaticCall ?? false,
overrides?.isDelegateCall ?? false,
overrides?.calldata ?? [],
overrides?.temporaryFunctionSelector ?? FunctionSelector.empty(),
);
}

Expand Down
2 changes: 1 addition & 1 deletion yarn-project/simulator/src/avm/journal/host_storage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CommitmentsDB, PublicContractsDB, PublicStateDB } from '../../index.js';
import { CommitmentsDB, PublicContractsDB, PublicStateDB } from '../../public/db.js';

/**
* Host storage
Expand Down
110 changes: 110 additions & 0 deletions yarn-project/simulator/src/avm/temporary_executor_migration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// All code in this file needs to die once the public executor is phased out.
import { FunctionL2Logs } from '@aztec/circuit-types';
import {
ContractStorageRead,
ContractStorageUpdateRequest,
GlobalVariables,
SideEffect,
SideEffectLinkedToNoteHash,
} from '@aztec/circuits.js';
import { Fr } from '@aztec/foundation/fields';

import { PublicExecution, PublicExecutionResult } from '../public/execution.js';
import { AvmExecutionEnvironment } from './avm_execution_environment.js';
import { AvmContractCallResults } from './avm_message_call_result.js';
import { JournalData } from './journal/journal.js';

/** Temporary Method
*
* Convert a PublicExecution(Environment) object to an AvmExecutionEnvironment
*
* @param current
* @param globalVariables
* @returns
*/
export function temporaryCreateAvmExecutionEnvironment(
current: PublicExecution,
globalVariables: GlobalVariables,
): AvmExecutionEnvironment {
// Function selector is included temporarily until noir codegens public contract bytecode in a single blob
return new AvmExecutionEnvironment(
current.contractAddress,
current.callContext.storageContractAddress,
current.callContext.msgSender, // TODO: origin is not available
current.callContext.msgSender,
current.callContext.portalContractAddress,
/*feePerL1Gas=*/ Fr.zero(),
/*feePerL2Gas=*/ Fr.zero(),
/*feePerDaGas=*/ Fr.zero(),
/*contractCallDepth=*/ Fr.zero(),
globalVariables,
current.callContext.isStaticCall,
current.callContext.isDelegateCall,
current.args,
current.functionData.selector,
);
}

/** Temporary Method
*
* Convert the result of an AVM contract call to a PublicExecutionResult for the public kernel
*
* @param execution
* @param newWorldState
* @param result
* @returns
*/
export function temporaryConvertAvmResults(
execution: PublicExecution,
newWorldState: JournalData,
result: AvmContractCallResults,
): PublicExecutionResult {
const newCommitments = newWorldState.newNoteHashes.map(noteHash => new SideEffect(noteHash, Fr.zero()));

const contractStorageReads: ContractStorageRead[] = [];
const reduceStorageReadRequests = (contractAddress: bigint, storageReads: Map<bigint, Fr[]>) => {
return storageReads.forEach((innerArray, key) => {
innerArray.forEach(value => {
contractStorageReads.push(new ContractStorageRead(new Fr(key), new Fr(value), 0));
});
});
};
newWorldState.storageReads.forEach((storageMap: Map<bigint, Fr[]>, address: bigint) =>
reduceStorageReadRequests(address, storageMap),
);

const contractStorageUpdateRequests: ContractStorageUpdateRequest[] = [];
const reduceStorageUpdateRequests = (contractAddress: bigint, storageUpdateRequests: Map<bigint, Fr[]>) => {
return storageUpdateRequests.forEach((innerArray, key) => {
innerArray.forEach(value => {
contractStorageUpdateRequests.push(
new ContractStorageUpdateRequest(new Fr(key), /*TODO: old value not supported */ Fr.zero(), new Fr(value), 0),
);
});
});
};
newWorldState.storageWrites.forEach((storageMap: Map<bigint, Fr[]>, address: bigint) =>
reduceStorageUpdateRequests(address, storageMap),
);

const returnValues = result.output;

// TODO(follow up in pr tree): NOT SUPPORTED YET, make sure hashing and log resolution is done correctly
// Disabled.
const nestedExecutions: PublicExecutionResult[] = [];
const newNullifiers: SideEffectLinkedToNoteHash[] = [];
const unencryptedLogs = FunctionL2Logs.empty();
const newL2ToL1Messages = newWorldState.newL1Messages.map(() => Fr.zero());

return {
execution,
newCommitments,
newL2ToL1Messages,
newNullifiers,
contractStorageReads,
contractStorageUpdateRequests,
returnValues,
nestedExecutions,
unencryptedLogs,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { createDebugLogger } from '@aztec/foundation/log';
import { extractReturnWitness } from '../acvm/deserialize.js';
import { Oracle, acvm, extractCallStack, toACVMWitness } from '../acvm/index.js';
import { ExecutionError } from '../common/errors.js';
import { AcirSimulator } from '../index.js';
import { AcirSimulator } from './simulator.js';
import { ViewDataOracle } from './view_data_oracle.js';

// docs:start:execute_unconstrained_function
Expand Down
34 changes: 34 additions & 0 deletions yarn-project/simulator/src/public/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ import { GlobalVariables, Header, PublicCircuitPublicInputs } from '@aztec/circu
import { createDebugLogger } from '@aztec/foundation/log';

import { Oracle, acvm, extractCallStack, extractReturnWitness } from '../acvm/index.js';
import { AvmContext } from '../avm/avm_context.js';
import { AvmMachineState } from '../avm/avm_machine_state.js';
import { AvmSimulator } from '../avm/avm_simulator.js';
import { HostStorage } from '../avm/journal/host_storage.js';
import { AvmWorldStateJournal } from '../avm/journal/index.js';
import {
temporaryConvertAvmResults,
temporaryCreateAvmExecutionEnvironment,
} from '../avm/temporary_executor_migration.js';
import { ExecutionError, createSimulationError } from '../common/errors.js';
import { SideEffectCounter } from '../common/index.js';
import { PackedArgsCache } from '../common/packed_args_cache.js';
Expand Down Expand Up @@ -121,4 +130,29 @@ export class PublicExecutor {
throw createSimulationError(err instanceof Error ? err : new Error('Unknown error during public execution'));
}
}

/**
* Executes a public execution request in the avm.
* @param execution - The execution to run.
* @param globalVariables - The global variables to use.
* @returns The result of the run plus all nested runs.
*/
public async simulateAvm(
execution: PublicExecution,
globalVariables: GlobalVariables,
): Promise<PublicExecutionResult> {
// Temporary code to construct the AVM context
// These data structures will permiate across the simulator when the public executor is phased out
const hostStorage = new HostStorage(this.stateDb, this.contractsDb, this.commitmentsDb);
const worldStateJournal = new AvmWorldStateJournal(hostStorage);
const executionEnv = temporaryCreateAvmExecutionEnvironment(execution, globalVariables);
const machineState = new AvmMachineState(0, 0, 0);

const context = new AvmContext(worldStateJournal, executionEnv, machineState);
const simulator = new AvmSimulator(context);

const result = await simulator.execute();
const newWorldState = context.worldState.flush();
return temporaryConvertAvmResults(execution, newWorldState, result);
}
}
15 changes: 14 additions & 1 deletion yarn-project/types/src/abi/contract_artifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ function generateFunctionArtifact(fn: NoirCompiledContractFunction): FunctionArt

// If the function is not unconstrained, the first item is inputs or CallContext which we should omit
let parameters = fn.abi.parameters.map(generateFunctionParameter);
if (functionType !== 'unconstrained') {
if (hasKernelFunctionInputs(parameters)) {
parameters = parameters.slice(1);
}

Expand All @@ -125,6 +125,19 @@ function generateFunctionArtifact(fn: NoirCompiledContractFunction): FunctionArt
};
}

/**
* Returns true if the first parameter is kernel function inputs.
*
* Noir macros #[aztec(private|public)] inject the following code
* fn <name>(inputs: <Public|Private>ContextInputs, ...otherparams) {}
*
* Return true if this injected parameter is found
*/
function hasKernelFunctionInputs(params: ABIParameter[]): boolean {
const firstParam = params[0];
return firstParam?.type.kind === 'struct' && firstParam.type.path.includes('ContextInputs');
}

/** Validates contract artifact instance, throwing on error. */
function validateContractArtifact(contract: ContractArtifact) {
const constructorArtifact = contract.functions.find(({ name }) => name === 'constructor');
Expand Down

0 comments on commit 7b4c6e7

Please sign in to comment.