Omniscia Avocado Fund Audit
AvocadoLending Manual Review Findings
AvocadoLending Manual Review Findings
ALG-01M: Inexistent Slippage Control of Borrow Rate
| Type | Severity | Location |
|---|---|---|
| Logical Fault | ![]() | AvocadoLending.sol:L98-L104 |
Description:
The AvocadoLending::borrow function does not permit prospective borrowers from controlling the borrowRateBps they are willing to accept, causing mutations that occur between a transaction's submission and its execution to affect them unfairly.
Impact:
While borrowRateBps updates are expected to be rare, they can significantly degrade the user experience of borrowers as they would be assigned unfair loan terms that do not reflect their original agreement.
Example:
98function borrow(uint256 amount)99 external100 nonReentrant101 whenNotPaused102 onlyApprovedBorrower103 returns (uint256 loanId)104{105 if (amount == 0) revert ZeroAmount();106
107 Borrower storage b = _borrowers[msg.sender];108 if (b.isDefaulted) revert BorrowerNotApproved(msg.sender);109
110 // Check credit headroom111 uint256 currentDebt = _currentPrincipal(msg.sender);112 if (currentDebt + amount > b.creditLimit) {113 revert ExceedsCreditLimit(amount, b.creditLimit - currentDebt);114 }115
116 // Check vault has enough idle liquidity117 uint256 vaultIdle = usdc.balanceOf(vault);118 uint256 vaultDeployed = AvocadoVault(vault).deployedAssets();119 // We hold the funds already deployed here; just check what vault can give us120 uint256 available = vaultDeployed == 0 ? vaultIdle : usdc.balanceOf(address(this));121 if (amount > usdc.balanceOf(address(this))) {122 // Need more from vault - vault owner must have called deployToLending first123 revert InsufficientVaultLiquidity(usdc.balanceOf(address(this)), amount);124 }125
126 // Accrue existing interest before new borrow127 _accrueInterest(msg.sender);128
129 // Record loan130 loanId = _loans[msg.sender].length;131 _loans[msg.sender].push(LoanTerms({132 amount: amount,133 interestRateBps: borrowRateBps,134 dueDate: block.timestamp + 30 days, // 30 day default term135 repaid: false136 }));137
138 b.principal += amount;139 b.lastInterestTime = uint64(block.timestamp);140 totalPrincipal += amount;141
142 // Transfer USDC to borrower143 usdc.safeTransfer(msg.sender, amount);144
145 emit LoanOriginated(msg.sender, loanId, amount, borrowRateBps);146}Recommendation:
We advise a slippage control variable to be introduced that specifies the maximum borrowRateBps they are willing to accept.
Alleviation (a859cd2191d509cbc6d47508bdd44ec6d3cc9844):
A proper slippage control variable maxBorrowRateBps has been introduced permitting a prospective borrower to control the maximum borrowRateBps they are willing to accept.
ALG-02M: Unsustainable Iteration Approach
| Type | Severity | Location |
|---|---|---|
| Logical Fault | ![]() | AvocadoLending.sol:L296 |
Description:
The AvocadoLending::accrueAllInterest function will breach the block's gas limit trivially as the borrower's list grows.
Impact:
The AvocadoLending::accrueAllInterest function might become inoperable as the borrowerList grows.
Example:
295function accrueAllInterest() external returns (uint256 totalInterest) {296 address[] memory list = borrowerList;297 for (uint256 i = 0; i < list.length; i++) {298 Borrower storage b = _borrowers[list[i]];299 if (b.isApproved && b.principal > 0) {300 totalInterest += _accrueInterest(list[i]);301 }302 }303 if (totalInterest > 0) {304 emit InterestAccrued(totalInterest);305 }306}Recommendation:
We advise the function to accept an input array argument instead, accruing interest on the input borrower list.
Alleviation (a859cd2191d509cbc6d47508bdd44ec6d3cc9844):
The AvocadoLending::accrueAllInterest function was updated to accept an input argument instead per our recommendation, alleviating this exhibit.
ALG-03M: Arbitrary Loan Borrow Rate
| Type | Severity | Location |
|---|---|---|
| Logical Fault | ![]() | AvocadoLending.sol:L380 |
Description:
The AvocadoLending::borrow function permits individual loans to be configured at varying borrowRateBps configurations, however, the interest accrual function AvocadoLending::_accrueInterest function accrues interest based on the global borrowRateBps.
Impact:
The acceptance of the slippage control variable exhibit ALG-01M indicates that borrow rates need to be agreed on safely, however, the current interest accrual mechanism disregards this.
Example:
368/// @notice Accrues simple interest for a specific borrower and stores it369/// @dev ALG-04M / ALG-05M fix: Interest is now accumulated in b.accruedInterest370/// so it persists across calls and is never silently discarded.371/// @return interest Amount of NEW interest accrued in this call372function _accrueInterest(address borrower) internal returns (uint256 interest) {373 Borrower storage b = _borrowers[borrower];374 if (b.principal == 0) return 0;375
376 uint256 elapsed = block.timestamp - b.lastInterestTime;377 if (elapsed == 0) return 0;378
379 // Simple interest: I = P * r * t / (SECONDS_PER_YEAR * BPS_DENOMINATOR)380 interest = (b.principal * borrowRateBps * elapsed) / (SECONDS_PER_YEAR * BPS_DENOMINATOR);381 b.lastInterestTime = uint64(block.timestamp);382
383 // ALG-04M fix: Store accrued interest in borrower struct384 b.accruedInterest += interest;385}Recommendation:
We advise the code to either accrue interest across individual positions with a cap on the maximum active positions a borrower may have, or permit multiple positions for a borrower to occur solely when the borrowRateBps remains unchanged, preventing new positions to be opened if it is altered and accruing interest based on the last borrowRateBps the user agreed to.
Alleviation (ca4268ecd3):
The Avocado Fund team evaluated this exhibit and opted to apply the second alleviation we advised.
During our inspection, we identified that the alleviation has not been applied properly. The borrowRateBps the borrower agrees to should be set during an AvocadoLending::borrow call, and subsequent AvocadoLending::borrow calls must ensure that, if the user has an active position, the borrowRateBps has remained unchanged. If it has not, no new positions should be able to be opened, as that would permit the borrower to update their borrow rate retroactively.
Alleviation (28abe24a8b):
The Avocado Fund team evaluated our follow-up recommendation and proceeded to properly alleviate this exhibit by ensuring the borrow rate BPS value remains unchanged if the borrow position principal is non-zero, indicating an active position.
ALG-04M: Incorrect Loan Repayment Tracking
| Type | Severity | Location |
|---|---|---|
| Logical Fault | ![]() | AvocadoLending.sol:L179 |
Description:
The loan repayment tracking in AvocadoLending::repay will mark a loan as repaid solely if all outstanding debt of a borrower has been repaid which is incorrect as a loan's debt may be repaid before the borrower's position closes.
Impact:
Individual loan repayments will presently malfunction and not mark them as repaid.
Example:
149function repay(uint256 loanId, uint256 amount) external nonReentrant {150 if (amount == 0) revert ZeroAmount();151
152 LoanTerms storage loan = _loans[msg.sender][loanId];153 if (loan.amount == 0) revert LoanNotFound(loanId);154 if (loan.repaid) revert LoanAlreadyRepaid(loanId);155
156 // Accrue interest first157 uint256 interestOwed = _accrueInterest(msg.sender);158
159 Borrower storage b = _borrowers[msg.sender];160
161 // Apply payment: first to interest, then to principal162 uint256 remaining = amount;163 uint256 interestPayment = 0;164 uint256 principalPayment = 0;165
166 if (remaining >= interestOwed) {167 interestPayment = interestOwed;168 remaining -= interestOwed;169 pendingInterest += interestPayment;170 } else {171 interestPayment = remaining;172 pendingInterest += interestPayment;173 remaining = 0;174 }175
176 if (remaining > 0) {177 principalPayment = remaining < b.principal ? remaining : b.principal;178 b.principal -= principalPayment;179 if (b.principal == 0) loan.repaid = true;180 }Recommendation:
We advise the code to properly mark a loan as repaid as long as its full amount has been paid back.
Alleviation (a859cd2191d509cbc6d47508bdd44ec6d3cc9844):
The AvocadoLending::repay function was properly refactored to accommodate for individual full loan repayments whilst the total position of the borrower is still in debt, properly marking individual repaid loans as repaid.
ALG-05M: Incorrect Interest Accrual
| Type | Severity | Location |
|---|---|---|
| Logical Fault | ![]() | AvocadoLending.sol:L127 |
Description:
The AvocadoLending::borrow function permits interest to be eliminated as the interest amount yielded by AvocadoLending::_accrueInterest remains unused.
Impact:
Any interest a user may have can be avoided by performing a 1 wei borrow operation.
Example:
126// Accrue existing interest before new borrow127_accrueInterest(msg.sender);Recommendation:
We advise it to be properly captured either via a Borrower data entry or some other contract-level storage entry to ensure that all interest is properly captured.
Alleviation (a859cd2191d509cbc6d47508bdd44ec6d3cc9844):
The internal AvocadoLending::_accrueInterest function was revised to immediately track any interest accrual in storage within the accruedInterest entry of the borrower's position, alleviating this exhibit.
ALG-06M: Incorrect Mass Interest Accrual
| Type | Severity | Location |
|---|---|---|
| Logical Fault | ![]() | AvocadoLending.sol:L295-L306 |
Description:
The AvocadoLending::accrueAllInterest function will iterate through all borrowers and attempt to accrue interest, however, in doing so it disregards it without recording it anywhere.
Impact:
Any AvocadoLending::accrueAllInterest function call will cause interest to be eliminated from all borrowers.
Example:
294/// @inheritdoc IAvocadoLending295function accrueAllInterest() external returns (uint256 totalInterest) {296 address[] memory list = borrowerList;297 for (uint256 i = 0; i < list.length; i++) {298 Borrower storage b = _borrowers[list[i]];299 if (b.isApproved && b.principal > 0) {300 totalInterest += _accrueInterest(list[i]);301 }302 }303 if (totalInterest > 0) {304 emit InterestAccrued(totalInterest);305 }306}Recommendation:
We advise interest accrual across borrowers to be corrected, ensuring that interest is stored in a permanent data entry.
Alleviation (a859cd2191d509cbc6d47508bdd44ec6d3cc9844):
Due to the revisions applied for ALG-04M, this exhibit was indirectly alleviated as a result given that each individual AvocadoLending::_accrueInterest call will store accrued interest in each borrower's position in storage.


