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: 83/120
Findings: 1
Award: $61.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
61.3462 USDC - $61.35
Consider using a modifier for access control.
Below are 10 instances of this issue:
function declareInsolvent() external whileBorrowing returns(bool) { if(arbiter != msg.sender) { revert CallerAccessDenied(); }
if (msg.sender != borrower && msg.sender != credit.lender) { revert CallerAccessDenied(); }
function liquidate( uint256 amount, address targetToken ) external whileBorrowing returns(uint256) { if(msg.sender != arbiter) { revert CallerAccessDenied(); }
function addSpigot(SpigotState storage self, address revenueContract, ISpigot.Setting memory setting) external returns (bool) { if(msg.sender != self.owner) { revert CallerAccessDenied(); }
/** see Spigot.removeSpigot */ function removeSpigot(SpigotState storage self, address revenueContract) external returns (bool) { if(msg.sender != self.owner) { revert CallerAccessDenied(); }
function updateOwnerSplit(SpigotState storage self, address revenueContract, uint8 ownerSplit) external returns(bool) { if(msg.sender != self.owner) { revert CallerAccessDenied(); }
function updateOwner(SpigotState storage self, address newOwner) external returns (bool) { if(msg.sender != self.owner) { revert CallerAccessDenied(); }
function updateOperator(SpigotState storage self, address newOperator) external returns (bool) { if(msg.sender != self.operator) { revert CallerAccessDenied(); }
function updateTreasury(SpigotState storage self, address newTreasury) external returns (bool) { if(msg.sender != self.operator && msg.sender != self.treasury) { revert CallerAccessDenied();
function updateWhitelistedFunction(SpigotState storage self, bytes4 func, bool allowed) external returns (bool) { if(msg.sender != self.owner) { revert CallerAccessDenied(); }
Consider moving this function into an abstract contract or an interface.
function _canDeclareInsolvent() internal virtual returns(bool) { // logic updated in Spigoted and Escrowed lines return true; }
If there is a return value, then every code-path should explicitly return something.
function _sortIntoQ(bytes32 p) internal returns (bool) { uint256 lastSpot = ids.length - 1; uint256 nextQSpot = lastSpot; bytes32 id; for (uint256 i; i <= lastSpot; ++i) { id = ids[i]; if (p != id) { if ( id == bytes32(0) || // deleted element. In the middle of the q because it was closed. nextQSpot != lastSpot || // position already found. skip to find `p` asap credits[id].principal > 0 //`id` should be placed before `p` ) continue; nextQSpot = i; // index of first undrawn line found } else { if(nextQSpot == lastSpot) return true; // nothing to update // swap positions ids[i] = ids[nextQSpot]; // id put into old `p` position ids[nextQSpot] = p; // p put at target index return true; } } }
In the assignment in the code below, it is not certain, that block.timestamp really is the right time for lastAccrued, since accrual does not happen locally. It should be ok in the current context but this is a potential source of problems.
rates[id] = Rate({ dRate: dRate, fRate: fRate, lastAccrued: block.timestamp });
There seems to be a "corresponding function". The event is emitted in line 191 in function 'repay'.
event RepayPrincipal(bytes32 indexed id, uint256 indexed amount); // Emits that a Borrower has repaid an amount of principal - there is no corresponding function
It returns a price. So it should be called be "IOracle(oracle).getLatestPrice(c.token);".
int256 price = IOracle(oracle).getLatestAnswer(c.token);
If 'price' can be negative (type int), then so can be 'value'. Either 'price' should be 'uint' or 'value' should allow negative numbers as well.
return price <= 0 ? 0 : (amount * uint(price)) / (1 * 10 ** decimals);
If too much is repayed an underflow might happen.
credit.principal -= principalPayment;
The "msg.value < amount" code should be "msg.value != amount". The contract should not collect excess eth. The correct check also saves a drop of gas.
if(msg.value < amount) { revert TransferFailed(); }
If a project is meant to be taken serious, there should be no "fucks up" in comments.
// cant claim revenue via operate() because that fucks up accounting logic. Owner shouldn't whitelist it anyway but just in case
Single letter variables names should only be reserved for counter and the like. The code becomes much more readable when other variables get proper, self-explanatory names.
Below are 2 instances of this issue:
(Credit memory c, uint256 _p, uint256 _i) = CreditLib.getOutstandingDebt(
returns (ILineOfCredit.Credit memory c, uint256 principal, uint256 interest)
In the following code 'borrower' should read 'borrowed'.
* @param token - ERC20 token that is being lent and borrower
In the following code 'pat' should read 'pay'.
* @param targetToken - The credit token that needs to be bought in order to pat down debt. Always `credits[ids[0]].token`
In the following code 'swithc' should read 'switch'.
* @notice helper function to allow borrower to easily swithc collateral to a new Line after repyment
In the following code 'priviliegad' should read 'privileged'.
*(@dev priviliegad internal function.
In the following code 'interset' should read 'interest'.
* @param oracle - interset rate contract used by line that will calculate interest owed
In the following code 'interset' should read 'interest'.
* @param interest - interset rate contract used by line that will calculate interest owed
Exceptions should not be used to handle normal control flow. When there is a known potential issue, it should be checked in code and handled with an explicit 'revert'.
Below are 2 instances of this issue:
// claimed = total balance - already accounted for balance claimed = existingBalance - self.escrowed[token]; // underflow revert ensures we have more tokens than we started with and actually claimed revenue
// claimed = total balance - existing balance claimed = LineLib.getBalance(token) - existingBalance; // underflow revert ensures we have more tokens than we started with and actually claimed revenue
Consider using 'allowlist' instead of 'whitelist'.
Below are 2 instances of this issue:
// cant claim revenue via operate() because that fucks up accounting logic. Owner shouldn't whitelist it anyway but just in case
return self.whitelistedFunctions[func];
Finished code should not have any open questions. Someone should answer them before the project is deployed.
self.escrowed[token] = 0; // keep 1 in escrow for recurring call gas optimizations?
#0 - c4-judge
2022-12-06T21:45:59Z
dmvt marked the issue as grade-b