diff --git a/src/modules/logicModule/LM_PC_FundingPot_v1.sol b/src/modules/logicModule/LM_PC_FundingPot_v1.sol index 1d1bd3369..f01b74df1 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,7 +155,11 @@ 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))) public + usedUnspentCaps; /// @notice Storage gap for future upgrades. uint[50] private __gap; @@ -284,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_, @@ -305,10 +299,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) { @@ -350,33 +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; - } - // ------------------------------------------------------------------------- // Public - Mutating @@ -467,12 +430,13 @@ 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_]; if (accessCriteriaType_ > MAX_ACCESS_CRITERIA_TYPE) { - revert Module__LM_PC_FundingPot__InvalidAccessCriteriaId(); + revert Module__LM_PC_FundingPot__InvalidAccessCriteriaType(); } _validateEditRoundParameters(round); @@ -494,7 +458,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(); } } @@ -533,6 +497,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]] @@ -540,12 +511,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 @@ -562,7 +528,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); @@ -622,7 +588,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( @@ -639,60 +605,17 @@ 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( - ); - } - - 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; + uint unspentPersonalCap = _calculateUnspentPersonalCap( + user_, roundId_, unspentPersonalRoundCaps_ + ); - if (userContribution < personalCap) { - unspentForThisEntry = personalCap - userContribution; - } - unspentPersonalCap += unspentForThisEntry; + 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; } } @@ -807,6 +730,101 @@ 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; + } + + if ( + usedUnspentCaps[user_][currentProcessingRoundId][roundCapInfo + .accessCriteriaId] + ) { + 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. @@ -831,14 +849,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(); } } @@ -899,7 +915,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( @@ -971,18 +987,16 @@ 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(); - } + if ( + !isEligible + && ( + accessCriteria.accessCriteriaType == AccessCriteriaType.NFT + || accessCriteria.accessCriteriaType + == AccessCriteriaType.MERKLE + || accessCriteria.accessCriteriaType == AccessCriteriaType.LIST + ) + ) { + revert Module__LM_PC_FundingPot__AccessCriteriaFailed(); } } @@ -1353,16 +1367,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 915a61dfd..54d2431f1 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(); @@ -259,8 +234,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(); @@ -271,17 +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(); - - /// @notice Access not permitted. - error Module__LM_PC_FundingPot__AccessNotPermitted(); + error Module__LM_PC_FundingPot__AccessCriteriaFailed(); /// @notice User has reached their personal contribution cap. error Module__LM_PC_FundingPot__PersonalCapReached(); @@ -320,6 +285,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 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 @@ -384,15 +355,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. @@ -410,33 +372,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); - // ------------------------------------------------------------------------- // Public - Mutating @@ -489,13 +424,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..7f3d06e1a 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 @@ -281,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 782714af7..f9a7df885 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); } // ------------------------------------------------------------------------- @@ -297,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 ) ); @@ -321,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 ) ); @@ -355,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 ( @@ -429,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, @@ -465,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, @@ -498,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; ( @@ -545,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_; @@ -572,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, @@ -613,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,,,,,,) = @@ -657,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, @@ -672,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 ) ); @@ -693,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, @@ -708,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 ) ); @@ -741,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; @@ -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( @@ -823,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, @@ -846,7 +850,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); vm.stopPrank(); } @@ -856,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, @@ -877,7 +882,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); } @@ -887,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, @@ -911,7 +917,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); } @@ -921,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, @@ -935,7 +942,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 ) ); @@ -945,7 +952,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); } @@ -955,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, @@ -977,7 +985,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); } @@ -987,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, @@ -1009,7 +1018,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); } @@ -1019,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, @@ -1041,7 +1051,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); } @@ -1049,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; ( @@ -1064,7 +1075,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); ( @@ -1096,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, @@ -1104,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, @@ -1113,7 +1125,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 1, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); } @@ -1136,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); @@ -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.roundCount(); + + 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 @@ -1355,20 +1446,33 @@ 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 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( ) 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; @@ -1380,7 +1484,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 @@ -1409,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); @@ -1422,7 +1532,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 @@ -1456,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; @@ -1472,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 ) ); @@ -1487,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; @@ -1503,7 +1619,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 @@ -1519,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 ) ); @@ -1534,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; @@ -1546,7 +1668,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 @@ -1562,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 ) ); @@ -1573,11 +1701,254 @@ 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.roundCount(); + + 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, + removedAddresses + ); + 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.roundCount(); + + fundingPot.setAccessCriteria( + round2Id, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses + ); + 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_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(); - uint32 roundId = fundingPot.getRoundCount(); + uint32 roundId = fundingPot.roundCount(); uint8 accessCriteriaId = 1; uint8 accessType = uint8(ILM_PC_FundingPot_v1.AccessCriteriaType.NFT); uint amount = 500; @@ -1589,7 +1960,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 @@ -1641,7 +2018,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 @@ -1711,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 @@ -1769,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; ( @@ -1779,7 +2163,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 @@ -1810,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); } @@ -1831,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_); @@ -1853,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); } @@ -1868,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 @@ -1895,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); } @@ -1927,7 +2320,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; @@ -1955,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); @@ -1981,7 +2380,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 @@ -2029,7 +2434,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); uint totalContribution = - fundingPot.getUserContributionToRound(roundId, contributor1_); + fundingPot.roundIdToUserToContribution(roundId, contributor1_); assertEq(totalContribution, personalCap); } @@ -2039,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; @@ -2051,7 +2456,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 @@ -2082,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); } @@ -2101,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 = @@ -2117,7 +2528,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( @@ -2135,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, @@ -2143,7 +2555,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); // Set personal cap of 400 for round 2 @@ -2185,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 ); } @@ -2209,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); @@ -2220,7 +2633,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 @@ -2237,9 +2656,15 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { _defaultRoundParams.autoClosure, _defaultRoundParams.accumulationMode ); - uint32 round2Id = fundingPot.getRoundCount(); + uint32 round2Id = fundingPot.roundCount(); fundingPot.setAccessCriteria( - round2Id, accessType, 0, nftContract, merkleRoot, allowedAddresses + round2Id, + accessType, + 0, + nftContract, + merkleRoot, + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges( round2Id, accessCriteriaId, 500, false, 0, 0, 0 @@ -2279,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() @@ -2329,7 +2754,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 @@ -2346,7 +2777,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 @@ -2363,7 +2800,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 @@ -2386,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 @@ -2418,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" ); @@ -2456,7 +2899,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 @@ -2473,7 +2922,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 @@ -2490,7 +2945,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 @@ -2513,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 @@ -2529,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" ); @@ -2545,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; @@ -2573,7 +3034,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 @@ -2590,7 +3057,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 @@ -2610,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" ); @@ -2654,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)" ); @@ -2692,7 +3165,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 @@ -2722,7 +3201,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 @@ -2732,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" ); @@ -2757,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" ); @@ -2782,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" ); @@ -2822,7 +3307,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 @@ -2851,7 +3342,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 @@ -2860,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" ); @@ -2883,24 +3380,159 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { c1AttemptR2, accessId, new bytes32[](0), - unspentCapsC1 + unspentCapsC1 + ); + vm.stopPrank(); + + // --- Assertions --- + assertEq( + fundingPot.roundIdToUserToContribution(round2Id, contributor1_), + r2BasePersonalCapC1, + "R2 C1 personal contribution should be clamped by R2's base personal cap (Disabled mode)" + ); + assertEq( + fundingPot.roundIdToTotalContributions(round2Id), + r2BasePersonalCapC1, + "R2 Total contributions should not be expanded by R1 (Disabled mode)" + ); + assertTrue( + fundingPot.roundIdToTotalContributions(round2Id) <= r2BaseCap, + "R2 Total contributions exceeded R2's original base cap (Disabled mode)" + ); + } + + 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(); - // --- Assertions --- + // Verify the contribution was recorded assertEq( - fundingPot.getUserContributionToRound(round2Id, contributor1_), - r2BasePersonalCapC1, - "R2 C1 personal contribution should be clamped by R2's base personal cap (Disabled mode)" + fundingPot.roundIdToUserToContribution(round3Id, contributor1_), + 500, + "User contribution should be 500" ); + + // Verify tokens were transferred assertEq( - fundingPot.getTotalRoundContribution(round2Id), - r2BasePersonalCapC1, - "R2 Total contributions should not be expanded by R1 (Disabled mode)" - ); - assertTrue( - fundingPot.getTotalRoundContribution(round2Id) <= r2BaseCap, - "R2 Total contributions exceeded R2's original base cap (Disabled mode)" + _token.balanceOf(contributor1_), + initialBalance - 500, + "Tokens should have been transferred from contributor" ); } @@ -2937,7 +3569,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 @@ -2966,7 +3604,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 @@ -2975,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" ); @@ -3004,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)" ); } @@ -3056,7 +3700,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 @@ -3070,7 +3720,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); vm.stopPrank(); assertEq( - fundingPot.getUserContributionToRound(round1Id, contributor1_), + fundingPot.roundIdToUserToContribution(round1Id, contributor1_), r1ContributionC1 ); @@ -3085,7 +3735,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 @@ -3099,7 +3755,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); vm.stopPrank(); assertEq( - fundingPot.getUserContributionToRound(round2Id, contributor1_), + fundingPot.roundIdToUserToContribution(round2Id, contributor1_), r2ContributionC1 ); @@ -3114,7 +3770,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 @@ -3122,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" ); @@ -3158,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" ); @@ -3225,7 +3887,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 @@ -3239,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) --- @@ -3253,7 +3921,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 @@ -3267,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) --- @@ -3285,14 +3959,15 @@ 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 ); assertEq( - fundingPot.getGlobalAccumulationStartRoundId(), + fundingPot.globalAccumulationStartRoundId(), 1, "Default global start round ID should be 1" ); @@ -3308,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" ); @@ -3355,7 +4030,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 @@ -3386,7 +4067,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 @@ -3394,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" ); @@ -3424,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" ); @@ -3466,7 +4153,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 @@ -3496,7 +4189,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( @@ -3505,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" ); @@ -3523,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)" ); @@ -3567,7 +4266,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 @@ -3596,7 +4301,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 @@ -3605,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" ); @@ -3634,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)" ); @@ -3677,7 +4388,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 @@ -3706,7 +4423,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 @@ -3715,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" ); @@ -3733,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)" ); @@ -3767,7 +4490,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 @@ -3784,7 +4513,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 @@ -3801,7 +4536,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 @@ -3883,7 +4624,7 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { ); vm.stopPrank(); assertEq( - fundingPot.getUserContributionToRound(round3Id, contributor1_), 100 + fundingPot.roundIdToUserToContribution(round3Id, contributor1_), 100 ); } @@ -3903,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 @@ -3921,7 +4662,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 @@ -3950,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( @@ -3959,7 +4701,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 @@ -3979,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 @@ -4012,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"); @@ -4029,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. @@ -4055,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() @@ -4074,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; @@ -4092,7 +4835,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 @@ -4120,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( @@ -4129,7 +4873,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 @@ -4149,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); @@ -4168,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" ); @@ -4176,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" ); @@ -4213,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" ); @@ -4221,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" ); @@ -4235,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" ); @@ -4243,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" ); @@ -4267,12 +5012,242 @@ 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" ); } + 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.roundIdToUserToContribution(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() @@ -4332,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( @@ -4352,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); @@ -4392,7 +5367,8 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + removedAddresses ); fundingPot.setAccessCriteriaPrivileges(roundId, 0, 1000, false, 0, 0, 0); @@ -4412,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); @@ -4429,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 ); @@ -4456,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); @@ -4467,7 +5443,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 @@ -4489,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); @@ -4506,7 +5488,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 @@ -4531,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); @@ -4550,7 +5538,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 @@ -4570,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); @@ -4591,7 +5585,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( @@ -4611,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; @@ -4629,7 +5629,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 @@ -4665,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); } //------------------------------------------------------------------------- @@ -4722,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( @@ -4737,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( @@ -4752,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( @@ -4772,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); @@ -4781,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); @@ -4790,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); @@ -4805,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); @@ -4915,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); @@ -4925,7 +5931,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 ) ); @@ -4968,7 +5974,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 @@ -5057,7 +6069,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, @@ -5119,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); @@ -5132,7 +6150,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 @@ -5164,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); @@ -5322,7 +6346,35 @@ contract LM_PC_FundingPot_v1_Test is ModuleTest { 0, nftContract, merkleRoot, - allowedAddresses + allowedAddresses, + 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 ); } }