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: 48/120
Findings: 2
Award: $67.18
🌟 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
45.8684 USDC - $45.87
 
I recommend adding check of 0-address for input validation of critical address parameters. Not doing so might lead to non-functional contract and have to redeploy the contract, when it is updated to 0-address accidentally.
Total of 10 instances found.
FraxlendPairCore.sol: "assetContract" address https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairCore.sol#L190
FraxlendPairCore.sol: "collateralContract" address https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairCore.sol#L191
FraxlendPairCore.sol: "CIRCUIT_BREAKER_ADDRESS" address https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairCore.sol#L172
FraxlendPairCore.sol: "COMPTROLLER_ADDRESS" address https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairCore.sol#L173
FraxlendPairCore.sol: "TIME_LOCK_ADDRESS" address https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairCore.sol#L174
FraxlendPairCore.sol: "FRAXLEND_WHITELIST" address https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairCore.sol#L175
FraxlendPairDeployer.sol: "FRAXLEND_WHITELIST_ADDRESS" address https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairDeployer.sol#L107
FraxlendPairDeployer.sol: "CIRCUIT_BREAKER_ADDRESS" address https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairDeployer.sol#L104
FraxlendPairDeployer.sol: "COMPTROLLER_ADDRESS" address https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairDeployer.sol#L105
FraxlendPairDeployer.sol: "TIME_LOCK_ADDRESS" address https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairDeployer.sol#L106
Add 0-address check for above addresses.
 
it is best practice to lock your pragma instead of using floating pragma. the use of floating pragma has a risk of accidentally get deployed using latest complier which may have higher risk of undiscovered bugs. Reference: https://consensys.github.io/smart-contract-best-practices/development-recommendations/solidity-specific/locking-pragmas/
./FraxlendPairDeployer.sol:2:pragma solidity ^0.8.15; ./VaultAccount.sol:2:pragma solidity ^0.8.15; ./SafeERC20.sol:2:pragma solidity ^0.8.15; ./FraxlendPair.sol:2:pragma solidity ^0.8.15; ./VariableInterestRate.sol:2:pragma solidity ^0.8.15; ./FraxlendWhitelist.sol:2:pragma solidity ^0.8.15; ./FraxlendPairConstants.sol:2:pragma solidity ^0.8.15; ./FraxlendPairCore.sol:2:pragma solidity ^0.8.15; ./LinearInterestRate.sol:2:pragma solidity ^0.8.15;
I suggest to lock your pragma and aviod using floating pragma.
// bad pragma solidity ^0.8.15; // good pragma solidity 0.8.15;
 
🌟 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.315 USDC - $21.31
 
The order of storage variables can be reordered in a way to reduce the usage amount of storage slots.
Reference from solidity documentation: Finally, in order to allow the EVM to optimize for this, ensure that you try to order your storage variables and struct members such that they can be packed tightly. For example, declaring your storage variables in the order of uint128, uint128, uint256 instead of uint128, uint256, uint128, as the former will only take up two slots of storage whereas the latter will take up three.
Total of 1 issue found.
Move 2 bool variabe at line 133 and 136 to bottom of address variable at line 86.
address public TIME_LOCK_ADDRESS; bool public immutable borrowerWhitelistActive; bool public immutable lenderWhitelistActive;
https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairCore.sol#L86 https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairCore.sol#L133 https://github.com/code-423n4/2022-08-frax/blob/c4189a3a98b38c8c962c5ea72f1a322fbc2ae45f/src/contracts/FraxlendPairCore.sol#L136
Reorder storage variables like shown in above PoC.
 
When there are multiple conditions in require statement, break down the require statement into multiple require statements instead of using && can save gas.
Total of 3 instances found.
./LinearInterestRate.sol:57: require(_minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT, "LinearInterestRate: _minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT"); ./LinearInterestRate.sol:61: require(_maxInterest <= MAX_INT && _vertexInterest <= _maxInterest && _maxInterest > MIN_INT, "LinearInterestRate: _maxInterest <= MAX_INT && _vertexInterest <= _maxInterest && _maxInterest > MIN_INT"); ./LinearInterestRate.sol:65: require(_vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0, "LinearInterestRate: _vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0");
Break down into several require statement. For example,
require(_vertexUtilization < MAX_VERTEX_UTIL); require(_vertexUtilization > 0, "LinearInterestRate: _vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0");
 
