Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "",
Expand Down
3 changes: 2 additions & 1 deletion src/morpho-pyth/interfaces/IMorphoPythOracleFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
87 changes: 68 additions & 19 deletions test/MorphoPythOracleTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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 {
Expand Down Expand Up @@ -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 * 1e8, // Price of 1 USD

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpik: it should be 1e6

0, // Confidence interval
-6, // Expo (-6 means price is multiplied by 10^-6)
1 * 1e8, // EMA price

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

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);
}
}