@gooddollar/goodcollective-contracts 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +14 -1
  2. package/contracts/DirectPayments/DirectPaymentsFactory.sol +13 -10
  3. package/contracts/DirectPayments/DirectPaymentsPool.sol +13 -4
  4. package/contracts/GoodCollective/GoodCollectiveSuperApp.sol +31 -55
  5. package/contracts/GoodCollective/IGoodCollectiveSuperApp.sol +6 -0
  6. package/contracts/UBI/UBIPool.sol +48 -36
  7. package/contracts/UBI/UBIPoolFactory.sol +10 -1
  8. package/contracts/test/HelperLibraryTest.sol +16 -0
  9. package/contracts/utils/HelperLibrary.sol +73 -5
  10. package/package.json +4 -3
  11. package/releases/deployment.json +21841 -565
  12. package/typechain-types/@uniswap/index.ts +0 -2
  13. package/typechain-types/contracts/DirectPayments/DirectPaymentsFactory.sol/DirectPaymentsFactory.ts +1327 -0
  14. package/typechain-types/contracts/{GoodCollective/GoodCollectiveSuperApp.sol → DirectPayments/DirectPaymentsFactory.sol}/index.ts +1 -1
  15. package/typechain-types/contracts/DirectPayments/DirectPaymentsFactory.ts +40 -5
  16. package/typechain-types/contracts/DirectPayments/DirectPaymentsPool.sol/DirectPaymentsPool.ts +49 -0
  17. package/typechain-types/contracts/GoodCollective/GoodCollectiveSuperApp.ts +49 -0
  18. package/typechain-types/contracts/{UBI/MultiClaimModule.sol/IClaimable.ts → GoodCollective/IGoodCollectiveSuperApp.sol/IRegistry.ts} +33 -23
  19. package/typechain-types/{factories/@uniswap/v3-periphery/contracts → contracts/GoodCollective/IGoodCollectiveSuperApp.sol}/index.ts +1 -1
  20. package/typechain-types/contracts/GoodCollective/index.ts +3 -2
  21. package/typechain-types/contracts/UBI/UBIPool.sol/UBIPool.ts +104 -49
  22. package/typechain-types/contracts/UBI/UBIPoolFactory.ts +43 -5
  23. package/typechain-types/contracts/UBI/index.ts +0 -2
  24. package/typechain-types/contracts/index.ts +2 -0
  25. package/typechain-types/contracts/test/HelperLibraryTest.ts +147 -0
  26. package/typechain-types/{@uniswap/v3-periphery/contracts/interfaces → contracts/test}/index.ts +1 -1
  27. package/typechain-types/factories/@uniswap/index.ts +0 -1
  28. package/typechain-types/factories/contracts/DirectPayments/DirectPaymentsFactory.sol/DirectPaymentsFactory__factory.ts +1004 -0
  29. package/typechain-types/factories/contracts/{GoodCollective/GoodCollectiveSuperApp.sol → DirectPayments/DirectPaymentsFactory.sol}/IRegistry__factory.ts +1 -1
  30. package/typechain-types/factories/contracts/{GoodCollective/GoodCollectiveSuperApp.sol → DirectPayments/DirectPaymentsFactory.sol}/index.ts +1 -1
  31. package/typechain-types/factories/contracts/DirectPayments/DirectPaymentsFactory__factory.ts +15 -2
  32. package/typechain-types/factories/contracts/DirectPayments/DirectPaymentsPool.sol/DirectPaymentsPool__factory.ts +57 -1
  33. package/typechain-types/factories/contracts/GoodCollective/GoodCollectiveSuperApp__factory.ts +57 -1
  34. package/typechain-types/factories/contracts/GoodCollective/IGoodCollectiveSuperApp.sol/IRegistry__factory.ts +52 -0
  35. package/typechain-types/{@gooddollar/goodprotocol/contracts/token/FeesFormula.sol → factories/contracts/GoodCollective/IGoodCollectiveSuperApp.sol}/index.ts +1 -1
  36. package/typechain-types/factories/contracts/GoodCollective/index.ts +2 -1
  37. package/typechain-types/factories/contracts/UBI/UBIPool.sol/UBIPool__factory.ts +109 -28
  38. package/typechain-types/factories/contracts/UBI/UBIPoolFactory__factory.ts +32 -4
  39. package/typechain-types/factories/contracts/UBI/index.ts +0 -1
  40. package/typechain-types/factories/contracts/index.ts +1 -0
  41. package/typechain-types/factories/contracts/test/HelperLibraryTest__factory.ts +154 -0
  42. package/typechain-types/{@uniswap/v3-periphery → factories/contracts/test}/index.ts +1 -2
  43. package/typechain-types/factories/contracts/utils/HelperLibrary__factory.ts +1 -1
  44. package/typechain-types/hardhat.d.ts +4 -31
  45. package/typechain-types/index.ts +6 -12
  46. package/contracts/UBI/MultiClaimModule.sol +0 -78
  47. package/typechain-types/@gooddollar/goodprotocol/contracts/token/FeesFormula.sol/IFeesFormula.ts +0 -115
  48. package/typechain-types/@uniswap/v3-periphery/contracts/index.ts +0 -5
  49. package/typechain-types/@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.ts +0 -369
  50. package/typechain-types/contracts/GoodCollective/GoodCollectiveSuperApp.sol/GoodCollectiveSuperApp.ts +0 -1000
  51. package/typechain-types/contracts/UBI/MultiClaimModule.sol/IModule.ts +0 -196
  52. package/typechain-types/contracts/UBI/MultiClaimModule.sol/MultiClaimModule.ts +0 -242
  53. package/typechain-types/contracts/UBI/MultiClaimModule.sol/index.ts +0 -6
  54. package/typechain-types/factories/@gooddollar/goodprotocol/contracts/token/FeesFormula.sol/IFeesFormula__factory.ts +0 -60
  55. package/typechain-types/factories/@gooddollar/goodprotocol/contracts/token/FeesFormula.sol/index.ts +0 -4
  56. package/typechain-types/factories/@uniswap/v3-periphery/contracts/interfaces/ISwapRouter__factory.ts +0 -263
  57. package/typechain-types/factories/@uniswap/v3-periphery/contracts/interfaces/index.ts +0 -4
  58. package/typechain-types/factories/@uniswap/v3-periphery/index.ts +0 -4
  59. package/typechain-types/factories/contracts/GoodCollective/GoodCollectiveSuperApp.sol/GoodCollectiveSuperApp__factory.ts +0 -728
  60. package/typechain-types/factories/contracts/UBI/MultiClaimModule.sol/IClaimable__factory.ts +0 -33
  61. package/typechain-types/factories/contracts/UBI/MultiClaimModule.sol/IModule__factory.ts +0 -84
  62. package/typechain-types/factories/contracts/UBI/MultiClaimModule.sol/MultiClaimModule__factory.ts +0 -150
  63. package/typechain-types/factories/contracts/UBI/MultiClaimModule.sol/index.ts +0 -6
  64. /package/typechain-types/contracts/{GoodCollective/GoodCollectiveSuperApp.sol → DirectPayments/DirectPaymentsFactory.sol}/IRegistry.ts +0 -0
