Debt DAO contest - c3phas'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: 48/120

Findings: 2

Award: $110.58

QA:
grade-b
Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

QA

Missing checks for address(0x0) when assigning values to address state variables

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/credit/LineOfCredit.sol#L55-L57

File: /contracts/modules/credit/LineOfCredit.sol
55:        oracle = IOracle(oracle_);
56:        arbiter = arbiter_;
57:        borrower = borrower_;

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/spigot/Spigot.sol#L36-L38

File: /contracts/modules/spigot/Spigot.sol
36:        state.owner = _owner;
37:        state.operator = _operator;
38:        state.treasury = _treasury;

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/oracle/Oracle.sol#L16

File: /contracts/modules/oracle/Oracle.sol
16:        registry = FeedRegistryInterface(_registry);

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/escrow/Escrow.sol#L49-L50

File: /contracts/modules/escrow/Escrow.sol
49:        oracle = _oracle;
50:        borrower = _borrower;

Constants should be defined rather than using magic numbers

There are several occurrences of literal values with unexplained meaning .Literal values in the codebase without an explained meaning make the code harder to read, understand and maintain, thus hindering the experience of developers, auditors and external contributors alike.

Developers should define a constant variable for every magic value used , giving it a clear and self-explanatory name.

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/utils/CreditLib.sol#L140

File: /contracts/utils/CreditLib.sol

//@audit: 18
140:          decimals = 18;
//@audit: 18
145:          decimals = !passed ? 18 : abi.decode(result, (uint8));

Open todos

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/factories/LineFactory.sol#L140

File: /contracts/modules/factories/LineFactory.sol
140:        // TODO: test

145:        // TODO: test

Missing revert strings

Require statements should have descriptive reasons to aid in understanding why a particular action failed.

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/credit/LineOfCredit.sol#L112

File: /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);

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/credit/SpigotedLine.sol#L62

File: /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);

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/credit/EscrowedLine.sol#L64

File: /contracts/modules/credit/EscrowedLine.sol
64:    require(escrow_.liquidate(amount, targetToken, to));

90:    require(escrow.updateLine(newLine));

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/utils/SpigotedLineLib.sol#L147

File: /contracts/utils/SpigotedLineLib.sol
147:      require(ISpigot(spigot).updateOwner(newLine));

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/utils/EscrowLib.sol#L91

File: /contracts/utils/EscrowLib.sol
91:        require(amount > 0);

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

161:        require(amount > 0);

198:        require(amount > 0);

Natspec is incomplete/Missing

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/utils/SpigotedLineLib.sol#L120-L127

File: /contracts/utils/SpigotedLineLib.sol
//@audit: Missing @param amount, @param sellToken, @param swapTarget,@param zeroExTradeData
120:    function trade(
121:        uint256 amount,
122:        address sellToken,
123:        address payable swapTarget,
124:        bytes calldata zeroExTradeData
125:    ) 
126:        public
127:        returns(bool)

