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>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {IERC3156FlashBorrower} from "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol";
|
||||
import {IERC3156FlashLender} from "@openzeppelin/contracts/interfaces/IERC3156FlashLender.sol";
|
||||
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";
|
||||
@@ -8,9 +10,12 @@ import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol
|
||||
|
||||
/// @notice Engine X maintained proof vault with virtual batch settlement.
|
||||
/// @dev Use only for accounting proofs: it compresses identical maintained loops into one net settlement.
|
||||
contract DBISEngineXVirtualBatchVault is Ownable, ReentrancyGuard {
|
||||
contract DBISEngineXVirtualBatchVault is IERC3156FlashLender, Ownable, ReentrancyGuard {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
bytes32 private constant _FLASH_RETURN_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan");
|
||||
uint256 public constant MAX_FLASH_FEE_BPS = 1_000;
|
||||
|
||||
IERC20 public immutable cWUSDC;
|
||||
IERC20 public immutable usdc;
|
||||
IERC20 public immutable xaut;
|
||||
@@ -22,7 +27,13 @@ contract DBISEngineXVirtualBatchVault is Ownable, ReentrancyGuard {
|
||||
uint256 public totalVirtualLoops;
|
||||
uint256 public totalVirtualDebtUsdc;
|
||||
uint256 public totalVirtualCwusdcIn;
|
||||
uint256 public totalFlashFeesCollectedUsdc;
|
||||
uint256 public flashFeeBps = 5;
|
||||
uint256 public maxFlashLoanAmount;
|
||||
bool public paused;
|
||||
bool public flashBorrowerAllowlistEnabled;
|
||||
mapping(bytes32 => bool) public usedProofIds;
|
||||
mapping(address => bool) public approvedFlashBorrower;
|
||||
|
||||
uint256 public immutable xautUsdPrice6;
|
||||
uint256 public immutable ltvBps;
|
||||
@@ -58,7 +69,27 @@ contract DBISEngineXVirtualBatchVault is Ownable, ReentrancyGuard {
|
||||
bytes32 auditEnvelopeHash,
|
||||
bytes32 pegProofHash
|
||||
);
|
||||
event OwnerWithdraw(address indexed token, address indexed to, uint256 amount);
|
||||
event PoolLiquidityWithdrawn(address indexed to, uint256 cwusdcAmount, uint256 usdcAmount);
|
||||
event LenderUsdcWithdrawn(address indexed to, uint256 amount);
|
||||
event UnaccountedTokenWithdrawn(address indexed token, address indexed to, uint256 amount);
|
||||
event FlashFeeBpsUpdated(uint256 feeBps);
|
||||
event MaxFlashLoanAmountUpdated(uint256 amount);
|
||||
event Paused(address indexed operator);
|
||||
event Unpaused(address indexed operator);
|
||||
event FlashBorrowerAllowlistEnabledUpdated(bool enabled);
|
||||
event FlashBorrowerApprovalUpdated(address indexed borrower, bool approved);
|
||||
event EngineXFlashLoan(
|
||||
address indexed initiator,
|
||||
IERC3156FlashBorrower indexed receiver,
|
||||
address indexed token,
|
||||
uint256 amount,
|
||||
uint256 fee
|
||||
);
|
||||
|
||||
modifier whenNotPaused() {
|
||||
require(!paused, "paused");
|
||||
_;
|
||||
}
|
||||
|
||||
constructor(
|
||||
address cWUSDC_,
|
||||
@@ -85,7 +116,7 @@ contract DBISEngineXVirtualBatchVault is Ownable, ReentrancyGuard {
|
||||
maxRoundTripLossBps = maxRoundTripLossBps_;
|
||||
}
|
||||
|
||||
function seedPool(uint256 cwusdcAmount, uint256 usdcAmount) external onlyOwner nonReentrant {
|
||||
function seedPool(uint256 cwusdcAmount, uint256 usdcAmount) external onlyOwner nonReentrant whenNotPaused {
|
||||
require(cwusdcAmount > 0 && usdcAmount > 0, "zero seed");
|
||||
cWUSDC.safeTransferFrom(msg.sender, address(this), cwusdcAmount);
|
||||
usdc.safeTransferFrom(msg.sender, address(this), usdcAmount);
|
||||
@@ -94,7 +125,7 @@ contract DBISEngineXVirtualBatchVault is Ownable, ReentrancyGuard {
|
||||
emit PoolSeeded(cwusdcAmount, usdcAmount);
|
||||
}
|
||||
|
||||
function fundLender(uint256 usdcAmount) external onlyOwner nonReentrant {
|
||||
function fundLender(uint256 usdcAmount) external onlyOwner nonReentrant whenNotPaused {
|
||||
require(usdcAmount > 0, "zero fund");
|
||||
usdc.safeTransferFrom(msg.sender, address(this), usdcAmount);
|
||||
lenderUsdcAvailable += usdcAmount;
|
||||
@@ -107,6 +138,38 @@ contract DBISEngineXVirtualBatchVault is Ownable, ReentrancyGuard {
|
||||
emit SurplusReceiverUpdated(receiver);
|
||||
}
|
||||
|
||||
function pause() external onlyOwner {
|
||||
paused = true;
|
||||
emit Paused(msg.sender);
|
||||
}
|
||||
|
||||
function unpause() external onlyOwner {
|
||||
paused = false;
|
||||
emit Unpaused(msg.sender);
|
||||
}
|
||||
|
||||
function setFlashFeeBps(uint256 newFlashFeeBps) external onlyOwner {
|
||||
require(newFlashFeeBps <= MAX_FLASH_FEE_BPS, "fee too high");
|
||||
flashFeeBps = newFlashFeeBps;
|
||||
emit FlashFeeBpsUpdated(newFlashFeeBps);
|
||||
}
|
||||
|
||||
function setMaxFlashLoanAmount(uint256 amount) external onlyOwner {
|
||||
maxFlashLoanAmount = amount;
|
||||
emit MaxFlashLoanAmountUpdated(amount);
|
||||
}
|
||||
|
||||
function setFlashBorrowerAllowlistEnabled(bool enabled) external onlyOwner {
|
||||
flashBorrowerAllowlistEnabled = enabled;
|
||||
emit FlashBorrowerAllowlistEnabledUpdated(enabled);
|
||||
}
|
||||
|
||||
function setFlashBorrowerApproved(address borrower, bool approved) external onlyOwner {
|
||||
require(borrower != address(0), "zero borrower");
|
||||
approvedFlashBorrower[borrower] = approved;
|
||||
emit FlashBorrowerApprovalUpdated(borrower, approved);
|
||||
}
|
||||
|
||||
function previewCwusdcInForExactUsdc(uint256 usdcOut) public view returns (uint256) {
|
||||
return _getAmountIn(usdcOut, poolCwusdcReserve, poolUsdcReserve);
|
||||
}
|
||||
@@ -119,6 +182,21 @@ contract DBISEngineXVirtualBatchVault is Ownable, ReentrancyGuard {
|
||||
return poolCwusdcReserve > poolUsdcReserve ? poolCwusdcReserve - poolUsdcReserve : 0;
|
||||
}
|
||||
|
||||
function maxFlashLoan(address token) external view override returns (uint256) {
|
||||
if (paused || token != address(usdc)) {
|
||||
return 0;
|
||||
}
|
||||
if (maxFlashLoanAmount == 0 || maxFlashLoanAmount > lenderUsdcAvailable) {
|
||||
return lenderUsdcAvailable;
|
||||
}
|
||||
return maxFlashLoanAmount;
|
||||
}
|
||||
|
||||
function flashFee(address token, uint256 amount) public view override returns (uint256) {
|
||||
require(token == address(usdc), "unsupported flash token");
|
||||
return (amount * flashFeeBps) / 10_000;
|
||||
}
|
||||
|
||||
function minimumXautCollateral(uint256 debtUsdc) public view returns (uint256) {
|
||||
uint256 numerator = debtUsdc * 1e6 * 10_000;
|
||||
uint256 denominator = xautUsdPrice6 * ltvBps;
|
||||
@@ -155,7 +233,11 @@ contract DBISEngineXVirtualBatchVault is Ownable, ReentrancyGuard {
|
||||
totalNeutralizedCwusdcAmount = cwusdcLossPerLoop * virtualLoops;
|
||||
}
|
||||
|
||||
function runVirtualProof(bytes32 proofId, uint256 debtUsdcPerLoop, uint256 virtualLoops) external nonReentrant {
|
||||
function runVirtualProof(bytes32 proofId, uint256 debtUsdcPerLoop, uint256 virtualLoops)
|
||||
external
|
||||
nonReentrant
|
||||
whenNotPaused
|
||||
{
|
||||
_runVirtualProofFor(
|
||||
msg.sender, proofId, debtUsdcPerLoop, virtualLoops, 0, msg.sender, bytes32(0), bytes32(0), bytes32(0)
|
||||
);
|
||||
@@ -164,6 +246,7 @@ contract DBISEngineXVirtualBatchVault is Ownable, ReentrancyGuard {
|
||||
function runVirtualProofTo(bytes32 proofId, uint256 debtUsdcPerLoop, uint256 virtualLoops, address outputRecipient)
|
||||
external
|
||||
nonReentrant
|
||||
whenNotPaused
|
||||
{
|
||||
require(outputRecipient != address(0), "zero output");
|
||||
_runVirtualProofFor(
|
||||
@@ -181,7 +264,7 @@ contract DBISEngineXVirtualBatchVault is Ownable, ReentrancyGuard {
|
||||
bytes32 iso20022DocumentHash,
|
||||
bytes32 auditEnvelopeHash,
|
||||
bytes32 pegProofHash
|
||||
) external nonReentrant {
|
||||
) external nonReentrant whenNotPaused {
|
||||
require(outputRecipient != address(0), "zero output");
|
||||
require(exactOutputAmount > 0, "zero exact output");
|
||||
require(roundingReceiver != address(0), "zero rounding");
|
||||
@@ -201,6 +284,48 @@ contract DBISEngineXVirtualBatchVault is Ownable, ReentrancyGuard {
|
||||
);
|
||||
}
|
||||
|
||||
function flashLoan(IERC3156FlashBorrower receiver, address token, uint256 amount, bytes calldata data)
|
||||
external
|
||||
override
|
||||
nonReentrant
|
||||
whenNotPaused
|
||||
returns (bool)
|
||||
{
|
||||
require(token == address(usdc), "unsupported flash token");
|
||||
require(amount > 0, "zero flash amount");
|
||||
require(amount <= lenderUsdcAvailable, "insufficient lender usdc");
|
||||
require(maxFlashLoanAmount == 0 || amount <= maxFlashLoanAmount, "flash amount too high");
|
||||
require(
|
||||
!flashBorrowerAllowlistEnabled || approvedFlashBorrower[address(receiver)], "flash borrower not approved"
|
||||
);
|
||||
|
||||
uint256 fee = flashFee(token, amount);
|
||||
uint256 balanceBefore = usdc.balanceOf(address(this));
|
||||
require(balanceBefore >= poolUsdcReserve + lenderUsdcAvailable, "accounting undercollateralized");
|
||||
|
||||
lenderUsdcAvailable -= amount;
|
||||
usdc.safeTransfer(address(receiver), amount);
|
||||
|
||||
bytes32 retval = receiver.onFlashLoan(msg.sender, token, amount, fee, data);
|
||||
require(retval == _FLASH_RETURN_VALUE, "invalid flash callback");
|
||||
|
||||
uint256 expectedBalance = balanceBefore + fee;
|
||||
uint256 balanceAfterCallback = usdc.balanceOf(address(this));
|
||||
if (balanceAfterCallback < expectedBalance) {
|
||||
usdc.safeTransferFrom(address(receiver), address(this), expectedBalance - balanceAfterCallback);
|
||||
}
|
||||
require(usdc.balanceOf(address(this)) >= expectedBalance, "flash repayment failed");
|
||||
|
||||
lenderUsdcAvailable += amount + fee;
|
||||
totalFlashFeesCollectedUsdc += fee;
|
||||
require(
|
||||
usdc.balanceOf(address(this)) >= poolUsdcReserve + lenderUsdcAvailable, "accounting undercollateralized"
|
||||
);
|
||||
|
||||
emit EngineXFlashLoan(msg.sender, receiver, token, amount, fee);
|
||||
return true;
|
||||
}
|
||||
|
||||
function _runVirtualProofFor(
|
||||
address outputRecipient,
|
||||
bytes32 proofId,
|
||||
@@ -272,10 +397,58 @@ contract DBISEngineXVirtualBatchVault is Ownable, ReentrancyGuard {
|
||||
}
|
||||
}
|
||||
|
||||
function withdrawPoolLiquidity(address to, uint256 cwusdcAmount, uint256 usdcAmount)
|
||||
external
|
||||
onlyOwner
|
||||
nonReentrant
|
||||
{
|
||||
require(to != address(0), "zero to");
|
||||
require(cwusdcAmount > 0 || usdcAmount > 0, "zero withdraw");
|
||||
require(cwusdcAmount <= poolCwusdcReserve, "insufficient pool cwusdc");
|
||||
require(usdcAmount <= poolUsdcReserve, "insufficient pool usdc");
|
||||
|
||||
uint256 nextCwusdcReserve = poolCwusdcReserve - cwusdcAmount;
|
||||
uint256 nextUsdcReserve = poolUsdcReserve - usdcAmount;
|
||||
require(nextCwusdcReserve == nextUsdcReserve, "would break maintained pool");
|
||||
|
||||
poolCwusdcReserve = nextCwusdcReserve;
|
||||
poolUsdcReserve = nextUsdcReserve;
|
||||
|
||||
if (cwusdcAmount > 0) {
|
||||
cWUSDC.safeTransfer(to, cwusdcAmount);
|
||||
}
|
||||
if (usdcAmount > 0) {
|
||||
usdc.safeTransfer(to, usdcAmount);
|
||||
}
|
||||
|
||||
emit PoolLiquidityWithdrawn(to, cwusdcAmount, usdcAmount);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// @notice Rescue or migrate only tokens that are not backing Engine X pool/lender accounting.
|
||||
function withdraw(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);
|
||||
emit OwnerWithdraw(token, to, amount);
|
||||
_requireAccountingCollateralized();
|
||||
emit UnaccountedTokenWithdrawn(token, to, amount);
|
||||
}
|
||||
|
||||
function _requireAccountingCollateralized() internal view {
|
||||
require(cWUSDC.balanceOf(address(this)) >= poolCwusdcReserve, "accounting undercollateralized");
|
||||
require(
|
||||
usdc.balanceOf(address(this)) >= poolUsdcReserve + lenderUsdcAvailable, "accounting undercollateralized"
|
||||
);
|
||||
}
|
||||
|
||||
function _getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut) internal pure returns (uint256) {
|
||||
|
||||
Reference in New Issue
Block a user