Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: Add basic invariant testing (SC-459) #7

Merged
merged 96 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
822694a
feat: first test working
lucas-manuel May 18, 2024
d6530e1
feat: use larger numbers:
lucas-manuel May 18, 2024
8f11df4
feat: test with initial burn amount passing
lucas-manuel May 18, 2024
78fb4ad
feat: update tests to work with updated burn logic, move conversion f…
lucas-manuel May 21, 2024
d32fd2f
feat: remove todos
lucas-manuel May 21, 2024
2290417
fix: update to remove console and update comment
lucas-manuel May 21, 2024
1fe351b
feat: get swap tests working
lucas-manuel May 22, 2024
cbea0ee
feat: get all swap tests working
lucas-manuel May 22, 2024
7d1101d
fix: update for three assets in logic
lucas-manuel May 22, 2024
80440e0
feat: all tests passing
lucas-manuel May 22, 2024
51b980d
fix: rm commented out test
lucas-manuel May 22, 2024
e6b67c6
feat: add preview swap tests
lucas-manuel May 22, 2024
ffa4513
feat: move logic out of single use internal and use conversion rate e…
lucas-manuel May 22, 2024
e9c519b
feat: move divRoundUp out of single use internal
lucas-manuel May 22, 2024
6ec2b23
feat: add full coverage for conversion tests
lucas-manuel May 22, 2024
2be5afc
feat: add more preview cases
lucas-manuel May 22, 2024
4299e03
feat: refactor PSM to use three assets
lucas-manuel May 22, 2024
64c9939
fix: rm comment
lucas-manuel May 22, 2024
2c43f92
feat: add interface, natspec, events, referral code, tests passing
lucas-manuel May 22, 2024
57c65d6
fix: update to rm consolegp
lucas-manuel May 22, 2024
c0616bf
Merge branch 'sc-447-build-threeway-swap' into sc-448-add-referral-code
lucas-manuel May 22, 2024
416034f
feat: add events testing
lucas-manuel May 23, 2024
114f92b
feat: make precisions internal and add state var natspec
lucas-manuel May 23, 2024
8c2f83b
feat: finish natspec
lucas-manuel May 23, 2024
61443c8
feat: add readme
lucas-manuel May 23, 2024
6ca25a8
feat: add referral code note
lucas-manuel May 23, 2024
c4f8c78
fix: update constructor test
lucas-manuel May 23, 2024
b070a44
fix: update links
lucas-manuel May 23, 2024
6947398
fix: reformatting
lucas-manuel May 23, 2024
9bdcfc5
fix: update testing section
lucas-manuel May 23, 2024
d69bc8b
fix: improve overview
lucas-manuel May 23, 2024
04cc6ac
feat: add emojis
lucas-manuel May 23, 2024
54a4afe
feat: remove all share burn logic, get all non inflation attack tests…
May 30, 2024
ce5653d
fix: cleanup diff
May 30, 2024
259cf16
fix: update to use initial deposit instead of burn
May 30, 2024
62ef5c2
feat: add readme section explaining attack
May 30, 2024
0720150
fix: minimize diff
May 30, 2024
5a03fde
feat: fix conflicts
May 30, 2024
c5f7cf0
feat: rm all references to initial burn, update tests
May 30, 2024
e6c654d
fix: address bartek comments
May 30, 2024
e540afa
feat: update all tests to work with new interfaces
May 30, 2024
c41b010
feat: add deposit failure mode tests
May 30, 2024
847a1db
feat: update to add assertions for return in deposit
May 30, 2024
ecbf99b
feat: add withdraw failure tests
May 30, 2024
44a5d52
feat: update to address comments outside sharesToBurn
Jun 3, 2024
9c3958e
feat: update inflation attack test and readme
Jun 3, 2024
4c9cf09
fix: update readme
Jun 3, 2024
cb9931a
feat: update test to constrain deposit/withdraw
Jun 3, 2024
e576672
feat: update to add both cases
Jun 3, 2024
e994287
fix: merge conflicts
Jun 3, 2024
9688892
fix: merge conflicts
Jun 3, 2024
b80ed6e
fix: merge conflicts
Jun 3, 2024
108624a
fix: merge conflicts
Jun 4, 2024
73d8c8c
feat: update per review
lucas-manuel Jun 4, 2024
d6c6f03
fix: merge conflicts
lucas-manuel Jun 4, 2024
5319255
Merge branch 'sc-448-add-referral-code' into sc-453-add-receiver-depo…
lucas-manuel Jun 4, 2024
0f182b2
feat: update to use underscore bound, fix test
lucas-manuel Jun 5, 2024
dfab7ec
Merge branch 'sc-447-build-threeway-swap' into sc-448-add-referral-code
lucas-manuel Jun 5, 2024
b288568
fix: merge conflicts
lucas-manuel Jun 5, 2024
ba44086
fix: typo
lucas-manuel Jun 5, 2024
62f5423
fix: merge conflicts
lucas-manuel Jun 5, 2024
9a243f9
feat: add overrides, remove referrals, update referral type
lucas-manuel Jun 10, 2024
cc68ed3
fix: update expect emit
lucas-manuel Jun 10, 2024
9862a53
feat: update name and remove todos
lucas-manuel Jun 10, 2024
376fa83
feat: move files and set up structure
lucas-manuel Jun 10, 2024
73cb854
feat: update to rename files, contracts, and errors
lucas-manuel Jun 10, 2024
f711954
fix: merge conflicts
lucas-manuel Jun 10, 2024
23c870d
fix: merge conflicts
lucas-manuel Jun 10, 2024
ce0949f
fix: merge conflicts
lucas-manuel Jun 10, 2024
81d2e37
fix: rm dup file, update toml
lucas-manuel Jun 10, 2024
fbff8d3
feat: get deposits working
lucas-manuel Jun 10, 2024
3e5a3eb
chore: refactor into proper inheritance structure
lucas-manuel Jun 10, 2024
2e60593
feat: get all functions working with reverts
lucas-manuel Jun 10, 2024
14fdf08
fix: cofnlicts
lucas-manuel Jun 13, 2024
defc84e
feat: update conversion
lucas-manuel Jun 13, 2024
bbc3e8e
feat: get swaps working without reverts
lucas-manuel Jun 13, 2024
4c04863
feat: add fully working deposit/withdraw/swaps, invariant_B failing
lucas-manuel Jun 13, 2024
d23bfbe
ci: update for ci
lucas-manuel Jun 13, 2024
7ba3e16
fix: update name
lucas-manuel Jun 13, 2024
499afae
chore: rm basly cased file
lucas-manuel Jun 13, 2024
d1daf4b
chore: re add
lucas-manuel Jun 13, 2024
84c3787
fix: re add invariant
lucas-manuel Jun 13, 2024
3814067
ci: experiment with 2 million total calls
lucas-manuel Jun 13, 2024
9215a29
ci: add show progress flag
lucas-manuel Jun 13, 2024
9caf3c6
fix: move file back
lucas-manuel Jun 13, 2024
094d613
ci: update verbosity
lucas-manuel Jun 13, 2024
2085360
ci: add PR profile
lucas-manuel Jun 13, 2024
0605586
fix: rm redundant files
lucas-manuel Jun 13, 2024
74dba88
feat: update from review changes
lucas-manuel Jun 14, 2024
ba9b5fe
Merge branch 'master' into sc-459-add-basic-invariant-testing
lucas-manuel Jun 20, 2024
608daf5
Merge branch 'master' into sc-459-add-basic-invariant-testing
lucas-manuel Jun 20, 2024
c4bf0cc
fix: update invariant
lucas-manuel Jun 20, 2024
df65c1b
fix: add fuzz failure
lucas-manuel Jun 20, 2024
42581aa
chore: rm indexing comment
lucas-manuel Jun 21, 2024
f982ff3
fix: rm redundant files from merge
lucas-manuel Jun 21, 2024
a2e6f05
feat: update to add seeding as part of invariants
lucas-manuel Jun 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions .github/workflows/ci.yml → .github/workflows/master.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
name: CI

