// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "forge-std/Test.sol"; import "../../contracts/vault/Ledger.sol"; import "../../contracts/vault/RegulatedEntityRegistry.sol"; import "../../contracts/vault/XAUOracle.sol"; import "../../contracts/vault/RateAccrual.sol"; import "../../contracts/oracle/Aggregator.sol"; contract LedgerTest is Test { Ledger public ledger; RegulatedEntityRegistry public entityRegistry; XAUOracle public xauOracle; RateAccrual public rateAccrual; Aggregator public ethPriceFeed; address public admin = address(0x1); address public vault = address(0x2); address public entity = address(0x3); address public eth = address(0); // Native ETH uint256 public constant ETH_PRICE_XAU = 0.05e18; // 1 ETH = 0.05 oz XAU (example) function setUp() public { vm.startPrank(admin); // Deploy Regulated Entity Registry entityRegistry = new RegulatedEntityRegistry(admin); entityRegistry.registerEntity(entity, keccak256("US"), new address[](0)); // Deploy ETH price feed ethPriceFeed = new Aggregator("ETH/XAU", admin, 3600, 50); ethPriceFeed.addTransmitter(admin); ethPriceFeed.updateAnswer(ETH_PRICE_XAU); // Deploy XAU Oracle xauOracle = new XAUOracle(admin); xauOracle.addPriceFeed(address(ethPriceFeed), 10000); // 100% weight xauOracle.updatePrice(); // Deploy Rate Accrual rateAccrual = new RateAccrual(admin); rateAccrual.setInterestRate(address(0), 500); // 5% annual rateAccrual.setInterestRate(address(0x100), 500); // 5% for test currency // Deploy Ledger ledger = new Ledger(admin, address(xauOracle), address(rateAccrual)); // Grant vault role ledger.grantVaultRole(vault); // Set risk parameters for ETH (Ledger requires liquidationRatio <= 10000) ledger.setRiskParameters( eth, 1_000_000e18, // debt ceiling 9000, // liquidation ratio (90%) 50000 // credit multiplier (5x) ); vm.stopPrank(); } function test_ModifyCollateral() public { vm.prank(vault); ledger.modifyCollateral(vault, eth, int256(10e18)); assertEq(ledger.collateral(vault, eth), 10e18); vm.prank(vault); ledger.modifyCollateral(vault, eth, -int256(5e18)); assertEq(ledger.collateral(vault, eth), 5e18); } function test_ModifyCollateral_RevertIfNotRegistered() public { address unregisteredAsset = address(0x999); vm.prank(vault); vm.expectRevert("Ledger: asset not registered"); ledger.modifyCollateral(vault, unregisteredAsset, int256(10e18)); } function test_ModifyCollateral_RevertIfInsufficient() public { vm.prank(vault); ledger.modifyCollateral(vault, eth, int256(10e18)); vm.prank(vault); vm.expectRevert("Ledger: insufficient collateral"); ledger.modifyCollateral(vault, eth, -int256(20e18)); } function test_ModifyDebt() public { address currency = address(0x100); vm.prank(vault); ledger.modifyDebt(vault, currency, int256(1000e18)); assertEq(ledger.debt(vault, currency), 1000e18); vm.prank(vault); ledger.modifyDebt(vault, currency, -int256(500e18)); assertEq(ledger.debt(vault, currency), 500e18); } function test_ModifyDebt_AccruesInterest() public { address currency = address(0x100); vm.prank(vault); ledger.modifyDebt(vault, currency, int256(1000e18)); uint256 accumulator1 = ledger.rateAccumulator(currency); // Fast forward time (1 year) vm.warp(block.timestamp + 365 days); vm.prank(vault); ledger.modifyDebt(vault, currency, int256(0)); // Trigger accrual uint256 accumulator2 = ledger.rateAccumulator(currency); assertGt(accumulator2, accumulator1, "Interest should accrue"); } function test_GetVaultHealth() public { // Deposit 10 ETH collateral vm.prank(vault); ledger.modifyCollateral(vault, eth, int256(10e18)); // Get health (no debt) (uint256 healthRatio, uint256 collateralValue, uint256 debtValue) = ledger.getVaultHealth(vault); assertEq(debtValue, 0); assertGt(collateralValue, 0); assertEq(healthRatio, type(uint256).max, "No debt = infinite health"); } function test_CanBorrow() public { address currency = address(0x100); // Set risk parameters for currency (requires PARAM_MANAGER_ROLE) vm.prank(admin); ledger.setRiskParameters(currency, 1_000_000e18, 9000, 50000); // Deposit 10 ETH collateral (worth 0.5 oz XAU at 0.05 ETH/XAU) // maxBorrowValue = 0.5e18 * 5 = 2.5e18; min health needs newDebtValue <= ~0.55e18 vm.prank(vault); ledger.modifyCollateral(vault, eth, int256(10e18)); // Check if can borrow small amount (0.5e18 fits within both limits) (bool canBorrow, bytes32 reasonCode) = ledger.canBorrow(vault, currency, 0.5e18); assertTrue(canBorrow, "Should be able to borrow"); assertEq(reasonCode, bytes32(0)); } function test_CanBorrow_RevertIfDebtCeilingExceeded() public { address currency = address(0x100); // Set low debt ceiling (requires PARAM_MANAGER_ROLE) vm.prank(admin); ledger.setRiskParameters(currency, 1000e18, 9000, 50000); vm.prank(vault); ledger.modifyCollateral(vault, eth, int256(100e18)); // Try to borrow more than ceiling (bool canBorrow, bytes32 reasonCode) = ledger.canBorrow(vault, currency, 2000e18); assertFalse(canBorrow); assertEq(reasonCode, keccak256("DEBT_CEILING_EXCEEDED")); } function test_SetRiskParameters() public { address asset = address(0x200); vm.prank(admin); ledger.setRiskParameters(asset, 1_000_000e18, 9000, 50000); assertEq(ledger.debtCeiling(asset), 1_000_000e18); assertEq(ledger.liquidationRatio(asset), 9000); assertEq(ledger.creditMultiplier(asset), 50000); assertTrue(ledger.isRegisteredAsset(asset)); } function test_SetRiskParameters_RevertIfInvalidRatio() public { address asset = address(0x200); vm.prank(admin); vm.expectRevert("Ledger: invalid liquidation ratio"); ledger.setRiskParameters(asset, 1_000_000e18, 15000, 50000); // > 100% vm.prank(admin); vm.expectRevert("Ledger: invalid credit multiplier"); ledger.setRiskParameters(asset, 1_000_000e18, 9000, 150000); // > 10x } }