Skip to content

Commit 858038c

Browse files
authored
feat: add mock oracle (#11)
Signed-off-by: Pablo Maldonado <pablo@umaproject.org>
1 parent c5707e5 commit 858038c

File tree

7 files changed

+153
-16
lines changed

7 files changed

+153
-16
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@
77
[submodule "lib/oval-quickstart"]
88
path = lib/oval-quickstart
99
url = https://github.com/UMAprotocol/oval-quickstart
10+
[submodule "lib/oval-contracts"]
11+
path = lib/oval-contracts
12+
url = https://github.com/UMAprotocol/oval-contracts

lib/oval-contracts

Submodule oval-contracts added at 25afc49

remappings.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
ds-test/=lib/forge-std/lib/ds-test/src/
22
forge-std/=lib/forge-std/src/
33
oval-quickstart/=lib/oval-quickstart/src/
4+
oval-contracts/=lib/oval-contracts/src/
45
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/

script/HoneyPot.s.sol

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ import {HoneyPot} from "../src/HoneyPot.sol";
88
import {HoneyPotDAO} from "../src/HoneyPotDAO.sol";
99
import {ChainlinkOvalImmutable, IAggregatorV3Source} from "oval-quickstart/ChainlinkOvalImmutable.sol";
1010

11+
import {MockV3Aggregator} from "../src/mock/MockV3Aggregator.sol";
12+
1113
contract HoneyPotDeploymentScript is Script {
1214
function run() external {
1315
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
14-
address chainlink = vm.envAddress("SOURCE_ADDRESS");
16+
address chainlink = vm.envOr("SOURCE_ADDRESS", address(0));
1517
uint256 lockWindow = vm.envUint("LOCK_WINDOW");
1618
uint256 maxTraversal = vm.envUint("MAX_TRAVERSAL");
1719
address unlocker = vm.envAddress("UNLOCKER");
@@ -24,9 +26,18 @@ contract HoneyPotDeploymentScript is Script {
2426
console.log(" - Max traversal: ", maxTraversal);
2527
console.log(" - Unlocker: ", unlockers[0]);
2628

29+
vm.startBroadcast(deployerPrivateKey);
30+
31+
if(chainlink == address(0)) {
32+
console.log("Chainlink source address is not set");
33+
console.log("Deploying a mock Chainlink source");
34+
MockV3Aggregator mock = new MockV3Aggregator(8, 100000000000);
35+
chainlink = address(mock);
36+
console.log("Deployed mock Chainlink source at address: ", chainlink);
37+
}
38+
2739
IAggregatorV3Source source = IAggregatorV3Source(chainlink);
2840
uint8 decimals = IAggregatorV3Source(chainlink).decimals();
29-
vm.startBroadcast(deployerPrivateKey);
3041

3142
ChainlinkOvalImmutable oracle =
3243
new ChainlinkOvalImmutable(source, decimals, lockWindow, maxTraversal, unlockers);

src/HoneyPot.sol

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ contract HoneyPot is Ownable {
1515
IAggregatorV3Source public oracle; // Oval serving as a Chainlink oracle
1616

1717
event OracleUpdated(address indexed newOracle);
18-
event HoneyPotCreated(address indexed creator, int256 liquidationPrice, uint256 initialBalance);
19-
event HoneyPotEmptied(address indexed honeyPotCreator, address indexed trigger, uint256 amount);
20-
event PotReset(address indexed owner, uint256 amount);
18+
event HoneyPotCreated(address indexed owner, int256 initialPrice, uint256 amount);
19+
event HoneyPotEmptied(address indexed owner, address indexed liquidator, uint256 amount);
20+
event HoneyPotReset(address indexed owner, uint256 amount);
2121

2222
constructor(IAggregatorV3Source _oracle) {
2323
oracle = _oracle;
@@ -40,30 +40,30 @@ contract HoneyPot is Ownable {
4040
emit HoneyPotCreated(msg.sender, currentPrice, msg.value);
4141
}
4242

43-
function _emptyPotForUser(address honeyPotCreator, address recipient) internal returns (uint256 amount) {
44-
HoneyPotDetails storage userPot = honeyPots[honeyPotCreator];
43+
function _emptyPotForUser(address owner, address recipient) internal returns (uint256 amount) {
44+
HoneyPotDetails storage userPot = honeyPots[owner];
4545

4646
amount = userPot.balance;
4747
userPot.balance = 0; // reset the balance
4848
userPot.liquidationPrice = 0; // reset the liquidation price
4949
Address.sendValue(payable(recipient), amount);
5050
}
5151

52-
function emptyHoneyPot(address honeyPotCreator) external {
52+
function emptyHoneyPot(address owner) external {
5353
(, int256 currentPrice,,,) = oracle.latestRoundData();
5454
require(currentPrice >= 0, "Invalid price from oracle");
5555

56-
HoneyPotDetails storage userPot = honeyPots[honeyPotCreator];
56+
HoneyPotDetails storage userPot = honeyPots[owner];
5757

5858
require(currentPrice != userPot.liquidationPrice, "Liquidation price reached for this user");
5959
require(userPot.balance > 0, "No balance to withdraw");
6060

61-
uint256 withdrawnAmount = _emptyPotForUser(honeyPotCreator, msg.sender);
62-
emit HoneyPotEmptied(honeyPotCreator, msg.sender, withdrawnAmount);
61+
uint256 withdrawnAmount = _emptyPotForUser(owner, msg.sender);
62+
emit HoneyPotEmptied(owner, msg.sender, withdrawnAmount);
6363
}
6464

6565
function resetPot() external {
6666
uint256 withdrawnAmount = _emptyPotForUser(msg.sender, msg.sender);
67-
emit PotReset(msg.sender, withdrawnAmount);
67+
emit HoneyPotReset(msg.sender, withdrawnAmount);
6868
}
6969
}

src/mock/MockV3Aggregator.sol

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
pragma solidity ^0.8.0;
2+
3+
import {IAggregatorV3} from "oval-contracts/interfaces/chainlink/IAggregatorV3.sol";
4+
5+
contract MockV3Aggregator is IAggregatorV3 {
6+
7+
event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt);
8+
9+
address public immutable aggregator;
10+
uint256 public constant version = 0;
11+
12+
uint8 public override decimals;
13+
int256 public override latestAnswer;
14+
uint256 public override latestTimestamp;
15+
uint256 public latestRound;
16+
17+
mapping(uint256 => int256) public getAnswer;
18+
mapping(uint256 => uint256) public getTimestamp;
19+
mapping(uint256 => uint256) private getStartedAt;
20+
21+
constructor(uint8 _decimals, int256 _initialAnswer) {
22+
decimals = _decimals;
23+
updateAnswer(_initialAnswer);
24+
aggregator = address(this); // For simplicity, we set the aggregator address to the contract address
25+
}
26+
27+
function updateAnswer(int256 _answer) internal {
28+
latestAnswer = _answer;
29+
latestTimestamp = block.timestamp;
30+
latestRound++;
31+
getAnswer[latestRound] = _answer;
32+
getTimestamp[latestRound] = block.timestamp;
33+
getStartedAt[latestRound] = block.timestamp;
34+
35+
emit AnswerUpdated(_answer, latestRound, block.timestamp);
36+
}
37+
38+
// This function is part of the AccessControlledOffchainAggregator interface. It is used to
39+
// update the new price and look like a transmit function from a real Chainlink Aggregator
40+
function transmit(
41+
bytes calldata _report, // _report should be the ABI encoded int256 new answer
42+
bytes32[] calldata _rs, bytes32[] calldata _ss, bytes32 _rawVs
43+
)
44+
external
45+
{
46+
int256 newAnswer = abi.decode(_report, (int256));
47+
updateAnswer(newAnswer);
48+
}
49+
50+
function updateRoundData(uint80 _roundId, int256 _answer, uint256 _timestamp, uint256 _startedAt) public {
51+
latestRound = _roundId;
52+
latestAnswer = _answer;
53+
latestTimestamp = _timestamp;
54+
getAnswer[latestRound] = _answer;
55+
getTimestamp[latestRound] = _timestamp;
56+
getStartedAt[latestRound] = _startedAt;
57+
}
58+
59+
function getRoundData(
60+
uint80 _roundId
61+
)
62+
external
63+
view
64+
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
65+
{
66+
return (_roundId, getAnswer[_roundId], getStartedAt[_roundId], getTimestamp[_roundId], _roundId);
67+
}
68+
69+
function latestRoundData()
70+
external
71+
view
72+
override
73+
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
74+
{
75+
return (
76+
uint80(latestRound),
77+
getAnswer[latestRound],
78+
getStartedAt[latestRound],
79+
getTimestamp[latestRound],
80+
uint80(latestRound)
81+
);
82+
}
83+
}

test/HoneyPot.sol

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ import {HoneyPot} from "../src/HoneyPot.sol";
66
import {HoneyPotDAO} from "../src/HoneyPotDAO.sol";
77
import {ChainlinkOvalImmutable, IAggregatorV3Source} from "oval-quickstart/ChainlinkOvalImmutable.sol";
88

9+
import {MockV3Aggregator} from "../src/mock/MockV3Aggregator.sol";
10+
911
contract HoneyPotTest is CommonTest {
1012
event ReceivedEther(address sender, uint256 amount);
1113
event DrainedEther(address to, uint256 amount);
1214
event OracleUpdated(address indexed newOracle);
13-
event HoneyPotCreated(address indexed creator, int256 liquidationPrice, uint256 initialBalance);
14-
event HoneyPotEmptied(address indexed honeyPotCreator, address indexed trigger, uint256 amount);
15-
event PotReset(address indexed owner, uint256 amount);
15+
event HoneyPotCreated(address indexed owner, int256 initialPrice, uint256 initialBalance);
16+
event HoneyPotEmptied(address indexed owner, address indexed liquidator, uint256 amount);
17+
event HoneyPotReset(address indexed owner, uint256 amount);
1618

1719
IAggregatorV3Source chainlink = IAggregatorV3Source(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419);
1820
ChainlinkOvalImmutable oracle;
@@ -63,7 +65,7 @@ contract HoneyPotTest is CommonTest {
6365

6466
// Reset HoneyPot for the caller
6567
vm.expectEmit(true, true, true, true);
66-
emit PotReset(address(this), honeyPotBalance);
68+
emit HoneyPotReset(address(this), honeyPotBalance);
6769
honeyPot.resetPot();
6870
(, uint256 testhoneyPotBalanceReset) = honeyPot.honeyPots(address(this));
6971
assertTrue(testhoneyPotBalanceReset == 0);
@@ -106,6 +108,42 @@ contract HoneyPotTest is CommonTest {
106108
assertTrue(testhoneyPotBalanceTwo == honeyPotBalance);
107109
}
108110

111+
function testCrackHoneyPotWithMockOracle() public {
112+
// Setup mock oracle
113+
MockV3Aggregator mock = new MockV3Aggregator(8, 100000000000);
114+
address[] memory unlockers = new address[](1);
115+
unlockers[0] = address(this);
116+
oracle = new ChainlinkOvalImmutable(IAggregatorV3Source(address(mock)), 8, 3, 10, unlockers);
117+
honeyPot = new HoneyPot(IAggregatorV3Source(address(oracle)));
118+
119+
// Create HoneyPot for the caller
120+
(, int256 currentPrice,,,) = oracle.latestRoundData();
121+
vm.expectEmit(true, true, true, true);
122+
emit HoneyPotCreated(address(this), currentPrice, honeyPotBalance);
123+
honeyPot.createHoneyPot{value: honeyPotBalance}();
124+
(, uint256 testhoneyPotBalance) = honeyPot.honeyPots(address(this));
125+
assertTrue(testhoneyPotBalance == honeyPotBalance);
126+
127+
// Simulate price change
128+
int256 newAnswer = (currentPrice * 103) / 100;
129+
bytes32[] memory empty = new bytes32[](0);
130+
mock.transmit(abi.encode(newAnswer), empty, empty, bytes32(0));
131+
132+
// Unlock the latest value
133+
oracle.unlockLatestValue();
134+
135+
uint256 liquidatorBalanceBefore = liquidator.balance;
136+
137+
vm.prank(liquidator);
138+
vm.expectEmit(true, true, true, true);
139+
emit HoneyPotEmptied(address(this), liquidator, honeyPotBalance);
140+
honeyPot.emptyHoneyPot(address(this));
141+
142+
uint256 liquidatorBalanceAfter = liquidator.balance;
143+
144+
assertTrue(liquidatorBalanceAfter == liquidatorBalanceBefore + honeyPotBalance);
145+
}
146+
109147
function testHoneyPotDAO() public {
110148
vm.expectEmit(true, true, true, true);
111149
emit ReceivedEther(address(this), 1 ether);

0 commit comments

Comments
 (0)