Skip to content

Commit

Permalink
fix: use percantages in schedule
Browse files Browse the repository at this point in the history
test: update tests accordingly
  • Loading branch information
andreivladbrg committed Dec 20, 2024
1 parent aa45dab commit 525ab17
Show file tree
Hide file tree
Showing 8 changed files with 42 additions and 22 deletions.
2 changes: 1 addition & 1 deletion precompiles/Precompiles.sol

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions script/CreateMerkleLL.s.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.22 <0.9.0;

import { ud2x18 } from "@prb/math/src/UD2x18.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol";

Expand Down Expand Up @@ -43,9 +44,9 @@ contract CreateMerkleLL is BaseScript {
transferable: true,
schedule: MerkleLL.Schedule({
startTime: 0, // i.e. block.timestamp
startAmount: 10e18,
startPercentage: ud2x18(0.01e18),
cliffDuration: 30 days,
cliffAmount: 10e18,
cliffPercentage: ud2x18(0.01e18),
totalDuration: 90 days
}),
aggregateAmount: 10_000e18,
Expand Down
9 changes: 7 additions & 2 deletions src/SablierMerkleLL.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity >=0.8.22;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { ZERO } from "@prb/math/src/UD60x18.sol";
import { ud60x18, ZERO } from "@prb/math/src/UD60x18.sol";
import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol";
import { Broker, Lockup, LockupLinear } from "@sablier/lockup/src/types/DataTypes.sol";

Expand Down Expand Up @@ -112,6 +112,11 @@ contract SablierMerkleLL is
timestamps.end = timestamps.start + _schedule.totalDuration;
}

// Calculate the unlock amounts based on the percentages.
LockupLinear.UnlockAmounts memory unlockAmounts;
unlockAmounts.start = ud60x18(amount).mul(_schedule.startPercentage.intoUD60x18()).intoUint128();
unlockAmounts.cliff = ud60x18(amount).mul(_schedule.cliffPercentage.intoUD60x18()).intoUint128();

// Interaction: create the stream via {SablierLockup}.
uint256 streamId = LOCKUP.createWithTimestampsLL(
Lockup.CreateWithTimestamps({
Expand All @@ -125,7 +130,7 @@ contract SablierMerkleLL is
shape: string(abi.encodePacked(SHAPE)),
broker: Broker({ account: address(0), fee: ZERO })
}),
LockupLinear.UnlockAmounts({ start: _schedule.startAmount, cliff: _schedule.cliffAmount }),
unlockAmounts,
cliffTime
);

Expand Down
6 changes: 3 additions & 3 deletions src/types/DataTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ library MerkleLL {
/// variables in `Lockup.CreateWithTimestampsLL`.
/// @dev A start time value of zero will be considered as `block.timestamp`.
/// @param startTime The start time of the stream.
/// @param startAmount The amount to be unlocked at the start time.
/// @param startAmount The percentage to be unlocked at the start time.
/// @param cliffDuration The duration of the cliff.
/// @param cliffAmount The amount to be unlocked at the cliff time.
/// @param totalDuration The total duration of the stream.
struct Schedule {
uint40 startTime;
uint128 startAmount;
UD2x18 startPercentage;
uint40 cliffDuration;
uint128 cliffAmount;
UD2x18 cliffPercentage;
uint40 totalDuration;
}
}
Expand Down
27 changes: 19 additions & 8 deletions tests/fork/merkle-campaign/MerkleLL.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ pragma solidity >=0.8.22 <0.9.0;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Arrays } from "@openzeppelin/contracts/utils/Arrays.sol";
import { Lockup } from "@sablier/lockup/src/types/DataTypes.sol";
import { ud60x18 } from "@prb/math/src/UD60x18.sol";
import { Lockup, LockupLinear } from "@sablier/lockup/src/types/DataTypes.sol";

import { ISablierMerkleFactory } from "src/interfaces/ISablierMerkleFactory.sol";
import { ISablierMerkleBase, ISablierMerkleLL } from "src/interfaces/ISablierMerkleLL.sol";
Expand Down Expand Up @@ -46,6 +47,7 @@ abstract contract MerkleLL_Fork_Test is Fork_Test {
bytes32 merkleRoot;
address[] recipients;
uint256 recipientCount;
LockupLinear.UnlockAmounts expectedUnlockAmounts;
}

// We need the leaves as a storage variable so that we can use OpenZeppelin's {Arrays.findUpperBound}.
Expand Down Expand Up @@ -75,11 +77,7 @@ abstract contract MerkleLL_Fork_Test is Fork_Test {
vars.indexes[i] = params.leafData[i].index;

// Bound each leaf amount so that `aggregateAmount` does not overflow.
vars.amounts[i] = boundUint128(
params.leafData[i].amount,
defaults.START_AMOUNT() + defaults.CLIFF_AMOUNT(),
uint128(MAX_UINT128 / vars.recipientCount - 1)
);
vars.amounts[i] = boundUint128(params.leafData[i].amount, 1, uint128(MAX_UINT128 / vars.recipientCount - 1));
vars.aggregateAmount += vars.amounts[i];

// Avoid zero recipient addresses.
Expand Down Expand Up @@ -199,6 +197,11 @@ abstract contract MerkleLL_Fork_Test is Fork_Test {
merkleProof: vars.merkleProof
});

