Skip to content

Commit

Permalink
feat: add name, introduces struct ConstructorParams (#262)
Browse files Browse the repository at this point in the history
* feat: add `name` string parameter in MerkleStreamer contract constructor
refactor: use struct `CreateWithLockupLinear` in Merkle streamer constructor

* test: name should not exceed 32 bytes

* refactor: use an struct for MerkleStreamer constructor parameters

* test: rename defaults function

* refactor: rename custom error

chore: small rewording changes
refactor: update import

* refactor: add constructor params in event

test: update tests accordingly
chore: remove unused imports

* refactor: use internal for NAME

* refactor: rename params to baseParams when referring to ConstructorParams struct

* build: update bun lockfile

---------

Co-authored-by: andreivladbrg <andreivladbrg@gmail.com>
Co-authored-by: Paul Razvan Berg <paul.razvan.berg@gmail.com>
  • Loading branch information
3 people committed Feb 13, 2024
1 parent 2c2643b commit ff4fc82
Show file tree
Hide file tree
Showing 17 changed files with 196 additions and 164 deletions.
Binary file modified bun.lockb
Binary file not shown.
17 changes: 4 additions & 13 deletions script/CreateMerkleStreamerLL.s.sol
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.22 <0.9.0;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
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";
import { ISablierV2MerkleStreamerLL } from "../src/interfaces/ISablierV2MerkleStreamerLL.sol";
import { MerkleStreamer } from "../src/types/DataTypes.sol";

contract CreateMerkleStreamerLL is BaseScript {
struct Params {
address initialAdmin;
MerkleStreamer.ConstructorParams baseParams;
ISablierV2LockupLinear lockupLinear;
IERC20 asset;
bytes32 merkleRoot;
uint40 expiration;
LockupLinear.Durations streamDurations;
bool cancelable;
bool transferable;
string ipfsCID;
uint256 campaignTotalAmount;
uint256 recipientsCount;
Expand All @@ -33,14 +29,9 @@ contract CreateMerkleStreamerLL is BaseScript {
returns (ISablierV2MerkleStreamerLL merkleStreamerLL)
{
merkleStreamerLL = merkleStreamerFactory.createMerkleStreamerLL(
params.initialAdmin,
params.baseParams,
params.lockupLinear,
params.asset,
params.merkleRoot,
params.expiration,
params.streamDurations,
params.cancelable,
params.transferable,
params.ipfsCID,
params.campaignTotalAmount,
params.recipientsCount
Expand Down
47 changes: 15 additions & 32 deletions src/SablierV2MerkleStreamerFactory.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.22;

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

import { SablierV2MerkleStreamerLL } from "./SablierV2MerkleStreamerLL.sol";
import { ISablierV2MerkleStreamerFactory } from "./interfaces/ISablierV2MerkleStreamerFactory.sol";
import { ISablierV2MerkleStreamerLL } from "./interfaces/ISablierV2MerkleStreamerLL.sol";
import { SablierV2MerkleStreamerLL } from "./SablierV2MerkleStreamerLL.sol";
import { MerkleStreamer } from "./types/DataTypes.sol";

/// @title SablierV2MerkleStreamerFactory
/// @notice See the documentation in {ISablierV2MerkleStreamerFactory}.
Expand All @@ -18,14 +18,9 @@ contract SablierV2MerkleStreamerFactory is ISablierV2MerkleStreamerFactory {

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

// Deploy the Merkle streamer with CREATE2.
merkleStreamerLL = new SablierV2MerkleStreamerLL{ salt: salt }(
initialAdmin, lockupLinear, asset, merkleRoot, expiration, streamDurations, cancelable, transferable
);
merkleStreamerLL = new SablierV2MerkleStreamerLL{ salt: salt }(baseParams, lockupLinear, streamDurations);

// Log the creation of the Merkle streamer, including some metadata that is not stored on-chain.
// Using a different function to emit the event to avoid stack too deep error.
emit CreateMerkleStreamerLL(
merkleStreamerLL,
initialAdmin,
lockupLinear,
asset,
merkleRoot,
expiration,
streamDurations,
cancelable,
transferable,
ipfsCID,
aggregateAmount,
recipientsCount
merkleStreamerLL, baseParams, lockupLinear, streamDurations, ipfsCID, aggregateAmount, recipientsCount
);
}
}
12 changes: 4 additions & 8 deletions src/SablierV2MerkleStreamerLL.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ud } from "@prb/math/src/UD60x18.sol";

import { SablierV2MerkleStreamer } from "./abstracts/SablierV2MerkleStreamer.sol";
import { ISablierV2MerkleStreamerLL } from "./interfaces/ISablierV2MerkleStreamerLL.sol";
import { MerkleStreamer } from "./types/DataTypes.sol";

/// @title SablierV2MerkleStreamerLL
/// @notice See the documentation in {ISablierV2MerkleStreamerLL}.
Expand Down Expand Up @@ -41,16 +42,11 @@ contract SablierV2MerkleStreamerLL is
/// @dev Constructs the contract by initializing the immutable state variables, and max approving the Sablier
/// contract.
constructor(
address initialAdmin,
MerkleStreamer.ConstructorParams memory baseParams,
ISablierV2LockupLinear lockupLinear,
IERC20 asset,
bytes32 merkleRoot,
uint40 expiration,
LockupLinear.Durations memory streamDurations_,
bool cancelable,
bool transferable
LockupLinear.Durations memory streamDurations_
)
SablierV2MerkleStreamer(initialAdmin, asset, merkleRoot, expiration, cancelable, transferable)
SablierV2MerkleStreamer(baseParams)
{
LOCKUP_LINEAR = lockupLinear;
streamDurations = streamDurations_;
Expand Down
43 changes: 29 additions & 14 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 @@ -38,6 +39,13 @@ abstract contract SablierV2MerkleStreamer is
/// @inheritdoc ISablierV2MerkleStreamer
bool public immutable override TRANSFERABLE;

/*//////////////////////////////////////////////////////////////////////////
INTERNAL CONSTANT
//////////////////////////////////////////////////////////////////////////*/

/// @dev The name of the campaign stored as bytes32.
bytes32 internal immutable NAME;

/*//////////////////////////////////////////////////////////////////////////
INTERNAL STORAGE
//////////////////////////////////////////////////////////////////////////*/
Expand All @@ -50,20 +58,22 @@ abstract contract SablierV2MerkleStreamer is
//////////////////////////////////////////////////////////////////////////*/

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

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

/*//////////////////////////////////////////////////////////////////////////
Expand All @@ -80,6 +90,11 @@ abstract contract SablierV2MerkleStreamer is
return EXPIRATION > 0 && EXPIRATION <= block.timestamp;
}

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

/*//////////////////////////////////////////////////////////////////////////
USER-FACING NON-CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
Expand Down
3 changes: 3 additions & 0 deletions src/interfaces/ISablierV2MerkleStreamer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,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
28 changes: 7 additions & 21 deletions src/interfaces/ISablierV2MerkleStreamerFactory.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.22;

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

import { ISablierV2MerkleStreamerLL } from "./ISablierV2MerkleStreamerLL.sol";
import { MerkleStreamer } from "../types/DataTypes.sol";

/// @title ISablierV2MerkleStreamerFactory
/// @notice Deploys new Lockup Linear Merkle streamers via CREATE2.
Expand All @@ -16,15 +16,10 @@ interface ISablierV2MerkleStreamerFactory {

/// @notice Emitted when a Sablier V2 Lockup Linear Merkle streamer is created.
event CreateMerkleStreamerLL(
ISablierV2MerkleStreamerLL merkleStreamer,
address indexed admin,
ISablierV2LockupLinear indexed lockupLinear,
IERC20 indexed asset,
bytes32 merkleRoot,
uint40 expiration,
ISablierV2MerkleStreamerLL indexed merkleStreamerLL,
MerkleStreamer.ConstructorParams indexed baseParams,
ISablierV2LockupLinear lockupLinear,
LockupLinear.Durations streamDurations,
bool cancelable,
bool transferable,
string ipfsCID,
uint256 aggregateAmount,
uint256 recipientsCount
Expand All @@ -36,27 +31,18 @@ interface ISablierV2MerkleStreamerFactory {

/// @notice Creates a new Merkle streamer that uses Lockup Linear.
/// @dev Emits a {CreateMerkleStreamerLL} event.
/// @param initialAdmin The initial admin of the Merkle streamer contract.
/// @param baseParams Struct encapsulating the {SablierV2MerkleStreamer} parameters, which are documented in
/// {DataTypes}.
/// @param lockupLinear The address of the {SablierV2LockupLinear} contract.
/// @param asset The address of the streamed ERC-20 asset.
/// @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.
/// @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(
address initialAdmin,
MerkleStreamer.ConstructorParams memory baseParams,
ISablierV2LockupLinear lockupLinear,
IERC20 asset,
bytes32 merkleRoot,
uint40 expiration,
LockupLinear.Durations memory streamDurations,
bool cancelable,
bool transferable,
string memory ipfsCID,
uint256 aggregateAmount,
uint256 recipientsCount
Expand Down
3 changes: 3 additions & 0 deletions src/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ library Errors {
/// @notice Thrown when trying to claim after the campaign has expired.
error SablierV2MerkleStreamer_CampaignExpired(uint256 currentTime, uint40 expiration);

/// @notice Thrown when trying to create a campaign with a name that is too long.
error SablierV2MerkleStreamer_CampaignNameTooLong(uint256 nameLength, uint256 maxLength);

/// @notice Thrown when trying to clawback when the campaign has not expired.
error SablierV2MerkleStreamer_CampaignNotExpired(uint256 currentTime, uint40 expiration);

Expand Down
21 changes: 21 additions & 0 deletions src/types/DataTypes.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
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 { Broker, LockupDynamic, LockupLinear } from "@sablier/v2-core/src/types/DataTypes.sol";

Expand Down Expand Up @@ -60,3 +61,23 @@ library Batch {
Broker broker;
}
}

library MerkleStreamer {
/// @notice Struct encapsulating the base constructor parameter of a {SablierV2MerkleStreamer} contract.
/// @param initialAdmin The initial admin of the Merkle streamer 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 cancelable Indicates if each stream will be cancelable.
/// @param transferable Indicates if each stream NFT will be transferable.
struct ConstructorParams {
address initialAdmin;
IERC20 asset;
string name;
bytes32 merkleRoot;
uint40 expiration;
bool cancelable;
bool transferable;
}
}
19 changes: 6 additions & 13 deletions test/Base.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -261,13 +261,14 @@ abstract contract Base_Test is DeployOptimized, Events, Merkle, V2CoreAssertions
bytes32 salt = keccak256(
abi.encodePacked(
admin,
lockupLinear,
asset,
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 @@ -286,16 +287,8 @@ abstract contract Base_Test is DeployOptimized, Events, Merkle, V2CoreAssertions
internal
returns (bytes memory)
{
bytes memory constructorArgs = abi.encode(
admin,
lockupLinear,
asset,
merkleRoot,
expiration,
defaults.durations(),
defaults.CANCELABLE(),
defaults.TRANSFERABLE()
);
bytes memory constructorArgs =
abi.encode(defaults.baseParams(admin, merkleRoot, expiration), lockupLinear, defaults.durations());
if (!isTestOptimizedProfile()) {
return bytes.concat(type(SablierV2MerkleStreamerLL).creationCode, constructorArgs);
} else {
Expand Down
Loading

0 comments on commit ff4fc82

Please sign in to comment.