Files
smom-dbis-138/contracts/flash/DBISEngineXXautUsdcBorrowVault.sol
defiQUG 76143a8fe3 feat(token-aggregation): reports, PMM quotes, config; Engine X flash vaults
- 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>
2026-05-10 12:56:30 -07:00

345 lines
13 KiB
Solidity

// 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";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
/// @notice Engine X XAUt-backed USDC borrowing vault.
/// @dev This vault lends only pre-funded USDC. cWUSDC can be referenced as proof
/// provenance, but debt is always repaid in official USDC.
contract DBISEngineXXautUsdcBorrowVault is Ownable, ReentrancyGuard {
using SafeERC20 for IERC20;
uint256 public constant BPS = 10_000;
uint256 public constant MAX_LTV_BPS = 9_500;
uint256 public constant MAX_LIQUIDATION_THRESHOLD_BPS = 9_800;
uint256 public constant MAX_LIQUIDATION_BONUS_BPS = 2_000;
IERC20 public immutable xaut;
IERC20 public immutable usdc;
IERC20 public immutable cWUSDC;
uint256 public xautUsdPrice6;
uint256 public lastPriceUpdate;
bytes32 public lastPriceSourceHash;
uint256 public ltvBps;
uint256 public liquidationThresholdBps;
uint256 public minHealthFactorBps;
uint256 public liquidationBonusBps;
uint256 public maxBorrowUsdc;
bool public paused;
uint256 public lenderUsdcAvailable;
uint256 public totalCollateralXaut;
uint256 public totalDebtUsdc;
uint256 public totalCwusdcProofRepayUsdc;
struct Position {
uint256 collateralXaut;
uint256 debtUsdc;
}
mapping(address => Position) public positions;
event PriceUpdated(uint256 price6, bytes32 indexed sourceHash);
event RiskParamsUpdated(
uint256 ltvBps,
uint256 liquidationThresholdBps,
uint256 minHealthFactorBps,
uint256 liquidationBonusBps,
uint256 maxBorrowUsdc
);
event Paused(address indexed operator);
event Unpaused(address indexed operator);
event LenderFunded(address indexed funder, uint256 amount);
event LenderUsdcWithdrawn(address indexed to, uint256 amount);
event CollateralSupplied(address indexed account, uint256 amount);
event CollateralWithdrawn(address indexed account, address indexed to, uint256 amount);
event UsdcBorrowed(address indexed account, address indexed to, uint256 amount, uint256 debtAfter);
event UsdcRepaid(address indexed account, address indexed payer, uint256 amount, uint256 debtAfter);
event CwusdcSourcedRepay(
address indexed account,
address indexed payer,
uint256 amount,
bytes32 indexed publicSwapTxHash,
bytes32 iso20022DocumentHash,
bytes32 auditEnvelopeHash,
bytes32 pegProofHash
);
event Liquidated(
address indexed account,
address indexed liquidator,
uint256 repayUsdc,
uint256 seizedXaut,
uint256 debtAfter
);
event UnaccountedTokenWithdrawn(address indexed token, address indexed to, uint256 amount);
modifier whenNotPaused() {
require(!paused, "paused");
_;
}
constructor(
address xaut_,
address usdc_,
address cWUSDC_,
address owner_,
uint256 xautUsdPrice6_,
uint256 ltvBps_,
uint256 liquidationThresholdBps_,
uint256 minHealthFactorBps_,
uint256 liquidationBonusBps_,
uint256 maxBorrowUsdc_,
bytes32 priceSourceHash_
) Ownable(owner_) {
require(xaut_ != address(0) && usdc_ != address(0) && cWUSDC_ != address(0), "zero token");
require(owner_ != address(0), "zero owner");
xaut = IERC20(xaut_);
usdc = IERC20(usdc_);
cWUSDC = IERC20(cWUSDC_);
_setPrice(xautUsdPrice6_, priceSourceHash_);
_setRiskParams(
ltvBps_, liquidationThresholdBps_, minHealthFactorBps_, liquidationBonusBps_, maxBorrowUsdc_
);
}
function pause() external onlyOwner {
paused = true;
emit Paused(msg.sender);
}
function unpause() external onlyOwner {
paused = false;
emit Unpaused(msg.sender);
}
function setXautUsdPrice6(uint256 price6, bytes32 sourceHash) external onlyOwner {
_setPrice(price6, sourceHash);
}
function setRiskParams(
uint256 ltvBps_,
uint256 liquidationThresholdBps_,
uint256 minHealthFactorBps_,
uint256 liquidationBonusBps_,
uint256 maxBorrowUsdc_
) external onlyOwner {
_setRiskParams(
ltvBps_, liquidationThresholdBps_, minHealthFactorBps_, liquidationBonusBps_, maxBorrowUsdc_
);
}
function fundLender(uint256 amount) external nonReentrant whenNotPaused {
require(amount > 0, "zero fund");
usdc.safeTransferFrom(msg.sender, address(this), amount);
lenderUsdcAvailable += amount;
emit LenderFunded(msg.sender, amount);
}
function withdrawLenderUsdc(address to, uint256 amount) external onlyOwner nonReentrant {
require(to != address(0), "zero to");
require(amount > 0, "zero withdraw");
require(amount <= lenderUsdcAvailable, "insufficient lender usdc");
lenderUsdcAvailable -= amount;
usdc.safeTransfer(to, amount);
emit LenderUsdcWithdrawn(to, amount);
}
function supplyCollateral(uint256 amount) external nonReentrant whenNotPaused {
require(amount > 0, "zero collateral");
xaut.safeTransferFrom(msg.sender, address(this), amount);
positions[msg.sender].collateralXaut += amount;
totalCollateralXaut += amount;
emit CollateralSupplied(msg.sender, amount);
}
function withdrawCollateral(uint256 amount, address to) external nonReentrant whenNotPaused {
require(to != address(0), "zero to");
require(amount > 0, "zero collateral");
Position storage position = positions[msg.sender];
require(amount <= position.collateralXaut, "insufficient collateral");
position.collateralXaut -= amount;
totalCollateralXaut -= amount;
_requireHealthy(msg.sender);
xaut.safeTransfer(to, amount);
emit CollateralWithdrawn(msg.sender, to, amount);
}
function borrowUsdc(uint256 amount, address to) external nonReentrant whenNotPaused {
require(to != address(0), "zero to");
require(amount > 0, "zero borrow");
require(amount <= lenderUsdcAvailable, "insufficient lender usdc");
if (maxBorrowUsdc != 0) {
require(totalDebtUsdc + amount <= maxBorrowUsdc, "max borrow exceeded");
}
Position storage position = positions[msg.sender];
position.debtUsdc += amount;
totalDebtUsdc += amount;
lenderUsdcAvailable -= amount;
_requireHealthy(msg.sender);
usdc.safeTransfer(to, amount);
emit UsdcBorrowed(msg.sender, to, amount, position.debtUsdc);
}
function repayUsdc(uint256 amount) external nonReentrant whenNotPaused returns (uint256 repaid) {
repaid = _repay(msg.sender, msg.sender, amount);
}
function repayUsdcFor(address account, uint256 amount) external nonReentrant whenNotPaused returns (uint256 repaid) {
require(account != address(0), "zero account");
repaid = _repay(account, msg.sender, amount);
}
function repayUsdcFromCwusdcProof(
uint256 amount,
bytes32 publicSwapTxHash,
bytes32 iso20022DocumentHash,
bytes32 auditEnvelopeHash,
bytes32 pegProofHash
) external nonReentrant whenNotPaused returns (uint256 repaid) {
require(publicSwapTxHash != bytes32(0), "zero swap hash");
require(iso20022DocumentHash != bytes32(0), "zero iso hash");
require(auditEnvelopeHash != bytes32(0), "zero audit hash");
require(pegProofHash != bytes32(0), "zero peg hash");
repaid = _repay(msg.sender, msg.sender, amount);
totalCwusdcProofRepayUsdc += repaid;
emit CwusdcSourcedRepay(
msg.sender, msg.sender, repaid, publicSwapTxHash, iso20022DocumentHash, auditEnvelopeHash, pegProofHash
);
}
function liquidate(address account, uint256 repayAmount) external nonReentrant whenNotPaused returns (uint256 seized) {
require(account != address(0), "zero account");
require(repayAmount > 0, "zero repay");
require(healthFactorBps(account) < minHealthFactorBps, "position healthy");
Position storage position = positions[account];
uint256 repaid = repayAmount > position.debtUsdc ? position.debtUsdc : repayAmount;
require(repaid > 0, "zero debt");
seized = _xautForUsdcWithBonus(repaid);
require(seized <= position.collateralXaut, "insufficient collateral");
usdc.safeTransferFrom(msg.sender, address(this), repaid);
position.debtUsdc -= repaid;
totalDebtUsdc -= repaid;
lenderUsdcAvailable += repaid;
position.collateralXaut -= seized;
totalCollateralXaut -= seized;
xaut.safeTransfer(msg.sender, seized);
emit Liquidated(account, msg.sender, repaid, seized, position.debtUsdc);
}
function rescueUnaccountedToken(address token, address to, uint256 amount) external onlyOwner nonReentrant {
require(to != address(0), "zero to");
require(amount > 0, "zero withdraw");
IERC20(token).safeTransfer(to, amount);
_requireAccountingCollateralized();
emit UnaccountedTokenWithdrawn(token, to, amount);
}
function collateralValueUsd6(address account) public view returns (uint256) {
return collateralValueUsd6ForRaw(positions[account].collateralXaut);
}
function collateralValueUsd6ForRaw(uint256 xautRaw) public view returns (uint256) {
return (xautRaw * xautUsdPrice6) / 1e6;
}
function maxDebtForCollateral(uint256 xautRaw) public view returns (uint256) {
uint256 collateralUsd6 = collateralValueUsd6ForRaw(xautRaw);
uint256 byLtv = (collateralUsd6 * ltvBps) / BPS;
uint256 byHealth = (collateralUsd6 * liquidationThresholdBps) / minHealthFactorBps;
return byLtv < byHealth ? byLtv : byHealth;
}
function maxAdditionalBorrow(address account) public view returns (uint256) {
uint256 maxDebt = maxDebtForCollateral(positions[account].collateralXaut);
if (positions[account].debtUsdc >= maxDebt) {
return 0;
}
return maxDebt - positions[account].debtUsdc;
}
function healthFactorBps(address account) public view returns (uint256) {
Position memory position = positions[account];
if (position.debtUsdc == 0) {
return type(uint256).max;
}
uint256 collateralUsd6 = collateralValueUsd6ForRaw(position.collateralXaut);
return (collateralUsd6 * liquidationThresholdBps) / position.debtUsdc;
}
function _repay(address account, address payer, uint256 amount) internal returns (uint256 repaid) {
require(amount > 0, "zero repay");
Position storage position = positions[account];
require(position.debtUsdc > 0, "zero debt");
repaid = amount > position.debtUsdc ? position.debtUsdc : amount;
usdc.safeTransferFrom(payer, address(this), repaid);
position.debtUsdc -= repaid;
totalDebtUsdc -= repaid;
lenderUsdcAvailable += repaid;
emit UsdcRepaid(account, payer, repaid, position.debtUsdc);
}
function _requireHealthy(address account) internal view {
Position memory position = positions[account];
if (position.debtUsdc == 0) {
return;
}
require(position.debtUsdc <= maxDebtForCollateral(position.collateralXaut), "exceeds collateral");
require(healthFactorBps(account) >= minHealthFactorBps, "health too low");
}
function _xautForUsdcWithBonus(uint256 usdcAmount) internal view returns (uint256) {
uint256 numerator = usdcAmount * 1e6 * (BPS + liquidationBonusBps);
uint256 denominator = xautUsdPrice6 * BPS;
return (numerator + denominator - 1) / denominator;
}
function _setPrice(uint256 price6, bytes32 sourceHash) internal {
require(price6 > 0, "zero price");
require(sourceHash != bytes32(0), "zero source");
xautUsdPrice6 = price6;
lastPriceUpdate = block.timestamp;
lastPriceSourceHash = sourceHash;
emit PriceUpdated(price6, sourceHash);
}
function _setRiskParams(
uint256 ltvBps_,
uint256 liquidationThresholdBps_,
uint256 minHealthFactorBps_,
uint256 liquidationBonusBps_,
uint256 maxBorrowUsdc_
) internal {
require(ltvBps_ > 0 && ltvBps_ <= MAX_LTV_BPS, "bad ltv");
require(
liquidationThresholdBps_ >= ltvBps_ && liquidationThresholdBps_ <= MAX_LIQUIDATION_THRESHOLD_BPS,
"bad threshold"
);
require(minHealthFactorBps_ >= BPS, "bad health");
require(liquidationBonusBps_ <= MAX_LIQUIDATION_BONUS_BPS, "bad bonus");
ltvBps = ltvBps_;
liquidationThresholdBps = liquidationThresholdBps_;
minHealthFactorBps = minHealthFactorBps_;
liquidationBonusBps = liquidationBonusBps_;
maxBorrowUsdc = maxBorrowUsdc_;
emit RiskParamsUpdated(
ltvBps_, liquidationThresholdBps_, minHealthFactorBps_, liquidationBonusBps_, maxBorrowUsdc_
);
}
function _requireAccountingCollateralized() internal view {
require(xaut.balanceOf(address(this)) >= totalCollateralXaut, "xaut undercollateralized");
require(usdc.balanceOf(address(this)) >= lenderUsdcAvailable, "usdc undercollateralized");
}
}