Sablier

Sablier
DeFiFoundry
53,440 USDC
View results
Submission Details
Severity: low
Invalid

Non Critical issues (38-40) of 73

NC038 - Zero as a function argument should have a descriptive meaning:

Consider using descriptive constants or an enum instead of passing zero directly on function calls, as that might be error-prone, to fully describe the caller's intention.

Click to show 5 findings
File: v2-core/src/SablierV2LockupLinear.sol
250 amounts: Lockup.Amounts({ deposited: createAmounts.deposit, refunded: 0, withdrawn: 0 }),

https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2LockupLinear.sol#L0:0

File: v2-core/src/abstracts/SablierV2Lockup.sol
36 UD60x18 public constant override MAX_BROKER_FEE = UD60x18.wrap(0.1e18);
36 UD60x18 public constant override MAX_BROKER_FEE = UD60x18.wrap(0.1e18);
328 emit BatchMetadataUpdate({ _fromTokenId: 1, _toTokenId: nextStreamId - 1 });

https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/abstracts/SablierV2Lockup.sol#L0:0

File: v2-core/src/libraries/Helpers.sol
91 return Lockup.CreateAmounts(0, 0);

https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/libraries/Helpers.sol#L0:0

File: v2-periphery/src/SablierV2MerkleLL.sol
89 broker: Broker({ account: address(0), fee: ud(0) })

https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-periphery/src/SablierV2MerkleLL.sol#L0:0

File: v2-periphery/src/abstracts/SablierV2MerkleLockup.sol
62 revert Errors.SablierV2MerkleLockup_CampaignNameTooLong({
63 nameLength: bytes(params.name).length,
64 maxLength: 32
65 });

https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-periphery/src/abstracts/SablierV2MerkleLockup.sol#L0:0

NC039 - Function names should differ to make the code more readable:

In Solidity, while function overriding allows for functions with the same name to coexist, it is advisable to avoid this practice to enhance code readability and maintainability. Having multiple functions with the same name, even with different parameters or in inherited contracts, can cause confusion and increase the likelihood of errors during development, testing, and debugging. Using distinct and descriptive function names not only clarifies the purpose and behavior of each function, but also helps prevent unintended function calls or incorrect overriding. By adopting a clear and consistent naming convention, developers can create more comprehensible and maintainable smart contracts.

