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: 57/120
Findings: 2
Award: $67.02
🌟 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.8349 USDC - $45.83
approve() will fail for certain token implementations that do not return a boolean value (). Hence it is recommend to use safeApprove().
src/contracts/FraxlendPairCore.sol:1103: _assetContract.approve(_swapperAddress, _borrowAmount); src/contracts/FraxlendPairCore.sol:1184: _collateralContract.approve(_swapperAddress, _collateralToSwap);
Update to safeapprove in the function.
Recommend using fixed solidity version
src/contracts/FraxlendWhitelist.sol:2:pragma solidity ^0.8.15; src/contracts/FraxlendPairCore.sol:2:pragma solidity ^0.8.15; src/contracts/LinearInterestRate.sol:2:pragma solidity ^0.8.15; src/contracts/VariableInterestRate.sol:2:pragma solidity ^0.8.15; src/contracts/FraxlendPairConstants.sol:2:pragma solidity ^0.8.15; src/contracts/libraries/SafeERC20.sol:2:pragma solidity ^0.8.15; src/contracts/libraries/VaultAccount.sol:2:pragma solidity ^0.8.15; src/contracts/FraxlendPair.sol:2:pragma solidity ^0.8.15; src/contracts/FraxlendPairDeployer.sol:2: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.1859 USDC - $21.19
Reading array length at each iteration of the loop takes 6 gas (3 for mload and 3 to place memory_offset) in the stack. Caching the array length in the stack saves around 3 gas per iteration.
src/contracts/FraxlendWhitelist.sol:51: for (uint256 i = 0; i < _addresses.length; i++) { src/contracts/FraxlendWhitelist.sol:66: for (uint256 i = 0; i < _addresses.length; i++) { src/contracts/FraxlendWhitelist.sol:81: for (uint256 i = 0; i < _addresses.length; i++) {
src/contracts/FraxlendPairCore.sol:265: for (uint256 i = 0; i < _approvedBorrowers.length; ++i) { src/contracts/FraxlendPairCore.sol:270: for (uint256 i = 0; i < _approvedLenders.length; ++i) {
src/contracts/FraxlendPair.sol:289: for (uint256 i = 0; i < _lenders.length; i++) { src/contracts/FraxlendPair.sol:308: for (uint256 i = 0; i < _borrowers.length; i++) {
Here, I suggest storing the array’s length in a variable before the for-loop, and use it instead.
The unchecked keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas PER LOOP
src/contracts/FraxlendWhitelist.sol:51: for (uint256 i = 0; i < _addresses.length; i++) { src/contracts/FraxlendWhitelist.sol:66: for (uint256 i = 0; i < _addresses.length; i++) { src/contracts/FraxlendWhitelist.sol:81: for (uint256 i = 0; i < _addresses.length; i++) {
src/contracts/libraries/SafeERC20.sol:27: for (i = 0; i < 32 && data[i] != 0; i++) {
src/contracts/FraxlendPair.sol:289: for (uint256 i = 0; i < _lenders.length; i++) { src/contracts/FraxlendPair.sol:308: for (uint256 i = 0; i < _borrowers.length; i++) {
Use calldata instead of memory to save gas. See here for reference: https://code4rena.com/reports/2022-04-badger-citadel/#g-12-stakedcitadeldepositall-use-calldata-instead-of-memory
src/contracts/FraxlendPairDeployer.sol:310: function deploy(bytes memory _configData) external returns (address _pairAddress) { src/contracts/FraxlendPairDeployer.sol:398: function globalPause(address[] memory _addresses) external returns (address[] memory _updatedAddresses)
Shortening revert strings to fit in 32 bytes will decrease deployment time gas and will decrease runtime gas when the revert condition is met.
Revert strings that are longer than 32 bytes require at least one additional mstore, along with additional overhead for computing memory offset, etc.
57: require( 58: _minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT, 59: "LinearInterestRate: _minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT" 60: ); 61: require( 62: _maxInterest <= MAX_INT && _vertexInterest <= _maxInterest && _maxInterest > MIN_INT, 63: "LinearInterestRate: _maxInterest <= MAX_INT && _vertexInterest <= _maxInterest && _maxInterest > MIN_INT" 64: ); 65: require( 66: _vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0, 67: "LinearInterestRate: _vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0" );
src/contracts/FraxlendPairDeployer.sol:205: require(deployedPairsBySalt[salt] == address(0), "FraxlendPairDeployer: Pair already deployed"); src/contracts/FraxlendPairDeployer.sol:228: require(_pairAddress != address(0), "FraxlendPairDeployer: create2 failed"); src/contracts/FraxlendPairDeployer.sol:253: require(deployedPairsByName[_name] == address(0), "FraxlendPairDeployer: Pair name must be unique"); src/contracts/FraxlendPairDeployer.sol:365: require(_maxLTV <= GLOBAL_MAX_LTV, "FraxlendPairDeployer: _maxLTV is too large"); src/contracts/FraxlendPairDeployer.sol:366: require(IFraxlendWhitelist(FRAXLEND_WHITELIST_ADDRESS).fraxlendDeployerWhitelist(msg.sender), "FraxlendPairDeployer: Only whitelisted addresses");
I suggest shortening the revert strings to fit in 32 bytes, or that using custom errors as described next.****
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met)
Source Custom Errors in Solidity:
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met)
Starting from Solidity v0.8.4,there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors. Until now, you could already use strings to give more information about failures (e.g., revert("Insufficient funds.");),but they are rather expensive, especially when it comes to deploy cost, and it is difficult to use dynamic information in them.
Custom errors are defined using the error statement, which can be used inside and outside of contracts (including interfaces and libraries).
57: require( 58: _minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT, 59: "LinearInterestRate: _minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT" 60: ); 61: require( 62: _maxInterest <= MAX_INT && _vertexInterest <= _maxInterest && _maxInterest > MIN_INT, 63: "LinearInterestRate: _maxInterest <= MAX_INT && _vertexInterest <= _maxInterest && _maxInterest > MIN_INT" 64: ); 65: require( 66: _vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0, 67: "LinearInterestRate: _vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0" );
src/contracts/FraxlendPairDeployer.sol:205: require(deployedPairsBySalt[salt] == address(0), "FraxlendPairDeployer: Pair already deployed"); src/contracts/FraxlendPairDeployer.sol:228: require(_pairAddress != address(0), "FraxlendPairDeployer: create2 failed"); src/contracts/FraxlendPairDeployer.sol:253: require(deployedPairsByName[_name] == address(0), "FraxlendPairDeployer: Pair name must be unique"); src/contracts/FraxlendPairDeployer.sol:365: require(_maxLTV <= GLOBAL_MAX_LTV, "FraxlendPairDeployer: _maxLTV is too large"); src/contracts/FraxlendPairDeployer.sol:366: require(IFraxlendWhitelist(FRAXLEND_WHITELIST_ADDRESS).fraxlendDeployerWhitelist(msg.sender), "FraxlendPairDeployer: Only whitelisted addresses"); src/contracts/FraxlendPairDeployer.sol:399: require(msg.sender == CIRCUIT_BREAKER_ADDRESS, "Circuit Breaker only");
https://blog.soliditylang.org/2021/04/21/custom-errors/
I suggest replacing revert strings with custom errors.
If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for address…). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
We can use uint number;
instead of uint number = 0;
LinearInterestRate.sol:33 FraxlendPairConstants.sol:47 SafeERC20.sol:22
src/contracts/LinearInterestRate.sol:33: uint256 private constant MIN_INT = 0; // 0.00% annual rate src/contracts/FraxlendPairConstants.sol:47: uint16 internal constant DEFAULT_PROTOCOL_FEE = 0; // 1e5 precision src/contracts/libraries/SafeERC20.sol:22: uint8 i = 0;
I suggest removing explicit initializations for default values.
There are 6 instances of this issue: FraxlendPairCore.sol:450 LinearInterestRate.sol:47 VariableInterestRate.sol:53
src/contracts/FraxlendPairCore.sol:450: bytes memory _rateData = abi.encode( src/contracts/LinearInterestRate.sol:47: return abi.encode(MIN_INT, MAX_INT, MAX_VERTEX_UTIL, UTIL_PREC); src/contracts/VariableInterestRate.sol:53: return abi.encode(MIN_UTIL, MAX_UTIL, UTIL_PREC, MIN_INT, MAX_INT, INT_HALF_LIFE); src/contracts/FraxlendPairDeployer.sol:213: abi.encode(
src/contracts/FraxlendPairDeployer.sol:331: abi.encode(CIRCUIT_BREAKER_ADDRESS, COMPTROLLER_ADDRESS, TIME_LOCK_ADDRESS, FRAXLEND_WHITELIST_ADDRESS), src/contracts/FraxlendPairDeployer.sol:374: abi.encode(CIRCUIT_BREAKER_ADDRESS, COMPTROLLER_ADDRESS, TIME_LOCK_ADDRESS, FRAXLEND_WHITELIST_ADDRESS),