Skip to content

Commit

Permalink
tests: all state invariants tests
Browse files Browse the repository at this point in the history
  • Loading branch information
0xtekgrinder committed Aug 5, 2024
1 parent e38a840 commit 2b3e539
Show file tree
Hide file tree
Showing 7 changed files with 358 additions and 17 deletions.
7 changes: 4 additions & 3 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ libs = ["lib"]
script = "scripts"
cache_path = "cache"
gas_reports = ["*"]
via_ir = true
via_ir = false
sizes = true
optimizer = true
optimizer_runs = 1000
Expand All @@ -17,8 +17,9 @@ ffi = true
runs = 10000

[invariant]
runs = 1000
depth = 30
fail_on_revert = true
runs = 10
depth = 1000

[rpc_endpoints]
arbitrum = "${ETH_NODE_URI_ARBITRUM}"
Expand Down
78 changes: 75 additions & 3 deletions test/invariant/BasicInvariants.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,31 @@

pragma solidity ^0.8.19;

import { UtilsLib } from "morpho/libraries/UtilsLib.sol";
import { UserActor } from "./actors/User.t.sol";
import { KeeperActor } from "./actors/Keeper.t.sol";
import { ParamActor } from "./actors/Param.t.sol";
import { MockRouter } from "../mock/MockRouter.sol";
import { VestingStore } from "./stores/VestingStore.sol";
import { StateVariableStore } from "./stores/StateVariableStore.sol";
import "../ERC4626StrategyTest.t.sol";

