Skip to content

Commit

Permalink
wip invariant tests
Browse files Browse the repository at this point in the history
  • Loading branch information
giovannidisiena committed Sep 12, 2024
1 parent a36b209 commit 6ed7639
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 45 deletions.
3 changes: 2 additions & 1 deletion test/foundry/fixtures/SmartVaultManagerFixture.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ import {MockNFTMetadataGenerator} from "src/test_utils/MockNFTMetadataGenerator.

contract SmartVaultManagerFixture is TokenManagerFixture {
SmartVaultManagerV6 smartVaultManager;
SmartVaultIndex smartVaultIndex;

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

SmartVaultDeployerV4 smartVaultDeployer = new SmartVaultDeployerV4(NATIVE);
SmartVaultIndex smartVaultIndex = new SmartVaultIndex();
smartVaultIndex = new SmartVaultIndex();

MockNFTMetadataGenerator nftMetadataGenerator = new MockNFTMetadataGenerator();

Expand Down
30 changes: 24 additions & 6 deletions test/foundry/invariant/BeforeAfter.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
// SPDX-License-Identifier: GPL-2.0
pragma solidity ^0.8.0;

import {SmartVaultV4, ISmartVault} from "src/SmartVaultV4.sol";

import {Helper} from "./Helper.sol";

// ghost variables for tracking state variable values before and after function calls
abstract contract BeforeAfter is Helper {
struct Vars {
uint256 todo;
ISmartVault.Status status;
bool undercollateralised;
uint256 nativeBalance;
}

Vars internal _before;
Expand All @@ -19,13 +23,27 @@ abstract contract BeforeAfter is Helper {
_;
}

function __snapshot(Vars storage vars) internal {}
// NOTE: this is helpful if we are considering only a single SmartVault
// which might actually make things easier
// modifier hasMintedUsds() {
// (_before.minted) = smartVault.status().minted;
// precondition(_before.minted > 0);
// _;
// }

function __snapshot(Vars storage vars, uint256 tokenId) internal {
SmartVaultV4 smartVault = _tokenIdToSmartVault(tokenId);
vars.status = smartVault.status();

vars.undercollateralised = smartVault.undercollateralised();
vars.nativeBalance = address(smartVault).balance;
}

function __before() internal {
__snapshot(_before);
function __before(uint256 tokenId) internal {
__snapshot(_before, tokenId);
}

function __after() internal {
__snapshot(_after);
function __after(uint256 tokenId) internal {
__snapshot(_after, tokenId);
}
}
26 changes: 24 additions & 2 deletions test/foundry/invariant/Helper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {Bounds} from "./Bounds.sol";
import {Setup} from "./Setup.sol";

import {SmartVaultV4} from "src/SmartVaultV4.sol";
import {ERC20Mock} from "src/test_utils/ERC20Mock.sol";

// import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

Expand All @@ -21,7 +22,28 @@ abstract contract Helper is Asserts, Bounds, Setup {
return users[between(uint256(uint160(user)), 0, users.length)];
}

function _getRandomSmartVault() internal returns (SmartVaultV4) {
return smartVaults[VAULT_OWNER][0].vault; // TODO: randomize vaults
function _getRandomSmartVault(uint256 tokenId) internal returns (SmartVaultV4, uint256) {
return (SmartVaultV4(_tokenIdToSmartVault(tokenId)), tokenId = _getRandomTokenId(tokenId));
}

function _getRandomTokenId(uint256 tokenId) internal returns (uint256) {
return between(tokenId, 0, vaultManager.totalSupply());
}

function _tokenIdToSmartVault(uint256 tokenId) internal returns (SmartVaultV4) {
return SmartVaultV4(smartVaultIndex.getVaultAddress(tokenId));
}

function _getRandomCollateral(uint256 symbolIndex) internal returns (ERC20Mock, bytes32) {
bytes32 symbol = _getRandomSymbol(symbolIndex);
return (_symbolToAddress(symbol), symbol);
}

function _getRandomSymbol(uint256 symbolIndex) internal returns (bytes32) {
return collateralSymbols[between(symbolIndex, 0, symbols.length)];
}

function _symbolToAddress(bytes32 symbol) internal returns (ERC20Mock) {
return collateralData[symbol].token
}
}
38 changes: 37 additions & 1 deletion test/foundry/invariant/PropertiesSpecifications.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,43 @@
pragma solidity ^0.8.0;

abstract contract PropertiesSpecifications {
string internal constant EXAMPLE_01 = "EXAMPLE_01: Example property specification";
string internal constant ADD_COLLATERAL_01 = "ADD_COLLATERAL_01: Deposits increase Smart Vault collateral value";

string internal constant REMOVE_COLLATERAL_NATIVE_01 = "REMOVE_COLLATERAL_NATIVE_01: Native withdrawals decrease Smart Vault collateral value";
string internal constant REMOVE_COLLATERAL_NATIVE_02 = "REMOVE_COLLATERAL_NATIVE_02: Native withdrawals decrease the Smart Vault balance";
string internal constant REMOVE_COLLATERAL_NATIVE_03 = "REMOVE_COLLATERAL_NATIVE_03: Native withdrawals increase the recipient balance";

string internal constant REMOVE_COLLATERAL_01 = "REMOVE_COLLATERAL_01: Collateral withdrawals decrease Smart Vault collateral value";
string internal constant REMOVE_COLLATERAL_02 = "REMOVE_COLLATERAL_02: Collateral withdrawals decrease the Smart Vault balance";
string internal constant REMOVE_COLLATERAL_03 = "REMOVE_COLLATERAL_03: Collateral withdrawals increase the recipient balance";

string internal constant REMOVE_ASSET_01 = "REMOVE_ASSET_01: Removes non-collateral assets without affecting the Smart Vault collateral value";
string internal constant REMOVE_ASSET_02 = "REMOVE_ASSET_02: Non-collateral asset withdrawals decrease the Smart Vault balance";
string internal constant REMOVE_ASSET_03 = "REMOVE_ASSET_03: Non-collateral asset withdrawals increase the recipient balance";
string internal constant REMOVE_ASSET_04 = "REMOVE_ASSET_04: Collateral asset withdrawals decrease the Smart Vault collateral value";
string internal constant REMOVE_ASSET_05 = "REMOVE_ASSET_05: Collateral asset withdrawals decrease the Smart Vault balance";
string internal constant REMOVE_ASSET_06 = "REMOVE_ASSET_06: Collateral asset withdrawals increase the recipient balance";
string internal constant REMOVE_ASSET_07 = "REMOVE_ASSET_07: Collateral asset withdrawals only succeed if the Smart Vault remains overcollateralised";

string internal constant MINT_01 = "MINT_01: Minting increases owner's USDs balance";
string internal constant MINT_02 = "MINT_02: Minting decreases max mintable";

string internal constant BURN_01 = "BURN_01: Burn decreases the caller's USDs balance";
string internal constant BURN_02 = "BURN_02: Burn increases max mintable";

string internal constant LIQUIDATE_01 = "LIQUIDATE_01: Liquidate only succeeds if the Smart Vault is undercollateralised";
string internal constant LIQUIDATE_02 = "LIQUIDATE_02: Liquidate increases the protocol's collateral";
string internal constant LIQUIDATE_03 = "LIQUIDATE_03: Liquidate clears the Smart Vault's minted USDs state";
string internal constant LIQUIDATE_04 = "LIQUIDATE_04: Liquidate marks the Smart Vault as liquidated";
string internal constant LIQUIDATE_05 = "LIQUIDATE_05: Liquidate decreases the Smart Vault's max mintable to zero";

string internal constant UNDERWATER_01 =
"UNDERWATER_01: A Smart Vault cannot execute an operation that leaves it underwater";
string internal constant UNDERWATER_02 = "UNDERWATER_02: Underwater Smart Vaults cannot mint USDs";

string internal constant FEES_01 =
"FEES_01: Fees are take on minting and burning USDs";
string internal constant FEES_02 = "FEES_02: Fees are taken on yield deposits and withdrawals";

string internal constant DOS = "DOS: Denial of Service";

Expand Down
8 changes: 3 additions & 5 deletions test/foundry/invariant/Setup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@ abstract contract Setup is BaseSetup, PropertiesConstants, SmartVaultFixture {
users.push(USER1);
users.push(USER2);
users.push(USER3);
users.push(VAULT_OWNER);
users.push(VAULT_MANAGER_OWNER);
users.push(PROTOCOL);
// users.push(VAULT_OWNER);
// users.push(VAULT_MANAGER_OWNER);
// users.push(PROTOCOL);
users.push(LIQUIDATOR);

// TODO: tokens, smartVaultYieldManager.addHypervisorData, etc
}
}
115 changes: 85 additions & 30 deletions test/foundry/invariant/TargetFunctions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,65 +6,109 @@ import {Properties} from "./Properties.sol";
import {vm} from "@chimera/Hevm.sol";

import {SmartVaultV4} from "src/SmartVaultV4.sol";
import {ERC20Mock} from "src/test_utils/ERC20Mock.sol";

abstract contract TargetFunctions is ExpectedErrors {
function smartVaultV4_liquidate() public getMsgSender checkExpectedErrors(LIQUIDATE_VAULT_ERRORS) {
__before();

SmartVaultV4 smartVault = _getRandomSmartVault();

vm.prank(msgSender);
(success, returnData) = address(smartVault).call(abi.encodeCall(smartVault.liquidate, ())); // TODO: smart vaults setup and get random vault helper

if (success) {
__after();
}
}

function smartVaultV4_removeCollateralNative(uint256 amount, address payable to)
// NOTE: see smartVaultManagerV6_liquidateVault below
// but keep this here in case liquidation logic changes
// function smartVaultV4_liquidate(uint256 _tokenId)
// public
// getMsgSender
// hasMintedUsds
// checkExpectedErrors(LIQUIDATE_VAULT_ERRORS)
// {
// (SmartVaultV4 smartVault, uint256 tokenId) = _getRandomSmartVault(_tokenId);

// __before(tokenId);

// vm.prank(msgSender);
// (success, returnData) = address(smartVault).call(abi.encodeCall(smartVault.liquidate, ()));

// if (success) {
// __after(tokenId);

// t(_before.undercollateralised, LIQUIDATE_01);
// // TODO: BeforeAfter state variables
// // gte(
// // _after.protocol.collateralTokenBalance,
// // _before.protocol.collateralTokenBalance,
// // LIQUIDATE_02
// // );
// eq(_after.minted, 0, LIQUIDATE_03);
// t(_after.liquidated, LIQUIDATE_04);
// eq(_after.maxMintable, 0, LIQUIDATE_05);
// }
// }

// TODO: add a helper to add collateral, or just mint directly to a single smart vault during setup (since borrowing is isolated to a single vault)

function smartVaultV4_removeCollateralNative(uint256 amount, address payable to, uint256 _tokenId)
public
getMsgSender
checkExpectedErrors(REMOVE_VAULT_TOKEN_ERRORS)
{
__before();

SmartVaultV4 smartVault = _getRandomSmartVault();
(SmartVaultV4 smartVault, uint256 tokenId) = _getRandomSmartVault(_tokenId);

__before(tokenId);
uint256 _toBalanceBefore = to.balance;

vm.prank(msgSender);
(success, returnData) =
address(smartVault).call(abi.encodeCall(smartVault.removeCollateralNative, (amount, to)));

if (success) {
__after();
__after(tokenId);

gte(_before.status.totalCollateralValue, _after.status.totalCollateralValue, REMOVE_COLLATERAL_NATIVE_01);
eq(_before.nativeBalance - amount, _after.nativeBalance, REMOVE_COLLATERAL_NATIVE_02);
eq(_toBalanceBefore + amount, to.balance, REMOVE_COLLATERAL_NATIVE_03);
}
}

function smartVaultV4_removeCollateral(bytes32 symbol, uint256 amount, address to)
function smartVaultV4_removeCollateral(uint256 symbolIndex, uint256 amount, address to)
public
getMsgSender
checkExpectedErrors(REMOVE_VAULT_TOKEN_ERRORS)
{
__before();

SmartVaultV4 smartVault = _getRandomSmartVault();
(SmartVaultV4 smartVault, uint256 tokenId) = _getRandomSmartVault(_tokenId);
(ERC20Mock collateral, bytes32 symbol) = _getRandomCollateral(symbolIndex);

__before(tokenId);

uint256 _toBalanceBefore = collateral.balanceOf(to);

vm.prank(msgSender);
(success, returnData) =
address(smartVault).call(abi.encodeCall(smartVault.removeCollateral, (symbol, amount, to)));

if (success) {
__after();
__after(tokenId);

gte(_before.status.totalCollateralValue, _after.status.totalCollateralValue, REMOVE_COLLATERAL_NATIVE_01);
for (uint256 i = 0; i < _before.status.collateral.length; i++) {
if (_before.status.collateral[i].token.symbol == symbol) {
eq(
_before.status.collateral[i].amount - amount,
_after.status.collateral[i].amount,
REMOVE_COLLATERAL_NATIVE_02
);
}
}
eq(_toBalanceBefore + amount, collateral.balanceOf(to), REMOVE_COLLATERAL_NATIVE_03);
}
}

function smartVaultV4_removeAsset(address token, uint256 amount, address to)
function smartVaultV4_removeAsset(uint256 symbolIndex, uint256 amount, address to, uint256 tokenId)
public
getMsgSender
checkExpectedErrors(REMOVE_VAULT_TOKEN_ERRORS)
{
__before();
(SmartVaultV4 smartVault, uint256 tokenId) = _getRandomSmartVault(_tokenId);
(ERC20Mock collateral, bytes32 symbol) = _getRandomCollateral(symbolIndex); // TODO: get random asset (for now just test collateral)

SmartVaultV4 smartVault = _getRandomSmartVault();
__before(tokenId);

uint256 _toBalanceBefore = collateral.balanceOf(to);

vm.prank(msgSender);
(success, returnData) = address(smartVault).call(abi.encodeCall(smartVault.removeAsset, (token, amount, to)));
Expand Down Expand Up @@ -189,21 +233,32 @@ abstract contract TargetFunctions is ExpectedErrors {
}
}

function smartVaultManagerV6_liquidateVault(uint256 tokenId)
function smartVaultManagerV6_liquidateVault(uint256 _tokenId)
public
getMsgSender
checkExpectedErrors(LIQUIDATE_VAULT_ERRORS)
{
__before();
(SmartVaultV4 smartVault, uint256 tokenId) = _getRandomSmartVault(_tokenId);

SmartVaultV4 smartVault = _getRandomSmartVault();
__before(tokenId);

vm.prank(msgSender);
(success, returnData) =
address(smartVaultManager).call(abi.encodeCall(smartVaultManager.liquidateVault, tokenId));

if (success) {
__after();
__after(tokenId);

t(_before.undercollateralised, LIQUIDATE_01);
// TODO: BeforeAfter state variables
// gte(
// _after.protocol.collateralTokenBalance,
// _before.protocol.collateralTokenBalance,
// LIQUIDATE_02
// );
eq(_after.minted, 0, LIQUIDATE_03);
t(_after.liquidated, LIQUIDATE_04);
eq(_after.maxMintable, 0, LIQUIDATE_05);
}
}
}

0 comments on commit 6ed7639

Please sign in to comment.