Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import {
addressingWithIndirectTagIssueTest,
addressingWithIndirectThenRelativeTagIssueTest,
addressingWithRelativeOverflowAndIndirectTagIssueTest,
castTruncationTest,
defaultGlobals,
instructionTruncatedTest,
invalidByteTest,
invalidOpcodeTest,
invalidTagValueAndInstructionTruncatedTest,
invalidTagValueTest,
pcOutOfRangeTest,
setTruncationTest,
} from '@aztec/simulator/public/fixtures';
import { NativeWorldStateService } from '@aztec/world-state';

Expand Down Expand Up @@ -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);
});
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Comment on lines +211 to +216

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly is this testing? The AVM? Or buildFromTagTruncating in TS?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goal is to test the AVM circuit as a whole in the presence of truncation performed by CAST and or SET. The rationale about adding this test is a recent change which removed the possibility to pass larger values than their type in the bulk test. To still keep the same testing coverage, I opened a ticket which resulted into the present PR. I made it a bit more exhaustive by testing more target types and covering both SET and CAST.

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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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);
});
});
Loading