Good Collective bulk add members#311
Conversation
… in contracts and SDK, including gas estimation and tests.
There was a problem hiding this comment.
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
packages/contracts/test/DirectPayments/DirectPayments.bulkMembers.test.ts
Show resolved
Hide resolved
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
packages/contracts/contracts/DirectPayments/DirectPaymentsPool.sol
Outdated
Show resolved
Hide resolved
packages/contracts/contracts/DirectPayments/DirectPaymentsPool.sol
Outdated
Show resolved
Hide resolved
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 |
packages/contracts/contracts/DirectPayments/DirectPaymentsPool.sol
Outdated
Show resolved
Hide resolved
packages/contracts/contracts/DirectPayments/DirectPaymentsPool.sol
Outdated
Show resolved
Hide resolved
packages/contracts/test/DirectPayments/DirectPayments.bulkMembers.test.ts
Show resolved
Hide resolved
|
@EmekaManuel please go over previous comments. |
|
@EmekaManuel ping |
sirpy
left a comment
There was a problem hiding this comment.
Hey @EmekaManuel
This has been inactive for over two weeks.
You could be removed from this bounty if you don't complete it.
|
@L03TJ3 CC |
|
@sirpy, yes, agree, let's give it till Thursday. |
so sorry @L03TJ3 Lewis and @sirpy, been sick for a while, just recovering. Let me take a look and address this now |
…yments and UBI pools fixing contract build error - Updated addMember functions in DirectPaymentsFactory, UBIPool, and UBIPoolFactory contracts from external to public visibility to enhance accessibility. - This change allows for more flexible member addition while maintaining existing access control mechanisms.
…ctPayments and UBI pools - Updated for-loops in addMembers functions to remove unnecessary unchecked increment statements, enhancing code clarity and maintainability. - This change streamlines the member addition process while preserving existing functionality.
|
@EmekaManuel see the unit tests that are failing |
- Introduced an internal helper function for adding members with validation, improving clarity and maintainability. - Updated the addMembers function to utilize the new helper, allowing for better error handling and skipping invalid members. - Adjusted member validation checks to ensure robust member addition processes while preserving existing functionality.
|
hi @sirpy i've gone through these tests, and I still get them failing, can i please get some assistance ? |
|
| * @param extraData Additional data to validate the member. | ||
| */ | ||
| function addMember(address member, bytes memory extraData) public returns (bool isMember) { | ||
| if (address(settings.uniquenessValidator) != address(0)) { |
There was a problem hiding this comment.
this is now duplicate code. why isnt it calling _addmember?
|
@EmekaManuel Hey pinging to see if any progress is being done at the moment? |
b25d8f6 to
c8c9b7d
Compare
- Enhanced the addMember function to utilize an internal helper for streamlined member addition and validation. - Improved error handling by determining specific revert reasons based on validation checks. - Updated tests to reflect changes in member validation logic and ensure accurate testing of member addition scenarios.
…r async handling - Changed the mock function call for isMemberValid to await its execution, ensuring accurate test behavior. - This adjustment improves the reliability of the test by correctly handling asynchronous operations.
85c7ca5 to
c0deb2c
Compare
|
|
||
| function addMembers(address[] calldata members) external onlyPool { | ||
| for (uint i = 0; i < members.length; i++) { | ||
| addMember(members[i]); |
There was a problem hiding this comment.
should call the internal _addmembertoregistry
| // For uniqueness validator, set a default that returns the input address (indicating valid) | ||
| // Note: Mock will return zero address for unmocked calls, so we use a workaround | ||
| // Set up a reverting default that we'll override per address as needed | ||
| uniquenessValidator.mock["getWhitelistedRoot"].returns(ethers.constants.AddressZero.slice(0, -1) + "1"); |
There was a problem hiding this comment.
For uniqueness validator, default return must be non-zero (valid/whitelisted). Waffle mock returns zero for unmocked calls; use a non-zero default so we only need to mock addresses we want to treat as invalid (return zero).
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