// 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 ); } }