@gooddollar/goodcollective-contracts 1.2.0 → 1.3.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 (45) hide show
  1. package/contracts/DirectPayments/DirectPaymentsFactory.sol +12 -7
  2. package/contracts/DirectPayments/DirectPaymentsLibrary.sol +54 -0
  3. package/contracts/DirectPayments/DirectPaymentsPool.sol +13 -38
  4. package/contracts/GoodCollective/GoodCollectiveSuperApp.sol +65 -13
  5. package/contracts/GoodCollective/IGoodCollectiveSuperApp.sol +6 -0
  6. package/contracts/Interfaces.sol +25 -0
  7. package/contracts/UBI/UBIPool.sol +76 -75
  8. package/contracts/UBI/UBIPoolFactory.sol +15 -10
  9. package/contracts/utils/HelperLibrary.sol +56 -23
  10. package/package.json +1 -1
  11. package/releases/deployment.json +4836 -3746
  12. package/typechain-types/contracts/DirectPayments/DirectPaymentsFactory.ts +16 -4
  13. package/typechain-types/contracts/DirectPayments/DirectPaymentsPool.sol/DirectPaymentsPool.ts +138 -9
  14. package/typechain-types/contracts/GoodCollective/GoodCollectiveSuperApp.ts +101 -6
  15. package/typechain-types/contracts/{DirectPayments/DirectPaymentsFactory.sol/IRegistry.ts → GoodCollective/IGoodCollectiveSuperApp.sol/IGoodCollectiveSuperApp.ts} +18 -27
  16. package/typechain-types/contracts/GoodCollective/IGoodCollectiveSuperApp.sol/index.ts +1 -0
  17. package/typechain-types/contracts/{UBI/UBIPool.sol → Interfaces.sol}/IIdentityV2.ts +1 -1
  18. package/typechain-types/contracts/{UBI/UBIPool.sol → Interfaces.sol}/IMembersValidator.ts +1 -1
  19. package/typechain-types/contracts/{UBI/UBIPool.sol → Interfaces.sol}/index.ts +0 -1
  20. package/typechain-types/contracts/UBI/{UBIPool.sol/UBIPool.ts → UBIPool.ts} +239 -47
  21. package/typechain-types/contracts/UBI/UBIPoolFactory.ts +58 -34
  22. package/typechain-types/contracts/UBI/index.ts +1 -2
  23. package/typechain-types/contracts/index.ts +2 -0
  24. package/typechain-types/contracts/utils/HelperLibrary.ts +46 -4
  25. package/typechain-types/factories/contracts/DirectPayments/DirectPaymentsFactory__factory.ts +11 -1
  26. package/typechain-types/factories/contracts/DirectPayments/DirectPaymentsPool.sol/DirectPaymentsPool__factory.ts +72 -1
  27. package/typechain-types/factories/contracts/GoodCollective/GoodCollectiveSuperApp__factory.ts +48 -0
  28. package/typechain-types/factories/contracts/{DirectPayments/DirectPaymentsFactory.sol/IRegistry__factory.ts → GoodCollective/IGoodCollectiveSuperApp.sol/IGoodCollectiveSuperApp__factory.ts} +19 -23
  29. package/typechain-types/factories/contracts/GoodCollective/IGoodCollectiveSuperApp.sol/index.ts +1 -0
  30. package/typechain-types/factories/contracts/{UBI/UBIPool.sol → Interfaces.sol}/IIdentityV2__factory.ts +1 -1
  31. package/typechain-types/factories/contracts/{UBI/UBIPool.sol → Interfaces.sol}/IMembersValidator__factory.ts +1 -1
  32. package/typechain-types/factories/contracts/{UBI/UBIPool.sol → Interfaces.sol}/index.ts +0 -1
  33. package/typechain-types/factories/contracts/UBI/UBIPoolFactory__factory.ts +51 -7
  34. package/typechain-types/factories/contracts/UBI/UBIPool__factory.ts +2052 -0
  35. package/typechain-types/factories/contracts/UBI/index.ts +1 -1
  36. package/typechain-types/factories/contracts/index.ts +1 -0
  37. package/typechain-types/factories/contracts/test/HelperLibraryTest__factory.ts +1 -1
  38. package/typechain-types/factories/contracts/utils/HelperLibrary__factory.ts +31 -1
  39. package/typechain-types/hardhat.d.ts +18 -9
  40. package/typechain-types/index.ts +4 -2
  41. package/typechain-types/contracts/DirectPayments/DirectPaymentsFactory.sol/DirectPaymentsFactory.ts +0 -1327
  42. package/typechain-types/contracts/DirectPayments/DirectPaymentsFactory.sol/index.ts +0 -5
  43. package/typechain-types/factories/contracts/DirectPayments/DirectPaymentsFactory.sol/DirectPaymentsFactory__factory.ts +0 -1004
  44. package/typechain-types/factories/contracts/DirectPayments/DirectPaymentsFactory.sol/index.ts +0 -5
  45. package/typechain-types/factories/contracts/UBI/UBIPool.sol/UBIPool__factory.ts +0 -1916
