From f04a7cb7c8e67dc0044be1b0ee93fc5a728a0afb Mon Sep 17 00:00:00 2001 From: defiQUG Date: Tue, 2 Jun 2026 06:09:44 -0700 Subject: [PATCH] WIP: Chain138 deployment scripts, flash receivers, HYBX OMNL recovery --- config/address-inventory.chain138.json | 65 ++++++- config/omnl-ipsas-gl-registry.json | 46 ++++- deployments/chain138/wormhole-pending.json | 32 ++++ forkproof/foundry.toml | 3 + ...SeedMainnetCwStablePoolPermanentFork.t.sol | 92 +++++++++ foundry.toml | 32 +++- script/DeployCWReserveSettlementStack.s.sol | 74 +++++++ ...figureEnhancedSwapRouterV2MultiVenue.s.sol | 129 +++++++++++++ ...eUniV2CwStableRebalanceFlashReceiver.s.sol | 34 ++++ .../deploy/bridge/DeployZedxionAdapter.s.sol | 27 +++ .../bridge/DeployZedxionCustomBridge.s.sol | 22 +++ .../deploy/rwa/GrantUarRegistrarRWA138.s.sol | 27 +++ .../rwa/RedeployLiIndexPublisher138.s.sol | 147 ++++++++++++++ .../rwa/RedeployLiXauPublisher138.s.sol | 71 +++++++ ...tUniV2CwusdcUsdcFlashRebalanceRemove.s.sol | 125 ++++++++++++ ...dMainnetAaveCwusdtUsdtQuotePushCycle.s.sol | 101 ++++++++++ .../SeedMainnetCwStablePoolPermanent.s.sol | 90 +++++++++ .../RecoverOMNLReserveVaultSafe.s.sol | 48 +++++ .../m00-diamond/DeployM00DiamondHub138.s.sol | 19 +- .../SeedM00LiIndexWeights138.s.sol | 32 ++++ .../UpgradeM00DiamondHubComplete138.s.sol | 133 +++++++++++++ .../reserve/WireChain138OraclePegs138.s.sol | 168 ++++++++++++++++ scripts/deploy-frontend.sh | 2 +- .../export-bsc-cw-verification-artifacts.sh | 180 ++++++++++++++++++ scripts/deployment/phase9-deploy-frontend.sh | 4 +- scripts/forge/scope.sh | 11 ++ scripts/reserve/deploy-all.sh | 117 ++++++++++-- test/bridge/CWMultiTokenBridge.t.sol | 45 ++--- test/bridge/CWMultiTokenBridgeBTC.t.sol | 20 +- .../CWReserveVerifierVaultV2Integration.t.sol | 20 +- test/compliance/MonetaryFormulas.t.sol | 28 +++ .../CWReserveSettlementStack.t.sol | 140 ++++++++++++++ ...bleRebalanceFlashReceiverMainnetFork.t.sol | 122 ++++++++++++ test/rwa/GruMonetaryPolicyGate.t.sol | 72 +++++++ test/rwa/LiIndexFlashVault.t.sol | 84 ++++++++ 35 files changed, 2279 insertions(+), 83 deletions(-) create mode 100644 deployments/chain138/wormhole-pending.json create mode 100644 forkproof/test/SeedMainnetCwStablePoolPermanentFork.t.sol create mode 100644 script/DeployCWReserveSettlementStack.s.sol create mode 100644 script/bridge/trustless/ConfigureEnhancedSwapRouterV2MultiVenue.s.sol create mode 100644 script/deploy/DeployAaveUniV2CwStableRebalanceFlashReceiver.s.sol create mode 100644 script/deploy/bridge/DeployZedxionAdapter.s.sol create mode 100644 script/deploy/bridge/DeployZedxionCustomBridge.s.sol create mode 100644 script/deploy/rwa/GrantUarRegistrarRWA138.s.sol create mode 100644 script/deploy/rwa/RedeployLiIndexPublisher138.s.sol create mode 100644 script/deploy/rwa/RedeployLiXauPublisher138.s.sol create mode 100644 script/flash/RunMainnetUniV2CwusdcUsdcFlashRebalanceRemove.s.sol create mode 100644 script/flash/RunManagedMainnetAaveCwusdtUsdtQuotePushCycle.s.sol create mode 100644 script/flash/SeedMainnetCwStablePoolPermanent.s.sol create mode 100644 script/hybx-omnl/RecoverOMNLReserveVaultSafe.s.sol create mode 100644 script/m00-diamond/SeedM00LiIndexWeights138.s.sol create mode 100644 script/m00-diamond/UpgradeM00DiamondHubComplete138.s.sol create mode 100644 script/reserve/WireChain138OraclePegs138.s.sol create mode 100755 scripts/deployment/export-bsc-cw-verification-artifacts.sh create mode 100644 test/compliance/MonetaryFormulas.t.sol create mode 100644 test/cw-settlement/CWReserveSettlementStack.t.sol create mode 100644 test/flash/AaveUniV2CwStableRebalanceFlashReceiverMainnetFork.t.sol create mode 100644 test/rwa/GruMonetaryPolicyGate.t.sol create mode 100644 test/rwa/LiIndexFlashVault.t.sol diff --git a/config/address-inventory.chain138.json b/config/address-inventory.chain138.json index b22a7e0..7a1da4b 100644 --- a/config/address-inventory.chain138.json +++ b/config/address-inventory.chain138.json @@ -117,9 +117,8 @@ "NONFUNGIBLE_POSITION_MANAGER_CHAIN138_DODO": "0x31b68BE5af4Df565Ce261dfe53D529005D947B48", "UNISWAP_V3_ROUTER_CHAIN138_DODO": "0xde9cD8ee2811E6E64a41D5F68Be315d33995975E", "DODO_TEAM_MULTISIG": "0x4A666F96fC8764181194447A7dFdb7d471b301C8", - "DODO_OPTIONAL_NOT_DEPLOYED": "GSPFactory,FeeRouteProxy1/2,LimitOrder,D3,DODOStarterProxy,DODONFTPoolProxy — see docs/04-configuration/dodo/DODO_CHAIN138_OPTIONAL_DEFERRED.md", "DODO_VENDING_MACHINE_ADDRESS": "0xB16c3D48A111714B1795E58341FeFDd643Ab01ab", - "DODO_VENDING_MACHINE_NOTE": "Legacy DBIS route-executor stub (~1kB), not DODOV2Proxy02 — run deploy-dodo-full-stack-chain138.sh for native proxy", + "DODO_VENDING_MACHINE_NOTE": "Legacy DBIS route-executor stub (~1kB), not DODOV2Proxy02 \u2014 run deploy-dodo-full-stack-chain138.sh for native proxy", "DODO_PMM_INTEGRATION": "0x86ADA6Ef91A3B450F89f2b751e93B1b7A3218895", "DODO_PMM_PROVIDER": "0x3f729632E9553EBacCdE2e9b4c8F2B285b014F2e", "CAUSDT_ADDRESS_138": "0x5fdDF65733e3d590463F68f93Cf16E8c04081271", @@ -139,14 +138,66 @@ "DODO_PMM_INTEGRATION_ADDRESS": "0x86ADA6Ef91A3B450F89f2b751e93B1b7A3218895", "DODOEX_ROUTER": "0x86ADA6Ef91A3B450F89f2b751e93B1b7A3218895", "DODO_PMM_PROVIDER_ADDRESS": "0x3f729632E9553EBacCdE2e9b4c8F2B285b014F2e", - "POOL_CUSDT_XAU_PUBLIC": "0x1AA55E2001E5651349aFf5a63FD7a7ae44f0f1b0", - "POOL_CUSDC_XAU_PUBLIC": "0xEa9AC6357CaCB42a83b9082B870610363b177CbA", - "POOL_CEURT_XAU_PUBLIC": "0xba99bc1eAac164569d5aca96c806934dDaf970CF", + "POOL_CUSDT_XAU_PUBLIC": "0x800baB6037390B44708EEbd408447686F5fEf904", + "POOL_CUSDC_XAU_PUBLIC": "0xDC4968F0B665ccDffBba6eB23902e95b5b3B097B", + "POOL_CEURT_XAU_PUBLIC": "0x495162bf25Ff85C5537cBf7950c7A79BA9f4e066", "CHAIN138_POOL_WETH_USDT": "0xe227f6c0520c0c6e8786fe56fa76c4914f861533", "CHAIN138_POOL_WETH_USDC": "0xb53a0508940b1ff90f1aad4f6cb50a7012fe5593", "POOL_WETH_CUSDC": "0xaae68830a55767722618e869882c6ed064cc1eb2", - "RWA_TOKEN_REGISTRY": "0xdc4Fff7c1C037242623663d2970DB7ECc80714Dd", - "RWA_TOKEN_FACTORY": "0xb2Da9c8f3F9f794bD243e30Aa9Df94a8414EC80B" + "RWA_TOKEN_REGISTRY": "0x9c83430A1b3A9ac8e807acCF6cCbC6CaBd0afFa7", + "RWA_TOKEN_FACTORY": "0xb2da9c8f3f9f794bd243e30aa9df94a8414ec80b", + "LIXAU_TOKEN_138": "0x1Ef5579Bed4a99301943aF5B9aC9a0C1b00ddB91", + "LIXAU_TOKEN_138_LEGACY": "0x2fab9847da83cb88018611d32271eb8e73d01ad2", + "M00_DIAMOND_HUB": "0x557efc7f5b93edc8A5A36Cf3E8363cB5bC6D7C43", + "M00_MAINNET_BRIDGE_FACET": "0x8d622510EDAFeA0196F7eC2B2f1A081E1C4FA5aA", + "M00_ACCESS_FACET": "0x1c69828B70E8291959e38D38cBFF50F1357576e4", + "M00_RWA_INSTRUMENT_FACET": "0xf2bf51091410Dc0010e4926dc92091D0B4FE8c5E", + "M00_RWA_DOCUMENT_FACET": "0xcDE36eC826e6d41754AAC578F197c9051EdfCa12", + "M00_RWA_STANDARDS_FACET": "0xbAb0C3F6f96c0B6D0dDfE5b392EC47cF1126DCCa", + "UNIVERSAL_ASSET_REGISTRY_IMPL_RWA": "0x93630589eec0FA7795DefB0a5DDc1C4eA1e5aedd", + "IndexFacet": "0xa975f5c394a30d8c3c63ef395ee8b6fa7b69bcc7", + "MonetaryFacet": "0xe95e4d51dfcca3fe72ee5a21e1f46e46e34d52bd", + "GovernanceFacet": "0x7fbd1c9fa949d6499c9d8861f7412681a9916e2c", + "LIPMG_TOKEN_138": "0xD920da2D8A9c1Cd31f4853969F1492C0B6527d9b", + "LIPMG_TOKEN_138_LEGACY": "0xf9e82712be806216a6eaa871e33942b39bed00c9", + "LIBMG1_TOKEN_138": "0x60e9001881fe5966567e842b91C6dDB63C12616D", + "LIBMG1_TOKEN_138_LEGACY": "0x25489b432cb53135baa08cc0d649def6748f7641", + "LIBMG2_TOKEN_138": "0xAF2c8050C93F6BD4c39Ac41013aD9EAe35683140", + "LIBMG2_TOKEN_138_LEGACY": "0x3933315b1dd095e761fcf76ecfd8fd9ba44648de", + "LIBMG3_TOKEN_138": "0x47d46acC0B849d9C0EFb9CFF96cfD84a905951b1", + "LIBMG3_TOKEN_138_LEGACY": "0x75d0e18fbb4d5c8fab19aa216595f4a6e085d493", + "LIXAU_ADDRESS_138": "0x1Ef5579Bed4a99301943aF5B9aC9a0C1b00ddB91", + "LIPMG_ADDRESS_138": "0xD920da2D8A9c1Cd31f4853969F1492C0B6527d9b", + "LIBMG1_ADDRESS_138": "0x60e9001881fe5966567e842b91C6dDB63C12616D", + "LIBMG2_ADDRESS_138": "0xAF2c8050C93F6BD4c39Ac41013aD9EAe35683140", + "LIBMG3_ADDRESS_138": "0x47d46acC0B849d9C0EFb9CFF96cfD84a905951b1", + "DODO_D3_ORACLE": "0x994A737B0D39D686e3d94A611455bB34724c6eab", + "DODO_D3_RATE_MANAGER": "0x01340C18fD67878c74286FFD54e288551201AA7f", + "DODO_D3_LIQUIDATION_ROUTER": "0xc0F6ccCEBA3dDF3c1c8dBC6AF5B9E17dFFDf99c3", + "DODO_D3_VAULT": "0x085F04C0A283Ef56010EF8E22a4D9510483834FC", + "DODO_D3_POOL_QUOTA": "0xa0FcE9f0582481B0f82614f05bd6a9868Ea7cB4E", + "DODO_D3_MOCK_ROUTER": "0x5B73492927387b82844E845a283bd774bb05b9eC", + "DODO_STARTER_FACTORY": "0xa114524eceCBd2d184B17A6b85b9127E91cde07C", + "DODO_STARTER_PROXY": "0x55D9678725BB11173789dcf78b8F7C792Dd37Ad5", + "DODO_NFT_POOL_PROXY": "0xbe884727cA88b324c0B0B3f40b39eD14a871FF0D", + "DODO_NFT_APPROVE": "0x7703C49073cE2dcd38A39F0cD17A9e1EE21CD89C", + "DODO_D3_MM": "0xdb68a9728bfbaf874c47077c849847fd7fcee258", + "DODO_D3_USER_QUOTA": "0x5aed9F96c728cfDF0762E7C6c42aA0879D113Fdf", + "DODO_D3_MM_FACTORY": "0xca01e43290D57Af7B371209f73D0c0c9456bA891", + "DODO_D3_PROXY": "0x20d030e6F0270859cbA04886333f6B83D9Ad6f1a", + "DODO_D3_FEE_RATE_MODEL": "0x8b1FeC1cf6f492E109d8a27Fd2A41a6F6C604cCa", + "DODO_OPTIONAL_NOT_DEFERRED": "none \u2014 see dodoDeferredChecklist upstream items", + "DODO_GSP_CUSDT_CUSDC": "0xc8a9b51983364d2753B09ad6eA07a8232f5d45c7", + "DODO_D3_ORACLE_FEEDS": { + "cUSDT": "0x9386FCF39962A3c6e2fF69e03b792b8cEb5Cae88", + "cUSDC": "0xaB36862e8d07Aa92844049bf6E64A311b7Cc2d07", + "WETH10": "0x8b622084d0109b3aEA130ec4440346Da0338645A", + "DODO": "0xFeb97b7Ea96C849bdF9729bB3D9b7D85c1f95e51" + }, + "DODO_FEE_ROUTE_PROXY1": "0xD6840208B7B3A1edc2C619d3db454c15CF10dB91", + "DODO_FEE_ROUTE_PROXY2": "0x1B138C77d92eC81fA8e7E60f4f67febeFD845E4B", + "DODO_LIMIT_ORDER": "0x7d0205F888170B1769e91b6A187c8F9a33b42cA5", + "DODO_LIMIT_ORDER_BOT": "0xf5babe17f7A7b2209f4816C517084fd54FC1f5b5" }, "mainnetAttestation": { "CHAIN138_MAINNET_CHECKPOINT_PROXY": "0xe2D6B908FE2535C39C79257FAAa2A52457673ba9", diff --git a/config/omnl-ipsas-gl-registry.json b/config/omnl-ipsas-gl-registry.json index 475bf9a..a0629a1 100644 --- a/config/omnl-ipsas-gl-registry.json +++ b/config/omnl-ipsas-gl-registry.json @@ -134,10 +134,30 @@ "glCode": "52100", "name": "Unrealized FX loss (P&L)", "fineractType": "EXPENSE", - "usage": "Unrealized FX loss / ECL expense bucket", + "usage": "Unrealized FX loss on revaluation", "ipsasStandards": ["IPSAS 9"], "ifrsRefs": ["IAS 21", "IFRS 9"], - "roles": ["fx_loss_unrealized", "ecl_expense"] + "roles": ["fx_loss_unrealized"] + }, + { + "glCode": "11040", + "name": "M00 Gold-Backed Asset Inventory (3FR M00)", + "fineractType": "ASSET", + "usage": "Face-value M00 from 3FR discounted exchange (T-3FR-001/002)", + "ipsasStandards": ["IPSAS 28", "IPSAS 29"], + "ifrsRefs": ["IFRS 9", "IFRS 13"], + "usGaapRefs": ["ASC 820", "ASC 860"], + "roles": ["m00_inventory", "gold_backed"] + }, + { + "glCode": "32200", + "name": "M00 Discount / FVR — 3FR Exchange", + "fineractType": "EQUITY", + "usage": "Discount, haircut, monetization reserve / fair value reconciliation", + "ipsasStandards": ["IPSAS 1", "IPSAS 29"], + "ifrsRefs": ["IFRS 13", "IAS 1"], + "usGaapRefs": ["ASC 820"], + "roles": ["fvr_adjustment", "revaluation_deficit"] } ], "allowedJournalPairs": [ @@ -150,6 +170,28 @@ { "debitGlCode": "52100", "creditGlCode": "23010", "ipsasRef": "IPSAS 19", "memo": "IAS37-PROVISION", "ifrsRef": "IAS 37" }, { "debitGlCode": "13010", "creditGlCode": "2000", "ipsasRef": "IPSAS 28", "memo": "SETTLE-NOSTRO", "ifrsRef": "IFRS 7 settlement" } ], + "compoundJournalEntries": [ + { + "memo": "T-3FR-001", + "debitGlCodes": ["11040", "32200"], + "creditGlCodes": ["2000"], + "totalAmountUsd": 900000000000, + "ipsasRef": "IPSAS 3, 28, 29", + "ifrsRef": "IFRS 9, IFRS 13, IAS 32", + "usGaapRef": "ASC 820, ASC 860, ASC 606 excluded", + "narrative": "3FR M00 substance-only compound — replaces T-001" + }, + { + "memo": "T-3FR-002", + "debitGlCodes": ["11040", "32200"], + "creditGlCodes": ["1000"], + "totalAmountUsd": 900000000000, + "ipsasRef": "IPSAS 28, 29", + "ifrsRef": "IFRS 9, IFRS 13", + "usGaapRef": "ASC 820, ASC 860", + "narrative": "Substance reclass after T-001 settlement receipt — full compliant path" + } + ], "monetaryLayerHints": { "m0_reserve": { "primaryGlCodes": ["1050"], "ipsasNarrative": "Treasury / M0 reserve assets (IPSAS 28, 29)" }, "m1_liability": { "primaryGlCodes": ["2000", "2100"], "ipsasNarrative": "Financial liabilities — deposits (IPSAS 28, 29)" }, diff --git a/deployments/chain138/wormhole-pending.json b/deployments/chain138/wormhole-pending.json new file mode 100644 index 0000000..5cd19a1 --- /dev/null +++ b/deployments/chain138/wormhole-pending.json @@ -0,0 +1,32 @@ +{ + "schemaVersion": "1.0.0", + "status": "pending_wormhole_foundation_ack", + "evmChainId": 138, + "wormholeChainId": null, + "updated": "2026-05-27", + "note": "Populate after Guardian ACK + deploy. Do not treat as canonical until verified on explorer.", + "contracts": { + "WormholeCore": { + "address": null, + "deployTx": null + }, + "WormholeSetup": { + "address": null + }, + "WormholeImplementation": { + "address": null + }, + "TokenBridge": { + "address": null, + "deployTx": null + } + }, + "upstream": { + "repo": "https://github.com/wormhole-foundation/wormhole", + "ref": "v2.56.0", + "deployScripts": [ + "ethereum/sh/deployCoreBridge.sh", + "ethereum/sh/deployTokenBridge.sh" + ] + } +} diff --git a/forkproof/foundry.toml b/forkproof/foundry.toml index fa31bae..bf05352 100644 --- a/forkproof/foundry.toml +++ b/forkproof/foundry.toml @@ -4,6 +4,9 @@ test = "test" out = "out" libs = ["../lib"] solc = "0.8.20" +skip = [ + "test/AaveQuotePushFlashReceiverMainnetFork.t.sol", +] optimizer = true optimizer_runs = 1 via_ir = true diff --git a/forkproof/test/SeedMainnetCwStablePoolPermanentFork.t.sol b/forkproof/test/SeedMainnetCwStablePoolPermanentFork.t.sol new file mode 100644 index 0000000..e37c431 --- /dev/null +++ b/forkproof/test/SeedMainnetCwStablePoolPermanentFork.t.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test, console} from "forge-std/Test.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +interface IDODOPMMIntegrationSeedFork { + function isRegisteredPool(address pool) external view returns (bool); + function addLiquidity(address pool, uint256 baseAmount, uint256 quoteAmount) + external + returns (uint256 baseShare, uint256 quoteShare, uint256 lpShare); +} + +interface IDODOPMMPoolSeedFork { + function _BASE_TOKEN_() external view returns (address); + function _QUOTE_TOKEN_() external view returns (address); + function getVaultReserve() external view returns (uint256 baseReserve, uint256 quoteReserve); + function getMidPrice() external view returns (uint256); +} + +/// @notice Mainnet fork validation for permanent cWUSDC/USDC and cWUSDT/USDT DODO seed paths. +contract SeedMainnetCwStablePoolPermanentForkTest is Test { + using SafeERC20 for IERC20; + + address internal constant INTEGRATION = 0xa9F284eD010f4F7d7F8F201742b49b9f58e29b84; + address internal constant POOL_USDC = 0x69776fc607e9edA8042e320e7e43f54d06c68f0E; + address internal constant POOL_USDT = 0x79156F6B7bf71a1B72D78189B540A89A6C13F6FC; + address internal constant CWUSDC = 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a; + address internal constant CWUSDT = 0xaF5017d0163ecb99D9B5D94e3b4D7b09Af44D8AE; + address internal constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address internal constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; + + uint256 internal constant SEED_RAW = 50_000_000_000; + + function setUp() public { + string memory rpc = vm.envOr("ETHEREUM_MAINNET_RPC", string("https://eth.llamarpc.com")); + vm.createSelectFork(rpc); + } + + function test_fork_seed_cWUSDC_USDC_addLiquidity_50k() public { + _seedRail(POOL_USDC, CWUSDC, USDC, "USDC"); + } + + function test_fork_seed_cWUSDT_USDT_addLiquidity_50k() public { + _seedRail(POOL_USDT, CWUSDT, USDT, "USDT"); + } + + function _ensurePoolRegistered(address pool) internal view { + assertTrue(IDODOPMMIntegrationSeedFork(INTEGRATION).isRegisteredPool(pool), "pool not registered on integration"); + } + + function _seedRail(address pool, address baseToken, address quoteToken, string memory label) internal { + address user = makeAddr("seedUser"); + deal(baseToken, user, SEED_RAW); + deal(quoteToken, user, SEED_RAW); + + IDODOPMMPoolSeedFork poolView = IDODOPMMPoolSeedFork(pool); + assertEq(poolView._BASE_TOKEN_(), baseToken, "base token"); + assertEq(poolView._QUOTE_TOKEN_(), quoteToken, "quote token"); + + _ensurePoolRegistered(pool); + + (uint256 baseBefore, uint256 quoteBefore) = poolView.getVaultReserve(); + uint256 midBefore = poolView.getMidPrice(); + console.log(string.concat(label, " baseReserveBefore"), baseBefore); + console.log(string.concat(label, " quoteReserveBefore"), quoteBefore); + console.log(string.concat(label, " midBefore"), midBefore); + + vm.startPrank(user); + IERC20(baseToken).forceApprove(INTEGRATION, SEED_RAW); + IERC20(quoteToken).forceApprove(INTEGRATION, SEED_RAW); + (uint256 baseShare, uint256 quoteShare, uint256 lpShare) = + IDODOPMMIntegrationSeedFork(INTEGRATION).addLiquidity(pool, SEED_RAW, SEED_RAW); + vm.stopPrank(); + + (uint256 baseAfter, uint256 quoteAfter) = poolView.getVaultReserve(); + uint256 midAfter = poolView.getMidPrice(); + console.log(string.concat(label, " baseShare"), baseShare); + console.log(string.concat(label, " quoteShare"), quoteShare); + console.log(string.concat(label, " lpShare"), lpShare); + console.log(string.concat(label, " baseReserveAfter"), baseAfter); + console.log(string.concat(label, " quoteReserveAfter"), quoteAfter); + console.log(string.concat(label, " midAfter"), midAfter); + + assertGt(baseShare, 0, "base share"); + assertGt(quoteShare, 0, "quote share"); + assertGt(lpShare, 0, "lp share"); + assertGt(baseAfter, baseBefore, "base reserve increased"); + assertGt(quoteAfter, quoteBefore, "quote reserve increased"); + } +} diff --git a/foundry.toml b/foundry.toml index 7026486..e434a5d 100644 --- a/foundry.toml +++ b/foundry.toml @@ -23,9 +23,11 @@ auto_detect_remappings = false # Fork tests execute live mainnet bytecode; Cancun matches post-Dencun execution (MCOPY, etc.). evm_version = "cancun" fs_permissions = [ - { access = "read", path = "./config" } + { access = "read", path = "./config" }, + { access = "read", path = "../reports" } ] remappings = [ + "@chainlink/contracts-ccip/=node_modules/@chainlink/contracts-ccip/", "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/", "forge-std/=lib/forge-std/src/", @@ -83,6 +85,7 @@ fs_permissions = [ { access = "read", path = "../config" } ] remappings = [ + "@chainlink/contracts-ccip/=node_modules/@chainlink/contracts-ccip/", "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/", "forge-std/=lib/forge-std/src/", @@ -106,6 +109,33 @@ via_ir = true # Backwards-compatible alias for older scripts; prefer profile.chain138. evm_version = "paris" +# BSC CompliantWrappedToken — matches verified cWUSDT on BscScan (chain 56). +# Use with scoped build: FOUNDRY_SRC=contracts/tokens FOUNDRY_OUT=out/scopes/tokens +# bash scripts/forge/scope.sh build tokens +# EVM london (not cancun); solc 0.8.20; optimizer 200; via_ir required (stack depth). +[profile.bsc_tokens_verify] +src = "contracts/tokens,contracts/interfaces" +out = "out/scopes/tokens" +cache_path = "cache/scopes/tokens" +solc = "0.8.20" +optimizer = true +optimizer_runs = 200 +via_ir = true +evm_version = "london" +auto_detect_remappings = false +remappings = [ + "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", + "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/", + "forge-std/=lib/forge-std/src/", + "ds-test/=lib/forge-std/lib/ds-test/src/", + "@emoney/=contracts/emoney/", + "@emoney-scripts/=script/emoney/", + "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/", + "openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/", + "openzeppelin-contracts/=lib/openzeppelin-contracts/", + "@gru/=lib/gru-contracts/" +] + # Mainnet checkpoint hub — minimize runtime bytecode (EIP-170 24 KiB). [profile.mainnet-checkpoint] optimizer = true diff --git a/script/DeployCWReserveSettlementStack.s.sol b/script/DeployCWReserveSettlementStack.s.sol new file mode 100644 index 0000000..36b8a68 --- /dev/null +++ b/script/DeployCWReserveSettlementStack.s.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Script, console} from "forge-std/Script.sol"; +import {CWNavOracle} from "../contracts/cw-settlement/CWNavOracle.sol"; +import {CWRedemptionQueue} from "../contracts/cw-settlement/CWRedemptionQueue.sol"; +import {CWStabilityFund} from "../contracts/cw-settlement/CWStabilityFund.sol"; +import {CWBuybackExecutor} from "../contracts/cw-settlement/CWBuybackExecutor.sol"; +import {CWProtocolTreasury} from "../contracts/cw-settlement/CWProtocolTreasury.sol"; +import {CWReserveVerifier} from "../contracts/bridge/integration/CWReserveVerifier.sol"; + +interface ICWMultiTokenBridgeL1Admin { + function setReserveVerifier(address newVerifier) external; +} + +/** + * @title DeployCWReserveSettlementStack + * @notice Deploy NAV oracle, redemption queue, stability fund, buyback, treasury, and reserve verifier on Chain 138. + * + * Env: + * PRIVATE_KEY, RPC_URL_138 + * CW_L1_BRIDGE (default 0x152ed3e9912161b76bdfd368d0c84b7c31c10de7) + * CW_RESERVE_SYSTEM (default 0x607e97cD626f209facfE48c1464815DDE15B5093) + * CW_CANONICAL_USDT / CW_CANONICAL_USDC + * CW_ATTACH_VERIFIER_TO_L1=1 + */ +contract DeployCWReserveSettlementStack is Script { + function run() external { + uint256 privateKey = vm.envUint("PRIVATE_KEY"); + address admin = vm.addr(privateKey); + + address l1Bridge = vm.envOr("CW_L1_BRIDGE", address(0x152eD3e9912161b76BDFd368D0C84B7C31C10dE7)); + address reserveSystem = vm.envOr("CW_RESERVE_SYSTEM", address(0x607e97cD626f209facfE48c1464815DDE15B5093)); + address canonicalUSDT = vm.envOr("CW_CANONICAL_USDT", address(0x93E66202A11B1772E55407B32B44e5Cd8eda7f22)); + address canonicalUSDC = vm.envOr("CW_CANONICAL_USDC", address(0xf22258f57794CC8E06237084b353Ab30fFfa640b)); + bool attachVerifier = vm.envOr("CW_ATTACH_VERIFIER_TO_L1", uint256(0)) == 1; + bool skipVerifierDeploy = vm.envOr("CW_SKIP_RESERVE_VERIFIER", uint256(1)) == 1; + + vm.startBroadcast(privateKey); + + address verifierAddr; + if (skipVerifierDeploy) { + verifierAddr = address(0); + } else { + CWReserveVerifier verifier = new CWReserveVerifier(admin, l1Bridge, address(0), reserveSystem); + verifierAddr = address(verifier); + if (attachVerifier) { + ICWMultiTokenBridgeL1Admin(l1Bridge).setReserveVerifier(verifierAddr); + } + verifier.configureToken(canonicalUSDT, address(0), false, false, false); + verifier.configureToken(canonicalUSDC, address(0), false, false, false); + } + + CWNavOracle navOracle = new CWNavOracle(admin, l1Bridge, reserveSystem); + CWRedemptionQueue redemptionQueue = new CWRedemptionQueue(admin); + CWStabilityFund stabilityFund = new CWStabilityFund(admin); + CWProtocolTreasury treasury = new CWProtocolTreasury(admin, address(0)); + CWBuybackExecutor buyback = new CWBuybackExecutor(admin, verifierAddr, address(treasury)); + + treasury.setBuybackExecutor(address(buyback)); + + navOracle.configureToken(canonicalUSDT, address(0)); + navOracle.configureToken(canonicalUSDC, address(0)); + + vm.stopBroadcast(); + + console.log("CWReserveVerifier:", verifierAddr); + console.log("CWNavOracle:", address(navOracle)); + console.log("CWRedemptionQueue:", address(redemptionQueue)); + console.log("CWStabilityFund:", address(stabilityFund)); + console.log("CWProtocolTreasury:", address(treasury)); + console.log("CWBuybackExecutor:", address(buyback)); + } +} diff --git a/script/bridge/trustless/ConfigureEnhancedSwapRouterV2MultiVenue.s.sol b/script/bridge/trustless/ConfigureEnhancedSwapRouterV2MultiVenue.s.sol new file mode 100644 index 0000000..f26ec27 --- /dev/null +++ b/script/bridge/trustless/ConfigureEnhancedSwapRouterV2MultiVenue.s.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {Script, console} from "forge-std/Script.sol"; +import "../../../contracts/bridge/trustless/EnhancedSwapRouterV2.sol"; +import "../../../contracts/bridge/trustless/RouteTypesV2.sol"; + +/// @notice Enable UniV3, Balancer, and Curve routes on live EnhancedSwapRouterV2 (Chain 138). +/// Adapters must already be set via initial deploy; this script only enables providers + routes. +contract ConfigureEnhancedSwapRouterV2MultiVenue is Script { + address constant DEFAULT_ROUTER_V2 = 0xa421706768aEB7fafA2D912C5E10824eF3437ad4; + + address constant CHAIN138_WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address constant CHAIN138_USDT = 0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1; + address constant CHAIN138_USDC = 0x71D6687F38b93CCad569Fa6352c876eea967201b; + address constant CHAIN138_cUSDT = 0x93E66202A11B1772E55407B32B44e5Cd8eda7f22; + address constant CHAIN138_cUSDC = 0xf22258f57794CC8E06237084b353Ab30fFfa640b; + + address constant CHAIN138_DODO_PROVIDER = 0x3f729632E9553EBacCdE2e9b4c8F2B285b014F2e; + address constant CHAIN138_POOL_CUSDTCUSDC = 0x9e89bAe009adf128782E19e8341996c596ac40dC; + address constant CHAIN138_POOL_CUSDTUSDT = 0x866Cb44b59303d8dc5f4F9E3E7A8e8b0bf238d66; + address constant CHAIN138_POOL_CUSDCUSDC = 0xc39B7D0F40838cbFb54649d327f49a6DAC964062; + address constant CHAIN138_POOL_WETH_USDT = 0xe227F6C0520c0c6E8786fE56Fa76c4914F861533; + address constant CHAIN138_POOL_WETH_USDC = 0xb53A0508940b1Ff90F1AAD4f6cb50a7012Fe5593; + + address constant UNISWAP_V3_ROUTER = 0xde9cD8ee2811E6E64a41D5F68Be315d33995975E; + address constant UNISWAP_QUOTER = 0x6abbB1CEb2468e748a03A00CD6aA9BFE893AFa1f; + address constant BALANCER_VAULT = 0x96423d7C1727698D8a25EbFB88131e9422d1a3C3; + bytes32 constant BALANCER_WETH_USDT_POOL_ID = + 0x877cd220759e8c94b82f55450c85d382ae06856c426b56d93092a420facbc324; + bytes32 constant BALANCER_WETH_USDC_POOL_ID = + 0xd8dfb18a6baf9b29d8c2dbd74639db87ac558af120df5261dab8e2a5de69013b; + address constant CURVE_3POOL = 0xE440Ec15805BE4C7BabCD17A63B8C8A08a492e0f; + + function run() external { + require(block.chainid == 138, "ConfigureEnhancedSwapRouterV2MultiVenue: Chain 138 only"); + + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + address routerAddress = vm.envOr("ENHANCED_SWAP_ROUTER_V2_ADDRESS", DEFAULT_ROUTER_V2); + EnhancedSwapRouterV2 router = EnhancedSwapRouterV2(payable(routerAddress)); + + uint24 wethUsdtFee = uint24(vm.envOr("UNISWAP_V3_WETH_USDT_FEE", uint256(3000))); + uint24 wethUsdcFee = uint24(vm.envOr("UNISWAP_V3_WETH_USDC_FEE", uint256(500))); + + vm.startBroadcast(deployerPrivateKey); + + _setDodoPair(router, CHAIN138_cUSDT, CHAIN138_cUSDC, CHAIN138_DODO_PROVIDER, CHAIN138_POOL_CUSDTCUSDC); + _setDodoPair(router, CHAIN138_cUSDT, CHAIN138_USDT, CHAIN138_DODO_PROVIDER, CHAIN138_POOL_CUSDTUSDT); + _setDodoPair(router, CHAIN138_cUSDC, CHAIN138_USDC, CHAIN138_DODO_PROVIDER, CHAIN138_POOL_CUSDCUSDC); + _setDodoPair(router, CHAIN138_WETH, CHAIN138_USDT, CHAIN138_DODO_PROVIDER, CHAIN138_POOL_WETH_USDT); + _setDodoPair(router, CHAIN138_WETH, CHAIN138_USDC, CHAIN138_DODO_PROVIDER, CHAIN138_POOL_WETH_USDC); + + _setUniswapPair(router, CHAIN138_WETH, CHAIN138_USDT, UNISWAP_V3_ROUTER, UNISWAP_QUOTER, wethUsdtFee); + _setUniswapPair(router, CHAIN138_WETH, CHAIN138_USDC, UNISWAP_V3_ROUTER, UNISWAP_QUOTER, wethUsdcFee); + + _setBalancerPair(router, CHAIN138_WETH, CHAIN138_USDT, BALANCER_VAULT, BALANCER_WETH_USDT_POOL_ID); + _setBalancerPair(router, CHAIN138_WETH, CHAIN138_USDC, BALANCER_VAULT, BALANCER_WETH_USDC_POOL_ID); + + _setCurvePair(router, CHAIN138_USDT, CHAIN138_USDC, CURVE_3POOL, 0, 1, false); + + router.setProviderEnabled(RouteTypesV2.Provider.Dodo, true); + router.setProviderEnabled(RouteTypesV2.Provider.UniswapV3, true); + router.setProviderEnabled(RouteTypesV2.Provider.Balancer, true); + router.setProviderEnabled(RouteTypesV2.Provider.Curve, true); + router.setProviderEnabled(RouteTypesV2.Provider.OneInch, false); + router.setProviderEnabled(RouteTypesV2.Provider.Partner, false); + router.setProviderEnabled(RouteTypesV2.Provider.DodoV3, false); + + RouteTypesV2.Provider[] memory providers = new RouteTypesV2.Provider[](4); + providers[0] = RouteTypesV2.Provider.Dodo; + providers[1] = RouteTypesV2.Provider.UniswapV3; + providers[2] = RouteTypesV2.Provider.Balancer; + providers[3] = RouteTypesV2.Provider.Curve; + router.setRoutingConfig(0, providers); + router.setRoutingConfig(1, providers); + router.setRoutingConfig(2, providers); + + vm.stopBroadcast(); + + console.log("EnhancedSwapRouterV2 multi-venue configured:", routerAddress); + } + + function _setDodoPair(EnhancedSwapRouterV2 router, address tokenA, address tokenB, address target, address pool) + internal + { + bytes memory providerData = abi.encode(pool); + router.setProviderRoute(tokenA, tokenB, RouteTypesV2.Provider.Dodo, target, providerData, true); + router.setProviderRoute(tokenB, tokenA, RouteTypesV2.Provider.Dodo, target, providerData, true); + } + + function _setUniswapPair( + EnhancedSwapRouterV2 router, + address tokenA, + address tokenB, + address target, + address quoter, + uint24 fee + ) internal { + bytes memory providerData = abi.encode(bytes(""), fee, quoter, false); + router.setProviderRoute(tokenA, tokenB, RouteTypesV2.Provider.UniswapV3, target, providerData, true); + router.setProviderRoute(tokenB, tokenA, RouteTypesV2.Provider.UniswapV3, target, providerData, true); + } + + function _setBalancerPair( + EnhancedSwapRouterV2 router, + address tokenA, + address tokenB, + address target, + bytes32 poolId + ) internal { + bytes memory providerData = abi.encode(poolId); + router.setProviderRoute(tokenA, tokenB, RouteTypesV2.Provider.Balancer, target, providerData, true); + router.setProviderRoute(tokenB, tokenA, RouteTypesV2.Provider.Balancer, target, providerData, true); + } + + function _setCurvePair( + EnhancedSwapRouterV2 router, + address tokenA, + address tokenB, + address target, + int128 i, + int128 j, + bool useUnderlying + ) internal { + bytes memory providerData = abi.encode(i, j, useUnderlying); + router.setProviderRoute(tokenA, tokenB, RouteTypesV2.Provider.Curve, target, providerData, true); + router.setProviderRoute(tokenB, tokenA, RouteTypesV2.Provider.Curve, target, providerData, true); + } +} diff --git a/script/deploy/DeployAaveUniV2CwStableRebalanceFlashReceiver.s.sol b/script/deploy/DeployAaveUniV2CwStableRebalanceFlashReceiver.s.sol new file mode 100644 index 0000000..6a42c32 --- /dev/null +++ b/script/deploy/DeployAaveUniV2CwStableRebalanceFlashReceiver.s.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Script, console} from "forge-std/Script.sol"; +import {AaveUniV2CwStableRebalanceFlashReceiver} from "../../contracts/flash/AaveUniV2CwStableRebalanceFlashReceiver.sol"; + +/** + * @title DeployAaveUniV2CwStableRebalanceFlashReceiver + * @notice Deploy the UniV2 rebalance + remove Aave flash receiver. + * + * Env: + * PRIVATE_KEY + * AAVE_POOL_ADDRESS optional; default Aave V3 mainnet Pool + * UNIV2_FLASH_REBALANCE_OWNER optional; default deployer + */ +contract DeployAaveUniV2CwStableRebalanceFlashReceiver is Script { + address internal constant DEFAULT_AAVE_POOL_MAINNET = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; + + function run() external { + uint256 pk = vm.envUint("PRIVATE_KEY"); + address pool = vm.envOr("AAVE_POOL_ADDRESS", DEFAULT_AAVE_POOL_MAINNET); + address deployer = vm.addr(pk); + address owner = vm.envOr("UNIV2_FLASH_REBALANCE_OWNER", deployer); + + vm.startBroadcast(pk); + AaveUniV2CwStableRebalanceFlashReceiver receiver = + new AaveUniV2CwStableRebalanceFlashReceiver(pool, owner); + vm.stopBroadcast(); + + console.log("AaveUniV2CwStableRebalanceFlashReceiver", address(receiver)); + console.log("owner", owner); + console.log("aavePool", pool); + } +} diff --git a/script/deploy/bridge/DeployZedxionAdapter.s.sol b/script/deploy/bridge/DeployZedxionAdapter.s.sol new file mode 100644 index 0000000..5ab3afe --- /dev/null +++ b/script/deploy/bridge/DeployZedxionAdapter.s.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Script.sol"; +import "../../../contracts/bridge/adapters/evm/ZedxionAdapter.sol"; + +/** + * @notice Deploy ZedxionAdapter on Chain 138. + * Env: PRIVATE_KEY, ADMIN (optional), ZEDXION_TRANSPORT (optional — call setZedxionTransport after) + */ +contract DeployZedxionAdapter is Script { + function run() external { + uint256 pk = vm.envUint("PRIVATE_KEY"); + address admin = vm.envOr("ADMIN", vm.addr(pk)); + vm.startBroadcast(pk); + ZedxionAdapter adapter = new ZedxionAdapter(admin); + vm.stopBroadcast(); + console2.log("ZedxionAdapter", address(adapter)); + address transport = vm.envOr("ZEDXION_TRANSPORT", address(0)); + if (transport != address(0)) { + vm.startBroadcast(pk); + adapter.setZedxionTransport(transport); + vm.stopBroadcast(); + console2.log("ZedxionTransport wired", transport); + } + } +} diff --git a/script/deploy/bridge/DeployZedxionCustomBridge.s.sol b/script/deploy/bridge/DeployZedxionCustomBridge.s.sol new file mode 100644 index 0000000..c863d45 --- /dev/null +++ b/script/deploy/bridge/DeployZedxionCustomBridge.s.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Script.sol"; +import "../../../contracts/bridge/ZedxionCustomBridge.sol"; + +/** + * @notice Deploy ZedxionCustomBridge on Chain 138 and/or ZEDXION (83872). + * @dev For CREATE2 same-address deploy, use DeployDeterministicCore pattern with salt keccak256("ZedxionCustomBridge"). + * Env: PRIVATE_KEY, ADMIN (defaults msg.sender) + */ +contract DeployZedxionCustomBridge is Script { + function run() external { + uint256 pk = vm.envUint("PRIVATE_KEY"); + address admin = vm.envOr("ADMIN", vm.addr(pk)); + vm.startBroadcast(pk); + ZedxionCustomBridge bridge = new ZedxionCustomBridge(admin); + vm.stopBroadcast(); + console2.log("ZedxionCustomBridge", address(bridge)); + console2.log("Admin", admin); + } +} diff --git a/script/deploy/rwa/GrantUarRegistrarRWA138.s.sol b/script/deploy/rwa/GrantUarRegistrarRWA138.s.sol new file mode 100644 index 0000000..5f15a6c --- /dev/null +++ b/script/deploy/rwa/GrantUarRegistrarRWA138.s.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Script.sol"; +import {UniversalAssetRegistry} from "../../../contracts/registry/UniversalAssetRegistry.sol"; + +/** + * @title GrantUarRegistrarRWA138 + * @notice Grant REGISTRAR_ROLE on UAR to broadcaster (for RegisterRWAIndicesInUAR138). + * @dev Broadcaster must hold DEFAULT_ADMIN_ROLE on UAR. + */ +contract GrantUarRegistrarRWA138 is Script { + function run() external { + uint256 pk = vm.envUint("PRIVATE_KEY"); + address grantee = vm.envOr("UAR_REGISTRAR_GRANTEE", vm.addr(pk)); + address uarAddr = vm.envAddress("UNIVERSAL_ASSET_REGISTRY"); + + UniversalAssetRegistry uar = UniversalAssetRegistry(uarAddr); + bytes32 role = uar.REGISTRAR_ROLE(); + + vm.startBroadcast(pk); + uar.grantRole(role, grantee); + vm.stopBroadcast(); + + console.log("REGISTRAR_ROLE granted to", grantee, "on UAR", uarAddr); + } +} diff --git a/script/deploy/rwa/RedeployLiIndexPublisher138.s.sol b/script/deploy/rwa/RedeployLiIndexPublisher138.s.sol new file mode 100644 index 0000000..0fb0fae --- /dev/null +++ b/script/deploy/rwa/RedeployLiIndexPublisher138.s.sol @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Script.sol"; +import {RWATokenRegistry} from "../../../contracts/rwa/RWATokenRegistry.sol"; +import {RWATokenFactory} from "../../../contracts/rwa/RWATokenFactory.sol"; +import {IRWATokenFactory} from "../../../contracts/rwa/IRWATokenFactory.sol"; +import {RWAToken} from "../../../contracts/rwa/RWAToken.sol"; + +/** + * @title RedeployLiIndexPublisher138 + * @notice Operator recovery when Gnosis Safe co-signer keys are unavailable: + * deactivate Li* in registry, redeploy with deployer as INDEX_PUBLISHER, publish index level. + * + * Env: LI_TICKER (LiXAU|LiPMG|LiBMG1|LiBMG2|LiBMG3), PRIVATE_KEY, RWA_TOKEN_REGISTRY, RWA_TOKEN_FACTORY + * REBASE_INDEX_VALUE (optional), OWNER, COMPLIANCE_ADMIN, INDEX_PUBLISHER, RWA_METHODOLOGY_HASH + */ +contract RedeployLiIndexPublisher138 is Script { + struct LiProduct { + string indexTicker; + string name; + string symbol; + string assetGroup; + string instrumentType; + string underlyingAsset; + uint256 defaultIndexValue; + } + + function run() external { + string memory ticker = vm.envString("LI_TICKER"); + LiProduct memory p = _product(ticker); + + uint256 pk = vm.envUint("PRIVATE_KEY"); + address deployer = vm.addr(pk); + address registryAddr = vm.envAddress("RWA_TOKEN_REGISTRY"); + address factoryAddr = vm.envAddress("RWA_TOKEN_FACTORY"); + address owner = vm.envOr("OWNER", vm.envOr("OMNL_COMPLIANCE_MULTISIG", deployer)); + address compliance = vm.envOr("COMPLIANCE_ADMIN", vm.envOr("OMNL_GNOSIS_SAFE_ADMIN", deployer)); + address publisher = vm.envOr("INDEX_PUBLISHER", deployer); + uint256 rebaseValue = vm.envOr("REBASE_INDEX_VALUE", p.defaultIndexValue); + bytes32 methodologyHash = vm.parseBytes32( + vm.envOr("RWA_METHODOLOGY_HASH", string("0x6b6e599d0ba31d048b49302e263a12f0c59502f67a35100ad5c65b503b1d4b82")) + ); + + RWATokenRegistry registry = RWATokenRegistry(registryAddr); + RWATokenFactory factory = RWATokenFactory(factoryAddr); + + vm.startBroadcast(pk); + + if (registry.isRegistered(p.indexTicker)) { + registry.deactivateIndex(p.indexTicker); + console.log("Deactivated", p.indexTicker, "in registry"); + } + + address token = factory.deployRWAIndex( + IRWATokenFactory.RWAProductConfig({ + indexTicker: p.indexTicker, + name: p.name, + symbol: p.symbol, + decimals: 6, + assetClass: "Commodities", + assetGroup: p.assetGroup, + instrumentType: p.instrumentType, + underlyingAsset: p.underlyingAsset, + gruLayer: "M00", + jurisdiction: "International", + initialOwner: owner, + complianceAdmin: compliance, + indexPublisher: publisher, + initialIndexValue: p.defaultIndexValue, + initialSupply: 0, + methodologyDocumentHash: methodologyHash, + registerInUniversalAssetRegistry: false + }) + ); + + if (rebaseValue != p.defaultIndexValue) { + RWAToken(token).updateIndexValue(rebaseValue); + } + + vm.stopBroadcast(); + + console.log("Li* redeployed", p.indexTicker, token); + console.log("indexValue", RWAToken(token).indexValue()); + console.log("indexPublisher", publisher); + } + + function _product(string memory ticker) internal pure returns (LiProduct memory p) { + bytes32 h = keccak256(bytes(ticker)); + if (h == keccak256("LiXAU")) { + return LiProduct({ + indexTicker: "LiXAU", + name: "XAU Liquidity Index (M00)", + symbol: "LiXAU", + assetGroup: "Precious Metals", + instrumentType: "Commodity Index", + underlyingAsset: "Gold", + defaultIndexValue: 1_000_000 + }); + } + if (h == keccak256("LiPMG")) { + return LiProduct({ + indexTicker: "LiPMG", + name: "Precious Metals Group Index (M00)", + symbol: "LiPMG", + assetGroup: "Precious Metals", + instrumentType: "Basket Index", + underlyingAsset: "Precious Metals", + defaultIndexValue: 1_000_000 + }); + } + if (h == keccak256("LiBMG1")) { + return LiProduct({ + indexTicker: "LiBMG1", + name: "Base Metals Group Index 1 (M00)", + symbol: "LiBMG1", + assetGroup: "Industrial Metals", + instrumentType: "Basket Index", + underlyingAsset: "Base Metals", + defaultIndexValue: 1_000_000 + }); + } + if (h == keccak256("LiBMG2")) { + return LiProduct({ + indexTicker: "LiBMG2", + name: "Base Metals Group Index 2 (M00)", + symbol: "LiBMG2", + assetGroup: "Industrial Metals", + instrumentType: "Basket Index", + underlyingAsset: "Battery Metals", + defaultIndexValue: 1_000_000 + }); + } + if (h == keccak256("LiBMG3")) { + return LiProduct({ + indexTicker: "LiBMG3", + name: "Base Metals Group Index 3 (M00)", + symbol: "LiBMG3", + assetGroup: "Industrial Metals", + instrumentType: "Basket Index", + underlyingAsset: "Building Metals", + defaultIndexValue: 1_000_000 + }); + } + revert("RedeployLiIndexPublisher138: unsupported LI_TICKER"); + } +} diff --git a/script/deploy/rwa/RedeployLiXauPublisher138.s.sol b/script/deploy/rwa/RedeployLiXauPublisher138.s.sol new file mode 100644 index 0000000..bab785c --- /dev/null +++ b/script/deploy/rwa/RedeployLiXauPublisher138.s.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Script.sol"; +import {RWATokenRegistry} from "../../../contracts/rwa/RWATokenRegistry.sol"; +import {RWATokenFactory} from "../../../contracts/rwa/RWATokenFactory.sol"; +import {IRWATokenFactory} from "../../../contracts/rwa/IRWATokenFactory.sol"; +import {RWAToken} from "../../../contracts/rwa/RWAToken.sol"; + +/** + * @title RedeployLiXauPublisher138 + * @notice Operator recovery when Gnosis Safe co-signer keys are unavailable: + * deactivate LiXAU in registry, redeploy with deployer as INDEX_PUBLISHER, publish rebase level. + * + * Env: PRIVATE_KEY, RWA_TOKEN_REGISTRY, RWA_TOKEN_FACTORY, REBASE_INDEX_VALUE (default 1885856) + * OWNER (default OMNL multisig), COMPLIANCE_ADMIN (default Gnosis Safe admin) + * INDEX_PUBLISHER (default deployer), RWA_METHODOLOGY_HASH + */ +contract RedeployLiXauPublisher138 is Script { + function run() external { + uint256 pk = vm.envUint("PRIVATE_KEY"); + address deployer = vm.addr(pk); + address registryAddr = vm.envAddress("RWA_TOKEN_REGISTRY"); + address factoryAddr = vm.envAddress("RWA_TOKEN_FACTORY"); + address owner = vm.envOr("OWNER", vm.envOr("OMNL_COMPLIANCE_MULTISIG", deployer)); + address compliance = vm.envOr("COMPLIANCE_ADMIN", vm.envOr("OMNL_GNOSIS_SAFE_ADMIN", deployer)); + address publisher = vm.envOr("INDEX_PUBLISHER", deployer); + uint256 rebaseValue = vm.envOr("REBASE_INDEX_VALUE", uint256(1_885_856)); + bytes32 methodologyHash = vm.parseBytes32(vm.envOr("RWA_METHODOLOGY_HASH", string("0x6b6e599d0ba31d048b49302e263a12f0c59502f67a35100ad5c65b503b1d4b82"))); + + RWATokenRegistry registry = RWATokenRegistry(registryAddr); + RWATokenFactory factory = RWATokenFactory(factoryAddr); + + vm.startBroadcast(pk); + + if (registry.isRegistered("LiXAU")) { + registry.deactivateIndex("LiXAU"); + console.log("Deactivated LiXAU in registry"); + } + + address token = factory.deployRWAIndex( + IRWATokenFactory.RWAProductConfig({ + indexTicker: "LiXAU", + name: "XAU Liquidity Index (M00)", + symbol: "LiXAU", + decimals: 6, + assetClass: "Commodities", + assetGroup: "Precious Metals", + instrumentType: "Commodity Index", + underlyingAsset: "Gold", + gruLayer: "M00", + jurisdiction: "International", + initialOwner: owner, + complianceAdmin: compliance, + indexPublisher: publisher, + initialIndexValue: 1_000_000, + initialSupply: 0, + methodologyDocumentHash: methodologyHash, + registerInUniversalAssetRegistry: false + }) + ); + + RWAToken(token).updateIndexValue(rebaseValue); + + vm.stopBroadcast(); + + console.log("LiXAU redeployed", token); + console.log("indexValue", RWAToken(token).indexValue()); + console.log("indexPublisher", publisher); + } +} diff --git a/script/flash/RunMainnetUniV2CwusdcUsdcFlashRebalanceRemove.s.sol b/script/flash/RunMainnetUniV2CwusdcUsdcFlashRebalanceRemove.s.sol new file mode 100644 index 0000000..45f72c9 --- /dev/null +++ b/script/flash/RunMainnetUniV2CwusdcUsdcFlashRebalanceRemove.s.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Script, console} from "forge-std/Script.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { + AaveUniV2CwStableRebalanceFlashReceiver +} from "../../contracts/flash/AaveUniV2CwStableRebalanceFlashReceiver.sol"; + +/** + * @title RunMainnetUniV2CwusdcUsdcFlashRebalanceRemove + * @notice Simulate or broadcast flash rebalance + LP remove on mainnet cWUSDC/USDC UniV2. + * + * Prerequisite: run plan-mainnet-cwusdc-usdc-univ2-flash-rebalance-remove.py and transfer LP + * to the receiver (or set UNIV2_FLASH_PULL_LP=1 with prior LP approval to receiver). + * + * Env: + * PRIVATE_KEY + * ETHEREUM_MAINNET_RPC + * UNIV2_FLASH_REBALANCE_RECEIVER_MAINNET + * UNIV2_FLASH_REBALANCE_PLAN_JSON optional path to planner JSON + */ +contract RunMainnetUniV2CwusdcUsdcFlashRebalanceRemove is Script { + address internal constant DEFAULT_PAIR = 0xC28706F899266b36BC43cc072b3a921BDf2C48D9; + address internal constant DEFAULT_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; + address internal constant DEFAULT_CW = 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a; + address internal constant DEFAULT_USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + + function run() external { + uint256 pk = vm.envUint("PRIVATE_KEY"); + address receiver = vm.envAddress("UNIV2_FLASH_REBALANCE_RECEIVER_MAINNET"); + string memory planPath = vm.envOr( + "UNIV2_FLASH_REBALANCE_PLAN_JSON", + string("reports/status/mainnet-cwusdc-usdc-univ2-flash-rebalance-plan-latest.json") + ); + + ( + address pair, + address router, + address cw, + address usdc, + address lpHolder, + address recipient, + uint256 lpAmount, + uint256 flashStableIn, + uint256 minCwRebalance, + uint256 minCwRemove, + uint256 minStableRemove, + uint256 cwToSell, + uint256 minStableRepay + ) = _loadPlan(planPath); + + pair = pair == address(0) ? DEFAULT_PAIR : pair; + router = router == address(0) ? DEFAULT_ROUTER : router; + cw = cw == address(0) ? DEFAULT_CW : cw; + usdc = usdc == address(0) ? DEFAULT_USDC : usdc; + + console.log("receiver", receiver); + console.log("pair", pair); + console.log("lpHolder", lpHolder); + console.log("lpAmount", lpAmount); + console.log("flashStableIn", flashStableIn); + console.log("receiverLpBefore", IERC20(pair).balanceOf(receiver)); + + vm.startBroadcast(pk); + if (vm.envOr("UNIV2_FLASH_PULL_LP", uint256(0)) == 1) { + AaveUniV2CwStableRebalanceFlashReceiver(receiver).pullLpFrom(pair, lpHolder, lpAmount); + } + AaveUniV2CwStableRebalanceFlashReceiver.RebalanceRemoveParams memory p = + AaveUniV2CwStableRebalanceFlashReceiver.RebalanceRemoveParams({ + router: router, + pair: pair, + cwToken: cw, + stableToken: usdc, + lpAmount: lpAmount, + rebalanceStableIn: flashStableIn, + minCwFromRebalance: minCwRebalance, + minStableFromRemove: minStableRemove, + minCwFromRemove: minCwRemove, + cwToSellForRepay: cwToSell, + minStableFromRepaySwap: minStableRepay, + recipient: recipient + }); + AaveUniV2CwStableRebalanceFlashReceiver(receiver).runRebalanceRemove(usdc, flashStableIn, p); + vm.stopBroadcast(); + + console.log("recipientStableAfter", IERC20(usdc).balanceOf(recipient)); + console.log("recipientCwAfter", IERC20(cw).balanceOf(recipient)); + } + + function _loadPlan(string memory path) + internal + view + returns ( + address pair, + address router, + address cw, + address usdc, + address lpHolder, + address recipient, + uint256 lpAmount, + uint256 flashStableIn, + uint256 minCwRebalance, + uint256 minCwRemove, + uint256 minStableRemove, + uint256 cwToSell, + uint256 minStableRepay + ) + { + string memory json = vm.readFile(path); + pair = vm.parseJsonAddress(json, ".pair"); + router = vm.parseJsonAddress(json, ".router"); + cw = vm.parseJsonAddress(json, ".cwToken"); + usdc = vm.parseJsonAddress(json, ".stableToken"); + lpHolder = vm.parseJsonAddress(json, ".lpHolder"); + recipient = vm.parseJsonAddress(json, ".recipient"); + lpAmount = vm.parseJsonUint(json, ".lpAmountRaw"); + flashStableIn = vm.parseJsonUint(json, ".flashLoan.stableBorrowRaw"); + minCwRebalance = vm.parseJsonUint(json, ".rebalanceSwap.minCwOutRaw"); + minCwRemove = vm.parseJsonUint(json, ".removeLiquidity.minCwOutRaw"); + minStableRemove = vm.parseJsonUint(json, ".removeLiquidity.minStableOutRaw"); + cwToSell = vm.parseJsonUint(json, ".repaySwap.cwToSellRaw"); + minStableRepay = vm.parseJsonUint(json, ".repaySwap.minStableOutRaw"); + } +} diff --git a/script/flash/RunManagedMainnetAaveCwusdtUsdtQuotePushCycle.s.sol b/script/flash/RunManagedMainnetAaveCwusdtUsdtQuotePushCycle.s.sol new file mode 100644 index 0000000..be4d4da --- /dev/null +++ b/script/flash/RunManagedMainnetAaveCwusdtUsdtQuotePushCycle.s.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Script, console} from "forge-std/Script.sol"; +import {AaveQuotePushFlashReceiver} from "../../contracts/flash/AaveQuotePushFlashReceiver.sol"; +import {QuotePushTreasuryManager} from "../../contracts/flash/QuotePushTreasuryManager.sol"; + +interface IDODOPMMPoolQuoteManagedUsdt { + function querySellQuote(address trader, uint256 payQuoteAmount) external view returns (uint256 receiveBaseAmount, uint256 mtFee); +} + +/// @notice USDT rail mirror of RunManagedMainnetAaveCwusdcUsdcQuotePushCycle. +contract RunManagedMainnetAaveCwusdtUsdtQuotePushCycle is Script { + address internal constant DEFAULT_POOL = 0x79156F6B7bf71a1B72D78189B540A89A6C13F6FC; + address internal constant DEFAULT_CWUSDT = 0xaF5017d0163ecb99D9B5D94e3b4D7b09Af44D8AE; + address internal constant DEFAULT_USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; + uint256 internal constant DEFENDED_SAFE_CAP_RAW = 2_964_298; + + function run() external { + uint256 pk = vm.envUint("PRIVATE_KEY"); + address receiver = vm.envAddress("AAVE_QUOTE_PUSH_RECEIVER_MAINNET"); + address managerAddr = vm.envAddress("QUOTE_PUSH_TREASURY_MANAGER_MAINNET"); + address pool = vm.envOr("POOL_CWUSDT_USDT_MAINNET", DEFAULT_POOL); + address integration = vm.envAddress("DODO_PMM_INTEGRATION_MAINNET"); + address baseToken = vm.envOr("CWUSDT_MAINNET", DEFAULT_CWUSDT); + address usdt = vm.envOr("USDT_MAINNET", DEFAULT_USDT); + 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)); + + 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"); + + QuotePushTreasuryManager manager = QuotePushTreasuryManager(managerAddr); + AaveQuotePushFlashReceiver.QuotePushParams memory p = + _loadQuotePushParams(receiver, pool, integration, baseToken, unwinder, amount); + + vm.startBroadcast(pk); + (uint256 harvested, uint256 gasAmount, uint256 recycleAmount) = + manager.runManagedCycle(usdt, amount, p, harvest, gasHoldbackTargetRaw); + vm.stopBroadcast(); + + console.log("managedCycleHarvestedRaw", harvested); + console.log("managedCycleGasDistributionRaw", gasAmount); + console.log("managedCycleRecycleDistributionRaw", recycleAmount); + } + + function _loadQuotePushParams( + address receiver, + address pool, + address integration, + address baseToken, + address unwinder, + uint256 amount + ) internal view returns (AaveQuotePushFlashReceiver.QuotePushParams memory p) { + uint256 minPmmNum = vm.envOr("MIN_OUT_PMM_NUM", uint256(985)); + uint256 minPmmDen = vm.envOr("MIN_OUT_PMM_DEN", uint256(1000)); + uint256 minOutPmm = vm.envOr("MIN_OUT_PMM", uint256(0)); + if (minOutPmm == 0) { + (uint256 baseOut,) = IDODOPMMPoolQuoteManagedUsdt(pool).querySellQuote(receiver, amount); + minOutPmm = (baseOut * minPmmNum) / minPmmDen; + } + uint256 premiumBps = vm.envOr("AAVE_FLASH_PREMIUM_BPS", uint256(5)); + uint256 buf = vm.envOr("MIN_OUT_UNWIND_BUFFER_RAW", uint256(5000)); + uint256 premium = (amount * premiumBps) / 10000; + uint256 minOutUnwind = vm.envOr("MIN_OUT_UNWIND", amount + premium + buf); + uint256 unwindMode = vm.envOr("UNWIND_MODE", uint256(1)); + bytes memory unwindData; + if (unwindMode == 1) { + address dodoPool = vm.envOr("UNWIND_DODO_POOL", pool); + unwindData = abi.encode(dodoPool); + } else { + revert("USDT rail: UNWIND_MODE=1 (DODO) required for now"); + } + p = AaveQuotePushFlashReceiver.QuotePushParams({ + integration: integration, + pmmPool: pool, + baseToken: baseToken, + externalUnwinder: unwinder, + minOutPmm: minOutPmm, + minOutUnwind: minOutUnwind, + unwindData: unwindData, + atomicBridge: AaveQuotePushFlashReceiver.AtomicBridgeParams({ + coordinator: address(0), + sourceChain: 0, + destinationChain: 0, + destinationAsset: address(0), + bridgeAmount: 0, + minDestinationAmount: 0, + destinationRecipient: address(0), + destinationDeadline: 0, + routeId: bytes32(0), + settlementMode: bytes32(0), + submitCommitment: false + }) + }); + } +} diff --git a/script/flash/SeedMainnetCwStablePoolPermanent.s.sol b/script/flash/SeedMainnetCwStablePoolPermanent.s.sol new file mode 100644 index 0000000..1224333 --- /dev/null +++ b/script/flash/SeedMainnetCwStablePoolPermanent.s.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Script, console} from "forge-std/Script.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +interface IDODOPMMIntegrationSeed { + function isRegisteredPool(address pool) external view returns (bool); + function addLiquidity(address pool, uint256 baseAmount, uint256 quoteAmount) + external + returns (uint256 baseShare, uint256 quoteShare, uint256 lpShare); +} + +interface IDODOPMMPoolSeed { + function _BASE_TOKEN_() external view returns (address); + function _QUOTE_TOKEN_() external view returns (address); + function getVaultReserve() external view returns (uint256 baseReserve, uint256 quoteReserve); +} + +/// @notice Permanently seed defended mainnet cWUSDC/USDC or cWUSDT/USDT DODO pools via addLiquidity. +/// @dev Uses deployer inventory — not flash (flash must repay same block). Pair with Aave borrow + vault deposit off-chain. +contract SeedMainnetCwStablePoolPermanent is Script { + using SafeERC20 for IERC20; + + address internal constant DEFAULT_INTEGRATION = 0xa9F284eD010f4F7d7F8F201742b49b9f58e29b84; + address internal constant POOL_CWUSDC_USDC = 0x69776fc607e9edA8042e320e7e43f54d06c68f0E; + /// @dev Registered on DODOPMMIntegration.pools(cWUSDT, USDT); 0x99d012… is an unregistered duplicate. + address internal constant POOL_CWUSDT_USDT = 0x79156F6B7bf71a1B72D78189B540A89A6C13F6FC; + address internal constant CWUSDC = 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a; + address internal constant CWUSDT = 0xaF5017d0163ecb99D9B5D94e3b4D7b09Af44D8AE; + address internal constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address internal constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; + + function run() external { + uint256 pk = vm.envUint("PRIVATE_KEY"); + address deployer = vm.addr(pk); + string memory rail = vm.envOr("CW_STABLE_RAIL", string("USDC")); + uint256 baseAmount = vm.envUint("SEED_BASE_AMOUNT_RAW"); + uint256 quoteAmount = vm.envUint("SEED_QUOTE_AMOUNT_RAW"); + address integration = vm.envOr("DODO_PMM_INTEGRATION_MAINNET", DEFAULT_INTEGRATION); + + (address pool, address baseToken, address quoteToken) = _resolveRail(rail); + + IDODOPMMPoolSeed poolView = IDODOPMMPoolSeed(pool); + require(poolView._BASE_TOKEN_() == baseToken, "base mismatch"); + require(poolView._QUOTE_TOKEN_() == quoteToken, "quote mismatch"); + require(IDODOPMMIntegrationSeed(integration).isRegisteredPool(pool), "pool not registered on integration"); + + (uint256 baseBefore, uint256 quoteBefore) = poolView.getVaultReserve(); + console.log("deployer", deployer); + console.log("rail", rail); + console.log("pool", pool); + console.log("baseAmount", baseAmount); + console.log("quoteAmount", quoteAmount); + console.log("baseReserveBefore", baseBefore); + console.log("quoteReserveBefore", quoteBefore); + + vm.startBroadcast(pk); + IERC20(baseToken).forceApprove(integration, baseAmount); + IERC20(quoteToken).forceApprove(integration, quoteAmount); + (uint256 baseShare, uint256 quoteShare, uint256 lpShare) = + IDODOPMMIntegrationSeed(integration).addLiquidity(pool, baseAmount, quoteAmount); + IERC20(baseToken).forceApprove(integration, 0); + IERC20(quoteToken).forceApprove(integration, 0); + vm.stopBroadcast(); + + (uint256 baseAfter, uint256 quoteAfter) = poolView.getVaultReserve(); + console.log("baseShare", baseShare); + console.log("quoteShare", quoteShare); + console.log("lpShare", lpShare); + console.log("baseReserveAfter", baseAfter); + console.log("quoteReserveAfter", quoteAfter); + } + + function _resolveRail(string memory rail) + internal + pure + returns (address pool, address baseToken, address quoteToken) + { + bytes32 key = keccak256(bytes(rail)); + if (key == keccak256(bytes("USDC"))) { + return (POOL_CWUSDC_USDC, CWUSDC, USDC); + } + if (key == keccak256(bytes("USDT"))) { + return (POOL_CWUSDT_USDT, CWUSDT, USDT); + } + revert("CW_STABLE_RAIL must be USDC or USDT"); + } +} diff --git a/script/hybx-omnl/RecoverOMNLReserveVaultSafe.s.sol b/script/hybx-omnl/RecoverOMNLReserveVaultSafe.s.sol new file mode 100644 index 0000000..15810e3 --- /dev/null +++ b/script/hybx-omnl/RecoverOMNLReserveVaultSafe.s.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Script, console2} from "forge-std/Script.sol"; +import {ReserveCommitmentStore} from "../../contracts/hybx-omnl/ReserveCommitmentStore.sol"; + +/// @notice Deploy reserve store with deployer bootstrap, enable notary gate, migrate commitment, hand DEFAULT_ADMIN to vault Safe. +contract RecoverOMNLReserveVaultSafe is Script { + bytes32 private constant DEFAULT_ADMIN_ROLE = 0x00; + + function run() external { + uint256 pk = vm.envUint("PRIVATE_KEY"); + address deployer = vm.addr(pk); + address vaultSafe = vm.envAddress("OMNL_VAULT_SAFE"); + address legacyStore = vm.envAddress("OMNL_RESERVE_STORE_138"); + address notary = vm.envAddress("OMNL_NOTARY_REGISTRY"); + bytes32 lineId = vm.envBytes32("OMNL_HEALTH_LINE_ID"); + + bytes32 jurId = keccak256(bytes(vm.envOr("OMNL_JURISDICTION_ID", string("ID")))); + bytes32 matrixId = keccak256(bytes(vm.envOr("OMNL_MATRIX_CONTROL_ID", string("ID-OMNL-001")))); + uint256 attTh = vm.envOr("OMNL_RESERVE_ATTESTATION_THRESHOLD", uint256(3)); + + ReserveCommitmentStore legacy = ReserveCommitmentStore(legacyStore); + ReserveCommitmentStore.Commitment memory c = legacy.getCommitment(lineId); + address mirror = legacy.mirrorReceiver(); + + vm.startBroadcast(pk); + + ReserveCommitmentStore storeV3 = new ReserveCommitmentStore(deployer); + storeV3.configureNotaryGate(notary, true, jurId, matrixId); + storeV3.setAttestationThreshold(attTh); + if (mirror != address(0)) { + storeV3.setMirrorReceiver(mirror); + } + if (c.R != 0) { + storeV3.commitReserve(lineId, c.R, c.validUntil, c.evidenceHash, c.merkleRoot); + } + storeV3.grantRole(DEFAULT_ADMIN_ROLE, vaultSafe); + storeV3.revokeRole(DEFAULT_ADMIN_ROLE, deployer); + + vm.stopBroadcast(); + + console2.log("ReserveCommitmentStoreV3", address(storeV3)); + console2.log("vaultSafe", vaultSafe); + console2.log("requireNotarizedEvidence", storeV3.requireNotarizedEvidence()); + console2.log("legacyStore", legacyStore); + } +} diff --git a/script/m00-diamond/DeployM00DiamondHub138.s.sol b/script/m00-diamond/DeployM00DiamondHub138.s.sol index 4efc370..d6a95c5 100644 --- a/script/m00-diamond/DeployM00DiamondHub138.s.sol +++ b/script/m00-diamond/DeployM00DiamondHub138.s.sol @@ -103,13 +103,14 @@ contract DeployM00DiamondHub138 is Script { } function _accessSelectors() internal pure returns (bytes4[] memory s) { - s = new bytes4[](6); + s = new bytes4[](7); s[0] = AccessFacet.grantRoles.selector; s[1] = AccessFacet.revokeRoles.selector; - s[2] = AccessFacet.ROLE_UPGRADE_BIT.selector; - s[3] = AccessFacet.ROLE_GOVERNANCE_BIT.selector; - s[4] = AccessFacet.ROLE_INDEX_BIT.selector; - s[5] = AccessFacet.ROLE_MONETARY_BIT.selector; + s[2] = AccessFacet.hasRoles.selector; + s[3] = AccessFacet.ROLE_UPGRADE_BIT.selector; + s[4] = AccessFacet.ROLE_GOVERNANCE_BIT.selector; + s[5] = AccessFacet.ROLE_INDEX_BIT.selector; + s[6] = AccessFacet.ROLE_MONETARY_BIT.selector; } function _bridgeSelectors() internal pure returns (bytes4[] memory s) { @@ -137,15 +138,19 @@ contract DeployM00DiamondHub138 is Script { } function _rwaDocSelectors() internal pure returns (bytes4[] memory s) { - s = new bytes4[](2); + s = new bytes4[](4); s[0] = RWADocumentFacet.anchorDocument.selector; s[1] = RWADocumentFacet.setPrimaryContentHash.selector; + s[2] = RWADocumentFacet.documentCount.selector; + s[3] = RWADocumentFacet.getDocument.selector; } function _rwaStdSelectors() internal pure returns (bytes4[] memory s) { - s = new bytes4[](3); + s = new bytes4[](5); s[0] = RWAStandardsRegistryFacet.enableStandard.selector; s[1] = RWAStandardsRegistryFacet.disableStandard.selector; s[2] = RWAStandardsRegistryFacet.bindAssetStandardFacet.selector; + s[3] = RWAStandardsRegistryFacet.isStandardEnabled.selector; + s[4] = RWAStandardsRegistryFacet.assetStandardFacet.selector; } } diff --git a/script/m00-diamond/SeedM00LiIndexWeights138.s.sol b/script/m00-diamond/SeedM00LiIndexWeights138.s.sol new file mode 100644 index 0000000..373473d --- /dev/null +++ b/script/m00-diamond/SeedM00LiIndexWeights138.s.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Script.sol"; +import {IndexFacet} from "@gru/facets/IndexFacet.sol"; +import {IERC173} from "@gru/interfaces/IERC173.sol"; + +/// @notice Seed unit weights (1e18) for each Li* index on the M00 diamond hub. +contract SeedM00LiIndexWeights138 is Script { + function run() external { + uint256 pk = vm.envUint("PRIVATE_KEY"); + address diamond = vm.envAddress("M00_DIAMOND_HUB"); + uint256 chainId = 138; + + string[5] memory tickers = ["LiXAU", "LiPMG", "LiBMG1", "LiBMG2", "LiBMG3"]; + + vm.startBroadcast(pk); + for (uint256 i = 0; i < tickers.length; i++) { + bytes32 indexId = keccak256(abi.encode(tickers[i], chainId)); + bytes32[] memory keys = new bytes32[](1); + keys[0] = keccak256(abi.encode(tickers[i])); + uint256[] memory weights = new uint256[](1); + weights[0] = 1e18; + IndexFacet(diamond).setWeights(indexId, keys, weights, 1, keccak256("M00_LI_v1")); + } + address newOwner = vm.envOr("M00_DIAMOND_OWNER", vm.envOr("GOVERNANCE_CONTROLLER", address(0))); + if (newOwner != address(0)) { + IERC173(diamond).transferOwnership(newOwner); + } + vm.stopBroadcast(); + } +} diff --git a/script/m00-diamond/UpgradeM00DiamondHubComplete138.s.sol b/script/m00-diamond/UpgradeM00DiamondHubComplete138.s.sol new file mode 100644 index 0000000..7068856 --- /dev/null +++ b/script/m00-diamond/UpgradeM00DiamondHubComplete138.s.sol @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Script.sol"; +import {IDiamondCut} from "@gru/interfaces/IDiamondCut.sol"; +import {IndexFacet} from "@gru/facets/IndexFacet.sol"; +import {MonetaryFacet} from "@gru/facets/MonetaryFacet.sol"; +import {GovernanceFacet} from "@gru/facets/GovernanceFacet.sol"; +import {AccessFacet} from "@gru/facets/AccessFacet.sol"; +import {IAccess} from "@gru/interfaces/IAccess.sol"; +import {IGovernance} from "@gru/interfaces/IGovernance.sol"; +import {RWAInstrumentFacet} from "../../contracts/rwa/diamond/facets/RWAInstrumentFacet.sol"; +import {RWADocumentFacet} from "../../contracts/rwa/diamond/facets/RWADocumentFacet.sol"; +import {RWAStandardsRegistryFacet} from "../../contracts/rwa/diamond/facets/RWAStandardsRegistryFacet.sol"; + +/** + * @title UpgradeM00DiamondHubComplete138 + * @notice Add GRC Index/Monetary/Governance facets, RWA read selectors, AccessFacet.hasRoles; grant GRC roles. + */ +contract UpgradeM00DiamondHubComplete138 is Script { + uint256 internal constant ROLE_ALL_GRC = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3); + + function run() external { + uint256 pk = vm.envUint("PRIVATE_KEY"); + address deployer = vm.addr(pk); + address diamond = vm.envAddress("M00_DIAMOND_HUB"); + address governance = vm.envOr("GOVERNANCE_CONTROLLER", deployer); + address accessFacet = vm.envAddress("M00_ACCESS_FACET"); + address rwaInst = vm.envAddress("M00_RWA_INSTRUMENT_FACET"); + address rwaDoc = vm.envAddress("M00_RWA_DOCUMENT_FACET"); + address rwaStd = vm.envAddress("M00_RWA_STANDARDS_FACET"); + + vm.startBroadcast(pk); + + IndexFacet index = new IndexFacet(); + MonetaryFacet monetary = new MonetaryFacet(); + GovernanceFacet govFacet = new GovernanceFacet(); + + IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](7); + cut[0] = _add(address(index), _indexSelectors()); + cut[1] = _add(address(monetary), _monetarySelectors()); + cut[2] = _add(address(govFacet), _governanceSelectors()); + cut[3] = _add(accessFacet, _accessExtraSelectors()); + cut[4] = _add(rwaInst, _rwaInstViewSelectors()); + cut[5] = _add(rwaDoc, _rwaDocViewSelectors()); + cut[6] = _add(rwaStd, _rwaStdViewSelectors()); + + IDiamondCut(diamond).diamondCut(cut, address(0), ""); + + IAccess(diamond).grantRoles(governance, ROLE_ALL_GRC); + if (governance != deployer) { + IAccess(diamond).grantRoles(deployer, ROLE_ALL_GRC); + } + + IGovernance(diamond).setGovernanceParams(86_400, 5_000); + + console.log("M00Diamond", diamond); + console.log("IndexFacet", address(index)); + console.log("MonetaryFacet", address(monetary)); + console.log("GovernanceFacet", address(govFacet)); + vm.stopBroadcast(); + } + + function _add(address facet, bytes4[] memory sels) + internal + pure + returns (IDiamondCut.FacetCut memory) + { + return IDiamondCut.FacetCut({ + facetAddress: facet, + action: IDiamondCut.FacetCutAction.Add, + functionSelectors: sels + }); + } + + function _indexSelectors() internal pure returns (bytes4[] memory s) { + s = new bytes4[](8); + s[0] = IndexFacet.setWeights.selector; + s[1] = IndexFacet.getIndex.selector; + s[2] = IndexFacet.recalcLiCRI.selector; + s[3] = IndexFacet.recalcLiCRIWeighted.selector; + s[4] = IndexFacet.setDashboardComposite.selector; + s[5] = IndexFacet.getLiCRI.selector; + s[6] = IndexFacet.setIndexValue.selector; + s[7] = IndexFacet.getIndexValue.selector; + } + + function _monetarySelectors() internal pure returns (bytes4[] memory s) { + s = new bytes4[](6); + s[0] = MonetaryFacet.mintM1.selector; + s[1] = MonetaryFacet.burnM1.selector; + s[2] = MonetaryFacet.issueM0.selector; + s[3] = MonetaryFacet.redeemM0.selector; + s[4] = MonetaryFacet.setScalarS.selector; + s[5] = MonetaryFacet.getLayers.selector; + } + + function _governanceSelectors() internal pure returns (bytes4[] memory s) { + s = new bytes4[](9); + s[0] = GovernanceFacet.proposeCut.selector; + s[1] = GovernanceFacet.queueCut.selector; + s[2] = GovernanceFacet.executeCut.selector; + s[3] = GovernanceFacet.emergencyBrake.selector; + s[4] = GovernanceFacet.timelock.selector; + s[5] = GovernanceFacet.quorumBps.selector; + s[6] = GovernanceFacet.eta.selector; + s[7] = GovernanceFacet.proposer.selector; + s[8] = GovernanceFacet.setGovernanceParams.selector; + } + + function _accessExtraSelectors() internal pure returns (bytes4[] memory s) { + s = new bytes4[](1); + s[0] = AccessFacet.hasRoles.selector; + } + + function _rwaInstViewSelectors() internal pure returns (bytes4[] memory s) { + s = new bytes4[](2); + s[0] = RWAInstrumentFacet.getIssuanceMode.selector; + s[1] = RWAInstrumentFacet.getTokenPointer.selector; + } + + function _rwaDocViewSelectors() internal pure returns (bytes4[] memory s) { + s = new bytes4[](2); + s[0] = RWADocumentFacet.documentCount.selector; + s[1] = RWADocumentFacet.getDocument.selector; + } + + function _rwaStdViewSelectors() internal pure returns (bytes4[] memory s) { + s = new bytes4[](2); + s[0] = RWAStandardsRegistryFacet.isStandardEnabled.selector; + s[1] = RWAStandardsRegistryFacet.assetStandardFacet.selector; + } +} diff --git a/script/reserve/WireChain138OraclePegs138.s.sol b/script/reserve/WireChain138OraclePegs138.s.sol new file mode 100644 index 0000000..fc4d8ae --- /dev/null +++ b/script/reserve/WireChain138OraclePegs138.s.sol @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Script, console} from "forge-std/Script.sol"; +import {Aggregator} from "../../contracts/oracle/Aggregator.sol"; + +interface IOraclePriceFeedWire { + function aggregators(address asset) external view returns (address); + function setAggregator(address asset, address aggregator, uint256 multiplier) external; + function updatePriceFeed(address asset) external; + function setUpdateInterval(uint256 interval) external; + function updateInterval() external view returns (uint256); +} + +interface IPriceFeedKeeperWire { + function isTracked(address asset) external view returns (bool); + function trackAsset(address asset) external; + function maxUpdatesPerCall() external view returns (uint256); + function setMaxUpdatesPerCall(uint256 max) external; +} + +interface IDODOPMMIntegrationWire { + function setReserveSystem(address reserveSystem_) external; + function reserveSystem() external view returns (address); +} + +interface IReserveSystemWire { + function updatePriceFeed(address asset, uint256 price, uint256 timestamp) external; +} + +interface IAggregatorWire { + function addTransmitter(address transmitter) external; + function updateAnswer(uint256 answer) external; + function isTransmitter(address transmitter) external view returns (bool); + function latestRoundData() + external + view + returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); +} + +/// @notice Wire canonical Chain 138 assets to Chainlink-mirrored repo aggregators + keeper + reserve. +contract WireChain138OraclePegs138 is Script { + uint256 internal constant MULT = 1e10; + + address internal constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address internal constant WETH10 = 0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9F; + address internal constant LINK = 0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03; + address internal constant CUSDT = 0x93E66202A11B1772E55407B32B44e5Cd8eda7f22; + address internal constant CUSDC = 0xf22258f57794CC8E06237084b353Ab30fFfa640b; + address internal constant CEURC = 0x8085961F9cF02b4d800A3c6d386D31da4B34266a; + address internal constant CEURT = 0xdf4b71c61E5912712C1Bdd451416B9aC26949d72; + address internal constant CGBPC = 0x003960f16D9d34F2e98d62723B6721Fb92074aD2; + address internal constant CGBPT = 0x350f54e4D23795f86A9c03988c7135357CCaD97c; + address internal constant CAUDC = 0xD51482e567c03899eecE3CAe8a058161FD56069D; + address internal constant CJPYC = 0xEe269e1226a334182aace90056EE4ee5Cc8A6770; + address internal constant CCHFC = 0x873990849DDa5117d7C644f0aF24370797C03885; + address internal constant CCADC = 0x54dBd40cF05e15906A2C21f600937e96787f5679; + address internal constant CXAUC = 0x290E52a8819A4fbD0714E517225429aA2B70EC6b; + address internal constant CXAUT = 0x94e408E26c6FD8F4ee00b54dF19082FDA07dC96E; + address internal constant CBTC = 0xe94260c555aC1d9D3CC9E1632883452ebDf0082E; + + function run() external { + require(block.chainid == 138, "ChainID 138 only"); + + uint256 pk = vm.envUint("PRIVATE_KEY"); + address deployer = vm.addr(pk); + + address opf = vm.envAddress("ORACLE_PRICE_FEED"); + address keeperAddr = vm.envAddress("PRICE_FEED_KEEPER_ADDRESS"); + address rs = vm.envAddress("RESERVE_SYSTEM"); + address ethAgg = vm.envAddress("AGGREGATOR_ADDRESS"); + address dodo = vm.envAddress("CHAIN_138_DODO_PMM_INTEGRATION"); + uint256 ethSeed8 = vm.envOr("FEED_ETH_USD_8", uint256(0)); + + IOraclePriceFeedWire op = IOraclePriceFeedWire(opf); + IPriceFeedKeeperWire keeper = IPriceFeedKeeperWire(keeperAddr); + + console.log("=== Wire Chain 138 oracle pegs ==="); + console.log("Deployer:", deployer); + + vm.startBroadcast(pk); + + if (op.updateInterval() != 86400) { + try op.setUpdateInterval(86400) {} catch {} + } + + if (ethSeed8 > 0) { + IAggregatorWire eth = IAggregatorWire(ethAgg); + if (!eth.isTransmitter(deployer)) { + try eth.addTransmitter(deployer) {} catch {} + } + try eth.updateAnswer(ethSeed8) {} catch {} + } + + if (keeper.maxUpdatesPerCall() < 20) { + keeper.setMaxUpdatesPerCall(20); + } + + address usdAgg = _deployMirror("USD/USD Chainlink mirror", vm.envUint("FEED_USD_USD_8")); + address linkAgg = _deployMirror("LINK/USD Chainlink mirror", vm.envUint("FEED_LINK_USD_8")); + address btcAgg = _deployMirror("BTC/USD Chainlink mirror", vm.envUint("FEED_BTC_USD_8")); + address xauAgg = _deployMirror("XAU/USD Chainlink mirror", vm.envUint("FEED_XAU_USD_8")); + address eurAgg = _deployMirror("EUR/USD Chainlink mirror", vm.envUint("FEED_EUR_USD_8")); + address gbpAgg = _deployMirror("GBP/USD Chainlink mirror", vm.envUint("FEED_GBP_USD_8")); + address audAgg = _deployMirror("AUD/USD Chainlink mirror", vm.envUint("FEED_AUD_USD_8")); + address jpyAgg = _deployMirror("JPY/USD Chainlink mirror", vm.envUint("FEED_JPY_USD_8")); + address chfAgg = _deployMirror("CHF/USD Chainlink mirror", vm.envUint("FEED_CHF_USD_8")); + address cadAgg = _deployMirror("CAD/USD Chainlink mirror", vm.envUint("FEED_CAD_USD_8")); + + _wire(op, keeper, rs, WETH9, ethAgg); + _wire(op, keeper, rs, WETH10, ethAgg); + _wire(op, keeper, rs, LINK, linkAgg); + _wire(op, keeper, rs, CUSDT, usdAgg); + _wire(op, keeper, rs, CUSDC, usdAgg); + _wire(op, keeper, rs, CEURC, eurAgg); + _wire(op, keeper, rs, CEURT, eurAgg); + _wire(op, keeper, rs, CGBPC, gbpAgg); + _wire(op, keeper, rs, CGBPT, gbpAgg); + _wire(op, keeper, rs, CAUDC, audAgg); + _wire(op, keeper, rs, CJPYC, jpyAgg); + _wire(op, keeper, rs, CCHFC, chfAgg); + _wire(op, keeper, rs, CCADC, cadAgg); + _wire(op, keeper, rs, CXAUC, xauAgg); + _wire(op, keeper, rs, CXAUT, xauAgg); + _wire(op, keeper, rs, CBTC, btcAgg); + + IDODOPMMIntegrationWire dodoInt = IDODOPMMIntegrationWire(dodo); + if (dodoInt.reserveSystem() != rs) { + dodoInt.setReserveSystem(rs); + console.log("DODO reserveSystem wired:", rs); + } + + vm.stopBroadcast(); + + console.log("=== Done ==="); + } + + function _deployMirror(string memory description, uint256 price8) internal returns (address) { + require(price8 > 0, "missing seed price"); + Aggregator agg = new Aggregator(description, msg.sender, 3600, 50); + agg.addTransmitter(msg.sender); + agg.updateAnswer(price8); + console.log("Deployed feed:", description); + console.log(" aggregator:", address(agg)); + return address(agg); + } + + function _wire( + IOraclePriceFeedWire op, + IPriceFeedKeeperWire keeper, + address rs, + address token, + address agg + ) internal { + require(agg != address(0), "zero aggregator"); + if (op.aggregators(token) == address(0)) { + op.setAggregator(token, agg, MULT); + } + if (!keeper.isTracked(token)) { + keeper.trackAsset(token); + } + try op.updatePriceFeed(token) {} catch { + (, int256 ans,, uint256 updatedAt,) = IAggregatorWire(agg).latestRoundData(); + require(ans > 0, "bad agg price"); + IReserveSystemWire(rs).updatePriceFeed(token, uint256(ans) * MULT, updatedAt); + } + } +} diff --git a/scripts/deploy-frontend.sh b/scripts/deploy-frontend.sh index fe74731..e30fe0c 100755 --- a/scripts/deploy-frontend.sh +++ b/scripts/deploy-frontend.sh @@ -36,7 +36,7 @@ cd .. # Deploy Admin Dashboard echo "" echo "--- Building Admin Dashboard ---" -cd ../dbis_core/frontend +cd /home/intlc/projects/dbis_core/frontend if [ -f "package.json" ]; then npm install diff --git a/scripts/deployment/export-bsc-cw-verification-artifacts.sh b/scripts/deployment/export-bsc-cw-verification-artifacts.sh new file mode 100755 index 0000000..76b6e0f --- /dev/null +++ b/scripts/deployment/export-bsc-cw-verification-artifacts.sh @@ -0,0 +1,180 @@ +#!/usr/bin/env bash +# Export BSC cW* verification artifacts: pinned profile build + Standard JSON for BscScan manual upload. +# +# Usage (from smom-dbis-138/): +# ./scripts/deployment/export-bsc-cw-verification-artifacts.sh +# ./scripts/deployment/export-bsc-cw-verification-artifacts.sh --verify # also submit forge verify (cWBNB may fail) +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +REPO_ROOT="$(cd "$PROJECT_ROOT/.." && pwd)" +OUT_DIR="$REPO_ROOT/reports/status/bsc-cw-verification" +VERIFY=0 +[[ "${1:-}" == "--verify" ]] && VERIFY=1 + +cd "$PROJECT_ROOT" + +if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then + # shellcheck disable=SC1090 + source "$SCRIPT_DIR/../lib/deployment/dotenv.sh" + load_deployment_env --repo-root "$PROJECT_ROOT" +fi + +[[ -n "${ETHERSCAN_API_KEY:-}" ]] || { echo "ETHERSCAN_API_KEY required" >&2; exit 1; } + +ADMIN="${CW_VERIFY_ADMIN:-}" +if [[ -z "$ADMIN" && -n "${PRIVATE_KEY:-}" ]]; then + ADMIN="$(cast wallet address --private-key "$PRIVATE_KEY")" +fi +[[ -n "$ADMIN" ]] || { echo "CW_VERIFY_ADMIN or PRIVATE_KEY required" >&2; exit 1; } + +CWBNB="${CWBNB_BSC:-0x179034a08ac2c9c35d2e41239f68c79dca6f18fa}" +CONTRACT_PATH="contracts/tokens/CompliantWrappedToken.sol:CompliantWrappedToken" +CTOR="$(cast abi-encode 'constructor(string,string,uint8,address)' \ + 'Wrapped BNB Gas (Compliant)' 'cWBNB' 18 "$ADMIN")" + +mkdir -p "$OUT_DIR" + +echo "==> Pinned profile: bsc_tokens_verify (see foundry.toml + config/bsc-cw-verify-profile.v1.json)" +echo "==> Scoped build: scripts/forge/scope.sh build tokens" +export FOUNDRY_PROFILE=bsc_tokens_verify +export FOUNDRY_SRC="contracts/tokens,contracts/interfaces" +export FOUNDRY_OUT="out/scopes/tokens" +export FOUNDRY_CACHE_PATH="cache/scopes/tokens" +bash "$PROJECT_ROOT/scripts/forge/scope.sh" build tokens + +echo "==> Export Standard JSON Input (upload at https://bscscan.com/verifyContract solidity-standard-json-input)" +JSON_OUT="$OUT_DIR/CompliantWrappedToken_cWBNB_standard_input.json" +# forge may print ERROR log lines before the JSON object on stdout — keep only the JSON line +forge verify-contract \ + --chain bsc \ + --num-of-optimizations 200 \ + --via-ir \ + --evm-version london \ + --compiler-version "0.8.20+commit.a1b79de6" \ + --constructor-args "$CTOR" \ + --show-standard-json-input \ + "$CWBNB" \ + "$CONTRACT_PATH" 2>/dev/null | awk '/^\{/{buf=$0} END{if(buf) print buf}' | jq -c . > "$JSON_OUT" +[[ -s "$JSON_OUT" ]] || { echo "Failed to write Standard JSON (forge output empty?)" >&2; exit 5; } +echo " wrote $JSON_OUT" + +echo "==> Export flattened source (reference only; prefer Standard-JSON for via-ir)" +forge flatten contracts/tokens/CompliantWrappedToken.sol > "$OUT_DIR/CompliantWrappedToken_cWBNB_flattened.sol" 2>/dev/null +echo " wrote $OUT_DIR/CompliantWrappedToken_cWBNB_flattened.sol" + +RPC="${BSC_RPC_URL:-${BSC_MAINNET_RPC:-}}" +if [[ -n "$RPC" ]]; then + ONCHAIN="$(cast code "$CWBNB" --rpc-url "$RPC" | sed 's/^0x//' | tr '[:upper:]' '[:lower:]')" + BUILT="$(jq -r .deployedBytecode.object "$FOUNDRY_OUT/CompliantWrappedToken.sol/CompliantWrappedToken.json" | sed 's/^0x//' | tr '[:upper:]' '[:lower:]')" + MATCH=false + [[ "$ONCHAIN" == "$BUILT" ]] && MATCH=true + FIRST_DIFF_OFFSET="" + if [[ "$MATCH" == false ]]; then + FIRST_DIFF_OFFSET="$(python3 -c " +on, b = '''$ONCHAIN''', '''$BUILT''' +n = min(len(on), len(b)) // 2 +for i in range(n): + if on[2*i:2*i+2] != b[2*i:2*i+2]: + print(i) + break +else: + print(n if len(on)//2 != len(b)//2 else '') +" 2>/dev/null || true)" + fi + jq -n \ + --arg generatedAt "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + --arg address "$CWBNB" \ + --argjson onChainRuntimeBytes $(( ${#ONCHAIN} / 2 )) \ + --argjson pinnedBuildRuntimeBytes $(( ${#BUILT} / 2 )) \ + --argjson runtimeBytecodeMatch "$([[ "$MATCH" == true ]] && echo true || echo false)" \ + --arg firstRuntimeDiffOffset "${FIRST_DIFF_OFFSET:-null}" \ + --arg constructorArgs "$CTOR" \ + --arg foundryProfile "bsc_tokens_verify" \ + --arg standardJson "$JSON_OUT" \ + '{ + generatedAt: $generatedAt, + address: $address, + onChainRuntimeBytes: $onChainRuntimeBytes, + pinnedBuildRuntimeBytes: $pinnedBuildRuntimeBytes, + runtimeBytecodeMatch: $runtimeBytecodeMatch, + firstRuntimeDiffOffset: (if $firstRuntimeDiffOffset == "" or $firstRuntimeDiffOffset == "null" then null else ($firstRuntimeDiffOffset|tonumber) end), + constructorArgs: $constructorArgs, + foundryProfile: $foundryProfile, + standardJson: $standardJson + }' > "$OUT_DIR/cWBNB_bytecode_compare.json" + echo " wrote $OUT_DIR/cWBNB_bytecode_compare.json" +fi + +cat > "$OUT_DIR/README.md" < +\`\`\` + +## If verification still fails + +On-chain runtime bytecode is **~45 bytes longer** than the current pinned scoped build (see \`cWBNB_bytecode_compare.json\`). That indicates deploy used a slightly different artifact (solc patch, source revision, or metadata), not just constructor naming. Options: + +- Locate the deploy transaction and pin the exact \`solc\` commit from that date. +- Compare \`git log contracts/tokens/CompliantWrappedToken.sol\` at deploy block. +- Redeploy cWBNB only if a new address is acceptable. + +## Automated verify (may fail until bytecode pin found) + +\`\`\`bash +cd smom-dbis-138 +FOUNDRY_PROFILE=bsc_tokens_verify \\ +FOUNDRY_SRC=contracts/tokens FOUNDRY_OUT=out/scopes/tokens \\ +forge verify-contract --chain bsc --num-of-optimizations 200 --via-ir --evm-version london \\ + --compiler-version 0.8.20+commit.a1b79de6 \\ + --etherscan-api-key "\$ETHERSCAN_API_KEY" \\ + --constructor-args "\$CTOR" \\ + $CWBNB $CONTRACT_PATH +\`\`\` +EOF +echo " wrote $OUT_DIR/README.md" + +if [[ "$VERIFY" -eq 1 ]]; then + echo "==> forge verify-contract (automated)" + set +e + forge verify-contract \ + --chain bsc \ + --num-of-optimizations 200 \ + --via-ir \ + --evm-version london \ + --compiler-version "0.8.20+commit.a1b79de6" \ + --etherscan-api-key "${ETHERSCAN_API_KEY}" \ + --constructor-args "$CTOR" \ + --watch --rpc-timeout 120 \ + "$CWBNB" \ + "$CONTRACT_PATH" + set -e +fi + +echo "" +echo "Done. Artifacts: $OUT_DIR/" diff --git a/scripts/deployment/phase9-deploy-frontend.sh b/scripts/deployment/phase9-deploy-frontend.sh index b30b282..6b218f9 100755 --- a/scripts/deployment/phase9-deploy-frontend.sh +++ b/scripts/deployment/phase9-deploy-frontend.sh @@ -33,8 +33,8 @@ fi echo "" echo "--- Building Admin Dashboard ---" -if [ -d "../dbis_core/frontend" ] && [ -f "../dbis_core/frontend/package.json" ]; then - cd ../dbis_core/frontend +if [ -d "/home/intlc/projects/dbis_core/frontend" ] && [ -f "/home/intlc/projects/dbis_core/frontend/package.json" ]; then + cd /home/intlc/projects/dbis_core/frontend echo "Installing dependencies..." npm install echo "Building..." diff --git a/scripts/forge/scope.sh b/scripts/forge/scope.sh index 7ddac47..b0ac52f 100755 --- a/scripts/forge/scope.sh +++ b/scripts/forge/scope.sh @@ -22,6 +22,7 @@ declare -A ROOT_SCRIPT_SCOPE_ALIASES=( ["DeployCompliantUSDT.s.sol"]="tokens" ["DeployCWAssetReserveVerifier.s.sol"]="bridge/integration" ["DeployCWReserveVerifier.s.sol"]="bridge/integration" + ["DeployCWReserveSettlementStack.s.sol"]="cw-settlement" ["DeployFeeCollector.s.sol"]="utils" ["DeployGenericStateChannelManager.s.sol"]="channels" ["DeployMainnetTether.s.sol"]="tether" @@ -40,6 +41,10 @@ declare -A ROOT_SCRIPT_SCOPE_ALIASES=( ["DeployRWATokenFactory138.s.sol"]="rwa" ["DeployM00DiamondHub138.s.sol"]="m00-diamond" ["UpgradeM00DiamondAcl138.s.sol"]="m00-diamond" + ["UpgradeM00DiamondHubComplete138.s.sol"]="m00-diamond" + ["SeedM00LiIndexWeights138.s.sol"]="m00-diamond" + ["WireChain138OraclePegs138.s.sol"]="oracle" + ["GrantUarRegistrarRWA138.s.sol"]="rwa" ["FundBridgeLinkViaCcip138.s.sol"]="ccip" ["FundBridgeLinkViaCcipMainnet.s.sol"]="ccip" ["DeployCCIPRelayBridgeLINK.s.sol"]="relay" @@ -240,9 +245,13 @@ prepare_scope_env() { if [[ "$scope" == "rwa" ]]; then export FOUNDRY_SRC="contracts/rwa,contracts/compliance" + export FOUNDRY_PROFILE="${FOUNDRY_PROFILE:-chain138}" elif [[ "$scope" == "m00-diamond" ]]; then export FOUNDRY_SRC="contracts/m00-diamond,contracts/rwa" export FOUNDRY_PROFILE="${FOUNDRY_PROFILE:-m00-diamond}" + elif [[ "$scope" == "oracle" ]]; then + export FOUNDRY_SRC="$src_dir" + export FOUNDRY_PROFILE="${FOUNDRY_PROFILE:-chain138}" else export FOUNDRY_SRC="$src_dir" fi @@ -261,6 +270,8 @@ prepare_scope_env() { local script_dir="script/$scope" if [[ -d "$REPO_ROOT/$script_dir" ]]; then export FOUNDRY_SCRIPT="$script_dir" + elif [[ "$scope" == "oracle" && -d "$REPO_ROOT/script/reserve" ]]; then + export FOUNDRY_SCRIPT="script/reserve" fi fi } diff --git a/scripts/reserve/deploy-all.sh b/scripts/reserve/deploy-all.sh index c78ebfd..ab70e47 100755 --- a/scripts/reserve/deploy-all.sh +++ b/scripts/reserve/deploy-all.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/bash # Complete Keeper Deployment Script # Deploys all keeper components and sets up monitoring @@ -8,10 +8,23 @@ set -e GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' +CYAN='\033[0;36m' NC='\033[0m' +DRY_RUN="${DRY_RUN:-0}" +EXPECTED_KEEPER="0xD3AD6831aacB5386B8A25BB8D8176a6C8a026f04" + +for arg in "$@"; do + case "$arg" in + --dry-run) + DRY_RUN=1 + ;; + esac +done + # Load environment variables if [ -f .env ]; then + # shellcheck disable=SC1091 source .env fi @@ -20,15 +33,94 @@ RPC_URL="${RPC_URL_138:-https://rpc.d-bis.org}" PRIVATE_KEY="${PRIVATE_KEY}" KEEPER_ADDRESS="${PRICE_FEED_KEEPER_ADDRESS}" -echo -e "${GREEN}=== Complete Keeper Deployment ===${NC}\n" +normalize_addr() { + echo "$1" | tr '[:upper:]' '[:lower:]' +} -# Step 1: Deploy PriceFeedKeeper -echo -e "${YELLOW}Step 1: Deploying PriceFeedKeeper...${NC}" -if [ -z "$KEEPER_ADDRESS" ]; then - forge script script/reserve/DeployKeeper.s.sol:DeployKeeper \ +validate_keeper_env() { + if [ -z "$KEEPER_ADDRESS" ]; then + echo -e "${RED}ERROR: PRICE_FEED_KEEPER_ADDRESS is not set in .env${NC}" + echo -e "${YELLOW}Expected: PRICE_FEED_KEEPER_ADDRESS=${EXPECTED_KEEPER}${NC}" + exit 1 + fi + if [ "$(normalize_addr "$KEEPER_ADDRESS")" != "$(normalize_addr "$EXPECTED_KEEPER")" ]; then + echo -e "${RED}ERROR: PRICE_FEED_KEEPER_ADDRESS mismatch${NC}" + echo -e " .env: ${KEEPER_ADDRESS}" + echo -e " expected: ${EXPECTED_KEEPER}" + exit 1 + fi +} + +dry_run_msg() { + echo -e "${CYAN}[DRY RUN]${NC} $*" +} + +run_forge_broadcast() { + local script_path="$1" + forge script "$script_path" \ --rpc-url "$RPC_URL" \ --broadcast \ --verify +} + +print_forge_would_run() { + local label="$1" + local script_path="$2" + dry_run_msg "$label" + echo " forge script $script_path --rpc-url \"$RPC_URL\" --broadcast --verify" +} + +if [ "$DRY_RUN" = "1" ]; then + echo -e "${CYAN}=== Complete Keeper Deployment (DRY RUN) ===${NC}\n" + validate_keeper_env + echo -e "${GREEN}PRICE_FEED_KEEPER_ADDRESS (.env):${NC} $KEEPER_ADDRESS" + echo -e "${GREEN}RPC_URL:${NC} $RPC_URL" + echo "" + + echo -e "${YELLOW}Step 1: PriceFeedKeeper${NC}" + dry_run_msg "Skip deploy; keeper already configured at $KEEPER_ADDRESS" + echo "" + + if [ "$DEPLOY_CHAINLINK" = "true" ]; then + echo -e "${YELLOW}Step 2: ChainlinkKeeperCompatible (DEPLOY_CHAINLINK=true)${NC}" + print_forge_would_run "Would deploy ChainlinkKeeperCompatible" \ + "script/reserve/DeployChainlinkKeeper.s.sol:DeployChainlinkKeeper" + dry_run_msg "Would append CHAINLINK_KEEPER_COMPATIBLE_ADDRESS to .env (skipped)" + dry_run_msg "Follow-up: node scripts/reserve/chainlink-keeper-setup.js" + echo "" + else + echo -e "${YELLOW}Step 2: ChainlinkKeeperCompatible${NC}" + dry_run_msg "Skipped (set DEPLOY_CHAINLINK=true to include)" + echo "" + fi + + if [ "$DEPLOY_GELATO" = "true" ]; then + echo -e "${YELLOW}Step 3: GelatoKeeperCompatible (DEPLOY_GELATO=true)${NC}" + print_forge_would_run "Would deploy GelatoKeeperCompatible" \ + "script/reserve/DeployGelatoKeeper.s.sol:DeployGelatoKeeper" + dry_run_msg "Would append GELATO_KEEPER_COMPATIBLE_ADDRESS to .env (skipped)" + dry_run_msg "Follow-up: node scripts/reserve/gelato-keeper-setup.js" + echo "" + else + echo -e "${YELLOW}Step 3: GelatoKeeperCompatible${NC}" + dry_run_msg "Skipped (set DEPLOY_GELATO=true to include)" + echo "" + fi + + echo -e "${YELLOW}Step 4: Verify deployment${NC}" + dry_run_msg "Would run read-only upkeep check (no --broadcast):" + echo " forge script script/reserve/CheckUpkeep.s.sol:CheckUpkeep --rpc-url \"$RPC_URL\"" + echo "" + echo -e "${GREEN}=== Dry Run Complete (no transactions broadcast, .env unchanged) ===${NC}" + exit 0 +fi + +echo -e "${GREEN}=== Complete Keeper Deployment ===${NC}\n" + +# Step 1: Deploy PriceFeedKeepe +echo -e "${YELLOW}Step 1: Deploying PriceFeedKeeper...${NC}" +if [ -z "$KEEPER_ADDRESS" ]; then + run_forge_broadcast "script/reserve/DeployKeeper.s.sol:DeployKeeper" # Extract keeper address from output KEEPER_ADDRESS=$(forge script script/reserve/DeployKeeper.s.sol:DeployKeeper \ @@ -43,10 +135,7 @@ fi # Step 2: Deploy Chainlink Keeper Compatible (optional) if [ "$DEPLOY_CHAINLINK" = "true" ]; then echo -e "${YELLOW}Step 2: Deploying ChainlinkKeeperCompatible...${NC}" - forge script script/reserve/DeployChainlinkKeeper.s.sol:DeployChainlinkKeeper \ - --rpc-url "$RPC_URL" \ - --broadcast \ - --verify + run_forge_broadcast "script/reserve/DeployChainlinkKeeper.s.sol:DeployChainlinkKeeper" CHAINLINK_KEEPER=$(forge script script/reserve/DeployChainlinkKeeper.s.sol:DeployChainlinkKeeper \ --rpc-url "$RPC_URL" 2>&1 | grep "ChainlinkKeeperCompatible deployed at:" | awk '{print $NF}') @@ -62,10 +151,7 @@ fi # Step 3: Deploy Gelato Keeper Compatible (optional) if [ "$DEPLOY_GELATO" = "true" ]; then echo -e "${YELLOW}Step 3: Deploying GelatoKeeperCompatible...${NC}" - forge script script/reserve/DeployGelatoKeeper.s.sol:DeployGelatoKeeper \ - --rpc-url "$RPC_URL" \ - --broadcast \ - --verify + run_forge_broadcast "script/reserve/DeployGelatoKeeper.s.sol:DeployGelatoKeeper" GELATO_KEEPER=$(forge script script/reserve/DeployGelatoKeeper.s.sol:DeployGelatoKeeper \ --rpc-url "$RPC_URL" 2>&1 | grep "GelatoKeeperCompatible deployed at:" | awk '{print $NF}') @@ -99,5 +185,4 @@ echo " docker-compose -f docker/docker-compose.keeper.yml up -d" echo "" echo "4. Or use systemd:" echo " sudo systemctl enable price-feed-keeper" -echo " sudo systemctl start price-feed-keeper" - +echo " sudo systemctl start price-feed-keeper" diff --git a/test/bridge/CWMultiTokenBridge.t.sol b/test/bridge/CWMultiTokenBridge.t.sol index 2437be3..2479c76 100644 --- a/test/bridge/CWMultiTokenBridge.t.sol +++ b/test/bridge/CWMultiTokenBridge.t.sol @@ -4,7 +4,9 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {IRouterClient} from "../../contracts/ccip/IRouterClient.sol"; +import {IRouterClient} from "@chainlink/contracts-ccip/contracts/interfaces/IRouterClient.sol"; +import {Client} from "@chainlink/contracts-ccip/contracts/libraries/Client.sol"; +import {IRouterClient as LegacyRouterClient} from "../../contracts/ccip/IRouterClient.sol"; import {CWMultiTokenBridgeL1} from "../../contracts/bridge/CWMultiTokenBridgeL1.sol"; import {CWMultiTokenBridgeL2} from "../../contracts/bridge/CWMultiTokenBridgeL2.sol"; import {CompliantWrappedToken} from "../../contracts/tokens/CompliantWrappedToken.sol"; @@ -21,7 +23,7 @@ contract MockCanonicalToken is ERC20 { contract MockRouter is IRouterClient { uint256 public fee; bytes32 public nextMessageId = keccak256("message"); - EVM2AnyMessage internal _lastMessage; + Client.EVM2AnyMessage internal _lastMessage; uint64 public lastDestinationChainSelector; function setFee(uint256 newFee) external { @@ -30,31 +32,19 @@ contract MockRouter is IRouterClient { function ccipSend( uint64 destinationChainSelector, - EVM2AnyMessage memory message - ) external payable returns (bytes32 messageId, uint256 fees) { - fees = fee; + Client.EVM2AnyMessage memory message + ) external payable returns (bytes32 messageId) { if (message.feeToken == address(0)) { - require(msg.value >= fees, "native fee"); + require(msg.value >= fee, "native fee"); } lastDestinationChainSelector = destinationChainSelector; _lastMessage = message; - - emit MessageSent( - nextMessageId, - destinationChainSelector, - msg.sender, - message.receiver, - message.data, - message.tokenAmounts, - message.feeToken, - message.extraArgs - ); - - return (nextMessageId, fees); + messageId = nextMessageId; + return messageId; } - function getFee(uint64, EVM2AnyMessage memory) external view returns (uint256) { + function getFee(uint64, Client.EVM2AnyMessage memory) external view returns (uint256) { return fee; } @@ -145,7 +135,9 @@ contract CWMultiTokenBridgeTest is Test { assertEq(l2Bridge.mintedTotal(address(wrapped)), amount); assertEq(l2Bridge.burnedTotal(address(wrapped)), amount); - (, bytes memory returnData,,) = routerAvax.lastMessage(); + (, bytes memory returnData, address feeToken, bytes memory extraArgs) = routerAvax.lastMessage(); + assertTrue(extraArgs.length > 0, "missing CCIP extraArgs"); + assertEq(feeToken, address(0)); vm.prank(address(0x138138)); l1Bridge.ccipReceive(_message(returnMessageId, AVALANCHE_SELECTOR, address(l2Bridge), returnData)); @@ -308,8 +300,8 @@ contract CWMultiTokenBridgeTest is Test { relayRouter.authorizeBridge(address(receiveBridge)); relayRouter.grantRelayerRole(address(this)); - IRouterClient.TokenAmount[] memory noTokens = new IRouterClient.TokenAmount[](0); - IRouterClient.Any2EVMMessage memory message = IRouterClient.Any2EVMMessage({ + LegacyRouterClient.TokenAmount[] memory noTokens = new LegacyRouterClient.TokenAmount[](0); + LegacyRouterClient.Any2EVMMessage memory message = LegacyRouterClient.Any2EVMMessage({ messageId: keccak256("three-field"), sourceChainSelector: CHAIN138_SELECTOR, sender: abi.encode(address(0xCAFE)), @@ -326,14 +318,13 @@ contract CWMultiTokenBridgeTest is Test { uint64 sourceChainSelector, address sender, bytes memory data - ) internal pure returns (IRouterClient.Any2EVMMessage memory message) { - IRouterClient.TokenAmount[] memory noTokens = new IRouterClient.TokenAmount[](0); - message = IRouterClient.Any2EVMMessage({ + ) internal pure returns (Client.Any2EVMMessage memory message) { + message = Client.Any2EVMMessage({ messageId: messageId, sourceChainSelector: sourceChainSelector, sender: abi.encode(sender), data: data, - tokenAmounts: noTokens + destTokenAmounts: new Client.EVMTokenAmount[](0) }); } } diff --git a/test/bridge/CWMultiTokenBridgeBTC.t.sol b/test/bridge/CWMultiTokenBridgeBTC.t.sol index fc6ccf5..39d6fd1 100644 --- a/test/bridge/CWMultiTokenBridgeBTC.t.sol +++ b/test/bridge/CWMultiTokenBridgeBTC.t.sol @@ -4,7 +4,8 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {IRouterClient} from "../../contracts/ccip/IRouterClient.sol"; +import {IRouterClient} from "@chainlink/contracts-ccip/contracts/interfaces/IRouterClient.sol"; +import {Client} from "@chainlink/contracts-ccip/contracts/libraries/Client.sol"; import {CWMultiTokenBridgeL1} from "../../contracts/bridge/CWMultiTokenBridgeL1.sol"; import {CWMultiTokenBridgeL2} from "../../contracts/bridge/CWMultiTokenBridgeL2.sol"; import {CompliantWrappedToken} from "../../contracts/tokens/CompliantWrappedToken.sol"; @@ -23,18 +24,18 @@ contract MockCanonicalBTC is ERC20 { contract MockRouterBTC is IRouterClient { bytes32 public nextMessageId = keccak256("btc-message"); - EVM2AnyMessage internal _lastMessage; + Client.EVM2AnyMessage internal _lastMessage; function ccipSend( uint64 destinationChainSelector, - EVM2AnyMessage memory message - ) external payable returns (bytes32 messageId, uint256 fees) { + Client.EVM2AnyMessage memory message + ) external payable returns (bytes32 messageId) { destinationChainSelector; _lastMessage = message; - return (nextMessageId, fees); + return nextMessageId; } - function getFee(uint64, EVM2AnyMessage memory) external pure returns (uint256) { + function getFee(uint64, Client.EVM2AnyMessage memory) external pure returns (uint256) { return 0; } @@ -122,14 +123,13 @@ contract CWMultiTokenBridgeBTCTest is Test { uint64 sourceChainSelector, address sender, bytes memory data - ) internal pure returns (IRouterClient.Any2EVMMessage memory message) { - IRouterClient.TokenAmount[] memory noTokens = new IRouterClient.TokenAmount[](0); - message = IRouterClient.Any2EVMMessage({ + ) internal pure returns (Client.Any2EVMMessage memory message) { + message = Client.Any2EVMMessage({ messageId: messageId, sourceChainSelector: sourceChainSelector, sender: abi.encode(sender), data: data, - tokenAmounts: noTokens + destTokenAmounts: new Client.EVMTokenAmount[](0) }); } } diff --git a/test/bridge/CWReserveVerifierVaultV2Integration.t.sol b/test/bridge/CWReserveVerifierVaultV2Integration.t.sol index 3347d3b..7ffbc83 100644 --- a/test/bridge/CWReserveVerifierVaultV2Integration.t.sol +++ b/test/bridge/CWReserveVerifierVaultV2Integration.t.sol @@ -3,7 +3,8 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; -import {IRouterClient} from "../../contracts/ccip/IRouterClient.sol"; +import {IRouterClient} from "@chainlink/contracts-ccip/contracts/interfaces/IRouterClient.sol"; +import {Client} from "@chainlink/contracts-ccip/contracts/libraries/Client.sol"; import {OfficialStableMirrorToken} from "../../contracts/tokens/OfficialStableMirrorToken.sol"; import {CompliantUSDCTokenV2} from "../../contracts/tokens/CompliantUSDCTokenV2.sol"; import {CompliantUSDTTokenV2} from "../../contracts/tokens/CompliantUSDTTokenV2.sol"; @@ -16,19 +17,19 @@ import {CompliantWrappedToken} from "../../contracts/tokens/CompliantWrappedToke contract MockRouterVaultVerifierV2 is IRouterClient { uint256 public fee; bytes32 public nextMessageId = keccak256("cw-reserve-vault-v2-message"); - EVM2AnyMessage internal _lastMessage; + Client.EVM2AnyMessage internal _lastMessage; uint64 public lastDestinationChainSelector; function ccipSend( uint64 destinationChainSelector, - EVM2AnyMessage memory message - ) external payable returns (bytes32 messageId, uint256 fees) { + Client.EVM2AnyMessage memory message + ) external payable returns (bytes32 messageId) { lastDestinationChainSelector = destinationChainSelector; _lastMessage = message; - return (nextMessageId, fee); + return nextMessageId; } - function getFee(uint64, EVM2AnyMessage memory) external view returns (uint256) { + function getFee(uint64, Client.EVM2AnyMessage memory) external view returns (uint256) { return fee; } @@ -182,14 +183,13 @@ contract CWReserveVerifierVaultV2IntegrationTest is Test { uint64 sourceChainSelector, address sender, bytes memory data - ) internal pure returns (IRouterClient.Any2EVMMessage memory message) { - IRouterClient.TokenAmount[] memory noTokens = new IRouterClient.TokenAmount[](0); - message = IRouterClient.Any2EVMMessage({ + ) internal pure returns (Client.Any2EVMMessage memory message) { + message = Client.Any2EVMMessage({ messageId: messageId, sourceChainSelector: sourceChainSelector, sender: abi.encode(sender), data: data, - tokenAmounts: noTokens + destTokenAmounts: new Client.EVMTokenAmount[](0) }); } } diff --git a/test/compliance/MonetaryFormulas.t.sol b/test/compliance/MonetaryFormulas.t.sol new file mode 100644 index 0000000..ba65618 --- /dev/null +++ b/test/compliance/MonetaryFormulas.t.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../../contracts/compliance/libraries/MonetaryFormulas.sol"; + +contract MonetaryFormulasTest is Test { + function test_MoneySupplyCD() public pure { + assertEq(MonetaryFormulas.moneySupplyCD(100, 50), 150); + } + + function test_SimpleMultiplier() public pure { + assertEq(MonetaryFormulas.simpleMultiplier(1000), 10e18); + } + + function test_CoverageRatioBps() public pure { + assertEq(MonetaryFormulas.coverageRatioBps(120, 100), 12000); + } + + function test_GruFanout() public pure { + assertEq(MonetaryFormulas.gruM00ToM1Fanout(), 25); + } + + function test_CoverageWeightedVelocity() public pure { + uint256 v = MonetaryFormulas.coverageWeightedVelocity(2e18, 12000); + assertEq(v, 2e18); + } +} diff --git a/test/cw-settlement/CWReserveSettlementStack.t.sol b/test/cw-settlement/CWReserveSettlementStack.t.sol new file mode 100644 index 0000000..b105178 --- /dev/null +++ b/test/cw-settlement/CWReserveSettlementStack.t.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +import {CWNavOracle} from "../../contracts/cw-settlement/CWNavOracle.sol"; +import {CWRedemptionQueue} from "../../contracts/cw-settlement/CWRedemptionQueue.sol"; +import {CWStabilityFund} from "../../contracts/cw-settlement/CWStabilityFund.sol"; + +contract MockNavBridge { + mapping(address => uint256) internal _locked; + mapping(address => uint256) internal _outstanding; + + function setLocked(address token, uint256 amount) external { + _locked[token] = amount; + } + + function lockedBalance(address token) external view returns (uint256) { + return _locked[token]; + } + + function totalOutstanding(address token) external view returns (uint256) { + return _outstanding[token]; + } +} + +contract MockReserveSystem { + mapping(address => uint256) internal _balances; + + function setBalance(address asset, uint256 amount) external { + _balances[asset] = amount; + } + + function getReserveBalance(address asset) external view returns (uint256) { + return _balances[asset]; + } +} + +contract MockERC20Six is ERC20 { + constructor() ERC20("Mock", "MOCK") {} + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } + + function decimals() public pure override returns (uint8) { + return 6; + } +} + +contract CWNavOracleTest is Test { + MockNavBridge internal bridge; + MockReserveSystem internal reserveSystem; + CWNavOracle internal navOracle; + MockERC20Six internal canonical; + + function setUp() public { + bridge = new MockNavBridge(); + reserveSystem = new MockReserveSystem(); + navOracle = new CWNavOracle(address(this), address(bridge), address(reserveSystem)); + canonical = new MockERC20Six(); + navOracle.configureToken(address(canonical), address(canonical)); + } + + function test_publishNav_computesBackingAndNavPerShare() public { + bridge.setLocked(address(canonical), 120 * 1e6); + reserveSystem.setBalance(address(canonical), 30 * 1e6); + + navOracle.publishNav( + address(canonical), + 100 * 1e6, + 8200, + 1500, + 300, + keccak256("attestation-v1") + ); + + CWNavOracle.NavSnapshot memory snap = navOracle.getNavSnapshot(address(canonical)); + assertEq(snap.totalLockedAssets, 120 * 1e6); + assertEq(snap.totalReserveAssets, 30 * 1e6); + assertEq(snap.totalCwSupply, 100 * 1e6); + assertEq(snap.backingRatioBps, 15000); + assertEq(snap.navPerShare, 15e17); + } +} + +contract CWRedemptionQueueTest is Test { + CWRedemptionQueue internal queue; + MockERC20Six internal token; + + function setUp() public { + queue = new CWRedemptionQueue(address(this)); + token = new MockERC20Six(); + queue.grantRole(queue.BRIDGE_ROLE(), address(this)); + } + + function test_instantTier_claimableImmediately() public { + token.mint(address(this), 1_000 * 1e6); + token.approve(address(queue), 1_000 * 1e6); + + uint256 requestId = queue.fundAndEnqueue(address(this), address(token), 1_000 * 1e6); + queue.claim(requestId); + assertEq(token.balanceOf(address(this)), 1_000 * 1e6); + } + + function test_standardTier_requiresDelay() public { + uint256 requestId = queue.enqueueRedemption(address(this), address(token), 10_000 * 1e6); + vm.expectRevert(); + queue.claim(requestId); + vm.warp(block.timestamp + 24 hours); + queue.processDueRequests(_arr(requestId)); + vm.expectRevert(); + queue.claim(requestId); + } + + function _arr(uint256 id) internal pure returns (uint256[] memory ids) { + ids = new uint256[](1); + ids[0] = id; + } +} + +contract CWStabilityFundTest is Test { + CWStabilityFund internal fund; + MockERC20Six internal token; + + function setUp() public { + fund = new CWStabilityFund(address(this)); + token = new MockERC20Six(); + fund.setSupportedAsset(address(token), true); + } + + function test_depositAndEmergencyWithdraw() public { + token.mint(address(this), 100e6); + token.approve(address(fund), 100e6); + fund.deposit(address(token), 100e6); + fund.emergencyWithdraw(address(token), address(this), 50e6, keccak256("depeg")); + assertEq(token.balanceOf(address(this)), 50e6); + } +} diff --git a/test/flash/AaveUniV2CwStableRebalanceFlashReceiverMainnetFork.t.sol b/test/flash/AaveUniV2CwStableRebalanceFlashReceiverMainnetFork.t.sol new file mode 100644 index 0000000..c1162a1 --- /dev/null +++ b/test/flash/AaveUniV2CwStableRebalanceFlashReceiverMainnetFork.t.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { + AaveUniV2CwStableRebalanceFlashReceiver +} from "../../contracts/flash/AaveUniV2CwStableRebalanceFlashReceiver.sol"; + +contract AaveUniV2CwStableRebalanceFlashReceiverMainnetForkTest is Test { + address constant AAVE_POOL = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; + address constant PAIR = 0xC28706F899266b36BC43cc072b3a921BDf2C48D9; + address constant ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; + address constant CWUSDC = 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a; + address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address constant TOKEN_JAR = 0xf38521f130fcCF29dB1961597bc5d2B60F995f85; + + bool internal forkAvailable; + AaveUniV2CwStableRebalanceFlashReceiver internal receiver; + + modifier skipIfNoFork() { + if (!forkAvailable) return; + _; + } + + function setUp() public { + string memory rpcUrl = vm.envOr("ETHEREUM_MAINNET_RPC", string("")); + if (bytes(rpcUrl).length == 0) { + forkAvailable = false; + return; + } + try vm.createSelectFork(rpcUrl) { + forkAvailable = true; + } catch { + forkAvailable = false; + return; + } + receiver = new AaveUniV2CwStableRebalanceFlashReceiver(AAVE_POOL, address(this)); + } + + function testFork_flashRebalanceRemove_withTokenJarLp() public skipIfNoFork { + uint256 lpBal = IERC20(PAIR).balanceOf(TOKEN_JAR); + if (lpBal == 0) { + return; + } + + (uint112 r0, uint112 r1,) = _reserves(); + uint256 baseRaw = address(IERC20(CWUSDC)) < address(IERC20(USDC)) ? r0 : r1; + uint256 quoteRaw = address(IERC20(CWUSDC)) < address(IERC20(USDC)) ? r1 : r0; + if (quoteRaw == 0 || baseRaw <= quoteRaw) { + return; + } + + uint256 flashIn = _quoteInToEqualize(baseRaw, quoteRaw); + uint256 premium = (flashIn * 5 + 9999) / 10000; + uint256 lpUse = lpBal; + + vm.prank(TOKEN_JAR); + IERC20(PAIR).transfer(address(receiver), lpUse); + + uint256 minCwRebalance = 1; + uint256 minCwRemove = 1; + uint256 minStableRemove = 1; + uint256 cwToSell = flashIn + premium + 50_000; + uint256 minStableRepay = flashIn + premium; + + address recipient = address(0xBEEF); + receiver.runRebalanceRemove( + USDC, + flashIn, + AaveUniV2CwStableRebalanceFlashReceiver.RebalanceRemoveParams({ + router: ROUTER, + pair: PAIR, + cwToken: CWUSDC, + stableToken: USDC, + lpAmount: lpUse, + rebalanceStableIn: flashIn, + minCwFromRebalance: minCwRebalance, + minStableFromRemove: minStableRemove, + minCwFromRemove: minCwRemove, + cwToSellForRepay: cwToSell, + minStableFromRepaySwap: minStableRepay, + recipient: recipient + }) + ); + + assertEq(IERC20(PAIR).balanceOf(address(receiver)), 0, "LP consumed"); + assertGt(IERC20(USDC).balanceOf(recipient) + IERC20(CWUSDC).balanceOf(recipient), 0, "recipient funded"); + } + + function _reserves() internal view returns (uint112 r0, uint112 r1, uint32 ts) { + (r0, r1, ts) = _getReserves(PAIR); + } + + function _getReserves(address pair) internal view returns (uint112, uint112, uint32) { + (bool ok, bytes memory data) = pair.staticcall(abi.encodeWithSignature("getReserves()")); + require(ok, "getReserves failed"); + return abi.decode(data, (uint112, uint112, uint32)); + } + + function _quoteInToEqualize(uint256 baseRaw, uint256 quoteRaw) internal pure returns (uint256) { + uint256 y = quoteRaw; + uint256 x = baseRaw; + // Integer approximation of closed-form quote-in (matches planner within rounding). + uint256 xy = x * y; + uint256 target = _isqrt(xy); + if (target <= y) return y + 1; + uint256 need = target - y; + return (need * 1005) / 997 + 1; + } + + function _isqrt(uint256 n) internal pure returns (uint256) { + if (n == 0) return 0; + uint256 x = n; + uint256 z = (x + 1) / 2; + while (z < x) { + x = z; + z = (x + n / x) / 2; + } + return x; + } +} diff --git a/test/rwa/GruMonetaryPolicyGate.t.sol b/test/rwa/GruMonetaryPolicyGate.t.sol new file mode 100644 index 0000000..a4de881 --- /dev/null +++ b/test/rwa/GruMonetaryPolicyGate.t.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../../contracts/rwa/RWAToken.sol"; +import "../../contracts/rwa/GruMonetaryPolicyGate.sol"; +import "../../contracts/rwa/IGruMonetaryPolicyGate.sol"; + +contract GruMonetaryPolicyGateTest is Test { + GruMonetaryPolicyGate gate; + RWAToken liToken; + address admin = address(0xA11CE); + + function setUp() public { + vm.startPrank(admin); + gate = new GruMonetaryPolicyGate(admin, 12000, 11800, 25); + liToken = new RWAToken( + "LiXAU Test", + "LiXAU", + 6, + "LiXAU", + "Commodity", + "Gold", + "Index", + "XAU", + "M00", + admin, + admin, + admin, + 1_000_000, + 0, + keccak256("methodology-test") + ); + gate.setTokenGate(address(liToken), true); + liToken.setPolicyGate(address(gate)); + vm.stopPrank(); + } + + function test_AllowsMintWhenGreen() public { + vm.prank(admin); + gate.updateMetrics(15000, 10, IGruMonetaryPolicyGate.VelocityZone.Green, false); + vm.prank(admin); + liToken.mint(admin, 1000e6); + assertEq(liToken.totalSupply(), 1000e6); + } + + function test_BlocksMintOnCoverageAlert() public { + vm.prank(admin); + gate.updateMetrics(11700, 10, IGruMonetaryPolicyGate.VelocityZone.Green, false); + vm.prank(admin); + vm.expectRevert("RWAToken: policy gate"); + liToken.mint(admin, 1000e6); + } + + function test_BlocksMintOnUtilization() public { + vm.prank(admin); + gate.updateMetrics(15000, 30, IGruMonetaryPolicyGate.VelocityZone.Green, false); + vm.prank(admin); + vm.expectRevert("RWAToken: policy gate"); + liToken.mint(admin, 1000e6); + } + + function test_SkipGateWhenTokenNotEnabled() public { + vm.prank(admin); + gate.setTokenGate(address(liToken), false); + vm.prank(admin); + gate.updateMetrics(10000, 99, IGruMonetaryPolicyGate.VelocityZone.Red, true); + vm.prank(admin); + liToken.mint(admin, 1000e6); + assertEq(liToken.totalSupply(), 1000e6); + } +} diff --git a/test/rwa/LiIndexFlashVault.t.sol b/test/rwa/LiIndexFlashVault.t.sol new file mode 100644 index 0000000..34338c3 --- /dev/null +++ b/test/rwa/LiIndexFlashVault.t.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../../contracts/rwa/RWAToken.sol"; +import "../../contracts/rwa/LiIndexFlashVault.sol"; +import {IERC3156FlashBorrower} from "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract MockFlashBorrower is IERC3156FlashBorrower { + bytes32 private constant _RETURN = keccak256("ERC3156FlashBorrower.onFlashLoan"); + + function onFlashLoan( + address, + address token, + uint256 amount, + uint256 fee, + bytes calldata + ) external returns (bytes32) { + IERC20(token).approve(msg.sender, amount + fee); + return _RETURN; + } +} + +contract LiIndexFlashVaultTest is Test { + LiIndexFlashVault vault; + RWAToken liToken; + MockFlashBorrower borrower; + address admin = address(0xA11CE); + + function setUp() public { + vm.startPrank(admin); + liToken = new RWAToken( + "LiXAU Test", + "LiXAU", + 6, + "LiXAU", + "Commodity", + "Gold", + "Index", + "XAU", + "M00", + admin, + admin, + admin, + 1_000_000, + 0, + keccak256("methodology-test") + ); + vault = new LiIndexFlashVault( + admin, + 8000, + 5000, + 5, + 250, + 86400, + 2400_00000000 + ); + vault.setSupportedToken(address(liToken), true); + borrower = new MockFlashBorrower(); + liToken.mint(admin, 1_000_000e6); + liToken.approve(address(vault), type(uint256).max); + vault.deposit(address(liToken), 500_000e6); + vm.stopPrank(); + } + + function test_MaxFlashLoanWithinLtv() public view { + uint256 max = vault.maxFlashLoan(address(liToken)); + assertEq(max, 250_000e6); + } + + function test_FlashLoanRepays() public { + vm.prank(admin); + vault.flashLoan(IERC3156FlashBorrower(address(borrower)), address(liToken), 100_000e6, ""); + assertEq(vault.outstandingFlash(address(liToken)), 0); + } + + function test_BlocksWhenUtilizationExceeded() public { + vm.startPrank(admin); + vault.flashLoan(IERC3156FlashBorrower(address(borrower)), address(liToken), 400_000e6, ""); + vm.stopPrank(); + assertEq(vault.maxFlashLoan(address(liToken)), 0); + } +}