contract BasicInvariants is ERC4626StrategyTest {
using UtilsLib for uint256;

uint256 internal constant _NUM_USER = 10;
uint256 internal constant _NUM_KEEPER = 2;
uint256 internal constant _NUM_PARAM = 5;

UserActor internal _userHandler;
KeeperActor internal _keeperHandler;
ParamActor internal _paramHandler;
VestingStore internal _vestingStore;
StateVariableStore internal _stateVariableStore;

// state variables
uint256 internal _previousDeveloperShares;
uint256 internal _previousIntegratorShares;

function setUp() public virtual override {
super.setUp();
Expand All @@ -26,15 +37,31 @@ contract BasicInvariants is ERC4626StrategyTest {
strategy.setTokenTransferAddress(address(router));
strategy.setSwapRouter(address(router));
vm.stopPrank();
deal(asset, address(router), 1e27);

// Deposit some assets
vm.startPrank(alice);
deal(asset, alice, 1e18);
IERC20(asset).approve(address(strategy), 1e18);
strategy.deposit(1e18, alice);
vm.stopPrank();

// Create stores
_vestingStore = new VestingStore();
_stateVariableStore = new StateVariableStore();

_stateVariableStore.addShares(1e18);
_stateVariableStore.addUnderlyingStrategyShares(ERC4626(strategyAsset).convertToShares(1e18));

// Create actors
_userHandler = new UserActor(_NUM_USER, address(strategy));
_keeperHandler = new KeeperActor(_NUM_KEEPER, address(strategy));
_userHandler = new UserActor(_NUM_USER, address(strategy), _stateVariableStore);
_keeperHandler = new KeeperActor(_NUM_KEEPER, address(strategy), _stateVariableStore, _vestingStore);
_paramHandler = new ParamActor(_NUM_PARAM, address(strategy));

// Label newly created addresses
for (uint256 i; i < _NUM_USER; i++) {
vm.label(_userHandler.actors(i), string.concat("User ", vm.toString(i)));
deal(asset, _userHandler.actors(i), 1e27);
}
vm.startPrank(developer);
for (uint256 i; i < _NUM_KEEPER; i++) {
Expand Down Expand Up @@ -71,5 +98,50 @@ contract BasicInvariants is ERC4626StrategyTest {
}
}

function invariant_XXXXX() public {}
function invariant_CorrectVesting() public {
VestingStore.Vesting[] memory vestings = _vestingStore.getVestings();
uint256 totalAmount;
for (uint256 i; i < vestings.length; i++) {
if (block.timestamp >= vestings[i].start + strategy.vestingPeriod()) {
totalAmount = 0;
} else {
uint256 nextTimestamp = i + 1 < vestings.length ? vestings[i + 1].start : block.timestamp;
uint256 amount = vestings[i].amount + vestings[i].previousLockedProfit;
totalAmount = amount - (amount * (nextTimestamp - vestings[i].start)) / strategy.vestingPeriod();
}
}
uint256 strategyBalance = ERC4626(strategyAsset).balanceOf(address(strategy));
assertApproxEqAbs(strategy.lockedProfit(), totalAmount, 1);
assertApproxEqAbs(
strategy.totalAssets(),
ERC4626(strategyAsset).convertToAssets(strategyBalance).zeroFloorSub(totalAmount),
1
);
}

function invariant_FeeRecipientNoBurn() public {
assertGe(strategy.balanceOf(strategy.integratorFeeRecipient()), _previousIntegratorShares);
assertGe(strategy.balanceOf(strategy.developerFeeRecipient()), _previousDeveloperShares);

_previousIntegratorShares = strategy.balanceOf(strategy.integratorFeeRecipient());
_previousDeveloperShares = strategy.balanceOf(strategy.developerFeeRecipient());
}

function invariant_CorrectTotalSupply() public {
assertEq(strategy.totalSupply(), _stateVariableStore.shares());
}

function invariant_CorrectTotalAssets() public {
assertApproxEqRel(
strategy.totalAssets(),
ERC4626(strategyAsset).convertToAssets(_stateVariableStore.underlyingStrategyShares()) -
strategy.lockedProfit(),
10
);
assertApproxEqRel(
_stateVariableStore.underlyingStrategyShares(),
ERC4626(strategyAsset).balanceOf(address(strategy)),
10
);
}
}
4 changes: 3 additions & 1 deletion test/invariant/actors/BaseActor.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity ^0.8.19;

import "../../Constants.t.sol";
import { ERC4626Strategy } from "../../../contracts/ERC4626Strategy.sol";
import { ERC4626Strategy, ERC4626 } from "../../../contracts/ERC4626Strategy.sol";
import { IERC20 } from "forge-std/interfaces/IERC20.sol";
import { MockRouter } from "../../mock/MockRouter.sol";
import { Test, stdMath, StdStorage, stdStorage, console } from "forge-std/Test.sol";
Expand All @@ -13,6 +13,7 @@ contract BaseActor is Test {

ERC4626Strategy public strategy;
IERC20 public asset;
ERC4626 public strategyAsset;
address public router;

mapping(address => uint256) public addressToIndex;
Expand All @@ -37,6 +38,7 @@ contract BaseActor is Test {

strategy = ERC4626Strategy(_strategy);
asset = IERC20(strategy.asset());
strategyAsset = ERC4626(strategy.STRATEGY_ASSET());
router = address(strategy.swapRouter());
}
}
61 changes: 58 additions & 3 deletions test/invariant/actors/Keeper.t.sol
Original file line number Diff line number Diff line change
@@ -1,27 +1,82 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;

import { UtilsLib } from "morpho/libraries/UtilsLib.sol";
import { VestingStore } from "../stores/VestingStore.sol";
import { StateVariableStore } from "../stores/StateVariableStore.sol";
import "./BaseActor.t.sol";

contract KeeperActor is BaseActor {
constructor(uint256 _nbrActor, address _strategy) BaseActor(_nbrActor, "keeper", _strategy) {}
using UtilsLib for uint256;

VestingStore public vestingStore;
StateVariableStore public stateVariableStore;

constructor(
uint256 _nbrActor,
address _strategy,
StateVariableStore _stateVariableStore,
VestingStore _vestingStore
) BaseActor(_nbrActor, "keeper", _strategy) {
stateVariableStore = _stateVariableStore;
vestingStore = _vestingStore;
}

function swap(uint256 actorIndexSeed, uint256 tokenIn, uint256 tokenOut) public useActor(actorIndexSeed) {
tokenIn = bound(tokenIn, 1e18, 1e21);
tokenOut = bound(tokenOut, 1e18, 1e21);

deal(USDC, address(strategy), tokenIn);
deal(address(asset), address(router), tokenOut);

address[] memory tokens = new address[](1);
tokens[0] = USDC;
uint256[] memory amounts = new uint256[](1);
amounts[0] = tokenIn;
bytes[] memory data = new bytes[](1);
data[0] = abi.encodeWithSelector(MockRouter.swap.selector, tokenIn, USDC, tokenOut, asset);

uint256 previousLockedProfit = strategy.lockedProfit();
strategy.swap(tokens, data, amounts);

assertEq(strategy.lockedProfit(), previousLockedProfit + tokenOut);
assertEq(strategy.vestingProfit(), previousLockedProfit + tokenOut);
assertEq(strategy.lastUpdate(), block.timestamp);

vestingStore.addVesting(block.timestamp, tokenOut, previousLockedProfit);
stateVariableStore.addUnderlyingStrategyShares(strategyAsset.convertToShares(tokenOut));
}

function accumulate(uint256 actorIndexSeed) public useActor(actorIndexSeed) {
function accumulate(uint256 actorIndexSeed, uint256 profit, uint8 negative) public useActor(actorIndexSeed) {
uint256 assetsHeld = strategyAsset.convertToAssets(strategyAsset.balanceOf(address(strategy)));
profit = bound(profit, 1, 1e8);

vm.mockCall(
address(strategyAsset),
abi.encodeWithSelector(ERC4626.convertToAssets.selector),
abi.encode(negative % 2 == 0 ? assetsHeld - profit : assetsHeld + profit)
);

uint256 totalAssets = strategy.totalAssets();
uint256 lastTotalAssets = strategy.lastTotalAssets();
uint256 previousDeveloperShares = strategy.balanceOf(strategy.developerFeeRecipient());
uint256 previousIntegratorShares = strategy.balanceOf(strategy.integratorFeeRecipient());

strategy.accumulate();

uint256 feeShare = strategy.convertToShares(
((totalAssets.zeroFloorSub(lastTotalAssets)) * strategy.performanceFee()) / strategy.BPS()
);
uint256 developerFeeShare = (feeShare * strategy.developerFee()) / strategy.BPS();

assertEq(strategy.lastTotalAssets(), totalAssets);
assertEq(
strategy.balanceOf(strategy.integratorFeeRecipient()),
previousIntegratorShares + feeShare - developerFeeShare
);
assertEq(strategy.balanceOf(strategy.developerFeeRecipient()), previousDeveloperShares + developerFeeShare);

vm.clearMockedCalls();

stateVariableStore.addShares(feeShare);
}
}
Loading

0 comments on commit 2b3e539

Please sign in to comment.