diff --git a/.gitignore b/.gitignore index 3b7203b..7b7c08c 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ datadir/ # Foundry out/ artifacts/ +.omnl-poller-state.json cache/ broadcast/ .cronos-verify/ diff --git a/README.md b/README.md index 2c7d0c8..bd4b844 100644 --- a/README.md +++ b/README.md @@ -578,6 +578,7 @@ See [ALL Mainnet Master Documentation](../docs/MASTER_INDEX.md) for complete int - [Financial Tokenization (Archived)](docs/archive/status-reports/operations-legacy/FINANCIAL_TOKENIZATION.md) - Historical ISO-20022 and SWIFT FIN tokenization notes - [ALL Mainnet Integration](../docs/MASTER_INDEX.md) - Complete ALL Mainnet (651940) integration guide - [Multi-Chain Deployment](docs/deployment/MULTI_CHAIN_DEPLOYMENT_GUIDE.md) - Multi-chain deployment guide +- [HYBX OMNL (reserves, compliance, CCIP mirror)](docs/hybx-omnl/README.md) - Policy, deployment, IPSAS anchor, token-aggregation routes ### πŸ“Š Operations & Runbooks diff --git a/config/chain138-pmm-pools.json b/config/chain138-pmm-pools.json index 3ae0d50..243a869 100644 --- a/config/chain138-pmm-pools.json +++ b/config/chain138-pmm-pools.json @@ -1,8 +1,8 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "description": "Desired-state pool spec for Chain 138 DODO PMM. Scripts should create and register only missing pools from this file, not redeploy contracts.", - "version": "1.0.0", - "updated": "2026-04-15", + "version": "1.1.0", + "updated": "2026-04-19", "chainId": 138, "defaults": { "lpFeeRate": 3, @@ -11,6 +11,7 @@ "enableTwap": false }, "tokens": { + "cBTC": "0xe94260c555aC1d9D3CC9E1632883452ebDf0082E", "cUSDT": "0x93E66202A11B1772E55407B32B44e5Cd8eda7f22", "cUSDC": "0xf22258f57794CC8E06237084b353Ab30fFfa640b", "cEURC": "0x8085961F9cF02b4d800A3c6d386D31da4B34266a", @@ -55,6 +56,9 @@ } }, "explicitPairs": [ + { "baseSymbol": "cBTC", "quoteSymbol": "cUSDT" }, + { "baseSymbol": "cBTC", "quoteSymbol": "cUSDC" }, + { "baseSymbol": "cBTC", "quoteSymbol": "cXAUC" }, { "baseSymbol": "cUSDT", "quoteSymbol": "cUSDC" }, { "baseSymbol": "cUSDT", "quoteSymbol": "USDT" }, { "baseSymbol": "cUSDC", "quoteSymbol": "USDC" }, @@ -70,6 +74,12 @@ { "baseSymbol": "cXAUT", "quoteSymbol": "cUSDC" }, { "baseSymbol": "cEURC", "quoteSymbol": "cEURT" }, { "baseSymbol": "cGBPC", "quoteSymbol": "cGBPT" }, - { "baseSymbol": "cXAUC", "quoteSymbol": "cXAUT" } - ] + { "baseSymbol": "cXAUC", "quoteSymbol": "cXAUT" }, + { "baseSymbol": "WETH", "quoteSymbol": "USDT" }, + { "baseSymbol": "WETH", "quoteSymbol": "USDC" }, + { "baseSymbol": "cUSDT", "quoteSymbol": "WETH" }, + { "baseSymbol": "cUSDC", "quoteSymbol": "WETH" }, + { "baseSymbol": "cEURT", "quoteSymbol": "WETH" } + ], + "plannedPairs": [] } diff --git a/docs/04-configuration/CRONOS_EXPLORER_OPERATIONS.md b/docs/04-configuration/CRONOS_EXPLORER_OPERATIONS.md index acedf33..aba0817 100644 --- a/docs/04-configuration/CRONOS_EXPLORER_OPERATIONS.md +++ b/docs/04-configuration/CRONOS_EXPLORER_OPERATIONS.md @@ -53,9 +53,9 @@ Per [Contract Deployment and Verification](https://docs.cronos.org/for-dapp-deve - Requires API key in Hardhat config ### 3. Foundry -- `forge verify-contract --verifier blockscout` fails (Cronos Explorer API format differs) -- Run `./scripts/deployment/verify-cronos-contracts.sh` to attempt; falls back to manual verification -- **Recommended**: Manual verification via the web interface above +- `forge verify-contract` does not successfully submit verification to Cronos Explorer (legacy Etherscan-style POST returns 404; Etherscan v2 does not support chain id 25). +- Run `./scripts/deployment/verify-cronos-contracts.sh` to confirm bytecode on RPC and print manual steps (optional `CRONOS_EXPORT_SOURCES=1` to regenerate Standard-JSON). +- **Recommended**: Manual verification via the web interface above and `docs/deployment/CRONOS_VERIFICATION_RUNBOOK.md` --- diff --git a/docs/MASTER_DOCUMENTATION_INDEX.md b/docs/MASTER_DOCUMENTATION_INDEX.md index c14a7d7..253edaa 100644 --- a/docs/MASTER_DOCUMENTATION_INDEX.md +++ b/docs/MASTER_DOCUMENTATION_INDEX.md @@ -66,6 +66,16 @@ This is the **master index** of all project documentation. Use this as your star - **[36-REGION-BLUEPRINT.md](deployment/36-REGION-BLUEPRINT.md)** - 36-region deployment blueprint - **[DEPLOYMENT_CONFIGURATION_AUDIT.md](deployment/DEPLOYMENT_CONFIGURATION_AUDIT.md)** - Configuration audit +### HYBX OMNL (reserves, compliance, CCIP mirror) + +- **[DEPLOYMENT_CHECKLIST.md](hybx-omnl/DEPLOYMENT_CHECKLIST.md)** β€” OMNL deploy and token-aggregation checklist +- **[OMNL_RECONCILE_CRON_AND_CI.md](hybx-omnl/OMNL_RECONCILE_CRON_AND_CI.md)** β€” IPSAS/journal anchor: cron, artifact script, GitHub Actions +- **[OPERATIONAL_COMPLIANCE.md](hybx-omnl/OPERATIONAL_COMPLIANCE.md)** β€” Webhooks, retention, reconciliation anchors +- **[OMNL_IPSAS_API.md](hybx-omnl/OMNL_IPSAS_API.md)** β€” IPSAS / GL API surface +- **[CCIP_MIRROR_FLOW.md](hybx-omnl/CCIP_MIRROR_FLOW.md)** β€” CCIP mirror flow +- **[HYBX_OMNL_POLICY_SPEC.md](hybx-omnl/HYBX_OMNL_POLICY_SPEC.md)** β€” Policy parameters (M0/M1 rules) +- **[EXTERNAL_AUDIT_CHECKLIST.md](hybx-omnl/EXTERNAL_AUDIT_CHECKLIST.md)** β€” Third-party audit scope + --- ## πŸ”§ Operations diff --git a/docs/archive/status-reports/operations-legacy/d-bis.org.txt b/docs/archive/status-reports/operations-legacy/d-bis.org.txt index 73170af..81f8110 100644 --- a/docs/archive/status-reports/operations-legacy/d-bis.org.txt +++ b/docs/archive/status-reports/operations-legacy/d-bis.org.txt @@ -37,7 +37,7 @@ besu.d-bis.org. 1 IN A 70.153.83.83 ; cf_tags=cf-proxied:true blockscout.d-bis.org. 1 IN A 20.215.32.42 ; cf_tags=cf-proxied:true blockscout.d-bis.org. 1 IN A 70.153.83.83 ; cf_tags=cf-proxied:true d-bis.org. 1 IN A 20.215.32.42 ; cf_tags=cf-proxied:true -d-bis.org. 1 IN A 20.215.32.15 ; Digital Bank of International Settlements - Defi Oracle Meta Mainnet cf_tags=cf-proxied:true +d-bis.org. 1 IN A 20.215.32.15 ; Digital Bank of International Settlements - DeFi Oracle Meta Mainnet cf_tags=cf-proxied:true docs.d-bis.org. 1 IN A 20.8.47.226 ; cf_tags=cf-proxied:false explorer.d-bis.org. 1 IN A 20.215.32.42 ; cf_tags=cf-proxied:true explorer.d-bis.org. 1 IN A 70.153.83.83 ; cf_tags=cf-proxied:true diff --git a/docs/deployment/CCIP_BRIDGE_DESTINATIONS_AND_LINK_FUNDING.md b/docs/deployment/CCIP_BRIDGE_DESTINATIONS_AND_LINK_FUNDING.md index b36e4a5..387ec3c 100644 --- a/docs/deployment/CCIP_BRIDGE_DESTINATIONS_AND_LINK_FUNDING.md +++ b/docs/deployment/CCIP_BRIDGE_DESTINATIONS_AND_LINK_FUNDING.md @@ -72,10 +72,16 @@ Repeat for each destination chain (BSC, Polygon, Base, Optimism, Avalanche, Cron --- -## 4. LINK Funding +## 4. LINK Funding and Activation Scope - CCIP charges fees for each cross-chain message; bridges typically pay in **LINK** (or native) depending on the contract’s `feeToken` and `ccipSend` usage. - **Per chain**: Ensure the **bridge contract** holds enough **LINK** (and native for gas) so it can pay CCIP fees when users call `sendCrossChain` (or equivalent). +- Important distinction: + - **Destination-only receipt** on a chain does not by itself require LINK on that chain's bridge contract, because `ccipReceive(...)` does not pay the CCIP fee. + - **Outbound sending** from a chain does require a fee payment path on that chain's bridge contract. +- Current WEMIX nuance: + - `CCIPWETH9Bridge` may be configured for native-fee mode by setting `feeToken = address(0)`. + - `CCIPWETH10Bridge` still requires LINK in the current implementation. - **How to fund**: 1. Get LINK on the same chain as the bridge (e.g. from an exchange or faucet). 2. Transfer LINK to the **bridge contract address** (the same address you use for `addDestination`), or use any approved mechanism (e.g. admin top-up function if the contract has one). diff --git a/docs/deployment/DEPLOYMENT_INDEX.md b/docs/deployment/DEPLOYMENT_INDEX.md index 11297df..b439e51 100644 --- a/docs/deployment/DEPLOYMENT_INDEX.md +++ b/docs/deployment/DEPLOYMENT_INDEX.md @@ -84,6 +84,12 @@ This index helps you find the right deployment guide for your needs. - [Cloud for Sovereignty Landing Zone](CLOUD_FOR_SOVEREIGNTY_LANDING_ZONE.md) - Cloud for sovereignty landing zone - [Deployment Firefly Cacti](DEPLOYMENT_FIREFLY_CACTI.md) - Firefly and Cacti deployment +### HYBX OMNL (reserve commitments, compliance, CCIP mirror, IPSAS GL) +- [HYBX OMNL documentation index](../hybx-omnl/README.md) - All OMNL guides in one place +- [OMNL deployment checklist](../hybx-omnl/DEPLOYMENT_CHECKLIST.md) - Contracts, env, API, verification +- [OMNL reconcile (cron & CI)](../hybx-omnl/OMNL_RECONCILE_CRON_AND_CI.md) - Anchor hash artifacts and automation +- [OMNL IPSAS / GL API](../hybx-omnl/OMNL_IPSAS_API.md) - Reporting API surface + ## Historical/Status Reports These are historical deployment reports and status documents. Consider archiving if older than 6 months: diff --git a/docs/deployment/ENV_EXAMPLE_CONTENT.md b/docs/deployment/ENV_EXAMPLE_CONTENT.md index e656dd7..1b73146 100644 --- a/docs/deployment/ENV_EXAMPLE_CONTENT.md +++ b/docs/deployment/ENV_EXAMPLE_CONTENT.md @@ -18,40 +18,50 @@ PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000000 # ============================================================================= # RPC Endpoints # ============================================================================= +# Infura project (used for supported public chains) +INFURA_PROJECT_ID=43b945b33d58463a9246cf5ca8aa6286 +INFURA_PROJECT_SECRET= +INFURA_GAS_API=https://gas.api.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286 + # Ethereum Mainnet -ETH_MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_KEY -# Alternative: ETH_MAINNET_RPC_URL=https://mainnet.infura.io/v3/YOUR_INFURA_KEY +ETH_MAINNET_RPC_URL=https://mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286 +ETHEREUM_MAINNET_RPC=https://mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286 # Cronos (Crypto.com) CRONOS_RPC_URL=https://evm.cronos.org +CRONOS_MAINNET_RPC=https://evm.cronos.org # BSC (BNB Smart Chain) -BSC_RPC_URL=https://bsc-dataseed1.binance.org -# Alternative: BSC_RPC_URL=https://bsc-dataseed.binance.org +BSC_RPC_URL=https://bsc-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286 +BSC_MAINNET_RPC=https://bsc-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286 # Polygon PoS -POLYGON_RPC_URL=https://polygon-rpc.com -# Alternative: POLYGON_RPC_URL=https://rpc-mainnet.maticvigil.com +POLYGON_RPC_URL=https://polygon-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286 +POLYGON_MAINNET_RPC=https://polygon-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286 # Gnosis Chain (PoA) GNOSIS_RPC_URL=https://rpc.gnosischain.com -# Alternative: GNOSIS_RPC_URL=https://xdai-archive.blockscout.com +GNOSIS_MAINNET_RPC=https://rpc.gnosischain.com # Avalanche C-Chain -AVALANCHE_RPC_URL=https://api.avax.network/ext/bc/C/rpc -# Alternative: AVALANCHE_RPC_URL=https://avalanche-mainnet.infura.io/v3/YOUR_KEY +AVALANCHE_RPC_URL=https://avalanche-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286 +AVALANCHE_MAINNET_RPC=https://avalanche-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286 # Base -BASE_RPC_URL=https://mainnet.base.org -# Alternative: BASE_RPC_URL=https://base-mainnet.g.alchemy.com/v2/YOUR_KEY +BASE_RPC_URL=https://base-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286 +BASE_MAINNET_RPC=https://base-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286 # Arbitrum One -ARBITRUM_RPC_URL=https://arb1.arbitrum.io/rpc -# Alternative: ARBITRUM_RPC_URL=https://arbitrum-mainnet.infura.io/v3/YOUR_KEY +ARBITRUM_RPC_URL=https://arbitrum-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286 +ARBITRUM_MAINNET_RPC=https://arbitrum-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286 # Optimism -OPTIMISM_RPC_URL=https://mainnet.optimism.io -# Alternative: OPTIMISM_RPC_URL=https://optimism-mainnet.infura.io/v3/YOUR_KEY +OPTIMISM_RPC_URL=https://optimism-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286 +OPTIMISM_MAINNET_RPC=https://optimism-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286 + +# Celo +CELO_RPC_URL=https://celo-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286 +CELO_MAINNET_RPC=https://celo-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286 # Chain-138 (DeFi Oracle Meta Mainnet) # For deployment use Core RPC (IP:port); do not use FQDN (DNS/tunnel can fail). @@ -229,6 +239,11 @@ CCIPLOGGER_MAINNET= # ============================================================================= # Default: CW_BRIDGE_ADDRESS=0x0; use per-chain overrides (best practice). In .env, CW_BRIDGE_ are set from the deployed bridge suite (CCIP_RELAY_BRIDGE_MAINNET, CCIPWETH9_BRIDGE_*). # Per-chain: CW_BRIDGE_MAINNET, CW_BRIDGE_CRONOS, CW_BRIDGE_BSC, CW_BRIDGE_POLYGON, CW_BRIDGE_GNOSIS, CW_BRIDGE_AVALANCHE, CW_BRIDGE_BASE, CW_BRIDGE_ARBITRUM, CW_BRIDGE_OPTIMISM +# Bridge-only operational-role defaults for new cW* deployments: +CW_STRICT_MODE=1 +CW_FREEZE_OPERATIONAL_ROLES=1 +# Optional: governance multisig/controller that should hold admin and governance metadata roles on newer cW* deployments +CW_GOVERNANCE_ADMIN= # After deploy, set: # CWUSDT_CRONOS=0x... # CWUSDC_CRONOS=0x... @@ -338,4 +353,3 @@ WETH10_ADDRESS=${WETH10_MAINNET} ``` 3. Never commit `.env` to version control! - diff --git a/foundry.toml b/foundry.toml index adea758..ac2ccfd 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,6 +6,13 @@ # `bash scripts/forge/scope.sh ...` so Forge only sees the contract subtree you # are actively editing. src = "contracts" +# Vendored Uniswap V2 trees pin solc 0.5.x / 0.6.x; skip lets `forge test` use 0.8.20 +# without legacy solc installs. Nothing outside vendor/ imports these paths. +skip = [ + "contracts/vendor/uniswap-v2-core/**/*.sol", + "contracts/vendor/uniswap-v2-periphery/**/*.sol", + "contracts/vendor/sushiswap-v2/**/*.sol", +] out = "out" libs = ["lib"] solc = "0.8.20" @@ -53,6 +60,15 @@ evm_version = "cancun" optimizer = true optimizer_runs = 100 via_ir = true +# Match default execution target (Chain 138 / Besu uses Cancun-capable clients). +evm_version = "cancun" + +[profile.cronos_legacy] +optimizer = true +optimizer_runs = 100 +via_ir = true +# Cronos chain 25 currently needs pre-Shanghai bytecode; target Paris to avoid PUSH0. +evm_version = "paris" # RPC endpoints β€” use: forge create ... --rpc-url chain138 # Prevents default localhost:8545 when ETH_RPC_URL not set @@ -66,6 +82,7 @@ cronos = "https://evm.cronos.org" [etherscan] avalanche = { key = "${SNOWTRACE_API_KEY:-${ETHERSCAN_API_KEY}}", url = "https://api.snowtrace.io/api" } arbitrum = { key = "${ARBISCAN_API_KEY:-${ETHERSCAN_API_KEY}}", url = "https://api.arbiscan.io/api" } -# Cronos mainnet (chain 25). Etherscan-style API; chainid required for v2. -# API key from explorer.cronos.org/register (CRONOSCAN_API_KEY) +# Cronos mainnet (chain 25). Legacy entry β€” forge verify-contract does not +# successfully submit to Cronos Explorer (see CRONOS_EXPLORER_OPERATIONS.md). +# Kept for cast/ethers compatibility if tooling expects [etherscan].cronos. cronos = { key = "${CRONOSCAN_API_KEY:-${ETHERSCAN_API_KEY}}", url = "https://explorer-api.cronos.org/mainnet/api?chainid=25" } diff --git a/frontend-dapp/src/config/__tests__/networks.test.ts b/frontend-dapp/src/config/__tests__/networks.test.ts index 9046a4d..c00c8b1 100644 --- a/frontend-dapp/src/config/__tests__/networks.test.ts +++ b/frontend-dapp/src/config/__tests__/networks.test.ts @@ -27,6 +27,6 @@ describe('frontend network config', () => { expect(networks.chain2138TestnetEnabled).toBe(true) expect(networks.frontendSourceChainIds).toEqual([138, 2138]) expect(networks.defaultFrontendChainId).toBe(2138) - expect(networks.defaultFrontendChainName).toBe('Defi Oracle Meta Testnet') + expect(networks.defaultFrontendChainName).toBe('DeFi Oracle Meta Testnet') }) }) diff --git a/frontend-dapp/src/config/networks.ts b/frontend-dapp/src/config/networks.ts index 25349ae..284b21d 100644 --- a/frontend-dapp/src/config/networks.ts +++ b/frontend-dapp/src/config/networks.ts @@ -30,10 +30,10 @@ const configuredDefaultFrontendChainId = Number( import.meta.env.VITE_DEFAULT_FRONTEND_CHAIN_ID || 138 ) -/** Chain 2138 - Defi Oracle Meta Testnet (optional; enable with VITE_ENABLE_CHAIN2138) */ +/** Chain 2138 - DeFi Oracle Meta Testnet (optional; enable with VITE_ENABLE_CHAIN2138) */ export const chain2138Testnet = defineChain({ id: 2138, - name: 'Defi Oracle Meta Testnet', + name: 'DeFi Oracle Meta Testnet', network: 'chain2138-testnet', nativeCurrency: { decimals: 18, @@ -46,7 +46,7 @@ export const chain2138Testnet = defineChain({ }, blockExplorers: { default: { - name: 'Defi Oracle Meta Testnet Explorer', + name: 'DeFi Oracle Meta Testnet Explorer', url: explorerUrl2138, }, }, diff --git a/package.json b/package.json index aa5c5c8..04cb48d 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,11 @@ "forge:test:vault": "FORGE_SCOPE=vault bash scripts/forge/scope.sh test --match-path 'test/vault/Ledger.t.sol'", "forge:test:iso": "FORGE_SCOPE=iso4217w bash scripts/forge/scope.sh test --match-path 'test/iso4217w/*.t.sol'", "forge:test:quick": "FORGE_SCOPE=vault bash scripts/forge/scope.sh test --match-contract LedgerTest", + "forge:test:omnl": "bash scripts/forge/scope.sh test hybx-omnl", + "omnl:verify": "bash scripts/hybx-omnl/verify-deployment.sh", + "omnl:reconcile:artifact": "bash scripts/hybx-omnl/omnl-reconcile-artifact.sh", + "omnl:validate-cross-chain": "node scripts/hybx-omnl/validate-cross-chain-config.mjs", + "omnl:sync-publish": "bash scripts/hybx-omnl/sync-to-publish.sh", "dodo-pools:create": "bash scripts/create-all-dodo-pools-from-token-api.sh", "dodo-pools:dry-run": "DRY_RUN=true bash scripts/create-all-dodo-pools-from-token-api.sh", "prereqs": "bash scripts/deployment/ensure-prerequisites.sh", diff --git a/script/deploy/DeployCWTokens.s.sol b/script/deploy/DeployCWTokens.s.sol index 69625a8..a8e2a8e 100644 --- a/script/deploy/DeployCWTokens.s.sol +++ b/script/deploy/DeployCWTokens.s.sol @@ -12,9 +12,9 @@ import {CompliantWrappedToken} from "../../contracts/tokens/CompliantWrappedToke * Env: * PRIVATE_KEY (required) * CW_BRIDGE_ADDRESS (required) β€” address that can mint/burn (e.g. CCIP receiver or custom bridge) - * CW_STRICT_MODE=1 (optional) β€” revoke deployer MINTER/BURNER after bridge grant + * CW_STRICT_MODE=0 (optional override) β€” by default deployer MINTER/BURNER are revoked after bridge grant * CW_GOVERNANCE_ADMIN=0x... (optional) β€” grant DEFAULT_ADMIN_ROLE to governance; if strict, revoke deployer admin when governance is set - * CW_FREEZE_OPERATIONAL_ROLES=1 (optional) β€” freeze future MINTER/BURNER changes after setup + * CW_FREEZE_OPERATIONAL_ROLES=0 (optional override) β€” by default freeze future MINTER/BURNER changes after setup * DEPLOY_CWUSDT=1, DEPLOY_CWUSDC=1, DEPLOY_CWUSDW=1, DEPLOY_CWEURC=1, ... (default all 1; set 0 to skip a token) */ contract DeployCWTokens is Script { @@ -24,8 +24,8 @@ contract DeployCWTokens is Script { uint256 pk = vm.envUint("PRIVATE_KEY"); address deployer = vm.addr(pk); address bridge = vm.envAddress("CW_BRIDGE_ADDRESS"); - bool strictMode = vm.envOr("CW_STRICT_MODE", uint256(0)) == 1; - bool freezeOperationalRoles = vm.envOr("CW_FREEZE_OPERATIONAL_ROLES", uint256(0)) == 1; + bool strictMode = vm.envOr("CW_STRICT_MODE", uint256(1)) == 1; + bool freezeOperationalRoles = vm.envOr("CW_FREEZE_OPERATIONAL_ROLES", uint256(1)) == 1; address governanceAdmin = vm.envOr("CW_GOVERNANCE_ADMIN", address(0)); require(bridge != address(0), "CW_BRIDGE_ADDRESS required"); diff --git a/script/deploy/DeploySingleCWToken.s.sol b/script/deploy/DeploySingleCWToken.s.sol index cdf5503..c3a4d92 100644 --- a/script/deploy/DeploySingleCWToken.s.sol +++ b/script/deploy/DeploySingleCWToken.s.sol @@ -14,6 +14,9 @@ import {CompliantWrappedToken} from "../../contracts/tokens/CompliantWrappedToke * CW_TOKEN_NAME (required) * CW_TOKEN_SYMBOL (required) * CW_TOKEN_DECIMALS (optional, default 6) + * CW_STRICT_MODE=0 (optional override) β€” by default deployer MINTER/BURNER are revoked after bridge grant + * CW_GOVERNANCE_ADMIN=0x... (optional) β€” grant DEFAULT_ADMIN_ROLE to governance; if strict, revoke deployer admin when governance is set + * CW_FREEZE_OPERATIONAL_ROLES=0 (optional override) β€” by default freeze future MINTER/BURNER changes after setup */ contract DeploySingleCWToken is Script { function run() external { @@ -23,6 +26,9 @@ contract DeploySingleCWToken is Script { string memory tokenName = vm.envString("CW_TOKEN_NAME"); string memory tokenSymbol = vm.envString("CW_TOKEN_SYMBOL"); uint8 decimals_ = uint8(vm.envOr("CW_TOKEN_DECIMALS", uint256(6))); + bool strictMode = vm.envOr("CW_STRICT_MODE", uint256(1)) == 1; + bool freezeOperationalRoles = vm.envOr("CW_FREEZE_OPERATIONAL_ROLES", uint256(1)) == 1; + address governanceAdmin = vm.envOr("CW_GOVERNANCE_ADMIN", address(0)); require(bridge != address(0), "CW_BRIDGE_ADDRESS required"); require(bytes(tokenName).length != 0, "CW_TOKEN_NAME required"); @@ -34,10 +40,32 @@ contract DeploySingleCWToken is Script { token.grantRole(token.MINTER_ROLE(), bridge); token.grantRole(token.BURNER_ROLE(), bridge); + if (strictMode) { + token.revokeRole(token.MINTER_ROLE(), admin); + token.revokeRole(token.BURNER_ROLE(), admin); + } + + if (governanceAdmin != address(0) && governanceAdmin != admin) { + token.grantRole(token.DEFAULT_ADMIN_ROLE(), governanceAdmin); + } + + if (freezeOperationalRoles) { + token.freezeOperationalRoles(); + } + + if (strictMode && governanceAdmin != address(0) && governanceAdmin != admin) { + token.revokeRole(token.DEFAULT_ADMIN_ROLE(), admin); + } + vm.stopBroadcast(); console.log(tokenSymbol, address(token)); console.log(" bridge", bridge); + console.log(" strictMode", strictMode); + console.log(" governanceAdmin", governanceAdmin); + console.log(" operationalRolesFrozen", token.operationalRolesFrozen()); + console.log(" deployerHasMinter", token.hasRole(token.MINTER_ROLE(), admin)); + console.log(" deployerHasBurner", token.hasRole(token.BURNER_ROLE(), admin)); console.log(" bridgeHasMinter", token.hasRole(token.MINTER_ROLE(), bridge)); console.log(" bridgeHasBurner", token.hasRole(token.BURNER_ROLE(), bridge)); } diff --git a/script/dex/AddLiquidityPMMPoolsChain138.s.sol b/script/dex/AddLiquidityPMMPoolsChain138.s.sol index 3f93a90..0c1cb19 100644 --- a/script/dex/AddLiquidityPMMPoolsChain138.s.sol +++ b/script/dex/AddLiquidityPMMPoolsChain138.s.sol @@ -7,9 +7,10 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; /** * @title AddLiquidityPMMPoolsChain138 - * @notice Add liquidity to the three DODO PMM pools on Chain 138 (cUSDT/cUSDC, cUSDT/USDT, cUSDC/USDC). + * @notice Add liquidity to the canonical DODO PMM pools on Chain 138, including the stable/WETH lanes. * @dev Env: PRIVATE_KEY, RPC_URL_138, DODO_PMM_INTEGRATION_ADDRESS (or DODO_PMM_INTEGRATION), * POOL_CUSDTCUSDC, POOL_CUSDTUSDT, POOL_CUSDCUSDC, + * optional POOL_CUSDTWETH, POOL_CUSDCWETH, POOL_CEURTWETH, * ADD_LIQUIDITY_BASE_AMOUNT, ADD_LIQUIDITY_QUOTE_AMOUNT (e.g. 1000000e6 for 1M units, 6 decimals). * Optional: ADD_LIQUIDITY_CUSDTCUSDC_BASE, ADD_LIQUIDITY_CUSDTCUSDC_QUOTE, etc. for per-pool amounts. * Optional: NEXT_NONCE β€” set to pending nonce (e.g. after mints) to avoid -32001 "Nonce too low" on broadcast. @@ -31,6 +32,9 @@ contract AddLiquidityPMMPoolsChain138 is Script { address poolCusdtCusdc = vm.envOr("POOL_CUSDTCUSDC", address(0)); address poolCusdtUsdt = vm.envOr("POOL_CUSDTUSDT", address(0)); address poolCusdcUsdc = vm.envOr("POOL_CUSDCUSDC", address(0)); + address poolCusdtWeth = vm.envOr("POOL_CUSDTWETH", address(0)); + address poolCusdcWeth = vm.envOr("POOL_CUSDCWETH", address(0)); + address poolCeurtWeth = vm.envOr("POOL_CEURTWETH", address(0)); uint256 defaultBase = vm.envOr("ADD_LIQUIDITY_BASE_AMOUNT", uint256(0)); uint256 defaultQuote = vm.envOr("ADD_LIQUIDITY_QUOTE_AMOUNT", uint256(0)); @@ -40,6 +44,8 @@ contract AddLiquidityPMMPoolsChain138 is Script { address cusdc = integration.compliantUSDC(); address usdt = integration.officialUSDT(); address usdc = integration.officialUSDC(); + address ceurt = vm.envOr("CEURT_ADDRESS_138", address(0xdf4b71c61E5912712C1Bdd451416B9aC26949d72)); + address weth = vm.envOr("WETH9_ADDRESS_138", vm.envOr("WETH_ADDRESS_138", address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2))); // On Chain 138, cUSDT/USDT and cUSDC/USDC should point at live local mirror quote tokens. // If the configured quote-side addresses have no code on 138, skip those pools to avoid "call to non-contract". @@ -73,6 +79,30 @@ contract AddLiquidityPMMPoolsChain138 is Script { console.log("Added liquidity to cUSDC/USDC pool:", poolCusdcUsdc); } } + if (poolCusdtWeth != address(0) && (defaultBase > 0 || defaultQuote > 0)) { + uint256 b = vm.envOr("ADD_LIQUIDITY_CUSDTWETH_BASE", defaultBase); + uint256 q = vm.envOr("ADD_LIQUIDITY_CUSDTWETH_QUOTE", defaultQuote); + if (b > 0 && q > 0) { + _approveAndAdd(integration, cusdt, weth, poolCusdtWeth, b, q); + console.log("Added liquidity to cUSDT/WETH pool:", poolCusdtWeth); + } + } + if (poolCusdcWeth != address(0) && (defaultBase > 0 || defaultQuote > 0)) { + uint256 b = vm.envOr("ADD_LIQUIDITY_CUSDCWETH_BASE", defaultBase); + uint256 q = vm.envOr("ADD_LIQUIDITY_CUSDCWETH_QUOTE", defaultQuote); + if (b > 0 && q > 0) { + _approveAndAdd(integration, cusdc, weth, poolCusdcWeth, b, q); + console.log("Added liquidity to cUSDC/WETH pool:", poolCusdcWeth); + } + } + if (poolCeurtWeth != address(0) && (defaultBase > 0 || defaultQuote > 0)) { + uint256 b = vm.envOr("ADD_LIQUIDITY_CEURTWETH_BASE", defaultBase); + uint256 q = vm.envOr("ADD_LIQUIDITY_CEURTWETH_QUOTE", defaultQuote); + if (b > 0 && q > 0) { + _approveAndAdd(integration, ceurt, weth, poolCeurtWeth, b, q); + console.log("Added liquidity to cEURT/WETH pool:", poolCeurtWeth); + } + } vm.stopBroadcast(); } diff --git a/script/flash/RunMainnetAaveCwusdcUsdcQuotePushOnce.s.sol b/script/flash/RunMainnetAaveCwusdcUsdcQuotePushOnce.s.sol index b84014e..ec0a4c1 100644 --- a/script/flash/RunMainnetAaveCwusdcUsdcQuotePushOnce.s.sol +++ b/script/flash/RunMainnetAaveCwusdcUsdcQuotePushOnce.s.sol @@ -22,9 +22,9 @@ interface IDODOPMMPoolQuote { * FLASH_QUOTE_AMOUNT_RAW gross USDC borrowed / pushed (6 decimals raw) * * Optional: - * POOL_CWUSDC_USDC_MAINNET default 0x69776fc607e9edA8042e320e7e43f54d06c68f0E * CWUSDC_MAINNET default canonical cWUSDC * USDC_MAINNET default official USDC + * MAX_FLASH_QUOTE_AMOUNT_RAW optional tighter local cap; must not exceed the defended-lane safe cap * MIN_OUT_PMM if unset, derived from querySellQuote(receiver, amount) * MIN_OUT_PMM_NUM / MIN_OUT_PMM_DEN (defaults 985/1000) * MIN_OUT_PMM_NUM / MIN_OUT_PMM_DEN * MIN_OUT_UNWIND if unset, amount + ceil(amount * AAVE_FLASH_PREMIUM_BPS / 10000) + MIN_OUT_UNWIND_BUFFER_RAW @@ -50,6 +50,7 @@ contract RunMainnetAaveCwusdcUsdcQuotePushOnce is Script { address internal constant DEFAULT_POOL = 0x69776fc607e9edA8042e320e7e43f54d06c68f0E; address internal constant DEFAULT_CWUSDC = 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a; address internal constant DEFAULT_USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + uint256 internal constant DEFENDED_SAFE_CAP_RAW = 2_964_298; function run() external { uint256 pk = vm.envUint("PRIVATE_KEY"); @@ -60,6 +61,9 @@ contract RunMainnetAaveCwusdcUsdcQuotePushOnce is Script { address usdc = vm.envOr("USDC_MAINNET", DEFAULT_USDC); address unwinder = vm.envAddress("QUOTE_PUSH_EXTERNAL_UNWINDER_MAINNET"); uint256 amount = vm.envUint("FLASH_QUOTE_AMOUNT_RAW"); + uint256 localCap = vm.envOr("MAX_FLASH_QUOTE_AMOUNT_RAW", DEFENDED_SAFE_CAP_RAW); + + _validateDefendedLane(pool, amount, localCap); uint256 minPmmNum = vm.envOr("MIN_OUT_PMM_NUM", uint256(985)); uint256 minPmmDen = vm.envOr("MIN_OUT_PMM_DEN", uint256(1000)); @@ -150,4 +154,10 @@ contract RunMainnetAaveCwusdcUsdcQuotePushOnce is Script { AaveQuotePushFlashReceiver(receiver).flashQuotePush(usdc, amount, p); vm.stopBroadcast(); } + + function _validateDefendedLane(address pool, uint256 amount, uint256 localCap) internal pure { + require(pool == DEFAULT_POOL, "defended pool only"); + require(localCap <= DEFENDED_SAFE_CAP_RAW, "local cap exceeds defended safe cap"); + require(amount <= localCap, "flash amount exceeds cap"); + } } diff --git a/script/flash/RunManagedMainnetAaveCwusdcUsdcQuotePushCycle.s.sol b/script/flash/RunManagedMainnetAaveCwusdcUsdcQuotePushCycle.s.sol index b97103a..b3354f2 100644 --- a/script/flash/RunManagedMainnetAaveCwusdcUsdcQuotePushCycle.s.sol +++ b/script/flash/RunManagedMainnetAaveCwusdcUsdcQuotePushCycle.s.sol @@ -31,6 +31,7 @@ contract RunManagedMainnetAaveCwusdcUsdcQuotePushCycle is Script { address internal constant DEFAULT_POOL = 0x69776fc607e9edA8042e320e7e43f54d06c68f0E; address internal constant DEFAULT_CWUSDC = 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a; address internal constant DEFAULT_USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + uint256 internal constant DEFENDED_SAFE_CAP_RAW = 2_964_298; function run() external { uint256 pk = vm.envUint("PRIVATE_KEY"); @@ -42,9 +43,12 @@ contract RunManagedMainnetAaveCwusdcUsdcQuotePushCycle is Script { address usdc = vm.envOr("USDC_MAINNET", DEFAULT_USDC); address unwinder = vm.envAddress("QUOTE_PUSH_EXTERNAL_UNWINDER_MAINNET"); uint256 amount = vm.envUint("FLASH_QUOTE_AMOUNT_RAW"); + uint256 localCap = vm.envOr("MAX_FLASH_QUOTE_AMOUNT_RAW", DEFENDED_SAFE_CAP_RAW); bool harvest = vm.envOr("QUOTE_PUSH_TREASURY_HARVEST", uint256(1)) == 1; uint256 gasHoldbackTargetRaw = vm.envOr("QUOTE_PUSH_TREASURY_GAS_HOLDBACK_TARGET_RAW", uint256(0)); + _validateDefendedLane(pool, amount, localCap); + QuotePushTreasuryManager manager = QuotePushTreasuryManager(managerAddr); AaveQuotePushFlashReceiver.QuotePushParams memory p = _loadQuotePushParams(receiver, pool, integration, baseToken, unwinder, amount); @@ -70,6 +74,12 @@ contract RunManagedMainnetAaveCwusdcUsdcQuotePushCycle is Script { console.log("receiverSweepableAfter", manager.receiverSweepableQuote()); } + function _validateDefendedLane(address pool, uint256 amount, uint256 localCap) internal pure { + require(pool == DEFAULT_POOL, "defended pool only"); + require(localCap <= DEFENDED_SAFE_CAP_RAW, "local cap exceeds defended safe cap"); + require(amount <= localCap, "flash amount exceeds cap"); + } + function _loadQuotePushParams( address receiver, address pool, diff --git a/script/liquidity/ImportProviderPoolsToIntegration.s.sol b/script/liquidity/ImportProviderPoolsToIntegration.s.sol index 64581cc..7e30acd 100644 --- a/script/liquidity/ImportProviderPoolsToIntegration.s.sol +++ b/script/liquidity/ImportProviderPoolsToIntegration.s.sol @@ -95,6 +95,14 @@ contract ImportProviderPoolsToIntegration is Script { _importIfNeeded(json, providerSource, integration, explicitBase, explicitQuote, lpFeeRate, initialPrice, kFactor, enableTwap); } + for (uint256 i = 0; json.keyExists(string.concat(".plannedPairs[", vm.toString(i), "]")); i++) { + string memory baseKey = string.concat(".plannedPairs[", vm.toString(i), "].baseSymbol"); + string memory quoteKey = string.concat(".plannedPairs[", vm.toString(i), "].quoteSymbol"); + string memory plannedBase = json.readString(baseKey); + string memory plannedQuote = json.readString(quoteKey); + _importIfNeeded(json, providerSource, integration, plannedBase, plannedQuote, lpFeeRate, initialPrice, kFactor, enableTwap); + } + vm.stopBroadcast(); } diff --git a/scripts/deployment/deploy-pmm-all-l2s.sh b/scripts/deployment/deploy-pmm-all-l2s.sh index 828a961..57b0376 100755 --- a/scripts/deployment/deploy-pmm-all-l2s.sh +++ b/scripts/deployment/deploy-pmm-all-l2s.sh @@ -28,6 +28,14 @@ if [[ ${#CHAIN_FILTER[@]} -eq 0 && -n "${DEPLOY_PMM_L2S_FILTER:-}" ]]; then for n in $DEPLOY_PMM_L2S_FILTER; do n="$(normalize_chain_name "$n")"; [[ -n "$n" ]] && CHAIN_FILTER+=("$n"); done fi +forge_profile_for_chain() { + local chain_id="$1" + case "$chain_id" in + 25) printf '%s' "cronos_legacy" ;; + *) printf '%s' "${FOUNDRY_PROFILE:-default}" ;; + esac +} + CHAINS=( "BSC:56:BSC_RPC_URL" "POLYGON:137:POLYGON_MAINNET_RPC" @@ -35,18 +43,25 @@ CHAINS=( "OPTIMISM:10:OPTIMISM_MAINNET_RPC" "ARBITRUM:42161:ARBITRUM_MAINNET_RPC" "AVALANCHE:43114:AVALANCHE_RPC_URL" - "CRONOS:25:CRONOS_RPC_URL" + "CRONOS:25:CRONOS_RPC_URL|CRONOS_RPC" "GNOSIS:100:GNOSIS_MAINNET_RPC" "CELO:42220:CELO_MAINNET_RPC" ) for entry in "${CHAINS[@]}"; do - IFS=: read -r name chain_id rpc_var <<< "$entry" + IFS=: read -r name chain_id rpc_vars <<< "$entry" if [[ ${#CHAIN_FILTER[@]} -gt 0 ]] && [[ ! " ${CHAIN_FILTER[*]} " =~ " $name " ]]; then continue; fi - rpc="${!rpc_var:-}" + rpc="" + IFS='|' read -r -a rpc_candidates <<< "$rpc_vars" + for rpc_var in "${rpc_candidates[@]}"; do + if [[ -n "${!rpc_var:-}" ]]; then + rpc="${!rpc_var}" + break + fi + done if [[ -z "$rpc" ]]; then - echo "Skip $name (chain $chain_id): $rpc_var not set" + echo "Skip $name (chain $chain_id): none of ${rpc_vars} set" continue fi @@ -85,12 +100,15 @@ for entry in "${CHAINS[@]}"; do echo "WARN $name: using global OFFICIAL_USDC_ADDRESS fallback; set ${usdc_var} or ${usdc_var_alt} for chain-specific safety" fi + forge_profile="$(forge_profile_for_chain "$chain_id")" echo "=== Deploying DODOPMMIntegration on $name (chain $chain_id) ===" + echo "Using Foundry profile: $forge_profile" DODO_VENDING_MACHINE_ADDRESS="$dvm" \ OFFICIAL_USDT_ADDRESS="$usdt" \ OFFICIAL_USDC_ADDRESS="$usdc" \ COMPLIANT_USDT_ADDRESS="$compliant_usdt" \ COMPLIANT_USDC_ADDRESS="$compliant_usdc" \ + FOUNDRY_PROFILE="$forge_profile" \ forge script script/dex/DeployDODOPMMIntegration.s.sol:DeployDODOPMMIntegration \ --rpc-url "$rpc" \ --chain-id "$chain_id" \ diff --git a/scripts/deployment/deploy-tokens-and-weth-all-chains-skip-canonical.sh b/scripts/deployment/deploy-tokens-and-weth-all-chains-skip-canonical.sh index dbf013d..75f9d97 100755 --- a/scripts/deployment/deploy-tokens-and-weth-all-chains-skip-canonical.sh +++ b/scripts/deployment/deploy-tokens-and-weth-all-chains-skip-canonical.sh @@ -259,7 +259,7 @@ run_deploy_cw() { fi local gas_opt="" [[ "$chain_id" == "42161" && -n "${ARBITRUM_GAS_PRICE:-}" ]] && gas_opt="--with-gas-price ${ARBITRUM_GAS_PRICE}" - run_cmd "${deploy_opts:+$deploy_opts }CW_BRIDGE_ADDRESS=$bridge forge script script/deploy/DeployCWTokens.s.sol:DeployCWTokens --rpc-url \"$rpc\" --chain-id \"$chain_id\" --broadcast --private-key \"\$PRIVATE_KEY\" --legacy ${gas_opt} -vvv" || true + run_cmd "${deploy_opts:+$deploy_opts }CW_BRIDGE_ADDRESS=$bridge CW_STRICT_MODE=${CW_STRICT_MODE:-1} CW_FREEZE_OPERATIONAL_ROLES=${CW_FREEZE_OPERATIONAL_ROLES:-1} forge script script/deploy/DeployCWTokens.s.sol:DeployCWTokens --rpc-url \"$rpc\" --chain-id \"$chain_id\" --broadcast --private-key \"\$PRIVATE_KEY\" --legacy ${gas_opt} -vvv" || true echo " β†’ Set CWUSDT_*, CWUSDC_*, CWEURC_*, CWEURT_*, CWGBPC_*, CWGBPT_*, CWAUDC_*, CWJPYC_*, CWCHFC_*, CWCADC_*, CWXAUC_*, CWXAUT_* in .env from script output." } diff --git a/scripts/deployment/fund-ccip-bridges-with-link.sh b/scripts/deployment/fund-ccip-bridges-with-link.sh index a6106ce..61e23ba 100755 --- a/scripts/deployment/fund-ccip-bridges-with-link.sh +++ b/scripts/deployment/fund-ccip-bridges-with-link.sh @@ -32,13 +32,42 @@ run_or_echo() { } ensure_rpc() { local rpc="$1"; type ensure_infura_rpc_url &>/dev/null && [[ -n "$rpc" ]] && rpc=$(ensure_infura_rpc_url "$rpc"); echo "$rpc"; } +get_token_balance() { + local token="$1" owner="$2" rpc="$3" + cast call "${token,,}" "balanceOf(address)(uint256)" "${owner,,}" --rpc-url "$rpc" 2>/dev/null || echo "0" +} +has_sufficient_link() { + local token="$1" owner="$2" rpc="$3" needed="$4" + local bal + bal=$(get_token_balance "$token" "$owner" "$rpc") + bal="${bal%% *}" + [[ "$bal" =~ ^[0-9]+$ ]] || bal="0" + python3 - <= needed else 1) +PY +} +print_skip() { + local label="$1" token="$2" owner="$3" rpc="$4" needed="$5" + local bal + bal=$(get_token_balance "$token" "$owner" "$rpc") + echo " Skipped (insufficient LINK balance: have ${bal%% *}, need $needed)" +} if [[ -z "$PRIVATE_KEY" ]]; then echo "ERROR: PRIVATE_KEY not set" >&2 exit 1 fi +DEPLOYER_ADDR=$(cast wallet address --private-key "$PRIVATE_KEY" 2>/dev/null || true) +if [[ -z "$DEPLOYER_ADDR" ]]; then + echo "ERROR: could not derive deployer address from PRIVATE_KEY" >&2 + exit 1 +fi + echo "Funding CCIP bridges with LINK (amount per bridge: $LINK_AMOUNT_WEI wei)" +echo "Deployer: $DEPLOYER_ADDR" echo "" # Chain 138 (Besu: gas estimation often fails; use explicit legacy gas like manual cast) @@ -48,8 +77,20 @@ if [[ -n "${RPC_URL_138:-}" && -n "${LINK_TOKEN_CHAIN138:-${LINK_TOKEN:-}}" ]]; rpc=$(ensure_rpc "$RPC_URL_138") echo "Chain 138 (RPC: ${rpc%%\?*}...)" _gas138="--legacy --gas-limit 250000 --gas-price 2000000000" - [[ -n "${CCIPWETH9_BRIDGE_CHAIN138:-}" ]] && run_or_echo "cast send $link \"transfer(address,uint256)\" ${CCIPWETH9_BRIDGE_CHAIN138,,} $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" $_gas138" - [[ -n "${CCIPWETH10_BRIDGE_CHAIN138:-}" ]] && run_or_echo "cast send $link \"transfer(address,uint256)\" ${CCIPWETH10_BRIDGE_CHAIN138,,} $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" $_gas138" + if [[ -n "${CCIPWETH9_BRIDGE_CHAIN138:-}" ]]; then + if has_sufficient_link "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI"; then + run_or_echo "cast send $link \"transfer(address,uint256)\" ${CCIPWETH9_BRIDGE_CHAIN138,,} $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" $_gas138" + else + print_skip "CHAIN138/WETH9" "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI" + fi + fi + if [[ -n "${CCIPWETH10_BRIDGE_CHAIN138:-}" ]]; then + if has_sufficient_link "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI"; then + run_or_echo "cast send $link \"transfer(address,uint256)\" ${CCIPWETH10_BRIDGE_CHAIN138,,} $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" $_gas138" + else + print_skip "CHAIN138/WETH10" "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI" + fi + fi echo "" fi @@ -58,8 +99,20 @@ if [[ -n "${ETHEREUM_MAINNET_RPC:-}" && -n "${MAINNET_LINK_TOKEN:-${CCIP_ETH_LIN link="${MAINNET_LINK_TOKEN:-$CCIP_ETH_LINK_TOKEN}" rpc=$(ensure_rpc "$ETHEREUM_MAINNET_RPC") echo "Ethereum Mainnet" - [[ -n "${MAINNET_CCIP_WETH9_BRIDGE:-}" ]] && run_or_echo "cast send $link \"transfer(address,uint256)\" $MAINNET_CCIP_WETH9_BRIDGE $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" --legacy" - [[ -n "${MAINNET_CCIP_WETH10_BRIDGE:-}" ]] && run_or_echo "cast send $link \"transfer(address,uint256)\" $MAINNET_CCIP_WETH10_BRIDGE $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" --legacy" + if [[ -n "${MAINNET_CCIP_WETH9_BRIDGE:-}" ]]; then + if has_sufficient_link "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI"; then + run_or_echo "cast send $link \"transfer(address,uint256)\" $MAINNET_CCIP_WETH9_BRIDGE $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" --legacy" + else + print_skip "ETH/WETH9" "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI" + fi + fi + if [[ -n "${MAINNET_CCIP_WETH10_BRIDGE:-}" ]]; then + if has_sufficient_link "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI"; then + run_or_echo "cast send $link \"transfer(address,uint256)\" $MAINNET_CCIP_WETH10_BRIDGE $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" --legacy" + else + print_skip "ETH/WETH10" "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI" + fi + fi echo "" fi @@ -89,8 +142,20 @@ for label in BSC POLYGON BASE OPTIMISM ARBITRUM AVALANCHE CRONOS GNOSIS CELO WEM [[ -z "$addr9" && -z "$addr10" ]] && continue echo "$label" - [[ -n "$addr9" ]] && run_or_echo "cast send ${link,,} \"transfer(address,uint256)\" ${addr9,,} $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" --legacy" - [[ -n "$addr10" ]] && run_or_echo "cast send ${link,,} \"transfer(address,uint256)\" ${addr10,,} $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" --legacy" + if [[ -n "$addr9" ]]; then + if has_sufficient_link "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI"; then + run_or_echo "cast send ${link,,} \"transfer(address,uint256)\" ${addr9,,} $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" --legacy" + else + print_skip "$label/WETH9" "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI" + fi + fi + if [[ -n "$addr10" ]]; then + if has_sufficient_link "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI"; then + run_or_echo "cast send ${link,,} \"transfer(address,uint256)\" ${addr10,,} $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" --legacy" + else + print_skip "$label/WETH10" "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI" + fi + fi echo "" done diff --git a/scripts/deployment/inventory-chain138-pmm-desired-state.sh b/scripts/deployment/inventory-chain138-pmm-desired-state.sh index f37deb1..a025a6b 100755 --- a/scripts/deployment/inventory-chain138-pmm-desired-state.sh +++ b/scripts/deployment/inventory-chain138-pmm-desired-state.sh @@ -125,6 +125,9 @@ fi while IFS=$'\t' read -r base_sym quote_sym; do add_pair "$base_sym" "$quote_sym" done < <(jq -r '.explicitPairs[]? | [.baseSymbol, .quoteSymbol] | @tsv' "$CONFIG_JSON") +while IFS=$'\t' read -r base_sym quote_sym; do + add_pair "$base_sym" "$quote_sym" +done < <(jq -r '.plannedPairs[]? | [.baseSymbol, .quoteSymbol] | @tsv' "$CONFIG_JSON") echo "=== Chain 138 PMM Desired-State Inventory ===" echo "Config: $CONFIG_JSON" diff --git a/scripts/deployment/sync-chain138-pmm-pools-from-json.sh b/scripts/deployment/sync-chain138-pmm-pools-from-json.sh index dc74ac2..97092ef 100755 --- a/scripts/deployment/sync-chain138-pmm-pools-from-json.sh +++ b/scripts/deployment/sync-chain138-pmm-pools-from-json.sh @@ -137,6 +137,9 @@ fi while IFS=$'\t' read -r base_sym quote_sym; do add_pair "$base_sym" "$quote_sym" done < <(jq -r '.explicitPairs[]? | [.baseSymbol, .quoteSymbol] | @tsv' "$CONFIG_JSON") +while IFS=$'\t' read -r base_sym quote_sym; do + add_pair "$base_sym" "$quote_sym" +done < <(jq -r '.plannedPairs[]? | [.baseSymbol, .quoteSymbol] | @tsv' "$CONFIG_JSON") zero_addr='0x0000000000000000000000000000000000000000' created=0 diff --git a/scripts/deployment/verify-cronos-contracts.sh b/scripts/deployment/verify-cronos-contracts.sh index d72dc0b..e36ca56 100755 --- a/scripts/deployment/verify-cronos-contracts.sh +++ b/scripts/deployment/verify-cronos-contracts.sh @@ -1,7 +1,18 @@ #!/usr/bin/env bash -# Verify all four Cronos contracts via Etherscan-style API. -# Uses explorer-api.cronos.org (module/action, solidity-standard-json-input). -# Run from smom-dbis-138/ +# Cronos (chain 25): confirm deployments and prepare manual verification. +# +# Cronos Explorer moved off legacy Etherscan-compatible POST endpoints on the +# public /mainnet/api route (404 on contract verification). Etherscan multichain +# v2 does not list Cronos chain id 25. Foundry forge verify-contract therefore +# cannot submit verification to Cronos reliably from CI. +# +# This script: (1) checks bytecode on RPC, (2) optionally exports Standard-JSON +# inputs for the web UI, (3) prints links to the manual runbook. +# +# Run from smom-dbis-138/: ./scripts/deployment/verify-cronos-contracts.sh +# +# Optional: CRONOS_EXPORT_SOURCES=1 β€” run export-cronos-verification-sources.sh +# Optional: CRONOS_REQUIRE_BYTECODE=1 β€” exit 1 if any listed address has no code (default: 1) set -euo pipefail @@ -25,66 +36,56 @@ elif [[ -n "${REPO_ROOT:-}" && -f "$REPO_ROOT/.env" ]]; then set +a fi +RPC="${CRONOS_RPC_URL:-https://evm.cronos.org}" +REQUIRE_CODE="${CRONOS_REQUIRE_BYTECODE:-1}" -if [ -z "${CRONOSCAN_API_KEY:-}" ]; then - echo "ERROR: CRONOSCAN_API_KEY not set. Set in .env (from explorer.cronos.org/register)." - exit 1 -fi - -export CRONOSCAN_API_KEY - -echo "Cronos verification (Etherscan-style API)" -echo " API: https://explorer-api.cronos.org/mainnet/api" -echo " Chain: cronos (from foundry.toml)" +echo "Cronos verification (Chain 25)" +echo " RPC: $RPC" +echo " Explorer: https://explorer.cronos.org" +echo " Manual UI: https://explorer.cronos.org/verifyContract" +echo "" +echo "Note: Automated forge/etherscan verification is not supported against the current" +echo " Cronos Explorer API (see docs/04-configuration/CRONOS_EXPLORER_OPERATIONS.md)." echo "" -verify() { - local addr="$1" - local contract="$2" - local extra_args="${3:-}" - echo "Verifying $contract at $addr..." - # shellcheck disable=SC2086 - if forge verify-contract \ - "$addr" "$contract" \ - --chain cronos \ - --etherscan-api-key "$CRONOSCAN_API_KEY" \ - --skip-is-verified-check \ - $extra_args \ - --watch 2>&1; then - echo " βœ“ $contract verified" - else - echo " βœ— $contract verification failed" - return 1 - fi -} +CONTRACTS=( + "0x99B3511A2d315A497C8112C1fdd8D508d4B1E506:WETH9" + "0x3304b747E565a97ec8AC220b0B6A1f6ffDB837e6:WETH10" + "0x3Cc23d086fCcbAe1e5f3FE2bA4A263E1D27d8Cab:CCIPWETH9Bridge" + "0x105F8A15b819948a89153505762444Ee9f324684:CCIPWETH10Bridge" +) FAIL=0 - -# WETH9 - no constructor args -verify "0x99B3511A2d315A497C8112C1fdd8D508d4B1E506" "contracts/tokens/WETH.sol:WETH" || FAIL=$((FAIL+1)) - -# WETH10 - no constructor args -verify "0x3304b747E565a97ec8AC220b0B6A1f6ffDB837e6" "contracts/tokens/WETH10.sol:WETH10" || FAIL=$((FAIL+1)) - -# CCIPWETH9Bridge - constructor(router, weth9, linkToken) -verify "0x3Cc23d086fCcbAe1e5f3FE2bA4A263E1D27d8Cab" "contracts/ccip/CCIPWETH9Bridge.sol:CCIPWETH9Bridge" \ - "--constructor-args $(cast abi-encode 'constructor(address,address,address)' 0xE26B0A098D861d5C7d9434aD471c0572Ca6EAa67 0x99B3511A2d315A497C8112C1fdd8D508d4B1E506 0x8c80A01F461f297Df7F9DA3A4f740D7297C8Ac85)" || FAIL=$((FAIL+1)) - -# CCIPWETH10Bridge - constructor(router, weth10, linkToken) -verify "0x105F8A15b819948a89153505762444Ee9f324684" "contracts/ccip/CCIPWETH10Bridge.sol:CCIPWETH10Bridge" \ - "--constructor-args $(cast abi-encode 'constructor(address,address,address)' 0xE26B0A098D861d5C7d9434aD471c0572Ca6EAa67 0x3304b747E565a97ec8AC220b0B6A1f6ffDB837e6 0x8c80A01F461f297Df7F9DA3A4f740D7297C8Ac85)" || FAIL=$((FAIL+1)) - +echo "On-chain bytecode:" +for entry in "${CONTRACTS[@]}"; do + addr="${entry%%:*}" + name="${entry##*:}" + code=$(cast code "$addr" --rpc-url "$RPC" 2>/dev/null || echo "0x") + if [[ "${#code}" -gt 10 ]]; then + echo " βœ“ $name $addr" + else + echo " βœ— $name $addr β€” no bytecode" + FAIL=$((FAIL + 1)) + fi +done echo "" -if [ "$FAIL" -eq 0 ]; then - echo "All four Cronos contracts verified." - exit 0 -else - echo "$FAIL contract(s) failed." - echo "" - echo "Manual verification (recommended):" - echo " 1. ./scripts/deployment/export-cronos-verification-sources.sh" - echo " 2. Open https://explorer.cronos.org/verifyContract" - echo " 3. Follow docs/deployment/CRONOS_VERIFICATION_RUNBOOK.md" + +if [[ "${CRONOS_EXPORT_SOURCES:-0}" == "1" ]] && [[ -x "$SCRIPT_DIR/export-cronos-verification-sources.sh" ]]; then + echo "Regenerating Standard-JSON inputs (.cronos-verify/)..." + bash "$SCRIPT_DIR/export-cronos-verification-sources.sh" || true echo "" +fi + +echo "Next steps (manual verification required):" +echo " 1. docs/deployment/CRONOS_VERIFICATION_RUNBOOK.md" +echo " 2. Or: ./scripts/deployment/export-cronos-verification-sources.sh" +echo " then upload each *_standard_input.json at https://explorer.cronos.org/verifyContract" +echo "" + +if [[ "$REQUIRE_CODE" == "1" ]] && [[ "$FAIL" -gt 0 ]]; then + echo "ERROR: $FAIL contract(s) missing bytecode on $RPC" >&2 exit 1 fi + +echo "verify-cronos-contracts.sh finished (deployment OK; verification is manual on Cronos)." +exit 0 diff --git a/scripts/forge/scope.sh b/scripts/forge/scope.sh index 5bf5126..89d377e 100755 --- a/scripts/forge/scope.sh +++ b/scripts/forge/scope.sh @@ -16,6 +16,8 @@ declare -A ROOT_SCRIPT_SCOPE_ALIASES=( ["DeployCCIPWETH9Bridge.s.sol"]="ccip" ["DeployCCIPWETH10Bridge.s.sol"]="ccip" ["DeployComplianceRegistry.s.sol"]="compliance" + ["DeployOMNLStack.s.sol"]="hybx-omnl" + ["DeployMirrorCoordinator.s.sol"]="hybx-omnl" ["DeployCompliantUSDC.s.sol"]="tokens" ["DeployCompliantUSDT.s.sol"]="tokens" ["DeployCWAssetReserveVerifier.s.sol"]="bridge/integration" @@ -55,6 +57,8 @@ Examples: FORGE_SCOPE=vault bash scripts/forge/scope.sh test --match-path 'test/vault/*.t.sol' Notes: + - Root `forge test` / `forge build` (no scope) honor `foundry.toml` `[profile.default] skip` for + legacy Uniswap V2 vendor trees (old solc); scoped builds unchanged. - Omit [scope] to use FORGE_SCOPE, otherwise default to 'full'. - 'full' preserves the historical repo-wide Forge behavior. - Any other scope is resolved relative to contracts/, for example: diff --git a/scripts/mint-for-liquidity.sh b/scripts/mint-for-liquidity.sh index 0d624cf..49191e2 100755 --- a/scripts/mint-for-liquidity.sh +++ b/scripts/mint-for-liquidity.sh @@ -95,11 +95,14 @@ if [[ "$RUN_ADD_LIQUIDITY" == true ]]; then export POOL_CUSDTCUSDC="${POOL_CUSDTCUSDC:-0x9e89bAe009adf128782E19e8341996c596ac40dC}" export POOL_CUSDTUSDT="${POOL_CUSDTUSDT:-0x866Cb44b59303d8dc5f4F9E3E7A8e8b0bf238d66}" export POOL_CUSDCUSDC="${POOL_CUSDCUSDC:-0xc39B7D0F40838cbFb54649d327f49a6DAC964062}" + export POOL_CUSDTWETH="${POOL_CUSDTWETH:-0xaE38a008Ba4Dbf8D9F141D03e9dC8f7Dbe0ce17c}" + export POOL_CUSDCWETH="${POOL_CUSDCWETH:-0xAAE68830a55767722618E869882c6Ed064Cc1eb2}" + export POOL_CEURTWETH="${POOL_CEURTWETH:-0x4a64c886cedF00db42ea5B946D6b304121ad9529}" if [[ -n "${DODO_PMM_INTEGRATION:-}" || -n "${DODO_PMM_INTEGRATION_ADDRESS:-}" ]]; then # Use pending nonce so broadcast does not get -32001 "Nonce too low" (mints just used N and N+1) NEXT_NONCE=$(cast nonce "$DEPLOYER" --rpc-url "$RPC" --block pending 2>/dev/null || true) [[ -n "$NEXT_NONCE" && "$NEXT_NONCE" =~ ^[0-9]+$ ]] && export NEXT_NONCE || unset -v NEXT_NONCE - echo "Running AddLiquidityPMMPoolsChain138 (cUSDT/cUSDC pool only if base/quote set)..." + echo "Running AddLiquidityPMMPoolsChain138..." forge script script/dex/AddLiquidityPMMPoolsChain138.s.sol:AddLiquidityPMMPoolsChain138 \ --rpc-url "$RPC" --broadcast --private-key "$PRIVATE_KEY" --with-gas-price 1000000000 --gas-estimate-multiplier 150 echo "Add-liquidity done." @@ -109,7 +112,8 @@ if [[ "$RUN_ADD_LIQUIDITY" == true ]]; then fi else echo "To add liquidity next: set ADD_LIQUIDITY_BASE_AMOUNT and ADD_LIQUIDITY_QUOTE_AMOUNT (base units, 6 decimals)," - echo "POOL_CUSDTCUSDC (and optional POOL_CUSDTUSDT, POOL_CUSDCUSDC), DODO_PMM_INTEGRATION in .env, then run:" + echo "POOL_CUSDTCUSDC (and optional POOL_CUSDTUSDT, POOL_CUSDCUSDC, POOL_CUSDTWETH, POOL_CUSDCWETH, POOL_CEURTWETH)," + echo "DODO_PMM_INTEGRATION in .env, then run:" echo " forge script script/dex/AddLiquidityPMMPoolsChain138.s.sol:AddLiquidityPMMPoolsChain138 --rpc-url \$RPC_URL_138 --broadcast --private-key \$PRIVATE_KEY" echo "Or run this script with --add-liquidity to mint and add in one go (uses half of minted for cUSDT/cUSDC pool)." fi diff --git a/services/relay/.env.mainnet-cw b/services/relay/.env.mainnet-cw index 8062d48..eeecb47 100644 --- a/services/relay/.env.mainnet-cw +++ b/services/relay/.env.mainnet-cw @@ -17,7 +17,9 @@ DEST_RELAY_BRIDGE_ALLOWLIST=0x2bF74583206A49Be07E0E8A94197C12987AbD7B5 RELAYER_PRIVATE_KEY=${PRIVATE_KEY} RELAYER_ADDRESS=0x4A666F96fC8764181194447A7dFdb7d471b301C8 -START_BLOCK=2706088 +# Historical cW backfill is complete; cold starts should resume near head rather +# than requeueing legacy unsupported canonical-token messages. +START_BLOCK=latest POLL_INTERVAL=5000 CONFIRMATION_BLOCKS=1 MAX_RETRIES=3 diff --git a/services/relay/src/RelayService.js b/services/relay/src/RelayService.js index 2be7eaa..2f5acdc 100644 --- a/services/relay/src/RelayService.js +++ b/services/relay/src/RelayService.js @@ -804,6 +804,33 @@ export class RelayService { return null; } + // Dedicated cW bridge workers can replay historical messages for canonical + // assets that are no longer configured on the destination multi-token bridge. + // When that mapping is absent, the destination bridge will always revert with + // "token not configured", so retire the message instead of retry-looping it. + if (targetBridgeContract.canonicalToMirrored && data && tokenAmounts.length === 0) { + try { + const decodedPayload = ethers.AbiCoder.defaultAbiCoder().decode( + ['address', 'address', 'uint256'], + data + ); + const canonicalToken = ethers.getAddress(decodedPayload[0]); + const mirroredToken = await targetBridgeContract.canonicalToMirrored(canonicalToken); + if (mirroredToken === ethers.ZeroAddress) { + this.logger.warn( + `Skipping queued message ${messageId}; canonical token ${canonicalToken} is not configured on destination bridge ${targetBridge}` + ); + await this.messageQueue.markProcessed(messageId); + return null; + } + } catch (mappingProbeErr) { + this.logger.debug( + `canonicalToMirrored probe skipped for ${messageId}; continuing with relay attempt`, + mappingProbeErr + ); + } + } + // Map token addresses from source chain to destination chain const mappedTokenAmounts = tokenAmounts.map(ta => { const sourceToken = ethers.getAddress(ta.token); diff --git a/services/relay/src/abis.js b/services/relay/src/abis.js index 5fe665f..20d4ae7 100644 --- a/services/relay/src/abis.js +++ b/services/relay/src/abis.js @@ -17,7 +17,8 @@ export const RelayBridgeABI = [ "function ccipReceive(tuple(bytes32 messageId, uint64 sourceChainSelector, bytes sender, bytes data, tuple(address token, uint256 amount, uint8 amountType)[] tokenAmounts) calldata message) external", "event CrossChainTransferCompleted(bytes32 indexed messageId, uint64 indexed sourceChainSelector, address indexed recipient, uint256 amount)", "function processed(bytes32) view returns (bool)", - "function processedTransfers(bytes32) view returns (bool)" + "function processedTransfers(bytes32) view returns (bool)", + "function canonicalToMirrored(address canonicalToken) view returns (address)" ]; export const ERC20ABI = [ diff --git a/services/token-aggregation/package.json b/services/token-aggregation/package.json index daff3f1..e44c1f5 100644 --- a/services/token-aggregation/package.json +++ b/services/token-aggregation/package.json @@ -10,10 +10,12 @@ "dev": "ts-node src/index.ts", "test": "jest", "test:ci": "jest --runInBand", + "test:omnl": "jest --runInBand --testPathPattern=omnl", "lint": "eslint src --ext .ts", "generate:route-matrix:v2": "ts-node scripts/generate-route-matrix-v2.ts", "migrate": "node -r dotenv/config dist/database/migrations.js", - "example:partner-payloads": "node scripts/resolve-partner-payloads-example.mjs" + "example:partner-payloads": "node scripts/resolve-partner-payloads-example.mjs", + "omnl:reconcile": "node scripts/omnl-reconcile-report.mjs" }, "dependencies": { "axios": "^1.13.5", diff --git a/services/token-aggregation/src/api/middleware/rate-limit.ts b/services/token-aggregation/src/api/middleware/rate-limit.ts index faaf16c..f32d929 100644 --- a/services/token-aggregation/src/api/middleware/rate-limit.ts +++ b/services/token-aggregation/src/api/middleware/rate-limit.ts @@ -18,3 +18,15 @@ export const strictRateLimiter = rateLimit({ standardHeaders: true, legacyHeaders: false, }); + +/** Stricter limit for RPC-heavy /api/v1/omnl/* (stacks with apiRateLimiter). */ +const omnlWindowMs = parseInt(process.env.OMNL_RATE_LIMIT_WINDOW_MS || '60000', 10); +const omnlMax = parseInt(process.env.OMNL_RATE_LIMIT_MAX || '30', 10); + +export const omnlRateLimiter = rateLimit({ + windowMs: omnlWindowMs, + max: omnlMax, + message: 'Too many OMNL API requests from this IP, please try again later.', + standardHeaders: true, + legacyHeaders: false, +}); diff --git a/services/token-aggregation/src/api/server.ts b/services/token-aggregation/src/api/server.ts index 5912cc6..b9f3aa2 100644 --- a/services/token-aggregation/src/api/server.ts +++ b/services/token-aggregation/src/api/server.ts @@ -1,4 +1,6 @@ import express, { Express, Request, Response, NextFunction } from 'express'; +import path from 'path'; +import { readFileSync, existsSync } from 'fs'; import cors from 'cors'; import compression from 'compression'; import { apiRateLimiter, strictRateLimiter } from './middleware/rate-limit'; @@ -15,7 +17,10 @@ import arbitrageRoutes from './routes/arbitrage'; import aggregatorRouteMatrixRoutes from './routes/aggregator-routes'; import partnerPayloadRoutes from './routes/partner-payloads'; import plannerV2Routes from './routes/planner-v2'; +import omnlRoutes from './routes/omnl'; +import omnlIpsasRoutes from './routes/omnl-ipsas'; import { MultiChainIndexer } from '../indexer/chain-indexer'; +import { OmnlEventPoller } from '../indexer/omnl-event-poller'; import { getDatabasePool } from '../database/client'; import winston from 'winston'; @@ -42,6 +47,7 @@ export class ApiServer { private port: number; private indexerEnabled: boolean; private indexer: MultiChainIndexer | null; + private omnlPoller: OmnlEventPoller | null; private resolveTrustProxySetting(): boolean | number | string { const raw = (process.env.EXPRESS_TRUST_PROXY ?? process.env.TRUST_PROXY ?? '1').trim(); @@ -58,6 +64,7 @@ export class ApiServer { this.port = parseInt(process.env.PORT || '3000', 10); this.indexerEnabled = this.resolveFeatureFlag('ENABLE_INDEXER', true); this.indexer = this.indexerEnabled ? new MultiChainIndexer() : null; + this.omnlPoller = this.resolveFeatureFlag('ENABLE_OMNL_EVENT_POLLER', false) ? new OmnlEventPoller() : null; this.setupMiddleware(); this.setupRoutes(); @@ -126,6 +133,24 @@ export class ApiServer { } }); + const dashboardPath = path.join(__dirname, '../../public/omnl-dashboard.html'); + this.app.get('/omnl/dashboard', (req: Request, res: Response) => { + const tok = process.env.OMNL_DASHBOARD_TOKEN?.trim(); + if (tok) { + const q = String(req.query.access_token ?? '').trim(); + const h = String(req.headers['x-omnl-dashboard-token'] ?? '').trim(); + if (q !== tok && h !== tok) { + res.status(401).type('text/plain').send('Unauthorized: set access_token query or X-OMNL-Dashboard-Token header'); + return; + } + } + if (!existsSync(dashboardPath)) { + res.status(404).type('text/plain').send('omnl-dashboard.html missing'); + return; + } + res.type('html').send(readFileSync(dashboardPath, 'utf8')); + }); + // API routes this.app.use('/api/v1', tokenRoutes); this.app.use('/api/v1', configRoutes); @@ -138,6 +163,8 @@ export class ApiServer { this.app.use('/api/v1', arbitrageRoutes); this.app.use('/api/v1', aggregatorRouteMatrixRoutes); this.app.use('/api/v1', partnerPayloadRoutes); + this.app.use('/api/v1', omnlRoutes); + this.app.use('/api/v1', omnlIpsasRoutes); this.app.use('/api/v2', plannerV2Routes); // Admin routes (stricter rate limit) @@ -152,6 +179,9 @@ export class ApiServer { health: '/health', api: '/api/v1', apiV2: '/api/v2', + omnlOpenApi: '/api/v1/omnl/openapi.json', + omnlCatalog: '/api/v1/omnl/catalog', + omnlDashboard: '/omnl/dashboard', }, }); }); @@ -183,6 +213,8 @@ export class ApiServer { logger.info('Token aggregation indexer disabled by ENABLE_INDEXER flag'); } + this.omnlPoller?.start(); + // Start server this.app.listen(this.port, () => { logger.info(`Token Aggregation Service listening on port ${this.port}`); @@ -196,6 +228,7 @@ export class ApiServer { } async stop(): Promise { + this.omnlPoller?.stop(); this.indexer?.stopAll(); logger.info('Server stopped'); } diff --git a/services/token-aggregation/src/config/canonical-tokens.ts b/services/token-aggregation/src/config/canonical-tokens.ts index 61796d1..09f755e 100644 --- a/services/token-aggregation/src/config/canonical-tokens.ts +++ b/services/token-aggregation/src/config/canonical-tokens.ts @@ -115,7 +115,7 @@ const FALLBACK_ADDRESSES: Record>> = { [42161]: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', // Arbitrum USDT [8453]: '0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2', // Base USDT [43114]: '0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7', // Avalanche USDT - [CHAIN_25]: '0x66e4286603D22FF153A6547700f37C7Eae42F8E2', // Cronos USDT + [CHAIN_25]: '0x66e428c3f67a68878562e79A0234c1F83c208770', // Cronos USDT [42220]: '0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e', // Celo USDT [1111]: '0xA649325Aa7C5093d12D6F98EB4378deAe68CE23F', // Wemix USDT }, diff --git a/terraform/phases/phase1/config/env.chain2138.example b/terraform/phases/phase1/config/env.chain2138.example index 1ae87ae..7cc6b6c 100644 --- a/terraform/phases/phase1/config/env.chain2138.example +++ b/terraform/phases/phase1/config/env.chain2138.example @@ -1,4 +1,4 @@ -# Chain ID 2138 β€” Defi Oracle Meta Testnet +# Chain ID 2138 β€” DeFi Oracle Meta Testnet # Copy to .env.chain2138 (or merge into smom-dbis-138/.env) and fill in values. # Runbook: proxmox parent repo docs/testnet/DEFI_ORACLE_META_TESTNET_2138_RUNBOOK.md # Chainlist metadata: pr-workspace/chains/_data/chains/eip155-2138.json diff --git a/test/config/Chain2138TestnetConfig.sol b/test/config/Chain2138TestnetConfig.sol index 8272e83..276c620 100644 --- a/test/config/Chain2138TestnetConfig.sol +++ b/test/config/Chain2138TestnetConfig.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.20; /** * @title Chain2138TestnetConfig - * @notice Configuration constants for Chain ID 2138 (Defi Oracle Meta Testnet) tests. + * @notice Configuration constants for Chain ID 2138 (DeFi Oracle Meta Testnet) tests. * @dev Mirror of Chain138Config pattern; override RPC via env in off-chain runners. */ library Chain2138TestnetConfig { uint256 public constant CHAIN_ID = 2138; - string public constant NETWORK_NAME = "Defi Oracle Meta Testnet"; + string public constant NETWORK_NAME = "DeFi Oracle Meta Testnet"; /// @dev Default public RPC from pr-workspace/chains/_data/chains/eip155-2138.json; use LAN/staging in CI as needed. string public constant RPC_URL = "https://rpc.public-2138.defi-oracle.io"; string public constant EXPLORER_URL = "https://public-2138.defi-oracle.io"; diff --git a/test/integration/UniversalBridge.t.sol b/test/integration/UniversalBridge.t.sol index 7c086f9..39e1129 100644 --- a/test/integration/UniversalBridge.t.sol +++ b/test/integration/UniversalBridge.t.sol @@ -6,8 +6,65 @@ import "../../contracts/registry/UniversalAssetRegistry.sol"; import "../../contracts/bridge/UniversalCCIPBridge.sol"; import "../../contracts/bridge/BridgeOrchestrator.sol"; import "../../contracts/governance/GovernanceController.sol"; +import "../../contracts/interfaces/IRegulatedAssetMetadata.sol"; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +/// @dev Minimal `IRegulatedAssetMetadata` so `_syncAssetMetadata` ABI-decodes cleanly +/// (Solidity try/catch does not catch return-data decode panics for empty/wrong-length returndata). +contract MockRegulatedAsset is IRegulatedAssetMetadata { + function assetId() external pure override returns (bytes32) { + return keccak256("MOCK_ASSET"); + } + + function assetVersionId() external pure override returns (bytes32) { + return keccak256("MOCK_VER"); + } + + function governanceProfileId() external pure override returns (bytes32) { + return bytes32(0); + } + + function supervisionProfileId() external pure override returns (bytes32) { + return bytes32(0); + } + + function storageNamespace() external pure override returns (bytes32) { + return bytes32(0); + } + + function primaryJurisdiction() external pure override returns (string memory) { + return ""; + } + + function regulatoryDisclosureURI() external pure override returns (string memory) { + return ""; + } + + function reportingURI() external pure override returns (string memory) { + return ""; + } + + function canonicalUnderlyingAsset() external pure override returns (address) { + return address(0); + } + + function supervisionRequired() external pure override returns (bool) { + return false; + } + + function governmentApprovalRequired() external pure override returns (bool) { + return false; + } + + function minimumUpgradeNoticePeriod() external pure override returns (uint256) { + return 0; + } + + function wrappedTransport() external pure override returns (bool) { + return false; + } +} + /** * @title UniversalBridge Integration Tests * @notice Comprehensive tests for all asset types through full bridge flow. @@ -86,10 +143,9 @@ contract UniversalBridgeTest is Test { // Add validator registry.addValidator(user1); - // Create proposal - address mockToken = makeAddr("mockToken2"); - vm.etch(mockToken, hex"00"); - + // Create proposal (real contract: metadata sync ABI-decodes return data; empty etch breaks execute) + address mockToken = address(new MockRegulatedAsset()); + bytes32 proposalId = registry.proposeAsset( mockToken, UniversalAssetRegistry.AssetType.Stablecoin,