@gooddollar/goodcollective-contracts 1.0.4 → 1.0.6-beta.0b8171a

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 (40) hide show
  1. package/contracts/DirectPayments/DirectPaymentsFactory.sol +40 -12
  2. package/contracts/DirectPayments/DirectPaymentsPool.sol +92 -37
  3. package/contracts/DirectPayments/ProvableNFT.sol +17 -11
  4. package/contracts/GoodCollective/GoodCollectiveSuperApp.sol +9 -66
  5. package/contracts/GoodCollective/IGoodCollectiveSuperApp.sol +12 -0
  6. package/contracts/utils/HelperLibrary.sol +83 -0
  7. package/package.json +13 -7
  8. package/releases/deployment.json +11286 -1911
  9. package/typechain-types/@gooddollar/goodprotocol/contracts/token/IFeesFormula.ts +115 -0
  10. package/typechain-types/@gooddollar/goodprotocol/contracts/token/index.ts +1 -0
  11. package/typechain-types/@openzeppelin/contracts/access/Ownable.ts +176 -0
  12. package/typechain-types/@openzeppelin/contracts/access/index.ts +4 -0
  13. package/typechain-types/@openzeppelin/contracts/index.ts +2 -0
  14. package/typechain-types/@openzeppelin/contracts/proxy/beacon/BeaconProxy.ts +115 -0
  15. package/typechain-types/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.ts +247 -0
  16. package/typechain-types/@openzeppelin/contracts/proxy/beacon/index.ts +2 -0
  17. package/typechain-types/contracts/DirectPayments/DirectPaymentsFactory.ts +29 -13
  18. package/typechain-types/contracts/DirectPayments/DirectPaymentsPool.sol/DirectPaymentsPool.ts +244 -197
  19. package/typechain-types/contracts/DirectPayments/ProvableNFT.ts +32 -3
  20. package/typechain-types/contracts/GoodCollective/GoodCollectiveSuperApp.ts +7 -7
  21. package/typechain-types/contracts/utils/HelperLibrary.ts +154 -0
  22. package/typechain-types/contracts/utils/index.ts +1 -0
  23. package/typechain-types/factories/@gooddollar/goodprotocol/contracts/token/IFeesFormula__factory.ts +60 -0
  24. package/typechain-types/factories/@gooddollar/goodprotocol/contracts/token/index.ts +1 -0
  25. package/typechain-types/factories/@openzeppelin/contracts/access/Ownable__factory.ts +78 -0
  26. package/typechain-types/factories/@openzeppelin/contracts/access/index.ts +4 -0
  27. package/typechain-types/factories/@openzeppelin/contracts/index.ts +1 -0
  28. package/typechain-types/factories/@openzeppelin/contracts/proxy/beacon/BeaconProxy__factory.ts +143 -0
  29. package/typechain-types/factories/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon__factory.ts +170 -0
  30. package/typechain-types/factories/@openzeppelin/contracts/proxy/beacon/index.ts +2 -0
  31. package/typechain-types/factories/contracts/DirectPayments/DirectPaymentsFactory__factory.ts +85 -4
  32. package/typechain-types/factories/contracts/DirectPayments/DirectPaymentsPool.sol/DirectPaymentsPool__factory.ts +298 -115
  33. package/typechain-types/factories/contracts/DirectPayments/ProvableNFT__factory.ts +56 -1
  34. package/typechain-types/factories/contracts/GoodCollective/GoodCollectiveSuperApp__factory.ts +1 -1
  35. package/typechain-types/factories/contracts/utils/HelperLibrary__factory.ts +129 -0
  36. package/typechain-types/factories/contracts/utils/index.ts +1 -0
  37. package/typechain-types/hardhat.d.ts +45 -0
  38. package/typechain-types/index.ts +8 -0
  39. package/typechain-types/contracts/DirectPayments/DirectPaymentsPool.sol/INameService.ts +0 -102
  40. package/typechain-types/factories/contracts/DirectPayments/DirectPaymentsPool.sol/INameService__factory.ts +0 -45
@@ -5,6 +5,9 @@ pragma solidity >=0.8.0;
5
5
  import "./DirectPaymentsPool.sol";
6
6
  import "./ProvableNFT.sol";
7
7
  import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
8
+ import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
9
+ import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
10
+
8
11
  import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
9
12
  import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
10
13
 
