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.
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)
{
Lockup.Stream memory lockupStream = _streams[streamId];
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)
{
LockupDynamic.Segment[] memory segments = Helpers.calculateSegmentTimestamps(params.segments);
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)
{
streamId = _create(params);
}
function _calculateStreamedAmount(uint256 streamId) internal view override returns (uint128) {
uint40 blockTimestamp = uint40(block.timestamp);
if (_streams[streamId].startTime >= blockTimestamp) {
return 0;
}
uint40 endTime = _streams[streamId].endTime;
if (endTime <= blockTimestamp) {
return _streams[streamId].amounts.deposited;
}
if (_segments[streamId].length > 1) {
return _calculateStreamedAmountForMultipleSegments(streamId);
} else {
return _calculateStreamedAmountForOneSegment(streamId);
}
}
function _create(LockupDynamic.CreateWithTimestamps memory params) internal returns (uint256 streamId) {
Lockup.CreateAmounts memory createAmounts =
Helpers.checkAndCalculateBrokerFee(params.totalAmount, params.broker.fee, MAX_BROKER_FEE);
Helpers.checkCreateLockupDynamic(createAmounts.deposit, params.segments, MAX_SEGMENT_COUNT, params.startTime);
streamId = nextStreamId;
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 {
uint256 segmentCount = params.segments.length;
stream.endTime = params.segments[segmentCount - 1].timestamp;
for (uint256 i = 0; i < segmentCount; ++i) {
_segments[streamId].push(params.segments[i]);
}
nextStreamId = streamId + 1;
}
_mint({ to: params.recipient, tokenId: streamId });
params.asset.safeTransferFrom({ from: msg.sender, to: address(this), value: createAmounts.deposit });
if (createAmounts.brokerFee > 0) {
params.asset.safeTransferFrom({ from: msg.sender, to: params.broker.account, value: createAmounts.brokerFee });
}
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)
{
Lockup.Stream memory lockupStream = _streams[streamId];
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)
{
LockupLinear.Timestamps memory timestamps;
timestamps.start = uint40(block.timestamp);
unchecked {
if (params.durations.cliff > 0) {
timestamps.cliff = timestamps.start + params.durations.cliff;
}
timestamps.end = timestamps.start + params.durations.total;
}
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)
{
streamId = _create(params);
}
function _calculateStreamedAmount(uint256 streamId) internal view override returns (uint128) {
uint256 cliffTime = uint256(_cliffs[streamId]);
uint256 blockTimestamp = block.timestamp;
if (cliffTime > blockTimestamp) {
return 0;
}
uint256 endTime = uint256(_streams[streamId].endTime);
if (blockTimestamp >= endTime) {
return _streams[streamId].amounts.deposited;
}
unchecked {
uint256 startTime = uint256(_streams[streamId].startTime);
UD60x18 elapsedTime = ud(blockTimestamp - startTime);
UD60x18 totalDuration = ud(endTime - startTime);
UD60x18 elapsedTimePercentage = elapsedTime.div(totalDuration);
UD60x18 depositedAmount = ud(_streams[streamId].amounts.deposited);
UD60x18 streamedAmount = elapsedTimePercentage.mul(depositedAmount);
if (streamedAmount.gt(depositedAmount)) {
return _streams[streamId].amounts.withdrawn;
}
return uint128(streamedAmount.intoUint256());
}
}
function _create(LockupLinear.CreateWithTimestamps memory params) internal returns (uint256 streamId) {
Lockup.CreateAmounts memory createAmounts =
Helpers.checkAndCalculateBrokerFee(params.totalAmount, params.broker.fee, MAX_BROKER_FEE);
Helpers.checkCreateLockupLinear(createAmounts.deposit, params.timestamps);
streamId = nextStreamId;
_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
});
if (params.timestamps.cliff > 0) {
_cliffs[streamId] = params.timestamps.cliff;
}
unchecked {
nextStreamId = streamId + 1;
}
_mint({ to: params.recipient, tokenId: streamId });
params.asset.safeTransferFrom({ from: msg.sender, to: address(this), value: createAmounts.deposit });
if (createAmounts.brokerFee > 0) {
params.asset.safeTransferFrom({ from: msg.sender, to: params.broker.account, value: createAmounts.brokerFee });
}
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)
{
Lockup.Stream memory lockupStream = _streams[streamId];
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)
{
LockupTranched.Tranche[] memory tranches = Helpers.calculateTrancheTimestamps(params.tranches);
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)
{
streamId = _create(params);
}
function _calculateStreamedAmount(uint256 streamId) internal view override returns (uint128) {
uint40 blockTimestamp = uint40(block.timestamp);
LockupTranched.Tranche[] memory tranches = _tranches[streamId];
if (tranches[0].timestamp > blockTimestamp) {
return 0;
}
if (_streams[streamId].endTime <= blockTimestamp) {
return _streams[streamId].amounts.deposited;
}
uint128 streamedAmount = tranches[0].amount;
for (uint256 i = 1; i < tranches.length; ++i) {
if (tranches[i].timestamp > blockTimestamp) {
break;
}
unchecked {
streamedAmount += tranches[i].amount;
}
}
return streamedAmount;
}
function _create(LockupTranched.CreateWithTimestamps memory params) internal returns (uint256 streamId) {
Lockup.CreateAmounts memory createAmounts =
Helpers.checkAndCalculateBrokerFee(params.totalAmount, params.broker.fee, MAX_BROKER_FEE);
Helpers.checkCreateLockupTranched(createAmounts.deposit, params.tranches, MAX_TRANCHE_COUNT, params.startTime);
streamId = nextStreamId;
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 {
uint256 trancheCount = params.tranches.length;
stream.endTime = params.tranches[trancheCount - 1].timestamp;
for (uint256 i = 0; i < trancheCount; ++i) {
_tranches[streamId].push(params.tranches[i]);
}
nextStreamId = streamId + 1;
}
_mint({ to: params.recipient, tokenId: streamId });
params.asset.safeTransferFrom({ from: msg.sender, to: address(this), value: createAmounts.deposit });
if (createAmounts.brokerFee > 0) {
params.asset.safeTransferFrom({ from: msg.sender, to: params.broker.account, value: createAmounts.brokerFee });
}
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) {
_requireOwned({ tokenId: streamId });
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;
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);
vars.status = stringifyStatus(vars.sablier.statusOf(streamId));
vars.streamedPercentage = calculateStreamedPercentage({
streamedAmount: vars.sablier.streamedAmountOf(streamId),
depositedAmount: vars.depositedAmount
});
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
})
);
(vars.success, vars.returnData) =
address(vars.sablier).staticcall(abi.encodeCall(ISablierV2Lockup.isTransferable, (streamId)));
vars.isTransferable = vars.success ? abi.decode(vars.returnData, (bool)) : true;
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)),
'"}'
);
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);
if (cardType == CardType.PROGRESS) {
if (circle.equal("")) {
width = 144;
} else {
width = 208;
}
}
else {
uint256 captionWidth = calculatePixelWidth({ text: caption, largeFont: false });
uint256 contentWidth = calculatePixelWidth({ text: content, largeFont: true });
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)
{
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(index, recipient, amount))));
_checkClaim(index, leaf, merkleProof);
_claimedBitMap.set(index);
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) })
})
);
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)
{
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(index, recipient, amount))));
_checkClaim(index, leaf, merkleProof);
LockupTranched.TrancheWithDuration[] memory tranches = _calculateTranches(amount);
_claimedBitMap.set(index);
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 })
})
);
emit Claim(index, recipient, amount, streamId);
}
https://github.com/Cyfrin/2024-05-Sablier/tree/main/v2-periphery/src/SablierV2MerkleLT.sol#L74:113