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: 26/120
Findings: 2
Award: $691.09
š Selected for report: 0
š Solo Findings: 0
š Selected for report: __141345__
Also found by: 0xSmartContract, 0xbepresent, Tomo, aphak5010, bin2chen, cloudjunky, datapunk, eierina, joestakey, rbserver
70.9705 USDC - $70.97
ETH can be accidentally sent with ERC20 in sendOutTokenOrETH function
User who wants to send ERC20 due to payable function can write a value in the value field
contracts/utils/LineLib.sol: 33 */ 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: }
ETH can be sent by mistake with ERC20 in sendOutTokenOrETH function, it is enough to add a simple require to prevent this
#0 - c4-judge
2022-11-17T16:47:07Z
dmvt marked the issue as duplicate of #89
#1 - c4-judge
2022-11-17T20:50:27Z
dmvt marked the issue as partial-50
#2 - C4-Staff
2022-12-20T06:05:46Z
liveactionllama marked the issue as duplicate of #355
š 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
620.1198 USDC - $620.12
Number | Issues Details | Context |
---|---|---|
[L-01] | Require messages are too short or not | 23 |
Total 1 issue
Number | Issues Details | Context |
---|---|---|
[NC-01 ] | Using Vulnerable Version of Openzeppelin | |
[NC-02] | 0 address check | 7 |
[NC-03] | Function writing that does not comply with the Solidity Style Guide | All contracts |
[NC-04] | Omissions in Events | 4 |
[NC-05] | Use a more recent version of Solidity | |
[NC-06] | Solidity compiler optimizations can be problematic | |
[NC-07] | Open TODOS | 2 |
[NC-08] | NatSpec is missing | 3 |
[NC-09] | Compliance with Solidity Style rules in Constant expressions | 2 |
[NC-10] | Need Fuzzing test | 6 |
[NC-11] | Add NatSpec Mapping comment | 10 |
Total 11 issues
Number | Suggestion Details | Context |
---|---|---|
[S-01] | Generate perfect code headers every time | 2 |
Total 1 suggestion
Context:
Description: The correct and clear error description explains to the user why the function reverts, but the error descriptions below in the project are not self-explanatory. These error descriptions are very important in the debug features of DApps like Tenderly. Error definitions should be added to the require block, not exceeding 32 bytes.
23 results - 6 files contracts/modules/credit/EscrowedLine.sol: 64: require(escrow_.liquidate(amount, targetToken, to)); 90: require(escrow.updateLine(newLine)); contracts/modules/credit/LineOfCredit.sol: 112: require(uint(status) >= uint( LineLib.STATUS.ACTIVE)); 241: require(interestRate.setRate(id, drate, frate)); 259: require(interestRate.setRate(id, drate, frate)); 326: require(amount <= credit.principal + credit.interestAccrued); contracts/modules/credit/SpigotedLine.sol: 62: require(defaultRevenueSplit_ <= SpigotedLineLib.MAX_SPLIT); 143: require(amount <= unusedTokens[credit.token]); 160: require(msg.sender == borrower); 239: require(msg.sender == arbiter); contracts/utils/EscrowLib.sol: 91: require(amount > 0); 105: require(msg.sender == ILineOfCredit(self.line).arbiter()); 161: require(amount > 0); 198: require(amount > 0); 216: require(msg.sender == self.line); contracts/utils/SpigotedLineLib.sol: 147: require(ISpigot(spigot).updateOwner(newLine)); contracts/utils/SpigotLib.sol: 96: require(LineLib.sendOutTokenOrETH(token, self.treasury, claimed - escrowedAmount)); 128: require(revenueContract != address(this)); 130: require(self.settings[revenueContract].transferOwnerFunction == bytes4(0)); 155: require(success); 180: require(newOwner != address(0)); 189: require(newOperator != address(0)); 201: require(newTreasury != address(0));
The package.json configuration file says that the project is using 4.7.0 of OpenZeppelin
openzeppelin/contracts vulnerabilities: https://security.snyk.io/package/npm/@openzeppelin%2Fcontracts/
lib/openzeppelin-contracts/package.json: 1: { 2: "name": "openzeppelin-solidity", 3: "description": "Secure Smart Contract library for Solidity", 4: "version": "4.7.0", 5: "files": [
Recommendation: Use OZ last 4.8.0 versions
0 address
checkContext:
7 results - 7 files contracts/modules/credit/EscrowedLine.sol: 15 16: constructor(address _escrow) { 17 escrow = IEscrow(_escrow); contracts/modules/credit/LineOfCredit.sol: 49: constructor( 50 address oracle_, contracts/modules/credit/SecuredLine.sol: 12 13: constructor( 14 address oracle_, contracts/modules/credit/SpigotedLine.sol: 52 */ 53: constructor( 54 address oracle_, contracts/modules/factories/LineFactory.sol: 19 20: constructor( 21 address moduleFactory, contracts/modules/oracle/Oracle.sol: 15: constructor(address _registry) { 16 registry = FeedRegistryInterface(_registry); contracts/modules/spigot/Spigot.sol: 30 */ 31: constructor ( 32 address _owner,
Description: Also check of the address to protect the code from 0x0 address problem just in case. This is best practice or instead of suggesting that they verify address != 0x0, you could add some good NatSpec comments explaining what is valid and what is invalid and what are the implications of accidentally using an invalid address.
Recommendation:
like this;
if (oracle == address(0)) revert ADDRESS_ZERO();
Function writing
that does not comply with the Solidity Style Guide
Context: All Contracts
Description: Order of Functions; ordering helps readers identify which functions they can call and to find the constructor and fallback definitions easier. But there are contracts in the project that do not comply with this.
https://docs.soliditylang.org/en/v0.8.17/style-guide.html
Functions should be grouped according to their visibility and ordered:
constructor receive function (if exists) fallback function (if exists) external public internal private within a grouping, place theĀ viewĀ andĀ pureĀ functions last
Throughout the codebase, events are generally emitted when sensitive changes are made to the contracts. However, some events are missing important parameters
The events should include the new value and old value where possible:
Events with no old value;
contracts/modules/credit/LineOfCredit.sol: 260: emit SetRates(id, drate, frate); 282: emit IncreaseCredit(id, amount); 362: emit Borrow(id, amount); 423: emit UpdateStatus(uint256(status_));
Context: All contracts
Description: For security, it is best practice to use the latest Solidity version. For the security fix list in the versions; https://github.com/ethereum/solidity/blob/develop/Changelog.md
Recommendation:
Old version of Solidity is used (^0.8.9)
, newer version can be used (0.8.17)
foundry.toml: 29 offline = false 30: optimizer = true 31: optimizer_runs = 200
Description: Protocol has enabled optional compiler optimizations in Solidity. There have been several optimization bugs with security implications. Moreover, optimizations are actively being developed. Solidity compiler optimizations are disabled by default, and it is unclear how many contracts in the wild actually use them.
Therefore, it is unclear how well they are being tested and exercised. High-severity security issues due to optimization bugs have occurred in the past. A high-severity bug in the emscripten-generated solc-js compiler used by Truffle and Remix persisted until late 2018. The fix for this bug was not reported in the Solidity CHANGELOG.
Another high-severity optimization bug resulting in incorrect bit shift results was patched in Solidity 0.5.6. More recently, another bug due to the incorrect caching of keccak256 was reported. A compiler audit of Solidity from November 2018 concluded that the optional optimizations may not be safe. It is likely that there are latent bugs related to optimization and that new bugs will be introduced due to future optimizations.
Exploit Scenario A latent or future bug in Solidity compiler optimizationsāor in the Emscripten transpilation to solc-jsācauses a security vulnerability in the contracts.
Recommendation: Short term, measure the gas savings from optimizations and carefully weigh them against the possibility of an optimization-related bug. Long term, monitor the development and adoption of Solidity compiler optimizations to assess their maturity.
Context:
2 results - 1 files contracts/modules/factories/LineFactory.sol: 139 ) external returns (address line) { 140: // TODO: test 141 if (mSpigot == address(0)) { 144 145: // TODO: test 146 if (mEscrow == address(0)) {
Recommendation: Use temporary TODOs as you work on a feature, but make sure to treat them before merging. Either add a link to a proper issue in your TODO, or remove it from the code.
Description: NatSpec is missing for the following functions , constructor and modifier:
Context: CreditLib.sol#L16 EscrowLib.sol#L12 EscrowLib.sol#L192
Context:
contracts/modules/factories/LineFactory.sol: 12: uint8 constant defaultRevenueSplit = 90; // 90% to debt repayment 14: uint32 constant defaultMinCRatio = 3000; // 30.00% minimum collateral ratio
Recommendation: Variables are declared asĀ constantĀ utilize theĀ UPPER_CASE_WITH_UNDERSCORESĀ format.
Context:
6 results - 3 files contracts/modules/credit/LineOfCredit.sol: 451: unchecked { ++count; } 499: unchecked { --count; } contracts/utils/CreditLib.sol: 174 returns (ILineOfCredit.Credit memory) 175: { unchecked { 176 if (amount <= credit.interestAccrued) { 208 returns (ILineOfCredit.Credit memory) 209: { unchecked { 210 if(amount > credit.deposit - credit.principal + credit.interestRepaid) { 245 returns (ILineOfCredit.Credit memory) 246: { unchecked { 247 // interest will almost always be less than deposit contracts/utils/SpigotedLineLib.sol: 110 ); 111: } else { unchecked {
Description: In total 3 contracts, 6 unchecked are used, the functions used are critical. For this reason, there must be fuzzing tests in the tests of the project. Not seen in tests.
Recommendation: Use should fuzzing test like Echidna.
As Alberto Cuesta Canada said: Fuzzing is not easy, the tools are rough, and the math is hard, but it is worth it. Fuzzing gives me a level of confidence in my smart contracts that I didnāt have before. Relying just on unit testing anymore and poking around in a testnet seems reckless now.
https://medium.com/coinmonks/smart-contract-fuzzing-d9b88e0b0a05
Description: Add NatSpec comments describing mapping keys and values
Context:
10 results - 6 files contracts/modules/credit/LineOfCredit.sol: 34 35: mapping(bytes32 => Credit) public credits; contracts/modules/credit/SpigotedLine.sol: 40: mapping(address => uint256) private unusedTokens; contracts/modules/interest-rate/InterestRateCredit.sol: 13 address immutable lineContract; 14: mapping(bytes32 => Rate) public rates; // position id -> lending rates contracts/utils/EscrowLib.sol: 16: mapping(address => bool) enabled; 18: mapping(address => IEscrow.Deposit) deposited; contracts/utils/MutualConsent.sol: 14: // Mapping of upgradable units and if consent has been initialized by other party 15: mapping(bytes32 => bool) public mutualConsents; contracts/utils/SpigotLib.sol: 12: mapping(address => uint256) escrowed; // token -> amount escrowed 14: mapping(bytes4 => bool) whitelistedFunctions; // function -> allowed 16: mapping(address => ISpigot.Setting) settings; // revenue contract -> settings
Recommendation:
/// @dev address(user) -> address(ERC0 Contract Address) -> uint256(allowance amount from user) mapping(address => mapping(address => uint256)) public allowance;
Description: I recommend using header for Solidity code layout and readability
https://github.com/transmissions11/headers
/*////////////////////////////////////////////////////////////// TESTING 123 //////////////////////////////////////////////////////////////*/
#0 - c4-judge
2022-12-07T17:07:38Z
dmvt marked the issue as grade-b
#1 - c4-judge
2022-12-07T20:41:53Z
dmvt marked the issue as grade-a