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 batch function #1070

Merged
merged 4 commits into from
Nov 1, 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
2 changes: 1 addition & 1 deletion src/core/SablierLockupDynamic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { Lockup, LockupDynamic } from "./types/DataTypes.sol";
/// @notice See the documentation in {ISablierLockupDynamic}.
contract SablierLockupDynamic is
ISablierLockupDynamic, // 5 inherited components
SablierLockup // 14 inherited components
SablierLockup // 15 inherited components
{
using CastingUint128 for uint128;
using CastingUint40 for uint40;
Expand Down
2 changes: 1 addition & 1 deletion src/core/SablierLockupLinear.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { Lockup, LockupLinear } from "./types/DataTypes.sol";
/// @notice See the documentation in {ISablierLockupLinear}.
contract SablierLockupLinear is
ISablierLockupLinear, // 5 inherited components
SablierLockup // 14 inherited components
SablierLockup // 15 inherited components
{
using SafeERC20 for IERC20;

Expand Down
2 changes: 1 addition & 1 deletion src/core/SablierLockupTranched.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { Lockup, LockupTranched } from "./types/DataTypes.sol";
/// @notice See the documentation in {ISablierLockupTranched}.
contract SablierLockupTranched is
ISablierLockupTranched, // 5 inherited components
SablierLockup // 14 inherited components
SablierLockup // 15 inherited components
{
using SafeERC20 for IERC20;

Expand Down
26 changes: 26 additions & 0 deletions src/core/abstracts/Batch.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.22;

import { IBatch } from "../interfaces/IBatch.sol";
import { Errors } from "../libraries/Errors.sol";

/// @title Batch
/// @notice See the documentation in {IBatch}.
/// @dev Forked from: https://github.com/boringcrypto/BoringSolidity/blob/master/contracts/BoringBatchable.sol
abstract contract Batch is IBatch {
/*//////////////////////////////////////////////////////////////////////////
USER-FACING NON-CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @inheritdoc IBatch
function batch(bytes[] calldata calls) external {
andreivladbrg marked this conversation as resolved.
Show resolved Hide resolved
uint256 count = calls.length;

for (uint256 i = 0; i < count; ++i) {
(bool success, bytes memory result) = address(this).delegatecall(calls[i]);
if (!success) {
revert Errors.BatchError(result);
}
}
}
}
2 changes: 2 additions & 0 deletions src/core/abstracts/SablierLockup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import { ISablierLockupRecipient } from "./../interfaces/ISablierLockupRecipient
import { Errors } from "./../libraries/Errors.sol";
import { Lockup } from "./../types/DataTypes.sol";
import { Adminable } from "./Adminable.sol";
import { Batch } from "./Batch.sol";
import { NoDelegateCall } from "./NoDelegateCall.sol";

/// @title SablierLockup
/// @notice See the documentation in {ISablierLockup}.
abstract contract SablierLockup is
Batch, // 0 inherited components
NoDelegateCall, // 0 inherited components
Adminable, // 1 inherited components
ISablierLockup, // 7 inherited components
Expand Down
9 changes: 9 additions & 0 deletions src/core/interfaces/IBatch.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.22;

/// @notice This contract implements logic to batch call any function.
interface IBatch {
/// @notice Allows batched call to self, `this` contract.
/// @param calls An array of inputs for each call.
function batch(bytes[] calldata calls) external;
}
2 changes: 2 additions & 0 deletions src/core/interfaces/ISablierLockup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import { UD60x18 } from "@prb/math/src/UD60x18.sol";

import { Lockup } from "../types/DataTypes.sol";
import { IAdminable } from "./IAdminable.sol";
import { IBatch } from "./IBatch.sol";
import { ILockupNFTDescriptor } from "./ILockupNFTDescriptor.sol";

/// @title ISablierLockup
/// @notice Common logic between all Sablier Lockup contracts.
interface ISablierLockup is
IAdminable, // 0 inherited components
IBatch, // 0 inherited components
IERC4906, // 2 inherited components
IERC721Metadata // 2 inherited components
{
Expand Down
3 changes: 3 additions & 0 deletions src/core/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ library Errors {
GENERICS
//////////////////////////////////////////////////////////////////////////*/

/// @notice Thrown when an unexpected error occurs during a batch call.
error BatchError(bytes errorData);

/// @notice Thrown when `msg.sender` is not the admin.
error CallerNotAdmin(address admin, address caller);

Expand Down
10 changes: 10 additions & 0 deletions test/core/integration/concrete/lockup-dynamic/LockupDynamic.t.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 { Integration_Test } from "./../../Integration.t.sol";
import { LockupDynamic_Integration_Shared_Test } from "./../../shared/lockup-dynamic/LockupDynamic.t.sol";
import { AllowToHook_Integration_Concrete_Test } from "./../lockup/allow-to-hook/allowToHook.t.sol";
import { Batch_Integration_Concrete_Test } from "./../lockup/batch/batch.t.sol";
import { Burn_Integration_Concrete_Test } from "./../lockup/burn/burn.t.sol";
import { CancelMultiple_Integration_Concrete_Test } from "./../lockup/cancel-multiple/cancelMultiple.t.sol";
import { Cancel_Integration_Concrete_Test } from "./../lockup/cancel/cancel.t.sol";
Expand Down Expand Up @@ -48,6 +49,15 @@ contract AllowToHook_LockupDynamic_Integration_Concrete_Test is
}
}

contract Batch_LockupDynamic_Integration_Concrete_Test is
LockupDynamic_Integration_Shared_Test,
Batch_Integration_Concrete_Test
{
function setUp() public virtual override(LockupDynamic_Integration_Shared_Test, Integration_Test) {
LockupDynamic_Integration_Shared_Test.setUp();
}
}

contract Burn_LockupDynamic_Integration_Concrete_Test is
LockupDynamic_Integration_Shared_Test,
Burn_Integration_Concrete_Test
Expand Down
10 changes: 10 additions & 0 deletions test/core/integration/concrete/lockup-linear/LockupLinear.t.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 { Integration_Test } from "./../../Integration.t.sol";
import { LockupLinear_Integration_Shared_Test } from "./../../shared/lockup-linear/LockupLinear.t.sol";
import { AllowToHook_Integration_Concrete_Test } from "./../lockup/allow-to-hook/allowToHook.t.sol";
import { Batch_Integration_Concrete_Test } from "./../lockup/batch/batch.t.sol";
import { Burn_Integration_Concrete_Test } from "./../lockup/burn/burn.t.sol";
import { CancelMultiple_Integration_Concrete_Test } from "./../lockup/cancel-multiple/cancelMultiple.t.sol";
import { Cancel_Integration_Concrete_Test } from "./../lockup/cancel/cancel.t.sol";
Expand Down Expand Up @@ -48,6 +49,15 @@ contract AllowToHook_LockupLinear_Integration_Concrete_Test is
}
}