@@ -13,7 +16,15 @@ import "hardhat/console.sol";
13
16
  contract DirectPaymentsFactory is AccessControlUpgradeable, UUPSUpgradeable {
14
17
  error NOT_PROJECT_OWNER();
15
18
 
16
- event PoolCreated(address indexed pool, string indexed projectId, string ipfs, uint32 indexed nftType);
19
+ event PoolCreated(
20
+ address indexed pool,
21
+ string indexed projectId,
22
+ string ipfs,
23
+ uint32 indexed nftType,
24
+ DirectPaymentsPool.PoolSettings poolSettings,
25
+ DirectPaymentsPool.SafetyLimits poolLimits
26
+ );
27
+
17
28
  event PoolDetailsChanged(address indexed pool, string ipfs);
18
29
  event PoolVerifiedChanged(address indexed pool, bool isVerified);
19
30
  event UpdatedImpl(address indexed impl);
@@ -24,7 +35,7 @@ contract DirectPaymentsFactory is AccessControlUpgradeable, UUPSUpgradeable {
24
35
  string projectId;
25
36
  }
26
37
 
27
- address public impl;
38
+ UpgradeableBeacon public impl;
28
39
  ProvableNFT public nft;
29
40
  uint32 public nextNftType;
30
41
 
@@ -60,18 +71,16 @@ contract DirectPaymentsFactory is AccessControlUpgradeable, UUPSUpgradeable {
60
71
  function initialize(
61
72
  address _owner,
62
73
  address _dpimpl,
63
- address _nftimpl,
74
+ ProvableNFT _nft,
64
75
  address _feeRecipient,
65
76
  uint32 _feeBps
66
77
  ) external initializer {
67
78
  nextNftType = 1;
68
- impl = _dpimpl;
69
- bytes memory initCall = abi.encodeWithSelector(ProvableNFT.initialize.selector, "GoodCollective NFT", "GC-NFT");
70
- nft = ProvableNFT(address(new ERC1967Proxy(_nftimpl, initCall)));
79
+ impl = new UpgradeableBeacon(_dpimpl);
80
+ nft = _nft;
71
81
  feeRecipient = _feeRecipient;
72
82
  feeBps = _feeBps;
73
83
 
74
- nft.grantRole(DEFAULT_ADMIN_ROLE, _owner);
75
84
  _setupRole(DEFAULT_ADMIN_ROLE, _owner);
76
85
  }
77
86
 
@@ -81,7 +90,9 @@ contract DirectPaymentsFactory is AccessControlUpgradeable, UUPSUpgradeable {
81
90
  string memory _ipfs,
82
91
  DirectPaymentsPool.PoolSettings memory _settings,
83
92
  DirectPaymentsPool.SafetyLimits memory _limits
84
- ) external onlyProjectOwnerOrNon(_projectId) returns (DirectPaymentsPool pool) {}
93
+ ) external onlyProjectOwnerOrNon(_projectId) returns (DirectPaymentsPool pool) {
94
+ return _createPool(_projectId, _ipfs, _settings, _limits, true);
95
+ }
85
96
 
86
97
  function createPool(
87
98
  string memory _projectId,
@@ -89,6 +100,16 @@ contract DirectPaymentsFactory is AccessControlUpgradeable, UUPSUpgradeable {
89
100
  DirectPaymentsPool.PoolSettings memory _settings,
90
101
  DirectPaymentsPool.SafetyLimits memory _limits
91
102
  ) external onlyProjectOwnerOrNon(_projectId) returns (DirectPaymentsPool pool) {
103
+ return _createPool(_projectId, _ipfs, _settings, _limits, false);
104
+ }
105
+
106
+ function _createPool(
107
+ string memory _projectId,
108
+ string memory _ipfs,
109
+ DirectPaymentsPool.PoolSettings memory _settings,
110
+ DirectPaymentsPool.SafetyLimits memory _limits,
111
+ bool useBeacon
112
+ ) internal returns (DirectPaymentsPool pool) {
92
113
  //TODO: add check if msg.sender is whitelisted
93
114
 
94
115
  _settings.nftType = nextNftType;
@@ -99,18 +120,25 @@ contract DirectPaymentsFactory is AccessControlUpgradeable, UUPSUpgradeable {
99
120
  _limits,
100
121
  address(this)
101
122
  );
102
- pool = DirectPaymentsPool(address(new ERC1967Proxy(impl, initCall)));
123
+
124
+ if (useBeacon) {
125
+ pool = DirectPaymentsPool(address(new BeaconProxy(address(impl), initCall)));
126
+ } else {
127
+ pool = DirectPaymentsPool(address(new ERC1967Proxy(impl.implementation(), initCall)));
128
+ }
103
129
 
104
130
  nft.grantRole(nft.getManagerRole(nextNftType), _settings.manager);
105
131
  nft.grantRole(nft.getManagerRole(nextNftType), address(pool));
106
132
  pool.grantRole(pool.MINTER_ROLE(), _settings.manager);
107
133
 
108
- projectIdToControlPool[keccak256(bytes(_projectId))] = pool;
134
+ //access control to project is determinted by the first pool access control rules
135
+ if (address(projectIdToControlPool[keccak256(bytes(_projectId))]) == address(0))
136
+ projectIdToControlPool[keccak256(bytes(_projectId))] = pool;
109
137
  registry[address(pool)].ipfs = _ipfs;
110
138
  registry[address(pool)].projectId = _projectId;
111
139
 
112
140
  pool.renounceRole(DEFAULT_ADMIN_ROLE, address(this));
113
- emit PoolCreated(address(pool), _projectId, _ipfs, nextNftType);
141
+ emit PoolCreated(address(pool), _projectId, _ipfs, nextNftType, _settings, _limits);
114
142
 
115
143
  nextNftType++;
116
144
  }
@@ -126,7 +154,7 @@ contract DirectPaymentsFactory is AccessControlUpgradeable, UUPSUpgradeable {
126
154
  }
127
155
 
128
156
  function updateImpl(address _impl) external onlyRole(DEFAULT_ADMIN_ROLE) {
129
- impl = _impl;
157
+ impl.upgradeTo(_impl);
130
158
  emit UpdatedImpl(_impl);
131
159
  }
132
160
 
@@ -41,22 +41,39 @@ contract DirectPaymentsPool is
41
41
  error NOT_MANAGER();
42
42
  error ALREADY_CLAIMED(uint256);
43
43
  error NFT_MISSING(uint256);
44
- error NOT_MEMBER(address);
45
- error NOT_WHITELISTED(address);
46
44
  error OVER_MEMBER_LIMITS(address);
47
45
  error OVER_GLOBAL_LIMITS();
48
46
  error UNSUPPORTED_NFT();
49
47
  error NO_BALANCE();
48
+ error NFTTYPE_CHANGED();
49
+ error EMPTY_MANAGER();
50
50
 
51
51
  bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE");
52
+ bytes32 public constant MEMBER_ROLE = keccak256("MEMBER_ROLE");
52
53
  bytes32 public constant MINTER_ROLE = keccak256("MINTER");
53
54
 
55
+ event PoolCreated(
56
+ address indexed pool,
57
+ string indexed projectId,
58
+ string ipfs,
59
+ uint32 indexed nftType,
60
+ DirectPaymentsPool.PoolSettings poolSettings,
61
+ DirectPaymentsPool.SafetyLimits poolLimits
62
+ );
63
+
54
64
  event PoolSettingsChanged(PoolSettings settings);
55
65
  event PoolLimitsChanged(SafetyLimits limits);
56
- event MemberAdded(address member);
57
- event MemberRemoved(address member);
58
- event EventRewardClaimed(uint256 indexed tokenId, ProvableNFT.EventData eventData);
66
+ event EventRewardClaimed(
67
+ uint256 indexed tokenId,
68
+ uint16 eventType,
69
+ uint32 eventTimestamp,
70
+ uint256 eventQuantity,
71
+ string eventUri,
72
+ address[] contributers,
73
+ uint256 rewardPerContributer
74
+ );
59
75
  event NFTClaimed(uint256 indexed tokenId, uint256 totalRewards);
76
+ event NOT_MEMBER_OR_WHITELISTED(address contributer);
60
77
 
61
78
  // Define functions
62
79
  struct PoolSettings {
@@ -67,6 +84,7 @@ contract DirectPaymentsPool is
67
84
  IMembersValidator membersValidator;
68
85
  IIdentityV2 uniquenessValidator;
69
86
  IERC20Upgradeable rewardToken;
87
+ bool allowRewardOverride;
70
88
  }
71
89
 
72
90
  struct SafetyLimits {
@@ -88,7 +106,7 @@ contract DirectPaymentsPool is
88
106
  ProvableNFT public nft;
89
107
 
90
108
  mapping(uint256 => bool) public claimedNfts;
91
- mapping(address => bool) public members;
109
+ mapping(address => bool) private members_unused; // using access control instead
92
110
  mapping(address => LimitsData) public memberLimits;
93
111
  LimitsData public globalLimits;
94
112
  DirectPaymentsFactory public registry;
@@ -100,7 +118,7 @@ contract DirectPaymentsPool is
100
118
  * @dev Authorizes an upgrade for the implementation contract.
101
119
  * @param impl The address of the new implementation contract.
102
120
  */
103
- function _authorizeUpgrade(address impl) internal virtual override {}
121
+ function _authorizeUpgrade(address impl) internal virtual override onlyRole(DEFAULT_ADMIN_ROLE) {}
104
122
 
105
123
  function getRegistry() public view override returns (DirectPaymentsFactory) {
106
124
  return DirectPaymentsFactory(registry);
@@ -124,12 +142,11 @@ contract DirectPaymentsPool is
124
142
  nft = _nft;
125
143
  _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
126
144
  _setupRole(DEFAULT_ADMIN_ROLE, _settings.manager);
127
-
128
145
  setSuperToken(ISuperToken(address(settings.rewardToken)));
129
146
  }
130
147
 
131
- function upgradeToLatest(bytes memory data) external payable virtual onlyProxy {
132
- address impl = DirectPaymentsFactory(registry).impl();
148
+ function upgradeToLatest(bytes memory data) external payable virtual {
149
+ address impl = address(DirectPaymentsFactory(registry).impl());
133
150
  _authorizeUpgrade(impl);
134
151
  _upgradeToAndCallUUPS(impl, data, false);
135
152
  }
@@ -170,14 +187,27 @@ contract DirectPaymentsPool is
170
187
  uint totalRewards;
171
188
  uint rewardsBalance = settings.rewardToken.balanceOf(address(this));
172
189
 
190
+ bool allowRewardOverride = settings.allowRewardOverride;
173
191
  for (uint256 i = 0; i < _data.events.length; i++) {
174
- uint128 reward = _eventReward(_data.events[i].subtype);
192
+ uint reward = (
193
+ allowRewardOverride && _data.events[i].rewardOverride > 0
194
+ ? _data.events[i].rewardOverride
195
+ : _eventReward(_data.events[i].subtype)
196
+ ) * _data.events[i].quantity;
175
197
  if (reward > 0) {
176
- totalRewards += reward * _data.events[i].quantity;
198
+ totalRewards += reward;
177
199
  if (totalRewards > rewardsBalance) revert NO_BALANCE();
178
200
  rewardsBalance -= totalRewards;
179
- _sendReward(_data.events[i].contributers, uint128(reward * _data.events[i].quantity));
180
- emit EventRewardClaimed(_nftId, _data.events[i]);
201
+ _sendReward(_data.events[i].contributers, uint128(reward));
202
+ emit EventRewardClaimed(
203
+ _nftId,
204
+ _data.events[i].subtype,
205
+ _data.events[i].timestamp,
206
+ _data.events[i].quantity,
207
+ _data.events[i].eventUri,
208
+ _data.events[i].contributers,
209
+ uint128(reward / _data.events[i].contributers.length)
210
+ );
181
211
  }
182
212
  }
183
213
 
@@ -203,11 +233,17 @@ contract DirectPaymentsPool is
203
233
  */
204
234
  function _sendReward(address[] memory recipients, uint128 reward) internal {
205
235
  uint128 perReward = uint128(reward / recipients.length);
236
+ uint128 totalSent;
206
237
  for (uint i = 0; i < recipients.length; i++) {
207
- _enforceAndUpdateMemberLimits(recipients[i], perReward);
208
- settings.rewardToken.safeTransfer(recipients[i], perReward);
238
+ bool valid = _enforceAndUpdateMemberLimits(recipients[i], perReward);
239
+ if (valid) {
240
+ settings.rewardToken.safeTransfer(recipients[i], perReward);
241
+ totalSent += perReward;
242
+ } else {
243
+ emit NOT_MEMBER_OR_WHITELISTED(recipients[i]);
244
+ }
209
245
  }
210
- _enforceAndUpdateGlobalLimits(reward);
246
+ _enforceAndUpdateGlobalLimits(totalSent);
211
247
  }
212
248
 
213
249
  /**
@@ -215,8 +251,11 @@ contract DirectPaymentsPool is
215
251
  * @param member The address of the member to enforce and update limits for.
216
252
  * @param reward The amount of rewards to enforce and update limits for.
217
253
  */
218
- function _enforceAndUpdateMemberLimits(address member, uint128 reward) internal {
219
- if (members[member] == false) revert NOT_MEMBER(member);
254
+ function _enforceAndUpdateMemberLimits(address member, uint128 reward) internal returns (bool) {
255
+ //dont revert on non valid members, just dont reward them (their reward is lost)
256
+ if (_addMember(member, "") == false) {
257
+ return false;
258
+ }
220
259
 
221
260
  uint64 curMonth = _month();
222
261
  if (memberLimits[member].lastReward + 60 * 60 * 24 < block.timestamp) //more than a day passed since last reward
@@ -241,6 +280,8 @@ contract DirectPaymentsPool is
241
280
  memberLimits[member].daily > limits.maxMemberPerDay ||
242
281
  memberLimits[member].monthly > limits.maxMemberPerMonth
243
282
  ) revert OVER_MEMBER_LIMITS(member);
283
+
284
+ return true;
244
285
  }
245
286
 
246
287
  /**
@@ -285,32 +326,23 @@ contract DirectPaymentsPool is
285
326
  * @param extraData Additional data to validate the member.
286
327
  */
287
328
 
288
- function addMember(address member, bytes memory extraData) external {
329
+ function _addMember(address member, bytes memory extraData) internal returns (bool isMember) {
330
+ if (hasRole(MEMBER_ROLE, member)) return true;
331
+
289
332
  if (address(settings.uniquenessValidator) != address(0)) {
290
333
  address rootAddress = settings.uniquenessValidator.getWhitelistedRoot(member);
291
- if (rootAddress == address(0)) revert NOT_WHITELISTED(member);
334
+ if (rootAddress == address(0)) return false;
292
335
  }
293
336
 
337
+ // if no members validator then anyone can join the pool
294
338
  if (address(settings.membersValidator) != address(0)) {
295
339
  if (settings.membersValidator.isMemberValid(address(this), msg.sender, member, extraData) == false) {
296
- revert NOT_MEMBER(member);
340
+ return false;
297
341
  }
298
- } else {
299
- // if no members validator then only admin can add members
300
- if (hasRole(DEFAULT_ADMIN_ROLE, msg.sender) == false) revert NOT_MANAGER();
301
342
  }
302
343
 
303
- members[member] = true;
304
- emit MemberAdded(member);
305
- }
306
-
307
- /**
308
- * @dev Removes a member from the contract.
309
- * @param member The address of the member to remove.
310
- */
311
- function removeMember(address member) external onlyRole(DEFAULT_ADMIN_ROLE) {
312
- members[member] = false;
313
- emit MemberRemoved(member);
344
+ _setupRole(MEMBER_ROLE, member);
345
+ return true;
314
346
  }
315
347
 
316
348
  function mintNFT(address _to, ProvableNFT.NFTData memory _nftData, bool withClaim) external onlyRole(MINTER_ROLE) {
@@ -321,7 +353,7 @@ contract DirectPaymentsPool is
321
353
  }
322
354
 
323
355
  /**
324
- * @dev Receives an ERC721 token and triggers a claim for rewards.
356
+ * @dev Receives an ERC721 token
325
357
  * @param operator The address of the operator that sent the token.
326
358
  * @param from The address of the sender that sent the token.
327
359
  * @param tokenId The ID of the token received.
@@ -347,4 +379,27 @@ contract DirectPaymentsPool is
347
379
  if (nftData.nftType != settings.nftType) revert UNSUPPORTED_NFT();
348
380
  return DirectPaymentsPool.onERC721Received.selector;
349
381
  }
382
+
383
+ /**
384
+ * @dev Sets the safety limits for the pool.
385
+ * @param _limits The new safety limits.
386
+ */
387
+ function setPoolLimits(SafetyLimits memory _limits) public onlyRole(DEFAULT_ADMIN_ROLE) {
388
+ limits = _limits;
389
+ emit PoolLimitsChanged(_limits);
390
+ }
391
+
392
+ /**
393
+ * @dev Sets the settings for the pool.
394
+ * @param _settings The new pool settings.
395
+ */
396
+ function setPoolSettings(PoolSettings memory _settings) public onlyRole(DEFAULT_ADMIN_ROLE) {
397
+ if (_settings.nftType != settings.nftType) revert NFTTYPE_CHANGED();
398
+ if (_settings.manager == address(0)) revert EMPTY_MANAGER();
399
+
400
+ _revokeRole(DEFAULT_ADMIN_ROLE, settings.manager);
401
+ settings = _settings;
402
+ _setupRole(DEFAULT_ADMIN_ROLE, _settings.manager);
403
+ emit PoolSettingsChanged(_settings);
404
+ }
350
405
  }
@@ -19,12 +19,15 @@ contract ProvableNFT is ERC721Upgradeable, AccessControlUpgradeable, UUPSUpgrade
19
19
  bytes32 public constant MINTER_ROLE = keccak256(abi.encodePacked("MINTER"));
20
20
  bytes16 private constant _SYMBOLS = "0123456789abcdef";
21
21
 
22
+ event ProvableNftMinted(uint256 tokenId, address to, bytes32 nftDataHash);
23
+
22
24
  struct EventData {
23
25
  uint16 subtype;
24
26
  uint32 timestamp;
25
27
  uint256 quantity;
26
28
  string eventUri; //extra data related to event
27
29
  address[] contributers;
30
+ uint128 rewardOverride; //override reward defined per 1 quantity of event, pool will use this instead of its own reward if allowRewardOverride is true
28
31
  }
29
32
 
30
33
  struct NFTData {
@@ -41,7 +44,9 @@ contract ProvableNFT is ERC721Upgradeable, AccessControlUpgradeable, UUPSUpgrade
41
44
  _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
42
45
  }
43
46
 
44
- function _authorizeUpgrade(address newimpl) internal virtual override onlyManager(0) {}
47
+ function _authorizeUpgrade(address /*newimpl*/) internal virtual override {
48
+ _onlyManager(0);
49
+ }
45
50
 
46
51
  function supportsInterface(
47
52
  bytes4 interfaceId
@@ -49,14 +54,13 @@ contract ProvableNFT is ERC721Upgradeable, AccessControlUpgradeable, UUPSUpgrade
49
54
  return super.supportsInterface(interfaceId);
50
55
  }
51
56
 
52
- modifier onlyManager(uint32 nftType) {
57
+ function _onlyManager(uint32 nftType) internal view {
53
58
  if (
54
59
  ((nftType > 0 && hasRole(getManagerRole(nftType), msg.sender)) ||
55
60
  hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) == false
56
61
  ) {
57
62
  revert NOT_MANAGER(nftType);
58
63
  }
59
- _;
60
64
  }
61
65
 
62
66
  /**
@@ -72,23 +76,24 @@ contract ProvableNFT is ERC721Upgradeable, AccessControlUpgradeable, UUPSUpgrade
72
76
  * @param _nftDataHash The hash of the NFT's data.
73
77
  * @return tokenId ID of the newly minted NFT.
74
78
  */
75
- function mint(
76
- address _to,
77
- string memory _uri,
78
- bytes32 _nftDataHash
79
- ) public onlyManager(0) returns (uint256 tokenId) {
80
- return _mint(_to, _uri, _nftDataHash, ""); //send false in calldata, assuming default receiver is a directpaymentspool. without nft data on chain it will fail.
79
+ function mint(address _to, string memory _uri, bytes32 _nftDataHash) public returns (uint256 tokenId) {
80
+ _onlyManager(0);
81
+ NFTData memory nftData;
82
+
83
+ return _mint(_to, _uri, _nftDataHash, nftData, ""); //send false in calldata, assuming default receiver is a directpaymentspool. without nft data on chain it will fail.
81
84
  }
82
85
 
83
86
  function _mint(
84
87
  address _to,
85
88
  string memory _uri,
86
89
  bytes32 _nftDataHash,
90
+ NFTData memory /*_nftData*/,
87
91
  bytes memory _callData
88
92
  ) internal returns (uint256 tokenId) {
89
93
  tokenId = uint256(_nftDataHash);
90
94
  nftDatas[tokenId].nftUri = _uri;
91
95
  _safeMint(_to, tokenId, _callData);
96
+ emit ProvableNftMinted(tokenId, _to, _nftDataHash);
92
97
  }
93
98
 
94
99
  /**
@@ -109,7 +114,8 @@ contract ProvableNFT is ERC721Upgradeable, AccessControlUpgradeable, UUPSUpgrade
109
114
  NFTData memory _nftData,
110
115
  bool _withStore,
111
116
  bytes memory _callData
112
- ) external onlyManager(_nftData.nftType) returns (uint256 tokenId) {
117
+ ) external returns (uint256 tokenId) {
118
+ _onlyManager(_nftData.nftType);
113
119
  if (_nftData.nftType == 0) revert BAD_NFTTYPE();
114
120
 
115
121
  bytes32 dataHash = keccak256(abi.encode(_nftData));
@@ -123,7 +129,7 @@ contract ProvableNFT is ERC721Upgradeable, AccessControlUpgradeable, UUPSUpgrade
123
129
  store.events.push(_nftData.events[i]);
124
130
  }
125
131
  }
126
- _mint(_to, _nftData.nftUri, dataHash, _callData);
132
+ _mint(_to, _nftData.nftUri, dataHash, _nftData, _callData);
127
133
  }
128
134
 
129
135
  function proveNFTData(uint256 _tokenId, NFTData memory _nftData) public view returns (NFTData memory data) {
@@ -12,6 +12,7 @@ import "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";
12
12
  import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
13
13
 
14
14
  import "../DirectPayments/DirectPaymentsFactory.sol";
15
+ import "../utils/HelperLibrary.sol";
15
16
 
16
17
  // import "hardhat/console.sol";
17
18
 
@@ -46,42 +47,18 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
46
47
  bool isFlowUpdate
47
48
  );
48
49
 
49
- //TODO: ask about "view" for beforeagreement functions
50
+ //TODO:
50
51
  // ask about "receiver" can it be different then app?
51
52
 
52
53
  /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
53
54
  ISwapRouter public immutable swapRouter;
54
55
 
55
- /**
56
- * @dev A struct containing information about a token swap
57
- * @param swapFrom The address of the token being swapped
58
- * @param amount The amount of tokens being swapped
59
- * @param minReturn The minimum amount of tokens to be received in the swap
60
- * @param timestamp The deadline for the swap to occur
61
- * @param path The path of tokens to take in a uniswap v3 multi-hop swap, encoded as bytes
62
- */
63
- struct SwapData {
64
- address swapFrom;
65
- uint256 amount;
66
- uint256 minReturn;
67
- uint256 deadline;
68
- bytes path;
69
- }
70
-
71
56
  struct SupporterData {
72
57
  uint256 contribution;
73
58
  int96 flowRate;
74
59
  uint128 lastUpdated;
75
60
  }
76
61
 
77
- struct Stats {
78
- uint256 netIncome; //without fees
79
- uint256 totalFees;
80
- uint256 lastUpdate;
81
- address lastFeeRecipient;
82
- int96 lastIncomeRate;
83
- }
84
-
85
62
  ISuperToken public superToken;
86
63
 
87
64
  mapping(address => SupporterData) public supporters;
@@ -89,7 +66,7 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
89
66
  //initialize cfaV1 variable
90
67
  CFAv1Library.InitData public cfaV1;
91
68
 
92
- Stats public stats;
69
+ IGoodCollectiveSuperApp.Stats public stats;
93
70
 
94
71
  uint256[48] private _reserved;
95
72
 
@@ -141,13 +118,7 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
141
118
  view
142
119
  returns (uint256 netIncome, uint256 totalFees, int96 incomeFlowRate, int96 feeRate)
143
120
  {
144
- incomeFlowRate = stats.lastIncomeRate;
145
- netIncome = stats.netIncome + uint96(stats.lastIncomeRate) * (block.timestamp - stats.lastUpdate);
146
- feeRate = superToken.getFlowRate(address(this), stats.lastFeeRecipient);
147
- totalFees =
148
- stats.totalFees +
149
- uint96(superToken.getFlowRate(address(this), stats.lastFeeRecipient)) *
150
- (block.timestamp - stats.lastUpdate);
121
+ return HelperLibrary.getRealtimeStats(stats, superToken);
151
122
  }
152
123
 
153
124
  /**
@@ -197,42 +168,11 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
197
168
  * @return Returns the context of the transaction
198
169
  */
199
170
  function handleSwap(
200
- SwapData memory _customData,
171
+ HelperLibrary.SwapData memory _customData,
201
172
  address _sender,
202
173
  bytes memory _ctx
203
174
  ) external onlyHostOrSender(_sender) returns (bytes memory) {
204
- // Transfer the tokens from the sender to this contract
205
- TransferHelper.safeTransferFrom(_customData.swapFrom, _sender, address(this), _customData.amount);
206
-
207
- // Approve the router to spend the tokens
208
- TransferHelper.safeApprove(_customData.swapFrom, address(swapRouter), _customData.amount);
209
-
210
- if (_customData.path.length > 0) {
211
- // If a path is provided, execute a multi-hop swap
212
- ISwapRouter.ExactInputParams memory params = ISwapRouter.ExactInputParams({
213
- path: _customData.path,
214
- recipient: _sender,
215
- deadline: _customData.deadline,
216
- amountIn: _customData.amount,
217
- amountOutMinimum: _customData.minReturn
218
- });
219
- swapRouter.exactInput(params);
220
- } else {
221
- // If no path is provided, execute a single-hop swap
222
- ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
223
- tokenIn: _customData.swapFrom,
224
- tokenOut: address(superToken),
225
- fee: 10000,
226
- recipient: _sender,
227
- deadline: _customData.deadline,
228
- amountIn: _customData.amount,
229
- amountOutMinimum: _customData.minReturn,
230
- sqrtPriceLimitX96: 0
231
- });
232
-
233
- // Execute the swap using `exactInputSingle`
234
- swapRouter.exactInputSingle(params);
235
- }
175
+ HelperLibrary.handleSwap(swapRouter, _customData, address(superToken), _sender);
236
176
 
237
177
  // Return the context of the transaction
238
178
  return _ctx;
@@ -392,6 +332,9 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
392
332
  TransferHelper.safeTransfer(address(superToken), recipient, fee);
393
333
  }
394
334
 
335
+ /**
336
+ * for methods that can be called via superfluid batch or directly
337
+ */
395
338
  modifier onlyHostOrSender(address _sender) {
396
339
  if (msg.sender != _sender && msg.sender != address(host)) revert ONLY_HOST_OR_SENDER(msg.sender);
397
340
  _;
@@ -0,0 +1,12 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity >=0.8.0;
3
+
4
+ interface IGoodCollectiveSuperApp {
5
+ struct Stats {
6
+ uint256 netIncome; //without fees
7
+ uint256 totalFees;
8
+ uint256 lastUpdate;
9
+ address lastFeeRecipient;
10
+ int96 lastIncomeRate;
11
+ }
12
+ }
@@ -0,0 +1,83 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ pragma solidity >=0.8.0;
4
+
5
+ import "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";
6
+ import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
7
+ import { ISuperToken } from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol";
8
+ import { SuperTokenV1Library } from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol";
9
+
10
+ import "../GoodCollective/IGoodCollectiveSuperApp.sol";
11
+
12
+ library HelperLibrary {
13
+ using SuperTokenV1Library for ISuperToken;
14
+
15
+ /**
16
+ * @dev A struct containing information about a token swap
17
+ * @param swapFrom The address of the token being swapped
18
+ * @param amount The amount of tokens being swapped
19
+ * @param minReturn The minimum amount of tokens to be received in the swap
20
+ * @param timestamp The deadline for the swap to occur
21
+ * @param path The path of tokens to take in a uniswap v3 multi-hop swap, encoded as bytes
22
+ */
23
+ struct SwapData {
24
+ address swapFrom;
25
+ uint256 amount;
26
+ uint256 minReturn;
27
+ uint256 deadline;
28
+ bytes path;
29
+ }
30
+
31
+ function handleSwap(
32
+ ISwapRouter swapRouter,
33
+ SwapData memory _customData,
34
+ address outTokenIfNoPath,
35
+ address _sender
36
+ ) external {
37
+ // Transfer the tokens from the sender to this contract
38
+ TransferHelper.safeTransferFrom(_customData.swapFrom, _sender, address(this), _customData.amount);
39
+
40
+ // Approve the router to spend the tokens
41
+ TransferHelper.safeApprove(_customData.swapFrom, address(swapRouter), _customData.amount);
42
+
43
+ if (_customData.path.length > 0) {
44
+ // If a path is provided, execute a multi-hop swap
45
+ ISwapRouter.ExactInputParams memory params = ISwapRouter.ExactInputParams({
46
+ path: _customData.path,
47
+ recipient: _sender,
48
+ deadline: _customData.deadline,
49
+ amountIn: _customData.amount,
50
+ amountOutMinimum: _customData.minReturn
51
+ });
52
+ swapRouter.exactInput(params);
53
+ } else {
54
+ // If no path is provided, execute a single-hop swap
55
+ ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
56
+ tokenIn: _customData.swapFrom,
57
+ tokenOut: outTokenIfNoPath,
58
+ fee: 10000,
59
+ recipient: _sender,
60
+ deadline: _customData.deadline,
61
+ amountIn: _customData.amount,
62
+ amountOutMinimum: _customData.minReturn,
63
+ sqrtPriceLimitX96: 0
64
+ });
65
+
66
+ // Execute the swap using `exactInputSingle`
67
+ swapRouter.exactInputSingle(params);
68
+ }
69
+ }
70
+
71
+ function getRealtimeStats(
72
+ IGoodCollectiveSuperApp.Stats memory stats,
73
+ ISuperToken superToken
74
+ ) external view returns (uint256 netIncome, uint256 totalFees, int96 incomeFlowRate, int96 feeRate) {
75
+ incomeFlowRate = stats.lastIncomeRate;
76
+ netIncome = stats.netIncome + uint96(stats.lastIncomeRate) * (block.timestamp - stats.lastUpdate);
77
+ feeRate = superToken.getFlowRate(address(this), stats.lastFeeRecipient);
78
+ totalFees =
79
+ stats.totalFees +
80
+ uint96(superToken.getFlowRate(address(this), stats.lastFeeRecipient)) *
81
+ (block.timestamp - stats.lastUpdate);
82
+ }
83
+ }