Click to show 25 findings
File: v2-core/src/SablierV2LockupDynamic.sol
function getStream(uint256 streamId)
external
view
override
notNull(streamId)
returns (LockupDynamic.StreamLD memory stream)
{
// Retrieve the Lockup stream from storage.
Lockup.Stream memory lockupStream = _streams[streamId];
// Settled streams cannot be canceled.
if (_statusOf(streamId) == Lockup.Status.SETTLED) {
lockupStream.isCancelable = false;
}
stream = LockupDynamic.StreamLD({
amounts: lockupStream.amounts,
asset: lockupStream.asset,
endTime: lockupStream.endTime,
isCancelable: lockupStream.isCancelable,
isDepleted: lockupStream.isDepleted,
isStream: lockupStream.isStream,
isTransferable: lockupStream.isTransferable,
recipient: _ownerOf(streamId),
segments: _segments[streamId],
sender: lockupStream.sender,
startTime: lockupStream.startTime,
wasCanceled: lockupStream.wasCanceled
});
}
function getTimestamps(uint256 streamId)
external
view
override
notNull(streamId)
returns (LockupDynamic.Timestamps memory timestamps)
{
timestamps = LockupDynamic.Timestamps({ start: _streams[streamId].startTime, end: _streams[streamId].endTime });
}
function createWithDurations(LockupDynamic.CreateWithDurations calldata params)
external
override
noDelegateCall
returns (uint256 streamId)
{
// Generate the canonical segments.
LockupDynamic.Segment[] memory segments = Helpers.calculateSegmentTimestamps(params.segments);
// Checks, Effects and Interactions: create the stream.
streamId = _create(
LockupDynamic.CreateWithTimestamps({
sender: params.sender,
recipient: params.recipient,
totalAmount: params.totalAmount,
asset: params.asset,
cancelable: params.cancelable,
transferable: params.transferable,
startTime: uint40(block.timestamp),
segments: segments,
broker: params.broker
})
);
}
function createWithTimestamps(LockupDynamic.CreateWithTimestamps calldata params)
external
override
noDelegateCall
returns (uint256 streamId)
{
// Checks, Effects and Interactions: create the stream.
streamId = _create(params);
}
function _calculateStreamedAmount(uint256 streamId) internal view override returns (uint128) {
// If the start time is in the future, return zero.
uint40 blockTimestamp = uint40(block.timestamp);
if (_streams[streamId].startTime >= blockTimestamp) {
return 0;
}
// If the end time is not in the future, return the deposited amount.
uint40 endTime = _streams[streamId].endTime;
if (endTime <= blockTimestamp) {
return _streams[streamId].amounts.deposited;
}
if (_segments[streamId].length > 1) {
// If there is more than one segment, it may be required to iterate over all of them.
return _calculateStreamedAmountForMultipleSegments(streamId);
} else {
// Otherwise, there is only one segment, and the calculation is simpler.
return _calculateStreamedAmountForOneSegment(streamId);
}
}
function _create(LockupDynamic.CreateWithTimestamps memory params) internal returns (uint256 streamId) {
// Check: verify the broker fee and calculate the amounts.
Lockup.CreateAmounts memory createAmounts =
Helpers.checkAndCalculateBrokerFee(params.totalAmount, params.broker.fee, MAX_BROKER_FEE);
// Check: validate the user-provided parameters.
Helpers.checkCreateLockupDynamic(createAmounts.deposit, params.segments, MAX_SEGMENT_COUNT, params.startTime);
// Load the stream ID in a variable.
streamId = nextStreamId;
// Effect: create the stream.
Lockup.Stream storage stream = _streams[streamId];
stream.amounts.deposited = createAmounts.deposit;
stream.asset = params.asset;
stream.isCancelable = params.cancelable;
stream.isStream = true;
stream.isTransferable = params.transferable;
stream.sender = params.sender;
stream.startTime = params.startTime;
unchecked {
// The segment count cannot be zero at this point.
uint256 segmentCount = params.segments.length;
stream.endTime = params.segments[segmentCount - 1].timestamp;
// Effect: store the segments. Since Solidity lacks a syntax for copying arrays of structs directly from
// memory to storage, a manual approach is necessary. See https://github.com/ethereum/solidity/issues/12783.
for (uint256 i = 0; i < segmentCount; ++i) {
_segments[streamId].push(params.segments[i]);
}
// Effect: bump the next stream ID.
// Using unchecked arithmetic because these calculations cannot realistically overflow, ever.
nextStreamId = streamId + 1;
}
// Effect: mint the NFT to the recipient.
_mint({ to: params.recipient, tokenId: streamId });
// Interaction: transfer the deposit amount.
params.asset.safeTransferFrom({ from: msg.sender, to: address(this), value: createAmounts.deposit });
// Interaction: pay the broker fee, if not zero.
if (createAmounts.brokerFee > 0) {
params.asset.safeTransferFrom({ from: msg.sender, to: params.broker.account, value: createAmounts.brokerFee });
}
// Log the newly created stream.
emit ISablierV2LockupDynamic.CreateLockupDynamicStream({
streamId: streamId,
funder: msg.sender,
sender: params.sender,
recipient: params.recipient,
amounts: createAmounts,
asset: params.asset,
cancelable: params.cancelable,
transferable: params.transferable,
segments: params.segments,
timestamps: LockupDynamic.Timestamps({ start: stream.startTime, end: stream.endTime }),
broker: params.broker.account
});
}

https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2LockupDynamic.sol#L316:378

