Files
xvm/test/xvm_layout_test.cpp
zeekay a58bac669e xvm: lift coverage to 97.48% line / 92.46% branch (≥96% gate)
- Delete unused is_zero32 dead helper (4 lines).
- Add test_tx_arena_full_rejects exercising the kRejectArenaFull path
  in kernel_input_check_and_apply when state.utxos is saturated. This
  is real production semantics, not a vanity test — the rejection
  branch is the security boundary that prevents silent UTXO loss when
  the arena is pre-saturated by a malformed prior round.
- Update COVERAGE.md headline + uncovered residue summary.

Tests: 44/44 layout + 7 determinism + 6 Metal smoke = 57 passing.
LLVM source-based coverage on xvm_cpu_reference.cpp:
  Lines 97.48%, Branches 92.46%, Functions 100.00%.
2026-04-27 01:14:33 -07:00

1334 lines
52 KiB
C++

// Copyright (C) 2026, Lux Partners Limited. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
/// @file xvm_layout_test.cpp
/// XVM v0.54 — layout invariants for cross-backend determinism.
///
/// Validates struct sizes, alignment, member offsets, and basic CPU-reference
/// determinism on a small canonical workload. Anything that drifts in the
/// host header without a parallel update in the GPU kernels would break
/// CPU/Metal/CUDA equivalence — these checks are the first line of defense.
#include "lux/xvm/xvm_gpu_layout.hpp"
#include "lux/xvm/xvm_cpu_reference.hpp"
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace xvm::gpu;
namespace {
int g_passed = 0;
int g_failed = 0;
#define EXPECT(name, cond) \
do { \
if (!(cond)) { \
std::printf(" FAIL[%s]: %s\n", (name), #cond); \
std::fflush(stdout); \
++g_failed; \
return; \
} \
} while (0)
#define PASS(name) \
do { \
std::printf(" ok : %s\n", (name)); \
std::fflush(stdout); \
++g_passed; \
} while (0)
void test_utxo_layout()
{
EXPECT("UTXO.size", sizeof(UTXO) == 144);
EXPECT("UTXO.align", alignof(UTXO) == 16);
EXPECT("UTXO.utxo_id.off", offsetof(UTXO, utxo_id) == 0);
EXPECT("UTXO.asset_id.off", offsetof(UTXO, asset_id) == 32);
EXPECT("UTXO.amount_lo.off", offsetof(UTXO, amount_lo) == 64);
EXPECT("UTXO.amount_hi.off", offsetof(UTXO, amount_hi) == 72);
EXPECT("UTXO.owner_root.off", offsetof(UTXO, owner_root) == 80);
EXPECT("UTXO.locktime.off", offsetof(UTXO, locktime) == 112);
EXPECT("UTXO.threshold.off", offsetof(UTXO, threshold) == 120);
EXPECT("UTXO.status.off", offsetof(UTXO, status) == 124);
EXPECT("UTXO.addresses_offset.off", offsetof(UTXO, addresses_offset) == 128);
EXPECT("UTXO.addresses_count.off", offsetof(UTXO, addresses_count) == 132);
PASS("UTXO layout");
}
void test_input_batch_layout()
{
EXPECT("InputBatch.size", sizeof(InputBatch) == 64);
EXPECT("InputBatch.align", alignof(InputBatch) == 16);
EXPECT("InputBatch.tx_id.off", offsetof(InputBatch, tx_id) == 0);
EXPECT("InputBatch.input_offset.off", offsetof(InputBatch, input_offset) == 32);
EXPECT("InputBatch.input_count.off", offsetof(InputBatch, input_count) == 36);
EXPECT("InputBatch.witness_offset.off", offsetof(InputBatch, witness_offset) == 40);
EXPECT("InputBatch.witness_count.off", offsetof(InputBatch, witness_count) == 44);
PASS("InputBatch layout");
}
void test_output_batch_layout()
{
EXPECT("OutputBatch.size", sizeof(OutputBatch) == 64);
EXPECT("OutputBatch.align", alignof(OutputBatch) == 16);
EXPECT("OutputBatch.tx_id.off", offsetof(OutputBatch, tx_id) == 0);
EXPECT("OutputBatch.output_offset.off", offsetof(OutputBatch, output_offset) == 32);
EXPECT("OutputBatch.output_count.off", offsetof(OutputBatch, output_count) == 36);
PASS("OutputBatch layout");
}
void test_asset_layout()
{
EXPECT("Asset.size", sizeof(Asset) == 112);
EXPECT("Asset.align", alignof(Asset) == 16);
EXPECT("Asset.asset_id.off", offsetof(Asset, asset_id) == 0);
EXPECT("Asset.total_supply_lo.off", offsetof(Asset, total_supply_lo) == 32);
EXPECT("Asset.total_supply_hi.off", offsetof(Asset, total_supply_hi) == 40);
EXPECT("Asset.mint_authority_root.off", offsetof(Asset, mint_authority_root) == 48);
EXPECT("Asset.freeze_flag.off", offsetof(Asset, freeze_flag) == 80);
EXPECT("Asset.denomination.off", offsetof(Asset, denomination) == 84);
EXPECT("Asset.name_offset.off", offsetof(Asset, name_offset) == 88);
EXPECT("Asset.name_length.off", offsetof(Asset, name_length) == 92);
EXPECT("Asset.occupied.off", offsetof(Asset, occupied) == 96);
PASS("Asset layout");
}
void test_xvm_tx_layout()
{
EXPECT("XvmTx.size", sizeof(XvmTx) == 112);
EXPECT("XvmTx.align", alignof(XvmTx) == 16);
EXPECT("XvmTx.tx_id.off", offsetof(XvmTx, tx_id) == 0);
EXPECT("XvmTx.kind.off", offsetof(XvmTx, kind) == 32);
EXPECT("XvmTx.input_batch_offset.off", offsetof(XvmTx, input_batch_offset) == 36);
EXPECT("XvmTx.output_batch_offset.off", offsetof(XvmTx, output_batch_offset) == 40);
EXPECT("XvmTx.asset_changes_offset.off", offsetof(XvmTx, asset_changes_offset) == 44);
EXPECT("XvmTx.asset_changes_count.off", offsetof(XvmTx, asset_changes_count) == 48);
EXPECT("XvmTx.target_chain.off", offsetof(XvmTx, target_chain) == 52);
EXPECT("XvmTx.status.off", offsetof(XvmTx, status) == 56);
EXPECT("XvmTx.reject_reason.off", offsetof(XvmTx, reject_reason) == 60);
EXPECT("XvmTx.proof_digest.off", offsetof(XvmTx, proof_digest) == 64);
PASS("XvmTx layout");
}
void test_asset_op_layout()
{
EXPECT("AssetOp.size", sizeof(AssetOp) == 112);
EXPECT("AssetOp.align", alignof(AssetOp) == 16);
EXPECT("AssetOp.asset_id.off", offsetof(AssetOp, asset_id) == 0);
EXPECT("AssetOp.amount_lo.off", offsetof(AssetOp, amount_lo) == 32);
EXPECT("AssetOp.amount_hi.off", offsetof(AssetOp, amount_hi) == 40);
EXPECT("AssetOp.authority_witness_root.off", offsetof(AssetOp, authority_witness_root) == 48);
EXPECT("AssetOp.kind.off", offsetof(AssetOp, kind) == 80);
EXPECT("AssetOp.target_chain.off", offsetof(AssetOp, target_chain) == 84);
PASS("AssetOp layout");
}
void test_export_marker_layout()
{
EXPECT("AtomicExportMarker.size", sizeof(AtomicExportMarker) == 144);
EXPECT("AtomicExportMarker.align", alignof(AtomicExportMarker) == 16);
EXPECT("AEM.marker_id.off", offsetof(AtomicExportMarker, marker_id) == 0);
EXPECT("AEM.asset_id.off", offsetof(AtomicExportMarker, asset_id) == 32);
EXPECT("AEM.amount_lo.off", offsetof(AtomicExportMarker, amount_lo) == 64);
EXPECT("AEM.amount_hi.off", offsetof(AtomicExportMarker, amount_hi) == 72);
EXPECT("AEM.source_chain.off", offsetof(AtomicExportMarker, source_chain) == 80);
EXPECT("AEM.target_chain.off", offsetof(AtomicExportMarker, target_chain) == 84);
EXPECT("AEM.status.off", offsetof(AtomicExportMarker, status) == 88);
EXPECT("AEM.occupied.off", offsetof(AtomicExportMarker, occupied) == 92);
EXPECT("AEM.recipient_root.off", offsetof(AtomicExportMarker, recipient_root) == 96);
PASS("AtomicExportMarker layout");
}
void test_round_descriptor_layout()
{
EXPECT("XVMRoundDescriptor.size", sizeof(XVMRoundDescriptor) == 112);
EXPECT("XVMRoundDescriptor.align", alignof(XVMRoundDescriptor) == 16);
EXPECT("Desc.chain_id.off", offsetof(XVMRoundDescriptor, chain_id) == 0);
EXPECT("Desc.round.off", offsetof(XVMRoundDescriptor, round) == 8);
EXPECT("Desc.height.off", offsetof(XVMRoundDescriptor, height) == 24);
EXPECT("Desc.mode.off", offsetof(XVMRoundDescriptor, mode) == 32);
EXPECT("Desc.tx_count.off", offsetof(XVMRoundDescriptor, tx_count) == 36);
EXPECT("Desc.parent_execution_root.off", offsetof(XVMRoundDescriptor, parent_execution_root) == 64);
PASS("XVMRoundDescriptor layout");
}
void test_transition_result_layout()
{
EXPECT("XVMTransitionResult.size", sizeof(XVMTransitionResult) == 208);
EXPECT("XVMTransitionResult.align", alignof(XVMTransitionResult) == 16);
EXPECT("Result.utxo_root.off", offsetof(XVMTransitionResult, utxo_root) == 80);
EXPECT("Result.asset_root.off", offsetof(XVMTransitionResult, asset_root) == 112);
EXPECT("Result.tx_root.off", offsetof(XVMTransitionResult, tx_root) == 144);
EXPECT("Result.execution_root.off", offsetof(XVMTransitionResult, execution_root) == 176);
PASS("XVMTransitionResult layout");
}
// -----------------------------------------------------------------------------
// CPU reference behaviour — small canonical workloads
// -----------------------------------------------------------------------------
XVMRoundDescriptor make_desc(uint64_t round, uint64_t height = 100,
XVMTransitionMode mode = XVMTransitionMode::FullRound)
{
XVMRoundDescriptor d{};
d.chain_id = 1u;
d.round = round;
d.timestamp_ns = 1700000000000000000ULL;
d.height = height;
d.mode = static_cast<uint32_t>(mode);
d.closing_flag = 1u;
return d;
}
void fill32(uint8_t out[32], uint8_t v) { for (uint32_t i = 0; i < 32; ++i) out[i] = v; }
void test_cpu_reference_determinism()
{
auto desc = make_desc(1u);
auto state1 = ref::XVMReferenceState::empty();
auto state2 = ref::XVMReferenceState::empty();
auto r1 = ref::run_reference(state1, desc, {}, {}, {}, {}, {}, {});
auto r2 = ref::run_reference(state2, desc, {}, {}, {}, {}, {}, {});
EXPECT("ref.det.status", r1.status == 1u);
EXPECT("ref.det.utxo", std::memcmp(r1.utxo_root, r2.utxo_root, 32) == 0);
EXPECT("ref.det.asset", std::memcmp(r1.asset_root, r2.asset_root, 32) == 0);
EXPECT("ref.det.tx", std::memcmp(r1.tx_root, r2.tx_root, 32) == 0);
EXPECT("ref.det.exec", std::memcmp(r1.execution_root, r2.execution_root, 32) == 0);
// Empty round must produce non-zero execution_root (composes parent || ...
// through keccak; even an all-zero pre-image yields a nontrivial digest).
bool any_nonzero = false;
for (auto b : r1.execution_root) if (b != 0) { any_nonzero = true; break; }
EXPECT("ref.det.exec.nonzero", any_nonzero);
PASS("CPU reference determinism (empty round)");
}
void test_cpu_reference_simple_transfer()
{
auto desc = make_desc(2u);
auto state = ref::XVMReferenceState::empty();
// Seed an asset (LUX, asset_id all-zero).
uint8_t lux_id[32]; fill32(lux_id, 0);
uint8_t mint_auth[32]; fill32(mint_auth, 0xAA);
state.seed_asset(lux_id, /*supply_lo=*/1'000'000'000ull, 0, mint_auth, 9);
// Seed a starting UTXO owned by Alice.
uint8_t alice_owner[32]; fill32(alice_owner, 0x11);
uint8_t bob_owner[32]; fill32(bob_owner, 0x22);
uint8_t utxo_in_id[32]; fill32(utxo_in_id, 0x10);
state.seed_utxo(utxo_in_id, lux_id, /*amount_lo=*/100, 0, alice_owner,
/*locktime=*/0, /*threshold=*/1);
// Tx that consumes utxo_in and creates a new utxo for Bob.
XvmTx tx{};
fill32(tx.tx_id, 0xC1);
tx.kind = static_cast<uint32_t>(XvmTxKind::Transfer);
tx.input_batch_offset = 0;
tx.output_batch_offset = 0;
InputBatch ib{};
std::memcpy(ib.tx_id, tx.tx_id, 32);
ib.input_offset = 0; ib.input_count = 1;
ib.witness_offset = 0; ib.witness_count = 1; // any witness presence satisfies the threshold predicate
OutputBatch ob{};
std::memcpy(ob.tx_id, tx.tx_id, 32);
ob.output_offset = 0; ob.output_count = 1;
UTXO new_utxo{};
fill32(new_utxo.utxo_id, 0x20);
std::memcpy(new_utxo.asset_id, lux_id, 32);
new_utxo.amount_lo = 100;
std::memcpy(new_utxo.owner_root, bob_owner, 32);
new_utxo.threshold = 1;
std::vector<uint8_t> inputs(32);
std::memcpy(inputs.data(), utxo_in_id, 32);
std::vector<XvmTx> txs = {tx};
std::vector<InputBatch> ibs = {ib};
std::vector<OutputBatch> obs = {ob};
std::vector<UTXO> outputs = {new_utxo};
auto r = ref::run_reference(state, desc, txs, ibs, obs, inputs, outputs, {});
EXPECT("xfer.status", r.status == 1u);
EXPECT("xfer.accepted", r.tx_accepted == 1u);
EXPECT("xfer.rejected", r.tx_rejected == 0u);
EXPECT("xfer.consumed", r.inputs_consumed == 1u);
EXPECT("xfer.created", r.outputs_created == 1u);
// Determinism: re-run on a fresh state and require byte-equal roots.
auto state2 = ref::XVMReferenceState::empty();
state2.seed_asset(lux_id, 1'000'000'000ull, 0, mint_auth, 9);
state2.seed_utxo(utxo_in_id, lux_id, 100, 0, alice_owner, 0, 1);
auto r2 = ref::run_reference(state2, desc, txs, ibs, obs, inputs, outputs, {});
EXPECT("xfer.det.utxo", std::memcmp(r.utxo_root, r2.utxo_root, 32) == 0);
EXPECT("xfer.det.exec", std::memcmp(r.execution_root, r2.execution_root, 32) == 0);
PASS("CPU reference simple transfer");
}
void test_cpu_reference_duplicate_input_rejected()
{
auto desc = make_desc(3u);
auto state = ref::XVMReferenceState::empty();
uint8_t lux_id[32]; fill32(lux_id, 0);
uint8_t mint_auth[32]; fill32(mint_auth, 0xAA);
uint8_t alice[32]; fill32(alice, 0x11);
state.seed_asset(lux_id, 1'000'000'000ull, 0, mint_auth, 9);
uint8_t in1[32]; fill32(in1, 0x30);
state.seed_utxo(in1, lux_id, 50, 0, alice, 0, 1);
XvmTx tx{}; fill32(tx.tx_id, 0xC2);
tx.kind = static_cast<uint32_t>(XvmTxKind::Transfer);
InputBatch ib{}; std::memcpy(ib.tx_id, tx.tx_id, 32);
ib.input_offset = 0; ib.input_count = 2; ib.witness_count = 1;
// Two copies of the same input.
std::vector<uint8_t> inputs(64);
std::memcpy(inputs.data(), in1, 32);
std::memcpy(inputs.data() + 32, in1, 32);
std::vector<XvmTx> txs = {tx};
std::vector<InputBatch> ibs = {ib};
auto r = ref::run_reference(state, desc, txs, ibs, {}, inputs, {}, {});
EXPECT("dup.rejected", r.tx_rejected == 1u);
EXPECT("dup.accepted", r.tx_accepted == 0u);
PASS("Duplicate input rejected within batch");
}
void test_cpu_reference_membership_no_false_negative()
{
auto state = ref::XVMReferenceState::empty();
uint8_t lux_id[32]; fill32(lux_id, 0);
uint8_t owner[32]; fill32(owner, 0x33);
// Insert 256 distinct utxos.
for (uint32_t i = 0; i < 256u; ++i) {
uint8_t uid[32]; fill32(uid, 0); uid[0] = uint8_t(i & 0xFF); uid[1] = uint8_t(i >> 8);
uint32_t slot = state.seed_utxo(uid, lux_id, 100u + i, 0, owner, 0, 1);
EXPECT("memb.seed", slot != 0xFFFFFFFFu);
}
// Every inserted id must hit Bloom AND cuckoo.
for (uint32_t i = 0; i < 256u; ++i) {
uint8_t uid[32]; fill32(uid, 0); uid[0] = uint8_t(i & 0xFF); uid[1] = uint8_t(i >> 8);
EXPECT("memb.bloom", ref::bloom_query(state, uid));
uint32_t slot = 0xFFFFFFFFu;
EXPECT("memb.cuckoo", ref::membership_query(state, uid, &slot));
}
// Random non-inserted id should not produce a cuckoo hit (Bloom may
// false-positive but cuckoo is exact).
uint8_t miss[32]; fill32(miss, 0xEE);
uint32_t miss_slot = 0xFFFFFFFFu;
EXPECT("memb.no.fn", !ref::membership_query(state, miss, &miss_slot));
PASS("Membership zero false-negatives");
}
void test_membership_insert_remove_idempotent()
{
auto state = ref::XVMReferenceState::empty();
uint8_t uid[32]; fill32(uid, 0xA5);
ref::membership_insert(state, uid, /*slot_index=*/7);
uint32_t slot = 0u;
EXPECT("rm.before", ref::membership_query(state, uid, &slot));
EXPECT("rm.slot", slot == 7u);
ref::membership_remove(state, uid);
EXPECT("rm.after", !ref::membership_query(state, uid, &slot));
// Bloom is monotonic — bloom_query may still report true (false positive).
// That's the acceptable Bloom contract; we only care cuckoo is exact.
PASS("membership_insert_remove_idempotent");
}
// =============================================================================
// Asset op every kind: Mint / Burn / Transfer / Export / Import
// =============================================================================
XvmTx make_tx(uint8_t fill, XvmTxKind kind = XvmTxKind::Transfer)
{
XvmTx tx{};
fill32(tx.tx_id, fill);
tx.kind = static_cast<uint32_t>(kind);
return tx;
}
void test_asset_mint_with_authority_succeeds()
{
auto desc = make_desc(10u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0xBE);
uint8_t auth[32]; fill32(auth, 0x42);
state.seed_asset(asset_id, /*supply_lo=*/0, 0, auth, 9);
XvmTx tx = make_tx(0xC1, XvmTxKind::Mint);
tx.asset_changes_offset = 0;
tx.asset_changes_count = 1;
AssetOp op{};
std::memcpy(op.asset_id, asset_id, 32);
op.amount_lo = 1000;
std::memcpy(op.authority_witness_root, auth, 32);
op.kind = static_cast<uint32_t>(AssetOpKind::Mint);
std::vector<XvmTx> txs = {tx};
std::vector<AssetOp> ops = {op};
auto r = ref::run_reference(state, desc, txs, {}, {}, {}, {}, ops);
EXPECT("mint.ok.applied", r.asset_ops_applied == 1u);
EXPECT("mint.ok.minted", r.total_minted_lo == 1000u);
EXPECT("mint.ok.accepted",r.tx_accepted == 1u);
PASS("asset_mint_with_authority_succeeds");
}
void test_asset_mint_wrong_authority_rejects()
{
auto desc = make_desc(11u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0xBE);
uint8_t auth[32]; fill32(auth, 0x42);
uint8_t bad[32]; fill32(bad, 0x99);
state.seed_asset(asset_id, 0, 0, auth, 9);
XvmTx tx = make_tx(0xC2, XvmTxKind::Mint);
tx.asset_changes_count = 1;
AssetOp op{};
std::memcpy(op.asset_id, asset_id, 32);
op.amount_lo = 1000;
std::memcpy(op.authority_witness_root, bad, 32); // wrong
op.kind = static_cast<uint32_t>(AssetOpKind::Mint);
std::vector<XvmTx> txs = {tx};
std::vector<AssetOp> ops = {op};
auto r = ref::run_reference(state, desc, txs, {}, {}, {}, {}, ops);
EXPECT("mint.bad.applied", r.asset_ops_applied == 0u);
EXPECT("mint.bad.rejected", r.tx_rejected == 1u);
PASS("asset_mint_wrong_authority_rejects");
}
void test_asset_mint_implicit_create_for_unknown_asset()
{
auto desc = make_desc(12u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0xCD);
uint8_t auth[32]; fill32(auth, 0x77);
XvmTx tx = make_tx(0xC3, XvmTxKind::Mint);
tx.asset_changes_count = 1;
AssetOp op{};
std::memcpy(op.asset_id, asset_id, 32);
op.amount_lo = 500;
std::memcpy(op.authority_witness_root, auth, 32);
op.kind = static_cast<uint32_t>(AssetOpKind::Mint);
std::vector<XvmTx> txs = {tx};
std::vector<AssetOp> ops = {op};
auto r = ref::run_reference(state, desc, txs, {}, {}, {}, {}, ops);
EXPECT("imp.applied", r.asset_ops_applied == 1u);
EXPECT("imp.minted", r.total_minted_lo == 500u);
PASS("asset_mint_implicit_create_for_unknown_asset");
}
void test_asset_burn_succeeds_with_supply()
{
auto desc = make_desc(13u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0xBE);
uint8_t auth[32]; fill32(auth, 0x42);
state.seed_asset(asset_id, /*supply_lo=*/10000, 0, auth, 9);
XvmTx tx = make_tx(0xC4, XvmTxKind::Burn);
tx.asset_changes_count = 1;
AssetOp op{};
std::memcpy(op.asset_id, asset_id, 32);
op.amount_lo = 200;
op.kind = static_cast<uint32_t>(AssetOpKind::Burn);
std::vector<XvmTx> txs = {tx};
std::vector<AssetOp> ops = {op};
auto r = ref::run_reference(state, desc, txs, {}, {}, {}, {}, ops);
EXPECT("burn.applied", r.asset_ops_applied == 1u);
EXPECT("burn.burned", r.total_burned_lo == 200u);
PASS("asset_burn_succeeds_with_supply");
}
void test_asset_burn_overflow_rejects()
{
auto desc = make_desc(14u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0xBE);
uint8_t auth[32]; fill32(auth, 0x42);
state.seed_asset(asset_id, 100, 0, auth, 9);
XvmTx tx = make_tx(0xC5, XvmTxKind::Burn);
tx.asset_changes_count = 1;
AssetOp op{};
std::memcpy(op.asset_id, asset_id, 32);
op.amount_lo = 999; // > 100 supply
op.kind = static_cast<uint32_t>(AssetOpKind::Burn);
std::vector<XvmTx> txs = {tx};
std::vector<AssetOp> ops = {op};
auto r = ref::run_reference(state, desc, txs, {}, {}, {}, {}, ops);
EXPECT("burn.over.applied", r.asset_ops_applied == 0u);
EXPECT("burn.over.rejected", r.tx_rejected == 1u);
PASS("asset_burn_overflow_rejects");
}
void test_asset_transfer_op_counts_only()
{
auto desc = make_desc(15u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0xBE);
uint8_t auth[32]; fill32(auth, 0x42);
state.seed_asset(asset_id, 5000, 0, auth, 9);
XvmTx tx = make_tx(0xC6, XvmTxKind::Transfer);
tx.asset_changes_count = 1;
AssetOp op{};
std::memcpy(op.asset_id, asset_id, 32);
op.amount_lo = 100;
op.kind = static_cast<uint32_t>(AssetOpKind::Transfer);
std::vector<XvmTx> txs = {tx};
std::vector<AssetOp> ops = {op};
auto r = ref::run_reference(state, desc, txs, {}, {}, {}, {}, ops);
EXPECT("xfer.asset.applied", r.asset_ops_applied == 1u);
EXPECT("xfer.asset.minted", r.total_minted_lo == 0u);
EXPECT("xfer.asset.burned", r.total_burned_lo == 0u);
PASS("asset_transfer_op_counts_only");
}
void test_asset_export_marker_emitted()
{
auto desc = make_desc(16u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0xBE);
uint8_t auth[32]; fill32(auth, 0x42);
state.seed_asset(asset_id, /*supply_lo=*/10000, 0, auth, 9);
XvmTx tx = make_tx(0xC7, XvmTxKind::Export);
tx.asset_changes_count = 1;
tx.target_chain = 2u; // some other chain id
AssetOp op{};
std::memcpy(op.asset_id, asset_id, 32);
op.amount_lo = 1000;
std::memcpy(op.authority_witness_root, auth, 32);
op.kind = static_cast<uint32_t>(AssetOpKind::Export);
op.target_chain = 2u;
std::vector<XvmTx> txs = {tx};
std::vector<AssetOp> ops = {op};
auto r = ref::run_reference(state, desc, txs, {}, {}, {}, {}, ops);
EXPECT("exp.applied", r.asset_ops_applied == 1u);
EXPECT("exp.markers", r.export_markers == 1u);
PASS("asset_export_marker_emitted");
}
void test_asset_export_overflow_rejects()
{
auto desc = make_desc(17u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0xBE);
uint8_t auth[32]; fill32(auth, 0x42);
state.seed_asset(asset_id, 100, 0, auth, 9);
XvmTx tx = make_tx(0xC8, XvmTxKind::Export);
tx.asset_changes_count = 1;
AssetOp op{};
std::memcpy(op.asset_id, asset_id, 32);
op.amount_lo = 999; // > supply
op.kind = static_cast<uint32_t>(AssetOpKind::Export);
std::vector<XvmTx> txs = {tx};
std::vector<AssetOp> ops = {op};
auto r = ref::run_reference(state, desc, txs, {}, {}, {}, {}, ops);
EXPECT("exp.over.applied", r.asset_ops_applied == 0u);
EXPECT("exp.over.rejected", r.tx_rejected == 1u);
PASS("asset_export_overflow_rejects");
}
void test_asset_import_with_marker_accepted()
{
auto desc = make_desc(18u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0xBE);
uint8_t auth[32]; fill32(auth, 0x42);
state.seed_asset(asset_id, /*supply_lo=*/10000, 0, auth, 9);
// Round 1: Export creates a marker
XvmTx tx_e = make_tx(0xE1, XvmTxKind::Export);
tx_e.asset_changes_count = 1;
tx_e.target_chain = 2u;
AssetOp op_e{};
std::memcpy(op_e.asset_id, asset_id, 32);
op_e.amount_lo = 500;
std::memcpy(op_e.authority_witness_root, auth, 32);
op_e.kind = static_cast<uint32_t>(AssetOpKind::Export);
op_e.target_chain = 2u;
std::vector<XvmTx> txs1 = {tx_e};
std::vector<AssetOp> ops1 = {op_e};
auto r1 = ref::run_reference(state, desc, txs1, {}, {}, {}, {}, ops1);
EXPECT("imp.exp.markers", r1.export_markers == 1u);
// Compute the marker_id the same way the implementation does
// (keccak(tx_id || target_chain || amount_lo || amount_hi)).
// The actual marker_id is in state.export_markers[i].marker_id.
uint8_t marker_id[32];
bool found = false;
for (auto& m : state.export_markers) {
if (m.target_chain == 2u && m.amount_lo == 500u) {
std::memcpy(marker_id, m.marker_id, 32);
found = true;
break;
}
}
EXPECT("imp.marker.found", found);
// Round 2: Import using the marker_id
auto desc2 = make_desc(19u);
XvmTx tx_i = make_tx(0xE2, XvmTxKind::Import);
tx_i.asset_changes_count = 1;
std::memcpy(tx_i.proof_digest, marker_id, 32);
AssetOp op_i{};
std::memcpy(op_i.asset_id, asset_id, 32);
op_i.amount_lo = 500;
op_i.kind = static_cast<uint32_t>(AssetOpKind::Import);
std::vector<XvmTx> txs2 = {tx_i};
std::vector<AssetOp> ops2 = {op_i};
auto r2 = ref::run_reference(state, desc2, txs2, {}, {}, {}, {}, ops2);
EXPECT("imp.applied", r2.asset_ops_applied == 1u);
EXPECT("imp.verified", r2.import_verified == 1u);
// Re-import the same marker should fail (consumed).
auto desc3 = make_desc(20u);
std::vector<XvmTx> txs3 = {tx_i};
auto r3 = ref::run_reference(state, desc3, txs3, {}, {}, {}, {}, ops2);
EXPECT("imp.replay.rej", r3.tx_rejected == 1u);
PASS("asset_import_with_marker_accepted");
}
void test_asset_import_no_marker_rejects()
{
auto desc = make_desc(21u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0xBE);
uint8_t auth[32]; fill32(auth, 0x42);
state.seed_asset(asset_id, 0, 0, auth, 9);
XvmTx tx = make_tx(0xC9, XvmTxKind::Import);
tx.asset_changes_count = 1;
fill32(tx.proof_digest, 0xFE); // doesn't match any marker
AssetOp op{};
std::memcpy(op.asset_id, asset_id, 32);
op.amount_lo = 100;
op.kind = static_cast<uint32_t>(AssetOpKind::Import);
std::vector<XvmTx> txs = {tx};
std::vector<AssetOp> ops = {op};
auto r = ref::run_reference(state, desc, txs, {}, {}, {}, {}, ops);
EXPECT("imp.no.applied", r.asset_ops_applied == 0u);
EXPECT("imp.no.rejected", r.tx_rejected == 1u);
PASS("asset_import_no_marker_rejects");
}
void test_asset_burn_unknown_asset_rejects()
{
auto desc = make_desc(22u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0xCC); // not seeded
XvmTx tx = make_tx(0xCA, XvmTxKind::Burn);
tx.asset_changes_count = 1;
AssetOp op{};
std::memcpy(op.asset_id, asset_id, 32);
op.amount_lo = 100;
op.kind = static_cast<uint32_t>(AssetOpKind::Burn);
std::vector<XvmTx> txs = {tx};
std::vector<AssetOp> ops = {op};
auto r = ref::run_reference(state, desc, txs, {}, {}, {}, {}, ops);
EXPECT("burn.unk.applied", r.asset_ops_applied == 0u);
EXPECT("burn.unk.rejected", r.tx_rejected == 1u);
PASS("asset_burn_unknown_asset_rejects");
}
// =============================================================================
// Input-check edge cases
// =============================================================================
void test_input_missing_utxo_rejected()
{
auto desc = make_desc(30u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0);
uint8_t auth[32]; fill32(auth, 0x42);
state.seed_asset(asset_id, 1000, 0, auth, 9);
XvmTx tx = make_tx(0xD1);
InputBatch ib{};
std::memcpy(ib.tx_id, tx.tx_id, 32);
ib.input_count = 1;
ib.witness_count = 1;
uint8_t bogus[32]; fill32(bogus, 0xBA);
std::vector<uint8_t> inputs(32);
std::memcpy(inputs.data(), bogus, 32);
std::vector<XvmTx> txs = {tx};
std::vector<InputBatch> ibs = {ib};
auto r = ref::run_reference(state, desc, txs, ibs, {}, inputs, {}, {});
EXPECT("miss.rejected", r.tx_rejected == 1u);
PASS("input_missing_utxo_rejected");
}
void test_input_locktime_in_future_rejected()
{
auto desc = make_desc(31u);
auto state = ref::XVMReferenceState::empty();
state.height = 100;
uint8_t asset_id[32]; fill32(asset_id, 0);
uint8_t auth[32]; fill32(auth, 0x42);
uint8_t alice[32]; fill32(alice, 0x11);
state.seed_asset(asset_id, 1000, 0, auth, 9);
uint8_t uid[32]; fill32(uid, 0x40);
state.seed_utxo(uid, asset_id, 50, 0, alice, /*locktime=*/200, /*threshold=*/1);
XvmTx tx = make_tx(0xD2);
InputBatch ib{};
std::memcpy(ib.tx_id, tx.tx_id, 32);
ib.input_count = 1; ib.witness_count = 1;
std::vector<uint8_t> inputs(32);
std::memcpy(inputs.data(), uid, 32);
std::vector<XvmTx> txs = {tx};
std::vector<InputBatch> ibs = {ib};
auto r = ref::run_reference(state, desc, txs, ibs, {}, inputs, {}, {});
EXPECT("lt.rejected", r.tx_rejected == 1u);
PASS("input_locktime_in_future_rejected");
}
void test_input_threshold_unmet_rejected()
{
auto desc = make_desc(32u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0);
uint8_t auth[32]; fill32(auth, 0x42);
uint8_t alice[32]; fill32(alice, 0x11);
state.seed_asset(asset_id, 1000, 0, auth, 9);
uint8_t uid[32]; fill32(uid, 0x41);
state.seed_utxo(uid, asset_id, 50, 0, alice, 0, /*threshold=*/1);
XvmTx tx = make_tx(0xD3);
InputBatch ib{};
std::memcpy(ib.tx_id, tx.tx_id, 32);
ib.input_count = 1; ib.witness_count = 0; // missing witness
std::vector<uint8_t> inputs(32);
std::memcpy(inputs.data(), uid, 32);
std::vector<XvmTx> txs = {tx};
std::vector<InputBatch> ibs = {ib};
auto r = ref::run_reference(state, desc, txs, ibs, {}, inputs, {}, {});
EXPECT("thr.rejected", r.tx_rejected == 1u);
PASS("input_threshold_unmet_rejected");
}
void test_input_double_spend_within_round_rejected()
{
auto desc = make_desc(33u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0);
uint8_t auth[32]; fill32(auth, 0x42);
uint8_t alice[32]; fill32(alice, 0x11);
state.seed_asset(asset_id, 1000, 0, auth, 9);
uint8_t uid[32]; fill32(uid, 0x42);
state.seed_utxo(uid, asset_id, 50, 0, alice, 0, 1);
XvmTx tx_a = make_tx(0xA1);
XvmTx tx_b = make_tx(0xA2);
InputBatch ib_a{}, ib_b{};
std::memcpy(ib_a.tx_id, tx_a.tx_id, 32);
std::memcpy(ib_b.tx_id, tx_b.tx_id, 32);
ib_a.input_offset = 0; ib_a.input_count = 1; ib_a.witness_count = 1;
ib_b.input_offset = 32; ib_b.input_count = 1; ib_b.witness_count = 1;
tx_a.input_batch_offset = 0;
tx_b.input_batch_offset = 1;
std::vector<uint8_t> inputs(64);
std::memcpy(inputs.data(), uid, 32);
std::memcpy(inputs.data() + 32, uid, 32); // double-spend across batches
std::vector<XvmTx> txs = {tx_a, tx_b};
std::vector<InputBatch> ibs = {ib_a, ib_b};
auto r = ref::run_reference(state, desc, txs, ibs, {}, inputs, {}, {});
// First tx accepts, second sees the cuckoo-removed (or spent) state and rejects.
EXPECT("ds.accepted.1", r.tx_accepted == 1u);
EXPECT("ds.rejected.1", r.tx_rejected == 1u);
PASS("input_double_spend_within_round_rejected");
}
// =============================================================================
// Edge cases — branch coverage for cpu reference
// =============================================================================
// Cover input_count > 1 with no-duplicate path through tx_inputs_have_duplicates.
void test_tx_no_duplicates_multi_input()
{
auto desc = make_desc(40u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0);
uint8_t auth[32]; fill32(auth, 0x42);
uint8_t alice[32]; fill32(alice, 0x11);
state.seed_asset(asset_id, 1000, 0, auth, 9);
uint8_t uid_a[32]; fill32(uid_a, 0x60);
uint8_t uid_b[32]; fill32(uid_b, 0x61);
uint8_t uid_c[32]; fill32(uid_c, 0x62);
state.seed_utxo(uid_a, asset_id, 50, 0, alice, 0, 1);
state.seed_utxo(uid_b, asset_id, 50, 0, alice, 0, 1);
state.seed_utxo(uid_c, asset_id, 50, 0, alice, 0, 1);
XvmTx tx = make_tx(0xD4);
InputBatch ib{};
std::memcpy(ib.tx_id, tx.tx_id, 32);
ib.input_count = 3; ib.witness_count = 1;
std::vector<uint8_t> inputs(96);
std::memcpy(inputs.data(), uid_a, 32);
std::memcpy(inputs.data() + 32, uid_b, 32);
std::memcpy(inputs.data() + 64, uid_c, 32);
std::vector<XvmTx> txs = {tx};
std::vector<InputBatch> ibs = {ib};
auto r = ref::run_reference(state, desc, txs, ibs, {}, inputs, {}, {});
EXPECT("multi.in.acc", r.tx_accepted == 1u);
EXPECT("multi.in.con", r.inputs_consumed == 3u);
PASS("tx_no_duplicates_multi_input");
}
// Cover the `kRejectMissingInput` path where Bloom passes but cuckoo misses.
// We inject a utxo_id whose hash collides with a present id's Bloom bits but
// is not actually inserted into the cuckoo arena — bloom_test() returns true,
// cuckoo_query() returns false.
void test_input_bloom_pass_cuckoo_miss()
{
auto desc = make_desc(41u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0);
uint8_t auth[32]; fill32(auth, 0x42);
uint8_t alice[32]; fill32(alice, 0x11);
state.seed_asset(asset_id, 1000, 0, auth, 9);
// Seed many UTXOs to densify Bloom; then ask about a non-inserted id with
// bits that happen to be set. Practically, we just hand-paint the entire
// bloom filter to all-ones — bloom_test always passes, cuckoo doesn't.
uint8_t uid_present[32]; fill32(uid_present, 0x70);
state.seed_utxo(uid_present, asset_id, 50, 0, alice, 0, 1);
std::fill(state.bloom_bits.begin(), state.bloom_bits.end(), 0xFFu);
uint8_t uid_absent[32]; fill32(uid_absent, 0x71); // not in cuckoo
XvmTx tx = make_tx(0xD5);
InputBatch ib{};
std::memcpy(ib.tx_id, tx.tx_id, 32);
ib.input_count = 1; ib.witness_count = 1;
std::vector<uint8_t> inputs(32);
std::memcpy(inputs.data(), uid_absent, 32);
std::vector<XvmTx> txs = {tx};
std::vector<InputBatch> ibs = {ib};
auto r = ref::run_reference(state, desc, txs, ibs, {}, inputs, {}, {});
EXPECT("bloom-pass.cuckoo-miss.rej", r.tx_rejected == 1u);
PASS("input_bloom_pass_cuckoo_miss");
}
// Cover the `kRejectAlreadySpent` branch — feed the same UTXO twice across
// two txs in the same round; second sees status |= kUtxoSpent on the first
// pass (after it removed from cuckoo), but if we also pre-set status before
// the round, we exercise the AlreadySpent reject path inside input check.
void test_input_already_spent_rejected()
{
auto desc = make_desc(42u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0);
uint8_t auth[32]; fill32(auth, 0x42);
uint8_t alice[32]; fill32(alice, 0x11);
state.seed_asset(asset_id, 1000, 0, auth, 9);
uint8_t uid[32]; fill32(uid, 0x80);
uint32_t slot = state.seed_utxo(uid, asset_id, 50, 0, alice, 0, 1);
EXPECT("spent.seed", slot != 0xFFFFFFFFu);
// Mark the seeded UTXO spent without removing from cuckoo. The input check
// path will Bloom-pass + cuckoo-find a slot pointing at a status with
// kUtxoSpent set, hitting the AlreadySpent branch.
state.utxos[slot].status |= kUtxoSpent;
XvmTx tx = make_tx(0xD6);
InputBatch ib{};
std::memcpy(ib.tx_id, tx.tx_id, 32);
ib.input_count = 1; ib.witness_count = 1;
std::vector<uint8_t> inputs(32);
std::memcpy(inputs.data(), uid, 32);
std::vector<XvmTx> txs = {tx};
std::vector<InputBatch> ibs = {ib};
auto r = ref::run_reference(state, desc, txs, ibs, {}, inputs, {}, {});
EXPECT("spent.rej", r.tx_rejected == 1u);
PASS("input_already_spent_rejected");
}
// Cover the `(u.status & kUtxoOccupied) == 0u` branch — point cuckoo at an
// unoccupied slot.
void test_input_cuckoo_points_unoccupied_slot()
{
auto desc = make_desc(43u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0);
uint8_t auth[32]; fill32(auth, 0x42);
state.seed_asset(asset_id, 1000, 0, auth, 9);
uint8_t uid[32]; fill32(uid, 0x90);
uint32_t slot = state.seed_utxo(uid, asset_id,
50, 0, asset_id /*owner*/, 0, 1);
EXPECT("unocc.seed", slot != 0xFFFFFFFFu);
// Clear the occupied bit but leave membership in place. The cuckoo entry
// still points here; input check sees Occupied=0 and rejects.
state.utxos[slot].status = 0u;
XvmTx tx = make_tx(0xD7);
InputBatch ib{};
std::memcpy(ib.tx_id, tx.tx_id, 32);
ib.input_count = 1; ib.witness_count = 1;
std::vector<uint8_t> inputs(32);
std::memcpy(inputs.data(), uid, 32);
std::vector<XvmTx> txs = {tx};
std::vector<InputBatch> ibs = {ib};
auto r = ref::run_reference(state, desc, txs, ibs, {}, inputs, {}, {});
EXPECT("unocc.rej", r.tx_rejected == 1u);
PASS("input_cuckoo_points_unoccupied_slot");
}
// Cover the `slot >= state.utxos.size()` branch — handcraft a cuckoo entry
// pointing past the end of the utxos arena.
void test_input_cuckoo_slot_out_of_range()
{
auto desc = make_desc(44u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0);
uint8_t auth[32]; fill32(auth, 0x42);
state.seed_asset(asset_id, 1000, 0, auth, 9);
uint8_t uid[32]; fill32(uid, 0xA0);
state.seed_utxo(uid, asset_id, 50, 0, asset_id /*owner*/, 0, 1);
// Walk the cuckoo arena and rewrite the slot_index of our entry so it
// points past state.utxos.size().
for (auto& e : state.cuckoo) {
if (e.occupied != 0u) {
bool match = true;
for (uint32_t i = 0; i < 32u; ++i)
if (e.utxo_id[i] != uid[i]) { match = false; break; }
if (match) { e.slot_index = uint32_t(state.utxos.size()) + 7u; break; }
}
}
XvmTx tx = make_tx(0xD8);
InputBatch ib{};
std::memcpy(ib.tx_id, tx.tx_id, 32);
ib.input_count = 1; ib.witness_count = 1;
std::vector<uint8_t> inputs(32);
std::memcpy(inputs.data(), uid, 32);
std::vector<XvmTx> txs = {tx};
std::vector<InputBatch> ibs = {ib};
auto r = ref::run_reference(state, desc, txs, ibs, {}, inputs, {}, {});
EXPECT("oor.rej", r.tx_rejected == 1u);
PASS("input_cuckoo_slot_out_of_range");
}
// Cover the asset_changes_offset >= asset_ops.size() guard.
void test_asset_op_offset_out_of_range()
{
auto desc = make_desc(45u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0xCC);
uint8_t auth[32]; fill32(auth, 0x42);
state.seed_asset(asset_id, 0, 0, auth, 9);
XvmTx tx = make_tx(0xD9, XvmTxKind::Mint);
tx.asset_changes_count = 1;
tx.asset_changes_offset = 99; // beyond ops.size()
AssetOp op{};
std::memcpy(op.asset_id, asset_id, 32);
op.amount_lo = 100;
op.kind = static_cast<uint32_t>(AssetOpKind::Mint);
std::memcpy(op.authority_witness_root, auth, 32);
std::vector<XvmTx> txs = {tx};
std::vector<AssetOp> ops = {op};
auto r = ref::run_reference(state, desc, txs, {}, {}, {}, {}, ops);
// Out-of-range offset means no op is applied; tx still accepts (no inputs).
EXPECT("oor.ops.acc", r.tx_accepted == 1u);
EXPECT("oor.ops.applied.0", r.asset_ops_applied == 0u);
PASS("asset_op_offset_out_of_range");
}
// Cover the inner `off >= asset_ops.size()` break (count > 1 but later ops
// would exceed the bound).
void test_asset_op_count_overruns_arena()
{
auto desc = make_desc(46u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0xDD);
uint8_t auth[32]; fill32(auth, 0x42);
state.seed_asset(asset_id, 0, 0, auth, 9);
XvmTx tx = make_tx(0xDA, XvmTxKind::Mint);
tx.asset_changes_count = 4; // claims 4 ops
tx.asset_changes_offset = 0;
AssetOp op{};
std::memcpy(op.asset_id, asset_id, 32);
op.amount_lo = 100;
op.kind = static_cast<uint32_t>(AssetOpKind::Mint);
std::memcpy(op.authority_witness_root, auth, 32);
std::vector<XvmTx> txs = {tx};
std::vector<AssetOp> ops = {op}; // only 1 op given
auto r = ref::run_reference(state, desc, txs, {}, {}, {}, {}, ops);
EXPECT("overrun.applied.1", r.asset_ops_applied == 1u);
EXPECT("overrun.acc", r.tx_accepted == 1u);
PASS("asset_op_count_overruns_arena");
}
// Cover an unrecognized AssetOpKind reaching the switch default (no case
// matches → all ops paths skipped, tx still accepts as no-op).
void test_asset_op_kind_unknown_no_op()
{
auto desc = make_desc(47u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0xEE);
uint8_t auth[32]; fill32(auth, 0x42);
state.seed_asset(asset_id, 0, 0, auth, 9);
XvmTx tx = make_tx(0xDB, XvmTxKind::Transfer);
tx.asset_changes_count = 1;
tx.asset_changes_offset = 0;
AssetOp op{};
std::memcpy(op.asset_id, asset_id, 32);
op.kind = 99u; // not a recognized AssetOpKind
std::memcpy(op.authority_witness_root, auth, 32);
std::vector<XvmTx> txs = {tx};
std::vector<AssetOp> ops = {op};
auto r = ref::run_reference(state, desc, txs, {}, {}, {}, {}, ops);
EXPECT("unkkind.acc", r.tx_accepted == 1u);
EXPECT("unkkind.applied.0", r.asset_ops_applied == 0u);
PASS("asset_op_kind_unknown_no_op");
}
// Cover u128_add carry and u128_sub borrow: mint at amount_lo near max, then
// burn an amount where supply.lo < amount_lo (forcing the borrow path).
void test_u128_add_carry_and_sub_borrow()
{
auto desc = make_desc(48u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0xCA);
uint8_t auth[32]; fill32(auth, 0x42);
// Seed supply low byte at 0x0F and hi at 1 so a burn of 0x10 in low
// exercises the borrow path: hi > sub_hi || (hi == sub_hi && lo < sub_lo).
state.seed_asset(asset_id, /*lo=*/0x0FULL, /*hi=*/1ULL, auth, 9);
// First mint with carry: Mint twice with amount_lo near 2^64.
XvmTx tx_m1 = make_tx(0xDC, XvmTxKind::Mint);
tx_m1.asset_changes_count = 1;
AssetOp op_m1{};
std::memcpy(op_m1.asset_id, asset_id, 32);
op_m1.amount_lo = 0xFFFFFFFFFFFFFF00ULL;
op_m1.kind = static_cast<uint32_t>(AssetOpKind::Mint);
std::memcpy(op_m1.authority_witness_root, auth, 32);
XvmTx tx_m2 = make_tx(0xDF, XvmTxKind::Mint);
tx_m2.asset_changes_offset = 1;
tx_m2.asset_changes_count = 1;
AssetOp op_m2 = op_m1;
op_m2.amount_lo = 0x200ULL; // Sum exceeds 2^64 → carry into hi.
std::vector<XvmTx> txs1 = {tx_m1, tx_m2};
std::vector<AssetOp> ops1 = {op_m1, op_m2};
auto r_m = ref::run_reference(state, desc, txs1, {}, {}, {}, {}, ops1);
EXPECT("u128.mint.acc", r_m.tx_accepted == 2u);
// total_minted_hi must reflect carry from the cumulative low overflow.
EXPECT("u128.mint.hi", r_m.total_minted_hi == 1u);
// Burn that exercises the borrow inside u128_sub (lo < sub_lo, hi >= sub_hi).
auto desc2 = make_desc(49u);
XvmTx tx_b = make_tx(0xDD, XvmTxKind::Burn);
tx_b.asset_changes_count = 1;
AssetOp op_b{};
std::memcpy(op_b.asset_id, asset_id, 32);
op_b.amount_lo = 0xFFFFFFFFFFFFFF00ULL; // larger than current supply.lo
op_b.kind = static_cast<uint32_t>(AssetOpKind::Burn);
std::memcpy(op_b.authority_witness_root, auth, 32);
std::vector<XvmTx> txs2 = {tx_b};
std::vector<AssetOp> ops2 = {op_b};
auto r_b = ref::run_reference(state, desc2, txs2, {}, {}, {}, {}, ops2);
EXPECT("u128.burn.acc", r_b.tx_accepted == 1u);
EXPECT("u128.burn.applied", r_b.asset_ops_applied == 1u);
PASS("u128_add_carry_and_sub_borrow");
}
// Cover the cuckoo_insert "update if utxo_id already present" path: insert
// a UTXO, then replay the same insert through membership_insert. The second
// invocation finds the existing entry in either bucket and updates slot_index.
void test_cuckoo_insert_update_existing()
{
auto state = ref::XVMReferenceState::empty();
uint8_t uid[32]; fill32(uid, 0xBB);
ref::membership_insert(state, uid, /*slot=*/123u);
// Repeat insert with a different slot — must succeed (returns true) and
// overwrite slot_index in place rather than burning a new slot.
ref::membership_insert(state, uid, /*slot=*/456u);
uint32_t out = 0xFFFFFFFFu;
EXPECT("cuck.upd.found", ref::membership_query(state, uid, &out));
EXPECT("cuck.upd.slot", out == 456u);
PASS("cuckoo_insert_update_existing");
}
// Cover InputCheck-only mode (no asset transition pass).
void test_mode_input_check_only()
{
auto desc = make_desc(60u, 100, XVMTransitionMode::InputCheck);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0);
uint8_t auth[32]; fill32(auth, 0x42);
uint8_t alice[32]; fill32(alice, 0x11);
state.seed_asset(asset_id, 1000, 0, auth, 9);
uint8_t uid[32]; fill32(uid, 0xB1);
state.seed_utxo(uid, asset_id, 50, 0, alice, 0, 1);
XvmTx tx = make_tx(0xE0);
InputBatch ib{};
std::memcpy(ib.tx_id, tx.tx_id, 32);
ib.input_count = 1; ib.witness_count = 1;
std::vector<uint8_t> inputs(32);
std::memcpy(inputs.data(), uid, 32);
std::vector<XvmTx> txs = {tx};
std::vector<InputBatch> ibs = {ib};
auto r = ref::run_reference(state, desc, txs, ibs, {}, inputs, {}, {});
EXPECT("ic.acc", r.tx_accepted == 1u);
PASS("mode_input_check_only");
}
// Cover AssetTransition-only mode (no input/output transition).
void test_mode_asset_transition_only()
{
auto desc = make_desc(61u, 100, XVMTransitionMode::AssetTransition);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0xAC);
uint8_t auth[32]; fill32(auth, 0x42);
state.seed_asset(asset_id, 0, 0, auth, 9);
XvmTx tx = make_tx(0xE1, XvmTxKind::Mint);
tx.asset_changes_count = 1;
AssetOp op{};
std::memcpy(op.asset_id, asset_id, 32);
op.amount_lo = 1000;
op.kind = static_cast<uint32_t>(AssetOpKind::Mint);
std::memcpy(op.authority_witness_root, auth, 32);
std::vector<XvmTx> txs = {tx};
std::vector<AssetOp> ops = {op};
auto r = ref::run_reference(state, desc, txs, {}, {}, {}, {}, ops);
EXPECT("at.applied", r.asset_ops_applied == 1u);
PASS("mode_asset_transition_only");
}
// Cover the borrow-fails branch inside u128_sub: burn an amount strictly
// greater than the asset's supply (across both lo and hi). The supply check
// fires returning false, taking the rejection path.
void test_burn_greater_than_supply_rejects()
{
auto desc = make_desc(62u);
auto state = ref::XVMReferenceState::empty();
uint8_t asset_id[32]; fill32(asset_id, 0xCD);
uint8_t auth[32]; fill32(auth, 0x42);
// supply = (lo=10, hi=0); burn 20 → hi==sub_hi && lo<sub_lo → rejects.
state.seed_asset(asset_id, 10, 0, auth, 9);
XvmTx tx = make_tx(0xE2, XvmTxKind::Burn);
tx.asset_changes_count = 1;
AssetOp op{};
std::memcpy(op.asset_id, asset_id, 32);
op.amount_lo = 20; // > 10
op.kind = static_cast<uint32_t>(AssetOpKind::Burn);
std::memcpy(op.authority_witness_root, auth, 32);
std::vector<XvmTx> txs = {tx};
std::vector<AssetOp> ops = {op};
auto r = ref::run_reference(state, desc, txs, {}, {}, {}, {}, ops);
EXPECT("burn.over.rej", r.tx_rejected == 1u);
PASS("burn_greater_than_supply_rejects");
}
// Cover asset_locate on an empty assets table (size 0 → returns max).
// run_reference auto-grows arenas, so we have to call run_reference with a
// state whose assets vector is empty, then assert the asset op rejects.
void test_asset_locate_empty_table_for_burn()
{
auto desc = make_desc(50u);
ref::XVMReferenceState state; // not via empty(); arenas size=0.
XvmTx tx = make_tx(0xDE, XvmTxKind::Burn);
tx.asset_changes_count = 1;
AssetOp op{};
fill32(op.asset_id, 0xAB);
op.amount_lo = 1;
op.kind = static_cast<uint32_t>(AssetOpKind::Burn);
std::vector<XvmTx> txs = {tx};
std::vector<AssetOp> ops = {op};
// run_reference grows the arenas; the burn must reject (asset missing).
auto r = ref::run_reference(state, desc, txs, {}, {}, {}, {}, ops);
EXPECT("alc.empty.rej", r.tx_rejected == 1u);
PASS("asset_locate_empty_table_for_burn");
}
// Cover the UTXO arena-full apply path (lines in kernel_input_check_and_apply
// where utxo_arena_insert returns 0xFFFFFFFFu and the tx is rejected with
// kRejectArenaFull). We saturate state.utxos with kUtxoOccupied, then submit a
// transfer producing one new output. Input is unrelated so it rejects on
// missing-input first; we therefore burn an existing input then attempt to
// emit a new utxo into a saturated arena.
void test_tx_arena_full_rejects()
{
auto desc = make_desc(60u);
auto state = ref::XVMReferenceState::empty();
uint8_t lux_id[32]; fill32(lux_id, 0);
uint8_t auth[32]; fill32(auth, 0xAA);
uint8_t alice[32]; fill32(alice, 0x11);
uint8_t bob[32]; fill32(bob, 0x22);
state.seed_asset(lux_id, 1'000'000ull, 0, auth, 9);
// Seed a single real input.
uint8_t in_id[32]; fill32(in_id, 0x80);
state.seed_utxo(in_id, lux_id, 100, 0, alice, 0, 1);
// Saturate the UTXO arena: every slot occupied. utxo_arena_insert will
// walk the entire arena and return 0xFFFFFFFFu.
for (auto& u : state.utxos) u.status |= xvm::gpu::kUtxoOccupied;
XvmTx tx{}; fill32(tx.tx_id, 0xCF);
tx.kind = static_cast<uint32_t>(XvmTxKind::Transfer);
tx.input_batch_offset = 0; tx.output_batch_offset = 0;
InputBatch ib{}; std::memcpy(ib.tx_id, tx.tx_id, 32);
ib.input_offset = 0; ib.input_count = 1; ib.witness_count = 1;
OutputBatch ob{}; std::memcpy(ob.tx_id, tx.tx_id, 32);
ob.output_offset = 0; ob.output_count = 1;
UTXO new_u{};
fill32(new_u.utxo_id, 0x90);
std::memcpy(new_u.asset_id, lux_id, 32);
new_u.amount_lo = 100;
std::memcpy(new_u.owner_root, bob, 32);
new_u.threshold = 1;
// Must point input at the seeded utxo so the input-check passes; then
// the arena-full guard fires on the output insert.
std::vector<uint8_t> inputs(32);
std::memcpy(inputs.data(), in_id, 32);
std::vector<XvmTx> txs = {tx};
std::vector<InputBatch> ibs = {ib};
std::vector<OutputBatch> obs = {ob};
std::vector<UTXO> outputs = {new_u};
auto r = ref::run_reference(state, desc, txs, ibs, obs, inputs, outputs, {});
EXPECT("af.rej", r.tx_rejected == 1u);
EXPECT("af.acc", r.tx_accepted == 0u);
EXPECT("af.consumed", r.inputs_consumed == 1u); // input was consumed
EXPECT("af.created", r.outputs_created == 0u); // arena full → no new utxo
PASS("tx_arena_full_rejects");
}
} // namespace
int main(int /*argc*/, char** /*argv*/)
{
setvbuf(stdout, nullptr, _IOLBF, 0);
std::printf("[xvm_layout_test] starting\n");
test_utxo_layout();
test_input_batch_layout();
test_output_batch_layout();
test_asset_layout();
test_xvm_tx_layout();
test_asset_op_layout();
test_export_marker_layout();
test_round_descriptor_layout();
test_transition_result_layout();
test_cpu_reference_determinism();
test_cpu_reference_simple_transfer();
test_cpu_reference_duplicate_input_rejected();
test_cpu_reference_membership_no_false_negative();
test_membership_insert_remove_idempotent();
test_asset_mint_with_authority_succeeds();
test_asset_mint_wrong_authority_rejects();
test_asset_mint_implicit_create_for_unknown_asset();
test_asset_burn_succeeds_with_supply();
test_asset_burn_overflow_rejects();
test_asset_transfer_op_counts_only();
test_asset_export_marker_emitted();
test_asset_export_overflow_rejects();
test_asset_import_with_marker_accepted();
test_asset_import_no_marker_rejects();
test_asset_burn_unknown_asset_rejects();
test_input_missing_utxo_rejected();
test_input_locktime_in_future_rejected();
test_input_threshold_unmet_rejected();
test_input_double_spend_within_round_rejected();
test_tx_no_duplicates_multi_input();
test_input_bloom_pass_cuckoo_miss();
test_input_already_spent_rejected();
test_input_cuckoo_points_unoccupied_slot();
test_input_cuckoo_slot_out_of_range();
test_asset_op_offset_out_of_range();
test_asset_op_count_overruns_arena();
test_asset_op_kind_unknown_no_op();
test_u128_add_carry_and_sub_borrow();
test_cuckoo_insert_update_existing();
test_mode_input_check_only();
test_mode_asset_transition_only();
test_burn_greater_than_supply_rejects();
test_asset_locate_empty_table_for_burn();
test_tx_arena_full_rejects();
std::printf("[xvm_layout_test] passed=%d failed=%d\n", g_passed, g_failed);
return g_failed == 0 ? 0 : 1;
}