Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: update oval oracle #10

Merged
merged 5 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
9 changes: 3 additions & 6 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -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
36 changes: 14 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@

Copy link
Member

Choose a reason for hiding this comment

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

do you think we should add some text about how searchers can make money from this? we could add it later I guess as well.

**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 calling the [`createHoneyPot`](https://github.com/UMAprotocol/oev-demo/blob/master/src/HoneyPot.sol#L31) function.
md0x marked this conversation as resolved.
Show resolved Hide resolved

## Getting Started

Expand All @@ -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/<YOUR_KEY>
```

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
Copy link
Member

Choose a reason for hiding this comment

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

Do we allow both mnemonics and private keys? I feel like mnemonics are easier for the user

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
```
1 change: 0 additions & 1 deletion lib/oev-contracts
Submodule oev-contracts deleted from bd6b77
1 change: 0 additions & 1 deletion lib/oval-contracts
Submodule oval-contracts deleted from 25afc4
1 change: 1 addition & 0 deletions lib/oval-quickstart
Submodule oval-quickstart added at 788767
3 changes: 1 addition & 2 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -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/
53 changes: 25 additions & 28 deletions script/HoneyPot.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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("UNLOCKERS");
Copy link
Member

Choose a reason for hiding this comment

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

In the README, this is called UNLOCKER, not UNLOCKERS. Since we only allow 1, I think UNLOCKER seems more correct.

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();
}
Expand Down
2 changes: 1 addition & 1 deletion src/HoneyPot.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Copy link
Member

Choose a reason for hiding this comment

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

this is great! I love how simple this is to get the new logic.


contract HoneyPot is Ownable {
struct HoneyPotDetails {
Expand Down
29 changes: 0 additions & 29 deletions src/HoneyPotOval.sol

This file was deleted.

29 changes: 0 additions & 29 deletions src/mock/ChronicleMedianSourceMock.sol

This file was deleted.

85 changes: 9 additions & 76 deletions test/HoneyPot.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Copy link
Member

Choose a reason for hiding this comment

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

+1 great reuse of code


contract HoneyPotTest is CommonTest {
event ReceivedEther(address sender, uint256 amount);
Expand All @@ -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;

Expand All @@ -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();
Expand Down Expand Up @@ -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}();
Expand All @@ -111,7 +87,7 @@ contract HoneyPotTest is CommonTest {
mockChainlinkPriceChange();

// Unlock the latest value
oval.unlockLatestValue();
oracle.unlockLatestValue();

uint256 liquidatorBalanceBefore = liquidator.balance;

Expand Down Expand Up @@ -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}();
Expand Down
Loading