diff --git a/bun.lockb b/bun.lockb index 7b3b42c..25d3aae 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/script/CreateMerkleInstant.s.sol b/script/CreateMerkleInstant.s.sol index 08ad810..6ef5eff 100644 --- a/script/CreateMerkleInstant.s.sol +++ b/script/CreateMerkleInstant.s.sol @@ -23,10 +23,10 @@ contract CreateMerkleInstant is BaseScript { // The admin of the campaign. baseParams.initialAdmin = 0x79Fb3e81aAc012c08501f41296CCC145a1E15844; - // Dummy values for the IPFS CID, Merkle root hash, and name. + // Dummy values for the campaign name, IPFS CID, and the Merkle root hash. + baseParams.campaignName = "The Boys Instant"; baseParams.ipfsCID = "QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR"; baseParams.merkleRoot = 0x0000000000000000000000000000000000000000000000000000000000000000; - baseParams.name = "The Boys Instant"; // The total amount to airdrop through the campaign. uint256 campaignTotalAmount = 10_000e18; diff --git a/script/CreateMerkleLL.s.sol b/script/CreateMerkleLL.s.sol index 9443f89..8ac9458 100644 --- a/script/CreateMerkleLL.s.sol +++ b/script/CreateMerkleLL.s.sol @@ -28,10 +28,10 @@ contract CreateMerkleLL is BaseScript { // The admin of the campaign. baseParams.initialAdmin = 0x79Fb3e81aAc012c08501f41296CCC145a1E15844; - // Dummy values for the IPFS CID, Merkle root hash, and name. + // Dummy values for the campaign name, IPFS CID, and the Merkle root hash. + baseParams.campaignName = "The Boys LL"; baseParams.ipfsCID = "QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR"; baseParams.merkleRoot = 0x0000000000000000000000000000000000000000000000000000000000000000; - baseParams.name = "The Boys LL"; // Deploy the MerkleLL contract. // TODO: Update address once deployed. diff --git a/script/CreateMerkleLT.s.sol b/script/CreateMerkleLT.s.sol index ea3197d..534c67e 100644 --- a/script/CreateMerkleLT.s.sol +++ b/script/CreateMerkleLT.s.sol @@ -29,10 +29,10 @@ contract CreateMerkleLT is BaseScript { // The admin of the campaign. baseParams.initialAdmin = 0x79Fb3e81aAc012c08501f41296CCC145a1E15844; - // Dummy values for the IPFS CID, Merkle root hash, and name. + // Dummy values for the campaign name, IPFS CID, and the Merkle root hash. + baseParams.campaignName = "The Boys LT"; baseParams.ipfsCID = "QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR"; baseParams.merkleRoot = 0x0000000000000000000000000000000000000000000000000000000000000000; - baseParams.name = "The Boys LT"; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); diff --git a/src/SablierMerkleFactory.sol b/src/SablierMerkleFactory.sol index 526c1f4..9219430 100644 --- a/src/SablierMerkleFactory.sol +++ b/src/SablierMerkleFactory.sol @@ -86,17 +86,7 @@ contract SablierMerkleFactory is returns (ISablierMerkleInstant merkleInstant) { // Hash the parameters to generate a salt. - bytes32 salt = keccak256( - abi.encodePacked( - msg.sender, - baseParams.token, - baseParams.expiration, - baseParams.initialAdmin, - abi.encode(baseParams.ipfsCID), - baseParams.merkleRoot, - bytes32(abi.encodePacked(baseParams.name)) - ) - ); + bytes32 salt = keccak256(abi.encodePacked(msg.sender, abi.encode(baseParams))); // Compute the fee for the user. uint256 fee = _computeFeeForUser(msg.sender); @@ -124,19 +114,7 @@ contract SablierMerkleFactory is { // Hash the parameters to generate a salt. bytes32 salt = keccak256( - abi.encodePacked( - msg.sender, - baseParams.token, - baseParams.expiration, - baseParams.initialAdmin, - abi.encode(baseParams.ipfsCID), - baseParams.merkleRoot, - bytes32(abi.encodePacked(baseParams.name)), - lockup, - cancelable, - transferable, - abi.encode(schedule) - ) + abi.encodePacked(msg.sender, abi.encode(baseParams), lockup, cancelable, transferable, abi.encode(schedule)) ); // Compute the fee for the user. @@ -258,12 +236,7 @@ contract SablierMerkleFactory is bytes32 salt = keccak256( abi.encodePacked( msg.sender, - baseParams.token, - baseParams.expiration, - baseParams.initialAdmin, - abi.encode(baseParams.ipfsCID), - baseParams.merkleRoot, - bytes32(abi.encodePacked(baseParams.name)), + abi.encode(baseParams), lockup, cancelable, transferable, diff --git a/src/SablierMerkleLL.sol b/src/SablierMerkleLL.sol index 74477f6..87d4a65 100644 --- a/src/SablierMerkleLL.sol +++ b/src/SablierMerkleLL.sol @@ -91,10 +91,11 @@ contract SablierMerkleLL is sender: admin, recipient: recipient, totalAmount: amount, - asset: TOKEN, + token: TOKEN, cancelable: CANCELABLE, transferable: TRANSFERABLE, timestamps: timestamps, + shape: shape, broker: Broker({ account: address(0), fee: ZERO }) }), LockupLinear.UnlockAmounts({ start: schedule.startAmount, cliff: schedule.cliffAmount }), diff --git a/src/SablierMerkleLT.sol b/src/SablierMerkleLT.sol index c724e99..7690f3b 100644 --- a/src/SablierMerkleLT.sol +++ b/src/SablierMerkleLT.sol @@ -115,10 +115,11 @@ contract SablierMerkleLT is sender: admin, recipient: recipient, totalAmount: amount, - asset: TOKEN, + token: TOKEN, cancelable: CANCELABLE, transferable: TRANSFERABLE, timestamps: Lockup.Timestamps({ start: startTime, end: endTime }), + shape: shape, broker: Broker({ account: address(0), fee: ZERO }) }), tranches diff --git a/src/abstracts/SablierMerkleBase.sol b/src/abstracts/SablierMerkleBase.sol index b843382..255263d 100644 --- a/src/abstracts/SablierMerkleBase.sol +++ b/src/abstracts/SablierMerkleBase.sol @@ -36,15 +36,18 @@ abstract contract SablierMerkleBase is /// @inheritdoc ISablierMerkleBase bytes32 public immutable override MERKLE_ROOT; - /// @dev The name of the campaign stored as bytes32. - bytes32 internal immutable NAME; - /// @inheritdoc ISablierMerkleBase IERC20 public immutable override TOKEN; + /// @inheritdoc ISablierMerkleBase + string public override campaignName; + /// @inheritdoc ISablierMerkleBase string public override ipfsCID; + /// @inheritdoc ISablierMerkleBase + string public override shape; + /// @dev Packed booleans that record the history of claims. BitMaps.BitMap internal _claimedBitMap; @@ -57,18 +60,14 @@ abstract contract SablierMerkleBase is /// @notice Constructs the contract by initializing the immutable state variables. constructor(MerkleBase.ConstructorParams memory params, uint256 fee) Adminable(params.initialAdmin) { - // Check: the campaign name is not greater than 32 bytes - if (bytes(params.name).length > 32) { - revert Errors.SablierMerkleBase_CampaignNameTooLong({ nameLength: bytes(params.name).length, maxLength: 32 }); - } - - TOKEN = params.token; EXPIRATION = params.expiration; FACTORY = msg.sender; FEE = fee; - ipfsCID = params.ipfsCID; MERKLE_ROOT = params.merkleRoot; - NAME = bytes32(abi.encodePacked(params.name)); + TOKEN = params.token; + campaignName = _truncateString(params.campaignName); + ipfsCID = params.ipfsCID; + shape = _truncateString(params.shape); } /*////////////////////////////////////////////////////////////////////////// @@ -90,11 +89,6 @@ abstract contract SablierMerkleBase is return EXPIRATION > 0 && EXPIRATION <= block.timestamp; } - /// @inheritdoc ISablierMerkleBase - function name() external view override returns (string memory) { - return string(abi.encodePacked(NAME)); - } - /*////////////////////////////////////////////////////////////////////////// USER-FACING NON-CONSTANT FUNCTIONS //////////////////////////////////////////////////////////////////////////*/ @@ -192,6 +186,15 @@ abstract contract SablierMerkleBase is return _firstClaimTime > 0 && block.timestamp > _firstClaimTime + 7 days; } + /// @notice Truncates a string to a maximum length of 32 bytes. + /// @dev If the string's length is 32 bytes or less, it is returned unchanged. + function _truncateString(string memory s) private pure returns (string memory) { + if (bytes(s).length > 32) { + return string(abi.encodePacked(bytes32(abi.encodePacked(s)))); + } + return s; + } + /*////////////////////////////////////////////////////////////////////////// INTERNAL NON-CONSTANT FUNCTIONS //////////////////////////////////////////////////////////////////////////*/ diff --git a/src/interfaces/ISablierMerkleBase.sol b/src/interfaces/ISablierMerkleBase.sol index a92bd1e..73ae445 100644 --- a/src/interfaces/ISablierMerkleBase.sol +++ b/src/interfaces/ISablierMerkleBase.sol @@ -36,6 +36,9 @@ interface ISablierMerkleBase is IAdminable { /// @dev This is an immutable state variable. function TOKEN() external returns (IERC20); + /// @notice Retrieves the name of the campaign. + function campaignName() external view returns (string memory); + /// @notice Returns the timestamp when the first claim is made. function getFirstClaimTime() external view returns (uint40); @@ -50,8 +53,8 @@ interface ISablierMerkleBase is IAdminable { /// @notice The content identifier for indexing the campaign on IPFS. function ipfsCID() external view returns (string memory); - /// @notice Retrieves the name of the campaign. - function name() external returns (string memory); + /// @notice Retrieves the shape of the lockup stream that the campaign produces upon claiming. + function shape() external view returns (string memory); /*////////////////////////////////////////////////////////////////////////// NON-CONSTANT FUNCTIONS diff --git a/src/libraries/Errors.sol b/src/libraries/Errors.sol index 99539d4..d1757f6 100644 --- a/src/libraries/Errors.sol +++ b/src/libraries/Errors.sol @@ -20,9 +20,6 @@ library Errors { /// @notice Thrown when trying to claim after the campaign has expired. error SablierMerkleBase_CampaignExpired(uint256 blockTimestamp, uint40 expiration); - /// @notice Thrown when trying to create a campaign with a name that is too long. - error SablierMerkleBase_CampaignNameTooLong(uint256 nameLength, uint256 maxLength); - /// @notice Thrown when trying to clawback when the current timestamp is over the grace period and the campaign has /// not expired. error SablierMerkleBase_ClawbackNotAllowed(uint256 blockTimestamp, uint40 expiration, uint40 firstClaimTime); diff --git a/src/types/DataTypes.sol b/src/types/DataTypes.sol index 4084504..7d561b9 100644 --- a/src/types/DataTypes.sol +++ b/src/types/DataTypes.sol @@ -11,14 +11,17 @@ library MerkleBase { /// @param initialAdmin The initial admin of the campaign. /// @param ipfsCID The content identifier for indexing the contract on IPFS. /// @param merkleRoot The Merkle root of the claim data. - /// @param name The name of the campaign. + /// @param campaignName The name of the campaign. + /// @param shape The shape of Lockup stream is used for differentiating between streams in the UI. It is truncated + /// if exceeding 32 bytes. struct ConstructorParams { IERC20 token; uint40 expiration; address initialAdmin; string ipfsCID; bytes32 merkleRoot; - string name; + string campaignName; + string shape; } } diff --git a/tests/Base.t.sol b/tests/Base.t.sol index aff3b03..b72eab9 100644 --- a/tests/Base.t.sol +++ b/tests/Base.t.sol @@ -14,6 +14,7 @@ import { SablierMerkleFactory } from "src/SablierMerkleFactory.sol"; import { SablierMerkleInstant } from "src/SablierMerkleInstant.sol"; import { SablierMerkleLL } from "src/SablierMerkleLL.sol"; import { SablierMerkleLT } from "src/SablierMerkleLT.sol"; +import { MerkleBase } from "src/types/DataTypes.sol"; import { ERC20Mock } from "./mocks/erc20/ERC20Mock.sol"; import { Assertions } from "./utils/Assertions.sol"; import { Constants } from "./utils/Constants.sol"; @@ -191,17 +192,13 @@ abstract contract Base_Test is Assertions, Constants, DeployOptimized, Modifiers view returns (address) { - bytes32 salt = keccak256( - abi.encodePacked( - caller, - address(token_), - expiration, - campaignOwner, - abi.encode(defaults.IPFS_CID()), - merkleRoot, - defaults.NAME_BYTES32() - ) - ); + MerkleBase.ConstructorParams memory baseParams = defaults.baseParams(); + baseParams.token = token_; + baseParams.expiration = expiration; + baseParams.initialAdmin = campaignOwner; + baseParams.merkleRoot = merkleRoot; + + bytes32 salt = keccak256(abi.encodePacked(caller, abi.encode(baseParams))); bytes32 creationBytecodeHash = keccak256(getMerkleInstantBytecode(campaignOwner, token_, merkleRoot, expiration, fee)); return vm.computeCreate2Address({ @@ -223,15 +220,15 @@ abstract contract Base_Test is Assertions, Constants, DeployOptimized, Modifiers view returns (address) { + MerkleBase.ConstructorParams memory baseParams = defaults.baseParams(); + baseParams.token = token_; + baseParams.expiration = expiration; + baseParams.initialAdmin = campaignOwner; + baseParams.merkleRoot = merkleRoot; bytes32 salt = keccak256( abi.encodePacked( caller, - address(token_), - expiration, - campaignOwner, - abi.encode(defaults.IPFS_CID()), - merkleRoot, - defaults.NAME_BYTES32(), + abi.encode(baseParams), lockup, defaults.CANCELABLE(), defaults.TRANSFERABLE(), @@ -259,15 +256,15 @@ abstract contract Base_Test is Assertions, Constants, DeployOptimized, Modifiers view returns (address) { + MerkleBase.ConstructorParams memory baseParams = defaults.baseParams(); + baseParams.token = token_; + baseParams.expiration = expiration; + baseParams.initialAdmin = campaignOwner; + baseParams.merkleRoot = merkleRoot; bytes32 salt = keccak256( abi.encodePacked( caller, - address(token_), - expiration, - campaignOwner, - abi.encode(defaults.IPFS_CID()), - merkleRoot, - defaults.NAME_BYTES32(), + abi.encode(baseParams), lockup, defaults.CANCELABLE(), defaults.TRANSFERABLE(), diff --git a/tests/fork/merkle-campaign/MerkleLL.t.sol b/tests/fork/merkle-campaign/MerkleLL.t.sol index dae1206..b3ae13f 100644 --- a/tests/fork/merkle-campaign/MerkleLL.t.sol +++ b/tests/fork/merkle-campaign/MerkleLL.t.sol @@ -199,26 +199,26 @@ abstract contract MerkleLL_Fork_Test is Fork_Test { // Assert that the stream has been created successfully. assertEq( - lockup.getDepositedAmount(vars.expectedStreamId), vars.amounts[params.posBeforeSort], "deposited amount" + lockup.getCliffTime(vars.expectedStreamId), getBlockTimestamp() + defaults.CLIFF_DURATION(), "cliff time" ); - assertEq(lockup.getRefundedAmount(vars.expectedStreamId), 0, "refunded amount"); - assertEq(lockup.getWithdrawnAmount(vars.expectedStreamId), 0, "withdrawn amount"); - assertEq(lockup.getAsset(vars.expectedStreamId), FORK_TOKEN, "token"); assertEq( - lockup.getCliffTime(vars.expectedStreamId), getBlockTimestamp() + defaults.CLIFF_DURATION(), "cliff time" + lockup.getDepositedAmount(vars.expectedStreamId), vars.amounts[params.posBeforeSort], "deposited amount" ); assertEq(lockup.getEndTime(vars.expectedStreamId), getBlockTimestamp() + defaults.TOTAL_DURATION(), "end time"); + assertEq(lockup.getLockupModel(vars.expectedStreamId), Lockup.Model.LOCKUP_LINEAR); + assertEq(lockup.getRecipient(vars.expectedStreamId), vars.recipients[params.posBeforeSort], "recipient"); + assertEq(lockup.getRefundedAmount(vars.expectedStreamId), 0, "refunded amount"); + assertEq(lockup.getSender(vars.expectedStreamId), params.campaignOwner, "sender"); + assertEq(lockup.getStartTime(vars.expectedStreamId), getBlockTimestamp(), "start time"); + assertEq(lockup.getUnderlyingToken(vars.expectedStreamId), FORK_TOKEN, "token"); + assertEq(lockup.getUnlockAmounts(vars.expectedStreamId).cliff, defaults.CLIFF_AMOUNT(), "unlock amounts cliff"); + assertEq(lockup.getUnlockAmounts(vars.expectedStreamId).start, defaults.START_AMOUNT(), "unlock amounts start"); + assertEq(lockup.getWithdrawnAmount(vars.expectedStreamId), 0, "withdrawn amount"); assertEq(lockup.isCancelable(vars.expectedStreamId), defaults.CANCELABLE(), "is cancelable"); assertEq(lockup.isDepleted(vars.expectedStreamId), false, "is depleted"); assertEq(lockup.isStream(vars.expectedStreamId), true, "is stream"); assertEq(lockup.isTransferable(vars.expectedStreamId), defaults.TRANSFERABLE(), "is transferable"); - assertEq(lockup.getRecipient(vars.expectedStreamId), vars.recipients[params.posBeforeSort], "recipient"); - assertEq(lockup.getSender(vars.expectedStreamId), params.campaignOwner, "sender"); - assertEq(lockup.getStartTime(vars.expectedStreamId), getBlockTimestamp(), "start time"); assertEq(lockup.wasCanceled(vars.expectedStreamId), false, "was canceled"); - assertEq(lockup.getUnlockAmounts(vars.expectedStreamId).start, defaults.START_AMOUNT(), "unlock amounts start"); - assertEq(lockup.getUnlockAmounts(vars.expectedStreamId).cliff, defaults.CLIFF_AMOUNT(), "unlock amounts cliff"); - assertEq(lockup.getLockupModel(vars.expectedStreamId), Lockup.Model.LOCKUP_LINEAR); assertTrue(vars.merkleLL.hasClaimed(vars.indexes[params.posBeforeSort])); diff --git a/tests/fork/merkle-campaign/MerkleLT.t.sol b/tests/fork/merkle-campaign/MerkleLT.t.sol index efc8f4d..dde0c1b 100644 --- a/tests/fork/merkle-campaign/MerkleLT.t.sol +++ b/tests/fork/merkle-campaign/MerkleLT.t.sol @@ -199,18 +199,12 @@ abstract contract MerkleLT_Fork_Test is Fork_Test { assertEq( lockup.getDepositedAmount(vars.expectedStreamId), vars.amounts[params.posBeforeSort], "deposited amount" ); - assertEq(lockup.getRefundedAmount(vars.expectedStreamId), 0, "refunded amount"); - assertEq(lockup.getWithdrawnAmount(vars.expectedStreamId), 0, "withdrawn amount"); - assertEq(lockup.getAsset(vars.expectedStreamId), FORK_TOKEN, "token"); assertEq(lockup.getEndTime(vars.expectedStreamId), getBlockTimestamp() + defaults.TOTAL_DURATION(), "end time"); - assertEq(lockup.isCancelable(vars.expectedStreamId), defaults.CANCELABLE(), "is cancelable"); - assertEq(lockup.isDepleted(vars.expectedStreamId), false, "is depleted"); - assertEq(lockup.isStream(vars.expectedStreamId), true, "is stream"); - assertEq(lockup.isTransferable(vars.expectedStreamId), defaults.TRANSFERABLE(), "is transferable"); + assertEq(lockup.getLockupModel(vars.expectedStreamId), Lockup.Model.LOCKUP_TRANCHED); assertEq(lockup.getRecipient(vars.expectedStreamId), vars.recipients[params.posBeforeSort], "recipient"); + assertEq(lockup.getRefundedAmount(vars.expectedStreamId), 0, "refunded amount"); assertEq(lockup.getSender(vars.expectedStreamId), params.campaignOwner, "sender"); assertEq(lockup.getStartTime(vars.expectedStreamId), getBlockTimestamp(), "start time"); - assertEq(lockup.wasCanceled(vars.expectedStreamId), false, "was canceled"); assertEq( lockup.getTranches(vars.expectedStreamId), defaults.tranchesMerkleLT({ @@ -219,7 +213,13 @@ abstract contract MerkleLT_Fork_Test is Fork_Test { }), "tranches" ); - assertEq(lockup.getLockupModel(vars.expectedStreamId), Lockup.Model.LOCKUP_TRANCHED); + assertEq(lockup.getUnderlyingToken(vars.expectedStreamId), FORK_TOKEN, "token"); + assertEq(lockup.getWithdrawnAmount(vars.expectedStreamId), 0, "withdrawn amount"); + assertEq(lockup.isCancelable(vars.expectedStreamId), defaults.CANCELABLE(), "is cancelable"); + assertEq(lockup.isDepleted(vars.expectedStreamId), false, "is depleted"); + assertEq(lockup.isStream(vars.expectedStreamId), true, "is stream"); + assertEq(lockup.isTransferable(vars.expectedStreamId), defaults.TRANSFERABLE(), "is transferable"); + assertEq(lockup.wasCanceled(vars.expectedStreamId), false, "was canceled"); assertTrue(vars.merkleLT.hasClaimed(vars.indexes[params.posBeforeSort])); diff --git a/tests/integration/concrete/factory/create-merkle-instant/createMerkleInstant.t.sol b/tests/integration/concrete/factory/create-merkle-instant/createMerkleInstant.t.sol index c99b7b4..50c866d 100644 --- a/tests/integration/concrete/factory/create-merkle-instant/createMerkleInstant.t.sol +++ b/tests/integration/concrete/factory/create-merkle-instant/createMerkleInstant.t.sol @@ -3,25 +3,19 @@ pragma solidity >=0.8.22 <0.9.0; import { ISablierMerkleFactory } from "src/interfaces/ISablierMerkleFactory.sol"; import { ISablierMerkleInstant } from "src/interfaces/ISablierMerkleInstant.sol"; -import { Errors } from "src/libraries/Errors.sol"; import { MerkleBase } from "src/types/DataTypes.sol"; import { Integration_Test } from "../../../Integration.t.sol"; contract CreateMerkleInstant_Integration_Test is Integration_Test { - function test_RevertWhen_NameTooLong() external { + /// @dev This test works because a default MerkleInstant contract is deployed in {Integration_Test.setUp} + function test_RevertGiven_CampaignAlreadyExists() external { MerkleBase.ConstructorParams memory baseParams = defaults.baseParams(); uint256 aggregateAmount = defaults.AGGREGATE_AMOUNT(); uint256 recipientCount = defaults.RECIPIENT_COUNT(); - baseParams.name = "this string is longer than 32 characters"; - - vm.expectRevert( - abi.encodeWithSelector( - Errors.SablierMerkleBase_CampaignNameTooLong.selector, bytes(baseParams.name).length, 32 - ) - ); - + // Expect a revert due to CREATE2. + vm.expectRevert(); merkleFactory.createMerkleInstant({ baseParams: baseParams, aggregateAmount: aggregateAmount, @@ -29,19 +23,20 @@ contract CreateMerkleInstant_Integration_Test is Integration_Test { }); } - /// @dev This test works because a default MerkleInstant contract is deployed in {Integration_Test.setUp} - function test_RevertGiven_CampaignAlreadyExists() external whenNameNotTooLong { + function test_WhenCampaignNameExceeds32Bytes() external givenCampaignNotExists { MerkleBase.ConstructorParams memory baseParams = defaults.baseParams(); - uint256 aggregateAmount = defaults.AGGREGATE_AMOUNT(); - uint256 recipientCount = defaults.RECIPIENT_COUNT(); + baseParams.campaignName = "this string is longer than 32 bytes"; - // Expect a revert due to CREATE2. - vm.expectRevert(); - merkleFactory.createMerkleInstant({ + ISablierMerkleInstant actualInstant = merkleFactory.createMerkleInstant({ baseParams: baseParams, - aggregateAmount: aggregateAmount, - recipientCount: recipientCount + aggregateAmount: defaults.AGGREGATE_AMOUNT(), + recipientCount: defaults.RECIPIENT_COUNT() }); + + // It should create the campaign with shape truncated to 32 bytes. + string memory actualCampaignName = actualInstant.campaignName(); + string memory expectedCampaignName = "this string is longer than 32 by"; + assertEq(actualCampaignName, expectedCampaignName, "shape"); } function test_GivenCustomFeeSet( @@ -50,8 +45,8 @@ contract CreateMerkleInstant_Integration_Test is Integration_Test { uint256 customFee ) external - whenNameNotTooLong givenCampaignNotExists + whenCampaignNameNotExceed32Bytes { // Set the custom fee to 0 for this test. resetPrank(users.admin); @@ -95,8 +90,8 @@ contract CreateMerkleInstant_Integration_Test is Integration_Test { uint40 expiration ) external - whenNameNotTooLong givenCampaignNotExists + whenCampaignNameNotExceed32Bytes { address expectedMerkleInstant = computeMerkleInstantAddress(campaignOwner, expiration); diff --git a/tests/integration/concrete/factory/create-merkle-instant/createMerkleInstant.tree b/tests/integration/concrete/factory/create-merkle-instant/createMerkleInstant.tree index 7ccf9b6..94302af 100644 --- a/tests/integration/concrete/factory/create-merkle-instant/createMerkleInstant.tree +++ b/tests/integration/concrete/factory/create-merkle-instant/createMerkleInstant.tree @@ -1,10 +1,10 @@ CreateMerkleInstant_Integration_Test -├── when name too long +├── given campaign already exists │ └── it should revert -└── when name not too long - ├── given campaign already exists - │ └── it should revert - └── given campaign not exists +└── given campaign not exists + ├── when campaign name exceeds 32 bytes + │ └── it should create the campaign with campaign name truncated to 32 bytes + └── when campaign name not exceed 32 bytes ├── given custom fee set │ ├── it should create the campaign with custom fee │ ├── it should set the current factory address diff --git a/tests/integration/concrete/factory/create-merkle-ll/createMerkleLL.t.sol b/tests/integration/concrete/factory/create-merkle-ll/createMerkleLL.t.sol index 561792b..5c899a0 100644 --- a/tests/integration/concrete/factory/create-merkle-ll/createMerkleLL.t.sol +++ b/tests/integration/concrete/factory/create-merkle-ll/createMerkleLL.t.sol @@ -3,13 +3,13 @@ pragma solidity >=0.8.22 <0.9.0; import { ISablierMerkleFactory } from "src/interfaces/ISablierMerkleFactory.sol"; import { ISablierMerkleLL } from "src/interfaces/ISablierMerkleLL.sol"; -import { Errors } from "src/libraries/Errors.sol"; import { MerkleBase, MerkleLL } from "src/types/DataTypes.sol"; import { Integration_Test } from "../../../Integration.t.sol"; contract CreateMerkleLL_Integration_Test is Integration_Test { - function test_RevertWhen_NameTooLong() external { + /// @dev This test works because a default MerkleLL contract is deployed in {Integration_Test.setUp} + function test_RevertGiven_CampaignAlreadyExists() external { MerkleBase.ConstructorParams memory baseParams = defaults.baseParams(); bool cancelable = defaults.CANCELABLE(); bool transferable = defaults.TRANSFERABLE(); @@ -17,14 +17,8 @@ contract CreateMerkleLL_Integration_Test is Integration_Test { uint256 aggregateAmount = defaults.AGGREGATE_AMOUNT(); uint256 recipientCount = defaults.RECIPIENT_COUNT(); - baseParams.name = "this string is longer than 32 characters"; - - vm.expectRevert( - abi.encodeWithSelector( - Errors.SablierMerkleBase_CampaignNameTooLong.selector, bytes(baseParams.name).length, 32 - ) - ); - + // Expect a revert due to CREATE2. + vm.expectRevert(); merkleFactory.createMerkleLL({ baseParams: baseParams, lockup: lockup, @@ -36,26 +30,44 @@ contract CreateMerkleLL_Integration_Test is Integration_Test { }); } - /// @dev This test works because a default MerkleLL contract is deployed in {Integration_Test.setUp} - function test_RevertGiven_CampaignAlreadyExists() external whenNameNotTooLong { + function test_WhenCampaignNameExceeds32Bytes() external givenCampaignNotExists { MerkleBase.ConstructorParams memory baseParams = defaults.baseParams(); - bool cancelable = defaults.CANCELABLE(); - bool transferable = defaults.TRANSFERABLE(); - MerkleLL.Schedule memory schedule = defaults.schedule(); - uint256 aggregateAmount = defaults.AGGREGATE_AMOUNT(); - uint256 recipientCount = defaults.RECIPIENT_COUNT(); + baseParams.campaignName = "this string is longer than 32 bytes"; - // Expect a revert due to CREATE2. - vm.expectRevert(); - merkleFactory.createMerkleLL({ + ISablierMerkleLL actualLL = merkleFactory.createMerkleLL({ baseParams: baseParams, lockup: lockup, - cancelable: cancelable, - transferable: transferable, - schedule: schedule, - aggregateAmount: aggregateAmount, - recipientCount: recipientCount + cancelable: defaults.CANCELABLE(), + transferable: defaults.TRANSFERABLE(), + schedule: defaults.schedule(), + aggregateAmount: defaults.AGGREGATE_AMOUNT(), + recipientCount: defaults.RECIPIENT_COUNT() + }); + + // It should create the campaign with shape truncated to 32 bytes. + string memory actualCampaignName = actualLL.campaignName(); + string memory expectedCampaignName = "this string is longer than 32 by"; + assertEq(actualCampaignName, expectedCampaignName, "shape"); + } + + function test_WhenShapeExceeds32Bytes() external givenCampaignNotExists whenCampaignNameNotExceed32Bytes { + MerkleBase.ConstructorParams memory baseParams = defaults.baseParams(); + baseParams.shape = "this string is longer than 32 bytes"; + + ISablierMerkleLL actualLL = merkleFactory.createMerkleLL({ + baseParams: baseParams, + lockup: lockup, + cancelable: defaults.CANCELABLE(), + transferable: defaults.TRANSFERABLE(), + schedule: defaults.schedule(), + aggregateAmount: defaults.AGGREGATE_AMOUNT(), + recipientCount: defaults.RECIPIENT_COUNT() }); + + // It should create the campaign with shape truncated to 32 bytes. + string memory actualShape = actualLL.shape(); + string memory expectedShape = "this string is longer than 32 by"; + assertEq(actualShape, expectedShape, "shape"); } function test_GivenCustomFeeSet( @@ -64,8 +76,9 @@ contract CreateMerkleLL_Integration_Test is Integration_Test { uint256 customFee ) external - whenNameNotTooLong givenCampaignNotExists + whenCampaignNameNotExceed32Bytes + whenShapeNotExceed32Bytes { // Set the custom fee to 0 for this test. resetPrank(users.admin); @@ -111,8 +124,9 @@ contract CreateMerkleLL_Integration_Test is Integration_Test { uint40 expiration ) external - whenNameNotTooLong givenCampaignNotExists + whenCampaignNameNotExceed32Bytes + whenShapeNotExceed32Bytes { address expectedLL = computeMerkleLLAddress(campaignOwner, expiration); @@ -141,6 +155,9 @@ contract CreateMerkleLL_Integration_Test is Integration_Test { assertGt(address(actualLL).code.length, 0, "MerkleLL contract not created"); assertEq(address(actualLL), expectedLL, "MerkleLL contract does not match computed address"); + // It should set the correct shape. + assertEq(actualLL.shape(), defaults.SHAPE(), "shape"); + // It should create the campaign with custom fee. assertEq(actualLL.FEE(), defaults.FEE(), "default fee"); diff --git a/tests/integration/concrete/factory/create-merkle-ll/createMerkleLL.tree b/tests/integration/concrete/factory/create-merkle-ll/createMerkleLL.tree index e6350cf..204698a 100644 --- a/tests/integration/concrete/factory/create-merkle-ll/createMerkleLL.tree +++ b/tests/integration/concrete/factory/create-merkle-ll/createMerkleLL.tree @@ -1,15 +1,18 @@ CreateMerkleLL_Integration_Test -├── when name too long +├── given campaign already exists │ └── it should revert -└── when name not too long - ├── given campaign already exists - │ └── it should revert - └── given campaign not exists - ├── given custom fee set - │ ├── it should create the campaign with custom fee - │ ├── it should set the current factory address - │ └── it should emit a {CreateMerkleLL} event - └── given custom fee not set - ├── it should create the campaign with default fee - ├── it should set the current factory address - └── it should emit a {CreateMerkleLL} event +└── given campaign not exists + ├── when campaign name exceeds 32 bytes + │ └── it should create the campaign with campaign name truncated to 32 bytes + └── when campaign name not exceed 32 bytes + ├── when shape exceeds 32 bytes + │ └── it should create the campaign with shape truncated to 32 bytes + └── when shape not exceed 32 bytes + ├── given custom fee set + │ ├── it should create the campaign with custom fee + │ ├── it should set the current factory address + │ └── it should emit a {CreateMerkleLL} event + └── given custom fee not set + ├── it should create the campaign with default fee + ├── it should set the current factory address + └── it should emit a {CreateMerkleLL} event diff --git a/tests/integration/concrete/factory/create-merkle-lt/createMerkleLT.t.sol b/tests/integration/concrete/factory/create-merkle-lt/createMerkleLT.t.sol index 96f26bc..efbe3e8 100644 --- a/tests/integration/concrete/factory/create-merkle-lt/createMerkleLT.t.sol +++ b/tests/integration/concrete/factory/create-merkle-lt/createMerkleLT.t.sol @@ -3,13 +3,13 @@ pragma solidity >=0.8.22 <0.9.0; import { ISablierMerkleFactory } from "src/interfaces/ISablierMerkleFactory.sol"; import { ISablierMerkleLT } from "src/interfaces/ISablierMerkleLT.sol"; -import { Errors } from "src/libraries/Errors.sol"; import { MerkleBase, MerkleLT } from "src/types/DataTypes.sol"; import { Integration_Test } from "../../../Integration.t.sol"; contract CreateMerkleLT_Integration_Test is Integration_Test { - function test_RevertWhen_NameTooLong() external { + /// @dev This test works because a default MerkleLT contract is deployed in {Integration_Test.setUp} + function test_RevertGiven_CampaignAlreadyExists() external { MerkleBase.ConstructorParams memory baseParams = defaults.baseParams(); bool cancelable = defaults.CANCELABLE(); bool transferable = defaults.TRANSFERABLE(); @@ -18,14 +18,8 @@ contract CreateMerkleLT_Integration_Test is Integration_Test { uint256 aggregateAmount = defaults.AGGREGATE_AMOUNT(); uint256 recipientCount = defaults.RECIPIENT_COUNT(); - baseParams.name = "this string is longer than 32 characters"; - - vm.expectRevert( - abi.encodeWithSelector( - Errors.SablierMerkleBase_CampaignNameTooLong.selector, bytes(baseParams.name).length, 32 - ) - ); - + // Expect a revert due to CREATE2. + vm.expectRevert(); merkleFactory.createMerkleLT( baseParams, lockup, @@ -38,28 +32,46 @@ contract CreateMerkleLT_Integration_Test is Integration_Test { ); } - /// @dev This test works because a default MerkleLT contract is deployed in {Integration_Test.setUp} - function test_RevertGiven_CampaignAlreadyExists() external whenNameNotTooLong { + function test_WhenCampaignNameExceeds32Bytes() external givenCampaignNotExists { MerkleBase.ConstructorParams memory baseParams = defaults.baseParams(); - bool cancelable = defaults.CANCELABLE(); - bool transferable = defaults.TRANSFERABLE(); - uint40 streamStartTime = defaults.STREAM_START_TIME_ZERO(); - MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = defaults.tranchesWithPercentages(); - uint256 aggregateAmount = defaults.AGGREGATE_AMOUNT(); - uint256 recipientCount = defaults.RECIPIENT_COUNT(); + baseParams.campaignName = "this string is longer than 32 bytes"; - // Expect a revert due to CREATE2. - vm.expectRevert(); - merkleFactory.createMerkleLT( - baseParams, - lockup, - cancelable, - transferable, - streamStartTime, - tranchesWithPercentages, - aggregateAmount, - recipientCount - ); + ISablierMerkleLT actualLL = merkleFactory.createMerkleLT({ + baseParams: baseParams, + lockup: lockup, + cancelable: defaults.CANCELABLE(), + transferable: defaults.TRANSFERABLE(), + streamStartTime: defaults.STREAM_START_TIME_ZERO(), + tranchesWithPercentages: defaults.tranchesWithPercentages(), + aggregateAmount: defaults.AGGREGATE_AMOUNT(), + recipientCount: defaults.RECIPIENT_COUNT() + }); + + // It should create the campaign with shape truncated to 32 bytes. + string memory actualCampaignName = actualLL.campaignName(); + string memory expectedCampaignName = "this string is longer than 32 by"; + assertEq(actualCampaignName, expectedCampaignName, "shape"); + } + + function test_WhenShapeExceeds32Bytes() external givenCampaignNotExists whenCampaignNameNotExceed32Bytes { + MerkleBase.ConstructorParams memory baseParams = defaults.baseParams(); + baseParams.shape = "this string is longer than 32 bytes"; + + ISablierMerkleLT actualLT = merkleFactory.createMerkleLT({ + baseParams: baseParams, + lockup: lockup, + cancelable: defaults.CANCELABLE(), + transferable: defaults.TRANSFERABLE(), + streamStartTime: defaults.STREAM_START_TIME_ZERO(), + tranchesWithPercentages: defaults.tranchesWithPercentages(), + aggregateAmount: defaults.AGGREGATE_AMOUNT(), + recipientCount: defaults.RECIPIENT_COUNT() + }); + + // It should create the campaign with shape truncated to 32 bytes. + string memory actualShape = actualLT.shape(); + string memory expectedShape = "this string is longer than 32 by"; + assertEq(actualShape, expectedShape, "shape"); } function test_GivenCustomFeeSet( @@ -68,8 +80,9 @@ contract CreateMerkleLT_Integration_Test is Integration_Test { uint256 customFee ) external - whenNameNotTooLong givenCampaignNotExists + whenCampaignNameNotExceed32Bytes + whenShapeNotExceed32Bytes { // Set the custom fee to 0 for this test. resetPrank(users.admin); @@ -116,8 +129,9 @@ contract CreateMerkleLT_Integration_Test is Integration_Test { uint40 expiration ) external - whenNameNotTooLong givenCampaignNotExists + whenCampaignNameNotExceed32Bytes + whenShapeNotExceed32Bytes { address expectedLT = computeMerkleLTAddress(campaignOwner, expiration); @@ -147,6 +161,9 @@ contract CreateMerkleLT_Integration_Test is Integration_Test { assertGt(address(actualLT).code.length, 0, "MerkleLT contract not created"); assertEq(address(actualLT), expectedLT, "MerkleLT contract does not match computed address"); + // It should set the correct shape. + assertEq(actualLT.shape(), defaults.SHAPE(), "shape"); + // It should create the campaign with custom fee. assertEq(actualLT.FEE(), defaults.FEE(), "default fee"); // It should set the current factory address. diff --git a/tests/integration/concrete/factory/create-merkle-lt/createMerkleLT.tree b/tests/integration/concrete/factory/create-merkle-lt/createMerkleLT.tree index 9ccdea7..a6d4b59 100644 --- a/tests/integration/concrete/factory/create-merkle-lt/createMerkleLT.tree +++ b/tests/integration/concrete/factory/create-merkle-lt/createMerkleLT.tree @@ -1,15 +1,18 @@ CreateMerkleLT_Integration_Test -├── when name too long +├── given campaign already exists │ └── it should revert -└── when name not too long - ├── given campaign already exists - │ └── it should revert - └── given campaign not exists - ├── given custom fee set - │ ├── it should create the campaign with custom fee - │ ├── it should set the current factory address - │ └── it should emit a {CreateMerkleLT} event - └── given custom fee not set - ├── it should create the campaign with default fee - ├── it should set the current factory address - └── it should emit a {CreateMerkleLT} event +└── given campaign not exists + ├── when campaign name exceeds 32 bytes + │ └── it should create the campaign with campaign name truncated to 32 bytes + └── when campaign name not exceed 32 bytes + ├── when shape exceeds 32 bytes + │ └── it should create the campaign with shape truncated to 32 bytes + └── when shape not exceed 32 bytes + ├── given custom fee set + │ ├── it should create the campaign with custom fee + │ ├── it should set the current factory address + │ └── it should emit a {CreateMerkleLT} event + └── given custom fee not set + ├── it should create the campaign with default fee + ├── it should set the current factory address + └── it should emit a {CreateMerkleLT} event diff --git a/tests/integration/concrete/instant/constructor.t.sol b/tests/integration/concrete/instant/constructor.t.sol index c941e79..8e7f9a2 100644 --- a/tests/integration/concrete/instant/constructor.t.sol +++ b/tests/integration/concrete/instant/constructor.t.sol @@ -9,19 +9,19 @@ contract Constructor_MerkleInstant_Integration_Test is Integration_Test { /// @dev Needed to prevent "Stack too deep" error struct Vars { address actualAdmin; + string actualCampaignName; uint40 actualExpiration; address actualFactory; string actualIpfsCID; bytes32 actualMerkleRoot; - string actualName; uint256 actualFee; address actualToken; address expectedAdmin; + string expectedCampaignName; uint40 expectedExpiration; address expectedFactory; string expectedIpfsCID; bytes32 expectedMerkleRoot; - bytes32 expectedName; uint256 expectedFee; address expectedToken; } @@ -38,9 +38,9 @@ contract Constructor_MerkleInstant_Integration_Test is Integration_Test { vars.expectedAdmin = users.campaignOwner; assertEq(vars.actualAdmin, vars.expectedAdmin, "admin"); - vars.actualToken = address(constructedInstant.TOKEN()); - vars.expectedToken = address(dai); - assertEq(vars.actualToken, vars.expectedToken, "token"); + vars.actualCampaignName = constructedInstant.campaignName(); + vars.expectedCampaignName = defaults.CAMPAIGN_NAME(); + assertEq(vars.actualCampaignName, vars.expectedCampaignName, "campaign name"); vars.actualExpiration = constructedInstant.EXPIRATION(); vars.expectedExpiration = defaults.EXPIRATION(); @@ -50,6 +50,10 @@ contract Constructor_MerkleInstant_Integration_Test is Integration_Test { vars.expectedFactory = address(merkleFactory); assertEq(vars.actualFactory, vars.expectedFactory, "factory"); + vars.actualFee = constructedInstant.FEE(); + vars.expectedFee = defaults.FEE(); + assertEq(vars.actualFee, vars.expectedFee, "fee"); + vars.actualIpfsCID = constructedInstant.ipfsCID(); vars.expectedIpfsCID = defaults.IPFS_CID(); assertEq(vars.actualIpfsCID, vars.expectedIpfsCID, "ipfsCID"); @@ -58,12 +62,8 @@ contract Constructor_MerkleInstant_Integration_Test is Integration_Test { vars.expectedMerkleRoot = defaults.MERKLE_ROOT(); assertEq(vars.actualMerkleRoot, vars.expectedMerkleRoot, "merkleRoot"); - vars.actualName = constructedInstant.name(); - vars.expectedName = defaults.NAME_BYTES32(); - assertEq(bytes32(abi.encodePacked(vars.actualName)), vars.expectedName, "name"); - - vars.actualFee = constructedInstant.FEE(); - vars.expectedFee = defaults.FEE(); - assertEq(vars.actualFee, vars.expectedFee, "fee"); + vars.actualToken = address(constructedInstant.TOKEN()); + vars.expectedToken = address(dai); + assertEq(vars.actualToken, vars.expectedToken, "token"); } } diff --git a/tests/integration/concrete/ll/claim/claim.t.sol b/tests/integration/concrete/ll/claim/claim.t.sol index df2cf30..4df6fb6 100644 --- a/tests/integration/concrete/ll/claim/claim.t.sol +++ b/tests/integration/concrete/ll/claim/claim.t.sol @@ -80,25 +80,24 @@ contract Claim_MerkleLL_Integration_Test is Claim_Integration_Test, MerkleLL_Int defaults.INDEX1(), users.recipient1, defaults.CLAIM_AMOUNT(), defaults.index1Proof() ); + uint128 expectedCliffAmount = cliffTime > 0 ? defaults.CLIFF_AMOUNT() : 0; + // Assert that the stream has been created successfully. - assertEq(lockup.getDepositedAmount(expectedStreamId), defaults.CLAIM_AMOUNT(), "depositedAmount"); - assertEq(lockup.getAsset(expectedStreamId), dai, "token"); assertEq(lockup.getCliffTime(expectedStreamId), cliffTime, "cliff time"); + assertEq(lockup.getDepositedAmount(expectedStreamId), defaults.CLAIM_AMOUNT(), "depositedAmount"); assertEq(lockup.getEndTime(expectedStreamId), startTime + defaults.TOTAL_DURATION(), "end time"); + assertEq(lockup.getRecipient(expectedStreamId), users.recipient1, "recipient"); + assertEq(lockup.getSender(expectedStreamId), users.campaignOwner, "sender"); + assertEq(lockup.getStartTime(expectedStreamId), startTime, "start time"); + assertEq(lockup.getUnderlyingToken(expectedStreamId), dai, "token"); + assertEq(lockup.getUnlockAmounts(expectedStreamId).cliff, expectedCliffAmount, "unlock amount cliff"); + assertEq(lockup.getUnlockAmounts(expectedStreamId).start, defaults.START_AMOUNT(), "unlock amount start"); assertEq(lockup.isCancelable(expectedStreamId), defaults.CANCELABLE(), "is cancelable"); assertEq(lockup.isDepleted(expectedStreamId), false, "is depleted"); assertEq(lockup.isStream(expectedStreamId), true, "is stream"); assertEq(lockup.isTransferable(expectedStreamId), defaults.TRANSFERABLE(), "is transferable"); - assertEq(lockup.getRecipient(expectedStreamId), users.recipient1, "recipient"); - assertEq(lockup.getSender(expectedStreamId), users.campaignOwner, "sender"); - assertEq(lockup.getStartTime(expectedStreamId), startTime, "start time"); assertEq(lockup.wasCanceled(expectedStreamId), false, "was canceled"); - assertEq(lockup.getUnlockAmounts(expectedStreamId).start, defaults.START_AMOUNT(), "unlock amount start"); - - uint128 expectedCliffAmount = cliffTime > 0 ? defaults.CLIFF_AMOUNT() : 0; - assertEq(lockup.getUnlockAmounts(expectedStreamId).cliff, expectedCliffAmount, "unlock amount cliff"); - assertTrue(merkleLL.hasClaimed(defaults.INDEX1()), "not claimed"); assertEq(address(merkleLL).balance, previousFeeAccrued + defaults.FEE(), "fee collected"); diff --git a/tests/integration/concrete/ll/constructor.t.sol b/tests/integration/concrete/ll/constructor.t.sol index 9ca66a4..dbbdcc9 100644 --- a/tests/integration/concrete/ll/constructor.t.sol +++ b/tests/integration/concrete/ll/constructor.t.sol @@ -11,26 +11,26 @@ contract Constructor_MerkleLL_Integration_Test is Integration_Test { struct Vars { address actualAdmin; uint256 actualAllowance; + string actualCampaignName; bool actualCancelable; uint40 actualExpiration; address actualFactory; string actualIpfsCID; address actualLockup; bytes32 actualMerkleRoot; - string actualName; uint256 actualFee; MerkleLL.Schedule actualSchedule; address actualToken; bool actualTransferable; address expectedAdmin; uint256 expectedAllowance; + string expectedCampaignName; bool expectedCancelable; uint40 expectedExpiration; address expectedFactory; string expectedIpfsCID; address expectedLockup; bytes32 expectedMerkleRoot; - bytes32 expectedName; uint256 expectedFee; MerkleLL.Schedule expectedSchedule; address expectedToken; @@ -60,9 +60,9 @@ contract Constructor_MerkleLL_Integration_Test is Integration_Test { vars.expectedAllowance = MAX_UINT256; assertEq(vars.actualAllowance, vars.expectedAllowance, "allowance"); - vars.actualToken = address(constructedLL.TOKEN()); - vars.expectedToken = address(dai); - assertEq(vars.actualToken, vars.expectedToken, "token"); + vars.actualCampaignName = constructedLL.campaignName(); + vars.expectedCampaignName = defaults.CAMPAIGN_NAME(); + assertEq(vars.actualCampaignName, vars.expectedCampaignName, "campaign name"); vars.actualCancelable = constructedLL.CANCELABLE(); vars.expectedCancelable = defaults.CANCELABLE(); @@ -76,6 +76,10 @@ contract Constructor_MerkleLL_Integration_Test is Integration_Test { vars.expectedFactory = address(merkleFactory); assertEq(vars.actualFactory, vars.expectedFactory, "factory"); + vars.actualFee = constructedLL.FEE(); + vars.expectedFee = defaults.FEE(); + assertEq(vars.actualFee, vars.expectedFee, "fee"); + vars.actualIpfsCID = constructedLL.ipfsCID(); vars.expectedIpfsCID = defaults.IPFS_CID(); assertEq(vars.actualIpfsCID, vars.expectedIpfsCID, "ipfsCID"); @@ -88,10 +92,6 @@ contract Constructor_MerkleLL_Integration_Test is Integration_Test { vars.expectedMerkleRoot = defaults.MERKLE_ROOT(); assertEq(vars.actualMerkleRoot, vars.expectedMerkleRoot, "merkleRoot"); - vars.actualName = constructedLL.name(); - vars.expectedName = defaults.NAME_BYTES32(); - assertEq(bytes32(abi.encodePacked(vars.actualName)), vars.expectedName, "name"); - ( vars.actualSchedule.startTime, vars.actualSchedule.startAmount, @@ -106,12 +106,14 @@ contract Constructor_MerkleLL_Integration_Test is Integration_Test { assertEq(vars.actualSchedule.cliffAmount, vars.expectedSchedule.cliffAmount, "schedule.cliffAmount"); assertEq(vars.actualSchedule.totalDuration, vars.expectedSchedule.totalDuration, "schedule.totalDuration"); + assertEq(constructedLL.shape(), defaults.SHAPE(), "shape"); + + vars.actualToken = address(constructedLL.TOKEN()); + vars.expectedToken = address(dai); + assertEq(vars.actualToken, vars.expectedToken, "token"); + vars.actualTransferable = constructedLL.TRANSFERABLE(); vars.expectedTransferable = defaults.TRANSFERABLE(); assertEq(vars.actualTransferable, vars.expectedTransferable, "transferable"); - - vars.actualFee = constructedLL.FEE(); - vars.expectedFee = defaults.FEE(); - assertEq(vars.actualFee, vars.expectedFee, "fee"); } } diff --git a/tests/integration/concrete/lt/claim/claim.t.sol b/tests/integration/concrete/lt/claim/claim.t.sol index c595ec3..534231b 100644 --- a/tests/integration/concrete/lt/claim/claim.t.sol +++ b/tests/integration/concrete/lt/claim/claim.t.sol @@ -135,22 +135,22 @@ contract Claim_MerkleLT_Integration_Test is Claim_Integration_Test, MerkleLT_Int // Assert that the stream has been created successfully. assertEq(lockup.getDepositedAmount(expectedStreamId), defaults.CLAIM_AMOUNT(), "depositedAmount"); - assertEq(lockup.getAsset(expectedStreamId), dai, "token"); assertEq(lockup.getEndTime(expectedStreamId), startTime + defaults.TOTAL_DURATION(), "end time"); - assertEq(lockup.isCancelable(expectedStreamId), defaults.CANCELABLE(), "is cancelable"); - assertEq(lockup.isDepleted(expectedStreamId), false, "is depleted"); - assertEq(lockup.isStream(expectedStreamId), true, "is stream"); - assertEq(lockup.isTransferable(expectedStreamId), defaults.TRANSFERABLE(), "is transferable"); assertEq(lockup.getRecipient(expectedStreamId), users.recipient1, "recipient"); assertEq(lockup.getSender(expectedStreamId), users.campaignOwner, "sender"); assertEq(lockup.getStartTime(expectedStreamId), startTime, "start time"); - assertEq(lockup.wasCanceled(expectedStreamId), false, "was canceled"); // It should create a stream with `STREAM_START_TIME` as start time. assertEq( lockup.getTranches(expectedStreamId), defaults.tranchesMerkleLT({ streamStartTime: streamStartTime, totalAmount: defaults.CLAIM_AMOUNT() }), "tranches" ); + assertEq(lockup.getUnderlyingToken(expectedStreamId), dai, "token"); + assertEq(lockup.isCancelable(expectedStreamId), defaults.CANCELABLE(), "is cancelable"); + assertEq(lockup.isDepleted(expectedStreamId), false, "is depleted"); + assertEq(lockup.isStream(expectedStreamId), true, "is stream"); + assertEq(lockup.isTransferable(expectedStreamId), defaults.TRANSFERABLE(), "is transferable"); + assertEq(lockup.wasCanceled(expectedStreamId), false, "was canceled"); assertTrue(merkleLT.hasClaimed(defaults.INDEX1()), "not claimed"); diff --git a/tests/integration/concrete/lt/constructor.t.sol b/tests/integration/concrete/lt/constructor.t.sol index d4fc645..becb215 100644 --- a/tests/integration/concrete/lt/constructor.t.sol +++ b/tests/integration/concrete/lt/constructor.t.sol @@ -11,13 +11,13 @@ contract Constructor_MerkleLT_Integration_Test is Integration_Test { struct Vars { address actualAdmin; uint256 actualAllowance; + string actualCampaignName; bool actualCancelable; uint40 actualExpiration; address actualFactory; string actualIpfsCID; address actualLockup; bytes32 actualMerkleRoot; - string actualName; uint256 actualFee; uint40 actualStreamStartTime; address actualToken; @@ -26,13 +26,13 @@ contract Constructor_MerkleLT_Integration_Test is Integration_Test { bool actualTransferable; address expectedAdmin; uint256 expectedAllowance; + string expectedCampaignName; bool expectedCancelable; uint40 expectedExpiration; address expectedFactory; string expectedIpfsCID; address expectedLockup; bytes32 expectedMerkleRoot; - bytes32 expectedName; uint256 expectedFee; uint40 expectedStreamStartTime; address expectedToken; @@ -65,9 +65,9 @@ contract Constructor_MerkleLT_Integration_Test is Integration_Test { vars.expectedAllowance = MAX_UINT256; assertEq(vars.actualAllowance, vars.expectedAllowance, "allowance"); - vars.actualToken = address(constructedLT.TOKEN()); - vars.expectedToken = address(dai); - assertEq(vars.actualToken, vars.expectedToken, "token"); + vars.actualCampaignName = constructedLT.campaignName(); + vars.expectedCampaignName = defaults.CAMPAIGN_NAME(); + assertEq(vars.actualCampaignName, vars.expectedCampaignName, "campaign name"); vars.actualCancelable = constructedLT.CANCELABLE(); vars.expectedCancelable = defaults.CANCELABLE(); @@ -81,6 +81,10 @@ contract Constructor_MerkleLT_Integration_Test is Integration_Test { vars.expectedFactory = address(merkleFactory); assertEq(vars.actualFactory, vars.expectedFactory, "factory"); + vars.actualFee = constructedLT.FEE(); + vars.expectedFee = defaults.FEE(); + assertEq(vars.actualFee, vars.expectedFee, "fee"); + vars.actualIpfsCID = constructedLT.ipfsCID(); vars.expectedIpfsCID = defaults.IPFS_CID(); assertEq(vars.actualIpfsCID, vars.expectedIpfsCID, "ipfsCID"); @@ -93,18 +97,16 @@ contract Constructor_MerkleLT_Integration_Test is Integration_Test { vars.expectedMerkleRoot = defaults.MERKLE_ROOT(); assertEq(vars.actualMerkleRoot, vars.expectedMerkleRoot, "merkleRoot"); - vars.actualName = constructedLT.name(); - vars.expectedName = defaults.NAME_BYTES32(); - assertEq(bytes32(abi.encodePacked(vars.actualName)), vars.expectedName, "name"); - - vars.actualFee = constructedLT.FEE(); - vars.expectedFee = defaults.FEE(); - assertEq(vars.actualFee, vars.expectedFee, "fee"); + assertEq(constructedLT.shape(), defaults.SHAPE(), "shape"); vars.actualStreamStartTime = constructedLT.STREAM_START_TIME(); vars.expectedStreamStartTime = defaults.STREAM_START_TIME_ZERO(); assertEq(vars.actualStreamStartTime, vars.expectedStreamStartTime, "streamStartTime"); + vars.actualToken = address(constructedLT.TOKEN()); + vars.expectedToken = address(dai); + assertEq(vars.actualToken, vars.expectedToken, "token"); + vars.actualTotalPercentage = constructedLT.TOTAL_PERCENTAGE(); vars.expectedTotalPercentage = defaults.TOTAL_PERCENTAGE(); assertEq(vars.actualTotalPercentage, vars.expectedTotalPercentage, "totalPercentage"); diff --git a/tests/utils/Defaults.sol b/tests/utils/Defaults.sol index f1ea179..1bb1333 100644 --- a/tests/utils/Defaults.sol +++ b/tests/utils/Defaults.sol @@ -33,6 +33,7 @@ contract Defaults is Constants, Merkle { //////////////////////////////////////////////////////////////////////////*/ uint256 public constant AGGREGATE_AMOUNT = CLAIM_AMOUNT * RECIPIENT_COUNT; + string public constant CAMPAIGN_NAME = "Airdrop Campaign"; bool public constant CANCELABLE = false; uint128 public constant CLAIM_AMOUNT = 10_000e18; uint40 public immutable EXPIRATION; @@ -46,8 +47,7 @@ contract Defaults is Constants, Merkle { uint256[] public LEAVES = new uint256[](RECIPIENT_COUNT); uint256 public constant RECIPIENT_COUNT = 4; bytes32 public MERKLE_ROOT; - string public constant NAME = "Airdrop Campaign"; - bytes32 public constant NAME_BYTES32 = bytes32(abi.encodePacked("Airdrop Campaign")); + string public constant SHAPE = "A custom stream shape"; uint40 public immutable STREAM_START_TIME_NON_ZERO = JULY_1_2024 - 2 days; uint40 public immutable STREAM_START_TIME_ZERO = 0; uint64 public constant TOTAL_PERCENTAGE = uUNIT; @@ -116,7 +116,8 @@ contract Defaults is Constants, Merkle { initialAdmin: campaignOwner, ipfsCID: IPFS_CID, merkleRoot: merkleRoot, - name: NAME + campaignName: CAMPAIGN_NAME, + shape: SHAPE }); } diff --git a/tests/utils/Modifiers.sol b/tests/utils/Modifiers.sol index bf1fd85..5cfd024 100644 --- a/tests/utils/Modifiers.sol +++ b/tests/utils/Modifiers.sol @@ -63,6 +63,10 @@ abstract contract Modifiers is Utils { _; } + modifier whenCampaignNameNotExceed32Bytes() { + _; + } + modifier whenExpirationNotZero() { _; } @@ -83,10 +87,6 @@ abstract contract Modifiers is Utils { _; } - modifier whenNameNotTooLong() { - _; - } - modifier whenPercentagesSumNot100Pct() { _; } @@ -107,6 +107,10 @@ abstract contract Modifiers is Utils { _; } + modifier whenShapeNotExceed32Bytes() { + _; + } + modifier whenTotalPercentage100() { _; }