Allow uncapped taker request inputs
Some checks failed
deploy / deploy (push) Failing after 33s

Proof: npm test (217/217), npm run operator-dashboard:build, and focused intent/trading-config tests cover DB-null request amount and slippage limits.

Assumptions: removing request amount and slippage caps is explicitly operator-approved for the current live-funds workflow; preflight remains side-effect-free and submit remains separate.

Still fake: request settlement truth still depends on inventory-delta attribution instead of venue-native terminal fill events.
This commit is contained in:
philipp 2026-05-18 14:13:07 +02:00
parent a73ea1c83f
commit b45d12d37c
9 changed files with 186 additions and 49 deletions

View file

@ -50,8 +50,10 @@ export function createIntentRequestController({
); );
const slippageBps = Number(body.slippage_bps ?? requestPair.slippageBps ?? config.intentRequestDefaultSlippageBps ?? 200); const slippageBps = Number(body.slippage_bps ?? requestPair.slippageBps ?? config.intentRequestDefaultSlippageBps ?? 200);
const minDeadlineMs = Number(body.min_deadline_ms || requestPair.minDeadlineMs || config.intentRequestMinDeadlineMs || 60_000); const minDeadlineMs = Number(body.min_deadline_ms || requestPair.minDeadlineMs || config.intentRequestMinDeadlineMs || 60_000);
const maxAmountUnits = parseDecimalToUnits( const maxAmountUnits = requestPair.requestMaxNotional == null
String(requestPair.requestMaxNotional || config.intentRequestMaxAmountEure || 5), ? null
: parseDecimalToUnits(
String(requestPair.requestMaxNotional),
sourceAsset.decimals, sourceAsset.decimals,
{ field: 'intent_request_max_notional' }, { field: 'intent_request_max_notional' },
); );
@ -80,22 +82,25 @@ export function createIntentRequestController({
); );
} }
sourceAmountUnits = parseDecimalToUnits(amountEure, sourceAsset.decimals, { field: 'amount_eure' }); sourceAmountUnits = parseDecimalToUnits(amountEure, sourceAsset.decimals, { field: 'amount_eure' });
if (BigInt(sourceAmountUnits) > BigInt(maxAmountUnits)) { if (maxAmountUnits != null && BigInt(sourceAmountUnits) > BigInt(maxAmountUnits)) {
blockedBeforeQuote = true; blockedBeforeQuote = true;
throw codedError( throw codedError(
'amount_exceeds_request_limit', 'amount_exceeds_request_limit',
`Requested ${amountEure} ${sourceAsset.symbol} exceeds configured live request limit ${requestPair.requestMaxNotional || config.intentRequestMaxAmountEure || 5} ${sourceAsset.symbol}.`, `Requested ${amountEure} ${sourceAsset.symbol} exceeds configured live request limit ${requestPair.requestMaxNotional} ${sourceAsset.symbol}.`,
); );
} }
if (!Number.isInteger(slippageBps) || slippageBps < 0) { if (!Number.isInteger(slippageBps) || slippageBps < 0) {
blockedBeforeQuote = true; blockedBeforeQuote = true;
throw codedError('invalid_slippage', 'Slippage must be a non-negative integer in basis points.'); throw codedError('invalid_slippage', 'Slippage must be a non-negative integer in basis points.');
} }
if (slippageBps > Number(requestPair.requestMaxSlippageBps ?? config.intentRequestMaxSlippageBps ?? 200)) { if (
requestPair.requestMaxSlippageBps != null
&& slippageBps > Number(requestPair.requestMaxSlippageBps)
) {
blockedBeforeQuote = true; blockedBeforeQuote = true;
throw codedError( throw codedError(
'slippage_exceeds_request_limit', 'slippage_exceeds_request_limit',
`Slippage ${slippageBps} bps exceeds configured limit ${requestPair.requestMaxSlippageBps ?? config.intentRequestMaxSlippageBps ?? 200} bps.`, `Slippage ${slippageBps} bps exceeds configured limit ${requestPair.requestMaxSlippageBps} bps.`,
); );
} }
@ -560,11 +565,9 @@ async function resolveIntentRequestPair({ body, config, getTradingConfig }) {
priceRoute: pair.priceRoute, priceRoute: pair.priceRoute,
requestDefaultNotional: requestDefaultNotional:
strategyConfig.requestDefaultNotional || config.intentRequestDefaultAmountEure, strategyConfig.requestDefaultNotional || config.intentRequestDefaultAmountEure,
requestMaxNotional: requestMaxNotional: strategyConfig.requestMaxNotional ?? null,
strategyConfig.requestMaxNotional || config.intentRequestMaxAmountEure,
slippageBps: strategyConfig.slippageBps ?? config.intentRequestDefaultSlippageBps, slippageBps: strategyConfig.slippageBps ?? config.intentRequestDefaultSlippageBps,
requestMaxSlippageBps: requestMaxSlippageBps: strategyConfig.requestMaxSlippageBps ?? null,
strategyConfig.requestMaxSlippageBps ?? strategyConfig.slippageBps ?? config.intentRequestMaxSlippageBps,
minDeadlineMs: strategyConfig.minDeadlineMs ?? config.intentRequestMinDeadlineMs, minDeadlineMs: strategyConfig.minDeadlineMs ?? config.intentRequestMinDeadlineMs,
priceMaxAgeMs: strategyConfig.priceMaxAgeMs ?? config.intentRequestPriceMaxAgeMs, priceMaxAgeMs: strategyConfig.priceMaxAgeMs ?? config.intentRequestPriceMaxAgeMs,
inventoryMaxAgeMs: strategyConfig.inventoryMaxAgeMs ?? config.intentRequestInventoryMaxAgeMs, inventoryMaxAgeMs: strategyConfig.inventoryMaxAgeMs ?? config.intentRequestInventoryMaxAgeMs,

View file

@ -900,14 +900,23 @@ function buildFundingSummary({ config, fundingObservations, recentDepositStatuse
} }
function buildIntentRequestSummary({ config, intentRequests = [], executorState = {} } = {}) { function buildIntentRequestSummary({ config, intentRequests = [], executorState = {} } = {}) {
const defaultPair = config.defaultTakerPair || null;
const strategyConfig = defaultPair?.strategyConfig || null;
const usingDbPairConfig = Boolean(config.tradingConfigLoaded && strategyConfig);
const sourceAsset = defaultPair?.assetIn || config.tradingEure;
const destinationAsset = defaultPair?.assetOut || config.tradingBtc;
return { return {
defaults: { defaults: {
source_symbol: config.tradingEure.symbol, source_symbol: sourceAsset?.symbol || 'Source',
destination_symbol: config.tradingBtc.symbol, destination_symbol: destinationAsset?.symbol || 'Destination',
amount_eure: String(config.intentRequestDefaultAmountEure || 5), amount_eure: String(strategyConfig?.requestDefaultNotional ?? config.intentRequestDefaultAmountEure ?? 5),
max_amount_eure: String(config.intentRequestMaxAmountEure || 5), max_amount_eure: usingDbPairConfig
slippage_bps: Number(config.intentRequestDefaultSlippageBps ?? 200), ? strategyConfig.requestMaxNotional ?? null
max_slippage_bps: Number(config.intentRequestMaxSlippageBps ?? 200), : String(config.intentRequestMaxAmountEure || 5),
slippage_bps: Number(strategyConfig?.slippageBps ?? config.intentRequestDefaultSlippageBps ?? 200),
max_slippage_bps: usingDbPairConfig
? strategyConfig.requestMaxSlippageBps ?? null
: Number(config.intentRequestMaxSlippageBps ?? 200),
}, },
executor_armed: executorState.armed ?? null, executor_armed: executorState.armed ?? null,
executor_paused: executorState.paused ?? null, executor_paused: executorState.paused ?? null,

View file

@ -19,8 +19,9 @@ export const CURRENT_REVERSE_PAIR_KEY = pairKey(CURRENT_EURE_ASSET_ID, CURRENT_N
export const CURRENT_EDGE_BPS = 49; export const CURRENT_EDGE_BPS = 49;
export const CURRENT_STRATEGY_MAX_NOTIONAL = '150'; export const CURRENT_STRATEGY_MAX_NOTIONAL = '150';
export const CURRENT_REQUEST_DEFAULT_NOTIONAL_EURE = '5'; export const CURRENT_REQUEST_DEFAULT_NOTIONAL_EURE = '5';
export const CURRENT_REQUEST_MAX_NOTIONAL_EURE = '5';
export const CURRENT_SLIPPAGE_BPS = 200; export const CURRENT_SLIPPAGE_BPS = 200;
export const CURRENT_REQUEST_MAX_NOTIONAL_EURE = null;
export const CURRENT_REQUEST_MAX_SLIPPAGE_BPS = null;
export const CURRENT_MIN_DEADLINE_MS = 60_000; export const CURRENT_MIN_DEADLINE_MS = 60_000;
export const CURRENT_PRICE_MAX_AGE_MS = 30_000; export const CURRENT_PRICE_MAX_AGE_MS = 30_000;
export const CURRENT_INVENTORY_MAX_AGE_MS = 30_000; export const CURRENT_INVENTORY_MAX_AGE_MS = 30_000;
@ -188,7 +189,7 @@ export function buildSeedStrategyConfig(pairId, {
inventoryMaxAgeMs: CURRENT_INVENTORY_MAX_AGE_MS, inventoryMaxAgeMs: CURRENT_INVENTORY_MAX_AGE_MS,
requestDefaultNotional: CURRENT_REQUEST_DEFAULT_NOTIONAL_EURE, requestDefaultNotional: CURRENT_REQUEST_DEFAULT_NOTIONAL_EURE,
requestMaxNotional: CURRENT_REQUEST_MAX_NOTIONAL_EURE, requestMaxNotional: CURRENT_REQUEST_MAX_NOTIONAL_EURE,
requestMaxSlippageBps: CURRENT_SLIPPAGE_BPS, requestMaxSlippageBps: CURRENT_REQUEST_MAX_SLIPPAGE_BPS,
createdBy, createdBy,
reason, reason,
}; };

View file

@ -719,9 +719,9 @@ export async function createPairStrategyConfigVersion(pool, {
minDeadlineMs = null, minDeadlineMs = null,
priceMaxAgeMs = null, priceMaxAgeMs = null,
inventoryMaxAgeMs = null, inventoryMaxAgeMs = null,
requestDefaultNotional = null, requestDefaultNotional = undefined,
requestMaxNotional = null, requestMaxNotional = undefined,
requestMaxSlippageBps = null, requestMaxSlippageBps = undefined,
changedBy = 'operator', changedBy = 'operator',
reason = 'operator config update', reason = 'operator config update',
} = {}) { } = {}) {
@ -763,17 +763,17 @@ export async function createPairStrategyConfigVersion(pool, {
inventoryMaxAgeMs: inventoryMaxAgeMs:
inventoryMaxAgeMs == null ? Number(active.inventory_max_age_ms) : Number(inventoryMaxAgeMs), inventoryMaxAgeMs == null ? Number(active.inventory_max_age_ms) : Number(inventoryMaxAgeMs),
requestDefaultNotional: requestDefaultNotional:
requestDefaultNotional == null requestDefaultNotional === undefined
? active.request_default_notional == null ? null : String(active.request_default_notional) ? active.request_default_notional == null ? null : String(active.request_default_notional)
: String(requestDefaultNotional), : nullablePositiveNumberString(requestDefaultNotional, 'request_default_notional'),
requestMaxNotional: requestMaxNotional:
requestMaxNotional == null requestMaxNotional === undefined
? active.request_max_notional == null ? null : String(active.request_max_notional) ? active.request_max_notional == null ? null : String(active.request_max_notional)
: String(requestMaxNotional), : nullablePositiveNumberString(requestMaxNotional, 'request_max_notional'),
requestMaxSlippageBps: requestMaxSlippageBps:
requestMaxSlippageBps == null requestMaxSlippageBps === undefined
? active.request_max_slippage_bps == null ? null : Number(active.request_max_slippage_bps) ? active.request_max_slippage_bps == null ? null : Number(active.request_max_slippage_bps)
: Number(requestMaxSlippageBps), : nullableNonNegativeInteger(requestMaxSlippageBps, 'request_max_slippage_bps'),
createdBy: changedBy, createdBy: changedBy,
reason, reason,
}; };
@ -874,9 +874,9 @@ export async function setTradingPairMode(pool, {
minDeadlineMs = null, minDeadlineMs = null,
priceMaxAgeMs = null, priceMaxAgeMs = null,
inventoryMaxAgeMs = null, inventoryMaxAgeMs = null,
requestDefaultNotional = null, requestDefaultNotional = undefined,
requestMaxNotional = null, requestMaxNotional = undefined,
requestMaxSlippageBps = null, requestMaxSlippageBps = undefined,
changedBy = 'operator', changedBy = 'operator',
reason = 'operator pair mode update', reason = 'operator pair mode update',
} = {}) { } = {}) {
@ -1336,9 +1336,9 @@ function buildInitialPairStrategyConfig(pairId, {
minDeadlineMs = null, minDeadlineMs = null,
priceMaxAgeMs = null, priceMaxAgeMs = null,
inventoryMaxAgeMs = null, inventoryMaxAgeMs = null,
requestDefaultNotional = null, requestDefaultNotional = undefined,
requestMaxNotional = null, requestMaxNotional = undefined,
requestMaxSlippageBps = null, requestMaxSlippageBps = undefined,
changedBy = 'operator', changedBy = 'operator',
reason = 'operator pair strategy config initialization', reason = 'operator pair strategy config initialization',
} = {}) { } = {}) {
@ -1396,11 +1396,6 @@ function nonNegativeIntegerOrDefault(value, fallback, field) {
return next; return next;
} }
function nullableNonNegativeIntegerOrDefault(value, fallback, field) {
if (!hasConfigOverride(value)) return fallback == null ? null : nonNegativeIntegerOrDefault(fallback, 0, field);
return nonNegativeIntegerOrDefault(value, 0, field);
}
function positiveNumberStringOrDefault(value, fallback, field) { function positiveNumberStringOrDefault(value, fallback, field) {
if (!hasConfigOverride(value)) return String(fallback); if (!hasConfigOverride(value)) return String(fallback);
const next = String(value).trim(); const next = String(value).trim();
@ -1416,10 +1411,25 @@ function nonNegativeNumberStringOrDefault(value, fallback, field) {
} }
function nullablePositiveNumberStringOrDefault(value, fallback, field) { function nullablePositiveNumberStringOrDefault(value, fallback, field) {
if (!hasConfigOverride(value)) return fallback == null ? null : positiveNumberStringOrDefault(fallback, '1', field); if (value === undefined) return fallback == null ? null : positiveNumberStringOrDefault(fallback, '1', field);
return nullablePositiveNumberString(value, field);
}
function nullablePositiveNumberString(value, field) {
if (!hasConfigOverride(value)) return null;
return positiveNumberStringOrDefault(value, '1', field); return positiveNumberStringOrDefault(value, '1', field);
} }
function nullableNonNegativeIntegerOrDefault(value, fallback, field) {
if (value === undefined) return fallback == null ? null : nonNegativeIntegerOrDefault(fallback, 0, field);
return nullableNonNegativeInteger(value, field);
}
function nullableNonNegativeInteger(value, field) {
if (!hasConfigOverride(value)) return null;
return nonNegativeIntegerOrDefault(value, 0, field);
}
function normalizeStrategyConfigRow(row) { function normalizeStrategyConfigRow(row) {
if (!row) return null; if (!row) return null;
return { return {

View file

@ -279,6 +279,10 @@ function IdentifierLine({ label, value }) {
function IntentRequestForm({ summary, onControl }) { function IntentRequestForm({ summary, onControl }) {
const defaults = summary?.defaults || {}; const defaults = summary?.defaults || {};
const sourceSymbol = defaults.source_symbol || 'EURe';
const destinationSymbol = defaults.destination_symbol || 'BTC';
const hasAmountCap = defaults.max_amount_eure != null && defaults.max_amount_eure !== '';
const hasSlippageCap = defaults.max_slippage_bps != null && defaults.max_slippage_bps !== '';
const [form, setForm] = useState({ const [form, setForm] = useState({
amount_eure: defaults.amount_eure || '5', amount_eure: defaults.amount_eure || '5',
slippage_bps: String(defaults.slippage_bps ?? 200), slippage_bps: String(defaults.slippage_bps ?? 200),
@ -303,36 +307,40 @@ function IntentRequestForm({ summary, onControl }) {
<form onSubmit={handlePreflight}> <form onSubmit={handlePreflight}>
<div className="form-grid"> <div className="form-grid">
<div className="field"> <div className="field">
<label htmlFor="intent-request-amount">Spend EURe</label> <label htmlFor="intent-request-amount">{`Spend ${sourceSymbol}`}</label>
<input <input
id="intent-request-amount" id="intent-request-amount"
max={defaults.max_amount_eure || '5'}
min="0.01" min="0.01"
name="amount_eure" name="amount_eure"
onChange={(event) => setForm((current) => ({ ...current, amount_eure: event.target.value }))} onChange={(event) => setForm((current) => ({ ...current, amount_eure: event.target.value }))}
step="0.01" step="0.01"
type="number" type="number"
value={form.amount_eure} value={form.amount_eure}
{...(hasAmountCap ? { max: defaults.max_amount_eure } : {})}
/> />
<div className="status-subtle">{`Max ${defaults.max_amount_eure || '5'} EURe per live test request`}</div> <div className="status-subtle">
{hasAmountCap ? `Max ${defaults.max_amount_eure} ${sourceSymbol}` : 'No request amount cap'}
</div>
</div> </div>
<div className="field"> <div className="field">
<label htmlFor="intent-request-slippage">Max slippage bps</label> <label htmlFor="intent-request-slippage">Max slippage bps</label>
<input <input
id="intent-request-slippage" id="intent-request-slippage"
max={defaults.max_slippage_bps ?? 200}
min="0" min="0"
name="slippage_bps" name="slippage_bps"
onChange={(event) => setForm((current) => ({ ...current, slippage_bps: event.target.value }))} onChange={(event) => setForm((current) => ({ ...current, slippage_bps: event.target.value }))}
step="1" step="1"
type="number" type="number"
value={form.slippage_bps} value={form.slippage_bps}
{...(hasSlippageCap ? { max: defaults.max_slippage_bps } : {})}
/> />
<div className="status-subtle">{`Max ${defaults.max_slippage_bps ?? 200} bps / 2%`}</div> <div className="status-subtle">
{hasSlippageCap ? `Max ${defaults.max_slippage_bps} bps` : 'No slippage cap'}
</div>
</div> </div>
</div> </div>
<div className="button-row"> <div className="button-row">
<button className="button" type="submit">Preflight EURe to BTC</button> <button className="button" type="submit">{`Preflight ${sourceSymbol} to ${destinationSymbol}`}</button>
<button <button
className="button secondary" className="button secondary"
onClick={() => onControl('trade-executor', 'intent-request-refresh-outcomes')} onClick={() => onControl('trade-executor', 'intent-request-refresh-outcomes')}

View file

@ -630,6 +630,16 @@ function PairConfigSection({ assetCatalog, pairConfig, onControl }) {
<td>{strategyConfig.edge_bps ?? 'Unavailable'} bps</td> <td>{strategyConfig.edge_bps ?? 'Unavailable'} bps</td>
<td> <td>
<div>{strategyConfig.max_notional || 'Unavailable'} max</div> <div>{strategyConfig.max_notional || 'Unavailable'} max</div>
<div className="status-subtle">
{strategyConfig.request_max_notional == null
? 'No request cap'
: `${strategyConfig.request_max_notional} request max`}
</div>
<div className="status-subtle">
{strategyConfig.request_max_slippage_bps == null
? 'No slippage cap'
: `${strategyConfig.request_max_slippage_bps} bps slippage max`}
</div>
<div className="status-subtle">{strategyConfig.price_max_age_ms || 'Unavailable'} ms price max age</div> <div className="status-subtle">{strategyConfig.price_max_age_ms || 'Unavailable'} ms price max age</div>
</td> </td>
<td>{route.source || 'Unavailable'}</td> <td>{route.source || 'Unavailable'}</td>

View file

@ -122,7 +122,14 @@ function buildRelay() {
}; };
} }
function buildController({ store = buildStore(), relay = buildRelay(), armed = true, verifierRegistered = true, withMakerSuppressed = async (operation) => operation() } = {}) { function buildController({
store = buildStore(),
relay = buildRelay(),
armed = true,
verifierRegistered = true,
withMakerSuppressed = async (operation) => operation(),
getTradingConfig = null,
} = {}) {
return { return {
store, store,
relay, relay,
@ -137,6 +144,7 @@ function buildController({ store = buildStore(), relay = buildRelay(), armed = t
signer: KeyPair.fromRandom('ed25519'), signer: KeyPair.fromRandom('ed25519'),
isArmed: () => armed, isArmed: () => armed,
isPaused: () => false, isPaused: () => false,
getTradingConfig,
withMakerSuppressed, withMakerSuppressed,
now: () => Date.parse('2026-04-12T10:00:00.000Z'), now: () => Date.parse('2026-04-12T10:00:00.000Z'),
uuid: (() => { uuid: (() => {
@ -196,6 +204,60 @@ test('preflight is side-effect-free and does not publish a live intent', async (
assert.equal(relay.publishCalls, 0); assert.equal(relay.publishCalls, 0);
}); });
test('DB null request limits allow operator-chosen amount and slippage', async () => {
const store = buildStore({ inventoryUnits: '6000000000000000000' });
const relay = buildRelay();
relay.quote = async function quote() {
this.quoteCalls += 1;
return [{
quote_hash: 'uncapped-quote-hash',
amount_out: '12000',
expiration_time: '2026-04-12T10:01:00.000Z',
}];
};
const pairId = `${EURE.assetId}->${BTC.assetId}`;
const pair = {
key: pairId,
pairId,
takerEnabled: true,
canTrade: true,
assetIn: EURE,
assetOut: BTC,
strategyConfig: {
requestDefaultNotional: '5',
requestMaxNotional: null,
slippageBps: 200,
requestMaxSlippageBps: null,
minDeadlineMs: 60_000,
priceMaxAgeMs: 30_000,
inventoryMaxAgeMs: 30_000,
},
priceRoute: {
source: 'btc_eur_reference',
},
};
const { controller } = buildController({
store,
relay,
getTradingConfig: async () => ({
ok: true,
tradingEure: EURE,
tradingBtc: BTC,
pairByKey: new Map([[pairId, pair]]),
defaultTakerPair: pair,
}),
});
const preflight = await controller.preflight({ amount_eure: '6', slippage_bps: 250 });
assert.equal(preflight.state, 'draft');
assert.equal(preflight.reason_code, 'quote_available');
assert.equal(preflight.request_max_notional, null);
assert.equal(preflight.slippage_bps, 250);
assert.equal(preflight.live_submit_capable, true);
assert.equal(relay.quoteCalls, 1);
});
test('insufficient spendable EURe blocks before solver quote or signing', async () => { test('insufficient spendable EURe blocks before solver quote or signing', async () => {
const store = buildStore({ inventoryUnits: '0' }); const store = buildStore({ inventoryUnits: '0' });
const relay = buildRelay(); const relay = buildRelay();

View file

@ -27,6 +27,8 @@ test('operator dashboard exposes DB-backed pair activation and pause controls',
assert.match(source, /control\.action === 'pause-pair'/); assert.match(source, /control\.action === 'pause-pair'/);
assert.match(source, /edgeBps: body\.edge_bps/); assert.match(source, /edgeBps: body\.edge_bps/);
assert.match(source, /maxNotional: body\.max_notional/); assert.match(source, /maxNotional: body\.max_notional/);
assert.match(source, /requestMaxNotional: bodyField\(body, 'request_max_notional', 'requestMaxNotional'\)/);
assert.match(source, /requestMaxSlippageBps: bodyField\(body, 'request_max_slippage_bps', 'requestMaxSlippageBps'\)/);
}); });
test('operator dashboard API auth failures are JSON for frontend fetches', () => { test('operator dashboard API auth failures are JSON for frontend fetches', () => {

View file

@ -213,6 +213,38 @@ test('edge update creates a new active strategy version', async () => {
assert.equal(versions.find((row) => row.version === 1).active, false); assert.equal(versions.find((row) => row.version === 1).active, false);
}); });
test('strategy config update can clear request amount and slippage caps', async () => {
const pool = createMemoryPool();
await seedTradingConfig(pool);
const pairId = `${CURRENT_EURE_ASSET_ID}->${CURRENT_NBTC_ASSET_ID}`;
await createPairStrategyConfigVersion(pool, {
pairId,
edgeBps: 49,
maxNotional: '200',
requestMaxNotional: '5',
requestMaxSlippageBps: 200,
changedBy: 'test',
reason: 'operator capped request limits',
});
const next = await createPairStrategyConfigVersion(pool, {
pairId,
edgeBps: 49,
maxNotional: '200',
requestMaxNotional: null,
requestMaxSlippageBps: null,
changedBy: 'test',
reason: 'operator uncapped request limits',
});
const snapshot = await loadTradingConfig(pool);
const pair = snapshot.pairByKey.get(pairId);
assert.equal(next.requestMaxNotional, null);
assert.equal(next.requestMaxSlippageBps, null);
assert.equal(pair.strategyConfig.requestMaxNotional, null);
assert.equal(pair.strategyConfig.requestMaxSlippageBps, null);
});
test('strategy config update rejects invalid max notional', async () => { test('strategy config update rejects invalid max notional', async () => {
const pool = createMemoryPool(); const pool = createMemoryPool();
await seedTradingConfig(pool); await seedTradingConfig(pool);