Skip to content

Commit

Permalink
feat: add mock oracle (#11)
Browse files Browse the repository at this point in the history
Signed-off-by: Pablo Maldonado <pablo@umaproject.org>
  • Loading branch information
md0x authored Feb 15, 2024
1 parent c5707e5 commit 858038c
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 16 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "lib/oval-quickstart"]
path = lib/oval-quickstart
url = https://github.com/UMAprotocol/oval-quickstart
[submodule "lib/oval-contracts"]
path = lib/oval-contracts
url = https://github.com/UMAprotocol/oval-contracts
1 change: 1 addition & 0 deletions lib/oval-contracts
Submodule oval-contracts added at 25afc4
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
oval-quickstart/=lib/oval-quickstart/src/
oval-contracts/=lib/oval-contracts/src/
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
15 changes: 13 additions & 2 deletions script/HoneyPot.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import {HoneyPot} from "../src/HoneyPot.sol";
import {HoneyPotDAO} from "../src/HoneyPotDAO.sol";
import {ChainlinkOvalImmutable, IAggregatorV3Source} from "oval-quickstart/ChainlinkOvalImmutable.sol";

import {MockV3Aggregator} from "../src/mock/MockV3Aggregator.sol";

contract HoneyPotDeploymentScript is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address chainlink = vm.envAddress("SOURCE_ADDRESS");
address chainlink = vm.envOr("SOURCE_ADDRESS", address(0));
uint256 lockWindow = vm.envUint("LOCK_WINDOW");
uint256 maxTraversal = vm.envUint("MAX_TRAVERSAL");
address unlocker = vm.envAddress("UNLOCKER");
Expand All @@ -24,9 +26,18 @@ contract HoneyPotDeploymentScript is Script {
console.log(" - Max traversal: ", maxTraversal);
console.log(" - Unlocker: ", unlockers[0]);

vm.startBroadcast(deployerPrivateKey);

if(chainlink == address(0)) {
console.log("Chainlink source address is not set");
console.log("Deploying a mock Chainlink source");
MockV3Aggregator mock = new MockV3Aggregator(8, 100000000000);
chainlink = address(mock);
console.log("Deployed mock Chainlink source at address: ", chainlink);
}

IAggregatorV3Source source = IAggregatorV3Source(chainlink);
uint8 decimals = IAggregatorV3Source(chainlink).decimals();
vm.startBroadcast(deployerPrivateKey);

ChainlinkOvalImmutable oracle =
new ChainlinkOvalImmutable(source, decimals, lockWindow, maxTraversal, unlockers);
Expand Down
20 changes: 10 additions & 10 deletions src/HoneyPot.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ contract HoneyPot is Ownable {
IAggregatorV3Source public oracle; // Oval serving as a Chainlink oracle

event OracleUpdated(address indexed newOracle);
event HoneyPotCreated(address indexed creator, int256 liquidationPrice, uint256 initialBalance);
event HoneyPotEmptied(address indexed honeyPotCreator, address indexed trigger, uint256 amount);
event PotReset(address indexed owner, uint256 amount);
event HoneyPotCreated(address indexed owner, int256 initialPrice, uint256 amount);
event HoneyPotEmptied(address indexed owner, address indexed liquidator, uint256 amount);
event HoneyPotReset(address indexed owner, uint256 amount);

constructor(IAggregatorV3Source _oracle) {
oracle = _oracle;
Expand All @@ -40,30 +40,30 @@ contract HoneyPot is Ownable {
emit HoneyPotCreated(msg.sender, currentPrice, msg.value);
}

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

amount = userPot.balance;
userPot.balance = 0; // reset the balance
userPot.liquidationPrice = 0; // reset the liquidation price
Address.sendValue(payable(recipient), amount);
}

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

HoneyPotDetails storage userPot = honeyPots[honeyPotCreator];
HoneyPotDetails storage userPot = honeyPots[owner];

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

uint256 withdrawnAmount = _emptyPotForUser(honeyPotCreator, msg.sender);
emit HoneyPotEmptied(honeyPotCreator, msg.sender, withdrawnAmount);
uint256 withdrawnAmount = _emptyPotForUser(owner, msg.sender);
emit HoneyPotEmptied(owner, msg.sender, withdrawnAmount);
}

function resetPot() external {
uint256 withdrawnAmount = _emptyPotForUser(msg.sender, msg.sender);
emit PotReset(msg.sender, withdrawnAmount);
emit HoneyPotReset(msg.sender, withdrawnAmount);
}
}
83 changes: 83 additions & 0 deletions src/mock/MockV3Aggregator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
pragma solidity ^0.8.0;

import {IAggregatorV3} from "oval-contracts/interfaces/chainlink/IAggregatorV3.sol";

contract MockV3Aggregator is IAggregatorV3 {

event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt);

address public immutable aggregator;
uint256 public constant version = 0;

uint8 public override decimals;
int256 public override latestAnswer;
uint256 public override latestTimestamp;
uint256 public latestRound;

mapping(uint256 => int256) public getAnswer;
mapping(uint256 => uint256) public getTimestamp;
mapping(uint256 => uint256) private getStartedAt;

constructor(uint8 _decimals, int256 _initialAnswer) {
decimals = _decimals;
updateAnswer(_initialAnswer);
aggregator = address(this); // For simplicity, we set the aggregator address to the contract address
}

