Platform: Code4rena
Start Date: 30/11/2021
Pot Size: $100,000 USDC
Total HM: 15
Participants: 36
Period: 7 days
Judge: 0xean
Total Solo HM: 4
Id: 62
League: ETH
Rank: 25/36
Findings: 2
Award: $1,191.24
π Selected for report: 1
π Solo Findings: 0
jonah1005
recoverTokens
The Stream creator can recover tokens that were mistakenly sent to the contract through. recoverTokens
Locke.sol#L646-L677
The creator should only be able to recover the excess token. However, it doesn't handle the flash loan fee.
uint256 excess = ERC20(token).balanceOf(address(this)) - (depositTokenAmount - redeemedDepositTokens);
If recoverTokens
is called before claimFees
the users would be double-charged.
In order to simplify the POC I added a debug function donateFlashLoanFee
in the Stream
contract.
function donateFlashLoanFee(uint256 amount) public { ERC20(depositToken).safeTransferFrom(msg.sender, address(this), amount); depositTokenFlashloanFeeAmount += uint112(amount); }
We can create a simplify Stream contract with no one stakes in the contract. The contract sends flashloanfee
back to the streamCreator
.
dai.functions.approve(stream.address, deposit_amount).transact() stream.functions.donateFlashLoanFee(deposit_amount).transact() w3.provider.make_request('evm_setNextBlockTimestamp', [hex(start_time + duration + duration)]) stream.functions.recoverTokens(dai.address, user).transact() // output: 0 print(dai.functions.balanceOf(stream.address).call())
hardhat
The dev handles recovering rewardTokens perfectly.Locke.sol#L672 Recommended to follow it.
uint256 excess = ERC20(token).balanceOf(address(this)) - (depositTokenAmount - redeemedDepositTokens) - depositTokenFlashloanFeeAmount;
#0 - 0xean
2022-01-14T20:52:26Z
dupe of #241
π Selected for report: jonah1005
805.8152 USDC - $805.82
jonah1005
rewardPerToken()
is calculated according to lastApplicableTime
and lastUpdate
. Locke.sol#L343-L353 Since lastUpdate
is set to startTime
before the start time. Locke.sol#L203-L250, it reverts before the start time.
lastApplicableTime()) - lastUpdate
would revert when lastUpdate
is bigger than lastApplicableTime()
.
This is the web3.py script:
stream.functions.stake(deposit_amount).transact() stream.functions.rewardPerToken().call()
Since rewardPerToken
returns zero when totalVirtualBalance equals zero, we have to stake a few funds to trigger this bug.
hardhat
Recommend to return zero before startTime.
function rewardPerToken() public view returns (uint256) { if (totalVirtualBalance == 0 || lastApplicableTime() < startTime) { return cumulativeRewardPerToken; } else { // βtime*rewardTokensPerSecond*oneDepositToken / totalVirtualBalance return cumulativeRewardPerToken + ( // NOTE: depositDecimalsOne ((uint256(lastApplicableTime()) - lastUpdate) * rewardTokenAmount * depositDecimalsOne/streamDuration) / totalVirtualBalance ); } }