diff --git a/src/feeds/PriceFeedNoStale.sol b/src/feeds/PriceFeedNoStale.sol new file mode 100644 index 0000000..eade3c4 --- /dev/null +++ b/src/feeds/PriceFeedNoStale.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "src/interfaces/IChainlinkFeed.sol"; + +contract PriceFeedNoStale { + IChainlinkFeed public immutable feed; + string public description; + + constructor( + address _feed + ) { + feed = IChainlinkFeed(_feed); + require(feed.decimals() == 18, "Wrong Decimals"); + description = feed.description(); + } + + /** + * @notice Retrieves the latest round data for the asset token price feed + * @return roundId Will return 0 + * @return usdPrice The latest asset price in USD with 18 decimals + * @return startedAt Will return 0 + * @return updatedAt Will return block.timestamp + * @return answeredInRound Will return 0 + */ + function latestRoundData() + public + view + returns ( + uint80 roundId, + int256 usdPrice, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + return (0, feed.latestAnswer(), 0, block.timestamp, 0 ); + } + + /** + * @notice Returns the latest price only + * @dev Unlike chainlink oracles, the latestAnswer will always be the same as in the latestRoundData + * @return int256 Returns the last finalized price of the chainlink oracle + */ + function latestAnswer() external view returns (int256) { + return feed.latestAnswer(); + } + + function decimals() external pure returns (uint8) { + return 18; + } +} diff --git a/test/feedForkTests/ChainlinkBridgeAssetBase.t.sol b/test/feedForkTests/ChainlinkBridgeAssetBase.t.sol index fe893cd..a5e13b4 100644 --- a/test/feedForkTests/ChainlinkBridgeAssetBase.t.sol +++ b/test/feedForkTests/ChainlinkBridgeAssetBase.t.sol @@ -7,7 +7,7 @@ import "src/feeds/ChainlinkBasePriceFeed.sol"; import {ChainlinkBridgeAssetFeed} from "src/feeds/ChainlinkBridgeAssetFeed.sol"; abstract contract ChainlinkBridgeAssetBase is Test { - ChainlinkBridgeAssetFeed feed; + ChainlinkBridgeAssetFeed internal feed; ChainlinkBasePriceFeed collateralToBridgeAssetFeed; // main coin1 feed ChainlinkBasePriceFeed bridgeAssetToUsdFeed; // main coin2 feed diff --git a/test/feedForkTests/SINV.t.sol b/test/feedForkTests/SINV.t.sol new file mode 100644 index 0000000..6da24e0 --- /dev/null +++ b/test/feedForkTests/SINV.t.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import {ERC4626Feed, IERC4626} from "src/feeds/ERC4626Feed.sol"; +import {ChainlinkBasePriceFeed} from "src/feeds/ChainlinkBasePriceFeed.sol"; +import {PriceFeedNoStale} from "src/feeds/PriceFeedNoStale.sol"; +import "forge-std/console2.sol"; + + +contract SINVFeedTest is Test { + ERC4626Feed vaultFeed; + PriceFeedNoStale priceFeed; + + IERC4626 sInv = IERC4626(0x08d23468A467d2bb86FaE0e32F247A26C7E2e994); + ChainlinkBasePriceFeed invToUsd = ChainlinkBasePriceFeed(0x54F1E4EB93c5b5F4C12776c96e08a49A9928FE84); + + function setUp() public { + string memory url = vm.rpcUrl("mainnet"); + vm.createSelectFork(url); + vaultFeed = new ERC4626Feed(address(sInv), address(invToUsd)); + priceFeed = new PriceFeedNoStale(address(vaultFeed)); + } + + function test_sINV_Feed() public { + uint256 exchangeRate = sInv.previewRedeem(1e18); + uint256 invPrice = uint(invToUsd.latestAnswer()); + uint256 sInvToUsd = exchangeRate * invPrice / 1e18; + assertEq(sInvToUsd, uint(vaultFeed.latestAnswer())); + assertEq(sInvToUsd, uint(priceFeed.latestAnswer())); + console2.log(uint(vaultFeed.latestAnswer())); + } + + function test_feedNoStale() public { + assertEq(vaultFeed.latestAnswer(), priceFeed.latestAnswer()); + (,int price, , uint updateAt,) = priceFeed.latestRoundData(); + assertEq(updateAt, block.timestamp); + assertEq(price, vaultFeed.latestAnswer()); + console2.log(priceFeed.description()); + } +} \ No newline at end of file diff --git a/test/feedForkTests/WOETH.t.sol b/test/feedForkTests/WOETH.t.sol new file mode 100644 index 0000000..cbaae4a --- /dev/null +++ b/test/feedForkTests/WOETH.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import {ERC4626Feed, IERC4626} from "src/feeds/ERC4626Feed.sol"; +import {ChainlinkBridgeAssetFeed} from "src/feeds/ChainlinkBridgeAssetFeed.sol"; +import {ChainlinkBridgeAssetBase} from "test/feedForkTests/ChainlinkBridgeAssetBase.t.sol"; +import {ChainlinkBasePriceFeed} from "src/feeds/ChainlinkBasePriceFeed.sol"; +import {PriceFeedNoStale} from "src/feeds/PriceFeedNoStale.sol"; +import "forge-std/console2.sol"; + + +contract WOETHFeedTest is ChainlinkBridgeAssetBase { + ERC4626Feed vaultFeed; + ChainlinkBasePriceFeed ethWrapper; + ChainlinkBasePriceFeed oEthToEthWrapper; + PriceFeedNoStale feedNoStale; + + address oEthToEth = 0x703118C4CbccCBF2AB31913e0f8075fbbb15f563; + address wOeth = 0xDcEe70654261AF21C44c093C300eD3Bb97b78192; + address ethToUsd = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; + + function setUp() public { + string memory url = vm.rpcUrl("mainnet"); + vm.createSelectFork(url); + oEthToEthWrapper = new ChainlinkBasePriceFeed(address(this), oEthToEth, address(0), 86400); + vaultFeed = new ERC4626Feed(wOeth, address(oEthToEthWrapper)); + ethWrapper = new ChainlinkBasePriceFeed(address(this),ethToUsd, address(0), 3600); + init(address(vaultFeed), address(ethWrapper), true); + feedNoStale = new PriceFeedNoStale(address(feed)); + } + + function test_woEth() public { + uint256 woEthToOEth = IERC4626(wOeth).previewRedeem(1e18); + uint256 oEthToEthPrice = uint(oEthToEthWrapper.latestAnswer()); + uint256 ethToUsdPrice = uint(ethWrapper.latestAnswer()); + uint256 woEthToEthPrice = woEthToOEth * oEthToEthPrice / 1e18; + uint256 woEthToUsdPrice = woEthToEthPrice * ethToUsdPrice / 1e18; + assertEq(woEthToUsdPrice, uint(feed.latestAnswer())); + console2.log(uint(feed.latestAnswer())); + } + + function test_feedNoStale() public { + assertEq(feed.latestAnswer(), feedNoStale.latestAnswer()); + (,int price, , uint updateAt,) = feedNoStale.latestRoundData(); + assertEq(updateAt, block.timestamp); + assertEq(price, feed.latestAnswer()); + console2.log(feedNoStale.description()); + } +} \ No newline at end of file