vars.expectedUnlockAmounts.start =
ud60x18(vars.amounts[params.posBeforeSort]).mul(defaults.START_PERCENTAGE().intoUD60x18()).intoUint128();
vars.expectedUnlockAmounts.cliff =
ud60x18(vars.amounts[params.posBeforeSort]).mul(defaults.CLIFF_PERCENTAGE().intoUD60x18()).intoUint128();

// Assert that the stream has been created successfully.
assertEq(
lockup.getCliffTime(vars.expectedStreamId), getBlockTimestamp() + defaults.CLIFF_DURATION(), "cliff time"
Expand All @@ -213,8 +216,16 @@ abstract contract MerkleLL_Fork_Test is Fork_Test {
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.getUnlockAmounts(vars.expectedStreamId).cliff,
vars.expectedUnlockAmounts.cliff,
"unlock amounts cliff"
);
assertEq(
lockup.getUnlockAmounts(vars.expectedStreamId).start,
vars.expectedUnlockAmounts.start,
"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");
Expand Down
3 changes: 2 additions & 1 deletion tests/integration/concrete/ll/claim/claim.t.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.22 <0.9.0;

import { ud2x18 } from "@prb/math/src/UD2x18.sol";
import { ISablierMerkleLL } from "src/interfaces/ISablierMerkleLL.sol";
import { MerkleLL } from "src/types/DataTypes.sol";

Expand All @@ -17,7 +18,7 @@ contract Claim_MerkleLL_Integration_Test is Claim_Integration_Test, MerkleLL_Int

function test_WhenScheduledCliffDurationZero() external whenMerkleProofValid whenScheduledStartTimeZero {
schedule.cliffDuration = 0;
schedule.cliffAmount = 0;
schedule.cliffPercentage = ud2x18(0);

merkleLL = merkleFactory.createMerkleLL({
baseParams: defaults.baseParams(),
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/concrete/ll/constructor.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ contract Constructor_MerkleLL_Integration_Test is Integration_Test {
vars.actualSchedule = constructedLL.getSchedule();
vars.expectedSchedule = defaults.schedule();
assertEq(vars.actualSchedule.startTime, vars.expectedSchedule.startTime, "schedule.startTime");
assertEq(vars.actualSchedule.startAmount, vars.expectedSchedule.startAmount, "schedule.startAmount");
assertEq(vars.actualSchedule.startPercentage, vars.expectedSchedule.startPercentage, "schedule.startAmount");
assertEq(vars.actualSchedule.cliffDuration, vars.expectedSchedule.cliffDuration, "schedule.cliffDuration");
assertEq(vars.actualSchedule.cliffAmount, vars.expectedSchedule.cliffAmount, "schedule.cliffAmount");
assertEq(vars.actualSchedule.cliffPercentage, vars.expectedSchedule.cliffPercentage, "schedule.cliffAmount");
assertEq(vars.actualSchedule.totalDuration, vars.expectedSchedule.totalDuration, "schedule.totalDuration");

assertEq(constructedLL.shape(), defaults.SHAPE(), "shape");
Expand Down
8 changes: 5 additions & 3 deletions tests/utils/Defaults.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity >=0.8.22;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Arrays } from "@openzeppelin/contracts/utils/Arrays.sol";
import { ud2x18, uUNIT } from "@prb/math/src/UD2x18.sol";
import { ud2x18, UD2x18, uUNIT } from "@prb/math/src/UD2x18.sol";
import { ud } from "@prb/math/src/UD60x18.sol";
import { LockupTranched } from "@sablier/lockup/src/types/DataTypes.sol";
import { Merkle } from "murky/src/Merkle.sol";
Expand Down Expand Up @@ -37,6 +37,7 @@ contract Defaults is Constants, Merkle {
string public constant CAMPAIGN_NAME = "Airdrop Campaign ";
bool public constant CANCELABLE = false;
uint128 public constant CLAIM_AMOUNT = 10_000e18;
UD2x18 public constant CLIFF_PERCENTAGE = UD2x18.wrap(0.25e18); // 25% of the claim amount
uint40 public immutable EXPIRATION;
uint256 public constant FEE = 0.005e18;
uint40 public constant FIRST_CLAIM_TIME = JULY_1_2024;
Expand All @@ -50,6 +51,7 @@ contract Defaults is Constants, Merkle {
bytes32 public MERKLE_ROOT;
// Since Factory stores shape as bytes32, extra spaces are padded to it.
string public constant SHAPE = "A custom stream shape ";
UD2x18 public constant START_PERCENTAGE = UD2x18.wrap(0.01e18); // 1% of the claim amount
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;
Expand Down Expand Up @@ -147,9 +149,9 @@ contract Defaults is Constants, Merkle {

function schedule() public pure returns (MerkleLL.Schedule memory schedule_) {
schedule_.startTime = STREAM_START_TIME_ZERO;
schedule_.startAmount = START_AMOUNT;
schedule_.startPercentage = START_PERCENTAGE;
schedule_.cliffDuration = CLIFF_DURATION;
schedule_.cliffAmount = CLIFF_AMOUNT;
schedule_.cliffPercentage = CLIFF_PERCENTAGE;
schedule_.totalDuration = TOTAL_DURATION;
}

Expand Down

0 comments on commit 525ab17

Please sign in to comment.