Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"lib/yieldnest-flex-strategy/src/FlexStrategy.sol": {
"content": "// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.28;
import { BaseStrategy } from "@yieldnest-vault/strategy/BaseStrategy.sol";
import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IAccountingModule } from "./AccountingModule.sol";
import { VaultLib } from "lib/yieldnest-vault/src/library/VaultLib.sol";
interface IFlexStrategy {
error NoAccountingModule();
error InvariantViolation();
error AccountingTokenMismatch();
event AccountingModuleUpdated(address newValue, address oldValue);
}
/**
* @notice Storage struct for FlexStrategy
*/
struct FlexStrategyStorage {
IAccountingModule accountingModule;
}
/**
* Flex strategy that proxies the deposited base asset to an associated safe,
* minting IOU accounting tokens in the process to represent transferred assets.
*/
contract FlexStrategy is IFlexStrategy, BaseStrategy {
using SafeERC20 for IERC20;
/// @notice The version of the flex strategy contract.
string public constant FLEX_STRATEGY_VERSION = "0.1.0";
/// @notice Storage slot for FlexStrategy data
bytes32 private constant FLEX_STRATEGY_STORAGE_SLOT = keccak256("yieldnest.storage.flexStrategy");
constructor() {
_disableInitializers();
}
/**
* @notice Get the storage struct
*/
function _getFlexStrategyStorage() internal pure returns (FlexStrategyStorage storage s) {
bytes32 slot = FLEX_STRATEGY_STORAGE_SLOT;
assembly {
s.slot := slot
}
}
/**
* @notice Initializes the vault.
* @param admin The address of the admin.
* @param name The name of the vault.
* @param symbol The symbol of the vault.
* @param decimals_ The number of decimals for the vault token.
* @param baseAsset The base asset of the vault.
* @param paused_ Whether the vault should start in a paused state.
*/
function initialize(
address admin,
string memory name,
string memory symbol,
uint8 decimals_,
address baseAsset,
address accountingToken,
bool paused_,
address provider,
bool alwaysComputeTotalAssets
)
external
virtual
initializer
{
if (admin == address(0)) revert ZeroAddress();
_initialize(
admin,
name,
symbol,
decimals_,
paused_,
false, // countNativeAsset. MUST be false. strategy is assumed to hold no native assets
alwaysComputeTotalAssets, // alwaysComputeTotalAssets
0 // defaultAssetIndex. MUST be 0. baseAsset is default
);
_addAsset(baseAsset, IERC20Metadata(baseAsset).decimals(), true);
_addAsset(accountingToken, IERC20Metadata(accountingToken).decimals(), false);
_setAssetWithdrawable(baseAsset, true);
VaultLib.setProvider(provider);
}
modifier hasAccountingModule() {
if (address(_getFlexStrategyStorage().accountingModule) == address(0)) revert NoAccountingModule();
_;
}
/**
* @notice Internal function to handle deposits.
* @param asset_ The address of the asset.
* @param caller The address of the caller.
* @param receiver The address of the receiver.
* @param assets The amount of assets to deposit.
* @param shares The amount of shares to mint.
* @param baseAssets The base asset conversion of shares.
*/
function _deposit(
address asset_,
address caller,
address receiver,
uint256 assets,
uint256 shares,
uint256 baseAssets
)
internal
virtual
override
hasAccountingModule
{
// call the base strategy deposit function for accounting
super._deposit(asset_, caller, receiver, assets, shares, baseAssets);
// virtual accounting
_getFlexStrategyStorage().accountingModule.deposit(assets);
}
/**
* @notice Internal function to handle withdrawals for base asset
* @param asset_ The address of the asset.
* @param caller The address of the caller.
* @param receiver The address of the receiver.
* @param owner The address of the owner.
* @param assets The amount of assets to withdraw.
* @param shares The equivalent amount of shares.
*/
function _withdrawAsset(
address asset_,
address caller,
address receiver,
address owner,
uint256 assets,
uint256 shares
)
internal
virtual
override
hasAccountingModule
onlyAllocator
{
if (asset_ != asset()) {
revert InvalidAsset(asset_);
}
// call the base strategy withdraw function for accounting
_subTotalAssets(_convertAssetToBase(asset_, assets));
if (caller != owner) {
_spendAllowance(owner, caller, shares);
}
// NOTE: burn shares before withdrawing the assets
_burn(owner, shares);
// burn virtual tokens
_getFlexStrategyStorage().accountingModule.withdraw(assets, receiver);
emit WithdrawAsset(caller, receiver, owner, asset_, assets, shares);
}
/**
* @notice Sets the accounting module.
* @param accountingModule_ address to check.
* @dev Will revoke approvals for outgoing accounting module, and approve max for incoming accounting module.
*/
function setAccountingModule(address accountingModule_) external virtual onlyRole(DEFAULT_ADMIN_ROLE) {
if (accountingModule_ == address(0)) revert ZeroAddress();
FlexStrategyStorage storage flexStorage = _getFlexStrategyStorage();
emit AccountingModuleUpdated(accountingModule_, address(flexStorage.accountingModule));
IAccountingModule oldAccounting = flexStorage.accountingModule;
if (address(oldAccounting) != address(0)) {
IERC20(asset()).approve(address(oldAccounting), 0);
if (IAccountingModule(accountingModule_).accountingToken() != oldAccounting.accountingToken()) {
revert AccountingTokenMismatch();
}
}
flexStorage.accountingModule = IAccountingModule(accountingModule_);
IERC20(asset()).approve(accountingModule_, type(uint256).max);
}
/**
* @notice Internal function to get the available amount of assets.
* @param asset_ The address of the asset.
* @return availableAssets The available amount of assets.
* @dev Overriden. This function is used to calculate the available assets for a given asset,
* It returns the balance of the asset in the associated SAFE.
*/
function _availableAssets(address asset_) internal view virtual override returns (uint256 availableAssets) {
address baseAsset = asset();
if (asset_ == baseAsset) {
return IERC20(baseAsset).balanceOf(_getFlexStrategyStorage().accountingModule.safe());
}
return super._availableAssets(asset_);
}
/**
* @notice Returns the fee on total amount.
* @return 0 as this strategy does not charge any fee on total amount.
*/
function _feeOnTotal(uint256) public view virtual override returns (uint256) {
return 0;
}
/**
* @notice Returns the fee on total amount.
* @return 0 as this strategy does not charge any fee on total amount.
*/
function _feeOnRaw(uint256) public view virtual override returns (uint256) {
return 0;
}
/// VIEWS ///
function accountingModule() public view returns (IAccountingModule) {
return _getFlexStrategyStorage().accountingModule;
}
}
"
},
"lib/yieldnest-flex-strategy/lib/yieldnest-vault/src/strategy/BaseStrategy.sol": {
"content": "// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.24;
import {IERC20Metadata as IERC20, Math, SafeERC20} from "src/Common.sol";
import {BaseVault} from "src/BaseVault.sol";
import {IBaseStrategy} from "src/interface/IBaseStrategy.sol";
/**
* @title BaseStrategy
* @author Yieldnest
* @notice This contract is a base strategy for any underlying protocol.
* vault.
*/
abstract contract BaseStrategy is BaseVault, IBaseStrategy {
/// @notice The version of the strategy contract.
string public constant STRATEGY_VERSION = "0.2.0";
/// @notice Role for allocator permissions
bytes32 public constant ALLOCATOR_ROLE = keccak256("ALLOCATOR_ROLE");
/// @notice Role for allocator manager permissions
bytes32 public constant ALLOCATOR_MANAGER_ROLE = keccak256("ALLOCATOR_MANAGER_ROLE");
/**
* @notice Returns whether the strategy has allocators.
* @return hasAllocators True if the strategy has allocators, otherwise false.
*/
function getHasAllocator() public view returns (bool hasAllocators) {
return _getBaseStrategyStorage().hasAllocators;
}
/**
* @notice Sets whether the strategy has allocators.
* @param hasAllocators_ The new value for the hasAllocator flag.
*/
function setHasAllocator(bool hasAllocators_) external onlyRole(ALLOCATOR_MANAGER_ROLE) {
BaseStrategyStorage storage strategyStorage = _getBaseStrategyStorage();
strategyStorage.hasAllocators = hasAllocators_;
emit SetHasAllocator(hasAllocators_);
}
/**
* @notice Returns whether the asset is withdrawable.
* @param asset_ The address of the asset.
* @return True if the asset is withdrawable, otherwise false.
*/
function getAssetWithdrawable(address asset_) external view returns (bool) {
return _getBaseStrategyStorage().isAssetWithdrawable[asset_];
}
/**
* @notice Sets whether the asset is withdrawable.
* @param asset_ The address of the asset.
* @param withdrawable_ The new value for the withdrawable flag.
*/
function setAssetWithdrawable(address asset_, bool withdrawable_) external onlyRole(ASSET_MANAGER_ROLE) {
_setAssetWithdrawable(asset_, withdrawable_);
}
/**
* @notice Internal function to set whether the asset is withdrawable.
* @param asset_ The address of the asset.
* @param withdrawable_ The new value for the withdrawable flag.
*/
function _setAssetWithdrawable(address asset_, bool withdrawable_) internal {
BaseStrategyStorage storage strategyStorage = _getBaseStrategyStorage();
strategyStorage.isAssetWithdrawable[asset_] = withdrawable_;
emit SetAssetWithdrawable(asset_, withdrawable_);
}
/**
* @notice Modifier to restrict access to allocator roles.
*/
modifier onlyAllocator() {
if (_getBaseStrategyStorage().hasAllocators && !hasRole(ALLOCATOR_ROLE, msg.sender)) {
revert AccessControlUnauthorizedAccount(msg.sender, ALLOCATOR_ROLE);
}
_;
}
/**
* @notice Returns the maximum amount of assets that can be withdrawn by a given owner.
* @param owner The address of the owner.
* @return maxAssets The maximum amount of assets.
*/
function maxWithdraw(address owner) public view override returns (uint256 maxAssets) {
maxAssets = _maxWithdrawAsset(asset(), owner);
}
/**
* @notice Returns the maximum amount of assets that can be withdrawn for a specific asset by a given owner.
* @param asset_ The address of the asset.
* @param owner The address of the owner.
* @return maxAssets The maximum amount of assets.
*/
function maxWithdrawAsset(address asset_, address owner) public view returns (uint256 maxAssets) {
maxAssets = _maxWithdrawAsset(asset_, owner);
}
/**
* @notice Internal function to get the maximum amount of assets that can be withdrawn by a given owner.
* @param asset_ The address of the asset.
* @param owner The address of the owner.
* @return maxAssets The maximum amount of assets.
*/
function _maxWithdrawAsset(address asset_, address owner) internal view virtual returns (uint256 maxAssets) {
if (paused() || !_getBaseStrategyStorage().isAssetWithdrawable[asset_]) {
return 0;
}
uint256 availableAssets = _availableAssets(asset_);
maxAssets = previewRedeemAsset(asset_, balanceOf(owner));
maxAssets = availableAssets < maxAssets ? availableAssets : maxAssets;
}
/**
* @notice Returns the maximum amount of shares that can be redeemed by a given owner.
* @param owner The address of the owner.
* @return maxShares The maximum amount of shares.
*/
function maxRedeem(address owner) public view override returns (uint256 maxShares) {
maxShares = _maxRedeemAsset(asset(), owner);
}
/**
* @notice Returns the maximum amount of shares that can be redeemed by a given owner.
* @param asset_ The address of the asset.
* @param owner The address of the owner.
* @return maxShares The maximum amount of shares.
*/
function maxRedeemAsset(address asset_, address owner) public view returns (uint256 maxShares) {
maxShares = _maxRedeemAsset(asset_, owner);
}
/**
* @notice Internal function to get the maximum amount of shares that can be redeemed by a given owner.
* @param asset_ The address of the asset.
* @param owner The address of the owner.
* @return maxShares The maximum amount of shares.
*/
function _maxRedeemAsset(address asset_, address owner) internal view virtual returns (uint256 maxShares) {
if (paused() || !_getBaseStrategyStorage().isAssetWithdrawable[asset_]) {
return 0;
}
uint256 availableAssets = _availableAssets(asset_);
maxShares = balanceOf(owner);
maxShares = availableAssets < previewRedeemAsset(asset_, maxShares)
? previewWithdrawAsset(asset_, availableAssets)
: maxShares;
}
/**
* @notice Previews the amount of assets that would be required to mint a given amount of shares.
* @param asset_ The address of the asset.
* @param shares The amount of shares to mint.
* @return assets The equivalent amount of assets.
*/
function previewMintAsset(address asset_, uint256 shares) public view virtual returns (uint256 assets) {
(assets,) = _convertToAssets(asset_, shares, Math.Rounding.Ceil);
}
/**
* @notice Previews the amount of assets that would be received for a given amount of shares.
* @param asset_ The address of the asset.
* @param shares The amount of shares to redeem.
* @return assets The equivalent amount of assets.
*/
function previewRedeemAsset(address asset_, uint256 shares) public view virtual returns (uint256 assets) {
(assets,) = _convertToAssets(asset_, shares, Math.Rounding.Floor);
assets = assets - _feeOnTotal(assets);
}
/**
* @notice Previews the amount of shares that would be received for a given amount of assets.
* @param asset_ The address of the asset.
* @param assets The amount of assets to deposit.
* @return shares The equivalent amount of shares.
*/
function previewWithdrawAsset(address asset_, uint256 assets) public view virtual returns (uint256 shares) {
uint256 fee = _feeOnRaw(assets);
(shares,) = _convertToShares(asset_, assets + fee, Math.Rounding.Ceil);
}
/**
* @notice Withdraws a given amount of assets and burns the equivalent amount of shares from the owner.
* @param assets The amount of assets to withdraw.
* @param receiver The address of the receiver.
* @param owner The address of the owner.
* @return shares The equivalent amount of shares.
*/
function withdraw(uint256 assets, address receiver, address owner)
public
virtual
override
nonReentrant
returns (uint256 shares)
{
shares = _withdrawAsset(asset(), assets, receiver, owner);
}
/**
* @notice Withdraws assets and burns equivalent shares from the owner.
* @param asset_ The address of the asset.
* @param assets The amount of assets to withdraw.
* @param receiver The address of the receiver.
* @param owner The address of the owner.
* @return shares The equivalent amount of shares burned.
*/
function withdrawAsset(address asset_, uint256 assets, address receiver, address owner)
public
virtual
nonReentrant
returns (uint256 shares)
{
shares = _withdrawAsset(asset_, assets, receiver, owner);
}
/**
* @notice Internal function for withdraws assets and burns equivalent shares from the owner.
* @param asset_ The address of the asset.
* @param assets The amount of assets to withdraw.
* @param receiver The address of the receiver.
* @param owner The address of the owner.
* @return shares The equivalent amount of shares burned.
*/
function _withdrawAsset(address asset_, uint256 assets, address receiver, address owner)
internal
returns (uint256 shares)
{
if (paused()) {
revert Paused();
}
uint256 maxAssets = maxWithdrawAsset(asset_, owner);
if (assets > maxAssets) {
revert ExceededMaxWithdraw(owner, assets, maxAssets);
}
shares = previewWithdrawAsset(asset_, assets);
_withdrawAsset(asset_, _msgSender(), receiver, owner, assets, shares);
}
/**
* @notice Internal function to handle withdrawals.
* @param caller The address of the caller.
* @param receiver The address of the receiver.
* @param owner The address of the owner.
* @param assets The amount of assets to withdraw.
* @param shares The equivalent amount of shares.
*/
function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares)
internal
virtual
override
{
_withdrawAsset(asset(), caller, receiver, owner, assets, shares);
}
/**
* @notice Internal function to handle withdrawals for specific assets.
* @param asset_ The address of the asset.
* @param caller The address of the caller.
* @param receiver The address of the receiver.
* @param owner The address of the owner.
* @param assets The amount of assets to withdraw.
* @param shares The equivalent amount of shares.
*/
function _withdrawAsset(
address asset_,
address caller,
address receiver,
address owner,
uint256 assets,
uint256 shares
) internal virtual onlyAllocator {
if (!_getBaseStrategyStorage().isAssetWithdrawable[asset_]) {
revert AssetNotWithdrawable();
}
_subTotalAssets(_convertAssetToBase(asset_, assets));
if (caller != owner) {
_spendAllowance(owner, caller, shares);
}
// NOTE: burn shares before withdrawing the assets
_burn(owner, shares);
SafeERC20.safeTransfer(IERC20(asset_), receiver, assets);
emit WithdrawAsset(caller, receiver, owner, asset_, assets, shares);
}
/**
* @notice Redeems a given amount of shares and transfers the equivalent amount of assets to the receiver.
* @param shares The amount of shares to redeem.
* @param receiver The address of the receiver.
* @param owner The address of the owner.
* @return assets The equivalent amount of assets.
*/
function redeem(uint256 shares, address receiver, address owner)
public
virtual
override
nonReentrant
returns (uint256 assets)
{
assets = _redeemAsset(asset(), shares, receiver, owner);
}
/**
* @notice Redeems shares and transfers equivalent assets to the receiver.
* @param asset_ The address of the asset.
* @param shares The amount of shares to redeem.
* @param receiver The address of the receiver.
* @param owner The address of the owner.
* @return assets The equivalent amount of assets.
*/
function redeemAsset(address asset_, uint256 shares, address receiver, address owner)
public
virtual
nonReentrant
returns (uint256 assets)
{
assets = _redeemAsset(asset_, shares, receiver, owner);
}
/**
* @notice Internal function for redeems shares and transfers equivalent assets to the receiver.
* @param asset_ The address of the asset.
* @param shares The amount of shares to redeem.
* @param receiver The address of the receiver.
* @param owner The address of the owner.
* @return assets The equivalent amount of assets.
*/
function _redeemAsset(address asset_, uint256 shares, address receiver, address owner)
internal
returns (uint256 assets)
{
if (paused()) {
revert Paused();
}
uint256 maxShares = maxRedeemAsset(asset_, owner);
if (shares > maxShares) {
revert ExceededMaxRedeem(owner, shares, maxShares);
}
assets = previewRedeemAsset(asset_, shares);
_withdrawAsset(asset_, _msgSender(), receiver, owner, assets, shares);
}
/**
* @notice Internal function to handle deposits.
* @param asset_ The address of the asset.
* @param caller The address of the caller.
* @param receiver The address of the receiver.
* @param assets The amount of assets to deposit.
* @param shares The amount of shares to mint.
* @param baseAssets The base asset conversion of shares.
*/
function _deposit(
address asset_,
address caller,
address receiver,
uint256 assets,
uint256 shares,
uint256 baseAssets
) internal virtual override onlyAllocator {
super._deposit(asset_, caller, receiver, assets, shares, baseAssets);
}
/**
* @notice Retrieves the strategy storage structure.
* @return $ The strategy storage structure.
*/
function _getBaseStrategyStorage() internal pure virtual returns (BaseStrategyStorage storage $) {
assembly {
// keccak256("yieldnest.storage.strategy.base")
$.slot := 0x5cfdf694cb3bdee9e4b3d9c4b43849916bf3f018805254a1c0e500548c668500
}
}
/**
* @notice Adds a new asset to the vault.
* @param asset_ The address of the asset.
* @param decimals_ The decimals of the asset.
* @param depositable_ Whether the asset is depositable.
* @param withdrawable_ Whether the asset is withdrawable.
*/
function addAsset(address asset_, uint8 decimals_, bool depositable_, bool withdrawable_)
public
virtual
onlyRole(ASSET_MANAGER_ROLE)
{
_addAsset(asset_, decimals_, depositable_);
_setAssetWithdrawable(asset_, withdrawable_);
}
/**
* @notice Adds a new asset to the vault.
* @param asset_ The address of the asset.
* @param depositable_ Whether the asset is depositable.
* @param withdrawable_ Whether the asset is withdrawable.
*/
function addAsset(address asset_, bool depositable_, bool withdrawable_)
external
virtual
onlyRole(ASSET_MANAGER_ROLE)
{
_addAsset(asset_, IERC20(asset_).decimals(), depositable_);
_setAssetWithdrawable(asset_, withdrawable_);
}
/**
* @notice Internal function to get the available amount of assets.
* @param asset_ The address of the asset.
* @return availableAssets The available amount of assets.
* @dev This function is used to calculate the available assets for a given asset,
* It returns the balance of the asset in the vault and also adds any assets
* already staked in an underlying staking protocol that is available for withdrawal.
*/
function _availableAssets(address asset_) internal view virtual returns (uint256 availableAssets) {
availableAssets = IERC20(asset_).balanceOf(address(this));
}
}
"
},
"lib/yieldnest-flex-strategy/lib/yieldnest-vault/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*/
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
_totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
* true using the following override:
* ```
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}
"
},
"lib/yieldnest-flex-strategy/lib/yieldnest-vault/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev An operation with an ERC20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}
"
},
"lib/yieldnest-flex-strategy/src/AccountingModule.sol": {
"content": "// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.28;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IAccountingToken } from "./AccountingToken.sol";
import { IVault } from "@yieldnest-vault/interface/IVault.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
interface IAccountingModule {
struct StrategySnapshot {
uint256 timestamp;
uint256 pricePerShare;
uint256 totalSupply;
uint256 totalAssets;
}
event LowerBoundUpdated(uint256 newValue, uint256 oldValue);
event TargetApyUpdated(uint256 newValue, uint256 oldValue);
event CooldownSecondsUpdated(uint16 newValue, uint16 oldValue);
event SafeUpdated(address newValue, address oldValue);
error ZeroAddress();
error TooEarly();
error NotStrategy();
error AccountingLimitsExceeded(uint256 aprSinceLastSnapshot, uint256 targetApr);
error LossLimitsExceeded(uint256 amount, uint256 lowerBoundAmount);
error InvariantViolation();
error TvlTooLow();
error CurrentTimestampBeforePreviousTimestamp();
error SnapshotIndexOutOfBounds(uint256 index);
function deposit(uint256 amount) external;
function withdraw(uint256 amount, address recipient) external;
function processRewards(uint256 amount) external;
function processRewards(uint256 amount, uint256 snapshotIndex) external;
function processLosses(uint256 amount) external;
function setCooldownSeconds(uint16 cooldownSeconds) external;
function baseAsset() external view returns (address);
function strategy() external view returns (address);
function DIVISOR() external view returns (uint256);
function YEAR() external view returns (uint256);
function accountingToken() external view returns (IAccountingToken);
function safe() external view returns (address);
function nextUpdateWindow() external view returns (uint64);
function targetApy() external view returns (uint256);
function lowerBound() external view returns (uint256);
function cooldownSeconds() external view returns (uint16);
function SAFE_MANAGER_ROLE() external view returns (bytes32);
function REWARDS_PROCESSOR_ROLE() external view returns (bytes32);
function LOSS_PROCESSOR_ROLE() external view returns (bytes32);
function calculateApr(
uint256 previousPricePerShare,
uint256 previousTimestamp,
uint256 currentPricePerShare,
uint256 currentTimestamp
)
external
view
returns (uint256 apr);
function snapshotsLength() external view returns (uint256);
function snapshots(uint256 index) external view returns (StrategySnapshot memory);
function lastSnapshot() external view returns (StrategySnapshot memory);
}
/**
* @notice Storage struct for AccountingModule
*/
struct AccountingModuleStorage {
IAccountingToken accountingToken;
address safe;
address baseAsset;
address strategy;
uint64 nextUpdateWindow;
uint16 cooldownSeconds;
uint256 targetApy; // in bips;
uint256 lowerBound; // in bips; % of tvl
uint256 minRewardableAssets;
IAccountingModule.StrategySnapshot[] _snapshots;
}
/**
* Module to configure strategy params,
* and mint/burn IOU tokens to represent value accrual/loss.
*/
contract AccountingModule is IAccountingModule, Initializable, AccessControlUpgradeable {
using SafeERC20 for IERC20;
/// @notice Role for safe manager permissions
bytes32 public constant SAFE_MANAGER_ROLE = keccak256("SAFE_MANAGER_ROLE");
/// @notice Role for processing rewards/losses
bytes32 public constant REWARDS_PROCESSOR_ROLE = keccak256("REWARDS_PROCESSOR_ROLE");
bytes32 public constant LOSS_PROCESSOR_ROLE = keccak256("LOSS_PROCESSOR_ROLE");
uint256 public constant YEAR = 365.25 days;
uint256 public constant DIVISOR = 1e18;
uint256 public constant MAX_LOWER_BOUND = DIVISOR / 2;
/// @notice Storage slot for AccountingModule data
bytes32 private constant ACCOUNTING_MODULE_STORAGE_SLOT = keccak256("yieldnest.storage.accountingModule");
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/**
* @notice Get the storage struct
*/
function _getAccountingModuleStorage() internal pure returns (AccountingModuleStorage storage s) {
bytes32 slot = ACCOUNTING_MODULE_STORAGE_SLOT;
assembly {
s.slot := slot
}
}
/**
* /**
* @notice Initializes the vault.
* @param strategy_ The strategy address.
* @param admin The address of the admin.
* @param safe_ The safe associated with the module.
* @param accountingToken_ The accountingToken associated with the module.
* @param targetApy_ The target APY of the strategy.
* @param lowerBound_ The lower bound of losses of the strategy(as % of TVL).
* @param minRewardableAssets_ The minimum rewardable assets.
* @param cooldownSeconds_ The cooldown period in seconds.
*/
function initialize(
address strategy_,
address admin,
address safe_,
IAccountingToken accountingToken_,
uint256 targetApy_,
uint256 lowerBound_,
uint256 minRewardableAssets_,
uint16 cooldownSeconds_
)
external
virtual
initializer
{
__AccessControl_init();
if (admin == address(0)) revert ZeroAddress();
_grantRole(DEFAULT_ADMIN_ROLE, admin);
AccountingModuleStorage storage s = _getAccountingModuleStorage();
if (address(accountingToken_) == address(0)) revert ZeroAddress();
s.accountingToken = accountingToken_;
s.minRewardableAssets = minRewardableAssets_;
if (strategy_ == address(0)) revert ZeroAddress();
s.strategy = strategy_;
s.baseAsset = IERC4626(strategy_).asset();
_setSafeAddress(safe_);
_setTargetApy(targetApy_);
_setLowerBound(lowerBound_);
_setCooldownSeconds(cooldownSeconds_);
createStrategySnapshot();
}
modifier checkAndResetCooldown() {
AccountingModuleStorage storage s = _getAccountingModuleStorage();
if (block.timestamp < s.nextUpdateWindow) revert TooEarly();
s.nextUpdateWindow = (uint64(block.timestamp) + s.cooldownSeconds);
_;
}
modifier onlyStrategy() {
AccountingModuleStorage storage s = _getAccountingModuleStorage();
if (msg.sender != s.strategy) revert NotStrategy();
_;
}
/// DEPOSIT/WITHDRAW ///
/**
* @notice Proxies deposit of base assets from caller to associated SAFE,
* and mints an equiv amount of accounting tokens
* @param amount amount to deposit
*/
function deposit(uint256 amount) external onlyStrategy {
AccountingModuleStorage storage s = _getAccountingModuleStorage();
IERC20(s.baseAsset).safeTransferFrom(s.strategy, s.safe, amount);
s.accountingToken.mintTo(s.strategy, amount);
}
/**
* @notice Proxies withdraw of base assets from associated SAFE to caller,
* and burns an equiv amount of accounting tokens
* @param amount amount to deposit
* @param recipient address to receive the base assets
*/
function withdraw(uint256 amount, address recipient) external onlyStrategy {
AccountingModuleStorage storage s = _getAccountingModuleStorage();
s.accountingToken.burnFrom(s.strategy, amount);
IERC20(s.baseAsset).safeTransferFrom(s.safe, recipient, amount);
}
/// REWARDS ///
/**
* @notice Process rewards by minting accounting tokens
* @param amount profits to mint
*/
function processRewards(uint256 amount) external onlyRole(REWARDS_PROCESSOR_ROLE) checkAndResetCooldown {
AccountingModuleStorage storage s = _getAccountingModuleStorage();
_processRewards(amount, s._snapshots.length - 1);
}
/**
* @notice Process rewards by minting accounting tokens with specific snapshot index
* @param amount profits to mint
* @param snapshotIndex index of the snapshot to compare against
*/
function processRewards(
uint256 amount,
uint256 snapshotIndex
)
external
onlyRole(REWARDS_PROCESSOR_ROLE)
checkAndResetCooldown
{
_processRewards(amount, snapshotIndex);
}
/**
* @notice Internal function to process rewards with snapshot validation
* @param amount profits to mint
* @param snapshotIndex index of the snapshot to compare against
*
* @dev This function validates rewards by comparing current PPS against a historical snapshot.
* Using a past snapshot (rather than the most recent) helps prevent APR manipulation
* by smoothing out reward distribution over time.
*
*
* Example with daily processRewards calls:
*
* Day 0: PPS = 100 [snapshot 0]
* Day 1: PPS = 101 [snapshot 1]
* Day 2: PPS = 102 [snapshot 2]
* Day 3: PPS = 107 [snapshot 3] ← Big jump due to delayed rewards
*
* If we only compared Day 2→3 (102→107):
* Daily return: 4.9% → ~720% APR (exceeds cap)
*
* Instead, compare Day 0→3 (100→107):
* Daily return: ~2.3% → ~240% APR (within sustainable range)
*
* This approach provides flexibility by allowing irregular reward distributions
* while still enforcing APR limits. By comparing against historical snapshots,
* the system can accommodate delayed or lump-sum rewards without triggering
* false positives, while maintaining protection against actual APR manipulation.
*/
function _processRewards(uint256 amount, uint256 snapshotIndex) internal {
AccountingModuleStorage storage s = _getAccountingModuleStorage();
// check if snapshot index is valid
if (snapshotIndex >= s._snapshots.length) revert SnapshotIndexOutOfBounds(snapshotIndex);
uint256 totalSupply = s.accountingToken.totalSupply();
if (totalSupply < s.minRewardableAssets) revert TvlTooLow();
IVault strategyVault = IVault(s.strategy);
s.accountingToken.mintTo(s.strategy, amount);
strategyVault.processAccounting();
// check if apr is within acceptable bounds
StrategySnapshot memory previousSnapshot = s._snapshots[snapshotIndex];
uint256 currentPricePerShare = createStrategySnapshot().pricePerShare;
// Check if APR is within acceptable bounds
uint256 aprSinceLastSnapshot = calculateApr(
previousSnapshot.pricePerShare, previousSnapshot.timestamp, currentPricePerShare, block.timestamp
);
if (aprSinceLastSnapshot > s.targetApy) revert AccountingLimitsExceeded(aprSinceLastSnapshot, s.targetApy);
}
function createStrategySnapshot() internal returns (StrategySnapshot memory) {
AccountingModuleStorage storage s = _getAccountingModuleStorage();
IVault strategyVault = IVault(s.strategy);
// Take snapshot of current state
uint256 currentPricePerShare = strategyVault.convertToAssets(10 ** strategyVault.decimals());
StrategySnapshot memory snapshot = StrategySnapshot({
timestamp: block.timestamp,
pricePerShare: currentPricePerShare,
totalSupply: strategyVault.totalSupply(),
totalAssets: strategyVault.totalAssets()
});
s._snapshots.push(snapshot);
return snapshot;
}
/**
* @notice Calculate APR based on price per share changes over time
* @param previousPricePerShare The price per share at the start of the period
* @param previousTimestamp The timestamp at the start of the period
* @param currentPricePerShare The price per share at the end of the period
* @param currentTimestamp The timestamp at the end of the period
* @return apr The calculated APR in basis points
*/
function calculateApr(
uint256 previousPricePerShare,
uint256 previousTimestamp,
uint256 currentPricePerShare,
uint256 currentTimestamp
)
public
pure
returns (uint256 apr)
{
/*
ppsStart - Price per share at the start of the period
ppsEnd - Price per share at the end of the period
t - Time period in years*
Formula: (ppsEnd - ppsStart) / (ppsStart * t)
*/
// Ensure timestamps are ordered (current should be after previous)
if (currentTimestamp <= previousTimestamp) revert CurrentTimestampBeforePreviousTimestamp();
// Prevent division by zero
if (previousPricePerShare == 0) revert InvariantViolation();
return (currentPricePerShare - previousPricePerShare) * YEAR * DIVISOR / previousPricePerShare
/ (currentTimestamp - previousTimestamp);
}
/// LOSS ///
/**
* @notice Process losses by burning accounting tokens
* @param amount losses to burn
*/
function processLosses(uint256 amount) external onlyRole(LOSS_PROCESSOR_ROLE) checkAndResetCooldown {
AccountingModuleStorage storage s = _getAccountingModuleStorage();
uint256 totalSupply = s.accountingToken.totalSupply();
if (totalSupply < 10 ** s.accountingToken.decimals()) revert TvlTooLow();
// check bound on losses
if (amount > totalSupply * s.lowerBound / DIVISOR) {
revert LossLimitsExceeded(amount, totalSupply * s.lowerBound / DIVISOR);
}
s.accountingToken.burnFrom(s.strategy, amount);
IVault(s.strategy).processAccounting();
createStrategySnapshot();
}
/// ADMIN ///
/**
* @notice Set target APY to determine upper bound. e.g. 1000 = 10% APY
* @param targetApyInBips in bips
* @dev hard max of 100% targetApy
*/
function setTargetApy(uint256 targetApyInBips) external onlyRole(SAFE_MANAGER_ROLE) {
_setTargetApy(targetApyInBips);
}
/**
* @notice Set lower bound as a function of tvl for losses. e.g. 1000 = 10% of tvl
* @param _lowerBound in bips, as a function of % of tvl
* @dev hard max of 50% of tvl
*/
function setLowerBound(uint256 _lowerBound) external onlyRole(SAFE_MANAGER_ROLE) {
_setLowerBound(_lowerBound);
}
/**
* @notice Set cooldown in seconds between every processing of rewards/losses
* @param cooldownSeconds_ new cooldown seconds
*/
function setCooldownSeconds(uint16 cooldownSeconds_) external onlyRole(SAFE_MANAGER_ROLE) {
_setCooldownSeconds(cooldownSeconds_);
}
/**
* @notice Set a new safe address
* @param newSafe new safe address
*/
function setSafeAddress(address newSafe) external virtual onlyRole(SAFE_MANAGER_ROLE) {
_setSafeAddress(newSafe);
}
/// ADMIN INTERNAL SETTERS ///
function _setTargetApy(uint256 targetApyInBips) internal {
AccountingModuleStorage storage s = _getAccountingModuleStorage();
if (targetApyInBips > 10 * DIVISOR) revert InvariantViolation();
emit TargetApyUpdated(targetApyInBips, s.targetApy);
s.targetApy = targetApyInBips;
}
function _setLowerBound(uint256 _lowerBound) internal {
AccountingModuleStorage storage s = _getAccountingModuleStorage();
if (_lowerBound > MAX_LOWER_BOUND) revert InvariantViolation();
emit LowerBoundUpdated(_lowerBound, s.lowerBound);
s.lowerBound = _lowerBound;
}
function _setCooldownSeconds(uint16 cooldownSeconds_) internal {
AccountingModuleStorage storage s = _getAccountingModuleStorage();
emit CooldownSecondsUpdated(cooldownSeconds_, s.cooldownSeconds);
s.cooldownSeconds = cooldownSeconds_;
}
function _setSafeAddress(address newSafe) internal {
AccountingModuleStorage storage s = _getAccountingModuleStorage();
if (newSafe == address(0)) revert ZeroAddress();
emit SafeUpdated(newSafe, s.safe);
s.safe = newSafe;
}
/// VIEWS ///
function baseAsset() external view returns (address) {
return _getAccountingModuleStorage().baseAsset;
}
function strategy() external view returns (address) {
return _getAccountingModuleStorage().strategy;
}
function accountingToken() external view returns (IAccountingToken) {
return _getAccountingModuleStorage().accountingToken;
}
function cooldownSeconds() external view returns (uint16) {
return _getAccountingModuleStorage().cooldownSeconds;
}
function lowerBound() external view returns (uint256) {
return _getAccountingModuleStorage().lowerBound;
}
function nextUpdateWindow() external view returns (uint64) {
return _getAccountingModuleStorage().nextUpdateWindow;
}
function safe() external view returns (address) {
return _getAccountingModuleStorage().safe;
}
function targetApy() external view returns (uint256) {
return _getAccountingModuleStorage().targetApy;
}
function snapshotsLength() external view returns (uint256) {
return _getAccountingModuleStorage()._snapshots.length;
}
function snapshots(uint256 index) external view returns (StrategySnapshot memory) {
return _getAccountingModuleStorage()._snapshots[index];
}
function lastSnapshot() external view returns (StrategySnapshot memory) {
AccountingModuleStorage storage s = _getAccountingModuleStorage();
return s._snapshots[s._snapshots.length - 1];
}
}
"
},
"lib/yieldnest-flex-strategy/lib/yieldnest-vault/src/library/VaultLib.sol": {
"content": "// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.24;
import {IVault} from "src/interface/IVault.sol";
import {IProvider} from "src/interface/IProvider.sol";
import {Math, IERC20} from "src/Common.sol";
import {Guard} from "src/module/Guard.sol";
library VaultLib {
using Math for uint256;
/// @custom:storage-location erc7201:openzeppelin.storage.ERC20
struct ERC20Storage {
mapping(address account => uint256) balances;
mapping(address account => mapping(address spender => uint256)) allowances;
uint256 totalSupply;
string name;
string symbol;
}
/**
* @notice Get the ERC20 storage.
* @return $ The ERC20 storage.
*/
function getERC20Storage() public pure returns (ERC20Storage storage $) {
assembly {
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC20")) - 1)) & ~bytes32(uint256(0xff))
$.slot := 0x52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00
}
}
/**
* @notice Get the vault storage.
* @return $ The vault storage.
*/
function getVaultStorage() public pure returns (IVault.VaultStorage storage $) {
assembly {
// keccak256("yieldnest.storage.vault")
$.slot := 0x22cdba5640455d74cb7564fb236bbbbaf66b93a0cc1bd221f1ee2a6b2d0a2427
}
}
/**
* @notice Get the asset storage.
* @return $ The asset storage.
*/
function getAssetStorage() public pure returns (IVault.AssetStorage storage $) {
assembly {
// keccak256("yieldnest.storage.asset")
$.slot := 0x2dd192a2474c87efcf5ffda906a4b4f8a678b0e41f9245666251cfed8041e680
}
}
/**
* @notice Get the processor storage.
* @return $ The processor storage.
*/
function getProcessorStorage() public pure returns (IVault.ProcessorStorage storage $) {
assembly {
// keccak256("yieldnest.storage.vault")
$.slot := 0x52bb806a772c899365572e319d3d6f49ed2259348d19ab0da8abccd4bd46abb5
}
}
/**
* @notice Get the fee storage.
* @return $ The fee storage.
*/
function getFeeStorage() public pure returns (IVault.FeeStorage storage $) {
assembly {
// keccak256("yieldnest.storage.fees")
$.slot := 0xde924653ae91bd33356774e603163bd5862c93462f31acccae5f965be6e6599b
}
}
/**
* @notice Adds a new asset to the vault.
* @param asset_ The address of the asset.
* @param active_ Whether the asset is active or not.
*/
function addAsset(address asset_, uint8 decimals_, bool active_) public {
if (asset_ == address(0)) {
revert IVault.ZeroAddress();
}
IVault.AssetStorage storage assetStorage = getAssetStorage();
uint256 index = assetStorage.list.length;
IVault.VaultStorage storage vaultStorage = getVaultStorage();
// if native asset is counted the Base Asset should match the decimals count.
if (index == 0 && vaultStorage.countNativeAsset && decimals_ != 18) {
revert IVault.InvalidNativeAssetDecimals(decimals_);
}
// If this is the first asset, check that its decimals are the same as the vault's decimals
if (index == 0 && decimals_ != vaultStorage.decimals) {
revert IVault.InvalidAssetDecimals(decimals_);
}
// If this is not the first asset, check that its decimals are not higher than the base asset
if (index > 0) {
uint8 baseAssetDecimals = assetStorage.assets[assetStorage.list[0]].decimals;
if (decimals_ > baseAssetDecimals) {
revert IVault.InvalidAssetDecimals(decimals_);
}
}
// Check if trying to add the Base Asset again
if (index > 0 && asset_ == assetStorage.list[0]) {
revert IVault.DuplicateAsset(asset_);
}
if (index > 0 && assetStorage.assets[as
Submitted on: 2025-09-18 12:00:36
Comments
Log in to comment.
No comments yet.