on:
workflow_dispatch:
pull_request:
push:
branches:
- master
branches: [master]

env:
FOUNDRY_PROFILE: ci
Expand Down Expand Up @@ -40,7 +37,7 @@ jobs:
ARBITRUM_NOVA_RPC_URL: ${{secrets.ARBITRUM_NOVA_RPC_URL}}
GNOSIS_CHAIN_RPC_URL: ${{secrets.GNOSIS_CHAIN_RPC_URL}}
BASE_RPC_URL: ${{secrets.BASE_RPC_URL}}
run: FOUNDRY_PROFILE=ci forge test
run: FOUNDRY_PROFILE=master forge test -vv --show-progress

# coverage:
# runs-on: ubuntu-latest
Expand Down
87 changes: 87 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: CI

on: [pull_request]

env:
FOUNDRY_PROFILE: ci

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1

- name: Build contracts
run: |
forge --version
forge build --sizes

test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1

- name: Run tests
env:
MAINNET_RPC_URL: ${{secrets.MAINNET_RPC_URL}}
OPTIMISM_RPC_URL: ${{secrets.OPTIMISM_RPC_URL}}
ARBITRUM_ONE_RPC_URL: ${{secrets.ARBITRUM_ONE_RPC_URL}}
ARBITRUM_NOVA_RPC_URL: ${{secrets.ARBITRUM_NOVA_RPC_URL}}
GNOSIS_CHAIN_RPC_URL: ${{secrets.GNOSIS_CHAIN_RPC_URL}}
BASE_RPC_URL: ${{secrets.BASE_RPC_URL}}
run: FOUNDRY_PROFILE=pr forge test -vv --show-progress

