From 058732718ffba41a51e2d5a2ddc2b4613b057c4a Mon Sep 17 00:00:00 2001 From: andreivladbrg Date: Sun, 10 Mar 2024 13:26:23 +0200 Subject: [PATCH] test: increase coverage by testing when there is a rounding error in tranches' amounts calculation --- .../merkle-lockup/lt/claim/claim.t.sol | 74 ++++++++++++++++++- .../merkle-lockup/lt/claim/claim.tree | 12 ++- test/utils/Defaults.sol | 4 + 3 files changed, 85 insertions(+), 5 deletions(-) diff --git a/test/integration/merkle-lockup/lt/claim/claim.t.sol b/test/integration/merkle-lockup/lt/claim/claim.t.sol index 69827756..508feb51 100644 --- a/test/integration/merkle-lockup/lt/claim/claim.t.sol +++ b/test/integration/merkle-lockup/lt/claim/claim.t.sol @@ -1,13 +1,21 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.22 <0.9.0; +import { Arrays } from "@openzeppelin/contracts/utils/Arrays.sol"; import { Lockup, LockupTranched } from "@sablier/v2-core/src/types/DataTypes.sol"; import { Errors } from "src/libraries/Errors.sol"; +import { MerkleLockup } from "src/types/DataTypes.sol"; +import { ISablierV2MerkleLockupLT } from "src/interfaces/ISablierV2MerkleLockupLT.sol"; + +import { Merkle } from "../../../../utils/Murky.sol"; +import { MerkleBuilder } from "../../../../utils/MerkleBuilder.sol"; import { MerkleLockup_Integration_Test } from "../../MerkleLockup.t.sol"; -contract Claim_Integration_Test is MerkleLockup_Integration_Test { +contract Claim_Integration_Test is Merkle, MerkleLockup_Integration_Test { + using MerkleBuilder for uint256[]; + function setUp() public virtual override { MerkleLockup_Integration_Test.setUp(); } @@ -101,7 +109,69 @@ contract Claim_Integration_Test is MerkleLockup_Integration_Test { _; } - function test_Claim() external givenCampaignNotExpired givenNotClaimed givenIncludedInMerkleTree { + // Needed this variables in storage due to how the imported libaries work. + uint256[] public leaves = new uint256[](4); // same number of recipients as in the defaults + + function test_Claim_TrancheAmountCalculationRoundingError() + external + givenCampaignNotExpired + givenNotClaimed + givenIncludedInMerkleTree + { + // Declare an amount that will cause a rounding error. + uint128 claimAmount = 340_282_366_920_938_463_463_374_607_431_768_211_453; + uint256 aggregateAmount = defaults.CLAIM_AMOUNT() * 3 + uint256(claimAmount); + + // Compute the Merkle tree. + leaves = defaults.getLeaves(); + leaves[0] = MerkleBuilder.computeLeaf(defaults.INDEX1(), users.recipient1, claimAmount); + MerkleBuilder.sortLeaves(leaves); + bytes32 merkleRoot = getRoot(leaves.toBytes32()); + + // Compute the Merkle proof. + uint256 leaf = MerkleBuilder.computeLeaf(defaults.INDEX1(), users.recipient1, claimAmount); + uint256 pos = Arrays.findUpperBound(leaves, leaf); + bytes32[] memory proof = getProof(leaves.toBytes32(), pos); + + /// Declare the constructor params. + MerkleLockup.ConstructorParams memory baseParams = defaults.baseParams(); + baseParams.merkleRoot = merkleRoot; + + // Deploy the new MerkleLockupLT contract. + ISablierV2MerkleLockupLT _merkleLockupLT = merkleLockupFactory.createMerkleLockupLT( + baseParams, lockupTranched, defaults.tranchesWithPercentages(), aggregateAmount, defaults.RECIPIENTS_COUNT() + ); + + // Fund the MerkleLockupLT contract. + deal({ token: address(dai), to: address(_merkleLockupLT), give: aggregateAmount }); + + uint256 expectedStreamId = lockupTranched.nextStreamId(); + + vm.expectEmit({ emitter: address(_merkleLockupLT) }); + emit Claim(defaults.INDEX1(), users.recipient1, claimAmount, expectedStreamId); + uint256 actualStreamId = _merkleLockupLT.claim(defaults.INDEX1(), users.recipient1, claimAmount, proof); + + LockupTranched.StreamLT memory actualStream = lockupTranched.getStream(actualStreamId); + LockupTranched.StreamLT memory expectedStream = LockupTranched.StreamLT({ + amounts: Lockup.Amounts({ deposited: claimAmount, refunded: 0, withdrawn: 0 }), + asset: dai, + endTime: uint40(block.timestamp) + defaults.TOTAL_DURATION(), + isCancelable: defaults.CANCELABLE(), + isDepleted: false, + isStream: true, + isTransferable: defaults.TRANSFERABLE(), + sender: users.admin, + startTime: uint40(block.timestamp), + tranches: defaults.tranches(claimAmount), + wasCanceled: false + }); + + assertTrue(_merkleLockupLT.hasClaimed(defaults.INDEX1()), "not claimed"); + assertEq(actualStreamId, expectedStreamId, "invalid stream id"); + assertEq(actualStream, expectedStream); + } + + function test_Claim() external { uint256 expectedStreamId = lockupTranched.nextStreamId(); vm.expectEmit({ emitter: address(merkleLockupLT) }); diff --git a/test/integration/merkle-lockup/lt/claim/claim.tree b/test/integration/merkle-lockup/lt/claim/claim.tree index c539535d..e75b1250 100644 --- a/test/integration/merkle-lockup/lt/claim/claim.tree +++ b/test/integration/merkle-lockup/lt/claim/claim.tree @@ -15,6 +15,12 @@ claim.t.sol │ └── when the Merkle proof is not valid │ └── it should revert └── given the claim is included in the Merkle tree - ├── it should mark the index as claimed - ├── it should create a LockupTranched stream - └── it should emit a {Claim} event + ├── when the calculation of the tranches' amounts causes a rounding error + │ ├── it should adjust the last tranche amount + │ ├── it should mark the index as claimed + │ ├── it should create a LockupTranched stream + │ └── it should emit a {Claim} event + └── when the calculation of the tranches' amounts does not cause a rounding error + ├── it should mark the index as claimed + ├── it should create a LockupTranched stream + └── it should emit a {Claim} event diff --git a/test/utils/Defaults.sol b/test/utils/Defaults.sol index f4ae0b74..7e7aa8ee 100644 --- a/test/utils/Defaults.sol +++ b/test/utils/Defaults.sol @@ -88,6 +88,10 @@ contract Defaults is Merkle { MERKLE_ROOT = getRoot(LEAVES.toBytes32()); } + function getLeaves() public view returns (uint256[] memory) { + return LEAVES; + } + /*////////////////////////////////////////////////////////////////////////// MERKLE-LOCKUP //////////////////////////////////////////////////////////////////////////*/