Skip to content

Conversation

@fabianschu
Copy link
Contributor

No description provided.

@fabianschu fabianschu requested a review from 0xNuggan April 30, 2025 22:37
}

if (block.timestamp > round_.roundStart) {
revert Module__LM_PC_FundingPot__RoundAlreadyStarted();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit of a nitpick, but since this will also trigger if somebody tries to edit an already finished round, maybe we can change the name (or add an extra error if size allows for it?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't really come up with a more descriptive error since the idea is that the admin can edit as long as a round hasn't started. Imo the error exactly tells this story.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, fair. My comment came from the thought of "if a round started and ended, it's weird to revert saying 'it started' ". But it makes sense, the condition is "rounds are editable before start". Let's leavi it like that.

Copy link
Contributor

@0xNuggan 0xNuggan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general looks good, there are a couple of things I pointed out. I also have still an open question about the caps/accesstypes, but I wanted to get this out first

@fabianschu fabianschu requested a review from 0xNuggan May 22, 2025 15:35
}

if (block.timestamp > round_.roundStart) {
revert Module__LM_PC_FundingPot__RoundAlreadyStarted();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, fair. My comment came from the thought of "if a round started and ended, it's weird to revert saying 'it started' ". But it makes sense, the condition is "rounds are editable before start". Let's leavi it like that.

// Constants

/// @notice The role that allows creating funding rounds.
bytes32 public constant FUNDING_POT_ADMIN_ROLE = "FUNDING_POT_ADMIN";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will need to update it for the new authorization logic

}

/// @inheritdoc ILM_PC_FundingPot_v1
function getRoundAccessCriteria(uint32 roundId_, uint8 accessCriteriaId_)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should revert if the roundId_ or its accessCriteriaId_ don't exist

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Unless returning all empty over revert is intended)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we were trying to reduce contract size here and so adding the revert may actually push us over the limit, if it doesn't then I think changing it makes sense but if it does then we'll have to leave as is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this function si not sued internally, so there is no internal logic problem.
To me, also returning isList_ = true when the AccessCriteriaType is equal to OPEN is unexpected, but it is impossible to say if this is problematic as we do not know the use case. What is the use case?

) external onlyModuleRole(FUNDING_POT_ADMIN_ROLE) {
Round storage round = rounds[roundId_];

if (accessCriteriaType_ > MAX_ACCESS_CRITERIA_ID) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it checks against the type not the id. According to the name and other uses of MAX_ACCESS_CRITERIA_ID, it should both check here against accessCriteriaId_ instead of accessCriteriaType_, and in if accessCriteriaId_ is 0 it should check roundIdToNextAccessCriteriaId won't be higher.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though it's a bit unclear why require it to be up to 4?
Was that meant to be limiting the type or the ID in fact?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice find, we fixed this in the fixes PR

// Clear all existing data to prevent stale data
round.accessCriterias[criteriaId].nftContract = address(0);
round.accessCriterias[criteriaId].merkleRoot = bytes32(0);
// @note: When changing allowlists, call removeAllowlistedAddresses first to clear previous entries

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be called here then to make sure it's removed?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to add a note here to be more explicit but we won't be fixing this for now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should not be problematic, as these data are read only if the relevant Type is set.
However, the logic is not consistent, and that can be confusing for someone who calls this function.
you could simplify this block of code (i.e. these reset lines plus the stuff on l 531f) by just these variables to the value of the parameters.

_validateEditRoundParameters(round);

for (uint i = 0; i < addressesToRemove_.length; i++) {
unchecked {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this unchecked for? (there's no math operation inside here)

Module__LM_PC_FundingPot__UnspentCapsRoundIdsNotStrictlyIncreasing(
);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could move lastSeenRoundId = currentProcessingRoundId; here and delete it from the 3 other places it's done below to avoid code repetition.

{
Round storage round = rounds[roundId_];

if (round.roundEnd == 0 && round.roundCap == 0) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this, you could just check if the round ID is lower than the current one in the counter (here and in other places this check is done)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or check roundStart != 0 as it cannot be 0 when a round is added

uint contributorCount = contributors.length;

// Check batch size is not zero
if (batchSize_ == 0 || batchSize_ > contributorCount) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might make operations more convenient to set batchSize_ to contributorCount instead of reverting if it's bigger

}

// --- Personal Cap Check ---
{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should have an if here to check if personal cap exists/ should be applied

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm now sure this is needed here's why

  • userPersonalCap will be 0 (default value)
  • If a user tries to contribute any amount (let's say 100 tokens)
  • The code checks: userPreviousContribution >= userPersonalCap
  • Since userPersonalCap is 0, and any contribution > 0, this will be true
  • The transaction will revert with PersonalCapReached

view
returns (bool isEligible, uint remainingAmountAllowedToContribute)
{
if (accessCriteriaId_ > MAX_ACCESS_CRITERIA_TYPE) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so here you compare accessCriteriaId with MAX_ACCESS_CRITERIA_TYPE, while in setAccessCriteria, it is the accessCriteriaType_ argument (not the id) that is supposed to be smaller than MAX_ACCESS_CRITERIA_TYPE.

Not sure what the original intention is here, but you should decide if the constnat MAX_ACCESS_CRITERIA_TYPE limits the ids or the type (I guess the latter)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed the if check, since it is not required for accessCriteriaId

@@ -0,0 +1,1438 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.8.23;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as a general remark, it is better (easier to review, easier to understand, easier to version control) to split the logic over diffferent classes/files rather than mixing all together in one huge file.

mapping(
uint32 roundId
=> mapping(uint8 accessCriteriaId_ => AccessCriteriaPrivileges)
) private roundIdToAccessCriteriaIdToPrivileges;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why put this in a separate mapping? is it not cleaner to have the priviliges part of the accessCriteria object instead of createing a separate mapping?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes for sure good note but we will not be fixing this time around

Round storage round = rounds[roundId_];

if (accessCriteriaType_ > MAX_ACCESS_CRITERIA_TYPE) {
revert Module__LM_PC_FundingPot__InvalidAccessCriteriaId();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps not the error you want to use here, as the id could be fine..

Copy link

@zzzuhaibmohd zzzuhaibmohd Jun 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed the error string from Module__LM_PC_FundingPot__InvalidAccessCriteriaId to Module__LM_PC_FundingPot__InvalidAccessCriteriaType

// Otherwise, edit the existing one
if (accessCriteriaId_ == 0) {
unchecked {
criteriaId = ++roundIdToNextAccessCriteriaId[roundId_];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you have prblems witht he contract size, a small gain is to have a global counter for teh criteriaId instead of a spearate one for each round.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks good to note but a little too late for us to change here, will keep noted for the next contract

round.accessCriterias[criteriaId].merkleRoot = merkleRoot_;
} else if (accessCriteriaType == AccessCriteriaType.LIST) {
// For LIST type, update the allowed addresses
for (uint i = 0; i < allowedAddresses_.length; i++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is important to note here that you are NOT overwriting the allowedlist here, but only adding to it. This is not obvious at all. I'd recomend to adda "addressesToRemove" parameter as well to the function signature to make this explicit.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the fix and supporting unit test as well!

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not see where it is enforced taht the unspentPersonalRoundCaps are "previous" to the given roundId_. This is important, as in this way, they could include the roundId_ itself (and so the personal cap would be counted twice), or even after roundId_ (if roundId_ has overrideContributionSpan set to true)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have pushed a fix for this, and added a test as well

.accessCriteriaId];

uint userContribution =
roundIdToUserToContribution[currentProcessingRoundId][user_];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

roundIdToUserToContribution does not seem to be updated for the currentPRocessingRoundId. That would mean that the personalCap of previous rounds can be used over and over again.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a proposed fix in mind as to how to fix this? some pseudo code would be helpful as well @jellegerbrandy

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have pushed a fix for this, and added a couple of tests.

@zzzuhaibmohd zzzuhaibmohd mentioned this pull request Jun 18, 2025
leeftk and others added 3 commits September 16, 2025 09:25
* fix: new audit fixes 1

* fix: new audit fixes 2

* fix: new audit fixes 3

* fix: audit fixes 4

* fix: new audit fixes 4 -remove unneccessary code

* fix: new audit fixes 5

* fix: new audit fixes 5: add getter function

* fix: contract size fix 1

* fix: contract size fix 2: remove getter functions and make public

* fix: contract size fix 3

* fix: contract size fix 3: if condition

* contract resizing

* chore:remove uncommented tests

* chore:remove hook validation

---------

Co-authored-by: Zuhaib Mohammed <zzzuhaibmohd@gmail.com>
Co-authored-by: Jeffrey Owoloko <72028836+JeffreyJoel@users.noreply.github.com>
Co-authored-by: JeffreyJoel <jowoloko@gmail.com>
@0xNuggan 0xNuggan merged commit 2c05ca6 into dev Sep 17, 2025
5 checks passed
@0xNuggan 0xNuggan deleted the feat/funding-pot branch September 17, 2025 07:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants