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: 14/36
Findings: 5
Award: $2,539.33
🌟 Selected for report: 3
🚀 Solo Findings: 0
gzeon
In recoverTokens
, when token == depositToken, the excess is defined as follow
excess = ERC20(token).balanceOf(address(this)) - (depositTokenAmount - redeemedDepositTokens);
Instead we should also consider depositTokenFlashloanFeeAmount gained from flashloan fee
excess = ERC20(token).balanceOf(address(this)) - (depositTokenAmount + depositTokenFlashloanFeeAmount - redeemedDepositTokens);
#0 - 0xean
2022-01-14T20:44:30Z
duplicate of #241
gzeon
There doesn't seems to be anything to prevent one the deploy a Stream with rewardToken == depositToken. If rewardToken == depositToken, some logic might be broken.
For example,
recoverTokens
logic would be broken because it does not calculate excess properly.
https://github.com/code-423n4/2021-11-streaming/blob/56d81204a00fc949d29ddd277169690318b36821/Streaming/src/Locke.sol#L646
the correct excess would beexcess := balance + redeemedDepositTokens - rewardTokenFeeAmount - depositTokenFlashloanFeeAmount - depositTokenAmount - rewardTokenAmount
flashloan
would accounted all fee to depositTokenFlashloanFeeAmountHandle these case or make sure rewardToken != depositToken
#0 - 0xean
2022-01-14T21:04:33Z
dupe of #215
🌟 Selected for report: toastedsteaksandwich
Also found by: Meta0xNull, Omik, ScopeLift, bitbopper, gzeon, pedroais, wuwe1
gzeon
arbitraryCall
did not check the balances of incentives, which allow inherited governance to steal the incentives.
Keep track of incentive token addresses in createIncentive
and check the balance of each token before and after the arbitrary call to ensure trustlessness
#0 - brockelmore
2021-12-08T23:10:03Z
@ judges, this report is lacking the actual functionality to do so - i am inclined to dispute this particular report to reward those that actually provided PoC.
#1 - 0xean
2022-01-14T22:00:13Z
@brockelmore - it is certainly light on the details, but does recommend a mitigation. I am going to leave it as a duplicate, but appreciate the sentiment that is would be hard if other's hadn't reported this issue to be sure of the vulnerability.
#2 - 0xean
2022-01-14T22:00:27Z
dupe of #199
🌟 Selected for report: gzeon
805.8152 USDC - $805.82
gzeon
The comment in https://github.com/code-423n4/2021-11-streaming/blob/56d81204a00fc949d29ddd277169690318b36821/Streaming/src/Locke.sol#L553 stated that:
Allows a receipt token holder (or original depositor in case of a sale) to claim their rewardTokens but the reward is only tracked to the original depositor in both case, see https://github.com/code-423n4/2021-11-streaming/blob/56d81204a00fc949d29ddd277169690318b36821/Streaming/src/Locke.sol#L558
TokenStream storage ts = tokensNotYetStreamed[msg.sender];
Transferring the LockeERC20 token does not transfer the TokenStream state.
#0 - brockelmore
2021-12-08T21:55:13Z
bad comment 👍
#1 - 0xean
2022-01-16T00:44:24Z
marking down to low per
1 — Low: Low: Assets are not at risk. State handling, function incorrect as to spec, issues with comments.
27.0568 USDC - $27.06
gzeon
There is an empty else block
#0 - 0xean
2022-01-17T13:48:16Z
dupe of #137
🌟 Selected for report: WatchPug
Also found by: gzeon, toastedsteaksandwich
27.0568 USDC - $27.06
gzeon
The flashloan
function in Locke.sol check balances of both deposit and reward tokens. While this is certainly the safest approach, it seems unnecessary to check for the balance of the token that was NOT flashloaned.
might consider rewrite to
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); }
#0 - 0xean
2022-01-17T13:46:55Z
dupe of #262
🌟 Selected for report: gzeon
gzeon
https://github.com/code-423n4/2021-11-streaming/blob/56d81204a00fc949d29ddd277169690318b36821/Streaming/src/Locke.sol#L403 can be re-written from
rewardTokenFeeAmount += feeAmt; rewardTokenAmount += amt; } else { amt = amount; rewardTokenAmount += amt; }
to
rewardTokenFeeAmount += feeAmt; } else { amt = amount; } rewardTokenAmount += amt;
to reduce cdoe size
🌟 Selected for report: gzeon
gzeon
Currently createStream
in StreamFactory
deploy a new Stream and LockeERC20 contract for every new Stream. We can instead deploy a minimal proxy point to the Stream implementation to save significant gas on createStream
.