Closed c4-bot-4 closed 8 months ago
Picodes marked the issue as duplicate of #141
Picodes marked the issue as selected for report
othernet-global (sponsor) acknowledged
othernet-global (sponsor) disputed
If _arbitrageProfits remains for a pool that was unwhitelisted and the pool is later whitelisted again, it is acceptable that the previous _arbitrageProfits value is used to give the pool some share of the pending rewards.
Flagging this report and its duplicate as a set of duplicate of #752. It's the same root cause and both are of Medium severity.
Picodes marked the issue as duplicate of #752
Picodes marked the issue as not selected for report
Picodes marked the issue as satisfactory
Picodes changed the severity to QA (Quality Assurance)
This previously downgraded issue has been upgraded by Picodes
Picodes marked the issue as not a duplicate
Picodes marked the issue as duplicate of #752
Lines of code
https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/pools/PoolStats.sol#L37 https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/pools/PoolStats.sol#L134 https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/pools/PoolStats.sol#L47
Vulnerability details
Impact
The Upkeep::performUpkeep function, in step 2, withdraws profits generated from arbitrage within the pools. These rewards are then distributed among the involved pools. The system accumulates arbitrage profits for each pool in the PoolStats::_updateProfitsFromArbitrage function. The execution process is as follows:
Upkeep::perfomUpkeep
is called, and it invokes dao::withdrawArbitrageProfits.dao::withdrawArbitrageProfits
function retrieves the arbitrage generated for the DAO inwETH
.The issue arises when a pool that has generated arbitrage profits is deactivated using the poolsConfig::unwhitelistPool function. This causes the arbitrage-generated wETH from the unwhitelisted pool to be sent to
SaltRewards
but without delivering those arbitrage profits to the pool since the pool is no longer active. Additionally, the PoolStats::_arbitrageProfits variable of the deactivated pool is not cleared, causing the system to continue counting arbitrage profits that have already been sent toSaltRewards
.Considering the above, let's examine the following scenario:
poolAB
generates5 wETH
, andpoolBC
generates5 wETH
as arbitrage profit.poolBC
is unwhitelisted.Upkeep::perfomUpkeep
is called, invokingdao::withdrawArbitrageProfits
, obtaining the total10e18 wETH tokens
, including the profits from the deactivated pool (poolBC
).SaltRewards::performUpkeep
, it is distributed only to the whitelisted poolAB, leaving the rest wETH in theSaltRewards
contract.Therefore, in the given example, the
PoolStats::_arbitrageProfits
variable of the deactivated pool (poolBC) remains unchanged. However, these profits have already been distributed among the POLs and SaltRewards to the whitelisted pools. Additionally, if the deactivated pool is reactivated, thePoolStats::_arbitrageProfits
variable will have a non-zero value, causing it to count arbitrage profits that were previously delivered toSaltRewards
.This demonstrates that even though those profits from the unwhitelisted pool have already been distributed improperly to SaltRewards, the system will still take into account that the inactive pool has arbitrage profits, which can be detrimental if that pool becomes active again.
Proof of Concept
A test scenario was created to illustrate the issue. In the test, the
poolIDBC
is assigned30e18
as profits. Subsequently,poolIDBC
is deactivated, and thePoolStats::clearProfitsForPools
function does not clearPoolStats::_arbitrageProfits
for poolBC, leaving it unchanged at30e18
.arbitrageProfits
forpoolIDAB
is empty because those rewards were sent to thepoolIDAB
.poolIDBC
still hasarbitrageProfits=30e18
which is incorrectly becausedao::withdrawArbitrageProfits
will extract all wETH arbitrage profits.Tools used
Manual review
Recommended Mitigation Steps
Before removing a pool, it is essential to send the arbitrage profits generated by that pool to
SaltRewards
to ensure thatPoolStats::_arbitrageProfits
is set to zero when clearProfitsForPools is called. This step will prevent the counting of arbitrage profits that have already been distributed among the POLs and SaltRewards. Implementing this mitigation step will maintain the integrity of arbitrage profit accounting within the system.Assessed type
Context