Environment variables
This commit is contained in:
+57
-1
@@ -11,6 +11,9 @@ import { CUSTODY_POLICY, MOCK_CREDIT_ATOMIC, custodyCommandSchema, custodyMessag
|
||||
type CustodyWithdrawal, type CustodyCommand } from '../shared/custody.ts';
|
||||
import { NETWORK } from '../shared/testnet.ts';
|
||||
import { address, CHAIN_ID } from '../server/testnet/consensus.ts';
|
||||
import { operatorMarketPolicy, validateMarketPolicy, type MarketPolicy } from '../server/market/policy.ts';
|
||||
import { ADDITIONAL_MARKET_FEE } from '../shared/market.ts';
|
||||
import { keccak } from 'hash-wasm';
|
||||
|
||||
function identity() {
|
||||
const keys = generateKeyPairSync('ec', { namedCurve: 'prime256v1' });
|
||||
@@ -42,9 +45,62 @@ async function fixture(t: { after(fn: () => Promise<void>): void }) {
|
||||
priceAtomicXmr: l.priceAtomicXmr, feeBps: l.feeBps });
|
||||
return { dir, envelope, execute, activity, credit, listing, reserve, get market() { return market; },
|
||||
changeCard: () => { present = false; }, advance: (ms: number) => { now += ms; },
|
||||
restart: async () => { market.close(); market = await CustodyMarket.open(dir, options); } };
|
||||
restart: async (policy: MarketPolicy = {}) => { market.close(); market = await CustodyMarket.open(dir, { ...options, ...policy }); } };
|
||||
}
|
||||
|
||||
async function feeAddress() {
|
||||
const prefix = Uint8Array.from([18, ...Array(64).fill(2)]);
|
||||
const data = Buffer.concat([prefix, Buffer.from((await keccak(prefix, 256)).slice(0, 8), 'hex')]);
|
||||
const alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
||||
const sizes: Record<number, number> = { 1: 2, 2: 3, 3: 5, 4: 6, 5: 7, 6: 9, 7: 10, 8: 11 };
|
||||
let encoded = '';
|
||||
for (let i = 0; i < data.length; i += 8) {
|
||||
const chunk = data.subarray(i, i + 8); let n = BigInt(`0x${chunk.toString('hex')}`), text = '';
|
||||
while (n) { text = alphabet[Number(n % 58n)] + text; n /= 58n; }
|
||||
encoded += text.padStart(sizes[chunk.length], '1');
|
||||
}
|
||||
return encoded;
|
||||
}
|
||||
test('operator market switches are strict and host fee defaults to fixed author with checksum validation', async () => {
|
||||
assert.deepEqual(operatorMarketPolicy({}), { marketEnabled: true, tradingEnabled: true, hostFeeAddress: ADDITIONAL_MARKET_FEE.address });
|
||||
for (const v of ['', 'true', 'false', 'yes', '2']) {
|
||||
assert.throws(() => operatorMarketPolicy({ WIKIDECK_MARKET_ENABLED: v }));
|
||||
assert.throws(() => operatorMarketPolicy({ WIKIDECK_MARKET_TRADING_ENABLED: v }));
|
||||
}
|
||||
await validateMarketPolicy(operatorMarketPolicy({}));
|
||||
await assert.rejects(validateMarketPolicy({ hostFeeAddress: '4'.repeat(95) }), /checksum|network/);
|
||||
});
|
||||
test('market disable hides public listings and rejects new business but retains private balances and safe release', async t => {
|
||||
const f = await fixture(t); f.credit(); const l = f.listing(), o = f.reserve(l);
|
||||
const w = f.execute<CustodyWithdrawal>(buyer, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '100', maxFeeAtomicXmr: '1' });
|
||||
await f.restart({ marketEnabled: false });
|
||||
assert.deepEqual(f.market.browse().listings, []); assert.equal(f.market.info().marketEnabled, false);
|
||||
assert.throws(() => f.listing('2'), /disabled/);
|
||||
assert.throws(() => f.credit(stranger), /disabled/);
|
||||
f.execute(buyer, { type: 'cancel-reservation', orderId: o.id });
|
||||
f.execute(buyer, { type: 'cancel-withdrawal', withdrawalId: w.id });
|
||||
assert.equal(f.activity().availableAtomicXmr, MOCK_CREDIT_ATOMIC);
|
||||
const withdrawal = f.execute<CustodyWithdrawal>(buyer, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '1', maxFeeAtomicXmr: '0' });
|
||||
assert.equal(withdrawal.status, 'reserved');
|
||||
});
|
||||
test('paused trading keeps browse and cancellations available, and fee recipients are signed and snapshotted', async t => {
|
||||
const f = await fixture(t), recipient = await feeAddress(); f.credit();
|
||||
await f.restart({ hostFeeAddress: recipient });
|
||||
assert.throws(() => f.listing(), /recipient/);
|
||||
const action = { type: 'list' as const, cardHash, sequence: 0, priceAtomicXmr: '1000', feeBps: 250, hostFeeAddress: recipient };
|
||||
const c = f.envelope(seller, action);
|
||||
assert.throws(() => f.market.execute({ ...c, action: { ...action, hostFeeAddress: ADDITIONAL_MARKET_FEE.address } }), /signature/);
|
||||
const l = f.market.execute(c) as CustodyListing;
|
||||
await f.restart({ hostFeeAddress: ADDITIONAL_MARKET_FEE.address, tradingEnabled: false });
|
||||
assert.equal(f.market.browse().listings[0].hostFeeAddress, recipient);
|
||||
assert.throws(() => f.reserve(l), /disabled/);
|
||||
await f.restart();
|
||||
assert.throws(() => f.reserve(l), /recipient/);
|
||||
const o = f.execute<CustodyOrder>(buyer, { type: 'reserve', listingId: l.id, priceAtomicXmr: l.priceAtomicXmr, feeBps: 250, hostFeeAddress: recipient });
|
||||
assert.equal(o.hostFeeAddress, recipient); assert.equal(o.authorAddress, ADDITIONAL_MARKET_FEE.address);
|
||||
f.market.settleFixture(o.id, evidence); assert.equal(f.market.audit().assetsAtomicXmr, MOCK_CREDIT_ATOMIC);
|
||||
});
|
||||
|
||||
test('custody mode is off by default and rejects all live configurations', () => {
|
||||
assert.equal(custodyMode({}), 'off'); assert.equal(custodyMode({ WIKIDECK_CUSTODY_MODE: 'sandbox' }), 'sandbox');
|
||||
for (const mode of ['live', 'mainnet', '', 'shkeeper']) assert.throws(() => custodyMode({ WIKIDECK_CUSTODY_MODE: mode }));
|
||||
|
||||
+13
-2
@@ -12,6 +12,7 @@ import { marketActionV3Schema, marketMessageV3, type MarketActionV3, type Market
|
||||
type MarketListingV3, type MarketOrderV3 } from '../shared/market-v3.ts';
|
||||
import { NETWORK, nftTransferMessage, testnetOwnerOf, transactionCardIds, type NftTransfer, type TestnetSnapshot, type TestnetBlock } from '../shared/testnet.ts';
|
||||
import type { CanonicalEvent } from '../server/testnet/canonical-index.ts';
|
||||
import type { MarketPolicy } from '../server/market/policy.ts';
|
||||
import { address, CHAIN_ID, GENESIS_HASH, transferId, verifySignature } from '../server/testnet/consensus.ts';
|
||||
|
||||
function identity() {
|
||||
@@ -105,9 +106,19 @@ async function fixture(t: { after(fn: () => Promise<void>): void }, indexed = fa
|
||||
};
|
||||
return { dir, chain, events, envelope, envelopeV3, run, list, buy, orders, delivery, confirm,
|
||||
get market() { return market; }, get now() { return time; }, advance: (ms: number) => { time += ms; },
|
||||
restart: async () => { market.close(); market = await Market.open(dir, adapter,
|
||||
{ feeBps: 500, confirmations: 5 }, () => time); } };
|
||||
restart: async (policy: MarketPolicy = {}) => { market.close(); market = await Market.open(dir, adapter,
|
||||
{ feeBps: 500, confirmations: 5, ...policy }, () => time); } };
|
||||
}
|
||||
test('mock market switches block new listings/bids/checkout while allowing paid-order delivery and private activity', async t => {
|
||||
const f = await fixture(t), l = await f.list(), o = await f.buy(l);
|
||||
await f.run(bob, { type: 'mock-pay', orderId: o.id });
|
||||
await f.restart({ marketEnabled: false });
|
||||
assert.equal(f.market.info().marketEnabled, false); assert.deepEqual(f.market.browse().listings, []);
|
||||
await assert.rejects(f.list(), /disabled/);
|
||||
assert.equal((await f.orders())[0].id, o.id);
|
||||
const tx = f.delivery(o); await f.run(alice, { type: 'deliver', orderId: o.id, transfer: tx });
|
||||
assert.equal(f.chain.state.pendingTransfers.length, 1);
|
||||
});
|
||||
|
||||
test('indexed market uses bounded durable event catch-up and never requests full snapshots', async t => {
|
||||
const f = await fixture(t, true);
|
||||
|
||||
@@ -27,4 +27,4 @@ test('database HBA requires TLS+SCRAM over TCP and provisioning refuses partial
|
||||
assert.match(setup, /Partial secret initialization/);
|
||||
assert.match(setup, /subjectAltName=DNS:database/);
|
||||
assert.match(setup, /-checkend/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -211,6 +211,21 @@ test('configuration requires secret files, verified TLS, literal backend, and lo
|
||||
await assert.rejects(postgresConfig({ WIKIDECK_CUSTODY_PG_URL_FILE: file }), /configuration/);
|
||||
});
|
||||
|
||||
test('PostgreSQL market disable preserves existing claims and signed withdrawal cancellation', async t => {
|
||||
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
|
||||
const listing = await f.execute(seller, { type: 'list', cardHash: hash, sequence: 0, priceAtomicXmr: '100', feeBps: 250 }) as CustodyListing;
|
||||
const order = await f.execute(buyer, { type: 'reserve', listingId: listing.id, priceAtomicXmr: '100', feeBps: 250 }) as CustodyOrder;
|
||||
const disabled = await PostgresCustody.open(f.runtime, { ...options, marketEnabled: false });
|
||||
try {
|
||||
assert.equal(disabled.info().marketEnabled, false); assert.deepEqual((await disabled.browse()).listings, []);
|
||||
await assert.rejects(disabled.execute(envelope(disabled.id, other, { type: 'mock-credit' })), /disabled/);
|
||||
await disabled.execute(envelope(disabled.id, buyer, { type: 'cancel-reservation', orderId: order.id }));
|
||||
const w = await disabled.execute(envelope(disabled.id, buyer, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '1', maxFeeAtomicXmr: '0' })) as CustodyWithdrawal;
|
||||
await disabled.execute(envelope(disabled.id, buyer, { type: 'cancel-withdrawal', withdrawalId: w.id }));
|
||||
assert.equal((await disabled.audit()).assetsAtomicXmr, '1000000000000');
|
||||
} finally { await disabled.close(); }
|
||||
});
|
||||
|
||||
test('full PostgreSQL dump/isolated restore preserves custody identity, receipts and journal backing', async t => {
|
||||
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
|
||||
const cmd = envelope(f.market.id, buyer, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '500', maxFeeAtomicXmr: '20' });
|
||||
|
||||
@@ -206,6 +206,7 @@ test('candidate drafts from real sandbox reservations without signing, changing
|
||||
assert.equal(market.info().nftSettlement, false);
|
||||
assert.throws(() => termsForCustodyOrder({ ...order, authorAtomicXmr: '1' }, context, options), /allocations/);
|
||||
assert.throws(() => termsForCustodyOrder({ ...order, status: 'cancelled' }, context, options), /awaiting-lock/);
|
||||
assert.throws(() => termsForCustodyOrder({ ...order, hostFeeAddress: 'different-recipient' }, context, options), /terms extension/);
|
||||
});
|
||||
|
||||
test('enumerated adversarial histories preserve exclusivity and deterministic replay across snapshot restores', () => {
|
||||
|
||||
Reference in New Issue
Block a user