@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.
- package/README.md +14 -1
- package/contracts/DirectPayments/DirectPaymentsFactory.sol +13 -10
- package/contracts/DirectPayments/DirectPaymentsPool.sol +13 -4
- package/contracts/GoodCollective/GoodCollectiveSuperApp.sol +31 -55
- package/contracts/GoodCollective/IGoodCollectiveSuperApp.sol +6 -0
- package/contracts/UBI/UBIPool.sol +48 -36
- package/contracts/UBI/UBIPoolFactory.sol +10 -1
- package/contracts/test/HelperLibraryTest.sol +16 -0
- package/contracts/utils/HelperLibrary.sol +73 -5
- package/package.json +4 -3
- package/releases/deployment.json +21841 -565
- package/typechain-types/@uniswap/index.ts +0 -2
- package/typechain-types/contracts/DirectPayments/DirectPaymentsFactory.sol/DirectPaymentsFactory.ts +1327 -0
- package/typechain-types/contracts/{GoodCollective/GoodCollectiveSuperApp.sol → DirectPayments/DirectPaymentsFactory.sol}/index.ts +1 -1
- package/typechain-types/contracts/DirectPayments/DirectPaymentsFactory.ts +40 -5
- package/typechain-types/contracts/DirectPayments/DirectPaymentsPool.sol/DirectPaymentsPool.ts +49 -0
- package/typechain-types/contracts/GoodCollective/GoodCollectiveSuperApp.ts +49 -0
- package/typechain-types/contracts/{UBI/MultiClaimModule.sol/IClaimable.ts → GoodCollective/IGoodCollectiveSuperApp.sol/IRegistry.ts} +33 -23
- package/typechain-types/{factories/@uniswap/v3-periphery/contracts → contracts/GoodCollective/IGoodCollectiveSuperApp.sol}/index.ts +1 -1
- package/typechain-types/contracts/GoodCollective/index.ts +3 -2
- package/typechain-types/contracts/UBI/UBIPool.sol/UBIPool.ts +104 -49
- package/typechain-types/contracts/UBI/UBIPoolFactory.ts +43 -5
- package/typechain-types/contracts/UBI/index.ts +0 -2
- package/typechain-types/contracts/index.ts +2 -0
- package/typechain-types/contracts/test/HelperLibraryTest.ts +147 -0
- package/typechain-types/{@uniswap/v3-periphery/contracts/interfaces → contracts/test}/index.ts +1 -1
- package/typechain-types/factories/@uniswap/index.ts +0 -1
- package/typechain-types/factories/contracts/DirectPayments/DirectPaymentsFactory.sol/DirectPaymentsFactory__factory.ts +1004 -0
- package/typechain-types/factories/contracts/{GoodCollective/GoodCollectiveSuperApp.sol → DirectPayments/DirectPaymentsFactory.sol}/IRegistry__factory.ts +1 -1
- package/typechain-types/factories/contracts/{GoodCollective/GoodCollectiveSuperApp.sol → DirectPayments/DirectPaymentsFactory.sol}/index.ts +1 -1
- package/typechain-types/factories/contracts/DirectPayments/DirectPaymentsFactory__factory.ts +15 -2
- package/typechain-types/factories/contracts/DirectPayments/DirectPaymentsPool.sol/DirectPaymentsPool__factory.ts +57 -1
- package/typechain-types/factories/contracts/GoodCollective/GoodCollectiveSuperApp__factory.ts +57 -1
- package/typechain-types/factories/contracts/GoodCollective/IGoodCollectiveSuperApp.sol/IRegistry__factory.ts +52 -0
- package/typechain-types/{@gooddollar/goodprotocol/contracts/token/FeesFormula.sol → factories/contracts/GoodCollective/IGoodCollectiveSuperApp.sol}/index.ts +1 -1
- package/typechain-types/factories/contracts/GoodCollective/index.ts +2 -1
- package/typechain-types/factories/contracts/UBI/UBIPool.sol/UBIPool__factory.ts +109 -28
- package/typechain-types/factories/contracts/UBI/UBIPoolFactory__factory.ts +32 -4
- package/typechain-types/factories/contracts/UBI/index.ts +0 -1
- package/typechain-types/factories/contracts/index.ts +1 -0
- package/typechain-types/factories/contracts/test/HelperLibraryTest__factory.ts +154 -0
- package/typechain-types/{@uniswap/v3-periphery → factories/contracts/test}/index.ts +1 -2
- package/typechain-types/factories/contracts/utils/HelperLibrary__factory.ts +1 -1
- package/typechain-types/hardhat.d.ts +4 -31
- package/typechain-types/index.ts +6 -12
- package/contracts/UBI/MultiClaimModule.sol +0 -78
- package/typechain-types/@gooddollar/goodprotocol/contracts/token/FeesFormula.sol/IFeesFormula.ts +0 -115
- package/typechain-types/@uniswap/v3-periphery/contracts/index.ts +0 -5
- package/typechain-types/@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.ts +0 -369
- package/typechain-types/contracts/GoodCollective/GoodCollectiveSuperApp.sol/GoodCollectiveSuperApp.ts +0 -1000
- package/typechain-types/contracts/UBI/MultiClaimModule.sol/IModule.ts +0 -196
- package/typechain-types/contracts/UBI/MultiClaimModule.sol/MultiClaimModule.ts +0 -242
- package/typechain-types/contracts/UBI/MultiClaimModule.sol/index.ts +0 -6
- package/typechain-types/factories/@gooddollar/goodprotocol/contracts/token/FeesFormula.sol/IFeesFormula__factory.ts +0 -60
- package/typechain-types/factories/@gooddollar/goodprotocol/contracts/token/FeesFormula.sol/index.ts +0 -4
- package/typechain-types/factories/@uniswap/v3-periphery/contracts/interfaces/ISwapRouter__factory.ts +0 -263
- package/typechain-types/factories/@uniswap/v3-periphery/contracts/interfaces/index.ts +0 -4
- package/typechain-types/factories/@uniswap/v3-periphery/index.ts +0 -4
- package/typechain-types/factories/contracts/GoodCollective/GoodCollectiveSuperApp.sol/GoodCollectiveSuperApp__factory.ts +0 -728
- package/typechain-types/factories/contracts/UBI/MultiClaimModule.sol/IClaimable__factory.ts +0 -33
- package/typechain-types/factories/contracts/UBI/MultiClaimModule.sol/IModule__factory.ts +0 -84
- package/typechain-types/factories/contracts/UBI/MultiClaimModule.sol/MultiClaimModule__factory.ts +0 -150
- package/typechain-types/factories/contracts/UBI/MultiClaimModule.sol/index.ts +0 -6
- /package/typechain-types/contracts/{GoodCollective/GoodCollectiveSuperApp.sol → DirectPayments/DirectPaymentsFactory.sol}/IRegistry.ts +0 -0
package/README.md
CHANGED
|
@@ -1 +1,14 @@
|
|
|
1
|
-
#
|
|
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.
|
|
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
|
|
178
|
-
memberPools[
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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
|
|
32
|
-
error
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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] ==
|
|
394
|
+
return status.lastClaimed[whitelistedRoot] == getCurrentDay();
|
|
383
395
|
}
|
|
384
396
|
|
|
385
397
|
function nextClaimTime() public view returns (uint256) {
|
|
386
|
-
return getCurrentDay() * (1 days)
|
|
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.
|
|
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:
|
|
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:
|
|
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.
|
|
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
|
-
"
|
|
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
|
+
}
|