Platform: Code4rena
Start Date: 07/03/2024
Pot Size: $63,000 USDC
Total HM: 20
Participants: 36
Period: 5 days
Judge: cccz
Total Solo HM: 11
Id: 349
League: BLAST
Rank: 16/36
Findings: 3
Award: $443.43
🌟 Selected for report: 1
🚀 Solo Findings: 0
156.622 USDC - $156.62
Whenever any operation with the given user as a beneficiary is performed, this user's rewards are checkpointed via function _updateRewardsGlobal()
, which calculates the reward checkpoint by a call to function _rewardPerToken
function _rewardPerToken(address rewardToken, uint256 lastTimeRewardApplicable_, uint256 totalSupply_) public view returns (uint256) { if (totalSupply_ == 0) { return _rewardData[rewardToken].rewardPerTokenStored; } uint256 timeElapsed = lastTimeRewardApplicable_ - _rewardData[rewardToken].lastUpdateTime; uint256 pendingRewardsPerToken = (timeElapsed * _rewardData[rewardToken].rewardRate * 1e18) / totalSupply_; return _rewardData[rewardToken].rewardPerTokenStored + pendingRewardsPerToken; }
rewardRate
is set in notifyRewardAmount
function as below:
reward.rewardRate = amount / _remainingRewardTime;
It is clearly that pendingRewardsPerToken
can be equal to 0. Consider this mock scenario:
timeElapsed
= 12 seconds (equal to the time of a block)_rewardData[rewardToken].rewardRate
= 2totalSupply_
= 1e22_rewardData[rewardToken].rewardPerTokenStored
= xSo the result of the function is
x + 12 * 2 * 1e18 / 1e22
Which is equal to
x + 24e18 / 1e22
= x
due to rounding down
Which is incorrect as the calculation does not account for the rewards accumulated since the last checkpoint.
Attacker can griefing attack by continously calling stake
or lock
function with dust amount, since they only revert when amount = 0:
function _stakeFor(address account, uint256 amount, bool lock_) internal { if (amount == 0) { revert ErrZeroAmount(); // <--- } function lock(uint256 amount) public whenNotPaused { if (amount == 0) { revert ErrZeroAmount(); // <-- }
Incorrect checkpoint value being stored for the beneficiary, which will affect future reward calculations for this beneficiary.
Manual review
Use validation checks to ensure that the required calculation doesn't round down to zero:
require(pendingRewardsPerToken >= totalSupply_, "rounding down to 0")
Other
#0 - c4-pre-sort
2024-03-15T12:53:33Z
141345 marked the issue as primary issue
#1 - 141345
2024-03-15T12:53:47Z
low decimal with big totalSupply group several reports on the same block of code, but some dup only addresses low decimal, or only totalSupply
#2 - c4-pre-sort
2024-03-15T12:53:49Z
141345 marked the issue as sufficient quality report
#3 - 0xCalibur
2024-03-17T04:36:36Z
no factor
#4 - c4-sponsor
2024-03-28T17:13:51Z
0xCalibur (sponsor) disputed
#5 - thereksfour
2024-03-29T13:30:23Z
I would think it's dust loss, the rewardRate in the example is very small
#6 - c4-judge
2024-03-29T13:30:36Z
thereksfour changed the severity to QA (Quality Assurance)
#7 - c4-judge
2024-04-07T08:52:26Z
This previously downgraded issue has been upgraded by thereksfour
#8 - c4-judge
2024-04-07T08:53:03Z
thereksfour marked issue #222 as primary and marked this issue as a duplicate of 222
#9 - c4-judge
2024-04-07T08:55:25Z
thereksfour marked the issue as partial-75
🌟 Selected for report: grearlake
Also found by: Breeje, blutorque, hals, roguereggiant
271.4781 USDC - $271.48
https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/mimswap/MagicLP.sol#L470-#L510 https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/mimswap/MagicLP.sol#L244-#L265
Because MagicLP
is forked from DODO, we will check DODO documentation to understand this contract. From DODO documentation, the "I" is the "i" value in here and it is directly related with the output amount a trader will receive when selling a quote/base token:
Adjusting the value of "I" directly by calling setParameters()
function will influence the price. This can be exploited by a MEV bot, simply by trading just before the setParameters()
function and exiting right after the price change. The profit gained from this operation essentially represents potential losses for the liquidity providers who supplied liquidity to the pool.
Since the price will change, the MEV bot can simply sandwich the tx and get profit.
Another note on this is that even though the adjustPrice called by onlyImplementationOwner
without getting frontrunned, it still creates a big price difference which requires immediate arbitrages. Usually these type of parameter changes that impacts the trades are setted by time via ramping to mitigate the unfair advantages that it can occur during the price update.
Manual review
Acknowledge the issue and use private RPC's to eliminate front-running or slowly ramp up the "I" so that the arbitrage opportunity is fair
MEV
#0 - c4-pre-sort
2024-03-15T12:30:27Z
141345 marked the issue as primary issue
#1 - c4-pre-sort
2024-03-15T12:30:35Z
141345 marked the issue as sufficient quality report
#2 - 141345
2024-03-15T12:31:15Z
admin func to change "I" needs time lock
#3 - c4-sponsor
2024-03-17T04:41:29Z
0xCalibur (sponsor) acknowledged
#4 - 0xCalibur
2024-03-17T04:42:18Z
Fixed in main https://github.com/Abracadabra-money/abracadabra-money-contracts/
We now have protocol own pool and can disable trading for our own pools, changing the parameters and make sure the pool is in good state before enabling it back.
#5 - c4-judge
2024-03-29T13:16:27Z
thereksfour marked the issue as satisfactory
#6 - c4-judge
2024-03-31T06:44:17Z
thereksfour marked the issue as selected for report
🌟 Selected for report: ether_sky
Also found by: 0x11singh99, 0xE1, 0xJaeger, Bauchibred, Bigsam, Bozho, Breeje, DarkTower, HChang26, SpicyMeatball, Trust, ZanyBonzy, albahaca, bareli, blutorque, grearlake, hals, hassan-truscova, hihen, oualidpro, pfapostol, ravikiranweb3, slvDev, zhaojie
15.328 USDC - $15.33
https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/blast/BlastOnboarding.sol#L132
From the contest documentation:
People who deposited unlocked into the LLE can withdraw at any time during and after
BlastOnboarding.withdraw()
function:
function withdraw(address token, uint256 amount) external whenNotPaused onlySupportedTokens(token) { balances[msg.sender][token].unlocked -= amount; balances[msg.sender][token].total -= amount; totals[token].unlocked -= amount; totals[token].total -= amount; token.safeTransfer(msg.sender, amount); emit LogWithdraw(msg.sender, token, amount); }
However, due to the whenNotPaused
modifier in the BlastOnboarding.withdraw()
function, users who have deposit unlocked will not be able to withdraw their funds. If the protocol is paused, it will fail, causing the transaction to revert and preventing users from withdrawing their funds.
Manual review
Remove whenNotPaused
modifier from withdraw()
function:
- function withdraw(address token, uint256 amount) external whenNotPaused onlySupportedTokens(token) + function withdraw(address token, uint256 amount) external onlySupportedTokens(token)
Other
#0 - c4-pre-sort
2024-03-15T12:47:11Z
141345 marked the issue as primary issue
#1 - c4-pre-sort
2024-03-15T12:47:22Z
141345 marked the issue as sufficient quality report
#2 - 141345
2024-03-15T12:47:44Z
can not withdraw if the contract is paused, contradict the doc
#3 - 0xCalibur
2024-03-17T04:33:29Z
no factor
#4 - c4-sponsor
2024-03-28T17:13:30Z
0xCalibur (sponsor) disputed
#5 - c4-judge
2024-03-29T13:34:57Z
thereksfour changed the severity to QA (Quality Assurance)
#6 - c4-judge
2024-03-29T16:31:57Z
thereksfour marked the issue as grade-b