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: 29/120
Findings: 2
Award: $669.35
๐ Selected for report: 0
๐ Solo Findings: 0
๐ 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
During the audit, 9 non-critical issues were found.
โ | Title | Risk Rating | Instance Count |
---|---|---|---|
NC-1 | Order of Functions | Non-Critical | 4 |
NC-2 | Order of Layout | Non-Critical | 2 |
NC-3 | Spaces between the control structures | Non-Critical | 87 |
NC-4 | Open TODOs | Non-Critical | 2 |
NC-5 | Open question | Non-Critical | 3 |
NC-6 | Typos | Non-Critical | 26 |
NC-7 | Maximum line length exceeded | Non-Critical | 50+ |
NC-8 | Missing NatSpec | Non-Critical | 50+ |
NC-9 | No error message in require | Non-Critical | 23 |
According to Style Guide, ordering helps readers identify which functions they can call and to find the constructor and fallback definitions easier.
Functions should be grouped according to their visibility and ordered:
receive() function at the end but should be after constructor:
external functions between internal:
public functions before external:
Reorder functions where possible.
According to Order of Layout, inside each contract, library or interface, use the following order:
Modifiers after constructor:
Place modifiers before constructor.
According to Style Guide, there should be a single space between the control structures if
, while
, and for
and the parenthetic block representing the conditional.
for(uint256 i; i < len; ++i) {
for(uint i = 1; i < len; ++i) {
if(
Change:
if(...) { ... }
to:
if (...) { ... }
Resolve issues.
[Bob - consider renaming to newIds
* Bob - consider renaming this function removeId()
self.escrowed[token] = 0; // keep 1 in escrow for recurring call gas optimizations?
// Line Financials aggregated accross all existing Credit
=> across
* @param arbiter_ - A neutral party with some special priviliges on behalf of Borrower and Lender.
=> privileges
* @dev - updates `status` variable in storage if current status is diferent from existing status
=> different
@notice - accrues token demoninated interest on a lender's position.
=> denominated
// ensure all money owed is accounted for. Accrue facility fee since prinicpal was paid off
=> principal
* @notice - updates `status` variable in storage if current status is diferent from existing status.
=> different
* @notice - Insert `p` into the next availble FIFO position in the repayment queue
=> available
* @param arbiter_ - neutral party with some special priviliges on behalf of borrower and lender
=> privileges
* @notice requires Spigot contract itselgf to be transfered to Arbiter and sold off to a 3rd party before declaring insolvent
=> itself
* @notice requires Spigot contract itselgf to be transfered to Arbiter and sold off to a 3rd party before declaring insolvent
=> transferred
* @dev - priviliged internal function
=> privileged
* @return isInsolvent - if the entire Line including all collateral sources is fuly insolvent.
=> fully
* @notice helper function to allow borrower to easily swithc collateral to a new Line after repyment
=> switch
* @notice helper function to allow borrower to easily swithc collateral to a new Line after repyment
=> repayment
* @dev - revenuContract's transfer func MUST only accept one paramteter which is the new owner.
=> parameter
* @dev - Used if we setup Escrow before Line exists. Line has no way to interface with this function so once transfered `line` is set forever
=> transferred
* @notice - allows the lines arbiter to enable thdeposits of an asset
=> the deposits
* - gives better risk segmentation forlenders
=> for lenders
* @notice Interest rate / acrrued interest calculation contract for Line of Credit contracts
=> accrued
* @param credit - The lender position that is being bwithdrawn from
=> withdrawn
// emit events before seeting to 0
=> setting
* @param interest - interset rate contract used by line that will calculate interest owed
=> interest
// get token demoninated interest accrued
=> denominated
* @dev priviliged internal function
=> privileged
* @param spigot - The Spigot to claim from. Must be owned by adddress(this)
=> address
* @notice - Transfers ownership of the entire Spigot and its revenuw streams from its then Owner to either
=> revenue
According to Style Guide, maximum suggested line length is 120 characters.
More than 50 instances.
Make the lines shorter.
NatSpec is missing for more than 50 functions.
Add NatSpec for all functions.
require
require(uint(status) >= uint( LineLib.STATUS.ACTIVE));
require(interestRate.setRate(id, drate, frate));
require(interestRate.setRate(id, drate, frate));
require(amount <= credit.principal + credit.interestAccrued);
require(defaultRevenueSplit_ <= SpigotedLineLib.MAX_SPLIT);
require(amount <= unusedTokens[credit.token]);
require(msg.sender == borrower);
require(msg.sender == arbiter);
require(escrow_.liquidate(amount, targetToken, to));
require(escrow.updateLine(newLine));
require(amount > 0);
require(msg.sender == ILineOfCredit(self.line).arbiter());
require(amount > 0);
require(amount > 0);
require(msg.sender == self.line);
require(LineLib.sendOutTokenOrETH(token, self.treasury, claimed - escrowedAmount));
require(revenueContract != address(this));
require(self.settings[revenueContract].transferOwnerFunction == bytes4(0));
require(success);
require(newOwner != address(0));
require(newOperator != address(0));
require(newTreasury != address(0));
require(ISpigot(spigot).updateOwner(newLine));
Add error messages.
#0 - c4-judge
2022-12-07T17:25:23Z
dmvt marked the issue as grade-b
#1 - c4-judge
2022-12-07T20:41:35Z
dmvt marked the issue as grade-a
๐ 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
During the audit, 3 gas issues were found.
Total savings ~800.
โ | Title | Instance Count | Saved |
---|---|---|---|
G-1 | Use calldata instead of memory for read-only arguments | 5 | 300 |
G-2 | Use unchecked blocks for incrementing i | 6 | 210 |
G-3 | Use unchecked blocks for subtractions where underflow is impossible | 9 | 315 |
calldata
instead of memory for
read-only argumentsSince Solidity v0.6.9, memory and calldata are allowed in all functions regardless of their visibility type (See "Calldata Variables" section here).
When function arguments should not be modified, it is cheaper to use calldata.
Consider using calldata where possible.
This saves at least 60 gas per iteration.
So, ~60*5 = 300
In Solidity 0.8+, thereโs a default overflow and underflow check on unsigned integers. In the loops, "i" will not overflow because the loop will run out of gas before that.
Change:
for (uint256 i; i < n; ++i) { // ... }
to:
for (uint256 i; i < n;) { // ... unchecked { ++i; } }
This saves ~30-40 gas per iteration.
So, ~35*6 = 210
In Solidity 0.8+, thereโs a default overflow and underflow check on unsigned integers. When an overflow or underflow isnโt possible (after require or if-statement), some gas can be saved by using unchecked blocks.
repaid - newTokens
newTokens - repaid
unusedTokens[credit.token] -= amount
ids[i - 1] = ids[i]
self.deposited[token].amount -= amount
self.deposited[token].amount -= amount
claimed - escrowedAmount
diff = oldClaimTokens - newClaimTokens;
unused - diff
This saves ~35.
So, ~35*9 = 315
#0 - c4-judge
2022-11-17T23:04:51Z
dmvt marked the issue as grade-b