You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository has been archived by the owner on Oct 1, 2023. It is now read-only.
sherlock-admin opened this issue
Mar 28, 2023
· 0 comments
Labels
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelHighA valid High severity issueRewardA payout will be made for this issue
Any depositor can avoid paying deposit fee by substituting deposit(id, ...) call with an atomically coupled deposit(0, ...) -> mintDepositInQueue(id, 1) call.
Core reason is that deposit fee is fully substituted with relayer fee sent back to a caller, while depositQueue is processed in LIFO order.
Vulnerability Detail
Bob the depositor wants to deposit just before epoch start to minimize the level of risk by maximizing the information as of the deposit time.
To avoid paying nearly full deposit fee instead of deposit(id, _assets, Bob) he runs deposit(0, _assets, Bob) -> mintDepositInQueue(id, 1) atomically.
As relayer fee is sent back to msg.sender, Bob is effectively avoided paying deposit fee, having only somewhat increased the overall gas costs (even not so substantially, as deposit(0, ...) performs only one queue SSTORE instead of fee calculation and token transfer of direct deposit), which is negligible whenever Bob's deposit is big enough.
Impact
As such a replacement can be done for any deposit, protocol can lose up to all deposit fees.
Code Snippet
_deposit() do not apply the fee if the call is for zero epoch and depositQueue is being used:
function _deposit(
uint256_id,
uint256_assets,
address_receiver
) internal {
// mint logic, either in queue or direct depositif (_id !=0) {
uint256 assetsToDeposit = _assets;
if (depositFee >0) {
(uint256maxX, , uint256minX) =getEpochConfig(_id);
// deposit fee is calcualted linearly between time of epoch creation and epoch starting (deposit window)// this is because late depositors have an informational advantageuint256 fee =_calculateFeePercent(int256(minX), int256(maxX));
// min minRequiredDeposit modifier ensures that _assets has high enough value to not devide by 0// 0.5% = multiply by 10000 then divide by 50uint256 feeAmount = _assets.mulDivDown(fee, 10000);
assetsToDeposit = _assets - feeAmount;
_asset().safeTransfer(treasury, feeAmount);
}
_mintShares(_receiver, _id, assetsToDeposit);
emitDeposit(msg.sender, _receiver, _id, _assets);
} else {
@> depositQueue.push(
QueueItem({assets: _assets, receiver: _receiver, epochId: _id})
);
emitDepositInQueue(msg.sender, _receiver, _id, _assets);
}
}
Then mintDepositInQueue() proceeds with the queue elements backwards, in LIFO order:
function mintDepositInQueue(uint256_epochId, uint256_operations)
externalepochIdExists(_epochId)
epochHasNotStarted(_epochId)
nonReentrant
{
// make sure there is already a new epoch set// epoch has not started
QueueItem[] memory queue = depositQueue;
uint256 length = depositQueue.length;
// dont allow minting if epochId is 0if (_epochId ==0) revertInvalidEpochId();
if (length ==0) revertOverflowQueue();
// relayers can always input a very big number to mint all deposit queues, without the need to read depostQueue length firstif (_operations > length) _operations = length;
// queue is executed from the tail to the head// get last index of queue
@>uint256 i = length -1;
while ((length - _operations) <= i) {
// this loop impelements FILO (first in last out) stack to reduce gas cost and improve code readability// changing it to FIFO (first in first out) would require more code changes and would be more expensive_mintShares(
queue[i].receiver,
_epochId,
queue[i].assets - relayerFee
);
emitDeposit(
msg.sender,
queue[i].receiver,
_epochId,
queue[i].assets - relayerFee
);
depositQueue.pop();
if (i ==0) break;
unchecked {
i--;
}
}
emitRelayerMinted(_epochId, _operations);
asset.safeTransfer(msg.sender, _operations * relayerFee);
}
Tool used
Manual Review
Recommendation
As an example, consider applying deposit fee on placing deposit queue request as well, i.e. do this part for all epoch ids:
if (depositFee >0) {
(uint256maxX, , uint256minX) =getEpochConfig(_id);
// deposit fee is calcualted linearly between time of epoch creation and epoch starting (deposit window)// this is because late depositors have an informational advantageuint256 fee =_calculateFeePercent(int256(minX), int256(maxX));
// min minRequiredDeposit modifier ensures that _assets has high enough value to not devide by 0// 0.5% = multiply by 10000 then divide by 50uint256 feeAmount = _assets.mulDivDown(fee, 10000);
assetsToDeposit = _assets - feeAmount;
_asset().safeTransfer(treasury, feeAmount);
}
It looks it needs to be the full fee as otherwise the demonstrated approach can be able to lower it.
As there is no way to remove deposit from the queue and user will be placing deposits there in advance, the fee for them will not be high anyway.
Sign up for freeto subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Labels
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelHighA valid High severity issueRewardA payout will be made for this issue
iglyx
high
Deposit fee can always be avoided
Summary
Any depositor can avoid paying deposit fee by substituting
deposit(id, ...)
call with an atomically coupleddeposit(0, ...) -> mintDepositInQueue(id, 1)
call.Core reason is that deposit fee is fully substituted with relayer fee sent back to a caller, while
depositQueue
is processed inLIFO
order.Vulnerability Detail
Bob the depositor wants to deposit just before epoch start to minimize the level of risk by maximizing the information as of the deposit time.
To avoid paying nearly full deposit fee instead of
deposit(id, _assets, Bob)
he runsdeposit(0, _assets, Bob) -> mintDepositInQueue(id, 1)
atomically.As relayer fee is sent back to
msg.sender
, Bob is effectively avoided paying deposit fee, having only somewhat increased the overall gas costs (even not so substantially, asdeposit(0, ...)
performs only one queue SSTORE instead of fee calculation and token transfer of direct deposit), which is negligible whenever Bob's deposit is big enough.Impact
As such a replacement can be done for any deposit, protocol can lose up to all deposit fees.
Code Snippet
_deposit() do not apply the fee if the call is for zero epoch and
depositQueue
is being used:https://github.com/sherlock-audit/2023-03-Y2K/blob/main/Earthquake/src/v2/Carousel/Carousel.sol#L470-L501
Then mintDepositInQueue() proceeds with the queue elements backwards, in LIFO order:
Tool used
Manual Review
Recommendation
As an example, consider applying deposit fee on placing deposit queue request as well, i.e. do this part for all epoch
id
s:https://github.com/sherlock-audit/2023-03-Y2K/blob/main/Earthquake/src/v2/Carousel/Carousel.sol#L495
It looks it needs to be the full fee as otherwise the demonstrated approach can be able to lower it.
As there is no way to remove deposit from the queue and user will be placing deposits there in advance, the fee for them will not be high anyway.
Duplicate of #75
The text was updated successfully, but these errors were encountered: