-
Notifications
You must be signed in to change notification settings - Fork 323
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore(avm): refactor AVM Simulator and fix issues #4424
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
42b8c12
chore(avm-simulator): refactor of AVM Simulator to more closely match YP
cceb4dc
redo context constructor inputs
f401019
remove interpreter
e5256ef
world state journal refactor
7d9b10d
formatting fix
f5417da
cleanup
cadf791
cleanup machine state
95a9dd8
comments
67e4fdc
cleanup
42f97e9
comments
0ebf063
cleanup tag checks
a6252a0
prettier
20f2ed3
fixes after refactor
09cba7f
formatting:fix
df26baa
WIP proposed changes
fcarreiro b574633
fix all the things
fcarreiro 8b58499
Merge remote-tracking branch 'origin/master' into fc/db/avm-simulator…
fcarreiro File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nested context needs to take in args for l1/l2/daGas as they aren't derived from the parent context but are actually arguments to the call/staticcall instructions. |
||
} | ||
} |
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.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gas metering actually is not preserved. You allocate gas to a nested context via the call instruction's gas argument.