Skip to content

Commit

Permalink
refactor: use struct ConstructorParams to encapsulate only SablierV2M…
Browse files Browse the repository at this point in the history
…erkleStreamer
  • Loading branch information
smol-ninja committed Jan 18, 2024
1 parent fa9af8f commit a248c8f
Show file tree
Hide file tree
Showing 16 changed files with 171 additions and 130 deletions.
14 changes: 12 additions & 2 deletions script/CreateMerkleStreamerLL.s.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.22 <0.9.0;

import { ISablierV2LockupLinear } from "@sablier/v2-core/src/interfaces/ISablierV2LockupLinear.sol";
import { LockupLinear } from "@sablier/v2-core/src/types/DataTypes.sol";

import { BaseScript } from "./Base.s.sol";

import { ISablierV2MerkleStreamerFactory } from "../src/interfaces/ISablierV2MerkleStreamerFactory.sol";
Expand All @@ -9,7 +12,9 @@ import { MerkleStreamer } from "../src/types/DataTypes.sol";

contract CreateMerkleStreamerLL is BaseScript {
struct Params {
MerkleStreamer.CreateWithLockupLinear createLLParams;
MerkleStreamer.ConstructorParams constructorParams;
ISablierV2LockupLinear lockupLinear;
LockupLinear.Durations streamDurations;
string ipfsCID;
uint256 campaignTotalAmount;
uint256 recipientsCount;
Expand All @@ -24,7 +29,12 @@ contract CreateMerkleStreamerLL is BaseScript {
returns (ISablierV2MerkleStreamerLL merkleStreamerLL)
{
merkleStreamerLL = merkleStreamerFactory.createMerkleStreamerLL(
params.createLLParams, params.ipfsCID, params.campaignTotalAmount, params.recipientsCount
params.constructorParams,
params.lockupLinear,
params.streamDurations,
params.ipfsCID,
params.campaignTotalAmount,
params.recipientsCount
);
}
}
63 changes: 43 additions & 20 deletions src/SablierV2MerkleStreamerFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity >=0.8.22;

import { LockupLinear } from "@sablier/v2-core/src/types/DataTypes.sol";
import { ISablierV2LockupLinear } from "@sablier/v2-core/src/interfaces/ISablierV2LockupLinear.sol";

import { ISablierV2MerkleStreamerFactory } from "./interfaces/ISablierV2MerkleStreamerFactory.sol";
import { ISablierV2MerkleStreamerLL } from "./interfaces/ISablierV2MerkleStreamerLL.sol";
Expand All @@ -17,7 +18,9 @@ contract SablierV2MerkleStreamerFactory is ISablierV2MerkleStreamerFactory {

/// @notice inheritdoc ISablierV2MerkleStreamerFactory
function createMerkleStreamerLL(
MerkleStreamer.CreateWithLockupLinear memory createLLParams,
MerkleStreamer.ConstructorParams memory params,
ISablierV2LockupLinear lockupLinear,
LockupLinear.Durations memory streamDurations,
string memory ipfsCID,
uint256 aggregateAmount,
uint256 recipientsCount
Expand All @@ -28,33 +31,53 @@ contract SablierV2MerkleStreamerFactory is ISablierV2MerkleStreamerFactory {
// Hash the parameters to generate a salt.
bytes32 salt = keccak256(
abi.encodePacked(
createLLParams.initialAdmin,
createLLParams.lockupLinear,
createLLParams.asset,
bytes32(abi.encodePacked(createLLParams.name)),
createLLParams.merkleRoot,
createLLParams.expiration,
abi.encode(createLLParams.streamDurations),
createLLParams.cancelable,
createLLParams.transferable
params.initialAdmin,
params.asset,
bytes32(abi.encodePacked(params.name)),
params.merkleRoot,
params.expiration,
params.cancelable,
params.transferable,
lockupLinear,
abi.encode(streamDurations)
)
);

// Deploy the Merkle streamer with CREATE2.
merkleStreamerLL = new SablierV2MerkleStreamerLL{ salt: salt }(createLLParams);
merkleStreamerLL = new SablierV2MerkleStreamerLL{ salt: salt }(params, lockupLinear, streamDurations);

// Using a different function to emit the event to avoid stack too deep error.
_emitLLEvent(merkleStreamerLL, params, lockupLinear, streamDurations, ipfsCID, aggregateAmount, recipientsCount);
}

/*//////////////////////////////////////////////////////////////////////////
INTERNAL NON-CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @dev Helper function to emit the {CreateMerkleStreamerLL} event.
function _emitLLEvent(
ISablierV2MerkleStreamerLL merkleStreamerLL,
MerkleStreamer.ConstructorParams memory params,
ISablierV2LockupLinear lockupLinear,
LockupLinear.Durations memory streamDurations,
string memory ipfsCID,
uint256 aggregateAmount,
uint256 recipientsCount
)
internal
{
// Log the creation of the Merkle streamer, including some metadata that is not stored on-chain.
emit CreateMerkleStreamerLL(
merkleStreamerLL,
createLLParams.initialAdmin,
createLLParams.lockupLinear,
createLLParams.asset,
createLLParams.name,
createLLParams.merkleRoot,
createLLParams.expiration,
createLLParams.streamDurations,
createLLParams.cancelable,
createLLParams.transferable,
params.initialAdmin,
params.asset,
params.name,
params.merkleRoot,
params.expiration,
params.cancelable,
params.transferable,
lockupLinear,
streamDurations,
ipfsCID,
aggregateAmount,
recipientsCount
Expand Down
20 changes: 8 additions & 12 deletions src/SablierV2MerkleStreamerLL.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,15 @@ contract SablierV2MerkleStreamerLL is

/// @dev Constructs the contract by initializing the immutable state variables, and max approving the Sablier
/// contract.
constructor(MerkleStreamer.CreateWithLockupLinear memory createParams)
SablierV2MerkleStreamer(
createParams.initialAdmin,
createParams.asset,
createParams.name,
createParams.merkleRoot,
createParams.expiration,
createParams.cancelable,
createParams.transferable
)
constructor(
MerkleStreamer.ConstructorParams memory params,
ISablierV2LockupLinear lockupLinear,
LockupLinear.Durations memory _streamDurations
)
SablierV2MerkleStreamer(params)
{
LOCKUP_LINEAR = createParams.lockupLinear;
streamDurations = createParams.streamDurations;
LOCKUP_LINEAR = lockupLinear;
streamDurations = _streamDurations;

// Max approve the Sablier contract to spend funds from the Merkle streamer.
ASSET.forceApprove(address(LOCKUP_LINEAR), type(uint256).max);
Expand Down
35 changes: 16 additions & 19 deletions src/abstracts/SablierV2MerkleStreamer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { BitMaps } from "@openzeppelin/contracts/utils/structs/BitMaps.sol";
import { Adminable } from "@sablier/v2-core/src/abstracts/Adminable.sol";

import { ISablierV2MerkleStreamer } from "../interfaces/ISablierV2MerkleStreamer.sol";
import { MerkleStreamer } from "../types/DataTypes.sol";
import { Errors } from "../libraries/Errors.sol";

/// @title SablierV2MerkleStreamer
Expand Down Expand Up @@ -57,26 +58,22 @@ abstract contract SablierV2MerkleStreamer is
//////////////////////////////////////////////////////////////////////////*/

/// @dev Constructs the contract by initializing the immutable state variables.
constructor(
address initialAdmin,
IERC20 asset,
string memory name,
bytes32 merkleRoot,
uint40 expiration,
bool cancelable,
bool transferable
) {
if (bytes(name).length > 32) {
revert Errors.SablierV2MerkleStreamer_NameTooLong();
constructor(MerkleStreamer.ConstructorParams memory params) {
// Checks: the campaign name is not greater than 32 bytes
if (bytes(params.name).length > 32) {
revert Errors.SablierV2MerkleStreamerFactory_CampaignNameTooLong({
nameLength: bytes(params.name).length,
maxLength: 32
});
}

admin = initialAdmin;
ASSET = asset;
_NAME = bytes32(abi.encodePacked(name));
MERKLE_ROOT = merkleRoot;
EXPIRATION = expiration;
CANCELABLE = cancelable;
TRANSFERABLE = transferable;
admin = params.initialAdmin;
ASSET = params.asset;
_NAME = bytes32(abi.encodePacked(params.name));
MERKLE_ROOT = params.merkleRoot;
EXPIRATION = params.expiration;
CANCELABLE = params.cancelable;
TRANSFERABLE = params.transferable;
}

/*//////////////////////////////////////////////////////////////////////////
Expand All @@ -94,7 +91,7 @@ abstract contract SablierV2MerkleStreamer is
}

/// @inheritdoc ISablierV2MerkleStreamer
function NAME() external view override returns (string memory) {
function name() external view override returns (string memory) {
return string(abi.encodePacked(_NAME));
}

Expand Down
6 changes: 3 additions & 3 deletions src/interfaces/ISablierV2MerkleStreamer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@ interface ISablierV2MerkleStreamer is IAdminable {
/// @dev This is an immutable state variable.
function ASSET() external returns (IERC20);

/// @notice The name of the Merkle streamer contract.
function NAME() external returns (string memory);

/// @notice A flag indicating whether the streams can be canceled.
/// @dev This is an immutable state variable.
function CANCELABLE() external returns (bool);
Expand All @@ -41,6 +38,9 @@ interface ISablierV2MerkleStreamer is IAdminable {
/// @dev This is an immutable state variable.
function EXPIRATION() external returns (uint40);

/// @notice Retrieves the name of the campaign.
function name() external returns (string memory);

/// @notice Returns a flag indicating whether a claim has been made for a given index.
/// @dev Uses a bitmap to save gas.
/// @param index The index of the recipient to check.
Expand Down
12 changes: 8 additions & 4 deletions src/interfaces/ISablierV2MerkleStreamerFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ interface ISablierV2MerkleStreamerFactory {
event CreateMerkleStreamerLL(
ISablierV2MerkleStreamerLL merkleStreamer,
address indexed admin,
ISablierV2LockupLinear indexed lockupLinear,
IERC20 indexed asset,
string name,
bytes32 merkleRoot,
uint40 expiration,
LockupLinear.Durations streamDurations,
bool cancelable,
bool transferable,
ISablierV2LockupLinear indexed lockupLinear,
LockupLinear.Durations streamDurations,
string ipfsCID,
uint256 aggregateAmount,
uint256 recipientsCount
Expand All @@ -38,14 +38,18 @@ interface ISablierV2MerkleStreamerFactory {

/// @notice Creates a new Merkle streamer that uses Lockup Linear.
/// @dev Emits a {CreateMerkleStreamerLL} event.
/// @param createLLParams Struct encapsulating the {SablierV2MerkleStreamerLL} parameters, which are documented in
/// @param params Struct encapsulating the {SablierV2MerkleStreamer} parameters, which are documented in
/// {DataTypes}.
/// @param lockupLinear The address of the {SablierV2LockupLinear} contract.
/// @param streamDurations The durations for each stream due to the recipient.
/// @param ipfsCID Metadata parameter emitted for indexing purposes.
/// @param aggregateAmount Total amount of ERC-20 assets to be streamed to all recipients.
/// @param recipientsCount Total number of recipients eligible to claim.
/// @return merkleStreamerLL The address of the newly created Merkle streamer contract.
function createMerkleStreamerLL(
MerkleStreamer.CreateWithLockupLinear memory createLLParams,
MerkleStreamer.ConstructorParams memory params,
ISablierV2LockupLinear lockupLinear,
LockupLinear.Durations memory streamDurations,
string memory ipfsCID,
uint256 aggregateAmount,
uint256 recipientsCount
Expand Down
4 changes: 2 additions & 2 deletions src/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ library Errors {
/// @notice Thrown when trying to claim the same stream more than once.
error SablierV2MerkleStreamer_StreamClaimed(uint256 index);

/// @notice Thrown when name of the Merkle Streamer contract is longer than 32 bytes.
error SablierV2MerkleStreamer_NameTooLong();
/// @notice Thrown when trying to create a campaign with a name that is too long.
error SablierV2MerkleStreamerFactory_CampaignNameTooLong(uint256 nameLength, uint256 maxLength);
}
11 changes: 2 additions & 9 deletions src/types/DataTypes.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 { ISablierV2Lockup } from "@sablier/v2-core/src/interfaces/ISablierV2Lockup.sol";
import { ISablierV2LockupLinear } from "@sablier/v2-core/src/interfaces/ISablierV2LockupLinear.sol";
import { Broker, LockupDynamic, LockupLinear } from "@sablier/v2-core/src/types/DataTypes.sol";

library Batch {
Expand Down Expand Up @@ -62,26 +61,20 @@ library Batch {
}

library MerkleStreamer {
/// @notice Struct encapsulating the {SablierV2MerkleStreamerLL} parameters for the
/// {SablierV2MerkleStreamerFactory.createMerkleStreamerLL}
/// function.
/// @notice Struct encapsulating all constructor parameters of {SablierV2MerkleStreamer}.
/// @param initialAdmin The initial admin of the Merkle streamer contract.
/// @param lockupLinear The address of the {SablierV2LockupLinear} contract.
/// @param asset The address of the streamed ERC-20 asset.
/// @param name The name of the Merkle streamer contract.
/// @param merkleRoot The Merkle root of the claim data.
/// @param expiration The expiration of the streaming campaign, as a Unix timestamp.
/// @param streamDurations The durations for each stream due to the recipient.
/// @param cancelable Indicates if each stream will be cancelable.
/// @param transferable Indicates if each stream NFT will be transferable.
struct CreateWithLockupLinear {
struct ConstructorParams {
address initialAdmin;
ISablierV2LockupLinear lockupLinear;
IERC20 asset;
string name;
bytes32 merkleRoot;
uint40 expiration;
LockupLinear.Durations streamDurations;
bool cancelable;
bool transferable;
}
Expand Down
34 changes: 7 additions & 27 deletions test/Base.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -246,28 +246,6 @@ abstract contract Base_Test is DeployOptimized, Events, Merkle, V2CoreAssertions
MERKLE-STREAMER
//////////////////////////////////////////////////////////////////////////*/

function createWithLockupLinear(
address admin,
bytes32 merkleRoot,
uint40 expiration
)
internal
view
returns (MerkleStreamer.CreateWithLockupLinear memory)
{
return MerkleStreamer.CreateWithLockupLinear({
initialAdmin: admin,
lockupLinear: lockupLinear,
asset: asset,
name: defaults.NAME_STRING(),
merkleRoot: merkleRoot,
expiration: expiration,
streamDurations: defaults.durations(),
cancelable: defaults.CANCELABLE(),
transferable: defaults.TRANSFERABLE()
});
}

function computeMerkleStreamerLLAddress(
address admin,
bytes32 merkleRoot,
Expand All @@ -279,14 +257,14 @@ abstract contract Base_Test is DeployOptimized, Events, Merkle, V2CoreAssertions
bytes32 salt = keccak256(
abi.encodePacked(
admin,
lockupLinear,
asset,
defaults.NAME(),
defaults.NAME_BYTES32(),
merkleRoot,
expiration,
abi.encode(defaults.durations()),
defaults.CANCELABLE(),
defaults.TRANSFERABLE()
defaults.TRANSFERABLE(),
lockupLinear,
abi.encode(defaults.durations())
)
);
bytes32 creationBytecodeHash = keccak256(getMerkleStreamerLLBytecode(admin, merkleRoot, expiration));
Expand All @@ -305,7 +283,9 @@ abstract contract Base_Test is DeployOptimized, Events, Merkle, V2CoreAssertions
internal
returns (bytes memory)
{
bytes memory constructorArgs = abi.encode(createWithLockupLinear(admin, merkleRoot, expiration));
bytes memory constructorArgs = abi.encode(
defaults.createConstructorParams(admin, merkleRoot, expiration), lockupLinear, defaults.durations()
);
if (!isTestOptimizedProfile()) {
return bytes.concat(type(SablierV2MerkleStreamerLL).creationCode, constructorArgs);
} else {
Expand Down
Loading

0 comments on commit a248c8f

Please sign in to comment.