File: v2-core/src/SablierV2LockupLinear.sol
function getStream(uint256 streamId)
external
view
override
notNull(streamId)
returns (LockupLinear.StreamLL memory stream)
{
// Retrieve the Lockup stream from storage.
Lockup.Stream memory lockupStream = _streams[streamId];
// Settled streams cannot be canceled.
if (_statusOf(streamId) == Lockup.Status.SETTLED) {
lockupStream.isCancelable = false;
}
stream = LockupLinear.StreamLL({
amounts: lockupStream.amounts,
asset: lockupStream.asset,
cliffTime: _cliffs[streamId],
endTime: lockupStream.endTime,
isCancelable: lockupStream.isCancelable,
isTransferable: lockupStream.isTransferable,
isDepleted: lockupStream.isDepleted,
isStream: lockupStream.isStream,
recipient: _ownerOf(streamId),
sender: lockupStream.sender,
startTime: lockupStream.startTime,
wasCanceled: lockupStream.wasCanceled
});
}
function getTimestamps(uint256 streamId)
external
view
override
notNull(streamId)
returns (LockupLinear.Timestamps memory timestamps)
{
timestamps = LockupLinear.Timestamps({
start: _streams[streamId].startTime,
cliff: _cliffs[streamId],
end: _streams[streamId].endTime
});
}
function createWithDurations(LockupLinear.CreateWithDurations calldata params)
external
override
noDelegateCall
returns (uint256 streamId)
{
// Set the current block timestamp as the stream's start time.
LockupLinear.Timestamps memory timestamps;
timestamps.start = uint40(block.timestamp);
// Calculate the cliff time and the end time. It is safe to use unchecked arithmetic because {_create} will
// nonetheless check that the end time is greater than the cliff time, and also that the cliff time, if set,
// is greater than or equal to the start time.
unchecked {
if (params.durations.cliff > 0) {
timestamps.cliff = timestamps.start + params.durations.cliff;
}
timestamps.end = timestamps.start + params.durations.total;
}
// Checks, Effects and Interactions: create the stream.
streamId = _create(
LockupLinear.CreateWithTimestamps({
sender: params.sender,
recipient: params.recipient,
totalAmount: params.totalAmount,
asset: params.asset,
cancelable: params.cancelable,
transferable: params.transferable,
timestamps: timestamps,
broker: params.broker
})
);
}
function createWithTimestamps(LockupLinear.CreateWithTimestamps calldata params)
external
override
noDelegateCall
returns (uint256 streamId)
{
// Checks, Effects and Interactions: create the stream.
streamId = _create(params);
}
function _calculateStreamedAmount(uint256 streamId) internal view override returns (uint128) {
// If the cliff time is in the future, return zero.
uint256 cliffTime = uint256(_cliffs[streamId]);
uint256 blockTimestamp = block.timestamp;
if (cliffTime > blockTimestamp) {
return 0;
}
// If the end time is not in the future, return the deposited amount.
uint256 endTime = uint256(_streams[streamId].endTime);
if (blockTimestamp >= endTime) {
return _streams[streamId].amounts.deposited;
}
// In all other cases, calculate the amount streamed so far. Normalization to 18 decimals is not needed
// because there is no mix of amounts with different decimals.
unchecked {
// Calculate how much time has passed since the stream started, and the stream's total duration.
uint256 startTime = uint256(_streams[streamId].startTime);
UD60x18 elapsedTime = ud(blockTimestamp - startTime);
UD60x18 totalDuration = ud(endTime - startTime);
// Divide the elapsed time by the stream's total duration.
UD60x18 elapsedTimePercentage = elapsedTime.div(totalDuration);
// Cast the deposited amount to UD60x18.
UD60x18 depositedAmount = ud(_streams[streamId].amounts.deposited);
// Calculate the streamed amount by multiplying the elapsed time percentage by the deposited amount.
UD60x18 streamedAmount = elapsedTimePercentage.mul(depositedAmount);
// Although the streamed amount should never exceed the deposited amount, this condition is checked
// without asserting to avoid locking funds in case of a bug. If this situation occurs, the withdrawn
// amount is considered to be the streamed amount, and the stream is effectively frozen.
if (streamedAmount.gt(depositedAmount)) {
return _streams[streamId].amounts.withdrawn;
}
// Cast the streamed amount to uint128. This is safe due to the check above.
return uint128(streamedAmount.intoUint256());
}
}
function _create(LockupLinear.CreateWithTimestamps memory params) internal returns (uint256 streamId) {
// Check: verify the broker fee and calculate the amounts.
Lockup.CreateAmounts memory createAmounts =
Helpers.checkAndCalculateBrokerFee(params.totalAmount, params.broker.fee, MAX_BROKER_FEE);
// Check: validate the user-provided parameters.
Helpers.checkCreateLockupLinear(createAmounts.deposit, params.timestamps);
// Load the stream ID.
streamId = nextStreamId;
// Effect: create the stream.
_streams[streamId] = Lockup.Stream({
amounts: Lockup.Amounts({ deposited: createAmounts.deposit, refunded: 0, withdrawn: 0 }),
asset: params.asset,
endTime: params.timestamps.end,
isCancelable: params.cancelable,
isDepleted: false,
isStream: true,
isTransferable: params.transferable,
sender: params.sender,
startTime: params.timestamps.start,
wasCanceled: false
});
// Effect: set the cliff time if it is greater than zero.
if (params.timestamps.cliff > 0) {
_cliffs[streamId] = params.timestamps.cliff;
}
// Effect: bump the next stream ID.
// Using unchecked arithmetic because these calculations cannot realistically overflow, ever.
unchecked {
nextStreamId = streamId + 1;
}
// Effect: mint the NFT to the recipient.
_mint({ to: params.recipient, tokenId: streamId });
// Interaction: transfer the deposit amount.
params.asset.safeTransferFrom({ from: msg.sender, to: address(this), value: createAmounts.deposit });
// Interaction: pay the broker fee, if not zero.
if (createAmounts.brokerFee > 0) {
params.asset.safeTransferFrom({ from: msg.sender, to: params.broker.account, value: createAmounts.brokerFee });
}
// Log the newly created stream.
emit ISablierV2LockupLinear.CreateLockupLinearStream({
streamId: streamId,
funder: msg.sender,
sender: params.sender,
recipient: params.recipient,
amounts: createAmounts,
asset: params.asset,
cancelable: params.cancelable,
transferable: params.transferable,
timestamps: params.timestamps,
broker: params.broker.account
});
}

