Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Address audit findings 3, 5, and 11 #1131

Merged
merged 6 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion benchmark/LockupDynamic.Gas.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ contract Lockup_Dynamic_Gas_Test is Benchmark_Test {
vm.writeFile({
path: benchmarkResultsFile,
data: string.concat(
"# Benchmarks for Lockup Dynamic Model\n\n", "| Implementation | Gas Usage |\n", "| --- | --- |\n"
"# Benchmarks for the Lockup Dynamic model\n\n", "| Implementation | Gas Usage |\n", "| --- | --- |\n"
)
});

Expand Down
2 changes: 1 addition & 1 deletion benchmark/LockupLinear.Gas.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ contract Lockup_Linear_Gas_Test is Benchmark_Test {
vm.writeFile({
path: benchmarkResultsFile,
data: string.concat(
"# Benchmarks for Lockup Linear Model\n\n", "| Implementation | Gas Usage |\n", "| --- | --- |\n"
"# Benchmarks for the Lockup Linear model\n\n", "| Implementation | Gas Usage |\n", "| --- | --- |\n"
)
});

Expand Down
2 changes: 1 addition & 1 deletion benchmark/LockupTranched.Gas.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ contract Lockup_Tranched_Gas_Test is Benchmark_Test {
vm.writeFile({
path: benchmarkResultsFile,
data: string.concat(
"# Benchmarks for Lockup Tranched Model\n\n", "| Implementation | Gas Usage |\n", "| --- | --- |\n"
"# Benchmarks for the Lockup Tranched model\n\n", "| Implementation | Gas Usage |\n", "| --- | --- |\n"
)
});

Expand Down
2 changes: 1 addition & 1 deletion benchmark/results/SablierLockup_Dynamic.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Benchmarks for Lockup Dynamic Model
# Benchmarks for the Lockup Dynamic model

| Implementation | Gas Usage |
| ------------------------------------------------------------ | --------- |
Expand Down
2 changes: 1 addition & 1 deletion benchmark/results/SablierLockup_Linear.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Benchmarks for Lockup Linear Model
# Benchmarks for the Lockup Linear model

| Implementation | Gas Usage |
| ------------------------------------------------------------- | --------- |
Expand Down
2 changes: 1 addition & 1 deletion benchmark/results/SablierLockup_Tranched.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Benchmarks for Lockup Tranched Model
# Benchmarks for the Lockup Tranched model

| Implementation | Gas Usage |
| ------------------------------------------------------------ | --------- |
Expand Down
5 changes: 1 addition & 4 deletions src/LockupNFTDescriptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,7 @@ contract LockupNFTDescriptor is ILockupNFTDescriptor {
pure
returns (uint256)
{
// This cannot overflow because both inputs are uint128s, and zero deposit amounts are not allowed in Sablier.
unchecked {
return streamedAmount * 10_000 / depositedAmount;
}
andreivladbrg marked this conversation as resolved.
Show resolved Hide resolved
return streamedAmount * 10_000 / depositedAmount;
}

/// @notice Generates a pseudo-random HSL color by hashing together the `chainid`, the `sablier` address,
Expand Down
23 changes: 14 additions & 9 deletions src/SablierLockup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {

// Calculate the cliff time and the end time. It is safe to use unchecked arithmetic because {_createLL} will
// nonetheless check that the end time is greater than the cliff time, and also that the cliff time, if set,
// is greater than or equal to the start time.
// is greater than the start time.
unchecked {
if (durations.cliff > 0) {
cliffTime = timestamps.start + durations.cliff;
Expand Down Expand Up @@ -302,7 +302,7 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {

// If the start time is in the future, return zero.
uint40 blockTimestamp = uint40(block.timestamp);
if (timestamps.start >= blockTimestamp) {
if (timestamps.start > blockTimestamp) {
return 0;
}

Expand All @@ -315,27 +315,32 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {
uint128 streamedAmount;
Lockup.Model lockupModel = _streams[streamId].lockupModel;

// Calculate streamed amount for Lockup Dynamic model.
// Calculate streamed amount for the Lockup Dynamic model.
andreivladbrg marked this conversation as resolved.
Show resolved Hide resolved
if (lockupModel == Lockup.Model.LOCKUP_DYNAMIC) {
streamedAmount = VestingMath.calculateLockupDynamicStreamedAmount({
segments: _segments[streamId],
blockTimestamp: blockTimestamp,
startTime: timestamps.start,
withdrawnAmount: _streams[streamId].amounts.withdrawn
});
}
// Calculate streamed amount for Lockup Linear model.
// Calculate streamed amount for the Lockup Linear model.
else if (lockupModel == Lockup.Model.LOCKUP_LINEAR) {
streamedAmount = VestingMath.calculateLockupLinearStreamedAmount({
depositedAmount: depositedAmount,
blockTimestamp: blockTimestamp,
timestamps: timestamps,
cliffTime: _cliffs[streamId],
unlockAmounts: _unlockAmounts[streamId],
withdrawnAmount: _streams[streamId].amounts.withdrawn
});
}
// Calculate streamed amount for Lockup Tranched model.
// Calculate streamed amount for the Lockup Tranched model.
else if (lockupModel == Lockup.Model.LOCKUP_TRANCHED) {
streamedAmount = VestingMath.calculateLockupTranchedStreamedAmount({ tranches: _tranches[streamId] });
streamedAmount = VestingMath.calculateLockupTranchedStreamedAmount({
blockTimestamp: blockTimestamp,
tranches: _tranches[streamId]
});
}

return streamedAmount;
Expand Down Expand Up @@ -471,16 +476,16 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {
// Load the stream ID in a variable.
streamId = nextStreamId;

// Effect: set the start unlock amount if its non-zero.
// Effect: set the start unlock amount if it is non-zero.
if (unlockAmounts.start > 0) {
_unlockAmounts[streamId].start = unlockAmounts.start;
}

// Effect: update cliff time if its non-zero.
// Effect: update cliff time if it is non-zero.
if (cliffTime > 0) {
_cliffs[streamId] = cliffTime;

// Effect: set the cliff unlock amount if its non-zero.
// Effect: set the cliff unlock amount if it is non-zero.
if (unlockAmounts.cliff > 0) {
_unlockAmounts[streamId].cliff = unlockAmounts.cliff;
}
Expand Down
3 changes: 3 additions & 0 deletions src/interfaces/ISablierLockup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ interface ISablierLockup is ISablierLockupBase {
/// - `params.recipient` must not be the zero address.
/// - `params.sender` must not be the zero address.
/// - `msg.sender` must have allowed this contract to spend at least `params.totalAmount` tokens.
/// - `params.shape.length` must not be greater than 32 characters.
///
/// @param params Struct encapsulating the function parameters, which are documented in {DataTypes}.
/// @param segments Segments used to compose the dynamic distribution function.
Expand Down Expand Up @@ -198,6 +199,7 @@ interface ISablierLockup is ISablierLockupBase {
/// deposit amount.
/// - If `params.timestamps.cliff` not set, the `params.unlockAmounts.cliff` must be zero.
/// - `msg.sender` must have allowed this contract to spend at least `params.totalAmount` tokens.
/// - `params.shape.length` must not be greater than 32 characters.
///
/// @param params Struct encapsulating the function parameters, which are documented in {DataTypes}.
/// @param cliffTime The Unix timestamp for the cliff period's end. A value of zero means there is no cliff.
Expand Down Expand Up @@ -234,6 +236,7 @@ interface ISablierLockup is ISablierLockupBase {
/// - `params.recipient` must not be the zero address.
/// - `params.sender` must not be the zero address.
/// - `msg.sender` must have allowed this contract to spend at least `params.totalAmount` tokens.
/// - `params.shape.length` must not be greater than 32 characters.
///
/// @param params Struct encapsulating the function parameters, which are documented in {DataTypes}.
/// @param tranches Tranches used to compose the tranched distribution function.
Expand Down
3 changes: 2 additions & 1 deletion src/interfaces/ISablierLockupBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,8 @@ interface ISablierLockupBase is
/// Notes:
/// - If there any tokens left for the recipient to withdraw, the stream is marked as canceled. Otherwise, the
/// stream is marked as depleted.
/// - This function attempts to invoke a hook on the recipient, if the resolved address is a contract.
/// - This function attempts to call a hook on the recipient of the stream, unless msg.sender is the recipient or
/// the recipient is not on the allowlist.
///
/// Requirements:
/// - Must not be delegate called.
Expand Down
40 changes: 29 additions & 11 deletions src/libraries/VestingMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,23 @@ library VestingMath {
/// 2. The stream's start time must be in the past so that the calculations below do not overflow.
/// 3. The stream's end time must be in the future so that the loop below does not panic with an "index out of
/// bounds" error.
///
/// Assumptions:
/// 1. The sum of all segment amounts does not overflow uint128.
/// 2. The segment timestamps are ordered chronologically.
/// 3. There are no duplicate segment timestamps.
/// 4. The number of segments does not exceed MAX_COUNT.
andreivladbrg marked this conversation as resolved.
Show resolved Hide resolved
function calculateLockupDynamicStreamedAmount(
LockupDynamic.Segment[] memory segments,
uint40 blockTimestamp,
uint40 startTime,
uint128 withdrawnAmount
)
public
view
pure
returns (uint128)
{
unchecked {
uint40 blockTimestamp = uint40(block.timestamp);

// Sum the amounts in all segments that precede the block timestamp.
uint128 previousSegmentAmounts;
uint40 currentSegmentTimestamp = segments[0].timestamp;
Expand Down Expand Up @@ -100,7 +105,9 @@ library VestingMath {
/// @dev Lockup linear model uses the following distribution function:
///
/// $$
/// f(x) = x * sa + s + c
/// ( x * sa + s, cliffTime > blockTimestamp
/// f(x) = (
/// ( x * sa + s + c, cliffTime <= blockTimestamp
andreivladbrg marked this conversation as resolved.
Show resolved Hide resolved
/// $$
///
/// Where:
Expand All @@ -109,19 +116,23 @@ library VestingMath {
/// - $sa$ is the streamable amount, i.e. deposited amount minus unlock amounts' sum.
PaulRBerg marked this conversation as resolved.
Show resolved Hide resolved
/// - $s$ is the start unlock amount.
/// - $c$ is the cliff unlock amount.
///
/// Assumptions:
/// 1. unlockAmounts.start + unlockAmounts.cliff does not overflow uint128
/// 2. timestamps.start < timestamps.end and timestamps.start <= block.timestamp <= timestamps.end
/// 3. If cliffTime > 0, timestamps.start < cliffTime < timestamps.end
function calculateLockupLinearStreamedAmount(
uint128 depositedAmount,
uint40 blockTimestamp,
Lockup.Timestamps memory timestamps,
uint40 cliffTime,
LockupLinear.UnlockAmounts memory unlockAmounts,
uint128 withdrawnAmount
)
public
view
pure
returns (uint128)
{
uint256 blockTimestamp = block.timestamp;

// If the cliff time is in the future, return the start unlock amount.
if (cliffTime > blockTimestamp) {
return unlockAmounts.start;
Expand Down Expand Up @@ -177,13 +188,20 @@ library VestingMath {
/// Where:
///
/// - $\Sigma(eta)$ is the sum of all vested tranches' amounts.
function calculateLockupTranchedStreamedAmount(LockupTranched.Tranche[] memory tranches)
///
/// Assumptions:
/// 1. The sum of all tranche amounts does not overflow uint128.
/// 2. The tranche timestamps are ordered chronologically.
/// 3. There are no duplicate tranche timestamps.
/// 4. The number of segments does not exceed MAX_COUNT.
andreivladbrg marked this conversation as resolved.
Show resolved Hide resolved
function calculateLockupTranchedStreamedAmount(
uint40 blockTimestamp,
LockupTranched.Tranche[] memory tranches
)
public
view
pure
returns (uint128)
{
uint256 blockTimestamp = block.timestamp;

// If the first tranche's timestamp is in the future, return zero.
if (tranches[0].timestamp > blockTimestamp) {
return 0;
Expand Down
Loading