-
Notifications
You must be signed in to change notification settings - Fork 20
Good Collective bulk add members #311
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
… in contracts and SDK, including gas estimation and tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes - here's some feedback:
- In both
UBIPool.addMembersandDirectPaymentsPool.addMembers, consider replacing the genericrevert("Length mismatch")with a named custom error (e.g.error LENGTH_MISMATCH();) for gas savings and consistency with the other custom errors. - In
estimateBulkAddMembersGasin the SDK, you computegasPerMemberasgasEstimate.div(members.length); it would be safer to explicitly handle themembers.length === 0case to avoid division by zero and to return a well-definedrecommendedBatchSizefor empty input. - The new
addMembersfunctions onDirectPaymentsFactoryandUBIPoolFactoryare not invoked by the new bulk add paths (which still go through_grantRole→addMember); if the intent is to centralize duplicate-prevention at the factory level, consider wiring the pools to calladdMembersfor bulk updates or clarifying why both variants are needed.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In both `UBIPool.addMembers` and `DirectPaymentsPool.addMembers`, consider replacing the generic `revert("Length mismatch")` with a named custom error (e.g. `error LENGTH_MISMATCH();`) for gas savings and consistency with the other custom errors.
- In `estimateBulkAddMembersGas` in the SDK, you compute `gasPerMember` as `gasEstimate.div(members.length)`; it would be safer to explicitly handle the `members.length === 0` case to avoid division by zero and to return a well-defined `recommendedBatchSize` for empty input.
- The new `addMembers` functions on `DirectPaymentsFactory` and `UBIPoolFactory` are not invoked by the new bulk add paths (which still go through `_grantRole` → `addMember`); if the intent is to centralize duplicate-prevention at the factory level, consider wiring the pools to call `addMembers` for bulk updates or clarifying why both variants are needed.
## Individual Comments
### Comment 1
<location> `packages/sdk-js/src/goodcollective/goodcollective.ts:855-864` </location>
<code_context>
+ const pool = poolType === 'ubi' ? this.ubipool.attach(poolAddress) : this.pool.attach(poolAddress);
+
+ // Estimate gas for the transaction
+ const gasEstimate = await pool.estimateGas.addMembers(members, extraData);
+
+ // Get current gas price
+ const gasPrice = await pool.provider.getGasPrice();
+
+ // Calculate native token required
+ const nativeTokenRequired = gasEstimate.mul(gasPrice);
+
+ // Calculate recommended batch size based on gas estimate
+ // Assuming block gas limit of 30M and targeting 50% usage for safety
+ const targetGasLimit = 15000000;
+ const gasPerMember = gasEstimate.div(members.length);
+ const recommendedBatchSize = Math.min(200, Math.floor(targetGasLimit / gasPerMember.toNumber()));
+
+ return {
</code_context>
<issue_to_address>
**issue (bug_risk):** Guard against empty members array and avoid BigNumber.toNumber() for gas arithmetic.
`members.length === 0` will cause `gasEstimate.div(members.length)` to throw. Also, `gasPerMember.toNumber()` risks exceeding JS’s safe integer range for large gas values. Consider short‑circuiting the empty‑array case (e.g., return zeros and batch size 0), and keep calculations in `BigNumber` until the end (e.g., `targetGasLimitBN.div(gasPerMember)` then clamp before converting to a JS number).
</issue_to_address>
### Comment 2
<location> `packages/sdk-js/src/goodcollective/goodcollective.ts:867` </location>
<code_context>
+ // Assuming block gas limit of 30M and targeting 50% usage for safety
+ const targetGasLimit = 15000000;
+ const gasPerMember = gasEstimate.div(members.length);
+ const recommendedBatchSize = Math.min(200, Math.floor(targetGasLimit / gasPerMember.toNumber()));
+
+ return {
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Avoid duplicating the MAX_BATCH_SIZE constant value in the SDK to prevent drift.
The SDK currently hardcodes `200` when calculating `recommendedBatchSize`, while the contracts define `MAX_BATCH_SIZE = 200`. If the contract limit changes, the SDK will become inconsistent. Consider deriving this value from the contract (e.g., a view/static property) or centralizing the constant in the SDK so it stays in sync.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Good Collective bulk add members
🚨 Report Summary
For more details view the full report in OpenZeppelin Code Inspector |
… pools with bulk updates and error handling improvements
packages/contracts/contracts/DirectPayments/DirectPaymentsPool.sol
Outdated
Show resolved
Hide resolved
packages/contracts/contracts/DirectPayments/DirectPaymentsPool.sol
Outdated
Show resolved
Hide resolved
packages/contracts/contracts/DirectPayments/DirectPaymentsPool.sol
Outdated
Show resolved
Hide resolved
| }; | ||
| }); | ||
|
|
||
| const fixture = async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dont duplicate code. import from existing tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you didnt reduce duplicated code here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still didnt address this comment
packages/contracts/test/DirectPayments/DirectPayments.bulkMembers.test.ts
Outdated
Show resolved
Hide resolved
- Simplified factory addMembers() to inline logic instead of calling external functions - Removed BATCH_TOO_LARGE guards - gas management is now caller's responsibility - Added MANAGER_ROLE access control to pool addMembers() methods - Removed _grantMemberRoleWithoutFactory() helper functions - Consolidated SDK methods into single addPoolMembers() with poolType parameter - Updated tests to remove obsolete double-counting and batch size checks - Fixed SDK TypeScript error in gas estimation function BREAKING CHANGE: SDK addDirectPaymentsPoolMembers() and addUBIPoolMembers() methods removed in favor of consolidated addPoolMembers(poolType) method
| ); | ||
| event NFTClaimed(uint256 indexed tokenId, uint256 totalRewards); | ||
| event NOT_MEMBER_OR_WHITELISTED_OR_LIMITS(address contributer); | ||
| event MemberAdded(address indexed member); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need for this event. the grantrole emits an event
| if (added) { | ||
| emit MemberAdded(members[i]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need
| emit MemberAdded(member, msg.sender); | ||
| } | ||
|
|
||
| memberPools[members[i]].push(msg.sender); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's already an addMember method
packages/contracts/test/DirectPayments/DirectPayments.bulkMembers.test.ts
Outdated
Show resolved
Hide resolved
…pools - Consolidated member addition logic in addMembers() functions to improve readability and maintainability. - Removed redundant event emissions and replaced them with role grants for better event handling. - Updated tests to reflect changes in event verification and member addition processes. - Adjusted SDK methods to align with the new member addition structure, enhancing usability.
|
kindly review @sirpy |
| for (uint i = 0; i < members.length; ) { | ||
| _addMember(members[i], extraData[i]); | ||
| unchecked { | ||
| ++i; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why aren't you using the regular for loop format?
| import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; | ||
| import { IERC721ReceiverUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol"; | ||
| import { | ||
| IERC721ReceiverUpgradeable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is this?
| import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; | ||
| import { IERC721ReceiverUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol"; | ||
| import { | ||
| IERC721ReceiverUpgradeable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is this?
|
|
||
| for (uint i = 0; i < members.length; ) { | ||
| addMember(members[i], extraData[i]); | ||
| unchecked { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not use regular for loop format?
| function addMembers(address[] calldata members) external onlyPool { | ||
| for (uint i = 0; i < members.length; ) { | ||
| addMember(members[i]); | ||
| unchecked { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
regular for loop
| }; | ||
| }); | ||
|
|
||
| const fixture = async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still didnt address this comment
|
@EmekaManuel please go over previous comments. |
Bulk Member Addition for DirectPayments and UBI Pools
#307
Description
This PR implements bulk member addition functionality for both DirectPayments and UBI pools, enabling efficient batch operations for adding multiple members in a single transaction. This significantly reduces gas costs and improves the onboarding experience when working with large member lists.
Summary of Changes
🧩 Smart Contracts
DirectPaymentsPool & UBIPool
Added a new function:
Includes:
uniquenessValidatormembersValidatorMemberAddedevent emitted for each successful additionMAX_BATCH_SIZE = 200enforced to avoid gas-limit issuesDirectPaymentsFactory & UBIPoolFactory
Added:
Functionality includes:
memberPoolsregistry for efficient lookupsMemberAddedevents at the factory level📦 SDK (packages/sdk-js)
Added three new
GoodCollectiveSDKmethods:addDirectPaymentsPoolMembers()– Add multiple DirectPayments pool membersaddUBIPoolMembers()– Add multiple UBI pool membersestimateBulkAddMembersGas()– Estimate gas + recommend batch sizesTests
Comprehensive test suites for both pool types:
DirectPayments.bulkMembers.test.tsUBIPool.bulkMembers.test.tsCoverage includes:
MAX_BATCH_SIZE = 200)Key Features
Breaking Changes
None.
This feature is fully additive and does not modify existing behavior.
How Has This Been Tested?
🧪 Automated Unit Tests
DirectPayments.bulkMembers.test.ts– 10 test casesUBIPool.bulkMembers.test.ts– 10 test cases (including UBI-specific logic)Coverage Includes:
Test Execution:
🧪 Manual Testing