Platform: Code4rena
Start Date: 12/08/2022
Pot Size: $50,000 USDC
Total HM: 15
Participants: 120
Period: 5 days
Judge: Justin Goro
Total Solo HM: 6
Id: 153
League: ETH
Rank: 40/120
Findings: 2
Award: $68.69
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0x1f8b
Also found by: 0x52, 0xA5DF, 0xDjango, 0xNazgul, 0xNineDec, 0xSmartContract, 0xmatt, 0xsolstars, Aymen0909, Bnke0x0, CertoraInc, Chom, CodingNameKiki, Deivitto, Dravee, ElKu, EthLedger, Funen, IllIllI, JC, Junnon, Lambda, LeoS, MiloTruck, Noah3o6, PaludoX0, ReyAdmirado, Rohan16, RoiEvenHaim, Rolezn, SaharAP, Sm4rty, SooYa, The_GUILD, TomJ, Waze, Yiko, _Adam, __141345__, a12jmx, ak1, asutorufos, auditor0517, ayeslick, ballx, beelzebufo, berndartmueller, bin2chen, brgltd, c3phas, cRat1st0s, cccz, cryptonue, cryptphi, d3e4, delfin454000, dipp, djxploit, durianSausage, dy, erictee, fatherOfBlocks, gogo, gzeon, hyh, ignacio, kyteg, ladboy233, medikko, mics, minhquanym, oyc_109, pfapostol, rbserver, reassor, ret2basic, robee, sach1r0, simon135, sryysryy, tabish, yac, yash90, zzzitron
47.519 USDC - $47.52
checking for address 0 is a best practice for avoiding some error because of 0 value parameters. Recalling the recent hack event <a href=https://twitter.com/samczsun/status/1554252024723546112>Nomad hack</a>, in some case not checking for 0 value can be fatal.
Instance: FraxlendPairCore.sol
FraxlendPairCore::constructor() affected variable: FraxlendPairCore::{ CIRCUIT_BREAKER_ADDRESS, COMPTROLLER_ADDRESS, TIME_LOCK_ADDRESS, FRAXLEND_WHITELIST_ADDRESS, assetContract, collateralContract, oracleMultiply, oracleDivide, rateContract } https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairCore.sol#L151-L237
FraxlendPairCore::initialize() affected variable: FraxlendPairCore::{ approvedBorrowers, (cannot added new if not filled first) approvedLenders (cannot added new if not filled first) } https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairCore.sol#L265-L272
FraxlendPairDeployer
FraxlendPairDeployer::constructor() affected variable FraxlendPaitDeployer::{ CIRCUIT_BREAKER_ADDRESS, COMPTROLLER_ADDRESS, TIME_LOCK_ADDRESS, FRAXLEND_WHITELIST_ADDRESS } https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairDeployer.sol#L98-L108
FraxlendPairDeployer::deploy() affected variable: FraxlendPairDeployer::deploy::{ _asset, _collateral, _rateContract } https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairDeployer.sol#L310-L343
NOTE:
actually FraxlendPairDeployer
should deploy FraxlendPair
which trigger FraxlendPairCore::{constructor, initialize}. both FraxlendPairDeployer and FraxlendPairCore have lack of checking the address 0
MITIGATION: set the address(0) check
if(addr == address(0)) revert AddressZero();
unused function parameters can make people confused while reading the code, add a comment for it will be explain why the parameters are unused.
instance: VariableInterestRate.sol VariableInterestRate::getNewRate()::_initData https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/VariableInterestRate.sol#L63
Mitigation: Make it exist inside the function and give it comment
function getNewRate(bytes calldata _data, bytes calldata _initData) external pure returns (uint64 _newRatePerSec) { _initData; //give your comment here ...//the next implementation }
it's a best practice to avoid variable collisions but since the instances are external view
function, it can be non-critical.
instance:
FraxlendPair.sol
1.FraxlendPair::maxWithdraw()
affected variable:
Ownable::owner
function maxWithdraw(address owner) external view returns (uint256) { return totalAsset.toAmount(balanceOf(owner), false); }
https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPair.sol#L144-L146 2. FraxlendPair::maxRedeem() affected variable: Ownable::owner
function maxRedeem(address owner) external view returns (uint256) { return balanceOf(owner); }
MITIGATION:
change the parameters name to _owner
since frax contracts is implemented openzeppelin's Ownable contract, owner can transfer the owner
role with one-step function which may have a risk for transfering ownership to invalid account.
instance: FraxlendPairCore
46 abstract contract FraxlendPairCore is FraxlendPairConstants, IERC4626, ERC20, Ownable, Pausable, ReentrancyGuard {
FraxlendPairDeployer
44 contract FraxlendPairDeployer is Ownable {
FraxlendPairWhitelist
30 contract FraxlendWhitelist is Ownable {
MITIGATION: I recommend to make ownership transfer to be two-phase, one for set the next owner, one to execute the transfership. Example
address pendingOwner; function setPendingOwner(address _pendingOwner) external OnlyOwner{ if(_pendingOwner == address(0)) revert AddressZero(); pendingOwner = _pendingOwner; emit ChangePendingOwner(); } function transferOwnership(address _nextOwner) external OnlyOwner{ if(_nextOwner != pendingOwner) revert InvalidPendingOwner(); _transferOwnership(_nextOwner); }
NOTE: if you considering this don't forget to change deployer contract too (FraxlendPairDeployer::_deploySecond).
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0xA5DF, 0xDjango, 0xNazgul, 0xSmartContract, 0xackermann, 0xbepresent, 0xc0ffEE, 0xkatana, 2997ms, Amithuddar, Aymen0909, Bnke0x0, Chinmay, Chom, CodingNameKiki, Deivitto, Diraco, Dravee, ElKu, EthLedger, Fitraldys, Funen, IgnacioB, JC, Junnon, Lambda, LeoS, Metatron, MiloTruck, Noah3o6, NoamYakov, PaludoX0, Randyyy, ReyAdmirado, Rohan16, Rolezn, Ruhum, SaharAP, Sm4rty, SooYa, TomJ, Tomio, Waze, Yiko, _Adam, __141345__, a12jmx, ajtra, ak1, asutorufos, ballx, brgltd, c3phas, cRat1st0s, carlitox477, chrisdior4, d3e4, delfin454000, dharma09, djxploit, durianSausage, erictee, fatherOfBlocks, find_a_bug, flyx, francoHacker, gerdusx, gogo, gzeon, hakerbaya, ignacio, jag, kyteg, ladboy233, ltyu, m_Rassska, medikko, mics, mrpathfindr, newfork01, nxrblsrpr, oyc_109, pfapostol, rbserver, reassor, ret2basic, robee, sach1r0, saian, simon135, sryysryy, zeesaw
21.1709 USDC - $21.17
Loop is a costly operation that will consume a lot of gas, we can make it cheaper with several small changes in the operation. This is what we should do to reduce gas consumtion in loop operation:
uint
have a default value 0, passing it with 0 value just consume gas for nothing.
Exampleuint i ; // is cheaper than uint i = 0;
array.length
for loop, instead pass it to new variable. array.length
will cost gas for every reading, in loop case it will read every loop step, for avoid that pass array.length
in variable first.
Example//expensive version for (uint i; i < array.length ) {} //cheaper version uint length = array.length; for (uint i; i < length ){}
++i
instead of i++
, because ++i
is cheaper.unchecked
for operation increment of index
Example//expensive version for (uint i; i < length; i++){ ...//implementation } //cheaper version for (uint i; i < length) { ... // implementation unchecked { ++i; } }
Instance: note: external view function now using gas for operation but if dev want it still better to fix it for cleaner and consistent code.
FraxlendPair.sol
FraxlendPairCore.sol
FraxlendPairDeployer.sol
FraxlendPairWhitelist.sol
constants
Marking a variable as constants
can make save gas and make sure the value never changed by any activities of contract, and it's now using a storage slot.
Instance: FraxlendDeployer.sol::FraxlendDeployer::{DEFAULT_MAX_LTV, GLOBAL_MAX_LTV, DEFAULT_LIQ_FEE} https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairDeployer.sol#L49-L51
constructor
can be marked as immutableImmutable variable more cheaper that the default one, it can make sure that the variable cannot changed too.
Instance: FraxlendDeployer.sol::FraxlendDeployer::{CIRCUIT_BREAKER_ADDRESS, COMPTROLLER_ADDRESS, TIME_LOCK_ADDRESS} https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairDeployer.sol#L57-L59
Importing an unused file will be cost gas for deploying the contract. So it's better get rid all unused import.
Instance: FraxlendPair.sol
import "./interfaces/IERC4626.sol"; import "./interfaces/IFraxlendWhitelist.sol"; import "./interfaces/IRateCalculator.sol"; import "./interfaces/ISwapper.sol";
for saving gas when transaction is reverted, revert
must be placed as early as possible, in some instance it can be checked first before everything else.
Instance: FraxlendPair.sol FraxlendPair::withdrawFees()
//reason 255 // Effects: write to states 256 // NOTE: will revert if _shares > balanceOf(address(this)) 257 _burn(address(this), _shares); //_shares > balanceOf(address(this)) can be checked in the begining of function
FraxlendPairCore.sol FraxlendPairCore::{leveragePosition, repayAssetWithCollateral}
///leveragePosition if (!swappers[_swapperAddress]) { revert BadSwapper(); } if (_path[0] != address(_assetContract)) { revert InvalidPath(address(_assetContract), _path[0]); } if (_path[_path.length - 1] != address(_collateralContract)) { revert InvalidPath(address(_collateralContract), _path[_path.length - 1]); }
///repayAssetWithCollateral if (!swappers[_swapperAddress]) { revert BadSwapper(); } if (_path[0] != address(_collateralContract)) { revert InvalidPath(address(_collateralContract), _path[0]); } if (_path[_path.length - 1] != address(_assetContract)) { revert InvalidPath(address(_assetContract), _path[_path.length - 1]); }
https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairCore.sol#L1083-L1091 can be checked before
_addInterest(); _updateExchangeRate();
in both function