diff --git a/avm-transpiler/src/opcodes.rs b/avm-transpiler/src/opcodes.rs index e5c01bf3c49..c09bc4b5cb5 100644 --- a/avm-transpiler/src/opcodes.rs +++ b/avm-transpiler/src/opcodes.rs @@ -59,6 +59,7 @@ pub enum AvmOpcode { EMITNULLIFIER, L1TOL2MSGEXISTS, HEADERMEMBER, + GETCONTRACTINSTANCE, EMITUNENCRYPTEDLOG, SENDL2TOL1MSG, // External calls @@ -148,6 +149,7 @@ impl AvmOpcode { // Accrued Substate AvmOpcode::EMITUNENCRYPTEDLOG => "EMITUNENCRYPTEDLOG", AvmOpcode::SENDL2TOL1MSG => "SENDL2TOL1MSG", + AvmOpcode::GETCONTRACTINSTANCE => "GETCONTRACTINSTANCE", // Control Flow - Contract Calls AvmOpcode::CALL => "CALL", diff --git a/avm-transpiler/src/transpile.rs b/avm-transpiler/src/transpile.rs index e5745fca608..1b97449d81c 100644 --- a/avm-transpiler/src/transpile.rs +++ b/avm-transpiler/src/transpile.rs @@ -301,6 +301,9 @@ fn handle_foreign_call( "avmOpcodePoseidon" => { handle_single_field_hash_instruction(avm_instrs, function, destinations, inputs) } + "avmOpcodeGetContractInstance" => { + handle_get_contract_instance(avm_instrs, destinations, inputs) + } "storageRead" => handle_storage_read(avm_instrs, destinations, inputs), "storageWrite" => handle_storage_write(avm_instrs, destinations, inputs), // Getters. @@ -969,6 +972,42 @@ fn handle_storage_write( }) } +/// Emit a GETCONTRACTINSTANCE opcode +fn handle_get_contract_instance( + avm_instrs: &mut Vec, + destinations: &Vec, + inputs: &Vec, +) { + assert!(inputs.len() == 1); + assert!(destinations.len() == 1); + + let address_offset_maybe = inputs[0]; + let address_offset = match address_offset_maybe { + ValueOrArray::MemoryAddress(slot_offset) => slot_offset.0, + _ => panic!("GETCONTRACTINSTANCE address should be a single value"), + }; + + let dest_offset_maybe = destinations[0]; + let dest_offset = match dest_offset_maybe { + ValueOrArray::HeapArray(HeapArray { pointer, .. }) => pointer.0, + _ => panic!("GETCONTRACTINSTANCE destination should be an array"), + }; + + avm_instrs.push(AvmInstruction { + opcode: AvmOpcode::GETCONTRACTINSTANCE, + indirect: Some(FIRST_OPERAND_INDIRECT), + operands: vec![ + AvmOperand::U32 { + value: address_offset as u32, + }, + AvmOperand::U32 { + value: dest_offset as u32, + }, + ], + ..Default::default() + }) +} + /// Emit a storage read opcode /// The current implementation reads an array of values from storage ( contiguous slots in memory ) fn handle_storage_read( diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp index 02296aa73e9..2fb4dcc0c4d 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp @@ -83,6 +83,7 @@ enum class OpCode : uint8_t { EMITNULLIFIER, // Notes & Nullifiers L1TOL2MSGEXISTS, // Messages HEADERMEMBER, // Archive tree & Headers + GETCONTRACTINSTANCE, // Accrued Substate EMITUNENCRYPTEDLOG, diff --git a/noir-projects/aztec-nr/aztec/src/oracle/get_contract_instance.nr b/noir-projects/aztec-nr/aztec/src/oracle/get_contract_instance.nr index d68d6ed1563..caf5b93bdb0 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/get_contract_instance.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/get_contract_instance.nr @@ -1,14 +1,35 @@ -use dep::protocol_types::{address::AztecAddress, contract_instance::ContractInstance, constants::CONTRACT_INSTANCE_LENGTH}; +use dep::protocol_types::{ + address::AztecAddress, contract_instance::ContractInstance, utils::arr_copy_slice, + constants::CONTRACT_INSTANCE_LENGTH, utils::reader::Reader +}; #[oracle(getContractInstance)] fn get_contract_instance_oracle(_address: AztecAddress) -> [Field; CONTRACT_INSTANCE_LENGTH] {} +// Returns a ContractInstance plus a boolean indicating whether the instance was found. +#[oracle(avmOpcodeGetContractInstance)] +fn get_contract_instance_oracle_avm(_address: AztecAddress) -> [Field; CONTRACT_INSTANCE_LENGTH + 1] {} + unconstrained fn get_contract_instance_internal(address: AztecAddress) -> [Field; CONTRACT_INSTANCE_LENGTH] { get_contract_instance_oracle(address) } +unconstrained fn get_contract_instance_internal_avm(address: AztecAddress) -> [Field; CONTRACT_INSTANCE_LENGTH + 1] { + get_contract_instance_oracle_avm(address) +} + pub fn get_contract_instance(address: AztecAddress) -> ContractInstance { let instance = ContractInstance::deserialize(get_contract_instance_internal(address)); assert(instance.to_address().eq(address)); instance } + +pub fn get_contract_instance_avm(address: AztecAddress) -> Option { + let mut reader = Reader::new(get_contract_instance_internal_avm(address)); + let found = reader.read(); + if found == 0 { + Option::none() + } else { + Option::some(reader.read_struct(ContractInstance::deserialize)) + } +} diff --git a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr index f09e441aa99..442d6c2fdfb 100644 --- a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr @@ -26,7 +26,11 @@ contract AvmTest { // Libs use dep::aztec::prelude::Map; use dep::aztec::state_vars::{PublicImmutable, PublicMutable}; - use dep::aztec::protocol_types::{address::{AztecAddress, EthAddress}, constants::L1_TO_L2_MESSAGE_LENGTH}; + use dep::aztec::protocol_types::{ + address::{AztecAddress, EthAddress}, constants::L1_TO_L2_MESSAGE_LENGTH, + contract_instance::ContractInstance + }; + use dep::aztec::oracle::get_contract_instance::{get_contract_instance_avm, get_contract_instance_internal_avm}; use dep::aztec::protocol_types::abis::function_selector::FunctionSelector; use dep::aztec::protocol_types::traits::ToField; use dep::aztec::protocol_types::constants::RETURN_VALUES_LENGTH; @@ -181,6 +185,28 @@ contract AvmTest { dep::std::hash::pedersen_hash_with_separator(data, 20) } + /************************************************************************ + * Contract instance + ************************************************************************/ + #[aztec(public-vm)] + fn test_get_contract_instance_raw() { + let fields = get_contract_instance_internal_avm(context.this_address()); + assert(fields.len() == 7); + assert(fields[0] == 0x1); + assert(fields[1] == 0x123); + assert(fields[2] == 0x456); + assert(fields[3] == 0x789); + assert(fields[4] == 0x101112); + assert(fields[5] == 0x131415); + assert(fields[6] == 0x161718); + } + + #[aztec(public-vm)] + fn test_get_contract_instance() { + let ci = get_contract_instance_avm(context.this_address()); + assert(ci.is_some()); + } + /************************************************************************ * AvmContext functions ************************************************************************/ diff --git a/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts index a9c2df4e411..2edb2328a20 100644 --- a/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts +++ b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts @@ -1,4 +1,4 @@ -import { AztecAddress, type Wallet } from '@aztec/aztec.js'; +import { AztecAddress, TxStatus, type Wallet } from '@aztec/aztec.js'; import { AvmTestContract } from '@aztec/noir-contracts.js'; import { jest } from '@jest/globals'; @@ -43,10 +43,17 @@ describe('e2e_avm_simulator', () => { }); }); + describe('Contract instance', () => { + it('Works', async () => { + const tx = await avmContact.methods.test_get_contract_instance().send().wait(); + expect(tx.status).toEqual(TxStatus.MINED); + }); + }); + describe('Nullifiers', () => { it('Emit and check', async () => { - await avmContact.methods.emit_nullifier_and_check(123456).send().wait(); - // TODO: check NOT reverted + const tx = await avmContact.methods.emit_nullifier_and_check(123456).send().wait(); + expect(tx.status).toEqual(TxStatus.MINED); }); }); }); diff --git a/yarn-project/simulator/src/avm/avm_gas_cost.ts b/yarn-project/simulator/src/avm/avm_gas_cost.ts index 69f80e2dfe7..1ba321a1c8f 100644 --- a/yarn-project/simulator/src/avm/avm_gas_cost.ts +++ b/yarn-project/simulator/src/avm/avm_gas_cost.ts @@ -89,6 +89,7 @@ export const GasCosts = { [Opcode.HEADERMEMBER]: TemporaryDefaultGasCost, [Opcode.EMITUNENCRYPTEDLOG]: TemporaryDefaultGasCost, [Opcode.SENDL2TOL1MSG]: TemporaryDefaultGasCost, + [Opcode.GETCONTRACTINSTANCE]: TemporaryDefaultGasCost, // External calls [Opcode.CALL]: TemporaryDefaultGasCost, [Opcode.STATICCALL]: TemporaryDefaultGasCost, diff --git a/yarn-project/simulator/src/avm/avm_memory_types.ts b/yarn-project/simulator/src/avm/avm_memory_types.ts index a9b2f1f9a5b..fdaf9c63968 100644 --- a/yarn-project/simulator/src/avm/avm_memory_types.ts +++ b/yarn-project/simulator/src/avm/avm_memory_types.ts @@ -221,6 +221,9 @@ export class TaggedMemory { assert(offset < TaggedMemory.MAX_MEMORY_SIZE); const word = this._mem[offset]; TaggedMemory.log(`get(${offset}) = ${word}`); + if (word === undefined) { + TaggedMemory.log.warn(`Memory at offset ${offset} is undefined! This might be OK if it's stack dumping.`); + } return word as T; } @@ -229,6 +232,7 @@ export class TaggedMemory { assert(offset + size < TaggedMemory.MAX_MEMORY_SIZE); const value = this._mem.slice(offset, offset + size); TaggedMemory.log(`getSlice(${offset}, ${size}) = ${value}`); + assert(!value.some(e => e === undefined), 'Memory slice contains undefined values.'); assert(value.length === size, `Expected slice of size ${size}, got ${value.length}.`); return value; } diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index 85d812b189b..b11decf2832 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -757,6 +757,30 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect([...storageTrace.values()]).toEqual([[value]]); }); }); + + describe('Contract', () => { + it(`GETCONTRACTINSTANCE deserializes correctly`, async () => { + const context = initContext(); + const contractInstance = { + address: AztecAddress.random(), + version: 1 as const, + salt: new Fr(0x123), + deployer: AztecAddress.fromBigInt(0x456n), + contractClassId: new Fr(0x789), + initializationHash: new Fr(0x101112), + portalContractAddress: EthAddress.fromField(new Fr(0x131415)), + publicKeysHash: new Fr(0x161718), + }; + + jest + .spyOn(context.persistableState.hostStorage.contractsDb, 'getContractInstance') + .mockReturnValue(Promise.resolve(contractInstance)); + const bytecode = getAvmTestContractBytecode('test_get_contract_instance_raw'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); + + expect(results.reverted).toBe(false); + }); + }); }); function getAvmTestContractBytecode(functionName: string): Buffer { diff --git a/yarn-project/simulator/src/avm/opcodes/contract.test.ts b/yarn-project/simulator/src/avm/opcodes/contract.test.ts new file mode 100644 index 00000000000..8b5c2dc021b --- /dev/null +++ b/yarn-project/simulator/src/avm/opcodes/contract.test.ts @@ -0,0 +1,89 @@ +import { AztecAddress, EthAddress, Fr } from '@aztec/circuits.js'; + +import { type DeepMockProxy, mockDeep } from 'jest-mock-extended'; + +import { type AvmContext } from '../avm_context.js'; +import { Field } from '../avm_memory_types.js'; +import { initContext } from '../fixtures/index.js'; +import { type AvmPersistableStateManager } from '../journal/journal.js'; +import { GetContractInstance } from './contract.js'; + +describe('Contract opcodes', () => { + let context: AvmContext; + let journal: DeepMockProxy; + const address = AztecAddress.random(); + + beforeEach(async () => { + journal = mockDeep(); + context = initContext({ + persistableState: journal, + }); + }); + + describe('GETCONTRACTINSTANCE', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + GetContractInstance.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // addressOffset + ...Buffer.from('a2345678', 'hex'), // dstOffset + ]); + const inst = new GetContractInstance( + /*indirect=*/ 0x01, + /*addressOffset=*/ 0x12345678, + /*dstOffset=*/ 0xa2345678, + ); + + expect(GetContractInstance.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + + it('should copy contract instance to memory if found', async () => { + context.machineState.memory.set(0, new Field(address.toField())); + + const contractInstance = { + address: address, + version: 1 as const, + salt: new Fr(20), + contractClassId: new Fr(30), + initializationHash: new Fr(40), + portalContractAddress: EthAddress.random(), + publicKeysHash: new Fr(50), + deployer: AztecAddress.random(), + }; + + journal.hostStorage.contractsDb.getContractInstance.mockReturnValue(Promise.resolve(contractInstance)); + + await new GetContractInstance(/*indirect=*/ 0, /*addressOffset=*/ 0, /*dstOffset=*/ 1).execute(context); + + const actual = context.machineState.memory.getSlice(1, 7); + expect(actual).toEqual([ + new Field(1), // found + new Field(contractInstance.salt), + new Field(contractInstance.deployer), + new Field(contractInstance.contractClassId), + new Field(contractInstance.initializationHash), + new Field(contractInstance.portalContractAddress.toField()), + new Field(contractInstance.publicKeysHash), + ]); + }); + + it('should return zeroes if not found', async () => { + context.machineState.memory.set(0, new Field(address.toField())); + journal.hostStorage.contractsDb.getContractInstance.mockReturnValue(Promise.resolve(undefined)); + + await new GetContractInstance(/*indirect=*/ 0, /*addressOffset=*/ 0, /*dstOffset=*/ 1).execute(context); + + const actual = context.machineState.memory.getSlice(1, 7); + expect(actual).toEqual([ + new Field(0), // found + new Field(0), + new Field(0), + new Field(0), + new Field(0), + new Field(0), + new Field(0), + ]); + }); + }); +}); diff --git a/yarn-project/simulator/src/avm/opcodes/contract.ts b/yarn-project/simulator/src/avm/opcodes/contract.ts new file mode 100644 index 00000000000..5e24dcf0d50 --- /dev/null +++ b/yarn-project/simulator/src/avm/opcodes/contract.ts @@ -0,0 +1,58 @@ +import { AztecAddress, Fr } from '@aztec/circuits.js'; + +import type { AvmContext } from '../avm_context.js'; +import { Field } from '../avm_memory_types.js'; +import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; +import { Addressing } from './addressing_mode.js'; +import { Instruction } from './instruction.js'; + +export class GetContractInstance extends Instruction { + static readonly type: string = 'GETCONTRACTINSTANCE'; + static readonly opcode: Opcode = Opcode.GETCONTRACTINSTANCE; + // Informs (de)serialization. See Instruction.deserialize. + static readonly wireFormat: OperandType[] = [ + OperandType.UINT8, + OperandType.UINT8, + OperandType.UINT32, + OperandType.UINT32, + ]; + + constructor(private indirect: number, private addressOffset: number, private dstOffset: number) { + super(); + } + + async execute(context: AvmContext): Promise { + const [addressOffset, dstOffset] = Addressing.fromWire(this.indirect).resolve( + [this.addressOffset, this.dstOffset], + context.machineState.memory, + ); + + const address = AztecAddress.fromField(context.machineState.memory.get(addressOffset).toFr()); + const instance = await context.persistableState.hostStorage.contractsDb.getContractInstance(address); + + const data = + instance === undefined + ? [ + new Field(0), // not found + new Field(0), + new Field(0), + new Field(0), + new Field(0), + new Field(0), + new Field(0), + ] + : [ + new Fr(1), // found + instance.salt, + instance.deployer.toField(), + instance.contractClassId, + instance.initializationHash, + instance.portalContractAddress.toField(), + instance.publicKeysHash, + ].map(f => new Field(f)); + + context.machineState.memory.setSlice(dstOffset, data); + + context.machineState.incrementPc(); + } +} diff --git a/yarn-project/simulator/src/avm/opcodes/index.ts b/yarn-project/simulator/src/avm/opcodes/index.ts index d8dc52a8cf5..e3263fdfd1c 100644 --- a/yarn-project/simulator/src/avm/opcodes/index.ts +++ b/yarn-project/simulator/src/avm/opcodes/index.ts @@ -1,6 +1,7 @@ export * from './arithmetic.js'; export * from './bitwise.js'; export * from './control_flow.js'; +export * from './contract.js'; export * from './instruction.js'; export * from './comparators.js'; export * from './memory.js'; diff --git a/yarn-project/simulator/src/avm/serialization/bytecode_serialization.ts b/yarn-project/simulator/src/avm/serialization/bytecode_serialization.ts index fe26496ea28..80c3d15f139 100644 --- a/yarn-project/simulator/src/avm/serialization/bytecode_serialization.ts +++ b/yarn-project/simulator/src/avm/serialization/bytecode_serialization.ts @@ -18,6 +18,7 @@ import { FeePerL1Gas, FeePerL2Gas, FieldDiv, + GetContractInstance, InternalCall, InternalReturn, Jump, @@ -126,6 +127,7 @@ const INSTRUCTION_SET = () => // Accrued Substate [EmitUnencryptedLog.opcode, EmitUnencryptedLog], [SendL2ToL1Message.opcode, SendL2ToL1Message], + [GetContractInstance.opcode, GetContractInstance], // Control Flow - Contract Calls [Call.opcode, Call], diff --git a/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts b/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts index 1a6ae62c82f..3957f5965b0 100644 --- a/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts +++ b/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts @@ -64,6 +64,7 @@ export enum Opcode { EMITNULLIFIER, L1TOL2MSGEXISTS, HEADERMEMBER, + GETCONTRACTINSTANCE, EMITUNENCRYPTEDLOG, SENDL2TOL1MSG, // External calls diff --git a/yarn-project/simulator/src/public/avm_executor.test.ts b/yarn-project/simulator/src/public/avm_executor.test.ts index 89cfdd48988..969f1c730f1 100644 --- a/yarn-project/simulator/src/public/avm_executor.test.ts +++ b/yarn-project/simulator/src/public/avm_executor.test.ts @@ -43,7 +43,7 @@ describe('AVM WitGen and Proof Generation', () => { // new Add(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2), // new Return(/*indirect=*/ 0, /*returnOffset=*/ 2, /*copySize=*/ 1), // ]); - const bytecode: Buffer = Buffer.from('IAAAAAAAAAAAAgAAAAAAAAYAAAAAAAAAAQAAAAI4AAAAAAIAAAAB', 'base64'); + const bytecode: Buffer = Buffer.from('IAAAAAAAAAAAAgAAAAAAAAYAAAAAAAAAAQAAAAI5AAAAAAIAAAAB', 'base64'); publicContracts.getBytecode.mockResolvedValue(bytecode); const executor = new PublicExecutor(publicState, publicContracts, commitmentsDb, header); const functionData = FunctionData.empty(); diff --git a/yarn-project/simulator/src/public/executor.ts b/yarn-project/simulator/src/public/executor.ts index f37814cfd92..075a3fb43c6 100644 --- a/yarn-project/simulator/src/public/executor.ts +++ b/yarn-project/simulator/src/public/executor.ts @@ -167,6 +167,7 @@ export class PublicExecutor { private readonly header: Header, ) {} + private readonly log = createDebugLogger('aztec:simulator:public_executor'); /** * Executes a public execution request. * @param execution - The execution to run. @@ -255,7 +256,7 @@ export class PublicExecutor { const worldStateJournal = new AvmPersistableStateManager(hostStorage); const executionEnv = temporaryCreateAvmExecutionEnvironment(execution, globalVariables); // TODO(@spalladino) Load initial gas from the public execution request - const machineState = new AvmMachineState(100_000, 100_000, 100_000); + const machineState = new AvmMachineState(1e6, 1e6, 1e6); const context = new AvmContext(worldStateJournal, executionEnv, machineState); const simulator = new AvmSimulator(context); @@ -285,6 +286,7 @@ export class PublicExecutor { const artifactsPath = path.resolve('target'); // Create the directory if it does not exist + await fs.rm(artifactsPath, { recursive: true, force: true }); await fs.mkdir(artifactsPath, { recursive: true }); const calldataPath = path.join(artifactsPath, 'calldata.bin'); @@ -294,34 +296,54 @@ export class PublicExecutor { const { args, functionData, contractAddress } = avmExecution; const bytecode = await this.contractsDb.getBytecode(contractAddress, functionData.selector); // Write call data and bytecode to files. - await Promise.all([ - fs.writeFile( - calldataPath, - args.map(c => c.toBuffer()), - ), - fs.writeFile(bytecodePath, bytecode!), - ]); - - const bbBinary = spawn(path.join(bbPath, 'build', 'bin', 'bb'), [ - 'avm_prove', - '-b', - bytecodePath, - '-d', + await fs.writeFile( calldataPath, - '-o', - proofPath, - ]); + args.map(c => c.toBuffer()), + ); + await fs.writeFile(bytecodePath, bytecode!); + + const bbExec = path.join(bbPath, 'build', 'bin', 'bb'); + const bbArgs = ['avm_prove', '-b', bytecodePath, '-d', calldataPath, '-o', proofPath]; + this.log(`calling '${bbExec} ${bbArgs.join(' ')}'`); + const bbBinary = spawn(bbExec, bbArgs); + // The binary writes the proof and the verification key to the write path. return new Promise((resolve, reject) => { + let stdout: string = ''; + let stderr: string = ''; + bbBinary.on('close', () => { - resolve(Promise.all([fs.readFile(proofPath), fs.readFile(path.join(artifactsPath, 'vk'))])); + this.log(`Proof generation complete. Reading proof and vk from ${proofPath}.`); + return resolve(Promise.all([fs.readFile(proofPath), fs.readFile(path.join(artifactsPath, 'vk'))])); + }); + + // Catch stdout. + bbBinary.stdout.on('data', (data: Buffer) => { + stdout += data.toString(); + }); + bbBinary.stdout.on('end', () => { + if (stdout.length > 0) { + this.log(`stdout: ${stdout}`); + } }); + + // Catch stderr. + bbBinary.stderr.on('data', (data: Buffer) => { + stderr += data.toString(); + }); + bbBinary.stderr.on('end', () => { + if (stderr.length > 0) { + this.log(`stderr: ${stderr}`); + } + }); + // Catch and propagate errors from spawning bbBinary.on('error', err => { reject(err); }); }); } + /** * Verifies an AVM proof. This function is currently only used for testing purposes, as verification * is not fully complete in the AVM yet. @@ -338,9 +360,14 @@ export class PublicExecutor { const proofPath = path.join(artifactsPath, 'proof'); // Write the verification key and the proof to files. - await Promise.all([fs.writeFile(vkPath, vk), fs.writeFile(proofPath, proof)]); + await fs.writeFile(vkPath, vk); + await fs.writeFile(proofPath, proof); + + const bbExec = path.join(bbPath, 'build', 'bin', 'bb'); + const bbArgs = ['avm_verify', '-p', proofPath]; + this.log(`calling '${bbPath} ${bbArgs.join(' ')}'`); + const bbBinary = spawn(bbExec, bbArgs); - const bbBinary = spawn(path.join(bbPath, 'build', 'bin', 'bb'), ['avm_verify', '-p', proofPath]); // The binary prints to stdout 1 if the proof is valid and 0 if it is not. return new Promise((resolve, reject) => { let result = Buffer.alloc(0); diff --git a/yellow-paper/docs/public-vm/gen/_instruction-set.mdx b/yellow-paper/docs/public-vm/gen/_instruction-set.mdx index 73902c179ef..3d6e3669825 100644 --- a/yellow-paper/docs/public-vm/gen/_instruction-set.mdx +++ b/yellow-paper/docs/public-vm/gen/_instruction-set.mdx @@ -392,7 +392,22 @@ if exists: - 0x33 [`EMITUNENCRYPTEDLOG`](#isa-section-emitunencryptedlog) + 0x33 [`GETCONTRACTINSTANCE`](#isa-section-getcontractinstance) + Copies contract instance data to memory + +{`M[dstOffset:dstOffset+CONTRACT_INSTANCE_SIZE+1] = [ + instance_found_in_address, + instance.salt ?? 0, + instance.deployer ?? 0, + instance.contractClassId ?? 0, + instance.initializationHash ?? 0, + instance.portalContractAddress ?? 0, + instance.publicKeysHash ?? 0, +]`} + + + + 0x34 [`EMITUNENCRYPTEDLOG`](#isa-section-emitunencryptedlog) Emit an unencrypted log {`context.accruedSubstate.unencryptedLogs.append( @@ -405,7 +420,7 @@ if exists: - 0x34 [`SENDL2TOL1MSG`](#isa-section-sendl2tol1msg) + 0x35 [`SENDL2TOL1MSG`](#isa-section-sendl2tol1msg) Send an L2-to-L1 message {`context.accruedSubstate.sentL2ToL1Messages.append( @@ -418,7 +433,7 @@ if exists: - 0x35 [`CALL`](#isa-section-call) + 0x36 [`CALL`](#isa-section-call) Call into another contract {`// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize } @@ -433,7 +448,7 @@ updateContextAfterNestedCall(context, instr.args, nestedContext)`} - 0x36 [`STATICCALL`](#isa-section-staticcall) + 0x37 [`STATICCALL`](#isa-section-staticcall) Call into another contract, disallowing World State and Accrued Substate modifications {`// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize } @@ -448,7 +463,7 @@ updateContextAfterNestedCall(context, instr.args, nestedContext)`} - 0x37 [`DELEGATECALL`](#isa-section-delegatecall) + 0x38 [`DELEGATECALL`](#isa-section-delegatecall) Call into another contract, but keep the caller's `sender` and `storageAddress` {`// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize } @@ -463,7 +478,7 @@ updateContextAfterNestedCall(context, instr.args, nestedContext)`} - 0x38 [`RETURN`](#isa-section-return) + 0x39 [`RETURN`](#isa-section-return) Halt execution within this context (without revert), optionally returning some data {`context.contractCallResults.output = M[retOffset:retOffset+retSize] @@ -471,7 +486,7 @@ halt`} - 0x39 [`REVERT`](#isa-section-revert) + 0x3a [`REVERT`](#isa-section-revert) Halt execution within this context as `reverted`, optionally returning some data {`context.contractCallResults.output = M[retOffset:retOffset+retSize] @@ -499,6 +514,7 @@ Addition (a + b) - **bOffset**: memory offset of the operation's right input - **dstOffset**: memory offset specifying where to store operation's result - **Expression**: `M[dstOffset] = M[aOffset] + M[bOffset] mod 2^k` +- **Details**: Wraps on overflow - **Tag checks**: `T[aOffset] == T[bOffset] == inTag` - **Tag updates**: `T[dstOffset] = inTag` - **Bit-size**: 128 @@ -520,6 +536,7 @@ Subtraction (a - b) - **bOffset**: memory offset of the operation's right input - **dstOffset**: memory offset specifying where to store operation's result - **Expression**: `M[dstOffset] = M[aOffset] - M[bOffset] mod 2^k` +- **Details**: Wraps on undeflow - **Tag checks**: `T[aOffset] == T[bOffset] == inTag` - **Tag updates**: `T[dstOffset] = inTag` - **Bit-size**: 128 @@ -541,6 +558,7 @@ Multiplication (a * b) - **bOffset**: memory offset of the operation's right input - **dstOffset**: memory offset specifying where to store operation's result - **Expression**: `M[dstOffset] = M[aOffset] * M[bOffset] mod 2^k` +- **Details**: Wraps on overflow - **Tag checks**: `T[aOffset] == T[bOffset] == inTag` - **Tag updates**: `T[dstOffset] = inTag` - **Bit-size**: 128 @@ -1584,12 +1602,42 @@ T[dstOffset] = field`} - **Bit-size**: 152 +### `GETCONTRACTINSTANCE` +Copies contract instance data to memory + +[See in table.](#isa-table-getcontractinstance) + +- **Opcode**: 0x33 +- **Category**: Other +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **addressOffset**: memory offset of the contract instance address + - **dstOffset**: location to write the contract instance information to +- **Expression**: + +{`M[dstOffset:dstOffset+CONTRACT_INSTANCE_SIZE+1] = [ + instance_found_in_address, + instance.salt ?? 0, + instance.deployer ?? 0, + instance.contractClassId ?? 0, + instance.initializationHash ?? 0, + instance.portalContractAddress ?? 0, + instance.publicKeysHash ?? 0, +]`} + +- **Additional AVM circuit checks**: TO-DO +- **Triggers downstream circuit operations**: TO-DO +- **Tag updates**: T[dstOffset:dstOffset+CONTRACT_INSTANCE_SIZE+1] = field +- **Bit-size**: 88 + + ### `EMITUNENCRYPTEDLOG` Emit an unencrypted log [See in table.](#isa-table-emitunencryptedlog) -- **Opcode**: 0x33 +- **Opcode**: 0x34 - **Category**: Accrued Substate - Logging - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. @@ -1616,7 +1664,7 @@ Send an L2-to-L1 message [See in table.](#isa-table-sendl2tol1msg) -- **Opcode**: 0x34 +- **Opcode**: 0x35 - **Category**: Accrued Substate - Messaging - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. @@ -1642,7 +1690,7 @@ Call into another contract [See in table.](#isa-table-call) -- **Opcode**: 0x35 +- **Opcode**: 0x36 - **Category**: Control Flow - Contract Calls - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. @@ -1690,7 +1738,7 @@ Call into another contract, disallowing World State and Accrued Substate modific [See in table.](#isa-table-staticcall) -- **Opcode**: 0x36 +- **Opcode**: 0x37 - **Category**: Control Flow - Contract Calls - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. @@ -1735,7 +1783,7 @@ Call into another contract, but keep the caller's `sender` and `storageAddress` [See in table.](#isa-table-delegatecall) -- **Opcode**: 0x37 +- **Opcode**: 0x38 - **Category**: Control Flow - Contract Calls - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. @@ -1780,7 +1828,7 @@ Halt execution within this context (without revert), optionally returning some d [See in table.](#isa-table-return) -- **Opcode**: 0x38 +- **Opcode**: 0x39 - **Category**: Control Flow - Contract Calls - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. @@ -1802,7 +1850,7 @@ Halt execution within this context as `reverted`, optionally returning some data [See in table.](#isa-table-revert) -- **Opcode**: 0x39 +- **Opcode**: 0x3a - **Category**: Control Flow - Contract Calls - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. diff --git a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js index 9eafdfcc205..5f1202d7079 100644 --- a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js +++ b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js @@ -1038,6 +1038,34 @@ T[existsOffset] = u8 T[dstOffset] = field `, }, + { + "id": "getcontractinstance", + "Name": "`GETCONTRACTINSTANCE`", + "Category": "Other", + "Flags": [ + {"name": "indirect", "description": INDIRECT_FLAG_DESCRIPTION}, + ], + "Args": [ + {"name": "addressOffset", "description": "memory offset of the contract instance address"}, + {"name": "dstOffset", "description": "location to write the contract instance information to"}, + ], + "Expression": ` +M[dstOffset:dstOffset+CONTRACT_INSTANCE_SIZE+1] = [ + instance_found_in_address, + instance.salt ?? 0, + instance.deployer ?? 0, + instance.contractClassId ?? 0, + instance.initializationHash ?? 0, + instance.portalContractAddress ?? 0, + instance.publicKeysHash ?? 0, +] +`, + "Summary": "Copies contract instance data to memory", + "Tag checks": "", + "Tag updates": "T[dstOffset:dstOffset+CONTRACT_INSTANCE_SIZE+1] = field", + "Additional AVM circuit checks": "TO-DO", + "Triggers downstream circuit operations": "TO-DO", + }, { "id": "emitunencryptedlog", "Name": "`EMITUNENCRYPTEDLOG`",