https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2LockupLinear.sol#L237:297

File: v2-core/src/SablierV2LockupTranched.sol
function getStream(uint256 streamId)
external
view
override
notNull(streamId)
returns (LockupTranched.StreamLT memory stream)
{
// Retrieve the Lockup stream from storage.
Lockup.Stream memory lockupStream = _streams[streamId];
// Settled streams cannot be canceled.
if (_statusOf(streamId) == Lockup.Status.SETTLED) {
lockupStream.isCancelable = false;
}
stream = LockupTranched.StreamLT({
amounts: lockupStream.amounts,
asset: lockupStream.asset,
endTime: lockupStream.endTime,
isCancelable: lockupStream.isCancelable,
isDepleted: lockupStream.isDepleted,
isStream: lockupStream.isStream,
isTransferable: lockupStream.isTransferable,
recipient: _ownerOf(streamId),
sender: lockupStream.sender,
startTime: lockupStream.startTime,
tranches: _tranches[streamId],
wasCanceled: lockupStream.wasCanceled
});
}
function getTimestamps(uint256 streamId)
external
view
override
notNull(streamId)
returns (LockupTranched.Timestamps memory timestamps)
{
timestamps = LockupTranched.Timestamps({ start: _streams[streamId].startTime, end: _streams[streamId].endTime });
}
function createWithDurations(LockupTranched.CreateWithDurations calldata params)
external
override
noDelegateCall
returns (uint256 streamId)
{
// Generate the canonical tranches.
LockupTranched.Tranche[] memory tranches = Helpers.calculateTrancheTimestamps(params.tranches);
// Checks, Effects and Interactions: create the stream.
streamId = _create(
LockupTranched.CreateWithTimestamps({
sender: params.sender,
recipient: params.recipient,
totalAmount: params.totalAmount,
asset: params.asset,
cancelable: params.cancelable,
transferable: params.transferable,
startTime: uint40(block.timestamp),
tranches: tranches,
broker: params.broker
})
);
}
function createWithTimestamps(LockupTranched.CreateWithTimestamps calldata params)
external
override
noDelegateCall
returns (uint256 streamId)
{
// Checks, Effects and Interactions: create the stream.
streamId = _create(params);
}
function _calculateStreamedAmount(uint256 streamId) internal view override returns (uint128) {
uint40 blockTimestamp = uint40(block.timestamp);
LockupTranched.Tranche[] memory tranches = _tranches[streamId];
// If the first tranche's timestamp is in the future, return zero.
if (tranches[0].timestamp > blockTimestamp) {
return 0;
}
// If the end time is not in the future, return the deposited amount.
if (_streams[streamId].endTime <= blockTimestamp) {
return _streams[streamId].amounts.deposited;
}
// Sum the amounts in all tranches that have already been vested.
// Using unchecked arithmetic is safe because the sum of the tranche amounts is equal to the total amount
// at this point.
uint128 streamedAmount = tranches[0].amount;
for (uint256 i = 1; i < tranches.length; ++i) {
// The loop breaks at the first tranche with a timestamp in the future. A tranche is considered vested if
// its timestamp is less than or equal to the block timestamp.
if (tranches[i].timestamp > blockTimestamp) {
break;
}
unchecked {
streamedAmount += tranches[i].amount;
}
}
return streamedAmount;
}
function _create(LockupTranched.CreateWithTimestamps memory params) internal returns (uint256 streamId) {
// Check: verify the broker fee and calculate the amounts.
Lockup.CreateAmounts memory createAmounts =
Helpers.checkAndCalculateBrokerFee(params.totalAmount, params.broker.fee, MAX_BROKER_FEE);
// Check: validate the user-provided parameters.
Helpers.checkCreateLockupTranched(createAmounts.deposit, params.tranches, MAX_TRANCHE_COUNT, params.startTime);
// Load the stream ID in a variable.
streamId = nextStreamId;
// Effect: create the stream.
Lockup.Stream storage stream = _streams[streamId];
stream.amounts.deposited = createAmounts.deposit;
stream.asset = params.asset;
stream.isCancelable = params.cancelable;
stream.isStream = true;
stream.isTransferable = params.transferable;
stream.sender = params.sender;
stream.startTime = params.startTime;
unchecked {
// The tranche count cannot be zero at this point.
uint256 trancheCount = params.tranches.length;
stream.endTime = params.tranches[trancheCount - 1].timestamp;
// Effect: store the tranches. Since Solidity lacks a syntax for copying arrays of structs directly from
// memory to storage, a manual approach is necessary. See https://github.com/ethereum/solidity/issues/12783.
for (uint256 i = 0; i < trancheCount; ++i) {
_tranches[streamId].push(params.tranches[i]);
}
// Effect: bump the next stream ID.
// Using unchecked arithmetic because these calculations cannot realistically overflow, ever.
nextStreamId = streamId + 1;
}
// Effect: mint the NFT to the recipient.
_mint({ to: params.recipient, tokenId: streamId });
// Interaction: transfer the deposit amount.
params.asset.safeTransferFrom({ from: msg.sender, to: address(this), value: createAmounts.deposit });
// Interaction: pay the broker fee, if not zero.
if (createAmounts.brokerFee > 0) {
params.asset.safeTransferFrom({ from: msg.sender, to: params.broker.account, value: createAmounts.brokerFee });
}
// Log the newly created stream.
emit ISablierV2LockupTranched.CreateLockupTranchedStream({
streamId: streamId,
funder: msg.sender,
sender: params.sender,
recipient: params.recipient,
amounts: createAmounts,
asset: params.asset,
cancelable: params.cancelable,
transferable: params.transferable,
tranches: params.tranches,
timestamps: LockupTranched.Timestamps({ start: stream.startTime, end: stream.endTime }),
broker: params.broker.account
});
}

