Add mainnet cW market registry and align ops scripts with Stack A.
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m18s
CI/CD Pipeline / Security Scanning (push) Successful in 2m27s
CI/CD Pipeline / Lint and Format (push) Failing after 46s
CI/CD Pipeline / Terraform Validation (push) Failing after 28s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 29s
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 1m0s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 29s
Validation / validate-genesis (push) Successful in 30s
Validation / validate-terraform (push) Failing after 32s
Validation / validate-kubernetes (push) Failing after 10s
Validation / validate-smart-contracts (push) Failing after 13s
Validation / validate-security (push) Failing after 1m23s
Validation / validate-documentation (push) Failing after 22s
Verify Deployment / Verify Deployment (push) Failing after 11m53s
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m18s
CI/CD Pipeline / Security Scanning (push) Successful in 2m27s
CI/CD Pipeline / Lint and Format (push) Failing after 46s
CI/CD Pipeline / Terraform Validation (push) Failing after 28s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 29s
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 1m0s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 29s
Validation / validate-genesis (push) Successful in 30s
Validation / validate-terraform (push) Failing after 32s
Validation / validate-kubernetes (push) Failing after 10s
Validation / validate-smart-contracts (push) Failing after 13s
Validation / validate-security (push) Failing after 1m23s
Validation / validate-documentation (push) Failing after 22s
Verify Deployment / Verify Deployment (push) Failing after 11m53s
- CWTokenMarketRegistry + USD feeds for mainnet cW* market anchors - HYBX cross-chain treasury line registry expansion - PMM seed default → live DODOPMMIntegration (0x86ADA6Ef…) - mint-xau-chain138: troy-ounce mint helper with multisig path - relay: prefer CCIP_MAINNET_LINK138 relayer key on default profile Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"updated": "2026-06-17",
|
||||
"updated": "2026-06-20",
|
||||
"description": "OMNL office settlement lines \u2014 treasury EVM wallets + canonical M1 token anchors per chain.",
|
||||
"lines": [
|
||||
{
|
||||
@@ -20,7 +20,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0x63246fbf065dc14fcd18890be3b3274a90faebb4defa602cfeaaf56254d5c3eb",
|
||||
@@ -39,7 +41,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0xf3b667aa86df7021f12238da02013ea06691654a19e14820a8e1f0810ea8584b",
|
||||
@@ -58,7 +62,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0x329d143e0dd4209e5dadc159dc39a1b2121f9f0e1daa7f6d4ef7fdd685017e86",
|
||||
@@ -77,7 +83,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0x00709b1e16c8ea744c115bbcf28c588bceefe2ee79b8ccf3ec723bafee375b0a",
|
||||
@@ -96,7 +104,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0x98179d4cf92f0703702f0f98eda788932cb5d3bf823df275b6ca826d40df941a",
|
||||
@@ -115,7 +125,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0x7e796548c9367f06628f65d7cbb8dbb104c08b0559e3079d3e4ba2868aab1a02",
|
||||
@@ -134,7 +146,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0x78287281703fd64c3bd014a5eb61130324075ea91e286def362ba3cbb06237ac",
|
||||
@@ -153,7 +167,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0xafe717d3727d08ef51307b96d4c090f69cd3f8b32622c6d6a9068d16b13da4f6",
|
||||
@@ -172,7 +188,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0x7eee9b83385d0220cac4fdae36222ed0364d445baf60e8a1fbd24d8100819e7e",
|
||||
@@ -191,7 +209,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0x751d9a27b003b5b8ec98f086003238f1ac16f1db5083817f1d8e8729bd3dbda8",
|
||||
@@ -210,7 +230,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0x2065d9d64806c8946d4d59e78cd709cadb0275f2962bc986e9bdd76d5b2bdde3",
|
||||
@@ -229,7 +251,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0x981d13dd8853a67cb3d865e9b77400c2e848a753be12c1cf693621288b2ba253",
|
||||
@@ -248,7 +272,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0x4b492f8afa40d6191fe1319190345b32fdc1918b727de2b54f86f9e890834f05",
|
||||
@@ -267,7 +293,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0xa86f3acebcecb46454e091fcf24a35f9efaa939421983f69563b53f55bb7ae94",
|
||||
@@ -286,7 +314,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0xc236020f755fe76acaf5eb5fd8a53114d1f49e8e58d985beb4f4410d52abd2a6",
|
||||
@@ -305,7 +335,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0xc820d6d2cf4b054b08435a1ea3318a88ffa849b51fc61ab12ef15b248423be96",
|
||||
@@ -324,7 +356,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0xf3c45215b31095db55af3e8a65668f86de60f689c4fdeb8d54e89f3504752c43",
|
||||
@@ -343,7 +377,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0xc96c3bb01849a9f3d8fd62f1114b5606a9235d53f771b424b299207952175f7d",
|
||||
@@ -362,7 +398,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0x5d44d97f66dcb6591d36b70375b84c73b73cfc8f3788228b5977632beb995b1d",
|
||||
@@ -381,7 +419,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0xa1fd57654dc5fef77117da02995c584a19a4b6949a03cd91ccb16551d7d3d01d",
|
||||
@@ -400,7 +440,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0xd5666338b8da738cf82c681e4a39ae5f817861061accfd98464a30470bdfcc58",
|
||||
@@ -419,7 +461,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0x3d83b0f803d19de0cd096a4c29fc27c8ef02248d8e5978a3a2e16bbe714754dc",
|
||||
@@ -438,7 +482,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0xaede6a63b3f49d0d18b13267855274e250516a97c84dcf0d31e17f359ceca4ff",
|
||||
@@ -457,7 +503,9 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
},
|
||||
{
|
||||
"lineId": "0xcdd67c94cd265ef8dd383a06b7ac4733e83a8c7cdf5030aed9ec36ab2999004a",
|
||||
@@ -476,7 +524,10 @@
|
||||
"tokenM0": "0x0000000000000000000000000000000000000000",
|
||||
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
|
||||
"inboundSettlementType": "gnosis-safe"
|
||||
}
|
||||
]
|
||||
],
|
||||
"globalInboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8"
|
||||
}
|
||||
|
||||
216
contracts/mainnet-market/CWTokenMarketRegistry.sol
Normal file
216
contracts/mainnet-market/CWTokenMarketRegistry.sol
Normal file
@@ -0,0 +1,216 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {IERC20Supply} from "./interfaces/IERC20Supply.sol";
|
||||
import {ICWTokenUsdFeedView, IUniV3PoolTokens} from "./interfaces/IMarketAnchor.sol";
|
||||
|
||||
/**
|
||||
* @title CWTokenMarketRegistry
|
||||
* @notice On-chain market anchor for mainnet cW* — face USD, published TWAP, x402 settlement mirror,
|
||||
* and Chainlink-compatible per-token feeds for indexers that ingest custom oracles.
|
||||
* @dev Does not unilaterally control Etherscan UI; pairs with UniV3 liquidity + operator sync.
|
||||
*/
|
||||
contract CWTokenMarketRegistry {
|
||||
uint8 public constant PRICE_DECIMALS = 8;
|
||||
/// @dev Max deviation from faceValueUsdE8 on publishPrice (1500 = 15%) for USD-face cW* stables.
|
||||
uint16 public constant MAX_PEG_DEVIATION_BPS = 1500;
|
||||
|
||||
address public admin;
|
||||
bool public paused;
|
||||
|
||||
struct TokenConfig {
|
||||
bool registered;
|
||||
uint8 tokenDecimals;
|
||||
uint64 faceValueUsdE8;
|
||||
address uniV3Pool;
|
||||
bool cwIsToken0;
|
||||
uint64 publishedPriceUsdE8;
|
||||
uint256 publishedAt;
|
||||
uint80 roundId;
|
||||
uint256 settlementCount;
|
||||
}
|
||||
|
||||
mapping(address => TokenConfig) public tokens;
|
||||
mapping(address => address) public tokenFeed;
|
||||
address[] public tokenList;
|
||||
|
||||
event AdminChanged(address indexed newAdmin);
|
||||
event Paused();
|
||||
event Unpaused();
|
||||
event TokenRegistered(
|
||||
address indexed token,
|
||||
uint64 faceValueUsdE8,
|
||||
address indexed uniV3Pool,
|
||||
address indexed feed
|
||||
);
|
||||
event MarketPricePublished(
|
||||
address indexed token,
|
||||
uint80 roundId,
|
||||
uint64 priceUsdE8,
|
||||
uint256 timestamp,
|
||||
string source
|
||||
);
|
||||
event X402SettlementRecorded(
|
||||
address indexed token,
|
||||
address indexed payer,
|
||||
address indexed payTo,
|
||||
uint256 amountRaw,
|
||||
uint64 faceUsdE8,
|
||||
uint256 settlementChainId,
|
||||
bytes32 settlementTxHash,
|
||||
bytes32 resourceHash
|
||||
);
|
||||
|
||||
modifier onlyAdmin() {
|
||||
require(msg.sender == admin, "only admin");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier whenNotPaused() {
|
||||
require(!paused, "paused");
|
||||
_;
|
||||
}
|
||||
|
||||
constructor(address _admin) {
|
||||
require(_admin != address(0), "zero admin");
|
||||
admin = _admin;
|
||||
}
|
||||
|
||||
function setAdmin(address newAdmin) external onlyAdmin {
|
||||
require(newAdmin != address(0), "zero admin");
|
||||
admin = newAdmin;
|
||||
emit AdminChanged(newAdmin);
|
||||
}
|
||||
|
||||
function pause() external onlyAdmin {
|
||||
paused = true;
|
||||
emit Paused();
|
||||
}
|
||||
|
||||
function unpause() external onlyAdmin {
|
||||
paused = false;
|
||||
emit Unpaused();
|
||||
}
|
||||
|
||||
function registerToken(
|
||||
address token,
|
||||
uint8 tokenDecimals,
|
||||
uint64 faceValueUsdE8,
|
||||
address uniV3Pool,
|
||||
bool cwIsToken0,
|
||||
address feed
|
||||
) external onlyAdmin whenNotPaused {
|
||||
require(token != address(0), "zero token");
|
||||
require(faceValueUsdE8 > 0, "zero face");
|
||||
require(feed != address(0), "zero feed");
|
||||
require(
|
||||
ICWTokenUsdFeedView(feed).registry() == address(this)
|
||||
&& ICWTokenUsdFeedView(feed).token() == token,
|
||||
"feed mismatch"
|
||||
);
|
||||
if (uniV3Pool != address(0)) {
|
||||
address t0 = IUniV3PoolTokens(uniV3Pool).token0();
|
||||
address t1 = IUniV3PoolTokens(uniV3Pool).token1();
|
||||
require(t0 == token || t1 == token, "pool token");
|
||||
}
|
||||
|
||||
if (tokens[token].registered) {
|
||||
require(tokens[token].tokenDecimals == tokenDecimals, "decimals locked");
|
||||
} else {
|
||||
tokenList.push(token);
|
||||
}
|
||||
|
||||
tokens[token] = TokenConfig({
|
||||
registered: true,
|
||||
tokenDecimals: tokenDecimals,
|
||||
faceValueUsdE8: faceValueUsdE8,
|
||||
uniV3Pool: uniV3Pool,
|
||||
cwIsToken0: cwIsToken0,
|
||||
publishedPriceUsdE8: faceValueUsdE8,
|
||||
publishedAt: block.timestamp,
|
||||
roundId: 1,
|
||||
settlementCount: tokens[token].settlementCount
|
||||
});
|
||||
tokenFeed[token] = feed;
|
||||
|
||||
emit TokenRegistered(token, faceValueUsdE8, uniV3Pool, feed);
|
||||
emit MarketPricePublished(token, 1, faceValueUsdE8, block.timestamp, "face_bootstrap");
|
||||
}
|
||||
|
||||
function publishPrice(address token, uint64 priceUsdE8, string calldata source)
|
||||
external
|
||||
onlyAdmin
|
||||
whenNotPaused
|
||||
{
|
||||
TokenConfig storage t = _requireToken(token);
|
||||
require(priceUsdE8 > 0, "zero price");
|
||||
_enforcePegBand(t.faceValueUsdE8, priceUsdE8);
|
||||
t.roundId += 1;
|
||||
t.publishedPriceUsdE8 = priceUsdE8;
|
||||
t.publishedAt = block.timestamp;
|
||||
emit MarketPricePublished(token, t.roundId, priceUsdE8, block.timestamp, source);
|
||||
}
|
||||
|
||||
function recordX402Settlement(
|
||||
address token,
|
||||
address payer,
|
||||
address payTo,
|
||||
uint256 amountRaw,
|
||||
uint64 faceUsdE8,
|
||||
uint256 settlementChainId,
|
||||
bytes32 settlementTxHash,
|
||||
bytes32 resourceHash
|
||||
) external onlyAdmin whenNotPaused {
|
||||
TokenConfig storage t = _requireToken(token);
|
||||
require(payer != address(0) && payTo != address(0), "zero addr");
|
||||
require(amountRaw > 0, "zero amount");
|
||||
t.settlementCount += 1;
|
||||
emit X402SettlementRecorded(
|
||||
token,
|
||||
payer,
|
||||
payTo,
|
||||
amountRaw,
|
||||
faceUsdE8,
|
||||
settlementChainId,
|
||||
settlementTxHash,
|
||||
resourceHash
|
||||
);
|
||||
}
|
||||
|
||||
function latestPriceUsdE8(address token) external view returns (uint64) {
|
||||
TokenConfig storage t = _requireToken(token);
|
||||
return t.publishedPriceUsdE8 != 0 ? t.publishedPriceUsdE8 : t.faceValueUsdE8;
|
||||
}
|
||||
|
||||
/// @notice USD market cap with 8 decimals: priceUsdE8 * totalSupply / 10^tokenDecimals
|
||||
function onchainMarketCapUsdE8(address token) external view returns (uint256) {
|
||||
TokenConfig storage t = _requireToken(token);
|
||||
uint64 price = t.publishedPriceUsdE8 != 0 ? t.publishedPriceUsdE8 : t.faceValueUsdE8;
|
||||
uint256 supply = IERC20Supply(token).totalSupply();
|
||||
return (uint256(price) * supply) / (10 ** uint256(t.tokenDecimals));
|
||||
}
|
||||
|
||||
function tokenCount() external view returns (uint256) {
|
||||
return tokenList.length;
|
||||
}
|
||||
|
||||
function latestRoundDataForToken(address token)
|
||||
external
|
||||
view
|
||||
returns (uint80 roundId, int256 answer, uint256 updatedAt)
|
||||
{
|
||||
TokenConfig storage t = _requireToken(token);
|
||||
uint64 price = t.publishedPriceUsdE8 != 0 ? t.publishedPriceUsdE8 : t.faceValueUsdE8;
|
||||
return (t.roundId, int256(uint256(price)), t.publishedAt);
|
||||
}
|
||||
|
||||
function _requireToken(address token) internal view returns (TokenConfig storage t) {
|
||||
t = tokens[token];
|
||||
require(t.registered, "unregistered");
|
||||
}
|
||||
|
||||
function _enforcePegBand(uint64 faceUsdE8, uint64 priceUsdE8) internal pure {
|
||||
uint256 maxDev = (uint256(faceUsdE8) * MAX_PEG_DEVIATION_BPS) / 10_000;
|
||||
require(priceUsdE8 >= faceUsdE8 - maxDev && priceUsdE8 <= faceUsdE8 + maxDev, "peg band");
|
||||
}
|
||||
}
|
||||
67
contracts/mainnet-market/CWTokenUsdFeed.sol
Normal file
67
contracts/mainnet-market/CWTokenUsdFeed.sol
Normal file
@@ -0,0 +1,67 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {IAggregatorV3} from "./interfaces/IAggregatorV3.sol";
|
||||
import {CWTokenMarketRegistry} from "./CWTokenMarketRegistry.sol";
|
||||
|
||||
/**
|
||||
* @title CWTokenUsdFeed
|
||||
* @notice Per-token Chainlink AggregatorV3-compatible USD feed backed by CWTokenMarketRegistry.
|
||||
*/
|
||||
contract CWTokenUsdFeed is IAggregatorV3 {
|
||||
CWTokenMarketRegistry public immutable registry;
|
||||
address public immutable token;
|
||||
string public description_;
|
||||
|
||||
constructor(CWTokenMarketRegistry _registry, address _token, string memory _description) {
|
||||
require(address(_registry) != address(0) && _token != address(0), "zero arg");
|
||||
registry = _registry;
|
||||
token = _token;
|
||||
description_ = _description;
|
||||
}
|
||||
|
||||
function decimals() external pure returns (uint8) {
|
||||
return 8;
|
||||
}
|
||||
|
||||
function description() external view returns (string memory) {
|
||||
return description_;
|
||||
}
|
||||
|
||||
function version() external pure returns (uint256) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
function getRoundData(uint80 _roundId)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
uint80 roundId,
|
||||
int256 answer,
|
||||
uint256 startedAt,
|
||||
uint256 updatedAt,
|
||||
uint80 answeredInRound
|
||||
)
|
||||
{
|
||||
(roundId, answer, startedAt, updatedAt, answeredInRound) = latestRoundData();
|
||||
if (_roundId != 0) {
|
||||
require(roundId == _roundId, "stale round");
|
||||
}
|
||||
}
|
||||
|
||||
function latestRoundData()
|
||||
public
|
||||
view
|
||||
returns (
|
||||
uint80 roundId,
|
||||
int256 answer,
|
||||
uint256 startedAt,
|
||||
uint256 updatedAt,
|
||||
uint80 answeredInRound
|
||||
)
|
||||
{
|
||||
(roundId, answer, updatedAt) = registry.latestRoundDataForToken(token);
|
||||
startedAt = updatedAt;
|
||||
answeredInRound = roundId;
|
||||
}
|
||||
}
|
||||
31
contracts/mainnet-market/interfaces/IAggregatorV3.sol
Normal file
31
contracts/mainnet-market/interfaces/IAggregatorV3.sol
Normal file
@@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
/// @notice Chainlink AggregatorV3-compatible surface for explorer / DeFi integrations.
|
||||
interface IAggregatorV3 {
|
||||
function decimals() external view returns (uint8);
|
||||
function description() external view returns (string memory);
|
||||
function version() external view returns (uint256);
|
||||
|
||||
function getRoundData(uint80 _roundId)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
uint80 roundId,
|
||||
int256 answer,
|
||||
uint256 startedAt,
|
||||
uint256 updatedAt,
|
||||
uint80 answeredInRound
|
||||
);
|
||||
|
||||
function latestRoundData()
|
||||
external
|
||||
view
|
||||
returns (
|
||||
uint80 roundId,
|
||||
int256 answer,
|
||||
uint256 startedAt,
|
||||
uint256 updatedAt,
|
||||
uint80 answeredInRound
|
||||
);
|
||||
}
|
||||
7
contracts/mainnet-market/interfaces/IERC20Supply.sol
Normal file
7
contracts/mainnet-market/interfaces/IERC20Supply.sol
Normal file
@@ -0,0 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
interface IERC20Supply {
|
||||
function totalSupply() external view returns (uint256);
|
||||
function decimals() external view returns (uint8);
|
||||
}
|
||||
12
contracts/mainnet-market/interfaces/IMarketAnchor.sol
Normal file
12
contracts/mainnet-market/interfaces/IMarketAnchor.sol
Normal file
@@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
interface ICWTokenUsdFeedView {
|
||||
function token() external view returns (address);
|
||||
function registry() external view returns (address);
|
||||
}
|
||||
|
||||
interface IUniV3PoolTokens {
|
||||
function token0() external view returns (address);
|
||||
function token1() external view returns (address);
|
||||
}
|
||||
59
script/mainnet-market/DeployCWTokenMarketRegistry.s.sol
Normal file
59
script/mainnet-market/DeployCWTokenMarketRegistry.s.sol
Normal file
@@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Script, console2} from "forge-std/Script.sol";
|
||||
import {CWTokenMarketRegistry} from "../../contracts/mainnet-market/CWTokenMarketRegistry.sol";
|
||||
import {CWTokenUsdFeed} from "../../contracts/mainnet-market/CWTokenUsdFeed.sol";
|
||||
|
||||
contract DeployCWTokenMarketRegistry is Script {
|
||||
struct TokenSeed {
|
||||
address token;
|
||||
uint8 decimals;
|
||||
uint64 faceUsdE8;
|
||||
address uniV3Pool;
|
||||
bool cwIsToken0;
|
||||
string feedDescription;
|
||||
}
|
||||
|
||||
function run() external {
|
||||
vm.startBroadcast();
|
||||
address admin = msg.sender;
|
||||
|
||||
CWTokenMarketRegistry registry = new CWTokenMarketRegistry(admin);
|
||||
|
||||
TokenSeed[] memory seeds = _seeds();
|
||||
for (uint256 i = 0; i < seeds.length; i++) {
|
||||
TokenSeed memory s = seeds[i];
|
||||
CWTokenUsdFeed feed = new CWTokenUsdFeed(registry, s.token, s.feedDescription);
|
||||
registry.registerToken(
|
||||
s.token, s.decimals, s.faceUsdE8, s.uniV3Pool, s.cwIsToken0, address(feed)
|
||||
);
|
||||
console2.log("Registered", s.token);
|
||||
console2.log(" feed", address(feed));
|
||||
}
|
||||
|
||||
vm.stopBroadcast();
|
||||
console2.log("CWTokenMarketRegistry", address(registry));
|
||||
}
|
||||
|
||||
function _seeds() internal pure returns (TokenSeed[] memory) {
|
||||
TokenSeed[] memory seeds = new TokenSeed[](2);
|
||||
seeds[0] = TokenSeed({
|
||||
token: 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a,
|
||||
decimals: 6,
|
||||
faceUsdE8: 100_000_000,
|
||||
uniV3Pool: 0x1Cf2e685682C7F7beF508F0Af15Dfb5CDda01ee3,
|
||||
cwIsToken0: true,
|
||||
feedDescription: "cWUSDC / USD (GRU market anchor)"
|
||||
});
|
||||
seeds[1] = TokenSeed({
|
||||
token: 0xaF5017d0163ecb99D9B5D94e3b4D7b09Af44D8AE,
|
||||
decimals: 6,
|
||||
faceUsdE8: 100_000_000,
|
||||
uniV3Pool: 0x6eF5A392BcbE42F3D0a855595bEa60CFf9451784,
|
||||
cwIsToken0: true,
|
||||
feedDescription: "cWUSDT / USD (GRU market anchor)"
|
||||
});
|
||||
return seeds;
|
||||
}
|
||||
}
|
||||
60
script/mainnet-market/RegisterAdditionalCWMarketTokens.s.sol
Normal file
60
script/mainnet-market/RegisterAdditionalCWMarketTokens.s.sol
Normal file
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Script, console2} from "forge-std/Script.sol";
|
||||
import {CWTokenMarketRegistry} from "../../contracts/mainnet-market/CWTokenMarketRegistry.sol";
|
||||
import {CWTokenUsdFeed} from "../../contracts/mainnet-market/CWTokenUsdFeed.sol";
|
||||
|
||||
/// @notice Register remaining mainnet CompliantWrappedToken assets on deployed registry.
|
||||
contract RegisterAdditionalCWMarketTokens is Script {
|
||||
struct Seed {
|
||||
address token;
|
||||
uint8 decimals;
|
||||
uint64 faceUsdE8;
|
||||
address uniV3Pool;
|
||||
bool cwIsToken0;
|
||||
string symbol;
|
||||
}
|
||||
|
||||
function run() external {
|
||||
address registryAddr = vm.envAddress("CW_MARKET_REGISTRY_MAINNET");
|
||||
CWTokenMarketRegistry registry = CWTokenMarketRegistry(registryAddr);
|
||||
|
||||
Seed[] memory seeds = _seeds();
|
||||
vm.startBroadcast();
|
||||
for (uint256 i = 0; i < seeds.length; i++) {
|
||||
Seed memory s = seeds[i];
|
||||
if (registry.tokenFeed(s.token) != address(0)) {
|
||||
console2.log("skip already registered", s.symbol, s.token);
|
||||
continue;
|
||||
}
|
||||
CWTokenUsdFeed feed = new CWTokenUsdFeed(
|
||||
registry, s.token, string.concat(s.symbol, " / USD (GRU market anchor)")
|
||||
);
|
||||
registry.registerToken(
|
||||
s.token, s.decimals, s.faceUsdE8, s.uniV3Pool, s.cwIsToken0, address(feed)
|
||||
);
|
||||
console2.log("Registered", s.symbol, s.token);
|
||||
console2.log(" feed", address(feed));
|
||||
}
|
||||
vm.stopBroadcast();
|
||||
console2.log("CWTokenMarketRegistry", registryAddr);
|
||||
}
|
||||
|
||||
function _seeds() internal pure returns (Seed[] memory) {
|
||||
// face $1.00 e8 for ISO fiat stables; pool=0 when no live UniV3 peg pool (DODO/UniV2 only)
|
||||
Seed[] memory seeds = new Seed[](10);
|
||||
seeds[0] = Seed(0xD4aEAa8cD3fB41Dc8437FaC7639B6d91B60A5e8d, 6, 100_000_000, address(0), true, "cWEURC");
|
||||
seeds[1] = Seed(0x855d74FFB6CF75721a9bAbc8B2ed35c8119241dC, 6, 100_000_000, address(0), true, "cWEURT");
|
||||
seeds[2] = Seed(0xc074007dc0Bfb384B1cf6426a56287Ed23FE4D52, 6, 100_000_000, address(0), true, "cWGBPC");
|
||||
seeds[3] = Seed(0x1dDF9970F01c76A692Fdba2706203E6f16e0C46F, 6, 100_000_000, address(0), true, "cWGBPT");
|
||||
seeds[4] = Seed(0x5020Db641B3Fc0dAbBc0c688C845bc4E3699f35F, 6, 100_000_000, address(0), true, "cWAUDC");
|
||||
seeds[5] = Seed(0x07EEd0D7dD40984e47B9D3a3bdded1c536435582, 6, 100_000_000, address(0), true, "cWJPYC");
|
||||
seeds[6] = Seed(0x0F91C5E6Ddd46403746aAC970D05d70FFe404780, 6, 100_000_000, address(0), true, "cWCHFC");
|
||||
seeds[7] = Seed(0x209FE32fe7B541751D190ae4e50cd005DcF8EDb4, 6, 100_000_000, address(0), true, "cWCADC");
|
||||
seeds[8] = Seed(0x572Be0fa8CA0534d642A567CEDb398B771D8a715, 6, 3_300_000_000_00, address(0), true, "cWXAUC");
|
||||
seeds[9] = Seed(0xACE1DBF857549a11aF1322e1f91F2F64b029c906, 6, 3_300_000_000_00, address(0), true, "cWXAUT");
|
||||
// cWBTC: register separately with live BTC/USD face (volatile)
|
||||
return seeds;
|
||||
}
|
||||
}
|
||||
41
script/mainnet-market/RegisterCWBTMarketToken.s.sol
Normal file
41
script/mainnet-market/RegisterCWBTMarketToken.s.sol
Normal file
@@ -0,0 +1,41 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Script, console2} from "forge-std/Script.sol";
|
||||
import {CWTokenMarketRegistry} from "../../contracts/mainnet-market/CWTokenMarketRegistry.sol";
|
||||
import {CWTokenUsdFeed} from "../../contracts/mainnet-market/CWTokenUsdFeed.sol";
|
||||
import {IAggregatorV3} from "../../contracts/mainnet-market/interfaces/IAggregatorV3.sol";
|
||||
|
||||
/// @notice Register mainnet cWBTC with live Chainlink BTC/USD face (8 decimals).
|
||||
contract RegisterCWBTMarketToken is Script {
|
||||
address internal constant CWBTC = 0x2BBe3c809386FA5349EB2da31E0848ee9f54EE59;
|
||||
address internal constant CHAINLINK_BTC_USD = 0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c;
|
||||
|
||||
function run() external {
|
||||
address registryAddr = vm.envAddress("CW_MARKET_REGISTRY_MAINNET");
|
||||
CWTokenMarketRegistry registry = CWTokenMarketRegistry(registryAddr);
|
||||
|
||||
if (registry.tokenFeed(CWBTC) != address(0)) {
|
||||
console2.log("skip already registered cWBTC", CWBTC);
|
||||
console2.log(" feed", registry.tokenFeed(CWBTC));
|
||||
return;
|
||||
}
|
||||
|
||||
(, int256 answer,, uint256 updatedAt,) = IAggregatorV3(CHAINLINK_BTC_USD).latestRoundData();
|
||||
require(answer > 0, "chainlink btc/usd stale");
|
||||
require(updatedAt > 0, "chainlink btc/usd no timestamp");
|
||||
uint64 faceUsdE8 = uint64(uint256(answer));
|
||||
require(faceUsdE8 > 0, "zero btc usd");
|
||||
|
||||
console2.log("Chainlink BTC/USD faceUsdE8", faceUsdE8);
|
||||
|
||||
vm.startBroadcast();
|
||||
CWTokenUsdFeed feed = new CWTokenUsdFeed(registry, CWBTC, "cWBTC / USD (GRU market anchor)");
|
||||
registry.registerToken(CWBTC, 8, faceUsdE8, address(0), true, address(feed));
|
||||
vm.stopBroadcast();
|
||||
|
||||
console2.log("Registered cWBTC", CWBTC);
|
||||
console2.log(" feed", address(feed));
|
||||
console2.log("CWTokenMarketRegistry", registryAddr);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ fi
|
||||
command -v cast >/dev/null 2>&1 || { echo "cast is required" >&2; exit 1; }
|
||||
|
||||
RPC_URL_138="${RPC_URL_138:-${RPC_URL:-http://192.168.11.221:8545}}"
|
||||
DODO_PMM_INTEGRATION_ADDRESS="${CHAIN_138_DODO_PMM_INTEGRATION:-${DODO_PMM_INTEGRATION_ADDRESS:-0x5BDc62f1ae7D630c37A8B363a1d49845356Ee72d}}"
|
||||
DODO_PMM_INTEGRATION_ADDRESS="${CHAIN_138_DODO_PMM_INTEGRATION:-${DODO_PMM_INTEGRATION_ADDRESS:-0x86ADA6Ef91A3B450F89f2b751e93B1b7A3218895}}"
|
||||
PRIVATE_KEY="${PRIVATE_KEY:-${DEPLOYER_PRIVATE_KEY:-}}"
|
||||
CHAIN_GAS_PRICE="${CHAIN_GAS_PRICE:-1000000000}"
|
||||
APPROVE_GAS_LIMIT="${APPROVE_GAS_LIMIT:-200000}"
|
||||
|
||||
@@ -65,9 +65,65 @@ echo " Troy ounces: $OZ -> base units: $BASE_UNITS"
|
||||
echo " cXAUC: $DO_CXAUC cXAUT: $DO_CXAUT"
|
||||
echo ""
|
||||
|
||||
MULTISIG="${ADDR_MULTISIG:-0xb9E29cFa1f89d369671E640d0BB3aD94Cab43965}"
|
||||
GAS_LIMIT_MULTISIG="${GAS_LIMIT_MULTISIG:-600000}"
|
||||
GAS_PRICE="${GAS_PRICE_138:-1000000000}"
|
||||
|
||||
extract_tx_hash() {
|
||||
awk '/^transactionHash[[:space:]]/ {print $2; exit}'
|
||||
}
|
||||
|
||||
tx_status_ok() {
|
||||
local txhash="$1"
|
||||
[[ -n "$txhash" && "$txhash" == 0x* ]] || return 1
|
||||
local st
|
||||
st=$(cast receipt "$txhash" status --rpc-url "$RPC" 2>/dev/null | awk '{print $1}' || echo "")
|
||||
[[ "$st" == "1" || "$st" == "true" ]]
|
||||
}
|
||||
|
||||
mint_via_multisig() {
|
||||
local addr="$1" label="$2"
|
||||
local data txid exec_out txhash count
|
||||
data=$(cast calldata "mint(address,uint256)" "$TO" "$BASE_UNITS")
|
||||
echo -n " multisig mint $label... "
|
||||
if [ -n "${DRY_RUN:-}" ]; then
|
||||
echo "[dry-run]"
|
||||
return 0
|
||||
fi
|
||||
cast send "$MULTISIG" \
|
||||
"submitTransaction(address,uint256,bytes)" "$addr" 0 "$data" \
|
||||
--rpc-url "$RPC" --private-key "$PRIVATE_KEY" --legacy \
|
||||
--gas-limit "$GAS_LIMIT_MULTISIG" --gas-price "$GAS_PRICE" >/dev/null || {
|
||||
echo "FAIL (submit)"; return 1
|
||||
}
|
||||
count=$(cast call "$MULTISIG" "getTransactionCount()(uint256)" --rpc-url "$RPC" | awk '{print $1}')
|
||||
txid=$((count - 1))
|
||||
exec_out=$(cast send "$MULTISIG" "executeTransaction(uint256)" "$txid" \
|
||||
--rpc-url "$RPC" --private-key "$PRIVATE_KEY" --legacy \
|
||||
--gas-limit "$GAS_LIMIT_MULTISIG" --gas-price "$GAS_PRICE" 2>&1) || {
|
||||
echo "FAIL (execute)"; return 1
|
||||
}
|
||||
txhash=$(echo "$exec_out" | extract_tx_hash)
|
||||
if tx_status_ok "$txhash"; then
|
||||
echo "OK (tx $txhash)"
|
||||
return 0
|
||||
fi
|
||||
echo "FAIL (receipt: $txhash)"
|
||||
return 1
|
||||
}
|
||||
|
||||
run_mint() {
|
||||
local addr="$1"
|
||||
local label="$2"
|
||||
local owner lc_to lc_own lc_ms
|
||||
owner=$(cast call "$addr" "owner()(address)" --rpc-url "$RPC" 2>/dev/null || echo "")
|
||||
lc_to=$(echo "$TO" | tr '[:upper:]' '[:lower:]')
|
||||
lc_own=$(echo "$owner" | tr '[:upper:]' '[:lower:]')
|
||||
lc_ms=$(echo "$MULTISIG" | tr '[:upper:]' '[:lower:]')
|
||||
if [[ "$lc_own" == "$lc_ms" ]]; then
|
||||
mint_via_multisig "$addr" "$label" || return 1
|
||||
return 0
|
||||
fi
|
||||
if [ -n "${DRY_RUN:-}" ]; then
|
||||
echo "[dry-run] cast send $label $addr mint $TO $BASE_UNITS"
|
||||
return 0
|
||||
|
||||
@@ -90,13 +90,15 @@ fi
|
||||
# Ensure PRIVATE_KEY is exported
|
||||
if [ -n "$PRIVATE_KEY" ]; then
|
||||
export PRIVATE_KEY
|
||||
if [ "$PROFILE" != "mainnet-link-138" ] || [ -z "${CCIP_MAINNET_LINK138_RELAYER_PRIVATE_KEY:-}" ]; then
|
||||
if { [ "$PROFILE" = "mainnet-link-138" ] || [ -z "$PROFILE" ]; } && [ -n "${CCIP_MAINNET_LINK138_RELAYER_PRIVATE_KEY:-}" ]; then
|
||||
: # dedicated relayer set below
|
||||
else
|
||||
export RELAYER_PRIVATE_KEY="$PRIVATE_KEY"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Dedicated relayer for mainnet-link-138 (wins over deployer PRIVATE_KEY).
|
||||
if [ "$PROFILE" = "mainnet-link-138" ] && [ -n "${CCIP_MAINNET_LINK138_RELAYER_PRIVATE_KEY:-}" ]; then
|
||||
# Dedicated relayer for mainnet-link-138 and default WETH lane (wins over deployer PRIVATE_KEY).
|
||||
if { [ "$PROFILE" = "mainnet-link-138" ] || [ -z "$PROFILE" ]; } && [ -n "${CCIP_MAINNET_LINK138_RELAYER_PRIVATE_KEY:-}" ]; then
|
||||
export RELAYER_PRIVATE_KEY="$CCIP_MAINNET_LINK138_RELAYER_PRIVATE_KEY"
|
||||
if [ -n "${CCIP_MAINNET_LINK138_RELAYER_ADDRESS:-}" ]; then
|
||||
export RELAYER_ADDRESS="$CCIP_MAINNET_LINK138_RELAYER_ADDRESS"
|
||||
|
||||
77
test/mainnet-market/CWTokenMarketRegistry.t.sol
Normal file
77
test/mainnet-market/CWTokenMarketRegistry.t.sol
Normal file
@@ -0,0 +1,77 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
import {CWTokenMarketRegistry} from "../../contracts/mainnet-market/CWTokenMarketRegistry.sol";
|
||||
import {CWTokenUsdFeed} from "../../contracts/mainnet-market/CWTokenUsdFeed.sol";
|
||||
|
||||
contract CWTokenMarketRegistryTest is Test {
|
||||
CWTokenMarketRegistry registry;
|
||||
MockErc20 token;
|
||||
CWTokenUsdFeed feed;
|
||||
|
||||
function setUp() public {
|
||||
registry = new CWTokenMarketRegistry(address(this));
|
||||
token = new MockErc20(6, 15_452_106_931_334_998_000_000);
|
||||
feed = new CWTokenUsdFeed(registry, address(token), "test cWUSD / USD");
|
||||
registry.registerToken(address(token), 6, 100_000_000, address(0), true, address(feed));
|
||||
}
|
||||
|
||||
function test_latestRoundData_faceBootstrap() public view {
|
||||
(, int256 answer,,,) = feed.latestRoundData();
|
||||
assertEq(answer, 100_000_000);
|
||||
}
|
||||
|
||||
function test_onchainMarketCap() public view {
|
||||
uint256 cap = registry.onchainMarketCapUsdE8(address(token));
|
||||
assertEq(cap, 15_452_106_931_334_998_000_000 * 100_000_000 / 1e6);
|
||||
}
|
||||
|
||||
function test_publishPrice_updatesFeed() public {
|
||||
registry.publishPrice(address(token), 99_500_000, "univ3_twap");
|
||||
(, int256 answer,,,) = feed.latestRoundData();
|
||||
assertEq(answer, 99_500_000);
|
||||
}
|
||||
|
||||
function test_publishPrice_revertsOutsidePegBand() public {
|
||||
vm.expectRevert(bytes("peg band"));
|
||||
registry.publishPrice(address(token), 50_000_000, "bad");
|
||||
}
|
||||
|
||||
function test_registerToken_revertsFeedMismatch() public {
|
||||
CWTokenUsdFeed badFeed = new CWTokenUsdFeed(registry, address(0xBEEF), "bad");
|
||||
vm.expectRevert(bytes("feed mismatch"));
|
||||
registry.registerToken(address(0xCAFE), 6, 100_000_000, address(0), true, address(badFeed));
|
||||
}
|
||||
|
||||
function test_recordX402Settlement() public {
|
||||
registry.recordX402Settlement(
|
||||
address(token),
|
||||
address(0x4A66),
|
||||
address(0x6435),
|
||||
10_000,
|
||||
10_000,
|
||||
8453,
|
||||
bytes32(uint256(1)),
|
||||
keccak256("resource")
|
||||
);
|
||||
(,,,,,,,, uint256 settlementCount) = registry.tokens(address(token));
|
||||
assertEq(settlementCount, 1);
|
||||
}
|
||||
|
||||
function test_pauseBlocksPublish() public {
|
||||
registry.pause();
|
||||
vm.expectRevert(bytes("paused"));
|
||||
registry.publishPrice(address(token), 100_000_000, "x");
|
||||
}
|
||||
}
|
||||
|
||||
contract MockErc20 {
|
||||
uint8 public decimals;
|
||||
uint256 public totalSupply;
|
||||
|
||||
constructor(uint8 d, uint256 supply) {
|
||||
decimals = d;
|
||||
totalSupply = supply;
|
||||
}
|
||||
}
|
||||
62
test/mainnet-market/CWTokenMarketRegistryFork.t.sol
Normal file
62
test/mainnet-market/CWTokenMarketRegistryFork.t.sol
Normal file
@@ -0,0 +1,62 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
import {CWTokenMarketRegistry} from "../../contracts/mainnet-market/CWTokenMarketRegistry.sol";
|
||||
import {CWTokenUsdFeed} from "../../contracts/mainnet-market/CWTokenUsdFeed.sol";
|
||||
|
||||
contract CWTokenMarketRegistryForkTest is Test {
|
||||
address constant CWUSDC = 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a;
|
||||
address constant CWUSDT = 0xaF5017d0163ecb99D9B5D94e3b4D7b09Af44D8AE;
|
||||
address constant POOL_CWUSDC = 0x1Cf2e685682C7F7beF508F0Af15Dfb5CDda01ee3;
|
||||
address constant POOL_CWUSDT = 0x6eF5A392BcbE42F3D0a855595bEa60CFf9451784;
|
||||
address constant CWBTC = 0x2BBe3c809386FA5349EB2da31E0848ee9f54EE59;
|
||||
address constant CHAINLINK_BTC_USD = 0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c;
|
||||
|
||||
function test_fork_registerMainnetTokensAndMarketCap() public {
|
||||
string memory rpc = vm.envOr(string("ETHEREUM_MAINNET_RPC"), string("https://ethereum.publicnode.com"));
|
||||
vm.createSelectFork(rpc);
|
||||
|
||||
CWTokenMarketRegistry registry = new CWTokenMarketRegistry(address(this));
|
||||
|
||||
CWTokenUsdFeed feedUsdc = new CWTokenUsdFeed(registry, CWUSDC, "cWUSDC / USD");
|
||||
registry.registerToken(CWUSDC, 6, 100_000_000, POOL_CWUSDC, true, address(feedUsdc));
|
||||
|
||||
CWTokenUsdFeed feedUsdt = new CWTokenUsdFeed(registry, CWUSDT, "cWUSDT / USD");
|
||||
registry.registerToken(CWUSDT, 6, 100_000_000, POOL_CWUSDT, true, address(feedUsdt));
|
||||
|
||||
(, int256 usdcPx,,,) = feedUsdc.latestRoundData();
|
||||
assertEq(usdcPx, 100_000_000);
|
||||
|
||||
uint256 mcap = registry.onchainMarketCapUsdE8(CWUSDC);
|
||||
assertGt(mcap, 0);
|
||||
|
||||
assertEq(registry.tokenFeed(CWUSDC), address(feedUsdc));
|
||||
assertEq(registry.tokenCount(), 2);
|
||||
}
|
||||
|
||||
function test_fork_registerCWBTWithChainlinkBtcUsd() public {
|
||||
string memory rpc = vm.envOr(string("ETHEREUM_MAINNET_RPC"), string("https://ethereum.publicnode.com"));
|
||||
vm.createSelectFork(rpc);
|
||||
|
||||
(, int256 answer,,,) = AggregatorV3(CHAINLINK_BTC_USD).latestRoundData();
|
||||
assertGt(answer, 0);
|
||||
|
||||
CWTokenMarketRegistry registry = new CWTokenMarketRegistry(address(this));
|
||||
uint64 faceUsdE8 = uint64(uint256(answer));
|
||||
|
||||
CWTokenUsdFeed feed = new CWTokenUsdFeed(registry, CWBTC, "cWBTC / USD");
|
||||
registry.registerToken(CWBTC, 8, faceUsdE8, address(0), true, address(feed));
|
||||
|
||||
(, int256 px,,,) = feed.latestRoundData();
|
||||
assertEq(px, answer);
|
||||
assertGt(registry.onchainMarketCapUsdE8(CWBTC), 0);
|
||||
}
|
||||
}
|
||||
|
||||
interface AggregatorV3 {
|
||||
function latestRoundData()
|
||||
external
|
||||
view
|
||||
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
|
||||
}
|
||||
Reference in New Issue
Block a user