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: 13/36
Findings: 2
Award: $740.43
🌟 Selected for report: 0
🚀 Solo Findings: 0
725.1017 USDC - $725.10
The _twapUpdate
takes pricedata after the first transaction in a block. This can be exploited by attackers who can manipulate the price and reset it back to the initial price at no external arbitrage risk unlike would have been incured if the pricedata was taken at the end of the block.
At the beginning of the block, when transactions are conducted, the TWAP is updated, subsequent transactions in the block will not be updated since timeElapsed
will still be 0.
Hence, an attacker can perform two transactions in one block which is first making a large swap through the reserves, which will change the TWAP price for the block since last update, then ending with a final swap back to the previous price, which will not reupdate the price as the elapsed time is still 0.
This will manipulate the price without inccuring any significant risks.
function _twapUpdate() internal { uint32 blockTimestamp = uint32(block.timestamp % 2 ** 32); uint32 timeElapsed = blockTimestamp - _BLOCK_TIMESTAMP_LAST_; if (timeElapsed > 0 && _BASE_RESERVE_ != 0 && _QUOTE_RESERVE_ != 0) { /// @dev It is desired and expected for this value to /// overflow once it has hit the max of `type.uint256`. unchecked { _BASE_PRICE_CUMULATIVE_LAST_ += getMidPrice() * timeElapsed; } } _BLOCK_TIMESTAMP_LAST_ = blockTimestamp; }
Manual code review
Take prices at the end of the block instead as an attacker that tries to manipulate the TWAP to an extreme value will have it there at the end of the block which opens them up to arbitrage losses.
Other
#0 - c4-pre-sort
2024-03-15T12:58:22Z
141345 marked the issue as duplicate of #227
#1 - thereksfour
2024-03-29T03:45:33Z
don't think this is a duplicate of #227 and that this is supposed to be invalid, the root cause of TWAP price manipulation is not what this finding describes..
#2 - c4-judge
2024-03-29T03:45:47Z
thereksfour marked the issue as not a duplicate
#3 - c4-judge
2024-03-29T03:45:54Z
thereksfour marked the issue as unsatisfactory: Invalid
#4 - c4-judge
2024-04-03T16:25:15Z
thereksfour marked the issue as duplicate of #227
#5 - c4-judge
2024-04-05T07:32:40Z
thereksfour changed the severity to 3 (High Risk)
#6 - c4-judge
2024-04-05T07:33:01Z
thereksfour marked the issue as satisfactory
#7 - c4-judge
2024-04-05T15:37:22Z
thereksfour marked the issue as partial-75
🌟 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
Links to affected code *
MagicLP contracts are deployed with solady's LibClone, which makes them able to receive ETH.
But the pool's are only able to recover non ether tokens.
function rescue(address token, address to, uint256 amount) external onlyImplementationOwner { if (token == _BASE_TOKEN_ || token == _QUOTE_TOKEN_) { revert ErrNotAllowed(); } token.safeTransfer(to, amount); emit TokenRescue(token, to, amount); }
As a consequence, any ether sent to the pools are lost and irretrievable, seeing as they can also be removed.
Manual code review
Include a rescueEth
function in the pools too.
Lines of code*
The withdraw
function in BlastOnboarding contract has a whenNotPaused
modifier. This can put users' funds at risk if a compromised/malicious admin decides to admin pause the contract. He might even renounce ownership, leaving the funds stuck in the contract forever.
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); }
Remove the whenNotPaused modifier from the withdraw
function
_twapUpdate
can be dossed due to potential underflow errorLinks to affected code *
The a normalization of the block.timestamp value to uint32 in the _twapUpdate
function will throw an underflow error due to the solidity version in use and the potential of block.timestamp to exceed type(uint32).max.
function _twapUpdate() internal { uint32 blockTimestamp = uint32(block.timestamp % 2 ** 32); uint32 timeElapsed = blockTimestamp - _BLOCK_TIMESTAMP_LAST_; if (timeElapsed > 0 && _BASE_RESERVE_ != 0 && _QUOTE_RESERVE_ != 0) { /// @dev It is desired and expected for this value to /// overflow once it has hit the max of `type.uint256`. unchecked { _BASE_PRICE_CUMULATIVE_LAST_ += getMidPrice() * timeElapsed; } } _BLOCK_TIMESTAMP_LAST_ = blockTimestamp; }
Consider wrapping the timeElapsed
in an unchecked block.
unchecked { uint32 timeElapsed = blockTimestamp - _BLOCK_TIMESTAMP_LAST_; }
latestRoundData
should be well validatedLines of code*
Using the latestRoundData
function requires price validation for 0 or negative values.
function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80) { return (0, latestAnswer(), 0, 0, 0); }
Include a check for negative or zero values.
require(latestAnswer() > 0 "Error");
Consider also checking for the roundid, startedAt and updatedAt time too.
Lines of code*
The MagicLpAggregator contract gets prices from chainlink without checking for active sequencer. In L2 oracles, Chainlink recommends consulting the Sequencer Uptime Feed to ensure that the sequencer is live before trusting the data returned by the Price Feed. If the Arbitrum Sequencer goes down, oracle data will not be kept up to date, and thus could become stale.
Include a function to call for active sequencer.`
Lines of code*
Including an explicit check for return values of low level calls can help prevent silent failures.
function execute(address to, uint256 value, bytes calldata data) external onlyOwner returns (bool success, bytes memory result) { (success, result) = to.call{value: value}(data); }
Lines of code*
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/auxiliary/FeeRateModel.sol#L57 https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/auxiliary/FeeRateModel.sol#L46 https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/periphery/Factory.sol#L96 https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/periphery/Factory.sol#L105 https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastBox.sol#L72 https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastBox.sol#L81 https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastGovernor.sol#L40 https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastMagicLP.sol#L89 https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastMagicLP.sol#L80 https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastOnboarding.sol#L147 https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastOnboarding.sol#L175 https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastOnboarding.sol#L185 https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastOnboarding.sol#L190 https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastOnboardingBoot.sol#L137 https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastOnboardingBoot.sol#L146 https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/staking/LockingMultiRewards.sol#L317
#0 - c4-pre-sort
2024-03-15T15:00:47Z
141345 marked the issue as sufficient quality report
#1 - 141345
2024-03-16T01:08:26Z
30 ZanyBonzy l r nc 3 0 1
L 1 l L 2 i L 3 l L 4 l L 5 d dup of https://github.com/code-423n4/2024-03-abracadabra-money-findings/issues/163 L 6 d dup of https://github.com/code-423n4/2024-03-abracadabra-money-findings/issues/102 L 7 n
#2 - c4-judge
2024-03-29T16:35:26Z
thereksfour marked the issue as grade-b
#3 - thereksfour
2024-04-07T09:05:44Z
Sorry, #163 is considered invalid, so the score for this report does not meet grade a