Platform: Code4rena
Start Date: 27/01/2022
Pot Size: $75,000 USDT
Total HM: 6
Participants: 29
Period: 7 days
Judge: leastwood
Total Solo HM: 6
Id: 72
League: ETH
Rank: 7/29
Findings: 2
Award: $3,359.86
🌟 Selected for report: 3
🚀 Solo Findings: 0
🌟 Selected for report: csanuragjain
1627.6042 USDT - $1,627.60
csanuragjain
User funds can be lost if Admin sets startTimes[i] to 0
Navigate to contract https://github.com/code-423n4/2022-01-openleverage/blob/main/openleverage-contracts/contracts/farming/FarmingPools.sol
Check the initDistributions function
function initDistributions(address[] memory stakeTokens, uint64[] memory startTimes, uint64[] memory durations) external onlyAdmin { for (uint256 i = 0; i < stakeTokens.length; i++) { require(distributions[stakeTokens[i]].starttime == 0, 'Init once'); distributions[stakeTokens[i]] = Distribution(durations[i], startTimes[i], 0, 0, 0, 0, 0); } }
Assume Admin calls this for token X with startTimes[i] as 0. This creates a new distribution with start time as 0 for token X
User Y stakes amount 500 for this token X
Admin calls initDistributions again with token X and startTimes[i] as 1000. This overwrites and reinitializes distributions[X] which means totalStaked becomes 0 and contract has lost all track of user funds now
Add a check to see startTimes[i]!=0 in initDistributions function
#0 - ColaM12
2022-02-07T03:08:14Z
Recommend severity 1(Low Risk)
#1 - 0xleastwood
2022-02-14T11:57:41Z
I think this is a very unlikely situation. It would require the admin
to be actively malicious and users to enter the farming pool with no expectation of rewards. If the admin
has also called notifyRewardAmount()
, then a user could claim the entire reward amount within one tx and they wouldn't be risking their funds if the admin
called initDistributions()
again after they had staked and withdrawn.
#2 - 0xleastwood
2022-02-14T11:58:05Z
Marking as 1(Low Risk)
.
🌟 Selected for report: csanuragjain
1627.6042 USDT - $1,627.60
csanuragjain
If contract does not have enough reward token, full user reward are stuck. Their is no provision for partial reward payment
Observe the getReward function
function getReward(address stakeToken) public updateReward(stakeToken, msg.sender) checkStart(stakeToken) { uint256 reward = rewards[stakeToken][msg.sender].rewards; if (reward > 0) { rewards[stakeToken][msg.sender].rewards = 0; oleToken.safeTransfer(msg.sender, reward); emit RewardPaid(stakeToken, msg.sender, reward); } }
if reward balance is less contract must transfer whatever amount is left in contract and rest can be recorded in user leftover rewards
#0 - ColaM12
2022-02-04T15:00:54Z
This issue only happens if wrong amount sets to Farming Pool. Recommend severity 1 (Low Risk).
#1 - 0xleastwood
2022-02-19T10:45:50Z
Agree with sponsor, this requires that notifyRewardAmount
is called incorrectly.
🌟 Selected for report: csanuragjain
104.6634 USDT - $104.66
csanuragjain
Gas savings and minor corrections
Adminable.sol - developer can be immutable
OLEToken.sol, ControllerDelegator.sol, OpenLevDelegator.sol, XOLEDelegator.sol, DexAggregatorDelegator.sol - admin can be immutable
Reserve.sol - admin & oleToken can be immutable
Timelock.sol - In queueTransaction function, Add check to see require(!queuedTransactions[txHash], "Already queued")
OLETokenLock.sol - In constructor, beneficiaries.length should be stored locally and then use for gas saving. Also check startTimes[i]!=endTimes[i]
FarmingPools.sol - a. In notifyRewardAmounts function, check stakeTokens[i]== reward[i] b. In initDistributions function, length should be same for stakeTokens, startTimes and durations c. rewardPerToken function, assert(lastTimeRewardApplicable >= distribution.lastUpdateTime); could be changed to assert(lastTimeRewardApplicable > distribution.lastUpdateTime); since if lastTimeRewardApplicable = distribution.lastUpdateTime then change in reward will be 0