- Expand token-aggregation API (report routes), canonical tokens, pools - Add flash vault contracts + tests (indexed, DODO cwUSDC, XAUT borrow) - PMM pools JSON, deploy/export scripts, metamask verified list Co-authored-by: Cursor <cursoragent@cursor.com>
243 lines
8.2 KiB
Solidity
243 lines
8.2 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
|
|
interface IEngineXUniswapV3PoolLike {
|
|
function token0() external view returns (address);
|
|
function token1() external view returns (address);
|
|
function fee() external view returns (uint24);
|
|
function liquidity() external view returns (uint128);
|
|
|
|
function slot0()
|
|
external
|
|
view
|
|
returns (
|
|
uint160 sqrtPriceX96,
|
|
int24 tick,
|
|
uint16 observationIndex,
|
|
uint16 observationCardinality,
|
|
uint16 observationCardinalityNext,
|
|
uint8 feeProtocol,
|
|
bool unlocked
|
|
);
|
|
}
|
|
|
|
/// @notice Public indexed-liquidity proof anchor for Engine X.
|
|
/// @dev This contract does not claim virtual DEX volume. It anchors Engine X proof IDs to a public UniV3 pool state.
|
|
contract DBISEngineXIndexedLiquidityVault is Ownable {
|
|
address public immutable cWUSDC;
|
|
address public immutable usdc;
|
|
IEngineXUniswapV3PoolLike public immutable pool;
|
|
uint24 public immutable fee;
|
|
|
|
uint256 public positionTokenId;
|
|
int24 public maxAbsTick;
|
|
uint128 public minLiquidity;
|
|
uint256 public minToken0Balance;
|
|
uint256 public minToken1Balance;
|
|
uint256 public maxProofSwapAmount;
|
|
bool public paused;
|
|
bool public operatorAllowlistEnabled;
|
|
|
|
mapping(address => bool) public approvedOperator;
|
|
mapping(bytes32 => bool) public usedProofIds;
|
|
|
|
struct IndexedProof {
|
|
bytes32 proofId;
|
|
bytes32 publicSwapTxHash;
|
|
bytes32 liquidityTxHash;
|
|
address outputRecipient;
|
|
uint256 exactOutputAmount;
|
|
bytes32 iso20022DocumentHash;
|
|
bytes32 auditEnvelopeHash;
|
|
bytes32 pegProofHash;
|
|
}
|
|
|
|
event RiskControlsUpdated(
|
|
int24 maxAbsTick,
|
|
uint128 minLiquidity,
|
|
uint256 minToken0Balance,
|
|
uint256 minToken1Balance,
|
|
uint256 maxProofSwapAmount
|
|
);
|
|
event PositionTokenIdUpdated(uint256 positionTokenId);
|
|
event Paused(address indexed operator);
|
|
event Unpaused(address indexed operator);
|
|
event OperatorAllowlistEnabledUpdated(bool enabled);
|
|
event OperatorApprovalUpdated(address indexed operator, bool approved);
|
|
event IndexedLiquidityProof(
|
|
bytes32 indexed proofId,
|
|
address indexed operator,
|
|
address indexed outputRecipient,
|
|
address pool,
|
|
uint24 fee,
|
|
uint256 positionTokenId,
|
|
uint160 sqrtPriceX96,
|
|
int24 tick,
|
|
uint128 liquidity,
|
|
uint256 token0Balance,
|
|
uint256 token1Balance,
|
|
uint256 exactOutputAmount,
|
|
bytes32 publicSwapTxHash,
|
|
bytes32 liquidityTxHash,
|
|
bytes32 iso20022DocumentHash,
|
|
bytes32 auditEnvelopeHash,
|
|
bytes32 pegProofHash
|
|
);
|
|
|
|
modifier whenNotPaused() {
|
|
require(!paused, "paused");
|
|
_;
|
|
}
|
|
|
|
modifier onlyApprovedOperator() {
|
|
require(!operatorAllowlistEnabled || approvedOperator[msg.sender], "operator not approved");
|
|
_;
|
|
}
|
|
|
|
constructor(
|
|
address cWUSDC_,
|
|
address usdc_,
|
|
address pool_,
|
|
address owner_,
|
|
int24 maxAbsTick_,
|
|
uint128 minLiquidity_,
|
|
uint256 maxProofSwapAmount_
|
|
) Ownable(owner_) {
|
|
require(cWUSDC_ != address(0) && usdc_ != address(0) && pool_ != address(0), "zero address");
|
|
require(owner_ != address(0), "zero owner");
|
|
cWUSDC = cWUSDC_;
|
|
usdc = usdc_;
|
|
pool = IEngineXUniswapV3PoolLike(pool_);
|
|
|
|
address token0 = pool.token0();
|
|
address token1 = pool.token1();
|
|
require((token0 == cWUSDC_ && token1 == usdc_) || (token0 == usdc_ && token1 == cWUSDC_), "pool token mismatch");
|
|
|
|
fee = pool.fee();
|
|
_setRiskControls(maxAbsTick_, minLiquidity_, 0, 0, maxProofSwapAmount_);
|
|
}
|
|
|
|
function pause() external onlyOwner {
|
|
paused = true;
|
|
emit Paused(msg.sender);
|
|
}
|
|
|
|
function unpause() external onlyOwner {
|
|
paused = false;
|
|
emit Unpaused(msg.sender);
|
|
}
|
|
|
|
function setOperatorAllowlistEnabled(bool enabled) external onlyOwner {
|
|
operatorAllowlistEnabled = enabled;
|
|
emit OperatorAllowlistEnabledUpdated(enabled);
|
|
}
|
|
|
|
function setOperatorApproved(address operator, bool approved) external onlyOwner {
|
|
require(operator != address(0), "zero operator");
|
|
approvedOperator[operator] = approved;
|
|
emit OperatorApprovalUpdated(operator, approved);
|
|
}
|
|
|
|
function setPositionTokenId(uint256 positionTokenId_) external onlyOwner {
|
|
positionTokenId = positionTokenId_;
|
|
emit PositionTokenIdUpdated(positionTokenId_);
|
|
}
|
|
|
|
function setRiskControls(
|
|
int24 maxAbsTick_,
|
|
uint128 minLiquidity_,
|
|
uint256 minToken0Balance_,
|
|
uint256 minToken1Balance_,
|
|
uint256 maxProofSwapAmount_
|
|
) external onlyOwner {
|
|
_setRiskControls(maxAbsTick_, minLiquidity_, minToken0Balance_, minToken1Balance_, maxProofSwapAmount_);
|
|
}
|
|
|
|
function currentPoolState()
|
|
public
|
|
view
|
|
returns (
|
|
uint160 sqrtPriceX96,
|
|
int24 tick,
|
|
uint128 activeLiquidity,
|
|
uint256 token0Balance,
|
|
uint256 token1Balance
|
|
)
|
|
{
|
|
(sqrtPriceX96, tick,,,,,) = pool.slot0();
|
|
activeLiquidity = pool.liquidity();
|
|
token0Balance = IERC20(pool.token0()).balanceOf(address(pool));
|
|
token1Balance = IERC20(pool.token1()).balanceOf(address(pool));
|
|
}
|
|
|
|
function recordIndexedProof(IndexedProof calldata proof)
|
|
external
|
|
whenNotPaused
|
|
onlyApprovedOperator
|
|
returns (uint160 sqrtPriceX96, int24 tick, uint128 activeLiquidity)
|
|
{
|
|
require(proof.proofId != bytes32(0), "zero proof");
|
|
require(!usedProofIds[proof.proofId], "proof used");
|
|
require(proof.publicSwapTxHash != bytes32(0), "zero swap hash");
|
|
require(proof.liquidityTxHash != bytes32(0), "zero liquidity hash");
|
|
require(proof.outputRecipient != address(0), "zero recipient");
|
|
require(proof.exactOutputAmount > 0, "zero output");
|
|
require(maxProofSwapAmount == 0 || proof.exactOutputAmount <= maxProofSwapAmount, "proof amount too high");
|
|
require(proof.iso20022DocumentHash != bytes32(0), "zero iso hash");
|
|
require(proof.auditEnvelopeHash != bytes32(0), "zero audit hash");
|
|
require(proof.pegProofHash != bytes32(0), "zero peg hash");
|
|
|
|
uint256 token0Balance;
|
|
uint256 token1Balance;
|
|
(sqrtPriceX96, tick, activeLiquidity, token0Balance, token1Balance) = currentPoolState();
|
|
require(_absTick(tick) <= uint24(maxAbsTick), "tick drift too high");
|
|
require(activeLiquidity >= minLiquidity, "insufficient liquidity");
|
|
require(token0Balance >= minToken0Balance, "insufficient token0 balance");
|
|
require(token1Balance >= minToken1Balance, "insufficient token1 balance");
|
|
|
|
usedProofIds[proof.proofId] = true;
|
|
emit IndexedLiquidityProof(
|
|
proof.proofId,
|
|
msg.sender,
|
|
proof.outputRecipient,
|
|
address(pool),
|
|
fee,
|
|
positionTokenId,
|
|
sqrtPriceX96,
|
|
tick,
|
|
activeLiquidity,
|
|
token0Balance,
|
|
token1Balance,
|
|
proof.exactOutputAmount,
|
|
proof.publicSwapTxHash,
|
|
proof.liquidityTxHash,
|
|
proof.iso20022DocumentHash,
|
|
proof.auditEnvelopeHash,
|
|
proof.pegProofHash
|
|
);
|
|
}
|
|
|
|
function _setRiskControls(
|
|
int24 maxAbsTick_,
|
|
uint128 minLiquidity_,
|
|
uint256 minToken0Balance_,
|
|
uint256 minToken1Balance_,
|
|
uint256 maxProofSwapAmount_
|
|
) internal {
|
|
require(maxAbsTick_ >= 0, "negative tick gate");
|
|
maxAbsTick = maxAbsTick_;
|
|
minLiquidity = minLiquidity_;
|
|
minToken0Balance = minToken0Balance_;
|
|
minToken1Balance = minToken1Balance_;
|
|
maxProofSwapAmount = maxProofSwapAmount_;
|
|
emit RiskControlsUpdated(maxAbsTick_, minLiquidity_, minToken0Balance_, minToken1Balance_, maxProofSwapAmount_);
|
|
}
|
|
|
|
function _absTick(int24 tick) internal pure returns (uint24) {
|
|
return tick >= 0 ? uint24(tick) : uint24(-tick);
|
|
}
|
|
}
|