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: 15/120
Findings: 3
Award: $370.15
🌟 Selected for report: 0
🚀 Solo Findings: 0
249.5468 USDC - $249.55
Judge has assessed an item in Issue #345 as Medium risk. The relevant finding follows:
#0 - gititGoro
2022-10-15T10:41:32Z
Finding L-01 was deemed a medium severity risk item. The item was titled
#1 - gititGoro
2022-10-17T04:02:05Z
Duplicate of #129
🌟 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
99.4236 USDC - $99.42
TIME_LOCK_ADDRESS
modifications when the the contract FraxlendPair.sol
is pausedFor the contract FraxlendPair.sol
, the variable TIME_LOCK_ADDRESS
is only used inside changeFee()
.
changeFee
can be paused and disallow changes. However, TIME_LOCK_ADDRESS
can be modified at any time.
File: contracts/FraxlendPair.sol function setTimeLock(address _newAddress) external onlyOwner { emit SetTimeLock(TIME_LOCK_ADDRESS, _newAddress); TIME_LOCK_ADDRESS = _newAddress; } /// @notice The ```ChangeFee``` event first when the fee is changed /// @param _newFee The new fee event ChangeFee(uint32 _newFee); /// @notice The ```changeFee``` function changes the protocol fee, max 50% // @param _newFee The new fee function changeFee(uint32 _newFee) external whenNotPaused { if (msg.sender != TIME_LOCK_ADDRESS) revert OnlyTimeLock(); if (_newFee > MAX_PROTOCOL_FEE) { revert BadProtocolFee(); } currentRateInfo.feeToProtocolRate = _newFee; emit ChangeFee(_newFee); }
https://github.com/code-423n4/2022-08-frax/blob/main/src/contracts/FraxlendPair.sol#L204-L222
The whenNotPaused
modifier could be added into setTimeLock
to ensure TIME_LOCK_ADDRESS
is paused in synchrony with changeFee()
.
$ git diff --no-index FraxlendPair.sol.orig FraxlendPair.sol diff --git a/FraxlendPair.sol.orig b/FraxlendPair.sol index d54b8f5..1ae7fa0 100644 --- a/FraxlendPair.sol.orig +++ b/FraxlendPair.sol @@ -201,7 +201,7 @@ contract FraxlendPair is FraxlendPairCore { /// @notice The ```setTimeLock``` function sets the TIME_LOCK address /// @param _newAddress the new time lock address - function setTimeLock(address _newAddress) external onlyOwner { + function setTimeLock(address _newAddress) external onlyOwner whenNotPaused { emit SetTimeLock(TIME_LOCK_ADDRESS, _newAddress); TIME_LOCK_ADDRESS = _newAddress; }
Locking the pragma helps to ensure that contracts do not accidentally get deployed using an outdated compiler version.
Note that 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 a package.
There's 7 non library files with floating pragma.
https://github.com/code-423n4/2022-08-frax/blob/main/src/contracts/LinearInterestRate.sol#L2
https://github.com/code-423n4/2022-08-frax/blob/main/src/contracts/VariableInterestRate.sol#L2
https://github.com/code-423n4/2022-08-frax/blob/main/src/contracts/FraxlendWhitelist.sol#L2
https://github.com/code-423n4/2022-08-frax/blob/main/src/contracts/FraxlendPairConstants.sol#L2
https://github.com/code-423n4/2022-08-frax/blob/main/src/contracts/FraxlendPairCore.sol#L2
https://github.com/code-423n4/2022-08-frax/blob/main/src/contracts/FraxlendPair.sol#L2
https://github.com/code-423n4/2022-08-frax/blob/main/src/contracts/FraxlendPairDeployer.sol
File: contracts/FraxlendPairDeployer.sol function setCreationCode(bytes calldata _creationCode) external onlyOwner { bytes memory _firstHalf = BytesLib.slice(_creationCode, 0, 13000); contractAddress1 = SSTORE2.write(_firstHalf); if (_creationCode.length > 13000) { bytes memory _secondHalf = BytesLib.slice(_creationCode, 13000, _creationCode.length - 13000); contractAddress2 = SSTORE2.write(_secondHalf); } }
File: contracts/FraxlendPairCore.sol#L194 194: dirtyLiquidationFee = (_liquidationFee * 9000) / LIQ_PRECISION; // 90% of clean fee
https://github.com/code-423n4/2022-08-frax/blob/main/src/contracts/FraxlendPairCore.sol#L194
File: contracts/FraxlendPairDeployer.sol // Constants uint256 public DEFAULT_MAX_LTV = 75000; // 75% with 1e5 precision uint256 public GLOBAL_MAX_LTV = 1e8; // 1000x (100,000%) with 1e5 precision, protects from rounding errors in LTV calc uint256 public DEFAULT_LIQ_FEE = 10000; // 10% with 1e5 precision
https://github.com/code-423n4/2022-08-frax/blob/main/src/contracts/FraxlendPairDeployer.sol#L48-L51
Conditionals with and without lines breaks are being used interchangibily.
File: contracts/FraxlendPair.sol if (msg.sender != TIME_LOCK_ADDRESS) revert OnlyTimeLock(); if (_newFee > MAX_PROTOCOL_FEE) { revert BadProtocolFee(); }
https://github.com/code-423n4/2022-08-frax/blob/main/src/contracts/FraxlendPair.sol#L216-L219
The project should choose a coding style and use throughout the project.
E.g. always use line breaking:
if (msg.sender != TIME_LOCK_ADDRESS) { revert OnlyTimeLock(); } if (_newFee > MAX_PROTOCOL_FEE) { revert BadProtocolFee(); }
Or don't break the lines for conditionals bellow a threshold witdh (e.g. 80 or 100 chars):
if (msg.sender != TIME_LOCK_ADDRESS) revert OnlyTimeLock(); if (_newFee > MAX_PROTOCOL_FEE) revert BadProtocolFee();
_newFee
_newFee
cannot be larger than 50000, due to this conditional:
File: contracts/FraxlendPair.sol if (_newFee > MAX_PROTOCOL_FEE) { revert BadProtocolFee(); }
https://github.com/code-423n4/2022-08-frax/blob/main/src/contracts/FraxlendPair.sol#L217-L219
The variable can be declared with uint16 (max 65535) instead of uint32 (4294967295). Therefore, the following changes can be made.
$ git diff --no-index FraxlendPair.sol.orig FraxlendPair.sol diff --git a/FraxlendPair.sol.orig b/FraxlendPair.sol index d54b8f5..5114db2 100644 --- a/FraxlendPair.sol.orig +++ b/FraxlendPair.sol @@ -208,11 +208,11 @@ contract FraxlendPair is FraxlendPairCore { /// @notice The ```ChangeFee``` event first when the fee is changed /// @param _newFee The new fee - event ChangeFee(uint32 _newFee); + event ChangeFee(uint16 _newFee); /// @notice The ```changeFee``` function changes the protocol fee, max 50% /// @param _newFee The new fee - function changeFee(uint32 _newFee) external whenNotPaused { + function changeFee(uint16 _newFee) external whenNotPaused { if (msg.sender != TIME_LOCK_ADDRESS) revert OnlyTimeLock(); if (_newFee > MAX_PROTOCOL_FEE) { revert BadProtocolFee();
deployedPairsLength
instead of computing deployedPairsArray.length
multiple timesdeployedPairsArray.length
is computed twice in FraxlendPairDeployer.sol
.
File: contracts/FraxlendPairDeployer.sol 123: string[] memory _deployedPairsArray = deployedPairsArray; 124: uint256 _lengthOfArray = _deployedPairsArray.length;
File: contracts/FraxlendPairDeployer.sol 324: (deployedPairsArray.length + 1).toString()
https://github.com/code-423n4/2022-08-frax/blob/main/src/contracts/FraxlendPairDeployer.sol#L324
The contract has an extenal function that returns the length of the array.
File: contracts/FraxlendPairDeployer.sol function deployedPairsLength() external view returns (uint256) { return deployedPairsArray.length; }
I would recommend converting the deployedPairsLength()
into a public function and using it to compute the array length throughout the FraxlendPairDeployed
contract.
File: contracts/FraxlendPair.sol#L100 100: function totalAssets() public view virtual returns (uint256) {
https://github.com/code-423n4/2022-08-frax/blob/main/src/contracts/FraxlendPair.sol#L100
#0 - gititGoro
2022-10-06T10:11:47Z
Well done! This was a no-frills, high quality report. The first issue isn't valid. If changefee is locked when paused, you may very well wish to change the timelock address in the event that the old one was compromised.
The third last one about 16 vs 32 bit was low impact and fairly finicky but I awarded points because it's not technically unhelpful.
🌟 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.1815 USDC - $21.18
There are 9 instances of this issue.
File: src/contracts/FraxlendPair.sol 289: for (uint256 i = 0; i < _lenders.length; i++) { 308: for (uint256 i = 0; i < _borrowers.length; i++) {
File: src/contracts/FraxlendPairDeployer.sol 130: i++; 158: i++;
File: src/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: src/contracts/libraries/SafeERC20.sol 24: i++; 27: for (i = 0; i < 32 && data[i] != 0; i++) {
There are 8 instances of this issue.
File: src/contracts/FraxlendPair.sol 289: for (uint256 i = 0; i < _lenders.length; i++) { 308: for (uint256 i = 0; i < _borrowers.length; i++) {
File: src/contracts/FraxlendPairCore.sol 265: for (uint256 i = 0; i < _approvedBorrowers.length; ++i) { 270: for (uint256 i = 0; i < _approvedLenders.length; ++i) {
File: src/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: src/contracts/libraries/SafeERC20.sol 27: for (i = 0; i < 32 && data[i] != 0; i++) {
There are 7 instances of this issue.
File: src/contracts/FraxlendPair.sol 289: for (uint256 i = 0; i < _lenders.length; i++) { 308: for (uint256 i = 0; i < _borrowers.length; i++) {
File: src/contracts/FraxlendPairCore.sol 265: for (uint256 i = 0; i < _approvedBorrowers.length; ++i) { 270: for (uint256 i = 0; i < _approvedLenders.length; ++i) {
File: src/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 9 instances of this issue.
File: src/contracts/FraxlendPair.sol 289: for (uint256 i = 0; i < _lenders.length; i++) { 308: for (uint256 i = 0; i < _borrowers.length; i++) {
File: src/contracts/FraxlendPairCore.sol 265: for (uint256 i = 0; i < _approvedBorrowers.length; ++i) { 270: for (uint256 i = 0; i < _approvedLenders.length; ++i) {
File: src/contracts/FraxlendPairDeployer.sol 402: for (uint256 i = 0; i < _lengthOfArray; ) {
File: src/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: src/contracts/libraries/SafeERC20.sol 22: uint8 i = 0;
Replace > 0
with != 0
for unsigned integers.
There are 7 instances of this issue.
File: src/contracts/FraxlendPairCore.sol 477: if (_currentRateInfo.feeToProtocolRate > 0) { 754: if (_collateralAmount > 0) { 835: if (userBorrowShares[msg.sender] > 0) { 1002: if (_leftoverBorrowShares > 0) { 1094: if (_initialCollateralAmount > 0) {
File: src/contracts/LinearInterestRate.sol 66: _vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0, 67: "LinearInterestRate: _vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0"
There are 5 instances of this issue.
File: src/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"); 399: require(msg.sender == CIRCUIT_BREAKER_ADDRESS, "Circuit Breaker only");
Custom errors and NatSpec comments can also provide rich information without wasting gas.
There are 4 instances of this issue.
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");
Threre are 7 instances with this issue.
724: userBorrowShares[msg.sender] += _sharesAdded; 772: userCollateralBalance[_borrower] += _collateralAmount; 773: totalCollateral += _collateralAmount; 813: userCollateralBalance[_borrower] -= _collateralAmount; 815: totalCollateral -= _collateralAmount; 867: userBorrowShares[_borrower] -= _shares; 1008: totalAsset.amount -= _amountToAdjust;