https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2LockupTranched.sol#L220:282

File: v2-core/src/abstracts/SablierV2Lockup.sol
function _calculateStreamedAmount(uint256 streamId) internal view virtual returns (uint128);
function tokenURI(uint256 streamId) public view override(IERC721Metadata, ERC721) returns (string memory uri) {
// Check: the stream NFT exists.
_requireOwned({ tokenId: streamId });
// Generate the URI describing the stream NFT.
uri = nftDescriptor.tokenURI({ sablier: this, streamId: streamId });
}

https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/abstracts/SablierV2Lockup.sol#L209:215

File: v2-core/src/SablierV2NFTDescriptor.sol
function tokenURI(IERC721Metadata sablier, uint256 streamId) external view override returns (string memory uri) {
TokenURIVars memory vars;
// Load the contracts.
vars.sablier = ISablierV2Lockup(address(sablier));
vars.sablierModel = mapSymbol(sablier);
vars.sablierStringified = address(sablier).toHexString();
vars.asset = address(vars.sablier.getAsset(streamId));
vars.assetSymbol = safeAssetSymbol(vars.asset);
vars.depositedAmount = vars.sablier.getDepositedAmount(streamId);
// Load the stream's data.
vars.status = stringifyStatus(vars.sablier.statusOf(streamId));
vars.streamedPercentage = calculateStreamedPercentage({
streamedAmount: vars.sablier.streamedAmountOf(streamId),
depositedAmount: vars.depositedAmount
});
// Generate the SVG.
vars.svg = NFTSVG.generateSVG(
NFTSVG.SVGParams({
accentColor: generateAccentColor(address(sablier), streamId),
amount: abbreviateAmount({ amount: vars.depositedAmount, decimals: safeAssetDecimals(vars.asset) }),
assetAddress: vars.asset.toHexString(),
assetSymbol: vars.assetSymbol,
duration: calculateDurationInDays({
startTime: vars.sablier.getStartTime(streamId),
endTime: vars.sablier.getEndTime(streamId)
}),
sablierAddress: vars.sablierStringified,
progress: stringifyPercentage(vars.streamedPercentage),
progressNumerical: vars.streamedPercentage,
status: vars.status,
sablierModel: vars.sablierModel
})
);
// Performs a low-level call to handle older deployments that miss the `isTransferable` function.
(vars.success, vars.returnData) =
address(vars.sablier).staticcall(abi.encodeCall(ISablierV2Lockup.isTransferable, (streamId)));
// When the call has failed, the stream NFT is assumed to be transferable.
vars.isTransferable = vars.success ? abi.decode(vars.returnData, (bool)) : true;
// Generate the JSON metadata.
vars.json = string.concat(
'{"attributes":',
generateAttributes({
assetSymbol: vars.assetSymbol,
sender: vars.sablier.getSender(streamId).toHexString(),
status: vars.status
}),
',"description":"',
generateDescription({
sablierModel: vars.sablierModel,
assetSymbol: vars.assetSymbol,
sablierStringified: vars.sablierStringified,
assetAddress: vars.asset.toHexString(),
streamId: streamId.toString(),
isTransferable: vars.isTransferable
}),
'","external_url":"https://sablier.com","name":"',
generateName({ sablierModel: vars.sablierModel, streamId: streamId.toString() }),
'","image":"data:image/svg+xml;base64,',
Base64.encode(bytes(vars.svg)),
'"}'
);
// Encode the JSON metadata in Base64.
uri = string.concat("data:application/json;base64,", Base64.encode(bytes(vars.json)));
}