@@ -11,7 +11,7 @@ import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
11
11
  import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
12
12
  import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
13
13
 
14
- import "hardhat/console.sol";
14
+ // import "hardhat/console.sol";
15
15
 
16
16
  contract DirectPaymentsFactory is AccessControlUpgradeable, UUPSUpgradeable {
17
17
  error NOT_PROJECT_OWNER();
@@ -98,18 +98,20 @@ contract DirectPaymentsFactory is AccessControlUpgradeable, UUPSUpgradeable {
98
98
  string memory _projectId,
99
99
  string memory _ipfs,
100
100
  DirectPaymentsPool.PoolSettings memory _settings,
101
- DirectPaymentsPool.SafetyLimits memory _limits
101
+ DirectPaymentsPool.SafetyLimits memory _limits,
102
+ uint32 _managerFeeBps
102
103
  ) external onlyProjectOwnerOrNon(_projectId) returns (DirectPaymentsPool pool) {
103
- return _createPool(_projectId, _ipfs, _settings, _limits, true);
104
+ return _createPool(_projectId, _ipfs, _settings, _limits, _managerFeeBps, true);
104
105
  }
105
106
 
106
107
  function createPool(
107
108
  string memory _projectId,
108
109
  string memory _ipfs,
109
110
  DirectPaymentsPool.PoolSettings memory _settings,
110
- DirectPaymentsPool.SafetyLimits memory _limits
111
+ DirectPaymentsPool.SafetyLimits memory _limits,
112
+ uint32 _managerFeeBps
111
113
  ) external onlyProjectOwnerOrNon(_projectId) returns (DirectPaymentsPool pool) {
112
- return _createPool(_projectId, _ipfs, _settings, _limits, false);
114
+ return _createPool(_projectId, _ipfs, _settings, _limits, _managerFeeBps, false);
113
115
  }
114
116
 
115
117
  function _createPool(
@@ -117,12 +119,16 @@ contract DirectPaymentsFactory is AccessControlUpgradeable, UUPSUpgradeable {
117
119
  string memory _ipfs,
118
120
  DirectPaymentsPool.PoolSettings memory _settings,
119
121
  DirectPaymentsPool.SafetyLimits memory _limits,
122
+ uint32 _managerFeeBps,
120
123
  bool useBeacon
121
124
  ) internal returns (DirectPaymentsPool pool) {
122
125
  //TODO: add check if msg.sender is whitelisted
123
126
 
124
127
  _settings.nftType = nextNftType;
125
- bytes memory initCall = abi.encodeCall(DirectPaymentsPool.initialize, (nft, _settings, _limits, this));
128
+ bytes memory initCall = abi.encodeCall(
129
+ DirectPaymentsPool.initialize,
130
+ (nft, _settings, _limits, _managerFeeBps, this)
131
+ );
126
132
 
127
133
  if (useBeacon) {
128
134
  pool = DirectPaymentsPool(address(new BeaconProxy(address(impl), initCall)));
@@ -130,7 +136,6 @@ contract DirectPaymentsFactory is AccessControlUpgradeable, UUPSUpgradeable {
130
136
  pool = DirectPaymentsPool(address(new ERC1967Proxy(impl.implementation(), initCall)));
131
137
  }
132
138
 
133
- nft.grantRole(nft.getManagerRole(nextNftType), _settings.manager);
134
139
  nft.grantRole(nft.getManagerRole(nextNftType), address(pool));
135
140
 
136
141
  //access control to project is determinted by the first pool access control rules
@@ -0,0 +1,54 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity >=0.8.0;
3
+
4
+ import "./DirectPaymentsPool.sol";
5
+
6
+ library DirectPayemntsLibrary {
7
+ function _updateMemberLimits(
8
+ DirectPaymentsPool.LimitsData storage memberStats,
9
+ uint128 reward,
10
+ uint64 curMonth
11
+ ) internal {
12
+ if (memberStats.lastReward + 60 * 60 * 24 < block.timestamp) //more than a day passed since last reward
13
+ {
14
+ memberStats.daily = reward;
15
+ } else {
16
+ memberStats.daily += reward;
17
+ }
18
+
19
+ if (memberStats.lastMonth < curMonth) //month switched
20
+ {
21
+ memberStats.monthly = reward;
22
+ } else {
23
+ memberStats.monthly += reward;
24
+ }
25
+
26
+ memberStats.total += reward;
27
+ memberStats.lastReward = uint64(block.timestamp);
28
+ memberStats.lastMonth = curMonth;
29
+ }
30
+
31
+ function _updateGlobalLimits(
32
+ DirectPaymentsPool.LimitsData storage globalLimits,
33
+ uint128 reward,
34
+ uint64 curMonth
35
+ ) internal {
36
+ if (globalLimits.lastReward + 60 * 60 * 24 < block.timestamp) //more than a day passed since last reward
37
+ {
38
+ globalLimits.daily = reward;
39
+ } else {
40
+ globalLimits.daily += reward;
41
+ }
42
+
43
+ if (globalLimits.lastMonth < curMonth) //month switched
44
+ {
45
+ globalLimits.monthly = reward;
46
+ } else {
47
+ globalLimits.monthly += reward;
48
+ }
49
+
50
+ globalLimits.total += reward;
51
+ globalLimits.lastReward = uint64(block.timestamp);
52
+ globalLimits.lastMonth = curMonth;
53
+ }
54
+ }
@@ -9,6 +9,7 @@ import { IERC721ReceiverUpgradeable } from "@openzeppelin/contracts-upgradeable/
9
9
 
10
10
  import { ProvableNFT } from "./ProvableNFT.sol";
11
11
  import { DirectPaymentsFactory } from "./DirectPaymentsFactory.sol";
12
+ import { DirectPayemntsLibrary } from "./DirectPaymentsLibrary.sol";
12
13
  import "../GoodCollective/GoodCollectiveSuperApp.sol";
13
14
 
14
15
  interface IMembersValidator {
@@ -111,6 +112,8 @@ contract DirectPaymentsPool is
111
112
  LimitsData public globalLimits;
112
113
  DirectPaymentsFactory public registry;
113
114
 
115
+ uint32 public managerFeeBps;
116
+
114
117
  /// @custom:oz-upgrades-unsafe-allow constructor
115
118
  constructor(ISuperfluid _host, IV3SwapRouter _swapRouter) GoodCollectiveSuperApp(_host, _swapRouter) {}
116
119
 
@@ -124,6 +127,10 @@ contract DirectPaymentsPool is
124
127
  return IRegistry(address(registry));
125
128
  }
126
129
 
130
+ function getManagerFee() public view override returns (address feeRecipient, uint32 feeBps) {
131
+ return (settings.manager, managerFeeBps);
132
+ }
133
+
127
134
  /**
128
135
  * @dev Initializes the contract with the given settings and limits.
129
136
  * @param _nft The ProvableNFT contract address.
@@ -134,12 +141,14 @@ contract DirectPaymentsPool is
134
141
  ProvableNFT _nft,
135
142
  PoolSettings memory _settings,
136
143
  SafetyLimits memory _limits,
144
+ uint32 _managerFeeBps,
137
145
  DirectPaymentsFactory _registry
138
146
  ) external initializer {
139
147
  registry = _registry;
140
148
  settings = _settings;
141
149
  limits = _limits;
142
150
  nft = _nft;
151
+ managerFeeBps = _managerFeeBps;
143
152
  _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); // when using factory this gives factory role which then set role to the real msg.sender
144
153
  _setupRole(MANAGER_ROLE, _settings.manager);
145
154
  _setupRole(MINTER_ROLE, _settings.manager);
@@ -259,24 +268,7 @@ contract DirectPaymentsPool is
259
268
  return false;
260
269
  }
261
270
 
262
- uint64 curMonth = _month();
263
- if (memberLimits[member].lastReward + 60 * 60 * 24 < block.timestamp) //more than a day passed since last reward
264
- {
265
- memberLimits[member].daily = reward;
266
- } else {
267
- memberLimits[member].daily += reward;
268
- }
269
-
270
- if (memberLimits[member].lastMonth < curMonth) //month switched
271
- {
272
- memberLimits[member].monthly = reward;
273
- } else {
274
- memberLimits[member].monthly += reward;
275
- }
276
-
277
- memberLimits[member].total += reward;
278
- memberLimits[member].lastReward = uint64(block.timestamp);
279
- memberLimits[member].lastMonth = curMonth;
271
+ DirectPayemntsLibrary._updateMemberLimits(memberLimits[member], reward, _month());
280
272
 
281
273
  if (
282
274
  memberLimits[member].daily > limits.maxMemberPerDay ||
@@ -291,25 +283,7 @@ contract DirectPaymentsPool is
291
283
  * @param reward The amount of rewards to enforce and update limits for.
292
284
  */
293
285
  function _enforceAndUpdateGlobalLimits(uint128 reward) internal {
294
- uint64 curMonth = _month();
295
-
296
- if (globalLimits.lastReward + 60 * 60 * 24 < block.timestamp) //more than a day passed since last reward
297
- {
298
- globalLimits.daily = reward;
299
- } else {
300
- globalLimits.daily += reward;
301
- }
302
-
303
- if (globalLimits.lastMonth < curMonth) //month switched
304
- {
305
- globalLimits.monthly = reward;
306
- } else {
307
- globalLimits.monthly += reward;
308
- }
309
-
310
- globalLimits.total += reward;
311
- globalLimits.lastReward = uint64(block.timestamp);
312
- globalLimits.lastMonth = curMonth;
286
+ DirectPayemntsLibrary._updateGlobalLimits(globalLimits, reward, _month());
313
287
 
314
288
  if (globalLimits.monthly > limits.maxTotalPerMonth) revert OVER_GLOBAL_LIMITS();
315
289
  }
@@ -409,7 +383,8 @@ contract DirectPaymentsPool is
409
383
  * @dev Sets the settings for the pool.
410
384
  * @param _settings The new pool settings.
411
385
  */
412
- function setPoolSettings(PoolSettings memory _settings) public onlyRole(MANAGER_ROLE) {
386
+ function setPoolSettings(PoolSettings memory _settings, uint32 _managerFeeBps) public onlyRole(MANAGER_ROLE) {
387
+ managerFeeBps = _managerFeeBps;
413
388
  if (_settings.nftType != settings.nftType) revert NFTTYPE_CHANGED();
414
389
  if (_settings.manager == address(0)) revert EMPTY_MANAGER();
415
390
 
@@ -14,6 +14,8 @@ 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
+
17
19
  abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
18
20
  int96 public constant MIN_FLOW_RATE = 386e9;
19
21
 
@@ -66,7 +68,7 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
66
68
 
67
69
  IGoodCollectiveSuperApp.Stats public stats;
68
70
 
69
- uint256[48] private _reserved;
71
+ uint256[45] private _reserved;
70
72
 
71
73
  /// @custom:oz-upgrades-unsafe-allow constructor
72
74
  constructor(ISuperfluid _host, IV3SwapRouter _swapRouter) SuperAppBaseFlow(_host) {
@@ -76,6 +78,8 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
76
78
 
77
79
  function getRegistry() public view virtual returns (IRegistry);
78
80
 
81
+ function getManagerFee() public view virtual returns (address admin, uint32 feeBps);
82
+
79
83
  /**
80
84
  * @dev Sets the address of the super token and registers the app with the host
81
85
  * @param _superToken The address of the super token contract
@@ -114,7 +118,15 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
114
118
  function getRealtimeStats()
115
119
  public
116
120
  view
117
- returns (uint256 netIncome, uint256 totalFees, int96 incomeFlowRate, int96 feeRate)
121
+ returns (
122
+ uint256 netIncome,
123
+ uint256 totalFees,
124
+ uint256 protocolFees,
125
+ uint256 managerFees,
126
+ int96 incomeFlowRate,
127
+ int96 feeRate,
128
+ int96 managerFeeRate
129
+ )
118
130
  {
119
131
  return HelperLibrary.getRealtimeStats(stats, superToken);
120
132
  }
@@ -249,6 +261,7 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
249
261
  bytes calldata _ctx
250
262
  ) internal virtual override returns (bytes memory /*newCtx*/) {
251
263
  // Update the supporter's information
264
+
252
265
  return _updateSupporter(_sender, _previousFlowRate, _lastUpdated, _ctx);
253
266
  }
254
267
 
@@ -267,10 +280,18 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
267
280
  ) internal returns (bytes memory newCtx) {
268
281
  newCtx = _ctx;
269
282
  bool _isFlow = _ctx.length > 0;
270
- HelperLibrary.updateStats(stats, superToken, getRegistry(), _isFlow ? 0 : uint256(_previousFlowRateOrAmount));
283
+ (address feeRecipient, uint32 feeBps) = getManagerFee();
284
+ HelperLibrary.updateStats(
285
+ stats,
286
+ superToken,
287
+ getRegistry(),
288
+ feeBps,
289
+ _isFlow ? 0 : uint256(_previousFlowRateOrAmount)
290
+ );
271
291
  // Get the current flow rate for the supporter
272
292
  int96 flowRate = superToken.getFlowRate(_supporter, address(this));
273
293
  uint256 prevContribution = supporters[_supporter].contribution;
294
+
274
295
  if (_isFlow) {
275
296
  //enforce minimal flow rate
276
297
  if (flowRate > 0 && flowRate < MIN_FLOW_RATE) revert MIN_FLOWRATE(flowRate);
@@ -278,21 +299,54 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
278
299
  supporters[_supporter].lastUpdated = uint128(block.timestamp);
279
300
  supporters[_supporter].flowRate = flowRate;
280
301
  supporters[_supporter].contribution +=
281
- uint96(int96(_previousFlowRateOrAmount)) *
302
+ uint256(_previousFlowRateOrAmount) *
282
303
  (block.timestamp - _lastUpdated);
304
+
305
+ // address feeRecipient;
306
+ // uint32 feeBps;
307
+ if (address(getRegistry()) != address(0)) {
308
+ feeRecipient = getRegistry().feeRecipient();
309
+ feeBps = getRegistry().feeBps();
310
+ // console.log("taking fees %s %s", feeRecipient, feeBps);
311
+
312
+ newCtx = HelperLibrary.takeFeeFlow(
313
+ cfaV1,
314
+ superToken,
315
+ stats.lastFeeRecipient,
316
+ feeRecipient,
317
+ feeBps,
318
+ flowRate - int96(_previousFlowRateOrAmount), // we use diff, because manager takes fee from many streams not just this one
319
+ newCtx
320
+ );
321
+ stats.lastFeeRecipient = feeRecipient;
322
+ }
323
+ // console.log("protocol fee stream ok");
324
+ (feeRecipient, feeBps) = getManagerFee();
325
+
283
326
  newCtx = HelperLibrary.takeFeeFlow(
284
327
  cfaV1,
285
- stats,
286
328
  superToken,
287
- getRegistry(),
288
- flowRate - int96(_previousFlowRateOrAmount),
289
- _ctx
329
+ stats.lastManagerFeeRecipient,
330
+ feeRecipient,
331
+ feeBps,
332
+ flowRate - int96(_previousFlowRateOrAmount), // we use diff, because manager takes fee from many streams not just this one
333
+ newCtx
290
334
  );
335
+
336
+ stats.lastManagerFeeRecipient = feeRecipient;
337
+ // console.log("admin fee stream ok");
291
338
  // we update the last rate after we do all changes to our own flows
292
339
  stats.lastIncomeRate = superToken.getNetFlowRate(address(this));
293
340
  } else {
341
+ if (address(getRegistry()) != address(0)) {
342
+ feeRecipient = getRegistry().feeRecipient();
343
+ feeBps = getRegistry().feeBps();
344
+ _takeFeeSingle(feeRecipient, feeBps, uint256(_previousFlowRateOrAmount));
345
+ }
346
+ (feeRecipient, feeBps) = getManagerFee();
347
+ _takeFeeSingle(feeRecipient, feeBps, uint256(_previousFlowRateOrAmount));
348
+
294
349
  supporters[_supporter].contribution += uint256(_previousFlowRateOrAmount);
295
- _takeFeeSingle(uint256(_previousFlowRateOrAmount));
296
350
  }
297
351
 
298
352
  emit SupporterUpdated(
@@ -305,12 +359,10 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
305
359
  );
306
360
  }
307
361
 
308
- function _takeFeeSingle(uint256 _amount) internal {
309
- if (address(getRegistry()) == address(0)) return;
310
- address recipient = getRegistry().feeRecipient();
362
+ function _takeFeeSingle(address recipient, uint32 feeBps, uint256 _amount) internal {
311
363
  if (recipient == address(0)) return;
312
364
 
313
- uint256 fee = (_amount * getRegistry().feeBps()) / 10000;
365
+ uint256 fee = (_amount * feeBps) / 10000;
314
366
  TransferHelper.safeTransfer(address(superToken), recipient, fee);
315
367
  }
316
368
 
@@ -14,5 +14,11 @@ interface IGoodCollectiveSuperApp {
14
14
  uint256 lastUpdate;
15
15
  address lastFeeRecipient;
16
16
  int96 lastIncomeRate;
17
+ address lastManagerFeeRecipient;
18
+ uint256 protocolFees;
19
+ uint256 managerFees;
20
+ // adding fields MUST update GoodCollectiveSuperApp storage layout
17
21
  }
22
+
23
+ function getAdminFee() external view returns (address admin, uint32 feeBps);
18
24
  }
@@ -0,0 +1,25 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ pragma solidity >=0.8.0;
4
+
5
+ import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
6
+
7
+ interface IMembersValidator {
8
+ function isMemberValid(
9
+ address pool,
10
+ address operator,
11
+ address member,
12
+ bytes memory extraData
13
+ ) external returns (bool);
14
+ }
15
+
16
+ interface IIdentityV2 {
17
+ function getWhitelistedRoot(address member) external view returns (address);
18
+ }
19
+
20
+ struct PoolSettings {
21
+ address manager;
22
+ IMembersValidator membersValidator;
23
+ IIdentityV2 uniquenessValidator;
24
+ IERC20Upgradeable rewardToken;
25
+ }
@@ -9,19 +9,7 @@ import { IERC721ReceiverUpgradeable } from "@openzeppelin/contracts-upgradeable/
9
9
 
10
10
  import "../GoodCollective/GoodCollectiveSuperApp.sol";
11
11
  import "./UBIPoolFactory.sol";
12
-
13
- interface IMembersValidator {
14
- function isMemberValid(
15
- address pool,
16
- address operator,
17
- address member,
18
- bytes memory extraData
19
- ) external returns (bool);
20
- }
21
-
22
- interface IIdentityV2 {
23
- function getWhitelistedRoot(address member) external view returns (address);
24
- }
12
+ import "../Interfaces.sol";
25
13
 
26
14
  contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgradeable {
27
15
  using SafeERC20Upgradeable for IERC20Upgradeable;
@@ -33,7 +21,8 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
33
21
  error ALREADY_CLAIMED(address whitelistedRoot);
34
22
  error INVALID_0_VALUE();
35
23
  error EMPTY_MANAGER();
36
- error MAX_CLAIMERS_REACHED();
24
+ error MAX_MEMBERS_REACHED();
25
+ error MAX_PERIOD_CLAIMERS_REACHED(uint256 claimers);
37
26
 
38
27
  bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE");
39
28
  bytes32 public constant MEMBER_ROLE = keccak256("MEMBER_ROLE");
@@ -53,13 +42,6 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
53
42
  event UBICycleCalculated(uint256 day, uint256 pool, uint256 cycleLength, uint256 dailyUBIPool);
54
43
 
55
44
  event UBIClaimed(address indexed whitelistedRoot, address indexed claimer, uint256 amount);
56
- // Define functions
57
- struct PoolSettings {
58
- address manager;
59
- IMembersValidator membersValidator;
60
- IIdentityV2 uniquenessValidator;
61
- IERC20Upgradeable rewardToken;
62
- }
63
45
 
64
46
  struct UBISettings {
65
47
  //number of days of each UBI pool cycle
@@ -70,8 +52,10 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
70
52
  uint32 minActiveUsers;
71
53
  // can you trigger claim for someone else
72
54
  bool claimForEnabled;
55
+ // max daily claim amount
73
56
  uint maxClaimAmount;
74
- uint32 maxClaimers;
57
+ // max number of members in a pool
58
+ uint32 maxMembers;
75
59
  bool onlyMembers;
76
60
  }
77
61
 
@@ -90,7 +74,16 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
90
74
  uint256 periodClaimers;
91
75
  uint256 periodDistributed;
92
76
  mapping(address => uint256) lastClaimed;
93
- uint32 claimersCount;
77
+ uint32 membersCount;
78
+ }
79
+
80
+ struct ExtendedSettings {
81
+ // max number of members that can claim in a day maxPeriodClaimers <= maxMembers
82
+ uint32 maxPeriodClaimers;
83
+ // min daily claim amount, daily amount will be 0 if <minClaimAmount
84
+ uint minClaimAmount;
85
+ // fees taken from income to the pool manager
86
+ uint32 managerFeeBps;
94
87
  }
95
88
 
96
89
  PoolSettings public settings;
@@ -98,6 +91,7 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
98
91
  PoolStatus public status;
99
92
 
100
93
  UBIPoolFactory public registry;
94
+ ExtendedSettings public extendedSettings;
101
95
 
102
96
  /// @custom:oz-upgrades-unsafe-allow constructor
103
97
  constructor(ISuperfluid _host, IV3SwapRouter _swapRouter) GoodCollectiveSuperApp(_host, _swapRouter) {}
@@ -112,6 +106,10 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
112
106
  return IRegistry(address(registry));
113
107
  }
114
108
 
109
+ function getManagerFee() public view override returns (address feeRecipient, uint32 feeBps) {
110
+ return (settings.manager, extendedSettings.managerFeeBps);
111
+ }
112
+
115
113
  /**
116
114
  * @dev Initializes the contract with the given settings and limits.
117
115
  * @param _settings The PoolSettings struct containing pool settings.
@@ -120,11 +118,13 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
120
118
  function initialize(
121
119
  PoolSettings memory _settings,
122
120
  UBISettings memory _ubiSettings,
121
+ ExtendedSettings memory _extendedSettings,
123
122
  UBIPoolFactory _registry
124
123
  ) external initializer {
125
124
  registry = _registry;
126
125
  settings = _settings;
127
126
  ubiSettings = _ubiSettings;
127
+ extendedSettings = _extendedSettings;
128
128
  _verifyPoolSettings(_settings);
129
129
  _verifyUBISettings(_ubiSettings);
130
130
  _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); // when using factory this gives factory role which then set role to the real msg.sender
@@ -148,36 +148,22 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
148
148
  * the daily balance is determined by dividing current pool by the cycle length
149
149
  * @return The amount of GoodDollar the user can claim
150
150
  */
151
- function distributionFormula() internal returns (uint256) {
151
+ function distributionFormula() public returns (uint256) {
152
152
  // once every claim cycle
153
153
  uint256 currentDay = getCurrentDay();
154
154
  if (currentDay >= status.currentDay + ubiSettings.claimPeriodDays) {
155
- status.currentDay = currentDay;
156
- uint32 cycleLength = ubiSettings.cycleLengthDays;
157
- uint256 currentBalance = settings.rewardToken.balanceOf(address(this));
158
- //start early cycle if daily pool size is +%5 previous pool or not enough until end of cycle
159
- uint256 nextDailyPool = currentBalance / cycleLength;
160
- bool shouldStartEarlyCycle = nextDailyPool > (status.dailyCyclePool * 105) / 100 ||
161
- (currentDayInCycle() <= cycleLength &&
162
- currentBalance < (status.dailyCyclePool * (cycleLength - currentDayInCycle())));
163
-
164
- if (
165
- currentDayInCycle() >= status.currentCycleLength || shouldStartEarlyCycle
166
- ) //start of cycle or first time
167
- {
155
+ (uint256 nextDailyPool, uint256 nextDailyUbi, bool newCycle) = _calcNextDailyUBI();
156
+ if (newCycle) {
157
+ uint256 currentBalance = settings.rewardToken.balanceOf(address(this));
168
158
  status.dailyCyclePool = nextDailyPool;
169
- status.currentCycleLength = cycleLength;
159
+ status.currentCycleLength = ubiSettings.cycleLengthDays;
170
160
  status.startOfCycle = currentDay;
171
- emit UBICycleCalculated(currentDay, currentBalance, cycleLength, nextDailyPool);
161
+ emit UBICycleCalculated(currentDay, currentBalance, ubiSettings.cycleLengthDays, nextDailyPool);
172
162
  }
173
163
 
174
- uint256 prevPeriodClaimers = status.periodClaimers;
175
- status.dailyUbi = min(
176
- ubiSettings.maxClaimAmount,
177
- status.dailyCyclePool / max((prevPeriodClaimers * 10500) / 10000, ubiSettings.minActiveUsers)
178
- );
179
- //update minActiveUsers as claimers grow
180
- ubiSettings.minActiveUsers = uint32(max(prevPeriodClaimers / 2, ubiSettings.minActiveUsers));
164
+ status.currentDay = currentDay;
165
+ status.dailyUbi = nextDailyUbi;
166
+ if (status.dailyUbi <= extendedSettings.minClaimAmount) status.dailyUbi = 0;
181
167
 
182
168
  emit UBICalculated(
183
169
  currentDay,
@@ -193,6 +179,32 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
193
179
  return status.dailyUbi;
194
180
  }
195
181
 
182
+ function _calcNextDailyUBI() internal view returns (uint256 nextPeriodPool, uint256 nextDailyUbi, bool newCycle) {
183
+ uint256 currentBalance = settings.rewardToken.balanceOf(address(this));
184
+ //start early cycle if we can increase the daily UBI pool
185
+ uint256 nextDailyPool = currentBalance / ubiSettings.cycleLengthDays;
186
+ bool shouldStartEarlyCycle = nextDailyPool > (status.dailyCyclePool * 105) / 100 ||
187
+ (currentDayInCycle() <= status.currentCycleLength &&
188
+ currentBalance < (status.dailyCyclePool * (status.currentCycleLength - currentDayInCycle())));
189
+
190
+ nextPeriodPool = status.dailyCyclePool;
191
+ nextDailyUbi;
192
+ if (
193
+ (currentDayInCycle() + 1) >= status.currentCycleLength || shouldStartEarlyCycle
194
+ ) //start of cycle or first time
195
+ {
196
+ nextPeriodPool = currentBalance / ubiSettings.cycleLengthDays;
197
+ newCycle = true;
198
+ }
199
+
200
+ nextDailyUbi = min(
201
+ ubiSettings.maxClaimAmount,
202
+ nextPeriodPool / max((status.periodClaimers * 10500) / 10000, ubiSettings.minActiveUsers)
203
+ );
204
+
205
+ if (nextDailyUbi < extendedSettings.minClaimAmount) nextDailyUbi = 0;
206
+ }
207
+
196
208
  /**
197
209
  * @dev returns the day count since start of current cycle
198
210
  */
@@ -231,12 +243,15 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
231
243
  if (whitelistedRoot == address(0)) revert NOT_WHITELISTED(claimer);
232
244
 
233
245
  // 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);
246
+ if ((ubiSettings.maxMembers > 0 && ubiSettings.onlyMembers == false)) _grantRole(MEMBER_ROLE, claimer);
235
247
 
236
248
  // check membership if has claimers limits or limited to members only
237
- if ((ubiSettings.maxClaimers > 0 || ubiSettings.onlyMembers) && hasRole(MEMBER_ROLE, claimer) == false)
249
+ if ((ubiSettings.maxMembers > 0 || ubiSettings.onlyMembers) && hasRole(MEMBER_ROLE, claimer) == false)
238
250
  revert NOT_MEMBER(claimer);
239
251
 
252
+ if (extendedSettings.maxPeriodClaimers > 0 && status.periodClaimers >= extendedSettings.maxPeriodClaimers)
253
+ revert MAX_PERIOD_CLAIMERS_REACHED(status.periodClaimers);
254
+
240
255
  // calculats the formula up today ie on day 0 there are no active users, on day 1 any user
241
256
  // (new or active) will trigger the calculation with the active users count of the day before
242
257
  // and so on. the new or inactive users that will become active today, will not take into account
@@ -287,17 +302,17 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
287
302
 
288
303
  function _grantRole(bytes32 role, address account) internal virtual override {
289
304
  if (role == MEMBER_ROLE && hasRole(MEMBER_ROLE, account) == false) {
290
- if (ubiSettings.maxClaimers > 0 && status.claimersCount > ubiSettings.maxClaimers)
291
- revert MAX_CLAIMERS_REACHED();
305
+ if (ubiSettings.maxMembers > 0 && status.membersCount > ubiSettings.maxMembers)
306
+ revert MAX_MEMBERS_REACHED();
292
307
  registry.addMember(account);
293
- status.claimersCount += 1;
308
+ status.membersCount += 1;
294
309
  }
295
310
  super._grantRole(role, account);
296
311
  }
297
312
 
298
313
  function _revokeRole(bytes32 role, address account) internal virtual override {
299
314
  if (role == MEMBER_ROLE && hasRole(MEMBER_ROLE, account)) {
300
- status.claimersCount -= 1;
315
+ status.membersCount -= 1;
301
316
  registry.removeMember(account);
302
317
  }
303
318
  super._revokeRole(role, account);
@@ -307,9 +322,13 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
307
322
  * @dev Sets the safety limits for the pool.
308
323
  * @param _ubiSettings The new safety limits.
309
324
  */
310
- function setUBISettings(UBISettings memory _ubiSettings) public onlyRole(MANAGER_ROLE) {
325
+ function setUBISettings(
326
+ UBISettings memory _ubiSettings,
327
+ ExtendedSettings memory _extendedSettings
328
+ ) public onlyRole(MANAGER_ROLE) {
311
329
  _verifyUBISettings(_ubiSettings);
312
330
  ubiSettings = _ubiSettings;
331
+ extendedSettings = _extendedSettings;
313
332
  emit UBISettingsChanged(_ubiSettings);
314
333
  }
315
334
 
@@ -345,28 +364,8 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
345
364
  ) revert INVALID_0_VALUE();
346
365
  }
347
366
 
348
- function estimateNextDailyUBI() public view returns (uint256) {
349
- uint256 currentBalance = settings.rewardToken.balanceOf(address(this));
350
- //start early cycle if we can increase the daily UBI pool
351
- uint256 nextDailyPool = currentBalance / ubiSettings.cycleLengthDays;
352
- bool shouldStartEarlyCycle = nextDailyPool > (status.dailyCyclePool * 105) / 100 ||
353
- (currentDayInCycle() <= status.currentCycleLength &&
354
- currentBalance < (status.dailyCyclePool * (status.currentCycleLength - currentDayInCycle())));
355
-
356
- uint256 _dailyCyclePool = status.dailyCyclePool;
357
- uint256 _dailyUbi;
358
- if (
359
- (currentDayInCycle() + 1) >= status.currentCycleLength || shouldStartEarlyCycle
360
- ) //start of cycle or first time
361
- {
362
- _dailyCyclePool = currentBalance / ubiSettings.cycleLengthDays;
363
- }
364
-
365
- _dailyUbi = min(
366
- ubiSettings.maxClaimAmount,
367
- _dailyCyclePool / max((status.periodClaimers * 10500) / 10000, ubiSettings.minActiveUsers)
368
- );
369
- return _dailyUbi;
367
+ function estimateNextDailyUBI() public view returns (uint256 nextDailyUbi) {
368
+ (, nextDailyUbi, ) = _calcNextDailyUBI();
370
369
  }
371
370
 
372
371
  function checkEntitlement() public view returns (uint256) {
@@ -384,6 +383,8 @@ contract UBIPool is AccessControlUpgradeable, GoodCollectiveSuperApp, UUPSUpgrad
384
383
  // current day has already been updated which means
385
384
  // that the dailyUbi has been updated
386
385
  if (status.currentDay == getCurrentDay() && status.dailyUbi > 0) {
386
+ if (extendedSettings.maxPeriodClaimers > 0 && status.periodClaimers >= extendedSettings.maxPeriodClaimers)
387
+ return 0;
387
388
  return hasClaimed(_member) ? 0 : status.dailyUbi;
388
389
  }
389
390
  return estimateNextDailyUBI();