From b90a3e7e64793b4f7851c0274e5423892ebdf58e Mon Sep 17 00:00:00 2001 From: Zuhaib Mohammed Date: Wed, 11 Jun 2025 14:51:56 +0530 Subject: [PATCH 01/14] fix: new audit fixes 1 --- src/modules/logicModule/LM_PC_FundingPot_v1.sol | 12 ++++-------- .../logicModule/interfaces/ILM_PC_FundingPot_v1.sol | 4 ++-- .../modules/logicModule/LM_PC_FundingPot_v1.t.sol | 4 ++-- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/modules/logicModule/LM_PC_FundingPot_v1.sol b/src/modules/logicModule/LM_PC_FundingPot_v1.sol index 1d1bd3369..c18b7613c 100644 --- a/src/modules/logicModule/LM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/LM_PC_FundingPot_v1.sol @@ -305,10 +305,6 @@ contract LM_PC_FundingPot_v1 is view returns (bool isEligible, uint remainingAmountAllowedToContribute) { - if (accessCriteriaId_ > MAX_ACCESS_CRITERIA_TYPE) { - revert Module__LM_PC_FundingPot__InvalidAccessCriteriaId(); - } - Round storage round = rounds[roundId_]; if (round.roundEnd == 0 && round.roundCap == 0) { @@ -472,7 +468,7 @@ contract LM_PC_FundingPot_v1 is Round storage round = rounds[roundId_]; if (accessCriteriaType_ > MAX_ACCESS_CRITERIA_TYPE) { - revert Module__LM_PC_FundingPot__InvalidAccessCriteriaId(); + revert Module__LM_PC_FundingPot__InvalidAccessCriteriaType(); } _validateEditRoundParameters(round); @@ -494,7 +490,7 @@ contract LM_PC_FundingPot_v1 is round.accessCriterias[criteriaId].accessCriteriaType == AccessCriteriaType.UNSET ) { - revert Module__LM_PC_FundingPot__InvalidAccessCriteriaId(); + revert Module__LM_PC_FundingPot__InvalidAccessCriteriaType(); } } @@ -562,7 +558,7 @@ contract LM_PC_FundingPot_v1 is round.accessCriterias[accessCriteriaId_].accessCriteriaType == AccessCriteriaType.UNSET ) { - revert Module__LM_PC_FundingPot__InvalidAccessCriteriaId(); + revert Module__LM_PC_FundingPot__InvalidAccessCriteriaType(); } _validateEditRoundParameters(round); @@ -899,7 +895,7 @@ contract LM_PC_FundingPot_v1 is } if (accessCriteriaId_ > MAX_ACCESS_CRITERIA_TYPE) { - revert Module__LM_PC_FundingPot__InvalidAccessCriteriaId(); + revert Module__LM_PC_FundingPot__InvalidAccessCriteriaType(); } _validateAccessCriteria( diff --git a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol index 915a61dfd..b4f3b0c75 100644 --- a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol @@ -259,8 +259,8 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { /// @notice Incorrect access criteria. error Module__LM_PC_FundingPot__MissingRequiredAccessCriteriaData(); - /// @notice Invalid access criteria ID. - error Module__LM_PC_FundingPot__InvalidAccessCriteriaId(); + /// @notice Invalid access criteria type. + error Module__LM_PC_FundingPot__InvalidAccessCriteriaType(); /// @notice Invalid times. error Module__LM_PC_FundingPot__InvalidTimes(); diff --git a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol index 782714af7..30e7fd79b 100644 --- a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol +++ b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol @@ -935,7 +935,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__InvalidAccessCriteriaId + .Module__LM_PC_FundingPot__InvalidAccessCriteriaType .selector ) ); @@ -4925,7 +4925,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__InvalidAccessCriteriaId + .Module__LM_PC_FundingPot__InvalidAccessCriteriaType .selector ) ); From 5d4e56a45541c9db3e0491f0f516ffdd4c9c87f1 Mon Sep 17 00:00:00 2001 From: JeffreyJoel Date: Wed, 11 Jun 2025 17:38:56 +0100 Subject: [PATCH 02/14] fix: new audit fixes 2 --- .../logicModule/LM_PC_FundingPot_v1.sol | 6 + .../interfaces/ILM_PC_FundingPot_v1.sol | 3 + .../logicModule/LM_PC_FundingPot_v1.t.sol | 131 +++++++++++++++++- 3 files changed, 136 insertions(+), 4 deletions(-) diff --git a/src/modules/logicModule/LM_PC_FundingPot_v1.sol b/src/modules/logicModule/LM_PC_FundingPot_v1.sol index c18b7613c..346e0da18 100644 --- a/src/modules/logicModule/LM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/LM_PC_FundingPot_v1.sol @@ -651,6 +651,12 @@ contract LM_PC_FundingPot_v1 is ); } + // Enforcement: Round IDs must be strictly before the current roundId_ + if (currentProcessingRoundId >= roundId_) { + revert + Module__LM_PC_FundingPot__UnspentCapsMustBeFromPreviousRounds(); + } + lastSeenRoundId = currentProcessingRoundId; // Update lastSeenRoundId before continuing // Skip if this round is before the global accumulation start round diff --git a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol index b4f3b0c75..a89f3af08 100644 --- a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol @@ -320,6 +320,9 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { /// @notice Thrown when round IDs in UnspentPersonalRoundCap array are not strictly increasing. error Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotStrictlyIncreasing(); + /// @notice Unspent personal round cap references a round that is not previous to the current round. + error Module__LM_PC_FundingPot__UnspentCapsMustBeFromPreviousRounds(); + // ------------------------------------------------------------------------- // Public - Getters diff --git a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol index 30e7fd79b..25af3be73 100644 --- a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol +++ b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol @@ -1355,10 +1355,14 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { │ │ └── When the user contributes to the round │ │ └── Then the transaction should revert │ │ - │ └── Given a user has already contributed up to their personal cap - │ └── When the user attempts to contribute again - │ └── Then the transaction should revert - │ + │ ├── Given a user has already contributed up to their personal cap + │ │ └── When the user attempts to contribute again + │ │ └── Then the transaction should revert + │ │ + │ ├── Given the user tries to use unspent caps not from a previous round(i.e. using the current or a future round's ID) + │ │ └── When the user attempts to contribute + │ │ └── Then the transaction should revert + │ │ └── Given the round contribution cap is reached └── When the user attempts to contribute └── Then the transaction should revert @@ -1573,6 +1577,125 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); } + function testContributeToRoundFor_revertsGivenUnspentCapsIsNotFromPreviousRounds( + ) public { + RoundParams memory params1 = _defaultRoundParams; + params1.accumulationMode = ILM_PC_FundingPot_v1.AccumulationMode.All; + + fundingPot.createRound( + params1.roundStart, + params1.roundEnd, + params1.roundCap, + params1.hookContract, + params1.hookFunction, + params1.autoClosure, + params1.accumulationMode + ); + uint32 round1Id = fundingPot.getRoundCount(); + + uint8 accessCriteriaId = 1; + uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.NFT); + + ( + address nftContract, + bytes32 merkleRoot, + address[] memory allowedAddresses + ) = _helper_createAccessCriteria(accessType, round1Id); + + fundingPot.setAccessCriteria( + round1Id, accessType, 0, nftContract, merkleRoot, allowedAddresses + ); + fundingPot.setAccessCriteriaPrivileges( + round1Id, accessCriteriaId, 500, false, 0, 0, 0 + ); + + mockNFTContract.mint(contributor1_); + + RoundParams memory params2 = _defaultRoundParams; + params2.roundStart = _defaultRoundParams.roundStart + 3 days; + params2.roundEnd = _defaultRoundParams.roundEnd + 3 days; + params2.accumulationMode = ILM_PC_FundingPot_v1.AccumulationMode.All; + + fundingPot.createRound( + params2.roundStart, + params2.roundEnd, + params2.roundCap, + params2.hookContract, + params2.hookFunction, + params2.autoClosure, + params2.accumulationMode + ); + uint32 round2Id = fundingPot.getRoundCount(); + + fundingPot.setAccessCriteria( + round2Id, accessType, 0, nftContract, merkleRoot, allowedAddresses + ); + fundingPot.setAccessCriteriaPrivileges( + round2Id, accessCriteriaId, 400, false, 0, 0, 0 + ); + + vm.warp(params2.roundStart + 1); + + //Attempt to use current round's ID + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + invalidUnspentCaps1 = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](1); + invalidUnspentCaps1[0] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap({ + roundId: round2Id, + accessCriteriaId: accessCriteriaId, + merkleProof: new bytes32[](0) + }); + + vm.startPrank(contributor1_); + _token.approve(address(fundingPot), 700); + + vm.expectRevert( + abi.encodeWithSelector( + ILM_PC_FundingPot_v1 + .Module__LM_PC_FundingPot__UnspentCapsMustBeFromPreviousRounds + .selector + ) + ); + fundingPot.contributeToRoundFor( + contributor1_, + round2Id, + 700, + accessCriteriaId, + new bytes32[](0), + invalidUnspentCaps1 + ); + + //Attempt to use future round's ID + uint32 round3Id = round2Id + 1; + + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + invalidUnspentCaps2 = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](1); + invalidUnspentCaps2[0] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap({ + roundId: round3Id, + accessCriteriaId: accessCriteriaId, + merkleProof: new bytes32[](0) + }); + + vm.expectRevert( + abi.encodeWithSelector( + ILM_PC_FundingPot_v1 + .Module__LM_PC_FundingPot__UnspentCapsMustBeFromPreviousRounds + .selector + ) + ); + fundingPot.contributeToRoundFor( + contributor1_, + round2Id, + 700, + accessCriteriaId, + new bytes32[](0), + invalidUnspentCaps2 + ); + + vm.stopPrank(); + } + function testContributeToRoundFor_revertsGivenPreviousContributionExceedsPersonalCap( ) public { testCreateRound(); From f779c36c912c4f302c1ad26c8d45c7351eebeee3 Mon Sep 17 00:00:00 2001 From: Zuhaib Mohammed Date: Fri, 13 Jun 2025 11:27:37 +0530 Subject: [PATCH 03/14] fix: new audit fixes 3 --- .../logicModule/LM_PC_FundingPot_v1.sol | 10 +- .../interfaces/ILM_PC_FundingPot_v1.sol | 4 +- test/e2e/logicModule/FundingPotE2E.t.sol | 8 +- .../logicModule/LM_PC_FundingPot_v1.t.sol | 558 +++++++++++++++--- 4 files changed, 500 insertions(+), 80 deletions(-) diff --git a/src/modules/logicModule/LM_PC_FundingPot_v1.sol b/src/modules/logicModule/LM_PC_FundingPot_v1.sol index 346e0da18..e32d5ccec 100644 --- a/src/modules/logicModule/LM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/LM_PC_FundingPot_v1.sol @@ -463,7 +463,8 @@ contract LM_PC_FundingPot_v1 is uint8 accessCriteriaId_, // Optional: 0 for new, non-zero for edit address nftContract_, bytes32 merkleRoot_, - address[] calldata allowedAddresses_ + address[] calldata allowedAddresses_, + address[] calldata removedAddresses_ ) external onlyModuleRole(FUNDING_POT_ADMIN_ROLE) { Round storage round = rounds[roundId_]; @@ -529,6 +530,13 @@ contract LM_PC_FundingPot_v1 is } else if (accessCriteriaType == AccessCriteriaType.MERKLE) { round.accessCriterias[criteriaId].merkleRoot = merkleRoot_; } else if (accessCriteriaType == AccessCriteriaType.LIST) { + // Remove the addresses from the allowed list if any + if (removedAddresses_.length > 0) { + for (uint i = 0; i < removedAddresses_.length; i++) { + round.accessCriterias[criteriaId].allowedAddresses[removedAddresses_[i]] + = false; + } + } // For LIST type, update the allowed addresses for (uint i = 0; i < allowedAddresses_.length; i++) { round.accessCriterias[criteriaId].allowedAddresses[allowedAddresses_[i]] diff --git a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol index a89f3af08..5a5c6e2ee 100644 --- a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol @@ -492,13 +492,15 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { /// @param nftContract_ Address of the NFT contract. /// @param merkleRoot_ Merkle root for the access criteria. /// @param allowedAddresses_ List of explicitly allowed addresses. + /// @param removedAddresses_ List of addresses to remove from the allowed list. function setAccessCriteria( uint32 roundId_, uint8 accessCriteriaType_, uint8 accessCriteriaId_, address nftContract_, bytes32 merkleRoot_, - address[] memory allowedAddresses_ + address[] memory allowedAddresses_, + address[] memory removedAddresses_ ) external; /// @notice Removes addresses from the allowed list for a specific access criteria. diff --git a/test/e2e/logicModule/FundingPotE2E.t.sol b/test/e2e/logicModule/FundingPotE2E.t.sol index 25ca300e0..38edb08b7 100644 --- a/test/e2e/logicModule/FundingPotE2E.t.sol +++ b/test/e2e/logicModule/FundingPotE2E.t.sol @@ -199,13 +199,16 @@ contract FundingPotE2E is E2ETest { allowedAddresses[0] = contributor1; allowedAddresses[1] = contributor2; + address[] memory removedAddresses = new address[](0); + fundingPot.setAccessCriteria( round1Id, uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.LIST), 0, address(0), bytes32(0), - allowedAddresses + allowedAddresses, + removedAddresses ); // Add access criteria to round 2 @@ -218,7 +221,8 @@ contract FundingPotE2E is E2ETest { 0, address(0), bytes32(0), - allowedAddresses + allowedAddresses, + removedAddresses ); // 5. Set access criteria privileges for the rounds diff --git a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol index 25af3be73..bdab724e8 100644 --- a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol +++ b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol @@ -81,6 +81,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ERC721Mock mockNFTContract = new ERC721Mock("NFT Mock", "NFT"); MockFailingHookContract failingHook = new MockFailingHookContract(); + address[] public removedAddresses; + // ------------------------------------------------------------------------- // Setup @@ -130,6 +132,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { true, ILM_PC_FundingPot_v1.AccumulationMode.All ); + + removedAddresses = new address[](0); } // ------------------------------------------------------------------------- @@ -812,7 +816,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { │ └── Then it should not revert └── Given all the valid parameters and access criteria is set └── When user attempts to edit access criteria - └── Then it should not revert + └── Then it should not revert */ function testFuzzSetAccessCriteria_revertsGivenUserDoesNotHaveFundingPotAdminRole( @@ -846,7 +850,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); vm.stopPrank(); } @@ -877,7 +882,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); } @@ -911,7 +917,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); } @@ -945,7 +952,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); } @@ -977,7 +985,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); } @@ -1009,7 +1018,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); } @@ -1041,7 +1051,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); } @@ -1064,7 +1075,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); ( @@ -1113,7 +1125,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 1, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); } @@ -1147,7 +1160,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) = _helper_createAccessCriteria(accessType, roundId); fundingPot.setAccessCriteria( - roundId, accessType, 0, nftContract, merkleRoot, allowedAddresses + roundId, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); address[] memory addressesToRemove = new address[](2); @@ -1172,6 +1191,76 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { assertTrue(otherAddressesHaveAccess); } + /* + ├── Given the round exists + | ├── Given an initial access criteria list with addresses [0x1, 0x2, 0x3] + │ │ └── When checking access for address 0x3 + │ │ └── Then access should be granted + │ │ + │ └── Given an update to the access criteria + │ ├── When adding new addresses [0x4, 0x5] + │ ├── And removing address [0x3] + │ │ └── Then access for address 0x3 should be revoked + │ └── And the final allowed list should contain [0x1, 0x2, 0x4, 0x5] + */ + function testRemoveAllowAddressesSetAccessCriteria() public { + testCreateRound(); + uint32 roundId = fundingPot.getRoundCount(); + + uint8 accessCriteriaId = 1; + uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.LIST); + ( + address nftContract, + bytes32 merkleRoot, + address[] memory allowedAddresses + ) = _helper_createAccessCriteria(accessType, roundId); + + allowedAddresses = new address[](3); + allowedAddresses[0] = address(0x1); + allowedAddresses[1] = address(0x2); + allowedAddresses[2] = address(0x3); + + fundingPot.setAccessCriteria( + roundId, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses + ); + + bool hasAccess = fundingPot.exposed_checkAccessCriteriaEligibility( + roundId, accessCriteriaId, new bytes32[](0), address(0x3) + ); + assertTrue(hasAccess); + //Admin wants to give access to two new users and removed one user + allowedAddresses = new address[](4); + allowedAddresses[0] = address(0x1); + allowedAddresses[1] = address(0x2); + allowedAddresses[2] = address(0x4); + allowedAddresses[3] = address(0x5); + + removedAddresses = new address[](1); + removedAddresses[0] = address(0x3); + + //Edit the AccessCriteria + fundingPot.setAccessCriteria( + roundId, + accessType, + 1, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses + ); + + hasAccess = fundingPot.exposed_checkAccessCriteriaEligibility( + roundId, accessCriteriaId, new bytes32[](0), address(0x3) + ); + assertFalse(hasAccess); + } + /* Test: setAccessCriteriaPrivileges() ├── Given user does not have FUNDING_POT_ADMIN_ROLE │ └── When user attempts to set access criteria privileges @@ -1213,7 +1302,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); vm.startPrank(user_); @@ -1258,7 +1348,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( @@ -1286,7 +1377,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ├── Given the access criteria does not exist │ └── When user attempts to get access criteria privileges │ └── Then it should return default values - + */ function testFuzzGetRoundAccessCriteriaPrivileges_returnsDefaultValuesGivenInvalidAccessCriteriaId( uint8 accessCriteriaEnum @@ -1384,7 +1475,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) = _helper_createAccessCriteria(accessType, roundId); fundingPot.setAccessCriteria( - roundId, accessType, 0, nftContract, merkleRoot, allowedAddresses + roundId, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( roundId, accessCriteriaId, 500, false, 0, 0, 0 @@ -1426,7 +1523,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) = _helper_createAccessCriteria(accessType, roundId); fundingPot.setAccessCriteria( - roundId, accessType, 0, nftContract, merkleRoot, allowedAddresses + roundId, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( roundId, accessCriteriaId, 500, false, 0, 0, 0 @@ -1507,7 +1610,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) = _helper_createAccessCriteria(accessType, roundId); fundingPot.setAccessCriteria( - roundId, accessType, 0, nftContract, merkleRoot, allowedAddresses + roundId, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( roundId, accessCriteriaId, 500, false, 0, 0, 0 @@ -1550,7 +1659,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) = _helper_createAccessCriteria(accessType, roundId); fundingPot.setAccessCriteria( - roundId, accessType, 0, nftContract, merkleRoot, allowedAddresses + roundId, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( roundId, accessCriteriaId, 500, false, 0, 0, 0 @@ -1603,7 +1718,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) = _helper_createAccessCriteria(accessType, round1Id); fundingPot.setAccessCriteria( - round1Id, accessType, 0, nftContract, merkleRoot, allowedAddresses + round1Id, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round1Id, accessCriteriaId, 500, false, 0, 0, 0 @@ -1628,7 +1749,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { uint32 round2Id = fundingPot.getRoundCount(); fundingPot.setAccessCriteria( - round2Id, accessType, 0, nftContract, merkleRoot, allowedAddresses + round2Id, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round2Id, accessCriteriaId, 400, false, 0, 0, 0 @@ -1712,7 +1839,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) = _helper_createAccessCriteria(accessType, roundId); fundingPot.setAccessCriteria( - roundId, accessType, 0, nftContract, merkleRoot, allowedAddresses + roundId, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( roundId, accessCriteriaId, 500, false, 0, 0, 0 @@ -1764,7 +1897,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { │ └── Then the funds are transferred to the funding pot │ And the contribution is recorded │ - ├── Given the access criteria is MERKLE + ├── Given the access criteria is MERKLE │ And the user fulfills the access criteria │ └── When the user contributes to the round │ └── Then the funds are transferred to the funding pot @@ -1902,7 +2035,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) = _helper_createAccessCriteria(accessType, roundId); fundingPot.setAccessCriteria( - roundId, accessType, 0, nftContract, merkleRoot, allowedAddresses + roundId, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( roundId, accessCriteriaId, 1000, false, 0, 0, 0 @@ -2050,7 +2189,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) = _helper_createAccessCriteria(accessType, roundId); fundingPot.setAccessCriteria( - roundId, accessType, 0, nftContract, merkleRoot, allowedAddresses + roundId, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); uint personalCap = 200; @@ -2104,7 +2249,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) = _helper_createAccessCriteria(accessType, roundId); fundingPot.setAccessCriteria( - roundId, accessType, 0, nftContract, merkleRoot, allowedAddresses + roundId, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( roundId, accessCriteriaId, personalCap, false, 0, 0, 0 @@ -2174,7 +2325,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) = _helper_createAccessCriteria(accessType, roundId); fundingPot.setAccessCriteria( - roundId, accessType, 0, nftContract, merkleRoot, allowedAddresses + roundId, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); // Set privileges with override capability @@ -2240,7 +2397,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( @@ -2266,7 +2424,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); // Set personal cap of 400 for round 2 @@ -2343,7 +2502,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { address[] memory allowedAddresses ) = _helper_createAccessCriteria(accessType, round1Id); fundingPot.setAccessCriteria( - round1Id, accessType, 0, nftContract, merkleRoot, allowedAddresses + round1Id, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round1Id, accessCriteriaId, 500, false, 0, 0, 0 @@ -2362,7 +2527,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); uint32 round2Id = fundingPot.getRoundCount(); fundingPot.setAccessCriteria( - round2Id, accessType, 0, nftContract, merkleRoot, allowedAddresses + round2Id, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round2Id, accessCriteriaId, 500, false, 0, 0, 0 @@ -2452,7 +2623,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Personal ); fundingPot.setAccessCriteria( - round1Id, 1, 0, address(0), bytes32(0), new address[](0) + round1Id, + 1, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); // Open access fundingPot.setAccessCriteriaPrivileges( round1Id, 1, r1PersonalCap, false, 0, 0, 0 @@ -2469,7 +2646,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Personal ); fundingPot.setAccessCriteria( - round2Id, 1, 0, address(0), bytes32(0), new address[](0) + round2Id, + 1, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round2Id, 1, r2PersonalCap, false, 0, 0, 0 @@ -2486,7 +2669,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Personal ); fundingPot.setAccessCriteria( - round3Id, 1, 0, address(0), bytes32(0), new address[](0) + round3Id, + 1, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round3Id, 1, r3BasePersonalCap, false, 0, 0, 0 @@ -2579,7 +2768,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Total ); fundingPot.setAccessCriteria( - round1Id, 1, 0, address(0), bytes32(0), new address[](0) + round1Id, + 1, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); // Open access fundingPot.setAccessCriteriaPrivileges( round1Id, 1, r1BaseCap, false, 0, 0, 0 @@ -2596,7 +2791,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Total ); fundingPot.setAccessCriteria( - round2Id, 1, 0, address(0), bytes32(0), new address[](0) + round2Id, + 1, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round2Id, 1, r2BaseCap, false, 0, 0, 0 @@ -2613,7 +2814,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Total ); fundingPot.setAccessCriteria( - round3Id, 1, 0, address(0), bytes32(0), new address[](0) + round3Id, + 1, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round3Id, 1, r3BaseCap + r2Contribution, false, 0, 0, 0 @@ -2696,7 +2903,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Personal ); fundingPot.setAccessCriteria( - round1Id, accessId, 0, address(0), bytes32(0), new address[](0) + round1Id, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round1Id, accessId, r1PersonalCapC1, false, 0, 0, 0 @@ -2713,7 +2926,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Personal ); fundingPot.setAccessCriteria( - round2Id, accessId, 0, address(0), bytes32(0), new address[](0) + round2Id, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round2Id, accessId, r2BasePersonalCapC1, false, 0, 0, 0 @@ -2815,7 +3034,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Total ); fundingPot.setAccessCriteria( - round1Id, accessId, 0, address(0), bytes32(0), new address[](0) + round1Id, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round1Id, accessId, r1PersonalCap, false, 0, 0, 0 @@ -2845,7 +3070,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Total ); fundingPot.setAccessCriteria( - round2Id, accessId, 0, address(0), bytes32(0), new address[](0) + round2Id, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); // Set personal cap for R2 to be at least the expected effective total cap uint r2ExpectedEffectiveTotalCap = r2BaseCap + r1UnusedTotal; // 500 + 400 = 900 @@ -2945,7 +3176,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Personal ); fundingPot.setAccessCriteria( - round1Id, accessId, 0, address(0), bytes32(0), new address[](0) + round1Id, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round1Id, accessId, r1PersonalCapC1, false, 0, 0, 0 @@ -2974,7 +3211,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Disabled ); fundingPot.setAccessCriteria( - round2Id, accessId, 0, address(0), bytes32(0), new address[](0) + round2Id, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round2Id, accessId, r2BasePersonalCapC1, false, 0, 0, 0 @@ -3060,7 +3303,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Personal ); fundingPot.setAccessCriteria( - round1Id, accessId, 0, address(0), bytes32(0), new address[](0) + round1Id, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round1Id, accessId, r1PersonalCapC1, false, 0, 0, 0 @@ -3089,7 +3338,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Personal ); fundingPot.setAccessCriteria( - round2Id, accessId, 0, address(0), bytes32(0), new address[](0) + round2Id, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round2Id, accessId, r2BasePersonalCapC1, false, 0, 0, 0 @@ -3179,7 +3434,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Personal ); fundingPot.setAccessCriteria( - round1Id, 1, 0, address(0), bytes32(0), new address[](0) + round1Id, + 1, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round1Id, 1, r1PersonalCapC1, false, 0, 0, 0 @@ -3208,7 +3469,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Personal ); fundingPot.setAccessCriteria( - round2Id, 1, 0, address(0), bytes32(0), new address[](0) + round2Id, + 1, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round2Id, 1, r2PersonalCapC1, false, 0, 0, 0 @@ -3237,7 +3504,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Personal ); fundingPot.setAccessCriteria( - round3Id, 1, 0, address(0), bytes32(0), new address[](0) + round3Id, + 1, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round3Id, 1, r3BasePersonalCapC1, false, 0, 0, 0 @@ -3348,7 +3621,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Total ); fundingPot.setAccessCriteria( - round1Id, 1, 0, address(0), bytes32(0), new address[](0) + round1Id, + 1, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round1Id, 1, r1BaseCap, false, 0, 0, 0 @@ -3376,7 +3655,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Total ); fundingPot.setAccessCriteria( - round2Id, 1, 0, address(0), bytes32(0), new address[](0) + round2Id, + 1, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round2Id, 1, r2BaseCap, false, 0, 0, 0 @@ -3408,8 +3693,9 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { (address nftR3, bytes32 merkleR3, address[] memory allowedR3) = _helper_createAccessCriteria(1, round3Id); - // TODO - fundingPot.setAccessCriteria(round3Id, 1, 0, nftR3, merkleR3, allowedR3); + fundingPot.setAccessCriteria( + round3Id, 1, 0, nftR3, merkleR3, allowedR3, removedAddresses + ); fundingPot.setAccessCriteriaPrivileges( round3Id, 1, r3ExpectedEffectiveCap, false, 0, 0, 0 ); @@ -3478,7 +3764,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.All ); fundingPot.setAccessCriteria( - round1Id, accessId, 0, address(0), bytes32(0), new address[](0) + round1Id, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round1Id, accessId, r1PersonalCapC1, false, 0, 0, 0 @@ -3509,7 +3801,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.All ); fundingPot.setAccessCriteria( - round2Id, accessId, 0, address(0), bytes32(0), new address[](0) + round2Id, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round2Id, accessId, r2BasePersonalCapC1, false, 0, 0, 0 @@ -3589,7 +3887,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.All ); fundingPot.setAccessCriteria( - round1Id, accessId, 0, address(0), bytes32(0), new address[](0) + round1Id, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round1Id, accessId, r1C1PersonalCap, false, 0, 0, 0 @@ -3619,7 +3923,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.All ); fundingPot.setAccessCriteria( - round2Id, accessId, 0, address(0), bytes32(0), new address[](0) + round2Id, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); uint r2ExpectedEffectiveTotalCap = r2BaseTotalCap + r1UnusedTotal; fundingPot.setAccessCriteriaPrivileges( @@ -3690,7 +4000,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.All ); fundingPot.setAccessCriteria( - round1Id, accessId, 0, address(0), bytes32(0), new address[](0) + round1Id, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round1Id, accessId, r1PersonalCapC1, false, 0, 0, 0 @@ -3719,7 +4035,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.All ); fundingPot.setAccessCriteria( - round2Id, accessId, 0, address(0), bytes32(0), new address[](0) + round2Id, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round2Id, accessId, r2BasePersonalCapC1, false, 0, 0, 0 @@ -3800,7 +4122,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.All ); fundingPot.setAccessCriteria( - round1Id, accessId, 0, address(0), bytes32(0), new address[](0) + round1Id, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round1Id, accessId, r1C1PersonalCap, false, 0, 0, 0 @@ -3829,7 +4157,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.All ); fundingPot.setAccessCriteria( - round2Id, accessId, 0, address(0), bytes32(0), new address[](0) + round2Id, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round2Id, accessId, r2BaseTotalCap, false, 0, 0, 0 @@ -3890,7 +4224,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Personal ); fundingPot.setAccessCriteria( - round1Id, accessId, 0, address(0), bytes32(0), new address[](0) + round1Id, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round1Id, accessId, personalCap, false, 0, 0, 0 @@ -3907,7 +4247,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Personal ); fundingPot.setAccessCriteria( - round2Id, accessId, 0, address(0), bytes32(0), new address[](0) + round2Id, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round2Id, accessId, personalCap, false, 0, 0, 0 @@ -3924,7 +4270,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ILM_PC_FundingPot_v1.AccumulationMode.Personal ); fundingPot.setAccessCriteria( - round3Id, accessId, 0, address(0), bytes32(0), new address[](0) + round3Id, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round3Id, accessId, personalCap, false, 0, 0, 0 @@ -4044,7 +4396,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, // accessCriteriaId (0 for new) nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); // Set a personal cap of 500 for round 1 @@ -4082,7 +4435,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, // accessCriteriaId (0 for new) nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); // Set a personal cap of 400 for round 2 @@ -4215,7 +4569,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, // accessCriteriaId (0 for new) nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); // Set a personal cap of 800 for round 1 @@ -4252,7 +4607,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, // accessCriteriaId (0 for new) nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); // Set a personal cap of 300 for round 2 @@ -4515,7 +4871,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges(roundId, 0, 1000, false, 0, 0, 0); @@ -4590,7 +4947,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) = _helper_createAccessCriteria(accessType, roundId); fundingPot.setAccessCriteria( - roundId, accessType, 0, nftContract, merkleRoot, allowedAddresses + roundId, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( roundId, accessCriteriaId, 1000, false, 0, 0, 0 @@ -4629,7 +4992,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) = _helper_createAccessCriteria(accessType, roundId); fundingPot.setAccessCriteria( - roundId, accessType, 0, nftContract, merkleRoot, allowedAddresses + roundId, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( roundId, accessCriteriaId, 1000, false, 0, 0, 0 @@ -4673,7 +5042,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) = _helper_createAccessCriteria(accessType, roundId); fundingPot.setAccessCriteria( - roundId, accessType, 0, nftContract, merkleRoot, allowedAddresses + roundId, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( roundId, accessCriteriaId, 1000, false, 0, 0, 0 @@ -4714,7 +5089,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) = _helper_createAccessCriteria(accessType, roundId); fundingPot.setAccessCriteria( - roundId, accessType, 0, nftContract, merkleRoot, allowedAddresses + roundId, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( @@ -4752,7 +5133,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) = _helper_createAccessCriteria(accessType, roundId); fundingPot.setAccessCriteria( - roundId, accessType, 0, nftContract, merkleRoot, allowedAddresses + roundId, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( roundId, accessCriteriaId, 1000, false, 0, 0, 0 @@ -5091,7 +5478,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { address[] memory allowedAddresses ) = _helper_createAccessCriteria(accessType, roundId); fundingPot.setAccessCriteria( - roundId, accessType, 0, nftContract, merkleRoot, allowedAddresses + roundId, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( roundId, accessCriteriaId, 1000, false, 0, 0, 0 @@ -5180,7 +5573,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { address[] memory allowedAddresses ) = _helper_createAccessCriteria(accessType, roundId); fundingPot.setAccessCriteria( - roundId, accessType, 0, nftContract, merkleRoot, allowedAddresses + roundId, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( roundId, @@ -5255,7 +5654,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) = _helper_createAccessCriteria(accessType, roundId); fundingPot.setAccessCriteria( - roundId, accessType, 0, nftContract, merkleRoot, allowedAddresses + roundId, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( roundId, accessCriteriaId, 1000, false, 0, 0, 0 @@ -5445,7 +5850,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); } } From 0fbdacca101ba05134796e6181c4b7f340923362 Mon Sep 17 00:00:00 2001 From: JeffreyJoel Date: Mon, 16 Jun 2025 14:38:52 +0100 Subject: [PATCH 04/14] fix: audit fixes 4 --- .../logicModule/LM_PC_FundingPot_v1.sol | 155 ++++++---- .../interfaces/ILM_PC_FundingPot_v1.sol | 7 +- .../logicModule/LM_PC_FundingPot_v1.t.sol | 290 ++++++++++++++++++ 3 files changed, 387 insertions(+), 65 deletions(-) diff --git a/src/modules/logicModule/LM_PC_FundingPot_v1.sol b/src/modules/logicModule/LM_PC_FundingPot_v1.sol index e32d5ccec..722cd9dee 100644 --- a/src/modules/logicModule/LM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/LM_PC_FundingPot_v1.sol @@ -626,7 +626,7 @@ contract LM_PC_FundingPot_v1 is uint32 roundId_, uint amount_, uint8 accessCriteriaId_, - bytes32[] calldata merkleProof_ + bytes32[] memory merkleProof_ ) external { // Call the internal function with no additional unspent personal cap _contributeToRoundFor( @@ -643,68 +643,9 @@ contract LM_PC_FundingPot_v1 is bytes32[] memory merkleProof_, UnspentPersonalRoundCap[] calldata unspentPersonalRoundCaps_ ) external { - uint unspentPersonalCap = 0; // Initialize to 0 - uint32 lastSeenRoundId = 0; // Tracks the last seen roundId to ensure strictly increasing order - - // Process each previous round cap that the user wants to carry over - for (uint i = 0; i < unspentPersonalRoundCaps_.length; i++) { - UnspentPersonalRoundCap memory roundCapInfo = - unspentPersonalRoundCaps_[i]; - uint32 currentProcessingRoundId = roundCapInfo.roundId; - - // Enforcement: Round IDs in the array must be strictly increasing. - if (currentProcessingRoundId <= lastSeenRoundId) { - revert - Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotStrictlyIncreasing( - ); - } - - // Enforcement: Round IDs must be strictly before the current roundId_ - if (currentProcessingRoundId >= roundId_) { - revert - Module__LM_PC_FundingPot__UnspentCapsMustBeFromPreviousRounds(); - } - - lastSeenRoundId = currentProcessingRoundId; // Update lastSeenRoundId before continuing - - // Skip if this round is before the global accumulation start round - if (currentProcessingRoundId < globalAccumulationStartRoundId) { - continue; - } - - // For PERSONAL cap rollover, the PREVIOUS round must have allowed it (Personal or All). - if ( - rounds[currentProcessingRoundId].accumulationMode - != AccumulationMode.Personal - && rounds[currentProcessingRoundId].accumulationMode - != AccumulationMode.All - ) { - continue; - } - - if ( - _checkAccessCriteriaEligibility( - currentProcessingRoundId, - roundCapInfo.accessCriteriaId, - roundCapInfo.merkleProof, - user_ - ) - ) { - AccessCriteriaPrivileges storage privileges = - roundIdToAccessCriteriaIdToPrivileges[currentProcessingRoundId][roundCapInfo - .accessCriteriaId]; - - uint userContribution = - roundIdToUserToContribution[currentProcessingRoundId][user_]; - uint personalCap = privileges.personalCap; - uint unspentForThisEntry = 0; - - if (userContribution < personalCap) { - unspentForThisEntry = personalCap - userContribution; - } - unspentPersonalCap += unspentForThisEntry; - } - } + uint unspentPersonalCap = _calculateUnspentPersonalCap( + user_, roundId_, unspentPersonalRoundCaps_ + ); _contributeToRoundFor( user_, @@ -817,6 +758,94 @@ contract LM_PC_FundingPot_v1 is // ------------------------------------------------------------------------- // Internal + /// @notice Calculates the unspent personal capacity from previous rounds. + /// @param user_ The user address to calculate unspent capacity for. + /// @param roundId_ The current round ID. + /// @param unspentPersonalRoundCaps_ Array of previous rounds and access criteria to calculate unused capacity from. + /// @return unspentPersonalCap The amount of unspent personal capacity that can be used. + function _calculateUnspentPersonalCap( + address user_, + uint32 roundId_, + UnspentPersonalRoundCap[] calldata unspentPersonalRoundCaps_ + ) internal view returns (uint unspentPersonalCap) { + if (unspentPersonalRoundCaps_.length == 0) { + return 0; + } + + uint totalAggregatedPersonalCap = 0; + uint totalSpentInPastRounds = 0; + uint32 firstRoundId = unspentPersonalRoundCaps_[0].roundId; + uint32 lastSeenRoundId = 0; + // Enforcement: All rounds from the start of the array to the end must be contiguous. + for (uint i = 0; i < unspentPersonalRoundCaps_.length; i++) { + UnspentPersonalRoundCap memory roundCapInfo = + unspentPersonalRoundCaps_[i]; + uint32 currentProcessingRoundId = roundCapInfo.roundId; + + // Check for strictly increasing first (order matters) + if (currentProcessingRoundId <= lastSeenRoundId) { + revert + Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotStrictlyIncreasing( + ); + } + lastSeenRoundId = currentProcessingRoundId; + + // Check for contiguity (consecutive sequence) + if (currentProcessingRoundId != firstRoundId + i) { + revert + Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotContiguous(); + } + + // Enforcement: Round IDs must be strictly before the current roundId_ + if (currentProcessingRoundId >= roundId_) { + revert + Module__LM_PC_FundingPot__UnspentCapsMustBeFromPreviousRounds(); + } + + // For PERSONAL cap rollover, the PREVIOUS round must have allowed it (Personal or All). + if ( + rounds[currentProcessingRoundId].accumulationMode + != AccumulationMode.Personal + && rounds[currentProcessingRoundId].accumulationMode + != AccumulationMode.All + ) { + continue; + } + + // Skip if this round is before the global accumulation start round + if (currentProcessingRoundId < globalAccumulationStartRoundId) { + continue; + } + + // Only count spent amounts from rounds that meet the accumulation criteria + totalSpentInPastRounds += + roundIdToUserToContribution[currentProcessingRoundId][user_]; + + // Check eligibility for the past round + if ( + _checkAccessCriteriaEligibility( + currentProcessingRoundId, + roundCapInfo.accessCriteriaId, + roundCapInfo.merkleProof, + user_ + ) + ) { + AccessCriteriaPrivileges storage privileges = + roundIdToAccessCriteriaIdToPrivileges[currentProcessingRoundId][roundCapInfo + .accessCriteriaId]; + + totalAggregatedPersonalCap += privileges.personalCap; + } + } + + if (totalAggregatedPersonalCap > totalSpentInPastRounds) { + unspentPersonalCap = + totalAggregatedPersonalCap - totalSpentInPastRounds; + } + + return unspentPersonalCap; + } + /// @notice Validates the round parameters. /// @param round_ The round to validate. /// @dev Reverts if the round parameters are invalid. diff --git a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol index 5a5c6e2ee..be786b8ef 100644 --- a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol @@ -247,7 +247,7 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { /// @notice Round has already started and cannot be modified. error Module__LM_PC_FundingPot__RoundAlreadyStarted(); - /// @notice Thrown when a hook contract is specified without a hook function. + /// @notice Thrown when a hook contract is specified with a hook function that has a non-empty implementation. error Module__LM_PC_FundingPot__HookFunctionRequiredWithHookContract(); /// @notice Thrown when a hook function is specified without a hook contract. @@ -320,9 +320,12 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { /// @notice Thrown when round IDs in UnspentPersonalRoundCap array are not strictly increasing. error Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotStrictlyIncreasing(); - /// @notice Unspent personal round cap references a round that is not previous to the current round. + /// @notice Unspent caps must be from previous rounds. error Module__LM_PC_FundingPot__UnspentCapsMustBeFromPreviousRounds(); + /// @notice The round IDs for unspent caps must be contiguous. + error Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotContiguous(); + // ------------------------------------------------------------------------- // Public - Getters diff --git a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol index bdab724e8..db3c61b1a 100644 --- a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol +++ b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol @@ -1454,9 +1454,18 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { │ │ └── When the user attempts to contribute │ │ └── Then the transaction should revert │ │ + │ ├── Given the user tries to use unspent caps with round IDs that are not strictly increasing + │ │ └── When the user attempts to contribute + │ │ └── Then the transaction should revert + │ │ + │ ├── Given the user tries to use unspent caps with non-contiguous round IDs + │ │ └── When the user attempts to contribute + │ │ └── Then the transaction should revert + │ │ └── Given the round contribution cap is reached └── When the user attempts to contribute └── Then the transaction should revert + */ function testContributeToRoundFor_revertsGivenContributionIsBeforeRoundStart( @@ -1823,6 +1832,118 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.stopPrank(); } + function testContributeToRoundFor_revertsGivenUnspentCapsWithNonContiguousRoundIds( + ) public { + // Setup: Create 3 rounds + uint8 accessCriteriaId = 1; + uint personalCap = 300; + + // Round 1 + uint32 round1Id = fundingPot.createRound( + block.timestamp + 1 days, + block.timestamp + 2 days, + 1000, + address(0), + bytes(""), + false, + ILM_PC_FundingPot_v1.AccumulationMode.Personal + ); + _helper_setupAccessCriteriaForRound( + round1Id, + uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), + accessCriteriaId, + personalCap + ); + + // Round 2 + uint32 round2Id = fundingPot.createRound( + block.timestamp + 3 days, + block.timestamp + 4 days, + 1000, + address(0), + bytes(""), + false, + ILM_PC_FundingPot_v1.AccumulationMode.Personal + ); + _helper_setupAccessCriteriaForRound( + round2Id, + uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), + accessCriteriaId, + personalCap + ); + + // Round 3 + uint32 round3Id = fundingPot.createRound( + block.timestamp + 5 days, + block.timestamp + 6 days, + 1000, + address(0), + bytes(""), + false, + ILM_PC_FundingPot_v1.AccumulationMode.Personal + ); + _helper_setupAccessCriteriaForRound( + round3Id, + uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), + accessCriteriaId, + personalCap + ); + + // Round 4 (target) + uint32 round4Id = fundingPot.createRound( + block.timestamp + 7 days, + block.timestamp + 8 days, + 1000, + address(0), + bytes(""), + false, + ILM_PC_FundingPot_v1.AccumulationMode.Personal + ); + _helper_setupAccessCriteriaForRound( + round4Id, + uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), + accessCriteriaId, + personalCap + ); + + vm.warp(block.timestamp + 7 days + 1 hours); // Enter Round 4 + + // Create non-contiguous unspent caps array (skipping round 2) + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + nonContiguousUnspentCaps = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](2); + nonContiguousUnspentCaps[0] = ILM_PC_FundingPot_v1 + .UnspentPersonalRoundCap({ + roundId: round1Id, + accessCriteriaId: accessCriteriaId, + merkleProof: new bytes32[](0) + }); + nonContiguousUnspentCaps[1] = ILM_PC_FundingPot_v1 + .UnspentPersonalRoundCap({ + roundId: round3Id, // Skip round 2, making it non-contiguous + accessCriteriaId: accessCriteriaId, + merkleProof: new bytes32[](0) + }); + + vm.startPrank(contributor1_); + _token.approve(address(fundingPot), 500); + + vm.expectRevert( + ILM_PC_FundingPot_v1 + .Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotContiguous + .selector + ); + fundingPot.contributeToRoundFor( + contributor1_, + round4Id, + 200, + accessCriteriaId, + new bytes32[](0), + nonContiguousUnspentCaps + ); + vm.stopPrank(); + } + function testContributeToRoundFor_revertsGivenPreviousContributionExceedsPersonalCap( ) public { testCreateRound(); @@ -1967,6 +2088,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { │ └── When calculating effective total cap │ └── Then unspent total capacity from all applicable previous rounds should expand the effective cap │ + └── Given the user has unspent caps from previous contiguous rounds + │ └── When the user attempts to contribute using valid unspent caps from previous rounds + │ └── Then the contribution should succeed + │ And the unspent caps should be applied to expand their effective personal cap + │ And the funds should be transferred to the funding pot + │ And the contribution should be recorded + │ ├── Given target round's AccumulationMode is Disabled │ └── When globalAccumulationStartRoundId is set to allow previous rounds │ └── Then no accumulation (personal or total) should occur for the target round @@ -3270,6 +3398,141 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); } + function testContributeToRoundFor_worksGivenUnspentCapsWithContiguousRoundIds( + ) public { + uint8 accessCriteriaId = 1; + uint personalCap = 300; + + // Round 1 - All accumulation enabled + uint32 round1Id = fundingPot.createRound( + block.timestamp + 1 days, + block.timestamp + 2 days, + 1000, + address(0), + bytes(""), + false, + ILM_PC_FundingPot_v1.AccumulationMode.All + ); + _helper_setupAccessCriteriaForRound( + round1Id, + uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), + accessCriteriaId, + personalCap + ); + + // Round 2 - Personal accumulation enabled + uint32 round2Id = fundingPot.createRound( + block.timestamp + 3 days, + block.timestamp + 4 days, + 1000, + address(0), + bytes(""), + false, + ILM_PC_FundingPot_v1.AccumulationMode.Personal + ); + _helper_setupAccessCriteriaForRound( + round2Id, + uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), + accessCriteriaId, + personalCap + ); + + // Round 3 - Target round with personal accumulation + uint32 round3Id = fundingPot.createRound( + block.timestamp + 5 days, + block.timestamp + 6 days, + 1000, + address(0), + bytes(""), + false, + ILM_PC_FundingPot_v1.AccumulationMode.Personal + ); + _helper_setupAccessCriteriaForRound( + round3Id, + uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), + accessCriteriaId, + personalCap + ); + + // Contribute to previous rounds + vm.warp(block.timestamp + 1 days + 1 hours); // Enter Round 1 + vm.startPrank(contributor1_); + _token.approve(address(fundingPot), 1000); + + fundingPot.contributeToRoundFor( + contributor1_, + round1Id, + 100, // Contributed 100 out of 300 cap + accessCriteriaId, + new bytes32[](0) + ); + vm.stopPrank(); + + vm.warp(block.timestamp + 2 days); // Enter Round 2 (3 days total from start) + vm.startPrank(contributor1_); + fundingPot.contributeToRoundFor( + contributor1_, + round2Id, + 150, // Contributed 150 out of 300 cap + accessCriteriaId, + new bytes32[](0) + ); + vm.stopPrank(); + + // Now contribute to round 3 using unspent caps from previous rounds + vm.warp(block.timestamp + 2 days); // Enter Round 3 (5 days total from start) + + // Create unspent caps array for rounds 1 and 2 + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory unspentCaps = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](2); + unspentCaps[0] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap({ + roundId: round1Id, + accessCriteriaId: accessCriteriaId, + merkleProof: new bytes32[](0) + }); + unspentCaps[1] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap({ + roundId: round2Id, + accessCriteriaId: accessCriteriaId, + merkleProof: new bytes32[](0) + }); + + vm.startPrank(contributor1_); + + // Should be able to contribute more than the base personal cap + // Round 1: 300 cap - 100 spent = 200 unused + // Round 2: 300 cap - 150 spent = 150 unused + // Total unspent = 350 + // Round 3 base cap = 300 + // Total effective cap for round 3 = 300 + 350 = 650 + + uint initialBalance = _token.balanceOf(contributor1_); + + fundingPot.contributeToRoundFor( + contributor1_, + round3Id, + 500, // Should work because effective cap is 650 + accessCriteriaId, + new bytes32[](0), + unspentCaps + ); + + vm.stopPrank(); + + // Verify the contribution was recorded + assertEq( + fundingPot.getUserContributionToRound(round3Id, contributor1_), + 500, + "User contribution should be 500" + ); + + // Verify tokens were transferred + assertEq( + _token.balanceOf(contributor1_), + initialBalance - 500, + "Tokens should have been transferred from contributor" + ); + } + function testContributeToRoundFor_noAccumulationWhenGlobalStartEqualsTargetRound( ) public { // SCENARIO: If globalAccumulationStartRoundId is set to the target round's ID (R2), @@ -5854,4 +6117,31 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { removedAddresses ); } + + // Helper function to set up access criteria for an existing round + function _helper_setupAccessCriteriaForRound( + uint32 roundId_, + uint8 accessCriteriaEnum_, + uint8 accessCriteriaId_, + uint personalCap_ + ) internal { + ( + address nftContract, + bytes32 merkleRoot, + address[] memory allowedAddresses + ) = _helper_createAccessCriteria(accessCriteriaEnum_, roundId_); + + fundingPot.setAccessCriteria( + roundId_, + accessCriteriaEnum_, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses + ); + fundingPot.setAccessCriteriaPrivileges( + roundId_, accessCriteriaId_, personalCap_, false, 0, 0, 0 + ); + } } From 30ed78e6a0bddb4075454d2e0559432dcb29d6af Mon Sep 17 00:00:00 2001 From: JeffreyJoel Date: Mon, 16 Jun 2025 14:50:42 +0100 Subject: [PATCH 05/14] fix: new audit fixes 4 -remove unneccessary code --- .../interfaces/ILM_PC_FundingPot_v1.sol | 2 +- .../logicModule/LM_PC_FundingPot_v1.t.sol | 27 ------------------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol index be786b8ef..52310465f 100644 --- a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol @@ -247,7 +247,7 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { /// @notice Round has already started and cannot be modified. error Module__LM_PC_FundingPot__RoundAlreadyStarted(); - /// @notice Thrown when a hook contract is specified with a hook function that has a non-empty implementation. + /// @notice Thrown when a hook contract is specified without a hook function. error Module__LM_PC_FundingPot__HookFunctionRequiredWithHookContract(); /// @notice Thrown when a hook function is specified without a hook contract. diff --git a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol index db3c61b1a..60467e9e7 100644 --- a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol +++ b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol @@ -6117,31 +6117,4 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { removedAddresses ); } - - // Helper function to set up access criteria for an existing round - function _helper_setupAccessCriteriaForRound( - uint32 roundId_, - uint8 accessCriteriaEnum_, - uint8 accessCriteriaId_, - uint personalCap_ - ) internal { - ( - address nftContract, - bytes32 merkleRoot, - address[] memory allowedAddresses - ) = _helper_createAccessCriteria(accessCriteriaEnum_, roundId_); - - fundingPot.setAccessCriteria( - roundId_, - accessCriteriaEnum_, - 0, - nftContract, - merkleRoot, - allowedAddresses, - removedAddresses - ); - fundingPot.setAccessCriteriaPrivileges( - roundId_, accessCriteriaId_, personalCap_, false, 0, 0, 0 - ); - } } From 5a9b2974679879946dd31776241e3ae30b17be5d Mon Sep 17 00:00:00 2001 From: Zuhaib Mohammed Date: Tue, 17 Jun 2025 20:14:44 +0530 Subject: [PATCH 06/14] fix: new audit fixes 5 --- .../logicModule/LM_PC_FundingPot_v1.sol | 21 ++ .../logicModule/LM_PC_FundingPot_v1.t.sol | 257 ++++++++++++++++++ 2 files changed, 278 insertions(+) diff --git a/src/modules/logicModule/LM_PC_FundingPot_v1.sol b/src/modules/logicModule/LM_PC_FundingPot_v1.sol index 722cd9dee..bd1d864c2 100644 --- a/src/modules/logicModule/LM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/LM_PC_FundingPot_v1.sol @@ -157,6 +157,10 @@ contract LM_PC_FundingPot_v1 is /// only previous rounds with roundId >= globalAccumulationStartRoundId will be included. uint32 internal globalAccumulationStartRoundId; + /// @notice Maps user addresses to a mapping of round IDs to a mapping of access criteria IDs to whether their unspent cap has been used + mapping(address => mapping(uint32 => mapping(uint8 => bool))) public + usedUnspentCaps; + /// @notice Storage gap for future upgrades. uint[50] private __gap; @@ -647,6 +651,16 @@ contract LM_PC_FundingPot_v1 is user_, roundId_, unspentPersonalRoundCaps_ ); + if (unspentPersonalCap > 0) { + // Mark the specific caps that were used in this contribution + for (uint i = 0; i < unspentPersonalRoundCaps_.length; i++) { + UnspentPersonalRoundCap memory roundCapInfo = + unspentPersonalRoundCaps_[i]; + usedUnspentCaps[user_][roundCapInfo.roundId][roundCapInfo + .accessCriteriaId] = true; + } + } + _contributeToRoundFor( user_, roundId_, @@ -812,6 +826,13 @@ contract LM_PC_FundingPot_v1 is continue; } + if ( + usedUnspentCaps[user_][currentProcessingRoundId][roundCapInfo + .accessCriteriaId] + ) { + continue; + } + // Skip if this round is before the global accumulation start round if (currentProcessingRoundId < globalAccumulationStartRoundId) { continue; diff --git a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol index 60467e9e7..8323c8915 100644 --- a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol +++ b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol @@ -5015,6 +5015,236 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); } + function testContributeToRoundFor_UsedUnspentCapsIsSet() public { + // Step 1: Create round 1 and round 2 + uint32 round1 = fundingPot.createRound({ + roundStart_: block.timestamp + 1, + roundEnd_: block.timestamp + 1 days, + roundCap_: 1000, + hookContract_: address(0), + hookFunction_: "", + autoClosure_: false, + accumulationMode_: ILM_PC_FundingPot_v1.AccumulationMode.Personal + }); + + uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN); + + ( + address nftContract, + bytes32 merkleRoot, + address[] memory allowedAddresses + ) = _helper_createAccessCriteria(accessType, round1); + + fundingPot.setAccessCriteria( + round1, + uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), + 0, + address(0), + 0, + allowedAddresses, + removedAddresses + ); + fundingPot.setAccessCriteriaPrivileges( + round1, 1, 200, false, block.timestamp, 0, block.timestamp + 1 days + ); + + vm.warp(block.timestamp + 2); + + // Contribute in round 1 + vm.startPrank(contributor1_); + _token.approve(address(fundingPot), 100); + fundingPot.contributeToRoundFor( + contributor1_, round1, 100, 1, new bytes32[](0) + ); + vm.stopPrank(); + + // Step 2: Create round 2 with accumulationMode enabled + uint32 round2 = fundingPot.createRound({ + roundStart_: block.timestamp + 1, + roundEnd_: block.timestamp + 2 days, + roundCap_: 1000, + hookContract_: address(0), + hookFunction_: "", + autoClosure_: false, + accumulationMode_: ILM_PC_FundingPot_v1.AccumulationMode.Personal + }); + + fundingPot.setAccessCriteria( + round2, + uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), + 0, + address(0), + 0, + allowedAddresses, + removedAddresses + ); + fundingPot.setAccessCriteriaPrivileges( + round2, 1, 200, false, block.timestamp, 0, block.timestamp + 1 days + ); + + // Step 3: Contribute to round 2 using unspent cap from round 1 + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory caps = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](1); + + caps[0] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap({ + roundId: round1, + accessCriteriaId: 1, + merkleProof: new bytes32[](0) + }); + + vm.warp(block.timestamp + 2); + + vm.startPrank(contributor1_); + _token.approve(address(fundingPot), 100); + fundingPot.contributeToRoundFor( + contributor1_, round2, 100, 1, new bytes32[](0), caps + ); + vm.stopPrank(); + + // Step 4: Validate that usedUnspentCaps is set to true + bool isUsed = fundingPot.usedUnspentCaps(contributor1_, round1, 1); // expose via helper function if needed + assertTrue(isUsed, "usedUnspentCaps should be true after contribution"); + } + + function testContributeToRoundFor_UsedUnspentCapsSkippedIfAlreadyUsed() + public + { + // Step 1: Create round 1 and round 2 + uint32 round1 = fundingPot.createRound({ + roundStart_: block.timestamp + 1, + roundEnd_: block.timestamp + 1 days, + roundCap_: 1000, + hookContract_: address(0), + hookFunction_: "", + autoClosure_: false, + accumulationMode_: ILM_PC_FundingPot_v1.AccumulationMode.Personal + }); + + uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN); + (address nftContract,, address[] memory allowedAddresses) = + _helper_createAccessCriteria(accessType, round1); + + fundingPot.setAccessCriteria( + round1, + accessType, + 0, + address(0), + 0, + allowedAddresses, + removedAddresses + ); + fundingPot.setAccessCriteriaPrivileges( + round1, 1, 300, false, block.timestamp, 0, block.timestamp + 1 days + ); + + vm.warp(block.timestamp + 2); + + // Step 1b: Contribute in round 1 + vm.startPrank(contributor1_); + _token.approve(address(fundingPot), 100); + fundingPot.contributeToRoundFor( + contributor1_, round1, 100, 1, new bytes32[](0) + ); + vm.stopPrank(); + + // Step 2: Create round 2 + uint32 round2 = fundingPot.createRound({ + roundStart_: block.timestamp + 1, + roundEnd_: block.timestamp + 2 days, + roundCap_: 1000, + hookContract_: address(0), + hookFunction_: "", + autoClosure_: false, + accumulationMode_: ILM_PC_FundingPot_v1.AccumulationMode.Personal + }); + + fundingPot.setAccessCriteria( + round2, + accessType, + 0, + address(0), + 0, + allowedAddresses, + removedAddresses + ); + fundingPot.setAccessCriteriaPrivileges( + round2, 1, 200, false, block.timestamp, 0, block.timestamp + 1 days + ); + + // Step 2b: Contribute using round1 cap → sets usedUnspentCaps + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory caps1 = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](1); + caps1[0] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap({ + roundId: round1, + accessCriteriaId: 1, + merkleProof: new bytes32[](0) + }); + + vm.warp(block.timestamp + 2); + vm.startPrank(contributor1_); + _token.approve(address(fundingPot), 200); + fundingPot.contributeToRoundFor( + contributor1_, round2, 200, 1, new bytes32[](0), caps1 + ); + vm.stopPrank(); + + // Step 3: Create round 3 + uint32 round3 = fundingPot.createRound({ + roundStart_: block.timestamp + 1, + roundEnd_: block.timestamp + 2 days, + roundCap_: 1000, + hookContract_: address(0), + hookFunction_: "", + autoClosure_: false, + accumulationMode_: ILM_PC_FundingPot_v1.AccumulationMode.Personal + }); + + fundingPot.setAccessCriteria( + round3, + accessType, + 0, + address(0), + 0, + allowedAddresses, + removedAddresses + ); + fundingPot.setAccessCriteriaPrivileges( + round3, 1, 300, false, block.timestamp, 0, block.timestamp + 1 days + ); + + // Step 4: Try reusing round1 cap again → should skip because it's already used + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory caps2 = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](1); + caps2[0] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap({ + roundId: round1, + accessCriteriaId: 1, + merkleProof: new bytes32[](0) + }); + + vm.warp(block.timestamp + 2); + vm.startPrank(contributor1_); + _token.approve(address(fundingPot), 100); + fundingPot.contributeToRoundFor( + contributor1_, round3, 100, 1, new bytes32[](0), caps2 + ); + vm.stopPrank(); + + // Step 5: Check that contribution in round3 is only based on round3 cap (not reused from round1) + uint contributed = + fundingPot.getUserContributionToRound(round3, contributor1_); + assertLe( + contributed, + 300, + "Should not include unspent cap from already-used round" + ); + + // Confirm usedUnspentCaps[round1] is still true, not overwritten or reused + bool isStillUsed = fundingPot.usedUnspentCaps(contributor1_, round1, 1); + assertTrue( + isStillUsed, "usedUnspentCaps should still be true from earlier use" + ); + } + // ------------------------------------------------------------------------- // Test: closeRound() @@ -6117,4 +6347,31 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { removedAddresses ); } + + // Helper function to set up access criteria for an existing round + function _helper_setupAccessCriteriaForRound( + uint32 roundId_, + uint8 accessCriteriaEnum_, + uint8 accessCriteriaId_, + uint personalCap_ + ) internal { + ( + address nftContract, + bytes32 merkleRoot, + address[] memory allowedAddresses + ) = _helper_createAccessCriteria(accessCriteriaEnum_, roundId_); + + fundingPot.setAccessCriteria( + roundId_, + accessCriteriaEnum_, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses + ); + fundingPot.setAccessCriteriaPrivileges( + roundId_, accessCriteriaId_, personalCap_, false, 0, 0, 0 + ); + } } From 498b54a52f92f362a8f90b176275f50ebf830094 Mon Sep 17 00:00:00 2001 From: Zuhaib Mohammed Date: Wed, 18 Jun 2025 13:11:03 +0530 Subject: [PATCH 07/14] fix: new audit fixes 5: add getter function --- src/modules/logicModule/LM_PC_FundingPot_v1.sol | 10 +++++++++- .../interfaces/ILM_PC_FundingPot_v1.sol | 14 ++++++++++++++ .../modules/logicModule/LM_PC_FundingPot_v1.t.sol | 6 ++++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/modules/logicModule/LM_PC_FundingPot_v1.sol b/src/modules/logicModule/LM_PC_FundingPot_v1.sol index bd1d864c2..0652f4d3f 100644 --- a/src/modules/logicModule/LM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/LM_PC_FundingPot_v1.sol @@ -158,7 +158,7 @@ contract LM_PC_FundingPot_v1 is uint32 internal globalAccumulationStartRoundId; /// @notice Maps user addresses to a mapping of round IDs to a mapping of access criteria IDs to whether their unspent cap has been used - mapping(address => mapping(uint32 => mapping(uint8 => bool))) public + mapping(address => mapping(uint32 => mapping(uint8 => bool))) private usedUnspentCaps; /// @notice Storage gap for future upgrades. @@ -377,6 +377,14 @@ contract LM_PC_FundingPot_v1 is return globalAccumulationStartRoundId; } + /// @inheritdoc ILM_PC_FundingPot_v1 + function getUserUsedUnspendCaps( + address user_, + uint32 roundId_, + uint8 accessCriteriaId_ + ) external view returns (bool) { + return usedUnspentCaps[user_][roundId_][accessCriteriaId_]; + } // ------------------------------------------------------------------------- // Public - Mutating diff --git a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol index 52310465f..733553315 100644 --- a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol @@ -443,6 +443,20 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { view returns (uint32); + /// @notice Checks whether a user has already used their unspent caps from previous rounds for a specific round and access criteria combination. + /// @dev This function is used to prevent double-counting of unspent caps when users contribute to rounds + /// with accumulation enabled. It tracks whether the user has already utilized their carry-over capacity + /// from previous rounds for the specified round and access criteria. + /// @param user_ The address of the user to check. + /// @param roundId_ The ID of the round to check for unspent cap usage. + /// @param accessCriteriaId_ The ID of the access criteria to check for unspent cap usage. + /// @return True if the user has already used their unspent caps for this round and access criteria combination, false otherwise. + function getUserUsedUnspendCaps( + address user_, + uint32 roundId_, + uint8 accessCriteriaId_ + ) external view returns (bool); + // ------------------------------------------------------------------------- // Public - Mutating diff --git a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol index 8323c8915..ee73c5e14 100644 --- a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol +++ b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol @@ -5102,7 +5102,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.stopPrank(); // Step 4: Validate that usedUnspentCaps is set to true - bool isUsed = fundingPot.usedUnspentCaps(contributor1_, round1, 1); // expose via helper function if needed + bool isUsed = + fundingPot.getUserUsedUnspendCaps(contributor1_, round1, 1); // expose via helper function if needed assertTrue(isUsed, "usedUnspentCaps should be true after contribution"); } @@ -5239,7 +5240,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); // Confirm usedUnspentCaps[round1] is still true, not overwritten or reused - bool isStillUsed = fundingPot.usedUnspentCaps(contributor1_, round1, 1); + bool isStillUsed = + fundingPot.getUserUsedUnspendCaps(contributor1_, round1, 1); assertTrue( isStillUsed, "usedUnspentCaps should still be true from earlier use" ); From ebd3a5c680b55dc32ac7c9c465ca7e9b66bd0def Mon Sep 17 00:00:00 2001 From: Zuhaib Mohammed Date: Thu, 19 Jun 2025 11:08:14 +0530 Subject: [PATCH 08/14] fix: contract size fix 1 --- .../logicModule/LM_PC_FundingPot_v1.sol | 23 ++---------- .../interfaces/ILM_PC_FundingPot_v1.sol | 35 +++---------------- .../logicModule/LM_PC_FundingPot_v1.t.sol | 12 +++---- 3 files changed, 14 insertions(+), 56 deletions(-) diff --git a/src/modules/logicModule/LM_PC_FundingPot_v1.sol b/src/modules/logicModule/LM_PC_FundingPot_v1.sol index 0652f4d3f..22776a1ef 100644 --- a/src/modules/logicModule/LM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/LM_PC_FundingPot_v1.sol @@ -556,12 +556,7 @@ contract LM_PC_FundingPot_v1 is } } - // Emit the appropriate event based on whether this is a new setting or an edit - if (isEdit) { - emit AccessCriteriaEdited(roundId_, criteriaId); - } else { - emit AccessCriteriaSet(roundId_, criteriaId); - } + emit AccessUpdated(isEdit, roundId_, criteriaId); } // Update removeAllowlistedAddresses to match the new approach @@ -899,14 +894,12 @@ contract LM_PC_FundingPot_v1 is if ( round_.hookContract != address(0) && round_.hookFunction.length == 0 ) { - revert - Module__LM_PC_FundingPot__HookFunctionRequiredWithHookContract(); + revert Module__LM_PC_FundingPot__InvalidHookConfiguration(); } if (round_.hookContract == address(0) && round_.hookFunction.length > 0) { - revert - Module__LM_PC_FundingPot__HookContractRequiredWithHookFunction(); + revert Module__LM_PC_FundingPot__InvalidHookConfiguration(); } } @@ -1421,16 +1414,6 @@ contract LM_PC_FundingPot_v1 is }); _addPaymentOrder(paymentOrder); - - emit PaymentOrderCreated( - roundId_, - recipient_, - accessCriteriaId_, - tokensAmount_, - start, - cliff, - end - ); } function _buyBondingCurveToken(uint32 roundId_) internal { diff --git a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol index 733553315..b17075aab 100644 --- a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol @@ -150,16 +150,12 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { AccumulationMode accumulationMode_ ); - /// @notice Emitted when access criteria is set for a round. - /// @param roundId_ The unique identifier of the round. - /// @param accessCriteriaId_ The identifier of the access criteria. - event AccessCriteriaSet(uint32 indexed roundId_, uint8 accessCriteriaId_); - /// @notice Emitted when access criteria is edited for a round. + /// @param isEdit_ represents new or edited setting /// @param roundId_ The unique identifier of the round. /// @param accessCriteriaId_ The identifier of the access criteria. - event AccessCriteriaEdited( - uint32 indexed roundId_, uint8 accessCriteriaId_ + event AccessUpdated( + bool isEdit_, uint32 indexed roundId_, uint8 accessCriteriaId_ ); /// @notice Emitted when access criteria privileges are set for a round. @@ -199,24 +195,6 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { uint32 roundId_, uint8 accessCriteriaId_, address[] addressesRemoved_ ); - /// @notice Emitted when a payment order is created. - /// @param roundId_ The ID of the round. - /// @param contributor_ The address of the contributor. - /// @param accessCriteriaId_ The ID of the access criteria. - /// @param tokensForThisAccessCriteria_ The amount of tokens contributed for this access criteria. - /// @param start_ The start timestamp for for when the linear vesting starts. - /// @param cliff_ The time in seconds from start time at which the unlock starts. - /// @param end_ The end timestamp for when the linear vesting ends. - event PaymentOrderCreated( - uint32 roundId_, - address contributor_, - uint8 accessCriteriaId_, - uint tokensForThisAccessCriteria_, - uint start_, - uint cliff_, - uint end_ - ); - /// @notice Emitted when a contributor batch is processed. /// @param roundId_ The ID of the round. /// @param startIndex_ The starting index in the contributors array. @@ -247,11 +225,8 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { /// @notice Round has already started and cannot be modified. error Module__LM_PC_FundingPot__RoundAlreadyStarted(); - /// @notice Thrown when a hook contract is specified without a hook function. - error Module__LM_PC_FundingPot__HookFunctionRequiredWithHookContract(); - - /// @notice Thrown when a hook function is specified without a hook contract. - error Module__LM_PC_FundingPot__HookContractRequiredWithHookFunction(); + /// @notice Error for invalid hook settings + error Module__LM_PC_FundingPot__InvalidHookConfiguration(); /// @notice Round does not exist. error Module__LM_PC_FundingPot__RoundNotCreated(); diff --git a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol index ee73c5e14..8dfe88c91 100644 --- a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol +++ b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol @@ -301,7 +301,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__HookFunctionRequiredWithHookContract + .Module__LM_PC_FundingPot__InvalidHookConfiguration .selector ) ); @@ -325,7 +325,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__HookContractRequiredWithHookFunction + .Module__LM_PC_FundingPot__InvalidHookConfiguration .selector ) ); @@ -676,7 +676,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__HookFunctionRequiredWithHookContract + .Module__LM_PC_FundingPot__InvalidHookConfiguration .selector ) ); @@ -712,7 +712,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__HookContractRequiredWithHookFunction + .Module__LM_PC_FundingPot__InvalidHookConfiguration .selector ) ); @@ -1116,8 +1116,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) = _helper_createAccessCriteria(newAccessCriteriaEnum, roundId); vm.expectEmit(true, true, true, false); - emit ILM_PC_FundingPot_v1.AccessCriteriaEdited( - roundId, uint8(newAccessCriteriaEnum) + emit ILM_PC_FundingPot_v1.AccessUpdated( + true, roundId, uint8(newAccessCriteriaEnum) ); fundingPot.setAccessCriteria( roundId, From 4ec4dad5798de1e27ae4d908fa8e3984b68c9286 Mon Sep 17 00:00:00 2001 From: Zuhaib Mohammed Date: Thu, 19 Jun 2025 12:10:11 +0530 Subject: [PATCH 09/14] fix: contract size fix 2: remove getter functions and make public --- .../logicModule/LM_PC_FundingPot_v1.sol | 57 +--- .../interfaces/ILM_PC_FundingPot_v1.sol | 53 --- test/e2e/logicModule/FundingPotE2E.t.sol | 4 +- .../logicModule/LM_PC_FundingPot_v1.t.sol | 303 +++++++++--------- 4 files changed, 160 insertions(+), 257 deletions(-) diff --git a/src/modules/logicModule/LM_PC_FundingPot_v1.sol b/src/modules/logicModule/LM_PC_FundingPot_v1.sol index 22776a1ef..432fe93c4 100644 --- a/src/modules/logicModule/LM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/LM_PC_FundingPot_v1.sol @@ -115,7 +115,7 @@ contract LM_PC_FundingPot_v1 is // State /// @notice The current round count. - uint32 private roundCount; + uint32 public roundCount; /// @notice Stores all funding rounds by their unique ID. mapping(uint32 => Round) private rounds; @@ -127,14 +127,14 @@ contract LM_PC_FundingPot_v1 is ) private roundIdToAccessCriteriaIdToPrivileges; /// @notice Maps round IDs to user addresses to contribution amounts. - mapping(uint32 => mapping(address => uint)) private + mapping(uint32 => mapping(address => uint)) public roundIdToUserToContribution; /// @notice Maps round IDs to total contributions. - mapping(uint32 => uint) private roundIdToTotalContributions; + mapping(uint32 => uint) public roundIdToTotalContributions; /// @notice Maps round IDs to closed status. - mapping(uint32 => bool) private roundIdToClosedStatus; + mapping(uint32 => bool) public roundIdToClosedStatus; /// @notice Maps round IDs to bonding curve tokens bought. mapping(uint32 => uint) private roundTokensBought; @@ -155,10 +155,10 @@ contract LM_PC_FundingPot_v1 is /// @notice The minimum round ID (inclusive, >= 1) to consider for accumulation calculations. /// @dev Defaults to 1. If a target round's mode allows accumulation, /// only previous rounds with roundId >= globalAccumulationStartRoundId will be included. - uint32 internal globalAccumulationStartRoundId; + uint32 public globalAccumulationStartRoundId; /// @notice Maps user addresses to a mapping of round IDs to a mapping of access criteria IDs to whether their unspent cap has been used - mapping(address => mapping(uint32 => mapping(uint8 => bool))) private + mapping(address => mapping(uint32 => mapping(uint8 => bool))) public usedUnspentCaps; /// @notice Storage gap for future upgrades. @@ -288,16 +288,6 @@ contract LM_PC_FundingPot_v1 is ); } - /// @inheritdoc ILM_PC_FundingPot_v1 - function getRoundCount() external view returns (uint32) { - return roundCount; - } - - /// @inheritdoc ILM_PC_FundingPot_v1 - function isRoundClosed(uint32 roundId_) external view returns (bool) { - return roundIdToClosedStatus[roundId_]; - } - /// @inheritdoc ILM_PC_FundingPot_v1 function getUserEligibility( uint32 roundId_, @@ -350,41 +340,6 @@ contract LM_PC_FundingPot_v1 is } } - /// @inheritdoc ILM_PC_FundingPot_v1 - function getTotalRoundContribution(uint32 roundId_) - external - view - returns (uint) - { - return roundIdToTotalContributions[roundId_]; - } - - /// @inheritdoc ILM_PC_FundingPot_v1 - function getUserContributionToRound(uint32 roundId_, address user_) - external - view - returns (uint) - { - return roundIdToUserToContribution[roundId_][user_]; - } - - /// @inheritdoc ILM_PC_FundingPot_v1 - function getGlobalAccumulationStartRoundId() - external - view - returns (uint32) - { - return globalAccumulationStartRoundId; - } - - /// @inheritdoc ILM_PC_FundingPot_v1 - function getUserUsedUnspendCaps( - address user_, - uint32 roundId_, - uint8 accessCriteriaId_ - ) external view returns (bool) { - return usedUnspentCaps[user_][roundId_][accessCriteriaId_]; - } // ------------------------------------------------------------------------- // Public - Mutating diff --git a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol index b17075aab..81d0fac45 100644 --- a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol @@ -255,9 +255,6 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { /// @notice User is not on the allowlist. error Module__LM_PC_FundingPot__AccessCriteriaListFailed(); - /// @notice Access not permitted. - error Module__LM_PC_FundingPot__AccessNotPermitted(); - /// @notice User has reached their personal contribution cap. error Module__LM_PC_FundingPot__PersonalCapReached(); @@ -365,15 +362,6 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { uint end_ ); - /// @notice Retrieves the total number of funding rounds. - /// @return roundCount_ The total number of funding rounds. - function getRoundCount() external view returns (uint32 roundCount_); - - /// @notice Retrieves the closed status of a round. - /// @param roundId_ The ID of the round. - /// @return The closed status of the round. - function isRoundClosed(uint32 roundId_) external view returns (bool); - /// @notice Gets eligibility information for a user in a specific round. /// @param roundId_ The ID of the round to check eligibility for. /// @param accessCriteriaId_ The ID of the access criteria to check eligibility for. @@ -391,47 +379,6 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { view returns (bool isEligible, uint remainingAmountAllowedToContribute); - /// @notice Retrieves the total contribution for a specific round. - /// @param roundId_ The ID of the round to check contributions for. - /// @return The total contributions for the specified round. - function getTotalRoundContribution(uint32 roundId_) - external - view - returns (uint); - - /// @notice Retrieves the contribution amount for a specific user in a round. - /// @param roundId_ The ID of the round to check contributions for. - /// @param user_ The address of the user. - /// @return The user's contribution amount for the specified round. - function getUserContributionToRound(uint32 roundId_, address user_) - external - view - returns (uint); - - /// @notice Retrieves the globally configured start round ID for accumulation calculations. - /// @dev Accumulation (both personal and total) will only consider previous rounds - /// with IDs greater than or equal to this value, provided the target round's - /// AccumulationMode allows it. Defaults to 1. - /// @return The first round ID (inclusive) to consider for accumulation. - function getGlobalAccumulationStartRoundId() - external - view - returns (uint32); - - /// @notice Checks whether a user has already used their unspent caps from previous rounds for a specific round and access criteria combination. - /// @dev This function is used to prevent double-counting of unspent caps when users contribute to rounds - /// with accumulation enabled. It tracks whether the user has already utilized their carry-over capacity - /// from previous rounds for the specified round and access criteria. - /// @param user_ The address of the user to check. - /// @param roundId_ The ID of the round to check for unspent cap usage. - /// @param accessCriteriaId_ The ID of the access criteria to check for unspent cap usage. - /// @return True if the user has already used their unspent caps for this round and access criteria combination, false otherwise. - function getUserUsedUnspendCaps( - address user_, - uint32 roundId_, - uint8 accessCriteriaId_ - ) external view returns (bool); - // ------------------------------------------------------------------------- // Public - Mutating diff --git a/test/e2e/logicModule/FundingPotE2E.t.sol b/test/e2e/logicModule/FundingPotE2E.t.sol index 38edb08b7..7f3d06e1a 100644 --- a/test/e2e/logicModule/FundingPotE2E.t.sol +++ b/test/e2e/logicModule/FundingPotE2E.t.sol @@ -285,8 +285,8 @@ contract FundingPotE2E is E2ETest { // 8. Close rounds fundingPot.closeRound(round1Id); - assertEq(fundingPot.isRoundClosed(round1Id), true); - assertEq(fundingPot.isRoundClosed(round2Id), true); // round2 is auto closed + assertEq(fundingPot.roundIdToClosedStatus(round1Id), true); + assertEq(fundingPot.roundIdToClosedStatus(round2Id), true); // round2 is auto closed assertEq(contributionToken.balanceOf(address(fundingPot)), 0); assertGt(issuanceToken.balanceOf(address(fundingPot)), 0); diff --git a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol index 8dfe88c91..060b9e836 100644 --- a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol +++ b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol @@ -359,7 +359,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { params.accumulationMode ); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); // Retrieve the stored parameters ( @@ -433,7 +433,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { { vm.assume(user_ != address(0) && user_ != address(this)); testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); RoundParams memory params = RoundParams({ roundStart: block.timestamp + 3 days, @@ -469,7 +469,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { function testEditRound_revertsGivenRoundIsNotCreated() public { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); RoundParams memory params = RoundParams({ roundStart: block.timestamp + 3 days, @@ -502,7 +502,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { function testEditRound_revertsGivenRoundIsActive() public { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); RoundParams memory params; ( @@ -549,7 +549,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { public { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); _editedRoundParams; vm.assume(roundStart_ < block.timestamp); _editedRoundParams.roundStart = roundStart_; @@ -576,7 +576,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { function testEditRound_revertsGivenRoundEndTimeAndCapAreBothZero() public { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); RoundParams memory params = RoundParams({ roundStart: block.timestamp + 3 days, @@ -617,7 +617,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { && roundEnd_ < roundStart_ ); testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); // Get the current round start time (uint currentRoundStart,,,,,,) = @@ -661,7 +661,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { public { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); RoundParams memory params = _helper_createEditRoundParams( block.timestamp + 3 days, @@ -697,7 +697,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { public { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); RoundParams memory params = _helper_createEditRoundParams( block.timestamp + 3 days, @@ -745,7 +745,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { function testEditRound() public { testCreateRound(); - uint32 lastRoundId = fundingPot.getRoundCount(); + uint32 lastRoundId = fundingPot.roundCount(); RoundParams memory params = _editedRoundParams; @@ -827,7 +827,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.assume(user_ != address(0) && user_ != address(this)); testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); ( address nftContract, @@ -861,7 +861,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) public { vm.assume(accessCriteriaEnum >= 0 && accessCriteriaEnum <= 4); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); ( address nftContract, @@ -893,7 +893,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.assume(accessCriteriaEnum >= 0 && accessCriteriaEnum <= 4); testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); ( address nftContract, @@ -928,7 +928,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.assume(accessCriteriaEnum > 4); testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); ( address nftContract, @@ -963,7 +963,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.NFT); testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); ( address nftContract, @@ -996,7 +996,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.MERKLE); testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); ( address nftContract, @@ -1029,7 +1029,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.LIST); testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); ( address nftContract, @@ -1060,7 +1060,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.assume(accessCriteriaEnum >= 0 && accessCriteriaEnum <= 4); testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); uint8 accessCriteriaId = 1; ( @@ -1108,7 +1108,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { testFuzzSetAccessCriteria(oldAccessCriteriaEnum); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); ( address nftContract, bytes32 merkleRoot, @@ -1149,7 +1149,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { */ function testRemoveAllowlistedAddresses() public { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); uint8 accessCriteriaId = 1; uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.LIST); @@ -1205,7 +1205,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { */ function testRemoveAllowAddressesSetAccessCriteria() public { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); uint8 accessCriteriaId = 1; uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.LIST); @@ -1472,7 +1472,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) public { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); uint8 accessCriteriaId = 1; uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN); uint amount = 250; @@ -1519,7 +1519,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); uint8 accessCriteriaId = 1; uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN); @@ -1572,7 +1572,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.NFT); _helper_setupRoundWithAccessCriteria(accessType); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); uint amount = 250; @@ -1603,7 +1603,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) public { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); uint8 accessCriteriaId = 1; uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.MERKLE); uint amount = 250; @@ -1656,7 +1656,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) public { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); uint8 accessCriteriaId = 1; uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.LIST); uint amount = 250; @@ -1715,7 +1715,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { params1.autoClosure, params1.accumulationMode ); - uint32 round1Id = fundingPot.getRoundCount(); + uint32 round1Id = fundingPot.roundCount(); uint8 accessCriteriaId = 1; uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.NFT); @@ -1755,7 +1755,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { params2.autoClosure, params2.accumulationMode ); - uint32 round2Id = fundingPot.getRoundCount(); + uint32 round2Id = fundingPot.roundCount(); fundingPot.setAccessCriteria( round2Id, @@ -1948,7 +1948,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) public { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); uint8 accessCriteriaId = 1; uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.NFT); uint amount = 500; @@ -2153,7 +2153,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { uint8 accessCriteriaId = 1; uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.NFT); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); uint amount = 250; ( @@ -2200,12 +2200,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0) ); - uint totalContributions = fundingPot.getTotalRoundContribution(roundId); + uint totalContributions = + fundingPot.roundIdToTotalContributions(roundId); assertEq(totalContributions, amount); uint personalContributions = - fundingPot.getUserContributionToRound(roundId, contributor1_); + fundingPot.roundIdToUserToContribution(roundId, contributor1_); assertEq(personalContributions, amount); } @@ -2221,7 +2222,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { uint8 accessCriteriaId = 1; _helper_setupRoundWithAccessCriteria(accessCriteriaId); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); mockNFTContract.mint(contributor1_); @@ -2243,10 +2244,11 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.stopPrank(); uint userContribution = - fundingPot.getUserContributionToRound(roundId, contributor1_); + fundingPot.roundIdToUserToContribution(roundId, contributor1_); assertEq(userContribution, 250); - uint totalContributions = fundingPot.getTotalRoundContribution(roundId); + uint totalContributions = + fundingPot.roundIdToTotalContributions(roundId); assertEq(totalContributions, 250); } @@ -2258,7 +2260,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { _helper_setupRoundWithAccessCriteria(accessType); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); (,,,, bytes32[] memory proofB) = _helper_generateMerkleTreeForTwoLeaves( contributor1_, contributor2_, roundId @@ -2285,10 +2287,11 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.stopPrank(); uint userContribution = - fundingPot.getUserContributionToRound(roundId, contributor2_); + fundingPot.roundIdToUserToContribution(roundId, contributor2_); assertEq(userContribution, contributionAmount); - uint totalContributions = fundingPot.getTotalRoundContribution(roundId); + uint totalContributions = + fundingPot.roundIdToTotalContributions(roundId); assertEq(totalContributions, contributionAmount); } @@ -2351,19 +2354,19 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.stopPrank(); uint contribution = - fundingPot.getUserContributionToRound(roundId, contributor2_); + fundingPot.roundIdToUserToContribution(roundId, contributor2_); assertEq(contribution, 50); - uint totalContribution = fundingPot.getTotalRoundContribution(roundId); + uint totalContribution = fundingPot.roundIdToTotalContributions(roundId); assertEq(totalContribution, _defaultRoundParams.roundCap); - assertTrue(fundingPot.isRoundClosed(roundId)); + assertTrue(fundingPot.roundIdToClosedStatus(roundId)); } function testContributeToRoundFor_worksGivenContributionPartiallyExceedingPersonalCap( ) public { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); uint8 accessCriteriaId = 1; uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.NFT); @@ -2431,7 +2434,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); uint totalContribution = - fundingPot.getUserContributionToRound(roundId, contributor1_); + fundingPot.roundIdToUserToContribution(roundId, contributor1_); assertEq(totalContribution, personalCap); } @@ -2441,7 +2444,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); uint8 accessCriteriaId = 1; uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.NFT); uint amount = 250; @@ -2490,7 +2493,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); // Verify the contribution was recorded - uint totalContribution = fundingPot.getTotalRoundContribution(roundId); + uint totalContribution = fundingPot.roundIdToTotalContributions(roundId); assertEq(totalContribution, amount); } @@ -2509,7 +2512,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { _defaultRoundParams.accumulationMode ); - uint32 round1Id = fundingPot.getRoundCount(); + uint32 round1Id = fundingPot.roundCount(); uint8 accessCriteriaId = 1; uint8 accessCriteriaType = @@ -2544,7 +2547,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { _defaultRoundParams.autoClosure, _defaultRoundParams.accumulationMode ); - uint32 round2Id = fundingPot.getRoundCount(); + uint32 round2Id = fundingPot.roundCount(); fundingPot.setAccessCriteria( round2Id, @@ -2595,11 +2598,11 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.stopPrank(); assertEq( - fundingPot.getUserContributionToRound(round1Id, contributor1_), 200 + fundingPot.roundIdToUserToContribution(round1Id, contributor1_), 200 ); assertEq( - fundingPot.getUserContributionToRound(round2Id, contributor1_), 700 + fundingPot.roundIdToUserToContribution(round2Id, contributor1_), 700 ); } @@ -2619,7 +2622,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { _defaultRoundParams.autoClosure, _defaultRoundParams.accumulationMode ); - uint32 round1Id = fundingPot.getRoundCount(); + uint32 round1Id = fundingPot.roundCount(); uint8 accessCriteriaId = 1; uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN); @@ -2653,7 +2656,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { _defaultRoundParams.autoClosure, _defaultRoundParams.accumulationMode ); - uint32 round2Id = fundingPot.getRoundCount(); + uint32 round2Id = fundingPot.roundCount(); fundingPot.setAccessCriteria( round2Id, accessType, @@ -2701,23 +2704,23 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); vm.stopPrank(); - assertEq(fundingPot.getTotalRoundContribution(round1Id), 500); + assertEq(fundingPot.roundIdToTotalContributions(round1Id), 500); assertEq( - fundingPot.getUserContributionToRound(round1Id, contributor1_), 300 + fundingPot.roundIdToUserToContribution(round1Id, contributor1_), 300 ); assertEq( - fundingPot.getUserContributionToRound(round1Id, contributor2_), 200 + fundingPot.roundIdToUserToContribution(round1Id, contributor2_), 200 ); - assertEq(fundingPot.getTotalRoundContribution(round2Id), 700); + assertEq(fundingPot.roundIdToTotalContributions(round2Id), 700); assertEq( - fundingPot.getUserContributionToRound(round2Id, contributor2_), 400 + fundingPot.roundIdToUserToContribution(round2Id, contributor2_), 400 ); assertEq( - fundingPot.getUserContributionToRound(round2Id, contributor3_), 300 + fundingPot.roundIdToUserToContribution(round2Id, contributor3_), 300 ); - assertEq(fundingPot.getTotalRoundContribution(round2Id), 700); + assertEq(fundingPot.roundIdToTotalContributions(round2Id), 700); } function testContributeToRoundFor_globalStartRestrictsPersonalAccumulation() @@ -2826,7 +2829,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Set Global Start --- fundingPot.setGlobalAccumulationStart(2); - assertEq(fundingPot.getGlobalAccumulationStartRoundId(), 2); + assertEq(fundingPot.globalAccumulationStartRoundId(), 2); // --- Attempt Contribution in Round 3 --- vm.warp(initialTimestamp + 5 days + 1 hours); // Enter Round 3 @@ -2858,7 +2861,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Assertion --- assertEq( - fundingPot.getUserContributionToRound(round3Id, contributor1_), + fundingPot.roundIdToUserToContribution(round3Id, contributor1_), expectedR3PersonalCap, "R3 personal contribution incorrect" ); @@ -2971,7 +2974,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Set Global Start --- fundingPot.setGlobalAccumulationStart(2); - assertEq(fundingPot.getGlobalAccumulationStartRoundId(), 2); + assertEq(fundingPot.globalAccumulationStartRoundId(), 2); // --- Attempt Contribution in Round 3 --- vm.warp(initialTimestamp + 5 days + 1 hours); // Enter Round 3 @@ -2987,12 +2990,12 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Assertion --- assertEq( - fundingPot.getTotalRoundContribution(round3Id), + fundingPot.roundIdToTotalContributions(round3Id), expectedR3EffectiveCap, "R3 total contribution incorrect, effective cap not as expected" ); assertEq( - fundingPot.getUserContributionToRound(round3Id, contributor1_), + fundingPot.roundIdToUserToContribution(round3Id, contributor1_), expectedR3EffectiveCap, "R3 user contribution incorrect" ); @@ -3003,7 +3006,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // SCENARIO: Default globalAccumulationStartRoundId = 1 allows accumulation from R1 for R2 (Personal mode) // 1. Setup: Round 1 (Personal), Round 2 (Personal). // Partial contribution by C1 in R1. - // 2. Action: Verify getGlobalAccumulationStartRoundId() == 1 (default). + // 2. Action: Verify globalAccumulationStartRoundId() == 1 (default). // 3. Verification: For C1's contribution to R2, unused personal capacity from R1 rolls over. uint initialTimestamp = block.timestamp; @@ -3080,7 +3083,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Verify Default Global Start Round ID --- assertEq( - fundingPot.getGlobalAccumulationStartRoundId(), + fundingPot.globalAccumulationStartRoundId(), 1, "Default global start round ID should be 1" ); @@ -3124,7 +3127,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Assertion --- assertEq( - fundingPot.getUserContributionToRound(round2Id, contributor1_), + fundingPot.roundIdToUserToContribution(round2Id, contributor1_), expectedC1ContributionR2, "R2 C1 personal contribution incorrect (should use R1 unused)" ); @@ -3214,7 +3217,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Verify Default Global Start Round ID --- assertEq( - fundingPot.getGlobalAccumulationStartRoundId(), + fundingPot.globalAccumulationStartRoundId(), 1, "Default global start round ID should be 1" ); @@ -3239,12 +3242,12 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Assertions --- assertEq( - fundingPot.getUserContributionToRound(round2Id, contributor1_), + fundingPot.roundIdToUserToContribution(round2Id, contributor1_), c1AttemptR2, "R2 C1 contribution incorrect" ); assertEq( - fundingPot.getTotalRoundContribution(round2Id), + fundingPot.roundIdToTotalContributions(round2Id), c1AttemptR2, "R2 Total contributions after C1 incorrect" ); @@ -3264,7 +3267,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { } assertEq( - fundingPot.getTotalRoundContribution(round2Id), + fundingPot.roundIdToTotalContributions(round2Id), r2ExpectedEffectiveTotalCap, "R2 final total contributions should match effective total cap" ); @@ -3354,7 +3357,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Set Global Start Round ID to allow R1 (to show it's ignored by R2's Disabled mode) --- fundingPot.setGlobalAccumulationStart(1); assertEq( - fundingPot.getGlobalAccumulationStartRoundId(), + fundingPot.globalAccumulationStartRoundId(), 1, "Global start round ID should be 1" ); @@ -3383,17 +3386,17 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Assertions --- assertEq( - fundingPot.getUserContributionToRound(round2Id, contributor1_), + fundingPot.roundIdToUserToContribution(round2Id, contributor1_), r2BasePersonalCapC1, "R2 C1 personal contribution should be clamped by R2's base personal cap (Disabled mode)" ); assertEq( - fundingPot.getTotalRoundContribution(round2Id), + fundingPot.roundIdToTotalContributions(round2Id), r2BasePersonalCapC1, "R2 Total contributions should not be expanded by R1 (Disabled mode)" ); assertTrue( - fundingPot.getTotalRoundContribution(round2Id) <= r2BaseCap, + fundingPot.roundIdToTotalContributions(round2Id) <= r2BaseCap, "R2 Total contributions exceeded R2's original base cap (Disabled mode)" ); } @@ -3520,7 +3523,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // Verify the contribution was recorded assertEq( - fundingPot.getUserContributionToRound(round3Id, contributor1_), + fundingPot.roundIdToUserToContribution(round3Id, contributor1_), 500, "User contribution should be 500" ); @@ -3616,7 +3619,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Set Global Start Round ID to be Round 2's ID --- fundingPot.setGlobalAccumulationStart(round2Id); assertEq( - fundingPot.getGlobalAccumulationStartRoundId(), + fundingPot.globalAccumulationStartRoundId(), round2Id, "Global start round ID not set to R2 ID" ); @@ -3645,17 +3648,17 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Assertions --- assertEq( - fundingPot.getUserContributionToRound(round2Id, contributor1_), + fundingPot.roundIdToUserToContribution(round2Id, contributor1_), r2BasePersonalCapC1, "R2 C1 personal contribution should be clamped by R2's base personal cap (global start = R2)" ); assertEq( - fundingPot.getTotalRoundContribution(round2Id), + fundingPot.roundIdToTotalContributions(round2Id), r2BasePersonalCapC1, "R2 Total contributions should not be expanded by R1 (global start = R2)" ); assertTrue( - fundingPot.getTotalRoundContribution(round2Id) <= r2BaseCap, + fundingPot.roundIdToTotalContributions(round2Id) <= r2BaseCap, "R2 Total contributions exceeded R2's original base cap (global start = R2)" ); } @@ -3717,7 +3720,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); vm.stopPrank(); assertEq( - fundingPot.getUserContributionToRound(round1Id, contributor1_), + fundingPot.roundIdToUserToContribution(round1Id, contributor1_), r1ContributionC1 ); @@ -3752,7 +3755,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); vm.stopPrank(); assertEq( - fundingPot.getUserContributionToRound(round2Id, contributor1_), + fundingPot.roundIdToUserToContribution(round2Id, contributor1_), r2ContributionC1 ); @@ -3781,7 +3784,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Verify Global Start Round ID --- assertEq( - fundingPot.getGlobalAccumulationStartRoundId(), + fundingPot.globalAccumulationStartRoundId(), 1, "Default global start round ID should be 1" ); @@ -3817,12 +3820,12 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Assertions --- assertEq( - fundingPot.getUserContributionToRound(round3Id, contributor1_), + fundingPot.roundIdToUserToContribution(round3Id, contributor1_), expectedR3PersonalCapC1, "R3 C1 personal contribution incorrect (should use R1 & R2 unused)" ); assertEq( - fundingPot.getTotalRoundContribution(round3Id), + fundingPot.roundIdToTotalContributions(round3Id), expectedR3PersonalCapC1, "R3 total contributions incorrect after C1" ); @@ -3904,7 +3907,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); vm.stopPrank(); assertEq( - fundingPot.getTotalRoundContribution(round1Id), r1ContributionC1 + fundingPot.roundIdToTotalContributions(round1Id), r1ContributionC1 ); // --- Create Round 2 (Total Mode) --- @@ -3938,7 +3941,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); vm.stopPrank(); assertEq( - fundingPot.getTotalRoundContribution(round2Id), r2ContributionC2 + fundingPot.roundIdToTotalContributions(round2Id), r2ContributionC2 ); // --- Create Round 3 (Total Mode) --- @@ -3964,7 +3967,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); assertEq( - fundingPot.getGlobalAccumulationStartRoundId(), + fundingPot.globalAccumulationStartRoundId(), 1, "Default global start round ID should be 1" ); @@ -3980,12 +3983,12 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Assertions --- assertEq( - fundingPot.getTotalRoundContribution(round3Id), + fundingPot.roundIdToTotalContributions(round3Id), r3ExpectedEffectiveCap, "R3 total contributions should match effective cap with rollover from R1 and R2" ); assertEq( - fundingPot.getUserContributionToRound(round3Id, contributor3_), + fundingPot.roundIdToUserToContribution(round3Id, contributor3_), r3ExpectedEffectiveCap, "R3 C3 contribution incorrect" ); @@ -4078,7 +4081,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Global Start ID Check --- assertEq( - fundingPot.getGlobalAccumulationStartRoundId(), + fundingPot.globalAccumulationStartRoundId(), 1, "Default global start ID is 1" ); @@ -4108,12 +4111,12 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Assertions --- assertEq( - fundingPot.getUserContributionToRound(round2Id, contributor1_), + fundingPot.roundIdToUserToContribution(round2Id, contributor1_), expectedEffectivePersonalCapC1R2, "R2 C1 personal contribution incorrect" ); assertEq( - fundingPot.getTotalRoundContribution(round2Id), + fundingPot.roundIdToTotalContributions(round2Id), expectedEffectivePersonalCapC1R2, "R2 Total contributions incorrect" ); @@ -4201,7 +4204,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Ensure Global Start Round ID is 1 --- assertEq( - fundingPot.getGlobalAccumulationStartRoundId(), + fundingPot.globalAccumulationStartRoundId(), 1, "Global start round ID should be 1 by default" ); @@ -4219,12 +4222,12 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Assertions --- assertEq( - fundingPot.getUserContributionToRound(round2Id, contributor1_), + fundingPot.roundIdToUserToContribution(round2Id, contributor1_), c1AttemptR2, "R2 C1 contribution should match attempt (filling effective total cap)" ); assertEq( - fundingPot.getTotalRoundContribution(round2Id), + fundingPot.roundIdToTotalContributions(round2Id), r2ExpectedEffectiveTotalCap, "R2 Total contributions should match effective total cap (All mode, global_start=1)" ); @@ -4313,7 +4316,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Set Global Start Round ID to Round 2's ID --- fundingPot.setGlobalAccumulationStart(round2Id); assertEq( - fundingPot.getGlobalAccumulationStartRoundId(), + fundingPot.globalAccumulationStartRoundId(), round2Id, "Global start ID not set to R2 ID" ); @@ -4342,12 +4345,12 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Assertions --- assertEq( - fundingPot.getUserContributionToRound(round2Id, contributor1_), + fundingPot.roundIdToUserToContribution(round2Id, contributor1_), r2BasePersonalCapC1, "R2 C1 personal contribution should be clamped by R2 base personal cap (All mode, global_start=R2)" ); assertEq( - fundingPot.getTotalRoundContribution(round2Id), + fundingPot.roundIdToTotalContributions(round2Id), r2BasePersonalCapC1, "R2 Total contributions should be C1's clamped amount (All mode, global_start=R2)" ); @@ -4435,7 +4438,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Set Global Start Round ID to Round 2's ID --- fundingPot.setGlobalAccumulationStart(round2Id); assertEq( - fundingPot.getGlobalAccumulationStartRoundId(), + fundingPot.globalAccumulationStartRoundId(), round2Id, "Global start ID not set to R2 ID" ); @@ -4453,12 +4456,12 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Assertions --- assertEq( - fundingPot.getUserContributionToRound(round2Id, contributor1_), + fundingPot.roundIdToUserToContribution(round2Id, contributor1_), r2BaseTotalCap, "R2 C1 contribution should be clamped by R2 base total cap (All mode, global_start=R2)" ); assertEq( - fundingPot.getTotalRoundContribution(round2Id), + fundingPot.roundIdToTotalContributions(round2Id), r2BaseTotalCap, "R2 Total contributions should be R2 base total cap (All mode, global_start=R2)" ); @@ -4621,7 +4624,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); vm.stopPrank(); assertEq( - fundingPot.getUserContributionToRound(round3Id, contributor1_), 100 + fundingPot.roundIdToUserToContribution(round3Id, contributor1_), 100 ); } @@ -4641,7 +4644,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { _defaultRoundParams.autoClosure, _defaultRoundParams.accumulationMode ); - uint32 round1Id = fundingPot.getRoundCount(); + uint32 round1Id = fundingPot.roundCount(); // Set up access criteria for round 1 uint8 accessCriteriaId = 1; // Open access @@ -4689,7 +4692,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { params.autoClosure, params.accumulationMode ); - uint32 round2Id = fundingPot.getRoundCount(); + uint32 round2Id = fundingPot.roundCount(); // Set up access criteria for round 2 fundingPot.setAccessCriteria( @@ -4719,7 +4722,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // Verify contribution to round 1 assertEq( - fundingPot.getUserContributionToRound(round1Id, contributor1_), 200 + fundingPot.roundIdToUserToContribution(round1Id, contributor1_), 200 ); // Move to round 2 @@ -4752,7 +4755,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // Verify contributions to round 2 - should be more than the personal cap of round 2 (400) // This verifies personal caps DO accumulate uint contributionAmount = - fundingPot.getUserContributionToRound(round2Id, contributor1_); + fundingPot.roundIdToUserToContribution(round2Id, contributor1_); assertEq(contributionAmount, 450); assertTrue(contributionAmount > 400, "Personal cap should accumulate"); @@ -4769,12 +4772,12 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); // Verify contributor 2's contribution was clamped to the remaining 50. assertEq( - fundingPot.getUserContributionToRound(round2Id, contributor2_), 50 + fundingPot.roundIdToUserToContribution(round2Id, contributor2_), 50 ); vm.stopPrank(); // Verify total contributions to round 2 is exactly the round cap (450 + 50 = 500). - assertEq(fundingPot.getTotalRoundContribution(round2Id), 500); + assertEq(fundingPot.roundIdToTotalContributions(round2Id), 500); // Additional contributor3 should not be able to contribute anything as the cap is full. // Attempting to contribute when the cap is already full should revert. @@ -4795,7 +4798,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.stopPrank(); // Final check that total contributions remain at the round cap. - assertEq(fundingPot.getTotalRoundContribution(round2Id), 500); + assertEq(fundingPot.roundIdToTotalContributions(round2Id), 500); } function testContributeToRoundFor_totalModeOnlyAccumulatesTotalCaps() @@ -4814,7 +4817,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { _defaultRoundParams.autoClosure, _defaultRoundParams.accumulationMode ); - uint32 round1Id = fundingPot.getRoundCount(); + uint32 round1Id = fundingPot.roundCount(); // Set up access criteria for round 1 (Open) uint8 accessCriteriaId = 1; @@ -4861,7 +4864,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { params.autoClosure, params.accumulationMode ); - uint32 round2Id = fundingPot.getRoundCount(); + uint32 round2Id = fundingPot.roundCount(); // Set up access criteria for round 2 (Open) fundingPot.setAccessCriteria( @@ -4891,9 +4894,9 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // Verify contribution to round 1 assertEq( - fundingPot.getUserContributionToRound(round1Id, contributor1_), 600 + fundingPot.roundIdToUserToContribution(round1Id, contributor1_), 600 ); - assertEq(fundingPot.getTotalRoundContribution(round1Id), 600); + assertEq(fundingPot.roundIdToTotalContributions(round1Id), 600); // Move to round 2 vm.warp(_defaultRoundParams.roundStart + 3 days + 1); @@ -4910,7 +4913,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); // Verify contributor 2's contribution was clamped by personal cap. assertEq( - fundingPot.getUserContributionToRound(round2Id, contributor2_), + fundingPot.roundIdToUserToContribution(round2Id, contributor2_), 300, "C2 contribution should be clamped by personal cap" ); @@ -4918,7 +4921,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // Verify total contributions after C2 is 300 assertEq( - fundingPot.getTotalRoundContribution(round2Id), + fundingPot.roundIdToTotalContributions(round2Id), 300, "Total after C2 should be 300" ); @@ -4955,7 +4958,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); // Verify contributor 1's contribution was clamped to their R2 personal cap. assertEq( - fundingPot.getUserContributionToRound(round2Id, contributor1_), + fundingPot.roundIdToUserToContribution(round2Id, contributor1_), 300, "C1 contribution should be clamped by personal cap" ); @@ -4963,7 +4966,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // Verify total round contributions: 300 (C2) + 300 (C1) = 600 assertEq( - fundingPot.getTotalRoundContribution(round2Id), + fundingPot.roundIdToTotalContributions(round2Id), 600, "Total after C1 and C2 should be 600" ); @@ -4977,7 +4980,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); // Verify C3 contributed 300 assertEq( - fundingPot.getUserContributionToRound(round2Id, contributor3_), + fundingPot.roundIdToUserToContribution(round2Id, contributor3_), 300, "C3 contributes remaining 300" ); @@ -4985,7 +4988,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // Total contributions should now be 900 (300 + 300 + 300), matching the effective cap. assertEq( - fundingPot.getTotalRoundContribution(round2Id), + fundingPot.roundIdToTotalContributions(round2Id), 900, "Total should match effective cap after C3" ); @@ -5009,7 +5012,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // Final total check should remain 900 assertEq( - fundingPot.getTotalRoundContribution(round2Id), + fundingPot.roundIdToTotalContributions(round2Id), 900, "Final total should be effective cap" ); @@ -5102,8 +5105,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.stopPrank(); // Step 4: Validate that usedUnspentCaps is set to true - bool isUsed = - fundingPot.getUserUsedUnspendCaps(contributor1_, round1, 1); // expose via helper function if needed + bool isUsed = fundingPot.usedUnspentCaps(contributor1_, round1, 1); // expose via helper function if needed assertTrue(isUsed, "usedUnspentCaps should be true after contribution"); } @@ -5232,7 +5234,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // Step 5: Check that contribution in round3 is only based on round3 cap (not reused from round1) uint contributed = - fundingPot.getUserContributionToRound(round3, contributor1_); + fundingPot.roundIdToUserToContribution(round3, contributor1_); assertLe( contributed, 300, @@ -5240,8 +5242,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); // Confirm usedUnspentCaps[round1] is still true, not overwritten or reused - bool isStillUsed = - fundingPot.getUserUsedUnspendCaps(contributor1_, round1, 1); + bool isStillUsed = fundingPot.usedUnspentCaps(contributor1_, round1, 1); assertTrue( isStillUsed, "usedUnspentCaps should still be true from earlier use" ); @@ -5306,7 +5307,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.assume(user_ != address(0) && user_ != address(this)); testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); vm.startPrank(user_); bytes32 roleId = _authorizer.generateRoleId( @@ -5326,7 +5327,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) public { vm.assume(accessCriteriaEnum >= 0 && accessCriteriaEnum <= 4); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); _helper_createAccessCriteria(accessCriteriaEnum, roundId); @@ -5387,7 +5388,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN); _helper_setupRoundWithAccessCriteria(accessCriteriaId); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); fundingPot.setAccessCriteriaPrivileges(roundId, 0, 1000, false, 0, 0, 0); @@ -5404,7 +5405,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN); _helper_setupRoundWithAccessCriteria(accessCriteriaId); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); fundingPot.setAccessCriteriaPrivileges( roundId, accessCriteriaId, 1000, false, 0, 0, 0 ); @@ -5431,7 +5432,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { function testCloseRound_worksGivenRoundHasStartedButNotEnded() public { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); uint8 accessCriteriaId = 1; uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN); @@ -5470,12 +5471,12 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { fundingPot.closeRound(roundId); // Verify round is closed - assertEq(fundingPot.isRoundClosed(roundId), true); + assertEq(fundingPot.roundIdToClosedStatus(roundId), true); } function testCloseRound_worksGivenRoundHasEnded() public { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); uint8 accessCriteriaId = 1; uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN); @@ -5518,13 +5519,13 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { fundingPot.closeRound(roundId); // Verify round is closed - assertEq(fundingPot.isRoundClosed(roundId), true); + assertEq(fundingPot.roundIdToClosedStatus(roundId), true); } function testCloseRound_worksGivenRoundCapHasBeenReached() public { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); uint8 accessCriteriaId = 1; uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.NFT); @@ -5563,15 +5564,15 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0) ); - assertEq(fundingPot.isRoundClosed(roundId), false); + assertEq(fundingPot.roundIdToClosedStatus(roundId), false); fundingPot.closeRound(roundId); - assertEq(fundingPot.isRoundClosed(roundId), true); + assertEq(fundingPot.roundIdToClosedStatus(roundId), true); } function testCloseRound_worksGivenRoundisAutoClosure() public { testEditRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); uint8 accessCriteriaId = 1; uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN); @@ -5610,12 +5611,12 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0) ); - assertEq(fundingPot.isRoundClosed(roundId), true); + assertEq(fundingPot.roundIdToClosedStatus(roundId), true); } function testCloseRound_worksWithMultipleContributors() public { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); // Set up access criteria uint8 accessCriteriaId = 1; @@ -5670,7 +5671,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { fundingPot.closeRound(roundId); // Verify round is closed - assertEq(fundingPot.isRoundClosed(roundId), true); + assertEq(fundingPot.roundIdToClosedStatus(roundId), true); } //------------------------------------------------------------------------- @@ -5727,7 +5728,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { function testCreatePaymentOrdersForContributorsBatch_revertsGivenRoundIsNotClosed( ) public { testContributeToRoundFor_worksGivenGenericConfigAndAccessCriteria(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); vm.expectRevert( abi.encodeWithSelector( @@ -5742,7 +5743,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { function testCreatePaymentOrdersForContributorsBatch_revertsGivenBatchSizeIsZero( ) public { testCloseRound_worksWithMultipleContributors(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); vm.expectRevert( abi.encodeWithSelector( @@ -5757,7 +5758,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { function testCreatePaymentOrdersForContributorsBatch_revertsGivenUserDoesNotHaveFundingPotAdminRole( ) public { testCloseRound_worksWithMultipleContributors(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); vm.startPrank(contributor1_); bytes32 roleId = _authorizer.generateRoleId( @@ -5777,7 +5778,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { function testCreatePaymentOrdersForContributorsBatch_worksGivenBatchSizeIsGreaterThanContributorCount( ) public { testCloseRound_worksWithMultipleContributors(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); fundingPot.createPaymentOrdersForContributorsBatch(roundId, 999); assertEq(fundingPot.paymentOrders().length, 3); @@ -5786,7 +5787,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { function testCreatePaymentOrdersForContributorsBatch_worksGivenRoundIsAutoClosure( ) public { testCloseRound_worksGivenRoundisAutoClosure(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); fundingPot.createPaymentOrdersForContributorsBatch(roundId, 1); assertEq(fundingPot.paymentOrders().length, 1); @@ -5795,7 +5796,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { function testCreatePaymentOrdersForContributorsBatch_worksGivenRoundIsManualClosure( ) public { testCloseRound_worksWithMultipleContributors(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); fundingPot.createPaymentOrdersForContributorsBatch(roundId, 3); assertEq(fundingPot.paymentOrders().length, 3); @@ -5810,7 +5811,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { bool canOverrideContributionSpan_, uint unspentPersonalCap_ ) external { - vm.assume(roundId_ > 0 && roundId_ >= fundingPot.getRoundCount()); + vm.assume(roundId_ > 0 && roundId_ >= fundingPot.roundCount()); vm.assume(amount_ <= 1000); vm.assume(accessCriteriaId_ <= 4); vm.assume(unspentPersonalCap_ >= 0); @@ -5920,7 +5921,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.assume(accessCriteriaEnum > 4); testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); (uint roundStart,,,,,,) = fundingPot.getRoundGenericParameters(roundId); vm.warp(roundStart + 1); @@ -6136,7 +6137,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { function test_closeRound_worksGivenCapReached() public { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); uint8 accessCriteriaId = 1; uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN); @@ -6187,14 +6188,14 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { uint32(roundId), startIndex, batchSize ); - assertTrue(fundingPot.isRoundClosed(roundId)); + assertTrue(fundingPot.roundIdToClosedStatus(roundId)); } // ------------------------------------------------------------------------- // Test: _buyBondingCurveToken function test_buyBondingCurveToken_revertsGivenNoContributions() public { testCreateRound(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); (uint roundStart,,,,,,) = fundingPot.getRoundGenericParameters(roundId); vm.warp(roundStart + 1); From bdd6dfdeac5111d2b9ed5b54fdec61647aced326 Mon Sep 17 00:00:00 2001 From: Zuhaib Mohammed Date: Thu, 19 Jun 2025 19:06:55 +0530 Subject: [PATCH 10/14] fix: contract size fix 3 --- src/modules/logicModule/LM_PC_FundingPot_v1.sol | 12 ------------ .../logicModule/interfaces/ILM_PC_FundingPot_v1.sol | 9 +-------- .../modules/logicModule/LM_PC_FundingPot_v1.t.sol | 6 +++--- 3 files changed, 4 insertions(+), 23 deletions(-) diff --git a/src/modules/logicModule/LM_PC_FundingPot_v1.sol b/src/modules/logicModule/LM_PC_FundingPot_v1.sol index 432fe93c4..e7ed9537d 100644 --- a/src/modules/logicModule/LM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/LM_PC_FundingPot_v1.sol @@ -987,19 +987,7 @@ contract LM_PC_FundingPot_v1 is roundId_, accessCriteriaId_, merkleProof_, user_ ); - if (!isEligible) { - if (accessCriteria.accessCriteriaType == AccessCriteriaType.NFT) { - revert Module__LM_PC_FundingPot__AccessCriteriaNftFailed(); - } - if (accessCriteria.accessCriteriaType == AccessCriteriaType.MERKLE) - { - revert Module__LM_PC_FundingPot__AccessCriteriaMerkleFailed(); - } - if (accessCriteria.accessCriteriaType == AccessCriteriaType.LIST) { - revert Module__LM_PC_FundingPot__AccessCriteriaListFailed(); - } - } } /// @notice Validates and adjusts the contribution amount considering caps and unspent capacity. diff --git a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol index 81d0fac45..54d2431f1 100644 --- a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol @@ -246,14 +246,7 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { /// @notice Round has already ended. error Module__LM_PC_FundingPot__RoundHasEnded(); - /// @notice User does not meet the NFT access criteria. - error Module__LM_PC_FundingPot__AccessCriteriaNftFailed(); - - /// @notice User does not meet the merkle proof access criteria. - error Module__LM_PC_FundingPot__AccessCriteriaMerkleFailed(); - - /// @notice User is not on the allowlist. - error Module__LM_PC_FundingPot__AccessCriteriaListFailed(); + error Module__LM_PC_FundingPot__AccessCriteriaFailed(); /// @notice User has reached their personal contribution cap. error Module__LM_PC_FundingPot__PersonalCapReached(); diff --git a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol index 060b9e836..f9a7df885 100644 --- a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol +++ b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol @@ -1588,7 +1588,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__AccessCriteriaNftFailed + .Module__LM_PC_FundingPot__AccessCriteriaFailed .selector ) ); @@ -1641,7 +1641,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__AccessCriteriaMerkleFailed + .Module__LM_PC_FundingPot__AccessCriteriaFailed .selector ) ); @@ -1690,7 +1690,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__AccessCriteriaListFailed + .Module__LM_PC_FundingPot__AccessCriteriaFailed .selector ) ); From c3bda895e128ba56af9612b4c697bd80a4f8aa24 Mon Sep 17 00:00:00 2001 From: Zuhaib Mohammed Date: Thu, 19 Jun 2025 19:22:28 +0530 Subject: [PATCH 11/14] fix: contract size fix 3: if condition --- src/modules/logicModule/LM_PC_FundingPot_v1.sol | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/modules/logicModule/LM_PC_FundingPot_v1.sol b/src/modules/logicModule/LM_PC_FundingPot_v1.sol index e7ed9537d..f01b74df1 100644 --- a/src/modules/logicModule/LM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/LM_PC_FundingPot_v1.sol @@ -987,7 +987,17 @@ contract LM_PC_FundingPot_v1 is roundId_, accessCriteriaId_, merkleProof_, user_ ); - + if ( + !isEligible + && ( + accessCriteria.accessCriteriaType == AccessCriteriaType.NFT + || accessCriteria.accessCriteriaType + == AccessCriteriaType.MERKLE + || accessCriteria.accessCriteriaType == AccessCriteriaType.LIST + ) + ) { + revert Module__LM_PC_FundingPot__AccessCriteriaFailed(); + } } /// @notice Validates and adjusts the contribution amount considering caps and unspent capacity. From 084987adfbc2f925b7eb085c0881c559785b34b6 Mon Sep 17 00:00:00 2001 From: Leandro Faria Date: Thu, 19 Jun 2025 17:05:39 -0400 Subject: [PATCH 12/14] contract resizing --- .../logicModule/LM_PC_FundingPot_v1.sol | 186 +-- .../interfaces/ILM_PC_FundingPot_v1.sol | 66 +- test/e2e/logicModule/FundingPotE2E.t.sol | 12 +- .../logicModule/LM_PC_FundingPot_v1.t.sol | 1053 +++++++++-------- 4 files changed, 601 insertions(+), 716 deletions(-) diff --git a/src/modules/logicModule/LM_PC_FundingPot_v1.sol b/src/modules/logicModule/LM_PC_FundingPot_v1.sol index f01b74df1..ae0fc7507 100644 --- a/src/modules/logicModule/LM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/LM_PC_FundingPot_v1.sol @@ -288,57 +288,7 @@ contract LM_PC_FundingPot_v1 is ); } - /// @inheritdoc ILM_PC_FundingPot_v1 - function getUserEligibility( - uint32 roundId_, - uint8 accessCriteriaId_, - bytes32[] memory merkleProof_, - address user_ - ) - external - view - returns (bool isEligible, uint remainingAmountAllowedToContribute) - { - Round storage round = rounds[roundId_]; - - if (round.roundEnd == 0 && round.roundCap == 0) { - revert Module__LM_PC_FundingPot__RoundNotCreated(); - } - - AccessCriteria storage accessCriteria = - round.accessCriterias[accessCriteriaId_]; - - if (accessCriteria.accessCriteriaType == AccessCriteriaType.UNSET) { - return (false, 0); - } - isEligible = _checkAccessCriteriaEligibility( - roundId_, accessCriteriaId_, merkleProof_, user_ - ); - - if (isEligible) { - AccessCriteriaPrivileges storage privileges = - roundIdToAccessCriteriaIdToPrivileges[roundId_][accessCriteriaId_]; - uint userPersonalCap = privileges.personalCap; - uint userContribution = roundIdToUserToContribution[roundId_][user_]; - - uint personalCapRemaining = userPersonalCap > userContribution - ? userPersonalCap - userContribution - : 0; - - uint totalContributions = roundIdToTotalContributions[roundId_]; - uint roundCapRemaining = round.roundCap > totalContributions - ? round.roundCap - totalContributions - : 0; - - remainingAmountAllowedToContribute = personalCapRemaining - < roundCapRemaining ? personalCapRemaining : roundCapRemaining; - - return (true, remainingAmountAllowedToContribute); - } else { - return (false, 0); - } - } // ------------------------------------------------------------------------- // Public - Mutating @@ -539,7 +489,7 @@ contract LM_PC_FundingPot_v1 is } emit AllowlistedAddressesRemoved( - roundId_, accessCriteriaId_, addressesToRemove_ + ); } @@ -582,19 +532,6 @@ contract LM_PC_FundingPot_v1 is ); } - /// @inheritdoc ILM_PC_FundingPot_v1 - function contributeToRoundFor( - address user_, - uint32 roundId_, - uint amount_, - uint8 accessCriteriaId_, - bytes32[] memory merkleProof_ - ) external { - // Call the internal function with no additional unspent personal cap - _contributeToRoundFor( - user_, roundId_, amount_, accessCriteriaId_, merkleProof_, 0 - ); - } /// @inheritdoc ILM_PC_FundingPot_v1 function contributeToRoundFor( @@ -609,15 +546,7 @@ contract LM_PC_FundingPot_v1 is user_, roundId_, unspentPersonalRoundCaps_ ); - if (unspentPersonalCap > 0) { - // Mark the specific caps that were used in this contribution - for (uint i = 0; i < unspentPersonalRoundCaps_.length; i++) { - UnspentPersonalRoundCap memory roundCapInfo = - unspentPersonalRoundCaps_[i]; - usedUnspentCaps[user_][roundCapInfo.roundId][roundCapInfo - .accessCriteriaId] = true; - } - } + _contributeToRoundFor( user_, @@ -677,14 +606,7 @@ contract LM_PC_FundingPot_v1 is EnumerableSet.values(contributorsByRound[roundId_]); uint contributorCount = contributors.length; - // Check batch size is not zero - if (batchSize_ == 0) { - revert Module__LM_PC_FundingPot__InvalidBatchParameters(); - } - // If batch size is greater than contributor count, set batch size to contributor count - if (batchSize_ > contributorCount) { - batchSize_ = contributorCount; - } + // If autoClosure is false, only admin can process contributors if (!round.autoClosure) { @@ -717,9 +639,7 @@ contract LM_PC_FundingPot_v1 is revert Module__LM_PC_FundingPot__StartRoundCannotBeZero(); } if (startRoundId_ > roundCount) { - revert Module__LM_PC_FundingPot__StartRoundGreaterThanRoundCount( - startRoundId_, roundCount - ); + revert Module__LM_PC_FundingPot__StartRoundGreaterThanRoundCount(); } globalAccumulationStartRoundId = startRoundId_; @@ -739,87 +659,56 @@ contract LM_PC_FundingPot_v1 is address user_, uint32 roundId_, UnspentPersonalRoundCap[] calldata unspentPersonalRoundCaps_ - ) internal view returns (uint unspentPersonalCap) { - if (unspentPersonalRoundCaps_.length == 0) { - return 0; - } + ) internal returns (uint unspentPersonalCap) { + uint totalAggregatedPersonalCap = 0; uint totalSpentInPastRounds = 0; - uint32 firstRoundId = unspentPersonalRoundCaps_[0].roundId; - uint32 lastSeenRoundId = 0; - // Enforcement: All rounds from the start of the array to the end must be contiguous. + for (uint i = 0; i < unspentPersonalRoundCaps_.length; i++) { - UnspentPersonalRoundCap memory roundCapInfo = - unspentPersonalRoundCaps_[i]; + UnspentPersonalRoundCap memory roundCapInfo = unspentPersonalRoundCaps_[i]; uint32 currentProcessingRoundId = roundCapInfo.roundId; - // Check for strictly increasing first (order matters) - if (currentProcessingRoundId <= lastSeenRoundId) { - revert - Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotStrictlyIncreasing( - ); - } - lastSeenRoundId = currentProcessingRoundId; - - // Check for contiguity (consecutive sequence) - if (currentProcessingRoundId != firstRoundId + i) { - revert - Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotContiguous(); + // Skip if this round is before the global accumulation start round + if (currentProcessingRoundId < globalAccumulationStartRoundId) { + continue; } - // Enforcement: Round IDs must be strictly before the current roundId_ + // Skip if round is current or future round if (currentProcessingRoundId >= roundId_) { - revert - Module__LM_PC_FundingPot__UnspentCapsMustBeFromPreviousRounds(); + revert Module__LM_PC_FundingPot__UnspentCapsMustBeFromPreviousRounds(); } - // For PERSONAL cap rollover, the PREVIOUS round must have allowed it (Personal or All). - if ( - rounds[currentProcessingRoundId].accumulationMode - != AccumulationMode.Personal - && rounds[currentProcessingRoundId].accumulationMode - != AccumulationMode.All - ) { + // Skip if cap was already used + if (usedUnspentCaps[user_][currentProcessingRoundId][roundCapInfo.accessCriteriaId]) { continue; } - if ( - usedUnspentCaps[user_][currentProcessingRoundId][roundCapInfo - .accessCriteriaId] - ) { - continue; - } - - // Skip if this round is before the global accumulation start round - if (currentProcessingRoundId < globalAccumulationStartRoundId) { + // For PERSONAL cap rollover, the PREVIOUS round must have allowed it (Personal or All) + if (rounds[currentProcessingRoundId].accumulationMode != AccumulationMode.Personal + && rounds[currentProcessingRoundId].accumulationMode != AccumulationMode.All) { continue; } // Only count spent amounts from rounds that meet the accumulation criteria - totalSpentInPastRounds += - roundIdToUserToContribution[currentProcessingRoundId][user_]; + totalSpentInPastRounds += roundIdToUserToContribution[currentProcessingRoundId][user_]; // Check eligibility for the past round - if ( - _checkAccessCriteriaEligibility( - currentProcessingRoundId, - roundCapInfo.accessCriteriaId, - roundCapInfo.merkleProof, - user_ - ) - ) { - AccessCriteriaPrivileges storage privileges = - roundIdToAccessCriteriaIdToPrivileges[currentProcessingRoundId][roundCapInfo - .accessCriteriaId]; - + if (_checkAccessCriteriaEligibility( + currentProcessingRoundId, + roundCapInfo.accessCriteriaId, + roundCapInfo.merkleProof, + user_ + )) { + AccessCriteriaPrivileges storage privileges = roundIdToAccessCriteriaIdToPrivileges[currentProcessingRoundId][roundCapInfo.accessCriteriaId]; totalAggregatedPersonalCap += privileges.personalCap; } + // Mark the specific caps that were used in this contribution + usedUnspentCaps[user_][currentProcessingRoundId][roundCapInfo.accessCriteriaId] = true; } if (totalAggregatedPersonalCap > totalSpentInPastRounds) { - unspentPersonalCap = - totalAggregatedPersonalCap - totalSpentInPastRounds; + unspentPersonalCap = totalAggregatedPersonalCap - totalSpentInPastRounds; } return unspentPersonalCap; @@ -832,17 +721,17 @@ contract LM_PC_FundingPot_v1 is // Validate round start time is in the future // @note: The below condition wont allow _roundStart == block.timestamp if (round_.roundStart <= block.timestamp) { - revert Module__LM_PC_FundingPot__RoundStartMustBeInFuture(); + revert Module__LM_PC_FundingPot__RoundParamsInvalid(); } // Validate that either end time or cap is set if (round_.roundEnd == 0 && round_.roundCap == 0) { - revert Module__LM_PC_FundingPot__RoundMustHaveEndTimeOrCap(); + revert Module__LM_PC_FundingPot__RoundParamsInvalid(); } // If end time is set, validate it's after start time if (round_.roundEnd > 0 && round_.roundEnd < round_.roundStart) { - revert Module__LM_PC_FundingPot__RoundEndMustBeAfterStart(); + revert Module__LM_PC_FundingPot__RoundParamsInvalid(); } // Validate hook contract and function consistency @@ -867,7 +756,7 @@ contract LM_PC_FundingPot_v1 is } if (block.timestamp > round_.roundStart) { - revert Module__LM_PC_FundingPot__RoundAlreadyStarted(); + revert Module__LM_PC_FundingPot__RoundParamsInvalid(); } } @@ -904,13 +793,13 @@ contract LM_PC_FundingPot_v1 is } Round storage round = rounds[roundId_]; - uint currentTime = block.timestamp; + if (round.roundEnd == 0 && round.roundCap == 0) { revert Module__LM_PC_FundingPot__RoundNotCreated(); } - if (currentTime < round.roundStart) { + if (block.timestamp < round.roundStart) { revert Module__LM_PC_FundingPot__RoundHasNotStarted(); } @@ -927,7 +816,7 @@ contract LM_PC_FundingPot_v1 is bool canOverrideContributionSpan = privileges.overrideContributionSpan; if ( - round.roundEnd > 0 && currentTime > round.roundEnd + round.roundEnd > 0 && block.timestamp > round.roundEnd && !canOverrideContributionSpan ) { revert Module__LM_PC_FundingPot__RoundHasEnded(); @@ -1161,9 +1050,6 @@ contract LM_PC_FundingPot_v1 is view returns (bool) { - if (nftContract_ == address(0) || user_ == address(0)) { - return false; - } try IERC721(nftContract_).balanceOf(user_) returns (uint balance) { return balance > 0; diff --git a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol index 54d2431f1..a406adea4 100644 --- a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol @@ -188,12 +188,7 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { event RoundClosed(uint32 roundId_, uint totalContributions_); /// @notice Emitted when addresses are removed from an access criteria's allowed list. - /// @param roundId_ The ID of the round. - /// @param accessCriteriaId_ The ID of the access criteria. - /// @param addressesRemoved_ The addresses that were removed from the allowlist. - event AllowlistedAddressesRemoved( - uint32 roundId_, uint8 accessCriteriaId_, address[] addressesRemoved_ - ); + event AllowlistedAddressesRemoved(); /// @notice Emitted when a contributor batch is processed. /// @param roundId_ The ID of the round. @@ -216,14 +211,8 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { /// @notice Round start time must be in the future. error Module__LM_PC_FundingPot__RoundStartMustBeInFuture(); - /// @notice Round must have either an end time or a funding cap. - error Module__LM_PC_FundingPot__RoundMustHaveEndTimeOrCap(); - - /// @notice Round end time must be after round start time. - error Module__LM_PC_FundingPot__RoundEndMustBeAfterStart(); - - /// @notice Round has already started and cannot be modified. - error Module__LM_PC_FundingPot__RoundAlreadyStarted(); + /// @notice Round parameters are invalid. + error Module__LM_PC_FundingPot__RoundParamsInvalid(); /// @notice Error for invalid hook settings error Module__LM_PC_FundingPot__InvalidHookConfiguration(); @@ -269,27 +258,16 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { /// @notice Invalid start index. error Module__LM_PC_FundingPot__InvalidStartIndex(); - /// @notice Invalid batch parameters. - error Module__LM_PC_FundingPot__InvalidBatchParameters(); /// @notice Start round ID must be greater than zero. error Module__LM_PC_FundingPot__StartRoundCannotBeZero(); /// @notice Start round ID cannot be greater than the current round count. - /// @param startRoundId_ The provided start round ID. - /// @param currentRoundCount_ The current total number of rounds. - error Module__LM_PC_FundingPot__StartRoundGreaterThanRoundCount( - uint32 startRoundId_, uint32 currentRoundCount_ - ); - - /// @notice Thrown when round IDs in UnspentPersonalRoundCap array are not strictly increasing. - error Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotStrictlyIncreasing(); + error Module__LM_PC_FundingPot__StartRoundGreaterThanRoundCount(); /// @notice Unspent caps must be from previous rounds. error Module__LM_PC_FundingPot__UnspentCapsMustBeFromPreviousRounds(); - /// @notice The round IDs for unspent caps must be contiguous. - error Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotContiguous(); // ------------------------------------------------------------------------- // Public - Getters @@ -355,23 +333,6 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { uint end_ ); - /// @notice Gets eligibility information for a user in a specific round. - /// @param roundId_ The ID of the round to check eligibility for. - /// @param accessCriteriaId_ The ID of the access criteria to check eligibility for. - /// @param merkleProof_ The Merkle proof for validation if needed. - /// @param user_ The address of the user to check. - /// @return isEligible Whether the user is eligible for the round through any criteria. - /// @return remainingAmountAllowedToContribute The remaining contribution the user can make. - function getUserEligibility( - uint32 roundId_, - uint8 accessCriteriaId_, - bytes32[] memory merkleProof_, - address user_ - ) - external - view - returns (bool isEligible, uint remainingAmountAllowedToContribute); - // ------------------------------------------------------------------------- // Public - Mutating @@ -465,22 +426,7 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { uint end_ ) external; - /// @notice Allows a user to contribute to a specific funding round. - /// @dev Verifies the contribution eligibility based on the provided Merkle proof. - /// @param user_ The address of the user to contribute for. - /// @param roundId_ The unique identifier of the funding round. - /// @param amount_ The amount of tokens being contributed. - /// @param accessCriteriaId_ The identifier for the access criteria to validate eligibility. - /// @param merkleProof_ The Merkle proof used to verify the contributor's eligibility. - function contributeToRoundFor( - address user_, - uint32 roundId_, - uint amount_, - uint8 accessCriteriaId_, - bytes32[] calldata merkleProof_ - ) external; - - /// @notice Allows a user to contribute to a round with unused capacity from previous rounds. + /// @notice Contributes to a round on behalf of a user. /// @param user_ The address of the user to contribute for. /// @param roundId_ The ID of the round to contribute to. /// @param amount_ The amount to contribute. @@ -492,7 +438,7 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { uint32 roundId_, uint amount_, uint8 accessCriteriaId_, - bytes32[] calldata merkleProof_, + bytes32[] memory merkleProof_, UnspentPersonalRoundCap[] calldata unspentPersonalRoundCaps_ ) external; diff --git a/test/e2e/logicModule/FundingPotE2E.t.sol b/test/e2e/logicModule/FundingPotE2E.t.sol index 7f3d06e1a..08d2533d7 100644 --- a/test/e2e/logicModule/FundingPotE2E.t.sol +++ b/test/e2e/logicModule/FundingPotE2E.t.sol @@ -261,22 +261,28 @@ contract FundingPotE2E is E2ETest { vm.startPrank(contributor1); contributionToken.approve(address(fundingPot), contributor1Amount); + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + unspentPersonalRoundCaps = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); fundingPot.contributeToRoundFor( - contributor1, round1Id, contributor1Amount, 1, new bytes32[](0) + contributor1, round1Id, contributor1Amount, 1, new bytes32[](0), unspentPersonalRoundCaps ); vm.stopPrank(); vm.startPrank(contributor2); contributionToken.approve(address(fundingPot), contributor2Amount); + unspentPersonalRoundCaps = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); fundingPot.contributeToRoundFor( - contributor2, round1Id, contributor2Amount, 1, new bytes32[](0) + contributor2, round1Id, contributor2Amount, 1, new bytes32[](0), unspentPersonalRoundCaps ); vm.stopPrank(); vm.startPrank(contributor3); contributionToken.approve(address(fundingPot), contributor3Amount); + fundingPot.contributeToRoundFor( - contributor3, round2Id, contributor3Amount, 1, new bytes32[](0) + contributor3, round2Id, contributor3Amount, 1, new bytes32[](0), unspentPersonalRoundCaps ); vm.stopPrank(); diff --git a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol index f9a7df885..7cebd81dd 100644 --- a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol +++ b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol @@ -66,6 +66,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // Default round parameters for testing RoundParams private _defaultRoundParams; RoundParams private _editedRoundParams; + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] private _unspentPersonalRoundCaps; // Struct to hold round parameters struct RoundParams { @@ -228,7 +229,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__RoundStartMustBeInFuture + .Module__LM_PC_FundingPot__RoundParamsInvalid .selector ) ); @@ -253,7 +254,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__RoundMustHaveEndTimeOrCap + .Module__LM_PC_FundingPot__RoundParamsInvalid .selector ) ); @@ -278,7 +279,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__RoundEndMustBeAfterStart + .Module__LM_PC_FundingPot__RoundParamsInvalid .selector ) ); @@ -529,7 +530,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__RoundAlreadyStarted + .Module__LM_PC_FundingPot__RoundParamsInvalid .selector ) ); @@ -557,7 +558,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__RoundStartMustBeInFuture + .Module__LM_PC_FundingPot__RoundParamsInvalid .selector ) ); @@ -591,7 +592,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__RoundMustHaveEndTimeOrCap + .Module__LM_PC_FundingPot__RoundParamsInvalid .selector ) ); @@ -640,7 +641,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__RoundEndMustBeAfterStart + .Module__LM_PC_FundingPot__RoundParamsInvalid .selector ) ); @@ -907,7 +908,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__RoundAlreadyStarted + .Module__LM_PC_FundingPot__RoundParamsInvalid .selector ) ); @@ -1510,7 +1511,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.prank(contributor1_); fundingPot.contributeToRoundFor( - contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0) + contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0), _unspentPersonalRoundCaps ); } @@ -1561,7 +1562,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.prank(contributor1_); fundingPot.contributeToRoundFor( - contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0) + contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0), _unspentPersonalRoundCaps ); } @@ -1595,7 +1596,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.prank(contributor1_); fundingPot.contributeToRoundFor( - contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0) + contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0), _unspentPersonalRoundCaps ); } @@ -1648,7 +1649,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.prank(contributor1_); fundingPot.contributeToRoundFor( - contributor1_, roundId, amount, accessCriteriaId, proofB + contributor1_, roundId, amount, accessCriteriaId, proofB, _unspentPersonalRoundCaps ); } @@ -1697,7 +1698,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.prank(contributor1_); fundingPot.contributeToRoundFor( - contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0) + contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0), _unspentPersonalRoundCaps ); } @@ -1832,117 +1833,117 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.stopPrank(); } - function testContributeToRoundFor_revertsGivenUnspentCapsWithNonContiguousRoundIds( - ) public { - // Setup: Create 3 rounds - uint8 accessCriteriaId = 1; - uint personalCap = 300; - - // Round 1 - uint32 round1Id = fundingPot.createRound( - block.timestamp + 1 days, - block.timestamp + 2 days, - 1000, - address(0), - bytes(""), - false, - ILM_PC_FundingPot_v1.AccumulationMode.Personal - ); - _helper_setupAccessCriteriaForRound( - round1Id, - uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), - accessCriteriaId, - personalCap - ); - - // Round 2 - uint32 round2Id = fundingPot.createRound( - block.timestamp + 3 days, - block.timestamp + 4 days, - 1000, - address(0), - bytes(""), - false, - ILM_PC_FundingPot_v1.AccumulationMode.Personal - ); - _helper_setupAccessCriteriaForRound( - round2Id, - uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), - accessCriteriaId, - personalCap - ); - - // Round 3 - uint32 round3Id = fundingPot.createRound( - block.timestamp + 5 days, - block.timestamp + 6 days, - 1000, - address(0), - bytes(""), - false, - ILM_PC_FundingPot_v1.AccumulationMode.Personal - ); - _helper_setupAccessCriteriaForRound( - round3Id, - uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), - accessCriteriaId, - personalCap - ); - - // Round 4 (target) - uint32 round4Id = fundingPot.createRound( - block.timestamp + 7 days, - block.timestamp + 8 days, - 1000, - address(0), - bytes(""), - false, - ILM_PC_FundingPot_v1.AccumulationMode.Personal - ); - _helper_setupAccessCriteriaForRound( - round4Id, - uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), - accessCriteriaId, - personalCap - ); - - vm.warp(block.timestamp + 7 days + 1 hours); // Enter Round 4 - - // Create non-contiguous unspent caps array (skipping round 2) - ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory - nonContiguousUnspentCaps = - new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](2); - nonContiguousUnspentCaps[0] = ILM_PC_FundingPot_v1 - .UnspentPersonalRoundCap({ - roundId: round1Id, - accessCriteriaId: accessCriteriaId, - merkleProof: new bytes32[](0) - }); - nonContiguousUnspentCaps[1] = ILM_PC_FundingPot_v1 - .UnspentPersonalRoundCap({ - roundId: round3Id, // Skip round 2, making it non-contiguous - accessCriteriaId: accessCriteriaId, - merkleProof: new bytes32[](0) - }); - - vm.startPrank(contributor1_); - _token.approve(address(fundingPot), 500); - - vm.expectRevert( - ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotContiguous - .selector - ); - fundingPot.contributeToRoundFor( - contributor1_, - round4Id, - 200, - accessCriteriaId, - new bytes32[](0), - nonContiguousUnspentCaps - ); - vm.stopPrank(); - } + // function testContributeToRoundFor_revertsGivenUnspentCapsWithNonContiguousRoundIds( + // ) public { + // // Setup: Create 3 rounds + // uint8 accessCriteriaId = 1; + // uint personalCap = 300; + + // // Round 1 + // uint32 round1Id = fundingPot.createRound( + // block.timestamp + 1 days, + // block.timestamp + 2 days, + // 1000, + // address(0), + // bytes(""), + // false, + // ILM_PC_FundingPot_v1.AccumulationMode.Personal + // ); + // _helper_setupAccessCriteriaForRound( + // round1Id, + // uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), + // accessCriteriaId, + // personalCap + // ); + + // // Round 2 + // uint32 round2Id = fundingPot.createRound( + // block.timestamp + 3 days, + // block.timestamp + 4 days, + // 1000, + // address(0), + // bytes(""), + // false, + // ILM_PC_FundingPot_v1.AccumulationMode.Personal + // ); + // _helper_setupAccessCriteriaForRound( + // round2Id, + // uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), + // accessCriteriaId, + // personalCap + // ); + + // // Round 3 + // uint32 round3Id = fundingPot.createRound( + // block.timestamp + 5 days, + // block.timestamp + 6 days, + // 1000, + // address(0), + // bytes(""), + // false, + // ILM_PC_FundingPot_v1.AccumulationMode.Personal + // ); + // _helper_setupAccessCriteriaForRound( + // round3Id, + // uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), + // accessCriteriaId, + // personalCap + // ); + + // // Round 4 (target) + // uint32 round4Id = fundingPot.createRound( + // block.timestamp + 7 days, + // block.timestamp + 8 days, + // 1000, + // address(0), + // bytes(""), + // false, + // ILM_PC_FundingPot_v1.AccumulationMode.Personal + // ); + // _helper_setupAccessCriteriaForRound( + // round4Id, + // uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), + // accessCriteriaId, + // personalCap + // ); + + // vm.warp(block.timestamp + 7 days + 1 hours); // Enter Round 4 + + // // Create non-contiguous unspent caps array (skipping round 2) + // ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + // nonContiguousUnspentCaps = + // new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](2); + // nonContiguousUnspentCaps[0] = ILM_PC_FundingPot_v1 + // .UnspentPersonalRoundCap({ + // roundId: round1Id, + // accessCriteriaId: accessCriteriaId, + // merkleProof: new bytes32[](0) + // }); + // nonContiguousUnspentCaps[1] = ILM_PC_FundingPot_v1 + // .UnspentPersonalRoundCap({ + // roundId: round3Id, // Skip round 2, making it non-contiguous + // accessCriteriaId: accessCriteriaId, + // merkleProof: new bytes32[](0) + // }); + + // vm.startPrank(contributor1_); + // _token.approve(address(fundingPot), 500); + + // vm.expectRevert( + // ILM_PC_FundingPot_v1 + // .Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotContiguous + // .selector + // ); + // fundingPot.contributeToRoundFor( + // contributor1_, + // round4Id, + // 200, + // accessCriteriaId, + // new bytes32[](0), + // nonContiguousUnspentCaps + // ); + // vm.stopPrank(); + // } function testContributeToRoundFor_revertsGivenPreviousContributionExceedsPersonalCap( ) public { @@ -1983,7 +1984,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.prank(contributor1_); fundingPot.contributeToRoundFor( - contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0) + contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0), _unspentPersonalRoundCaps ); // Attempt to contribute beyond personal cap @@ -1997,7 +1998,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.prank(contributor1_); fundingPot.contributeToRoundFor( - contributor1_, roundId, 251, accessCriteriaId, new bytes32[](0) + contributor1_, roundId, 251, accessCriteriaId, new bytes32[](0), _unspentPersonalRoundCaps ); } @@ -2176,14 +2177,6 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); mockNFTContract.mint(contributor1_); - (bool isEligible, uint remainingAmountAllowedToContribute) = fundingPot - .getUserEligibility( - roundId, accessCriteriaId, new bytes32[](0), contributor1_ - ); - - assertTrue(isEligible); - assertEq(remainingAmountAllowedToContribute, 1000); - vm.warp(_defaultRoundParams.roundStart + 1); // Approve @@ -2197,7 +2190,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.prank(contributor1_); fundingPot.contributeToRoundFor( - contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0) + contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0), _unspentPersonalRoundCaps ); uint totalContributions = @@ -2239,7 +2232,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { emit ILM_PC_FundingPot_v1.ContributionMade(roundId, contributor1_, 250); fundingPot.contributeToRoundFor( - contributor1_, roundId, 250, accessCriteriaId, new bytes32[](0) + contributor1_, roundId, 250, accessCriteriaId, new bytes32[](0), _unspentPersonalRoundCaps ); vm.stopPrank(); @@ -2281,7 +2274,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { roundId, contributor2_, contributionAmount ); fundingPot.contributeToRoundFor( - contributor2_, roundId, contributionAmount, accessCriteriaId, proofB + contributor2_, roundId, contributionAmount, accessCriteriaId, proofB, _unspentPersonalRoundCaps ); vm.stopPrank(); @@ -2342,14 +2335,14 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.startPrank(contributor1_); _token.approve(address(fundingPot), 100); fundingPot.contributeToRoundFor( - contributor1_, roundId, 100, accessCriteriaId, new bytes32[](0) + contributor1_, roundId, 100, accessCriteriaId, new bytes32[](0), _unspentPersonalRoundCaps ); vm.stopPrank(); vm.startPrank(contributor2_); _token.approve(address(fundingPot), 100); fundingPot.contributeToRoundFor( - contributor2_, roundId, 100, accessCriteriaId, new bytes32[](0) + contributor2_, roundId, 100, accessCriteriaId, new bytes32[](0), _unspentPersonalRoundCaps ); vm.stopPrank(); @@ -2413,7 +2406,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { roundId, firstAmount, accessCriteriaId, - new bytes32[](0) + new bytes32[](0), + _unspentPersonalRoundCaps ); uint secondAmount = 200; @@ -2430,7 +2424,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { roundId, secondAmount, accessCriteriaId, - new bytes32[](0) + new bytes32[](0), + _unspentPersonalRoundCaps ); uint totalContribution = @@ -2489,7 +2484,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // This should succeed despite being after round end, due to override privilege vm.prank(contributor1_); fundingPot.contributeToRoundFor( - contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0) + contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0), _unspentPersonalRoundCaps ); // Verify the contribution was recorded @@ -2568,7 +2563,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.startPrank(contributor1_); _token.approve(address(fundingPot), 1000); fundingPot.contributeToRoundFor( - contributor1_, round1Id, 200, accessCriteriaId, new bytes32[](0) + contributor1_, round1Id, 200, accessCriteriaId, new bytes32[](0), _unspentPersonalRoundCaps ); // Warp to round 2 @@ -2676,14 +2671,14 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.startPrank(contributor1_); _token.approve(address(fundingPot), 300); fundingPot.contributeToRoundFor( - contributor1_, round1Id, 300, accessCriteriaId, new bytes32[](0) + contributor1_, round1Id, 300, accessCriteriaId, new bytes32[](0), _unspentPersonalRoundCaps ); vm.stopPrank(); vm.startPrank(contributor2_); _token.approve(address(fundingPot), 200); fundingPot.contributeToRoundFor( - contributor2_, round1Id, 200, accessCriteriaId, new bytes32[](0) + contributor2_, round1Id, 200, accessCriteriaId, new bytes32[](0), _unspentPersonalRoundCaps ); vm.stopPrank(); @@ -2693,14 +2688,14 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.startPrank(contributor2_); _token.approve(address(fundingPot), 400); fundingPot.contributeToRoundFor( - contributor2_, round2Id, 400, accessCriteriaId, new bytes32[](0) + contributor2_, round2Id, 400, accessCriteriaId, new bytes32[](0), _unspentPersonalRoundCaps ); vm.stopPrank(); vm.startPrank(contributor3_); _token.approve(address(fundingPot), 300); fundingPot.contributeToRoundFor( - contributor3_, round2Id, 300, accessCriteriaId, new bytes32[](0) + contributor3_, round2Id, 300, accessCriteriaId, new bytes32[](0), _unspentPersonalRoundCaps ); vm.stopPrank(); @@ -2818,12 +2813,12 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.warp(initialTimestamp + 1 days + 1 hours); // Enter Round 1 fundingPot.contributeToRoundFor( - contributor1_, round1Id, r1Contribution, 1, new bytes32[](0) + contributor1_, round1Id, r1Contribution, 1, new bytes32[](0), _unspentPersonalRoundCaps ); vm.warp(initialTimestamp + 3 days + 1 hours); // Enter Round 2 fundingPot.contributeToRoundFor( - contributor1_, round2Id, r2Contribution, 1, new bytes32[](0) + contributor1_, round2Id, r2Contribution, 1, new bytes32[](0), _unspentPersonalRoundCaps ); vm.stopPrank(); @@ -2963,12 +2958,12 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.warp(initialTimestamp + 1 days + 1 hours); // Enter Round 1 fundingPot.contributeToRoundFor( - contributor1_, round1Id, r1Contribution, 1, new bytes32[](0) + contributor1_, round1Id, r1Contribution, 1, new bytes32[](0), _unspentPersonalRoundCaps ); vm.warp(initialTimestamp + 3 days + 1 hours); // Enter Round 2 fundingPot.contributeToRoundFor( - contributor1_, round2Id, r2Contribution, 1, new bytes32[](0) + contributor1_, round2Id, r2Contribution, 1, new bytes32[](0), _unspentPersonalRoundCaps ); vm.stopPrank(); @@ -2983,8 +2978,9 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.startPrank(contributor1_); // Attempt to contribute up to the expected new effective cap + fundingPot.contributeToRoundFor( - contributor1_, round3Id, expectedR3EffectiveCap, 1, new bytes32[](0) + contributor1_, round3Id, expectedR3EffectiveCap, 1, new bytes32[](0), _unspentPersonalRoundCaps ); vm.stopPrank(); @@ -3009,20 +3005,21 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // 2. Action: Verify globalAccumulationStartRoundId() == 1 (default). // 3. Verification: For C1's contribution to R2, unused personal capacity from R1 rolls over. - uint initialTimestamp = block.timestamp; - uint8 accessId = 1; // Open access + // --- Round Parameters & Contributions for C1 --- uint r1PersonalCapC1 = 500; uint r1ContributionC1 = 100; // C1 leaves 400 personal unused from R1 uint r2BasePersonalCapC1 = 300; // C1's base personal cap in R2 + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + unspentPersonalRoundCaps = new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); // --- Approvals --- vm.startPrank(contributor1_); _token.approve(address(fundingPot), type(uint).max); vm.stopPrank(); - + uint initialTimestamp = block.timestamp; // --- Create Round 1 (Personal Mode) --- uint32 round1Id = fundingPot.createRound( initialTimestamp + 1 days, @@ -3035,7 +3032,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); fundingPot.setAccessCriteria( round1Id, - accessId, + 1, 0, address(0), bytes32(0), @@ -3043,7 +3040,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { removedAddresses ); fundingPot.setAccessCriteriaPrivileges( - round1Id, accessId, r1PersonalCapC1, false, 0, 0, 0 + round1Id, 1, r1PersonalCapC1, false, 0, 0, 0 ); // --- Create Round 2 (Personal Mode) --- @@ -3058,7 +3055,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); fundingPot.setAccessCriteria( round2Id, - accessId, + 1, 0, address(0), bytes32(0), @@ -3066,7 +3063,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { removedAddresses ); fundingPot.setAccessCriteriaPrivileges( - round2Id, accessId, r2BasePersonalCapC1, false, 0, 0, 0 + round2Id, 1, r2BasePersonalCapC1, false, 0, 0, 0 ); // --- Contribution by C1 to Round 1 --- @@ -3076,8 +3073,9 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { contributor1_, round1Id, r1ContributionC1, - accessId, - new bytes32[](0) + 1, + new bytes32[](0), + unspentPersonalRoundCaps ); vm.stopPrank(); @@ -3095,7 +3093,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](1); unspentCapsC1[0] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap( round1Id, - accessId, + 1, new bytes32[](0) // Should be counted ); @@ -3105,7 +3103,6 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { uint expectedC1EffectivePersonalCapR2 = r2BasePersonalCapC1 + r1UnusedPersonalC1; - uint c1AttemptR2 = expectedC1EffectivePersonalCapR2 + 50; // Try to contribute slightly more uint expectedC1ContributionR2 = expectedC1EffectivePersonalCapR2; // Should be clamped // Ensure the attempt is not clamped by the round cap (which is large) @@ -3118,8 +3115,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { fundingPot.contributeToRoundFor( contributor1_, round2Id, - c1AttemptR2, - accessId, + r2BasePersonalCapC1 + r1UnusedPersonalC1 + 50, + 1, new bytes32[](0), unspentCapsC1 ); @@ -3144,7 +3141,6 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Round 1 Parameters & Contribution --- uint r1BaseCap = 1000; uint r1ContributionC1 = 600; // Leaves 400 unused total from R1 - uint r1PersonalCap = 1000; // --- Round 2 Parameters --- uint r2BaseCap = 500; @@ -3174,21 +3170,23 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { removedAddresses ); fundingPot.setAccessCriteriaPrivileges( - round1Id, accessId, r1PersonalCap, false, 0, 0, 0 + round1Id, accessId, r1BaseCap, false, 0, 0, 0 ); // --- Contribution by C1 to Round 1 --- vm.warp(initialTimestamp + 1 days + 1 hours); vm.startPrank(contributor1_); + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + unspentPersonalRoundCaps = new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); fundingPot.contributeToRoundFor( contributor1_, round1Id, r1ContributionC1, accessId, - new bytes32[](0) + new bytes32[](0), + _unspentPersonalRoundCaps ); vm.stopPrank(); - uint r1UnusedTotal = r1BaseCap - r1ContributionC1; // Should be 400 // --- Create Round 2 (Total Mode) --- uint32 round2Id = fundingPot.createRound( @@ -3210,7 +3208,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { removedAddresses ); // Set personal cap for R2 to be at least the expected effective total cap - uint r2ExpectedEffectiveTotalCap = r2BaseCap + r1UnusedTotal; // 500 + 400 = 900 + uint r2ExpectedEffectiveTotalCap = r2BaseCap + r1BaseCap - r1ContributionC1; // 500 + 400 = 900 fundingPot.setAccessCriteriaPrivileges( round2Id, accessId, r2ExpectedEffectiveTotalCap, false, 0, 0, 0 ); @@ -3236,7 +3234,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.startPrank(contributor1_); fundingPot.contributeToRoundFor( - contributor1_, round2Id, c1AttemptR2, accessId, new bytes32[](0) + contributor1_, round2Id, c1AttemptR2, accessId, new bytes32[](0), _unspentPersonalRoundCaps ); vm.stopPrank(); @@ -3261,7 +3259,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { round2Id, remainingToFill, accessId, - new bytes32[](0) + new bytes32[](0), + unspentPersonalRoundCaps ); vm.stopPrank(); } @@ -3327,7 +3326,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { round1Id, r1ContributionC1, accessId, - new bytes32[](0) + new bytes32[](0), + _unspentPersonalRoundCaps ); vm.stopPrank(); @@ -3467,7 +3467,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { round1Id, 100, // Contributed 100 out of 300 cap accessCriteriaId, - new bytes32[](0) + new bytes32[](0), + _unspentPersonalRoundCaps ); vm.stopPrank(); @@ -3478,7 +3479,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { round2Id, 150, // Contributed 150 out of 300 cap accessCriteriaId, - new bytes32[](0) + new bytes32[](0), + _unspentPersonalRoundCaps ); vm.stopPrank(); @@ -3584,12 +3586,15 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Contribution by C1 to Round 1 --- vm.warp(initialTimestamp + 1 days + 1 hours); vm.startPrank(contributor1_); + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + unspentPersonalRoundCaps = new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); fundingPot.contributeToRoundFor( contributor1_, round1Id, r1ContributionC1, accessId, - new bytes32[](0) + new bytes32[](0), + unspentPersonalRoundCaps ); vm.stopPrank(); @@ -3716,7 +3721,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.warp(initialTimestamp + 1 days + 1 hours); vm.startPrank(contributor1_); fundingPot.contributeToRoundFor( - contributor1_, round1Id, r1ContributionC1, 1, new bytes32[](0) + contributor1_, round1Id, r1ContributionC1, 1, new bytes32[](0), _unspentPersonalRoundCaps ); vm.stopPrank(); assertEq( @@ -3751,7 +3756,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.warp(initialTimestamp + 3 days + 1 hours); vm.startPrank(contributor1_); fundingPot.contributeToRoundFor( - contributor1_, round2Id, r2ContributionC1, 1, new bytes32[](0) + contributor1_, round2Id, r2ContributionC1, 1, new bytes32[](0), _unspentPersonalRoundCaps ); vm.stopPrank(); assertEq( @@ -3902,8 +3907,10 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Contribution by C1 to Round 1 --- vm.warp(initialTimestamp + 1 days + 1 hours); vm.startPrank(contributor1_); + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + unspentPersonalRoundCaps = new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); fundingPot.contributeToRoundFor( - contributor1_, round1Id, r1ContributionC1, 1, new bytes32[](0) + contributor1_, round1Id, r1ContributionC1, 1, new bytes32[](0), unspentPersonalRoundCaps ); vm.stopPrank(); assertEq( @@ -3936,8 +3943,9 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Contribution by C2 to Round 2 --- vm.warp(initialTimestamp + 3 days + 1 hours); vm.startPrank(contributor2_); + fundingPot.contributeToRoundFor( - contributor2_, round2Id, r2ContributionC2, 1, new bytes32[](0) + contributor2_, round2Id, r2ContributionC2, 1, new bytes32[](0), unspentPersonalRoundCaps ); vm.stopPrank(); assertEq( @@ -3977,7 +3985,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.startPrank(contributor3_); fundingPot.contributeToRoundFor( - contributor3_, round3Id, r3ExpectedEffectiveCap, 1, new bytes32[](0) + contributor3_, round3Id, r3ExpectedEffectiveCap, 1, new bytes32[](0), unspentPersonalRoundCaps ); vm.stopPrank(); @@ -4003,7 +4011,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) ); fundingPot.contributeToRoundFor( - contributor1_, round3Id, 1, 1, new bytes32[](0) + contributor1_, round3Id, 1, 1, new bytes32[](0), unspentPersonalRoundCaps ); vm.stopPrank(); } @@ -4045,12 +4053,15 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.warp(initialTimestamp + 1 days + 1 hours); vm.startPrank(contributor1_); _token.approve(address(fundingPot), type(uint).max); + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + unspentPersonalRoundCaps = new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); fundingPot.contributeToRoundFor( contributor1_, round1Id, r1ContributionC1, accessId, - new bytes32[](0) + new bytes32[](0), + unspentPersonalRoundCaps ); vm.stopPrank(); uint r1UnusedPersonalForC1 = r1PersonalCapC1 - r1ContributionC1; @@ -4168,12 +4179,16 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // --- Contribution by C1 to Round 1 --- vm.warp(initialTimestamp + 1 days + 1 hours); vm.startPrank(contributor1_); + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + unspentPersonalRoundCaps = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); fundingPot.contributeToRoundFor( contributor1_, round1Id, r1C1Contribution, accessId, - new bytes32[](0) + new bytes32[](0), + unspentPersonalRoundCaps ); vm.stopPrank(); uint r1UnusedTotal = r1BaseTotalCap - r1C1Contribution; @@ -4216,7 +4231,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.startPrank(contributor1_); fundingPot.contributeToRoundFor( - contributor1_, round2Id, c1AttemptR2, accessId, new bytes32[](0) + contributor1_, round2Id, c1AttemptR2, accessId, new bytes32[](0), unspentPersonalRoundCaps ); vm.stopPrank(); @@ -4286,7 +4301,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { round1Id, r1ContributionC1, accessId, - new bytes32[](0) + new bytes32[](0), + _unspentPersonalRoundCaps ); vm.stopPrank(); @@ -4356,277 +4372,278 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); } - function testContributeToRoundFor_allModeWithGlobalStartRestrictsTotalAccumulation( - ) public { - // SCENARIO: globalAccumulationStartRoundId = 2 restricts total cap accumulation - // from R1 for R2, when both are in All mode. - - uint initialTimestamp = block.timestamp; - uint8 accessId = 1; // Open access - - // --- Round 1 Parameters (All Mode) --- - uint r1BaseTotalCap = 1000; - uint r1C1PersonalCap = 800; - uint r1C1Contribution = 400; - - // --- Round 2 Parameters (All Mode) --- - uint r2BaseTotalCap = 200; - - // --- Approvals --- - vm.startPrank(contributor1_); - _token.approve(address(fundingPot), type(uint).max); - vm.stopPrank(); - - // --- Create Round 1 (All Mode) --- - uint32 round1Id = fundingPot.createRound( - initialTimestamp + 1 days, - initialTimestamp + 2 days, - r1BaseTotalCap, - address(0), - bytes(""), - false, - ILM_PC_FundingPot_v1.AccumulationMode.All - ); - fundingPot.setAccessCriteria( - round1Id, - accessId, - 0, - address(0), - bytes32(0), - new address[](0), - removedAddresses - ); - fundingPot.setAccessCriteriaPrivileges( - round1Id, accessId, r1C1PersonalCap, false, 0, 0, 0 - ); - - // --- Contribution by C1 to Round 1 --- - vm.warp(initialTimestamp + 1 days + 1 hours); - vm.startPrank(contributor1_); - fundingPot.contributeToRoundFor( - contributor1_, - round1Id, - r1C1Contribution, - accessId, - new bytes32[](0) - ); - vm.stopPrank(); - - // --- Create Round 2 (All Mode) --- - uint32 round2Id = fundingPot.createRound( - initialTimestamp + 3 days, - initialTimestamp + 4 days, - r2BaseTotalCap, - address(0), - bytes(""), - false, - ILM_PC_FundingPot_v1.AccumulationMode.All - ); - fundingPot.setAccessCriteria( - round2Id, - accessId, - 0, - address(0), - bytes32(0), - new address[](0), - removedAddresses - ); - fundingPot.setAccessCriteriaPrivileges( - round2Id, accessId, r2BaseTotalCap, false, 0, 0, 0 - ); - - // --- Set Global Start Round ID to Round 2's ID --- - fundingPot.setGlobalAccumulationStart(round2Id); - assertEq( - fundingPot.globalAccumulationStartRoundId(), - round2Id, - "Global start ID not set to R2 ID" - ); - - // --- Attempt Contribution in Round 2 by C1 --- - vm.warp(initialTimestamp + 3 days + 1 hours); - - uint c1AttemptR2 = r2BaseTotalCap + 100; - - vm.startPrank(contributor1_); - fundingPot.contributeToRoundFor( - contributor1_, round2Id, c1AttemptR2, accessId, new bytes32[](0) - ); - vm.stopPrank(); - - // --- Assertions --- - assertEq( - fundingPot.roundIdToUserToContribution(round2Id, contributor1_), - r2BaseTotalCap, - "R2 C1 contribution should be clamped by R2 base total cap (All mode, global_start=R2)" - ); - assertEq( - fundingPot.roundIdToTotalContributions(round2Id), - r2BaseTotalCap, - "R2 Total contributions should be R2 base total cap (All mode, global_start=R2)" - ); - } - - function testContributeToRoundFor_revertsGivenUnspentCapsRoundIdsNotStrictlyIncreasing( - ) public { - // Setup: Round 1 (Personal), Round 2 (Personal), Round 3 (Personal for contribution) - uint initialTimestamp = block.timestamp; - uint8 accessId = 1; // Open access - uint personalCap = 500; - uint roundCap = 10_000; - - vm.startPrank(contributor1_); - _token.approve(address(fundingPot), type(uint).max); - vm.stopPrank(); - - // Round 1 - uint32 round1Id = fundingPot.createRound( - initialTimestamp + 1 days, - initialTimestamp + 2 days, - roundCap, - address(0), - bytes(""), - false, - ILM_PC_FundingPot_v1.AccumulationMode.Personal - ); - fundingPot.setAccessCriteria( - round1Id, - accessId, - 0, - address(0), - bytes32(0), - new address[](0), - removedAddresses - ); - fundingPot.setAccessCriteriaPrivileges( - round1Id, accessId, personalCap, false, 0, 0, 0 - ); - - // Round 2 - uint32 round2Id = fundingPot.createRound( - initialTimestamp + 3 days, - initialTimestamp + 4 days, - roundCap, - address(0), - bytes(""), - false, - ILM_PC_FundingPot_v1.AccumulationMode.Personal - ); - fundingPot.setAccessCriteria( - round2Id, - accessId, - 0, - address(0), - bytes32(0), - new address[](0), - removedAddresses - ); - fundingPot.setAccessCriteriaPrivileges( - round2Id, accessId, personalCap, false, 0, 0, 0 - ); - - // Round 3 (target for contribution) - uint32 round3Id = fundingPot.createRound( - initialTimestamp + 5 days, - initialTimestamp + 6 days, - roundCap, - address(0), - bytes(""), - false, - ILM_PC_FundingPot_v1.AccumulationMode.Personal - ); - fundingPot.setAccessCriteria( - round3Id, - accessId, - 0, - address(0), - bytes32(0), - new address[](0), - removedAddresses - ); - fundingPot.setAccessCriteriaPrivileges( - round3Id, accessId, personalCap, false, 0, 0, 0 - ); - - vm.warp(initialTimestamp + 5 days + 1 hours); // Enter Round 3 - - // Case 1: Out of order - ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory - unspentCapsOutOfOrder = - new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](2); - unspentCapsOutOfOrder[0] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap( - round2Id, accessId, new bytes32[](0) - ); - unspentCapsOutOfOrder[1] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap( - round1Id, accessId, new bytes32[](0) - ); - - vm.startPrank(contributor1_); - vm.expectRevert( - ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotStrictlyIncreasing - .selector - ); - fundingPot.contributeToRoundFor( - contributor1_, - round3Id, - 100, - accessId, - new bytes32[](0), - unspentCapsOutOfOrder - ); - vm.stopPrank(); - - // Case 2: Duplicate roundId - ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory - unspentCapsDuplicate = - new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](2); - unspentCapsDuplicate[0] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap( - round1Id, accessId, new bytes32[](0) - ); - unspentCapsDuplicate[1] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap( - round1Id, accessId, new bytes32[](0) - ); - - vm.startPrank(contributor1_); - vm.expectRevert( - ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotStrictlyIncreasing - .selector - ); - fundingPot.contributeToRoundFor( - contributor1_, - round3Id, - 100, - accessId, - new bytes32[](0), - unspentCapsDuplicate - ); - vm.stopPrank(); - - // Case 3: Correct order but first element's roundId is 0 (if lastSeenRoundId starts at 0) - // This specific case won't be hit if round IDs must be >0, but good to be aware. - // Assuming valid round IDs start from 1, this case might not be directly testable if 0 isn't a valid roundId. - // The current check `currentProcessingRoundId <= lastSeenRoundId` covers this if roundId can be 0. - // If round IDs are always >= 1, then an initial lastSeenRoundId=0 is fine. - - // Case 4: Empty array (should not revert with this specific error, but pass) - ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory unspentCapsEmpty = - new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); - vm.startPrank(contributor1_); - fundingPot.contributeToRoundFor( // This should pass (or revert with a different error if amount is 0 etc.) - contributor1_, - round3Id, - 100, - accessId, - new bytes32[](0), - unspentCapsEmpty - ); - vm.stopPrank(); - assertEq( - fundingPot.roundIdToUserToContribution(round3Id, contributor1_), 100 - ); - } + // function testContributeToRoundFor_allModeWithGlobalStartRestrictsTotalAccumulation( + // ) public { + // // SCENARIO: globalAccumulationStartRoundId = 2 restricts total cap accumulation + // // from R1 for R2, when both are in All mode. + + // uint initialTimestamp = block.timestamp; + // uint8 accessId = 1; // Open access + + // // --- Round 1 Parameters (All Mode) --- + // uint r1BaseTotalCap = 1000; + // uint r1C1PersonalCap = 800; + // uint r1C1Contribution = 400; + + // // --- Round 2 Parameters (All Mode) --- + // uint r2BaseTotalCap = 200; + + // // --- Approvals --- + // vm.startPrank(contributor1_); + // _token.approve(address(fundingPot), type(uint).max); + // vm.stopPrank(); + + // // --- Create Round 1 (All Mode) --- + // uint32 round1Id = fundingPot.createRound( + // initialTimestamp + 1 days, + // initialTimestamp + 2 days, + // r1BaseTotalCap, + // address(0), + // bytes(""), + // false, + // ILM_PC_FundingPot_v1.AccumulationMode.All + // ); + // fundingPot.setAccessCriteria( + // round1Id, + // accessId, + // 0, + // address(0), + // bytes32(0), + // new address[](0), + // removedAddresses + // ); + // fundingPot.setAccessCriteriaPrivileges( + // round1Id, accessId, r1C1PersonalCap, false, 0, 0, 0 + // ); + + // // --- Contribution by C1 to Round 1 --- + // vm.warp(initialTimestamp + 1 days + 1 hours); + // vm.startPrank(contributor1_); + // fundingPot.contributeToRoundFor( + // contributor1_, + // round1Id, + // r1C1Contribution, + // accessId, + // new bytes32[](0), + // _unspentPersonalRoundCaps + // ); + // vm.stopPrank(); + + // // --- Create Round 2 (All Mode) --- + // uint32 round2Id = fundingPot.createRound( + // initialTimestamp + 3 days, + // initialTimestamp + 4 days, + // r2BaseTotalCap, + // address(0), + // bytes(""), + // false, + // ILM_PC_FundingPot_v1.AccumulationMode.All + // ); + // fundingPot.setAccessCriteria( + // round2Id, + // accessId, + // 0, + // address(0), + // bytes32(0), + // new address[](0), + // removedAddresses + // ); + // fundingPot.setAccessCriteriaPrivileges( + // round2Id, accessId, r2BaseTotalCap, false, 0, 0, 0 + // ); + + // // --- Set Global Start Round ID to Round 2's ID --- + // fundingPot.setGlobalAccumulationStart(round2Id); + // assertEq( + // fundingPot.globalAccumulationStartRoundId(), + // round2Id, + // "Global start ID not set to R2 ID" + // ); + + // // --- Attempt Contribution in Round 2 by C1 --- + // vm.warp(initialTimestamp + 3 days + 1 hours); + + // uint c1AttemptR2 = r2BaseTotalCap + 100; + + // vm.startPrank(contributor1_); + // fundingPot.contributeToRoundFor( + // contributor1_, round2Id, c1AttemptR2, accessId, new bytes32[](0), _unspentPersonalRoundCaps + // ); + // vm.stopPrank(); + + // // --- Assertions --- + // assertEq( + // fundingPot.roundIdToUserToContribution(round2Id, contributor1_), + // r2BaseTotalCap, + // "R2 C1 contribution should be clamped by R2 base total cap (All mode, global_start=R2)" + // ); + // assertEq( + // fundingPot.roundIdToTotalContributions(round2Id), + // r2BaseTotalCap, + // "R2 Total contributions should be R2 base total cap (All mode, global_start=R2)" + // ); + // } + + // function testContributeToRoundFor_revertsGivenUnspentCapsRoundIdsNotStrictlyIncreasing( + // ) public { + // // Setup: Round 1 (Personal), Round 2 (Personal), Round 3 (Personal for contribution) + // uint initialTimestamp = block.timestamp; + // uint8 accessId = 1; // Open access + // uint personalCap = 500; + // uint roundCap = 10_000; + + // vm.startPrank(contributor1_); + // _token.approve(address(fundingPot), type(uint).max); + // vm.stopPrank(); + + // // Round 1 + // uint32 round1Id = fundingPot.createRound( + // initialTimestamp + 1 days, + // initialTimestamp + 2 days, + // roundCap, + // address(0), + // bytes(""), + // false, + // ILM_PC_FundingPot_v1.AccumulationMode.Personal + // ); + // fundingPot.setAccessCriteria( + // round1Id, + // accessId, + // 0, + // address(0), + // bytes32(0), + // new address[](0), + // removedAddresses + // ); + // fundingPot.setAccessCriteriaPrivileges( + // round1Id, accessId, personalCap, false, 0, 0, 0 + // ); + + // // Round 2 + // uint32 round2Id = fundingPot.createRound( + // initialTimestamp + 3 days, + // initialTimestamp + 4 days, + // roundCap, + // address(0), + // bytes(""), + // false, + // ILM_PC_FundingPot_v1.AccumulationMode.Personal + // ); + // fundingPot.setAccessCriteria( + // round2Id, + // accessId, + // 0, + // address(0), + // bytes32(0), + // new address[](0), + // removedAddresses + // ); + // fundingPot.setAccessCriteriaPrivileges( + // round2Id, accessId, personalCap, false, 0, 0, 0 + // ); + + // // Round 3 (target for contribution) + // uint32 round3Id = fundingPot.createRound( + // initialTimestamp + 5 days, + // initialTimestamp + 6 days, + // roundCap, + // address(0), + // bytes(""), + // false, + // ILM_PC_FundingPot_v1.AccumulationMode.Personal + // ); + // fundingPot.setAccessCriteria( + // round3Id, + // accessId, + // 0, + // address(0), + // bytes32(0), + // new address[](0), + // removedAddresses + // ); + // fundingPot.setAccessCriteriaPrivileges( + // round3Id, accessId, personalCap, false, 0, 0, 0 + // ); + + // vm.warp(initialTimestamp + 5 days + 1 hours); // Enter Round 3 + + // // Case 1: Out of order + // ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + // unspentCapsOutOfOrder = + // new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](2); + // unspentCapsOutOfOrder[0] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap( + // round2Id, accessId, new bytes32[](0) + // ); + // unspentCapsOutOfOrder[1] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap( + // round1Id, accessId, new bytes32[](0) + // ); + + // vm.startPrank(contributor1_); + // vm.expectRevert( + // ILM_PC_FundingPot_v1 + // .Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotStrictlyIncreasing + // .selector + // ); + // fundingPot.contributeToRoundFor( + // contributor1_, + // round3Id, + // 100, + // accessId, + // new bytes32[](0), + // unspentCapsOutOfOrder + // ); + // vm.stopPrank(); + + // // Case 2: Duplicate roundId + // ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + // unspentCapsDuplicate = + // new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](2); + // unspentCapsDuplicate[0] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap( + // round1Id, accessId, new bytes32[](0) + // ); + // unspentCapsDuplicate[1] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap( + // round1Id, accessId, new bytes32[](0) + // ); + + // vm.startPrank(contributor1_); + // vm.expectRevert( + // ILM_PC_FundingPot_v1 + // .Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotStrictlyIncreasing + // .selector + // ); + // fundingPot.contributeToRoundFor( + // contributor1_, + // round3Id, + // 100, + // accessId, + // new bytes32[](0), + // unspentCapsDuplicate + // ); + // vm.stopPrank(); + + // // Case 3: Correct order but first element's roundId is 0 (if lastSeenRoundId starts at 0) + // // This specific case won't be hit if round IDs must be >0, but good to be aware. + // // Assuming valid round IDs start from 1, this case might not be directly testable if 0 isn't a valid roundId. + // // The current check `currentProcessingRoundId <= lastSeenRoundId` covers this if roundId can be 0. + // // If round IDs are always >= 1, then an initial lastSeenRoundId=0 is fine. + + // // Case 4: Empty array (should not revert with this specific error, but pass) + // ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory unspentCapsEmpty = + // new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); + // vm.startPrank(contributor1_); + // fundingPot.contributeToRoundFor( // This should pass (or revert with a different error if amount is 0 etc.) + // contributor1_, + // round3Id, + // 100, + // accessId, + // new bytes32[](0), + // unspentCapsEmpty + // ); + // vm.stopPrank(); + // assertEq( + // fundingPot.roundIdToUserToContribution(round3Id, contributor1_), 100 + // ); + // } function testContributeToRoundFor_personalModeOnlyAccumulatesPersonalCaps() public @@ -4715,8 +4732,11 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.startPrank(contributor1_); _token.approve(address(fundingPot), 1000); + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + unspentPersonalRoundCaps = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); fundingPot.contributeToRoundFor( - contributor1_, round1Id, 200, accessCriteriaId, new bytes32[](0) + contributor1_, round1Id, 200, accessCriteriaId, new bytes32[](0), unspentPersonalRoundCaps ); vm.stopPrank(); @@ -4768,7 +4788,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // Since contributor1 contributed 450 and round cap is 500, only 50 is remaining. // The contribution should be clamped to 50. fundingPot.contributeToRoundFor( - contributor2_, round2Id, 100, accessCriteriaId, new bytes32[](0) + contributor2_, round2Id, 100, accessCriteriaId, new bytes32[](0), + _unspentPersonalRoundCaps ); // Verify contributor 2's contribution was clamped to the remaining 50. assertEq( @@ -4793,7 +4814,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ) ); fundingPot.contributeToRoundFor( - contributor3_, round2Id, 1, accessCriteriaId, new bytes32[](0) + contributor3_, round2Id, 1, accessCriteriaId, new bytes32[](0), unspentPersonalRoundCaps ); vm.stopPrank(); @@ -4887,8 +4908,11 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.warp(_defaultRoundParams.roundStart + 1); vm.startPrank(contributor1_); _token.approve(address(fundingPot), 1000); + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + unspentPersonalRoundCaps = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); fundingPot.contributeToRoundFor( - contributor1_, round1Id, 600, accessCriteriaId, new bytes32[](0) + contributor1_, round1Id, 600, accessCriteriaId, new bytes32[](0), unspentPersonalRoundCaps ); vm.stopPrank(); @@ -4909,7 +4933,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // Contributor 2 attempts to contribute 700. // Personal Cap (R2) is 300. Gets clamped to 300. fundingPot.contributeToRoundFor( - contributor2_, round2Id, 700, accessCriteriaId, new bytes32[](0) + contributor2_, round2Id, 700, accessCriteriaId, new bytes32[](0), unspentPersonalRoundCaps ); // Verify contributor 2's contribution was clamped by personal cap. assertEq( @@ -4976,7 +5000,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.startPrank(contributor3_); _token.approve(address(fundingPot), 300); fundingPot.contributeToRoundFor( - contributor3_, round2Id, 300, accessCriteriaId, new bytes32[](0) + contributor3_, round2Id, 300, accessCriteriaId, new bytes32[](0), unspentPersonalRoundCaps ); // Verify C3 contributed 300 assertEq( @@ -5004,9 +5028,9 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { .Module__LM_PC_FundingPot__RoundCapReached .selector ) - ); + ); fundingPot.contributeToRoundFor( - contributor3_, round2Id, 1, accessCriteriaId, new bytes32[](0) + contributor3_, round2Id, 1, accessCriteriaId, new bytes32[](0), unspentPersonalRoundCaps ); vm.stopPrank(); @@ -5056,8 +5080,11 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // Contribute in round 1 vm.startPrank(contributor1_); _token.approve(address(fundingPot), 100); + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + unspentPersonalRoundCaps = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); fundingPot.contributeToRoundFor( - contributor1_, round1, 100, 1, new bytes32[](0) + contributor1_, round1, 100, 1, new bytes32[](0), unspentPersonalRoundCaps ); vm.stopPrank(); @@ -5145,8 +5172,11 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // Step 1b: Contribute in round 1 vm.startPrank(contributor1_); _token.approve(address(fundingPot), 100); + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + unspentPersonalRoundCaps = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); fundingPot.contributeToRoundFor( - contributor1_, round1, 100, 1, new bytes32[](0) + contributor1_, round1, 100, 1, new bytes32[](0), unspentPersonalRoundCaps ); vm.stopPrank(); @@ -5416,8 +5446,11 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.startPrank(contributor1_); _token.approve(address(fundingPot), 1000); + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + unspentPersonalRoundCaps = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); fundingPot.contributeToRoundFor( - contributor1_, roundId, 1000, accessCriteriaId, new bytes32[](0) + contributor1_, roundId, 1000, accessCriteriaId, new bytes32[](0), unspentPersonalRoundCaps ); vm.stopPrank(); @@ -5462,8 +5495,11 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { // Make a contribution vm.startPrank(contributor1_); _token.approve(address(fundingPot), 1000); + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + unspentPersonalRoundCaps = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); fundingPot.contributeToRoundFor( - contributor1_, roundId, 1000, accessCriteriaId, new bytes32[](0) + contributor1_, roundId, 1000, accessCriteriaId, new bytes32[](0), unspentPersonalRoundCaps ); vm.stopPrank(); @@ -5507,8 +5543,11 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.startPrank(contributor1_); _token.approve(address(fundingPot), 500); + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + unspentPersonalRoundCaps = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); fundingPot.contributeToRoundFor( - contributor1_, roundId, 500, accessCriteriaId, new bytes32[](0) + contributor1_, roundId, 500, accessCriteriaId, new bytes32[](0), unspentPersonalRoundCaps ); vm.stopPrank(); @@ -5560,8 +5599,11 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { _token.approve(address(fundingPot), 1000); vm.prank(contributor1_); + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + unspentPersonalRoundCaps = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); fundingPot.contributeToRoundFor( - contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0) + contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0), unspentPersonalRoundCaps ); assertEq(fundingPot.roundIdToClosedStatus(roundId), false); @@ -5607,8 +5649,11 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { _token.approve(address(fundingPot), 2000); vm.prank(contributor1_); + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + unspentPersonalRoundCaps = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); fundingPot.contributeToRoundFor( - contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0) + contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0), unspentPersonalRoundCaps ); assertEq(fundingPot.roundIdToClosedStatus(roundId), true); @@ -5641,6 +5686,10 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { roundId, accessCriteriaId, 1000, false, 0, 0, 0 ); + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + unspentPersonalRoundCaps = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); + // Warp to round start (uint roundStart,,,,,,) = fundingPot.getRoundGenericParameters(roundId); vm.warp(roundStart + 1); @@ -5649,21 +5698,22 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.startPrank(contributor1_); _token.approve(address(fundingPot), 500); fundingPot.contributeToRoundFor( - contributor1_, roundId, 500, accessCriteriaId, new bytes32[](0) + contributor1_, roundId, 500, accessCriteriaId, new bytes32[](0), unspentPersonalRoundCaps ); vm.stopPrank(); vm.startPrank(contributor2_); _token.approve(address(fundingPot), 200); fundingPot.contributeToRoundFor( - contributor2_, roundId, 200, accessCriteriaId, new bytes32[](0) + contributor2_, roundId, 200, accessCriteriaId, new bytes32[](0), unspentPersonalRoundCaps ); vm.stopPrank(); vm.startPrank(contributor3_); _token.approve(address(fundingPot), 300); + fundingPot.contributeToRoundFor( - contributor3_, roundId, 300, accessCriteriaId, new bytes32[](0) + contributor3_, roundId, 300, accessCriteriaId, new bytes32[](0), unspentPersonalRoundCaps ); vm.stopPrank(); @@ -5740,20 +5790,6 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { fundingPot.createPaymentOrdersForContributorsBatch(roundId, 1); } - function testCreatePaymentOrdersForContributorsBatch_revertsGivenBatchSizeIsZero( - ) public { - testCloseRound_worksWithMultipleContributors(); - uint32 roundId = fundingPot.roundCount(); - - vm.expectRevert( - abi.encodeWithSelector( - ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__InvalidBatchParameters - .selector - ) - ); - fundingPot.createPaymentOrdersForContributorsBatch(roundId, 0); - } function testCreatePaymentOrdersForContributorsBatch_revertsGivenUserDoesNotHaveFundingPotAdminRole( ) public { @@ -5853,21 +5889,21 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { } } - function testFuzz_ValidTimes(uint start, uint cliff, uint end) public { - vm.assume(cliff <= type(uint).max - start); + // function testFuzz_ValidTimes(uint start, uint cliff, uint end) public { + // vm.assume(cliff <= type(uint).max - start); - bool isValid = fundingPot.exposed_validTimes(start, cliff, end); + // bool isValid = fundingPot.exposed_validTimes(start, cliff, end); - assertEq(isValid, start + cliff <= end); + // assertEq(isValid, start + cliff <= end); - if (start > end) { - assertFalse(isValid); - } + // if (start > end) { + // assertFalse(isValid); + // } - if (start == end) { - assertEq(isValid, cliff == 0); - } - } + // if (start == end) { + // assertEq(isValid, cliff == 0); + // } + // } // ------------------------------------------------------------------------- // Test: _calculateUnusedCapacityFromPreviousRounds @@ -5989,12 +6025,16 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.warp(params.roundStart + 1); vm.startPrank(contributor1_); _token.approve(address(fundingPot), params.roundCap); + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + unspentPersonalRoundCaps = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); fundingPot.contributeToRoundFor( contributor1_, roundId, params.roundCap, accessCriteriaId, - new bytes32[](0) + new bytes32[](0), + unspentPersonalRoundCaps ); vm.stopPrank(); @@ -6094,12 +6134,16 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.warp(params.roundStart + 1); vm.startPrank(contributor1_); _token.approve(address(fundingPot), params.roundCap); + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + unspentPersonalRoundCaps = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); fundingPot.contributeToRoundFor( contributor1_, roundId, params.roundCap, accessCriteriaId, - new bytes32[](0) + new bytes32[](0), + unspentPersonalRoundCaps ); vm.stopPrank(); @@ -6172,8 +6216,11 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { _token.approve(address(fundingPot), 1000); vm.prank(contributor1_); + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory + unspentPersonalRoundCaps = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); fundingPot.contributeToRoundFor( - contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0) + contributor1_, roundId, amount, accessCriteriaId, new bytes32[](0), unspentPersonalRoundCaps ); assertTrue( From 4ac313f2746e8e123366dc505db630f09efe0d0b Mon Sep 17 00:00:00 2001 From: Leandro Faria Date: Thu, 19 Jun 2025 18:03:23 -0400 Subject: [PATCH 13/14] chore:remove uncommented tests --- .../logicModule/LM_PC_FundingPot_v1.t.sol | 619 +++--------------- 1 file changed, 78 insertions(+), 541 deletions(-) diff --git a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol index 7cebd81dd..537e0edb0 100644 --- a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol +++ b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol @@ -1833,117 +1833,6 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.stopPrank(); } - // function testContributeToRoundFor_revertsGivenUnspentCapsWithNonContiguousRoundIds( - // ) public { - // // Setup: Create 3 rounds - // uint8 accessCriteriaId = 1; - // uint personalCap = 300; - - // // Round 1 - // uint32 round1Id = fundingPot.createRound( - // block.timestamp + 1 days, - // block.timestamp + 2 days, - // 1000, - // address(0), - // bytes(""), - // false, - // ILM_PC_FundingPot_v1.AccumulationMode.Personal - // ); - // _helper_setupAccessCriteriaForRound( - // round1Id, - // uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), - // accessCriteriaId, - // personalCap - // ); - - // // Round 2 - // uint32 round2Id = fundingPot.createRound( - // block.timestamp + 3 days, - // block.timestamp + 4 days, - // 1000, - // address(0), - // bytes(""), - // false, - // ILM_PC_FundingPot_v1.AccumulationMode.Personal - // ); - // _helper_setupAccessCriteriaForRound( - // round2Id, - // uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), - // accessCriteriaId, - // personalCap - // ); - - // // Round 3 - // uint32 round3Id = fundingPot.createRound( - // block.timestamp + 5 days, - // block.timestamp + 6 days, - // 1000, - // address(0), - // bytes(""), - // false, - // ILM_PC_FundingPot_v1.AccumulationMode.Personal - // ); - // _helper_setupAccessCriteriaForRound( - // round3Id, - // uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), - // accessCriteriaId, - // personalCap - // ); - - // // Round 4 (target) - // uint32 round4Id = fundingPot.createRound( - // block.timestamp + 7 days, - // block.timestamp + 8 days, - // 1000, - // address(0), - // bytes(""), - // false, - // ILM_PC_FundingPot_v1.AccumulationMode.Personal - // ); - // _helper_setupAccessCriteriaForRound( - // round4Id, - // uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), - // accessCriteriaId, - // personalCap - // ); - - // vm.warp(block.timestamp + 7 days + 1 hours); // Enter Round 4 - - // // Create non-contiguous unspent caps array (skipping round 2) - // ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory - // nonContiguousUnspentCaps = - // new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](2); - // nonContiguousUnspentCaps[0] = ILM_PC_FundingPot_v1 - // .UnspentPersonalRoundCap({ - // roundId: round1Id, - // accessCriteriaId: accessCriteriaId, - // merkleProof: new bytes32[](0) - // }); - // nonContiguousUnspentCaps[1] = ILM_PC_FundingPot_v1 - // .UnspentPersonalRoundCap({ - // roundId: round3Id, // Skip round 2, making it non-contiguous - // accessCriteriaId: accessCriteriaId, - // merkleProof: new bytes32[](0) - // }); - - // vm.startPrank(contributor1_); - // _token.approve(address(fundingPot), 500); - - // vm.expectRevert( - // ILM_PC_FundingPot_v1 - // .Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotContiguous - // .selector - // ); - // fundingPot.contributeToRoundFor( - // contributor1_, - // round4Id, - // 200, - // accessCriteriaId, - // new bytes32[](0), - // nonContiguousUnspentCaps - // ); - // vm.stopPrank(); - // } function testContributeToRoundFor_revertsGivenPreviousContributionExceedsPersonalCap( ) public { @@ -4372,455 +4261,119 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); } - // function testContributeToRoundFor_allModeWithGlobalStartRestrictsTotalAccumulation( - // ) public { - // // SCENARIO: globalAccumulationStartRoundId = 2 restricts total cap accumulation - // // from R1 for R2, when both are in All mode. - - // uint initialTimestamp = block.timestamp; - // uint8 accessId = 1; // Open access - - // // --- Round 1 Parameters (All Mode) --- - // uint r1BaseTotalCap = 1000; - // uint r1C1PersonalCap = 800; - // uint r1C1Contribution = 400; - - // // --- Round 2 Parameters (All Mode) --- - // uint r2BaseTotalCap = 200; - - // // --- Approvals --- - // vm.startPrank(contributor1_); - // _token.approve(address(fundingPot), type(uint).max); - // vm.stopPrank(); - - // // --- Create Round 1 (All Mode) --- - // uint32 round1Id = fundingPot.createRound( - // initialTimestamp + 1 days, - // initialTimestamp + 2 days, - // r1BaseTotalCap, - // address(0), - // bytes(""), - // false, - // ILM_PC_FundingPot_v1.AccumulationMode.All - // ); - // fundingPot.setAccessCriteria( - // round1Id, - // accessId, - // 0, - // address(0), - // bytes32(0), - // new address[](0), - // removedAddresses - // ); - // fundingPot.setAccessCriteriaPrivileges( - // round1Id, accessId, r1C1PersonalCap, false, 0, 0, 0 - // ); - - // // --- Contribution by C1 to Round 1 --- - // vm.warp(initialTimestamp + 1 days + 1 hours); - // vm.startPrank(contributor1_); - // fundingPot.contributeToRoundFor( - // contributor1_, - // round1Id, - // r1C1Contribution, - // accessId, - // new bytes32[](0), - // _unspentPersonalRoundCaps - // ); - // vm.stopPrank(); - - // // --- Create Round 2 (All Mode) --- - // uint32 round2Id = fundingPot.createRound( - // initialTimestamp + 3 days, - // initialTimestamp + 4 days, - // r2BaseTotalCap, - // address(0), - // bytes(""), - // false, - // ILM_PC_FundingPot_v1.AccumulationMode.All - // ); - // fundingPot.setAccessCriteria( - // round2Id, - // accessId, - // 0, - // address(0), - // bytes32(0), - // new address[](0), - // removedAddresses - // ); - // fundingPot.setAccessCriteriaPrivileges( - // round2Id, accessId, r2BaseTotalCap, false, 0, 0, 0 - // ); - - // // --- Set Global Start Round ID to Round 2's ID --- - // fundingPot.setGlobalAccumulationStart(round2Id); - // assertEq( - // fundingPot.globalAccumulationStartRoundId(), - // round2Id, - // "Global start ID not set to R2 ID" - // ); - - // // --- Attempt Contribution in Round 2 by C1 --- - // vm.warp(initialTimestamp + 3 days + 1 hours); - - // uint c1AttemptR2 = r2BaseTotalCap + 100; - - // vm.startPrank(contributor1_); - // fundingPot.contributeToRoundFor( - // contributor1_, round2Id, c1AttemptR2, accessId, new bytes32[](0), _unspentPersonalRoundCaps - // ); - // vm.stopPrank(); - - // // --- Assertions --- - // assertEq( - // fundingPot.roundIdToUserToContribution(round2Id, contributor1_), - // r2BaseTotalCap, - // "R2 C1 contribution should be clamped by R2 base total cap (All mode, global_start=R2)" - // ); - // assertEq( - // fundingPot.roundIdToTotalContributions(round2Id), - // r2BaseTotalCap, - // "R2 Total contributions should be R2 base total cap (All mode, global_start=R2)" - // ); - // } - - // function testContributeToRoundFor_revertsGivenUnspentCapsRoundIdsNotStrictlyIncreasing( - // ) public { - // // Setup: Round 1 (Personal), Round 2 (Personal), Round 3 (Personal for contribution) - // uint initialTimestamp = block.timestamp; - // uint8 accessId = 1; // Open access - // uint personalCap = 500; - // uint roundCap = 10_000; - - // vm.startPrank(contributor1_); - // _token.approve(address(fundingPot), type(uint).max); - // vm.stopPrank(); - - // // Round 1 - // uint32 round1Id = fundingPot.createRound( - // initialTimestamp + 1 days, - // initialTimestamp + 2 days, - // roundCap, - // address(0), - // bytes(""), - // false, - // ILM_PC_FundingPot_v1.AccumulationMode.Personal - // ); - // fundingPot.setAccessCriteria( - // round1Id, - // accessId, - // 0, - // address(0), - // bytes32(0), - // new address[](0), - // removedAddresses - // ); - // fundingPot.setAccessCriteriaPrivileges( - // round1Id, accessId, personalCap, false, 0, 0, 0 - // ); - - // // Round 2 - // uint32 round2Id = fundingPot.createRound( - // initialTimestamp + 3 days, - // initialTimestamp + 4 days, - // roundCap, - // address(0), - // bytes(""), - // false, - // ILM_PC_FundingPot_v1.AccumulationMode.Personal - // ); - // fundingPot.setAccessCriteria( - // round2Id, - // accessId, - // 0, - // address(0), - // bytes32(0), - // new address[](0), - // removedAddresses - // ); - // fundingPot.setAccessCriteriaPrivileges( - // round2Id, accessId, personalCap, false, 0, 0, 0 - // ); - - // // Round 3 (target for contribution) - // uint32 round3Id = fundingPot.createRound( - // initialTimestamp + 5 days, - // initialTimestamp + 6 days, - // roundCap, - // address(0), - // bytes(""), - // false, - // ILM_PC_FundingPot_v1.AccumulationMode.Personal - // ); - // fundingPot.setAccessCriteria( - // round3Id, - // accessId, - // 0, - // address(0), - // bytes32(0), - // new address[](0), - // removedAddresses - // ); - // fundingPot.setAccessCriteriaPrivileges( - // round3Id, accessId, personalCap, false, 0, 0, 0 - // ); - - // vm.warp(initialTimestamp + 5 days + 1 hours); // Enter Round 3 - - // // Case 1: Out of order - // ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory - // unspentCapsOutOfOrder = - // new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](2); - // unspentCapsOutOfOrder[0] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap( - // round2Id, accessId, new bytes32[](0) - // ); - // unspentCapsOutOfOrder[1] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap( - // round1Id, accessId, new bytes32[](0) - // ); - - // vm.startPrank(contributor1_); - // vm.expectRevert( - // ILM_PC_FundingPot_v1 - // .Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotStrictlyIncreasing - // .selector - // ); - // fundingPot.contributeToRoundFor( - // contributor1_, - // round3Id, - // 100, - // accessId, - // new bytes32[](0), - // unspentCapsOutOfOrder - // ); - // vm.stopPrank(); - - // // Case 2: Duplicate roundId - // ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory - // unspentCapsDuplicate = - // new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](2); - // unspentCapsDuplicate[0] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap( - // round1Id, accessId, new bytes32[](0) - // ); - // unspentCapsDuplicate[1] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap( - // round1Id, accessId, new bytes32[](0) - // ); - - // vm.startPrank(contributor1_); - // vm.expectRevert( - // ILM_PC_FundingPot_v1 - // .Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotStrictlyIncreasing - // .selector - // ); - // fundingPot.contributeToRoundFor( - // contributor1_, - // round3Id, - // 100, - // accessId, - // new bytes32[](0), - // unspentCapsDuplicate - // ); - // vm.stopPrank(); - - // // Case 3: Correct order but first element's roundId is 0 (if lastSeenRoundId starts at 0) - // // This specific case won't be hit if round IDs must be >0, but good to be aware. - // // Assuming valid round IDs start from 1, this case might not be directly testable if 0 isn't a valid roundId. - // // The current check `currentProcessingRoundId <= lastSeenRoundId` covers this if roundId can be 0. - // // If round IDs are always >= 1, then an initial lastSeenRoundId=0 is fine. - - // // Case 4: Empty array (should not revert with this specific error, but pass) - // ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory unspentCapsEmpty = - // new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); - // vm.startPrank(contributor1_); - // fundingPot.contributeToRoundFor( // This should pass (or revert with a different error if amount is 0 etc.) - // contributor1_, - // round3Id, - // 100, - // accessId, - // new bytes32[](0), - // unspentCapsEmpty - // ); - // vm.stopPrank(); - // assertEq( - // fundingPot.roundIdToUserToContribution(round3Id, contributor1_), 100 - // ); - // } - - function testContributeToRoundFor_personalModeOnlyAccumulatesPersonalCaps() - public - { - // 1. Create the first round with AccumulationMode.Personal - _defaultRoundParams.accumulationMode = - ILM_PC_FundingPot_v1.AccumulationMode.Personal; + function testContributeToRoundFor_allModeWithGlobalStartRestrictsTotalAccumulation( + ) public { + // SCENARIO: globalAccumulationStartRoundId = 2 restricts total cap accumulation + // from R1 for R2, when both are in All mode. - fundingPot.createRound( - _defaultRoundParams.roundStart, - _defaultRoundParams.roundEnd, - 1000, // Round cap of 1000 - _defaultRoundParams.hookContract, - _defaultRoundParams.hookFunction, - _defaultRoundParams.autoClosure, - _defaultRoundParams.accumulationMode - ); - uint32 round1Id = fundingPot.roundCount(); + uint initialTimestamp = block.timestamp; + uint8 accessId = 1; // Open access - // Set up access criteria for round 1 - uint8 accessCriteriaId = 1; // Open access - ( - address nftContract, - bytes32 merkleRoot, - address[] memory allowedAddresses - ) = _helper_createAccessCriteria( - uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), round1Id - ); + // --- Round 1 Parameters (All Mode) --- + uint r1BaseTotalCap = 1000; + uint r1C1PersonalCap = 800; + uint r1C1Contribution = 400; + + // --- Round 2 Parameters (All Mode) --- + uint r2BaseTotalCap = 200; + + // --- Approvals --- + vm.startPrank(contributor1_); + _token.approve(address(fundingPot), type(uint).max); + vm.stopPrank(); + // --- Create Round 1 (All Mode) --- + uint32 round1Id = fundingPot.createRound( + initialTimestamp + 1 days, + initialTimestamp + 2 days, + r1BaseTotalCap, + address(0), + bytes(""), + false, + ILM_PC_FundingPot_v1.AccumulationMode.All + ); fundingPot.setAccessCriteria( round1Id, - uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), // accessCriteriaType - 0, // accessCriteriaId (0 for new) - nftContract, - merkleRoot, - allowedAddresses, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), removedAddresses ); - - // Set a personal cap of 500 for round 1 fundingPot.setAccessCriteriaPrivileges( - round1Id, accessCriteriaId, 500, false, 0, 0, 0 + round1Id, accessId, r1C1PersonalCap, false, 0, 0, 0 ); - // 2. Create the second round, also with AccumulationMode.Personal - // Use different start and end times to avoid overlap - RoundParams memory params = _helper_createEditRoundParams( - _defaultRoundParams.roundStart + 3 days, - _defaultRoundParams.roundEnd + 3 days, - 500, // Round cap of 500 - _defaultRoundParams.hookContract, - _defaultRoundParams.hookFunction, - _defaultRoundParams.autoClosure, - ILM_PC_FundingPot_v1.AccumulationMode.Personal + // --- Contribution by C1 to Round 1 --- + vm.warp(initialTimestamp + 1 days + 1 hours); + vm.startPrank(contributor1_); + fundingPot.contributeToRoundFor( + contributor1_, + round1Id, + r1C1Contribution, + accessId, + new bytes32[](0), + _unspentPersonalRoundCaps ); + vm.stopPrank(); - fundingPot.createRound( - params.roundStart, - params.roundEnd, - params.roundCap, - params.hookContract, - params.hookFunction, - params.autoClosure, - params.accumulationMode + // --- Create Round 2 (All Mode) --- + uint32 round2Id = fundingPot.createRound( + initialTimestamp + 3 days, + initialTimestamp + 4 days, + r2BaseTotalCap, + address(0), + bytes(""), + false, + ILM_PC_FundingPot_v1.AccumulationMode.All ); - uint32 round2Id = fundingPot.roundCount(); - - // Set up access criteria for round 2 fundingPot.setAccessCriteria( round2Id, - uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN), // accessCriteriaType - 0, // accessCriteriaId (0 for new) - nftContract, - merkleRoot, - allowedAddresses, + accessId, + 0, + address(0), + bytes32(0), + new address[](0), removedAddresses ); - - // Set a personal cap of 400 for round 2 fundingPot.setAccessCriteriaPrivileges( - round2Id, accessCriteriaId, 400, false, 0, 0, 0 - ); - - // First round contribution: user contributes 200 out of their 500 personal cap - vm.warp(_defaultRoundParams.roundStart + 1); - - vm.startPrank(contributor1_); - _token.approve(address(fundingPot), 1000); - ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory - unspentPersonalRoundCaps = - new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](0); - fundingPot.contributeToRoundFor( - contributor1_, round1Id, 200, accessCriteriaId, new bytes32[](0), unspentPersonalRoundCaps + round2Id, accessId, r2BaseTotalCap, false, 0, 0, 0 ); - vm.stopPrank(); - // Verify contribution to round 1 + // --- Set Global Start Round ID to Round 2's ID --- + fundingPot.setGlobalAccumulationStart(round2Id); assertEq( - fundingPot.roundIdToUserToContribution(round1Id, contributor1_), 200 + fundingPot.globalAccumulationStartRoundId(), + round2Id, + "Global start ID not set to R2 ID" ); - // Move to round 2 - vm.warp(_defaultRoundParams.roundStart + 3 days + 1); + // --- Attempt Contribution in Round 2 by C1 --- + vm.warp(initialTimestamp + 3 days + 1 hours); - // ------------ PART 1: VERIFY PERSONAL CAP ACCUMULATION ------------ - // Create unspent capacity structure - ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory unspentCaps = - new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](1); - unspentCaps[0] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap({ - roundId: round1Id, - accessCriteriaId: accessCriteriaId, - merkleProof: new bytes32[](0) - }); + uint c1AttemptR2 = r2BaseTotalCap + 100; - // Try to contribute more than the round 2 personal cap (400) - // In Personal mode, this should succeed up to the personal cap (400) + unspent from round 1 (300) = 700 - // But capped by round cap of 500 vm.startPrank(contributor1_); fundingPot.contributeToRoundFor( - contributor1_, - round2Id, - 450, // More than the personal cap of round 2 - accessCriteriaId, - new bytes32[](0), - unspentCaps + contributor1_, round2Id, c1AttemptR2, accessId, new bytes32[](0), _unspentPersonalRoundCaps ); vm.stopPrank(); - // Verify contributions to round 2 - should be more than the personal cap of round 2 (400) - // This verifies personal caps DO accumulate - uint contributionAmount = - fundingPot.roundIdToUserToContribution(round2Id, contributor1_); - assertEq(contributionAmount, 450); - assertTrue(contributionAmount > 400, "Personal cap should accumulate"); - - // ------------ PART 2: VERIFY TOTAL CAP NON-ACCUMULATION ------------ - // Attempt to contribute more than the remaining round cap - vm.startPrank(contributor2_); - _token.approve(address(fundingPot), 200); - - // Contributor 2 attempts to contribute 100. - // Since contributor1 contributed 450 and round cap is 500, only 50 is remaining. - // The contribution should be clamped to 50. - fundingPot.contributeToRoundFor( - contributor2_, round2Id, 100, accessCriteriaId, new bytes32[](0), - _unspentPersonalRoundCaps + // --- Assertions --- + assertEq( + fundingPot.roundIdToUserToContribution(round2Id, contributor1_), + r2BaseTotalCap, + "R2 C1 contribution should be clamped by R2 base total cap (All mode, global_start=R2)" ); - // Verify contributor 2's contribution was clamped to the remaining 50. assertEq( - fundingPot.roundIdToUserToContribution(round2Id, contributor2_), 50 + fundingPot.roundIdToTotalContributions(round2Id), + r2BaseTotalCap, + "R2 Total contributions should be R2 base total cap (All mode, global_start=R2)" ); - vm.stopPrank(); - - // Verify total contributions to round 2 is exactly the round cap (450 + 50 = 500). - assertEq(fundingPot.roundIdToTotalContributions(round2Id), 500); - - // Additional contributor3 should not be able to contribute anything as the cap is full. - // Attempting to contribute when the cap is already full should revert. - vm.startPrank(contributor3_); - _token.approve(address(fundingPot), 100); + } - // Expect revert because the round cap (500) is already met. - vm.expectRevert( - abi.encodeWithSelector( - ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__RoundCapReached - .selector - ) - ); - fundingPot.contributeToRoundFor( - contributor3_, round2Id, 1, accessCriteriaId, new bytes32[](0), unspentPersonalRoundCaps - ); - vm.stopPrank(); - // Final check that total contributions remain at the round cap. - assertEq(fundingPot.roundIdToTotalContributions(round2Id), 500); - } function testContributeToRoundFor_totalModeOnlyAccumulatesTotalCaps() public @@ -5889,22 +5442,6 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { } } - // function testFuzz_ValidTimes(uint start, uint cliff, uint end) public { - // vm.assume(cliff <= type(uint).max - start); - - // bool isValid = fundingPot.exposed_validTimes(start, cliff, end); - - // assertEq(isValid, start + cliff <= end); - - // if (start > end) { - // assertFalse(isValid); - // } - - // if (start == end) { - // assertEq(isValid, cliff == 0); - // } - // } - // ------------------------------------------------------------------------- // Test: _calculateUnusedCapacityFromPreviousRounds From e31b65c81931c03b326e6d42dc9fa2cd2f6e5e68 Mon Sep 17 00:00:00 2001 From: Leandro Faria Date: Tue, 26 Aug 2025 11:08:06 -0400 Subject: [PATCH 14/14] chore:remove hook validation --- .../logicModule/LM_PC_FundingPot_v1.sol | 40 +-- .../interfaces/ILM_PC_FundingPot_v1.sol | 32 +- .../logicModule/LM_PC_FundingPot_v1.t.sol | 332 +++++++++--------- 3 files changed, 194 insertions(+), 210 deletions(-) diff --git a/src/modules/logicModule/LM_PC_FundingPot_v1.sol b/src/modules/logicModule/LM_PC_FundingPot_v1.sol index ae0fc7507..b987a6e28 100644 --- a/src/modules/logicModule/LM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/LM_PC_FundingPot_v1.sol @@ -162,7 +162,7 @@ contract LM_PC_FundingPot_v1 is usedUnspentCaps; /// @notice Storage gap for future upgrades. - uint[50] private __gap; + uint[47] private __gap; // ------------------------------------------------------------------------- // Modifiers @@ -436,7 +436,6 @@ contract LM_PC_FundingPot_v1 is round.accessCriterias[criteriaId].nftContract = address(0); round.accessCriterias[criteriaId].merkleRoot = bytes32(0); // @note: When changing allowlists, call removeAllowlistedAddresses first to clear previous entries - // Set the access criteria type round.accessCriterias[criteriaId].accessCriteriaType = accessCriteriaType; @@ -508,7 +507,7 @@ contract LM_PC_FundingPot_v1 is _validateEditRoundParameters(round); if (!_validTimes(start_, cliff_, end_)) { - revert Module__LM_PC_FundingPot__InvalidTimes(); + revert Module__LM_PC_FundingPot__InvalidInput(); } AccessCriteriaPrivileges storage accessCriteriaPrivileges = @@ -542,11 +541,15 @@ contract LM_PC_FundingPot_v1 is bytes32[] memory merkleProof_, UnspentPersonalRoundCap[] calldata unspentPersonalRoundCaps_ ) external { + // If using unspent caps, only the owner can use them + if (unspentPersonalRoundCaps_.length > 0 && _msgSender() != user_) { + revert Module__LM_PC_FundingPot__OnlyOwnerCanUseUnspentCaps(); + } + uint unspentPersonalCap = _calculateUnspentPersonalCap( user_, roundId_, unspentPersonalRoundCaps_ ); - _contributeToRoundFor( user_, @@ -721,30 +724,19 @@ contract LM_PC_FundingPot_v1 is // Validate round start time is in the future // @note: The below condition wont allow _roundStart == block.timestamp if (round_.roundStart <= block.timestamp) { - revert Module__LM_PC_FundingPot__RoundParamsInvalid(); + revert Module__LM_PC_FundingPot__InvalidInput(); } // Validate that either end time or cap is set if (round_.roundEnd == 0 && round_.roundCap == 0) { - revert Module__LM_PC_FundingPot__RoundParamsInvalid(); + revert Module__LM_PC_FundingPot__InvalidInput(); } // If end time is set, validate it's after start time if (round_.roundEnd > 0 && round_.roundEnd < round_.roundStart) { - revert Module__LM_PC_FundingPot__RoundParamsInvalid(); - } - - // Validate hook contract and function consistency - if ( - round_.hookContract != address(0) && round_.hookFunction.length == 0 - ) { - revert Module__LM_PC_FundingPot__InvalidHookConfiguration(); + revert Module__LM_PC_FundingPot__InvalidInput(); } - if (round_.hookContract == address(0) && round_.hookFunction.length > 0) - { - revert Module__LM_PC_FundingPot__InvalidHookConfiguration(); - } } /// @notice Validates the round parameters before editing. @@ -756,7 +748,7 @@ contract LM_PC_FundingPot_v1 is } if (block.timestamp > round_.roundStart) { - revert Module__LM_PC_FundingPot__RoundParamsInvalid(); + revert Module__LM_PC_FundingPot__InvalidInput(); } } @@ -789,7 +781,7 @@ contract LM_PC_FundingPot_v1 is uint unspentPersonalCap_ ) internal { if (amount_ == 0) { - revert Module__LM_PC_FundingPot__InvalidDepositAmount(); + revert Module__LM_PC_FundingPot__InvalidInput(); } Round storage round = rounds[roundId_]; @@ -1083,12 +1075,10 @@ contract LM_PC_FundingPot_v1 is Round storage round = rounds[roundId_]; roundIdToClosedStatus[roundId_] = true; - + // @note: we don't check if the hook contract is valid here, because we don't want to revert the round closure + // if the hook contract is invalid. if (round.hookContract != address(0)) { (bool success,) = round.hookContract.call(round.hookFunction); - if (!success) { - revert Module__LM_PC_FundingPot__HookExecutionFailed(); - } } emit RoundClosed(roundId_, roundIdToTotalContributions[roundId_]); @@ -1114,7 +1104,7 @@ contract LM_PC_FundingPot_v1 is uint contributorCount = contributors.length; if (startIndex_ >= contributorCount) { - revert Module__LM_PC_FundingPot__InvalidStartIndex(); + revert Module__LM_PC_FundingPot__InvalidInput(); } // Calculate the end index (don't exceed array bounds) diff --git a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol index a406adea4..dae5bd750 100644 --- a/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol +++ b/src/modules/logicModule/interfaces/ILM_PC_FundingPot_v1.sol @@ -205,17 +205,8 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { // ------------------------------------------------------------------------- // Errors - /// @notice Amount can not be zero. - error Module__LM_PC_FundingPot__InvalidDepositAmount(); - - /// @notice Round start time must be in the future. - error Module__LM_PC_FundingPot__RoundStartMustBeInFuture(); - - /// @notice Round parameters are invalid. - error Module__LM_PC_FundingPot__RoundParamsInvalid(); - - /// @notice Error for invalid hook settings - error Module__LM_PC_FundingPot__InvalidHookConfiguration(); + /// @notice Invalid input validation. + error Module__LM_PC_FundingPot__InvalidInput(); /// @notice Round does not exist. error Module__LM_PC_FundingPot__RoundNotCreated(); @@ -226,18 +217,16 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { /// @notice Invalid access criteria type. error Module__LM_PC_FundingPot__InvalidAccessCriteriaType(); - /// @notice Invalid times. - error Module__LM_PC_FundingPot__InvalidTimes(); - /// @notice Round has not started yet. error Module__LM_PC_FundingPot__RoundHasNotStarted(); /// @notice Round has already ended. error Module__LM_PC_FundingPot__RoundHasEnded(); + /// @notice Access criteria failed. error Module__LM_PC_FundingPot__AccessCriteriaFailed(); - /// @notice User has reached their personal contribution cap. + /// @notice User has reached their personal contribution cap. error Module__LM_PC_FundingPot__PersonalCapReached(); /// @notice Round contribution cap has been reached. @@ -246,19 +235,12 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { /// @notice Round Closure conditions are not met. error Module__LM_PC_FundingPot__ClosureConditionsNotMet(); - /// @notice Hook execution failed. - error Module__LM_PC_FundingPot__HookExecutionFailed(); - /// @notice No contributions were made to the round. error Module__LM_PC_FundingPot__NoContributions(); /// @notice Round is not closed. error Module__LM_PC_FundingPot__RoundNotClosed(); - /// @notice Invalid start index. - error Module__LM_PC_FundingPot__InvalidStartIndex(); - - /// @notice Start round ID must be greater than zero. error Module__LM_PC_FundingPot__StartRoundCannotBeZero(); @@ -268,6 +250,12 @@ interface ILM_PC_FundingPot_v1 is IERC20PaymentClientBase_v2 { /// @notice Unspent caps must be from previous rounds. error Module__LM_PC_FundingPot__UnspentCapsMustBeFromPreviousRounds(); + /// @notice Thrown when someone tries to use another user's unspent caps + error Module__LM_PC_FundingPot__OnlyOwnerCanUseUnspentCaps(); + + /// @notice Hook execution failed. + error Module__LM_PC_FundingPot__HookExecutionFailed(); + // ------------------------------------------------------------------------- // Public - Getters diff --git a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol index 537e0edb0..247fa865d 100644 --- a/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol +++ b/test/unit/modules/logicModule/LM_PC_FundingPot_v1.t.sol @@ -229,7 +229,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__RoundParamsInvalid + .Module__LM_PC_FundingPot__InvalidInput .selector ) ); @@ -254,7 +254,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__RoundParamsInvalid + .Module__LM_PC_FundingPot__InvalidInput .selector ) ); @@ -279,7 +279,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__RoundParamsInvalid + .Module__LM_PC_FundingPot__InvalidInput .selector ) ); @@ -294,52 +294,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); } - function testCreateRound_revertsGivenHookContractIsSetButHookFunctionIsEmpty( - ) public { - RoundParams memory params = _defaultRoundParams; - params.hookContract = address(1); - params.hookFunction = bytes(""); - vm.expectRevert( - abi.encodeWithSelector( - ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__InvalidHookConfiguration - .selector - ) - ); - fundingPot.createRound( - params.roundStart, - params.roundEnd, - params.roundCap, - params.hookContract, - params.hookFunction, - params.autoClosure, - params.accumulationMode - ); - } - function testCreateRound_revertsGivenHookFunctionIsSetButHookContractIsEmpty( - ) public { - RoundParams memory params = _defaultRoundParams; - - params.hookContract = address(0); - params.hookFunction = bytes("test"); - vm.expectRevert( - abi.encodeWithSelector( - ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__InvalidHookConfiguration - .selector - ) - ); - fundingPot.createRound( - params.roundStart, - params.roundEnd, - params.roundCap, - params.hookContract, - params.hookFunction, - params.autoClosure, - params.accumulationMode - ); - } /* Test Fuzz createRound() ├── Given all the valid parameters are provided @@ -530,7 +485,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__RoundParamsInvalid + .Module__LM_PC_FundingPot__InvalidInput .selector ) ); @@ -558,7 +513,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__RoundParamsInvalid + .Module__LM_PC_FundingPot__InvalidInput .selector ) ); @@ -592,7 +547,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__RoundParamsInvalid + .Module__LM_PC_FundingPot__InvalidInput .selector ) ); @@ -641,7 +596,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__RoundParamsInvalid + .Module__LM_PC_FundingPot__InvalidInput .selector ) ); @@ -658,77 +613,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); } - function testEditRound_revertsGivenHookContractIsSetButHookFunctionIsEmpty() - public - { - testCreateRound(); - uint32 roundId = fundingPot.roundCount(); - RoundParams memory params = _helper_createEditRoundParams( - block.timestamp + 3 days, - block.timestamp + 4 days, - 2000, - address(1), - bytes(""), - true, - ILM_PC_FundingPot_v1.AccumulationMode.All - ); - - vm.expectRevert( - abi.encodeWithSelector( - ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__InvalidHookConfiguration - .selector - ) - ); - - fundingPot.editRound( - roundId, - params.roundStart, - params.roundEnd, - params.roundCap, - params.hookContract, - params.hookFunction, - params.autoClosure, - params.accumulationMode - ); - } - - function testEditRound_revertsGivenHookFunctionIsSetButHookContractIsEmpty() - public - { - testCreateRound(); - uint32 roundId = fundingPot.roundCount(); - - RoundParams memory params = _helper_createEditRoundParams( - block.timestamp + 3 days, - block.timestamp + 4 days, - 2000, - address(0), - bytes("test"), - true, - ILM_PC_FundingPot_v1.AccumulationMode.All - ); - - vm.expectRevert( - abi.encodeWithSelector( - ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__InvalidHookConfiguration - .selector - ) - ); - - fundingPot.editRound( - roundId, - params.roundStart, - params.roundEnd, - params.roundCap, - params.hookContract, - params.hookFunction, - params.autoClosure, - params.accumulationMode - ); - } /* Test editRound() └── Given a round has been created @@ -908,7 +793,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { vm.expectRevert( abi.encodeWithSelector( ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__RoundParamsInvalid + .Module__LM_PC_FundingPot__InvalidInput .selector ) ); @@ -4924,47 +4809,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { fundingPot.closeRound(roundId); } - function testCloseRound_revertsGivenHookExecutionFails() public { - uint8 accessCriteriaId = - uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.OPEN); - - uint32 roundId = fundingPot.createRound( - _defaultRoundParams.roundStart, - _defaultRoundParams.roundEnd, - _defaultRoundParams.roundCap, - address(failingHook), - abi.encodeWithSignature("executeHook()"), - _defaultRoundParams.autoClosure, - _defaultRoundParams.accumulationMode - ); - ( - address nftContract, - bytes32 merkleRoot, - address[] memory allowedAddresses - ) = _helper_createAccessCriteria(accessCriteriaId, roundId); - - fundingPot.setAccessCriteria( - roundId, - accessCriteriaId, - 0, - nftContract, - merkleRoot, - allowedAddresses, - removedAddresses - ); - - fundingPot.setAccessCriteriaPrivileges(roundId, 0, 1000, false, 0, 0, 0); - - vm.warp(_defaultRoundParams.roundEnd + 1); - - vm.expectRevert( - ILM_PC_FundingPot_v1 - .Module__LM_PC_FundingPot__HookExecutionFailed - .selector - ); - fundingPot.closeRound(roundId); - } function testCloseRound_revertsGivenClosureConditionsNotMet() public { uint8 accessCriteriaId = @@ -5961,4 +5806,165 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { roundId_, accessCriteriaId_, personalCap_, false, 0, 0, 0 ); } + + // ========================================================================= + // Test: contributeToRoundFor Authorization + // ========================================================================= + + function testContributeToRoundFor_revertsWhenNonOwnerTriesToUseUnspentCaps() public { + // Setup: Create two users - Alice and Bob + address alice = address(0x1111); + address bob = address(0x2222); + + // Give both users some tokens + vm.deal(alice, 10 ether); + vm.deal(bob, 10 ether); + _token.mint(alice, 1000); + _token.mint(bob, 1000); + + // Create first round where Alice contributes + vm.startPrank(address(this)); + uint32 round1 = fundingPot.createRound( + block.timestamp + 1 days, // start + block.timestamp + 7 days, // end + 500, // cap + address(0), // hookContract + "", // hookFunction + false, // autoClosure + ILM_PC_FundingPot_v1.AccumulationMode.Personal // allow personal accumulation + ); + + // Set up access criteria for round 1 + address[] memory allowedAddresses = new address[](2); + allowedAddresses[0] = alice; + allowedAddresses[1] = bob; + address[] memory localRemovedAddresses; + + fundingPot.setAccessCriteria( + round1, + uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.LIST), + 0, // new access criteria + address(0), + bytes32(0), + allowedAddresses, + localRemovedAddresses + ); + + // Set personal cap for access criteria + fundingPot.setAccessCriteriaPrivileges( + round1, + 1, // accessCriteriaId + 200, // personalCap + false, // overrideContributionSpan + 0, 0, 0 // time parameters + ); + vm.stopPrank(); + + // Alice contributes to round 1 (only partially using her cap) + vm.warp(block.timestamp + 1 days + 1); // move to round start + vm.startPrank(alice); + _token.approve(address(fundingPot), 100); + + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory emptyUnspentCaps; + bytes32[] memory emptyProof; + + fundingPot.contributeToRoundFor( + alice, + round1, + 100, // only use 100 out of 200 cap + 1, // accessCriteriaId + emptyProof, + emptyUnspentCaps + ); + vm.stopPrank(); + + // Close round 1 + vm.warp(block.timestamp + 7 days); + vm.prank(address(this)); + fundingPot.closeRound(round1); + + // Create second round with personal accumulation + vm.startPrank(address(this)); + uint32 round2 = fundingPot.createRound( + block.timestamp + 1 days, + block.timestamp + 7 days, + 500, + address(0), + "", + false, + ILM_PC_FundingPot_v1.AccumulationMode.Personal + ); + + // Set up same access criteria for round 2 + fundingPot.setAccessCriteria( + round2, + uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.LIST), + 0, + address(0), + bytes32(0), + allowedAddresses, + localRemovedAddresses + ); + + fundingPot.setAccessCriteriaPrivileges( + round2, + 1, + 200, // same personal cap + false, + 0, 0, 0 + ); + vm.stopPrank(); + + // Create Alice's unspent cap data from round 1 + ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[] memory aliceUnspentCaps = + new ILM_PC_FundingPot_v1.UnspentPersonalRoundCap[](1); + aliceUnspentCaps[0] = ILM_PC_FundingPot_v1.UnspentPersonalRoundCap({ + roundId: round1, + accessCriteriaId: 1, + merkleProof: emptyProof + }); + + // Move to round 2 start + vm.warp(block.timestamp + 1 days + 1); + + // Test: Bob tries to use Alice's unspent caps for his own contribution + vm.startPrank(bob); + _token.approve(address(fundingPot), 50); + + // Should revert with OnlyOwnerCanUseUnspentCaps + vm.expectRevert( + ILM_PC_FundingPot_v1.Module__LM_PC_FundingPot__OnlyOwnerCanUseUnspentCaps.selector + ); + + fundingPot.contributeToRoundFor( + alice, // Bob contributing FOR Alice + round2, + 50, + 1, + emptyProof, + aliceUnspentCaps // Using Alice's unspent caps but called by Bob + ); + vm.stopPrank(); + + // Verify: Alice can still use her own unspent caps + vm.startPrank(alice); + _token.approve(address(fundingPot), 150); + + // This should work - Alice using her own unspent caps + fundingPot.contributeToRoundFor( + alice, + round2, + 150, // Alice can contribute more than base cap due to unspent caps + 1, + emptyProof, + aliceUnspentCaps + ); + vm.stopPrank(); + + // Verify Alice's contribution succeeded + assertEq(fundingPot.roundIdToUserToContribution(round2, alice), 150); + + // Verify Bob has no contributions (since his attack failed) + assertEq(fundingPot.roundIdToUserToContribution(round2, bob), 0); + } }