@aztec/simulator 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/acvm/oracle/oracle.d.ts +2 -2
- package/dest/acvm/oracle/oracle.d.ts.map +1 -1
- package/dest/acvm/oracle/oracle.js +5 -5
- package/dest/acvm/oracle/typed_oracle.d.ts +2 -2
- package/dest/acvm/oracle/typed_oracle.d.ts.map +1 -1
- package/dest/acvm/oracle/typed_oracle.js +3 -3
- package/dest/avm/avm_execution_environment.d.ts +3 -2
- package/dest/avm/avm_execution_environment.d.ts.map +1 -1
- package/dest/avm/avm_execution_environment.js +6 -5
- package/dest/avm/avm_memory_types.d.ts +120 -39
- package/dest/avm/avm_memory_types.d.ts.map +1 -1
- package/dest/avm/avm_memory_types.js +91 -109
- package/dest/avm/avm_simulator.d.ts.map +1 -1
- package/dest/avm/avm_simulator.js +2 -3
- package/dest/avm/errors.d.ts +3 -1
- package/dest/avm/errors.d.ts.map +1 -1
- package/dest/avm/errors.js +9 -3
- package/dest/avm/fixtures/index.d.ts +4 -0
- package/dest/avm/fixtures/index.d.ts.map +1 -1
- package/dest/avm/fixtures/index.js +11 -3
- package/dest/avm/journal/host_storage.d.ts +1 -1
- package/dest/avm/journal/host_storage.d.ts.map +1 -1
- package/dest/avm/opcodes/addressing_mode.d.ts +24 -0
- package/dest/avm/opcodes/addressing_mode.d.ts.map +1 -0
- package/dest/avm/opcodes/addressing_mode.js +62 -0
- package/dest/avm/opcodes/comparators.d.ts.map +1 -1
- package/dest/avm/opcodes/comparators.js +8 -5
- package/dest/avm/opcodes/instruction.d.ts +6 -4
- package/dest/avm/opcodes/instruction.d.ts.map +1 -1
- package/dest/avm/opcodes/instruction.js +4 -3
- package/dest/avm/opcodes/memory.d.ts +9 -2
- package/dest/avm/opcodes/memory.d.ts.map +1 -1
- package/dest/avm/opcodes/memory.js +46 -8
- package/dest/avm/serialization/bytecode_serialization.d.ts.map +1 -1
- package/dest/avm/serialization/bytecode_serialization.js +24 -22
- package/dest/avm/serialization/instruction_serialization.d.ts +17 -15
- package/dest/avm/serialization/instruction_serialization.d.ts.map +1 -1
- package/dest/avm/serialization/instruction_serialization.js +22 -17
- package/dest/avm/temporary_executor_migration.d.ts +25 -0
- package/dest/avm/temporary_executor_migration.d.ts.map +1 -0
- package/dest/avm/temporary_executor_migration.js +71 -0
- package/dest/client/client_execution_context.d.ts +4 -2
- package/dest/client/client_execution_context.d.ts.map +1 -1
- package/dest/client/client_execution_context.js +7 -4
- package/dest/client/execution_result.d.ts +2 -0
- package/dest/client/execution_result.d.ts.map +1 -1
- package/dest/client/execution_result.js +1 -1
- package/dest/client/simulator.d.ts +10 -5
- package/dest/client/simulator.d.ts.map +1 -1
- package/dest/client/simulator.js +19 -11
- package/dest/client/unconstrained_execution.js +2 -2
- package/dest/public/execution.js +2 -2
- package/dest/public/executor.d.ts +7 -0
- package/dest/public/executor.d.ts.map +1 -1
- package/dest/public/executor.js +26 -1
- package/dest/public/public_execution_context.js +2 -2
- package/dest/public/state_actions.d.ts +1 -1
- package/dest/public/state_actions.d.ts.map +1 -1
- package/dest/public/state_actions.js +5 -6
- package/dest/utils.d.ts +5 -20
- package/dest/utils.d.ts.map +1 -1
- package/dest/utils.js +4 -20
- package/package.json +7 -5
- package/src/acvm/acvm.ts +156 -0
- package/src/acvm/acvm_types.ts +11 -0
- package/src/acvm/deserialize.ts +36 -0
- package/src/acvm/index.ts +5 -0
- package/src/acvm/oracle/debug.ts +109 -0
- package/src/acvm/oracle/index.ts +17 -0
- package/src/acvm/oracle/oracle.ts +332 -0
- package/src/acvm/oracle/typed_oracle.ts +217 -0
- package/src/acvm/serialize.ts +75 -0
- package/src/avm/avm_context.ts +63 -0
- package/src/avm/avm_execution_environment.ts +98 -0
- package/src/avm/avm_machine_state.ts +93 -0
- package/src/avm/avm_memory_types.ts +309 -0
- package/src/avm/avm_message_call_result.ts +29 -0
- package/src/avm/avm_simulator.ts +89 -0
- package/src/avm/errors.ts +57 -0
- package/src/avm/fixtures/index.ts +90 -0
- package/src/avm/journal/host_storage.ts +20 -0
- package/src/avm/journal/index.ts +2 -0
- package/src/avm/journal/journal.ts +266 -0
- package/src/avm/opcodes/.eslintrc.cjs +8 -0
- package/src/avm/opcodes/accrued_substate.ts +92 -0
- package/src/avm/opcodes/addressing_mode.ts +66 -0
- package/src/avm/opcodes/arithmetic.ts +79 -0
- package/src/avm/opcodes/bitwise.ts +129 -0
- package/src/avm/opcodes/comparators.ts +72 -0
- package/src/avm/opcodes/control_flow.ts +129 -0
- package/src/avm/opcodes/environment_getters.ts +199 -0
- package/src/avm/opcodes/external_calls.ts +122 -0
- package/src/avm/opcodes/index.ts +10 -0
- package/src/avm/opcodes/instruction.ts +64 -0
- package/src/avm/opcodes/instruction_impl.ts +52 -0
- package/src/avm/opcodes/memory.ts +193 -0
- package/src/avm/opcodes/storage.ts +76 -0
- package/src/avm/serialization/buffer_cursor.ts +109 -0
- package/src/avm/serialization/bytecode_serialization.ts +172 -0
- package/src/avm/serialization/instruction_serialization.ts +167 -0
- package/src/avm/temporary_executor_migration.ts +108 -0
- package/src/client/client_execution_context.ts +472 -0
- package/src/client/db_oracle.ts +184 -0
- package/src/client/execution_note_cache.ts +90 -0
- package/src/client/execution_result.ts +89 -0
- package/src/client/index.ts +3 -0
- package/src/client/pick_notes.ts +125 -0
- package/src/client/private_execution.ts +78 -0
- package/src/client/simulator.ts +316 -0
- package/src/client/unconstrained_execution.ts +49 -0
- package/src/client/view_data_oracle.ts +243 -0
- package/src/common/errors.ts +61 -0
- package/src/common/index.ts +3 -0
- package/src/common/packed_args_cache.ts +55 -0
- package/src/common/side_effect_counter.ts +12 -0
- package/src/index.ts +3 -0
- package/src/public/db.ts +85 -0
- package/src/public/execution.ts +137 -0
- package/src/public/executor.ts +158 -0
- package/src/public/index.ts +9 -0
- package/src/public/public_execution_context.ts +217 -0
- package/src/public/state_actions.ts +100 -0
- package/src/test/utils.ts +38 -0
- package/src/utils.ts +18 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { FunctionSelector, GlobalVariables } from '@aztec/circuits.js';
|
|
2
|
+
import { AztecAddress } from '@aztec/foundation/aztec-address';
|
|
3
|
+
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
4
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Contains variables that remain constant during AVM execution
|
|
8
|
+
* These variables are provided by the public kernel circuit
|
|
9
|
+
*/
|
|
10
|
+
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/3992): gas not implemented
|
|
11
|
+
export class AvmExecutionEnvironment {
|
|
12
|
+
constructor(
|
|
13
|
+
public readonly address: AztecAddress,
|
|
14
|
+
|
|
15
|
+
public readonly storageAddress: AztecAddress,
|
|
16
|
+
|
|
17
|
+
public readonly origin: AztecAddress,
|
|
18
|
+
|
|
19
|
+
public readonly sender: AztecAddress,
|
|
20
|
+
|
|
21
|
+
public readonly portal: EthAddress,
|
|
22
|
+
|
|
23
|
+
public readonly feePerL1Gas: Fr,
|
|
24
|
+
|
|
25
|
+
public readonly feePerL2Gas: Fr,
|
|
26
|
+
|
|
27
|
+
public readonly feePerDaGas: Fr,
|
|
28
|
+
|
|
29
|
+
public readonly contractCallDepth: Fr,
|
|
30
|
+
|
|
31
|
+
public readonly globals: GlobalVariables,
|
|
32
|
+
|
|
33
|
+
public readonly isStaticCall: boolean,
|
|
34
|
+
|
|
35
|
+
public readonly isDelegateCall: boolean,
|
|
36
|
+
|
|
37
|
+
public readonly calldata: Fr[],
|
|
38
|
+
|
|
39
|
+
public readonly temporaryFunctionSelector: FunctionSelector,
|
|
40
|
+
) {}
|
|
41
|
+
|
|
42
|
+
public deriveEnvironmentForNestedCall(address: AztecAddress, calldata: Fr[]): AvmExecutionEnvironment {
|
|
43
|
+
return new AvmExecutionEnvironment(
|
|
44
|
+
/*address=*/ address,
|
|
45
|
+
/*storageAddress=*/ address,
|
|
46
|
+
this.origin,
|
|
47
|
+
this.sender,
|
|
48
|
+
this.portal,
|
|
49
|
+
this.feePerL1Gas,
|
|
50
|
+
this.feePerL2Gas,
|
|
51
|
+
this.feePerDaGas,
|
|
52
|
+
this.contractCallDepth,
|
|
53
|
+
this.globals,
|
|
54
|
+
this.isStaticCall,
|
|
55
|
+
this.isDelegateCall,
|
|
56
|
+
/*calldata=*/ calldata,
|
|
57
|
+
this.temporaryFunctionSelector,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public deriveEnvironmentForNestedStaticCall(address: AztecAddress, calldata: Fr[]): AvmExecutionEnvironment {
|
|
62
|
+
return new AvmExecutionEnvironment(
|
|
63
|
+
/*address=*/ address,
|
|
64
|
+
/*storageAddress=*/ address,
|
|
65
|
+
this.origin,
|
|
66
|
+
this.sender,
|
|
67
|
+
this.portal,
|
|
68
|
+
this.feePerL1Gas,
|
|
69
|
+
this.feePerL2Gas,
|
|
70
|
+
this.feePerDaGas,
|
|
71
|
+
this.contractCallDepth,
|
|
72
|
+
this.globals,
|
|
73
|
+
/*isStaticCall=*/ true,
|
|
74
|
+
this.isDelegateCall,
|
|
75
|
+
/*calldata=*/ calldata,
|
|
76
|
+
this.temporaryFunctionSelector,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public newDelegateCall(address: AztecAddress, calldata: Fr[]): AvmExecutionEnvironment {
|
|
81
|
+
return new AvmExecutionEnvironment(
|
|
82
|
+
/*address=*/ address,
|
|
83
|
+
this.storageAddress,
|
|
84
|
+
this.origin,
|
|
85
|
+
this.sender,
|
|
86
|
+
this.portal,
|
|
87
|
+
this.feePerL1Gas,
|
|
88
|
+
this.feePerL2Gas,
|
|
89
|
+
this.feePerDaGas,
|
|
90
|
+
this.contractCallDepth,
|
|
91
|
+
this.globals,
|
|
92
|
+
this.isStaticCall,
|
|
93
|
+
/*isDelegateCall=*/ true,
|
|
94
|
+
/*calldata=*/ calldata,
|
|
95
|
+
this.temporaryFunctionSelector,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Fr } from '@aztec/circuits.js';
|
|
2
|
+
|
|
3
|
+
import { TaggedMemory } from './avm_memory_types.js';
|
|
4
|
+
import { AvmContractCallResults } from './avm_message_call_result.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A few fields of machine state are initialized from AVM session inputs or call instruction arguments
|
|
8
|
+
*/
|
|
9
|
+
export type InitialAvmMachineState = {
|
|
10
|
+
l1GasLeft: number;
|
|
11
|
+
l2GasLeft: number;
|
|
12
|
+
daGasLeft: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Avm state modified on an instruction-per-instruction basis.
|
|
17
|
+
*/
|
|
18
|
+
export class AvmMachineState {
|
|
19
|
+
public l1GasLeft: number;
|
|
20
|
+
/** gas remaining of the gas allocated for a contract call */
|
|
21
|
+
public l2GasLeft: number;
|
|
22
|
+
public daGasLeft: number;
|
|
23
|
+
/** program counter */
|
|
24
|
+
public pc: number = 0;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* On INTERNALCALL, internal call stack is pushed to with the current pc + 1
|
|
28
|
+
* On INTERNALRETURN, value is popped from the internal call stack and assigned to the pc.
|
|
29
|
+
*/
|
|
30
|
+
public internalCallStack: number[] = [];
|
|
31
|
+
|
|
32
|
+
/** Memory accessible to user code */
|
|
33
|
+
public readonly memory: TaggedMemory = new TaggedMemory();
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Signals that execution should end.
|
|
37
|
+
* AvmContext execution continues executing instructions until the machine state signals "halted"
|
|
38
|
+
* */
|
|
39
|
+
public halted: boolean = false;
|
|
40
|
+
/** Signals that execution has reverted normally (this does not cover exceptional halts) */
|
|
41
|
+
private reverted: boolean = false;
|
|
42
|
+
/** Output data must NOT be modified once it is set */
|
|
43
|
+
private output: Fr[] = [];
|
|
44
|
+
|
|
45
|
+
constructor(l1GasLeft: number, l2GasLeft: number, daGasLeft: number) {
|
|
46
|
+
this.l1GasLeft = l1GasLeft;
|
|
47
|
+
this.l2GasLeft = l2GasLeft;
|
|
48
|
+
this.daGasLeft = daGasLeft;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public static fromState(state: InitialAvmMachineState): AvmMachineState {
|
|
52
|
+
return new AvmMachineState(state.l1GasLeft, state.l2GasLeft, state.daGasLeft);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Most instructions just increment PC before they complete
|
|
57
|
+
*/
|
|
58
|
+
public incrementPc() {
|
|
59
|
+
this.pc++;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Halt as successful
|
|
64
|
+
* Output data must NOT be modified once it is set
|
|
65
|
+
* @param output
|
|
66
|
+
*/
|
|
67
|
+
public return(output: Fr[]) {
|
|
68
|
+
this.halted = true;
|
|
69
|
+
this.output = output;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Halt as reverted
|
|
74
|
+
* Output data must NOT be modified once it is set
|
|
75
|
+
* @param output
|
|
76
|
+
*/
|
|
77
|
+
public revert(output: Fr[]) {
|
|
78
|
+
this.halted = true;
|
|
79
|
+
this.reverted = true;
|
|
80
|
+
this.output = output;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get a summary of execution results for a halted machine state
|
|
85
|
+
* @returns summary of execution results
|
|
86
|
+
*/
|
|
87
|
+
public getResults(): AvmContractCallResults {
|
|
88
|
+
if (!this.halted) {
|
|
89
|
+
throw new Error('Execution results are not ready! Execution is ongoing.');
|
|
90
|
+
}
|
|
91
|
+
return new AvmContractCallResults(this.reverted, this.output);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
2
|
+
|
|
3
|
+
import { strict as assert } from 'assert';
|
|
4
|
+
|
|
5
|
+
import { TagCheckError } from './errors.js';
|
|
6
|
+
|
|
7
|
+
/** MemoryValue gathers the common operations for all memory types. */
|
|
8
|
+
export abstract class MemoryValue {
|
|
9
|
+
public abstract add(rhs: MemoryValue): MemoryValue;
|
|
10
|
+
public abstract sub(rhs: MemoryValue): MemoryValue;
|
|
11
|
+
public abstract mul(rhs: MemoryValue): MemoryValue;
|
|
12
|
+
public abstract div(rhs: MemoryValue): MemoryValue;
|
|
13
|
+
|
|
14
|
+
public abstract equals(rhs: MemoryValue): boolean;
|
|
15
|
+
|
|
16
|
+
// We need this to be able to build an instance of the subclasses.
|
|
17
|
+
public abstract build(n: bigint): MemoryValue;
|
|
18
|
+
|
|
19
|
+
// Use sparingly.
|
|
20
|
+
public abstract toBigInt(): bigint;
|
|
21
|
+
|
|
22
|
+
// To field
|
|
23
|
+
public toFr(): Fr {
|
|
24
|
+
return new Fr(this.toBigInt());
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** IntegralValue gathers the common operations for all integral memory types. */
|
|
29
|
+
export abstract class IntegralValue extends MemoryValue {
|
|
30
|
+
public abstract shl(rhs: IntegralValue): IntegralValue;
|
|
31
|
+
public abstract shr(rhs: IntegralValue): IntegralValue;
|
|
32
|
+
public abstract and(rhs: IntegralValue): IntegralValue;
|
|
33
|
+
public abstract or(rhs: IntegralValue): IntegralValue;
|
|
34
|
+
public abstract xor(rhs: IntegralValue): IntegralValue;
|
|
35
|
+
public abstract not(): IntegralValue;
|
|
36
|
+
|
|
37
|
+
public abstract lt(rhs: IntegralValue): boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* This function creates a class for unsigned integers of a given number of bits.
|
|
42
|
+
* In TypeScript terms, it's a class mixin.
|
|
43
|
+
**/
|
|
44
|
+
function UnsignedIntegerClassFactory(bits: number) {
|
|
45
|
+
return class NewUintClass extends IntegralValue {
|
|
46
|
+
static readonly mod: bigint = 1n << BigInt(bits);
|
|
47
|
+
static readonly bitmask: bigint = this.mod - 1n;
|
|
48
|
+
public readonly n: bigint; // Cannot be private due to TS limitations.
|
|
49
|
+
|
|
50
|
+
public constructor(n: bigint | number) {
|
|
51
|
+
super();
|
|
52
|
+
this.n = BigInt(n);
|
|
53
|
+
assert(n < NewUintClass.mod, `Value ${n} is too large for ${this.constructor.name}.`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public build(n: bigint): NewUintClass {
|
|
57
|
+
return new this.constructor.prototype.constructor(n);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public add(rhs: NewUintClass): NewUintClass {
|
|
61
|
+
return this.build((this.n + rhs.n) & NewUintClass.bitmask);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public sub(rhs: NewUintClass): NewUintClass {
|
|
65
|
+
const res: bigint = this.n - rhs.n;
|
|
66
|
+
return this.build(res >= 0 ? res : res + NewUintClass.mod);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public mul(rhs: NewUintClass): NewUintClass {
|
|
70
|
+
return this.build((this.n * rhs.n) & NewUintClass.bitmask);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public div(rhs: NewUintClass): NewUintClass {
|
|
74
|
+
return this.build(this.n / rhs.n);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// No sign extension.
|
|
78
|
+
public shr(rhs: NewUintClass): NewUintClass {
|
|
79
|
+
// Note that this.n is > 0 by class invariant.
|
|
80
|
+
return this.build(this.n >> rhs.n);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public shl(rhs: NewUintClass): NewUintClass {
|
|
84
|
+
return this.build((this.n << rhs.n) & NewUintClass.bitmask);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public and(rhs: NewUintClass): NewUintClass {
|
|
88
|
+
return this.build(this.n & rhs.n);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public or(rhs: NewUintClass): NewUintClass {
|
|
92
|
+
return this.build(this.n | rhs.n);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public xor(rhs: NewUintClass): NewUintClass {
|
|
96
|
+
return this.build(this.n ^ rhs.n);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public not(): NewUintClass {
|
|
100
|
+
return this.build(~this.n & NewUintClass.bitmask);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public equals(rhs: NewUintClass): boolean {
|
|
104
|
+
return this.n === rhs.n;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public lt(rhs: NewUintClass): boolean {
|
|
108
|
+
return this.n < rhs.n;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public toBigInt(): bigint {
|
|
112
|
+
return this.n;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Now we can create the classes for each unsigned integer type.
|
|
118
|
+
// We extend instead of just assigning so that the class has the right name.
|
|
119
|
+
// Otherwise they are all called "NewUintClass".
|
|
120
|
+
export class Uint8 extends UnsignedIntegerClassFactory(8) {}
|
|
121
|
+
export class Uint16 extends UnsignedIntegerClassFactory(16) {}
|
|
122
|
+
export class Uint32 extends UnsignedIntegerClassFactory(32) {}
|
|
123
|
+
export class Uint64 extends UnsignedIntegerClassFactory(64) {}
|
|
124
|
+
export class Uint128 extends UnsignedIntegerClassFactory(128) {}
|
|
125
|
+
|
|
126
|
+
export class Field extends MemoryValue {
|
|
127
|
+
public static readonly MODULUS: bigint = Fr.MODULUS;
|
|
128
|
+
private readonly rep: Fr;
|
|
129
|
+
|
|
130
|
+
constructor(v: number | bigint | Fr) {
|
|
131
|
+
super();
|
|
132
|
+
this.rep = new Fr(v);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public build(n: bigint): Field {
|
|
136
|
+
return new Field(n);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public add(rhs: Field): Field {
|
|
140
|
+
return new Field(this.rep.add(rhs.rep));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public sub(rhs: Field): Field {
|
|
144
|
+
return new Field(this.rep.sub(rhs.rep));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
public mul(rhs: Field): Field {
|
|
148
|
+
return new Field(this.rep.mul(rhs.rep));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
public div(rhs: Field): Field {
|
|
152
|
+
return new Field(this.rep.div(rhs.rep));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
public equals(rhs: Field): boolean {
|
|
156
|
+
return this.rep.equals(rhs.rep);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public toBigInt(): bigint {
|
|
160
|
+
return this.rep.toBigInt();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export enum TypeTag {
|
|
165
|
+
UNINITIALIZED,
|
|
166
|
+
UINT8,
|
|
167
|
+
UINT16,
|
|
168
|
+
UINT32,
|
|
169
|
+
UINT64,
|
|
170
|
+
UINT128,
|
|
171
|
+
FIELD,
|
|
172
|
+
INVALID,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// TODO: Consider automatic conversion when getting undefined values.
|
|
176
|
+
export class TaggedMemory {
|
|
177
|
+
// FIXME: memory should be 2^32, but TS doesn't allow for arrays that big.
|
|
178
|
+
static readonly MAX_MEMORY_SIZE = Number((1n << 32n) - 2n);
|
|
179
|
+
private _mem: MemoryValue[];
|
|
180
|
+
|
|
181
|
+
constructor() {
|
|
182
|
+
// We do not initialize memory size here because otherwise tests blow up when diffing.
|
|
183
|
+
this._mem = [];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
public get(offset: number): MemoryValue {
|
|
187
|
+
assert(offset < TaggedMemory.MAX_MEMORY_SIZE);
|
|
188
|
+
return this.getAs<MemoryValue>(offset);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
public getAs<T>(offset: number): T {
|
|
192
|
+
assert(offset < TaggedMemory.MAX_MEMORY_SIZE);
|
|
193
|
+
const word = this._mem[offset];
|
|
194
|
+
return word as T;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
public getSlice(offset: number, size: number): MemoryValue[] {
|
|
198
|
+
assert(offset < TaggedMemory.MAX_MEMORY_SIZE);
|
|
199
|
+
assert(offset + size < TaggedMemory.MAX_MEMORY_SIZE);
|
|
200
|
+
return this._mem.slice(offset, offset + size);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
public getSliceAs<T>(offset: number, size: number): T[] {
|
|
204
|
+
assert(offset < TaggedMemory.MAX_MEMORY_SIZE);
|
|
205
|
+
assert(offset + size < TaggedMemory.MAX_MEMORY_SIZE);
|
|
206
|
+
return this._mem.slice(offset, offset + size) as T[];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
public getSliceTags(offset: number, size: number): TypeTag[] {
|
|
210
|
+
assert(offset < TaggedMemory.MAX_MEMORY_SIZE);
|
|
211
|
+
assert(offset + size < TaggedMemory.MAX_MEMORY_SIZE);
|
|
212
|
+
return this._mem.slice(offset, offset + size).map(TaggedMemory.getTag);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
public set(offset: number, v: MemoryValue) {
|
|
216
|
+
assert(offset < TaggedMemory.MAX_MEMORY_SIZE);
|
|
217
|
+
this._mem[offset] = v;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
public setSlice(offset: number, vs: MemoryValue[]) {
|
|
221
|
+
assert(offset < TaggedMemory.MAX_MEMORY_SIZE);
|
|
222
|
+
assert(offset + vs.length < TaggedMemory.MAX_MEMORY_SIZE);
|
|
223
|
+
// We may need to extend the memory size, otherwise splice doesn't insert.
|
|
224
|
+
if (offset + vs.length > this._mem.length) {
|
|
225
|
+
this._mem.length = offset + vs.length;
|
|
226
|
+
}
|
|
227
|
+
this._mem.splice(offset, vs.length, ...vs);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
public getTag(offset: number): TypeTag {
|
|
231
|
+
return TaggedMemory.getTag(this._mem[offset]);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Check that the memory at the given offset matches the specified tag.
|
|
236
|
+
*/
|
|
237
|
+
public checkTag(tag: TypeTag, offset: number) {
|
|
238
|
+
if (this.getTag(offset) !== tag) {
|
|
239
|
+
throw TagCheckError.forOffset(offset, TypeTag[this.getTag(offset)], TypeTag[tag]);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
public static checkIsIntegralTag(tag: TypeTag) {
|
|
244
|
+
if (![TypeTag.UINT8, TypeTag.UINT16, TypeTag.UINT32, TypeTag.UINT64, TypeTag.UINT128].includes(tag)) {
|
|
245
|
+
throw TagCheckError.forTag(TypeTag[tag], 'integral');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Check tags for memory at all of the specified offsets.
|
|
251
|
+
*/
|
|
252
|
+
public checkTags(tag: TypeTag, ...offsets: number[]) {
|
|
253
|
+
for (const offset of offsets) {
|
|
254
|
+
this.checkTag(tag, offset);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Check tags for all memory in the specified range.
|
|
260
|
+
*/
|
|
261
|
+
public checkTagsRange(tag: TypeTag, startOffset: number, size: number) {
|
|
262
|
+
for (let offset = startOffset; offset < startOffset + size; offset++) {
|
|
263
|
+
this.checkTag(tag, offset);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// TODO: this might be slow, but I don't want to have the types know of their tags.
|
|
268
|
+
// It might be possible to have a map<Prototype, TypeTag>.
|
|
269
|
+
public static getTag(v: MemoryValue | undefined): TypeTag {
|
|
270
|
+
let tag = TypeTag.INVALID;
|
|
271
|
+
|
|
272
|
+
if (v === undefined) {
|
|
273
|
+
tag = TypeTag.UNINITIALIZED;
|
|
274
|
+
} else if (v instanceof Field) {
|
|
275
|
+
tag = TypeTag.FIELD;
|
|
276
|
+
} else if (v instanceof Uint8) {
|
|
277
|
+
tag = TypeTag.UINT8;
|
|
278
|
+
} else if (v instanceof Uint16) {
|
|
279
|
+
tag = TypeTag.UINT16;
|
|
280
|
+
} else if (v instanceof Uint32) {
|
|
281
|
+
tag = TypeTag.UINT32;
|
|
282
|
+
} else if (v instanceof Uint64) {
|
|
283
|
+
tag = TypeTag.UINT64;
|
|
284
|
+
} else if (v instanceof Uint128) {
|
|
285
|
+
tag = TypeTag.UINT128;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return tag;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Truncates the value to fit the type.
|
|
292
|
+
public static integralFromTag(v: bigint | number, tag: TypeTag): IntegralValue {
|
|
293
|
+
v = BigInt(v);
|
|
294
|
+
switch (tag) {
|
|
295
|
+
case TypeTag.UINT8:
|
|
296
|
+
return new Uint8(v & ((1n << 8n) - 1n));
|
|
297
|
+
case TypeTag.UINT16:
|
|
298
|
+
return new Uint16(v & ((1n << 16n) - 1n));
|
|
299
|
+
case TypeTag.UINT32:
|
|
300
|
+
return new Uint32(v & ((1n << 32n) - 1n));
|
|
301
|
+
case TypeTag.UINT64:
|
|
302
|
+
return new Uint64(v & ((1n << 64n) - 1n));
|
|
303
|
+
case TypeTag.UINT128:
|
|
304
|
+
return new Uint128(v & ((1n << 128n) - 1n));
|
|
305
|
+
default:
|
|
306
|
+
throw new Error(`${TypeTag[tag]} is not a valid integral type.`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Results of an contract call's execution in the AVM.
|
|
5
|
+
*/
|
|
6
|
+
export class AvmContractCallResults {
|
|
7
|
+
public readonly reverted: boolean;
|
|
8
|
+
public readonly output: Fr[];
|
|
9
|
+
|
|
10
|
+
/** For exceptional halts */
|
|
11
|
+
public readonly revertReason: Error | undefined;
|
|
12
|
+
|
|
13
|
+
constructor(reverted: boolean, output: Fr[], revertReason?: Error) {
|
|
14
|
+
this.reverted = reverted;
|
|
15
|
+
this.output = output;
|
|
16
|
+
this.revertReason = revertReason;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generate a string representation of call results.
|
|
21
|
+
*/
|
|
22
|
+
toString(): string {
|
|
23
|
+
let resultsStr = `reverted: ${this.reverted}, output: ${this.output}`;
|
|
24
|
+
if (this.revertReason) {
|
|
25
|
+
resultsStr += `, revertReason: ${this.revertReason}`;
|
|
26
|
+
}
|
|
27
|
+
return resultsStr;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { DebugLogger, createDebugLogger } from '@aztec/foundation/log';
|
|
2
|
+
|
|
3
|
+
import { strict as assert } from 'assert';
|
|
4
|
+
|
|
5
|
+
import type { AvmContext } from './avm_context.js';
|
|
6
|
+
import { AvmContractCallResults } from './avm_message_call_result.js';
|
|
7
|
+
import { AvmExecutionError, InvalidProgramCounterError, NoBytecodeForContractError } from './errors.js';
|
|
8
|
+
import type { Instruction } from './opcodes/index.js';
|
|
9
|
+
import { decodeFromBytecode } from './serialization/bytecode_serialization.js';
|
|
10
|
+
|
|
11
|
+
export class AvmSimulator {
|
|
12
|
+
private log: DebugLogger = createDebugLogger('aztec:avm_simulator');
|
|
13
|
+
|
|
14
|
+
constructor(private context: AvmContext) {}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Fetch the bytecode and execute it in the current context.
|
|
18
|
+
*/
|
|
19
|
+
public async execute(): Promise<AvmContractCallResults> {
|
|
20
|
+
const instructions = await this.fetchAndDecodeBytecode();
|
|
21
|
+
return this.executeInstructions(instructions);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Executes the provided instructions in the current context.
|
|
26
|
+
* This method is useful for testing and debugging.
|
|
27
|
+
*/
|
|
28
|
+
public async executeInstructions(instructions: Instruction[]): Promise<AvmContractCallResults> {
|
|
29
|
+
assert(instructions.length > 0);
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Execute instruction pointed to by the current program counter
|
|
33
|
+
// continuing until the machine state signifies a halt
|
|
34
|
+
while (!this.context.machineState.halted) {
|
|
35
|
+
const instruction = instructions[this.context.machineState.pc];
|
|
36
|
+
assert(!!instruction); // This should never happen
|
|
37
|
+
|
|
38
|
+
this.log(`Executing PC=${this.context.machineState.pc}: ${instruction.toString()}`);
|
|
39
|
+
// Execute the instruction.
|
|
40
|
+
// Normal returns and reverts will return normally here.
|
|
41
|
+
// "Exceptional halts" will throw.
|
|
42
|
+
await instruction.execute(this.context);
|
|
43
|
+
|
|
44
|
+
if (this.context.machineState.pc >= instructions.length) {
|
|
45
|
+
this.log('Passed end of program!');
|
|
46
|
+
throw new InvalidProgramCounterError(this.context.machineState.pc, /*max=*/ instructions.length);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Return results for processing by calling context
|
|
51
|
+
const results = this.context.machineState.getResults();
|
|
52
|
+
this.log(`Context execution results: ${results.toString()}`);
|
|
53
|
+
return results;
|
|
54
|
+
} catch (e) {
|
|
55
|
+
this.log('Exceptional halt');
|
|
56
|
+
if (!(e instanceof AvmExecutionError)) {
|
|
57
|
+
this.log(`Unknown error thrown by avm: ${e}`);
|
|
58
|
+
throw e;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Return results for processing by calling context
|
|
62
|
+
// Note: "exceptional halts" cannot return data
|
|
63
|
+
const results = new AvmContractCallResults(/*reverted=*/ true, /*output=*/ [], /*revertReason=*/ e);
|
|
64
|
+
this.log(`Context execution results: ${results.toString()}`);
|
|
65
|
+
return results;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Fetch contract bytecode from world state and decode into executable instructions.
|
|
71
|
+
*/
|
|
72
|
+
private async fetchAndDecodeBytecode(): Promise<Instruction[]> {
|
|
73
|
+
// NOTE: the following is mocked as getPublicBytecode does not exist yet
|
|
74
|
+
|
|
75
|
+
const selector = this.context.environment.temporaryFunctionSelector;
|
|
76
|
+
const bytecode = await this.context.worldState.hostStorage.contractsDb.getBytecode(
|
|
77
|
+
this.context.environment.address,
|
|
78
|
+
selector,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// This assumes that we will not be able to send messages to accounts without code
|
|
82
|
+
// Pending classes and instances impl details
|
|
83
|
+
if (!bytecode) {
|
|
84
|
+
throw new NoBytecodeForContractError(this.context.environment.address);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return decodeFromBytecode(bytecode);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { AztecAddress } from '@aztec/circuits.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Avm-specific errors should derive from this
|
|
5
|
+
*/
|
|
6
|
+
export abstract class AvmExecutionError extends Error {
|
|
7
|
+
constructor(message: string, ...rest: any[]) {
|
|
8
|
+
super(message, ...rest);
|
|
9
|
+
this.name = 'AvmInterpreterError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class NoBytecodeForContractError extends AvmExecutionError {
|
|
14
|
+
constructor(contractAddress: AztecAddress) {
|
|
15
|
+
super(`No bytecode found at: ${contractAddress}`);
|
|
16
|
+
this.name = 'NoBytecodeFoundInterpreterError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Error is thrown when the program counter goes to an invalid location.
|
|
22
|
+
* There is no instruction at the provided pc
|
|
23
|
+
*/
|
|
24
|
+
export class InvalidProgramCounterError extends AvmExecutionError {
|
|
25
|
+
constructor(pc: number, max: number) {
|
|
26
|
+
super(`Invalid program counter ${pc}, max is ${max}`);
|
|
27
|
+
this.name = 'InvalidProgramCounterError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Error thrown during an instruction's execution (during its execute()).
|
|
33
|
+
*/
|
|
34
|
+
export class InstructionExecutionError extends AvmExecutionError {
|
|
35
|
+
constructor(message: string) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = 'InstructionExecutionError';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Error thrown on failed AVM memory tag check.
|
|
43
|
+
*/
|
|
44
|
+
export class TagCheckError extends AvmExecutionError {
|
|
45
|
+
public static forOffset(offset: number, gotTag: string, expectedTag: string): TagCheckError {
|
|
46
|
+
return new TagCheckError(`Tag mismatch at offset ${offset}, got ${gotTag}, expected ${expectedTag}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public static forTag(gotTag: string, expectedTag: string): TagCheckError {
|
|
50
|
+
return new TagCheckError(`Tag mismatch, got ${gotTag}, expected ${expectedTag}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
constructor(message: string) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.name = 'TagCheckError';
|
|
56
|
+
}
|
|
57
|
+
}
|