feat: restore operator WIP — PMM JSON sync entrypoint, dotenv RPC trim + secrets, pool env alignment
- Resolve stash: merge load_deployment_env path with secure-secrets and CR/LF RPC strip - create-pmm-full-mesh-chain138.sh delegates to sync-chain138-pmm-pools-from-json.sh - env.additions.example: canonical PMM pool defaults (cUSDT/USDT per crosscheck) - Include Chain138 scripts, official mirror deploy scaffolding, and prior staged changes Made-with: Cursor
This commit is contained in:
@@ -12,6 +12,7 @@ import "./interfaces/IAggregationRouter.sol";
|
||||
import "./interfaces/IDodoexRouter.sol";
|
||||
import "./interfaces/IBalancerVault.sol";
|
||||
import "./interfaces/IWETH.sol";
|
||||
import "../../liquidity/interfaces/ILiquidityProvider.sol";
|
||||
|
||||
/**
|
||||
* @title EnhancedSwapRouter
|
||||
@@ -67,6 +68,7 @@ contract EnhancedSwapRouter is AccessControl, ReentrancyGuard {
|
||||
|
||||
// Dodoex PMM pool addresses (tokenIn => tokenOut => PMM pool address)
|
||||
mapping(address => mapping(address => address)) public dodoPoolAddresses;
|
||||
address public dodoLiquidityProvider;
|
||||
|
||||
/// @dev Uniswap V3 Quoter for on-chain quotes; set via setUniswapQuoter when deployed on 138/651940
|
||||
address public uniswapQuoter;
|
||||
@@ -91,6 +93,7 @@ contract EnhancedSwapRouter is AccessControl, ReentrancyGuard {
|
||||
error ProviderDisabled();
|
||||
error InsufficientOutput();
|
||||
error InvalidRoutingConfig();
|
||||
error DodoRouteNotConfigured();
|
||||
|
||||
/**
|
||||
* @notice Constructor
|
||||
@@ -324,6 +327,15 @@ contract EnhancedSwapRouter is AccessControl, ReentrancyGuard {
|
||||
dodoPoolAddresses[tokenIn][tokenOut] = poolAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Set DODO liquidity provider implementation for environments that execute
|
||||
* swaps via a local provider/integration instead of an external Dodoex router.
|
||||
* @param provider Address of ILiquidityProvider-compatible contract
|
||||
*/
|
||||
function setDodoLiquidityProvider(address provider) external onlyRole(ROUTING_MANAGER_ROLE) {
|
||||
dodoLiquidityProvider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Set Uniswap V3 Quoter address for on-chain quotes
|
||||
* @param _quoter Quoter contract address (address(0) to use 0.5% slippage estimate)
|
||||
@@ -349,9 +361,23 @@ contract EnhancedSwapRouter is AccessControl, ReentrancyGuard {
|
||||
if (amountIn == 0) revert ZeroAmount();
|
||||
if (tokenIn == address(0) || tokenOut == address(0)) revert ZeroAddress();
|
||||
address pool = dodoPoolAddresses[tokenIn][tokenOut];
|
||||
require(pool != address(0), "EnhancedSwapRouter: Dodoex pool not configured");
|
||||
if (!_hasDodoRoute(tokenIn, tokenOut, pool)) revert DodoRouteNotConfigured();
|
||||
|
||||
IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
|
||||
|
||||
if (dodoLiquidityProvider != address(0)) {
|
||||
IERC20(tokenIn).approve(dodoLiquidityProvider, amountIn);
|
||||
amountOut = ILiquidityProvider(dodoLiquidityProvider).executeSwap(
|
||||
tokenIn,
|
||||
tokenOut,
|
||||
amountIn,
|
||||
amountOutMin
|
||||
);
|
||||
require(amountOut >= amountOutMin, "EnhancedSwapRouter: insufficient output");
|
||||
IERC20(tokenOut).safeTransfer(msg.sender, amountOut);
|
||||
return amountOut;
|
||||
}
|
||||
|
||||
IERC20(tokenIn).approve(dodoexRouter, amountIn);
|
||||
|
||||
address[] memory dodoPairs = new address[](1);
|
||||
@@ -476,7 +502,18 @@ contract EnhancedSwapRouter is AccessControl, ReentrancyGuard {
|
||||
uint256 amountOutMin
|
||||
) internal returns (uint256) {
|
||||
address pool = dodoPoolAddresses[weth][stablecoinToken];
|
||||
require(pool != address(0), "EnhancedSwapRouter: Dodoex pool not configured");
|
||||
if (!_hasDodoRoute(weth, stablecoinToken, pool)) revert DodoRouteNotConfigured();
|
||||
|
||||
if (dodoLiquidityProvider != address(0)) {
|
||||
IERC20 wethTokenViaProvider = IERC20(weth);
|
||||
wethTokenViaProvider.approve(dodoLiquidityProvider, amountIn);
|
||||
return ILiquidityProvider(dodoLiquidityProvider).executeSwap(
|
||||
weth,
|
||||
stablecoinToken,
|
||||
amountIn,
|
||||
amountOutMin
|
||||
);
|
||||
}
|
||||
|
||||
IERC20 wethToken = IERC20(weth);
|
||||
wethToken.approve(dodoexRouter, amountIn);
|
||||
@@ -605,9 +642,30 @@ contract EnhancedSwapRouter is AccessControl, ReentrancyGuard {
|
||||
address stablecoinToken,
|
||||
uint256 amountIn
|
||||
) external view returns (uint256) {
|
||||
if (dodoLiquidityProvider != address(0)) {
|
||||
if (!ILiquidityProvider(dodoLiquidityProvider).supportsTokenPair(weth, stablecoinToken)) {
|
||||
return 0;
|
||||
}
|
||||
(uint256 amountOut,) = ILiquidityProvider(dodoLiquidityProvider).getQuote(weth, stablecoinToken, amountIn);
|
||||
return amountOut;
|
||||
}
|
||||
if (dodoPoolAddresses[weth][stablecoinToken] == address(0)) {
|
||||
return 0;
|
||||
}
|
||||
return IDodoexRouter(dodoexRouter).getDodoSwapQuote(weth, stablecoinToken, amountIn);
|
||||
}
|
||||
|
||||
function _hasDodoRoute(
|
||||
address tokenIn,
|
||||
address tokenOut,
|
||||
address configuredPool
|
||||
) internal view returns (bool) {
|
||||
if (dodoLiquidityProvider != address(0)) {
|
||||
return ILiquidityProvider(dodoLiquidityProvider).supportsTokenPair(tokenIn, tokenOut);
|
||||
}
|
||||
return configuredPool != address(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Get Balancer quote (view)
|
||||
* When poolId configured, would query Balancer. Otherwise estimate for stablecoins.
|
||||
@@ -686,4 +744,3 @@ contract EnhancedSwapRouter is AccessControl, ReentrancyGuard {
|
||||
// Allow contract to receive ETH
|
||||
receive() external payable {}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,6 +110,12 @@ contract DODOPMMIntegration is AccessControl, ReentrancyGuard {
|
||||
);
|
||||
event PoolRemoved(address indexed pool);
|
||||
event ReserveSystemSet(address indexed reserveSystem);
|
||||
event PoolImported(
|
||||
address indexed pool,
|
||||
address indexed baseToken,
|
||||
address indexed quoteToken,
|
||||
address importer
|
||||
);
|
||||
|
||||
constructor(
|
||||
address admin,
|
||||
@@ -164,22 +170,7 @@ contract DODOPMMIntegration is AccessControl, ReentrancyGuard {
|
||||
isOpenTWAP // Enable TWAP
|
||||
);
|
||||
|
||||
// Register pool
|
||||
pools[compliantUSDT][officialUSDT] = pool;
|
||||
pools[officialUSDT][compliantUSDT] = pool;
|
||||
isRegisteredPool[pool] = true;
|
||||
allPools.push(pool);
|
||||
|
||||
poolConfigs[pool] = PoolConfig({
|
||||
pool: pool,
|
||||
baseToken: compliantUSDT,
|
||||
quoteToken: officialUSDT,
|
||||
lpFeeRate: lpFeeRate,
|
||||
i: initialPrice,
|
||||
k: k,
|
||||
isOpenTWAP: isOpenTWAP,
|
||||
createdAt: block.timestamp
|
||||
});
|
||||
_recordPool(pool, compliantUSDT, officialUSDT, lpFeeRate, initialPrice, k, isOpenTWAP);
|
||||
|
||||
emit PoolCreated(pool, compliantUSDT, officialUSDT, msg.sender);
|
||||
}
|
||||
@@ -208,21 +199,7 @@ contract DODOPMMIntegration is AccessControl, ReentrancyGuard {
|
||||
isOpenTWAP
|
||||
);
|
||||
|
||||
pools[compliantUSDC][officialUSDC] = pool;
|
||||
pools[officialUSDC][compliantUSDC] = pool;
|
||||
isRegisteredPool[pool] = true;
|
||||
allPools.push(pool);
|
||||
|
||||
poolConfigs[pool] = PoolConfig({
|
||||
pool: pool,
|
||||
baseToken: compliantUSDC,
|
||||
quoteToken: officialUSDC,
|
||||
lpFeeRate: lpFeeRate,
|
||||
i: initialPrice,
|
||||
k: k,
|
||||
isOpenTWAP: isOpenTWAP,
|
||||
createdAt: block.timestamp
|
||||
});
|
||||
_recordPool(pool, compliantUSDC, officialUSDC, lpFeeRate, initialPrice, k, isOpenTWAP);
|
||||
|
||||
emit PoolCreated(pool, compliantUSDC, officialUSDC, msg.sender);
|
||||
}
|
||||
@@ -251,21 +228,7 @@ contract DODOPMMIntegration is AccessControl, ReentrancyGuard {
|
||||
isOpenTWAP
|
||||
);
|
||||
|
||||
pools[compliantUSDT][compliantUSDC] = pool;
|
||||
pools[compliantUSDC][compliantUSDT] = pool;
|
||||
isRegisteredPool[pool] = true;
|
||||
allPools.push(pool);
|
||||
|
||||
poolConfigs[pool] = PoolConfig({
|
||||
pool: pool,
|
||||
baseToken: compliantUSDT,
|
||||
quoteToken: compliantUSDC,
|
||||
lpFeeRate: lpFeeRate,
|
||||
i: initialPrice,
|
||||
k: k,
|
||||
isOpenTWAP: isOpenTWAP,
|
||||
createdAt: block.timestamp
|
||||
});
|
||||
_recordPool(pool, compliantUSDT, compliantUSDC, lpFeeRate, initialPrice, k, isOpenTWAP);
|
||||
|
||||
emit PoolCreated(pool, compliantUSDT, compliantUSDC, msg.sender);
|
||||
}
|
||||
@@ -301,25 +264,46 @@ contract DODOPMMIntegration is AccessControl, ReentrancyGuard {
|
||||
isOpenTWAP
|
||||
);
|
||||
|
||||
pools[baseToken][quoteToken] = pool;
|
||||
pools[quoteToken][baseToken] = pool;
|
||||
isRegisteredPool[pool] = true;
|
||||
allPools.push(pool);
|
||||
|
||||
poolConfigs[pool] = PoolConfig({
|
||||
pool: pool,
|
||||
baseToken: baseToken,
|
||||
quoteToken: quoteToken,
|
||||
lpFeeRate: lpFeeRate,
|
||||
i: initialPrice,
|
||||
k: k,
|
||||
isOpenTWAP: isOpenTWAP,
|
||||
createdAt: block.timestamp
|
||||
});
|
||||
_recordPool(pool, baseToken, quoteToken, lpFeeRate, initialPrice, k, isOpenTWAP);
|
||||
|
||||
emit PoolCreated(pool, baseToken, quoteToken, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Import an already-deployed DODO PMM pool into integration state.
|
||||
* @dev Use this when the pool exists at the DODO/provider layer but was not
|
||||
* recorded in the integration mappings or allPools inventory.
|
||||
*/
|
||||
function importExistingPool(
|
||||
address pool,
|
||||
address baseToken,
|
||||
address quoteToken,
|
||||
uint256 lpFeeRate,
|
||||
uint256 initialPrice,
|
||||
uint256 k,
|
||||
bool isOpenTWAP
|
||||
) external onlyRole(POOL_MANAGER_ROLE) {
|
||||
require(pool != address(0), "DODOPMMIntegration: zero pool");
|
||||
require(baseToken != address(0), "DODOPMMIntegration: zero base");
|
||||
require(quoteToken != address(0), "DODOPMMIntegration: zero quote");
|
||||
require(baseToken != quoteToken, "DODOPMMIntegration: same token");
|
||||
require(pools[baseToken][quoteToken] == address(0), "DODOPMMIntegration: pair already recorded");
|
||||
require(!isRegisteredPool[pool], "DODOPMMIntegration: pool already registered");
|
||||
|
||||
address actualBase = IDODOPMMPool(pool)._BASE_TOKEN_();
|
||||
address actualQuote = IDODOPMMPool(pool)._QUOTE_TOKEN_();
|
||||
bool matchesForward = actualBase == baseToken && actualQuote == quoteToken;
|
||||
bool matchesReverse = actualBase == quoteToken && actualQuote == baseToken;
|
||||
require(matchesForward || matchesReverse, "DODOPMMIntegration: pool token mismatch");
|
||||
|
||||
if (matchesReverse) {
|
||||
(baseToken, quoteToken) = (quoteToken, baseToken);
|
||||
}
|
||||
|
||||
_recordPool(pool, baseToken, quoteToken, lpFeeRate, initialPrice, k, isOpenTWAP);
|
||||
emit PoolImported(pool, baseToken, quoteToken, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Add liquidity to a DODO PMM pool
|
||||
* @param pool Pool address
|
||||
@@ -595,5 +579,30 @@ contract DODOPMMIntegration is AccessControl, ReentrancyGuard {
|
||||
|
||||
emit PoolRemoved(pool);
|
||||
}
|
||||
}
|
||||
|
||||
function _recordPool(
|
||||
address pool,
|
||||
address baseToken,
|
||||
address quoteToken,
|
||||
uint256 lpFeeRate,
|
||||
uint256 initialPrice,
|
||||
uint256 k,
|
||||
bool isOpenTWAP
|
||||
) internal {
|
||||
pools[baseToken][quoteToken] = pool;
|
||||
pools[quoteToken][baseToken] = pool;
|
||||
isRegisteredPool[pool] = true;
|
||||
allPools.push(pool);
|
||||
|
||||
poolConfigs[pool] = PoolConfig({
|
||||
pool: pool,
|
||||
baseToken: baseToken,
|
||||
quoteToken: quoteToken,
|
||||
lpFeeRate: lpFeeRate,
|
||||
i: initialPrice,
|
||||
k: k,
|
||||
isOpenTWAP: isOpenTWAP,
|
||||
createdAt: block.timestamp
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ contract PriceFeedKeeper is AccessControl, ReentrancyGuard {
|
||||
address[] public trackedAssets;
|
||||
mapping(address => bool) public isTracked;
|
||||
|
||||
// Update configuration
|
||||
uint256 public updateInterval = 30; // seconds
|
||||
// Update configuration (default 6s aligns with PMM mesh automation; admin may set via setUpdateInterval)
|
||||
uint256 public updateInterval = 6; // seconds
|
||||
mapping(address => uint256) public lastUpdateTime;
|
||||
|
||||
// Keeper configuration
|
||||
|
||||
@@ -11,6 +11,12 @@ import "../compliance/LegallyCompliantBase.sol";
|
||||
* @notice Generic ISO-4217 compliant fiat/commodity token (ERC-20, DEX-ready)
|
||||
* @dev Use for cEURC, cGBPC, cAUDC, cJPYC, cCHFC, cCADC, cXAUC, cXAUT, etc.
|
||||
* Full ERC-20 for DEX liquidity pools; inherits LegallyCompliantBase.
|
||||
*
|
||||
* XAU (gold) invariant — enforced by policy and integrators, not by ERC-20 math:
|
||||
* When `currencyCode()` is `"XAU"` (e.g. cXAUC, cXAUT), **one full token** means
|
||||
* **exactly one troy ounce** of fine gold: balances and `mint`/`transfer` amounts use
|
||||
* `10 ** decimals()` base units per troy ounce (with 6 decimals, `1_000_000` base = 1 oz).
|
||||
* Fiat codes (USD, EUR, …) use one full token as one unit of that currency at `decimals`.
|
||||
*/
|
||||
contract CompliantFiatToken is ERC20, Pausable, Ownable, LegallyCompliantBase {
|
||||
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
|
||||
|
||||
40
contracts/tokens/OfficialStableMirrorToken.sol
Normal file
40
contracts/tokens/OfficialStableMirrorToken.sol
Normal file
@@ -0,0 +1,40 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
|
||||
/**
|
||||
* @title OfficialStableMirrorToken
|
||||
* @notice Lightweight ERC-20 used as the local Chain 138 "official" stable mirror.
|
||||
* @dev This token exists only to provide a live ERC-20 quote-side asset for local PMM pools.
|
||||
* It is intentionally separate from the compliant token contracts.
|
||||
*/
|
||||
contract OfficialStableMirrorToken is ERC20, Ownable {
|
||||
uint8 private immutable _decimalsStorage;
|
||||
|
||||
constructor(
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
uint8 decimals_,
|
||||
address initialOwner,
|
||||
uint256 initialSupply
|
||||
) ERC20(name_, symbol_) Ownable(initialOwner) {
|
||||
_decimalsStorage = decimals_;
|
||||
if (initialSupply > 0) {
|
||||
_mint(msg.sender, initialSupply);
|
||||
}
|
||||
}
|
||||
|
||||
function decimals() public view override returns (uint8) {
|
||||
return _decimalsStorage;
|
||||
}
|
||||
|
||||
function mint(address to, uint256 amount) external onlyOwner {
|
||||
_mint(to, amount);
|
||||
}
|
||||
|
||||
function burn(address from, uint256 amount) external onlyOwner {
|
||||
_burn(from, amount);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user