// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {Test} from "forge-std/Test.sol"; import {PluginOracleAdapter} from "../src/PluginOracleAdapter.sol"; import {ChainlinkScaledAdapter} from "../src/ChainlinkScaledAdapter.sol"; import {PluginStalenessDefaults} from "../src/PluginStalenessDefaults.sol"; import {IPluginFeed, IAggregatorV3Like} from "../src/interfaces/IPluginFeed.sol"; import {MockPluginFeed} from "./mocks/Mocks.sol"; contract PluginOracleAdapterTest is Test { MockPluginFeed feed; PluginOracleAdapter adapter; function setUp() public { vm.warp(1_000_000); feed = new MockPluginFeed(16); feed.set(3.4e14, block.timestamp); // 0.034 with 16 dp adapter = new PluginOracleAdapter(IPluginFeed(address(feed)), 1 hours, 0, 0); } function test_NativeDecimals() public view { (, int256 a,,,) = adapter.latestRoundData(); assertEq(a, 3.4e14); assertEq(adapter.decimals(), 16); assertEq(adapter.latestPrice(), 3.4e14); } function test_ScaledWrapperTo8() public { ChainlinkScaledAdapter s = new ChainlinkScaledAdapter(IAggregatorV3Like(address(adapter))); (, int256 a,,,) = s.latestRoundData(); assertEq(s.decimals(), 8); assertEq(a, 3.4e6); MockPluginFeed f6 = new MockPluginFeed(6); f6.set(1.5e6, block.timestamp); ChainlinkScaledAdapter up = new ChainlinkScaledAdapter(IAggregatorV3Like(address(new PluginOracleAdapter(IPluginFeed(address(f6)), 1 hours, 0, 0)))); (, a,,,) = up.latestRoundData(); assertEq(a, 1.5e8); } function test_ScaledWrapperPrecisionLoss() public { feed.set(1, block.timestamp); ChainlinkScaledAdapter s = new ChainlinkScaledAdapter(IAggregatorV3Like(address(adapter))); vm.expectRevert(); s.latestRoundData(); } function test_RevertStale() public { vm.warp(block.timestamp + 1 hours + 1); vm.expectRevert(); adapter.latestRoundData(); } function test_RevertZeroOrFutureUpdatedAt() public { feed.set(1e16, 0); vm.expectRevert(); adapter.latestRoundData(); feed.set(1e16, block.timestamp + 1); vm.expectRevert(); adapter.latestRoundData(); } function test_RevertNonPositive() public { feed.set(0, block.timestamp); vm.expectRevert(abi.encodeWithSelector(PluginOracleAdapter.NonPositivePrice.selector, int256(0))); adapter.latestRoundData(); feed.set(-1, block.timestamp); vm.expectRevert(); adapter.latestRoundData(); } function test_RevertIncompleteRound() public { feed.setRounds(5, 4); vm.expectRevert(abi.encodeWithSelector(PluginOracleAdapter.IncompleteRound.selector, uint80(5), uint80(4))); adapter.latestRoundData(); } function test_OptionalBounds() public { PluginOracleAdapter b = new PluginOracleAdapter(IPluginFeed(address(feed)), 1 hours, 1e14, 1e15); assertEq(b.latestPrice(), 3.4e14); feed.set(2e15, block.timestamp); vm.expectRevert(abi.encodeWithSelector(PluginOracleAdapter.OutOfBounds.selector, int256(2e15))); b.latestRoundData(); assertEq(adapter.latestPrice(), 2e15); // bounds off by default } function test_NoAccessCustomError() public { feed.setNoAccess(true); vm.expectRevert(abi.encodeWithSelector(PluginOracleAdapter.FeedAccessDenied.selector, address(feed))); adapter.latestRoundData(); vm.expectRevert(abi.encodeWithSelector(PluginOracleAdapter.FeedAccessDenied.selector, address(feed))); new PluginOracleAdapter(IPluginFeed(address(feed)), 1 hours, 0, 0); } function test_OtherRevertWrapped() public { feed.setCustomRevert(true); vm.expectRevert( abi.encodeWithSelector( PluginOracleAdapter.FeedReverted.selector, address(feed), abi.encodeWithSelector(MockPluginFeed.Boom.selector) ) ); adapter.latestRoundData(); } function test_DefaultStalenessTable() public { address xdcUsdt = 0x896CC6fE8AD04dF9f481332606c445768e286076; assertEq(PluginStalenessDefaults.get(xdcUsdt), 3600); assertEq(PluginStalenessDefaults.get(0xd73aDd20E3905410B3817725CCabbE639B75B7Cc), 259200); // AAPL/USD daily // unknown feed + 0 => revert; explicit value overrides vm.expectRevert(abi.encodeWithSelector(PluginOracleAdapter.UnknownStaleness.selector, address(feed))); new PluginOracleAdapter(IPluginFeed(address(feed)), 0, 0, 0); // known feed + 0 => table default (mock code etched at a real feed address) vm.etch(xdcUsdt, address(feed).code); vm.store(xdcUsdt, bytes32(0), bytes32(uint256(16))); // _decimals slot PluginOracleAdapter d = new PluginOracleAdapter(IPluginFeed(xdcUsdt), 0, 0, 0); assertEq(d.maxStaleness(), 3600); PluginOracleAdapter o = new PluginOracleAdapter(IPluginFeed(xdcUsdt), 600, 0, 0); assertEq(o.maxStaleness(), 600); } function test_RevertBadConfig() public { vm.expectRevert(PluginOracleAdapter.ZeroAddress.selector); new PluginOracleAdapter(IPluginFeed(address(0)), 1, 0, 0); vm.expectRevert(PluginOracleAdapter.BadConfig.selector); new PluginOracleAdapter(IPluginFeed(address(feed)), 1, 10, 5); } function testFuzz_StalenessBoundary(uint256 dt) public { dt = bound(dt, 0, 2 hours); vm.warp(block.timestamp + dt); if (dt > 1 hours) vm.expectRevert(); adapter.latestRoundData(); } }