From 9344d192afb594241b1bbdd2aaa090d365b3f884 Mon Sep 17 00:00:00 2001 From: jeanmon Date: Tue, 21 Apr 2026 08:54:11 +0000 Subject: [PATCH] AVM integration tests for truncation --- .../avm_check_circuit_custom_bc.test.ts | 26 ++++ .../public/fixtures/custom_bytecode_tests.ts | 140 +++++++++++++++++- .../apps_tests/custom_bc.test.ts | 34 +++++ 3 files changed, 199 insertions(+), 1 deletion(-) diff --git a/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit_custom_bc.test.ts b/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit_custom_bc.test.ts index c2f720db3d8b..eb86d7596105 100644 --- a/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit_custom_bc.test.ts +++ b/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit_custom_bc.test.ts @@ -3,6 +3,7 @@ import { addressingWithIndirectTagIssueTest, addressingWithIndirectThenRelativeTagIssueTest, addressingWithRelativeOverflowAndIndirectTagIssueTest, + castTruncationTest, defaultGlobals, instructionTruncatedTest, invalidByteTest, @@ -10,6 +11,7 @@ import { invalidTagValueAndInstructionTruncatedTest, invalidTagValueTest, pcOutOfRangeTest, + setTruncationTest, } from '@aztec/simulator/public/fixtures'; import { NativeWorldStateService } from '@aztec/world-state'; @@ -97,3 +99,27 @@ describe('AVM bytecode flow unhappy paths', () => { expect(result.revertCode.isOK()).toBe(false); }); }); + +describe('AVM custom bytecodes truncation', () => { + let tester: AvmProvingTester; + let worldStateService: NativeWorldStateService; + + beforeEach(async () => { + worldStateService = await NativeWorldStateService.tmp(); + tester = await AvmProvingTester.new(worldStateService, /*checkCircuitOnly*/ true, /*globals=*/ defaultGlobals()); + }); + + afterEach(async () => { + await worldStateService.close(); + }); + + it('SET truncation to narrower target tags', async () => { + const result = await setTruncationTest(tester); + expect(result.revertCode.isOK()).toBe(true); + }, 20_000); + + it('CAST truncation to narrower target tags', async () => { + const result = await castTruncationTest(tester); + expect(result.revertCode.isOK()).toBe(true); + }, 20_000); +}); diff --git a/yarn-project/simulator/src/public/fixtures/custom_bytecode_tests.ts b/yarn-project/simulator/src/public/fixtures/custom_bytecode_tests.ts index 220432369f85..237d8dc6d4e6 100644 --- a/yarn-project/simulator/src/public/fixtures/custom_bytecode_tests.ts +++ b/yarn-project/simulator/src/public/fixtures/custom_bytecode_tests.ts @@ -2,7 +2,7 @@ import { strict as assert } from 'assert'; import { TypeTag } from '../avm/avm_memory_types.js'; import { Addressing, AddressingMode } from '../avm/opcodes/addressing_mode.js'; -import { Add, CalldataCopy, Jump, Return, Set } from '../avm/opcodes/index.js'; +import { Add, CalldataCopy, Cast, Jump, Return, Set } from '../avm/opcodes/index.js'; import { encodeToBytecode } from '../avm/serialization/bytecode_serialization.js'; import { MAX_OPCODE_VALUE, @@ -208,6 +208,144 @@ export async function invalidTagValueAndInstructionTruncatedTest(tester: PublicT return await deployAndExecuteCustomBytecode(bytecode, tester, txLabel); } +// Exercise SET truncation: set values whose widths exceed the target tag and +// rely on `buildFromTagTruncating` to truncate to the low bits of the tag. +// Covers sources larger than 128 bits (via SET_FF) and sources in (32, 128] +// bits (via SET_64) against destination tags U1/U8/U16/U32/U64/U128. +export async function setTruncationTest(tester: PublicTxSimulationTester) { + // 200-bit value: forces truncation for every target tag up to U128. + const LARGE_FIELD_VALUE = (1n << 200n) + 0x1234567890abcdef1234567890abcdefn; + // 40-bit value: forces truncation for target tags up to U32. + const LARGE_U64_VALUE = (1n << 40n) + 0xdeadbeefn; + + const bytecode = encodeToBytecode([ + // Zero U32 at offset 0 — used as the Return copy-size slot. + new Set(/*addressing_mode=*/ 0, /*dstOffset=*/ 0, TypeTag.UINT32, /*value=*/ 0).as(Opcode.SET_8, Set.wireFormat8), + + // Source >128 bits (via SET_FF) truncated to smaller target tags. + new Set(/*addressing_mode=*/ 0, /*dstOffset=*/ 1, TypeTag.UINT128, LARGE_FIELD_VALUE).as( + Opcode.SET_FF, + Set.wireFormatFF, + ), + new Set(/*addressing_mode=*/ 0, /*dstOffset=*/ 2, TypeTag.UINT64, LARGE_FIELD_VALUE).as( + Opcode.SET_FF, + Set.wireFormatFF, + ), + new Set(/*addressing_mode=*/ 0, /*dstOffset=*/ 3, TypeTag.UINT32, LARGE_FIELD_VALUE).as( + Opcode.SET_FF, + Set.wireFormatFF, + ), + new Set(/*addressing_mode=*/ 0, /*dstOffset=*/ 4, TypeTag.UINT16, LARGE_FIELD_VALUE).as( + Opcode.SET_FF, + Set.wireFormatFF, + ), + new Set(/*addressing_mode=*/ 0, /*dstOffset=*/ 5, TypeTag.UINT8, LARGE_FIELD_VALUE).as( + Opcode.SET_FF, + Set.wireFormatFF, + ), + new Set(/*addressing_mode=*/ 0, /*dstOffset=*/ 6, TypeTag.UINT1, LARGE_FIELD_VALUE).as( + Opcode.SET_FF, + Set.wireFormatFF, + ), + + // Source in (32, 128] bits (via SET_64) truncated to smaller target tags. + new Set(/*addressing_mode=*/ 0, /*dstOffset=*/ 7, TypeTag.UINT32, LARGE_U64_VALUE).as( + Opcode.SET_64, + Set.wireFormat64, + ), + new Set(/*addressing_mode=*/ 0, /*dstOffset=*/ 8, TypeTag.UINT16, LARGE_U64_VALUE).as( + Opcode.SET_64, + Set.wireFormat64, + ), + new Set(/*addressing_mode=*/ 0, /*dstOffset=*/ 9, TypeTag.UINT8, LARGE_U64_VALUE).as( + Opcode.SET_64, + Set.wireFormat64, + ), + new Set(/*addressing_mode=*/ 0, /*dstOffset=*/ 10, TypeTag.UINT1, LARGE_U64_VALUE).as( + Opcode.SET_64, + Set.wireFormat64, + ), + + new Return(/*addressing_mode=*/ 0, /*copySizeOffset=*/ 0, /*returnOffset=*/ 0), + ]); + + const txLabel = 'SetTruncation'; + return await deployAndExecuteCustomBytecode(bytecode, tester, txLabel); +} + +// Exercise CAST truncation: store a wide source value in memory then CAST it +// to smaller destination tags. Covers sources larger than 128 bits (FIELD +// source) and sources in (32, 128] bits (UINT64 source) against destination +// tags U1/U8/U16/U32/U64/U128. +export async function castTruncationTest(tester: PublicTxSimulationTester) { + // 200-bit source: stored as FIELD so that CASTs to any integer tag truncate. + const LARGE_FIELD_VALUE = (1n << 200n) + 0x1234567890abcdef1234567890abcdefn; + // 40-bit source: stored as UINT64 so CASTs to U1/U8/U16/U32 truncate. + const LARGE_U64_VALUE = (1n << 40n) + 0xdeadbeefn; + + const bytecode = encodeToBytecode([ + // Zero U32 at offset 0 — used as the Return copy-size slot. + new Set(/*addressing_mode=*/ 0, /*dstOffset=*/ 0, TypeTag.UINT32, /*value=*/ 0).as(Opcode.SET_8, Set.wireFormat8), + + // Store wide FIELD source at offset 10, then CAST to smaller tags. + new Set(/*addressing_mode=*/ 0, /*dstOffset=*/ 10, TypeTag.FIELD, LARGE_FIELD_VALUE).as( + Opcode.SET_FF, + Set.wireFormatFF, + ), + new Cast(/*addressing_mode=*/ 0, /*srcOffset=*/ 10, /*dstOffset=*/ 11, TypeTag.UINT128).as( + Opcode.CAST_8, + Cast.wireFormat8, + ), + new Cast(/*addressing_mode=*/ 0, /*srcOffset=*/ 10, /*dstOffset=*/ 12, TypeTag.UINT64).as( + Opcode.CAST_8, + Cast.wireFormat8, + ), + new Cast(/*addressing_mode=*/ 0, /*srcOffset=*/ 10, /*dstOffset=*/ 13, TypeTag.UINT32).as( + Opcode.CAST_8, + Cast.wireFormat8, + ), + new Cast(/*addressing_mode=*/ 0, /*srcOffset=*/ 10, /*dstOffset=*/ 14, TypeTag.UINT16).as( + Opcode.CAST_8, + Cast.wireFormat8, + ), + new Cast(/*addressing_mode=*/ 0, /*srcOffset=*/ 10, /*dstOffset=*/ 15, TypeTag.UINT8).as( + Opcode.CAST_8, + Cast.wireFormat8, + ), + new Cast(/*addressing_mode=*/ 0, /*srcOffset=*/ 10, /*dstOffset=*/ 16, TypeTag.UINT1).as( + Opcode.CAST_8, + Cast.wireFormat8, + ), + + // Store UINT64 source at offset 20, then CAST to smaller integer tags. + new Set(/*addressing_mode=*/ 0, /*dstOffset=*/ 20, TypeTag.UINT64, LARGE_U64_VALUE).as( + Opcode.SET_64, + Set.wireFormat64, + ), + new Cast(/*addressing_mode=*/ 0, /*srcOffset=*/ 20, /*dstOffset=*/ 21, TypeTag.UINT32).as( + Opcode.CAST_8, + Cast.wireFormat8, + ), + new Cast(/*addressing_mode=*/ 0, /*srcOffset=*/ 20, /*dstOffset=*/ 22, TypeTag.UINT16).as( + Opcode.CAST_8, + Cast.wireFormat8, + ), + new Cast(/*addressing_mode=*/ 0, /*srcOffset=*/ 20, /*dstOffset=*/ 23, TypeTag.UINT8).as( + Opcode.CAST_8, + Cast.wireFormat8, + ), + new Cast(/*addressing_mode=*/ 0, /*srcOffset=*/ 20, /*dstOffset=*/ 24, TypeTag.UINT1).as( + Opcode.CAST_8, + Cast.wireFormat8, + ), + + new Return(/*addressing_mode=*/ 0, /*copySizeOffset=*/ 0, /*returnOffset=*/ 0), + ]); + + const txLabel = 'CastTruncation'; + return await deployAndExecuteCustomBytecode(bytecode, tester, txLabel); +} + /** * Returns the offset of the tag in an instruction. * @details Loops over the wire format operand type entries until it finds the tag. diff --git a/yarn-project/simulator/src/public/public_tx_simulator/apps_tests/custom_bc.test.ts b/yarn-project/simulator/src/public/public_tx_simulator/apps_tests/custom_bc.test.ts index 0e1a4f213eb3..b3d61ec8d66e 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator/apps_tests/custom_bc.test.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator/apps_tests/custom_bc.test.ts @@ -7,12 +7,14 @@ import { import { NativeWorldStateService } from '@aztec/world-state/native'; import { + castTruncationTest, instructionTruncatedTest, invalidByteTest, invalidOpcodeTest, invalidTagValueAndInstructionTruncatedTest, invalidTagValueTest, pcOutOfRangeTest, + setTruncationTest, } from '../../fixtures/custom_bytecode_tests.js'; import { PublicTxSimulationTester } from '../../fixtures/public_tx_simulation_tester.js'; @@ -114,3 +116,35 @@ describe.each([ expect(result.revertCode.isOK()).toBe(false); }); }); + +describe.each([ + { useCppSimulator: false, simulatorName: 'TS Simulator' }, + { useCppSimulator: true, simulatorName: 'Cpp Simulator' }, +])('Public TX simulator apps tests: custom bytecodes truncation ($simulatorName)', ({ useCppSimulator }) => { + let worldStateService: NativeWorldStateService; + let tester: PublicTxSimulationTester; + + beforeEach(async () => { + worldStateService = await NativeWorldStateService.tmp(); + tester = await PublicTxSimulationTester.create( + worldStateService, + /*globals=*/ undefined, + /*metrics=*/ undefined, + useCppSimulator, + ); + }); + + afterEach(async () => { + await worldStateService.close(); + }); + + it('SET truncation to narrower target tags', async () => { + const result = await setTruncationTest(tester); + expect(result.revertCode.isOK()).toBe(true); + }); + + it('CAST truncation to narrower target tags', async () => { + const result = await castTruncationTest(tester); + expect(result.revertCode.isOK()).toBe(true); + }); +});