NC001 - Constants should be defined rather than using magic numbers:
Even assembly can benefit from using readable constants instead of hex/numeric literals.
Click to show 4 findings
File: v2-core/src/SablierV2NFTDescriptor.sol
146 } else if (truncatedAmount >= 1e15) {
150 string[5] memory suffixes = ["", "K", "M", "B", "T"];
156 while (truncatedAmount >= 1000) {
157 fractionalAmount = (truncatedAmount / 10) % 100;
158 truncatedAmount /= 1000;
180 } else if (durationInDays > 9999) {
200 return streamedAmount * 10_000 / depositedAmount;
218 uint256 hue = (bitField >> 16) % 360;
223 uint256 saturation = ((bitField >> 8) & 0xFF) % 80 + 20;
228 uint256 lightness = (bitField & 0xFF) % 70 + 30;
324 if (success && returnData.length == 32) {
338 if (!success || returnData.length <= 64) {
346 if (bytes(symbol).length > 30) {
374 string memory fractionalPart = stringifyFractionalAmount(percentage % 100);
377 string memory wholePart = (percentage / 100).toString();
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2NFTDescriptor.sol#L0:0
File: v2-core/src/libraries/NFTSVG.sol
72 vars.amountWidth + vars.durationWidth + vars.progressWidth + vars.statusWidth + CARD_MARGIN * 3;
79 vars.progressXPosition = (1000 - vars.cardsWidth) / 2;
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/libraries/NFTSVG.sol#L0:0
File: v2-core/src/libraries/SVGElements.sol
94 width = 144;
96 width = 208;
106 width = Math.max(captionWidth, contentWidth) + 40;
217 (10_000 - progressNumerical).toString(),
240 uint256 charWidth = largeFont ? 16 : 13;
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/libraries/SVGElements.sol#L0:0
File: v2-periphery/src/abstracts/SablierV2MerkleLockup.sol
61 if (bytes(params.name).length > 32) {
64 maxLength: 32
157 return _firstClaimTime > 0 && block.timestamp > _firstClaimTime + 7 days;
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-periphery/src/abstracts/SablierV2MerkleLockup.sol#L0:0
NC002 - Use scientific notation (e.g. 1e18) rather than exponentiation (e.g. 10**18):
While the compiler knows to optimize away the exponentiation, it's still better coding practice to use idioms that do not require compiler optimization, if they exist.
File: v2-core/src/SablierV2NFTDescriptor.sol
140 truncatedAmount = decimals == 0 ? amount : amount / 10 ** decimals;
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2NFTDescriptor.sol#L0:0
NC003 - Function ordering does not follow the Solidity style guide:
According to the Solidity style guide, functions should be laid out in the following order :constructor()
, receive()
, fallback()
, external
, public
, internal
, private
, but the cases below do not follow this pattern.
File: v2-core/src/abstracts/SablierV2Lockup.sol
function wasCanceled(uint256 streamId) external view override notNull(streamId) returns (bool result) {
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/abstracts/SablierV2Lockup.sol#L218:218
File: v2-periphery/src/abstracts/SablierV2MerkleLockup.sol
function name() external view override returns (string memory) {
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-periphery/src/abstracts/SablierV2MerkleLockup.sol#L98:98
NC004 - Imports could be organized more systematically:
This issue arises when the contract's interface is not imported first, followed by each of the interfaces it uses, followed by all other files.
Click to show 11 findings
File: v2-core/src/SablierV2LockupDynamic.sol
12 import { ISablierV2LockupDynamic } from "./interfaces/ISablierV2LockupDynamic.sol";
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2LockupDynamic.sol#L0:0
File: v2-core/src/SablierV2LockupLinear.sol
11 import { ISablierV2LockupLinear } from "./interfaces/ISablierV2LockupLinear.sol";
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2LockupLinear.sol#L0:0
File: v2-core/src/SablierV2LockupTranched.sol
9 import { ISablierV2LockupTranched } from "./interfaces/ISablierV2LockupTranched.sol";
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2LockupTranched.sol#L0:0
File: v2-core/src/SablierV2NFTDescriptor.sol
10 import { ISablierV2Lockup } from "./interfaces/ISablierV2Lockup.sol";
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2NFTDescriptor.sol#L0:0
File: v2-core/src/abstracts/SablierV2Lockup.sol
8 import { IERC721Metadata } from "hot/node_modules/@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/abstracts/SablierV2Lockup.sol#L0:0
File: v2-periphery/src/SablierV2BatchLockup.sol
6 import { ISablierV2LockupDynamic } from "hot/@sablier/v2-core/src/interfaces/ISablierV2LockupDynamic.sol";
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-periphery/src/SablierV2BatchLockup.sol#L0:0
File: v2-periphery/src/SablierV2MerkleLL.sol
5 import { IERC20 } from "hot/node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol";
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-periphery/src/SablierV2MerkleLL.sol#L0:0
File: v2-periphery/src/SablierV2MerkleLT.sol
5 import { IERC20 } from "hot/node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol";
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-periphery/src/SablierV2MerkleLT.sol#L0:0
File: v2-periphery/src/SablierV2MerkleLockupFactory.sol
5 import { ISablierV2LockupLinear } from "hot/@sablier/v2-core/src/interfaces/ISablierV2LockupLinear.sol";
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-periphery/src/SablierV2MerkleLockupFactory.sol#L0:0
File: v2-periphery/src/abstracts/SablierV2MerkleLockup.sol
10 import { ISablierV2MerkleLockup } from "../interfaces/ISablierV2MerkleLockup.sol";
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-periphery/src/abstracts/SablierV2MerkleLockup.sol#L0:0
File: v2-periphery/src/types/DataTypes.sol
6 import { ISablierV2Lockup } from "hot/@sablier/v2-core/src/interfaces/ISablierV2Lockup.sol";
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-periphery/src/types/DataTypes.sol#L0:0
NC005 - Constants in comparisons should appear on the left side:
This issue arises when constants in comparisons appear on the right side, which can lead to typo bugs.
Click to show 9 findings
File: v2-core/src/SablierV2LockupDynamic.sol
204 if (_segments[streamId].length > 1) {
243 if (index == 0) {
360 if (createAmounts.brokerFee > 0) {
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2LockupDynamic.sol#L0:0
File: v2-core/src/SablierV2LockupLinear.sol
141 if (params.durations.cliff > 0) {
263 if (params.timestamps.cliff > 0) {
280 if (createAmounts.brokerFee > 0) {
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2LockupLinear.sol#L0:0
File: v2-core/src/SablierV2LockupTranched.sol
264 if (createAmounts.brokerFee > 0) {
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2LockupTranched.sol#L0:0
File: v2-core/src/SablierV2NFTDescriptor.sol
134 if (amount == 0) {
140 truncatedAmount = decimals == 0 ? amount : amount / 10 ** decimals;
144 if (truncatedAmount < 1) {
146 } else if (truncatedAmount >= 1e15) {
156 while (truncatedAmount >= 1000) {
178 if (durationInDays == 0) {
180 } else if (durationInDays > 9999) {
184 string memory suffix = durationInDays == 1 ? " Day" : " Days";
324 if (success && returnData.length == 32) {
338 if (!success || returnData.length <= 64) {
346 if (bytes(symbol).length > 30) {
357 if (fractionalAmount == 0) {
361 else if (fractionalAmount < 10) {
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2NFTDescriptor.sol#L0:0
File: v2-core/src/abstracts/SablierV2Lockup.sol
309 if (recipient.code.length > 0) {
354 if (amount == 0) {
382 if (msg.sender != recipient && recipient.code.length > 0) {
394 if (msg.sender != sender && sender.code.length > 0 && sender != recipient) {
427 if (withdrawableAmount > 0) {
584 if (recipientAmount == 0) {
609 if (recipient.code.length > 0) {
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/abstracts/SablierV2Lockup.sol#L0:0
File: v2-core/src/libraries/Helpers.sol
90 if (totalAmount == 0) {
121 if (depositAmount == 0) {
126 if (startTime == 0) {
132 if (segmentCount == 0) {
148 if (depositAmount == 0) {
153 if (timestamps.start == 0) {
158 if (timestamps.cliff > 0) {
193 if (depositAmount == 0) {
198 if (startTime == 0) {
204 if (trancheCount == 0) {
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/libraries/Helpers.sol#L0:0
File: v2-core/src/libraries/SVGElements.sol
206 if (progressNumerical == 0) {
235 if (length == 0) {
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/libraries/SVGElements.sol#L0:0
File: v2-periphery/src/SablierV2BatchLockup.sol
36 if (batchSize == 0) {
84 if (batchSize == 0) {
137 if (batchSize == 0) {
185 if (batchSize == 0) {
237 if (batchSize == 0) {
285 if (batchSize == 0) {
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-periphery/src/SablierV2BatchLockup.sol#L0:0
File: v2-periphery/src/abstracts/SablierV2MerkleLockup.sol
61 if (bytes(params.name).length > 32) {
94 return EXPIRATION > 0 && EXPIRATION <= block.timestamp;
149 if (_firstClaimTime == 0) {
157 return _firstClaimTime > 0 && block.timestamp > _firstClaimTime + 7 days;
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-periphery/src/abstracts/SablierV2MerkleLockup.sol#L0:0
NC006 - else-block not required:
One level of nesting can be removed by not having an else block when the if-block returns
Click to show 4 findings
File: v2-core/src/SablierV2LockupDynamic.sol
204 if (_segments[streamId].length > 1) {
205
206 return _calculateStreamedAmountForMultipleSegments(streamId);
207 } else {
208
209 return _calculateStreamedAmountForOneSegment(streamId);
210 }
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2LockupDynamic.sol#L0:0
File: v2-core/src/SablierV2NFTDescriptor.sol
144 if (truncatedAmount < 1) {
145 return string.concat(SVGElements.SIGN_LT, " 1");
146 } else if (truncatedAmount >= 1e15) {
147 return string.concat(SVGElements.SIGN_GT, " 999.99T");
148 }
178 if (durationInDays == 0) {
179 return string.concat(SVGElements.SIGN_LT, " 1 Day");
180 } else if (durationInDays > 9999) {
181 return string.concat(SVGElements.SIGN_GT, " 9999 Days");
182 }
309 if (symbol.equal("SAB-V2-LOCKUP-LIN")) {
310 return "Lockup Linear";
311 } else if (symbol.equal("SAB-V2-LOCKUP-DYN")) {
312 return "Lockup Dynamic";
313 } else if (symbol.equal("SAB-V2-LOCKUP-TRA")) {
314 return "Lockup Tranched";
315 } else {
316 revert Errors.SablierV2NFTDescriptor_UnknownNFT(sablier, symbol);
317 }
311 } else if (symbol.equal("SAB-V2-LOCKUP-DYN")) {
312 return "Lockup Dynamic";
313 } else if (symbol.equal("SAB-V2-LOCKUP-TRA")) {
314 return "Lockup Tranched";
315 } else {
316 revert Errors.SablierV2NFTDescriptor_UnknownNFT(sablier, symbol);
317 }
313 } else if (symbol.equal("SAB-V2-LOCKUP-TRA")) {
314 return "Lockup Tranched";
315 } else {
316 revert Errors.SablierV2NFTDescriptor_UnknownNFT(sablier, symbol);
317 }
324 if (success && returnData.length == 32) {
325 return abi.decode(returnData, (uint8));
326 } else {
327 return 0;
328 }
346 if (bytes(symbol).length > 30) {
347 return "Long Symbol";
348 } else {
349 return symbol;
350 }
357 if (fractionalAmount == 0) {
358 return "";
359 }
360
361 else if (fractionalAmount < 10) {
362 return string.concat(".0", fractionalAmount.toString());
363 }
364
365 else {
366 return string.concat(".", fractionalAmount.toString());
367 }
361 else if (fractionalAmount < 10) {
362 return string.concat(".0", fractionalAmount.toString());
363 }
364
365 else {
366 return string.concat(".", fractionalAmount.toString());
367 }
385 if (status == Lockup.Status.DEPLETED) {
386 return "Depleted";
387 } else if (status == Lockup.Status.CANCELED) {
388 return "Canceled";
389 } else if (status == Lockup.Status.STREAMING) {
390 return "Streaming";
391 } else if (status == Lockup.Status.SETTLED) {
392 return "Settled";
393 } else {
394 return "Pending";
395 }
387 } else if (status == Lockup.Status.CANCELED) {
388 return "Canceled";
389 } else if (status == Lockup.Status.STREAMING) {
390 return "Streaming";
391 } else if (status == Lockup.Status.SETTLED) {
392 return "Settled";
393 } else {
394 return "Pending";
395 }
389 } else if (status == Lockup.Status.STREAMING) {
390 return "Streaming";
391 } else if (status == Lockup.Status.SETTLED) {
392 return "Settled";
393 } else {
394 return "Pending";
395 }
391 } else if (status == Lockup.Status.SETTLED) {
392 return "Settled";
393 } else {
394 return "Pending";
395 }
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2NFTDescriptor.sol#L0:0
File: v2-core/src/abstracts/SablierV2Lockup.sol
482 if (_streams[streamId].isDepleted) {
483 return Lockup.Status.DEPLETED;
484 } else if (_streams[streamId].wasCanceled) {
485 return Lockup.Status.CANCELED;
486 }
492 if (_calculateStreamedAmount(streamId) < _streams[streamId].amounts.deposited) {
493 return Lockup.Status.STREAMING;
494 } else {
495 return Lockup.Status.SETTLED;
496 }
503 if (_streams[streamId].isDepleted) {
504 return amounts.withdrawn;
505 } else if (_streams[streamId].wasCanceled) {
506 return amounts.deposited - amounts.refunded;
507 }
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/abstracts/SablierV2Lockup.sol#L0:0
File: v2-core/src/libraries/SVGElements.sol
256 if (cardType == CardType.PROGRESS) {
257 return "Progress";
258 } else if (cardType == CardType.STATUS) {
259 return "Status";
260 } else if (cardType == CardType.AMOUNT) {
261 return "Amount";
262 } else {
263 return "Duration";
264 }
258 } else if (cardType == CardType.STATUS) {
259 return "Status";
260 } else if (cardType == CardType.AMOUNT) {
261 return "Amount";
262 } else {
263 return "Duration";
264 }
260 } else if (cardType == CardType.AMOUNT) {
261 return "Amount";
262 } else {
263 return "Duration";
264 }
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/libraries/SVGElements.sol#L0:0
NC007 - Events may be emitted out of order due to reentrancy:
Ensure that events follow the best practice of check-effects-interaction, and are emitted before external calls
Click to show 7 findings
File: v2-core/src/SablierV2LockupDynamic.sol
365 emit ISablierV2LockupDynamic.CreateLockupDynamicStream({
366 streamId: streamId,
367 funder: msg.sender,
368 sender: params.sender,
369 recipient: params.recipient,
370 amounts: createAmounts,
371 asset: params.asset,
372 cancelable: params.cancelable,
373 transferable: params.transferable,
374 segments: params.segments,
375 timestamps: LockupDynamic.Timestamps({ start: stream.startTime, end: stream.endTime }),
376 broker: params.broker.account
377 });
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2LockupDynamic.sol#L365:377
File: v2-core/src/SablierV2LockupLinear.sol
285 emit ISablierV2LockupLinear.CreateLockupLinearStream({
286 streamId: streamId,
287 funder: msg.sender,
288 sender: params.sender,
289 recipient: params.recipient,
290 amounts: createAmounts,
291 asset: params.asset,
292 cancelable: params.cancelable,
293 transferable: params.transferable,
294 timestamps: params.timestamps,
295 broker: params.broker.account
296 });
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2LockupLinear.sol#L285:296
File: v2-core/src/SablierV2LockupTranched.sol
269 emit ISablierV2LockupTranched.CreateLockupTranchedStream({
270 streamId: streamId,
271 funder: msg.sender,
272 sender: params.sender,
273 recipient: params.recipient,
274 amounts: createAmounts,
275 asset: params.asset,
276 cancelable: params.cancelable,
277 transferable: params.transferable,
278 tranches: params.tranches,
279 timestamps: LockupTranched.Timestamps({ start: stream.startTime, end: stream.endTime }),
280 broker: params.broker.account
281 });
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2LockupTranched.sol#L269:281
File: v2-core/src/abstracts/SablierV2Lockup.sol
602 emit ISablierV2Lockup.CancelLockupStream(streamId, sender, recipient, asset, senderAmount, recipientAmount);
655 emit ISablierV2Lockup.WithdrawFromLockupStream(streamId, to, asset, amount);
605 emit MetadataUpdate({ _tokenId: streamId });
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/abstracts/SablierV2Lockup.sol#L605:605
File: v2-periphery/src/abstracts/SablierV2MerkleLockup.sol
121 emit Clawback(admin, to, amount);
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-periphery/src/abstracts/SablierV2MerkleLockup.sol#L121:121
NC008 - If-statement can be converted to a ternary:
The code can be made more compact while also increasing readability by converting the following if-statements to ternaries (e.g. foo += (x > y) ? a : b)
Click to show 4 findings
File: v2-core/src/SablierV2LockupDynamic.sol
204 if (_segments[streamId].length > 1) {
205
206 return _calculateStreamedAmountForMultipleSegments(streamId);
207 } else {
208
209 return _calculateStreamedAmountForOneSegment(streamId);
210 }
243 if (index == 0) {
244
245
246 previousTimestamp = stream.startTime;
247 } else {
248
249
250 previousTimestamp = segments[index - 1].timestamp;
251 }
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2LockupDynamic.sol#L0:0
File: v2-core/src/SablierV2NFTDescriptor.sol
324 if (success && returnData.length == 32) {
325 return abi.decode(returnData, (uint8));
326 } else {
327 return 0;
328 }
346 if (bytes(symbol).length > 30) {
347 return "Long Symbol";
348 } else {
349 return symbol;
350 }
361 else if (fractionalAmount < 10) {
362 return string.concat(".0", fractionalAmount.toString());
363 }
364
365 else {
366 return string.concat(".", fractionalAmount.toString());
367 }
391 } else if (status == Lockup.Status.SETTLED) {
392 return "Settled";
393 } else {
394 return "Pending";
395 }
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2NFTDescriptor.sol#L0:0
File: v2-core/src/abstracts/SablierV2Lockup.sol
492 if (_calculateStreamedAmount(streamId) < _streams[streamId].amounts.deposited) {
493 return Lockup.Status.STREAMING;
494 } else {
495 return Lockup.Status.SETTLED;
496 }
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/abstracts/SablierV2Lockup.sol#L0:0
File: v2-core/src/libraries/SVGElements.sol
260 } else if (cardType == CardType.AMOUNT) {
261 return "Amount";
262 } else {
263 return "Duration";
264 }
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/libraries/SVGElements.sol#L0:0
NC009 - Public functions not called by the contract should be declared external instead:
Contracts are allowed to override their parents' functions and change the visibility from external
to public
.
File: v2-core/src/abstracts/SablierV2Lockup.sol
198 function streamedAmountOf(uint256 streamId)
199 public
200 view
201 override
202 notNull(streamId)
203 returns (uint128 streamedAmount)
204 {
205 streamedAmount = _streamedAmountOf(streamId);
206 }
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/abstracts/SablierV2Lockup.sol#L0:0
File: v2-periphery/src/abstracts/SablierV2MerkleLockup.sol
88 function hasClaimed(uint256 index) public view override returns (bool) {
89 return _claimedBitMap.get(index);
90 }
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-periphery/src/abstracts/SablierV2MerkleLockup.sol#L0:0
NC010 - Variables need not be initialized to zero:
The default value for variables is zero, so initializing them to zero is superfluous.
Click to show 7 findings
File: v2-core/src/SablierV2LockupDynamic.sol
344 for (uint256 i = 0; i < segmentCount; ++i) {
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2LockupDynamic.sol#L0:0
File: v2-core/src/SablierV2LockupTranched.sol
248 for (uint256 i = 0; i < trancheCount; ++i) {
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2LockupTranched.sol#L0:0
File: v2-core/src/abstracts/SablierV2Lockup.sol
277 for (uint256 i = 0; i < count; ++i) {
452 for (uint256 i = 0; i < streamIdsCount; ++i) {
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/abstracts/SablierV2Lockup.sol#L0:0
File: v2-core/src/libraries/Helpers.sol
252 for (uint256 index = 0; index < count; ++index) {
315 for (uint256 index = 0; index < count; ++index) {
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/libraries/Helpers.sol#L0:0
File: v2-core/src/libraries/SVGElements.sol
242 for (uint256 i = 0; i < length; ++i) {
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/libraries/SVGElements.sol#L0:0
File: v2-periphery/src/SablierV2MerkleLT.sol
52 for (uint256 i = 0; i < count; ++i) {
135 for (uint256 i = 0; i < trancheCount; ++i) {
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-periphery/v2-core/src/SablierV2MerkleLT.sol#L0:0
File: v2-periphery/src/SablierV2MerkleLockupFactory.sol
56 for (uint256 i = 0; i < tranchesWithPercentages.length; ++i) {
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-periphery/v2-core/src/SablierV2MerkleLockupFactory.sol#L0:0