Platform: Code4rena
Start Date: 21/04/2022
Pot Size: $100,000 USDC
Total HM: 18
Participants: 60
Period: 7 days
Judge: gzeon
Total Solo HM: 10
Id: 112
League: ETH
Rank: 15/60
Findings: 4
Award: $1,032.46
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Dravee
Also found by: IllIllI, MaratCerby, UnusualTurtle, WatchPug, antonttc, berndartmueller, cccz, danb, horsefacts, hyh, pauliax, rayn, wuwe1
Originally submitted by warden pauliax in https://github.com/code-423n4/2022-04-backd-findings/issues/173, duplicate of https://github.com/code-423n4/2022-04-backd-findings/issues/52.
payable(to).transfer(amount); payable(msg.sender).transfer(amount);
It is currently not recommended as recipients with custom fallback functions (smart contracts) will not be able to handle that. You can read more here: https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/
Solution (don't forget re-entrancy protection): https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol#L53-L59
Originally submitted by warden pauliax in https://github.com/code-423n4/2022-04-backd-findings/issues/173, duplicate of https://github.com/code-423n4/2022-04-backd-findings/issues/49.
function _decimalMultiplier(address token_) internal view returns (uint256) { return 10**(18 - IERC20Full(token_).decimals()); }
#0 - JeeberC4
2022-05-13T19:33:42Z
Manually created json file
🌟 Selected for report: IllIllI
Also found by: 0v3rf10w, 0x52, 0xDjango, 0xkatana, Dravee, Funen, Kenshin, Ruhum, StyxRave, Tadashi, TerrierLover, TrungOre, antonttc, berndartmueller, catchup, csanuragjain, defsec, dipp, fatherOfBlocks, hake, horsefacts, hubble, jayjonah8, joestakey, kebabsec, kenta, m4rio_eth, oyc_109, pauliax, peritoflores, rayn, remora, robee, securerodd, simon135, sorrynotsorry, sseefried, z3s
169.5152 USDC - $169.52
Consider also introducing a _MAX_DELAY in Preparable contract to avoid situations when deadline cannot be bet due to unreasonable delay.
There are TODOs left in the code. While this does not cause any direct issue, it indicates a bad smell and uncertainty and makes it harder for an auditor to make assumptions:
// TODO: add constant gas consumed for transfer and tx prologue // TODO Add validation of curve pools // TODO Test validation
record.depositTokenBalance = uint128(totalLockAmount); position.totalTopUpAmount -= uint128(vars.totalActionTokenAmount); position.depositTokenBalance -= uint128(vars.depositAmountWithFees);
function _getDex(address token_) internal view returns (UniswapRouter02) { return swapViaUniswap[token_] ? _UNISWAP : _SUSHISWAP; }
While this may be an intended behavior, I think Uniswap is more widely used and supports more pairs, so it might be a good idea to default to it instead.
You may also consider a more universal approach, e.g. token to dex mapping and general adapter for each dex to support not just these 2 dexes, but have more flexibility.
function register in TopUpAction could have a max totalLockAmount parameter to let the users control the slippage especially when depositToken should be swapped to actionToken.
Tokens having more than 18 decimals are not supported, the calculation will revert here:
function _decimalMultiplier(address token_) internal view returns (uint256) { return 10**(18 - IERC20Full(token_).decimals()); }
payable(to).transfer(amount); payable(msg.sender).transfer(amount);
It is currently not recommended as recipients with custom fallback functions (smart contracts) will not be able to handle that. You can read more here: https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/
Solution (don't forget re-entrancy protection): https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol#L53-L59
using ScaledMath for uint128;
ScaledMath is written to support only uint256 type, so you shouldn't use it with lower types as they are automatically converted to uint256 and thus overflow/underflow might be escaped.
🌟 Selected for report: joestakey
Also found by: 0v3rf10w, 0x1f8b, 0x4non, 0xDjango, 0xNazgul, 0xkatana, 0xmint, Dravee, Funen, IllIllI, MaratCerby, NoamYakov, Tadashi, TerrierLover, Tomio, WatchPug, catchup, defsec, fatherOfBlocks, hake, horsefacts, kenta, oyc_109, pauliax, rayn, rfa, robee, saian, securerodd, simon135, slywaters, sorrynotsorry, tin537, z3s
89.3504 USDC - $89.35
constructor(IController _controller) Authorization(_controller.addressProvider().getRoleManager()) { require(address(_controller) != address(0), Error.ZERO_ADDRESS_NOT_ALLOWED);
controller.inflationManager() in practice can only be set once, so you can cache this value in the constructor of the StakerVault (require that it not empty address(0)). controller.addressProvider() is set in the constructor, so you could cache this too to avoid gas consuming external calls.
Here you can just return the boolean condition:
return _swapperImplementations[fromToken][toToken] != address(0) ? true : false;
Is the same as:
return _swapperImplementations[fromToken][toToken] != address(0);
stakerVault.stake(depositAmount); stakerVault.increaseActionLockedBalance(payer, depositAmount);
There are some massive structs, e.g. ExecuteLocalVars. Gas efficiency can be achieved by tightly packing the struct. Struct variables are stored in 32 bytes each so you can group smaller types to occupy less storage. You can read more here: https://fravoll.github.io/solidity-patterns/tight_variable_packing.html or in the official documentation: https://docs.soliditylang.org/en/v0.4.21/miscellaneous.html
To optimize function unstakeAndRedeem for gas usage, I would refactor it to something like this:
unchecked { if (lpBalance_ < redeemLpTokens) { staker.unstakeFor(msg.sender, msg.sender, redeemLpTokens - lpBalance_); } } uint256 lpBalance_ = lpToken.balanceOf(msg.sender); require(lpBalance_ >= redeemLpTokens, Error.INSUFFICIENT_BALANCE); return redeem(redeemLpTokens, minRedeemAmount);