-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathSablierMerkleFactory.sol
303 lines (262 loc) · 13.3 KB
/
SablierMerkleFactory.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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.22;
import { uUNIT } from "@prb/math/src/UD2x18.sol";
import { Adminable } from "@sablier/lockup/src/abstracts/Adminable.sol";
import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol";
import { ISablierMerkleBase } from "./interfaces/ISablierMerkleBase.sol";
import { ISablierMerkleFactory } from "./interfaces/ISablierMerkleFactory.sol";
import { ISablierMerkleInstant } from "./interfaces/ISablierMerkleInstant.sol";
import { ISablierMerkleLL } from "./interfaces/ISablierMerkleLL.sol";
import { ISablierMerkleLT } from "./interfaces/ISablierMerkleLT.sol";
import { SablierMerkleInstant } from "./SablierMerkleInstant.sol";
import { SablierMerkleLL } from "./SablierMerkleLL.sol";
import { SablierMerkleLT } from "./SablierMerkleLT.sol";
import { MerkleBase, MerkleFactory, MerkleLL, MerkleLT } from "./types/DataTypes.sol";
/*
███████╗ █████╗ ██████╗ ██╗ ██╗███████╗██████╗
██╔════╝██╔══██╗██╔══██╗██║ ██║██╔════╝██╔══██╗
███████╗███████║██████╔╝██║ ██║█████╗ ██████╔╝
╚════██║██╔══██║██╔══██╗██║ ██║██╔══╝ ██╔══██╗
███████║██║ ██║██████╔╝███████╗██║███████╗██║ ██║
╚══════╝╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝╚══════╝╚═╝ ╚═╝
███╗ ███╗███████╗██████╗ ██╗ ██╗██╗ ███████╗ ███████╗ █████╗ ██████╗████████╗ ██████╗ ██████╗ ██╗ ██╗
████╗ ████║██╔════╝██╔══██╗██║ ██╔╝██║ ██╔════╝ ██╔════╝██╔══██╗██╔════╝╚══██╔══╝██╔═══██╗██╔══██╗╚██╗ ██╔╝
██╔████╔██║█████╗ ██████╔╝█████╔╝ ██║ █████╗ █████╗ ███████║██║ ██║ ██║ ██║██████╔╝ ╚████╔╝
██║╚██╔╝██║██╔══╝ ██╔══██╗██╔═██╗ ██║ ██╔══╝ ██╔══╝ ██╔══██║██║ ██║ ██║ ██║██╔══██╗ ╚██╔╝
██║ ╚═╝ ██║███████╗██║ ██║██║ ██╗███████╗███████╗ ██║ ██║ ██║╚██████╗ ██║ ╚██████╔╝██║ ██║ ██║
╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝
*/
/// @title SablierMerkleFactory
/// @notice See the documentation in {ISablierMerkleFactory}.
contract SablierMerkleFactory is
ISablierMerkleFactory, // 2 inherited components
Adminable // 1 inherited component
{
/*//////////////////////////////////////////////////////////////////////////
STATE VARIABLES
//////////////////////////////////////////////////////////////////////////*/
/// @inheritdoc ISablierMerkleFactory
uint256 public override defaultFee;
/// @dev A mapping of custom fees mapped by campaign creator addresses.
mapping(address campaignCreator => MerkleFactory.CustomFee customFee) private _customFees;
/*//////////////////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////////////////*/
/// @param initialAdmin The address of the initial contract admin.
constructor(address initialAdmin) Adminable(initialAdmin) { }
/*//////////////////////////////////////////////////////////////////////////
USER-FACING CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @inheritdoc ISablierMerkleFactory
function getCustomFee(address campaignCreator) external view override returns (MerkleFactory.CustomFee memory) {
return _customFees[campaignCreator];
}
/// @inheritdoc ISablierMerkleFactory
function getFee(address campaignCreator) external view returns (uint256) {
return _getFee(campaignCreator);
}
/// @inheritdoc ISablierMerkleFactory
function isPercentagesSum100(MerkleLT.TrancheWithPercentage[] calldata tranches)
external
pure
override
returns (bool result)
{
uint256 totalPercentage;
for (uint256 i = 0; i < tranches.length; ++i) {
totalPercentage += tranches[i].unlockPercentage.unwrap();
}
return totalPercentage == uUNIT;
}
/*//////////////////////////////////////////////////////////////////////////
USER-FACING NON-CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @inheritdoc ISablierMerkleFactory
function collectFees(ISablierMerkleBase merkleBase) external override {
// Effect: collect the fees from the MerkleBase contract.
uint256 feeAmount = merkleBase.collectFees(admin);
// Log the fee withdrawal.
emit CollectFees({ admin: admin, merkleBase: merkleBase, feeAmount: feeAmount });
}
/// @inheritdoc ISablierMerkleFactory
function createMerkleInstant(
MerkleBase.ConstructorParams memory baseParams,
uint256 aggregateAmount,
uint256 recipientCount
)
external
override
returns (ISablierMerkleInstant merkleInstant)
{
// Hash the parameters to generate a salt.
bytes32 salt = keccak256(abi.encodePacked(msg.sender, abi.encode(baseParams)));
// Deploy the MerkleInstant contract with CREATE2.
merkleInstant = new SablierMerkleInstant{ salt: salt }({ baseParams: baseParams, campaignCreator: msg.sender });
// Log the creation of the MerkleInstant contract, including some metadata that is not stored on-chain.
emit CreateMerkleInstant({
merkleInstant: merkleInstant,
baseParams: baseParams,
aggregateAmount: aggregateAmount,
recipientCount: recipientCount,
fee: _getFee(msg.sender)
});
}
/// @inheritdoc ISablierMerkleFactory
function createMerkleLL(
MerkleBase.ConstructorParams memory baseParams,
ISablierLockup lockup,
bool cancelable,
bool transferable,
MerkleLL.Schedule memory schedule,
uint256 aggregateAmount,
uint256 recipientCount
)
external
override
returns (ISablierMerkleLL merkleLL)
{
// Hash the parameters to generate a salt.
bytes32 salt = keccak256(
abi.encodePacked(msg.sender, abi.encode(baseParams), lockup, cancelable, transferable, abi.encode(schedule))
);
// Deploy the MerkleLL contract with CREATE2.
merkleLL = new SablierMerkleLL{ salt: salt }({
baseParams: baseParams,
campaignCreator: msg.sender,
lockup: lockup,
cancelable: cancelable,
transferable: transferable,
schedule: schedule
});
// Log the creation of the MerkleLL contract, including some metadata that is not stored on-chain.
emit CreateMerkleLL({
merkleLL: merkleLL,
baseParams: baseParams,
lockup: lockup,
cancelable: cancelable,
transferable: transferable,
schedule: schedule,
aggregateAmount: aggregateAmount,
recipientCount: recipientCount,
fee: _getFee(msg.sender)
});
}
/// @inheritdoc ISablierMerkleFactory
function createMerkleLT(
MerkleBase.ConstructorParams memory baseParams,
ISablierLockup lockup,
bool cancelable,
bool transferable,
uint40 streamStartTime,
MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages,
uint256 aggregateAmount,
uint256 recipientCount
)
external
override
returns (ISablierMerkleLT merkleLT)
{
// Calculate the sum of percentages and durations across all tranches.
uint256 count = tranchesWithPercentages.length;
uint256 totalDuration;
for (uint256 i = 0; i < count; ++i) {
unchecked {
// Safe to use `unchecked` because its only used in the event.
totalDuration += tranchesWithPercentages[i].duration;
}
}
// Deploy the MerkleLT contract.
merkleLT = _deployMerkleLT({
baseParams: baseParams,
lockup: lockup,
cancelable: cancelable,
transferable: transferable,
streamStartTime: streamStartTime,
tranchesWithPercentages: tranchesWithPercentages
});
// Log the creation of the MerkleLT contract, including some metadata that is not stored on-chain.
emit CreateMerkleLT({
merkleLT: merkleLT,
baseParams: baseParams,
lockup: lockup,
cancelable: cancelable,
transferable: transferable,
streamStartTime: streamStartTime,
tranchesWithPercentages: tranchesWithPercentages,
totalDuration: totalDuration,
aggregateAmount: aggregateAmount,
recipientCount: recipientCount,
fee: _getFee(msg.sender)
});
}
/// @inheritdoc ISablierMerkleFactory
function resetCustomFee(address campaignCreator) external override onlyAdmin {
delete _customFees[campaignCreator];
// Log the reset.
emit ResetCustomFee({ admin: msg.sender, campaignCreator: campaignCreator });
}
/// @inheritdoc ISablierMerkleFactory
function setCustomFee(address campaignCreator, uint256 newFee) external override onlyAdmin {
MerkleFactory.CustomFee storage customFeeByUser = _customFees[campaignCreator];
// Check: if the user is not in the custom fee list.
if (!customFeeByUser.enabled) {
customFeeByUser.enabled = true;
}
// Effect: update the custom fee for the given campaign creator.
customFeeByUser.fee = newFee;
// Log the update.
emit SetCustomFee({ admin: msg.sender, campaignCreator: campaignCreator, customFee: newFee });
}
/// @inheritdoc ISablierMerkleFactory
function setDefaultFee(uint256 defaultFee_) external override onlyAdmin {
// Effect: update the default fee.
defaultFee = defaultFee_;
emit SetDefaultFee(msg.sender, defaultFee_);
}
/*//////////////////////////////////////////////////////////////////////////
PRIVATE CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @notice Retrieves the fee for the provided campaign creator, using the default fee if no custom fee is set.
function _getFee(address campaignCreator) private view returns (uint256) {
return _customFees[campaignCreator].enabled ? _customFees[campaignCreator].fee : defaultFee;
}
/*//////////////////////////////////////////////////////////////////////////
PRIVATE NON-CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @notice Deploys a new MerkleLT contract with CREATE2.
/// @dev We need a separate function to prevent the stack too deep error.
function _deployMerkleLT(
MerkleBase.ConstructorParams memory baseParams,
ISablierLockup lockup,
bool cancelable,
bool transferable,
uint40 streamStartTime,
MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages
)
private
returns (ISablierMerkleLT merkleLT)
{
// Hash the parameters to generate a salt.
bytes32 salt = keccak256(
abi.encodePacked(
msg.sender,
abi.encode(baseParams),
lockup,
cancelable,
transferable,
streamStartTime,
abi.encode(tranchesWithPercentages)
)
);
// Deploy the MerkleLT contract with CREATE2.
merkleLT = new SablierMerkleLT{ salt: salt }({
baseParams: baseParams,
campaignCreator: msg.sender,
lockup: lockup,
cancelable: cancelable,
transferable: transferable,
streamStartTime: streamStartTime,
tranchesWithPercentages: tranchesWithPercentages
});
}
}