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: 15/36
Findings: 4
Award: $2,487.20
🌟 Selected for report: 1
🚀 Solo Findings: 0
pauliax
function createStream should validate that depositToken != rewardToken, otherwise, some functionality may not work as intended, e.g. in function recoverTokens it will become impossible to reach the second 'if' statement.
require depositToken != rewardToken
#0 - 0xean
2022-01-14T21:04:47Z
dupe of #215
🌟 Selected for report: harleythedog
pauliax
function creatorClaimSoldTokens should nullify depositTokenAmount, otherwise it may not be possible to recover deposit tokens later because the balance will be lower than accounted depositTokenAmount:
uint256 excess = ERC20(token).balanceOf(address(this)) - (depositTokenAmount - redeemedDepositTokens);
Possible solution (function creatorClaimSoldTokens):
uint112 amount = depositTokenAmount; depositTokenAmount = 0;
#0 - 0xean
2022-01-15T01:31:47Z
dupe of #121
362.6168 USDC - $362.62
pauliax
functions transfer and transferFrom could validate that 'to' is not an empty address to prevent accidental burns. recoverTokens could validate that recipient is not empty. Function creatorClaimSoldTokens and claimFees could prevent an empty destination. Similarly, acceptGov could check that pendingGov is not a zero address.
Consider validating that mentioned address parameters are not empty.
#0 - brockelmore
2022-01-05T17:38:38Z
duplicate #136
🌟 Selected for report: Meta0xNull
pauliax
There are TODOs left in the code. While this does not cause any direct issue, it indicates a bad smell and uncertainty:
// TODO: we could have start_time+stream_duration+depositlocktime as maturity-date // TODO: figure out sane salt, i.e. streamid + x? streamid guaranteed to be unique
Consider either fixing TODOs or removing them to avoid confusion.
#0 - 0xean
2022-01-16T14:12:42Z
dupe of #78
🌟 Selected for report: gpersoon
Also found by: GiveMeTestEther, Meta0xNull, bitbopper, hack3r-0m, pauliax, pedroais, wuwe1
48.1774 USDC - $48.18
pauliax
function updateStreamInternal accepts a parameter 'who' but uses msg.sender when accessing the stream:
function updateStreamInternal(address who) internal { ... TokenStream storage ts = tokensNotYetStreamed[msg.sender];
This will not update the expected stream when who != msg.sender. Currently, this function is only called from within the modifier, and the modifier is applied with msg.sender only, but still this risk needs to be addressed.
Consider either updating 'who', or totally getting rid of this parameter to avoid confusion.
#0 - brockelmore
2022-01-05T17:37:19Z
gas optimization
#1 - brockelmore
2022-01-05T17:37:31Z
duplicate #125
18.2633 USDC - $18.26
pauliax
function exit performs updateStream and then calls withdraw but withdraw also has a updateStream modifier so this update is executed twice. Because you probably need an exit to have an up to date ts.tokens, an option to consider is extracting an internal _withdraw function with no updateStream modifier and you can re-use it with both exit and withdraw.
#0 - 0xean
2022-01-17T13:50:22Z
dupe of #187
pauliax
This can be simplified to avoid useless calculations:
uint112 feeAmt = amount * 10 / 10000; // 10bps fee
uint112 feeAmt = amount / 1000; // 0.1% fee
#0 - 0xean
2022-01-17T13:56:17Z
dupe of #188
27.0568 USDC - $27.06
pauliax
Don't need an empty else clause here:
if (!isSale) { _burn(msg.sender, amount); } else { } if (!isSale) { // not a straight sale, so give the user some receipt tokens _mint(msg.sender, trueDepositAmt); } else { }
#0 - 0xean
2022-01-17T13:57:05Z
dupe of #137
18.2633 USDC - $18.26
pauliax
Assigned operations to constant variables are re-evaluated every time.
bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
See https://github.com/ethereum/solidity/issues/9232
Change from 'constant' to 'immutable'.
#0 - 0xean
2022-01-17T13:55:43Z
dupe of #231
18.2633 USDC - $18.26
pauliax
'unchecked' directive can be applied where overflow/underflow is not possible, e.g.:
require(newBal <= type(uint112).max && newBal > prevBal, "erc"); uint112 amt = uint112(newBal - prevBal); require(newBal <= type(uint112).max && newBal > prevBal, "erc"); uint112 trueDepositAmt = uint112(newBal - prevBal); require(newBal < type(uint112).max && newBal > prevBal, "erc"); amount = uint112(newBal - prevBal);
#0 - 0xean
2022-01-17T13:43:07Z
dupe of #238
🌟 Selected for report: pauliax
pauliax
I think variable 'amt' in function fundStream can be eliminated if you just re-use the 'amount'. This will save some gas.