diff --git a/package-lock.json b/package-lock.json index 043e607..07f0cf9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "morpho-blue-oracles", + "name": "pyth-morpho-wrapper", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "morpho-blue-oracles", + "name": "pyth-morpho-wrapper", "version": "1.0.0", "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index 3d54a17..b839f56 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,10 @@ "test": "test" }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "forge test", + "test:verbose": "forge test -vvv", + "test:gas": "forge test --gas-report", + "coverage": "forge coverage" }, "keywords": [], "author": "", diff --git a/src/morpho-pyth/interfaces/IMorphoPythOracleFactory.sol b/src/morpho-pyth/interfaces/IMorphoPythOracleFactory.sol index bc67e62..47a94a9 100644 --- a/src/morpho-pyth/interfaces/IMorphoPythOracleFactory.sol +++ b/src/morpho-pyth/interfaces/IMorphoPythOracleFactory.sol @@ -43,7 +43,8 @@ interface IMorphoPythOracleFactory { /// @param quoteFeed2 Second quote feed. Pass bytes32(0) if the price = 1. We recommend using stablecoin feeds /// instead of passing 1. /// @param quoteTokenDecimals Quote token decimals. - /// @param priceFeedMaxAge The maximum age in secondsfor the oracles prices to be considered valid. + /// @param priceFeedMaxAge The maximum age in seconds for the oracles prices to be considered valid. We have only + /// one price feed max age for all price feed. /// @param salt The salt to use for the CREATE2. /// @dev The base asset should be the collateral token and the quote asset the loan token. function createMorphoPythOracle( diff --git a/test/MorphoPythOracleTest.sol b/test/MorphoPythOracleTest.sol index 719fa67..e3fb88c 100644 --- a/test/MorphoPythOracleTest.sol +++ b/test/MorphoPythOracleTest.sol @@ -13,6 +13,29 @@ contract MorphoPythOracleTest is Test { function setUp() public { mockPyth = new MockPyth(60, 1); + // Update the price feed + mockPyth.updatePriceFeeds{value: 2}(getPriceUpdateData()); + + assertEq(mockPyth.getPriceUnsafe(pythWbtcUsdFeed).price, 30000 * 1e8); + assertEq(mockPyth.getPriceUnsafe(pythUsdtUsdFeed).price, 1 * 1e8); + + oracle = new MorphoPythOracle( + address(mockPyth), + vaultZero, + 1, + pythWbtcUsdFeed, + pythFeedZero, + pythWbtcUsdTokenDecimals, + vaultZero, + 1, + pythUsdtUsdFeed, + pythFeedZero, + pythUsdtUsdTokenDecimals, + oneMinute + ); + } + + function getPriceUpdateData() internal view returns (bytes[] memory) { // Create price feed update data for WBTC/USD bytes[] memory updateData = new bytes[](2); updateData[0] = mockPyth.createPriceFeedUpdateData( @@ -36,25 +59,7 @@ contract MorphoPythOracleTest is Test { uint64(block.timestamp), uint64(block.timestamp) ); - // Update the price feed - mockPyth.updatePriceFeeds{value: 2}(updateData); - assertEq(mockPyth.getPriceUnsafe(pythWbtcUsdFeed).price, 30000 * 1e8); - assertEq(mockPyth.getPriceUnsafe(pythUsdtUsdFeed).price, 1 * 1e8); - - oracle = new MorphoPythOracle( - address(mockPyth), - vaultZero, - 1, - pythWbtcUsdFeed, - pythFeedZero, - pythWbtcUsdTokenDecimals, - vaultZero, - 1, - pythUsdtUsdFeed, - pythFeedZero, - pythUsdtUsdTokenDecimals, - oneHour - ); + return updateData; } function testInitialSetup() public { @@ -88,4 +93,48 @@ contract MorphoPythOracleTest is Test { ) / uint256(int256(mockPyth.getPriceUnsafe(pythUsdtUsdFeed).price)) ); } + + function testPriceFeedAgeValidation() public { + // This should work - price is current + uint256 price = oracle.price(); + assertTrue(price > 0); + } + + function testPriceFeedStalePrice() public { + vm.warp(block.timestamp + oneMinute + 1); + // This should revert due to stale price + vm.expectRevert(bytes4(0x19abf40e)); // StalePrice error + oracle.price(); + } + + function testZeroPriceHandling() public { + // Set up price feeds with zero price + bytes[] memory updateData = new bytes[](2); + updateData[0] = mockPyth.createPriceFeedUpdateData( + pythWbtcUsdFeed, + 0, // Zero price + 1000000, // Confidence interval + -6, // Expo (-6 means price is multiplied by 10^-6) + 0, // EMA price + 0, // EMA Confidence interval + uint64(block.timestamp) + 1, + uint64(block.timestamp) + 1 + ); + + updateData[1] = mockPyth.createPriceFeedUpdateData( + pythUsdtUsdFeed, + 1 * 1e6, // Price of 1 USD + 0, // Confidence interval + -6, // Expo (-6 means price is multiplied by 10^-6) + 1 * 1e6, // EMA price + 0, // EMA Confidence interval + uint64(block.timestamp) + 1, + uint64(block.timestamp) + 1 + ); + mockPyth.updatePriceFeeds{value: 2}(updateData); + + // Zero price should be valid (price = 0 is allowed, only negative is rejected) + uint256 price = oracle.price(); + assertEq(price, 0); + } }