function updateAnswer(int256 _answer) internal {
latestAnswer = _answer;
latestTimestamp = block.timestamp;
latestRound++;
getAnswer[latestRound] = _answer;
getTimestamp[latestRound] = block.timestamp;
getStartedAt[latestRound] = block.timestamp;

emit AnswerUpdated(_answer, latestRound, block.timestamp);
}

// This function is part of the AccessControlledOffchainAggregator interface. It is used to
// update the new price and look like a transmit function from a real Chainlink Aggregator
function transmit(
bytes calldata _report, // _report should be the ABI encoded int256 new answer
bytes32[] calldata _rs, bytes32[] calldata _ss, bytes32 _rawVs
)
external
{
int256 newAnswer = abi.decode(_report, (int256));
updateAnswer(newAnswer);
}

function updateRoundData(uint80 _roundId, int256 _answer, uint256 _timestamp, uint256 _startedAt) public {
latestRound = _roundId;
latestAnswer = _answer;
latestTimestamp = _timestamp;
getAnswer[latestRound] = _answer;
getTimestamp[latestRound] = _timestamp;
getStartedAt[latestRound] = _startedAt;
}

function getRoundData(
uint80 _roundId
)
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
{
return (_roundId, getAnswer[_roundId], getStartedAt[_roundId], getTimestamp[_roundId], _roundId);
}

function latestRoundData()
external
view
override
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
{
return (
uint80(latestRound),
getAnswer[latestRound],
getStartedAt[latestRound],
getTimestamp[latestRound],
uint80(latestRound)
);
}
}
46 changes: 42 additions & 4 deletions test/HoneyPot.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import {HoneyPot} from "../src/HoneyPot.sol";
import {HoneyPotDAO} from "../src/HoneyPotDAO.sol";
import {ChainlinkOvalImmutable, IAggregatorV3Source} from "oval-quickstart/ChainlinkOvalImmutable.sol";

import {MockV3Aggregator} from "../src/mock/MockV3Aggregator.sol";

contract HoneyPotTest is CommonTest {
event ReceivedEther(address sender, uint256 amount);
event DrainedEther(address to, uint256 amount);
event OracleUpdated(address indexed newOracle);
event HoneyPotCreated(address indexed creator, int256 liquidationPrice, uint256 initialBalance);
event HoneyPotEmptied(address indexed honeyPotCreator, address indexed trigger, uint256 amount);
event PotReset(address indexed owner, uint256 amount);
event HoneyPotCreated(address indexed owner, int256 initialPrice, uint256 initialBalance);
event HoneyPotEmptied(address indexed owner, address indexed liquidator, uint256 amount);
event HoneyPotReset(address indexed owner, uint256 amount);

IAggregatorV3Source chainlink = IAggregatorV3Source(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419);
ChainlinkOvalImmutable oracle;
Expand Down Expand Up @@ -63,7 +65,7 @@ contract HoneyPotTest is CommonTest {

// Reset HoneyPot for the caller
vm.expectEmit(true, true, true, true);
emit PotReset(address(this), honeyPotBalance);
emit HoneyPotReset(address(this), honeyPotBalance);
honeyPot.resetPot();
(, uint256 testhoneyPotBalanceReset) = honeyPot.honeyPots(address(this));
assertTrue(testhoneyPotBalanceReset == 0);
Expand Down Expand Up @@ -106,6 +108,42 @@ contract HoneyPotTest is CommonTest {
assertTrue(testhoneyPotBalanceTwo == honeyPotBalance);
}

function testCrackHoneyPotWithMockOracle() public {
// Setup mock oracle
MockV3Aggregator mock = new MockV3Aggregator(8, 100000000000);
address[] memory unlockers = new address[](1);
unlockers[0] = address(this);
oracle = new ChainlinkOvalImmutable(IAggregatorV3Source(address(mock)), 8, 3, 10, unlockers);
honeyPot = new HoneyPot(IAggregatorV3Source(address(oracle)));

// Create HoneyPot for the caller
(, int256 currentPrice,,,) = oracle.latestRoundData();
vm.expectEmit(true, true, true, true);
emit HoneyPotCreated(address(this), currentPrice, honeyPotBalance);
honeyPot.createHoneyPot{value: honeyPotBalance}();
(, uint256 testhoneyPotBalance) = honeyPot.honeyPots(address(this));
assertTrue(testhoneyPotBalance == honeyPotBalance);

// Simulate price change
int256 newAnswer = (currentPrice * 103) / 100;
bytes32[] memory empty = new bytes32[](0);
mock.transmit(abi.encode(newAnswer), empty, empty, bytes32(0));

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

uint256 liquidatorBalanceBefore = liquidator.balance;

vm.prank(liquidator);
vm.expectEmit(true, true, true, true);
emit HoneyPotEmptied(address(this), liquidator, honeyPotBalance);
honeyPot.emptyHoneyPot(address(this));

uint256 liquidatorBalanceAfter = liquidator.balance;

assertTrue(liquidatorBalanceAfter == liquidatorBalanceBefore + honeyPotBalance);
}

function testHoneyPotDAO() public {
vm.expectEmit(true, true, true, true);
emit ReceivedEther(address(this), 1 ether);
Expand Down

0 comments on commit 858038c

Please sign in to comment.