Skip to content

Commit

Permalink
test(invariant): more invariant testing work
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexangelj committed Dec 29, 2022
1 parent c57f6e3 commit 000d65d
Show file tree
Hide file tree
Showing 13 changed files with 216 additions and 298 deletions.
2 changes: 0 additions & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ runs = 1000
# `max_local_rejects` option isn't exposed here since we're not using
# `prop_filter`.
max_test_rejects = 65536
# Being deprecated in favor of `max_test_rejects`. Will be removed in future versions.
max_global_rejects = 65536
# The weight of the dictionary
dictionary_weight = 40
# The flag indicating whether to include values from storage
Expand Down
Empty file removed test/E2E/E2E.test.ts
Empty file.
154 changes: 6 additions & 148 deletions test/E2E/InvariantAllocate.sol
Original file line number Diff line number Diff line change
@@ -1,25 +1,10 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "solmate/test/utils/DSTestPlus.sol";
import {State} from "./setup/TestE2ESetup.sol";
import "test/helpers/HelperHyperView.sol";
import {HyperPool, HyperPosition, HyperTimeOverride, TestERC20} from "test/helpers/HyperTestOverrides.sol";
import "./setup/InvariantTargetContract.sol";

contract InvariantAllocate is HelperHyperView, DSTestPlus {
uint48 public __poolId__ = 0x000100000001;

HyperTimeOverride public __hyper__; // Actual contract
TestERC20 public __quote__;
TestERC20 public __asset__;

Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

constructor(address hyper_, address asset_, address quote_) {
__hyper__ = HyperTimeOverride(payable(hyper_));
__asset__ = TestERC20(asset_);
__quote__ = TestERC20(quote_);
}
contract InvariantAllocate is InvariantTargetContract {
constructor(address hyper_, address asset_, address quote_) InvariantTargetContract(hyper_, asset_, quote_) {}

event SentTokens(uint amount);

Expand All @@ -43,9 +28,9 @@ contract InvariantAllocate is HelperHyperView, DSTestPlus {
__quote__.mint(address(this), expectedDeltaQuote);

// Execution
State memory prev = getState();
HyperState memory prev = getState();
(uint deltaAsset, uint deltaQuote) = __hyper__.allocate(__poolId__, deltaLiquidity);
State memory post = getState();
HyperState memory post = getState();

// Postconditions
{
Expand Down Expand Up @@ -80,7 +65,7 @@ contract InvariantAllocate is HelperHyperView, DSTestPlus {
(uint unallocatedAsset, uint unallocatedQuote) = __hyper__.unallocate(__poolId__, deltaLiquidity);

{
State memory end = getState();
HyperState memory end = getState();
assertEq(unallocatedAsset, deltaAsset);
assertEq(unallocatedQuote, deltaQuote);
assertEq(end.reserveAsset, prev.reserveAsset);
Expand All @@ -90,131 +75,4 @@ contract InvariantAllocate is HelperHyperView, DSTestPlus {
assertEq(end.callerPositionLiquidity, prev.callerPositionLiquidity);
}
}

function getState() internal view returns (State memory) {
// Execution
uint sumAsset;
uint sumQuote;
uint sumPositionLiquidity;

sumAsset += __hyper__.getBalance(address(this), address(__asset__));
sumQuote += __hyper__.getBalance(address(this), address(__quote__));
sumPositionLiquidity += getPosition(address(__hyper__), address(this), __poolId__).totalLiquidity;

HyperPool memory pool = getPool(address(__hyper__), __poolId__);
HyperPosition memory position = getPosition(address(__hyper__), address(this), __poolId__);
uint feeGrowthAssetPool = pool.feeGrowthGlobalAsset;
uint feeGrowthQuotePool = pool.feeGrowthGlobalQuote;
uint feeGrowthAssetPosition = position.feeGrowthAssetLast;
uint feeGrowthQuotePosition = position.feeGrowthQuoteLast;

State memory prev = State(
__hyper__.getReserve(address(__asset__)),
__hyper__.getReserve(address(__quote__)),
__asset__.balanceOf(address(__hyper__)),
__quote__.balanceOf(address(__hyper__)),
sumAsset,
sumQuote,
sumPositionLiquidity,
pool.liquidity,
position.totalLiquidity,
feeGrowthAssetPool,
feeGrowthQuotePool,
feeGrowthAssetPosition,
feeGrowthQuotePosition
);

return prev;
}

function getBalances(address token) internal view returns (uint reserve, uint physical, uint balanceSum) {
reserve = __hyper__.getReserve(token);
physical = TestERC20(token).balanceOf(address(__hyper__));
balanceSum += __hyper__.getBalance(address(this), token);
}
}

interface Vm {
// Set block.timestamp (newTimestamp)
function warp(uint256) external;

// Set block.height (newHeight)
function roll(uint256) external;

// Set block.basefee (newBasefee)
function fee(uint256) external;

// Loads a storage slot from an address (who, slot)
function load(address, bytes32) external returns (bytes32);

// Stores a value to an address' storage slot, (who, slot, value)
function store(address, bytes32, bytes32) external;

// Signs data, (privateKey, digest) => (v, r, s)
function sign(uint256, bytes32) external returns (uint8, bytes32, bytes32);

// Gets address for a given private key, (privateKey) => (address)
function addr(uint256) external returns (address);

// Performs a foreign function call via terminal, (stringInputs) => (result)
function ffi(string[] calldata) external returns (bytes memory);

// Sets the *next* call's msg.sender to be the input address
function prank(address) external;

// Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called
function startPrank(address) external;

// Sets the *next* call's msg.sender to be the input address, and the tx.origin to be the second input
function prank(address, address) external;

// Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called, and the tx.origin to be the second input
function startPrank(address, address) external;

// Resets subsequent calls' msg.sender to be `address(this)`
function stopPrank() external;

// Sets an address' balance, (who, newBalance)
function deal(address, uint256) external;

// Sets an address' code, (who, newCode)
function etch(address, bytes calldata) external;

// Expects an error on next call
function expectRevert(bytes calldata) external;

function expectRevert(bytes4) external;

// Record all storage reads and writes
function record() external;

// Gets all accessed reads and write slot from a recording session, for a given address
function accesses(address) external returns (bytes32[] memory reads, bytes32[] memory writes);

// Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData).
// Call this function, then emit an event, then call a function. Internally after the call, we check if
// logs were emitted in the expected order with the expected topics and data (as specified by the booleans)
function expectEmit(bool, bool, bool, bool) external;

// Mocks a call to an address, returning specified data.
// Calldata can either be strict or a partial match, e.g. if you only
// pass a Solidity selector to the expected calldata, then the entire Solidity
// function will be mocked.
function mockCall(address, bytes calldata, bytes calldata) external;

// Clears all mocked calls
function clearMockedCalls() external;

// Expect a call to an address with the specified calldata.
// Calldata can either be strict or a partial match
function expectCall(address, bytes calldata) external;

// Gets the code from an artifact file. Takes in the relative path to the json file
function getCode(string calldata) external returns (bytes memory);

// Labels an address in call traces
function label(address, string calldata) external;

// If the condition is false, discard this run's fuzz inputs and generate new ones
function assume(bool) external;
}
34 changes: 34 additions & 0 deletions test/E2E/InvariantGlobal.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "./setup/InvariantTargetContract.sol";

bytes32 constant SLOT_LOCKED = bytes32(uint(5));

contract InvariantGlobal is InvariantTargetContract {
constructor(address hyper_, address asset_, address quote_) InvariantTargetContract(hyper_, asset_, quote_) {}

function invariant_global() public {
bytes32 locked = vm.load(address(__hyper__), SLOT_LOCKED);
assertEq(uint(locked), 1, "invariant-locked");

(bool prepared, bool settled) = __hyper__.__account__();
assertTrue(!prepared, "invariant-prepared");
assertTrue(settled, "invariant-settled");

uint balance = address(__hyper__).balance;
assertEq(balance, 0, "invariant-ether");

(uint reserve, uint physical, uint balances) = getBalances(address(__asset__));
assertTrue(physical >= reserve + balances, "invariant-asset-physical-balance");

(reserve, physical, balances) = getBalances(address(__quote__));
assertTrue(physical >= reserve + balances, "invariant-quite-physical-balance");
}

function getBalances(address token) internal view returns (uint reserve, uint physical, uint balances) {
reserve = getReserve(address(__hyper__), token);
physical = getPhysicalBalance(address(__hyper__), token);
balances = getBalanceSum(address(__hyper__), token, ctx.users());
}
}
29 changes: 5 additions & 24 deletions test/E2E/TestE2E.t.sol
Original file line number Diff line number Diff line change
@@ -1,44 +1,25 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import {InvariantAllocate} from "./InvariantAllocate.sol";
import "./setup/TestE2ESetup.sol";
import "./setup/TestInvariantSetup.sol";

contract InvariantBreaker {
bool public flag0 = true;
bool public flag1 = true;

function set0(int val) public returns (bool) {
if (val % 100 == 0) flag0 = false;
return flag0;
}

function set1(int val) public returns (bool) {
if (val % 10 == 0 && !flag0) flag1 = false;
return flag1;
}
}
import {InvariantAllocate} from "./InvariantAllocate.sol";

contract TestE2EInvariant is TestInvariantSetup, TestE2ESetup {
InvariantAllocate internal _allocate;
InvariantBreaker inv;

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

inv = new InvariantBreaker();
_allocate = new InvariantAllocate(address(__hyper__), address(__asset__), address(__quote__));

addTargetContract(address(_allocate));
addTargetContract(address(inv));
}

function invariant_global() public withGlobalInvariants {
console.log("Woooo");
}

function invariant_neverFalse() public {
require(inv.flag1());
function invariant_global_account() public {
(bool prepared, bool settled) = __hyper__.__account__();
assertTrue(!prepared, "invariant-prepared");
assertTrue(settled, "invariant-settled");
}
}
8 changes: 4 additions & 4 deletions test/E2E/TestE2EAllocateUnallocate.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.0;
import "./setup/TestE2ESetup.sol";

contract TestE2EAllocateUnallocate is TestE2ESetup {
function testFuzzAllocateUnallocateSuccessful(uint deltaLiquidity) public withGlobalInvariants {
function testFuzzAllocateUnallocateSuccessful(uint deltaLiquidity) public {
vm.assume(deltaLiquidity > 0);
vm.assume(deltaLiquidity < 2 ** 127);
// TODO: Add use max flag support.
Expand All @@ -18,9 +18,9 @@ contract TestE2EAllocateUnallocate is TestE2ESetup {
fundUsers(expectedDeltaAsset, expectedDeltaQuote);

// Execution
State memory prev = getState();
HyperState memory prev = getState();
(uint deltaAsset, uint deltaQuote) = __hyper__.allocate(__poolId__, deltaLiquidity);
State memory post = getState();
HyperState memory post = getState();

// Postconditions
{
Expand Down Expand Up @@ -53,7 +53,7 @@ contract TestE2EAllocateUnallocate is TestE2ESetup {
(uint unallocatedAsset, uint unallocatedQuote) = __hyper__.unallocate(__poolId__, deltaLiquidity);

{
State memory end = getState();
HyperState memory end = getState();
assertEq(unallocatedAsset, deltaAsset);
assertEq(unallocatedQuote, deltaQuote);
assertEq(end.reserveAsset, prev.reserveAsset);
Expand Down
2 changes: 1 addition & 1 deletion test/E2E/TestE2ECreate.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ pragma solidity ^0.8.0;
import "./setup/TestE2ESetup.sol";

contract TestE2ECreate is TestE2ESetup {
function testFuzzCreate() public withGlobalInvariants {}
function testFuzzCreate() public {}
}
8 changes: 4 additions & 4 deletions test/E2E/TestE2EFundDraw.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.0;
import "./setup/TestE2ESetup.sol";

contract TestE2EFundDraw is TestE2ESetup {
function invariantFuzzFundDrawSuccessful() public withGlobalInvariants {
function invariantFuzzFundDrawSuccessful() public {
uint amount = 1239423423e21;
vm.assume(amount < type(uint).max);
vm.assume(amount > 0);
Expand All @@ -15,9 +15,9 @@ contract TestE2EFundDraw is TestE2ESetup {

// Execution
uint preBal = getBalance(address(__hyper__), address(this), address(__asset__));
State memory prev = getState();
HyperState memory prev = getState();
__hyper__.fund(address(__asset__), amount);
State memory post = getState();
HyperState memory post = getState();
uint postBal = getBalance(address(__hyper__), address(this), address(__asset__));

// Post conditions
Expand All @@ -28,7 +28,7 @@ contract TestE2EFundDraw is TestE2ESetup {
assertEq(post.totalBalanceAsset, prev.totalBalanceAsset + amount, "total-bal-increase");

__hyper__.draw(address(__asset__), amount, address(this));
State memory end = getState();
HyperState memory end = getState();
uint endBal = getBalance(address(__hyper__), address(this), address(__asset__));

assertEq(endBal, preBal, "reverse-exact-bal");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "./setup/TestInvariantSetup.sol";

/** @dev Example invariant testing contract, for reference only. */
contract InvariantBreaker {
/** @dev Example invariant testing contract, for reference only. https://github.com/foundry-rs/foundry/pull/1572#discussion_r869737535 */
contract InvariantBreaker is Test {
bool public flag0 = true;
bool public flag1 = true;

Expand All @@ -20,7 +20,7 @@ contract InvariantBreaker {
}
}

/** @dev Example invariant test. */
/** @dev Example invariant test. Always fails! */
contract TestInvariantBasic is TestInvariantSetup, Test {
InvariantBreaker inv;

Expand All @@ -29,7 +29,8 @@ contract TestInvariantBasic is TestInvariantSetup, Test {
addTargetContract(address(inv));
}

function invariant_neverFalse() public {
require(inv.flag1());
function invariant_neverFalse() public view {
// note: uncomment to test invariant testing
// require(inv.flag1());
}
}
Loading

0 comments on commit 000d65d

Please sign in to comment.