YoVault → V2 upgrade

Vault contract upgrades are deployed to improve security, efficiency, and functionality. Each upgrade undergoes thorough auditing and governance approval before deployment.

Scheduled: June 4, 2026

Upgrade In
--

Hours

:
--

Min

:
--

Sec

Local: June 4, 2026 at 3:00 PM

UTC: June 4, 2026 at 3:00 PM

Summary

Upgrades all six yoVaults (yoETH, yoUSD, yoloUSD, yoEUR, yoGOLD, yoBTC) on Ethereum to V2, introducing the operator-safety stack. Vault allocations are now constrained by on-chain registries (allowlisted Morpho markets, ERC4626/IPOR vaults, swap pairs) and per-token approval caps, so the operator can only route funds to pre-approved destinations within bounded limits. Storage layout is preserved and share balances and accounting are unchanged, so no user action is required and deposits and withdrawals are unaffected. The change is timelocked for 48 hours and executed by the admin multisig.

Code Changes

contracts/YoVault.sol
+158-108
@@ -1,77 +1,80 @@
11 // SPDX-License-Identifier: MIT
2-pragma solidity 0.8.28;
2+pragma solidity 0.8.34;
33
4-import {Errors} from "./libraries/Errors.sol";
5-import {IYoVault} from "./interfaces/IYoVault.sol";
6-import {IYoOracle} from "./interfaces/IYoOracle.sol";
4+import { Errors } from "./libraries/Errors.sol";
5+import { IYoVault } from "./interfaces/IYoVault.sol";
6+import { IYoOracle } from "./interfaces/IYoOracle.sol";
7+import { IYoApprovalRegistry } from "./interfaces/IYoApprovalRegistry.sol";
78
8-import {Compatible} from "./base/Compatible.sol";
9-import {AuthUpgradeable, Authority} from "./base/AuthUpgradeable.sol";
9+import { Compatible } from "./base/Compatible.sol";
10+import { AuthUpgradeable } from "./base/AuthUpgradeable.sol";
11+import { IAuthority } from "./interfaces/IAuthority.sol";
1012
11-import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
12-import {Address} from "@openzeppelin/contracts/utils/Address.sol";
13-import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
14-import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
15-import {ERC4626Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";
16-import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
13+import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
14+import { Address } from "@openzeppelin/contracts/utils/Address.sol";
15+import { IERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
16+import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
17+import { ERC4626Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";
18+import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
1719
1820 // __ __ ____ _ _
1921 // \ \ / /__ | _ \ _ __ ___ | |_ ___ ___ ___ | |
2022 // \ V / _ \| |_) | '__/ _ \| __/ _ \ / __/ _ \| |
2123 // | | (_) | __/| | | (_) | || (_) | (_| (_) | |
2224 // |_|\___/|_| |_| \___/ \__\___/ \___\___/|_|
23-/// @title yoVault_V2 - A simple vault contract that allows for an operator to manage the vault.
24-/// @dev This contract is based on the ERC4626 standard and uses the Auth contract for access control.
25-/// It provides an asynchronous redeem mechanism that allows users to request a redeem and the operator to fulfill it.
26-/// This would allow the operator to move funds to a different chain or strategy before the user can claim the assets.
27-/// If the vault has enough assets to fulfill the request, the assets are withdrawn and returned to the owner
28-/// immediately. Otherwise, the assets are transferred to the vault and the request is stored until the operator
29-/// fulfills it.
25+/// @title YoVault - Operator-managed ERC4626 vault with asynchronous redemptions.
26+/// @dev Extends ERC4626 with role-based access control and an async redeem mechanism.
27+/// Users call `requestRedeem` — if the vault holds enough assets the withdrawal is instant,
28+/// otherwise shares are escrowed until the operator calls `fulfillRedeem`.
29+/// Oracle-driven pricing: share conversions query `_oracleAsset()` via `ORACLE_ADDRESS`,
30+/// which subclasses can override to price against a different asset.
3031
31-contract YoVault_V2 is ERC4626Upgradeable, Compatible, IYoVault, AuthUpgradeable, PausableUpgradeable {
32+contract YoVault is ERC4626Upgradeable, Compatible, IYoVault, AuthUpgradeable, PausableUpgradeable {
3233 using Math for uint256;
3334 using Address for address;
3435 using SafeERC20 for IERC20;
3536
36- /// @dev A slot for storing an address value.
37+ /// @dev Helper struct for reading an address from a storage slot.
3738 struct AddressSlot {
3839 address value;
3940 }
4041
41- /// @dev The slot for the implementation contract.
42+ /// @dev ERC-1967 implementation slot.
4243 bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
4344
44- /// @dev Assume requests are non-fungible and all have ID = 0, so we can differentiate between a request ID and the
45- /// assets amount.
45+ /// @dev Non-fungible request sentinel — all pending redeems share ID 0.
4646 uint256 internal constant REQUEST_ID = 0;
47- /// @dev The denominator used for precision calculations.
47+ /// @dev 1e18 precision denominator for fee and percentage calculations.
4848 uint256 internal constant DENOMINATOR = 1e18;
49- /// @dev The maximum fee that can be set for the vault operations. 1e17 = 10%.
49+ /// @dev Maximum allowed fee (10%).
5050 uint256 internal constant MAX_FEE = 1e17;
51- /// @dev the address of the oracle contract
51+ /// @dev YoOracle contract used for share-price lookups.
5252 address public constant ORACLE_ADDRESS = 0x6E879d0CcC85085A709eBf5539224f53d0D396B0;
5353
54- /// @dev the aggregated underlying balances across all strategies/chains, reported by an oracle
54+ /// @dev Deprecated — preserved for storage-layout compatibility.
5555 uint256 private deprecated_aggregatedUnderlyingBalances;
56- /// @dev the last block number when the aggregated underlying balances were updated
56+ /// @dev Deprecated — preserved for storage-layout compatibility.
5757 uint256 private deprecated_lastBlockUpdated;
58- /// @dev the last price per share calculated after the aggregated underlying balances are reported
58+ /// @dev Deprecated — preserved for storage-layout compatibility.
5959 uint256 private deprecated_lastPricePerShare;
60- /// @dev the total amount of assets that are pending redemption
60+ /// @notice Total assets locked in pending redemption requests.
6161 uint256 public totalPendingAssets;
62- /// @dev the maximum percentage change allowed before the vault is paused. It can be updated by the owner.
63- /// 1e18 = 100%. It's value depends on the frequency of the oracle updates.
62+ /// @dev Deprecated — preserved for storage-layout compatibility.
6463 uint256 private deprecated_maxPercentageChange;
65- /// @dev the fee charged for the withdraws, it's a percentage of the assets redeemed
64+ /// @notice Withdrawal fee as an 18-decimal fraction (e.g. 1e16 = 1%).
6665 uint256 public feeOnWithdraw;
67- /// @dev the fee charged for the deposits, it's a percentage of the assets deposited
66+ /// @notice Deposit fee as an 18-decimal fraction (e.g. 1e16 = 1%).
6867 uint256 public feeOnDeposit;
69- /// @dev the address that receives the fees for the vault operations, if it's zero, no fees are charged
68+ /// @notice Recipient of collected fees. No fees are collected when zero.
7069 address public feeRecipient;
7170
72- /// @dev used to store the amount of shares that are pending redemption, it must be fulfilled by the vault operator
71+ /// @dev Per-user pending redemption state, fulfilled by the vault operator.
7372 mapping(address user => PendingRedeem redeem) internal _pendingRedeem;
7473
74+ /// @notice Approval registry consulted by `approveToken`. Set after upgrade via
75+ /// `setApprovalRegistry`. Reads return `address(0)` until configured.
76+ IYoApprovalRegistry public approvalRegistry;
77+
7578 //============================== CONSTRUCTOR ===============================
7679
7780 /// @custom:oz-upgrades-unsafe-allow constructor
@@ -84,39 +87,46 @@
8487 __Context_init();
8588 __ERC20_init(_name, _symbol);
8689 __ERC4626_init(_asset);
87- __Auth_init(_owner, Authority(address(0)));
90+ __Auth_init(_owner, IAuthority(address(0)));
8891 __Pausable_init();
8992 }
9093
9194 // ========================================= PUBLIC FUNCTIONS =========================================
9295
93- /// @notice Allows the vault operator to manage the vault.
94- /// @param target The target contract to make a call to.
95- /// @param data The data to send to the target contract.
96- /// @param value The amount of native assets to send with the call.
96+ /// @notice Execute an authorized call to an external contract.
97+ /// @param target Contract to call.
98+ /// @param data Calldata forwarded to `target`.
99+ /// @param value Native currency (wei) sent with the call.
97100 function manage(
98101 address target,
99102 bytes calldata data,
100103 uint256 value
101- ) external requiresAuth returns (bytes memory result) {
104+ )
105+ external
106+ requiresAuth
107+ returns (bytes memory result)
108+ {
102109 bytes4 functionSig = bytes4(data);
103110 require(
104- authority().canCall(msg.sender, target, functionSig),
105- Errors.TargetMethodNotAuthorized(target, functionSig)
111+ authority().canCall(msg.sender, target, functionSig), Errors.TargetMethodNotAuthorized(target, functionSig)
106112 );
107113
108114 result = target.functionCallWithValue(data, value);
109115 }
110116
111- /// @notice Same as `manage` but allows for multiple calls in a single transaction.
112- /// @param targets The target contracts to make calls to.
113- /// @param data The data to send to the target contracts.
114- /// @param values The amounts of native assets to send with the calls.
117+ /// @notice Batch version of {manage} — execute multiple authorized calls atomically.
118+ /// @param targets Contracts to call.
119+ /// @param data Calldata arrays forwarded to each `target`.
120+ /// @param values Native currency (wei) sent with each call.
115121 function manage(
116122 address[] calldata targets,
117123 bytes[] calldata data,
118124 uint256[] calldata values
119- ) external requiresAuth returns (bytes[] memory results) {
125+ )
126+ external
127+ requiresAuth
128+ returns (bytes[] memory results)
129+ {
120130 uint256 targetsLength = targets.length;
121131 results = new bytes[](targetsLength);
122132 for (uint256 i; i < targetsLength; ++i) {
@@ -129,25 +139,51 @@
129139 }
130140 }
131141
132- /// @notice Pause the contract to prevent any further deposits, withdrawals, or transfers.
142+ /// @notice Approve `spender` to spend `amount` of `token`. Registry-gated: requires
143+ /// `(this, token, spender)` to be allowlisted with `amount ≤ cap`.
144+ /// @param token ERC-20 token being approved.
145+ /// @param spender Address authorized to pull `token`.
146+ /// @param amount Allowance to set (use 0 to revoke).
147+ function approveToken(address token, address spender, uint256 amount) external requiresAuth {
148+ IYoApprovalRegistry registry = approvalRegistry;
149+ require(address(registry) != address(0), ApprovalRegistryUnset());
150+ // `amount == 0` is always permitted so a live allowance can be revoked even after the admin
151+ // sets the registry cap to zero. Without this bypass, "disabling" a spender via the
152+ // registry would not be able to clear an existing on-chain allowance.
153+ if (amount != 0) {
154+ uint256 cap = registry.maxApproval(address(this), token, spender);
155+ require(cap != 0, SpenderNotAllowed(token, spender));
156+ require(amount <= cap, AmountExceedsCap(amount, cap));
157+ }
158+ IERC20(token).forceApprove(spender, amount);
159+ }
160+
161+ /// @notice Replace the approval registry pointer.
162+ /// @param newRegistry New approval-registry address (zero disables `approveToken`).
163+ function setApprovalRegistry(IYoApprovalRegistry newRegistry) external requiresAuth {
164+ IYoApprovalRegistry previous = approvalRegistry;
165+ approvalRegistry = newRegistry;
166+ emit ApprovalRegistrySet(address(previous), address(newRegistry));
167+ }
168+
169+ /// @notice Pause deposits, withdrawals, and transfers.
133170 function pause() public requiresAuth {
134171 _pause();
135172 }
136173
137- /// @notice Unpause the contract to allow deposits, withdrawals, and transfers.
174+ /// @notice Resume deposits, withdrawals, and transfers.
138175 function unpause() public requiresAuth {
139176 _unpause();
140177 }
141178
142- /// @notice If the vault has enough assets to fulfill the request,
143- /// withdraw the assets and return them to the owner.
144- /// Otherwise, transfer the shares to the vault and store the request.
145- /// The shares are burned when the request is fulfilled and the assets are transferred to the owner.
146- /// @param shares The amount of shares to redeem.
147- /// @param receiver The address of the receiver of the assets.
148- /// @param owner The address of the owner.
149- /// @return The ID of the request which is always 0 or the assets amount if the request is immediately
150- /// processed.
179+ /// @notice Request an asynchronous redemption of `shares`.
180+ /// If the vault holds enough liquid assets the withdrawal is executed instantly and the
181+ /// returned value equals the redeemed asset amount. Otherwise the shares are escrowed and
182+ /// `REQUEST_ID` (0) is returned — the operator must later call {fulfillRedeem}.
183+ /// @param shares Amount of shares to redeem.
184+ /// @param receiver Address that will receive the underlying assets.
185+ /// @param owner Share holder (must equal `msg.sender`).
186+ /// @return Asset amount on instant redemption, or `REQUEST_ID` (0) when queued.
151187 function requestRedeem(uint256 shares, address receiver, address owner) public whenNotPaused returns (uint256) {
152188 require(receiver != address(0), Errors.ZeroReceiver());
153189 require(shares > 0, Errors.SharesAmountZero());
@@ -175,10 +211,10 @@
175211 return REQUEST_ID;
176212 }
177213
178- /// @notice The operator can fulfill a redeem request. Requires authorization.
179- /// @param receiver The address of the receiver of the assets.
180- /// @param shares The amount of shares to fulfil.
181- /// @param assetsWithFee The amount of assets to fulfil including the fee.
214+ /// @notice Fulfill a pending redemption — burns escrowed shares and transfers assets.
215+ /// @param receiver Address whose pending request is being fulfilled.
216+ /// @param shares Amount of escrowed shares to burn.
217+ /// @param assetsWithFee Gross asset amount (including withdrawal fee).
182218 function fulfillRedeem(address receiver, uint256 shares, uint256 assetsWithFee) external requiresAuth {
183219 PendingRedeem storage pending = _pendingRedeem[receiver];
184220 require(pending.shares != 0 && shares <= pending.shares, Errors.InvalidSharesAmount());
@@ -193,10 +229,10 @@
193229 _withdraw(address(this), receiver, address(this), assetsWithFee, shares);
194230 }
195231
196- /// @notice The operator can cancel a redeem request in case of an black swan event.
197- /// @param receiver The address of the receiver of the assets.
198- /// @param shares The amount of shares to cancel.
199- /// @param assetsWithFee The amount of assets to cancel including the fee.
232+ /// @notice Cancel a pending redemption — returns escrowed shares to the receiver.
233+ /// @param receiver Address whose pending request is being cancelled.
234+ /// @param shares Amount of escrowed shares to return.
235+ /// @param assetsWithFee Gross asset amount to release from the pending total.
200236 function cancelRedeem(address receiver, uint256 shares, uint256 assetsWithFee) external requiresAuth {
201237 PendingRedeem storage pending = _pendingRedeem[receiver];
202238 require(pending.shares != 0 && shares <= pending.shares, Errors.InvalidSharesAmount());
@@ -211,24 +247,24 @@
211247 _transfer(address(this), receiver, shares);
212248 }
213249
214- /// @notice Update the fee charged for the vault operations.
215- /// @param newFee The new fee charged for the vault operations.
250+ /// @notice Set the withdrawal fee. Must be below {MAX_FEE}.
251+ /// @param newFee New withdrawal fee as an 18-decimal fraction.
216252 function updateWithdrawFee(uint256 newFee) external requiresAuth {
217253 require(newFee < MAX_FEE, Errors.InvalidFee());
218254 emit WithdrawFeeUpdated(feeOnWithdraw, newFee);
219255 feeOnWithdraw = newFee;
220256 }
221257
222- /// @notice Update the fee charged for the vault operations.
223- /// @param newFee The new fee charged for the vault operations.
258+ /// @notice Set the deposit fee. Must be below {MAX_FEE}.
259+ /// @param newFee New deposit fee as an 18-decimal fraction.
224260 function updateDepositFee(uint256 newFee) external requiresAuth {
225261 require(newFee < MAX_FEE, Errors.InvalidFee());
226262 emit DepositFeeUpdated(feeOnDeposit, newFee);
227263 feeOnDeposit = newFee;
228264 }
229265
230- /// @notice Update the address that receives the fees for the vault operations.
231- /// @param newFeeRecipient The new address that receives the fees for the vault operations.
266+ /// @notice Set the fee recipient. Pass `address(0)` to disable fee collection.
267+ /// @param newFeeRecipient Address that will receive future fees.
232268 function updateFeeRecipient(address newFeeRecipient) external requiresAuth {
233269 emit FeeRecipientUpdated(feeRecipient, newFeeRecipient);
234270 feeRecipient = newFeeRecipient;
@@ -236,99 +272,102 @@
236272
237273 //============================== VIEW FUNCTIONS ===============================
238274
275+ /// @notice Total assets under management, derived from oracle price and total share supply.
239276 function totalAssets() public view override returns (uint256) {
240- (uint256 price, ) = IYoOracle(ORACLE_ADDRESS).getLatestPrice(address(this));
277+ (uint256 price,) = IYoOracle(ORACLE_ADDRESS).getLatestPrice(_oracleAsset());
241278 return price.mulDiv(super.totalSupply(), 10 ** decimals(), Math.Rounding.Floor);
242279 }
243280
244- /// @notice Get the last price per share from the oracle.
281+ /// @notice Oracle price per share, normalized to 18 decimals.
245282 function lastPricePerShare() public view returns (uint256 price) {
246- (price, ) = IYoOracle(ORACLE_ADDRESS).getLatestPrice(address(this));
283+ (price,) = IYoOracle(ORACLE_ADDRESS).getLatestPrice(_oracleAsset());
247284 return price * (10 ** (18 - decimals()));
248285 }
249286
250- /// @notice Get the amount of assets and shares that are pending redemption.
251- /// @param user The address of the user.
287+ /// @notice Pending redemption state for a given user.
288+ /// @param user Address to query.
252289 function pendingRedeemRequest(address user) public view returns (uint256 assets, uint256 pendingShares) {
253290 return (_pendingRedeem[user].assets, _pendingRedeem[user].shares);
254291 }
255292
256293 //============================== OVERRIDES ===============================
257294
258- /// @dev Override the default `deposit` function to add the `whenNotPaused` modifier.
295+ /// @inheritdoc ERC4626Upgradeable
296+ /// @dev Reverts when paused.
259297 function deposit(uint256 assets, address receiver) public override whenNotPaused returns (uint256) {
260298 return super.deposit(assets, receiver);
261299 }
262300
263- /// @dev Override the default `mint` function to add the `whenNotPaused` modifier.
301+ /// @inheritdoc ERC4626Upgradeable
302+ /// @dev Reverts when paused.
264303 function mint(uint256 shares, address receiver) public override whenNotPaused returns (uint256) {
265304 return super.mint(shares, receiver);
266305 }
267306
268- /// @notice This method is disabled. Use `requestRedeem` or `redeem`instead.
307+ /// @notice Disabled — always reverts. Use {requestRedeem} or {redeem} instead.
269308 function withdraw(uint256, address, address) public override whenNotPaused returns (uint256) {
270309 revert Errors.UseRequestRedeem();
271310 }
272311
312+ /// @notice Delegates to {requestRedeem}.
273313 function redeem(uint256 shares, address receiver, address owner) public override whenNotPaused returns (uint256) {
274314 return requestRedeem(shares, receiver, owner);
275315 }
276316
277- /// @dev Override the default `_update` function to add the `whenNotPaused` modifier.
278- /// The _update function is called on all transfers, mints and burns.
317+ /// @dev Enforces pause on all token movements (transfers, mints, burns).
279318 function _update(address from, address to, uint256 value) internal override whenNotPaused {
280319 super._update(from, to, value);
281320 }
282321
283- /// @dev Converts assets to shares using the last price per share read from the oracle, ignoring the total assets and total
284- /// supply (shares)
322+ /// @dev Oracle-driven asset→share conversion (ignores totalSupply/totalAssets).
285323 function _convertToShares(uint256 assets, Math.Rounding rounding) internal view override returns (uint256) {
286- (uint256 pricePerShare, ) = IYoOracle(ORACLE_ADDRESS).getLatestPrice(address(this));
324+ (uint256 pricePerShare,) = IYoOracle(ORACLE_ADDRESS).getLatestPrice(_oracleAsset());
287325 require(pricePerShare > 0, Errors.InvalidPrice());
288326 return assets.mulDiv(10 ** decimals(), pricePerShare, rounding);
289327 }
290328
291- /// @dev Converts shares to assets using the last price per share read from the oracle, ignoring the total assets and total
292- /// supply (shares)
329+ /// @dev Oracle-driven share→asset conversion (ignores totalSupply/totalAssets).
293330 function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view override returns (uint256) {
294- (uint256 pricePerShare, ) = IYoOracle(ORACLE_ADDRESS).getLatestPrice(address(this));
331+ (uint256 pricePerShare,) = IYoOracle(ORACLE_ADDRESS).getLatestPrice(_oracleAsset());
295332 require(pricePerShare > 0, Errors.InvalidPrice());
296333 return shares.mulDiv(pricePerShare, 10 ** decimals(), rounding);
297334 }
298335
299- /// @notice Get the implementation contract address of a proxy.
336+ /// @notice Returns the ERC-1967 implementation address.
300337 function getImplementation() external view returns (address) {
301338 AddressSlot storage r;
339+ // solhint-disable-next-line no-inline-assembly
302340 assembly ("memory-safe") {
303341 r.slot := _IMPLEMENTATION_SLOT
304342 }
305343 return r.value;
306344 }
307345
308- /// @dev Preview taking an entry fee on deposit. See {IERC4626-previewDeposit}.
346+ /// @dev Fee-adjusted deposit preview. See {IERC4626-previewDeposit}.
309347 function previewDeposit(uint256 assets) public view virtual override returns (uint256) {
310348 uint256 fee = _feeOnTotal(assets, feeOnDeposit);
311349 return super.previewDeposit(assets - fee);
312350 }
313351
314- /// @dev Preview adding an entry fee on mint. See {IERC4626-previewMint}.
352+ /// @dev Fee-adjusted mint preview. See {IERC4626-previewMint}.
315353 function previewMint(uint256 shares) public view virtual override returns (uint256) {
316354 uint256 assets = super.previewMint(shares);
317355 return assets + _feeOnRaw(assets, feeOnDeposit);
318356 }
319357
320- /// @dev Preview adding an exit fee on withdraw. See {IERC4626-previewWithdraw}.
358+ /// @dev Fee-adjusted withdraw preview. See {IERC4626-previewWithdraw}.
321359 function previewWithdraw(uint256 assets) public view virtual override returns (uint256) {
322360 uint256 fee = _feeOnRaw(assets, feeOnWithdraw);
323361 return super.previewWithdraw(assets + fee);
324362 }
325363
326- /// @dev Preview taking an exit fee on redeem. See {IERC4626-previewRedeem}.
364+ /// @dev Fee-adjusted redeem preview. See {IERC4626-previewRedeem}.
327365 function previewRedeem(uint256 shares) public view virtual override returns (uint256) {
328366 uint256 assets = super.previewRedeem(shares);
329367 return assets - _feeOnTotal(assets, feeOnWithdraw);
330368 }
331369
370+ /// @dev Returns 0 when paused. See {IERC4626-maxDeposit}.
332371 function maxDeposit(address receiver) public view virtual override returns (uint256) {
333372 if (paused()) {
334373 return 0;
@@ -336,6 +375,7 @@
336375 return super.maxDeposit(receiver);
337376 }
338377
378+ /// @dev Returns 0 when paused. See {IERC4626-maxMint}.
339379 function maxMint(address receiver) public view virtual override returns (uint256) {
340380 if (paused()) {
341381 return 0;
@@ -343,6 +383,7 @@
343383 return super.maxMint(receiver);
344384 }
345385
386+ /// @dev Returns 0 when paused. See {IERC4626-maxWithdraw}.
346387 function maxWithdraw(address owner) public view virtual override returns (uint256) {
347388 if (paused()) {
348389 return 0;
@@ -350,6 +391,7 @@
350391 return super.maxWithdraw(owner);
351392 }
352393
394+ /// @dev Returns 0 when paused. See {IERC4626-maxRedeem}.
353395 function maxRedeem(address owner) public view virtual override returns (uint256) {
354396 if (paused()) {
355397 return 0;
@@ -357,14 +399,17 @@
357399 return super.maxRedeem(owner);
358400 }
359401
360- /// @dev Account for the fee charged for the vault operations if the fee recipient and fee are set.
402+ /// @dev Deducts the withdrawal fee from `assetsWithFee` and transfers it to {feeRecipient}.
361403 function _withdraw(
362404 address caller,
363405 address receiver,
364406 address owner,
365407 uint256 assetsWithFee,
366408 uint256 shares
367- ) internal override {
409+ )
410+ internal
411+ override
412+ {
368413 uint256 feeAmount = _feeOnTotal(assetsWithFee, feeOnWithdraw);
369414 uint256 assets = assetsWithFee - feeAmount;
370415 address recipient = feeRecipient;
@@ -376,7 +421,8 @@
376421 }
377422 }
378423
379- function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override {
424+ /// @dev Deducts the deposit fee from `assets` and transfers it to {feeRecipient}.
425+ function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual override {
380426 uint256 feeAmount = _feeOnTotal(assets, feeOnDeposit);
381427 address recipient = feeRecipient;
382428
@@ -387,22 +433,26 @@
387433 }
388434 }
389435
390- //============================== PRIVATE FUNCTIONS ===============================
436+ //============================== INTERNAL FUNCTIONS ===============================
391437
392- /// @dev Calculates the fees that should be added to an amount `assets` that does not already include fees.
393- /// Used in {IERC4626-mint} and {IERC4626-withdraw} operations.
394- function _feeOnRaw(uint256 assets, uint256 feeBasisPoints) private pure returns (uint256) {
438+ /// @dev Address passed to the oracle for share-price lookups.
439+ /// Override to price shares against a different asset (see {yoUSDT}).
440+ function _oracleAsset() internal view virtual returns (address) {
441+ return address(this);
442+ }
443+
444+ /// @dev Fee to add on top of a raw (fee-exclusive) amount. Used by {previewMint} and {previewWithdraw}.
445+ function _feeOnRaw(uint256 assets, uint256 feeBasisPoints) internal pure returns (uint256) {
395446 return assets.mulDiv(feeBasisPoints, DENOMINATOR, Math.Rounding.Ceil);
396447 }
397448
398- /// @dev Calculates the fee part of an amount `assets` that already includes fees.
399- /// Used in {IERC4626-deposit} and {IERC4626-redeem} operations.
400- function _feeOnTotal(uint256 assets, uint256 feeBasisPoints) private pure returns (uint256) {
449+ /// @dev Fee portion already embedded in a gross (fee-inclusive) amount. Used by {previewDeposit} and
450+ /// {previewRedeem}.
451+ function _feeOnTotal(uint256 assets, uint256 feeBasisPoints) internal pure returns (uint256) {
401452 return assets.mulDiv(feeBasisPoints, feeBasisPoints + DENOMINATOR, Math.Rounding.Ceil);
402453 }
403454
404- /// @dev The available balance is the balance of the vault minus the total pending assets.
405- /// @return The available balance.
455+ /// @dev Liquid balance available for instant redemptions (total balance minus pending claims).
406456 function _getAvailableBalance() internal view returns (uint256) {
407457 uint256 balance = IERC20(asset()).balanceOf(address(this));
408458 return balance > totalPendingAssets ? balance - totalPendingAssets : 0;

Previous Upgrades