- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault. - Token-aggregation service routes, planner, chain config, relay env templates. - Config snapshots and multi-chain deployment markdown updates. - gitignore services/btc-intake/dist/ (tsc output); do not track dist. Run forge build && forge test before deploy (large solc graph). Made-with: Cursor
251 lines
9.4 KiB
Solidity
251 lines
9.4 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import "@openzeppelin/contracts/access/AccessControl.sol";
|
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
import "../interfaces/ILiquidityProvider.sol";
|
|
import "../../dex/DODOPMMIntegration.sol";
|
|
|
|
/**
|
|
* @title DODOPMMProvider
|
|
* @notice Wrapper for DODO PMM with extended functionality
|
|
* @dev Implements ILiquidityProvider for multi-provider liquidity system
|
|
*/
|
|
contract DODOPMMProvider is ILiquidityProvider, AccessControl {
|
|
using SafeERC20 for IERC20;
|
|
|
|
bytes32 public constant POOL_MANAGER_ROLE = keccak256("POOL_MANAGER_ROLE");
|
|
|
|
DODOPMMIntegration public dodoIntegration;
|
|
|
|
// Pool tracking
|
|
mapping(address => mapping(address => address)) public pools; // tokenIn => tokenOut => pool
|
|
mapping(address => bool) public isKnownPool;
|
|
|
|
event PoolCreated(address indexed tokenIn, address indexed tokenOut, address pool);
|
|
event PoolOptimized(address indexed pool, uint256 newK, uint256 newI);
|
|
|
|
constructor(address _dodoIntegration, address admin) {
|
|
require(_dodoIntegration != address(0), "Zero address");
|
|
|
|
dodoIntegration = DODOPMMIntegration(_dodoIntegration);
|
|
|
|
_grantRole(DEFAULT_ADMIN_ROLE, admin);
|
|
_grantRole(POOL_MANAGER_ROLE, admin);
|
|
}
|
|
|
|
/**
|
|
* @notice Get quote from DODO PMM
|
|
*/
|
|
function getQuote(
|
|
address tokenIn,
|
|
address tokenOut,
|
|
uint256 amountIn
|
|
) external view override returns (uint256 amountOut, uint256 slippageBps) {
|
|
address pool = pools[tokenIn][tokenOut];
|
|
|
|
if (pool == address(0)) {
|
|
return (0, 10000); // No pool, 100% slippage
|
|
}
|
|
|
|
try dodoIntegration.getPoolConfig(pool) returns (DODOPMMIntegration.PoolConfig memory config) {
|
|
if (tokenIn == config.baseToken && tokenOut == config.quoteToken) {
|
|
try IDODOPMMPool(pool).querySellBase(address(this), amountIn) returns (uint256 quoteAmount, uint256) {
|
|
return (quoteAmount, 30);
|
|
} catch {
|
|
return _fallbackReserveQuote(pool, config, true, amountIn);
|
|
}
|
|
}
|
|
|
|
if (tokenIn == config.quoteToken && tokenOut == config.baseToken) {
|
|
try IDODOPMMPool(pool).querySellQuote(address(this), amountIn) returns (uint256 baseAmount, uint256) {
|
|
return (baseAmount, 30);
|
|
} catch {
|
|
return _fallbackReserveQuote(pool, config, false, amountIn);
|
|
}
|
|
}
|
|
|
|
return (0, 10000);
|
|
} catch {
|
|
return (0, 10000);
|
|
}
|
|
}
|
|
|
|
function _fallbackReserveQuote(
|
|
address pool,
|
|
DODOPMMIntegration.PoolConfig memory config,
|
|
bool sellBase,
|
|
uint256 amountIn
|
|
) internal view returns (uint256 amountOut, uint256 slippageBps) {
|
|
try IDODOPMMPool(pool).getVaultReserve() returns (uint256 baseReserve, uint256 quoteReserve) {
|
|
if (baseReserve == 0 || quoteReserve == 0 || amountIn == 0) {
|
|
return (0, 10000);
|
|
}
|
|
|
|
uint256 netAmountIn = amountIn;
|
|
if (config.lpFeeRate < 10_000) {
|
|
netAmountIn = (amountIn * (10_000 - config.lpFeeRate)) / 10_000;
|
|
}
|
|
|
|
if (netAmountIn == 0) {
|
|
return (0, 10000);
|
|
}
|
|
|
|
if (sellBase) {
|
|
amountOut = (netAmountIn * quoteReserve) / (baseReserve + netAmountIn);
|
|
} else {
|
|
amountOut = (netAmountIn * baseReserve) / (quoteReserve + netAmountIn);
|
|
}
|
|
|
|
// Treat fallback quotes as conservative / read-only approximations.
|
|
slippageBps = 100;
|
|
return (amountOut, slippageBps);
|
|
} catch {
|
|
return (0, 10000);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @notice Execute swap via DODO PMM
|
|
*/
|
|
function executeSwap(
|
|
address tokenIn,
|
|
address tokenOut,
|
|
uint256 amountIn,
|
|
uint256 minAmountOut
|
|
) external override returns (uint256 amountOut) {
|
|
// Get pool for token pair
|
|
address pool = pools[tokenIn][tokenOut];
|
|
require(pool != address(0), "Pool not found");
|
|
|
|
// Transfer tokens from caller
|
|
IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
|
|
|
|
// Route to appropriate swap method: use dedicated methods for the 6 legacy pairs,
|
|
// otherwise use generic swapExactIn for full-mesh routing (any registered pool).
|
|
if (tokenIn == dodoIntegration.compliantUSDT() && tokenOut == dodoIntegration.officialUSDT()) {
|
|
IERC20(tokenIn).approve(address(dodoIntegration), amountIn);
|
|
amountOut = dodoIntegration.swapCUSDTForUSDT(pool, amountIn, minAmountOut);
|
|
} else if (tokenIn == dodoIntegration.officialUSDT() && tokenOut == dodoIntegration.compliantUSDT()) {
|
|
IERC20(tokenIn).approve(address(dodoIntegration), amountIn);
|
|
amountOut = dodoIntegration.swapUSDTForCUSDT(pool, amountIn, minAmountOut);
|
|
} else if (tokenIn == dodoIntegration.compliantUSDC() && tokenOut == dodoIntegration.officialUSDC()) {
|
|
IERC20(tokenIn).approve(address(dodoIntegration), amountIn);
|
|
amountOut = dodoIntegration.swapCUSDCForUSDC(pool, amountIn, minAmountOut);
|
|
} else if (tokenIn == dodoIntegration.officialUSDC() && tokenOut == dodoIntegration.compliantUSDC()) {
|
|
IERC20(tokenIn).approve(address(dodoIntegration), amountIn);
|
|
amountOut = dodoIntegration.swapUSDCForCUSDC(pool, amountIn, minAmountOut);
|
|
} else if (tokenIn == dodoIntegration.compliantUSDT() && tokenOut == dodoIntegration.compliantUSDC()) {
|
|
IERC20(tokenIn).approve(address(dodoIntegration), amountIn);
|
|
amountOut = dodoIntegration.swapCUSDTForUSDC(pool, amountIn, minAmountOut);
|
|
} else if (tokenIn == dodoIntegration.compliantUSDC() && tokenOut == dodoIntegration.compliantUSDT()) {
|
|
IERC20(tokenIn).approve(address(dodoIntegration), amountIn);
|
|
amountOut = dodoIntegration.swapUSDCForCUSDT(pool, amountIn, minAmountOut);
|
|
} else {
|
|
// Full mesh: any registered pool (c* vs c*, c* vs official, etc.)
|
|
IERC20(tokenIn).approve(address(dodoIntegration), amountIn);
|
|
amountOut = dodoIntegration.swapExactIn(pool, tokenIn, amountIn, minAmountOut);
|
|
}
|
|
|
|
// Transfer output tokens to caller
|
|
IERC20(tokenOut).safeTransfer(msg.sender, amountOut);
|
|
|
|
return amountOut;
|
|
}
|
|
|
|
/**
|
|
* @notice Check if provider supports token pair
|
|
*/
|
|
function supportsTokenPair(
|
|
address tokenIn,
|
|
address tokenOut
|
|
) external view override returns (bool) {
|
|
return pools[tokenIn][tokenOut] != address(0);
|
|
}
|
|
|
|
/**
|
|
* @notice Get provider name
|
|
*/
|
|
function providerName() external pure override returns (string memory) {
|
|
return "DODO PMM";
|
|
}
|
|
|
|
/**
|
|
* @notice Estimate gas for swap
|
|
*/
|
|
function estimateGas(
|
|
address,
|
|
address,
|
|
uint256
|
|
) external pure override returns (uint256) {
|
|
return 150000; // Typical gas for DODO swap
|
|
}
|
|
|
|
/**
|
|
* @notice Ensure pool exists for token pair
|
|
*/
|
|
function ensurePoolExists(address tokenIn, address tokenOut) external onlyRole(POOL_MANAGER_ROLE) {
|
|
if (pools[tokenIn][tokenOut] == address(0)) {
|
|
_createOptimalPool(tokenIn, tokenOut);
|
|
}
|
|
}
|
|
|
|
/// @dev Default params for stablecoin pairs: 0.03% fee, 1:1 price, k=0.5e18
|
|
uint256 private constant DEFAULT_LP_FEE = 3;
|
|
uint256 private constant DEFAULT_I = 1e18;
|
|
uint256 private constant DEFAULT_K = 0.5e18;
|
|
|
|
/**
|
|
* @notice Create optimal DODO pool via DODOPMMIntegration
|
|
* @dev Requires DODOPMMIntegration to grant POOL_MANAGER_ROLE to this contract
|
|
*/
|
|
function _createOptimalPool(address tokenIn, address tokenOut) internal {
|
|
address pool = dodoIntegration.createPool(
|
|
tokenIn,
|
|
tokenOut,
|
|
DEFAULT_LP_FEE,
|
|
DEFAULT_I,
|
|
DEFAULT_K,
|
|
false // isOpenTWAP - disable for stablecoins
|
|
);
|
|
pools[tokenIn][tokenOut] = pool;
|
|
pools[tokenOut][tokenIn] = pool;
|
|
isKnownPool[pool] = true;
|
|
emit PoolCreated(tokenIn, tokenOut, pool);
|
|
}
|
|
|
|
/**
|
|
* @notice Register existing pool
|
|
*/
|
|
function registerPool(
|
|
address tokenIn,
|
|
address tokenOut,
|
|
address pool
|
|
) external onlyRole(POOL_MANAGER_ROLE) {
|
|
require(pool != address(0), "Zero address");
|
|
|
|
pools[tokenIn][tokenOut] = pool;
|
|
isKnownPool[pool] = true;
|
|
|
|
emit PoolCreated(tokenIn, tokenOut, pool);
|
|
}
|
|
|
|
/**
|
|
* @notice Optimize pool parameters (K, I)
|
|
* @dev DODO PMM parameters are typically oracle-driven; DVM pools may expose
|
|
* setK/setI. Emits for off-chain monitoring. If the pool supports
|
|
* parameter updates, extend this to call the pool's update functions.
|
|
*/
|
|
function optimizePoolParameters(
|
|
address pool,
|
|
uint256 newK,
|
|
uint256 newI
|
|
) external onlyRole(POOL_MANAGER_ROLE) {
|
|
require(isKnownPool[pool], "Unknown pool");
|
|
// DODO DVM/PMM: parameters set at creation; oracle-driven rebalancing.
|
|
// Emit for observability; integrate pool.setK/setI when supported.
|
|
emit PoolOptimized(pool, newK, newI);
|
|
}
|
|
}
|