From c6d2a02b626b0da7318987249feedc7ebfab8726 Mon Sep 17 00:00:00 2001 From: Reinis Martinsons Date: Thu, 28 Dec 2023 23:39:11 +0000 Subject: [PATCH] test: pyth updates Signed-off-by: Reinis Martinsons --- src/mock/PythSourceMock.sol | 26 ++++++++++++++++++++ test/HoneyPot.sol | 48 +++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 src/mock/PythSourceMock.sol diff --git a/src/mock/PythSourceMock.sol b/src/mock/PythSourceMock.sol new file mode 100644 index 0000000..0c7707f --- /dev/null +++ b/src/mock/PythSourceMock.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.17; + +import {IPyth} from "oev-contracts/interfaces/pyth/IPyth.sol"; + +contract PythSourceMock is IPyth { + int64 public price; + uint64 public conf; + int32 public expo; + uint256 public publishTime; + + function setLatestPrice(int64 _price, uint64 _conf, int32 _expo, uint256 _publishTime) public { + price = _price; + conf = _conf; + expo = _expo; + publishTime = _publishTime; + } + + function getPrice(bytes32 id) external view returns (IPyth.Price memory) { + return getPriceUnsafe(id); + } + + function getPriceUnsafe(bytes32 id) public view returns (IPyth.Price memory) { + return IPyth.Price({price: price, conf: conf, expo: expo, publishTime: publishTime}); + } +} diff --git a/test/HoneyPot.sol b/test/HoneyPot.sol index dd9412a..0b3ad6e 100644 --- a/test/HoneyPot.sol +++ b/test/HoneyPot.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity 0.8.17; +import "forge-std/console.sol"; + import {CommonTest} from "./Common.sol"; import {IAggregatorV3Source} from "oev-contracts/interfaces/chainlink/IAggregatorV3Source.sol"; import {IMedian} from "oev-contracts/interfaces/chronicle/IMedian.sol"; @@ -10,6 +12,7 @@ import {HoneyPotOEVShare} from "../src/HoneyPotOEVShare.sol"; import {HoneyPot} from "../src/HoneyPot.sol"; import {HoneyPotDAO} from "../src/HoneyPotDAO.sol"; import {ChronicleMedianSourceMock} from "../src/mock/ChronicleMedianSourceMock.sol"; +import {PythSourceMock} from "../src/mock/PythSourceMock.sol"; contract HoneyPotTest is CommonTest { event ReceivedEther(address sender, uint256 amount); @@ -25,6 +28,7 @@ contract HoneyPotTest is CommonTest { bytes32 pythPriceId = 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace; ChronicleMedianSourceMock chronicleMock; + PythSourceMock pythMock; HoneyPotOEVShare oevShare; HoneyPot honeyPot; @@ -48,6 +52,7 @@ contract HoneyPotTest is CommonTest { _whitelistOnChronicle(); oevShare.setUnlocker(address(this), true); chronicleMock = new ChronicleMedianSourceMock(); + pythMock = new PythSourceMock(); } receive() external payable {} @@ -203,4 +208,47 @@ contract HoneyPotTest is CommonTest { emit OracleUpdated(random); honeyPot.setOracle(IAggregatorV3Source(random)); } + + function testCannotEmptyLockedPythUpdate() public { + // Set initial Pyth price 1% above current Chainlink price at current timestamp. + (, int256 chainlinkPrice,, uint256 chainlinkTime,) = chainlink.latestRoundData(); + int64 pythPrice = int64(chainlinkPrice) * 101 / 100; + pythMock.setLatestPrice(pythPrice, 0, -8, block.timestamp); + + // Deploy and setup contracts. + HoneyPotOEVShare oevShare3 = + new HoneyPotOEVShare(address(chainlink), address(chronicleMock), address(pythMock), pythPriceId, 8); + oevShare3.setUnlocker(address(this), true); + HoneyPot honeyPot3 = new HoneyPot(IAggregatorV3Source(address(oevShare3))); + + // Check that the latest Oval price is the same as the latest Chainlink price before the unlock. + (, int256 ovalPrice,, uint256 ovalTime,) = oevShare3.latestRoundData(); + assertTrue(ovalPrice == chainlinkPrice); + assertTrue(ovalTime == chainlinkTime); + + // Unlock the latest value so that the latest Pyth price is used when creating the HoneyPot. + oevShare3.unlockLatestValue(); + (, ovalPrice,, ovalTime,) = oevShare3.latestRoundData(); + assertTrue(ovalPrice == pythPrice); + assertTrue(ovalTime == block.timestamp); + console.log("Price at honeyPot creation: %s, @%s", uint256(ovalPrice), ovalTime); + + // Create HoneyPot for the caller. + honeyPot3.createHoneyPot{value: honeyPotBalance}(); + (int256 liquidationPrice, uint256 testhoneyPotBalance) = honeyPot3.honeyPots(address(this)); + assertTrue(liquidationPrice == pythPrice); + assertTrue(testhoneyPotBalance == honeyPotBalance); + + // Update Pyth price by additional 1% after 10 minutes. + skip(600); + pythPrice = pythPrice * 101 / 100; + pythMock.setLatestPrice(pythPrice, 0, -8, block.timestamp); + + // It should not be possible to empty the HoneyPot without unlocking the latest value. + (, ovalPrice,, ovalTime,) = oevShare3.latestRoundData(); + console.log("Price at honeyPot empty: %s, @%s", uint256(ovalPrice), ovalTime); + vm.prank(liquidator); + vm.expectRevert("Liquidation price reached for this user"); + honeyPot3.emptyHoneyPot(address(this)); + } }