-
Notifications
You must be signed in to change notification settings - Fork 48
/
Helpers.sol
210 lines (181 loc) · 8.53 KB
/
Helpers.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
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.22;
import { UD60x18, ud } from "@prb/math/src/UD60x18.sol";
import { Lockup, LockupDynamic, LockupLinear } from "../types/DataTypes.sol";
import { Errors } from "./Errors.sol";
/// @title Helpers
/// @notice Library with helper functions needed across the Sablier V2 contracts.
library Helpers {
/*//////////////////////////////////////////////////////////////////////////
INTERNAL CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @dev Checks that neither fee is greater than `maxFee`, and then calculates the protocol fee amount, the
/// broker fee amount, and the deposit amount from the total amount.
function checkAndCalculateFees(
uint128 totalAmount,
UD60x18 protocolFee,
UD60x18 brokerFee,
UD60x18 maxFee
)
internal
pure
returns (Lockup.CreateAmounts memory amounts)
{
// When the total amount is zero, the fees are also zero.
if (totalAmount == 0) {
return Lockup.CreateAmounts(0, 0, 0);
}
// Checks: the protocol fee is not greater than `maxFee`.
if (protocolFee.gt(maxFee)) {
revert Errors.SablierV2Lockup_ProtocolFeeTooHigh(protocolFee, maxFee);
}
// Checks: the broker fee is not greater than `maxFee`.
if (brokerFee.gt(maxFee)) {
revert Errors.SablierV2Lockup_BrokerFeeTooHigh(brokerFee, maxFee);
}
// Calculate the protocol fee amount.
// The cast to uint128 is safe because the maximum fee is hard coded.
amounts.protocolFee = uint128(ud(totalAmount).mul(protocolFee).intoUint256());
// Calculate the broker fee amount.
// The cast to uint128 is safe because the maximum fee is hard coded.
amounts.brokerFee = uint128(ud(totalAmount).mul(brokerFee).intoUint256());
// Assert that the total amount is strictly greater than the sum of the protocol fee amount and the
// broker fee amount.
assert(totalAmount > amounts.protocolFee + amounts.brokerFee);
// Calculate the deposit amount (the amount to stream, net of fees).
amounts.deposit = totalAmount - amounts.protocolFee - amounts.brokerFee;
}
/// @dev Checks the parameters of the {SablierV2LockupDynamic-_createWithTimestamps} function.
function checkCreateWithTimestamps(
uint128 depositAmount,
LockupDynamic.Segment[] memory segments,
uint256 maxSegmentCount,
uint40 startTime
)
internal
view
{
// Checks: the deposit amount is not zero.
if (depositAmount == 0) {
revert Errors.SablierV2Lockup_DepositAmountZero();
}
// Checks: the segment count is not zero.
uint256 segmentCount = segments.length;
if (segmentCount == 0) {
revert Errors.SablierV2LockupDynamic_SegmentCountZero();
}
// Checks: the segment count is not greater than the maximum allowed.
if (segmentCount > maxSegmentCount) {
revert Errors.SablierV2LockupDynamic_SegmentCountTooHigh(segmentCount);
}
// Checks: requirements of segments variables.
_checkSegments(segments, depositAmount, startTime);
}
/// @dev Checks the parameters of the {SablierV2LockupLinear-_createWithTimestamps} function.
function checkCreateWithTimestamps(uint128 depositAmount, LockupLinear.Range memory range) internal view {
// Checks: the deposit amount is not zero.
if (depositAmount == 0) {
revert Errors.SablierV2Lockup_DepositAmountZero();
}
// Checks: the start time is less than or equal to the cliff time.
if (range.start > range.cliff) {
revert Errors.SablierV2LockupLinear_StartTimeGreaterThanCliffTime(range.start, range.cliff);
}
// Checks: the cliff time is strictly less than the end time.
if (range.cliff >= range.end) {
revert Errors.SablierV2LockupLinear_CliffTimeNotLessThanEndTime(range.cliff, range.end);
}
// Checks: the end time is in the future.
uint40 currentTime = uint40(block.timestamp);
if (currentTime >= range.end) {
revert Errors.SablierV2Lockup_EndTimeNotInTheFuture(currentTime, range.end);
}
}
/// @dev Checks that the segment array counts match, and then adjusts the segments by calculating the timestamps.
function checkDurationsAndCalculateTimestamps(LockupDynamic.SegmentWithDuration[] memory segments)
internal
view
returns (LockupDynamic.Segment[] memory segmentsWithTimestamps)
{
uint256 segmentCount = segments.length;
segmentsWithTimestamps = new LockupDynamic.Segment[](segmentCount);
// Make the current time the stream's start time.
uint40 startTime = uint40(block.timestamp);
// It is safe to use unchecked arithmetic because {_createWithTimestamps} will nonetheless check the soundness
// of the calculated segment timestamps.
unchecked {
// Precompute the first segment because of the need to add the start time to the first segment duration.
segmentsWithTimestamps[0] = LockupDynamic.Segment({
amount: segments[0].amount,
exponent: segments[0].exponent,
timestamp: startTime + segments[0].duration
});
// Copy the segment amounts and exponents, and calculate the segment timestamps.
for (uint256 i = 1; i < segmentCount; ++i) {
segmentsWithTimestamps[i] = LockupDynamic.Segment({
amount: segments[i].amount,
exponent: segments[i].exponent,
timestamp: segmentsWithTimestamps[i - 1].timestamp + segments[i].duration
});
}
}
}
/*//////////////////////////////////////////////////////////////////////////
PRIVATE CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @dev Checks that:
///
/// 1. The first timestamp is strictly greater than the start time.
/// 2. The timestamps are ordered chronologically.
/// 3. There are no duplicate timestamps.
/// 4. The deposit amount is equal to the sum of all segment amounts.
function _checkSegments(
LockupDynamic.Segment[] memory segments,
uint128 depositAmount,
uint40 startTime
)
private
view
{
// Checks: the start time is strictly less than the first segment timestamp.
if (startTime >= segments[0].timestamp) {
revert Errors.SablierV2LockupDynamic_StartTimeNotLessThanFirstSegmentTimestamp(
startTime, segments[0].timestamp
);
}
// Pre-declare the variables needed in the for loop.
uint128 segmentAmountsSum;
uint40 currentTimestamp;
uint40 previousTimestamp;
// Iterate over the segments to:
//
// 1. Calculate the sum of all segment amounts.
// 2. Check that the timestamps are ordered.
uint256 count = segments.length;
for (uint256 index = 0; index < count; ++index) {
// Add the current segment amount to the sum.
segmentAmountsSum += segments[index].amount;
// Checks: the current timestamp is strictly greater than the previous timestamp.
currentTimestamp = segments[index].timestamp;
if (currentTimestamp <= previousTimestamp) {
revert Errors.SablierV2LockupDynamic_SegmentTimestampsNotOrdered(
index, previousTimestamp, currentTimestamp
);
}
// Make the current timestamp the previous timestamp of the next loop iteration.
previousTimestamp = currentTimestamp;
}
// Checks: the last timestamp is in the future.
// When the loop exits, the current timestamp is the last timestamp, i.e. the stream's end time.
uint40 currentTime = uint40(block.timestamp);
if (currentTime >= currentTimestamp) {
revert Errors.SablierV2Lockup_EndTimeNotInTheFuture(currentTime, currentTimestamp);
}
// Checks: the deposit amount is equal to the segment amounts sum.
if (depositAmount != segmentAmountsSum) {
revert Errors.SablierV2LockupDynamic_DepositAmountNotEqualToSegmentAmountsSum(
depositAmount, segmentAmountsSum
);
}
}
}