# coverage:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v3

# - name: Install Foundry
# uses: foundry-rs/foundry-toolchain@v1

# - name: Run coverage
# env:
# MAINNET_RPC_URL: ${{secrets.MAINNET_RPC_URL}}
# OPTIMISM_RPC_URL: ${{secrets.OPTIMISM_RPC_URL}}
# ARBITRUM_ONE_RPC_URL: ${{secrets.ARBITRUM_ONE_RPC_URL}}
# ARBITRUM_NOVA_RPC_URL: ${{secrets.ARBITRUM_NOVA_RPC_URL}}
# GNOSIS_CHAIN_RPC_URL: ${{secrets.GNOSIS_CHAIN_RPC_URL}}
# BASE_RPC_URL: ${{secrets.BASE_RPC_URL}}
# run: forge coverage --report summary --report lcov

# # To ignore coverage for certain directories modify the paths in this step as needed. The
# # below default ignores coverage results for the test and script directories. Alternatively,
# # to include coverage in all directories, comment out this step. Note that because this
# # filtering applies to the lcov file, the summary table generated in the previous step will
# # still include all files and directories.
# # The `--rc lcov_branch_coverage=1` part keeps branch info in the filtered report, since lcov
# # defaults to removing branch info.
# - name: Filter directories
# run: |
# sudo apt update && sudo apt install -y lcov
# lcov --remove lcov.info 'test/*' 'script/*' --output-file lcov.info --rc lcov_branch_coverage=1

# # This step posts a detailed coverage report as a comment and deletes previous comments on
# # each push. The below step is used to fail coverage if the specified coverage threshold is
# # not met. The below step can post a comment (when it's `github-token` is specified) but it's
# # not as useful, and this action cannot fail CI based on a minimum coverage threshold, which
# # is why we use both in this way.
# - name: Post coverage report
# if: github.event_name == 'pull_request' # This action fails when ran outside of a pull request.
# uses: romeovs/[email protected]
# with:
# delete-old-comments: true
# lcov-file: ./lcov.info
# github-token: ${{ secrets.GITHUB_TOKEN }} # Adds a coverage summary comment to the PR.

# - name: Verify minimum coverage
# uses: zgosalvez/github-actions-report-lcov@v2
# with:
# coverage-files: ./lcov.info
# minimum-coverage: 90 # Set coverage threshold.
18 changes: 18 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,24 @@ optimizer_runs = 200
[fuzz]
runs = 1000

[invariant]
runs = 20
depth = 1000

[profile.pr.invariant]
runs = 200
depth = 1000

[profile.pr.fuzz]
runs = 100_000

[profile.master.invariant]
runs = 200
depth = 10_000

[profile.master.fuzz]
runs = 1_000_000

# See more config options https://github.com/foundry-rs/foundry/tree/master/config

