Omniscia Ultra Yield Audit

UltraFeeder Manual Review Findings

UltraFeeder Manual Review Findings

UFR-01M: Inefficient Redemption Fulfilment Structure

Description:

The referenced redemption fulfilment function structure is complicated unnecessarily, relying on before, after, as well as extra after-execution logic for redemption fulfilments.

Impact:

While this is a gas optimization, the complexity of the code is prone to error and must be simplified.

Example:

src/vaults/UltraFeeder.sol
184/// @dev Before fulfill redeem - transfer funds from fundsHolder to vault
185/// @dev "assets" will already be correct given the token user requested
186function beforeFulfillRedeem(address _asset, uint256 assets, uint256 shares) internal override {
187 IUltraVault mainVault_ = mainVault();
188 // Fulfill redeem in main vault. Returns asset units
189 mainVault_.fulfillRedeemOfAsset(_asset, shares, address(this));
190 uint256 mainAssetsClaimed = mainVault_.redeemAsset(_asset, shares, address(this), address(this));
191
192 // Deduct the expected withdrawal fee from the total amount of assets
193 uint256 expectedAssetsAfterFees = assets - mainVault_.calculateWithdrawalFee(assets);
194 require(mainAssetsClaimed == expectedAssetsAfterFees, AssetNumberMismatch());
195}
196
197/// @dev Internal fulfill redeem request logic
198function _fulfillRedeemOfAsset(
199 address _asset,
200 uint256 assets,
201 uint256 shares,
202 address controller
203) internal override returns (uint256) {
204 uint256 assetsFulfilled = super._fulfillRedeemOfAsset(_asset, assets, shares, controller);
205 // Deduct the expected withdrawal fee from the total amount of assets
206 uint256 withdrawalFee = mainVault().calculateWithdrawalFee(assetsFulfilled);
207 return assetsFulfilled - withdrawalFee;
208}
209
210/// @dev Hook for inheriting contracts after fulfill redeem
211/// @dev Correct claimable redeem amounts to account for underlying vault fees
212function afterFulfillRedeem(
213 address _asset,
214 uint256 assets,
215 uint256, // shares
216 address controller
217) internal override {
218 // The base implementation has added `_assets` to the claimable assets
219 // We need to calculate the withdrawal fee and deduct it from the claimable assets
220 uint256 withdrawalFee = mainVault().calculateWithdrawalFee(assets);
221 _consumeClaimableRedeem(controller, _asset, withdrawalFee, 0);
222}

Recommendation:

We advise the code of the BaseControlledAsyncRedeem::_fulfillRedeemOfAsset function to be updated to utilize a return value by the UltraFeeder::beforeFulfillRedeem function, permitting the actual assets to be redeemed to be yielded and thus simplifying the code greatly.

Alleviation (28f27853965de07fb79f4f2b5fed696d35120032):

The Ultra Yield team evaluated this exhibit and opted to not implement the optimization outlined as it would significantly affect the logic of the UltraVault and UltraFeeder contracts.