Platform: Code4rena
Start Date: 03/10/2023
Pot Size: $24,500 USDC
Total HM: 6
Participants: 62
Period: 3 days
Judge: LSDan
Total Solo HM: 3
Id: 288
League: ETH
Rank: 6/62
Findings: 2
Award: $1,020.85
π Selected for report: 0
π Solo Findings: 0
1015.9097 USDC - $1,015.91
protocolCmd()
will brick as it's not marked payable.
protocolCmd()
is not marked as payable.
function protocolCmd(bytes calldata cmd) public virtual { (uint8 code, bytes32 poolHash, uint32 weekFrom, uint32 weekTo, uint64 weeklyReward) = abi.decode(cmd, (uint8, bytes32, uint32, uint32, uint64)); if (code == ProtocolCmd.SET_CONC_REWARDS_CODE) { setConcRewards(poolHash, weekFrom, weekTo, weeklyReward); } else if (code == ProtocolCmd.SET_AMB_REWARDS_CODE) { setAmbRewards(poolHash, weekFrom, weekTo, weeklyReward); } else { revert("Invalid protocol command"); } }
Proxy contracts often rely on delegateCall
or similar mechanisms to forward function calls and data to an underlying implementation contract. When a function call is forwarded, it's executed in the context of the proxy contract. If the function in the implementation contract is marked as payable
, it can accept Ether (via msg.value
) if the forwarded function call contains any Ether. If the function in the proxy contract is not marked as payable
, forwarding a call to a payable
function in the implementation contract would fail, even if the implementation contract is capable of handling Ether.
So, marking proxy entry points as payable
ensures that the proxy contract can forward function calls, including those that may involve Ether, to the implementation contract without issues. It's a defensive measure to ensure that Ether transfers are properly handled by the underlying contract, even if the proxy itself doesn't deal with Ether directly.
Manual Review
- function protocolCmd(bytes calldata cmd) public virtual { + function protocolCmd(bytes calldata cmd) public payable virtual { (uint8 code, bytes32 poolHash, uint32 weekFrom, uint32 weekTo, uint64 weeklyReward) = abi.decode(cmd, (uint8, bytes32, uint32, uint32, uint64)); if (code == ProtocolCmd.SET_CONC_REWARDS_CODE) { setConcRewards(poolHash, weekFrom, weekTo, weeklyReward); } else if (code == ProtocolCmd.SET_AMB_REWARDS_CODE) { setAmbRewards(poolHash, weekFrom, weekTo, weeklyReward); } else { revert("Invalid protocol command"); } }
Error
#0 - c4-pre-sort
2023-10-08T12:22:51Z
141345 marked the issue as duplicate of #239
#1 - c4-pre-sort
2023-10-09T16:51:52Z
141345 marked the issue as sufficient quality report
#2 - c4-judge
2023-10-18T21:54:57Z
dmvt marked the issue as satisfactory
π Selected for report: adriro
Also found by: 0x3b, 0xAadi, 0xDING99YA, 0xTheC0der, 0xWaitress, 0xdice91, 100su, 3docSec, BRONZEDISC, BoRonGod, Eurovickk, GKBG, HChang26, IceBear, JP_Courses, MatricksDeCoder, Mike_Bello90, SovaSlava, Topmark, albahaca, cookedcookee, gzeon, hunter_w3b, kutugu, lukejohn, marqymarq10, matrix_0wl, orion, pep7siup, radev_sw, sces60107, taner2344, tpiliposian, wahedtalash77, xAriextz, zpan
4.9369 USDC - $4.94
claimAmbientRewards()
can expectedly revert when called at certain block.timestamp.
In the claimAmbientRewards()
function, there's an issue related to how it checks and handles the weeks for which users can claim rewards. Users can specify which weeks they want to claim rewards for, and the function validates these selections. Specifically, it checks if the selected weeks have completed(in the past), using the condition week + WEEK < block.timestamp
.
require(week + WEEK < block.timestamp, "Week not over yet"); require( !ambLiquidityRewardsClaimed_[poolIdx][posKey][week], "Already claimed" );
In most situations, this check works correctly. However, in certain edge cases, it can unexpectedly cause the function to revert. The problem arises when block.timestamp
aligns exactly with the end of a week. Even though the week is technically completed, the user is unable to claim rewards due to the require
statement.
Imagine following scenario:
claimAmbientRewards()
precisely at the end of week 3/start of week 4, where block.timestamp = 1,814,400
.accrueAmbientPositionTimeWeightedLiquidity()
and accrueAmbientGlobalTimeWeightedLiquidity()
functions.week + WEEK < block.timestamp
.Manual Review
function claimAmbientRewards( address owner, bytes32 poolIdx, uint32[] memory weeksToClaim ) internal { CurveMath.CurveState memory curve = curves_[poolIdx]; accrueAmbientPositionTimeWeightedLiquidity(payable(owner), poolIdx); accrueAmbientGlobalTimeWeightedLiquidity(poolIdx, curve); bytes32 posKey = encodePosKey(owner, poolIdx); uint256 rewardsToSend; for (uint256 i; i < weeksToClaim.length; ++i) { uint32 week = weeksToClaim[i]; - require(week + WEEK < block.timestamp, "Week not over yet"); + require(week + WEEK <= block.timestamp, "Week not over yet"); require( !ambLiquidityRewardsClaimed_[poolIdx][posKey][week], "Already claimed" ); uint256 overallTimeWeightedLiquidity = timeWeightedWeeklyGlobalAmbLiquidity_[ poolIdx ][week]; if (overallTimeWeightedLiquidity > 0) { uint256 rewardsForWeek = (timeWeightedWeeklyPositionAmbLiquidity_[ poolIdx ][posKey][week] * ambRewardPerWeek_[poolIdx][week]) / overallTimeWeightedLiquidity; rewardsToSend += rewardsForWeek; } ambLiquidityRewardsClaimed_[poolIdx][posKey][week] = true; } if (rewardsToSend > 0) { (bool sent, ) = owner.call{value: rewardsToSend}(""); require(sent, "Sending rewards failed"); } }
Invalid Validation
#0 - 141345
2023-10-08T04:06:48Z
<= or < for block.timestamp condition, no big loss.
QA might be more appropriate
#1 - c4-pre-sort
2023-10-09T16:50:03Z
141345 marked the issue as sufficient quality report
#2 - c4-sponsor
2023-10-11T10:46:05Z
OpenCoreCH marked the issue as disagree with severity
#3 - c4-sponsor
2023-10-11T10:46:10Z
OpenCoreCH (sponsor) acknowledged
#4 - OpenCoreCH
2023-10-11T10:46:34Z
Impact is that the user has to wait 1 second / block more before claiming
#5 - c4-judge
2023-10-18T21:53:44Z
dmvt changed the severity to QA (Quality Assurance)
#6 - c4-judge
2023-10-18T22:45:52Z
dmvt marked the issue as grade-b