test(builder): close PR-9 review debt - datum-kind pin, empty-string datum/redeemer, boundary scans scripts/

This commit is contained in:
FilipBl4gojevic
2026-07-29 16:24:19 +02:00
parent d822a47177
commit d240d3f26e
2 changed files with 52 additions and 0 deletions

View File

@@ -12,6 +12,7 @@ import { resolve, join, relative } from 'node:path';
* non-custodial split; growing it should be impossible to do accidentally.
*/
const SRC_ROOT = resolve(import.meta.dirname!, '../src');
const SCRIPTS_ROOT = resolve(import.meta.dirname!, '../scripts');
const CUSTODIAL_UNTIL_LATER_PR = new Set([
'vector/agent-network.ts', // family 2 — goes keyless in spec PR 7
'vector/self-improvement.ts', // family 3 — goes keyless in spec PR 8
@@ -46,6 +47,16 @@ describe('builder custody boundary', () => {
if (FORBIDDEN.test(line)) offenders.push(`${rel}:${i + 1}: ${line.trim()}`);
});
}
// scripts/ is dev-tooling, not shipped builder source - it has no
// custodial carve-out, so nothing here is ever checked against the
// allowlist (unlike SRC_ROOT files above).
for (const file of sourceFiles(SCRIPTS_ROOT)) {
const rel = relative(SCRIPTS_ROOT, file).replace(/\\/g, '/');
const lines = readFileSync(file, 'utf-8').split('\n');
lines.forEach((line, i) => {
if (FORBIDDEN.test(line)) offenders.push(`scripts/${rel}:${i + 1}: ${line.trim()}`);
});
}
assert.deepEqual(offenders, [], `key-material vocabulary found in keyless builder source:\n${offenders.join('\n')}`);
});

View File

@@ -66,6 +66,14 @@ describe('buildDeployContract', () => {
const vkeys = CML.Transaction.from_cbor_hex(r.txCbor).witness_set().vkeywitnesses();
assert.ok(vkeys === undefined || vkeys.len() === 0);
});
test("deploy with initialDatum '' fails loudly instead of silently voiding", async () => {
// '' is falsy but caller-supplied; ?? must let it flow through and fail
// loudly downstream rather than defaulting to Data.void().
await assert.rejects(buildDeployContract(lucid, {
scriptCbor: ALWAYS_SUCCEEDS_V2, scriptType: 'PlutusV2', initialDatum: '',
}));
});
});
describe('buildInteractContract', () => {
@@ -89,6 +97,24 @@ describe('buildInteractContract', () => {
}));
});
test('lock datum is inline (kind), not a hash reference', async () => {
const r = await buildInteractContract(lucid, {
scriptCbor: ALWAYS_SUCCEEDS_V2, scriptType: 'PlutusV2', action: 'lock',
changeAddress: OWN_ADDRESS, lovelaceAmount: 2_000_000, datum: 'd87980',
});
const tx = CML.Transaction.from_cbor_hex(r.txCbor);
const outs = tx.body().outputs();
let inlineSeen = false;
for (let i = 0; i < outs.len(); i++) {
const d = outs.get(i).datum();
// CML DatumOptionKind: Hash = 0, Datum = 1 (verified against the
// package's own .d.ts) - kind() === 1 is an inline datum.
if (d && d.kind() === 1) inlineSeen = true;
if (d && d.kind() === 0) assert.fail('lock produced a datum HASH output - must be inline');
}
assert.ok(inlineSeen, 'no inline-datum output found on the lock tx');
});
test('rejects an invalid action', async () => {
await assert.rejects(
() => buildInteractContract(lucid, {
@@ -172,4 +198,19 @@ describe('buildInteractContract', () => {
/not at script address/,
);
});
test("spend: rejects an empty-string redeemer instead of silently treating it as void", async () => {
// '' is falsy but caller-supplied; ?? must let it flow through and fail
// loudly downstream rather than defaulting to Data.void().
const scriptUtxo: UTxO = {
txHash: FIXTURE_TXHASH, outputIndex: 7, address: SCRIPT_ADDRESS,
assets: { lovelace: 2_000_000n }, datum: 'd87980',
};
const withScript = new FixtureProvider([...FIXTURE_UTXOS, scriptUtxo]);
const l = await lucidForAddress(withScript, OWN_ADDRESS);
await assert.rejects(buildInteractContract(l, {
scriptCbor: ALWAYS_SUCCEEDS_V2, scriptType: 'PlutusV2', action: 'spend',
changeAddress: OWN_ADDRESS, redeemer: '',
}));
});
});