https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/SablierV2NFTDescriptor.sol#L47:117

File: v2-core/src/libraries/SVGElements.sol
function card(CardType cardType, string memory content) internal pure returns (uint256, string memory) {
return card({ cardType: cardType, content: content, circle: "" });
}
function card(
CardType cardType,
string memory content,
string memory circle
)
internal
pure
returns (uint256 width, string memory card_)
{
string memory caption = stringifyCardType(cardType);
// The progress card can have a fixed width because the content is never longer than the caption. The former
// has 6 characters (at most, e.g. "42.09%"), whereas the latter has 8 characters ("Progress").
if (cardType == CardType.PROGRESS) {
// The progress can be 0%, in which case the circle is not rendered.
if (circle.equal("")) {
width = 144; // 2 * 20 (margins) + 8 * 13 (charWidth)
} else {
width = 208; // 3 * 20 (margins) + 8 * 13 (charWidth) + 44 (diameter)
}
}
// For the other cards, the width is calculated dynamically based on the number of characters.
else {
uint256 captionWidth = calculatePixelWidth({ text: caption, largeFont: false });
uint256 contentWidth = calculatePixelWidth({ text: content, largeFont: true });
// Use the greater of the two widths, and add the left and the right margin.
unchecked {
width = Math.max(captionWidth, contentWidth) + 40;
}
}
card_ = string.concat(
'<g id="',
caption,
'" fill="#fff">',
'<rect width="',
width.toString(),
'" height="100" fill-opacity=".03" rx="15" ry="15" stroke="#fff" stroke-opacity=".1" stroke-width="4"/>',
'<text x="20" y="34" font-family="\'Courier New\',Arial,monospace" font-size="22px">',
caption,
"</text>",
'<text x="20" y="72" font-family="\'Courier New\',Arial,monospace" font-size="26px">',
content,
"</text>",
circle,
"</g>"
);
}