remappings = [
Expand Down
2 changes: 0 additions & 2 deletions src/interfaces/IPSM3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { IERC20 } from "erc20-helpers/interfaces/IERC20.sol";

interface IPSM3 {

// TODO: Determine priority for indexing

/**********************************************************************************************/
/*** Events ***/
/**********************************************************************************************/
Expand Down
80 changes: 80 additions & 0 deletions test/invariant/Invariants.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13;

import "forge-std/Test.sol";

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

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

contract PSMInvariantTests is PSMTestBase {

LpHandler public lpHandler;
SwapperHandler public swapperHandler;

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

// NOTE [CRITICAL]: All invariant tests are operating under the assumption that the initial seed
// deposit of 1e18 shares has been made. This is a key requirement and
// assumption for all invariant tests.
function setUp() public override {
super.setUp();

// Seed the pool 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);

// TODO: Add rate updates
rateProvider.__setConversionRate(1.25e27);

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

function invariant_A() public view {
assertEq(
psm.shares(address(lpHandler.lps(0))) +
psm.shares(address(lpHandler.lps(1))) +
psm.shares(address(lpHandler.lps(2))) +
1e18, // Seed amount
psm.totalShares()
);
}

function invariant_B() public view {
assertApproxEqAbs(
psm.getPsmTotalValue(),
psm.convertToAssetValue(psm.totalShares()),
2
);
}

function invariant_C() public view {
assertApproxEqAbs(
psm.convertToAssetValue(psm.shares(address(lpHandler.lps(0)))) +
psm.convertToAssetValue(psm.shares(address(lpHandler.lps(1)))) +
psm.convertToAssetValue(psm.shares(address(lpHandler.lps(2)))) +
psm.convertToAssetValue(1e18), // Seed amount
psm.getPsmTotalValue(),
4
);
}

function invariant_logs() public view {
console.log("depositCount ", lpHandler.depositCount());
console.log("withdrawCount ", lpHandler.withdrawCount());
console.log("swapCount ", swapperHandler.swapCount());
console.log("zeroBalanceCount", swapperHandler.zeroBalanceCount());
console.log(
"sum ",
lpHandler.depositCount() +
lpHandler.withdrawCount() +
swapperHandler.swapCount() +
swapperHandler.zeroBalanceCount()
);
}

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

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

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

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

contract HandlerBase is CommonBase, StdCheatsSafe, StdUtils {

PSM3 public psm;

MockERC20[3] public assets;

constructor(
PSM3 psm_,
MockERC20 asset0,
MockERC20 asset1,
MockERC20 asset2
) {
psm = psm_;

assets[0] = asset0;
assets[1] = asset1;
assets[2] = asset2;
}

function _getAsset(uint256 indexSeed) internal view returns (MockERC20) {
return assets[indexSeed % assets.length];
}

function _hash(uint256 number_, string memory salt) internal pure returns (uint256 hash_) {
hash_ = uint256(keccak256(abi.encode(number_, salt)));
}

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

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

import { HandlerBase } from "test/invariant/handlers/HandlerBase.sol";

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

contract LpHandler is HandlerBase {

address[] public lps;

uint256 public depositCount;
uint256 public withdrawCount;

uint256 public constant TRILLION = 1e12;

constructor(
PSM3 psm_,
MockERC20 asset0,
MockERC20 asset1,
MockERC20 asset2,
uint256 lpCount
) HandlerBase(psm_, asset0, asset1, asset2) {
for (uint256 i = 0; i < lpCount; i++) {
lps.push(makeAddr(string(abi.encodePacked("lp-", i))));
}
}

function _getLP(uint256 indexSeed) internal view returns (address) {
return lps[indexSeed % lps.length];
}

function deposit(uint256 assetSeed, uint256 lpSeed, uint256 amount) public {
MockERC20 asset = _getAsset(assetSeed);
address lp = _getLP(lpSeed);

amount = _bound(amount, 1, TRILLION * 10 ** asset.decimals());

vm.startPrank(lp);
asset.mint(lp, amount);
asset.approve(address(psm), amount);
psm.deposit(address(asset), lp, amount);
vm.stopPrank();

depositCount++;
}

function withdraw(uint256 assetSeed, uint256 lpSeed, uint256 amount) public {
MockERC20 asset = _getAsset(assetSeed);
address lp = _getLP(lpSeed);

amount = _bound(amount, 1, TRILLION * 10 ** asset.decimals());

vm.prank(lp);
psm.withdraw(address(asset), lp, amount);
vm.stopPrank();

withdrawCount++;
}

}
Loading
Loading