// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "forge-std/Test.sol"; import "../src/ExchangeRateOracle.sol"; import "../src/interfaces/IChainlinkPriceFeed.sol"; contract MockPriceFeed is IChainlinkPriceFeed { int256 private _price; uint256 private _updatedAt; uint8 private _decimals; constructor(int256 price, uint8 dec) { _price = price; _updatedAt = block.timestamp; _decimals = dec; } function setPrice(int256 price) external { _price = price; _updatedAt = block.timestamp; } function setUpdatedAt(uint256 ts) external { _updatedAt = ts; } function latestRoundData() external view override returns (uint80, int256, uint256, uint256, uint80) { return (1, _price, block.timestamp, _updatedAt, 1); } function decimals() external view override returns (uint8) { return _decimals; } function description() external pure override returns (string memory) { return "Mock Price Feed"; } } contract ExchangeRateOracleTest is Test { ExchangeRateOracle oracle; MockPriceFeed cnyFeed; MockPriceFeed jpyFeed; address admin = address(1); function setUp() public { vm.startPrank(admin); oracle = new ExchangeRateOracle(); oracle.initialize(900, admin); // 15 min staleness cnyFeed = new MockPriceFeed(7_2500_0000, 8); // 7.25 CNY/USD jpyFeed = new MockPriceFeed(150_0000_0000, 8); // 150 JPY/USD oracle.setPriceFeed("CNY", address(cnyFeed)); oracle.setPriceFeed("JPY", address(jpyFeed)); vm.stopPrank(); } function test_GetRateCNY() public view { uint256 rate = oracle.getRate("CNY"); assertEq(rate, 7_2500_0000); } function test_GetRateJPY() public view { uint256 rate = oracle.getRate("JPY"); assertEq(rate, 150_0000_0000); } function test_UnsupportedCurrencyReverts() public { vm.expectRevert("Oracle: unsupported currency"); oracle.getRate("EUR"); } function test_StaleDataReverts() public { // Ensure block.timestamp is large enough to avoid underflow vm.warp(2000); // 设置 CNY feed 过期(20分钟前更新) cnyFeed.setUpdatedAt(block.timestamp - 1200); vm.expectRevert("Oracle: stale price data"); oracle.getRate("CNY"); } function test_GetRateDetails() public view { (uint256 rate, uint256 updatedAt, uint8 decimals) = oracle.getRateDetails("CNY"); assertEq(rate, 7_2500_0000); assertGt(updatedAt, 0); assertEq(decimals, 8); } function test_SetMaxStaleness() public { vm.prank(admin); oracle.setMaxStaleness(600); // 10 min assertEq(oracle.maxStaleness(), 600); } function test_RemovePriceFeed() public { vm.prank(admin); oracle.removePriceFeed("CNY"); vm.expectRevert("Oracle: unsupported currency"); oracle.getRate("CNY"); } function test_SupportedCurrencyCount() public view { assertEq(oracle.supportedCurrencyCount(), 2); } function test_InvalidPriceReverts() public { MockPriceFeed badFeed = new MockPriceFeed(0, 8); vm.prank(admin); oracle.setPriceFeed("BAD", address(badFeed)); vm.expectRevert("Oracle: invalid price"); oracle.getRate("BAD"); } }