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

Move all duplicated logic in the abstract contract SablierV2Lockup #813

Merged
merged 14 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
336 changes: 38 additions & 298 deletions src/SablierV2LockupDynamic.sol

Large diffs are not rendered by default.

328 changes: 36 additions & 292 deletions src/SablierV2LockupLinear.sol

Large diffs are not rendered by default.

290 changes: 264 additions & 26 deletions src/abstracts/SablierV2Lockup.sol

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/interfaces/ISablierV2LockupDynamic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ interface ISablierV2LockupDynamic is ISablierV2Lockup {
/// @param streamId The stream id for the query.
function getSegments(uint256 streamId) external view returns (LockupDynamic.Segment[] memory segments);

/// @notice Retrieves the stream entity.
/// @notice Retrieves the stream details, which is a struct documented in {DataTypes}.
/// @dev Reverts if `streamId` references a null stream.
/// @param streamId The stream id for the query.
function getStream(uint256 streamId) external view returns (LockupDynamic.Stream memory stream);
function getStream(uint256 streamId) external view returns (LockupDynamic.StreamLD memory stream);

/// @notice Calculates the amount streamed to the recipient, denoted in units of the asset's decimals.
///
Expand Down
14 changes: 9 additions & 5 deletions src/interfaces/ISablierV2LockupLinear.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ interface ISablierV2LockupLinear is ISablierV2Lockup {
CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @notice Retrieves the stream's cliff time, which is a Unix timestamp.
/// @notice Retrieves the stream's cliff time, which is a Unix timestamp. A value of zero means there
/// is no cliff.
andreivladbrg marked this conversation as resolved.
Show resolved Hide resolved
/// @dev Reverts if `streamId` references a null stream.
/// @param streamId The stream id for the query.
function getCliffTime(uint256 streamId) external view returns (uint40 cliffTime);
Expand All @@ -54,10 +55,10 @@ interface ISablierV2LockupLinear is ISablierV2Lockup {
/// @param streamId The stream id for the query.
function getRange(uint256 streamId) external view returns (LockupLinear.Range memory range);

/// @notice Retrieves the stream entity.
/// @notice Retrieves the stream details, which is a struct documented in {DataTypes}.
/// @dev Reverts if `streamId` references a null stream.
/// @param streamId The stream id for the query.
function getStream(uint256 streamId) external view returns (LockupLinear.Stream memory stream);
function getStream(uint256 streamId) external view returns (LockupLinear.StreamLL memory stream);

/// @notice Calculates the amount streamed to the recipient, denoted in units of the asset's decimals.
///
Expand Down Expand Up @@ -106,14 +107,17 @@ interface ISablierV2LockupLinear is ISablierV2Lockup {
/// @dev Emits a {Transfer} and {CreateLockupLinearStream} event.
///
/// Notes:
/// - A cliff time of zero means there is no cliff.
/// - As long as the times are ordered, it is not an error for the start or the cliff time to be in the past.
///
/// Requirements:
/// - Must not be delegate called.
/// - `params.totalAmount` must be greater than zero.
/// - If set, `params.broker.fee` must not be greater than `MAX_FEE`.
/// - `params.range.start` must be less than or equal to `params.range.cliff`.
/// - `params.range.cliff` must be less than `params.range.end`.
/// - `params.range.start` must be greater than zero.
/// - `params.range.start` must be less than `params.range.end`.
/// - If set, `params.range.cliff` must be greater than `params.range.start`.
/// - If set, `params.range.cliff` must be less than `params.range.end`.
/// - `params.range.end` must be in the future.
/// - `params.recipient` must not be the zero address.
/// - `msg.sender` must have allowed this contract to spend at least `params.totalAmount` assets.
Expand Down
8 changes: 7 additions & 1 deletion src/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,13 @@ library Errors {
error SablierV2LockupLinear_CliffTimeNotLessThanEndTime(uint40 cliffTime, uint40 endTime);

/// @notice Thrown when trying to create a stream with a start time greater than the cliff time.
error SablierV2LockupLinear_StartTimeGreaterThanCliffTime(uint40 startTime, uint40 cliffTime);
error SablierV2LockupLinear_StartTimeNotLessThanCliffTime(uint40 startTime, uint40 cliffTime);

/// @notice Thrown when trying to create a stream with a start time greater than the end time.
error SablierV2LockupLinear_StartTimeNotLessThanEndTime(uint40 startTime, uint40 endTime);

/// @notice Thrown when trying to create a stream with a start time equal to zero.
error SablierV2LockupLinear_StartTimeZero();

/*//////////////////////////////////////////////////////////////////////////
SABLIER-V2-NFT-DESCRIPTOR
Expand Down
16 changes: 13 additions & 3 deletions src/libraries/Helpers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,19 @@ library Helpers {
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 start time is not zero.
if (range.start == 0) {
revert Errors.SablierV2LockupLinear_StartTimeZero();
}

// Checks: the start time is strictly less than the end time.
if (range.start >= range.end) {
revert Errors.SablierV2LockupLinear_StartTimeNotLessThanEndTime(range.start, range.end);
}

// Checks: the start time is strictly less than the cliff time when cliff time is not zero.
if (range.cliff > 0 && range.start >= range.cliff) {
revert Errors.SablierV2LockupLinear_StartTimeNotLessThanCliffTime(range.start, range.cliff);
}

// Checks: the cliff time is strictly less than the end time.
Expand Down
78 changes: 39 additions & 39 deletions src/types/DataTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,35 @@ library Lockup {
CANCELED,
DEPLETED
}

/// @notice A common data structure to be stored in all child contracts of {SablierV2Lockup}.
/// @dev The fields are arranged like this to save gas via tight variable packing.
/// @param sender The address streaming the assets, with the ability to cancel the stream.
/// @param startTime The Unix timestamp indicating the stream's start.
/// @param endTime The Unix timestamp indicating the stream's end.
/// @param isCancelable Boolean indicating if the stream is cancelable.
/// @param wasCanceled Boolean indicating if the stream was canceled.
/// @param asset The contract address of the ERC-20 asset used for streaming.
/// @param isDepleted Boolean indicating if the stream is depleted.
/// @param isStream Boolean indicating if the struct entity exists.
/// @param isTransferable Boolean indicating if the stream NFT is transferable.
/// @param amounts Struct containing the deposit, withdrawn, and refunded amounts, all denoted in units of the
/// asset's decimals.
struct Stream {
// slot 0
address sender;
uint40 startTime;
uint40 endTime;
bool isCancelable;
bool wasCanceled;
// slot 1
IERC20 asset;
bool isDepleted;
bool isStream;
bool isTransferable;
// slot 2 and 3
Lockup.Amounts amounts;
}
}

/// @notice Namespace for the structs used in {SablierV2LockupDynamic}.
Expand Down Expand Up @@ -149,35 +178,20 @@ library LockupDynamic {
uint40 duration;
}

/// @notice Lockup Dynamic stream.
/// @dev The fields are arranged like this to save gas via tight variable packing.
/// @param sender The address streaming the assets, with the ability to cancel the stream.
/// @param startTime The Unix timestamp indicating the stream's start.
/// @param endTime The Unix timestamp indicating the stream's end.
/// @param isCancelable Boolean indicating if the stream is cancelable.
/// @param wasCanceled Boolean indicating if the stream was canceled.
/// @param asset The contract address of the ERC-20 asset used for streaming.
/// @param isDepleted Boolean indicating if the stream is depleted.
/// @param isStream Boolean indicating if the struct entity exists.
/// @param isTransferable Boolean indicating if the stream NFT is transferable.
/// @param amounts Struct containing the deposit, withdrawn, and refunded amounts, all denoted in units of the
/// asset's decimals.
/// @param segments Segments used to compose the custom streaming curve.
struct Stream {
// slot 0
/// @notice Struct encapsulating all the data for a specific id, allowing anyone to retrieve all information within
/// one call to the contract.
/// @dev It contains the same data as the `Lockup.Stream` struct, plus the segments.
struct StreamLD {
address sender;
uint40 startTime;
uint40 endTime;
bool isCancelable;
bool wasCanceled;
// slot 1
IERC20 asset;
bool isDepleted;
bool isStream;
bool isTransferable;
// slot 2 and 3
Lockup.Amounts amounts;
// slots [4..n]
Segment[] segments;
}
}
Expand Down Expand Up @@ -241,42 +255,28 @@ library LockupLinear {

/// @notice Struct encapsulating the time range.
/// @param start The Unix timestamp for the stream's start.
/// @param cliff The Unix timestamp for the cliff period's end.
/// @param cliff The Unix timestamp for the cliff period's end. A value of zero means there is no cliff.
/// @param end The Unix timestamp for the stream's end.
struct Range {
smol-ninja marked this conversation as resolved.
Show resolved Hide resolved
uint40 start;
uint40 cliff;
uint40 end;
}

/// @notice Lockup Linear stream.
/// @dev The fields are arranged like this to save gas via tight variable packing.
/// @param sender The address streaming the assets, with the ability to cancel the stream.
/// @param startTime The Unix timestamp indicating the stream's start.
/// @param cliffTime The Unix timestamp indicating the cliff period's end.
/// @param isCancelable Boolean indicating if the stream is cancelable.
/// @param wasCanceled Boolean indicating if the stream was canceled.
/// @param asset The contract address of the ERC-20 asset used for streaming.
/// @param endTime The Unix timestamp indicating the stream's end.
/// @param isDepleted Boolean indicating if the stream is depleted.
/// @param isStream Boolean indicating if the struct entity exists.
/// @param isTransferable Boolean indicating if the stream NFT is transferable.
/// @param amounts Struct containing the deposit, withdrawn, and refunded amounts, all denoted in units of the
/// asset's decimals.
struct Stream {
// slot 0
/// @notice Struct encapsulating all the data for a specific id, allowing anyone to retrieve all information within
/// one call to the contract.
/// @dev It contains the same data as the `Lockup.Stream` struct, plus the cliff value.
struct StreamLL {
address sender;
uint40 startTime;
uint40 cliffTime;
bool isCancelable;
bool wasCanceled;
// slot 1
IERC20 asset;
uint40 endTime;
bool isDepleted;
bool isStream;
bool isTransferable;
// slot 2 and 3
Lockup.Amounts amounts;
uint40 cliffTime;
}
}
2 changes: 1 addition & 1 deletion test/fork/LockupDynamic.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ abstract contract LockupDynamic_Fork_Test is Fork_Test {
vars.isCancelable = vars.isSettled ? false : true;

// Assert that the stream has been created.
LockupDynamic.Stream memory actualStream = lockupDynamic.getStream(vars.streamId);
LockupDynamic.StreamLD memory actualStream = lockupDynamic.getStream(vars.streamId);
assertEq(actualStream.amounts, Lockup.Amounts(vars.createAmounts.deposit, 0, 0));
assertEq(actualStream.asset, ASSET, "asset");
assertEq(actualStream.endTime, vars.range.end, "endTime");
Expand Down
4 changes: 2 additions & 2 deletions test/fork/LockupLinear.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ abstract contract LockupLinear_Fork_Test is Fork_Test {
params.broker.fee = _bound(params.broker.fee, 0, MAX_FEE);
params.protocolFee = _bound(params.protocolFee, 0, MAX_FEE);
params.range.start = boundUint40(params.range.start, currentTime - 1000 seconds, currentTime + 10_000 seconds);
params.range.cliff = boundUint40(params.range.cliff, params.range.start, params.range.start + 52 weeks);
params.range.cliff = boundUint40(params.range.cliff, params.range.start + 1, params.range.start + 52 weeks);
params.totalAmount = boundUint128(params.totalAmount, 1, uint128(initialHolderBalance));
params.transferable = true;

Expand Down Expand Up @@ -193,7 +193,7 @@ abstract contract LockupLinear_Fork_Test is Fork_Test {
);

// Assert that the stream has been created.
LockupLinear.Stream memory actualStream = lockupLinear.getStream(vars.streamId);
LockupLinear.StreamLL memory actualStream = lockupLinear.getStream(vars.streamId);
assertEq(actualStream.amounts, Lockup.Amounts(vars.createAmounts.deposit, 0, 0));
assertEq(actualStream.asset, ASSET, "asset");
assertEq(actualStream.cliffTime, params.range.cliff, "cliffTime");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,8 @@ contract CreateWithDurations_LockupDynamic_Integration_Concrete_Test is
createDefaultStreamWithDurations();

// Assert that the stream has been created.
LockupDynamic.Stream memory actualStream = lockupDynamic.getStream(streamId);
LockupDynamic.Stream memory expectedStream = defaults.lockupDynamicStream();
LockupDynamic.StreamLD memory actualStream = lockupDynamic.getStream(streamId);
LockupDynamic.StreamLD memory expectedStream = defaults.lockupDynamicStream();
expectedStream.endTime = range.end;
expectedStream.segments = segments;
expectedStream.startTime = range.start;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,8 +383,8 @@ contract CreateWithTimestamps_LockupDynamic_Integration_Concrete_Test is
streamId = createDefaultStreamWithAsset(IERC20(asset));

// Assert that the stream has been created.
LockupDynamic.Stream memory actualStream = lockupDynamic.getStream(streamId);
LockupDynamic.Stream memory expectedStream = defaults.lockupDynamicStream();
LockupDynamic.StreamLD memory actualStream = lockupDynamic.getStream(streamId);
LockupDynamic.StreamLD memory expectedStream = defaults.lockupDynamicStream();
expectedStream.asset = IERC20(asset);
assertEq(actualStream, expectedStream);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ contract GetStream_LockupDynamic_Integration_Concrete_Test is LockupDynamic_Inte

function test_GetStream_StatusSettled() external givenNotNull {
vm.warp({ timestamp: defaults.END_TIME() });
LockupDynamic.Stream memory actualStream = lockupDynamic.getStream(defaultStreamId);
LockupDynamic.Stream memory expectedStream = defaults.lockupDynamicStream();
LockupDynamic.StreamLD memory actualStream = lockupDynamic.getStream(defaultStreamId);
LockupDynamic.StreamLD memory expectedStream = defaults.lockupDynamicStream();
expectedStream.isCancelable = false;
assertEq(actualStream, expectedStream);
}
Expand All @@ -38,8 +38,8 @@ contract GetStream_LockupDynamic_Integration_Concrete_Test is LockupDynamic_Inte

function test_GetStream() external givenNotNull givenStatusNotSettled {
uint256 streamId = createDefaultStream();
LockupDynamic.Stream memory actualStream = lockupDynamic.getStream(streamId);
LockupDynamic.Stream memory expectedStream = defaults.lockupDynamicStream();
LockupDynamic.StreamLD memory actualStream = lockupDynamic.getStream(streamId);
LockupDynamic.StreamLD memory expectedStream = defaults.lockupDynamicStream();
assertEq(actualStream, expectedStream);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,30 +28,6 @@ contract CreateWithDurations_LockupLinear_Integration_Concrete_Test is
expectRevertDueToDelegateCall(success, returnData);
}

function test_RevertWhen_CliffDurationCalculationOverflows() external whenNotDelegateCalled {
smol-ninja marked this conversation as resolved.
Show resolved Hide resolved
uint40 startTime = getBlockTimestamp();
uint40 cliffDuration = MAX_UINT40 - startTime + 1 seconds;

// Calculate the end time. Needs to be "unchecked" to avoid an overflow.
uint40 cliffTime;
unchecked {
cliffTime = startTime + cliffDuration;
}

// Expect the relevant error to be thrown.
vm.expectRevert(
abi.encodeWithSelector(
Errors.SablierV2LockupLinear_StartTimeGreaterThanCliffTime.selector, startTime, cliffTime
)
);

// Set the total duration to be the same as the cliff duration.
uint40 totalDuration = cliffDuration;

// Create the stream.
createDefaultStreamWithDurations(LockupLinear.Durations({ cliff: cliffDuration, total: totalDuration }));
}

function test_RevertWhen_TotalDurationCalculationOverflows()
external
whenNotDelegateCalled
Expand All @@ -61,7 +37,7 @@ contract CreateWithDurations_LockupLinear_Integration_Concrete_Test is
LockupLinear.Durations memory durations =
LockupLinear.Durations({ cliff: 0, total: MAX_UINT40 - startTime + 1 seconds });

// Calculate the cliff time and the end time. Needs to be "unchecked" to avoid an overflow.
// Calculate the cliff time and the end time. Needs to be "unchecked" to allow an overflow.
uint40 cliffTime;
uint40 endTime;
unchecked {
Expand All @@ -72,7 +48,7 @@ contract CreateWithDurations_LockupLinear_Integration_Concrete_Test is
// Expect the relevant error to be thrown.
vm.expectRevert(
abi.encodeWithSelector(
Errors.SablierV2LockupLinear_CliffTimeNotLessThanEndTime.selector, cliffTime, endTime
Errors.SablierV2LockupLinear_StartTimeNotLessThanEndTime.selector, startTime, endTime
)
);

Expand Down Expand Up @@ -131,8 +107,8 @@ contract CreateWithDurations_LockupLinear_Integration_Concrete_Test is
createDefaultStreamWithDurations();

// Assert that the stream has been created.
LockupLinear.Stream memory actualStream = lockupLinear.getStream(streamId);
LockupLinear.Stream memory expectedStream = defaults.lockupLinearStream();
LockupLinear.StreamLL memory actualStream = lockupLinear.getStream(streamId);
LockupLinear.StreamLL memory expectedStream = defaults.lockupLinearStream();
expectedStream.startTime = range.start;
expectedStream.cliffTime = range.cliff;
expectedStream.endTime = range.end;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ createWithDurations.t.sol
├── when delegate called
│ └── it should revert
└── when not delegate called
├── when the cliff duration calculation overflows uint256
│ └── it should revert due to the start time being greater than the cliff time
└── when the cliff duration calculation does not overflow uint256
├── when the total duration calculation overflows uint256
│ └── it should revert
└── when the total duration calculation does not overflow uint256
Expand Down
Loading
Loading