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

refactor: remove protocol fee checks in MerkleStreamerLL #257

Merged
merged 2 commits into from
Jan 10, 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/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());
andreivladbrg marked this conversation as resolved.
Show resolved Hide resolved

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
Loading