Add Engine X virtual batch canary vault
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m8s
CI/CD Pipeline / Security Scanning (push) Successful in 2m27s
CI/CD Pipeline / Lint and Format (push) Failing after 40s
CI/CD Pipeline / Terraform Validation (push) Failing after 21s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 23s
Validation / validate-genesis (push) Successful in 25s
Validation / validate-terraform (push) Failing after 22s
Validation / validate-kubernetes (push) Failing after 7s
Validation / validate-smart-contracts (push) Failing after 7s
Validation / validate-security (push) Failing after 1m19s
Validation / validate-documentation (push) Failing after 13s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (push) Failing after 25s
Verify Deployment / Verify Deployment (push) Failing after 45s
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m8s
CI/CD Pipeline / Security Scanning (push) Successful in 2m27s
CI/CD Pipeline / Lint and Format (push) Failing after 40s
CI/CD Pipeline / Terraform Validation (push) Failing after 21s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 23s
Validation / validate-genesis (push) Successful in 25s
Validation / validate-terraform (push) Failing after 22s
Validation / validate-kubernetes (push) Failing after 7s
Validation / validate-smart-contracts (push) Failing after 7s
Validation / validate-security (push) Failing after 1m19s
Validation / validate-documentation (push) Failing after 13s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (push) Failing after 25s
Verify Deployment / Verify Deployment (push) Failing after 45s
This commit is contained in:
297
contracts/flash/DBISEngineXVirtualBatchVault.sol
Normal file
297
contracts/flash/DBISEngineXVirtualBatchVault.sol
Normal file
@@ -0,0 +1,297 @@
|
||||
// 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 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 {
|
||||
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 totalNeutralizedCwusdc;
|
||||
uint256 public totalVirtualLoops;
|
||||
uint256 public totalVirtualDebtUsdc;
|
||||
uint256 public totalVirtualCwusdcIn;
|
||||
mapping(bytes32 => bool) public usedProofIds;
|
||||
|
||||
uint256 public immutable xautUsdPrice6;
|
||||
uint256 public immutable ltvBps;
|
||||
uint256 public immutable maxRoundTripLossBps;
|
||||
|
||||
address public surplusReceiver;
|
||||
|
||||
event PoolSeeded(uint256 cwusdcAmount, uint256 usdcAmount);
|
||||
event LenderFunded(uint256 usdcAmount);
|
||||
event SurplusReceiverUpdated(address indexed receiver);
|
||||
event VirtualProofClosed(
|
||||
bytes32 indexed proofId,
|
||||
uint256 virtualLoops,
|
||||
uint256 debtUsdcPerLoop,
|
||||
uint256 collateralXaut,
|
||||
uint256 cwusdcInPerLoop,
|
||||
uint256 cwusdcOutPerLoop,
|
||||
uint256 cwusdcLossPerLoop,
|
||||
uint256 totalCwusdcInAmount,
|
||||
uint256 totalCwusdcOutAmount,
|
||||
uint256 totalNeutralizedCwusdcAmount,
|
||||
uint256 poolCwusdcReserveAfter,
|
||||
uint256 poolUsdcReserveAfter
|
||||
);
|
||||
event VirtualProofAuditEvidence(
|
||||
bytes32 indexed proofId,
|
||||
address indexed operator,
|
||||
address indexed outputRecipient,
|
||||
uint256 exactOutputAmount,
|
||||
uint256 outputRoundingAmount,
|
||||
address roundingReceiver,
|
||||
bytes32 iso20022DocumentHash,
|
||||
bytes32 auditEnvelopeHash,
|
||||
bytes32 pegProofHash
|
||||
);
|
||||
event OwnerWithdraw(address indexed token, address indexed to, uint256 amount);
|
||||
|
||||
constructor(
|
||||
address cWUSDC_,
|
||||
address usdc_,
|
||||
address xaut_,
|
||||
address owner_,
|
||||
address surplusReceiver_,
|
||||
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(surplusReceiver_ != address(0), "zero receiver");
|
||||
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_);
|
||||
surplusReceiver = surplusReceiver_;
|
||||
xautUsdPrice6 = xautUsdPrice6_;
|
||||
ltvBps = ltvBps_;
|
||||
maxRoundTripLossBps = maxRoundTripLossBps_;
|
||||
}
|
||||
|
||||
function seedPool(uint256 cwusdcAmount, uint256 usdcAmount) external onlyOwner nonReentrant {
|
||||
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 nonReentrant {
|
||||
require(usdcAmount > 0, "zero fund");
|
||||
usdc.safeTransferFrom(msg.sender, address(this), usdcAmount);
|
||||
lenderUsdcAvailable += usdcAmount;
|
||||
emit LenderFunded(usdcAmount);
|
||||
}
|
||||
|
||||
function setSurplusReceiver(address receiver) external onlyOwner {
|
||||
require(receiver != address(0), "zero receiver");
|
||||
surplusReceiver = receiver;
|
||||
emit SurplusReceiverUpdated(receiver);
|
||||
}
|
||||
|
||||
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 currentSurplusCwusdc() public view returns (uint256) {
|
||||
return poolCwusdcReserve > poolUsdcReserve ? poolCwusdcReserve - poolUsdcReserve : 0;
|
||||
}
|
||||
|
||||
function minimumXautCollateral(uint256 debtUsdc) public view returns (uint256) {
|
||||
uint256 numerator = debtUsdc * 1e6 * 10_000;
|
||||
uint256 denominator = xautUsdPrice6 * ltvBps;
|
||||
return (numerator + denominator - 1) / denominator;
|
||||
}
|
||||
|
||||
function previewVirtualProof(uint256 debtUsdcPerLoop, uint256 virtualLoops)
|
||||
public
|
||||
view
|
||||
returns (
|
||||
uint256 collateralXaut,
|
||||
uint256 cwusdcInPerLoop,
|
||||
uint256 cwusdcOutPerLoop,
|
||||
uint256 cwusdcLossPerLoop,
|
||||
uint256 totalCwusdcInAmount,
|
||||
uint256 totalCwusdcOutAmount,
|
||||
uint256 totalNeutralizedCwusdcAmount
|
||||
)
|
||||
{
|
||||
require(virtualLoops > 0, "zero loops");
|
||||
require(poolCwusdcReserve == poolUsdcReserve, "pool not maintained");
|
||||
require(lenderUsdcAvailable >= debtUsdcPerLoop, "insufficient lender usdc");
|
||||
collateralXaut = minimumXautCollateral(debtUsdcPerLoop);
|
||||
cwusdcInPerLoop = _getAmountIn(debtUsdcPerLoop, poolCwusdcReserve, poolUsdcReserve);
|
||||
|
||||
uint256 cwusdcReserveAfterIn = poolCwusdcReserve + cwusdcInPerLoop;
|
||||
uint256 usdcReserveAfterOut = poolUsdcReserve - debtUsdcPerLoop;
|
||||
cwusdcOutPerLoop = _getAmountOut(debtUsdcPerLoop, usdcReserveAfterOut, cwusdcReserveAfterIn);
|
||||
cwusdcLossPerLoop = cwusdcInPerLoop - cwusdcOutPerLoop;
|
||||
require(cwusdcLossPerLoop * 10_000 <= cwusdcInPerLoop * maxRoundTripLossBps, "roundtrip loss too high");
|
||||
|
||||
totalCwusdcInAmount = cwusdcInPerLoop * virtualLoops;
|
||||
totalCwusdcOutAmount = cwusdcOutPerLoop * virtualLoops;
|
||||
totalNeutralizedCwusdcAmount = cwusdcLossPerLoop * virtualLoops;
|
||||
}
|
||||
|
||||
function runVirtualProof(bytes32 proofId, uint256 debtUsdcPerLoop, uint256 virtualLoops) external nonReentrant {
|
||||
_runVirtualProofFor(
|
||||
msg.sender, proofId, debtUsdcPerLoop, virtualLoops, 0, msg.sender, bytes32(0), bytes32(0), bytes32(0)
|
||||
);
|
||||
}
|
||||
|
||||
function runVirtualProofTo(bytes32 proofId, uint256 debtUsdcPerLoop, uint256 virtualLoops, address outputRecipient)
|
||||
external
|
||||
nonReentrant
|
||||
{
|
||||
require(outputRecipient != address(0), "zero output");
|
||||
_runVirtualProofFor(
|
||||
outputRecipient, proofId, debtUsdcPerLoop, virtualLoops, 0, msg.sender, bytes32(0), bytes32(0), bytes32(0)
|
||||
);
|
||||
}
|
||||
|
||||
function runVirtualProofExactOutTo(
|
||||
bytes32 proofId,
|
||||
uint256 debtUsdcPerLoop,
|
||||
uint256 virtualLoops,
|
||||
address outputRecipient,
|
||||
uint256 exactOutputAmount,
|
||||
address roundingReceiver,
|
||||
bytes32 iso20022DocumentHash,
|
||||
bytes32 auditEnvelopeHash,
|
||||
bytes32 pegProofHash
|
||||
) external nonReentrant {
|
||||
require(outputRecipient != address(0), "zero output");
|
||||
require(exactOutputAmount > 0, "zero exact output");
|
||||
require(roundingReceiver != address(0), "zero rounding");
|
||||
require(iso20022DocumentHash != bytes32(0), "zero iso hash");
|
||||
require(auditEnvelopeHash != bytes32(0), "zero audit hash");
|
||||
require(pegProofHash != bytes32(0), "zero peg hash");
|
||||
_runVirtualProofFor(
|
||||
outputRecipient,
|
||||
proofId,
|
||||
debtUsdcPerLoop,
|
||||
virtualLoops,
|
||||
exactOutputAmount,
|
||||
roundingReceiver,
|
||||
iso20022DocumentHash,
|
||||
auditEnvelopeHash,
|
||||
pegProofHash
|
||||
);
|
||||
}
|
||||
|
||||
function _runVirtualProofFor(
|
||||
address outputRecipient,
|
||||
bytes32 proofId,
|
||||
uint256 debtUsdcPerLoop,
|
||||
uint256 virtualLoops,
|
||||
uint256 exactOutputAmount,
|
||||
address roundingReceiver,
|
||||
bytes32 iso20022DocumentHash,
|
||||
bytes32 auditEnvelopeHash,
|
||||
bytes32 pegProofHash
|
||||
) internal {
|
||||
require(proofId != bytes32(0), "zero proof");
|
||||
require(!usedProofIds[proofId], "proof used");
|
||||
usedProofIds[proofId] = true;
|
||||
(
|
||||
uint256 collateralXaut,
|
||||
uint256 cwusdcInPerLoop,
|
||||
uint256 cwusdcOutPerLoop,
|
||||
uint256 cwusdcLossPerLoop,
|
||||
uint256 totalCwusdcInAmount,
|
||||
uint256 totalCwusdcOutAmount,
|
||||
uint256 totalNeutralizedCwusdcAmount
|
||||
) = previewVirtualProof(debtUsdcPerLoop, virtualLoops);
|
||||
require(exactOutputAmount <= totalCwusdcOutAmount, "exact output too high");
|
||||
|
||||
uint256 outputAmount = exactOutputAmount == 0 ? totalCwusdcOutAmount : exactOutputAmount;
|
||||
uint256 outputRoundingAmount = totalCwusdcOutAmount - outputAmount;
|
||||
|
||||
xaut.safeTransferFrom(msg.sender, address(this), collateralXaut);
|
||||
cWUSDC.safeTransferFrom(msg.sender, address(this), totalCwusdcInAmount);
|
||||
cWUSDC.safeTransfer(outputRecipient, outputAmount);
|
||||
if (outputRoundingAmount > 0) {
|
||||
cWUSDC.safeTransfer(roundingReceiver, outputRoundingAmount);
|
||||
}
|
||||
cWUSDC.safeTransfer(surplusReceiver, totalNeutralizedCwusdcAmount);
|
||||
xaut.safeTransfer(msg.sender, collateralXaut);
|
||||
|
||||
totalNeutralizedCwusdc += totalNeutralizedCwusdcAmount;
|
||||
totalVirtualLoops += virtualLoops;
|
||||
totalVirtualDebtUsdc += debtUsdcPerLoop * virtualLoops;
|
||||
totalVirtualCwusdcIn += totalCwusdcInAmount;
|
||||
|
||||
emit VirtualProofClosed(
|
||||
proofId,
|
||||
virtualLoops,
|
||||
debtUsdcPerLoop,
|
||||
collateralXaut,
|
||||
cwusdcInPerLoop,
|
||||
cwusdcOutPerLoop,
|
||||
cwusdcLossPerLoop,
|
||||
totalCwusdcInAmount,
|
||||
totalCwusdcOutAmount,
|
||||
totalNeutralizedCwusdcAmount,
|
||||
poolCwusdcReserve,
|
||||
poolUsdcReserve
|
||||
);
|
||||
if (iso20022DocumentHash != bytes32(0) || auditEnvelopeHash != bytes32(0) || pegProofHash != bytes32(0)) {
|
||||
emit VirtualProofAuditEvidence(
|
||||
proofId,
|
||||
msg.sender,
|
||||
outputRecipient,
|
||||
exactOutputAmount,
|
||||
outputRoundingAmount,
|
||||
roundingReceiver,
|
||||
iso20022DocumentHash,
|
||||
auditEnvelopeHash,
|
||||
pegProofHash
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function withdraw(address token, address to, uint256 amount) external onlyOwner nonReentrant {
|
||||
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;
|
||||
}
|
||||
}
|
||||
246
test/flash/DBISEngineXVirtualBatchVault.t.sol
Normal file
246
test/flash/DBISEngineXVirtualBatchVault.t.sol
Normal file
@@ -0,0 +1,246 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
import {DBISEngineXVirtualBatchVault} from "../../contracts/flash/DBISEngineXVirtualBatchVault.sol";
|
||||
import {MockMintableToken} from "../dbis/MockMintableToken.sol";
|
||||
|
||||
contract DBISEngineXVirtualBatchVaultTest is Test {
|
||||
MockMintableToken internal cwusdc;
|
||||
MockMintableToken internal usdc;
|
||||
MockMintableToken internal xaut;
|
||||
DBISEngineXVirtualBatchVault internal vault;
|
||||
|
||||
address internal constant USER = address(0xBEEF);
|
||||
address internal constant SURPLUS_RECEIVER = address(0xCAFE);
|
||||
address internal constant OUTPUT_RECIPIENT = address(0xD00D);
|
||||
address internal constant ROUNDING_RECEIVER = address(0xA11CE);
|
||||
|
||||
uint256 internal constant LIVE_POOL_RESERVE = 85_763_529;
|
||||
uint256 internal constant LENDER_USDC = 5_000_000;
|
||||
uint256 internal constant XAUT_USD_PRICE6 = 3_226_640_000;
|
||||
uint256 internal constant LTV_BPS = 8_000;
|
||||
uint256 internal constant MAX_ROUND_TRIP_LOSS_BPS = 100;
|
||||
bytes32 internal constant ISO_HASH = bytes32(uint256(0x1001));
|
||||
bytes32 internal constant AUDIT_HASH = bytes32(uint256(0x1002));
|
||||
bytes32 internal constant PEG_HASH = bytes32(uint256(0x1003));
|
||||
|
||||
event VirtualProofAuditEvidence(
|
||||
bytes32 indexed proofId,
|
||||
address indexed operator,
|
||||
address indexed outputRecipient,
|
||||
uint256 exactOutputAmount,
|
||||
uint256 outputRoundingAmount,
|
||||
address roundingReceiver,
|
||||
bytes32 iso20022DocumentHash,
|
||||
bytes32 auditEnvelopeHash,
|
||||
bytes32 pegProofHash
|
||||
);
|
||||
|
||||
function setUp() public {
|
||||
cwusdc = new MockMintableToken("Wrapped cWUSDC", "cWUSDC", 6, address(this));
|
||||
usdc = new MockMintableToken("USD Coin", "USDC", 6, address(this));
|
||||
xaut = new MockMintableToken("Tether Gold", "XAUt", 6, address(this));
|
||||
|
||||
vault = new DBISEngineXVirtualBatchVault(
|
||||
address(cwusdc),
|
||||
address(usdc),
|
||||
address(xaut),
|
||||
address(this),
|
||||
SURPLUS_RECEIVER,
|
||||
XAUT_USD_PRICE6,
|
||||
LTV_BPS,
|
||||
MAX_ROUND_TRIP_LOSS_BPS
|
||||
);
|
||||
|
||||
cwusdc.mint(address(this), LIVE_POOL_RESERVE);
|
||||
usdc.mint(address(this), LIVE_POOL_RESERVE + LENDER_USDC);
|
||||
cwusdc.approve(address(vault), type(uint256).max);
|
||||
usdc.approve(address(vault), type(uint256).max);
|
||||
vault.seedPool(LIVE_POOL_RESERVE, LIVE_POOL_RESERVE);
|
||||
vault.fundLender(LENDER_USDC);
|
||||
|
||||
cwusdc.mint(USER, 10_000_000_000_000_000);
|
||||
xaut.mint(USER, 10_000_000);
|
||||
vm.startPrank(USER);
|
||||
cwusdc.approve(address(vault), type(uint256).max);
|
||||
xaut.approve(address(vault), type(uint256).max);
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function testPreviewMatchesLiveMaintainedLoopMath() public view {
|
||||
(
|
||||
uint256 collateralXaut,
|
||||
uint256 cwusdcInPerLoop,
|
||||
uint256 cwusdcOutPerLoop,
|
||||
uint256 cwusdcLossPerLoop,
|
||||
uint256 totalCwusdcInAmount,
|
||||
uint256 totalCwusdcOutAmount,
|
||||
uint256 totalNeutralizedCwusdcAmount
|
||||
) = vault.previewVirtualProof(LENDER_USDC, 3);
|
||||
|
||||
assertEq(collateralXaut, 1_937, "collateral should match live 5 USDC floor");
|
||||
assertEq(cwusdcInPerLoop, 5_325_523, "cwusdc in should match live proof");
|
||||
assertEq(cwusdcOutPerLoop, 5_295_471, "cwusdc out should match live proof");
|
||||
assertEq(cwusdcLossPerLoop, 30_052, "neutralized loss should match live proof");
|
||||
assertEq(totalCwusdcInAmount, 15_976_569, "three-loop cWUSDC in should match live proof");
|
||||
assertEq(totalCwusdcOutAmount, 15_886_413, "three-loop cWUSDC out should match live proof");
|
||||
assertEq(totalNeutralizedCwusdcAmount, 90_156, "three-loop neutralization should match live proof");
|
||||
}
|
||||
|
||||
function testVirtualProofNetSettlesAndKeepsPoolMaintained() public {
|
||||
uint256 userCwusdcBefore = cwusdc.balanceOf(USER);
|
||||
uint256 userXautBefore = xaut.balanceOf(USER);
|
||||
|
||||
vm.prank(USER);
|
||||
vault.runVirtualProof(bytes32("proof-1"), LENDER_USDC, 3);
|
||||
|
||||
assertEq(vault.poolCwusdcReserve(), LIVE_POOL_RESERVE, "pool cWUSDC reserve should not drift");
|
||||
assertEq(vault.poolUsdcReserve(), LIVE_POOL_RESERVE, "pool USDC reserve should not drift");
|
||||
assertEq(vault.currentSurplusCwusdc(), 0, "pool surplus should stay neutralized");
|
||||
assertEq(vault.lenderUsdcAvailable(), LENDER_USDC, "lender bucket should be reusable");
|
||||
|
||||
assertEq(cwusdc.balanceOf(USER), userCwusdcBefore - 90_156, "user only loses neutralized amount");
|
||||
assertEq(cwusdc.balanceOf(SURPLUS_RECEIVER), 90_156, "receiver gets neutralized cWUSDC");
|
||||
assertEq(xaut.balanceOf(USER), userXautBefore, "XAUt collateral should be returned");
|
||||
|
||||
assertEq(vault.totalVirtualLoops(), 3, "virtual loop counter");
|
||||
assertEq(vault.totalVirtualDebtUsdc(), 15_000_000, "virtual debt counter");
|
||||
assertEq(vault.totalVirtualCwusdcIn(), 15_976_569, "virtual cWUSDC in counter");
|
||||
assertEq(vault.totalNeutralizedCwusdc(), 90_156, "neutralized counter");
|
||||
assertTrue(vault.usedProofIds(bytes32("proof-1")), "proof id should be consumed");
|
||||
}
|
||||
|
||||
function testVirtualProofCanSendOutputToRecipient() public {
|
||||
uint256 userCwusdcBefore = cwusdc.balanceOf(USER);
|
||||
uint256 userXautBefore = xaut.balanceOf(USER);
|
||||
|
||||
vm.prank(USER);
|
||||
vault.runVirtualProofTo(bytes32("proof-to"), LENDER_USDC, 3, OUTPUT_RECIPIENT);
|
||||
|
||||
assertEq(vault.poolCwusdcReserve(), LIVE_POOL_RESERVE, "pool cWUSDC reserve should not drift");
|
||||
assertEq(vault.poolUsdcReserve(), LIVE_POOL_RESERVE, "pool USDC reserve should not drift");
|
||||
assertEq(cwusdc.balanceOf(USER), userCwusdcBefore - 15_976_569, "sender supplies gross cWUSDC");
|
||||
assertEq(cwusdc.balanceOf(OUTPUT_RECIPIENT), 15_886_413, "recipient gets cWUSDC output");
|
||||
assertEq(cwusdc.balanceOf(SURPLUS_RECEIVER), 90_156, "receiver gets neutralized cWUSDC");
|
||||
assertEq(xaut.balanceOf(USER), userXautBefore, "XAUt collateral should be returned");
|
||||
}
|
||||
|
||||
function testVirtualProofExactOutToAnchorsAuditProofsAndSendsRounding() public {
|
||||
uint256 userCwusdcBefore = cwusdc.balanceOf(USER);
|
||||
uint256 userXautBefore = xaut.balanceOf(USER);
|
||||
uint256 exactOutput = 15_886_000;
|
||||
bytes32 proofId = bytes32("proof-audit");
|
||||
|
||||
vm.expectEmit(true, true, true, true, address(vault));
|
||||
emit VirtualProofAuditEvidence(
|
||||
proofId, USER, OUTPUT_RECIPIENT, exactOutput, 413, ROUNDING_RECEIVER, ISO_HASH, AUDIT_HASH, PEG_HASH
|
||||
);
|
||||
vm.prank(USER);
|
||||
vault.runVirtualProofExactOutTo(
|
||||
proofId,
|
||||
LENDER_USDC,
|
||||
3,
|
||||
OUTPUT_RECIPIENT,
|
||||
exactOutput,
|
||||
ROUNDING_RECEIVER,
|
||||
ISO_HASH,
|
||||
AUDIT_HASH,
|
||||
PEG_HASH
|
||||
);
|
||||
|
||||
assertEq(vault.poolCwusdcReserve(), LIVE_POOL_RESERVE, "pool cWUSDC reserve should not drift");
|
||||
assertEq(vault.poolUsdcReserve(), LIVE_POOL_RESERVE, "pool USDC reserve should not drift");
|
||||
assertEq(cwusdc.balanceOf(USER), userCwusdcBefore - 15_976_569, "sender supplies gross cWUSDC");
|
||||
assertEq(cwusdc.balanceOf(OUTPUT_RECIPIENT), exactOutput, "recipient gets exact cWUSDC");
|
||||
assertEq(cwusdc.balanceOf(ROUNDING_RECEIVER), 413, "rounding receiver gets output dust");
|
||||
assertEq(cwusdc.balanceOf(SURPLUS_RECEIVER), 90_156, "receiver gets neutralized cWUSDC");
|
||||
assertEq(xaut.balanceOf(USER), userXautBefore, "XAUt collateral should be returned");
|
||||
}
|
||||
|
||||
function testVirtualLoopCountScalesTotalsButNotSequentialCollateral() public view {
|
||||
uint256 loops = 938_874_924;
|
||||
(
|
||||
uint256 collateralXaut,
|
||||
uint256 cwusdcInPerLoop,,
|
||||
uint256 cwusdcLossPerLoop,
|
||||
uint256 totalCwusdcInAmount,,
|
||||
uint256 totalNeutralizedCwusdcAmount
|
||||
) = vault.previewVirtualProof(LENDER_USDC, loops);
|
||||
|
||||
assertEq(collateralXaut, 1_937, "sequential virtual batch reuses one loop of XAUt collateral");
|
||||
assertEq(cwusdcInPerLoop, 5_325_523, "per-loop input stays constant");
|
||||
assertEq(cwusdcLossPerLoop, 30_052, "per-loop neutralization stays constant");
|
||||
assertEq(totalCwusdcInAmount, 5_000_000_001_885_252, "virtual batch covers 5B cWUSDC gross");
|
||||
assertEq(totalNeutralizedCwusdcAmount, 28_215_069_216_048, "5B batch neutralized amount");
|
||||
}
|
||||
|
||||
function testPreviewRevertsWhenPoolIsNotMaintained() public {
|
||||
DBISEngineXVirtualBatchVault unevenVault = new DBISEngineXVirtualBatchVault(
|
||||
address(cwusdc),
|
||||
address(usdc),
|
||||
address(xaut),
|
||||
address(this),
|
||||
SURPLUS_RECEIVER,
|
||||
XAUT_USD_PRICE6,
|
||||
LTV_BPS,
|
||||
MAX_ROUND_TRIP_LOSS_BPS
|
||||
);
|
||||
cwusdc.mint(address(this), LIVE_POOL_RESERVE + 1);
|
||||
usdc.mint(address(this), LIVE_POOL_RESERVE + LENDER_USDC);
|
||||
cwusdc.approve(address(unevenVault), type(uint256).max);
|
||||
usdc.approve(address(unevenVault), type(uint256).max);
|
||||
unevenVault.seedPool(LIVE_POOL_RESERVE + 1, LIVE_POOL_RESERVE);
|
||||
unevenVault.fundLender(LENDER_USDC);
|
||||
|
||||
vm.expectRevert(bytes("pool not maintained"));
|
||||
unevenVault.previewVirtualProof(LENDER_USDC, 1);
|
||||
}
|
||||
|
||||
function testRunVirtualProofRejectsDuplicateProofIds() public {
|
||||
vm.prank(USER);
|
||||
vault.runVirtualProof(bytes32("proof-1"), LENDER_USDC, 1);
|
||||
|
||||
vm.expectRevert(bytes("proof used"));
|
||||
vm.prank(USER);
|
||||
vault.runVirtualProof(bytes32("proof-1"), LENDER_USDC, 1);
|
||||
}
|
||||
|
||||
function testRunVirtualProofToRejectsZeroRecipient() public {
|
||||
vm.expectRevert(bytes("zero output"));
|
||||
vm.prank(USER);
|
||||
vault.runVirtualProofTo(bytes32("proof-zero"), LENDER_USDC, 1, address(0));
|
||||
}
|
||||
|
||||
function testRunVirtualProofExactOutToRejectsMissingAuditHashes() public {
|
||||
vm.expectRevert(bytes("zero iso hash"));
|
||||
vm.prank(USER);
|
||||
vault.runVirtualProofExactOutTo(
|
||||
bytes32("proof-no-iso"),
|
||||
LENDER_USDC,
|
||||
1,
|
||||
OUTPUT_RECIPIENT,
|
||||
1,
|
||||
ROUNDING_RECEIVER,
|
||||
bytes32(0),
|
||||
AUDIT_HASH,
|
||||
PEG_HASH
|
||||
);
|
||||
}
|
||||
|
||||
function testRunVirtualProofExactOutToRejectsOutputAbovePreview() public {
|
||||
vm.expectRevert(bytes("exact output too high"));
|
||||
vm.prank(USER);
|
||||
vault.runVirtualProofExactOutTo(
|
||||
bytes32("proof-too-high"),
|
||||
LENDER_USDC,
|
||||
1,
|
||||
OUTPUT_RECIPIENT,
|
||||
5_295_472,
|
||||
ROUNDING_RECEIVER,
|
||||
ISO_HASH,
|
||||
AUDIT_HASH,
|
||||
PEG_HASH
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user