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: 47/120
Findings: 2
Award: $67.20
🌟 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.8345 USDC - $45.83
FraxlendPair.sol, 200, b' event SetTimeLock(address _oldAddress, address _newAddress);' FraxlendPair.sol, 211, b' event ChangeFee(uint32 _newFee);' FraxlendPair.sol, 228, b' event WithdrawFees(uint128 _shares, address _recipient, uint256 _amountToTransfer);' FraxlendPair.sol, 268, b' event SetSwapper(address _swapper, bool _approval);' FraxlendPairCore.sol, 382, b' event AddInterest(\n uint256 _interestEarned,\n uint256 _rate,\n uint256 _deltaTime,\n uint256 _feesAmount,\n uint256 _feesShare\n );' FraxlendPairCore.sol, 389, b' event UpdateRate(uint256 _ratePerSec, uint256 _deltaTime, uint256 _utilizationRate, uint256 _newRatePerSec);' FraxlendPairCore.sol, 504, b' event UpdateExchangeRate(uint256 _rate);'
TRANSFER()/TRANSFERFROM() NOT CHECKED Not all IERC20 implementations revert() when there’s a failure in transfer()/transferFrom(). The function signature has a boolean return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually making a payment
FraxlendPair.sol, 261, b' assetContract.safeTransfer(_recipient, _amountToTransfer);' FraxlendPairCore.sol, 574, b' assetContract.safeTransferFrom(msg.sender, address(this), _amount);' FraxlendPairCore.sol, 651, b' assetContract.safeTransfer(_receiver, _amountToReturn);' FraxlendPairCore.sol, 728, b' assetContract.safeTransfer(_receiver, _borrowAmount);' FraxlendPairCore.sol, 777, b' collateralContract.safeTransferFrom(_sender, address(this), _collateralAmount);' FraxlendPairCore.sol, 819, b' collateralContract.safeTransfer(_receiver, _collateralAmount);' FraxlendPairCore.sol, 872, b' assetContract.safeTransferFrom(_payer, address(this), _amountToRepay);' FraxlendPairDeployer.sol, 262, b' _fraxlendPair.transferOwnership(COMPTROLLER_ADDRESS);' libraries/SafeERC20.sol, 65, b' OZSafeERC20.safeTransfer(token, to, value);' libraries/SafeERC20.sol, 74, b' OZSafeERC20.safeTransferFrom(token, from, to, value);'
_mint() is discouraged in favor of _safeMint() which ensures that the recipient is either an EOA or implements IERC721Receiver. Both open OpenZeppelin and solmate have versions of this function so that NFTs aren’t lost if they’re minted to contracts that cannot transfer them back out.
FraxlendPairCore.sol, 487, b' _mint(address(this), _feesShare);' FraxlendPairCore.sol, 570, b' _mint(_receiver, _shares);'
Not all IERC20 implementations revert() when there’s a failure in approve(). The function signature has a boolean return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually approving anything
FraxlendPairCore.sol, 1103, b' _assetContract.approve(_swapperAddress, _borrowAmount);' FraxlendPairCore.sol, 1184, b' _collateralContract.approve(_swapperAddress, _collateralToSwap);'
If the caller makes a copy-paste error, the lengths may be mismatchd and an operation believed to have been completed may not in fact have been completed function withdrawMultipleERC721(address[] memory _tokens, uint256[] memory _tokenId, address _to) external override {
FraxlendPairCore.sol, 285, b' function initialize(\n string calldata _name,\n address[] calldata _approvedBorrowers,\n address[] calldata _approvedLenders,\n bytes calldata _rateInitCallData\n ) external ' FraxlendPairDeployer.sol, 388, b' function deployCustom(\n string memory _name,\n bytes memory _configData,\n uint256 _maxLTV,\n uint256 _liquidationFee,\n uint256 _maturityDate,\n uint256 _penaltyRate,\n address[] memory _approvedBorrowers,\n address[] memory _approvedLenders\n ) external returns (address _pairAddress) '
🌟 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.3715 USDC - $21.37
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, as explained https://blog.soliditylang.org/2021/04/21/custom-errors/. Custom errors are defined using the error statement.
FraxlendPairDeployer.sol, 205, b' require(deployedPairsBySalt[salt] == address(0), "FraxlendPairDeployer: Pair already deployed");' FraxlendPairDeployer.sol, 228, b' require(_pairAddress != address(0), "FraxlendPairDeployer: create2 failed");' FraxlendPairDeployer.sol, 253, b' require(deployedPairsByName[_name] == address(0), "FraxlendPairDeployer: Pair name must be unique");' FraxlendPairDeployer.sol, 365, b' require(_maxLTV <= GLOBAL_MAX_LTV, "FraxlendPairDeployer: _maxLTV is too large");' FraxlendPairDeployer.sol, 366, b' require(' FraxlendPairDeployer.sol, 399, b' require(msg.sender == CIRCUIT_BREAKER_ADDRESS, "Circuit Breaker only");' LinearInterestRate.sol, 57, b' require(' LinearInterestRate.sol, 61, b' require(' LinearInterestRate.sol, 65, b' require('
0 is less gas efficient than !0 if you enable the optimizer at 10k AND you’re in a require statement. Detailed explanation with the opcodes https://twitter.com/gzeon/status/1485428085885640706
FraxlendPairCore.sol, 477, b' if (_currentRateInfo.feeToProtocolRate > 0) {' FraxlendPairCore.sol, 754, b' if (_collateralAmount > 0) {' FraxlendPairCore.sol, 835, b' if (userBorrowShares[msg.sender] > 0) {' FraxlendPairCore.sol, 1002, b' if (_leftoverBorrowShares > 0) {' FraxlendPairCore.sol, 1094, b' if (_initialCollateralAmount > 0) {' LinearInterestRate.sol, 66, b' _vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0,'
prefix increment ++i is more cheaper than postfix i++
FraxlendPair.sol, 289, b' for (uint256 i = 0; i < _lenders.length; i++) {' FraxlendPair.sol, 308, b' for (uint256 i = 0; i < _borrowers.length; i++) {' FraxlendPairDeployer.sol, 130, b' i++;' FraxlendPairDeployer.sol, 158, b' i++;' FraxlendWhitelist.sol, 51, b' for (uint256 i = 0; i < _addresses.length; i++) {' FraxlendWhitelist.sol, 66, b' for (uint256 i = 0; i < _addresses.length; i++) {' FraxlendWhitelist.sol, 81, b' for (uint256 i = 0; i < _addresses.length; i++) {' libraries/SafeERC20.sol, 24, b' i++;' libraries/SafeERC20.sol, 27, b' for (i = 0; i < 32 && data[i] != 0; i++) {'
FraxlendPairCore.sol, 773, b' totalCollateral += _collateralAmount;' FraxlendPairCore.sol, 815, b' totalCollateral -= _collateralAmount;'
// Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled.
FraxlendPairCore.sol, 78, b' mapping(address => bool) public swappers; // approved swapper addresses' FraxlendPairCore.sol, 134, b' mapping(address => bool) public approvedBorrowers;' FraxlendPairCore.sol, 137, b' mapping(address => bool) public approvedLenders;' FraxlendPairDeployer.sol, 94, b' mapping(address => bool) public deployedPairCustomStatusByAddress;' FraxlendWhitelist.sol, 32, b' mapping(address => bool) public oracleContractWhitelist;' FraxlendWhitelist.sol, 35, b' mapping(address => bool) public rateContractWhitelist;' FraxlendWhitelist.sol, 38, b' mapping(address => bool) public fraxlendDeployerWhitelist;'
resign the default value to the variables will cost more gas.
FraxlendPair.sol, 289, b' for (uint256 i = 0; i < _lenders.length; i++) {' FraxlendPair.sol, 308, b' for (uint256 i = 0; i < _borrowers.length; i++) {' FraxlendPairCore.sol, 265, b' for (uint256 i = 0; i < _approvedBorrowers.length; ++i) {' FraxlendPairCore.sol, 270, b' for (uint256 i = 0; i < _approvedLenders.length; ++i) {' FraxlendPairDeployer.sol, 402, b' for (uint256 i = 0; i < _lengthOfArray; ) {' FraxlendWhitelist.sol, 51, b' for (uint256 i = 0; i < _addresses.length; i++) {' FraxlendWhitelist.sol, 66, b' for (uint256 i = 0; i < _addresses.length; i++) {' FraxlendWhitelist.sol, 81, b' for (uint256 i = 0; i < _addresses.length; i++) {' libraries/SafeERC20.sol, 22, b' uint8 i = 0;'
FraxlendPair.sol, 289, b' for (uint256 i = 0; i < _lenders.length; i++) {' FraxlendPair.sol, 308, b' for (uint256 i = 0; i < _borrowers.length; i++) {' FraxlendPairCore.sol, 265, b' for (uint256 i = 0; i < _approvedBorrowers.length; ++i) {' FraxlendPairCore.sol, 270, b' for (uint256 i = 0; i < _approvedLenders.length; ++i) {' FraxlendPairDeployer.sol, 130, b' i++;' FraxlendPairDeployer.sol, 158, b' i++;' FraxlendWhitelist.sol, 51, b' for (uint256 i = 0; i < _addresses.length; i++) {' FraxlendWhitelist.sol, 66, b' for (uint256 i = 0; i < _addresses.length; i++) {' FraxlendWhitelist.sol, 81, b' for (uint256 i = 0; i < _addresses.length; i++) {' libraries/SafeERC20.sol, 24, b' i++;' libraries/SafeERC20.sol, 27, b' for (i = 0; i < 32 && data[i] != 0; i++) {'
Use a solidity version of at least 0.8.2 to get simple compiler automatic inlining Use a solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than revert()/require() strings Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value
See this issue(https://github.com/code-423n4/2022-01-xdefi-findings/issues/128) which describes the fact that there is a larger deployment gas cost, but with enough runtime calls, the change ends up being cheaper
LinearInterestRate.sol, 60, b' require(\n _minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT,\n "LinearInterestRate: _minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT"\n );' LinearInterestRate.sol, 64, b' require(\n _maxInterest <= MAX_INT && _vertexInterest <= _maxInterest && _maxInterest > MIN_INT,\n "LinearInterestRate: _maxInterest <= MAX_INT && _vertexInterest <= _maxInterest && _maxInterest > MIN_INT"\n );' LinearInterestRate.sol, 68, b' require(\n _vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0,\n "LinearInterestRate: _vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0"\n );'
FraxlendPairCore.sol, 450, b' bytes memory _rateData = abi.encode(' FraxlendPairDeployer.sol, 213, b' abi.encode(' LinearInterestRate.sol, 47, b' return abi.encode(MIN_INT, MAX_INT, MAX_VERTEX_UTIL, UTIL_PREC);' VariableInterestRate.sol, 53, b' return abi.encode(MIN_UTIL, MAX_UTIL, UTIL_PREC, MIN_INT, MAX_INT, INT_HALF_LIFE);'
raxlendPair.sol, 146, b' function maxWithdraw(address owner) external view returns (uint256) ' FraxlendPair.sol, 150, b' function maxRedeem(address owner) external view returns (uint256) ' FraxlendPair.sol, 207, b' function setTimeLock(address _newAddress) external onlyOwner ' FraxlendPair.sol, 263, b' function withdrawFees(uint128 _shares, address _recipient) external onlyOwner returns (uint256 _amountToTransfer) ' FraxlendPair.sol, 277, b' function setSwapper(address _swapper, bool _approval) external onlyOwner ' FraxlendPair.sol, 328, b' function pause() external ' FraxlendPair.sol, 337, b' function unpause() external ' FraxlendPairCore.sol, 653, b' function _redeem(\n VaultAccount memory _totalAsset,\n uint128 _amountToReturn,\n uint128 _shares,\n address _receiver,\n address _owner\n ) internal ' FraxlendPairCore.sol, 669, b' function redeem(\n uint256 _shares,\n address _receiver,\n address _owner\n ) external nonReentrant returns (uint256 _amountToReturn) ' FraxlendPairCore.sol, 685, b' function withdraw(\n uint256 _amount,\n address _receiver,\n address _owner\n ) external nonReentrant returns (uint256 _shares) ' FraxlendPairDeployer.sol, 177, b' function setCreationCode(bytes calldata _creationCode) external onlyOwner ' FraxlendPairDeployer.sol, 263, b' function _deploySecond(\n string memory _name,\n address _pairAddress,\n bytes memory _configData,\n address[] memory _approvedBorrowers,\n address[] memory _approvedLenders\n ) private ' FraxlendWhitelist.sol, 55, b' function setOracleContractWhitelist(address[] calldata _addresses, bool _bool) external onlyOwner ' FraxlendWhitelist.sol, 70, b' function setRateContractWhitelist(address[] calldata _addresses, bool _bool) external onlyOwner ' FraxlendWhitelist.sol, 85, b' function setFraxlendDeployerWhitelist(address[] calldata _addresses, bool _bool) external onlyOwner '
If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it’s used, and not adding another entry to the method ID table
FraxlendPairCore.sol, 59, b' IERC20 public immutable collateralContract;' FraxlendPairCore.sol, 62, b' address public immutable oracleMultiply;' FraxlendPairCore.sol, 63, b' address public immutable oracleDivide;' FraxlendPairCore.sol, 64, b' uint256 public immutable oracleNormalization;' FraxlendPairCore.sol, 67, b' uint256 public immutable maxLTV;' FraxlendPairCore.sol, 70, b' uint256 public immutable cleanLiquidationFee;' FraxlendPairCore.sol, 71, b' uint256 public immutable dirtyLiquidationFee;' FraxlendPairCore.sol, 74, b' IRateCalculator public immutable rateContract; // For complex rate calculations' FraxlendPairCore.sol, 81, b' address public immutable DEPLOYER_ADDRESS;' FraxlendPairCore.sol, 84, b' address public immutable CIRCUIT_BREAKER_ADDRESS;' FraxlendPairCore.sol, 85, b' address public immutable COMPTROLLER_ADDRESS;' FraxlendPairCore.sol, 89, b' address public immutable FRAXLEND_WHITELIST_ADDRESS;' FraxlendPairCore.sol, 95, b' uint256 public immutable maturityDate;' FraxlendPairCore.sol, 96, b' uint256 public immutable penaltyRate;' FraxlendPairCore.sol, 133, b' bool public immutable borrowerWhitelistActive;' FraxlendPairCore.sol, 136, b' bool public immutable lenderWhitelistActive;'