From dd1f751750cf2f312afc3ff9bddefc1ab666e51f Mon Sep 17 00:00:00 2001 From: smol-ninja Date: Sat, 10 Feb 2024 21:00:46 +0000 Subject: [PATCH] feat: include "amount" in LD leaf test: refine fork tests refactor: use "airstream campaign" terminology --- src/SablierV2MerkleLockupLD.sol | 2 +- .../ISablierV2MerkleLockupFactory.sol | 2 +- src/interfaces/ISablierV2MerkleLockupLD.sol | 2 +- test/fork/merkle-lockup/MerkleLockupLD.t.sol | 45 ++++++------------- test/fork/merkle-lockup/MerkleLockupLL.t.sol | 20 ++------- .../merkle-lockup/ld/claim/claim.t.sol | 20 +-------- .../merkle-lockup/ll/claim/claim.t.sol | 15 +------ test/utils/Defaults.sol | 14 +++--- test/utils/MerkleBuilder.sol | 9 ++-- test/utils/MerkleBuilder.t.sol | 16 ++++--- 10 files changed, 45 insertions(+), 100 deletions(-) diff --git a/src/SablierV2MerkleLockupLD.sol b/src/SablierV2MerkleLockupLD.sol index 9e56e6d6..c6734e6c 100644 --- a/src/SablierV2MerkleLockupLD.sol +++ b/src/SablierV2MerkleLockupLD.sol @@ -64,7 +64,7 @@ contract SablierV2MerkleLockupLD is { // Generate the Merkle tree leaf by hashing the corresponding parameters. Hashing twice prevents second // preimage attacks. - bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(index, recipient, segments)))); + bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(index, recipient, amount, segments)))); // Checks: validate the function. _checkClaim(index, leaf, merkleProof); diff --git a/src/interfaces/ISablierV2MerkleLockupFactory.sol b/src/interfaces/ISablierV2MerkleLockupFactory.sol index ab2286c9..9a8ece37 100644 --- a/src/interfaces/ISablierV2MerkleLockupFactory.sol +++ b/src/interfaces/ISablierV2MerkleLockupFactory.sol @@ -10,7 +10,7 @@ import { ISablierV2MerkleLockupLL } from "./ISablierV2MerkleLockupLL.sol"; import { MerkleLockup } from "../types/DataTypes.sol"; /// @title ISablierV2MerkleLockupFactory -/// @notice Deploys new Merkle lockups via CREATE2. +/// @notice Deploys new airstream campaigns via CREATE2. interface ISablierV2MerkleLockupFactory { /*////////////////////////////////////////////////////////////////////////// EVENTS diff --git a/src/interfaces/ISablierV2MerkleLockupLD.sol b/src/interfaces/ISablierV2MerkleLockupLD.sol index 73660339..c9302d30 100644 --- a/src/interfaces/ISablierV2MerkleLockupLD.sol +++ b/src/interfaces/ISablierV2MerkleLockupLD.sol @@ -31,7 +31,7 @@ interface ISablierV2MerkleLockupLD is ISablierV2MerkleLockup { /// /// @param index The index of the recipient in the Merkle tree. /// @param recipient The address of the stream holder. - /// @param amount The amount of tokens to be streamed. It must include fee when protocol fee is enabled. + /// @param amount The amount of tokens to be streamed. /// @param segments The segments with durations to create the custom streaming curve. /// @param merkleProof The Merkle proof of inclusion in the stream. /// @return streamId The id of the newly created stream. diff --git a/test/fork/merkle-lockup/MerkleLockupLD.t.sol b/test/fork/merkle-lockup/MerkleLockupLD.t.sol index 0c7a7d41..fc1ea10b 100644 --- a/test/fork/merkle-lockup/MerkleLockupLD.t.sol +++ b/test/fork/merkle-lockup/MerkleLockupLD.t.sol @@ -81,29 +81,18 @@ abstract contract MerkleLockupLD_Fork_Test is Fork_Test { vars.streamDurations = new uint40[](vars.recipientsCount); for (uint256 i = 0; i < vars.recipientsCount; ++i) { - vm.assume( - params.leafData[i].segments.length > 0 - && params.leafData[i].segments.length < defaults.MAX_SEGMENT_COUNT() - ); + vm.assume(params.leafData[i].segments.length > 0); vars.indexes[i] = params.leafData[i].index; // Bound each leaf's segment duration to avoid overflows. fuzzSegmentDurations(params.leafData[i].segments); - uint256 segmentCount = params.leafData[i].segments.length; - for (uint256 j = 0; j < segmentCount; ++j) { - // Bound each leaf's segment amount so that `aggregateAmount` does not overflow. - params.leafData[i].segments[j].amount = uint128( - _bound( - params.leafData[i].segments[j].amount, - 1, - MAX_UINT128 / (vars.recipientsCount * segmentCount) - 1 - ) - ); - - vars.amounts[i] += params.leafData[i].segments[j].amount; - vars.streamDurations[i] += params.leafData[i].segments[j].duration; - } + (vars.amounts[i],) = fuzzDynamicStreamAmounts({ + upperBound: uint128(MAX_UINT128 / vars.recipientsCount - 1), + segments: params.leafData[i].segments, + protocolFee: defaults.PROTOCOL_FEE(), + brokerFee: defaults.BROKER_FEE() + }); vars.aggregateAmount += vars.amounts[i]; @@ -113,10 +102,13 @@ abstract contract MerkleLockupLD_Fork_Test is Fork_Test { vars.segments[i] = params.leafData[i].segments; vars.segmentsWithTimestamps[i] = getSegmentsWithTimestamps(params.leafData[i].segments); + + vars.streamDurations[i] = + vars.segmentsWithTimestamps[i][params.leafData[i].segments.length - 1].timestamp - getBlockTimestamp(); } leaves = new uint256[](vars.recipientsCount); - leaves = MerkleBuilder.computeLeavesLD(vars.indexes, vars.recipients, vars.segments); + leaves = MerkleBuilder.computeLeavesLD(vars.indexes, vars.recipients, vars.amounts, vars.segments); // Sort the leaves in ascending order to match the production environment. MerkleBuilder.sortLeaves(leaves); @@ -164,6 +156,7 @@ abstract contract MerkleLockupLD_Fork_Test is Fork_Test { vars.leafToClaim = MerkleBuilder.computeLeafLD( vars.indexes[params.posBeforeSort], vars.recipients[params.posBeforeSort], + vars.amounts[params.posBeforeSort], vars.segments[params.posBeforeSort] ); vars.leafPos = Arrays.findUpperBound(leaves, vars.leafToClaim); @@ -200,19 +193,7 @@ abstract contract MerkleLockupLD_Fork_Test is Fork_Test { assertTrue(vars.merkleLockupLD.hasClaimed(vars.indexes[params.posBeforeSort])); assertEq(vars.actualStreamId, vars.expectedStreamId); - assertEq(vars.actualStream.amounts.deposited, vars.expectedStream.amounts.deposited); - assertEq(vars.actualStream.amounts.refunded, vars.expectedStream.amounts.refunded); - assertEq(vars.actualStream.amounts.withdrawn, vars.expectedStream.amounts.withdrawn); - assertEq(vars.actualStream.asset, vars.expectedStream.asset); - assertEq(vars.actualStream.endTime, vars.expectedStream.endTime); - assertEq(vars.actualStream.isCancelable, vars.expectedStream.isCancelable); - assertEq(vars.actualStream.isDepleted, vars.expectedStream.isDepleted); - assertEq(vars.actualStream.isStream, vars.expectedStream.isStream); - assertEq(vars.actualStream.isTransferable, vars.expectedStream.isTransferable); - assertEq(vars.actualStream.segments, vars.expectedStream.segments); - assertEq(vars.actualStream.sender, vars.expectedStream.sender); - assertEq(vars.actualStream.startTime, vars.expectedStream.startTime); - assertEq(vars.actualStream.wasCanceled, vars.expectedStream.wasCanceled); + assertEq(vars.actualStream, vars.expectedStream); /*////////////////////////////////////////////////////////////////////////// CLAWBACK diff --git a/test/fork/merkle-lockup/MerkleLockupLL.t.sol b/test/fork/merkle-lockup/MerkleLockupLL.t.sol index a25779ad..ff7a1106 100644 --- a/test/fork/merkle-lockup/MerkleLockupLL.t.sol +++ b/test/fork/merkle-lockup/MerkleLockupLL.t.sol @@ -76,10 +76,8 @@ abstract contract MerkleLockupLL_Fork_Test is Fork_Test { vars.indexes[i] = params.leafData[i].index; // Bound each leaf amount so that `aggregateAmount` does not overflow. - params.leafData[i].amount = - uint128(_bound(params.leafData[i].amount, 1, MAX_UINT256 / vars.recipientsCount - 1)); - vars.amounts[i] = params.leafData[i].amount; - vars.aggregateAmount += params.leafData[i].amount; + vars.amounts[i] = uint128(_bound(params.leafData[i].amount, 1, MAX_UINT256 / vars.recipientsCount - 1)); + vars.aggregateAmount += vars.amounts[i]; // Avoid zero recipient addresses. uint256 boundedRecipientSeed = _bound(params.leafData[i].recipientSeed, 1, type(uint160).max); @@ -172,19 +170,7 @@ abstract contract MerkleLockupLL_Fork_Test is Fork_Test { assertTrue(vars.merkleLockupLL.hasClaimed(vars.indexes[params.posBeforeSort])); assertEq(vars.actualStreamId, vars.expectedStreamId); - assertEq(vars.actualStream.amounts.deposited, vars.expectedStream.amounts.deposited); - assertEq(vars.actualStream.amounts.refunded, vars.expectedStream.amounts.refunded); - assertEq(vars.actualStream.amounts.withdrawn, vars.expectedStream.amounts.withdrawn); - assertEq(vars.actualStream.asset, vars.expectedStream.asset); - assertEq(vars.actualStream.cliffTime, vars.expectedStream.cliffTime); - assertEq(vars.actualStream.endTime, vars.expectedStream.endTime); - assertEq(vars.actualStream.isCancelable, vars.expectedStream.isCancelable); - assertEq(vars.actualStream.isDepleted, vars.expectedStream.isDepleted); - assertEq(vars.actualStream.isStream, vars.expectedStream.isStream); - assertEq(vars.actualStream.isTransferable, vars.expectedStream.isTransferable); - assertEq(vars.actualStream.sender, vars.expectedStream.sender); - assertEq(vars.actualStream.startTime, vars.expectedStream.startTime); - assertEq(vars.actualStream.wasCanceled, vars.expectedStream.wasCanceled); + assertEq(vars.actualStream, vars.expectedStream); /*////////////////////////////////////////////////////////////////////////// CLAWBACK diff --git a/test/integration/merkle-lockup/ld/claim/claim.t.sol b/test/integration/merkle-lockup/ld/claim/claim.t.sol index 0676443e..fb8904b8 100644 --- a/test/integration/merkle-lockup/ld/claim/claim.t.sol +++ b/test/integration/merkle-lockup/ld/claim/claim.t.sol @@ -200,24 +200,6 @@ contract ClaimLD_Integration_Test is MerkleLockup_Integration_Test { assertTrue(merkleLockupLD.hasClaimed(defaults.INDEX1()), "not claimed"); assertEq(actualStreamId, expectedStreamId, "invalid stream id"); - - assertEq(actualStream.amounts.deposited, expectedStream.amounts.deposited); - assertEq(actualStream.amounts.refunded, expectedStream.amounts.refunded); - assertEq(actualStream.amounts.withdrawn, expectedStream.amounts.withdrawn); - assertEq(actualStream.asset, expectedStream.asset); - assertEq(actualStream.endTime, expectedStream.endTime); - assertEq(actualStream.isCancelable, expectedStream.isCancelable); - assertEq(actualStream.isDepleted, expectedStream.isDepleted); - assertEq(actualStream.isStream, expectedStream.isStream); - assertEq(actualStream.isTransferable, expectedStream.isTransferable); - assertEq(actualStream.segments[0].amount, expectedStream.segments[0].amount); - assertEq(actualStream.segments[0].exponent, expectedStream.segments[0].exponent); - assertEq(actualStream.segments[0].timestamp, expectedStream.segments[0].timestamp); - assertEq(actualStream.segments[1].amount, expectedStream.segments[1].amount); - assertEq(actualStream.segments[1].exponent, expectedStream.segments[1].exponent); - assertEq(actualStream.segments[1].timestamp, expectedStream.segments[1].timestamp); - assertEq(actualStream.sender, expectedStream.sender); - assertEq(actualStream.startTime, expectedStream.startTime); - assertEq(actualStream.wasCanceled, expectedStream.wasCanceled); + assertEq(actualStream, expectedStream); } } diff --git a/test/integration/merkle-lockup/ll/claim/claim.t.sol b/test/integration/merkle-lockup/ll/claim/claim.t.sol index 4f238eb3..33247dff 100644 --- a/test/integration/merkle-lockup/ll/claim/claim.t.sol +++ b/test/integration/merkle-lockup/ll/claim/claim.t.sol @@ -153,19 +153,6 @@ contract ClaimLL_Integration_Test is MerkleLockup_Integration_Test { assertTrue(merkleLockupLL.hasClaimed(defaults.INDEX1()), "not claimed"); assertEq(actualStreamId, expectedStreamId, "invalid stream id"); - - assertEq(actualStream.amounts.deposited, expectedStream.amounts.deposited); - assertEq(actualStream.amounts.refunded, expectedStream.amounts.refunded); - assertEq(actualStream.amounts.withdrawn, expectedStream.amounts.withdrawn); - assertEq(actualStream.asset, expectedStream.asset); - assertEq(actualStream.cliffTime, expectedStream.cliffTime); - assertEq(actualStream.endTime, expectedStream.endTime); - assertEq(actualStream.isCancelable, expectedStream.isCancelable); - assertEq(actualStream.isDepleted, expectedStream.isDepleted); - assertEq(actualStream.isStream, expectedStream.isStream); - assertEq(actualStream.isTransferable, expectedStream.isTransferable); - assertEq(actualStream.sender, expectedStream.sender); - assertEq(actualStream.startTime, expectedStream.startTime); - assertEq(actualStream.wasCanceled, expectedStream.wasCanceled); + assertEq(actualStream, expectedStream); } } diff --git a/test/utils/Defaults.sol b/test/utils/Defaults.sol index 44ec18bb..86e579dc 100644 --- a/test/utils/Defaults.sol +++ b/test/utils/Defaults.sol @@ -81,15 +81,15 @@ contract Defaults is Merkle { END_TIME = START_TIME + TOTAL_DURATION; EXPIRATION = uint40(block.timestamp) + 12 weeks; - // Initialize the Merkle tree for Lockup Dynamic streams. - LEAVES_LD[0] = MerkleBuilder.computeLeafLD(INDEX1, users.recipient1, segmentsWithDurations()); - LEAVES_LD[1] = MerkleBuilder.computeLeafLD(INDEX2, users.recipient2, segmentsWithDurations()); - LEAVES_LD[2] = MerkleBuilder.computeLeafLD(INDEX3, users.recipient3, segmentsWithDurations()); - LEAVES_LD[3] = MerkleBuilder.computeLeafLD(INDEX4, users.recipient4, segmentsWithDurations()); + // Initialize the Merkle tree for Lockup Dynamic campaign. + LEAVES_LD[0] = MerkleBuilder.computeLeafLD(INDEX1, users.recipient1, CLAIM_AMOUNT, segmentsWithDurations()); + LEAVES_LD[1] = MerkleBuilder.computeLeafLD(INDEX2, users.recipient2, CLAIM_AMOUNT, segmentsWithDurations()); + LEAVES_LD[2] = MerkleBuilder.computeLeafLD(INDEX3, users.recipient3, CLAIM_AMOUNT, segmentsWithDurations()); + LEAVES_LD[3] = MerkleBuilder.computeLeafLD(INDEX4, users.recipient4, CLAIM_AMOUNT, segmentsWithDurations()); MerkleBuilder.sortLeaves(LEAVES_LD); MERKLE_ROOT_LD = getRoot(LEAVES_LD.toBytes32()); - // Initialize the Merkle tree for Lockup Linear streams. + // Initialize the Merkle tree for Lockup Linear campaign. LEAVES_LL[0] = MerkleBuilder.computeLeafLL(INDEX1, users.recipient1, CLAIM_AMOUNT); LEAVES_LL[1] = MerkleBuilder.computeLeafLL(INDEX2, users.recipient2, CLAIM_AMOUNT); LEAVES_LL[2] = MerkleBuilder.computeLeafLL(INDEX3, users.recipient3, CLAIM_AMOUNT); @@ -135,7 +135,7 @@ contract Defaults is Merkle { } function indexProofLD(uint256 index, address recipient) internal view returns (bytes32[] memory) { - uint256 leaf = MerkleBuilder.computeLeafLD(index, recipient, segmentsWithDurations()); + uint256 leaf = MerkleBuilder.computeLeafLD(index, recipient, CLAIM_AMOUNT, segmentsWithDurations()); uint256 pos = Arrays.findUpperBound(LEAVES_LD, leaf); return getProof(LEAVES_LD.toBytes32(), pos); } diff --git a/test/utils/MerkleBuilder.sol b/test/utils/MerkleBuilder.sol index 9f7bccd4..8d7a905f 100644 --- a/test/utils/MerkleBuilder.sol +++ b/test/utils/MerkleBuilder.sol @@ -15,19 +15,21 @@ library MerkleBuilder { function computeLeafLD( uint256 index, address recipient, + uint128 amount, LockupDynamic.SegmentWithDuration[] memory segments ) internal pure returns (uint256 leaf) { - leaf = uint256(keccak256(bytes.concat(keccak256(abi.encode(index, recipient, segments))))); + leaf = uint256(keccak256(bytes.concat(keccak256(abi.encode(index, recipient, amount, segments))))); } /// @dev A batch function for `computeLeafLD`. function computeLeavesLD( uint256[] memory indexes, address[] memory recipients, + uint128[] memory amounts, LockupDynamic.SegmentWithDuration[][] memory segments ) internal @@ -36,11 +38,12 @@ library MerkleBuilder { { uint256 count = indexes.length; require( - count == recipients.length && count == segments.length, "Merkle leaves arrays must have the same length" + count == recipients.length && count == amounts.length && count == segments.length, + "Merkle leaves arrays must have the same length" ); leaves = new uint256[](count); for (uint256 i = 0; i < count; ++i) { - leaves[i] = computeLeafLD(indexes[i], recipients[i], segments[i]); + leaves[i] = computeLeafLD(indexes[i], recipients[i], amounts[i], segments[i]); } } diff --git a/test/utils/MerkleBuilder.t.sol b/test/utils/MerkleBuilder.t.sol index bfffb054..d5d46558 100644 --- a/test/utils/MerkleBuilder.t.sol +++ b/test/utils/MerkleBuilder.t.sol @@ -15,12 +15,14 @@ contract MerkleBuilder_Test is PRBTest, StdUtils { function testFuzz_ComputeLeafLD( uint256 index, address recipient, + uint128 amount, LockupDynamic.SegmentWithDuration[] memory segments ) external { - uint256 actualLeaf = MerkleBuilder.computeLeafLD(index, recipient, segments); - uint256 expectedLeaf = uint256(keccak256(bytes.concat(keccak256(abi.encode(index, recipient, segments))))); + uint256 actualLeaf = MerkleBuilder.computeLeafLD(index, recipient, amount, segments); + uint256 expectedLeaf = + uint256(keccak256(bytes.concat(keccak256(abi.encode(index, recipient, amount, segments))))); assertEq(actualLeaf, expectedLeaf, "computeLeafLD"); } @@ -28,6 +30,7 @@ contract MerkleBuilder_Test is PRBTest, StdUtils { struct LeavesParamsLD { uint256 indexes; address recipients; + uint128 amounts; LockupDynamic.SegmentWithDuration[] segments; } @@ -36,20 +39,23 @@ contract MerkleBuilder_Test is PRBTest, StdUtils { uint256[] memory indexes = new uint256[](count); address[] memory recipients = new address[](count); + uint128[] memory amounts = new uint128[](count); LockupDynamic.SegmentWithDuration[][] memory segments = new LockupDynamic.SegmentWithDuration[][](count); for (uint256 i = 0; i < count; ++i) { indexes[i] = params[i].indexes; recipients[i] = params[i].recipients; + amounts[i] = params[i].amounts; segments[i] = params[i].segments; } uint256[] memory actualLeaves = new uint256[](count); - actualLeaves = MerkleBuilder.computeLeavesLD(indexes, recipients, segments); + actualLeaves = MerkleBuilder.computeLeavesLD(indexes, recipients, amounts, segments); uint256[] memory expectedLeaves = new uint256[](count); for (uint256 i = 0; i < count; ++i) { - expectedLeaves[i] = - uint256(keccak256(bytes.concat(keccak256(abi.encode(indexes[i], recipients[i], segments[i]))))); + expectedLeaves[i] = uint256( + keccak256(bytes.concat(keccak256(abi.encode(indexes[i], recipients[i], amounts[i], segments[i])))) + ); } assertEq(actualLeaves, expectedLeaves, "computeLeavesLD");