- 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>
176 lines
6.6 KiB
Solidity
176 lines
6.6 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import {Test} from "forge-std/Test.sol";
|
|
import {DBISEngineXXautUsdcBorrowVault} from "../../contracts/flash/DBISEngineXXautUsdcBorrowVault.sol";
|
|
import {MockMintableToken} from "../dbis/MockMintableToken.sol";
|
|
|
|
contract DBISEngineXXautUsdcBorrowVaultTest is Test {
|
|
MockMintableToken internal xaut;
|
|
MockMintableToken internal usdc;
|
|
MockMintableToken internal cwusdc;
|
|
DBISEngineXXautUsdcBorrowVault internal vault;
|
|
|
|
address internal constant BORROWER = address(0xB0B);
|
|
address internal constant LENDER = address(0x1EAD);
|
|
address internal constant LIQUIDATOR = address(0xA11CE);
|
|
bytes32 internal constant PRICE_SOURCE_HASH = bytes32(uint256(0x5052494345));
|
|
bytes32 internal constant SWAP_TX = bytes32(uint256(0x51574150));
|
|
bytes32 internal constant ISO_HASH = bytes32(uint256(0x150));
|
|
bytes32 internal constant AUDIT_HASH = bytes32(uint256(0xA0017));
|
|
bytes32 internal constant PEG_HASH = bytes32(uint256(0x9E6));
|
|
|
|
uint256 internal constant XAUT_PRICE6 = 3_226_640_000;
|
|
uint256 internal constant LTV_BPS = 7_500;
|
|
uint256 internal constant LIQUIDATION_THRESHOLD_BPS = 8_000;
|
|
uint256 internal constant MIN_HEALTH_FACTOR_BPS = 11_000;
|
|
uint256 internal constant LIQUIDATION_BONUS_BPS = 500;
|
|
uint256 internal constant LENDER_USDC = 5_000_000_000;
|
|
|
|
event CwusdcSourcedRepay(
|
|
address indexed account,
|
|
address indexed payer,
|
|
uint256 amount,
|
|
bytes32 indexed publicSwapTxHash,
|
|
bytes32 iso20022DocumentHash,
|
|
bytes32 auditEnvelopeHash,
|
|
bytes32 pegProofHash
|
|
);
|
|
|
|
function setUp() public {
|
|
xaut = new MockMintableToken("Tether Gold", "XAUt", 6, address(this));
|
|
usdc = new MockMintableToken("USD Coin", "USDC", 6, address(this));
|
|
cwusdc = new MockMintableToken("Wrapped cWUSDC", "cWUSDC", 6, address(this));
|
|
|
|
vault = new DBISEngineXXautUsdcBorrowVault(
|
|
address(xaut),
|
|
address(usdc),
|
|
address(cwusdc),
|
|
address(this),
|
|
XAUT_PRICE6,
|
|
LTV_BPS,
|
|
LIQUIDATION_THRESHOLD_BPS,
|
|
MIN_HEALTH_FACTOR_BPS,
|
|
LIQUIDATION_BONUS_BPS,
|
|
0,
|
|
PRICE_SOURCE_HASH
|
|
);
|
|
|
|
usdc.mint(LENDER, LENDER_USDC);
|
|
vm.startPrank(LENDER);
|
|
usdc.approve(address(vault), type(uint256).max);
|
|
vault.fundLender(LENDER_USDC);
|
|
vm.stopPrank();
|
|
|
|
xaut.mint(BORROWER, 1_000_000);
|
|
usdc.mint(BORROWER, 1_000_000_000);
|
|
vm.startPrank(BORROWER);
|
|
xaut.approve(address(vault), type(uint256).max);
|
|
usdc.approve(address(vault), type(uint256).max);
|
|
vm.stopPrank();
|
|
|
|
usdc.mint(LIQUIDATOR, 1_000_000_000);
|
|
vm.prank(LIQUIDATOR);
|
|
usdc.approve(address(vault), type(uint256).max);
|
|
}
|
|
|
|
function testBorrowRepayAndWithdrawCollateral() public {
|
|
vm.startPrank(BORROWER);
|
|
vault.supplyCollateral(1_000_000);
|
|
assertEq(vault.collateralValueUsd6(BORROWER), XAUT_PRICE6, "1 XAUt value");
|
|
|
|
vault.borrowUsdc(2_000_000_000, BORROWER);
|
|
assertEq(usdc.balanceOf(BORROWER), 3_000_000_000, "borrowed USDC");
|
|
assertEq(vault.lenderUsdcAvailable(), 3_000_000_000, "lender bucket lent out");
|
|
assertEq(vault.healthFactorBps(BORROWER), 12_906, "health factor");
|
|
|
|
vault.repayUsdc(2_000_000_000);
|
|
vault.withdrawCollateral(1_000_000, BORROWER);
|
|
vm.stopPrank();
|
|
|
|
(uint256 collateral, uint256 debt) = vault.positions(BORROWER);
|
|
assertEq(collateral, 0, "collateral closed");
|
|
assertEq(debt, 0, "debt closed");
|
|
assertEq(xaut.balanceOf(BORROWER), 1_000_000, "xaut returned");
|
|
assertEq(vault.lenderUsdcAvailable(), LENDER_USDC, "lender restored");
|
|
}
|
|
|
|
function testBorrowRejectsDebtAboveEffectiveCollateralLimit() public {
|
|
vm.startPrank(BORROWER);
|
|
vault.supplyCollateral(1_000_000);
|
|
|
|
vm.expectRevert(bytes("exceeds collateral"));
|
|
vault.borrowUsdc(2_400_000_000, BORROWER);
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testBorrowRejectsGlobalBorrowCap() public {
|
|
vault.setRiskParams(LTV_BPS, LIQUIDATION_THRESHOLD_BPS, MIN_HEALTH_FACTOR_BPS, LIQUIDATION_BONUS_BPS, 1_000_000_000);
|
|
|
|
vm.startPrank(BORROWER);
|
|
vault.supplyCollateral(1_000_000);
|
|
|
|
vm.expectRevert(bytes("max borrow exceeded"));
|
|
vault.borrowUsdc(1_000_000_001, BORROWER);
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testRepayFromCwusdcProofStillSettlesInUsdcAndAnchorsHashes() public {
|
|
vm.startPrank(BORROWER);
|
|
vault.supplyCollateral(1_000_000);
|
|
vault.borrowUsdc(1_000_000_000, BORROWER);
|
|
|
|
vm.expectEmit(true, true, true, true, address(vault));
|
|
emit CwusdcSourcedRepay(BORROWER, BORROWER, 250_000_000, SWAP_TX, ISO_HASH, AUDIT_HASH, PEG_HASH);
|
|
vault.repayUsdcFromCwusdcProof(250_000_000, SWAP_TX, ISO_HASH, AUDIT_HASH, PEG_HASH);
|
|
vm.stopPrank();
|
|
|
|
(, uint256 debt) = vault.positions(BORROWER);
|
|
assertEq(debt, 750_000_000, "debt reduced");
|
|
assertEq(vault.totalCwusdcProofRepayUsdc(), 250_000_000, "proof-sourced repay counter");
|
|
}
|
|
|
|
function testLiquidationAfterPriceDrop() public {
|
|
vm.startPrank(BORROWER);
|
|
vault.supplyCollateral(1_000_000);
|
|
vault.borrowUsdc(2_000_000_000, BORROWER);
|
|
vm.stopPrank();
|
|
|
|
vault.setXautUsdPrice6(2_000_000_000, bytes32(uint256(0x44524f50)));
|
|
assertEq(vault.healthFactorBps(BORROWER), 8_000, "unhealthy after price drop");
|
|
|
|
vm.prank(LIQUIDATOR);
|
|
uint256 seized = vault.liquidate(BORROWER, 100_000_000);
|
|
|
|
assertEq(seized, 52_500, "5 percent bonus on 0.05 XAUt");
|
|
assertEq(xaut.balanceOf(LIQUIDATOR), 52_500, "liquidator receives XAUt");
|
|
(, uint256 debt) = vault.positions(BORROWER);
|
|
assertEq(debt, 1_900_000_000, "debt after partial liquidation");
|
|
}
|
|
|
|
function testOwnerCanWithdrawOnlyUnborrowedLenderUsdc() public {
|
|
vm.prank(BORROWER);
|
|
vault.supplyCollateral(1_000_000);
|
|
vm.prank(BORROWER);
|
|
vault.borrowUsdc(500_000_000, BORROWER);
|
|
|
|
vault.withdrawLenderUsdc(address(this), 4_500_000_000);
|
|
assertEq(vault.lenderUsdcAvailable(), 0, "available bucket withdrawn");
|
|
|
|
vm.expectRevert(bytes("insufficient lender usdc"));
|
|
vault.withdrawLenderUsdc(address(this), 1);
|
|
}
|
|
|
|
function testPauseBlocksMutableUserFlows() public {
|
|
vault.pause();
|
|
|
|
vm.expectRevert(bytes("paused"));
|
|
vm.prank(BORROWER);
|
|
vault.supplyCollateral(1);
|
|
|
|
vault.unpause();
|
|
vm.prank(BORROWER);
|
|
vault.supplyCollateral(1);
|
|
}
|
|
}
|