Skip to content

Commit

Permalink
test: Use DSROracle in invariant tests (SC-484) (#16)
Browse files Browse the repository at this point in the history
* feat: first test working

* feat: use larger numbers:

* feat: test with initial burn amount passing

* feat: update tests to work with updated burn logic, move conversion functions around and use previews

* feat: remove todos

* fix: update to remove console and update comment

* feat: get swap tests working

* feat: get all swap tests working

* fix: update for three assets in logic

* feat: all tests passing

* fix: rm commented out test

* feat: add preview swap tests

* feat: move logic out of single use internal and use conversion rate everywhere

* feat: move divRoundUp out of single use internal

* feat: add full coverage for conversion tests

* feat: add more preview cases

* feat: refactor PSM to use three assets

* fix: rm comment

* feat: add interface, natspec, events, referral code, tests passing

* fix: update to rm consolegp

* feat: add events testing

* feat: make precisions internal and add state var natspec

* feat: finish natspec

* feat: add readme

* feat: add referral code note

* fix: update constructor test

* fix: update links

* fix: reformatting

* fix: update testing section

* fix: improve overview

* feat: add emojis

* feat: remove all share burn logic, get all non inflation attack tests to pass

* fix: cleanup diff

* fix: update to use initial deposit instead of burn

* feat: add readme section explaining attack

* fix: minimize diff

* fix: address bartek comments

* feat: update all tests to work with new interfaces

* feat: add deposit failure mode tests

* feat: update to add assertions for return in deposit

* feat: add withdraw failure tests

* feat: update to address comments outside sharesToBurn

* feat: update inflation attack test and readme

* fix: update readme

* feat: update test to constrain deposit/withdraw

* feat: update to add both cases

* feat: update per review

* feat: update to use underscore bound, fix test

* fix: typo

* feat: add overrides, remove referrals, update referral type

* fix: update expect emit

* feat: update name and remove todos

* feat: move files and set up structure

* feat: update to rename files, contracts, and errors

* fix: rm dup file, update toml

* feat: get deposits working

* chore: refactor into proper inheritance structure

* feat: get all functions working with reverts

* feat: update conversion

* feat: get swaps working without reverts

* feat: add fully working deposit/withdraw/swaps, invariant_B failing

* ci: update for ci

* fix: update name

* chore: rm basly cased file

* chore: re add

* fix: re add invariant

* ci: experiment with 2 million total calls

* ci: add show progress flag

* fix: move file back

* ci: update verbosity

* ci: add PR profile

* fix: rm redundant files

* feat: update from review changes

* feat: add afterInvariant hook

* fix: update invariant

* fix: add fuzz failure

* chore: rm indexing comment

* feat: refactor structure

* feat: both invariants working

* fix: update comment

* feat: add rate setting logic

* fix: update toml

* fix: rm redundant files from merge

* fix: update tolerances

* feat: update to add seeding as part of invariants

* fix: update toml

* fix: rm redundant files from merge

* fix: update tolerances

* fix: rm invariant logs

* fix: update tolerance

* forge install: xchain-dsr-oracle

v1.0.0

* feat: setup timebased handler, refactor rate provider structure

* feat: tests passing

* chore: first cleanup

* fix: test names

* fix: update dsr value, change chi approach
  • Loading branch information
lucas-manuel authored Jul 4, 2024
1 parent fafd16e commit ebef16e
Show file tree
Hide file tree
Showing 16 changed files with 246 additions and 79 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "lib/erc20-helpers"]
path = lib/erc20-helpers
url = https://github.com/marsfoundation/erc20-helpers
[submodule "lib/xchain-dsr-oracle"]
path = lib/xchain-dsr-oracle
url = https://github.com/marsfoundation/xchain-dsr-oracle
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ runs = 1000
[invariant]
runs = 20
depth = 1000
shrink_run_limit = 0
shrink_run_limit = 1000

[profile.pr.invariant]
runs = 200
Expand Down
1 change: 1 addition & 0 deletions lib/xchain-dsr-oracle
Submodule xchain-dsr-oracle added at a02e59
7 changes: 2 additions & 5 deletions src/PSM3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@ import { IERC20 } from "erc20-helpers/interfaces/IERC20.sol";

import { SafeERC20 } from "erc20-helpers/SafeERC20.sol";

import { IPSM3 } from "src/interfaces/IPSM3.sol";

interface IRateProviderLike {
function getConversionRate() external view returns (uint256);
}
import { IPSM3 } from "src/interfaces/IPSM3.sol";
import { IRateProviderLike } from "src/interfaces/IRateProviderLike.sol";

