diff --git a/test/invariants/handlers/Handler.sol b/test/invariants/handlers/Handler.sol index 19efd4e..37e9aee 100644 --- a/test/invariants/handlers/Handler.sol +++ b/test/invariants/handlers/Handler.sol @@ -70,7 +70,10 @@ contract Handler is Test, GrantFundTestHelper { _tokenDeployer = tokenDeployer_; // instantiate actors - actors = _buildActors(numOfActors_, tokensToDistribute_); + address[] memory newActors = _buildActors(numOfActors_, tokensToDistribute_); + for (uint256 i = 0; i < newActors.length; ++i) { + if (newActors[i] != address(0)) actors.push(newActors[i]); + } // set Test invariant contract testContract = ITestBase(testContract_); @@ -141,9 +144,11 @@ contract Handler is Test, GrantFundTestHelper { actors_ = new address[](numOfActors_); uint256 tokensDistributed = 0; + uint256 existingActors = actors.length; + for (uint256 i = 0; i < numOfActors_; ++i) { // create actor - address actor = makeAddr(string(abi.encodePacked("Actor", Strings.toString(i)))); + address actor = makeAddr(string(abi.encodePacked("Actor", Strings.toString(existingActors + i)))); actors_[i] = actor; // transfer ajna tokens to the actor diff --git a/test/invariants/handlers/StandardHandler.sol b/test/invariants/handlers/StandardHandler.sol index c95547a..d569fb6 100644 --- a/test/invariants/handlers/StandardHandler.sol +++ b/test/invariants/handlers/StandardHandler.sol @@ -388,6 +388,55 @@ contract StandardHandler is Handler { } } + function fundTreasury(uint256 actorIndex_, uint256 treasuryAmount_) external useCurrentBlock useRandomActor(actorIndex_) { + numberOfCalls['SFH.fundTreasury']++; + + // bound treasury amount + treasuryAmount_ = bound(treasuryAmount_, 0, _ajna.balanceOf(_actor)); + + if (treasuryAmount_ == 0) return; + + uint256 previousTreasury = _grantFund.treasury(); + + // fund treasury + changePrank(_actor); + _ajna.approve(address(_grantFund), type(uint256).max); + _grantFund.fundTreasury(treasuryAmount_); + + // ensure amount is added into treasury + assertEq(_grantFund.treasury(), previousTreasury + treasuryAmount_); + } + + function transferAjna(uint256 fromActorIndex_, uint256 toActorIndex_, uint256 amountToTransfer_) external useCurrentBlock useRandomActor(fromActorIndex_) { + numberOfCalls['SFH.transferAjna']++; + + // bound actor + toActorIndex_ = bound(toActorIndex_, 0, actors.length - 1); + address toActor = actors[toActorIndex_]; + + amountToTransfer_ = bound(amountToTransfer_, 0, _ajna.balanceOf(_actor)); + + if (amountToTransfer_ == 0 || _actor == toActor) return; + + _ajna.transfer(toActor, amountToTransfer_); + } + + function addActors(uint256 noOfActorsToAdd_, uint256 tokensToDistribute_) external useCurrentBlock { + numberOfCalls['SFH.addActors']++; + + // bound tokens to distribute and no of actors to add + noOfActorsToAdd_ = bound(noOfActorsToAdd_, 1, 10); + tokensToDistribute_ = bound(tokensToDistribute_, 0, _ajna.balanceOf(_tokenDeployer)); + + if (tokensToDistribute_ == 0) return; + + address[] memory newActors = _buildActors(noOfActorsToAdd_, tokensToDistribute_); + + // add new actors to actors array + for (uint256 i = 0; i < newActors.length; ++i) { + if (newActors[i] != address(0)) actors.push(newActors[i]); + } + } /**********************************/ /*** External Utility Functions ***/ /**********************************/ diff --git a/test/invariants/scenarios/MultipleTreasuryFundingInvariant.t.sol b/test/invariants/scenarios/MultipleTreasuryFundingInvariant.t.sol new file mode 100644 index 0000000..af114b4 --- /dev/null +++ b/test/invariants/scenarios/MultipleTreasuryFundingInvariant.t.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.18; + +import { console } from "@std/console.sol"; +import { SafeCast } from "@oz/utils/math/SafeCast.sol"; + +import { Maths } from "../../../src/grants/libraries/Maths.sol"; + +import { StandardTestBase } from "../base/StandardTestBase.sol"; +import { StandardHandler } from "../handlers/StandardHandler.sol"; +import { Handler } from "../handlers/Handler.sol"; + +contract MultipleTreasuryFundingInvariant is StandardTestBase { + + // run tests against all functions, having just started a distribution period + function setUp() public virtual override { + super.setUp(); + + // set the list of function selectors to run + bytes4[] memory selectors = new bytes4[](11); + selectors[0] = _standardHandler.startNewDistributionPeriod.selector; + selectors[1] = _standardHandler.propose.selector; + selectors[2] = _standardHandler.screeningVote.selector; + selectors[3] = _standardHandler.fundingVote.selector; + selectors[4] = _standardHandler.updateSlate.selector; + selectors[5] = _standardHandler.execute.selector; + selectors[6] = _standardHandler.claimDelegateReward.selector; + selectors[7] = _standardHandler.roll.selector; + selectors[8] = _standardHandler.fundTreasury.selector; + selectors[9] = _standardHandler.transferAjna.selector; + selectors[10] = _standardHandler.addActors.selector; + + // ensure utility functions are excluded from the invariant runs + targetSelector(FuzzSelector({ + addr: address(_standardHandler), + selectors: selectors + })); + + // update scenarioType to fast to have larger rolls + _standardHandler.setCurrentScenarioType(Handler.ScenarioType.Fast); + + vm.roll(block.number + 100); + currentBlock = block.number; + } + + function invariant_all() external useCurrentBlock { + // screening invariants + _invariant_SS1_SS3_SS4_SS5_SS6_SS7_SS8_SS10_SS11_P1_P2(_grantFund, _standardHandler); + _invariant_SS2_SS4_SS9(_grantFund, _standardHandler); + + // funding invariants + _invariant_FS1_FS2_FS3(_grantFund, _standardHandler); + _invariant_FS4_FS5_FS6_FS7_FS8(_grantFund, _standardHandler); + + // finalize invariants + _invariant_CS1_CS2_CS3_CS4_CS5_CS6_CS7(_grantFund, _standardHandler); + _invariant_ES1_ES2_ES3_ES4_ES5(_grantFund, _standardHandler); + _invariant_DR1_DR2_DR3_DR4_DR5(_grantFund, _standardHandler); + + // distribution period invariants + _invariant_DP1_DP2_DP3_DP4_DP5(_grantFund, _standardHandler); + _invariant_DP6(_grantFund, _standardHandler); + _invariant_T1_T2(_grantFund); + } + + function invariant_call_summary() external useCurrentBlock { + uint24 distributionId = _grantFund.getDistributionId(); + + _logger.logCallSummary(); + _logger.logTimeSummary(); + _logger.logProposalSummary(); + console.log("scenario type", uint8(_standardHandler.getCurrentScenarioType())); + + while (distributionId > 0) { + + _logger.logFundingSummary(distributionId); + _logger.logFinalizeSummary(distributionId); + _logger.logActorSummary(distributionId, true, true); + _logger.logActorDelegationRewards(distributionId); + + --distributionId; + } + } +}