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: 11/36
Findings: 5
Award: $2,731.09
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: cmichel
Also found by: GeekyLumberjack, hyh, kenzo, pedroais
pedroais
The recoverTokens function will become unusable with reward tokens if they are withdrawn first.
With reward tokens excess is defined as balance - (rewardTokenAmount + rewardTokenFeeAmount) https://github.com/code-423n4/2021-11-streaming/blob/5a87fce1190e0da1cf881919ded3573ca9ec4b08/Streaming/src/Locke.sol#L672
The variable rewardTokenAmount never gets updated when rewards are withdrawn so after a withdrawal the balance will decrease while rewardTokenAmount is still the same. As a result, the function will revert and the excess tokens will be lost.
Manual
Add a variable called redeemed reward tokens and define excess as balance - (rewardTokenAmount + rewardTokenFeeAmount-redeemedRewards)
#0 - 0xean
2022-01-14T21:13:00Z
dupe of #214
🌟 Selected for report: toastedsteaksandwich
Also found by: Meta0xNull, Omik, ScopeLift, bitbopper, gzeon, pedroais, wuwe1
481.7736 USDC - $481.77
pedroais
Detailed description of the impact of this finding.
The arbitrary call function checks (incentives[who] == 0, "inc") to disallow calling the incentive token contract. https://github.com/code-423n4/2021-11-streaming/blob/56d81204a00fc949d29ddd277169690318b36821/Streaming/src/Locke.sol#L735
This can be front runned by calling the contract before the token is sent. The governance could use the arbitrary call to front-run an incentive deposit and call the allowance function of the token on behalf of the contract effectively stealing all tokens.
steps :
-Stream is created -The arbitrary call function is used to set allowance on some token (this could be done by watching the mempool and front running incentive deposits) -User deposits incentive tokens -The incentive tokens can be drained by however got the allowance
Manual
Check allowance stays 0 in the arbitrary call function
#0 - brockelmore
2021-12-08T22:29:18Z
#199 is probably better worded
🌟 Selected for report: harleythedog
pedroais
If the stream is a sale recover tokens function won't work.
Context : The function recover tokens uses balance - (depositTokenAmount- redeemedDepositTokens) to compute excess deposit tokens. RedeemedDepositTokens keeps track of tokens that were redeemed using the claimDepositTokens function.
Problem : If the stream is a sale the tokens are redeemed using creatorClaimSoldTokens and redeemedDepositTokens won't be updated.
Consequence : If the stream is a sale redeemedDepositTokens will be 0. The function will become unusable because if tokens are claimed (with function to claim sales) the contract's balance will decrease. As a result ERC20(token).balanceOf(address(this)) - (depositTokenAmount - 0) will revert and excess tokens will be locked.
Manual
Update redeemedDepositTokens in creatorClaimSoldTokens .
#0 - 0xean
2022-01-15T01:31:06Z
dupe of #121
🌟 Selected for report: gpersoon
Also found by: GiveMeTestEther, Meta0xNull, bitbopper, hack3r-0m, pauliax, pedroais, wuwe1
pedroais
The function update stream internal uses an address "who" as input but it's never used inside the function.
#0 - 0xean
2022-01-16T14:13:19Z
dupe of #125
pedroais
Public functions that aren't used inside the contract can be declared external to save gas
#0 - 0xean
2022-01-16T14:26:45Z
dupe of #260
🌟 Selected for report: ye0lde
Also found by: 0x0x0x, Jujic, pedroais, pmerkleplant
13.1496 USDC - $13.15
pedroais
!= 0 is a cheaper operation compared to > 0, when dealing with uint.
Streaming/src/Locke.sol: 226: if (acctTimeDelta > 0 && ts.tokens > 0) { Streaming/src/Locke.sol: 236: if (tdelta > 0 && unstreamed > 0) { Streaming/src/Locke.sol: 378: require(amount > 0, "amt"); Streaming/src/Locke.sol: 418: require(amount > 0, "amt"); Streaming/src/Locke.sol: 456: require(amount > 0, "amt"); Streaming/src/Locke.sol: 522: require(amount > 0, "amt"); Streaming/src/Locke.sol: 572: require(rewardAmt > 0, "amt"); Streaming/src/Locke.sol: 612: if (fees > 0) { Streaming/src/Locke.sol: 226: if (fees > 0) { Streaming/src/Locke.sol: 612: if (incentives[token] > 0) { Streaming/src/Locke.sol: 679: if (acctTimeDelta > 0 && ts.tokens > 0) {
#0 - 0xean
2022-01-16T14:20:07Z
dupe of #143
45.0947 USDC - $45.09
pedroais
Useless checks that cost gas
Since the Flashloan function has the lock modifier reentrancy is not possible so checking both tokens is useless.
Proposed new function with less code :
function flashloan(address token, address to, uint112 amount, bytes memory data) public lock { require(token == depositToken || token == rewardToken, "erc"); uint256 preTokenBalance = ERC20(token).balanceOf(address(this)); ERC20(token).safeTransfer(to, amount); // the `to` contract should have a public function with the signature: // function lockeCall(address initiator, address token, uint256 amount, bytes memory data); LockeCallee(to).lockeCall(msg.sender, token, amount, data); uint256 postTokenBalance = ERC20(token).balanceOf(address(this)); uint112 feeAmt = amount * 10 / 10000; // 10bps fee require(preTokenBalance + feeAmt <= postTokenBalance, "f1"); if (token == depositToken) { depositTokenFlashloanFeeAmount += feeAmt; } else { rewardTokenFeeAmount += feeAmt; } emit Flashloaned(token, msg.sender, amount, feeAmt); }
18.2633 USDC - $18.26
pedroais
Save gas
#0 - brockelmore
2021-12-02T09:40:49Z
Adds to bytecode size and we are basically at the 24kb limit.
#1 - 0xean
2022-01-17T12:08:16Z
dupe of #231