From c5707e5660b79a02e6eb6d9c544da99ac17fbe19 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Wed, 7 Feb 2024 15:58:53 +0000 Subject: [PATCH] feat: update oval oracle (#10) Signed-off-by: Pablo Maldonado Co-authored-by: Chris Maree --- .gitmodules | 9 +-- README.md | 36 +++++------ lib/oev-contracts | 1 - lib/oval-contracts | 1 - lib/oval-quickstart | 1 + remappings.txt | 3 +- script/HoneyPot.s.sol | 53 ++++++++-------- src/HoneyPot.sol | 2 +- src/HoneyPotOval.sol | 29 --------- src/mock/ChronicleMedianSourceMock.sol | 29 --------- test/HoneyPot.sol | 85 +++----------------------- 11 files changed, 54 insertions(+), 195 deletions(-) delete mode 160000 lib/oev-contracts delete mode 160000 lib/oval-contracts create mode 160000 lib/oval-quickstart delete mode 100644 src/HoneyPotOval.sol delete mode 100644 src/mock/ChronicleMedianSourceMock.sol diff --git a/.gitmodules b/.gitmodules index 8693d44..7e7b428 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,9 +4,6 @@ [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts -[submodule "lib/oval-contracts"] - path = lib/oval-contracts - url = https://github.com/UMAprotocol/oval-contracts -[submodule "lib/oev-contracts"] - path = lib/oev-contracts - url = https://github.com/UMAprotocol/oev-contracts +[submodule "lib/oval-quickstart"] + path = lib/oval-quickstart + url = https://github.com/UMAprotocol/oval-quickstart diff --git a/README.md b/README.md index fdd3685..6f05e2c 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,9 @@ **This repository is a demonstration of the Oval system and a HoneyPot mechanism. It showcases how a backrunner can liquidate a position, in this particular case, how a HoneyPot can be emptied given a specific price change.** -![Github Actions](https://github.com/UMAprotocol/oval-demo/workflows/CI/badge.svg) - ## Introduction -The HoneyPot mechanism is a unique setup where funds are kept in a contract that is designed to be emptied out based on specific criteria, in this case, a change in the price from an oracle. +The HoneyPot is a contract abstracting in a simple way a money market or any other contract that can be liquidated. The HoneyPot is initialized with a specific amount of funds and the Chainlink price at the creation of the pot. The HoneyPot can be subject to liquidation (`emptyHoneyPot` function) if the Chainlink price feed reports a value different from the initial price at the time of the HoneyPot's creation. The owner of the HoneyPot can reset the funds and the price at any time. It can support one honey per address by calling the [`createHoneyPot`](https://github.com/UMAprotocol/oev-demo/blob/master/src/HoneyPot.sol#L31) function. ## Getting Started @@ -20,33 +18,27 @@ forge test` ## Contracts Overview -- **HoneyPot**: Represents the honey pot, which can be emptied when a price oracle returns a value different from a pre-defined liquidation price. The honey pot's funds can also be reset by its owner. -- **HoneyPotOval**: Acts as the oracle which retrieves prices from various sources like Chainlink, Chronicle, and Pyth. -- **Test Contract**: Sets up the environment, including simulating price changes and testing the mechanisms for creating and emptying the HoneyPot. +- **HoneyPot**: This represents the honey pot, which can be emptied when the Chainlink price feed reports a value different from the initial price at the time of the honey pot's creation. The funds in the honey pot can also be reset by its owner. +- **ChainlinkOvalImmutable**: Serves as the oracle that retrieves prices from Chainlink. This is the simplest version of Oval that can be used to retrieve prices from Chainlink. It's pulled from the [Oval-Quickstart](https://github.com/UMAprotocol/oval-quickstart) repository. +- **ChainlinkOvalImmutable**: This is a simple example of a contract that can receive the Oval MEV-Share refunds for Oracle auction kickback. ## Deploy the Contracts -Can be run against a fork with anvil: +(Optional) Can be run against a fork with anvil by first running: ```bash anvil --fork-url https://mainnet.infura.io/v3/ ``` -Then: +Run the following command to deploy the contracts: ```bash - export MNEMONIC="test test test test test test test test test test test junk" - export DEPLOYER_WALLET=$(cast wallet address --mnemonic "$MNEMONIC") - export ETH_RPC_URL="http://127.0.0.1:8545" - - # The following variables can be skipped if you want to use the default values - export CHAINLINK_SOURCE = "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419" // chosen from https://docs.chain.link/docs/reference-contracts - export PYTH_SOURCE = "0x4305FB66699C3B2702D4d05CF36551390A4c69C6" // chosen from https://pyth.network/markets - export PYTH_PRICE_ID = "0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace" // chosen from https://pyth.network/markets - - forge script script/HoneyPot.s.sol \ - --fork-url $ETH_RPC_URL \ - --mnemonics "$MNEMONIC" \ - --sender $DEPLOYER_WALLET \ - --broadcast + export PRIVATE_KEY=0xPUT_YOUR_PRIVATE_KEY_HERE # This account will do the deployment + export SOURCE_ADDRESS=0x5f4ec3df9cbd43714fe2740f5e3616155c5b8419 # example Chainlink ETH/USD + export LOCK_WINDOW=60 # How long each update is blocked for OEV auction to run. + export MAX_TRAVERSAL=4 # How many iterations to look back for historic data. + export UNLOCKER=0xPUT_YOUR_UNLOCKER_ADDRESS_HERE # Your address provided on Discord. + export ETH_RPC_URL=PUT_YOUR_RPC_URL_HERE # Your network or fork RPC Url. + + forge script script/HoneyPot.s.sol --rpc-url $ETH_RPC_URL --broadcast ``` diff --git a/lib/oev-contracts b/lib/oev-contracts deleted file mode 160000 index bd6b77b..0000000 --- a/lib/oev-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bd6b77ba41e4c21a63fce6bdd7e87da34a041379 diff --git a/lib/oval-contracts b/lib/oval-contracts deleted file mode 160000 index 25afc49..0000000 --- a/lib/oval-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 25afc497af220adbc580b9c4ab04ab2485f3a564 diff --git a/lib/oval-quickstart b/lib/oval-quickstart new file mode 160000 index 0000000..7887679 --- /dev/null +++ b/lib/oval-quickstart @@ -0,0 +1 @@ +Subproject commit 78876792d0dc1b93b1da9b8c72effee12233ff8e diff --git a/remappings.txt b/remappings.txt index e4f5a15..a421151 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,5 +1,4 @@ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ -oval-contracts/=lib/oval-contracts/src/ -oev-contracts/=lib/oev-contracts/src/ +oval-quickstart/=lib/oval-quickstart/src/ @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ \ No newline at end of file diff --git a/script/HoneyPot.s.sol b/script/HoneyPot.s.sol index 9d7bd55..920541f 100644 --- a/script/HoneyPot.s.sol +++ b/script/HoneyPot.s.sol @@ -4,45 +4,42 @@ pragma solidity ^0.8.0; import "forge-std/console2.sol"; import "forge-std/Script.sol"; -import {ChronicleMedianSourceMock} from "../src/mock/ChronicleMedianSourceMock.sol"; -import {IMedian} from "oev-contracts/interfaces/chronicle/IMedian.sol"; -import {HoneyPotOval} from "../src/HoneyPotOval.sol"; import {HoneyPot} from "../src/HoneyPot.sol"; import {HoneyPotDAO} from "../src/HoneyPotDAO.sol"; -import {IAggregatorV3Source} from "oval-contracts/interfaces/chainlink/IAggregatorV3Source.sol"; +import {ChainlinkOvalImmutable, IAggregatorV3Source} from "oval-quickstart/ChainlinkOvalImmutable.sol"; contract HoneyPotDeploymentScript is Script { - HoneyPotOval oval; - HoneyPot honeyPot; - HoneyPotDAO honeyPotDAO; - ChronicleMedianSourceMock chronicleMock; - function run() external { - vm.startBroadcast(); + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + address chainlink = vm.envAddress("SOURCE_ADDRESS"); + uint256 lockWindow = vm.envUint("LOCK_WINDOW"); + uint256 maxTraversal = vm.envUint("MAX_TRAVERSAL"); + address unlocker = vm.envAddress("UNLOCKER"); + address[] memory unlockers = new address[](1); + unlockers[0] = unlocker; + + console.log("Deploying HoneyPot with the following parameters:"); + console.log(" - Chainlink source address: ", chainlink); + console.log(" - Lock window: ", lockWindow); + console.log(" - Max traversal: ", maxTraversal); + console.log(" - Unlocker: ", unlockers[0]); + + IAggregatorV3Source source = IAggregatorV3Source(chainlink); + uint8 decimals = IAggregatorV3Source(chainlink).decimals(); + vm.startBroadcast(deployerPrivateKey); - address chainlink = vm.envOr("CHAINLINK_SOURCE", 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419); - address pyth = vm.envOr("PYTH_SOURCE", 0x4305FB66699C3B2702D4d05CF36551390A4c69C6); + ChainlinkOvalImmutable oracle = + new ChainlinkOvalImmutable(source, decimals, lockWindow, maxTraversal, unlockers); - bytes32 defaultId = 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace; - bytes32 pythPriceId = vm.envOr("PYTH_PRICE_ID", bytes32(0)); - if (pythPriceId == bytes32(0)) { - pythPriceId = defaultId; - } + console.log("Deployed ChainlinkOvalImmutable contract at address: ", address(oracle)); - // Create mock ChronicleMedianSource and set the latest source data. - chronicleMock = new ChronicleMedianSourceMock(); + HoneyPot honeyPot = new HoneyPot(IAggregatorV3Source(address(oracle))); - oval = new HoneyPotOval( - chainlink, - address(chronicleMock), - pyth, - pythPriceId, - 8 - ); + console.log("Deployed HoneyPot contract at address: ", address(honeyPot)); - honeyPot = new HoneyPot(IAggregatorV3Source(address(oval))); + HoneyPotDAO honeyPotDAO = new HoneyPotDAO(); - honeyPotDAO = new HoneyPotDAO(); + console.log("Deployed HoneyPotDAO contract at address: ", address(honeyPotDAO)); vm.stopBroadcast(); } diff --git a/src/HoneyPot.sol b/src/HoneyPot.sol index e868d91..809d77c 100644 --- a/src/HoneyPot.sol +++ b/src/HoneyPot.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.17; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; -import {IAggregatorV3Source} from "oval-contracts/interfaces/chainlink/IAggregatorV3Source.sol"; +import {IAggregatorV3Source} from "oval-quickstart/ChainlinkOvalImmutable.sol"; contract HoneyPot is Ownable { struct HoneyPotDetails { diff --git a/src/HoneyPotOval.sol b/src/HoneyPotOval.sol deleted file mode 100644 index 81feb16..0000000 --- a/src/HoneyPotOval.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.17; - -import {BoundedUnionSourceAdapter} from "oev-contracts/adapters/source-adapters/BoundedUnionSourceAdapter.sol"; -import {BaseController} from "oev-contracts/controllers/BaseController.sol"; -import {ChainlinkDestinationAdapter} from "oev-contracts/adapters/destination-adapters/ChainlinkDestinationAdapter.sol"; -import {IAggregatorV3Source} from "oev-contracts/interfaces/chainlink/IAggregatorV3Source.sol"; -import {IMedian} from "oev-contracts/interfaces/chronicle/IMedian.sol"; -import {IPyth} from "oev-contracts/interfaces/pyth/IPyth.sol"; - -contract HoneyPotOval is BaseController, BoundedUnionSourceAdapter, ChainlinkDestinationAdapter { - constructor( - address chainlinkSource, - address chronicleSource, - address pythSource, - bytes32 pythPriceId, - uint8 decimals - ) - BoundedUnionSourceAdapter( - IAggregatorV3Source(chainlinkSource), - IMedian(chronicleSource), - IPyth(pythSource), - pythPriceId, - 0.1e18 - ) - BaseController() - ChainlinkDestinationAdapter(decimals) - {} -} diff --git a/src/mock/ChronicleMedianSourceMock.sol b/src/mock/ChronicleMedianSourceMock.sol deleted file mode 100644 index d0e148d..0000000 --- a/src/mock/ChronicleMedianSourceMock.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.17; - -import {IMedian} from "oev-contracts/interfaces/chronicle/IMedian.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; - -contract ChronicleMedianSourceMock is IMedian, Ownable { - uint256 public value; - uint32 public ageValue; - - function age() external view returns (uint32) { - return ageValue; - } - - function read() external view returns (uint256) { - return value; - } - - function peek() external view returns (uint256, bool) { - return (value, true); - } - - function setLatestSourceData(uint256 _value, uint32 _age) public onlyOwner { - value = _value; - ageValue = _age; - } - - function kiss(address) external override {} -} diff --git a/test/HoneyPot.sol b/test/HoneyPot.sol index 820d817..a1cb51e 100644 --- a/test/HoneyPot.sol +++ b/test/HoneyPot.sol @@ -2,14 +2,9 @@ pragma solidity 0.8.17; import {CommonTest} from "./Common.sol"; -import {IAggregatorV3Source} from "oval-contracts/interfaces/chainlink/IAggregatorV3Source.sol"; -import {IMedian} from "oev-contracts/interfaces/chronicle/IMedian.sol"; -import {IPyth} from "oev-contracts/interfaces/pyth/IPyth.sol"; - -import {HoneyPotOval} from "../src/HoneyPotOval.sol"; import {HoneyPot} from "../src/HoneyPot.sol"; import {HoneyPotDAO} from "../src/HoneyPotDAO.sol"; -import {ChronicleMedianSourceMock} from "../src/mock/ChronicleMedianSourceMock.sol"; +import {ChainlinkOvalImmutable, IAggregatorV3Source} from "oval-quickstart/ChainlinkOvalImmutable.sol"; contract HoneyPotTest is CommonTest { event ReceivedEther(address sender, uint256 amount); @@ -20,13 +15,7 @@ contract HoneyPotTest is CommonTest { event PotReset(address indexed owner, uint256 amount); IAggregatorV3Source chainlink = IAggregatorV3Source(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419); - IMedian chronicle = IMedian(0x64DE91F5A373Cd4c28de3600cB34C7C6cE410C85); - IPyth pyth = IPyth(0x4305FB66699C3B2702D4d05CF36551390A4c69C6); - bytes32 pythPriceId = 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace; - - ChronicleMedianSourceMock chronicleMock; - - HoneyPotOval oval; + ChainlinkOvalImmutable oracle; HoneyPot honeyPot; HoneyPotDAO honeyPotDAO; @@ -35,30 +24,17 @@ contract HoneyPotTest is CommonTest { function setUp() public { vm.createSelectFork("mainnet", 18419040); // Recent block on mainnet - oval = new HoneyPotOval( - address(chainlink), - address(chronicle), - address(pyth), - pythPriceId, - 8 - ); + address[] memory unlockers = new address[](1); + unlockers[0] = address(this); + uint8 decimals = chainlink.decimals(); + oracle = new ChainlinkOvalImmutable(chainlink, decimals, 3, 10, unlockers); - honeyPot = new HoneyPot(IAggregatorV3Source(address(oval))); + honeyPot = new HoneyPot(IAggregatorV3Source(address(oracle))); honeyPotDAO = new HoneyPotDAO(); - _whitelistOnChronicle(); - oval.setUnlocker(address(this), true); - chronicleMock = new ChronicleMedianSourceMock(); } receive() external payable {} - function _whitelistOnChronicle() internal { - vm.startPrank(0xBE8E3e3618f7474F8cB1d074A26afFef007E98FB); // DSPause that is a ward (can add kiss to chronicle) - chronicle.kiss(address(oval)); - chronicle.kiss(address(this)); // So that we can read Chronicle directly. - vm.stopPrank(); - } - function mockChainlinkPriceChange() public { (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) = chainlink.latestRoundData(); @@ -96,7 +72,7 @@ contract HoneyPotTest is CommonTest { function testCrackHoneyPot() public { // Create HoneyPot for the caller - (, int256 currentPrice,,,) = oval.latestRoundData(); + (, int256 currentPrice,,,) = oracle.latestRoundData(); vm.expectEmit(true, true, true, true); emit HoneyPotCreated(address(this), currentPrice, honeyPotBalance); honeyPot.createHoneyPot{value: honeyPotBalance}(); @@ -111,7 +87,7 @@ contract HoneyPotTest is CommonTest { mockChainlinkPriceChange(); // Unlock the latest value - oval.unlockLatestValue(); + oracle.unlockLatestValue(); uint256 liquidatorBalanceBefore = liquidator.balance; @@ -140,49 +116,6 @@ contract HoneyPotTest is CommonTest { honeyPotDAO.drain(); } - function testChronicleMock() public { - uint32 age = chronicle.age(); - uint256 read = chronicle.read(); - chronicleMock.setLatestSourceData(read, age); - - HoneyPotOval oval2 = new HoneyPotOval( - address(chainlink), - address(chronicleMock), - address(pyth), - pythPriceId, - 8 - ); - oval2.setUnlocker(address(this), true); - - HoneyPot honeyPot2 = new HoneyPot( - IAggregatorV3Source(address(oval2)) - ); - - // Create HoneyPot for the caller - honeyPot2.createHoneyPot{value: honeyPotBalance}(); - (, uint256 testhoneyPotBalance) = honeyPot2.honeyPots(address(this)); - assertTrue(testhoneyPotBalance == honeyPotBalance); - - vm.prank(liquidator); - vm.expectRevert("Liquidation price reached for this user"); - honeyPot2.emptyHoneyPot(address(this)); - - // Simulate price change - chronicleMock.setLatestSourceData((read * 103) / 100, uint32(block.timestamp - 1)); - - // Unlock the latest value - oval2.unlockLatestValue(); - - uint256 liquidatorBalanceBefore = liquidator.balance; - - vm.prank(liquidator); - honeyPot2.emptyHoneyPot(address(this)); - - uint256 liquidatorBalanceAfter = liquidator.balance; - - assertTrue(liquidatorBalanceAfter == liquidatorBalanceBefore + honeyPotBalance); - } - function testCreateHoneyPotWithNoValue() public { vm.expectRevert("No value sent"); honeyPot.createHoneyPot{value: 0}();