// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.24; import {Test, console} from "forge-std/Test.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {USDR} from "../src/USDR.sol"; import {ReserveManager} from "../src/ReserveManager.sol"; import {SettlementEscrow} from "../src/SettlementEscrow.sol"; import {SolvencyGuard} from "../src/SolvencyGuard.sol"; import {PSM} from "../src/PSM.sol"; import {RainMarketAdapter} from "../src/adapters/RainMarketAdapter.sol"; /// @notice Full-system E2E on a FORK of Arbitrum One: real USDT (USD₮0), the real /// live Rain pool, our whole stack deployed and exercised end-to-end. /// Run: forge test --mc E2EForkTest --fork-url https://arb1.arbitrum.io/rpc contract E2EForkTest is Test { // canonical Arbitrum One addresses address constant USDT = 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9; // USD₮0 (rain-sdk prod config) address constant USDC = 0xaf88d065e77c8cC2239327C5EDb3A432268e5831; // native USDC // live Rain pool (crypto market-cap market, created 2026-07-04) address constant LIVE_POOL = 0xd190f9a7490d7fEB44473211f654d7f9CdAEF363; USDR usdr; ReserveManager reserve; SettlementEscrow escrow; SolvencyGuard guard; PSM psm; RainMarketAdapter adapter; address timelock = makeAddr("timelock"); address guardian = makeAddr("guardian"); address trader = makeAddr("trader"); function setUp() public { if (block.chainid != 42161) return; usdr = new USDR(); escrow = new SettlementEscrow(USDT, USDC); reserve = new ReserveManager(USDT, USDC); guard = new SolvencyGuard(); psm = new PSM(address(usdr), address(reserve), guardian); adapter = new RainMarketAdapter(address(guard), timelock); usdr.addMinter(address(psm)); usdr.finalizeMinters(); reserve.setup(address(psm), address(escrow), address(guard)); escrow.setup(address(reserve)); escrow.finalize(); guard.setup(address(reserve), makeAddr("engine")); guard.addAdapter(address(adapter)); guard.finalize(); deal(USDT, trader, 1_000_000e6); // real USDT via fork cheatcode } function test_e2e_fullFlow_onArbitrumFork() public { if (block.chainid != 42161) { vm.skip(true); } // 1. trader mints USDR 1:1 through the PSM with REAL USDT vm.startPrank(trader); IERC20(USDT).approve(address(reserve), 100_000e6); psm.mint(USDT, 100_000e6); vm.stopPrank(); assertEq(usdr.balanceOf(trader), 100_000e18); assertEq(reserve.reserveBalance(), 100_000e18); console.log("1. PSM mint vs real USDT: OK"); // 2. register the LIVE Rain pool; adapter computes net MaxLoss from real state vm.prank(timelock); adapter.registerMarket(LIVE_POOL, keccak256("CRYPTO_PRICE"), 10_000); (uint256 normal, uint256 stress) = adapter.previewExposure(LIVE_POOL); console.log("2. live pool exposure (normal, stress):", normal, stress); assertTrue(guard.isSolvent()); // 3. escrow rebalances to exactly Stress-MaxLoss reserve.rebalanceEscrow(); assertEq(escrow.escrowBalance(), guard.stressMaxLoss()); console.log("3. escrow == Stress-MaxLoss: OK"); // 4. redemption capped by slack: full exit possible only up to Reserve - MaxLoss uint256 slack = reserve.slack(); vm.startPrank(trader); if (slack >= 50_000e18) { psm.redeem(USDT, 50_000e18); assertEq(usdr.balanceOf(trader), 50_000e18); console.log("4. slack redemption: OK"); } else { vm.expectRevert(); psm.redeem(USDT, slack + 1e18); console.log("4. beyond-slack redemption refused: OK"); } vm.stopPrank(); // 5. invariant holds throughout assertTrue(guard.isSolvent()); console.log("5. invariant held end-to-end: OK"); } }