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

feat: add unlock amounts functionality #1075

Merged
merged 8 commits into from
Nov 19, 2024
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
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion 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
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 @@ -4,6 +4,7 @@ pragma solidity >=0.8.22 <0.9.0;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import { ISablierLockup } from "../../src/core/interfaces/ISablierLockup.sol";
import { LockupLinear } from "../../src/core/types/DataTypes.sol";
import { ISablierMerkleFactory } from "../../src/periphery/interfaces/ISablierMerkleFactory.sol";
import { ISablierMerkleLL } from "../../src/periphery/interfaces/ISablierMerkleLL.sol";
import { MerkleBase, MerkleLL } from "../../src/periphery/types/DataTypes.sol";
Expand Down Expand Up @@ -37,6 +38,7 @@ contract CreateMerkleLL is BaseScript {
cliffDuration: 30 days,
totalDuration: 90 days
}),
unlockAmounts: LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }),
aggregateAmount: 10_000e18,
recipientCount: 100
});
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,18 @@ 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,
startTime: timestamps.start,
cliffTime: _cliffs[streamId],
endTime: endTime,
endTime: timestamps.end,
unlockAmounts: _unlockAmounts[streamId],
withdrawnAmount: _streams[streamId].amounts.withdrawn
});
}
Expand All @@ -322,6 +345,7 @@ contract SablierLockup is ISablierLockup, SablierLockupBase {
Lockup.Model lockupModel
)
internal
returns (Lockup.Common 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.Common({
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.Common 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.Common 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.Common 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
Loading