127 lines
3.4 KiB
Solidity
127 lines
3.4 KiB
Solidity
// 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");
|
||
}
|
||
}
|