Debt DAO contest - Rolezn's results

A cryptonative credit marketplace for fully anon and trustless loans to DAOs.

General Information

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

Debt DAO

Findings Distribution

Researcher Performance

Rank: 18/120

Findings: 2

Award: $1,553.44

QA:
grade-a
Gas:
grade-a

🌟 Selected for report: 0

🚀 Solo Findings: 0

Summary<a name="Summary">

Low Risk Issues

IssueContexts
LOW‑1Missing Checks for Address(0x0)1
LOW‑2Unused receive() Function Will Lock Ether In Contract1
LOW‑3The Contract Should approve(0) First1
LOW‑4Missing Contract-existence Checks Before Low-level Calls11
LOW‑5Missing parameter validation in constructor8
LOW‑6The nonReentrant modifier should occur before all other modifiers3
LOW‑7Contracts are not using their OZ Upgradeable counterparts30

Total: 55 contexts over 7 issues

Non-critical Issues

IssueContexts
NC‑1Use a more recent version of SolidityAll in-scope contracts
NC‑2Redundant Cast2
NC‑3require() / revert() Statements Should Have Descriptive Reason Strings18
NC‑4Implementation contract may not be initialized7
NC‑5Avoid Floating Pragmas: The Version Should Be Locked5
NC‑6Non-usage of specific imports1
NC‑7Lines are too long14

Total: 64 contexts over 7 issues

Low Risk Issues

<a href="#Summary">[LOW‑1]</a><a name="LOW&#x2011;1"> Missing Checks for Address(0x0)

Lack of zero-address validation on address parameters may lead to transaction reverts, waste gas, require resubmission of transactions and may even force contract redeployments in certain cases within the protocol.

<ins>Proof Of Concept</ins>
function updateLine: address _line 217: self.line = _line;

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/EscrowLib.sol#L215-L217

<ins>Recommended Mitigation Steps</ins>

Consider adding explicit zero-address validation on input parameters of address type.

<a href="#Summary">[LOW‑2]</a><a name="LOW&#x2011;2"> Unused receive() Function Will Lock Ether In Contract

If the intention is for the Ether to be used, the function should call another function, otherwise it should revert

<ins>Proof Of Concept</ins>
272: receive() external payable {}

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/SpigotedLine.sol#L272

<ins>Recommended Mitigation Steps</ins>

The function should call another function, otherwise it should revert

<a href="#Summary">[LOW‑3]</a><a name="LOW&#x2011;3"> The Contract Should approve(0) First

Some tokens (like USDT L199) do not work when changing the allowance from an existing non-zero allowance value. They must first be approved by zero and then the actual allowance must be approved.

<ins>Proof Of Concept</ins>
134: IERC20(sellToken).approve(swapTarget, amount);

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotedLineLib.sol#L134

<ins>Recommended Mitigation Steps</ins>

Approve with a zero amount first before setting the actual amount.

<a href="#Summary">[LOW‑4]</a><a name="LOW&#x2011;4"> Missing Contract-existence Checks Before Low-level Calls

Low-level calls return success if there is no code present at the specified address.

