Skip to content
This repository has been archived by the owner on Jan 31, 2025. It is now read-only.

Commit

Permalink
Merge pull request #257 from sablier-labs/refactor/fee-check
Browse files Browse the repository at this point in the history
refactor: remove protocol fee checks in `MerkleStreamerLL`
  • Loading branch information
andreivladbrg authored Jan 10, 2024
2 parents e6094c7 + f72e402 commit 01d7b56
Show file tree
Hide file tree
Showing 9 changed files with 24 additions and 73 deletions.
2 changes: 1 addition & 1 deletion src/SablierV2MerkleStreamerLL.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ contract SablierV2MerkleStreamerLL is
bool cancelable,
bool transferable
)
SablierV2MerkleStreamer(initialAdmin, asset, lockupLinear, merkleRoot, expiration, cancelable, transferable)
SablierV2MerkleStreamer(initialAdmin, asset, merkleRoot, expiration, cancelable, transferable)
{
LOCKUP_LINEAR = lockupLinear;
streamDurations = streamDurations_;
Expand Down
24 changes: 2 additions & 22 deletions src/abstracts/SablierV2MerkleStreamer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s
import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import { BitMaps } from "@openzeppelin/contracts/utils/structs/BitMaps.sol";
import { Adminable } from "@sablier/v2-core/src/abstracts/Adminable.sol";
import { ISablierV2Lockup } from "@sablier/v2-core/src/interfaces/ISablierV2Lockup.sol";
import { UD60x18, ud } from "@prb/math/src/UD60x18.sol";

import { ISablierV2MerkleStreamer } from "../interfaces/ISablierV2MerkleStreamer.sol";
import { Errors } from "../libraries/Errors.sol";
Expand All @@ -34,9 +32,6 @@ abstract contract SablierV2MerkleStreamer is
/// @inheritdoc ISablierV2MerkleStreamer
uint40 public immutable override EXPIRATION;

/// @inheritdoc ISablierV2MerkleStreamer
ISablierV2Lockup public immutable override LOCKUP;

/// @inheritdoc ISablierV2MerkleStreamer
bytes32 public immutable override MERKLE_ROOT;

Expand All @@ -58,15 +53,13 @@ abstract contract SablierV2MerkleStreamer is
constructor(
address initialAdmin,
IERC20 asset,
ISablierV2Lockup lockup,
bytes32 merkleRoot,
uint40 expiration,
bool cancelable,
bool transferable
) {
admin = initialAdmin;
ASSET = asset;
LOCKUP = lockup;
MERKLE_ROOT = merkleRoot;
EXPIRATION = expiration;
CANCELABLE = cancelable;
Expand All @@ -93,12 +86,8 @@ abstract contract SablierV2MerkleStreamer is

/// @inheritdoc ISablierV2MerkleStreamer
function clawback(address to, uint128 amount) external override onlyAdmin {
// Safe Interactions: query the protocol fee. This is safe because it's a known Sablier contract that does
// not call other unknown contracts.
UD60x18 protocolFee = LOCKUP.comptroller().protocolFees(ASSET);

// Checks: the campaign is not expired and the protocol fee is zero.
if (!hasExpired() && !protocolFee.gt(ud(0))) {
// Checks: the campaign is not expired.
if (!hasExpired()) {
revert Errors.SablierV2MerkleStreamer_CampaignNotExpired({
currentTime: block.timestamp,
expiration: EXPIRATION
Expand Down Expand Up @@ -135,14 +124,5 @@ abstract contract SablierV2MerkleStreamer is
if (!MerkleProof.verify(merkleProof, MERKLE_ROOT, leaf)) {
revert Errors.SablierV2MerkleStreamer_InvalidProof();
}

// Safe Interactions: query the protocol fee. This is safe because it's a known Sablier contract that does
// not call other unknown contracts.
UD60x18 protocolFee = LOCKUP.comptroller().protocolFees(ASSET);

// Checks: the protocol fee is zero.
if (protocolFee.gt(ud(0))) {
revert Errors.SablierV2MerkleStreamer_ProtocolFeeNotZero();
}
}
}
7 changes: 0 additions & 7 deletions src/interfaces/ISablierV2MerkleStreamer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ pragma solidity >=0.8.22;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IAdminable } from "@sablier/v2-core/src/interfaces/IAdminable.sol";
import { ISablierV2Lockup } from "@sablier/v2-core/src/interfaces/ISablierV2Lockup.sol";

/// @title ISablierV2MerkleStreamer
/// @notice A contract that lets user claim Sablier streams using Merkle proofs. An interesting use case for
Expand Down Expand Up @@ -47,9 +46,6 @@ interface ISablierV2MerkleStreamer is IAdminable {
/// @notice Returns a flag indicating whether the Merkle streamer has expired.
function hasExpired() external view returns (bool);

/// @notice The address of the {SablierV2Lockup} contract.
function LOCKUP() external returns (ISablierV2Lockup);

/// @notice The root of the Merkle tree used to validate the claims.
/// @dev This is an immutable state variable.
function MERKLE_ROOT() external returns (bytes32);
Expand All @@ -66,9 +62,6 @@ interface ISablierV2MerkleStreamer is IAdminable {
///
/// @dev Emits a {Clawback} event.
///
/// Notes:
/// - If the protocol is not zero, the expiration check is not made.
///
/// Requirements:
/// - The caller must be the admin.
/// - The campaign must either be expired or not have an expiration.
Expand Down
1 change: 0 additions & 1 deletion src/interfaces/ISablierV2MerkleStreamerLL.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ interface ISablierV2MerkleStreamerLL is ISablierV2MerkleStreamer {
/// Requirements:
/// - The campaign must not have expired.
/// - The stream must not have been claimed already.
/// - The protocol fee must be zero.
/// - The Merkle proof must be valid.
///
/// @param index The index of the recipient in the Merkle tree.
Expand Down
3 changes: 0 additions & 3 deletions src/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ library Errors {
/// @notice Thrown when trying to claim with an invalid Merkle proof.
error SablierV2MerkleStreamer_InvalidProof();

/// @notice Thrown when trying to claim when the protocol fee is not zero.
error SablierV2MerkleStreamer_ProtocolFeeNotZero();

/// @notice Thrown when trying to claim the same stream more than once.
error SablierV2MerkleStreamer_StreamClaimed(uint256 index);
}
17 changes: 10 additions & 7 deletions test/integration/merkle-streamer/ll/claim/claim.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity >=0.8.22 <0.9.0;

import { Lockup, LockupLinear } from "@sablier/v2-core/src/types/DataTypes.sol";
import { ud } from "@prb/math/src/UD60x18.sol";
import { ud, UD60x18 } from "@prb/math/src/UD60x18.sol";

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

Expand Down Expand Up @@ -102,18 +102,16 @@ contract Claim_Integration_Test is MerkleStreamer_Integration_Test {
_;
}

function test_RevertGiven_ProtocolFeeNotZero()
function test_Claim_ProtocolFeeNotZero()
external
givenCampaignNotExpired
givenNotClaimed
givenIncludedInMerkleTree
{
uint128 claimAmount = defaults.CLAIM_AMOUNT();
bytes32[] memory merkleProof = defaults.index1Proof();
changePrank({ msgSender: users.admin });
comptroller.setProtocolFee({ asset: asset, newProtocolFee: ud(0.1e18) });
vm.expectRevert(Errors.SablierV2MerkleStreamer_ProtocolFeeNotZero.selector);
merkleStreamerLL.claim({ index: 1, recipient: users.recipient1, amount: claimAmount, merkleProof: merkleProof });

test_Claim({ protocolFee: ud(0.1e18) });
}

modifier givenProtocolFeeZero() {
Expand All @@ -127,15 +125,20 @@ contract Claim_Integration_Test is MerkleStreamer_Integration_Test {
givenIncludedInMerkleTree
givenProtocolFeeZero
{
test_Claim({ protocolFee: ud(0) });
}

function test_Claim(UD60x18 protocolFee) internal {
uint256 expectedStreamId = lockupLinear.nextStreamId();
uint128 feeAmount = uint128(ud(defaults.CLAIM_AMOUNT()).mul(protocolFee).intoUint256());

vm.expectEmit({ emitter: address(merkleStreamerLL) });
emit Claim(defaults.INDEX1(), users.recipient1, defaults.CLAIM_AMOUNT(), expectedStreamId);
uint256 actualStreamId = claimLL();

LockupLinear.Stream memory actualStream = lockupLinear.getStream(actualStreamId);
LockupLinear.Stream memory expectedStream = LockupLinear.Stream({
amounts: Lockup.Amounts({ deposited: defaults.CLAIM_AMOUNT(), refunded: 0, withdrawn: 0 }),
amounts: Lockup.Amounts({ deposited: defaults.CLAIM_AMOUNT() - feeAmount, refunded: 0, withdrawn: 0 }),
asset: asset,
cliffTime: uint40(block.timestamp) + defaults.CLIFF_DURATION(),
endTime: uint40(block.timestamp) + defaults.TOTAL_DURATION(),
Expand Down
4 changes: 3 additions & 1 deletion test/integration/merkle-streamer/ll/claim/claim.tree
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ claim.t.sol
│ └── it should revert
└── given the claim is included in the Merkle tree
├── given the protocol fee is greater than zero
│ └── it should revert
│ ├── it should mark the index as claimed
│ ├── it should create a LockupLinear stream
│ └── it should emit a {Claim} event
└── given the protocol fee is not greater than zero
├── it should mark the index as claimed
├── it should create a LockupLinear stream
Expand Down
21 changes: 3 additions & 18 deletions test/integration/merkle-streamer/ll/clawback/clawback.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
pragma solidity >=0.8.22 <0.9.0;

import { Errors as V2CoreErrors } from "@sablier/v2-core/src/libraries/Errors.sol";
import { ud } from "@prb/math/src/UD60x18.sol";

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

Expand All @@ -24,11 +23,7 @@ contract Clawback_Integration_Test is MerkleStreamer_Integration_Test {
_;
}

modifier givenProtocolFeeZero() {
_;
}

function test_RevertGiven_CampaignNotExpired() external whenCallerAdmin givenProtocolFeeZero {
function test_RevertGiven_CampaignNotExpired() external whenCallerAdmin {
vm.expectRevert(
abi.encodeWithSelector(
Errors.SablierV2MerkleStreamer_CampaignNotExpired.selector, block.timestamp, defaults.EXPIRATION()
Expand All @@ -44,21 +39,11 @@ contract Clawback_Integration_Test is MerkleStreamer_Integration_Test {
_;
}

function test_Clawback() external whenCallerAdmin givenProtocolFeeZero givenCampaignExpired {
function test_Clawback() external whenCallerAdmin givenCampaignExpired {
test_Clawback(users.admin);
}

modifier givenProtocolFeeNotZero() {
comptroller.setProtocolFee({ asset: asset, newProtocolFee: ud(0.03e18) });
_;
}

function testFuzz_Clawback_CampaignNotExpired(address to) external whenCallerAdmin givenProtocolFeeNotZero {
vm.assume(to != address(0));
test_Clawback(to);
}

function testFuzz_Clawback(address to) external whenCallerAdmin givenCampaignExpired givenProtocolFeeNotZero {
function testFuzz_Clawback(address to) external whenCallerAdmin givenCampaignExpired {
vm.assume(to != address(0));
test_Clawback(to);
}
Expand Down
18 changes: 5 additions & 13 deletions test/integration/merkle-streamer/ll/clawback/clawback.tree
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,8 @@ clawback.t.sol
├── when the caller is not the admin
│ └── it should revert
└── when the caller is the admin
├── given the protocol fee is not greater than zero
│ ├── given the campaign has not expired
│ │ └── it should revert
│ └── given the campaign has expired
│ ├── it should perform the ERC-20 transfer
│ └── it should emit a {Clawback} event
└── given the protocol fee is greater than zero
├── given the campaign has not expired
│ ├── it should perform the ERC-20 transfer
│ └── it should emit a {Clawback} event
└── given the campaign has expired
├── it should perform the ERC-20 transfer
└── it should emit a {Clawback} event
├── given the campaign has not expired
│ └── it should revert
└── given the campaign has expired
├── it should perform the ERC-20 transfer
└── it should emit a {Clawback} event

0 comments on commit 01d7b56

Please sign in to comment.