WIP: Chain138 deployment scripts, flash receivers, HYBX OMNL recovery

This commit is contained in:
defiQUG
2026-06-02 06:09:44 -07:00
parent e1560a880b
commit f04a7cb7c8
35 changed files with 2279 additions and 83 deletions

View File

@@ -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",

View File

@@ -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)" },

View File

@@ -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"
]
}
}

View File

@@ -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

View File

@@ -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");
}
}

View File

@@ -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

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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");
}
}

View File

@@ -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);
}
}

View File

@@ -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");
}
}

View File

@@ -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
})
});
}
}

View File

@@ -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");
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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" <<EOF
# BSC cWBNB verification artifacts
Generated by \`smom-dbis-138/scripts/deployment/export-bsc-cw-verification-artifacts.sh\`.
## Pinned compiler (matches verified BSC cWUSDT)
| Setting | Value |
|---------|--------|
| Profile | \`bsc_tokens_verify\` in \`foundry.toml\` |
| solc | \`0.8.20+commit.a1b79de6\` |
| Optimizer | enabled, **200** runs |
| via-ir | **true** (required — stack too deep without) |
| EVM | **london** (not cancun) |
| Scope | \`FOUNDRY_SRC=contracts/tokens,contracts/interfaces\`, \`FOUNDRY_OUT=out/scopes/tokens\` |
## Manual BscScan steps
1. Open https://bscscan.com/verifyContract
2. Contract address: \`$CWBNB\`
3. Compiler type: **Solidity (Standard-Json-Input)**
4. Upload \`CompliantWrappedToken_cWBNB_standard_input.json\`
5. Constructor arguments ABI-encoded (hex): see \`cWBNB_bytecode_compare.json\` or run:
\`\`\`bash
cast abi-encode 'constructor(string,string,uint8,address)' 'Wrapped BNB Gas (Compliant)' 'cWBNB' 18 <ADMIN>
\`\`\`
## 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/"

View File

@@ -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..."

View File

@@ -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
}

View File

@@ -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"

View File

@@ -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)
});
}
}

View File

@@ -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)
});
}
}

View File

@@ -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)
});
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}