It is cheaper gas to use calldata than memory if the function parameter is read only. Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory. More details on following link. link: https://docs.soliditylang.org/en/v0.8.15/types.html#data-location
Total of 7 instances found.
./FraxlendPairDeployer.sol:310: function deploy(bytes memory _configData) external returns (address _pairAddress) { ./FraxlendPairDeployer.sol:356: string memory _name, ./FraxlendPairDeployer.sol:357: bytes memory _configData, ./FraxlendPairDeployer.sol:362: address[] memory _approvedBorrowers, ./FraxlendPairDeployer.sol:363: address[] memory _approvedLenders ./FraxlendPairDeployer.sol:398: function globalPause(address[] memory _addresses) external returns (address[] memory _updatedAddresses) { ./FraxlendPairCore.sol:1067: address[] memory _path
Change memory to calldata
 
Since EVM operates on 32 bytes at a time, if the element is smaller than that, the EVM must use more operations in order to reduce the elements from 32 bytes to specified size.
Reference: https://docs.soliditylang.org/en/v0.8.15/internals/layout_in_storage.html
Total of 25 instances found.
./SafeERC20.sol:22: uint8 i = 0; ./SafeERC20.sol:55: function safeDecimals(IERC20 token) internal view returns (uint8) { ./SafeERC20.sol:57: return success && data.length == 32 ? abi.decode(data, (uint8)) : 18; ./FraxlendPair.sol:84: function decimals() public pure override(ERC20, IERC20Metadata) returns (uint8) { ./FraxlendPair.sol:137: return type(uint128).max; ./FraxlendPair.sol:141: return type(uint128).max; ./FraxlendPair.sol:165: uint64 _DEFAULT_INT, ./FraxlendPair.sol:166: uint16 _DEFAULT_PROTOCOL_FEE, ./FraxlendPair.sol:211: event ChangeFee(uint32 _newFee); ./FraxlendPair.sol:215: function changeFee(uint32 _newFee) external whenNotPaused { ./VariableInterestRate.sol:63: function getNewRate(bytes calldata _data, bytes calldata _initData) external pure returns (uint64 _newRatePerSec) { ./FraxlendPairCore.sol:400: uint64 _newRate ./FraxlendPairCore.sol:415: uint64 _newRate ./FraxlendPairCore.sol:561: uint128 _amount, ./FraxlendPairCore.sol:562: uint128 _shares, ./FraxlendPairCore.sol:625: uint128 _amountToReturn, ./FraxlendPairCore.sol:626: uint128 _shares, ./FraxlendPairCore.sol:857: uint128 _amountToRepay, ./FraxlendPairCore.sol:858: uint128 _shares, ./FraxlendPairCore.sol:951: uint128 _sharesToLiquidate, ./FraxlendPairCore.sol:967: uint128 _borrowerShares = userBorrowShares[_borrower].toUint128(); ./FraxlendPairCore.sol:993: uint128 _amountLiquidatorToRepay = (_totalBorrow.toAmount(_sharesToLiquidate, true)).toUint128(); ./FraxlendPairCore.sol:997: uint128 _sharesToAdjust; ./FraxlendPairCore.sol:998: uint128 _amountToAdjust; ./LinearInterestRate.sol:76: function getNewRate(bytes calldata _data, bytes calldata _initData) external pure returns (uint64 _newRatePerSec) {
I suggest using uint256 instead of anything smaller or downcast where needed.
 
When variable is not initialized, it will have its default values. For example, 0 for uint, false for bool and address(0) for address. Reference: https://docs.soliditylang.org/en/v0.8.15/control-structures.html#scoping-and-declarations
Total of 14 instances found.
./FraxlendPairDeployer.sol:127: for (i = 0; i < _lengthOfArray; ) { ./FraxlendPairDeployer.sol:152: for (i = 0; i < _lengthOfArray; ) { ./FraxlendPairDeployer.sol:402: for (uint256 i = 0; i < _lengthOfArray; ) { ./SafeERC20.sol:22: uint8 i = 0; ./SafeERC20.sol:27: for (i = 0; i < 32 && data[i] != 0; i++) { ./FraxlendPair.sol:289: for (uint256 i = 0; i < _lenders.length; i++) { ./FraxlendPair.sol:308: for (uint256 i = 0; i < _borrowers.length; i++) { ./FraxlendWhitelist.sol:51: for (uint256 i = 0; i < _addresses.length; i++) { ./FraxlendWhitelist.sol:66: for (uint256 i = 0; i < _addresses.length; i++) { ./FraxlendWhitelist.sol:81: for (uint256 i = 0; i < _addresses.length; i++) { ./FraxlendPairConstants.sol:47: uint16 internal constant DEFAULT_PROTOCOL_FEE = 0; // 1e5 precision ./FraxlendPairCore.sol:265: for (uint256 i = 0; i < _approvedBorrowers.length; ++i) { ./FraxlendPairCore.sol:270: for (uint256 i = 0; i < _approvedLenders.length; ++i) { ./LinearInterestRate.sol:33: uint256 private constant MIN_INT = 0; // 0.00% annual rate
I suggest removing default value initialization. For example,
 
Prefix increments/decrements (++i or --i) costs cheaper gas than postfix increment/decrements (i++ or i--).
Total of 9 instances found.
./FraxlendPairDeployer.sol:130: i++; ./FraxlendPairDeployer.sol:158: i++; ./SafeERC20.sol:24: i++; ./SafeERC20.sol:27: for (i = 0; i < 32 && data[i] != 0; i++) { ./FraxlendPair.sol:289: for (uint256 i = 0; i < _lenders.length; i++) { ./FraxlendPair.sol:308: for (uint256 i = 0; i < _borrowers.length; i++) { ./FraxlendWhitelist.sol:51: for (uint256 i = 0; i < _addresses.length; i++) { ./FraxlendWhitelist.sol:66: for (uint256 i = 0; i < _addresses.length; i++) { ./FraxlendWhitelist.sol:81: for (uint256 i = 0; i < _addresses.length; i++) {
Change it to postfix increments/decrements. It saves 6 gas per loop.
 
There are function with empty blocks. These should do something like emit an event or reverting. If not it should be removed from the contract.
Total of 3 instances found.
./FraxlendPairDeployer.sol:406: } catch {} ./VariableInterestRate.sol:57: function requireValidInitData(bytes calldata _initData) external pure {} ./FraxlendWhitelist.sol:40: constructor() Ownable() {}
Add emit or revert in the function block.
 
Since each storage slot is size of 32 bytes, error messages that is longer than this will need extra storage slot leading to more gas cost.
Total of 8 instances found.
./FraxlendPairDeployer.sol:205: require(deployedPairsBySalt[salt] == address(0), "FraxlendPairDeployer: Pair already deployed"); ./FraxlendPairDeployer.sol:228: require(_pairAddress != address(0), "FraxlendPairDeployer: create2 failed"); ./FraxlendPairDeployer.sol:253: require(deployedPairsByName[_name] == address(0), "FraxlendPairDeployer: Pair name must be unique"); ./FraxlendPairDeployer.sol:365: require(_maxLTV <= GLOBAL_MAX_LTV, "FraxlendPairDeployer: _maxLTV is too large"); ./FraxlendPairDeployer.sol:366: require(IFraxlendWhitelist(FRAXLEND_WHITELIST_ADDRESS).fraxlendDeployerWhitelist(msg.sender), "FraxlendPairDeployer: Only whitelisted addresses"); ./LinearInterestRate.sol:57: require(_minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT, "LinearInterestRate: _minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT"); ./LinearInterestRate.sol:61: require(_maxInterest <= MAX_INT && _vertexInterest <= _maxInterest && _maxInterest > MIN_INT, "LinearInterestRate: _maxInterest <= MAX_INT && _vertexInterest <= _maxInterest && _maxInterest > MIN_INT"); ./LinearInterestRate.sol:65: require(_vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0, "LinearInterestRate: _vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0");
Simply keep the error messages within 32 bytes to avoid extra storage slot cost.
 
Custom errors from Solidity 0.8.4 are cheaper than revert strings. Details are explained here: https://blog.soliditylang.org/2021/04/21/custom-errors/
Total of 9 instances found.
./FraxlendPairDeployer.sol:205: require(deployedPairsBySalt[salt] == address(0), "FraxlendPairDeployer: Pair already deployed"); ./FraxlendPairDeployer.sol:228: require(_pairAddress != address(0), "FraxlendPairDeployer: create2 failed"); ./FraxlendPairDeployer.sol:253: require(deployedPairsByName[_name] == address(0), "FraxlendPairDeployer: Pair name must be unique"); ./FraxlendPairDeployer.sol:365: require(_maxLTV <= GLOBAL_MAX_LTV, "FraxlendPairDeployer: _maxLTV is too large"); ./FraxlendPairDeployer.sol:366: require(IFraxlendWhitelist(FRAXLEND_WHITELIST_ADDRESS).fraxlendDeployerWhitelist(msg.sender), "FraxlendPairDeployer: Only whitelisted addresses"); ./FraxlendPairDeployer.sol:399: require(msg.sender == CIRCUIT_BREAKER_ADDRESS, "Circuit Breaker only"); ./LinearInterestRate.sol:57: require(_minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT, "LinearInterestRate: _minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT"); ./LinearInterestRate.sol:61: require(_maxInterest <= MAX_INT && _vertexInterest <= _maxInterest && _maxInterest > MIN_INT, "LinearInterestRate: _maxInterest <= MAX_INT && _vertexInterest <= _maxInterest && _maxInterest > MIN_INT"); ./LinearInterestRate.sol:65: require(_vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0, "LinearInterestRate: _vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0");
I suggest implementing custom errors to save gas.