Platform: Code4rena
Start Date: 03/11/2022
Pot Size: $115,500 USDC
Total HM: 17
Participants: 120
Period: 7 days
Judge: LSDan
Total Solo HM: 1
Id: 174
League: ETH
Rank: 45/120
Findings: 3
Award: $113.25
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: __141345__
Also found by: 0xdeadbeef0x, 8olidity, Amithuddar, Bnke0x0, Ch_301, Deivitto, IllIllI, KingNFT, Nyx, RaymondFam, RedOneN, Satyam_Sharma, SmartSek, Tomo, adriro, bananasboys, carlitox477, cccz, cloudjunky, codexploder, corerouter, cryptonue, d3e4, datapunk, joestakey, martin, merlin, minhquanym, pashov, peanuts, rvierdiiev
2.6694 USDC - $2.67
The use of payable.transfer()
is heavily frowned upon because it can lead to the locking of funds. The transfer()
call requires that the recipient has a payable callback, only provides 2300 gas for its operation.
This means the following cases can cause the transfer to fail:
If a user falls into one of the above categories, they’ll be unable to receive funds. Inaccessible funds means loss of funds, which is Medium severity.
The LineLib.sol
sendOutTokenOrETH is being use extensively from other contracts, as this is kind of a library for sending token or ETH from the contract to other receiver contract.
File: LineLib.sol 34: function sendOutTokenOrETH( 35: address token, 36: address receiver, 37: uint256 amount 38: ) 39: external 40: returns (bool) 41: { 42: if(token == address(0)) { revert TransferFailed(); } 43: 44: // both branches revert if call failed 45: if(token!= Denominations.ETH) { // ERC20 46: IERC20(token).safeTransfer(receiver, amount); 47: } else { // ETH 48: payable(receiver).transfer(amount); 49: } 50: return true; 51: }
Manual analysis
Use address.call{value:x}()
instead.
#0 - c4-judge
2022-11-17T15:49:31Z
dmvt marked the issue as duplicate of #14
#1 - c4-judge
2022-11-17T19:18:00Z
dmvt marked the issue as partial-50
#2 - c4-judge
2022-12-06T14:42:38Z
dmvt marked the issue as satisfactory
#3 - C4-Staff
2022-12-20T05:56:43Z
liveactionllama marked the issue as duplicate of #369
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0xNazgul, 0xRoxas, 0xSmartContract, Awesome, Aymen0909, B2, BClabs, Bnke0x0, Deekshith99, Deivitto, Diana, Dinesh11G, Funen, HE1M, HardlyCodeMan, Josiah, Nyx, Rahoz, RaymondFam, RedOneN, ReyAdmirado, Rolezn, Saintcode_, TomJ, Trust, __141345__, a12jmx, adriro, ajtra, aphak5010, apostle0x01, brgltd, btk, bulej93, c3phas, carlitox477, catwhiskeys, ch0bu, chaduke, chrisdior4, cryptonue, cryptostellar5, csanuragjain, ctf_sec, delfin454000, djxploit, durianSausage, erictee, fatherOfBlocks, gogo, i_got_hacked, immeas, joestakey, jumpdest7d, lukris02, martin, mcwildy, merlin, minhquanym, oyc_109, pashov, peanuts, pedr02b2, rbserver, rotcivegaf, rvierdiiev, sakman, saneryee, seyni, shark, slowmoses, tnevler, trustindistrust, w0Lfrum, yurahod, zaskoh
61.3462 USDC - $61.35
Recommend considering implementing a two step process where the owner or admin nominates an account and the nominated account needs to call an acceptOwnership() function for the transfer of ownership to fully succeed. This ensures the nominated EOA account is a valid and active account.
File: SpigotLib.sol 178: function updateOwner(SpigotState storage self, address newOwner) external returns (bool) { 179: if(msg.sender != self.owner) { revert CallerAccessDenied(); } 180: require(newOwner != address(0)); 181: self.owner = newOwner; 182: emit UpdateOwner(newOwner); 183: return true; 184: }
When sendOutTokenOrETH()
being called, there is no check on amount
> 0, it will be better to just revert or skip the transfer if the amount is 0. This will also save gas because no transaction will be executed if amount is 0.
File: LineLib.sol 34: function sendOutTokenOrETH( 35: address token, 36: address receiver, 37: uint256 amount 38: ) 39: external 40: returns (bool) 41: { 42: if(token == address(0)) { revert TransferFailed(); } 43: 44: // both branches revert if call failed 45: if(token!= Denominations.ETH) { // ERC20 46: IERC20(token).safeTransfer(receiver, amount); 47: } else { // ETH 48: payable(receiver).transfer(amount); 49: } 50: return true; 51: }
Avoid floating pragmas for non-library contracts.
While floating pragmas make sense for libraries to allow them to be included with multiple different versions of applications, it may be a security risk for application implementations.
A known vulnerable compiler version may accidentally be selected or security tools might fall-back to an older compiler version ending up checking a different EVM compilation that is ultimately deployed on the blockchain.
It is recommended to pin to a concrete compiler version.
File: LineOfCredit.sol 1: pragma solidity ^0.8.9; File: SecuredLine.sol 1: pragma solidity ^0.8.9; File: SpigotedLine.sol 1: pragma solidity ^0.8.9; File: InterestRateCredit.sol 1: pragma solidity ^0.8.9; File: Oracle.sol 2: pragma solidity ^0.8.9;
If the intention is for the Ether to be used, the function should call another function, otherwise it should revert
File: SpigotedLine.sol 272: receive() external payable {}
File: Spigot.sol 227: receive() external payable { 228: return; 229: }
decimals() is not part of the official ERC20 standard and might fail for tokens that do not implement it. While in practice it is very unlikely, as usually most of the tokens implement it, this should still be considered as a potential issue.
File: CreditLib.sol 142: (bool passed, bytes memory result) = token.call( 143: abi.encodeWithSignature("decimals()") 144: );
File: EscrowLib.sol 131: (bool successDecimals, bytes memory decimalBytes) = deposit 132: .asset 133: .call(abi.encodeWithSignature("decimals()"));
Most of the contract use 0.8.9 version of solidity, use a solidity version of at least 0.8.12 to get string.concat() instead of abi.encodePacked(,) Use a solidity version of at least 0.8.13 to get the ability to use using for
with a list of free functions
Any typos in contract code (even for comments) might considered a low quality code/documentation.
availble
doesnt
prinicpal
itselgf
pat down debt
dont
fuly
It's better to remove commented code if it's not being used again
File: EscrowedLine.sol 10: // import { SecuredLine } from "./SecuredLine.sol";
Source codes lines should be limited to a certain number of characters, usually lines in source code are limited to 80 characters. Today’s screens are much larger so it’s reasonable to stretch this in some cases. A good practice is to ensure the code does not require a horizontal scroll bar on GitHub. Even if it's for a comments it's better to follow this limit. Since the files will most likely reside in GitHub, and GitHub starts using a scroll bar in all cases when the length is over 164 characters, longer than that should be split.
Most of the time it use uint256
but some other part uint
. Even though it's the same meaning, but a good consistency is better for a quality code.
#0 - c4-judge
2022-12-06T22:55:44Z
dmvt marked the issue as grade-b
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0xRajkumar, Awesome, Aymen0909, B2, Bnke0x0, Deivitto, Diana, JC, Metatron, Rahoz, RaymondFam, RedOneN, ReyAdmirado, Rolezn, Saintcode_, TomJ, __141345__, ajtra, aphak5010, brgltd, c3phas, ch0bu, chrisdior4, cryptonue, durianSausage, emrekocak, erictee, exolorkistis, gogo, karanctf, lukris02, martin, me_na0mi, oyc_109, peanuts, rotcivegaf, saneryee, seyni, tnevler, zaskoh
49.2315 USDC - $49.23
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.
File: CreditListLib.sol 23: for(uint256 i; i < len; ++i) { 51: for(uint i = 1; i < len; ++i) {
When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html
Each operation involving a uint8 costs an extra 28 gas as compared to ones involving uint256, due to the compiler having to clear the higher bits of the memory word before operating on the uint8, as well as the associated stack operations of doing so. Use a larger size then downcast where needed.
File: ILineFactory.sol 04: struct CoreLineParams { 05: address borrower; 06: uint256 ttl; 07: uint32 cratio; 08: uint8 revenueSplit; 09: } File: ISpigot.sol 5: struct Setting { 6: uint8 ownerSplit; // x/100 % to Owner, rest to Treasury 7: bytes4 claimFunction; // function signature on contract to call and claim revenue 8: bytes4 transferOwnerFunction; // function signature on contract to call and transfer ownership 9: } File: SpigotedLine.sol 32: uint8 public immutable defaultRevenueSplit; File: LineFactory.sol 12: uint8 constant defaultRevenueSplit = 90; // 90% to debt repayment 13: uint8 constant MAX_SPLIT = 100; // max % to take 14: uint32 constant defaultMinCRatio = 3000; // 30.00% minimum collateral ratio
When we define internal functions to perform computation:
When it does not affect readability, it is recommended to inline functions in order to save gas Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.
File: LineOfCredit.sol 167: function _updateOutstandingDebt() 168: internal 169: returns (uint256 principal, uint256 interest) 170: { File: InterestRateCredit.sol 42: function _accrueInterest( 43: bytes32 id, 44: uint256 drawnBalance, 45: uint256 facilityBalance 46: ) internal returns (uint256) { File: MutualConsent.sol 38: function _mutualConsent(address _signerOne, address _signerTwo) internal returns(bool) {
X += Y costs 22 more gas than X = X + Y. This can mean a lot of gas wasted in a function call when the computation is repeated n times (loops)
File: LineOfCredit.sol 179: for (uint256 i; i < len; ++i) { ... 192: principal += _p; 193: interest += _i; ... 196: }
File: EscrowLib.sol 57: for (uint256 i; i < length; ++i) { ... 62: if (deposit != 0) { ... 75: collateralValue += CreditLib.calculateValue( 76: o.getLatestAnswer(d.asset), 77: deposit, 78: d.assetDecimals 79: ); 80: } 81: }
When a require statement is used multiple times, it is cheaper in deployment costs to use a modifier instead.
File: EscrowLib.sol 91: require(amount > 0); ... 161: require(amount > 0); ... 198: require(amount > 0);
#0 - c4-judge
2022-11-17T23:03:32Z
dmvt marked the issue as grade-b