https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-core/src/libraries/SVGElements.sol#L78:126

File: v2-periphery/src/SablierV2MerkleLL.sol
function claim(
uint256 index,
address recipient,
uint128 amount,
bytes32[] calldata merkleProof
)
external
override
returns (uint256 streamId)
{
// 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, amount))));
// Check: validate the function.
_checkClaim(index, leaf, merkleProof);
// Effect: mark the index as claimed.
_claimedBitMap.set(index);
// Interaction: create the stream via {SablierV2LockupLinear}.
streamId = LOCKUP_LINEAR.createWithDurations(
LockupLinear.CreateWithDurations({
sender: admin,
recipient: recipient,
totalAmount: amount,
asset: ASSET,
cancelable: CANCELABLE,
transferable: TRANSFERABLE,
durations: streamDurations,
broker: Broker({ account: address(0), fee: ud(0) })
})
);
// Log the claim.
emit Claim(index, recipient, amount, streamId);
}

https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-periphery/src/SablierV2MerkleLL.sol#L59:95

File: v2-periphery/src/SablierV2MerkleLT.sol
function claim(
uint256 index,
address recipient,
uint128 amount,
bytes32[] calldata merkleProof
)
external
override
returns (uint256 streamId)
{
// 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, amount))));
// Check: validate the function.
_checkClaim(index, leaf, merkleProof);
// Calculate the tranches based on the unlock percentages.
LockupTranched.TrancheWithDuration[] memory tranches = _calculateTranches(amount);
// Effect: mark the index as claimed.
_claimedBitMap.set(index);
// Interaction: create the stream via {SablierV2LockupTranched}.
streamId = LOCKUP_TRANCHED.createWithDurations(
LockupTranched.CreateWithDurations({
sender: admin,
recipient: recipient,
totalAmount: amount,
asset: ASSET,
cancelable: CANCELABLE,
transferable: TRANSFERABLE,
tranches: tranches,
broker: Broker({ account: address(0), fee: ZERO })
})
);
// Log the claim.
emit Claim(index, recipient, amount, streamId);
}

https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-periphery/src/SablierV2MerkleLT.sol#L74:113

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Info/Gas/Invalid as per Docs

https://docs.codehawks.com/hawks-auditors/how-to-determine-a-finding-validity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.