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: 42/120
Findings: 2
Award: $67.72
π 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
46.2693 USDC - $46.27
Issue | Instances | |
---|---|---|
1 | Setters should check the input value | 1 |
2 | Immutable addresses lack ZERO-ADDRESS check | 3 |
3 | Non-library/interface files should use fixed compiler versions, not floating ones | 7 |
Setters should check the input value - ie make revert if it is the zero address or zero
Low
Instances include:
FraxlendPairCore.sol
function setTimeLock(address _newAddress)
Add non-zero checks - address - to the setters aforementioned.
Constructors should check the address written in an immutable address variable is not the zero address
Low
Instances include:
FraxlendPairCore.sol
CIRCUIT_BREAKER_ADDRESS = _circuitBreaker;
COMPTROLLER_ADDRESS = _comptrollerAddress;
FRAXLEND_WHITELIST_ADDRESS = _fraxlendWhitelistAddress;
assetContract = IERC20(_asset);
collateralContract = IERC20(_collateral);
Add zero address checks inside the constructor for the instances mentioned above.
Contracts should be deployed with the same compiler version and flags that they have been tested with thoroughly. Locking the pragma helps to ensure that contracts do not accidentally get deployed using, for example, an outdated compiler version that might introduce bugs that affect the contract system negatively.
check this source : https://swcregistry.io/docs/SWC-103
Low
All contracts use a floating solidity version
pragma solidity ^0.8.15;
Lock the pragma version to the same version as used in the other contracts and also consider known bugs (https://github.com/ethereum/solidity/releases) for the compiler version that is chosen.
Pragma statements can be allowed to float when a contract is intended for consumption by other developers, as in the case with contracts in a library or EthPM package. Otherwise, the developer would need to manually update the pragma in order to compile it locally.
π 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.4538 USDC - $21.45
Issue | Instances | |
---|---|---|
1 | Multiple address mappings can be combined into a single mapping of an address to a struct | 2 |
2 | Use custom errors rather than require()/revert() strings to save deployments gas | 9 |
3 | Splitting require() statements that uses && saves gas | 3 |
4 | require()/revert() strings longer than 32 bytes cost extra gas | 8 |
5 | Use calldata instead of memory for function parameters type | 5 |
6 | It costs more gas to initialize variables to zero than to let the default of zero be applied | 14 |
7 | Use of ++i cost less gas than i++/i=i+1 in for loops | 9 |
8 | ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as in the case when used in for & while loops | 8 |
9 | X += Y/X -= Y costs more gas than X = X + Y/X = X - Y for state variables | 6 |
10 | Functions guaranteed to revert when called by normal users can be marked payable | 9 |
Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.
There are 2 instances of this issue :
File: contracts/FraxlendPairCore.sol 127 mapping(address => uint256) public userCollateralBalance; 129 mapping(address => uint256) public userBorrowShares;
These mappings could be refactored into the following struct and mapping for example :
struct UserInfo { uint256 collateralBalance; uint256 borrowShares; } mapping(address => UserInfo) public usersInfo;
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met) while providing the same amount of information.
There are 9 instances of this issue :
File: contracts/FraxlendPairDeployer.sol 205 require(deployedPairsBySalt[salt] == address(0), "FraxlendPairDeployer: Pair already deployed"); 228 require(_pairAddress != address(0), "FraxlendPairDeployer: create2 failed"); 253 require(deployedPairsByName[_name] == address(0), "FraxlendPairDeployer: Pair name must be unique"); 365 require(_maxLTV <= GLOBAL_MAX_LTV, "FraxlendPairDeployer: _maxLTV is too large"); 367 require(IFraxlendWhitelist(FRAXLEND_WHITELIST_ADDRES).fraxlendDeployerWhitelist(msg.sender), "FraxlendPairDeployer: Only whitelisted addresses"); 399 require(msg.sender == CIRCUIT_BREAKER_ADDRESS, "Circuit Breaker only"); File: contracts/LinearInterestRate.sol 57 require(_minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT, "LinearInterestRate: _minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT"); 61 require(_maxInterest <= MAX_INT && _vertexInterest <= _maxInterest && _maxInterest > MIN_INT, "LinearInterestRate: _maxInterest <= MAX_INT && _vertexInterest <= _maxInterest && _maxInterest > MIN_INT"); 65 require(_vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0, "LinearInterestRate: _vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0");
There are 3 instances of this issue :
File: contracts/LinearInterestRate.sol 57 require(_minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT, "LinearInterestRate: _minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT"); 61 require(_maxInterest <= MAX_INT && _vertexInterest <= _maxInterest && _maxInterest > MIN_INT, "LinearInterestRate: _maxInterest <= MAX_INT && _vertexInterest <= _maxInterest && _maxInterest > MIN_INT"); 65 require(_vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0, "LinearInterestRate: _vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0");
Each extra memory word of bytes past the original 32 incurs an MSTORE which costs 3 gas.
There are 8 instances of this issue :
File: contracts/FraxlendPairDeployer.sol 205 require(deployedPairsBySalt[salt] == address(0), "FraxlendPairDeployer: Pair already deployed"); 228 require(_pairAddress != address(0), "FraxlendPairDeployer: create2 failed"); 253 require(deployedPairsByName[_name] == address(0), "FraxlendPairDeployer: Pair name must be unique"); 365 require(_maxLTV <= GLOBAL_MAX_LTV, "FraxlendPairDeployer: _maxLTV is too large"); 367 require(IFraxlendWhitelist(FRAXLEND_WHITELIST_ADDRES).fraxlendDeployerWhitelist(msg.sender), "FraxlendPairDeployer: Only whitelisted addresses"); File: contracts/LinearInterestRate.sol 57 require(_minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT, "LinearInterestRate: _minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT"); 61 require(_maxInterest <= MAX_INT && _vertexInterest <= _maxInterest && _maxInterest > MIN_INT, "LinearInterestRate: _maxInterest <= MAX_INT && _vertexInterest <= _maxInterest && _maxInterest > MIN_INT"); 65 require(_vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0, "LinearInterestRate: _vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0");
If a reference type function parameter is read-only, it is cheaper in gas to use calldata instead of memory. Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory.
There are 5 instances of this issue:
File: contracts/FraxlendPairCore.sol 1062 function leveragedPosition(address _swapperAddress, uint256 _borrowAmount, uint256 _initialCollateralAmount, uint256 _amountCollateralOutMin, address[] memory _path) File: contracts/FraxlendPairDeployer.sol 242 function _deploySecond(string memory _name, address _pairAddress, bytes memory _configData, address[] memory _approvedBorrowers, address[] memory _approvedLenders) private 272 function _logDeploy(string memory _name, address _pairAddress, bytes memory _configData, uint256 _maxLTV, uint256 _liquidationFee, uint256 _maturityDate) private 355 function deployCustom(string memory _name, bytes memory _configData, uint256 _maxLTV, uint256 _liquidationFee, uint256 _maturityDate, uint256 _penaltyRate, address[] memory _approvedBorrowers, address[] memory _approvedLenders) external returns (address _pairAddres) 398 function globalPause(address[] memory _addresses) external returns (address[] memory _updatedAddresses)
If a variable is not set/initialized, it is assumed to have the default value (0 for uint or int, false for bool, address(0) for addressβ¦). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
There are 14 instances of this issue:
File: contracts/libraries/SafeERC20.sol 22 uint8 i = 0; 27 for (i = 0; i < 32 && data[i] != 0; i++) File: contracts/FraxlendPair.sol 289 for (uint256 i = 0; i < _lenders.length; i++) 308 for (uint256 i = 0; i < _borrowers.length; i++) File: contracts/FraxlendPairConstants.sol 47 uint16 internal constant DEFAULT_PROTOCOL_FEE = 0; File: contracts/FraxlendPairCore.sol 265 for (uint256 i = 0; i < _approvedBorrowers.length; ++i) 270 for (uint256 i = 0; i < _approvedLenders.length; ++i) File: contracts/FraxlendPairDeployer.sol 127 for (i = 0; i < _lengthOfArray; ) 152 for (i = 0; i < _lengthOfArray; ) 402 for (uint256 i = 0; i < _lengthOfArray; ) File: contracts/FraxlendWhitelist.sol 51 for (uint256 i = 0; i < _addresses.length; i++) 66 for (uint256 i = 0; i < _addresses.length; i++) 81 for (uint256 i = 0; i < _addresses.length; i++) File: contracts/LinearInterestRate.sol 33 uint256 private constant MIN_INT = 0;
There are 9 instances of this issue:
File: contracts/libraries/SafeERC20.sol 27 for (i = 0; i < 32 && data[i] != 0; i++) File: contracts/FraxlendPair.sol 289 for (uint256 i = 0; i < _lenders.length; i++) 308 for (uint256 i = 0; i < _borrowers.length; i++) File: contracts/FraxlendPairDeployer.sol 138 i++; 158 i++; 408 i = i + 1; File: contracts/FraxlendWhitelist.sol 51 for (uint256 i = 0; i < _addresses.length; i++) 66 for (uint256 i = 0; i < _addresses.length; i++) 81 for (uint256 i = 0; i < _addresses.length; i++)
There are 8 instances of this issue:
File: contracts/libraries/SafeERC20.sol 27 for (i = 0; i < 32 && data[i] != 0; i++) File: contracts/FraxlendPair.sol 289 for (uint256 i = 0; i < _lenders.length; i++) 308 for (uint256 i = 0; i < _borrowers.length; i++) File: contracts/FraxlendPairCore.sol 265 for (uint256 i = 0; i < _approvedBorrowers.length; ++i) 270 for (uint256 i = 0; i < _approvedLenders.length; ++i) File: contracts/FraxlendWhitelist.sol 51 for (uint256 i = 0; i < _addresses.length; i++) 66 for (uint256 i = 0; i < _addresses.length; i++) 81 for (uint256 i = 0; i < _addresses.length; i++)
There are 6 instances of this issue:
File: contracts/FraxlendPairCore.sol 724 userBorrowShares[msg.sender] += _sharesAdded; 772 userCollateralBalance[_borrower] += _collateralAmount; 773 totalCollateral += _collateralAmount; 813 userCollateralBalance[_borrower] -= _collateralAmount; 815 totalCollateral -= _collateralAmount; 867 userBorrowShares[_borrower] -= _shares;
If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for the owner because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are :
CALLVALUE(gas=2), DUP1(gas=3), ISZERO(gas=3), PUSH2(gas=3), JUMPI(gas=10), PUSH1(gas=3), DUP1(gas=3), REVERT(gas=0), JUMPDEST(gas=1), POP(gas=2). Which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost
There are 9 instances of this issue:
File: contracts/FraxlendPair.sol 204 function setTimeLock(address _newAddress) external onlyOwner 234 function withdrawFees(uint128 _shares, address _recipient) external onlyOwner returns (uint256 _amountToTransfer) 274 function setSwapper(address _swapper, bool _approval) external onlyOwner 288 function setApprovedLenders(address[] calldata _lenders, bool _approval) external approvedLender(msg.sender) 307 function setApprovedBorrowers(address[] calldata _borrowers, bool _approval) external approvedBorrower 317 function pause() external 330 function unpause() external File: contracts/FraxlendWhitelist.sol 50 function setOracleContractWhitelist(address[] calldata _addresses, bool _bool) external onlyOwner 65 function setRateContractWhitelist(address[] calldata _addresses, bool _bool) external onlyOwner 80 function setFraxlendDeployerWhitelist(address[] calldata _addresses, bool _bool) external onlyOwner File: contracts/FraxlendPairDeployer.sol 170 function setCreationCode(bytes calldata _creationCode) external onlyOwner