Skip to content

Commit

Permalink
Invariant framework improvements (#769)
Browse files Browse the repository at this point in the history
* Update forge library, set default env vars using envOr instead reading from env file

* Remove InvariantsTestHelpers contract and move invariants helper functions to BaseInvariants

* env cleanup for invariants

* Update invariant testing scripts

* Add skipTime in merge collateral handler

* Add fuzzed time skip between kick and take/bucketTake

---------

Co-authored-by: grandizzy <[email protected]>
  • Loading branch information
prateek105 and grandizzy authored Apr 26, 2023
1 parent 02b6082 commit 235c757
Show file tree
Hide file tree
Showing 17 changed files with 71 additions and 135 deletions.
14 changes: 8 additions & 6 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ ETHERSCAN_API_KEY=
## for deployment ##
ALCHEMY_API_KEY=

## Infura API key
## Infura API key ##
WEB3_INFURA_PROJECT_ID=

## Ethereum node endpoint ##
Expand All @@ -21,19 +21,21 @@ ETH_GAS=15000000
## Solidity Compiler Version ##
SOLC_VERSION=0.8.14

# AJNA token address for target chain
## AJNA token address for target chain ##
AJNA_TOKEN=0xaadebCF61AA7Da0573b524DE57c67aDa797D46c5

# path to the JSON keystore file for your deployment account
## path to the JSON keystore file for your deployment account ##
DEPLOY_KEY=

# Default token precisions for (invariant) testing

## INVARIANT TESTING ##
# token precisions, defaults to 18 if not specified
QUOTE_PRECISION = 18
COLLATERAL_PRECISION = 18

# Default bucket Index for (invariant) testing
# bucket Index, defaults to 2570 and 850 if not specified
BUCKET_INDEX_ERC20 = 2570
BUCKET_INDEX_ERC721 = 850

# Default no of buckets to use for (invariant) testing
# no of buckets, defaults to 3 if not specified
NO_OF_BUCKETS = 3
6 changes: 0 additions & 6 deletions .github/workflows/forge-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@ env:

jobs:
check:
env:
QUOTE_PRECISION: 18
COLLATERAL_PRECISION: 18
BUCKET_INDEX_ERC20: 2570
BUCKET_INDEX_ERC721: 850
NO_OF_BUCKETS: 3
strategy:
fail-fast: true

Expand Down
21 changes: 6 additions & 15 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,6 @@
# (-include to ignore error if it does not exist)
-include .env

# Default token precisions for invariant testing
QUOTE_PRECISION = 18
COLLATERAL_PRECISION = 18

# Default buckets for invariant testing
BUCKET_INDEX_ERC20 = 2570
BUCKET_INDEX_ERC721 = 850
NO_OF_BUCKETS = 3

all: clean install build

# Clean the repo
Expand All @@ -26,12 +17,12 @@ build :; forge clean && forge build
test :; forge test --no-match-test "testLoad|invariant|test_regression" # --ffi # enable if you need the `ffi` cheat code on HEVM
test-with-gas-report :; forge test --no-match-test "testLoad|invariant|test_regression" --gas-report # --ffi # enable if you need the `ffi` cheat code on HEVM
test-load :; forge test --match-test testLoad --gas-report
test-invariant :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} BUCKET_INDEX_ERC20=${BUCKET_INDEX_ERC20} BUCKET_INDEX_ERC721=${BUCKET_INDEX_ERC721} NO_OF_BUCKETS=${NO_OF_BUCKETS} forge t --mt invariant --nmc RegressionTest
test-invariant-erc20 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} BUCKET_INDEX_ERC20=${BUCKET_INDEX_ERC20} NO_OF_BUCKETS=${NO_OF_BUCKETS} forge t --mt invariant --nmc RegressionTest --mc ERC20
test-invariant-erc721 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} BUCKET_INDEX_ERC721=${BUCKET_INDEX_ERC721} NO_OF_BUCKETS=${NO_OF_BUCKETS} forge t --mt invariant --nmc RegressionTest --mc ERC721
test-regression :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} BUCKET_INDEX_ERC20=${BUCKET_INDEX_ERC20} BUCKET_INDEX_ERC721=${BUCKET_INDEX_ERC721} NO_OF_BUCKETS=${NO_OF_BUCKETS} forge t --mt test_regression
test-regression-erc20 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} BUCKET_INDEX_ERC20=${BUCKET_INDEX_ERC20} NO_OF_BUCKETS=${NO_OF_BUCKETS} forge t --mt test_regression --mc ERC20
test-regression-erc721 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} BUCKET_INDEX_ERC721=${BUCKET_INDEX_ERC721} NO_OF_BUCKETS=${NO_OF_BUCKETS} forge t --mt test_regression --mc ERC721
test-invariant :; forge t --mt invariant --nmc RegressionTest
test-invariant-erc20 :; forge t --mt invariant --nmc RegressionTest --mc ERC20
test-invariant-erc721 :; forge t --mt invariant --nmc RegressionTest --mc ERC721
test-regression :; forge t --mt test_regression
test-regression-erc20 :; forge t --mt test_regression --mc ERC20
test-regression-erc721 :; forge t --mt test_regression --mc ERC721
coverage :; forge coverage --no-match-test "testLoad|invariant"
test-invariant-erc20-precision :; ./tests/forge/invariants/test-invariant-erc20-precision.sh
test-invariant-erc721-precision :; ./tests/forge/invariants/test-invariant-erc721-precision.sh
Expand Down
2 changes: 1 addition & 1 deletion lib/forge-std
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ contract BasicERC20PoolInvariants is BasicInvariants {

super.setUp();

_collateral = new TokenWithNDecimals("Collateral", "C", uint8(vm.envUint("COLLATERAL_PRECISION")));
_collateral = new TokenWithNDecimals("Collateral", "C", uint8(vm.envOr("COLLATERAL_PRECISION", uint256(18))));
_erc20poolFactory = new ERC20PoolFactory(address(_ajna));
_impl = _erc20poolFactory.implementation();
_erc20pool = ERC20Pool(_erc20poolFactory.deployPool(address(_collateral), address(_quote), 0.05 * 10**18));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ abstract contract BaseERC20PoolHandler is BaseHandler {
address testContract_
) BaseHandler(pool_, ajna_, quote_, poolInfo_, testContract_) {

LENDER_MIN_BUCKET_INDEX = vm.envUint("BUCKET_INDEX_ERC20");
LENDER_MAX_BUCKET_INDEX = LENDER_MIN_BUCKET_INDEX + vm.envUint("NO_OF_BUCKETS") - 1;
LENDER_MIN_BUCKET_INDEX = vm.envOr("BUCKET_INDEX_ERC20", uint256(2570));
LENDER_MAX_BUCKET_INDEX = LENDER_MIN_BUCKET_INDEX + vm.envOr("NO_OF_BUCKETS", uint256(3)) - 1;

MIN_QUOTE_AMOUNT = 1e3;
MAX_QUOTE_AMOUNT = 1e30;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan
}

function mergeCollateral(
uint256 actorIndex_
) external useRandomActor(actorIndex_) useTimestamps {
uint256 actorIndex_,
uint256 skippedTime_
) external useRandomActor(actorIndex_) useTimestamps skipTime(skippedTime_) {
numberOfCalls['BBasicHandler.mergeCollateral']++;

// Prepare test phase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ abstract contract BaseERC721PoolHandler is BaseHandler {
address testContract_
) BaseHandler(pool_, ajna_, quote_, poolInfo_, testContract_) {

LENDER_MIN_BUCKET_INDEX = vm.envUint("BUCKET_INDEX_ERC721");
LENDER_MAX_BUCKET_INDEX = LENDER_MIN_BUCKET_INDEX + vm.envUint("NO_OF_BUCKETS") - 1;
LENDER_MIN_BUCKET_INDEX = vm.envOr("BUCKET_INDEX_ERC721", uint256(850));
LENDER_MAX_BUCKET_INDEX = LENDER_MIN_BUCKET_INDEX + vm.envOr("NO_OF_BUCKETS", uint256(3)) - 1;

MIN_QUOTE_AMOUNT = 1e3;
/*
Expand Down
22 changes: 18 additions & 4 deletions tests/forge/invariants/base/BaseInvariants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ import { Pool } from 'src/base/Pool.sol';

import { TokenWithNDecimals, BurnableToken } from '../../utils/Tokens.sol';

import { InvariantsTestHelpers } from './InvariantsTestHelpers.sol';

abstract contract BaseInvariants is InvariantsTestHelpers, Test {
abstract contract BaseInvariants is Test {

uint256 internal LENDER_MIN_BUCKET_INDEX;
uint256 internal LENDER_MAX_BUCKET_INDEX;
Expand Down Expand Up @@ -50,7 +48,7 @@ abstract contract BaseInvariants is InvariantsTestHelpers, Test {
function setUp() public virtual {
// Tokens
_ajna = new BurnableToken("Ajna", "A");
_quote = new TokenWithNDecimals("Quote", "Q", uint8(vm.envUint("QUOTE_PRECISION")));
_quote = new TokenWithNDecimals("Quote", "Q", uint8(vm.envOr("QUOTE_PRECISION", uint256(18))));

// Pool
_poolInfo = new PoolInfoUtils();
Expand All @@ -61,4 +59,20 @@ abstract contract BaseInvariants is InvariantsTestHelpers, Test {
function setCurrentTimestamp(uint256 currentTimestamp_) external {
currentTimestamp = currentTimestamp_;
}

/************************/
/*** Helper Functions ***/
/************************/

function getDiff(uint256 x, uint256 y) internal pure returns (uint256 diff) {
diff = x > y ? x - y : y - x;
}

function requireWithinDiff(uint256 x, uint256 y, uint256 expectedDiff, string memory err) internal pure {
require(getDiff(x, y) <= expectedDiff, err);
}

function greaterThanWithinDiff(uint256 x, uint256 y, uint256 expectedDiff, string memory err) internal pure {
require(x > y || getDiff(x, y) <= expectedDiff, err);
}
}
80 changes: 0 additions & 80 deletions tests/forge/invariants/base/InvariantsTestHelpers.sol

This file was deleted.

25 changes: 15 additions & 10 deletions tests/forge/invariants/base/handlers/LiquidationPoolHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,10 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas
// Prepare test phase
address borrower;
address taker = _actor;
(amount_, borrower) = _preTake(amount_, borrowerIndex_, takerIndex_);
(amount_, borrower) = _preTake(amount_, borrowerIndex_, takerIndex_, skippedTime_);

// Action phase
changePrank(taker);
// skip time to make auction takeable
vm.warp(block.timestamp + 2 hours);
_takeAuction(borrower, amount_, taker);
}