//@audit: Missing @param spigot, @param newLine
146:    function rollover(address spigot, address newLine) external returns(bool) {

//@audit: Missing @param spigot, @param arbiter
151:    function canDeclareInsolvent(address spigot, address arbiter) external view returns (bool) {

//@audit: Missing @param status, @param defaultSplit
169:    function updateSplit(address spigot, address revenueContract, LineLib.STATUS status, uint8 defaultSplit) external returns (bool) {

//@audit: Missing @param spigot, @param status, @param borrower, @param arbiter, @param to
194:    function releaseSpigot(address spigot, LineLib.STATUS status, address borrower, address arbiter, address to) external returns (bool) {

//@audit: Missing @param to, @param token, @param amount, @param status, @param borrower, @param arbiter
217:    function sweep(address to, address token, uint256 amount, LineLib.STATUS status, address borrower, address arbiter) external returns (bool) {

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/utils/CreditLib.sol#L73-L81

File: /contracts/utils/CreditLib.sol

//@audit: Missing @param credit, @param id, @param oracle, @param interestRate
//@audit: Missing @return c, @return principal,@return interest
73:  function getOutstandingDebt(
74:    ILineOfCredit.Credit memory credit,
75:    bytes32 id,
76:    address oracle,
77:    address interestRate
78:  )
79:    external
80:    returns (ILineOfCredit.Credit memory c, uint256 principal, uint256 interest)
81:  {


//@audit: Missing @param id, @param amount, @param lender, @param token
//@audit: Missing @return credit
125:  function create(
126:      bytes32 id,
127:      uint256 amount,
128:      address lender,
129:      address token,
130:      address oracle
131:  )
132:      external 
133:      returns(ILineOfCredit.Credit memory credit)

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/interest-rate/InterestRateCredit.sol#L34-L38

File:/contracts/modules/interest-rate/InterestRateCredit.sol

//@audit: Missing @param id, @param drawnBalance, @param facilityBalance
34:    function accrueInterest(
35:        bytes32 id,
36:        uint256 drawnBalance,
37:        uint256 facilityBalance
38:    ) external override onlyLineContract returns (uint256) {

//@audit: Missing @param id, @param dRate, @param fRate
74:    function setRate(
75:        bytes32 id,
76:        uint128 dRate,
77:        uint128 fRate
78:    ) external onlyLineContract returns (bool) {

The nonReentrant modifier should occur before all other modifiers

This is a best-practice to protect against reentrancy in other modifiers

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/credit/SpigotedLine.sol#L93-L98

File: /contracts/modules/credit/SpigotedLine.sol
193:    function claimAndRepay(address claimToken, bytes calldata zeroExTradeData)
194:        external
195:        whileBorrowing
196:        nonReentrant
197:        returns (uint256)
198:    {

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/credit/SpigotedLine.sol#L154-L159

File: /contracts/modules/credit/SpigotedLine.sol
154:    function claimAndTrade(address claimToken, bytes calldata zeroExTradeData)
155:        external
156:        whileBorrowing
157:        nonReentrant
158:        returns (uint256)
159:    {

#0 - c4-judge

2022-12-07T17:54:55Z

dmvt marked the issue as grade-b

Awards

49.2315 USDC - $49.23

Labels

bug
G (Gas Optimization)
grade-b
G-39

External Links

FINDINGS

NB: Some functions have been truncated where neccessary to just show affected parts of the code Throught the report some places might be denoted with audit tags to show the actual place affected.

Cache storage values in memory to minimize SLOADs

The code can be optimized by minimizing the number of SLOADs.

SLOADs are expensive (100 gas after the 1st one) compared to MLOADs/MSTOREs (3 gas each). Storage values read multiple times should instead be cached in memory the first time (costing 1 SLOAD) and then read from this cache to avoid multiple SLOADs.

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/utils/EscrowLib.sol#L152-L164

EscrowLib.sol.releaseCollateral(): The value of self.deposited[token].amount should be cached

File: /contracts/utils/EscrowLib.sol
152:    function releaseCollateral(

160:    ) external returns (uint256) {

163:        if(self.deposited[token].amount < amount) { revert InvalidCollateral(); } //@audit: Initial access
164:        self.deposited[token].amount -= amount;//@audit: second access
diff --git a/contracts/utils/EscrowLib.sol b/contracts/utils/EscrowLib.sol
index e838d7f..cfb4a9c 100644
--- a/contracts/utils/EscrowLib.sol
+++ b/contracts/utils/EscrowLib.sol
@@ -160,8 +160,9 @@ library EscrowLib {
     ) external returns (uint256) {
         require(amount > 0);
         if(msg.sender != borrower) { revert CallerAccessDenied(); }
-        if(self.deposited[token].amount < amount) { revert InvalidCollateral(); }
-        self.deposited[token].amount -= amount;
+        uint _depositedAmount = self.deposited[token].amount;
+        if(_depositedAmount < amount) { revert InvalidCollateral(); }
+        self.deposited[token].amount = _depositedAmount - amount;

         LineLib.sendOutTokenOrETH(token, to, amount);

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/utils/EscrowLib.sol#L192-L207

EscrowLib.sol.liquidate(): The value of self.deposited[token].amount should be cached

File: /contracts/utils/EscrowLib.sol
192:    function liquidate(

200:        if(self.deposited[token].amount < amount) { revert InvalidCollateral(); }

202:        self.deposited[token].amount -= amount;
diff --git a/contracts/utils/EscrowLib.sol b/contracts/utils/EscrowLib.sol
index e838d7f..92d9aef 100644
--- a/contracts/utils/EscrowLib.sol
+++ b/contracts/utils/EscrowLib.sol
@@ -197,9 +197,10 @@ library EscrowLib {
     ) external returns (bool) {
         require(amount > 0);
         if(msg.sender != self.line) { revert CallerAccessDenied(); }
-        if(self.deposited[token].amount < amount) { revert InvalidCollateral(); }
+        uint _depositedAmount = self.deposited[token].amount;
+        if(_depositedAmount < amount) { revert InvalidCollateral(); }

-        self.deposited[token].amount -= amount;
+        self.deposited[token].amount = _depositedAmount - amount;

         LineLib.sendOutTokenOrETH(token, to, amount);

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/credit/SpigotedLine.sol#L137-L151

SpigotedLine.sol.useAndRepay(): unusedTokens[credit.token] should be cached

File: /contracts/modules/credit/SpigotedLine.sol
137:    function useAndRepay(uint256 amount) external whileBorrowing returns(bool) {

143:      require(amount <= unusedTokens[credit.token]);
144:      unusedTokens[credit.token] -= amount;
diff --git a/contracts/modules/credit/SpigotedLine.sol b/contracts/modules/credit/SpigotedLine.sol
index 8c91716..748f190 100644
--- a/contracts/modules/credit/SpigotedLine.sol
+++ b/contracts/modules/credit/SpigotedLine.sol
@@ -140,8 +140,9 @@ contract SpigotedLine is ISpigotedLine, LineOfCredit, ReentrancyGuard {
       if (msg.sender != borrower && msg.sender != credit.lender) {
         revert CallerAccessDenied();
       }
-      require(amount <= unusedTokens[credit.token]);
-      unusedTokens[credit.token] -= amount;
+      uint256 _unusedTokens = unusedTokens[credit.token];
+      require(amount <= _unusedTokens);
+      unusedTokens[credit.token] = _unusedTokens - amount;

       credits[id] = _repay(_accrue(credit, id), id, amount);

Internal/Private functions only called once can be inlined to save gas

Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.

Affected code:

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/interest-rate/InterestRateCredit.sol#L42-L46

File: /contracts/modules/interest-rate/InterestRateCredit.sol
42:    function _accrueInterest(
43:        bytes32 id,
44:        uint256 drawnBalance,
45:        uint256 facilityBalance
46:    ) internal returns (uint256) {

The above function is only called once on Line 39

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/credit/LineOfCredit.sol#L167-L170

File: /contracts/modules/credit/LineOfCredit.sol
167:    function _updateOutstandingDebt()
168:        internal
169:        returns (uint256 principal, uint256 interest)
170:    {


435:    function _createCredit(
436:        address lender,
437:        address token,
438:        uint256 amount
439:    )
440:        internal


516:    function _sortIntoQ(bytes32 p) internal returns (bool) {

Avoid contract existence checks by using solidity version 0.8.10 or later

Prior to 0.8.10 the compiler inserted extra code, including EXTCODESIZE (100 gas), to check for contract existence for external calls. In more recent solidity versions, the compiler will not insert these checks if the external call has a return value

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/utils/CreditLib.sol#L84

File: /contracts/utils/CreditLib.sol
84:    int256 price = IOracle(oracle).getLatestAnswer(c.token);

135:      int price = IOracle(oracle).getLatestAnswer(token);

251:      uint256 accruedToken = IInterestRateCredit(interest).accrueInterest(

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/factories/LineFactory.sol#L64-L70

File: /contracts/modules/factories/LineFactory.sol
64:        address s = factory.deploySpigot(address(this), borrower, borrower);
65:        address e = factory.deployEscrow(
66:            defaultMinCRatio,
67:            oracle,
68:            address(this),
69:            borrower
70:        );


96:        address s = factory.deploySpigot(
97:            address(this),
98:            coreParams.borrower,
99:            coreParams.borrower
100:        );
101:        address e = factory.deployEscrow(
102:            coreParams.cratio,
103:            oracle,
104:            address(this),
105:            coreParams.borrower
106:        );

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 Use a larger size then downcast where needed

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/credit/LineOfCredit.sol#L223-L229

File: /contracts/modules/credit/LineOfCredit.sol

//@audit: int128 drate,uint128 frate
223:    function addCredit(
224:        uint128 drate,
225:        uint128 frate,
226:        uint256 amount,
227:        address token,
228:        address lender
229:    )


//@audit: uint128 drate,uint128 frate
247:    function setRates(
248:        bytes32 id,
249:        uint128 drate,
250:        uint128 frate
251:    )

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/utils/LineFactoryLib.sol#L77-L86

File: /contracts/utils/LineFactoryLib.sol

//@audit: uint8 revenueSplit
77:    function deploySecuredLine(
78:        address oracle, 
79:        address arbiter,
80:        address borrower, 
81:        address payable swapTarget,
82:        address s,
83:        address e,
84:        uint ttl, 
86:        uint8 revenueSplit
87:    ) external returns(address){

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/utils/SpigotedLineLib.sol#L169

File: /contracts/utils/SpigotedLineLib.sol

//@audit:  uint8 defaultSplit
169:    function updateSplit(address spigot, address revenueContract, LineLib.STATUS status, uint8 defaultSplit) external returns (bool) {

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/utils/CreditLib.sol#L109-L113

File: /contracts/utils/CreditLib.sol

//@audit: uint8 decimals
109:  function calculateValue(
110:    int price,
111:    uint256 amount,
112:    uint8 decimals
113:  )

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/factories/LineFactory.sol#L42-L46

File: /contracts/modules/factories/LineFactory.sol

//@audit: uint32 minCRatio
42:    function deployEscrow(
43:        uint32 minCRatio,
44:        address owner,
45:        address borrower
46:    ) external returns (address) {

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/interest-rate/InterestRateCredit.sol#L74-L78

File: /contracts/modules/interest-rate/InterestRateCredit.sol

//@audit: uint128 dRate,uint128 fRate
74:    function setRate(
75:        bytes32 id,
76:        uint128 dRate,
77:        uint128 fRate
78:    ) external onlyLineContract returns (bool) {

Using unchecked blocks to save gas

Solidity version 0.8+ comes with implicit overflow and underflow checks on unsigned integers. When an overflow or an underflow isn’t possible (as an example, when a comparison is made before the arithmetic operation), some gas can be saved by using an unchecked block see resource

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/utils/SpigotedLineLib.sol#L101

File: /contracts/utils/SpigotedLineLib.sol
101:          uint256 diff = oldClaimTokens - newClaimTokens;
diff --git a/contracts/utils/SpigotedLineLib.sol b/contracts/utils/SpigotedLineLib.sol
index ac86b45..a46d9e8 100644
--- a/contracts/utils/SpigotedLineLib.sol
+++ b/contracts/utils/SpigotedLineLib.sol
@@ -98,7 +98,10 @@ library SpigotedLineLib {

         // used reserve revenue to repay debt
         if(oldClaimTokens > newClaimTokens) {
-          uint256 diff = oldClaimTokens - newClaimTokens;
+          uint256 diff;
+          unchecked {
+           diff = oldClaimTokens - newClaimTokens;
+          }
           // used more tokens than we had in revenue reserves.
           // prevent borrower from pulling idle lender funds to repay other lenders

The above operation cannot underflow due to the check on Line 100 that checks that the value of oldClaimTokens is greater than newClaimTokens before perfoming the operation

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/utils/SpigotedLineLib.sol#L109

File: /contracts/utils/SpigotedLineLib.sol
109:            unused - diff

The above operation cannot underflow due to the check on Line 105 that checks that the value of unused is greater than diff before perfoming the operation

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/utils/EscrowLib.sol#L164

File: /contracts/utils/EscrowLib.sol
164:        self.deposited[token].amount -= amount;

The above operation cannot underflow due to the check on Line 163 that checks that the value of self.deposited[token].amount is greater than amount before perfoming the operation

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/utils/EscrowLib.sol#L202

File: /contracts/utils/EscrowLib.sol
202:         self.deposited[token].amount -= amount;

The above operation cannot underflow due to the check on Line 200 that checks that the value of self.deposited[token].amount is greater than amount before perfoming the operation

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/credit/SpigotedLine.sol#L122

File: /contracts/modules/credit/SpigotedLine.sol
122:            unusedTokens[credit.token] -= repaid - newTokens;

The operation repaid - newTokens cannot underflow due to the check on Line 120 that ensures that repaid is greater than newTokens before perfoming the subtraction

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/credit/SpigotedLine.sol#L125

File: /contracts/modules/credit/SpigotedLine.sol
125:            unusedTokens[credit.token] += newTokens - repaid;

The operation newTokens - repaid would only be performed if newTokens is greater than repaid

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/credit/SpigotedLine.sol#L144

File: /contracts/modules/credit/SpigotedLine.sol
144:      unusedTokens[credit.token] -= amount;

The operation unusedTokens[credit.token] - amount cannot underflow as we have a check on Line 143 that ensures that the value of unusedTokens[credit.token] is greater than that of amount

Using unchecked blocks to save gas - Increments in for loop can be unchecked ( save 30-40 gas per loop iteration)

The majority of Solidity for loops increment a uint256 variable that starts at 0. These increment operations never need to be checked for over/underflow because the variable will never reach the max number of uint256 (will run out of gas long before that happens). The default over/underflow check wastes gas in every iteration of virtually every for loop . eg.

e.g Let's work with a sample loop below.

for(uint256 i; i < 10; i++){
//doSomething
}

can be written as shown below.

for(uint256 i; i < 10;) {
  // loop logic
  unchecked { i++; }
}

We can also write it as an inlined function like below.

function inc(i) internal pure returns (uint256) {
  unchecked { return i + 1; }
}
for(uint256 i; i < 10; i = inc(i)) {
  // doSomething
}

Affected code

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/credit/LineOfCredit.sol#L179-L196

File: /contracts/modules/credit/LineOfCredit.sol
        for (uint256 i; i < len; ++i) {
            id = ids[i];


            // null element in array from closing a position. skip for gas savings
            if(id == bytes32(0)) { continue; }


            (Credit memory c, uint256 _p, uint256 _i) = CreditLib.getOutstandingDebt(
              credits[id],
              id,
              oracle_,
              interestRate_
            );
            // update total outstanding debt
            principal += _p;
            interest += _i;
            // save changes to storage
            credits[id] = c;
        }
diff --git a/contracts/modules/credit/LineOfCredit.sol b/contracts/modules/credit/LineOfCredit.sol
index 21ef853..b6c6e36 100644
--- a/contracts/modules/credit/LineOfCredit.sol
+++ b/contracts/modules/credit/LineOfCredit.sol
@@ -176,7 +176,7 @@ contract LineOfCredit is ILineOfCredit, MutualConsent {
         address oracle_ = address(oracle);  // gas savings
         address interestRate_ = address(interestRate); // gas savings

-        for (uint256 i; i < len; ++i) {
+        for (uint256 i; i < len;) {
             id = ids[i];

             // null element in array from closing a position. skip for gas savings
@@ -193,6 +193,9 @@ contract LineOfCredit is ILineOfCredit, MutualConsent {
             interest += _i;
             // save changes to storage
             credits[id] = c;
+            unchecked {
+                ++i;
+            }
         }
     }

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/credit/LineOfCredit.sol#L203-L207

File:/contracts/modules/credit/LineOfCredit.sol
203:        for (uint256 i; i < len; ++i) {
204:          id = ids[i];
205:          Credit memory credit = credits[id];
206:          credits[id] = _accrue(credit, id);
207:        }
diff --git a/contracts/modules/credit/LineOfCredit.sol b/contracts/modules/credit/LineOfCredit.sol
index 21ef853..0360364 100644
--- a/contracts/modules/credit/LineOfCredit.sol
+++ b/contracts/modules/credit/LineOfCredit.sol
@@ -200,10 +200,13 @@ contract LineOfCredit is ILineOfCredit, MutualConsent {
     function accrueInterest() external override returns(bool) {
         uint256 len = ids.length;
         bytes32 id;
-        for (uint256 i; i < len; ++i) {
+        for (uint256 i; i < len;) {
           id = ids[i];
           Credit memory credit = credits[id];
           credits[id] = _accrue(credit, id);
+          unchecked {
+            ++i;
+          }
         }

         return true;

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/credit/LineOfCredit.sol#L520-L537

File: /contracts/modules/credit/LineOfCredit.sol
        for (uint256 i; i <= lastSpot; ++i) {
          
        }
diff --git a/contracts/modules/credit/LineOfCredit.sol b/contracts/modules/credit/LineOfCredit.sol
index 21ef853..0aca439 100644
--- a/contracts/modules/credit/LineOfCredit.sol
+++ b/contracts/modules/credit/LineOfCredit.sol
@@ -517,7 +517,7 @@ contract LineOfCredit is ILineOfCredit, MutualConsent {
         uint256 lastSpot = ids.length - 1;
         uint256 nextQSpot = lastSpot;
         bytes32 id;
-        for (uint256 i; i <= lastSpot; ++i) {
+        for (uint256 i; i <= lastSpot;) {
             id = ids[i];
             if (p != id) {
                 if (
@@ -533,6 +533,10 @@ contract LineOfCredit is ILineOfCredit, MutualConsent {
                 ids[nextQSpot] = p;         // p put at target index
                 return true;
             }
+
+            unchecked {
+                ++i;
+            }

         }
     }

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/utils/EscrowLib.sol#L57-L81

File: /contracts/utils/EscrowLib.sol
57:        for (uint256 i; i < length; ++i) {

81:        }
diff --git a/contracts/utils/EscrowLib.sol b/contracts/utils/EscrowLib.sol
index e838d7f..8c945c4 100644
--- a/contracts/utils/EscrowLib.sol
+++ b/contracts/utils/EscrowLib.sol
@@ -54,7 +54,7 @@ library EscrowLib {
         uint256 length = self.collateralTokens.length;
         IOracle o = IOracle(oracle);
         IEscrow.Deposit memory d;
-        for (uint256 i; i < length; ++i) {
+        for (uint256 i; i < length;) {
             address token = self.collateralTokens[i];
             d = self.deposited[token];
              // new var so we don't override original deposit amount for 4626 tokens
@@ -78,6 +78,9 @@ library EscrowLib {
                   d.assetDecimals
                 );
             }
+            unchecked {
+                ++i;
+            }
         }

         return collateralValue;

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/utils/CreditListLib.sol#L23-L29

File: /contracts/utils/CreditListLib.sol
23:      for(uint256 i; i < len; ++i) {
          
29:      }
diff --git a/contracts/utils/CreditListLib.sol b/contracts/utils/CreditListLib.sol
index 1c378e4..28b062a 100644
--- a/contracts/utils/CreditListLib.sol
+++ b/contracts/utils/CreditListLib.sol
@@ -20,12 +20,14 @@ library CreditListLib {
     function removePosition(bytes32[] storage ids, bytes32 id) external returns(bool) {
       uint256 len = ids.length;

-      for(uint256 i; i < len; ++i) {
+      for(uint256 i; i < len;) {
           if(ids[i] == id) {
               delete ids[i];
               return true;
           }
-
+        unchecked {
+          ++i;
+        }
       }

       return true;

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/utils/CreditListLib.sol#L51-L54

File: /contracts/utils/CreditListLib.sol
51:        for(uint i = 1; i < len; ++i) {
52:          ids[i - 1] = ids[i]; // could also clean arr here like in _SortIntoQ
          
54:        }

Since the operation len - 1 cannot underflow we can wrap the whole loop with unchecked blocks

diff --git a/contracts/utils/CreditListLib.sol b/contracts/utils/CreditListLib.sol
index 1c378e4..749dcfc 100644
--- a/contracts/utils/CreditListLib.sol
+++ b/contracts/utils/CreditListLib.sol
@@ -48,12 +48,14 @@ library CreditListLib {
         ids[1] = last;
       } else {
         // move all existing ids up in the queue
-        for(uint i = 1; i < len; ++i) {
-          ids[i - 1] = ids[i]; // could also clean arr here like in _SortIntoQ
+        unchecked {
+          for(uint i = 1; i < len; ++i) {
+            ids[i - 1] = ids[i]; // could also clean arr here like in _SortIntoQ

-        }
+          }
         // cycle first id back to end of queue
-        ids[len - 1] = last;
+         ids[len - 1] = last;
+        }
       }

       return true;

see resource

Use shorter revert strings(less than 32 bytes)

Every reason string takes at least 32 bytes so make sure your string fits in 32 bytes or it will become more expensive.Each extra chunk of byetes past the original 32 incurs an MSTORE which costs 3 gas

Shortening revert strings to fit in 32 bytes will decrease deployment time gas and will decrease runtime gas when the revert condition is met. Revert strings that are longer than 32 bytes require at least one additional mstore, along with additional overhead for computing memory offset, etc.

https://github.com/debtdao/Line-of-Credit/blob/e8aa08b44f6132a5ed901f8daa231700c5afeb3a/contracts/modules/interest-rate/InterestRateCredit.sol#L26-L29

File: /contracts/modules/interest-rate/InterestRateCredit.sol
26:        require(
27:            msg.sender == lineContract,
28:            "InterestRateCred: only line contract."
29:        );

#0 - c4-judge

2022-11-17T23:07:06Z

dmvt marked the issue as grade-b

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