// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; /// @notice Mainnet proof-of-concept vault for the Engine X XAU/USD secured loop. /// @dev This is intentionally small and deterministic: it is not a production lender. contract DBISEngineXProofVault is Ownable { using SafeERC20 for IERC20; IERC20 public immutable cWUSDC; IERC20 public immutable usdc; IERC20 public immutable xaut; uint256 public poolCwusdcReserve; uint256 public poolUsdcReserve; uint256 public lenderUsdcAvailable; uint256 public immutable xautUsdPrice6; uint256 public immutable ltvBps; uint256 public immutable maxRoundTripLossBps; event PoolSeeded(uint256 cwusdcAmount, uint256 usdcAmount); event LenderFunded(uint256 usdcAmount); event ProofStep( bytes32 indexed proofId, uint256 indexed step, uint256 debtUsdc, uint256 collateralXaut, uint256 cwusdcIn, uint256 cwusdcOut, uint256 cwusdcLoss, uint256 poolCwusdcReserveAfter, uint256 poolUsdcReserveAfter ); event ProofClosed(bytes32 indexed proofId, uint256 loops, uint256 totalDebtUsdc, uint256 totalCwusdcIn); event OwnerWithdraw(address indexed token, address indexed to, uint256 amount); constructor( address cWUSDC_, address usdc_, address xaut_, address owner_, uint256 xautUsdPrice6_, uint256 ltvBps_, uint256 maxRoundTripLossBps_ ) Ownable(owner_) { require(cWUSDC_ != address(0) && usdc_ != address(0) && xaut_ != address(0), "zero token"); require(owner_ != address(0), "zero owner"); require(xautUsdPrice6_ > 0, "zero price"); require(ltvBps_ > 0 && ltvBps_ < 10_000, "bad ltv"); require(maxRoundTripLossBps_ > 0 && maxRoundTripLossBps_ < 10_000, "bad loss"); cWUSDC = IERC20(cWUSDC_); usdc = IERC20(usdc_); xaut = IERC20(xaut_); xautUsdPrice6 = xautUsdPrice6_; ltvBps = ltvBps_; maxRoundTripLossBps = maxRoundTripLossBps_; } function seedPool(uint256 cwusdcAmount, uint256 usdcAmount) external onlyOwner { require(cwusdcAmount > 0 && usdcAmount > 0, "zero seed"); cWUSDC.safeTransferFrom(msg.sender, address(this), cwusdcAmount); usdc.safeTransferFrom(msg.sender, address(this), usdcAmount); poolCwusdcReserve += cwusdcAmount; poolUsdcReserve += usdcAmount; emit PoolSeeded(cwusdcAmount, usdcAmount); } function fundLender(uint256 usdcAmount) external onlyOwner { require(usdcAmount > 0, "zero fund"); usdc.safeTransferFrom(msg.sender, address(this), usdcAmount); lenderUsdcAvailable += usdcAmount; emit LenderFunded(usdcAmount); } function previewCwusdcInForExactUsdc(uint256 usdcOut) public view returns (uint256) { return _getAmountIn(usdcOut, poolCwusdcReserve, poolUsdcReserve); } function previewCwusdcOutForExactUsdcIn(uint256 usdcIn) public view returns (uint256) { return _getAmountOut(usdcIn, poolUsdcReserve, poolCwusdcReserve); } function minimumXautCollateral(uint256 debtUsdc) public view returns (uint256) { uint256 numerator = debtUsdc * 1e6 * 10_000; uint256 denominator = xautUsdPrice6 * ltvBps; return (numerator + denominator - 1) / denominator; } function runProof(bytes32 proofId, uint256 debtUsdcPerLoop, uint256 loops) external { require(proofId != bytes32(0), "zero proof"); require(debtUsdcPerLoop > 0, "zero debt"); require(loops > 0 && loops <= 32, "bad loops"); uint256 collateralPerLoop = minimumXautCollateral(debtUsdcPerLoop); uint256 totalCollateral = collateralPerLoop * loops; uint256 totalDebt; uint256 totalCwusdcIn; xaut.safeTransferFrom(msg.sender, address(this), totalCollateral); for (uint256 i = 0; i < loops; i++) { require(lenderUsdcAvailable >= debtUsdcPerLoop, "insufficient lender usdc"); uint256 poolCwusdcBefore = poolCwusdcReserve; uint256 poolUsdcBefore = poolUsdcReserve; lenderUsdcAvailable -= debtUsdcPerLoop; uint256 cwusdcIn = _getAmountIn(debtUsdcPerLoop, poolCwusdcReserve, poolUsdcReserve); cWUSDC.safeTransferFrom(msg.sender, address(this), cwusdcIn); poolCwusdcReserve += cwusdcIn; poolUsdcReserve -= debtUsdcPerLoop; lenderUsdcAvailable += debtUsdcPerLoop; uint256 cwusdcOut = _getAmountOut(debtUsdcPerLoop, poolUsdcReserve, poolCwusdcReserve); poolUsdcReserve += debtUsdcPerLoop; poolCwusdcReserve -= cwusdcOut; cWUSDC.safeTransfer(msg.sender, cwusdcOut); uint256 cwusdcLoss = cwusdcIn - cwusdcOut; require(cwusdcLoss * 10_000 <= cwusdcIn * maxRoundTripLossBps, "roundtrip loss too high"); require(poolUsdcReserve == poolUsdcBefore, "usdc reserve drift"); require(poolCwusdcReserve >= poolCwusdcBefore, "cw reserve drift"); totalDebt += debtUsdcPerLoop; totalCwusdcIn += cwusdcIn; emit ProofStep( proofId, i + 1, debtUsdcPerLoop, collateralPerLoop, cwusdcIn, cwusdcOut, cwusdcLoss, poolCwusdcReserve, poolUsdcReserve ); } xaut.safeTransfer(msg.sender, totalCollateral); emit ProofClosed(proofId, loops, totalDebt, totalCwusdcIn); } function runProofBatch(bytes32[] calldata proofIds, uint256[] calldata debtsUsdcPerLoop, uint256[] calldata loops_) external { require(proofIds.length == debtsUsdcPerLoop.length && proofIds.length == loops_.length, "length mismatch"); for (uint256 i = 0; i < proofIds.length; i++) { _runProofFor(msg.sender, proofIds[i], debtsUsdcPerLoop[i], loops_[i]); } } function _runProofFor(address account, bytes32 proofId, uint256 debtUsdcPerLoop, uint256 loops) internal { require(proofId != bytes32(0), "zero proof"); require(debtUsdcPerLoop > 0, "zero debt"); require(loops > 0 && loops <= 32, "bad loops"); uint256 collateralPerLoop = minimumXautCollateral(debtUsdcPerLoop); uint256 totalCollateral = collateralPerLoop * loops; uint256 totalDebt; uint256 totalCwusdcIn; xaut.safeTransferFrom(account, address(this), totalCollateral); for (uint256 i = 0; i < loops; i++) { require(lenderUsdcAvailable >= debtUsdcPerLoop, "insufficient lender usdc"); uint256 poolCwusdcBefore = poolCwusdcReserve; uint256 poolUsdcBefore = poolUsdcReserve; lenderUsdcAvailable -= debtUsdcPerLoop; uint256 cwusdcIn = _getAmountIn(debtUsdcPerLoop, poolCwusdcReserve, poolUsdcReserve); cWUSDC.safeTransferFrom(account, address(this), cwusdcIn); poolCwusdcReserve += cwusdcIn; poolUsdcReserve -= debtUsdcPerLoop; lenderUsdcAvailable += debtUsdcPerLoop; uint256 cwusdcOut = _getAmountOut(debtUsdcPerLoop, poolUsdcReserve, poolCwusdcReserve); poolUsdcReserve += debtUsdcPerLoop; poolCwusdcReserve -= cwusdcOut; cWUSDC.safeTransfer(account, cwusdcOut); uint256 cwusdcLoss = cwusdcIn - cwusdcOut; require(cwusdcLoss * 10_000 <= cwusdcIn * maxRoundTripLossBps, "roundtrip loss too high"); require(poolUsdcReserve == poolUsdcBefore, "usdc reserve drift"); require(poolCwusdcReserve >= poolCwusdcBefore, "cw reserve drift"); totalDebt += debtUsdcPerLoop; totalCwusdcIn += cwusdcIn; emit ProofStep( proofId, i + 1, debtUsdcPerLoop, collateralPerLoop, cwusdcIn, cwusdcOut, cwusdcLoss, poolCwusdcReserve, poolUsdcReserve ); } xaut.safeTransfer(account, totalCollateral); emit ProofClosed(proofId, loops, totalDebt, totalCwusdcIn); } function withdraw(address token, address to, uint256 amount) external onlyOwner { require(to != address(0), "zero to"); IERC20(token).safeTransfer(to, amount); emit OwnerWithdraw(token, to, amount); } function _getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut) internal pure returns (uint256) { require(amountOut > 0, "insufficient output"); require(reserveIn > 0 && reserveOut > amountOut, "insufficient liquidity"); uint256 numerator = reserveIn * amountOut * 1000; uint256 denominator = (reserveOut - amountOut) * 997; return (numerator / denominator) + 1; } function _getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) internal pure returns (uint256) { require(amountIn > 0, "insufficient input"); require(reserveIn > 0 && reserveOut > 0, "insufficient liquidity"); uint256 amountInWithFee = amountIn * 997; uint256 numerator = amountInWithFee * reserveOut; uint256 denominator = reserveIn * 1000 + amountInWithFee; return numerator / denominator; } }