Expand All @@ -71,11 +69,9 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas

// Prepare test phase
address taker = _actor;
(address borrower, uint256 bucketIndex) = _preBucketTake(borrowerIndex_, takerIndex_, bucketIndex_);
(address borrower, uint256 bucketIndex) = _preBucketTake(borrowerIndex_, takerIndex_, bucketIndex_, skippedTime_);

changePrank(taker);
// skip time to make auction takeable
vm.warp(block.timestamp + 2 hours);
_bucketTake(taker, borrower, depositTake_, bucketIndex);
}

Expand All @@ -96,8 +92,6 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas

// Action phase
changePrank(actor);
// skip time to make auction clearable
vm.warp(block.timestamp + 73 hours);
_settleAuction(borrower, maxDepth);

// Cleanup phase
Expand Down Expand Up @@ -132,19 +126,30 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas
}
}

function _preTake(uint256 amount_, uint256 borrowerIndex_, uint256 kickerIndex_) internal returns(uint256 boundedAmount_, address borrower_){
function _preTake(uint256 amount_, uint256 borrowerIndex_, uint256 kickerIndex_, uint256 skipTime_) internal returns(uint256 boundedAmount_, address borrower_){
boundedAmount_ = _constrictTakeAmount(amount_);
borrower_ = _kickAuction(borrowerIndex_, boundedAmount_ * 100, kickerIndex_);

// skip time to make auction takeable
skipTime_ = constrictToRange(skipTime_, 2 hours, 71 hours);
vm.warp(block.timestamp + skipTime_);
}

function _preBucketTake(uint256 borrowerIndex_, uint256 kickerIndex_, uint256 bucketIndex_) internal returns(address borrower_, uint256 bucket_) {
function _preBucketTake(uint256 borrowerIndex_, uint256 kickerIndex_, uint256 bucketIndex_, uint256 skipTime_) internal returns(address borrower_, uint256 bucket_) {
bucket_ = constrictToRange(bucketIndex_, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX);
borrower_ = _kickAuction(borrowerIndex_, 1e24, kickerIndex_);

// skip time to make auction takeable
skipTime_ = constrictToRange(skipTime_, 2 hours, 71 hours);
vm.warp(block.timestamp + skipTime_);
}

function _preSettleAuction(uint256 borrowerIndex_, uint256 kickerIndex_) internal returns(address borrower_, uint256 maxDepth_) {
maxDepth_ = LENDER_MAX_BUCKET_INDEX - LENDER_MIN_BUCKET_INDEX;
borrower_ = _kickAuction(borrowerIndex_, 1e24, kickerIndex_);

// skip time to make auction clearable
vm.warp(block.timestamp + 73 hours);
}

/************************/
Expand Down
4 changes: 3 additions & 1 deletion tests/forge/invariants/test-invariant-erc20-buckets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
set -e
for bucket_index in 1 500 1500 2500 3500 4500 5500 6500 7369
do
make test-invariant-erc20 QUOTE_PRECISION=18 COLLATERAL_PRECISION=18 BUCKET_INDEX_ERC20=${bucket_index}
export BUCKET_INDEX_ERC20=${bucket_index}
echo "Running test with Starting bucketIndex ${bucket_index}"
forge t --mt invariant --nmc RegressionTest --mc ERC20
done
5 changes: 4 additions & 1 deletion tests/forge/invariants/test-invariant-erc20-precision.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ for quote_precision in 6 8 18
do
for collateral_precision in 6 8 18
do
make test-invariant-erc20 QUOTE_PRECISION=${quote_precision} COLLATERAL_PRECISION=${collateral_precision}
export QUOTE_PRECISION=${quote_precision}
export COLLATERAL_PRECISION=${collateral_precision}
echo "Running test with ${QUOTE_PRECISION} quote precision and ${COLLATERAL_PRECISION} collateral precision"
forge t --mt invariant --nmc RegressionTest --mc ERC20
done
done
4 changes: 3 additions & 1 deletion tests/forge/invariants/test-invariant-erc721-buckets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
set -e
for bucket_index in 1 500 1500 2500 3500 4500 5500 6500 7369
do
make test-invariant-erc721 QUOTE_PRECISION=18 BUCKET_INDEX_ERC721=${bucket_index}
export BUCKET_INDEX_ERC721=${bucket_index}
echo "Running test with Starting bucketIndex ${bucket_index}"
forge t --mt invariant --nmc RegressionTest --mc ERC721
done
4 changes: 3 additions & 1 deletion tests/forge/invariants/test-invariant-erc721-precision.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
set -e
for quote_precision in 6 8 18
do
make test-invariant-erc721 QUOTE_PRECISION=${quote_precision}
export QUOTE_PRECISION=${quote_precision}
echo "Running test with ${QUOTE_PRECISION} quote precision"
forge t --mt invariant --nmc RegressionTest --mc ERC721
done
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ contract RegressionTestLiquidationERC721Pool is LiquidationERC721PoolInvariants
Fixed by updating depositTime in 'repayDebt' handler when collateral is added for borrower in bucket.
*/
function test_regression_invariant_B5_1() external {
_liquidationERC721PoolHandler.mergeCollateral(26379999973303451405097860853);
_liquidationERC721PoolHandler.mergeCollateral(26379999973303451405097860853, 0);
_liquidationERC721PoolHandler.pledgeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639933, 198376655287800286010070308646851129242192857410305918128183504, 0);
_liquidationERC721PoolHandler.kickAuction(3, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0, 0);
_liquidationERC721PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 3, 43157292751004395266775137041853631830242177281331941838335997503, 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ contract RegressionTestReserveERC721Pool is ReserveERC721PoolInvariants {
function test_regression_invariant_F2() external {
_reserveERC721PoolHandler.pledgeCollateral(3, 3249247182472647789271370143468153988911, 0);
_reserveERC721PoolHandler.takeAuction(3, 68002012319987217885680836087921689752254473803305406, 3, 0);
_reserveERC721PoolHandler.mergeCollateral(280792061588141829088525786592236158704094119781363060);
_reserveERC721PoolHandler.mergeCollateral(280792061588141829088525786592236158704094119781363060, 0);

invariant_fenwick_depositAtIndex_F1();
invariant_fenwick_depositsTillIndex_F2();
Expand Down

0 comments on commit 235c757

Please sign in to comment.