-
Notifications
You must be signed in to change notification settings - Fork 48
/
SablierV2LockupLinear.sol
307 lines (264 loc) · 14.4 KB
/
SablierV2LockupLinear.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
304
305
306
307
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.22;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import { UD60x18, ud } from "@prb/math/src/UD60x18.sol";
import { SablierV2Lockup } from "./abstracts/SablierV2Lockup.sol";
import { SablierV2Lockup } from "./abstracts/SablierV2Lockup.sol";
import { ISablierV2Comptroller } from "./interfaces/ISablierV2Comptroller.sol";
import { ISablierV2LockupLinear } from "./interfaces/ISablierV2LockupLinear.sol";
import { ISablierV2NFTDescriptor } from "./interfaces/ISablierV2NFTDescriptor.sol";
import { Helpers } from "./libraries/Helpers.sol";
import { Lockup, LockupLinear } from "./types/DataTypes.sol";
/*
███████╗ █████╗ ██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗██████╗
██╔════╝██╔══██╗██╔══██╗██║ ██║██╔════╝██╔══██╗ ██║ ██║╚════██╗
███████╗███████║██████╔╝██║ ██║█████╗ ██████╔╝ ██║ ██║ █████╔╝
╚════██║██╔══██║██╔══██╗██║ ██║██╔══╝ ██╔══██╗ ╚██╗ ██╔╝██╔═══╝
███████║██║ ██║██████╔╝███████╗██║███████╗██║ ██║ ╚████╔╝ ███████╗
╚══════╝╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝
██╗ ██████╗ ██████╗██╗ ██╗██╗ ██╗██████╗ ██╗ ██╗███╗ ██╗███████╗ █████╗ ██████╗
██║ ██╔═══██╗██╔════╝██║ ██╔╝██║ ██║██╔══██╗ ██║ ██║████╗ ██║██╔════╝██╔══██╗██╔══██╗
██║ ██║ ██║██║ █████╔╝ ██║ ██║██████╔╝ ██║ ██║██╔██╗ ██║█████╗ ███████║██████╔╝
██║ ██║ ██║██║ ██╔═██╗ ██║ ██║██╔═══╝ ██║ ██║██║╚██╗██║██╔══╝ ██╔══██║██╔══██╗
███████╗╚██████╔╝╚██████╗██║ ██╗╚██████╔╝██║ ███████╗██║██║ ╚████║███████╗██║ ██║██║ ██║
╚══════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝
*/
/// @title SablierV2LockupLinear
/// @notice See the documentation in {ISablierV2LockupLinear}.
contract SablierV2LockupLinear is
ISablierV2LockupLinear, // 5 inherited components
SablierV2Lockup // 14 inherited components
{
using SafeERC20 for IERC20;
/*//////////////////////////////////////////////////////////////////////////
STATE VARIABLES
//////////////////////////////////////////////////////////////////////////*/
/// @dev Cliff times mapped by stream id.
mapping(uint256 id => uint40 cliff) internal _cliffs;
/*//////////////////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////////////////*/
/// @dev Emits a {TransferAdmin} event.
/// @param initialAdmin The address of the initial contract admin.
/// @param initialComptroller The address of the initial comptroller.
/// @param initialNFTDescriptor The address of the initial NFT descriptor.
constructor(
address initialAdmin,
ISablierV2Comptroller initialComptroller,
ISablierV2NFTDescriptor initialNFTDescriptor
)
ERC721("Sablier V2 Lockup Linear NFT", "SAB-V2-LOCKUP-LIN")
SablierV2Lockup(initialAdmin, initialComptroller, initialNFTDescriptor)
{
nextStreamId = 1;
}
/*//////////////////////////////////////////////////////////////////////////
USER-FACING CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @inheritdoc ISablierV2LockupLinear
function getCliffTime(uint256 streamId) external view override notNull(streamId) returns (uint40 cliffTime) {
cliffTime = _cliffs[streamId];
}
/// @inheritdoc ISablierV2LockupLinear
function getRange(uint256 streamId)
external
view
override
notNull(streamId)
returns (LockupLinear.Range memory range)
{
range = LockupLinear.Range({
start: _streams[streamId].startTime,
cliff: _cliffs[streamId],
end: _streams[streamId].endTime
});
}
/// @inheritdoc ISablierV2LockupLinear
function getStream(uint256 streamId)
external
view
override
notNull(streamId)
returns (LockupLinear.Stream memory stream)
{
Lockup.Stream memory lockupStream = _streams[streamId];
// Settled streams cannot be canceled.
if (_statusOf(streamId) == Lockup.Status.SETTLED) {
lockupStream.isCancelable = false;
}
stream = LockupLinear.Stream({
amounts: lockupStream.amounts,
asset: lockupStream.asset,
cliffTime: _cliffs[streamId],
endTime: lockupStream.endTime,
isCancelable: lockupStream.isCancelable,
isTransferable: lockupStream.isTransferable,
isDepleted: lockupStream.isDepleted,
isStream: lockupStream.isStream,
sender: lockupStream.sender,
startTime: lockupStream.startTime,
wasCanceled: lockupStream.wasCanceled
});
}
function streamedAmountOf(uint256 streamId)
public
view
override(SablierV2Lockup, ISablierV2LockupLinear)
returns (uint128)
{
return super.streamedAmountOf(streamId);
}
/*//////////////////////////////////////////////////////////////////////////
USER-FACING NON-CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @inheritdoc ISablierV2LockupLinear
function createWithDurations(LockupLinear.CreateWithDurations calldata params)
external
override
noDelegateCall
returns (uint256 streamId)
{
// Set the current block timestamp as the stream's start time.
LockupLinear.Range memory range;
range.start = uint40(block.timestamp);
// Calculate the cliff time and the end time. It is safe to use unchecked arithmetic because
// {_createWithTimestamps} will nonetheless check that the end time is greater than the cliff time,
// and also that the cliff time is greater than or equal to the start time.
unchecked {
range.cliff = range.start + params.durations.cliff;
range.end = range.start + params.durations.total;
}
// Checks, Effects and Interactions: create the stream.
streamId = _createWithTimestamps(
LockupLinear.CreateWithTimestamps({
sender: params.sender,
recipient: params.recipient,
totalAmount: params.totalAmount,
asset: params.asset,
cancelable: params.cancelable,
transferable: params.transferable,
range: range,
broker: params.broker
})
);
}
/// @inheritdoc ISablierV2LockupLinear
function createWithTimestamps(LockupLinear.CreateWithTimestamps calldata params)
external
override
noDelegateCall
returns (uint256 streamId)
{
// Checks, Effects and Interactions: create the stream.
streamId = _createWithTimestamps(params);
}
/*//////////////////////////////////////////////////////////////////////////
INTERNAL CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @dev Calculates the streamed amount without looking up the stream's status.
function _calculateStreamedAmount(uint256 streamId) internal view override returns (uint128) {
// If the cliff time is in the future, return zero.
uint256 cliffTime = uint256(_cliffs[streamId]);
uint256 currentTime = block.timestamp;
if (cliffTime > currentTime) {
return 0;
}
// If the end time is not in the future, return the deposited amount.
uint256 endTime = uint256(_streams[streamId].endTime);
if (currentTime >= endTime) {
return _streams[streamId].amounts.deposited;
}
// In all other cases, calculate the amount streamed so far. Normalization to 18 decimals is not needed
// because there is no mix of amounts with different decimals.
unchecked {
// Calculate how much time has passed since the stream started, and the stream's total duration.
uint256 startTime = uint256(_streams[streamId].startTime);
UD60x18 elapsedTime = ud(currentTime - startTime);
UD60x18 totalTime = ud(endTime - startTime);
// Divide the elapsed time by the stream's total duration.
UD60x18 elapsedTimePercentage = elapsedTime.div(totalTime);
// Cast the deposited amount to UD60x18.
UD60x18 depositedAmount = ud(_streams[streamId].amounts.deposited);
// Calculate the streamed amount by multiplying the elapsed time percentage by the deposited amount.
UD60x18 streamedAmount = elapsedTimePercentage.mul(depositedAmount);
// Although the streamed amount should never exceed the deposited amount, this condition is checked
// without asserting to avoid locking funds in case of a bug. If this situation occurs, the withdrawn
// amount is considered to be the streamed amount, and the stream is effectively frozen.
if (streamedAmount.gt(depositedAmount)) {
return _streams[streamId].amounts.withdrawn;
}
// Cast the streamed amount to uint128. This is safe due to the check above.
return uint128(streamedAmount.intoUint256());
}
}
/*//////////////////////////////////////////////////////////////////////////
INTERNAL NON-CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @dev See the documentation for the user-facing functions that call this internal function.
function _createWithTimestamps(LockupLinear.CreateWithTimestamps memory params)
internal
returns (uint256 streamId)
{
// Safe Interactions: query the protocol fee. This is safe because it's a known Sablier contract that does
// not call other unknown contracts.
UD60x18 protocolFee = comptroller.protocolFees(params.asset);
// Checks: check the fees and calculate the fee amounts.
Lockup.CreateAmounts memory createAmounts =
Helpers.checkAndCalculateFees(params.totalAmount, protocolFee, params.broker.fee, MAX_FEE);
// Checks: validate the user-provided parameters.
Helpers.checkCreateWithTimestamps(createAmounts.deposit, params.range);
// Load the stream id.
streamId = nextStreamId;
// Effects: create the stream.
_streams[streamId] = Lockup.Stream({
amounts: Lockup.Amounts({ deposited: createAmounts.deposit, refunded: 0, withdrawn: 0 }),
asset: params.asset,
endTime: params.range.end,
isCancelable: params.cancelable,
isTransferable: params.transferable,
isDepleted: false,
isStream: true,
sender: params.sender,
startTime: params.range.start,
wasCanceled: false
});
// Effects: set the cliff time.
_cliffs[streamId] = params.range.cliff;
// Effects: bump the next stream id and record the protocol fee.
// Using unchecked arithmetic because these calculations cannot realistically overflow, ever.
unchecked {
nextStreamId = streamId + 1;
protocolRevenues[params.asset] = protocolRevenues[params.asset] + createAmounts.protocolFee;
}
// Effects: mint the NFT to the recipient.
_mint({ to: params.recipient, tokenId: streamId });
// Interactions: transfer the deposit and the protocol fee.
// Using unchecked arithmetic because the deposit and the protocol fee are bounded by the total amount.
unchecked {
params.asset.safeTransferFrom({
from: msg.sender,
to: address(this),
value: createAmounts.deposit + createAmounts.protocolFee
});
}
// Interactions: pay the broker fee, if not zero.
if (createAmounts.brokerFee > 0) {
params.asset.safeTransferFrom({ from: msg.sender, to: params.broker.account, value: createAmounts.brokerFee });
}
// Log the newly created stream.
emit ISablierV2LockupLinear.CreateLockupLinearStream({
streamId: streamId,
funder: msg.sender,
sender: params.sender,
recipient: params.recipient,
amounts: createAmounts,
asset: params.asset,
cancelable: params.cancelable,
transferable: params.transferable,
range: params.range,
broker: params.broker.account
});
}
}