-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathMAODistribution.sol
276 lines (240 loc) · 13 KB
/
MAODistribution.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
// SPDX-License-Identifier: MIT
pragma solidity =0.8.28;
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import "@peeramid-labs/eds/src/interfaces/IDistribution.sol";
import "@peeramid-labs/eds/src/libraries/LibSemver.sol";
import {DistributableGovernanceERC20, MintSettings} from "../tokens/DistributableGovernanceERC20.sol";
import {IERC7746} from "@peeramid-labs/eds/src/interfaces/IERC7746.sol";
import {SimpleAccessManager} from "@peeramid-labs/eds/src/managers/SimpleAccessManager.sol";
import {IDistributor} from "@peeramid-labs/eds/src/interfaces/IDistributor.sol";
import {RankToken} from "../tokens/RankToken.sol";
import "../initializers/RankifyInstanceInit.sol";
import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import "@peeramid-labs/eds/src/abstracts/CodeIndexer.sol";
import "hardhat/console.sol";
import {TokenSettings} from "../vendor/aragon/interfaces.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {ShortStrings, ShortString} from "@openzeppelin/contracts/utils/ShortStrings.sol";
/**
* @title MAODistribution
* @dev This contract implements the IDistribution and CodeIndexer interfaces. It uses the Clones library for address cloning.
*
* @notice The contract is responsible for creating and managing DAOs and Rankify distributions.
* @author Peeramid Labs, 2024
*/
contract MAODistribution is IDistribution, CodeIndexer {
struct UserRankifySettings {
uint256 principalCost;
uint96 principalTimeConstant;
string rankTokenURI;
string rankTokenContractURI;
}
struct TokenArguments {
string tokenName;
string tokenSymbol;
}
struct DistributorArguments {
TokenArguments tokenSettings;
UserRankifySettings rankifySettings;
}
using Clones for address;
address private immutable _trustedForwarder;
ShortString private immutable _distributionName;
uint256 private immutable _distributionVersion;
address private immutable _rankTokenBase;
IDistribution private immutable _RankifyDistributionBase;
address private immutable _governanceERC20Base;
address private immutable _accessManagerBase;
address private immutable _paymentToken;
address private immutable _beneficiary;
uint256 private immutable _minParticipantsInCircle;
/**
* @notice Initializes the contract with the provided parameters and performs necessary checks.
* @dev Retrieves contract addresses from a contract index using the provided identifiers
* and initializes the distribution system.
* @dev WARNING: distributionName must be less then 31 bytes long to comply with ShortStrings immutable format
* @param trustedForwarder Address of the trusted forwarder for meta-transactions (WARNING: Not yet reviewed)
* @param paymentToken Address of the token used for payments in the system
* @param beneficiary Address that receives payments and fees
* @param rankTokenCodeId Identifier for the rank token implementation in CodeIndex
* @param RankifyDIistributionId Identifier for the Rankify distribution implementation
* @param accessManagerId Identifier for the access manager implementation
* @param governanceERC20BaseId Identifier for the governance token implementation
* @param distributionName Name identifier for this distribution
* @param distributionVersion Semantic version information as LibSemver.Version struct
* @param minParticipantsInCircle Minimum number of participants in a circle
*/
constructor(
address trustedForwarder,
address paymentToken,
address beneficiary,
bytes32 rankTokenCodeId,
bytes32 RankifyDIistributionId,
bytes32 accessManagerId,
bytes32 governanceERC20BaseId,
string memory distributionName,
LibSemver.Version memory distributionVersion,
uint256 minParticipantsInCircle
) {
require(minParticipantsInCircle > 2, "minParticipantsInCircle must be greater than 2");
_minParticipantsInCircle = minParticipantsInCircle;
_trustedForwarder = trustedForwarder;
_distributionName = ShortStrings.toShortString(distributionName);
_distributionVersion = LibSemver.toUint256(distributionVersion);
_rankTokenBase = getContractsIndex().get(rankTokenCodeId);
_governanceERC20Base = getContractsIndex().get(governanceERC20BaseId);
if (_governanceERC20Base == address(0)) {
revert("Governance ERC20 base not found");
}
if (beneficiary == address(0)) {
revert("Beneficiary not found");
}
_beneficiary = beneficiary;
if (paymentToken == address(0)) {
revert("Payment token not found");
}
_paymentToken = paymentToken;
if (_rankTokenBase == address(0)) {
revert("Rank token base not found");
}
_RankifyDistributionBase = IDistribution(getContractsIndex().get(RankifyDIistributionId));
if (address(_RankifyDistributionBase) == address(0)) {
revert("Rankify distribution base not found");
}
_accessManagerBase = getContractsIndex().get(accessManagerId);
if (_accessManagerBase == address(0)) {
revert("Access manager base not found");
}
require(
ERC165Checker.supportsInterface(_accessManagerBase, type(IERC7746).interfaceId),
"Access manager does not support IERC7746"
);
}
function createToken(TokenArguments memory args) internal returns (address[] memory instances, bytes32, uint256) {
MintSettings memory mintSettings = MintSettings(new address[](1), new uint256[](1));
mintSettings.receivers[0] = address(this);
mintSettings.amounts[0] = 0;
address token = _governanceERC20Base.clone();
TokenSettings memory tokenSettings = TokenSettings(token, args.tokenName, args.tokenSymbol);
SimpleAccessManager.SimpleAccessManagerInitializer[]
memory govTokenAccessSettings = new SimpleAccessManager.SimpleAccessManagerInitializer[](1);
govTokenAccessSettings[0].selector = DistributableGovernanceERC20.mint.selector;
govTokenAccessSettings[0].disallowedAddresses = new address[](1);
govTokenAccessSettings[0].distributionComponentsOnly = true;
SimpleAccessManager govTokenAccessManager = SimpleAccessManager(_accessManagerBase.clone());
govTokenAccessManager.initialize(govTokenAccessSettings, tokenSettings.addr, IDistributor(msg.sender)); // msg.sender must be IDistributor or it will revert
DistributableGovernanceERC20(tokenSettings.addr).initialize(
tokenSettings.name,
tokenSettings.symbol,
mintSettings,
address(govTokenAccessManager)
);
address[] memory returnValue = new address[](2);
returnValue[0] = token;
returnValue[1] = address(govTokenAccessManager);
return (returnValue, "OSxDistribution", 1);
}
function createRankify(
UserRankifySettings memory args,
address derivedToken
) internal returns (address[] memory instances, bytes32, uint256) {
address rankToken = _rankTokenBase.clone();
bytes4[] memory rankTokenSelectors = new bytes4[](6);
rankTokenSelectors[0] = RankToken.mint.selector;
rankTokenSelectors[1] = RankToken.lock.selector;
rankTokenSelectors[2] = RankToken.unlock.selector;
rankTokenSelectors[3] = RankToken.batchMint.selector;
rankTokenSelectors[4] = RankToken.setURI.selector;
rankTokenSelectors[5] = RankToken.setContractURI.selector;
SimpleAccessManager rankTokenAccessManager = SimpleAccessManager(_accessManagerBase.clone());
SimpleAccessManager.SimpleAccessManagerInitializer[]
memory RankTokenAccessSettings = new SimpleAccessManager.SimpleAccessManagerInitializer[](6);
RankTokenAccessSettings[0].selector = RankToken.mint.selector;
RankTokenAccessSettings[0].disallowedAddresses = new address[](1);
RankTokenAccessSettings[0].distributionComponentsOnly = true;
RankTokenAccessSettings[1].selector = RankToken.lock.selector;
RankTokenAccessSettings[1].disallowedAddresses = new address[](1);
RankTokenAccessSettings[1].distributionComponentsOnly = true;
RankTokenAccessSettings[2].selector = RankToken.unlock.selector;
RankTokenAccessSettings[2].disallowedAddresses = new address[](1);
RankTokenAccessSettings[2].distributionComponentsOnly = true;
RankTokenAccessSettings[3].selector = RankToken.batchMint.selector;
RankTokenAccessSettings[3].disallowedAddresses = new address[](1);
RankTokenAccessSettings[3].distributionComponentsOnly = true;
RankTokenAccessSettings[4].selector = RankToken.setURI.selector;
RankTokenAccessSettings[4].distributionComponentsOnly = true;
RankTokenAccessSettings[5].selector = RankToken.setContractURI.selector;
RankTokenAccessSettings[5].distributionComponentsOnly = true;
rankTokenAccessManager.initialize(RankTokenAccessSettings, rankToken, IDistributor(msg.sender)); // msg.sender must be IDistributor or it will revert
RankToken(rankToken).initialize(args.rankTokenURI, args.rankTokenContractURI, address(rankTokenAccessManager));
(
address[] memory RankifyDistrAddresses,
bytes32 RankifyDistributionName,
uint256 RankifyDistributionVersion
) = _RankifyDistributionBase.instantiate("");
RankifyInstanceInit.contractInitializer memory RankifyInit = RankifyInstanceInit.contractInitializer({
rewardToken: rankToken,
principalCost: args.principalCost,
principalTimeConstant: args.principalTimeConstant,
minimumParticipantsInCircle: _minParticipantsInCircle,
paymentToken: _paymentToken,
beneficiary: _beneficiary,
derivedToken: derivedToken
});
RankifyInstanceInit(RankifyDistrAddresses[0]).init(
ShortStrings.toString(ShortString.wrap(RankifyDistributionName)),
LibSemver.toString(LibSemver.parse(RankifyDistributionVersion)),
RankifyInit
);
address[] memory returnValue = new address[](RankifyDistrAddresses.length + 2);
for (uint256 i; i < RankifyDistrAddresses.length; ++i) {
returnValue[i] = RankifyDistrAddresses[i];
}
returnValue[RankifyDistrAddresses.length] = address(rankTokenAccessManager);
returnValue[RankifyDistrAddresses.length + 1] = rankToken;
return (returnValue, RankifyDistributionName, RankifyDistributionVersion);
}
/**
* @notice Instantiates a new instance with the provided data.
* @param data The initialization data for the new instance, typeof {DistributorArguments}.
* @return instances An array of addresses representing the new instances.
* @return distributionName A bytes32 value representing the name of the distribution.
* @return distributionVersion A uint256 value representing the version of the distribution.
* @dev `instances` array contents: GovernanceToken, Gov Token AccessManager, Rankify Diamond, 8x Rankify Diamond facets, RankTokenAccessManager, RankToken
*/
function instantiate(
bytes memory data
) public override returns (address[] memory instances, bytes32 distributionName, uint256 distributionVersion) {
DistributorArguments memory args = abi.decode(data, (DistributorArguments));
(address[] memory tokenInstances, , ) = createToken(args.tokenSettings);
(address[] memory RankifyInstances, , ) = createRankify(args.rankifySettings, tokenInstances[0]);
address[] memory returnValue = new address[](tokenInstances.length + RankifyInstances.length);
for (uint256 i; i < tokenInstances.length; ++i) {
returnValue[i] = tokenInstances[i];
}
for (uint256 i; i < RankifyInstances.length; ++i) {
returnValue[tokenInstances.length + i] = RankifyInstances[i];
}
return (returnValue, ShortString.unwrap(_distributionName), _distributionVersion);
}
function contractURI() public pure virtual override returns (string memory) {
return "";
}
function get() external view returns (address[] memory sources, bytes32, uint256) {
address[] memory srcs = new address[](5);
srcs[0] = address(_trustedForwarder);
srcs[1] = address(_rankTokenBase);
srcs[2] = address(_RankifyDistributionBase);
srcs[3] = address(_governanceERC20Base);
srcs[4] = address(_accessManagerBase);
return (srcs, ShortString.unwrap(_distributionName), _distributionVersion);
}
/**
* @notice Returns the schema of the distribution.
* @dev This is only needed to ensure `DistributorArguments` are provided in ABI, as it would be internal otherwise.
* @return DistributorArguments The schema of the distribution.
*/
function distributionSchema(DistributorArguments memory args) external pure returns (DistributorArguments memory) {
return args;
}
}