<ins>Proof Of Concept</ins>
142: (bool passed, bytes memory result) = token.call(

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/CreditLib.sol#L142

65: (bool success, bytes memory assetAmount) = token.call(

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/EscrowLib.sol#L65

115: (bool passed, bytes memory tokenAddrBytes) = token.call(

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/EscrowLib.sol#L115

133: .call(abi.encodeWithSignature("decimals()"));

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/EscrowLib.sol#L133

58: (bool success, bytes memory returnVal) = spigot.call(

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/LineFactoryLib.sol#L58

62: (bool success2, bytes memory returnVal2) = escrow.call(

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/LineFactoryLib.sol#L62

131: (bool success, ) = swapTarget.call{value: amount}(zeroExTradeData);

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotedLineLib.sol#L131

135: (bool success, ) = swapTarget.call(zeroExTradeData);

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotedLineLib.sol#L135

43: (bool claimSuccess,) = revenueContract.call(data);

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotLib.sol#L43

76: (bool success,) = revenueContract.call(data);

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotLib.sol#L76

149: (bool success,) = revenueContract.call(

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotLib.sol#L149

<ins>Recommended Mitigation Steps</ins>

In addition to the zero-address checks, add a check to verify that <address>.code.length > 0

<a href="#Summary">[LOW‑5]</a><a name="LOW&#x2011;5"> Missing parameter validation in constructor

Some parameters of constructors are not checked for invalid values.

<ins>Proof Of Concept</ins>
17: escrow = IEscrow(_escrow);

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/EscrowedLine.sol#L17

55: oracle = IOracle(oracle_); 56: arbiter = arbiter_; 57: borrower = borrower_;

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L55-L57

14: address oracle_ 15: address arbiter_ 16: address borrower_ 18: address spigot_ 19: address escrow_

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/SecuredLine.sol#L14-L19

54: address oracle_ 55: address arbiter_ 56: address borrower_ 57: address spigot_

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/SpigotedLine.sol#L54-L57

48: minimumCollateralRatio = _minimumCollateralRatio; 49: oracle = _oracle; 50: borrower = _borrower; 51: state.line = _line;

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/escrow/Escrow.sol#L48-L51

26: factory = IModuleFactory(moduleFactory);

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/factories/LineFactory.sol#L26

16: registry = FeedRegistryInterface(_registry);

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/oracle/Oracle.sol#L16

36: state.owner = _owner; 37: state.operator = _operator; 38: state.treasury = _treasury;

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/spigot/Spigot.sol#L36-L38

<ins>Recommended Mitigation Steps</ins>

Validate the parameters.

<a href="#Summary">[LOW‑6]</a><a name="LOW&#x2011;6"> The nonReentrant modifier should occur before all other modifiers

Currently the nonReentrant modifier is not the first to occur, it should occur before all other modifiers.

<ins>Proof Of Concept</ins>
93: function claimAndRepay(address claimToken, bytes calldata zeroExTradeData) external whileBorrowing nonReentrant returns (uint256) {

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/SpigotedLine.sol#L93

154: function claimAndTrade(address claimToken, bytes calldata zeroExTradeData) external whileBorrowing nonReentrant returns (uint256) {

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/SpigotedLine.sol#L154

85: function claimEscrow(address token) external nonReentrant returns (uint256 claimed) {

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/spigot/Spigot.sol#L85

<ins>Recommended Mitigation Steps</ins>

Re-sort modifiers so that the nonReentrant modifier occurs first.

<a href="#Summary">[LOW‑7]</a><a name="LOW&#x2011;7"> Contracts are not using their OZ Upgradeable counterparts

The non-upgradeable standard version of OpenZeppelin’s library are inherited / used by the contracts. It would be safer to use the upgradeable versions of the library contracts to avoid unexpected behaviour.

<ins>Proof of Concept</ins>

According to remappings.txt, the standard openzeppelin contracts are used:

openzeppelin/=lib/openzeppelin-contracts/contracts/
4: import { IERC20 } from "openzeppelin/token/ERC20/IERC20.sol"; 5: import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol";

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L4-L5

2: import { IERC20 } from "openzeppelin/token/ERC20/IERC20.sol"; 4: import { ReentrancyGuard } from "openzeppelin/security/ReentrancyGuard.sol";

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/SpigotedLine.sol#L2-L4

12: import { IERC20 } from "openzeppelin/token/ERC20/IERC20.sol"; 13: import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol";

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/SpigotedLine.sol#L12-L13

4: import { IERC20 } from "openzeppelin/token/ERC20/IERC20.sol"; 5: import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol";

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/escrow/Escrow.sol#L4-L5

3: import { ReentrancyGuard } from "openzeppelin/security/ReentrancyGuard.sol";

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/spigot/Spigot.sol#L3

4: import { IERC20 } from "openzeppelin/token/ERC20/IERC20.sol"; 5: import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol";

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/EscrowLib.sol#L4-L5

5: import { IERC20 } from "openzeppelin/token/ERC20/IERC20.sol"; 6: import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol";

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/LineLib.sol#L5-L6

6: import { IERC20 } from "openzeppelin/token/ERC20/IERC20.sol";

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotedLineLib.sol#L6

3: import { ReentrancyGuard } from "openzeppelin/security/ReentrancyGuard.sol";

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotLib.sol#L3

Non Critical Issues

<a href="#Summary">[NC‑1]</a><a name="NC&#x2011;1"> Use a more recent version of Solidity

Use a solidity version of at least 0.8.12 to get string.concat() instead of abi.encodePacked(<str>,<str>)

Use a solidity version of at least 0.8.13 to get the ability to use using for with a list of free functions

<ins>Proof Of Concept</ins>

pragma solidity 0.8.9; was found to be in use in all in-scope contracts.

<ins>Recommended Mitigation Steps</ins>

Consider updating to a more recent solidity version.

<a href="#Summary">[NC‑2]</a><a name="NC&#x2011;2"> Redundant Cast

The type of the variable is the same as the type to which the variable is being cast

<ins>Proof Of Concept</ins>

No need to cast price since if its negative then it will be return 0

117: return price <= 0 ? 0 : (amount * uint(price)) / (1 * 10 ** decimals);

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/CreditLib.sol#L117

60: address(line)

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/LineFactoryLib.sol#L60

<a href="#Summary">[NC‑3]</a><a name="NC&#x2011;3"> require() / revert() Statements Should Have Descriptive Reason Strings

<ins>Proof Of Concept</ins>
90: require(escrow.updateLine(newLine));

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/EscrowedLine.sol#L90

112: require(uint(status) >= uint( LineLib.STATUS.ACTIVE));

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L112

326: require(amount <= credit.principal + credit.interestAccrued);

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L326

143: require(amount <= unusedTokens[credit.token]);

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/SpigotedLine.sol#L143

160: require(msg.sender == borrower);

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/SpigotedLine.sol#L160

239: require(msg.sender == arbiter);

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/SpigotedLine.sol#L239

91: require(amount > 0);

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/EscrowLib.sol#L91

105: require(msg.sender == ILineOfCredit(self.line).arbiter());

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/EscrowLib.sol#L105

161: require(amount > 0);

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/EscrowLib.sol#L161

198: require(amount > 0);

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/EscrowLib.sol#L198

216: require(msg.sender == self.line);

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/EscrowLib.sol#L216

147: require(ISpigot(spigot).updateOwner(newLine));

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotedLineLib.sol#L147

128: require(revenueContract != address(this));

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotLib.sol#L128

130: require(self.settings[revenueContract].transferOwnerFunction == bytes4(0));

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotLib.sol#L130

155: require(success);

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotLib.sol#L155

180: require(newOwner != address(0));

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotLib.sol#L180

189: require(newOperator != address(0));

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotLib.sol#L189

201: require(newTreasury != address(0));

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotLib.sol#L201

<a href="#Summary">[NC‑4]</a><a name="NC&#x2011;4"> Implementation contract may not be initialized

OpenZeppelin recommends that the initializer modifier be applied to constructors. Per OZs Post implementation contract should be initialized to avoid potential griefs or exploits. https://forum.openzeppelin.com/t/uupsupgradeable-vulnerability-post-mortem/15680/5

<ins>Proof Of Concept</ins>
constructor( address oracle_, address arbiter_, address borrower_, uint256 ttl_ ) {

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L49-L53

constructor( address oracle_, address arbiter_, address borrower_, address payable swapTarget_, address spigot_, address escrow_, uint ttl_, uint8 defaultSplit_ ) SpigotedLine( oracle_, arbiter_, borrower_, spigot_, swapTarget_, ttl_, defaultSplit_ ) EscrowedLine(escrow_) {

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/SecuredLine.sol#L14-L30

constructor( address oracle_, address arbiter_, address borrower_, address spigot_, address payable swapTarget_, uint256 ttl_, uint8 defaultRevenueSplit_ ) LineOfCredit(oracle_, arbiter_, borrower_, ttl_) {

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/SpigotedLine.sol#L53-L61

constructor( uint32 _minimumCollateralRatio, address _oracle, address _line, address _borrower ) {

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/escrow/Escrow.sol#L42-L47

constructor( address moduleFactory, address arbiter_, address oracle_, address swapTarget_ ) {

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/factories/LineFactory.sol#L20-L25

constructor() {

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/interest-rate/InterestRateCredit.sol#L19

constructor(address _registry) {

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/oracle/Oracle.sol#L15

<a href="#Summary">[NC‑5]</a><a name="NC&#x2011;5"> Avoid Floating Pragmas: The Version Should Be Locked

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.

<ins>Proof Of Concept</ins>

Found usage of floating pragmas ^0.8.9 of Solidity in the following contracts:

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L1

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/SecuredLine.sol#L1

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/SpigotedLine.sol#L1

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/interest-rate/InterestRateCredit.sol#L1

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/oracle/Oracle.sol#L2

<a href="#Summary">[NC‑6]</a><a name="NC&#x2011;6"> Non-usage of specific imports

The current form of relative path import is not recommended for use because it can unpredictably pollute the namespace. Instead, the Solidity docs recommend specifying imported symbols explicitly. https://docs.soliditylang.org/en/v0.8.15/layout-of-source-files.html#importing-other-source-files

<ins>Proof Of Concept</ins>
6: import "../../interfaces/IOracle.sol";

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/oracle/Oracle.sol#L6

<ins>Recommended Mitigation Steps</ins>

Use specific imports syntax per solidity docs recommendation.

<a href="#Summary">[NC‑7]</a><a name="NC&#x2011;7"> Lines are too long

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. 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, the lines below should be split when they reach that length Reference: https://docs.soliditylang.org/en/v0.8.10/style-guide.html#maximum-line-length

<ins>Proof Of Concept</ins>
35: // id -> Reference ID for a credit line provided by a single Lender for a given token on a Line of Credit

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L35

58: // we dont check borrower is same on both lines because borrower might want new address managing new line

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/SecuredLine.sol#L58

28: /// @notice exchange aggregator (mainly 0x router) to trade revenue tokens from a Spigot for credit tokens owed to lenders

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/SpigotedLine.sol#L28

23: // the minimum value of the collateral in relation to the outstanding debt e.g. 10% of outstanding debt

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/escrow/Escrow.sol#L23

8: // Must divide by 100 too offset bps in numerator and divide by another 100 to offset % and get actual token amount

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/interest-rate/InterestRateCredit.sol#L8

144: // Changes the revenue split between the Treasury and the Owner based upon the status of the Line of Credit

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/spigot/Spigot.sol#L144

23: /// @notice Emits data re Lender removes funds (principal) - there is no corresponding function, just withdraw()

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/CreditLib.sol#L23

26: /// @notice Emits data re Lender withdraws interest - there is no corresponding function, just withdraw()

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/CreditLib.sol#L26

31: /// @notice After accrueInterest runs, emits the amount of interest added to a Borrower's outstanding balance of interest due

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/CreditLib.sol#L31

1: // forked from https://github.com/IndexCoop/index-coop-smart-contracts/blob/master/contracts/lib/MutualConsent.sol

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/MutualConsent.sol#L1

175: // if Line of Credit is healthy then set the split to the prior agreed default split of revenue tokens

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotedLineLib.sol#L175

15: // Configurations for revenue contracts related to the split of revenue, access control to claiming revenue tokens and transfer of Spigot ownership

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotLib.sol#L15

24: // Maximum numerator for Setting.ownerSplit param to ensure that the Owner can't claim more than 100% of revenue

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotLib.sol#L24

69: // cant claim revenue via operate() because that fucks up accounting logic. Owner shouldn't whitelist it anyway but just in case

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotLib.sol#L69

#0 - c4-judge

2022-12-06T17:15:30Z

dmvt marked the issue as grade-a

Awards

933.3247 USDC - $933.32

Labels

bug
G (Gas Optimization)
grade-a
G-05

External Links

Summary<a name="Summary">

Gas Optimizations

IssueContexts
GAS‑1Empty Blocks Should Be Removed Or Emit Something1
GAS‑2++i/i++ Should Be unchecked{++i}/unchecked{i++} When It Is Not Possible For Them To Overflow, As Is The Case When Used In For- And While-loops6
GAS‑3abi.encode() is less efficient than abi.encodepacked()1
GAS‑4Use assembly to check for address(0)1
GAS‑5Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead12
GAS‑6Optimize names to save gas10
GAS‑7Use uint256(1)/uint256(2) instead for true and false boolean states5
GAS‑8Do not calculate constants5
GAS‑9Structs can be packed into fewer storage slots2

Total: 80 contexts over 15 issues

Gas Optimizations

<a href="#Summary">[GAS‑1]</a><a name="GAS&#x2011;1"> Empty Blocks Should Be Removed Or Emit Something

The code should be refactored such that they no longer exist, or the block should do something useful, such as emitting an event or reverting. If the contract is meant to be extended, the contract should be abstract and the function signatures be added without any default implementation. If the block is an empty if-statement block to avoid doing subsequent checks in the else-if/else conditions, the else-if/else conditions should be nested under the negation of the if-statement, because they involve different classes of checks, which may lead to the introduction of errors when the code is later modified (if(x){}else if(y){...}else{...} => if(!x){if(y){...}else{...}})

<ins>Proof Of Concept</ins>
272: receive() external payable {}

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/SpigotedLine.sol#L272

<a href="#Summary">[GAS‑2]</a><a name="GAS&#x2011;2"> ++i/i++ Should Be unchecked{++i}/unchecked{i++} When It Is Not Possible For Them To Overflow, As Is The Case When Used In For- And While-loops

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

<ins>Proof Of Concept</ins>
179: for (uint256 i; i < len; ++i) {

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L179

203: for (uint256 i; i < len; ++i) {

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L203

520: for (uint256 i; i <= lastSpot; ++i) {

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L520

23: for(uint256 i; i < len; ++i) {

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/CreditListLib.sol#L23

51: for(uint i = 1; i < len; ++i) {

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/CreditListLib.sol#L51

57: for (uint256 i; i < length; ++i) {

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/EscrowLib.sol#L57

<a href="#Summary">[GAS‑3]</a><a name="GAS&#x2011;3"> abi.encode() is less efficient than abi.encodepacked()

See for more information: https://github.com/ConnorBlockchain/Solidity-Encode-Gas-Comparison

<ins>Proof Of Concept</ins>
69: return keccak256(abi.encode(line, lender, token));

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/CreditLib.sol#L69

<a href="#Summary">[GAS‑4]</a><a name="GAS&#x2011;4"> Use assembly to check for address(0)

Save 6 gas per instance if using assembly to check for address(0)

e.g.

assembly { if iszero(_addr) { mstore(0x00, "AddressZero") revert(0x00, 0x20) } }
<ins>Proof Of Concept</ins>
81: if(token == address(0)) return 0;

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/LineLib.sol#L81

<a href="#Summary">[GAS‑5]</a><a name="GAS&#x2011;5"> Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead

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 22-28 gas (depending on whether the other operand is also a variable of type uint8) 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

<ins>Proof Of Concept</ins>
32: uint8 public immutable defaultRevenueSplit;

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/credit/SpigotedLine.sol#L32

24: uint32 public immutable minimumCollateralRatio;

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/escrow/Escrow.sol#L24

12: uint8 constant defaultRevenueSplit = 90; 13: uint8 constant MAX_SPLIT = 100; 14: uint32 constant defaultMinCRatio = 3000;

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/factories/LineFactory.sol#L12-L14

71: uint8 split = defaultRevenueSplit;

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/factories/LineFactory.sol#L71

138: uint8 decimals;

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/CreditLib.sol#L138

51: uint8 split = SecuredLine(oldLine).defaultRevenueSplit();

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/LineFactoryLib.sol#L51

13: uint8 constant MAX_SPLIT = 100;

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotedLineLib.sol#L13

25: uint8 constant MAX_SPLIT = 100;

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotLib.sol#L25

<a href="#Summary">[GAS‑6]</a><a name="GAS&#x2011;6"> Optimize names to save gas

public/external function names and public member variable names can be optimized to save gas. See this link for an example of how it works. Method IDs that have two leading zero bytes can save 128 gas each during deployment, and renaming functions to have lower method IDs will save 22 gas per call, per sorted position shifted

<ins>Proof Of Concept</ins>

All in-scope contracts

<a href="#Summary">[GAS‑7]</a><a name="GAS&#x2011;7"> Use uint256(1)/uint256(2) instead for true and false boolean states

If you don't use boolean for storage you will avoid Gwarmaccess 100 gas. In addition, state changes of boolean from true to false can cost up to ~20000 gas rather than uint256(2) to uint256(1) that would cost significantly less.

<ins>Proof Of Concept</ins>
16: mapping(address => bool) enabled;

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/EscrowLib.sol#L16

15: mapping(bytes32 => bool) public mutualConsents;

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/MutualConsent.sol#L15

14: mapping(bytes4 => bool) whitelistedFunctions; // function -> allowed

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotLib.sol#L14

<a href="#Summary">[GAS‑8]</a><a name="GAS&#x2011;8"> Do not calculate constants

Due to how constant variables are implemented (replacements at compile-time), an expression assigned to a constant variable is recomputed each time that the variable is used, which wastes some gas.

<ins>Proof Of Concept</ins>
11: uint256 constant INTEREST_DENOMINATOR = ONE_YEAR * BASE_DENOMINATOR;

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/modules/interest-rate/InterestRateCredit.sol#L11

27: uint256 constant MAX_REVENUE = type(uint256).max / MAX_SPLIT;

https://github.com/debtdao/Line-of-Credit/tree/audit/code4rena-2022-11-03/contracts/utils/SpigotLib.sol#L27

<a href="#Summary">[GAS‑9]</a><a name="GAS&#x2011;9"> Structs can be packed into fewer storage slots

Each slot saved can avoid an extra Gsset (20000 gas) for the first setting of the struct. Subsequent reads as well as writes have smaller gas savings

<ins>Proof Of Concept</ins>

https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/interfaces/ILineFactory.sol#L4-L9

struct CoreLineParams { address borrower; uint256 ttl; uint32 cratio; uint8 revenueSplit; }

Can save 2 slots by changing to:

struct CoreLineParams { address borrower; //@audit 160 bits uint32 cratio; //@audit 32 bits uint8 revenueSplit; //@audit 8 bits uint256 ttl; }

https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/utils/SpigotLib.sol#L7-L16

struct SpigotState { address owner; address operator; address treasury; // Total amount of revenue tokens escrowed by the Spigot and available to be claimed mapping(address => uint256) escrowed; // token -> amount escrowed // Functions that the operator is allowed to run on all revenue contracts controlled by the Spigot mapping(bytes4 => bool) whitelistedFunctions; // function -> allowed // Configurations for revenue contracts related to the split of revenue, access control to claiming revenue tokens and transfer of Spigot ownership mapping(address => ISpigot.Setting) settings; // revenue contract -> settings }

Can potentially save 1 slot due to mapping of whitelistedFunctions sized bool (8 bits) by putting after address treasury sized 160 bits:

struct SpigotState { address owner; address operator; address treasury; // Functions that the operator is allowed to run on all revenue contracts controlled by the Spigot mapping(bytes4 => bool) whitelistedFunctions; // function -> allowed // Total amount of revenue tokens escrowed by the Spigot and available to be claimed mapping(address => uint256) escrowed; // token -> amount escrowed // Configurations for revenue contracts related to the split of revenue, access control to claiming revenue tokens and transfer of Spigot ownership mapping(address => ISpigot.Setting) settings; // revenue contract -> settings }

#0 - c4-judge

2022-11-17T22:52:05Z

dmvt marked the issue as grade-a

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter