chore(sim): refresh deployment status, pool matrix, schemas, and scenario scripts
- deployment-status and pool-matrix snapshots aligned with validate-deployment-status.cjs. - Micro-trade / scorecard docs and run-scenario wiring. Made-with: Cursor
This commit is contained in:
@@ -30,7 +30,7 @@ Validates `config/deployment-status.json` for minimum viable deployed graph. Use
|
||||
**Rules:**
|
||||
|
||||
- If `bridgeAvailable === true` on a chain, `cwTokens` must include at least **cWUSDT** and **cWUSDC** (phase 1).
|
||||
- For each `pmmPool`: `role` ∈ {defense, public_routing}; `feeBps` and `k` present; `base`/`quote` (or `tokenIn`/`tokenOut`) exist in `cwTokens` or `anchorAddresses`.
|
||||
- For each `pmmPool` and each `pmmPoolsVolatile[]` entry: `role` ∈ {defense, public_routing, truu_routing}; `feeBps` and `k` present; `base`/`quote` (or `tokenIn`/`tokenOut`) exist in `cwTokens` or `anchorAddresses` (e.g. mainnet **TRUU** under `anchorAddresses.TRUU`). Non-zero `poolAddress` must not be the zero address.
|
||||
|
||||
**Run:**
|
||||
|
||||
@@ -40,12 +40,16 @@ node scripts/validate-deployment-status.cjs
|
||||
|
||||
**Exit code:** 0 if valid, 1 if invalid (errors to stderr).
|
||||
|
||||
**Parent proxmox repo:** live Mainnet cW/TRUU pool deploy and ratio-matched top-up scripts live under `scripts/deployment/` (`deploy-mainnet-pmm-cw-truu-pool.sh`, `add-mainnet-truu-pmm-topup.sh`); see `docs/03-deployment/MAINNET_PMM_TRUU_CWUSD_PEG_AND_BOT_RUNBOOK.md` §11.
|
||||
|
||||
---
|
||||
|
||||
## run-scenario.cjs
|
||||
|
||||
Builds the **real** routing graph from configs, runs epochs with PMM state updates, path enumeration + waterfilling, **arb step** (implied-price deviation, capped corrective trades, profit gate), **bot step** (inject/withdraw at 0.5×/1.5× I_T^* with intervention cost β/γ/ρ), and optional **bridge shock** trades. Emits a **real scorecard** (PR#2). Runs are **deterministic** when `scenario.seed` is set or derived from scenario name.
|
||||
|
||||
Scenarios may also include an optional **microTradePolicy** for gas-budgeted support trades in selected USD wrapper rails. This is meant to model tiny, controlled `cWUSDC` / `cWUSDT` turnover-seeding or inventory-corrective activity, not to turn the PMM into a venue.
|
||||
|
||||
**Configs used:** `simulation-params.json`, `token-map.json`, `routing-controls.json`; `deployment-status.json` only when `graphMode = deployed`. Pool topology from `pool-matrix.json` and scenario `topology` / `fullQuoteChains`.
|
||||
|
||||
**Tuning constants (in script):**
|
||||
@@ -64,8 +68,9 @@ Builds the **real** routing graph from configs, runs epochs with PMM state updat
|
||||
node scripts/run-scenario.cjs hub_only_11
|
||||
node scripts/run-scenario.cjs --scenario full_quote_1_56_137
|
||||
node scripts/run-scenario.cjs bridge_shock_137_56
|
||||
node scripts/run-scenario.cjs micro_support_usd_wrappers_1_56_137
|
||||
```
|
||||
|
||||
**Output:** JSON scorecard including: `capture_mean`, `churn_mean`, `drain_half_life_epochs`, `path_concentration_index`; `intervention_cost_total` / `intervention_cost_inject_total` / `intervention_cost_withdraw_total` / `intervention_cost_by_chain` / `intervention_cost_per_1M_volume`; `peak_deviation_bps` (post-arb), `peak_deviation_bps_pre_arb`, `peak_deviation_bps_post_arb`, `peak_deviation_bps_post_bot`; `arb_volume_total`, `arb_profit_total` (execution-based, not mid). See [docs/12-sim-scorecard.md](../docs/12-sim-scorecard.md) and [config/scorecard-schema.json](../config/scorecard-schema.json).
|
||||
**Output:** JSON scorecard including: `capture_mean`, `churn_mean`, `drain_half_life_epochs`, `path_concentration_index`; `intervention_cost_total` / `intervention_cost_inject_total` / `intervention_cost_withdraw_total` / `intervention_cost_by_chain` / `intervention_cost_per_1M_volume`; `micro_trade_count` / `micro_trade_volume_total` / `micro_trade_gas_cost_total`; `peak_deviation_bps` (post-arb), `peak_deviation_bps_pre_arb`, `peak_deviation_bps_post_arb`, `peak_deviation_bps_post_bot`; `arb_volume_total`, `arb_profit_total` (execution-based, not mid). See [docs/12-sim-scorecard.md](../docs/12-sim-scorecard.md) and [config/scorecard-schema.json](../config/scorecard-schema.json).
|
||||
|
||||
**Orderflow:** Trade sizes use `distribution: "uniform"` by default. Scenario schema supports `lognormal` / `pareto` for skewed (many small + occasional whale) flows; implement in `sampleTrade()` when needed.
|
||||
|
||||
@@ -355,6 +355,173 @@ function getBridgeRho(scenario, fromChain, toChain) {
|
||||
return (blocks ?? 10) * rhoPerBlock / 10000;
|
||||
}
|
||||
|
||||
function getMatchedQuoteForBase(base) {
|
||||
if (base === 'cWUSDT') return 'USDT';
|
||||
if (base === 'cWUSDC') return 'USDC';
|
||||
return null;
|
||||
}
|
||||
|
||||
function getMicroTradeCandidates(graph, policy) {
|
||||
const tokenFilter = new Set(policy.tokens || []);
|
||||
const quoteFilter = new Set(policy.quoteTokens || []);
|
||||
let candidates = graph.pmmEdges.filter((e) => {
|
||||
if (tokenFilter.size > 0 && !tokenFilter.has(e.base)) return false;
|
||||
if (quoteFilter.size > 0 && !quoteFilter.has(e.quote)) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
if (policy.preferMatchedQuote) {
|
||||
const perChainBase = new Map();
|
||||
for (const edge of candidates) {
|
||||
const key = `${edge.chainId}:${edge.base}`;
|
||||
const preferredQuote = getMatchedQuoteForBase(edge.base);
|
||||
const existing = perChainBase.get(key);
|
||||
if (!existing) {
|
||||
perChainBase.set(key, edge);
|
||||
continue;
|
||||
}
|
||||
const score = edge.quote === preferredQuote ? 0 : 1;
|
||||
const existingScore = existing.quote === preferredQuote ? 0 : 1;
|
||||
if (
|
||||
score < existingScore
|
||||
|| (score === existingScore && edge.quote.localeCompare(existing.quote) < 0)
|
||||
) {
|
||||
perChainBase.set(key, edge);
|
||||
}
|
||||
}
|
||||
candidates = Array.from(perChainBase.values());
|
||||
}
|
||||
|
||||
candidates.sort((a, b) => (
|
||||
a.chainId.localeCompare(b.chainId)
|
||||
|| a.base.localeCompare(b.base)
|
||||
|| a.quote.localeCompare(b.quote)
|
||||
));
|
||||
return candidates;
|
||||
}
|
||||
|
||||
function resolveMicroTradeDirection(policy, poolState, epochIndex, tradeIndex) {
|
||||
const mode = policy.mode || 'inventory_or_alternate';
|
||||
const bandFrac = Number(policy.inventoryBandFraction ?? 0.05);
|
||||
const lower = (1 - bandFrac) * poolState.I_T_star;
|
||||
const upper = (1 + bandFrac) * poolState.I_T_star;
|
||||
|
||||
if (mode === 'inventory' || mode === 'inventory_or_alternate') {
|
||||
if (poolState.I_T < lower) return 'sell_base';
|
||||
if (poolState.I_T > upper) return 'buy_base';
|
||||
if (mode === 'inventory') return null;
|
||||
}
|
||||
|
||||
if (mode === 'alternate' || mode === 'inventory_or_alternate') {
|
||||
return ((epochIndex + tradeIndex) % 2 === 0) ? 'buy_base' : 'sell_base';
|
||||
}
|
||||
if (mode === 'buy_base') return 'buy_base';
|
||||
if (mode === 'sell_base') return 'sell_base';
|
||||
return null;
|
||||
}
|
||||
|
||||
function runMicroTradeSupportStep(graph, state, scenario, epochIndex) {
|
||||
const policy = scenario.microTradePolicy || {};
|
||||
if (policy.enabled === false || Object.keys(policy).length === 0) {
|
||||
return {
|
||||
state,
|
||||
microTradeCount: 0,
|
||||
microTradeBuyCount: 0,
|
||||
microTradeSellCount: 0,
|
||||
microTradeVolumeTotal: 0,
|
||||
microTradeGasCostTotal: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const requestedTrades = Math.max(0, parseInt(policy.tradesPerEpoch ?? 0, 10));
|
||||
if (requestedTrades === 0) {
|
||||
return {
|
||||
state,
|
||||
microTradeCount: 0,
|
||||
microTradeBuyCount: 0,
|
||||
microTradeSellCount: 0,
|
||||
microTradeVolumeTotal: 0,
|
||||
microTradeGasCostTotal: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const candidates = getMicroTradeCandidates(graph, policy);
|
||||
if (candidates.length === 0) {
|
||||
return {
|
||||
state,
|
||||
microTradeCount: 0,
|
||||
microTradeBuyCount: 0,
|
||||
microTradeSellCount: 0,
|
||||
microTradeVolumeTotal: 0,
|
||||
microTradeGasCostTotal: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const gasCostPerTrade = Number(policy.gasCostPerTradeUnits ?? 0);
|
||||
const gasBudgetPerEpoch = Number(policy.gasBudgetPerEpochUnits ?? 0);
|
||||
let tradesAllowed = requestedTrades;
|
||||
if (gasCostPerTrade > 0 && gasBudgetPerEpoch > 0) {
|
||||
tradesAllowed = Math.min(tradesAllowed, Math.floor(gasBudgetPerEpoch / gasCostPerTrade));
|
||||
}
|
||||
if (tradesAllowed <= 0) {
|
||||
return {
|
||||
state,
|
||||
microTradeCount: 0,
|
||||
microTradeBuyCount: 0,
|
||||
microTradeSellCount: 0,
|
||||
microTradeVolumeTotal: 0,
|
||||
microTradeGasCostTotal: 0,
|
||||
};
|
||||
}
|
||||
|
||||
let curState = state;
|
||||
let microTradeCount = 0;
|
||||
let microTradeBuyCount = 0;
|
||||
let microTradeSellCount = 0;
|
||||
let microTradeVolumeTotal = 0;
|
||||
let microTradeGasCostTotal = 0;
|
||||
|
||||
for (let i = 0; i < tradesAllowed; i++) {
|
||||
const edge = candidates[(epochIndex * tradesAllowed + i) % candidates.length];
|
||||
const poolState = curState[edge.key];
|
||||
if (!poolState || poolState.I_T_star == null) continue;
|
||||
|
||||
const rawTradeSize = Number(policy.tradeSizeUnits ?? 0);
|
||||
const maxFractionOfTarget = Number(policy.maxFractionOfTarget ?? 0.01);
|
||||
const maxTradeSize = maxFractionOfTarget > 0 ? poolState.I_T_star * maxFractionOfTarget : rawTradeSize;
|
||||
const tradeSize = Math.max(0, Math.min(rawTradeSize, maxTradeSize || rawTradeSize));
|
||||
if (tradeSize <= 0) continue;
|
||||
|
||||
const direction = resolveMicroTradeDirection(policy, poolState, epochIndex, i);
|
||||
if (!direction) continue;
|
||||
|
||||
if (direction === 'sell_base') {
|
||||
const { newState } = pmmSellT(edge.key, tradeSize, curState);
|
||||
curState = newState;
|
||||
microTradeSellCount += 1;
|
||||
microTradeVolumeTotal += tradeSize;
|
||||
} else {
|
||||
const quoteSpend = tradeSize * (poolState.P || 1);
|
||||
const { outputT, newState } = pmmBuyT(edge.key, quoteSpend, curState);
|
||||
curState = newState;
|
||||
microTradeBuyCount += 1;
|
||||
microTradeVolumeTotal += outputT;
|
||||
}
|
||||
|
||||
microTradeCount += 1;
|
||||
microTradeGasCostTotal += gasCostPerTrade;
|
||||
}
|
||||
|
||||
return {
|
||||
state: curState,
|
||||
microTradeCount,
|
||||
microTradeBuyCount,
|
||||
microTradeSellCount,
|
||||
microTradeVolumeTotal,
|
||||
microTradeGasCostTotal,
|
||||
};
|
||||
}
|
||||
|
||||
function runBotStep(graph, state, scenario, configs) {
|
||||
const chains = graph.chains;
|
||||
let curState = state;
|
||||
@@ -625,6 +792,11 @@ function runEpoch(scenario, graph, state, configs, epochIndex) {
|
||||
let pmmVolume = 0;
|
||||
let churnSum = 0;
|
||||
const I_T_start = {};
|
||||
let microTradeCount = 0;
|
||||
let microTradeBuyCount = 0;
|
||||
let microTradeSellCount = 0;
|
||||
let microTradeVolumeTotal = 0;
|
||||
let microTradeGasCostTotal = 0;
|
||||
|
||||
for (const k of Object.keys(state)) {
|
||||
if (state[k].I_T_star != null) I_T_start[k] = state[k].I_T;
|
||||
@@ -682,6 +854,15 @@ function runEpoch(scenario, graph, state, configs, epochIndex) {
|
||||
}
|
||||
}
|
||||
|
||||
const micro = runMicroTradeSupportStep(graph, curState, scenario, epochIndex);
|
||||
curState = micro.state;
|
||||
microTradeCount += micro.microTradeCount || 0;
|
||||
microTradeBuyCount += micro.microTradeBuyCount || 0;
|
||||
microTradeSellCount += micro.microTradeSellCount || 0;
|
||||
microTradeVolumeTotal += micro.microTradeVolumeTotal || 0;
|
||||
microTradeGasCostTotal += micro.microTradeGasCostTotal || 0;
|
||||
totalVolume += micro.microTradeVolumeTotal || 0;
|
||||
|
||||
const peakDeviationBpsPreArb = maxDeviationBpsOverPools(graph, curState);
|
||||
const worstPreArb = getWorstPoolDiagnostic(graph, curState);
|
||||
const { state: afterArb, arbVolumeTotal, arbProfitTotal, peakDeviationBps: peakDeviationBpsPostArb } = runArbStep(graph, curState, configs);
|
||||
@@ -717,6 +898,11 @@ function runEpoch(scenario, graph, state, configs, epochIndex) {
|
||||
interventionCostInject: interventionCostInject || 0,
|
||||
interventionCostWithdraw: interventionCostWithdraw || 0,
|
||||
interventionCostByChain: interventionCostByChain || {},
|
||||
microTradeCount,
|
||||
microTradeBuyCount,
|
||||
microTradeSellCount,
|
||||
microTradeVolumeTotal,
|
||||
microTradeGasCostTotal,
|
||||
peakDeviationBpsPreArb: peakDeviationBpsPreArb || 0,
|
||||
peakDeviationBpsPostArb: peakDeviationBpsPostArb || 0,
|
||||
peakDeviationBpsPostBot: peakDeviationBpsPostBot || 0,
|
||||
@@ -753,6 +939,11 @@ function computeScorecard(scenario, scenarioName, graph, initialState, epochResu
|
||||
let peakDeviationBpsPreArb = 0;
|
||||
let peakDeviationBpsPostArb = 0;
|
||||
let peakDeviationBpsPostBot = 0;
|
||||
let microTradeCountTotal = 0;
|
||||
let microTradeBuyCountTotal = 0;
|
||||
let microTradeSellCountTotal = 0;
|
||||
let microTradeVolumeTotal = 0;
|
||||
let microTradeGasCostTotal = 0;
|
||||
|
||||
for (const r of epochResults) {
|
||||
totalVolume += r.totalVolume;
|
||||
@@ -763,6 +954,11 @@ function computeScorecard(scenario, scenarioName, graph, initialState, epochResu
|
||||
arbProfitTotal += r.arbProfitTotal || 0;
|
||||
interventionCostInjectTotal += r.interventionCostInject || 0;
|
||||
interventionCostWithdrawTotal += r.interventionCostWithdraw || 0;
|
||||
microTradeCountTotal += r.microTradeCount || 0;
|
||||
microTradeBuyCountTotal += r.microTradeBuyCount || 0;
|
||||
microTradeSellCountTotal += r.microTradeSellCount || 0;
|
||||
microTradeVolumeTotal += r.microTradeVolumeTotal || 0;
|
||||
microTradeGasCostTotal += r.microTradeGasCostTotal || 0;
|
||||
for (const [chainId, v] of Object.entries(r.interventionCostByChain || {})) {
|
||||
if (!interventionCostByChain[chainId]) interventionCostByChain[chainId] = { inject: 0, withdraw: 0 };
|
||||
interventionCostByChain[chainId].inject += v.inject || 0;
|
||||
@@ -838,6 +1034,11 @@ function computeScorecard(scenario, scenarioName, graph, initialState, epochResu
|
||||
intervention_cost_withdraw_total: Math.round(interventionCostWithdrawTotal),
|
||||
intervention_cost_by_chain: interventionCostByChain,
|
||||
intervention_cost_per_1M_volume: Math.round(interventionPer1M * 100) / 100,
|
||||
micro_trade_count: Math.round(microTradeCountTotal),
|
||||
micro_trade_buy_count: Math.round(microTradeBuyCountTotal),
|
||||
micro_trade_sell_count: Math.round(microTradeSellCountTotal),
|
||||
micro_trade_volume_total: Math.round(microTradeVolumeTotal),
|
||||
micro_trade_gas_cost_total: Math.round(microTradeGasCostTotal * 100) / 100,
|
||||
peak_deviation_bps: Math.round(Number.isFinite(peakDeviationBpsPostArb) ? peakDeviationBpsPostArb : 0),
|
||||
peak_deviation_bps_pre_arb: Math.round(peakDeviationBpsPreArb),
|
||||
peak_deviation_bps_post_arb: Math.round(peakDeviationBpsPostArb),
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
*
|
||||
* Rules:
|
||||
* - If bridgeAvailable === true on a chain, cwTokens must include at least cWUSDT and cWUSDC (phase 1).
|
||||
* - For each pmmPool: role in {defense, public_routing}, feeBps and k present,
|
||||
* base/quote (or tokenIn/tokenOut) exist in cwTokens or anchorAddresses.
|
||||
* - For each pmmPool / pmmPoolsVolatile[]: role in {defense, public_routing, truu_routing},
|
||||
* feeBps and k present, base/quote (or tokenIn/tokenOut) exist in cwTokens or anchorAddresses.
|
||||
* TRUU must be listed under anchorAddresses when used as quote (e.g. mainnet chain 1).
|
||||
*
|
||||
* Exit code: 0 if valid, 1 if invalid (and prints errors to stderr).
|
||||
*/
|
||||
@@ -18,12 +19,41 @@ const CONFIG_DIR = path.join(__dirname, '..', 'config');
|
||||
const DEPLOYMENT_STATUS_PATH = path.join(CONFIG_DIR, 'deployment-status.json');
|
||||
|
||||
const PHASE1_CW = ['cWUSDT', 'cWUSDC'];
|
||||
const VALID_ROLES = ['defense', 'public_routing'];
|
||||
const VALID_ROLES = ['defense', 'public_routing', 'truu_routing'];
|
||||
const VALID_REFERENCE_PROTOCOLS = ['uniswap_v3', 'balancer', 'curve', '1inch'];
|
||||
|
||||
function loadJson(p) {
|
||||
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
||||
}
|
||||
|
||||
function validatePoolEntries(chainId, pools, listLabel, knownTokens, errors) {
|
||||
for (let i = 0; i < pools.length; i++) {
|
||||
const pool = pools[i];
|
||||
const base = pool.base ?? pool.tokenIn;
|
||||
const quote = pool.quote ?? pool.tokenOut;
|
||||
|
||||
if (!VALID_ROLES.includes(pool.role)) {
|
||||
errors.push(`Chain ${chainId} ${listLabel}[${i}]: role must be one of ${VALID_ROLES.join(', ')}`);
|
||||
}
|
||||
if (pool.feeBps == null || pool.k == null) {
|
||||
errors.push(`Chain ${chainId} ${listLabel}[${i}]: feeBps and k required`);
|
||||
}
|
||||
if (base && !knownTokens.has(base)) {
|
||||
errors.push(`Chain ${chainId} ${listLabel}[${i}]: base/tokenIn "${base}" not in cwTokens or anchorAddresses`);
|
||||
}
|
||||
if (quote && !knownTokens.has(quote)) {
|
||||
errors.push(`Chain ${chainId} ${listLabel}[${i}]: quote/tokenOut "${quote}" not in cwTokens or anchorAddresses`);
|
||||
}
|
||||
const addr = pool.poolAddress;
|
||||
if (addr != null && addr !== '') {
|
||||
const z = String(addr).toLowerCase();
|
||||
if (z === '0x0000000000000000000000000000000000000000') {
|
||||
errors.push(`Chain ${chainId} ${listLabel}[${i}]: poolAddress must not be zero when set`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const status = loadJson(DEPLOYMENT_STATUS_PATH);
|
||||
const chains = status.chains || {};
|
||||
@@ -31,8 +61,13 @@ function main() {
|
||||
|
||||
for (const [chainId, chain] of Object.entries(chains)) {
|
||||
const cwTokens = chain.cwTokens || {};
|
||||
const gasMirrors = chain.gasMirrors || {};
|
||||
const anchorAddresses = chain.anchorAddresses || {};
|
||||
const gasQuoteAddresses = chain.gasQuoteAddresses || {};
|
||||
const pmmPools = chain.pmmPools || [];
|
||||
const pmmPoolsVolatile = chain.pmmPoolsVolatile || [];
|
||||
const gasPmmPools = chain.gasPmmPools || [];
|
||||
const gasReferenceVenues = chain.gasReferenceVenues || [];
|
||||
const bridgeAvailable = chain.bridgeAvailable;
|
||||
|
||||
if (bridgeAvailable === true) {
|
||||
@@ -43,24 +78,63 @@ function main() {
|
||||
}
|
||||
}
|
||||
|
||||
const knownTokens = new Set([...Object.keys(cwTokens), ...Object.keys(anchorAddresses)]);
|
||||
const knownTokens = new Set([
|
||||
...Object.keys(cwTokens),
|
||||
...Object.keys(gasMirrors),
|
||||
...Object.keys(anchorAddresses),
|
||||
...Object.keys(gasQuoteAddresses),
|
||||
]);
|
||||
|
||||
for (let i = 0; i < pmmPools.length; i++) {
|
||||
const pool = pmmPools[i];
|
||||
const base = pool.base ?? pool.tokenIn;
|
||||
const quote = pool.quote ?? pool.tokenOut;
|
||||
validatePoolEntries(chainId, pmmPools, 'pmmPools', knownTokens, errors);
|
||||
validatePoolEntries(chainId, pmmPoolsVolatile, 'pmmPoolsVolatile', knownTokens, errors);
|
||||
validatePoolEntries(chainId, gasPmmPools, 'gasPmmPools', knownTokens, errors);
|
||||
|
||||
if (!VALID_ROLES.includes(pool.role)) {
|
||||
errors.push(`Chain ${chainId} pmmPools[${i}]: role must be one of ${VALID_ROLES.join(', ')}`);
|
||||
const gasPoolsByFamily = new Map();
|
||||
for (const pool of gasPmmPools) {
|
||||
if (!pool.familyKey || typeof pool.familyKey !== 'string') {
|
||||
errors.push(`Chain ${chainId} gasPmmPools entry is missing familyKey`);
|
||||
continue;
|
||||
}
|
||||
if (pool.feeBps == null || pool.k == null) {
|
||||
errors.push(`Chain ${chainId} pmmPools[${i}]: feeBps and k required`);
|
||||
if (!gasPoolsByFamily.has(pool.familyKey)) gasPoolsByFamily.set(pool.familyKey, []);
|
||||
gasPoolsByFamily.get(pool.familyKey).push(pool);
|
||||
}
|
||||
|
||||
for (const [familyKey, pools] of gasPoolsByFamily.entries()) {
|
||||
const poolTypes = new Set(pools.map((pool) => pool.poolType));
|
||||
if (!poolTypes.has('wrapped_native')) {
|
||||
errors.push(`Chain ${chainId} gas family ${familyKey}: missing wrapped_native DODO pool`);
|
||||
}
|
||||
if (base && !knownTokens.has(base)) {
|
||||
errors.push(`Chain ${chainId} pmmPools[${i}]: base/tokenIn "${base}" not in cwTokens or anchorAddresses`);
|
||||
if (!poolTypes.has('stable_quote')) {
|
||||
errors.push(`Chain ${chainId} gas family ${familyKey}: missing stable_quote DODO pool`);
|
||||
}
|
||||
if (quote && !knownTokens.has(quote)) {
|
||||
errors.push(`Chain ${chainId} pmmPools[${i}]: quote/tokenOut "${quote}" not in cwTokens or anchorAddresses`);
|
||||
}
|
||||
|
||||
const referenceVenuesByFamily = new Map();
|
||||
for (let i = 0; i < gasReferenceVenues.length; i++) {
|
||||
const venue = gasReferenceVenues[i];
|
||||
if (!VALID_REFERENCE_PROTOCOLS.includes(venue.protocol)) {
|
||||
errors.push(`Chain ${chainId} gasReferenceVenues[${i}]: protocol must be one of ${VALID_REFERENCE_PROTOCOLS.join(', ')}`);
|
||||
}
|
||||
if (!venue.familyKey || typeof venue.familyKey !== 'string') {
|
||||
errors.push(`Chain ${chainId} gasReferenceVenues[${i}]: familyKey required`);
|
||||
continue;
|
||||
}
|
||||
if (!referenceVenuesByFamily.has(venue.familyKey)) referenceVenuesByFamily.set(venue.familyKey, []);
|
||||
referenceVenuesByFamily.get(venue.familyKey).push(venue);
|
||||
}
|
||||
|
||||
for (const [familyKey, venues] of referenceVenuesByFamily.entries()) {
|
||||
const protocols = new Set(venues.map((venue) => venue.protocol));
|
||||
if (!protocols.has('uniswap_v3')) {
|
||||
errors.push(`Chain ${chainId} gas family ${familyKey}: missing uniswap_v3 reference venue`);
|
||||
}
|
||||
const oneInch = venues.find((venue) => venue.protocol === '1inch');
|
||||
if (oneInch?.routingVisible === true || oneInch?.live === true) {
|
||||
const hasUniswap = venues.some((venue) => venue.protocol === 'uniswap_v3' && venue.live === true);
|
||||
const hasDodo = (gasPoolsByFamily.get(familyKey) || []).some((pool) => pool.publicRoutingEnabled === true);
|
||||
if (!hasUniswap || !hasDodo) {
|
||||
errors.push(`Chain ${chainId} gas family ${familyKey}: 1inch cannot be live/routingVisible before DODO and Uniswap venues are live`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user