contract Batch_LockupLinear_Integration_Concrete_Test is
LockupLinear_Integration_Shared_Test,
Batch_Integration_Concrete_Test
{
function setUp() public virtual override(LockupLinear_Integration_Shared_Test, Integration_Test) {
LockupLinear_Integration_Shared_Test.setUp();
}
}

contract Burn_LockupLinear_Integration_Concrete_Test is
LockupLinear_Integration_Shared_Test,
Burn_Integration_Concrete_Test
Expand Down
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 { Integration_Test } from "./../../Integration.t.sol";
import { LockupTranched_Integration_Shared_Test } from "./../../shared/lockup-tranched/LockupTranched.t.sol";
import { AllowToHook_Integration_Concrete_Test } from "./../lockup/allow-to-hook/allowToHook.t.sol";
import { Batch_Integration_Concrete_Test } from "./../lockup/batch/batch.t.sol";
import { Burn_Integration_Concrete_Test } from "./../lockup/burn/burn.t.sol";
import { CancelMultiple_Integration_Concrete_Test } from "./../lockup/cancel-multiple/cancelMultiple.t.sol";
import { Cancel_Integration_Concrete_Test } from "./../lockup/cancel/cancel.t.sol";
Expand Down Expand Up @@ -48,6 +49,15 @@ contract AllowToHook_LockupTranched_Integration_Concrete_Test is
}
}

contract Batch_LockupTranched_Integration_Concrete_Test is
LockupTranched_Integration_Shared_Test,
Batch_Integration_Concrete_Test
{
function setUp() public virtual override(LockupTranched_Integration_Shared_Test, Integration_Test) {
LockupTranched_Integration_Shared_Test.setUp();
}
}

contract Burn_LockupTranched_Integration_Concrete_Test is
LockupTranched_Integration_Shared_Test,
Burn_Integration_Concrete_Test
Expand Down
43 changes: 43 additions & 0 deletions test/core/integration/concrete/lockup/batch/batch.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.22 <0.9.0;

import { Errors } from "src/core/libraries/Errors.sol";

import { Integration_Test } from "./../../../Integration.t.sol";

abstract contract Batch_Integration_Concrete_Test is Integration_Test {
function test_RevertWhen_CallFunctionNotExist() external {
bytes[] memory calls = new bytes[](1);
calls[0] = abi.encodeWithSignature("nonExistentFunction()");

vm.expectRevert();
lockup.batch(calls);
}

function test_RevertWhen_DataInvalid() external whenCallFunctionExists {
uint256 nonExistentStreamId = 1337;

bytes[] memory calls = new bytes[](1);
calls[0] = abi.encodeCall(lockup.getDepositedAmount, (nonExistentStreamId));

bytes memory expectedRevertData = abi.encodeWithSelector(
Errors.BatchError.selector, abi.encodeWithSelector(Errors.SablierLockup_Null.selector, nonExistentStreamId)
);

vm.expectRevert(expectedRevertData);

lockup.batch(calls);
}

function test_WhenDataValid() external whenCallFunctionExists {
resetPrank({ msgSender: users.sender });

assertFalse(lockup.wasCanceled(defaultStreamId));

bytes[] memory calls = new bytes[](1);
calls[0] = abi.encodeCall(lockup.cancel, (defaultStreamId));

lockup.batch(calls);
assertTrue(lockup.wasCanceled(defaultStreamId));
andreivladbrg marked this conversation as resolved.
Show resolved Hide resolved
}
}
10 changes: 10 additions & 0 deletions test/core/integration/concrete/lockup/batch/batch.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Batch_Integration_Concrete_Test
├── when call function not exist
│ └── it should revert
└── when call function exists
├── when data invalid
│ └── it should revert
└── when data valid
└── it should call the function


8 changes: 8 additions & 0 deletions test/utils/Modifiers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ abstract contract Modifiers is Fuzzers {
_;
}

/*//////////////////////////////////////////////////////////////////////////
BATCH
//////////////////////////////////////////////////////////////////////////*/

modifier whenCallFunctionExists() {
_;
}

/*//////////////////////////////////////////////////////////////////////////
BURN
//////////////////////////////////////////////////////////////////////////*/
Expand Down
Loading