// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "forge-std/Test.sol"; import "../../contracts/iso4217w/oracle/ReserveOracle.sol"; contract ReserveOracleTest is Test { ReserveOracle public oracle; address public admin = address(0x1); address public oracle1 = address(0x2); address public oracle2 = address(0x3); address public oracle3 = address(0x4); string public constant CURRENCY_CODE = "USD"; uint256 public constant STALENESS_THRESHOLD = 3600; // 1 hour uint256 public constant QUORUM_SIZE = 3; function setUp() public { vm.warp(STALENESS_THRESHOLD + 1); // Avoid underflow: block.timestamp - stalenessThreshold vm.startPrank(admin); oracle = new ReserveOracle(admin, QUORUM_SIZE, STALENESS_THRESHOLD); // Add oracles (also grants ORACLE_ROLE) oracle.addOracle(oracle1); oracle.addOracle(oracle2); oracle.addOracle(oracle3); vm.stopPrank(); } function test_SubmitReserveReport() public { uint256 reserve = 10000e2; bytes32 attestationHash = keccak256("attestation"); // Submit from all 3 oracles to reach quorum (QUORUM_SIZE=3) vm.prank(oracle1); oracle.submitReserveReport(CURRENCY_CODE, reserve, attestationHash); vm.prank(oracle2); oracle.submitReserveReport(CURRENCY_CODE, reserve, attestationHash); vm.prank(oracle3); oracle.submitReserveReport(CURRENCY_CODE, reserve, attestationHash); (uint256 verifiedReserve, uint256 timestamp) = oracle.getVerifiedReserve(CURRENCY_CODE); assertEq(verifiedReserve, reserve); assertGt(timestamp, 0); } function test_GetVerifiedReserve_Quorum() public { // Submit reports from all 3 oracles uint256 reserve = 10000e2; bytes32 attestationHash = keccak256("attestation"); vm.prank(oracle1); oracle.submitReserveReport(CURRENCY_CODE, reserve, attestationHash); vm.prank(oracle2); oracle.submitReserveReport(CURRENCY_CODE, reserve, attestationHash); vm.prank(oracle3); oracle.submitReserveReport(CURRENCY_CODE, reserve, attestationHash); (uint256 verifiedReserve, ) = oracle.getVerifiedReserve(CURRENCY_CODE); // Should get consensus (all same) assertEq(verifiedReserve, reserve); } function test_IsQuorumMet() public { uint256 reserve = 10000e2; bytes32 attestationHash = keccak256("attestation"); // Submit from 2 oracles (not quorum yet) vm.prank(oracle1); oracle.submitReserveReport(CURRENCY_CODE, reserve, attestationHash); vm.prank(oracle2); oracle.submitReserveReport(CURRENCY_CODE, reserve, attestationHash); (bool quorum1, ) = oracle.isQuorumMet(CURRENCY_CODE); assertFalse(quorum1, "Should not have quorum with 2/3"); // Submit from third oracle vm.prank(oracle3); oracle.submitReserveReport(CURRENCY_CODE, reserve, attestationHash); (bool quorum2, ) = oracle.isQuorumMet(CURRENCY_CODE); assertTrue(quorum2, "Should have quorum with 3/3"); } function test_RemoveStaleReports() public { uint256 reserve = 10000e2; bytes32 attestationHash = keccak256("attestation"); vm.prank(oracle1); oracle.submitReserveReport(CURRENCY_CODE, reserve, attestationHash); // Fast forward time to make reports stale vm.warp(block.timestamp + STALENESS_THRESHOLD + 1); // Stale reports should not count towards quorum (bool quorum, ) = oracle.isQuorumMet(CURRENCY_CODE); assertFalse(quorum, "Stale reports should not count"); } function test_GetConsensusReserve() public { bytes32 attestationHash = keccak256("attestation"); // Submit different reports vm.prank(oracle1); oracle.submitReserveReport(CURRENCY_CODE, 10000e2, attestationHash); vm.prank(oracle2); oracle.submitReserveReport(CURRENCY_CODE, 10001e2, attestationHash); // 1 USD difference vm.prank(oracle3); oracle.submitReserveReport(CURRENCY_CODE, 9999e2, attestationHash); // 1 USD difference // Consensus should be median or average uint256 consensus = oracle.getConsensusReserve(CURRENCY_CODE); assertGt(consensus, 0, "Should have consensus value"); } }