// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import {Test, console} from "forge-std/Test.sol"; import {Aggregator} from "../../contracts/oracle/Aggregator.sol"; import {CCIPSender} from "../../contracts/ccip/CCIPSender.sol"; contract NetworkResilienceTest is Test { Aggregator public aggregator; address public transmitter1; address public transmitter2; address public transmitter3; function setUp() public { aggregator = new Aggregator("ETH/USD", address(this), 60, 50); transmitter1 = address(0x111); transmitter2 = address(0x222); transmitter3 = address(0x333); aggregator.addTransmitter(transmitter1); aggregator.addTransmitter(transmitter2); aggregator.addTransmitter(transmitter3); } function testOracleContinuesWithOneTransmitterFailure() public { uint256 price1 = 25000000000; uint256 price2 = 25100000000; // Transmitter 1 updates vm.prank(transmitter1); aggregator.updateAnswer(price1); // Fast forward past heartbeat to create new round vm.warp(block.timestamp + 61); // Transmitter 1 fails, transmitter 2 continues with new round vm.prank(transmitter2); aggregator.updateAnswer(price2); (uint256 roundId, int256 answer, , , ) = aggregator.latestRoundData(); assertEq(uint256(answer), price2, "Oracle should continue with remaining transmitters"); assertGt(roundId, 1, "Should create new round"); } function testOracleHandlesMultipleTransmitterFailures() public { uint256 price1 = 25000000000; uint256 price2 = 25100000000; uint256 price3 = 25200000000; // All transmitters update in round 1 vm.prank(transmitter1); aggregator.updateAnswer(price1); vm.prank(transmitter2); aggregator.updateAnswer(price1); vm.prank(transmitter3); aggregator.updateAnswer(price1); // Fast forward past heartbeat to create new round vm.warp(block.timestamp + 61); // Two transmitters fail, one continues with new round vm.prank(transmitter3); aggregator.updateAnswer(price3); (uint256 roundId, int256 answer, , , ) = aggregator.latestRoundData(); assertEq(uint256(answer), price3, "Oracle should continue with remaining transmitter"); assertGt(roundId, 1, "Should create new round"); } function testOracleRecoversFromPause() public { uint256 price1 = 25000000000; uint256 price2 = 25100000000; // Update before pause vm.prank(transmitter1); aggregator.updateAnswer(price1); // Verify initial state (uint256 roundId1, int256 answer1, , , ) = aggregator.latestRoundData(); assertEq(uint256(answer1), price1, "Initial price should be set"); // Pause oracle (must be called by admin) aggregator.pause(); // Try to update (should fail) vm.prank(transmitter1); vm.expectRevert("Aggregator: paused"); aggregator.updateAnswer(price2); // Unpause (must be called by admin) aggregator.unpause(); // Fast forward past heartbeat to ensure new round is created vm.warp(block.timestamp + 61); // Update should work again vm.prank(transmitter1); aggregator.updateAnswer(price2); (uint256 roundId2, int256 answer2, , , ) = aggregator.latestRoundData(); assertEq(uint256(answer2), price2, "Oracle should recover after unpause"); assertGt(roundId2, roundId1, "Should create new round after unpause"); } function testOracleHandlesStaleData() public { uint256 price1 = 25000000000; // Initial update vm.prank(transmitter1); aggregator.updateAnswer(price1); // Fast forward past heartbeat vm.warp(block.timestamp + 61); // New update should create new round vm.prank(transmitter1); aggregator.updateAnswer(price1); (uint256 roundId, , , , ) = aggregator.latestRoundData(); assertGt(roundId, 1, "Should create new round after heartbeat"); } function testOracleHandlesPriceDeviation() public { uint256 price1 = 25000000000; // $250.00 uint256 price2 = 25125000000; // $251.25 (0.5% deviation) // Initial update vm.prank(transmitter1); aggregator.updateAnswer(price1); // Update with deviation vm.prank(transmitter1); aggregator.updateAnswer(price2); (uint256 roundId, int256 answer, , , ) = aggregator.latestRoundData(); assertEq(uint256(answer), price2, "Oracle should accept price deviation"); assertGt(roundId, 1, "Should create new round on deviation"); } }