Omniscia Avocado Fund Audit

AvocadoVault Manual Review Findings

AvocadoVault Manual Review Findings

AVT-01M: Incorrect Cap Assumption

Description:

The AvocadoVault::_deposit function assumes that the cap will always be larger than the current amount of assets which may not be correct due to re-configurations of the cap or a direct token transfer.

Impact:

Deposits that should normally fail due to the VaultCapExceeded error would yield an undefined underflow error in the current implementation due to an incorrect assumption.

Example:

contracts/src/AvocadoVault.sol
122function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override whenNotPaused {
123 uint256 cap = vaultCap;
124 uint256 current = totalAssets();
125 if (current + assets > cap) revert VaultCapExceeded(assets, cap - current);
126 super._deposit(caller, receiver, assets, shares);
127}

Recommendation:

We advise the code to use a saturating subtraction, yielding 0 if the cap is lower than the current asset balance.

Alleviation (a859cd2191d509cbc6d47508bdd44ec6d3cc9844):

The code was updated to properly perform a saturating subtraction and thus yield a correct VaultCapExceeded error regardless of what the cap is presently configured as.

AVT-02M: Improper Performance Fee Adjustment

Description:

The AvocadoVault::setPerformanceFee function permits a retro-active application of a new performanceFeeBps configuration on already-gathered interest which is incorrect.

Impact:

A performanceFeeBps adjustment can retroactively apply to a large amount of interest that has already gathered which is unfair to depositors who supplied funds at different configurations.

Example:

contracts/src/AvocadoVault.sol
224/// @inheritdoc IAvocadoVault
225function setPerformanceFee(uint16 newFeeBps) external onlyOwner {
226 if (newFeeBps > MAX_FEE_BPS) revert InvalidFee(newFeeBps);
227 emit PerformanceFeeUpdated(performanceFeeBps, newFeeBps);
228 performanceFeeBps = newFeeBps;
229}

Recommendation:

We advise the function to invoke the AvocadoVault::collectFees function before the performance fee BPS is reconfigured, ensuring that performance fee BPS adjustments are not retroactive.

Alleviation (a859cd2191d509cbc6d47508bdd44ec6d3cc9844):

The AvocadoVault::setPerformanceFee function was updated to properly collect any pending interest prior to updating the performanceFeeBps value, addressing this exhibit.

AVT-03M: Inexistent Access Control of Fee Collection

Description:

The AvocadoVault::collectFees function does not have access control, permitting a user to collect fees right before the feeRecipient is changed as well as collecting fees at a time the feeRecipient might not be aware of them.

Impact:

Any adjustment of the feeRecipient is slightly unsafe as a transaction can be submitted before it claiming any pending interest to the wrong previously-configured account.

Example:

contracts/src/AvocadoVault.sol
231/// @inheritdoc IAvocadoVault
232function setFeeRecipient(address newRecipient) external onlyOwner {
233 if (newRecipient == address(0)) revert ZeroAddress();
234 feeRecipient = newRecipient;
235}

Recommendation:

We advise some form of access control to be imposed, either by requiring the caller to be the feeRecipient or the owner of the contract.

Alleviation (a859cd2191d509cbc6d47508bdd44ec6d3cc9844):

The AvocadoVault::collectFees function was updated to properly impose access control, ensuring that it can be solely invoked by the owner of the contract or the feeRecipient themselves.

AVT-04M: Incorrect Handling of Interest

Description:

The AvocadoVault::collectFees function incorrectly handles the performance fee on interest as it calculates shares based on the feeAmount that is part of the TVL of the vault at the given time whereas normal deposit flows are based on the TVL before the funds are pulled into the contract.

Impact:

All performance fee collections presently cause a dilution to occur, with the S-02 fix being incorrect as presently implemented.

Example:

contracts/src/AvocadoVault.sol
181/// @notice Collect performance fees on accrued interest
182/// @dev Mints shares to the fee recipient rather than transferring USDC
183/// (avoids liquidity drain; fee recipient redeems at will)
184function collectFees() external {
185 uint256 interest = accruedInterest;
186 if (interest == 0) return;
187
188 uint256 feeAmount = (interest * performanceFeeBps) / BPS_DENOMINATOR;
189 if (feeAmount == 0) return;
190
191 accruedInterest = 0;
192
193 // S-02 fix: compute feeShares BEFORE reducing deployedAssets
194 // to prevent share dilution from lowered totalAssets()
195 uint256 feeShares = convertToShares(feeAmount);
196
197 // Reduce deployedAssets AFTER share conversion
198 if (feeAmount <= deployedAssets) {
199 deployedAssets -= feeAmount;
200 }
201
202 // Mint fee shares to recipient
203 _mint(feeRecipient, feeShares);
204
205 emit FeesCollected(feeRecipient, feeAmount);
206}

Recommendation:

We advise the code and flow to be revised, increasing the deployedAssets in AvocadoVault::reportInterest by the fee-free amount that remains from the interestAmount, instead storing the actual fee in the accruedInterest data entry.

This will permit the AvocadoVault::collectFees function to measure the accruedInterest as a new deposit, increasing the deployedAssets by the interestAmount after measuring the share amount required.

Alleviation (a859cd2191d509cbc6d47508bdd44ec6d3cc9844):

The overall interest accrual flow has been revised per our instructions, ensuring that the fee shares that are minted from fee-related interest are tracked properly and do not dilute existing holders.