package/README.md CHANGED
@@ -1 +1,14 @@
1
- # contracts
1
+ # Contracts
2
+
3
+ ## Structs
4
+
5
+ ### Pool Settings
6
+
7
+ - `nftType`: 1, // when you update the settings with setPoolSettings, `nftType` should always match what has been set when creating the pool
8
+ - `uniquenessValidator`: `ethers.constants.AddressZero`,
9
+ - `rewardPerEvent`: `[100, 300]`,
10
+ - `validEvents`: `[1, 2]`, // to be defined
11
+ - `manager`: `<address of the owner/creator of the pool>`,
12
+ - `membersValidator`: `ethers.constants.AddressZero`, // used to only accept certain members (address zero for anyone can join)
13
+ - `rewardToken`: `'0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A'`, // what token will a steward/member receive. currently only supports production G$'s
14
+ - `allowRewardOverride`: `false`,
@@ -122,13 +122,7 @@ contract DirectPaymentsFactory is AccessControlUpgradeable, UUPSUpgradeable {
122
122
  //TODO: add check if msg.sender is whitelisted
123
123
 
124
124
  _settings.nftType = nextNftType;
125
- bytes memory initCall = abi.encodeWithSelector(
126
- DirectPaymentsPool.initialize.selector,
127
- nft,
128
- _settings,
129
- _limits,
130
- address(this)
131
- );
125
+ bytes memory initCall = abi.encodeCall(DirectPaymentsPool.initialize, (nft, _settings, _limits, this));
132
126
 
133
127
  if (useBeacon) {
134
128
  pool = DirectPaymentsPool(address(new BeaconProxy(address(impl), initCall)));
@@ -138,7 +132,6 @@ contract DirectPaymentsFactory is AccessControlUpgradeable, UUPSUpgradeable {
138
132
 
139
133
  nft.grantRole(nft.getManagerRole(nextNftType), _settings.manager);
140
134
  nft.grantRole(nft.getManagerRole(nextNftType), address(pool));
141
- pool.grantRole(pool.MINTER_ROLE(), _settings.manager);
142
135
 
143
136
  //access control to project is determinted by the first pool access control rules
144
137
  if (address(projectIdToControlPool[keccak256(bytes(_projectId))]) == address(0))
@@ -146,6 +139,7 @@ contract DirectPaymentsFactory is AccessControlUpgradeable, UUPSUpgradeable {
146
139
  registry[address(pool)].ipfs = _ipfs;
147
140
  registry[address(pool)].projectId = _projectId;
148
141
 
142
+ pool.grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
149
143
  pool.renounceRole(DEFAULT_ADMIN_ROLE, address(this));
150
144
  pools.push(address(pool));
151
145
 
@@ -174,7 +168,16 @@ contract DirectPaymentsFactory is AccessControlUpgradeable, UUPSUpgradeable {
174
168
  feeRecipient = _feeRecipient;
175
169
  }
176
170
 
177
- function addMember(address account) external onlyPool {
178
- memberPools[account].push(msg.sender);
171
+ function addMember(address member) external onlyPool {
172
+ memberPools[member].push(msg.sender);
173
+ }
174
+
175
+ function removeMember(address member) external onlyPool {
176
+ for (uint i = 0; i < memberPools[member].length; i++) {
177
+ if (memberPools[member][i] == msg.sender) {
178
+ memberPools[member][i] = memberPools[member][memberPools[member].length - 1];
179
+ memberPools[member].pop();
180
+ }
181
+ }
179
182
  }
180
183
  }
@@ -140,8 +140,10 @@ contract DirectPaymentsPool is
140
140
  settings = _settings;
141
141
  limits = _limits;
142
142
  nft = _nft;
143
- _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
144
- _setupRole(DEFAULT_ADMIN_ROLE, _settings.manager);
143
+ _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); // when using factory this gives factory role which then set role to the real msg.sender
144
+ _setupRole(MANAGER_ROLE, _settings.manager);
145
+ _setupRole(MINTER_ROLE, _settings.manager);
146
+
145
147
  setSuperToken(ISuperToken(address(settings.rewardToken)));
146
148
  }
147
149
 
@@ -352,6 +354,13 @@ contract DirectPaymentsPool is
352
354
  super._grantRole(role, account);
353
355
  }
354
356
 
357
+ function _revokeRole(bytes32 role, address account) internal virtual override {
358
+ if (role == MEMBER_ROLE) {
359
+ registry.removeMember(account);
360
+ }
361
+ super._revokeRole(role, account);
362
+ }
363
+
355
364
  function mintNFT(address _to, ProvableNFT.NFTData memory _nftData, bool withClaim) external onlyRole(MINTER_ROLE) {
356
365
  uint nftId = nft.mintPermissioned(_to, _nftData, true, "");
357
366
  if (withClaim) {
@@ -391,7 +400,7 @@ contract DirectPaymentsPool is
391
400
  * @dev Sets the safety limits for the pool.
392
401
  * @param _limits The new safety limits.
393
402
  */
394
- function setPoolLimits(SafetyLimits memory _limits) public onlyRole(DEFAULT_ADMIN_ROLE) {
403
+ function setPoolLimits(SafetyLimits memory _limits) public onlyRole(MANAGER_ROLE) {
395
404
  limits = _limits;
396
405
  emit PoolLimitsChanged(_limits);
397
406
  }
@@ -400,7 +409,7 @@ contract DirectPaymentsPool is
400
409
  * @dev Sets the settings for the pool.
401
410
  * @param _settings The new pool settings.
402
411
  */
403
- function setPoolSettings(PoolSettings memory _settings) public onlyRole(DEFAULT_ADMIN_ROLE) {
412
+ function setPoolSettings(PoolSettings memory _settings) public onlyRole(MANAGER_ROLE) {
404
413
  if (_settings.nftType != settings.nftType) revert NFTTYPE_CHANGED();
405
414
  if (_settings.manager == address(0)) revert EMPTY_MANAGER();
406
415
 
@@ -14,14 +14,6 @@ import "@uniswap/swap-router-contracts/contracts/interfaces/IV3SwapRouter.sol";
14
14
  import "../DirectPayments/DirectPaymentsFactory.sol";
15
15
  import "../utils/HelperLibrary.sol";
16
16
 
17
- // import "hardhat/console.sol";
18
-
19
- interface IRegistry {
20
- function feeRecipient() external view returns (address);
21
-
22
- function feeBps() external view returns (uint32);
23
- }
24
-
25
17
  abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
26
18
  int96 public constant MIN_FLOW_RATE = 386e9;
27
19
 
@@ -166,6 +158,28 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
166
158
  return _ctx;
167
159
  }
168
160
 
161
+ /**
162
+ * @dev allow single contribution. user needs to approve tokens first. can be used in superfluid batch actions.
163
+ * @param _sender The address of the sender who is contributing tokens.
164
+ * @param _customData The SwapData struct containing information about the swap
165
+ * @param _ctx The context of the transaction for superfluid in case this was used in superfluid batch. otherwise can be empty.
166
+ * @return Returns the context of the transaction.
167
+ */
168
+ function supportWithSwap(
169
+ address _sender,
170
+ HelperLibrary.SwapData memory _customData,
171
+ bytes memory _ctx
172
+ ) external onlyHostOrSender(_sender) returns (bytes memory) {
173
+ uint256 balance = superToken.balanceOf(address(this));
174
+ HelperLibrary.handleSwap(swapRouter, _customData, address(superToken), _sender, address(this));
175
+ uint256 amountReceived = superToken.balanceOf(address(this)) - balance;
176
+ if (amountReceived == 0) revert ZERO_AMOUNT();
177
+
178
+ // Update the contribution amount for the sender in the supporters mapping
179
+ _updateSupporter(_sender, int256(amountReceived), 0, ""); //we pass empty ctx since this is not a flow but a single donation
180
+ return _ctx;
181
+ }
182
+
169
183
  /**
170
184
  * @dev Handles the swap of tokens using the SwapData struct
171
185
  * @param _customData The SwapData struct containing information about the swap
@@ -253,7 +267,7 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
253
267
  ) internal returns (bytes memory newCtx) {
254
268
  newCtx = _ctx;
255
269
  bool _isFlow = _ctx.length > 0;
256
- _updateStats(_isFlow ? 0 : uint256(_previousFlowRateOrAmount));
270
+ HelperLibrary.updateStats(stats, superToken, getRegistry(), _isFlow ? 0 : uint256(_previousFlowRateOrAmount));
257
271
  // Get the current flow rate for the supporter
258
272
  int96 flowRate = superToken.getFlowRate(_supporter, address(this));
259
273
  uint256 prevContribution = supporters[_supporter].contribution;
@@ -266,7 +280,14 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
266
280
  supporters[_supporter].contribution +=
267
281
  uint96(int96(_previousFlowRateOrAmount)) *
268
282
  (block.timestamp - _lastUpdated);
269
- newCtx = _takeFeeFlow(flowRate - int96(_previousFlowRateOrAmount), _ctx);
283
+ newCtx = HelperLibrary.takeFeeFlow(
284
+ cfaV1,
285
+ stats,
286
+ superToken,
287
+ getRegistry(),
288
+ flowRate - int96(_previousFlowRateOrAmount),
289
+ _ctx
290
+ );
270
291
  // we update the last rate after we do all changes to our own flows
271
292
  stats.lastIncomeRate = superToken.getNetFlowRate(address(this));
272
293
  } else {
@@ -284,51 +305,6 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
284
305
  );
285
306
  }
286
307
 
287
- // this should be called before any flow rate changes
288
- function _updateStats(uint256 _amount) internal {
289
- //use last rate before the current possible rate update
290
- stats.netIncome += uint96(stats.lastIncomeRate) * (block.timestamp - stats.lastUpdate);
291
- uint feeBps;
292
- if (address(getRegistry()) != address(0)) {
293
- feeBps = getRegistry().feeBps();
294
- //fees sent to last recipient, the flowRate to recipient still wasnt updated.
295
- stats.totalFees +=
296
- uint96(superToken.getFlowRate(address(this), stats.lastFeeRecipient)) *
297
- (block.timestamp - stats.lastUpdate);
298
- }
299
- if (_amount > 0) {
300
- stats.netIncome += (_amount * (10000 - feeBps)) / 10000;
301
- stats.totalFees += (_amount * feeBps) / 10000;
302
- }
303
- stats.lastUpdate = block.timestamp;
304
- }
305
-
306
- function _takeFeeFlow(int96 _diffRate, bytes memory _ctx) internal returns (bytes memory newCtx) {
307
- newCtx = _ctx;
308
- if (address(getRegistry()) == address(0)) return newCtx;
309
- address recipient = getRegistry().feeRecipient();
310
- int96 curFeeRate = superToken.getFlowRate(address(this), stats.lastFeeRecipient);
311
- bool newRecipient;
312
- if (recipient != stats.lastFeeRecipient) {
313
- newRecipient = true;
314
- if (stats.lastFeeRecipient != address(0)) {
315
- //delete old recipient flow
316
- if (curFeeRate > 0)
317
- newCtx = cfaV1.deleteFlowWithCtx(newCtx, address(this), stats.lastFeeRecipient, superToken); //passing in the ctx which is sent to the callback here
318
- }
319
- stats.lastFeeRecipient = recipient;
320
- }
321
- if (recipient == address(0)) return newCtx;
322
-
323
- int96 feeRateChange = (_diffRate * int32(getRegistry().feeBps())) / 10000;
324
- int96 newFeeRate = curFeeRate + feeRateChange;
325
- if (newFeeRate <= 0 && newRecipient == false) {
326
- newCtx = cfaV1.deleteFlowWithCtx(newCtx, address(this), recipient, superToken); //passing in the ctx which is sent to the callback here
327
- } else if (curFeeRate > 0 && newRecipient == false) {
328
- newCtx = cfaV1.updateFlowWithCtx(newCtx, recipient, superToken, newFeeRate); //passing in the ctx which is sent to the callback here
329
- } else if (newFeeRate > 0) newCtx = cfaV1.createFlowWithCtx(newCtx, recipient, superToken, newFeeRate); //passing in the ctx which is sent to the callback here
330
- }
331
-
332
308
  function _takeFeeSingle(uint256 _amount) internal {
333
309
  if (address(getRegistry()) == address(0)) return;
334
310
  address recipient = getRegistry().feeRecipient();
@@ -1,6 +1,12 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity >=0.8.0;
3
3
 
4
+ interface IRegistry {
5
+ function feeRecipient() external view returns (address);
6
+
7
+ function feeBps() external view returns (uint32);
8
+ }
9
+
4
10
  interface IGoodCollectiveSuperApp {
5
11
  struct Stats {
6
12
  uint256 netIncome; //without fees
@@ -28,11 +28,12 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
28
28
 
29
29
  error CLAIMFOR_DISABLED();
30
30
  error NOT_MEMBER(address claimer);
31
- error NOT_WHITELISTED(address claimer);
32
- error ALREADY_CLAIMED(address claimer);
31
+ error NOT_MANAGER(address manager);
32
+ error NOT_WHITELISTED(address whitelistedRoot);
33
+ error ALREADY_CLAIMED(address whitelistedRoot);
33
34
  error INVALID_0_VALUE();
34
35
  error EMPTY_MANAGER();
35
- error MAX_MEMBERS_REACHED();
36
+ error MAX_CLAIMERS_REACHED();
36
37
 
37
38
  bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE");
38
39
  bytes32 public constant MEMBER_ROLE = keccak256("MEMBER_ROLE");
@@ -70,7 +71,8 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
70
71
  // can you trigger claim for someone else
71
72
  bool claimForEnabled;
72
73
  uint maxClaimAmount;
73
- uint32 maxMembers;
74
+ uint32 maxClaimers;
75
+ bool onlyMembers;
74
76
  }
75
77
 
76
78
  struct PoolStatus {
@@ -88,7 +90,7 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
88
90
  uint256 periodClaimers;
89
91
  uint256 periodDistributed;
90
92
  mapping(address => uint256) lastClaimed;
91
- uint32 membersCount;
93
+ uint32 claimersCount;
92
94
  }
93
95
 
94
96
  PoolSettings public settings;
@@ -123,8 +125,9 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
123
125
  registry = _registry;
124
126
  settings = _settings;
125
127
  ubiSettings = _ubiSettings;
128
+ _verifyPoolSettings(_settings);
126
129
  _verifyUBISettings(_ubiSettings);
127
- _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
130
+ _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); // when using factory this gives factory role which then set role to the real msg.sender
128
131
  _setupRole(MANAGER_ROLE, _settings.manager);
129
132
  setSuperToken(ISuperToken(address(settings.rewardToken)));
130
133
  }
@@ -148,14 +151,15 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
148
151
  function distributionFormula() internal returns (uint256) {
149
152
  // once every claim cycle
150
153
  uint256 currentDay = getCurrentDay();
151
- if (currentDay > status.currentDay + ubiSettings.claimPeriodDays) {
154
+ if (currentDay >= status.currentDay + ubiSettings.claimPeriodDays) {
152
155
  status.currentDay = currentDay;
153
156
  uint32 cycleLength = ubiSettings.cycleLengthDays;
154
157
  uint256 currentBalance = settings.rewardToken.balanceOf(address(this));
155
158
  //start early cycle if daily pool size is +%5 previous pool or not enough until end of cycle
156
159
  uint256 nextDailyPool = currentBalance / cycleLength;
157
160
  bool shouldStartEarlyCycle = nextDailyPool > (status.dailyCyclePool * 105) / 100 ||
158
- currentBalance < (status.dailyCyclePool * (cycleLength - currentDayInCycle()));
161
+ (currentDayInCycle() <= cycleLength &&
162
+ currentBalance < (status.dailyCyclePool * (cycleLength - currentDayInCycle())));
159
163
 
160
164
  if (
161
165
  currentDayInCycle() >= status.currentCycleLength || shouldStartEarlyCycle
@@ -225,7 +229,12 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
225
229
  function _claim(address claimer, bool sendToWhitelistedRoot) internal {
226
230
  address whitelistedRoot = IIdentityV2(settings.uniquenessValidator).getWhitelistedRoot(claimer);
227
231
  if (whitelistedRoot == address(0)) revert NOT_WHITELISTED(claimer);
228
- if (address(settings.membersValidator) != address(0) && hasRole(MEMBER_ROLE, claimer) == false)
232
+
233
+ // if open for anyone but has limits, we add the first claimers as members to handle the max claimers
234
+ if ((ubiSettings.maxClaimers > 0 && ubiSettings.onlyMembers == false)) _grantRole(MEMBER_ROLE, claimer);
235
+
236
+ // check membership if has claimers limits or limited to members only
237
+ if ((ubiSettings.maxClaimers > 0 || ubiSettings.onlyMembers) && hasRole(MEMBER_ROLE, claimer) == false)
229
238
  revert NOT_MEMBER(claimer);
230
239
 
231
240
  // calculats the formula up today ie on day 0 there are no active users, on day 1 any user
@@ -246,18 +255,6 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
246
255
  emit UBIClaimed(whitelistedRoot, claimer, dailyUbi);
247
256
  }
248
257
 
249
- function addMemberByManager(address member) external onlyRole(MANAGER_ROLE) returns (bool) {
250
- if (hasRole(MEMBER_ROLE, member)) return true;
251
-
252
- if (address(settings.uniquenessValidator) != address(0)) {
253
- address rootAddress = settings.uniquenessValidator.getWhitelistedRoot(member);
254
- if (rootAddress == address(0)) revert NOT_WHITELISTED(member);
255
- }
256
-
257
- _grantRole(MEMBER_ROLE, member);
258
- return true;
259
- }
260
-
261
258
  /**
262
259
  * @dev Adds a member to the contract.
263
260
  * @param member The address of the member to add.
@@ -265,37 +262,43 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
265
262
  */
266
263
 
267
264
  function addMember(address member, bytes memory extraData) external returns (bool isMember) {
268
- if (hasRole(MEMBER_ROLE, member)) return true;
269
-
270
265
  if (address(settings.uniquenessValidator) != address(0)) {
271
266
  address rootAddress = settings.uniquenessValidator.getWhitelistedRoot(member);
272
267
  if (rootAddress == address(0)) revert NOT_WHITELISTED(member);
273
268
  }
274
269
 
275
- // if no members validator then anyone can join the pool
276
- if (address(settings.membersValidator) != address(0)) {
270
+ if (address(settings.membersValidator) != address(0) && hasRole(MANAGER_ROLE, msg.sender) == false) {
277
271
  if (settings.membersValidator.isMemberValid(address(this), msg.sender, member, extraData) == false) {
278
- return false;
272
+ revert NOT_MEMBER(member);
279
273
  }
280
274
  }
275
+ // if no members validator then if members only only manager can add members
276
+ else if (ubiSettings.onlyMembers && hasRole(MANAGER_ROLE, msg.sender) == false) {
277
+ revert NOT_MANAGER(member);
278
+ }
281
279
 
282
280
  _grantRole(MEMBER_ROLE, member);
283
281
  return true;
284
282
  }
285
283
 
284
+ function removeMember(address member) external onlyRole(MANAGER_ROLE) {
285
+ _revokeRole(MEMBER_ROLE, member);
286
+ }
287
+
286
288
  function _grantRole(bytes32 role, address account) internal virtual override {
287
- if (role == MEMBER_ROLE) {
289
+ if (role == MEMBER_ROLE && hasRole(MEMBER_ROLE, account) == false) {
290
+ if (ubiSettings.maxClaimers > 0 && status.claimersCount > ubiSettings.maxClaimers)
291
+ revert MAX_CLAIMERS_REACHED();
288
292
  registry.addMember(account);
289
- if (ubiSettings.maxMembers > 0 && status.membersCount > ubiSettings.maxMembers)
290
- revert MAX_MEMBERS_REACHED();
291
- status.membersCount += 1;
293
+ status.claimersCount += 1;
292
294
  }
293
295
  super._grantRole(role, account);
294
296
  }
295
297
 
296
298
  function _revokeRole(bytes32 role, address account) internal virtual override {
297
- if (role == MEMBER_ROLE) {
298
- status.membersCount -= 1;
299
+ if (role == MEMBER_ROLE && hasRole(MEMBER_ROLE, account)) {
300
+ status.claimersCount -= 1;
301
+ registry.removeMember(account);
299
302
  }
300
303
  super._revokeRole(role, account);
301
304
  }
@@ -324,7 +327,7 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
324
327
  * @param _settings The new pool settings.
325
328
  */
326
329
  function setPoolSettings(PoolSettings memory _settings) public onlyRole(MANAGER_ROLE) {
327
- if (_settings.manager == address(0)) revert EMPTY_MANAGER();
330
+ _verifyPoolSettings(_settings);
328
331
 
329
332
  if (_settings.manager != settings.manager) {
330
333
  _revokeRole(MANAGER_ROLE, settings.manager);
@@ -334,12 +337,21 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
334
337
  emit PoolSettingsChanged(_settings);
335
338
  }
336
339
 
340
+ function _verifyPoolSettings(PoolSettings memory _poolSettings) internal pure {
341
+ if (
342
+ _poolSettings.manager == address(0) ||
343
+ address(_poolSettings.uniquenessValidator) == address(0) ||
344
+ address(_poolSettings.rewardToken) == address(0)
345
+ ) revert INVALID_0_VALUE();
346
+ }
347
+
337
348
  function estimateNextDailyUBI() public view returns (uint256) {
338
349
  uint256 currentBalance = settings.rewardToken.balanceOf(address(this));
339
350
  //start early cycle if we can increase the daily UBI pool
340
351
  uint256 nextDailyPool = currentBalance / ubiSettings.cycleLengthDays;
341
352
  bool shouldStartEarlyCycle = nextDailyPool > (status.dailyCyclePool * 105) / 100 ||
342
- currentBalance < (status.dailyCyclePool * (status.currentCycleLength - currentDayInCycle()));
353
+ (currentDayInCycle() <= status.currentCycleLength &&
354
+ currentBalance < (status.dailyCyclePool * (status.currentCycleLength - currentDayInCycle())));
343
355
 
344
356
  uint256 _dailyCyclePool = status.dailyCyclePool;
345
357
  uint256 _dailyUbi;
@@ -379,10 +391,10 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
379
391
 
380
392
  function hasClaimed(address _member) public view returns (bool) {
381
393
  address whitelistedRoot = IIdentityV2(settings.uniquenessValidator).getWhitelistedRoot(_member);
382
- return status.lastClaimed[whitelistedRoot] == status.currentDay;
394
+ return status.lastClaimed[whitelistedRoot] == getCurrentDay();
383
395
  }
384
396
 
385
397
  function nextClaimTime() public view returns (uint256) {
386
- return getCurrentDay() * (1 days) - (12 hours);
398
+ return (getCurrentDay() + ubiSettings.claimPeriodDays) * (1 days) + 12 hours;
387
399
  }
388
400
  }
@@ -108,7 +108,7 @@ contract UBIPoolFactory is AccessControlUpgradeable, UUPSUpgradeable {
108
108
  ) internal returns (UBIPool pool) {
109
109
  //TODO: add check if msg.sender is whitelisted
110
110
 
111
- bytes memory initCall = abi.encodeWithSelector(UBIPool.initialize.selector, _settings, _limits, address(this));
111
+ bytes memory initCall = abi.encodeCall(UBIPool.initialize, (_settings, _limits, this));
112
112
 
113
113
  if (useBeacon) {
114
114
  pool = UBIPool(address(new BeaconProxy(address(impl), initCall)));
@@ -153,6 +153,15 @@ contract UBIPoolFactory is AccessControlUpgradeable, UUPSUpgradeable {
153
153
  memberPools[account].push(msg.sender);
154
154
  }
155
155
 
156
+ function removeMember(address member) external onlyPool {
157
+ for (uint i = 0; i < memberPools[member].length; i++) {
158
+ if (memberPools[member][i] == msg.sender) {
159
+ memberPools[member][i] = memberPools[member][memberPools[member].length - 1];
160
+ memberPools[member].pop();
161
+ }
162
+ }
163
+ }
164
+
156
165
  function getMemberPools(address member) external view returns (address[] memory) {
157
166
  return memberPools[member];
158
167
  }
@@ -0,0 +1,16 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ pragma solidity >=0.8.0;
4
+
5
+ import "../utils/HelperLibrary.sol";
6
+
7
+ contract HelperLibraryTest {
8
+ function handleSwap(
9
+ IV3SwapRouter swapRouter,
10
+ HelperLibrary.SwapData memory _customData,
11
+ address outTokenIfNoPath,
12
+ address _sender
13
+ ) external returns (uint256 amountOut) {
14
+ return HelperLibrary.handleSwap(swapRouter, _customData, outTokenIfNoPath, _sender, _sender);
15
+ }
16
+ }
@@ -6,11 +6,13 @@ import "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";
6
6
  import "@uniswap/swap-router-contracts/contracts/interfaces/IV3SwapRouter.sol";
7
7
  import { ISuperToken } from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol";
8
8
  import { SuperTokenV1Library } from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol";
9
+ import { CFAv1Library } from "@superfluid-finance/ethereum-contracts/contracts/apps/CFAv1Library.sol";
9
10
 
10
11
  import "../GoodCollective/IGoodCollectiveSuperApp.sol";
11
12
 
12
13
  library HelperLibrary {
13
14
  using SuperTokenV1Library for ISuperToken;
15
+ using CFAv1Library for CFAv1Library.InitData;
14
16
 
15
17
  /**
16
18
  * @dev A struct containing information about a token swap
@@ -33,7 +35,17 @@ library HelperLibrary {
33
35
  SwapData memory _customData,
34
36
  address outTokenIfNoPath,
35
37
  address _sender
36
- ) external {
38
+ ) external returns (uint256 amountOut) {
39
+ return handleSwap(swapRouter, _customData, outTokenIfNoPath, _sender, _sender);
40
+ }
41
+
42
+ function handleSwap(
43
+ IV3SwapRouter swapRouter,
44
+ SwapData memory _customData,
45
+ address outTokenIfNoPath,
46
+ address _sender,
47
+ address _recipient
48
+ ) public returns (uint256 amountOut) {
37
49
  // Transfer the tokens from the sender to this contract
38
50
  TransferHelper.safeTransferFrom(_customData.swapFrom, _sender, address(this), _customData.amount);
39
51
 
@@ -44,25 +56,25 @@ library HelperLibrary {
44
56
  // If a path is provided, execute a multi-hop swap
45
57
  IV3SwapRouter.ExactInputParams memory params = IV3SwapRouter.ExactInputParams({
46
58
  path: _customData.path,
47
- recipient: _sender,
59
+ recipient: _recipient,
48
60
  amountIn: _customData.amount,
49
61
  amountOutMinimum: _customData.minReturn
50
62
  });
51
- swapRouter.exactInput(params);
63
+ return swapRouter.exactInput(params);
52
64
  } else {
53
65
  // If no path is provided, execute a single-hop swap
54
66
  IV3SwapRouter.ExactInputSingleParams memory params = IV3SwapRouter.ExactInputSingleParams({
55
67
  tokenIn: _customData.swapFrom,
56
68
  tokenOut: outTokenIfNoPath,
57
69
  fee: 10000,
58
- recipient: _sender,
70
+ recipient: _recipient,
59
71
  amountIn: _customData.amount,
60
72
  amountOutMinimum: _customData.minReturn,
61
73
  sqrtPriceLimitX96: 0
62
74
  });
63
75
 
64
76
  // Execute the swap using `exactInputSingle`
65
- swapRouter.exactInputSingle(params);
77
+ return swapRouter.exactInputSingle(params);
66
78
  }
67
79
  }
68
80
 
@@ -78,4 +90,60 @@ library HelperLibrary {
78
90
  uint96(superToken.getFlowRate(address(this), stats.lastFeeRecipient)) *
79
91
  (block.timestamp - stats.lastUpdate);
80
92
  }
93
+
94
+ // this should be called before any flow rate changes
95
+ function updateStats(
96
+ IGoodCollectiveSuperApp.Stats storage stats,
97
+ ISuperToken superToken,
98
+ IRegistry registry,
99
+ uint256 _amount
100
+ ) external {
101
+ //use last rate before the current possible rate update
102
+ stats.netIncome += uint96(stats.lastIncomeRate) * (block.timestamp - stats.lastUpdate);
103
+ uint feeBps;
104
+ if (address(registry) != address(0)) {
105
+ feeBps = registry.feeBps();
106
+ //fees sent to last recipient, the flowRate to recipient still wasnt updated.
107
+ stats.totalFees +=
108
+ uint96(superToken.getFlowRate(address(this), stats.lastFeeRecipient)) *
109
+ (block.timestamp - stats.lastUpdate);
110
+ }
111
+ if (_amount > 0) {
112
+ stats.netIncome += (_amount * (10000 - feeBps)) / 10000;
113
+ stats.totalFees += (_amount * feeBps) / 10000;
114
+ }
115
+ stats.lastUpdate = block.timestamp;
116
+ }
117
+
118
+ function takeFeeFlow(
119
+ CFAv1Library.InitData storage cfaV1,
120
+ IGoodCollectiveSuperApp.Stats storage stats,
121
+ ISuperToken superToken,
122
+ IRegistry registry,
123
+ int96 _diffRate,
124
+ bytes memory _ctx
125
+ ) public returns (bytes memory newCtx) {
126
+ newCtx = _ctx;
127
+ if (address(registry) == address(0)) return newCtx;
128
+ address recipient = registry.feeRecipient();
129
+ int96 curFeeRate = superToken.getFlowRate(address(this), stats.lastFeeRecipient);
130
+ bool newRecipient;
131
+ if (recipient != stats.lastFeeRecipient) {
132
+ newRecipient = true;
133
+ if (stats.lastFeeRecipient != address(0)) {
134
+ //delete old recipient flow
135
+ if (curFeeRate > 0)
136
+ newCtx = cfaV1.deleteFlowWithCtx(newCtx, address(this), stats.lastFeeRecipient, superToken); //passing in the ctx which is sent to the callback here
137
+ }
138
+ stats.lastFeeRecipient = recipient;
139
+ }
140
+ if (recipient == address(0)) return newCtx;
141
+
142
+ int96 newFeeRate = curFeeRate + (_diffRate * int32(registry.feeBps())) / 10000;
143
+ if (newFeeRate <= 0 && newRecipient == false) {
144
+ newCtx = cfaV1.deleteFlowWithCtx(newCtx, address(this), recipient, superToken); //passing in the ctx which is sent to the callback here
145
+ } else if (curFeeRate > 0 && newRecipient == false) {
146
+ newCtx = cfaV1.updateFlowWithCtx(newCtx, recipient, superToken, newFeeRate); //passing in the ctx which is sent to the callback here
147
+ } else if (newFeeRate > 0) newCtx = cfaV1.createFlowWithCtx(newCtx, recipient, superToken, newFeeRate); //passing in the ctx which is sent to the callback here
148
+ }
81
149
  }
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@gooddollar/goodcollective-contracts",
3
3
  "packageManager": "yarn@3.2.1",
4
- "version": "1.1.0",
4
+ "version": "1.2.0",
5
5
  "license": "MIT",
6
6
  "types": "./typechain-types/index.ts",
7
7
  "files": [
8
8
  "contracts",
9
+ "artifacts/contracts",
9
10
  "typechain-types",
10
11
  "releases"
11
12
  ],
@@ -63,9 +64,9 @@
63
64
  "test": "npx hardhat test",
64
65
  "test:coverage": "npx hardhat coverage",
65
66
  "deploy": "hardhat deploy --export-all ./releases/deployment.json",
66
- "prepublish": "yarn version patch && yarn compile && git add package.json && git commit -m \"version bump\"",
67
+ "bump": "yarn version patch && yarn compile && git add package.json && git commit -m \"version bump\"",
67
68
  "publish": "yarn npm publish --access public",
68
69
  "test:setup": "yarn exec ./scripts/deployContracts.sh",
69
70
  "verify": "npx hardhat run scripts/verify.ts --network ${0}"
70
71
  }
71
- }
72
+ }