Skip to content

Commit

Permalink
feat: add unlock amounts functionality (#1075)
Browse files Browse the repository at this point in the history
* feat: add unlock amounts functionality

refactor: some polishes

docs: update explanatory comments
test: add new test branches in create function

test: streamed amount in lockup linear

test: fix common streamed amount tests
test: remove unneeded branches

build: update deps

test: fix tranches default function

feat: include unlock amounts in StreamLL

docs: fix some comments
test: improve fuzz streamed amount tests
test: update fork with unlock amounts

test: fix last failing tests

docs: last polishes

* refactor: emit common parameters in Create event through Common struct

* refactor: extend Schedule struct to have unlock amounts for Airstreams

* test: remove redundant comment

* refactor: rename Common struct to CreateEventCommon

* refactor: pass timestamps in linear calculate function

shub feedback

* docs: add return natspec in _create

test: add lockupCreateEvent in Defaults

---------

Co-authored-by: smol-ninja <shubhamy2015@gmail.com>
  • Loading branch information
andreivladbrg and smol-ninja authored Nov 19, 2024
1 parent 6a8ec13 commit 9e037a4
Show file tree
Hide file tree
Showing 75 changed files with 1,010 additions and 552 deletions.
2 changes: 1 addition & 1 deletion .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"extends": "solhint:recommended",
"rules": {
"avoid-low-level-calls": "off",
"code-complexity": ["error", 9],
"code-complexity": ["error", 10],
"compiler-version": ["error", ">=0.8.22"],
"contract-name-camelcase": "off",
"const-name-snakecase": "off",
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ forge install --no-commit sablier-labs/lockup
Second, install the project's dependencies:

```shell
forge install --no-commit OpenZeppelin/openzeppelin-contracts@v5.0.2 PaulRBerg/prb-math@v4.0.3
forge install --no-commit OpenZeppelin/openzeppelin-contracts@v5.0.2 PaulRBerg/prb-math@v4.1.0
```

Finally, add these to your `remappings.txt` file:
Expand Down
Binary file modified bun.lockb
Binary file not shown.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"dependencies": {
"@openzeppelin/contracts": "5.0.2",
"@prb/math": "4.0.3"
"@prb/math": "4.1.0"
},
"devDependencies": {
"forge-std": "github:foundry-rs/forge-std#v1.8.2",
Expand Down Expand Up @@ -55,7 +55,7 @@
"web3"
],
"peerDependencies": {
"@prb/math": "4.0.x"
"@prb/math": "4.x.x"
},
"publishConfig": {
"access": "public"
Expand Down
2 changes: 1 addition & 1 deletion script/core/Init.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ pragma solidity >=0.8.22 <0.9.0;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ud2x18 } from "@prb/math/src/UD2x18.sol";
import { ud60x18 } from "@prb/math/src/UD60x18.sol";

import { Solarray } from "solarray/src/Solarray.sol";

import { ISablierLockup } from "../../src/core/interfaces/ISablierLockup.sol";
Expand Down Expand Up @@ -54,6 +53,7 @@ contract Init is BaseScript {
transferable: true,
broker: Broker(address(0), ud60x18(0))
}),
LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }),
LockupLinear.Durations({ cliff: cliffDurations[i], total: totalDurations[i] })
);
}
Expand Down
2 changes: 2 additions & 0 deletions script/periphery/CreateMerkleLL.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ contract CreateMerkleLL is BaseScript {
transferable: true,
schedule: MerkleLL.Schedule({
startTime: 0, // i.e. block.timestamp
startAmount: 10e18,
cliffDuration: 30 days,
cliffAmount: 10e18,
totalDuration: 90 days
}),
aggregateAmount: 10_000e18,
Expand Down
105 changes: 65 additions & 40 deletions src/core/SablierLockup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {
/// @dev Stream tranches mapped by stream IDs. This is used in Lockup Tranched models.
mapping(uint256 streamId => LockupTranched.Tranche[] tranches) internal _tranches;

/// @dev Unlock amounts mapped by stream IDs. This is used in Lockup Linear models.
mapping(uint256 streamId => LockupLinear.UnlockAmounts unlockAmounts) internal _unlockAmounts;

/*//////////////////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -108,6 +111,21 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {
tranches = _tranches[streamId];
}

/// @inheritdoc ISablierLockup
function getUnlockAmounts(uint256 streamId)
external
view
override
notNull(streamId)
returns (LockupLinear.UnlockAmounts memory unlockAmounts)
{
if (_streams[streamId].lockupModel != Lockup.Model.LOCKUP_LINEAR) {
revert Errors.SablierLockup_NotExpectedModel(_streams[streamId].lockupModel, Lockup.Model.LOCKUP_LINEAR);
}

unlockAmounts = _unlockAmounts[streamId];
}

/*//////////////////////////////////////////////////////////////////////////
USER-FACING NON-CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -148,6 +166,7 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {
/// @inheritdoc ISablierLockup
function createWithDurationsLL(
Lockup.CreateWithDurations calldata params,
LockupLinear.UnlockAmounts calldata unlockAmounts,
LockupLinear.Durations calldata durations
)
external
Expand Down Expand Up @@ -182,6 +201,7 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {
timestamps: timestamps,
broker: params.broker
}),
unlockAmounts,
cliffTime
);
}
Expand Down Expand Up @@ -236,6 +256,7 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {
/// @inheritdoc ISablierLockup
function createWithTimestampsLL(
Lockup.CreateWithTimestamps calldata params,
LockupLinear.UnlockAmounts calldata unlockAmounts,
uint40 cliffTime
)
external
Expand All @@ -244,7 +265,7 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {
returns (uint256 streamId)
{
// Checks, Effects and Interactions: create the stream.
streamId = _createLL(params, cliffTime);
streamId = _createLL(params, unlockAmounts, cliffTime);
}

/// @inheritdoc ISablierLockup
Expand All @@ -267,17 +288,18 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {

/// @inheritdoc SablierLockupBase
function _calculateStreamedAmount(uint256 streamId) internal view override returns (uint128) {
Lockup.Timestamps memory timestamps =
Lockup.Timestamps({ start: _streams[streamId].startTime, end: _streams[streamId].endTime });

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

// If the end time is not in the future, return the deposited amount.
uint40 endTime = _streams[streamId].endTime;
uint128 depositedAmount = _streams[streamId].amounts.deposited;
if (endTime <= blockTimestamp) {
if (timestamps.end <= blockTimestamp) {
return depositedAmount;
}

Expand All @@ -288,17 +310,17 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {
if (lockupModel == Lockup.Model.LOCKUP_DYNAMIC) {
streamedAmount = VestingMath.calculateLockupDynamicStreamedAmount({
segments: _segments[streamId],
startTime: startTime,
startTime: timestamps.start,
withdrawnAmount: _streams[streamId].amounts.withdrawn
});
}
// Calculate streamed amount for Lockup Linear model.
else if (lockupModel == Lockup.Model.LOCKUP_LINEAR) {
streamedAmount = VestingMath.calculateLockupLinearStreamedAmount({
depositedAmount: depositedAmount,
startTime: startTime,
timestamps: timestamps,
cliffTime: _cliffs[streamId],
endTime: endTime,
unlockAmounts: _unlockAmounts[streamId],
withdrawnAmount: _streams[streamId].amounts.withdrawn
});
}
Expand All @@ -315,13 +337,15 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {
//////////////////////////////////////////////////////////////////////////*/

/// @dev Common logic for creating a stream.
/// @return The common parameters emitted in the craete event between all Lockup models.
function _create(
uint256 streamId,
Lockup.CreateWithTimestamps memory params,
Lockup.CreateAmounts memory createAmounts,
Lockup.Model lockupModel
)
internal
returns (Lockup.CreateEventCommon memory)
{
// Effect: create the stream.
_streams[streamId] = Lockup.Stream({
Expand Down Expand Up @@ -353,6 +377,18 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {
if (createAmounts.brokerFee > 0) {
params.asset.safeTransferFrom({ from: msg.sender, to: params.broker.account, value: createAmounts.brokerFee });
}

return Lockup.CreateEventCommon({
funder: msg.sender,
sender: params.sender,
recipient: params.recipient,
amounts: createAmounts,
asset: params.asset,
cancelable: params.cancelable,
transferable: params.transferable,
timestamps: params.timestamps,
broker: params.broker.account
});
}

/// @dev See the documentation for the user-facing functions that call this internal function.
Expand Down Expand Up @@ -385,7 +421,7 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {
}

// Effect: create the stream, mint the NFT and transfer the deposit amount.
_create({
Lockup.CreateEventCommon memory commonParams = _create({
streamId: streamId,
params: params,
createAmounts: createAmounts,
Expand All @@ -395,22 +431,15 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {
// Log the newly created stream.
emit ISablierLockup.CreateLockupDynamicStream({
streamId: streamId,
funder: msg.sender,
sender: params.sender,
recipient: params.recipient,
amounts: createAmounts,
asset: params.asset,
cancelable: params.cancelable,
transferable: params.transferable,
timestamps: params.timestamps,
segments: segments,
broker: params.broker.account
commonParams: commonParams,
segments: segments
});
}

/// @dev See the documentation for the user-facing functions that call this internal function.
function _createLL(
Lockup.CreateWithTimestamps memory params,
LockupLinear.UnlockAmounts memory unlockAmounts,
uint40 cliffTime
)
internal
Expand All @@ -422,20 +451,31 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {
timestamps: params.timestamps,
cliffTime: cliffTime,
totalAmount: params.totalAmount,
unlockAmounts: unlockAmounts,
brokerFee: params.broker.fee,
maxBrokerFee: MAX_BROKER_FEE
});

// Load the stream ID in a variable.
streamId = nextStreamId;

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

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

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

// Effect: create the stream, mint the NFT and transfer the deposit amount.
_create({
Lockup.CreateEventCommon memory commonParams = _create({
streamId: streamId,
params: params,
createAmounts: createAmounts,
Expand All @@ -445,16 +485,9 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {
// Log the newly created stream.
emit ISablierLockup.CreateLockupLinearStream({
streamId: streamId,
funder: msg.sender,
sender: params.sender,
recipient: params.recipient,
amounts: createAmounts,
asset: params.asset,
cancelable: params.cancelable,
transferable: params.transferable,
timestamps: params.timestamps,
commonParams: commonParams,
cliffTime: cliffTime,
broker: params.broker.account
unlockAmounts: unlockAmounts
});
}

Expand Down Expand Up @@ -488,7 +521,7 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {
}

// Effect: create the stream, mint the NFT and transfer the deposit amount.
_create({
Lockup.CreateEventCommon memory commonParams = _create({
streamId: streamId,
params: params,
createAmounts: createAmounts,
Expand All @@ -498,16 +531,8 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {
// Log the newly created stream.
emit ISablierLockup.CreateLockupTranchedStream({
streamId: streamId,
funder: msg.sender,
sender: params.sender,
recipient: params.recipient,
amounts: createAmounts,
asset: params.asset,
cancelable: params.cancelable,
transferable: params.transferable,
timestamps: params.timestamps,
tranches: tranches,
broker: params.broker.account
commonParams: commonParams,
tranches: tranches
});
}
}
Loading

0 comments on commit 9e037a4

Please sign in to comment.