Summary
The checkCreateLockupTranched function lacks explicit checks for maximum and minimum durations of the lockup. This omission can lead to potential misuse and unexpected behavior of the protocol, as users can create lockups with durations that are either impractically short or excessively long.
Proof of Concept (PoC)
To demonstrate the impact, consider the following scenarios where users create lockups with impractical durations:
Excessively Long Duration:
uint128 depositAmount = 1000;
LockupTranched.Tranche[] memory tranches = new LockupTranched.Tranche[](1);
tranches[0] = LockupTranched.Tranche({amount: 1000, timestamp: block.timestamp + 100 * 365 * 24 * 60 * 60});
uint256 maxTrancheCount = 10;
uint40 startTime = uint40(block.timestamp);
checkCreateLockupTranched(depositAmount, tranches, maxTrancheCount, startTime);
Impractically Short Duration:
uint128 depositAmount = 1000;
LockupTranched.Tranche[] memory tranches = new LockupTranched.Tranche[](1);
tranches[0] = LockupTranched.Tranche({amount: 1000, timestamp: block.timestamp + 1});
uint256 maxTrancheCount = 10;
uint40 startTime = uint40(block.timestamp);
checkCreateLockupTranched(depositAmount, tranches, maxTrancheCount, startTime);
Impact
Users can create lockups with very long durations, which might lock assets for an impractical amount of time. This could lead to liquidity issues and limit the usability of the protocol.
Users can create lockups with very short durations, which might not serve the intended purpose of the lockup. This could undermine the security and functionality of the protocol.
Malicious actors can exploit the absence of these checks to create lockups with durations that could disrupt the protocol’s operations or lead to unexpected edge cases.
Tools Used
manual review
Recommendations
To mitigate this issue, add explicit checks for maximum and minimum durations within the checkCreateLockupTranched function and _checkTranches function. Here are the enhanced versions of these functions:
Updated checkCreateLockupTranched Function
function checkCreateLockupTranched(
uint128 depositAmount,
LockupTranched.Tranche[] memory tranches,
uint256 maxTrancheCount,
uint40 startTime,
uint40 maxDuration,
uint40 minDuration
)
internal
view
{
if (depositAmount == 0) {
revert Errors.SablierV2Lockup_DepositAmountZero();
}
if (startTime == 0) {
revert Errors.SablierV2Lockup_StartTimeZero();
}
uint40 blockTimestamp = uint40(block.timestamp);
if (startTime <= blockTimestamp) {
revert Errors.SablierV2Lockup_StartTimeNotInTheFuture(blockTimestamp, startTime);
}
uint256 trancheCount = tranches.length;
if (trancheCount == 0) {
revert Errors.SablierV2LockupTranched_TrancheCountZero();
}
if (trancheCount > maxTrancheCount) {
revert Errors.SablierV2LockupTranched_TrancheCountTooHigh(trancheCount);
}
_checkTranches(tranches, depositAmount, startTime, maxDuration, minDuration);
}
Updated _checkTranches Function
function _checkTranches(
LockupTranched.Tranche[] memory tranches,
uint128 depositAmount,
uint40 startTime,
uint40 maxDuration,
uint40 minDuration
)
private
view
{
if (startTime >= tranches[0].timestamp) {
revert Errors.SablierV2LockupTranched_StartTimeNotLessThanFirstTrancheTimestamp(
startTime, tranches[0].timestamp
);
}
uint128 trancheAmountsSum;
uint40 currentTrancheTimestamp;
uint40 previousTrancheTimestamp;
uint256 count = tranches.length;
for (uint256 index = 0; index < count; ++index) {
trancheAmountsSum += tranches[index].amount;
currentTrancheTimestamp = tranches[index].timestamp;
if (currentTrancheTimestamp <= previousTrancheTimestamp) {
revert Errors.SablierV2LockupTranched_TrancheTimestampsNotOrdered(
index, previousTrancheTimestamp, currentTrancheTimestamp
);
}
previousTrancheTimestamp = currentTrancheTimestamp;
}
uint40 blockTimestamp = uint40(block.timestamp);
if (blockTimestamp >= currentTrancheTimestamp) {
revert Errors.SablierV2Lockup_EndTimeNotInTheFuture(blockTimestamp, currentTrancheTimestamp);
}
if (depositAmount != trancheAmountsSum) {
revert Errors.SablierV2LockupTranched_DepositAmountNotEqualToTrancheAmountsSum(
depositAmount, trancheAmountsSum
);
}
uint40 endTime = currentTrancheTimestamp;
uint40 duration = endTime - startTime;
if (duration > maxDuration) {
revert Errors.SablierV2LockupTranched_DurationTooLong(duration, maxDuration);
}
if (duration < minDuration) {
revert Errors.SablierV2LockupTranched_DurationTooShort(duration, minDuration);
}
}