contract PSM3 is IPSM3 {

Expand Down
6 changes: 6 additions & 0 deletions src/interfaces/IRateProviderLike.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13;

interface IRateProviderLike {
function getConversionRate() external view returns (uint256);
}
12 changes: 9 additions & 3 deletions test/PSMTestBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import "forge-std/Test.sol";

import { PSM3 } from "src/PSM3.sol";

import { IRateProviderLike } from "src/interfaces/IRateProviderLike.sol";

import { MockERC20 } from "erc20-helpers/MockERC20.sol";

import { MockRateProvider } from "test/mocks/MockRateProvider.sol";
Expand All @@ -18,7 +20,9 @@ contract PSMTestBase is Test {
MockERC20 public usdc;
MockERC20 public sDai;

MockRateProvider public rateProvider;
IRateProviderLike public rateProvider; // Can be overridden by dsrOracle using same interface

MockRateProvider public mockRateProvider; // Interface used for mocking

modifier assertAtomicPsmValueDoesNotChange {
uint256 beforeValue = _getPsmValue();
Expand All @@ -36,10 +40,12 @@ contract PSMTestBase is Test {
usdc = new MockERC20("usdc", "usdc", 6);
sDai = new MockERC20("sDai", "sDai", 18);

rateProvider = new MockRateProvider();
mockRateProvider = new MockRateProvider();

// NOTE: Using 1.25 for easy two way conversions
rateProvider.__setConversionRate(1.25e27);
mockRateProvider.__setConversionRate(1.25e27);

rateProvider = IRateProviderLike(address(mockRateProvider));

psm = new PSM3(address(dai), address(usdc), address(sDai), address(rateProvider));

Expand Down
130 changes: 119 additions & 11 deletions test/invariant/Invariants.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,27 @@ pragma solidity ^0.8.13;

import "forge-std/Test.sol";

import { DSRAuthOracle } from "lib/xchain-dsr-oracle/src/DSRAuthOracle.sol";

import { PSM3 } from "src/PSM3.sol";

import { IRateProviderLike } from "src/interfaces/IRateProviderLike.sol";

import { PSMTestBase } from "test/PSMTestBase.sol";

import { LpHandler } from "test/invariant/handlers/LpHandler.sol";
import { RateSetterHandler } from "test/invariant/handlers/RateSetterHandler.sol";
import { SwapperHandler } from "test/invariant/handlers/SwapperHandler.sol";
import { TransferHandler } from "test/invariant/handlers/TransferHandler.sol";
import { LpHandler } from "test/invariant/handlers/LpHandler.sol";
import { RateSetterHandler } from "test/invariant/handlers/RateSetterHandler.sol";
import { SwapperHandler } from "test/invariant/handlers/SwapperHandler.sol";
import { TimeBasedRateHandler } from "test/invariant/handlers/TimeBasedRateHandler.sol";
import { TransferHandler } from "test/invariant/handlers/TransferHandler.sol";

abstract contract PSMInvariantTestBase is PSMTestBase {

LpHandler public lpHandler;
RateSetterHandler public rateSetterHandler;
SwapperHandler public swapperHandler;
TransferHandler public transferHandler;
LpHandler public lpHandler;
RateSetterHandler public rateSetterHandler;
SwapperHandler public swapperHandler;
TransferHandler public transferHandler;
TimeBasedRateHandler public timeBasedRateHandler;

address BURN_ADDRESS = makeAddr("burn-address");

Expand Down Expand Up @@ -47,7 +55,7 @@ abstract contract PSMInvariantTestBase is PSMTestBase {
assertApproxEqAbs(
psm.getPsmTotalValue(),
psm.convertToAssetValue(psm.totalShares()),
3
4
);
}

Expand Down Expand Up @@ -260,7 +268,7 @@ contract PSMInvariants_RateSetting_NoTransfer is PSMInvariantTestBase {
super.setUp();

lpHandler = new LpHandler(psm, dai, usdc, sDai, 3);
rateSetterHandler = new RateSetterHandler(rateProvider, 1.25e27);
rateSetterHandler = new RateSetterHandler(address(rateProvider), 1.25e27);
swapperHandler = new SwapperHandler(psm, dai, usdc, sDai, 3);

targetContract(address(lpHandler));
Expand Down Expand Up @@ -292,7 +300,7 @@ contract PSMInvariants_RateSetting_WithTransfers is PSMInvariantTestBase {
super.setUp();

lpHandler = new LpHandler(psm, dai, usdc, sDai, 3);
rateSetterHandler = new RateSetterHandler(rateProvider, 1.25e27);
rateSetterHandler = new RateSetterHandler(address(rateProvider), 1.25e27);
swapperHandler = new SwapperHandler(psm, dai, usdc, sDai, 3);
transferHandler = new TransferHandler(psm, dai, usdc, sDai);

Expand All @@ -319,3 +327,103 @@ contract PSMInvariants_RateSetting_WithTransfers is PSMInvariantTestBase {
}

}

contract PSMInvariants_TimeBasedRateSetting_NoTransfer is PSMInvariantTestBase {

function setUp() public override {
super.setUp();

DSRAuthOracle dsrOracle = new DSRAuthOracle();

// Redeploy PSM with new rate provider
psm = new PSM3(address(dai), address(usdc), address(sDai), address(dsrOracle));

// Seed the new PSM with 1e18 shares (1e18 of value)
_deposit(address(dai), BURN_ADDRESS, 1e18);

lpHandler = new LpHandler(psm, dai, usdc, sDai, 3);
swapperHandler = new SwapperHandler(psm, dai, usdc, sDai, 3);
timeBasedRateHandler = new TimeBasedRateHandler(dsrOracle);

// Handler acts in the same way as a receiver on L2, so add as a data provider to the
// oracle.
dsrOracle.grantRole(dsrOracle.DATA_PROVIDER_ROLE(), address(timeBasedRateHandler));

rateProvider = IRateProviderLike(address(dsrOracle));

// Manually set initial values for the oracle through the handler to start
timeBasedRateHandler.setPotData(1e27, block.timestamp);

targetContract(address(lpHandler));
targetContract(address(swapperHandler));
targetContract(address(timeBasedRateHandler));
}

function invariant_A() public view {
_checkInvariant_A();
}

function invariant_B() public view {
_checkInvariant_B();
}

function invariant_C() public view {
_checkInvariant_C();
}

function afterInvariant() public {
_withdrawAllPositions();
}

}

contract PSMInvariants_TimeBasedRateSetting_WithTransfers is PSMInvariantTestBase {

function setUp() public override {
super.setUp();

DSRAuthOracle dsrOracle = new DSRAuthOracle();

// Redeploy PSM with new rate provider
psm = new PSM3(address(dai), address(usdc), address(sDai), address(dsrOracle));

// Seed the new PSM with 1e18 shares (1e18 of value)
_deposit(address(dai), BURN_ADDRESS, 1e18);

lpHandler = new LpHandler(psm, dai, usdc, sDai, 3);
swapperHandler = new SwapperHandler(psm, dai, usdc, sDai, 3);
timeBasedRateHandler = new TimeBasedRateHandler(dsrOracle);
transferHandler = new TransferHandler(psm, dai, usdc, sDai);

// Handler acts in the same way as a receiver on L2, so add as a data provider to the
// oracle.
dsrOracle.grantRole(dsrOracle.DATA_PROVIDER_ROLE(), address(timeBasedRateHandler));

rateProvider = IRateProviderLike(address(dsrOracle));

// Manually set initial values for the oracle through the handler to start
timeBasedRateHandler.setPotData(1e27, block.timestamp);

targetContract(address(lpHandler));
targetContract(address(swapperHandler));
targetContract(address(timeBasedRateHandler));
targetContract(address(transferHandler));
}

function invariant_A() public view {
_checkInvariant_A();
}

function invariant_B() public view {
_checkInvariant_B();
}

function invariant_C() public view {
_checkInvariant_C();
}

function afterInvariant() public {
_withdrawAllPositions();
}

}
5 changes: 2 additions & 3 deletions test/invariant/handlers/RateSetterHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ contract RateSetterHandler is StdUtils {

uint256 public setRateCount;

constructor(MockRateProvider rateProvider_, uint256 initialRate) {
rateProvider = rateProvider_;
constructor(address rateProvider_, uint256 initialRate) {
rateProvider = MockRateProvider(rateProvider_);
rate = initialRate;
}

Expand All @@ -26,5 +26,4 @@ contract RateSetterHandler is StdUtils {

setRateCount++;
}

}
47 changes: 47 additions & 0 deletions test/invariant/handlers/TimeBasedRateHandler.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13;

import { StdCheats } from "forge-std/StdCheats.sol";
import { StdUtils } from "forge-std/StdUtils.sol";

import { DSRAuthOracle } from "lib/xchain-dsr-oracle/src/DSRAuthOracle.sol";
import { IDSROracle } from "lib/xchain-dsr-oracle/src/interfaces/IDSROracle.sol";

contract TimeBasedRateHandler is StdCheats, StdUtils {

uint256 public dsr;
uint256 public chi;
uint256 public rho;

uint256 constant ONE_HUNDRED_PCT_APY_DSR = 1.000000021979553151239153027e27;

DSRAuthOracle public dsrOracle;

uint256 public setRateCount;

constructor(DSRAuthOracle dsrOracle_) {
dsrOracle = dsrOracle_;
}

// This acts as a receiver on an L2.
function setPotData(uint256 newDsr, uint256 newRho) external {
dsr = _bound(newDsr, 1e27, ONE_HUNDRED_PCT_APY_DSR);
rho = _bound(newRho, rho, block.timestamp);

// If chi hasn't been set yet, set to 1e27, else recalculate it in the same way it would
// happen during a refresh.
uint256 rate = dsrOracle.getConversionRate();
uint256 chi = rate == 0 ? 1e27 : rate;

dsrOracle.setPotData(IDSROracle.PotData({
dsr: uint96(dsr),
chi: uint120(chi),
rho: uint40(rho)
}));
}

function warp(uint256 skipTime) external {
skip(_bound(skipTime, 0, 45 days));
}

}
Loading

0 comments on commit ebef16e

Please sign in to comment.