diff --git a/README.md b/README.md index 72856aa..5efe8b6 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,26 @@ forge test forge coverage ``` +## Invariant Testing Suite + +This project has been set up with a suite of tests that check for specific invariants for the reward streams, implemented by [vnmrtz.eth](https://twitter.com/vn_martinez_). These tests are located in the `test/invariants` directory. They are written in Solidity and are designed to be run with the [echidna](https://github.com/crytic/echidna) fuzzing tool. + +Installation and usage of these tools is outside the scope of this README, but you can find more information in the respective repositories: +- [Echidna Installation](https://github.com/crytic/echidna) + +To run invariant tests with Echidna: + +```sh +./test/scripts/echidna.sh +``` + +To run assert tests with Echidna: + +```sh +./test/scripts/echidna-assert.sh +``` + + ## Safety This software is **experimental** and is provided "as is" and "as available". diff --git a/lib/ethereum-vault-connector b/lib/ethereum-vault-connector index ed18402..c30606b 160000 --- a/lib/ethereum-vault-connector +++ b/lib/ethereum-vault-connector @@ -1 +1 @@ -Subproject commit ed184024742628791b3416c2179e10fb3d4f345f +Subproject commit c30606b44f1d1cabba303dfd13046e5444cab775 diff --git a/lib/forge-std b/lib/forge-std index d44c4fb..5dd1c68 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit d44c4fbbb9ff054fb334babbdd34f9b6e899b3d6 +Subproject commit 5dd1c68131ddd3c89ef169666eb262b92e90507c diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index 255e27e..11dc5e3 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 255e27e6d22934ddaf00c7f279039142d725382d +Subproject commit 11dc5e3809ebe07d5405fe524385cbe4f890a08b diff --git a/test/invariants/CryticToFoundry.t.sol b/test/invariants/CryticToFoundry.t.sol index 5d30cef..82647ee 100644 --- a/test/invariants/CryticToFoundry.t.sol +++ b/test/invariants/CryticToFoundry.t.sol @@ -230,6 +230,23 @@ contract CryticToFoundry is Invariants, Setup { echidna_UPDATE_REWARDS_INVARIANT(); } + function test_claimSpilloverReward() public { + uint128[] memory rewards = new uint128[](1); + rewards[0] = 2; + this.registerReward(0, 0, rewards); + _delay(4026900 + 204167); + this.claimSpilloverReward(0, 0); + } + + function test_DISTRIBUTION_INVARIANTS8() public { + uint128[] memory rewards = new uint128[](1); + rewards[0] = 152; + this.registerReward(0, 0, rewards); + _delay(1800477); + this.claimSpilloverReward(0, 0); + echidna_DISTRIBUTION_INVARIANTS(); + } + /////////////////////////////////////////////////////////////////////////////////////////////// // HELPERS // /////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/test/invariants/handlers/BaseRewardsHandler.t.sol b/test/invariants/handlers/BaseRewardsHandler.t.sol index 0610fed..aa3b7ab 100644 --- a/test/invariants/handlers/BaseRewardsHandler.t.sol +++ b/test/invariants/handlers/BaseRewardsHandler.t.sol @@ -12,6 +12,8 @@ import {BaseRewardStreamsHarness} from "test/harness/BaseRewardStreamsHarness.so // Interfaces import {IRewardStreams} from "src/interfaces/IRewardStreams.sol"; +import "forge-std/console.sol"; + /// @title BaseRewardsHandler /// @notice Handler test contract for the risk balance forwarder module actions contract BaseRewardsHandler is BaseHandler { @@ -81,8 +83,9 @@ contract BaseRewardsHandler is BaseHandler { _before(address(actor), _rewarded, reward); - (success, returnData) = - actor.proxy(_target, abi.encodeWithSelector(IRewardStreams.updateReward.selector, _rewarded, reward)); + (success, returnData) = actor.proxy( + _target, abi.encodeWithSelector(IRewardStreams.updateReward.selector, _rewarded, reward, address(0)) + ); if (success) { _after(address(actor), _rewarded, reward); @@ -139,12 +142,12 @@ contract BaseRewardsHandler is BaseHandler { // Get one of the two setups randomly (address _rewarded, address _target) = _getRandomRewards(j); - uint256 spilloverReward = target.earnedReward(address(0), _rewarded, reward, true); + uint256 spilloverReward = target.earnedReward(address(0), _rewarded, reward, false); _before(address(actor), _rewarded, reward); (success, returnData) = actor.proxy( - _target, abi.encodeWithSelector(IRewardStreams.claimSpilloverReward.selector, _rewarded, reward, recipient) + _target, abi.encodeWithSelector(IRewardStreams.updateReward.selector, _rewarded, reward, recipient) ); if (success) { @@ -210,16 +213,4 @@ contract BaseRewardsHandler is BaseHandler { } } } - - function assert_VIEW_INVARIANT_A(uint8 i, uint48 epoch, uint48 lastUpdated) external setup { - // Get one of the two setups randomly - (, address _target) = _getRandomRewards(i); - - BaseRewardStreamsHarness target_ = BaseRewardStreamsHarness(_target); - - try target_.timeElapsedInEpochPublic(epoch, lastUpdated) {} - catch { - assertTrue(false, VIEW_INVARIANT_A); - } - } } diff --git a/test/invariants/handlers/simulators/ERC20BalanceForwarderHandler.t.sol b/test/invariants/handlers/simulators/ERC20BalanceForwarderHandler.t.sol index 1f0a768..ae15551 100644 --- a/test/invariants/handlers/simulators/ERC20BalanceForwarderHandler.t.sol +++ b/test/invariants/handlers/simulators/ERC20BalanceForwarderHandler.t.sol @@ -24,9 +24,8 @@ contract ERC20BalanceForwarderHandler is BaseHandler { bool success; bytes memory returnData; - (success, returnData) = actor.proxy( - address(trackingDistributor), abi.encodeWithSelector(IBalanceForwarder.enableBalanceForwarding.selector) - ); + (success, returnData) = + actor.proxy(trackingRewarded, abi.encodeWithSelector(IBalanceForwarder.enableBalanceForwarding.selector)); if (success) { assert(true); @@ -37,9 +36,8 @@ contract ERC20BalanceForwarderHandler is BaseHandler { bool success; bytes memory returnData; - (success, returnData) = actor.proxy( - address(trackingDistributor), abi.encodeWithSelector(IBalanceForwarder.disableBalanceForwarding.selector) - ); + (success, returnData) = + actor.proxy(trackingRewarded, abi.encodeWithSelector(IBalanceForwarder.disableBalanceForwarding.selector)); if (success) { assert(true); @@ -53,7 +51,7 @@ contract ERC20BalanceForwarderHandler is BaseHandler { address account = _getRandomActor(i); (success, returnData) = - actor.proxy(address(trackingDistributor), abi.encodeWithSelector(ERC20.transfer.selector, account, amount)); + actor.proxy(trackingRewarded, abi.encodeWithSelector(ERC20.transfer.selector, account, amount)); if (success) { assert(true); diff --git a/test/invariants/invariants/BaseInvariants.t.sol b/test/invariants/invariants/BaseInvariants.t.sol index f57f074..b3fbd67 100644 --- a/test/invariants/invariants/BaseInvariants.t.sol +++ b/test/invariants/invariants/BaseInvariants.t.sol @@ -21,7 +21,7 @@ abstract contract BaseInvariants is HandlerAggregator { function assert_BASE_INVARIANT_B(address _rewarded, address _reward, address _target) internal { (uint256 totalEligible,,) = BaseRewardStreamsHarness(_target).getDistributionTotals(_rewarded, _reward); if (totalEligible == 0) { - try BaseRewardStreamsHarness(_target).claimSpilloverReward(_rewarded, _reward, address(this)) {} + try BaseRewardStreamsHarness(_target).updateReward(_rewarded, _reward, address(0)) {} catch { assertTrue(false, BASE_INVARIANT_B); } @@ -42,8 +42,8 @@ abstract contract BaseInvariants is HandlerAggregator { function assert_UPDATE_REWARDS_INVARIANT_B(address _rewarded, address _reward, address _target) internal { BaseRewardStreamsHarness target_ = BaseRewardStreamsHarness(_target); - (uint48 totalEligible,,,,) = target_.getDistributionData(_rewarded, _reward); - assertGe(target_.currentEpoch(), target_.getEpoch(totalEligible), UPDATE_REWARDS_INVARIANT_B); + (uint48 lastUpdated,,,,) = target_.getDistributionData(_rewarded, _reward); + assertGe(target_.currentEpoch(), target_.getEpoch(lastUpdated), UPDATE_REWARDS_INVARIANT_B); } function assert_UPDATE_REWARDS_INVARIANT_C(