Skip to content

Commit

Permalink
Feat/add batch functions (#49)
Browse files Browse the repository at this point in the history
* feat: add withdrawMultiple function

feat: add cancelMultiple functions

* chore: capitalize ID in comments

* test: rename function to expectRevertNull

test: rename function to expectRevertCanceled

* test: cancelMultiple function

test: add user eve and use for the malicious third party tests

* test: add defaultStreamIds array in Integration_Test

* test: withdrawMultiple function

test: set the block.timestamp to May 1 2024

* feat: implement createMultiple function

* feat: implement createAndDepositMultiple function

refactor: use specific amount names instead of a generic one

* docs: improve readability for streamId requirements

test: say "given" for balance zero tests

* refactor: change order of array counts in custom error

test: createMultiple function

* test: createAndDepositMultiple function

* Refactoring open ended (#52)

* perf: optimize modifiers

* refactor: rename streamDebt to streamDebtOf

* fix: add override

* style: solhint-disable no-console

* chore: use return variable in streamDebtOf

* test: update streamDebt files

---------

Co-authored-by: andreivladbrg <[email protected]>

* test: refactor streamDebtOf

* test: remove unneeded console log

* refactor: say "amount" in function paramaters instead of explicit names

* test: correct the contract name

test: remove unneeded delegate call test

* perf: optimize createAndDepositMultiple

* test: remove unneeded delegatecall test in cancelMultiple

* test: refactoring relates changes

* test: remove unneeded tree branches

* test: add modifiers in test_CancelMultiple

---------

Co-authored-by: smol-ninja <[email protected]>
  • Loading branch information
andreivladbrg and smol-ninja authored May 10, 2024
1 parent 55ce73f commit e424083
Show file tree
Hide file tree
Showing 32 changed files with 1,173 additions and 228 deletions.
3 changes: 2 additions & 1 deletion script/Base.s.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// solhint-disable no-console
pragma solidity >=0.8.22;

import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
Expand Down Expand Up @@ -57,7 +58,7 @@ abstract contract BaseScript is Script {
string memory json = vm.readFile("package.json");
string memory version = json.readString(".version");
string memory create2Salt = string.concat("ChainID ", chainId, ", Version ", version);
console2.log("The CREATE2 salt is \"%s\"", create2Salt);
console2.log("The CREATE2 salt is %s", create2Salt);
return bytes32(abi.encodePacked(create2Salt));
}
}
183 changes: 156 additions & 27 deletions src/SablierV2OpenEnded.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ contract SablierV2OpenEnded is ISablierV2OpenEnded, NoDelegateCall, SablierV2Ope
view
override
notCanceled(streamId)
notNull(streamId)
returns (uint128 refundableAmount)
{
refundableAmount = _refundableAmountOf(streamId, uint40(block.timestamp));
Expand All @@ -42,25 +43,40 @@ contract SablierV2OpenEnded is ISablierV2OpenEnded, NoDelegateCall, SablierV2Ope
view
override
notCanceled(streamId)
notNull(streamId)
returns (uint128 refundableAmount)
{
refundableAmount = _refundableAmountOf(streamId, time);
}

/// @inheritdoc ISablierV2OpenEnded
function streamDebt(uint256 streamId) external view notCanceled(streamId) returns (uint128 debt) {
function streamDebtOf(uint256 streamId)
external
view
override
notCanceled(streamId)
notNull(streamId)
returns (uint128 debt)
{
uint128 balance = _streams[streamId].balance;
uint128 streamedAmount = _streamedAmountOf(streamId, uint40(block.timestamp));

if (balance >= streamedAmount) {
if (balance < streamedAmount) {
debt = streamedAmount - balance;
} else {
return 0;
}

debt = streamedAmount - balance;
}

/// @inheritdoc ISablierV2OpenEnded
function streamedAmountOf(uint256 streamId) external view notCanceled(streamId) returns (uint128 streamedAmount) {
function streamedAmountOf(uint256 streamId)
external
view
override
notCanceled(streamId)
notNull(streamId)
returns (uint128 streamedAmount)
{
streamedAmount = _streamedAmountOf(streamId, uint40(block.timestamp));
}

Expand All @@ -71,7 +87,9 @@ contract SablierV2OpenEnded is ISablierV2OpenEnded, NoDelegateCall, SablierV2Ope
)
external
view
override
notCanceled(streamId)
notNull(streamId)
returns (uint128 streamedAmount)
{
streamedAmount = _streamedAmountOf(streamId, time);
Expand All @@ -81,7 +99,9 @@ contract SablierV2OpenEnded is ISablierV2OpenEnded, NoDelegateCall, SablierV2Ope
function withdrawableAmountOf(uint256 streamId)
external
view
override
notCanceled(streamId)
notNull(streamId)
returns (uint128 withdrawableAmount)
{
withdrawableAmount = _withdrawableAmountOf(streamId, uint40(block.timestamp));
Expand All @@ -94,7 +114,9 @@ contract SablierV2OpenEnded is ISablierV2OpenEnded, NoDelegateCall, SablierV2Ope
)
external
view
override
notCanceled(streamId)
notNull(streamId)
returns (uint128 withdrawableAmount)
{
withdrawableAmount = _withdrawableAmountOf(streamId, time);
Expand All @@ -110,19 +132,38 @@ contract SablierV2OpenEnded is ISablierV2OpenEnded, NoDelegateCall, SablierV2Ope
uint128 newRatePerSecond
)
external
override
noDelegateCall
notCanceled(streamId)
notNull(streamId)
onlySender(streamId)
{
// Effects and Interactions: adjust the stream.
_adjustRatePerSecond(streamId, newRatePerSecond);
}

/// @inheritdoc ISablierV2OpenEnded
function cancel(uint256 streamId) external noDelegateCall notCanceled(streamId) onlySender(streamId) {
function cancel(uint256 streamId)
public
override
noDelegateCall
notCanceled(streamId)
notNull(streamId)
onlySender(streamId)
{
_cancel(streamId);
}

/// @inheritdoc ISablierV2OpenEnded
function cancelMultiple(uint256[] calldata streamIds) external override {
// Iterate over the provided array of stream IDs and cancel each stream.
uint256 count = streamIds.length;
for (uint256 i = 0; i < count; ++i) {
// Effects and Interactions: cancel the stream.
cancel(streamIds[i]);
}
}

/// @inheritdoc ISablierV2OpenEnded
function create(
address sender,
Expand All @@ -131,6 +172,7 @@ contract SablierV2OpenEnded is ISablierV2OpenEnded, NoDelegateCall, SablierV2Ope
IERC20 asset
)
external
override
returns (uint256 streamId)
{
// Checks, Effects and Interactions: create the stream.
Expand All @@ -143,26 +185,92 @@ contract SablierV2OpenEnded is ISablierV2OpenEnded, NoDelegateCall, SablierV2Ope
address recipient,
uint128 ratePerSecond,
IERC20 asset,
uint128 depositAmount
uint128 amount
)
external
override
returns (uint256 streamId)
{
// Checks, Effects and Interactions: create the stream.
streamId = _create(sender, recipient, ratePerSecond, asset);

// Checks, Effects and Interactions: deposit on stream.
_deposit(streamId, depositAmount);
_deposit(streamId, amount);
}

/// @inheritdoc ISablierV2OpenEnded
function createMultiple(
address[] calldata recipients,
address[] calldata senders,
uint128[] calldata ratesPerSecond,
IERC20 asset
)
public
override
returns (uint256[] memory streamIds)
{
uint256 recipientsCount = recipients.length;
uint256 sendersCount = senders.length;
uint256 ratesPerSecondCount = ratesPerSecond.length;

// Check: count of `senders`, `recipients` and `ratesPerSecond` matches.
if (recipientsCount != sendersCount || recipientsCount != ratesPerSecondCount) {
revert Errors.SablierV2OpenEnded_CreateMultipleArrayCountsNotEqual(
recipientsCount, sendersCount, ratesPerSecondCount
);
}

streamIds = new uint256[](recipientsCount);
for (uint256 i = 0; i < recipientsCount; ++i) {
// Checks, Effects and Interactions: create the stream.
streamIds[i] = _create(senders[i], recipients[i], ratesPerSecond[i], asset);
}
}

/// @inheritdoc ISablierV2OpenEnded
function createAndDepositMultiple(
address[] calldata recipients,
address[] calldata senders,
uint128[] calldata ratesPerSecond,
IERC20 asset,
uint128[] calldata amounts
)
external
override
returns (uint256[] memory streamIds)
{
streamIds = new uint256[](recipients.length);
streamIds = createMultiple(recipients, senders, ratesPerSecond, asset);

uint256 streamIdsCount = streamIds.length;
if (streamIdsCount != amounts.length) {
revert Errors.SablierV2OpenEnded_DepositArrayCountsNotEqual(streamIdsCount, amounts.length);
}

// Deposit on each stream.
for (uint256 i = 0; i < streamIdsCount; ++i) {
// Checks, Effects and Interactions: deposit on stream.
_deposit(streamIds[i], amounts[i]);
}
}

/// @inheritdoc ISablierV2OpenEnded
function deposit(uint256 streamId, uint128 amount) external noDelegateCall notCanceled(streamId) {
function deposit(
uint256 streamId,
uint128 amount
)
external
override
noDelegateCall
notCanceled(streamId)
notNull(streamId)
{
// Checks, Effects and Interactions: deposit on stream.
_deposit(streamId, amount);
}

/// @inheritdoc ISablierV2OpenEnded
function depositMultiple(uint256[] calldata streamIds, uint128[] calldata amounts) external noDelegateCall {
function depositMultiple(uint256[] memory streamIds, uint128[] calldata amounts) public override noDelegateCall {
uint256 streamIdsCount = streamIds.length;
uint256 amountsCount = amounts.length;

Expand All @@ -171,36 +279,30 @@ contract SablierV2OpenEnded is ISablierV2OpenEnded, NoDelegateCall, SablierV2Ope
revert Errors.SablierV2OpenEnded_DepositArrayCountsNotEqual(streamIdsCount, amountsCount);
}

uint256 streamId;
uint128 amount;
for (uint256 i = 0; i < streamIdsCount; ++i) {
streamId = streamIds[i];

// Check: the stream is not canceled.
if (isCanceled(streamId)) {
revert Errors.SablierV2OpenEnded_StreamCanceled(streamId);
if (isCanceled(streamIds[i])) {
revert Errors.SablierV2OpenEnded_StreamCanceled(streamIds[i]);
}

amount = amounts[i];

// Checks, Effects and Interactions: deposit on stream.
_deposit(streamId, amount);
_deposit(streamIds[i], amounts[i]);
}
}

/// @inheritdoc ISablierV2OpenEnded
function restartStream(uint256 streamId, uint128 ratePerSecond) external {
function restartStream(uint256 streamId, uint128 ratePerSecond) external override {
// Checks, Effects and Interactions: restart the stream.
_restartStream(streamId, ratePerSecond);
}

/// @inheritdoc ISablierV2OpenEnded
function restartStreamAndDeposit(uint256 streamId, uint128 ratePerSecond, uint128 depositAmount) external {
function restartStreamAndDeposit(uint256 streamId, uint128 ratePerSecond, uint128 amount) external override {
// Checks, Effects and Interactions: restart the stream.
_restartStream(streamId, ratePerSecond);

// Checks, Effects and Interactions: deposit on stream.
_deposit(streamId, depositAmount);
_deposit(streamId, amount);
}

/// @inheritdoc ISablierV2OpenEnded
Expand All @@ -209,22 +311,40 @@ contract SablierV2OpenEnded is ISablierV2OpenEnded, NoDelegateCall, SablierV2Ope
uint128 amount
)
external
override
noDelegateCall
notCanceled(streamId)
notNull(streamId)
onlySender(streamId)
{
// Checks, Effects and Interactions: make the refund.
_refundFromStream(streamId, amount);
}

/// @inheritdoc ISablierV2OpenEnded
function withdraw(uint256 streamId, address to, uint40 time) external {
function withdraw(uint256 streamId, address to, uint40 time) external override {
// Checks, Effects and Interactions: make the withdrawal.
_withdraw(streamId, to, time);
}

/// @inheritdoc ISablierV2OpenEnded
function withdrawMax(uint256 streamId, address to) external {
function withdrawMultiple(uint256[] calldata streamIds, uint40[] calldata times) external override noDelegateCall {
// Check: there is an equal number of `streamIds` and `amounts`.
uint256 streamIdsCount = streamIds.length;
uint256 timesCount = times.length;
if (streamIdsCount != timesCount) {
revert Errors.SablierV2OpenEnded_WithdrawMultipleArrayCountsNotEqual(streamIdsCount, timesCount);
}

// Iterate over the provided array of stream IDs, and withdraw from each stream to the recipient.
for (uint256 i = 0; i < streamIdsCount; ++i) {
// Checks, Effects and Interactions: check the parameters and make the withdrawal.
_withdraw({ streamId: streamIds[i], to: _streams[streamIds[i]].recipient, time: times[i] });
}
}

/// @inheritdoc ISablierV2OpenEnded
function withdrawMax(uint256 streamId, address to) external override {
// Checks, Effects and Interactions: make the withdrawal.
_withdraw(streamId, to, uint40(block.timestamp));
}
Expand Down Expand Up @@ -292,7 +412,7 @@ contract SablierV2OpenEnded is ISablierV2OpenEnded, NoDelegateCall, SablierV2Ope

/// @dev Calculates the streamed amount.
function _streamedAmountOf(uint256 streamId, uint40 time) internal view returns (uint128) {
uint128 lastTimeUpdate = uint128(_streams[streamId].lastTimeUpdate);
uint40 lastTimeUpdate = _streams[streamId].lastTimeUpdate;

// If the time reference is less than or equal to the `lastTimeUpdate`, return zero.
if (time <= lastTimeUpdate) {
Expand Down Expand Up @@ -473,7 +593,7 @@ contract SablierV2OpenEnded is ISablierV2OpenEnded, NoDelegateCall, SablierV2Ope

/// @dev See the documentation for the user-facing functions that call this internal function.
function _deposit(uint256 streamId, uint128 amount) internal {
// Check: the amount is not zero.
// Check: the deposit amount is not zero.
if (amount == 0) {
revert Errors.SablierV2OpenEnded_DepositAmountZero();
}
Expand Down Expand Up @@ -567,7 +687,16 @@ contract SablierV2OpenEnded is ISablierV2OpenEnded, NoDelegateCall, SablierV2Ope
}

/// @dev See the documentation for the user-facing functions that call this internal function.
function _withdraw(uint256 streamId, address to, uint40 time) internal noDelegateCall notCanceled(streamId) {
function _withdraw(
uint256 streamId,
address to,
uint40 time
)
internal
noDelegateCall
notCanceled(streamId)
notNull(streamId)
{
// Check: the withdrawal address is not zero.
if (to == address(0)) {
revert Errors.SablierV2OpenEnded_WithdrawToZeroAddress();
Expand Down
Loading

0 comments on commit e424083

Please sign in to comment.