forked from visoftsolutions/noir_rs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(avm): refactor AVM Simulator and fix issues (AztecProtocol#4424)
- Loading branch information
Showing
39 changed files
with
1,008 additions
and
1,075 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,18 @@ | ||
describe('Avm', () => { | ||
it('Executes a simple call', () => {}); | ||
// import { AztecAddress, Fr } from '@aztec/circuits.js'; | ||
// import { initContext } from './fixtures/index.js'; | ||
|
||
describe('Avm Context', () => { | ||
it('New call should fork context correctly', () => { | ||
// const context = initContext(); | ||
// const newAddress = AztecAddress.random(); | ||
// const newCalldata = [new Fr(1), new Fr(2)]; | ||
// const newContext = context.createNestedContractCallContext(newAddress, newCalldata); | ||
}); | ||
|
||
it('New static call should fork context correctly', () => { | ||
// const context = initContext(); | ||
// const newAddress = AztecAddress.random(); | ||
// const newCalldata = [new Fr(1), new Fr(2)]; | ||
// const newContext = context.createNestedContractStaticCallContext(newAddress, newCalldata); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,140 +1,63 @@ | ||
import { AztecAddress, FunctionSelector } from '@aztec/circuits.js'; | ||
import { AztecAddress } from '@aztec/circuits.js'; | ||
import { Fr } from '@aztec/foundation/fields'; | ||
|
||
import { AvmExecutionEnvironment } from './avm_execution_environment.js'; | ||
import { AvmMachineState } from './avm_machine_state.js'; | ||
import { AvmMessageCallResult } from './avm_message_call_result.js'; | ||
import { AvmInterpreterError, executeAvm } from './interpreter/index.js'; | ||
import { AvmJournal } from './journal/journal.js'; | ||
import { Instruction } from './opcodes/instruction.js'; | ||
import { decodeFromBytecode } from './serialization/bytecode_serialization.js'; | ||
|
||
// FIXME: dependency cycle. | ||
import { AvmWorldStateJournal } from './journal/journal.js'; | ||
|
||
/** | ||
* Avm Executor manages the execution of the AVM | ||
* | ||
* It stores a state manager | ||
* An execution context includes the information necessary to initiate AVM | ||
* execution along with all state maintained by the AVM throughout execution. | ||
*/ | ||
export class AvmContext { | ||
/** Contains constant variables provided by the kernel */ | ||
private executionEnvironment: AvmExecutionEnvironment; | ||
/** Manages mutable state during execution - (caching, fetching) */ | ||
private journal: AvmJournal; | ||
|
||
constructor(executionEnvironment: AvmExecutionEnvironment, journal: AvmJournal) { | ||
this.executionEnvironment = executionEnvironment; | ||
this.journal = journal; | ||
} | ||
|
||
/** | ||
* Call a contract with the given calldata | ||
* | ||
* - We get the contract from storage | ||
* - We interpret the bytecode | ||
* - We run the interpreter | ||
* | ||
*/ | ||
async call(): Promise<AvmMessageCallResult> { | ||
// NOTE: the following is mocked as getPublicBytecode does not exist yet | ||
const selector = new FunctionSelector(0); | ||
const bytecode = await this.journal.hostStorage.contractsDb.getBytecode( | ||
this.executionEnvironment.address, | ||
selector, | ||
); | ||
|
||
// This assumes that we will not be able to send messages to accounts without code | ||
// Pending classes and instances impl details | ||
if (!bytecode) { | ||
throw new NoBytecodeFoundInterpreterError(this.executionEnvironment.address); | ||
} | ||
|
||
const instructions: Instruction[] = decodeFromBytecode(bytecode); | ||
|
||
const machineState = new AvmMachineState(this.executionEnvironment); | ||
return executeAvm(machineState, this.journal, instructions); | ||
} | ||
|
||
/** | ||
* Create a new forked avm context - for internal calls | ||
*/ | ||
public newWithForkedState(): AvmContext { | ||
const forkedState = AvmJournal.branchParent(this.journal); | ||
return new AvmContext(this.executionEnvironment, forkedState); | ||
} | ||
|
||
/** | ||
* Create a new forked avm context - for external calls | ||
* Create a new AVM context | ||
* @param worldState - Manages mutable state during execution - (caching, fetching) | ||
* @param environment - Contains constant variables provided by the kernel | ||
* @param machineState - VM state that is modified on an instruction-by-instruction basis | ||
* @returns new AvmContext instance | ||
*/ | ||
public static newWithForkedState(executionEnvironment: AvmExecutionEnvironment, journal: AvmJournal): AvmContext { | ||
const forkedState = AvmJournal.branchParent(journal); | ||
return new AvmContext(executionEnvironment, forkedState); | ||
} | ||
constructor( | ||
public worldState: AvmWorldStateJournal, | ||
public environment: AvmExecutionEnvironment, | ||
public machineState: AvmMachineState, | ||
) {} | ||
|
||
/** | ||
* Prepare a new AVM context that will be ready for an external call | ||
* - It will fork the journal | ||
* - It will set the correct execution Environment Variables for a call | ||
* - Alter both address and storageAddress | ||
* Prepare a new AVM context that will be ready for an external/nested call | ||
* - Fork the world state journal | ||
* - Derive a machine state from the current state | ||
* - E.g., gas metering is preserved but pc is reset | ||
* - Derive an execution environment from the caller/parent | ||
* - Alter both address and storageAddress | ||
* | ||
* @param address - The contract to call | ||
* @param executionEnvironment - The current execution environment | ||
* @param journal - The current journal | ||
* @param address - The contract instance to initialize a context for | ||
* @param calldata - Data/arguments for nested call | ||
* @returns new AvmContext instance | ||
*/ | ||
public static prepExternalCallContext( | ||
address: AztecAddress, | ||
calldata: Fr[], | ||
executionEnvironment: AvmExecutionEnvironment, | ||
journal: AvmJournal, | ||
): AvmContext { | ||
const newExecutionEnvironment = executionEnvironment.newCall(address, calldata); | ||
const forkedState = AvmJournal.branchParent(journal); | ||
return new AvmContext(newExecutionEnvironment, forkedState); | ||
public createNestedContractCallContext(address: AztecAddress, calldata: Fr[]): AvmContext { | ||
const newExecutionEnvironment = this.environment.deriveEnvironmentForNestedCall(address, calldata); | ||
const forkedWorldState = this.worldState.fork(); | ||
const machineState = AvmMachineState.fromState(this.machineState); | ||
return new AvmContext(forkedWorldState, newExecutionEnvironment, machineState); | ||
} | ||
|
||
/** | ||
* Prepare a new AVM context that will be ready for an external static call | ||
* - It will fork the journal | ||
* - It will set the correct execution Environment Variables for a call | ||
* - Alter both address and storageAddress | ||
* Prepare a new AVM context that will be ready for an external/nested static call | ||
* - Fork the world state journal | ||
* - Derive a machine state from the current state | ||
* - E.g., gas metering is preserved but pc is reset | ||
* - Derive an execution environment from the caller/parent | ||
* - Alter both address and storageAddress | ||
* | ||
* @param address - The contract to call | ||
* @param executionEnvironment - The current execution environment | ||
* @param journal - The current journal | ||
* @param address - The contract instance to initialize a context for | ||
* @param calldata - Data/arguments for nested call | ||
* @returns new AvmContext instance | ||
*/ | ||
public static prepExternalStaticCallContext( | ||
address: AztecAddress, | ||
calldata: Fr[], | ||
executionEnvironment: AvmExecutionEnvironment, | ||
journal: AvmJournal, | ||
): AvmContext { | ||
const newExecutionEnvironment = executionEnvironment.newStaticCall(address, calldata); | ||
const forkedState = AvmJournal.branchParent(journal); | ||
return new AvmContext(newExecutionEnvironment, forkedState); | ||
} | ||
|
||
/** | ||
* Merge the journal of this call with it's parent | ||
* NOTE: this should never be called on a root context - only from within a nested call | ||
*/ | ||
public mergeJournalSuccess() { | ||
this.journal.mergeSuccessWithParent(); | ||
} | ||
|
||
/** | ||
* Merge the journal of this call with it's parent | ||
* For when the child call fails ( we still must track state accesses ) | ||
*/ | ||
public mergeJournalFailure() { | ||
this.journal.mergeFailureWithParent(); | ||
} | ||
} | ||
|
||
class NoBytecodeFoundInterpreterError extends AvmInterpreterError { | ||
constructor(contractAddress: AztecAddress) { | ||
super(`No bytecode found at: ${contractAddress}`); | ||
this.name = 'NoBytecodeFoundInterpreterError'; | ||
public createNestedContractStaticCallContext(address: AztecAddress, calldata: Fr[]): AvmContext { | ||
const newExecutionEnvironment = this.environment.deriveEnvironmentForNestedStaticCall(address, calldata); | ||
const forkedWorldState = this.worldState.fork(); | ||
const machineState = AvmMachineState.fromState(this.machineState); | ||
return new AvmContext(forkedWorldState, newExecutionEnvironment, machineState); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
108 changes: 66 additions & 42 deletions
108
yarn-project/acir-simulator/src/avm/avm_machine_state.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,69 +1,93 @@ | ||
import { Fr } from '@aztec/foundation/fields'; | ||
import { Fr } from '@aztec/circuits.js'; | ||
|
||
import { AvmExecutionEnvironment } from './avm_execution_environment.js'; | ||
import { TaggedMemory } from './avm_memory_types.js'; | ||
import { AvmContractCallResults } from './avm_message_call_result.js'; | ||
|
||
/** | ||
* Store's data for an Avm execution frame | ||
* A few fields of machine state are initialized from AVM session inputs or call instruction arguments | ||
*/ | ||
export type InitialAvmMachineState = { | ||
l1GasLeft: number; | ||
l2GasLeft: number; | ||
daGasLeft: number; | ||
}; | ||
|
||
/** | ||
* Avm state modified on an instruction-per-instruction basis. | ||
*/ | ||
export class AvmMachineState { | ||
public l1GasLeft: number; | ||
/** gas remaining of the gas allocated for a contract call */ | ||
public l2GasLeft: number; | ||
public daGasLeft: number; | ||
/** program counter */ | ||
public pc: number = 0; | ||
|
||
/** | ||
* Execution environment contains hard coded information that is received from the kernel | ||
* Items like, the block header and global variables fall within this category | ||
* On INTERNALCALL, internal call stack is pushed to with the current pc + 1 | ||
* On INTERNALRETURN, value is popped from the internal call stack and assigned to the pc. | ||
*/ | ||
public readonly executionEnvironment: AvmExecutionEnvironment; | ||
public internalCallStack: number[] = []; | ||
|
||
private returnData: Fr[]; | ||
|
||
public readonly memory: TaggedMemory; | ||
/** Memory accessible to user code */ | ||
public readonly memory: TaggedMemory = new TaggedMemory(); | ||
|
||
/** | ||
* When an internal_call is invoked, the internal call stack is added to with the current pc + 1 | ||
* When internal_return is invoked, the latest value is popped from the internal call stack and set to the pc. | ||
*/ | ||
public internalCallStack: number[]; | ||
* Signals that execution should end. | ||
* AvmContext execution continues executing instructions until the machine state signals "halted" | ||
* */ | ||
public halted: boolean = false; | ||
/** Signals that execution has reverted normally (this does not cover exceptional halts) */ | ||
private reverted: boolean = false; | ||
/** Output data must NOT be modified once it is set */ | ||
private output: Fr[] = []; | ||
|
||
public pc: number; | ||
constructor(l1GasLeft: number, l2GasLeft: number, daGasLeft: number) { | ||
this.l1GasLeft = l1GasLeft; | ||
this.l2GasLeft = l2GasLeft; | ||
this.daGasLeft = daGasLeft; | ||
} | ||
|
||
public callStack: number[]; | ||
public static fromState(state: InitialAvmMachineState): AvmMachineState { | ||
return new AvmMachineState(state.l1GasLeft, state.l2GasLeft, state.daGasLeft); | ||
} | ||
|
||
/** | ||
* If an instruction triggers a halt, then it ends execution of the VM | ||
* Most instructions just increment PC before they complete | ||
*/ | ||
public halted: boolean; | ||
/** | ||
* Signifies if the execution has reverted ( due to a revert instruction ) | ||
*/ | ||
public reverted: boolean; | ||
public incrementPc() { | ||
this.pc++; | ||
} | ||
|
||
/** | ||
* Create a new avm context | ||
* @param executionEnvironment - Machine context that is passed to the avm | ||
* Halt as successful | ||
* Output data must NOT be modified once it is set | ||
* @param output | ||
*/ | ||
constructor(executionEnvironment: AvmExecutionEnvironment) { | ||
this.returnData = []; | ||
this.memory = new TaggedMemory(); | ||
this.internalCallStack = []; | ||
|
||
this.pc = 0; | ||
this.callStack = []; | ||
|
||
this.halted = false; | ||
this.reverted = false; | ||
|
||
this.executionEnvironment = executionEnvironment; | ||
public return(output: Fr[]) { | ||
this.halted = true; | ||
this.output = output; | ||
} | ||
|
||
/** | ||
* Return data must NOT be modified once it is set | ||
* @param returnData - | ||
* Halt as reverted | ||
* Output data must NOT be modified once it is set | ||
* @param output | ||
*/ | ||
public setReturnData(returnData: Fr[]) { | ||
this.returnData = returnData; | ||
Object.freeze(returnData); | ||
public revert(output: Fr[]) { | ||
this.halted = true; | ||
this.reverted = true; | ||
this.output = output; | ||
} | ||
|
||
public getReturnData(): Fr[] { | ||
return this.returnData; | ||
/** | ||
* Get a summary of execution results for a halted machine state | ||
* @returns summary of execution results | ||
*/ | ||
public getResults(): AvmContractCallResults { | ||
if (!this.halted) { | ||
throw new Error('Execution results are not ready! Execution is ongoing.'); | ||
} | ||
return new AvmContractCallResults(this.reverted, this.output); | ||
} | ||
} |
Oops, something went wrong.