diff --git a/.gitignore b/.gitignore
index d8a1d07..b33441b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
cache/
out/
+broadcast/
\ No newline at end of file
diff --git a/README.md b/README.md
index 9457853..a0c69a3 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,3 @@
-
#
OEV Share HoneyPot Demo
**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.**
@@ -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/
-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.
\ No newline at end of file
+- **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/
+```
+
+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
+```
diff --git a/script/HoneyPot.s.sol b/script/HoneyPot.s.sol
new file mode 100644
index 0000000..d05bd65
--- /dev/null
+++ b/script/HoneyPot.s.sol
@@ -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();
+ }
+}
diff --git a/src/HoneyPotDAO.sol b/src/HoneyPotDAO.sol
new file mode 100644
index 0000000..b0f4ab0
--- /dev/null
+++ b/src/HoneyPotDAO.sol
@@ -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);
+ }
+}
diff --git a/src/mock/ChronicleMedianSourceMock.sol b/src/mock/ChronicleMedianSourceMock.sol
new file mode 100644
index 0000000..d0e148d
--- /dev/null
+++ b/src/mock/ChronicleMedianSourceMock.sol
@@ -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 {}
+}
diff --git a/test/HoneyPot.sol b/test/HoneyPot.sol
index fb9fe55..7ef0df7 100644
--- a/test/HoneyPot.sol
+++ b/test/HoneyPot.sol
@@ -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;
@@ -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 {}
@@ -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();
@@ -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;
@@ -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);
+ }
}