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: honeypotdao and deployment script #5

Merged
merged 29 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
cache/
out/
broadcast/
39 changes: 32 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# <h1 align="center"> OEV Share HoneyPot Demo </h1>

**This repository is a demonstration of the OEV Share 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.**
Expand All @@ -12,16 +11,42 @@ The HoneyPot mechanism is a unique setup where funds are kept in a contract that
## Getting Started

To test the demo run the following commands:

```
forge install
export RPC_MAINNET=https://mainnet.infura.io/v3/<YOUR_INFURA_KEY>
forge test`
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.

- **HoneyPotOEVShare**: 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**: 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.
- **HoneyPotOEVShare**: 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.

## Deploy the Contracts

Can be run against a fork with anvil:

```bash
anvil --fork-url https://mainnet.infura.io/v3/<YOUR_KEY>
```

Then:

```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
```
49 changes: 49 additions & 0 deletions script/HoneyPot.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: AGPL-3.0-only
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 {HoneyPotOEVShare} from "../src/HoneyPotOEVShare.sol";
import {HoneyPot} from "../src/HoneyPot.sol";
import {HoneyPotDAO} from "../src/HoneyPotDAO.sol";
import {IAggregatorV3Source} from "oev-contracts/interfaces/chainlink/IAggregatorV3Source.sol";

contract HoneyPotDeploymentScript is Script {
HoneyPotOEVShare oevShare;
HoneyPot honeyPot;
HoneyPotDAO honeyPotDAO;
ChronicleMedianSourceMock chronicleMock;

function run() external {
vm.startBroadcast();

address chainlink = vm.envOr("CHAINLINK_SOURCE", 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419);
address pyth = vm.envOr("PYTH_SOURCE", 0x4305FB66699C3B2702D4d05CF36551390A4c69C6);

bytes32 defaultId = 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace;
bytes32 pythPriceId = vm.envOr("PYTH_PRICE_ID", bytes32(0));
if (pythPriceId == bytes32(0)) {
pythPriceId = defaultId;
}

// Create mock ChronicleMedianSource and set the latest source data.
chronicleMock = new ChronicleMedianSourceMock();

oevShare = new HoneyPotOEVShare(
chainlink,
address(chronicleMock),
pyth,
pythPriceId,
8
);

honeyPot = new HoneyPot(IAggregatorV3Source(address(oevShare)));

honeyPotDAO = new HoneyPotDAO();

vm.stopBroadcast();
}
}
21 changes: 21 additions & 0 deletions src/HoneyPotDAO.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";

contract HoneyPotDAO is Ownable {
// Define events
event ReceivedEther(address sender, uint256 amount);
event DrainedEther(address to, uint256 amount);

receive() external payable {
emit ReceivedEther(msg.sender, msg.value);
}

function drain() external onlyOwner {
uint256 balance = address(this).balance;
Address.sendValue(payable(owner()), balance);
emit DrainedEther(owner(), balance);
}
}
29 changes: 29 additions & 0 deletions src/mock/ChronicleMedianSourceMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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 {}
}
67 changes: 65 additions & 2 deletions test/HoneyPot.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,23 @@ import {IPyth} from "oev-contracts/interfaces/pyth/IPyth.sol";

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";

contract HoneyPotTest is CommonTest {
event ReceivedEther(address sender, uint256 amount);
event DrainedEther(address to, uint256 amount);

IAggregatorV3Source chainlink = IAggregatorV3Source(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419);
IMedian chronicle = IMedian(0x64DE91F5A373Cd4c28de3600cB34C7C6cE410C85);
IPyth pyth = IPyth(0x4305FB66699C3B2702D4d05CF36551390A4c69C6);
bytes32 pythPriceId = 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace;

ChronicleMedianSourceMock chronicleMock;

HoneyPotOEVShare oevShare;
HoneyPot honeyPot;
HoneyPotDAO honeyPotDAO;

uint256 public constant liquidationPrice = 0.1e18;
uint256 public honeyPotBalance = 1 ether;
Expand All @@ -32,8 +40,10 @@ contract HoneyPotTest is CommonTest {
);

honeyPot = new HoneyPot(IAggregatorV3Source(address(oevShare)));
honeyPotDAO = new HoneyPotDAO();
_whitelistOnChronicle();
oevShare.setUnlocker(address(this), true);
chronicleMock = new ChronicleMedianSourceMock();
}

receive() external payable {}
Expand Down Expand Up @@ -86,7 +96,7 @@ contract HoneyPotTest is CommonTest {

vm.prank(liquidator);
vm.expectRevert("Liquidation price reached for this user");
honeyPot.emptyHoneyPot(address(this)); // emptyHoneyPot now requires the creator's address
honeyPot.emptyHoneyPot(address(this));

// Simulate price change
mockChainlinkPriceChange();
Expand All @@ -97,7 +107,7 @@ contract HoneyPotTest is CommonTest {
uint256 liquidatorBalanceBefore = liquidator.balance;

vm.prank(liquidator);
honeyPot.emptyHoneyPot(address(this)); // emptyHoneyPot now requires the creator's address
honeyPot.emptyHoneyPot(address(this));

uint256 liquidatorBalanceAfter = liquidator.balance;

Expand All @@ -108,4 +118,57 @@ contract HoneyPotTest is CommonTest {
(, uint256 testhoneyPotBalanceTwo) = honeyPot.honeyPots(address(this));
assertTrue(testhoneyPotBalanceTwo == honeyPotBalance);
}

function testHoneyPotDAO() public {
vm.expectEmit(true, true, true, true);
emit ReceivedEther(address(this), 1 ether);
payable(address(honeyPotDAO)).transfer(1 ether);

vm.expectEmit(true, true, true, true);
emit DrainedEther(address(this), 1 ether);
honeyPotDAO.drain();
}

function testChronicleMock() public {
uint32 age = chronicle.age();
uint256 read = chronicle.read();
chronicleMock.setLatestSourceData(read, age);

HoneyPotOEVShare oevShare2 = new HoneyPotOEVShare(
address(chainlink),
address(chronicleMock),
address(pyth),
pythPriceId,
8
);
oevShare2.setUnlocker(address(this), true);

HoneyPot honeyPot2 = new HoneyPot(
IAggregatorV3Source(address(oevShare2))
);

// 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
oevShare2.unlockLatestValue();

uint256 liquidatorBalanceBefore = liquidator.balance;

vm.prank(liquidator);
honeyPot2.emptyHoneyPot(address(this));

uint256 liquidatorBalanceAfter = liquidator.balance;

assertTrue(liquidatorBalanceAfter == liquidatorBalanceBefore + honeyPotBalance);
}
}