Skip to content

Commit

Permalink
feat: include "amount" in LD leaf
Browse files Browse the repository at this point in the history
test: refine fork tests
refactor: use "airstream campaign" terminology
  • Loading branch information
smol-ninja committed Feb 10, 2024
1 parent a42d0ce commit dd1f751
Show file tree
Hide file tree
Showing 10 changed files with 45 additions and 100 deletions.
2 changes: 1 addition & 1 deletion src/SablierV2MerkleLockupLD.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/ISablierV2MerkleLockupFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/ISablierV2MerkleLockupLD.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
45 changes: 13 additions & 32 deletions test/fork/merkle-lockup/MerkleLockupLD.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
20 changes: 3 additions & 17 deletions test/fork/merkle-lockup/MerkleLockupLL.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
20 changes: 1 addition & 19 deletions test/integration/merkle-lockup/ld/claim/claim.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
15 changes: 1 addition & 14 deletions test/integration/merkle-lockup/ll/claim/claim.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
14 changes: 7 additions & 7 deletions test/utils/Defaults.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down
9 changes: 6 additions & 3 deletions test/utils/MerkleBuilder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]);
}
}

Expand Down
16 changes: 11 additions & 5 deletions test/utils/MerkleBuilder.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,22 @@ 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");
}

/// @dev We declare this struct so that we will not need cheatcodes in the `computeLeavesLD` test.
struct LeavesParamsLD {
uint256 indexes;
address recipients;
uint128 amounts;
LockupDynamic.SegmentWithDuration[] segments;
}

Expand All @@ -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");
Expand Down

0 comments on commit dd1f751

Please sign in to comment.