From f0b8c0c0726ca86faa16ae960c13c93389622e68 Mon Sep 17 00:00:00 2001 From: Dmitri Tsumak Date: Thu, 27 Aug 2026 10:43:21 +0300 Subject: [PATCH] Fix doc accuracy against source repos and regenerate contracts API --- docs/contracts/api/base/ERC20Upgradeable.md | 134 ++- docs/contracts/api/base/Multicall.md | 6 +- .../contracts/api/curators/BalancedCurator.md | 44 +- .../api/curators/CuratorsRegistry.md | 61 +- docs/contracts/api/index.md | 3 +- docs/contracts/api/keeper/KeeperContract.md | 62 +- docs/contracts/api/keeper/KeeperOracles.md | 89 +- docs/contracts/api/keeper/KeeperRewards.md | 286 ++----- docs/contracts/api/keeper/KeeperValidators.md | 105 +-- docs/contracts/api/libraries/EIP712Utils.md | 20 +- docs/contracts/api/libraries/Errors.md | 24 +- docs/contracts/api/libraries/ExitPositions.md | 88 ++ docs/contracts/api/libraries/ExitQueue.md | 85 +- docs/contracts/api/libraries/OsTokenUtils.md | 71 +- .../contracts/api/libraries/ValidatorUtils.md | 76 +- docs/contracts/api/misc/EthRewardSplitter.md | 46 +- docs/contracts/api/misc/GnoRewardSplitter.md | 62 +- docs/contracts/api/misc/RewardSplitter.md | 186 ++-- .../api/misc/RewardSplitterFactory.md | 31 +- docs/contracts/api/nodes/EthNodesManager.md | 131 +++ docs/contracts/api/nodes/NodesManager.md | 647 ++++++++++++++ docs/contracts/api/nodes/_category_.json | 5 + .../api/tokens/EthOsTokenRedeemer.md | 67 +- .../api/tokens/EthOsTokenVaultEscrow.md | 69 +- .../api/tokens/GnoOsTokenRedeemer.md | 75 +- .../api/tokens/GnoOsTokenVaultEscrow.md | 63 +- docs/contracts/api/tokens/OsToken.md | 43 +- docs/contracts/api/tokens/OsTokenConfig.md | 71 +- .../contracts/api/tokens/OsTokenFlashLoans.md | 30 +- docs/contracts/api/tokens/OsTokenRedeemer.md | 527 +++++------- .../api/tokens/OsTokenVaultController.md | 238 +++--- .../api/tokens/OsTokenVaultEscrow.md | 270 +++--- docs/contracts/api/tokens/PriceFeed.md | 25 +- .../api/validators/ConsolidationsChecker.md | 67 +- .../api/validators/DepositDataRegistry.md | 90 +- .../api/validators/EthValidatorsChecker.md | 76 +- .../api/validators/GnoValidatorsChecker.md | 85 +- .../api/validators/ValidatorsChecker.md | 153 +++- .../contracts/api/vaults/SubVaultsRegistry.md | 800 ++++++++++++++++++ .../api/vaults/SubVaultsRegistryFactory.md | 62 ++ docs/contracts/api/vaults/VaultsRegistry.md | 145 +--- .../vaults/ethereum/EthBlocklistErc20Vault.md | 100 ++- .../api/vaults/ethereum/EthBlocklistVault.md | 66 +- .../api/vaults/ethereum/EthErc20MetaVault.md | 515 +++++++++++ .../api/vaults/ethereum/EthErc20Vault.md | 223 +++-- .../api/vaults/ethereum/EthGenesisVault.md | 168 +++- .../api/vaults/ethereum/EthMetaVault.md | 410 +++++++++ .../vaults/ethereum/EthMetaVaultFactory.md | 71 ++ .../vaults/ethereum/EthPrivErc20MetaVault.md | 178 ++++ .../api/vaults/ethereum/EthPrivErc20Vault.md | 79 +- .../api/vaults/ethereum/EthPrivMetaVault.md | 167 ++++ .../api/vaults/ethereum/EthPrivVault.md | 68 +- .../contracts/api/vaults/ethereum/EthVault.md | 150 ++-- .../api/vaults/ethereum/EthVaultFactory.md | 59 +- .../ethereum/custom/EthCommunityVault.md | 144 ++++ .../api/vaults/ethereum/custom/EthFoxVault.md | 105 +-- .../vaults/ethereum/custom/EthMetaVault.md | 261 ------ .../ethereum/custom/EthMetaVaultFactory.md | 85 -- .../vaults/ethereum/custom/_category_.json | 5 + .../api/vaults/ethereum/mev/OwnMevEscrow.md | 48 +- .../vaults/ethereum/mev/SharedMevEscrow.md | 35 +- .../vaults/gnosis/GnoBlocklistErc20Vault.md | 76 +- .../api/vaults/gnosis/GnoBlocklistVault.md | 61 +- .../api/vaults/gnosis/GnoErc20Vault.md | 243 ++++-- .../api/vaults/gnosis/GnoGenesisVault.md | 136 ++- .../api/vaults/gnosis/GnoMetaVault.md | 337 ++++++++ .../api/vaults/gnosis/GnoMetaVaultFactory.md | 86 ++ .../api/vaults/gnosis/GnoPrivErc20Vault.md | 77 +- .../api/vaults/gnosis/GnoPrivVault.md | 63 +- docs/contracts/api/vaults/gnosis/GnoVault.md | 172 ++-- .../api/vaults/gnosis/GnoVaultFactory.md | 67 +- .../api/vaults/gnosis/mev/GnoOwnMevEscrow.md | 68 ++ .../vaults/gnosis/mev/GnoSharedMevEscrow.md | 66 ++ .../api/vaults/gnosis/mev/_category_.json | 5 + .../api/vaults/modules/VaultAdmin.md | 76 +- .../api/vaults/modules/VaultBlocklist.md | 90 +- .../api/vaults/modules/VaultEnterExit.md | 156 ++-- .../api/vaults/modules/VaultEthStaking.md | 81 +- docs/contracts/api/vaults/modules/VaultFee.md | 122 ++- .../api/vaults/modules/VaultGnoStaking.md | 149 +++- .../api/vaults/modules/VaultImmutables.md | 79 +- docs/contracts/api/vaults/modules/VaultMev.md | 90 +- .../api/vaults/modules/VaultOsToken.md | 250 ++++-- .../api/vaults/modules/VaultState.md | 256 +++++- .../api/vaults/modules/VaultSubVaults.md | 315 ++++--- .../api/vaults/modules/VaultToken.md | 90 +- .../api/vaults/modules/VaultValidators.md | 268 +++--- .../api/vaults/modules/VaultVersion.md | 44 +- .../api/vaults/modules/VaultWhitelist.md | 90 +- docs/contracts/audits.mdx | 3 +- docs/contracts/networks/Gnosis.mdx | 3 +- docs/contracts/networks/Hoodi.mdx | 10 +- docs/contracts/networks/Mainnet.mdx | 10 +- docs/contracts/networks/index.mdx | 57 +- docs/contracts/verify-vault-contract.mdx | 2 +- docs/docs/glossary.mdx | 63 +- docs/docs/oracles/intro.mdx | 22 +- docs/docs/ostoken/how-ostoken-works.mdx | 6 +- docs/docs/ostoken/ostoken-redemptions.mdx | 2 +- .../understanding-liquid-staking.mdx | 4 +- docs/docs/vaults/boost.mdx | 2 +- docs/docs/vaults/configuration.mdx | 16 +- docs/docs/vaults/how-vaults-work.mdx | 4 +- docs/docs/vaults/intro.mdx | 2 +- docs/docs/vaults/technical-architecture.mdx | 8 +- docs/docs/vaults/vault-types.mdx | 8 +- docusaurus.config.ts | 1 + .../alternative-key-management/api-mode.mdx | 13 +- .../img/Distributed Validator Technology.png | Bin 118705 -> 0 bytes .../hashicorp-vault.mdx | 8 +- .../remote-signer.mdx | 4 +- operator/introduction.mdx | 2 +- operator/launch-operator-service.mdx | 6 +- .../automated-node-setup.mdx | 10 +- .../consolidate-validators.mdx | 6 +- .../manually-exit-validators.mdx | 8 +- .../manage-validators/recover-validators.mdx | 17 + operator/manage-vault/fee-claiming.mdx | 2 +- operator/manage-vault/overview.mdx | 2 +- .../manage-vault/vault-administration.mdx | 4 +- .../alternative-key-management/api-mode.mdx | 8 +- .../dvt/obol-setup.mdx | 4 +- .../dvt/ssv-setup.mdx | 4 +- .../hashicorp-vault.mdx | 4 +- .../alternative-key-management/overview.mdx | 2 +- .../remote-signer.mdx | 4 +- .../img/architecture_overview.png | Bin 99640 -> 0 bytes operator/operator-service-v3/overview.mdx | 4 +- .../run-operator-service-v3/installation.mdx | 2 +- .../prepare-operator.mdx | 12 +- .../start-operator.mdx | 6 +- .../operator-service-v3/v4-upgrade-guide.mdx | 6 +- operator/smoothing-pool-relays.mdx | 27 +- operator/staking-nodes.mdx | 2 +- operator/start-operator.mdx | 21 +- operator/validator-keys.mdx | 8 +- operator/validators-manager.mdx | 4 +- operator/vault-interface.mdx | 2 +- package.json | 3 +- redirects.ts | 8 + scripts/checkRedirects/util/contentRoots.ts | 6 +- scripts/checkRedirects/util/parser.ts | 3 +- scripts/generateContractsApi/index.ts | 194 +++++ .../generateContractsApi/util/categories.ts | 25 + .../util/externalLinks.ts | 20 + .../generateContractsApi/util/transform.ts | 136 +++ .../Tooltip/TooltipTrigger/TooltipTrigger.tsx | 9 +- src/css/admonitions.scss | 49 +- src/theme/Admonition/Types.js | 59 +- .../HomeView/CommonQuestions/questions.ts | 14 +- staker/boost.mdx | 28 +- staker/introduction.mdx | 4 +- staker/risks.mdx | 13 +- staker/simple-staking.mdx | 27 +- ...01-migrate-to-stakewise-v3-on-ethereum.mdx | 6 +- ...igrate-to-stakewise-v3-on-gnosis-chain.mdx | 4 + staker/vault-staking.mdx | 13 +- static/icons/aave.svg | 1 - static/icons/code.svg | 1 - static/icons/file.svg | 1 - static/icons/safe.png | Bin 378 -> 0 bytes static/icons/stakewise/asterisk.png | Bin 17840 -> 0 bytes static/icons/stakewise/background.png | Bin 16129 -> 0 bytes static/icons/stakewise/brush.png | Bin 2381 -> 0 bytes static/icons/stakewise/cross.png | Bin 4009 -> 0 bytes static/icons/stakewise/email.png | Bin 15121 -> 0 bytes static/icons/stakewise/eth_coin.png | Bin 18142 -> 0 bytes static/icons/stakewise/exclamation_mark.png | Bin 6338 -> 0 bytes static/icons/stakewise/flip_button.png | Bin 4831 -> 0 bytes static/icons/stakewise/link.png | Bin 12415 -> 0 bytes static/icons/stakewise/osETH.png | Bin 7183 -> 0 bytes static/icons/stakewise/rewards.png | Bin 5957 -> 0 bytes static/icons/stakewise/safe.png | Bin 13579 -> 0 bytes static/icons/stakewise/search.png | Bin 10565 -> 0 bytes static/icons/stakewise/sparks.png | Bin 8109 -> 0 bytes static/icons/stakewise/stop.png | Bin 16621 -> 0 bytes static/icons/tokens/ethereum.png | Bin 522 -> 0 bytes static/icons/tokens/ethereum.svg | 1 - static/icons/tokens/osETH.svg | 1 - 179 files changed, 9575 insertions(+), 3244 deletions(-) create mode 100644 docs/contracts/api/libraries/ExitPositions.md create mode 100644 docs/contracts/api/nodes/EthNodesManager.md create mode 100644 docs/contracts/api/nodes/NodesManager.md create mode 100644 docs/contracts/api/nodes/_category_.json create mode 100644 docs/contracts/api/vaults/SubVaultsRegistry.md create mode 100644 docs/contracts/api/vaults/SubVaultsRegistryFactory.md create mode 100644 docs/contracts/api/vaults/ethereum/EthErc20MetaVault.md create mode 100644 docs/contracts/api/vaults/ethereum/EthMetaVault.md create mode 100644 docs/contracts/api/vaults/ethereum/EthMetaVaultFactory.md create mode 100644 docs/contracts/api/vaults/ethereum/EthPrivErc20MetaVault.md create mode 100644 docs/contracts/api/vaults/ethereum/EthPrivMetaVault.md create mode 100644 docs/contracts/api/vaults/ethereum/custom/EthCommunityVault.md delete mode 100644 docs/contracts/api/vaults/ethereum/custom/EthMetaVault.md delete mode 100644 docs/contracts/api/vaults/ethereum/custom/EthMetaVaultFactory.md create mode 100644 docs/contracts/api/vaults/ethereum/custom/_category_.json create mode 100644 docs/contracts/api/vaults/gnosis/GnoMetaVault.md create mode 100644 docs/contracts/api/vaults/gnosis/GnoMetaVaultFactory.md create mode 100644 docs/contracts/api/vaults/gnosis/mev/GnoOwnMevEscrow.md create mode 100644 docs/contracts/api/vaults/gnosis/mev/GnoSharedMevEscrow.md create mode 100644 docs/contracts/api/vaults/gnosis/mev/_category_.json delete mode 100644 operator/alternative-key-management/dvt/img/Distributed Validator Technology.png delete mode 100644 operator/operator-service-v3/img/architecture_overview.png create mode 100644 scripts/generateContractsApi/index.ts create mode 100644 scripts/generateContractsApi/util/categories.ts create mode 100644 scripts/generateContractsApi/util/externalLinks.ts create mode 100644 scripts/generateContractsApi/util/transform.ts delete mode 100644 static/icons/aave.svg delete mode 100644 static/icons/code.svg delete mode 100644 static/icons/file.svg delete mode 100644 static/icons/safe.png delete mode 100644 static/icons/stakewise/asterisk.png delete mode 100644 static/icons/stakewise/background.png delete mode 100644 static/icons/stakewise/brush.png delete mode 100644 static/icons/stakewise/cross.png delete mode 100644 static/icons/stakewise/email.png delete mode 100644 static/icons/stakewise/eth_coin.png delete mode 100644 static/icons/stakewise/exclamation_mark.png delete mode 100644 static/icons/stakewise/flip_button.png delete mode 100644 static/icons/stakewise/link.png delete mode 100644 static/icons/stakewise/osETH.png delete mode 100644 static/icons/stakewise/rewards.png delete mode 100644 static/icons/stakewise/safe.png delete mode 100644 static/icons/stakewise/search.png delete mode 100644 static/icons/stakewise/sparks.png delete mode 100644 static/icons/stakewise/stop.png delete mode 100644 static/icons/tokens/ethereum.png delete mode 100644 static/icons/tokens/ethereum.svg delete mode 100644 static/icons/tokens/osETH.svg diff --git a/docs/contracts/api/base/ERC20Upgradeable.md b/docs/contracts/api/base/ERC20Upgradeable.md index 84582bc7..986ad74d 100644 --- a/docs/contracts/api/base/ERC20Upgradeable.md +++ b/docs/contracts/api/base/ERC20Upgradeable.md @@ -6,14 +6,22 @@ description: "Modern and gas-efficient upgradeable ERC20 token implementation wi # ERC20Upgradeable -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/base/ERC20Upgradeable.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/base/ERC20Upgradeable.sol) -**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [IERC20Permit ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/IERC20Permit.sol), [IERC20 ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol), [IERC20Metadata ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/IERC20Metadata.sol) +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [IERC20Permit ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/IERC20Permit.sol), [IERC20 ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol), [IERC20Metadata ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/IERC20Metadata.sol) -Modern and gas efficient [ERC20 ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol) + [EIP-2612 ↗](https://eips.ethereum.org/EIPS/eip-2612) implementation. +Modern and gas efficient ERC20 + EIP-2612 implementation ## State Variables +### _permitTypeHash + +```solidity +bytes32 private constant _permitTypeHash = + keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") +``` + + ### name Returns the name of the token. @@ -42,9 +50,6 @@ uint8 public constant override decimals = 18 ### allowance -Returns the remaining number of tokens that `spender` is allowed -to spend on behalf of `owner` - ```solidity mapping(address => mapping(address => uint256)) public override allowance @@ -52,26 +57,61 @@ mapping(address => mapping(address => uint256)) public override allowance ### nonces -Returns the current nonce for `owner`. This value must be -included whenever a signature is generated for `permit`. -Every successful call to `permit` increases `owner`'s nonce by one. This -prevents a signature from being used multiple times. - ```solidity mapping(address => uint256) public override nonces ``` +### _initialChainId +**Note:** +oz-upgrades-unsafe-allow: state-variable-immutable + + +```solidity +uint256 private immutable _initialChainId +``` + + +### _initialDomainSeparator + +```solidity +bytes32 private _initialDomainSeparator +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` + + ## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor() ; +``` ### approve Sets `amount` as the allowance of `spender` over the caller's tokens. -:::custom-warning[Front-Running Risk] -Be aware of potential front-running attacks. See [EIP-20 Discussion ↗](https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729) for details. -::: +Be aware of front-running risks: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 ```solidity @@ -99,23 +139,21 @@ function transferFrom(address from, address to, uint256 amount) public virtual o ### permit -Sets `value` as the allowance of `spender` over `owner`'s tokens using a signed approval. - -:::custom-warning[Security Considerations] -- The same front-running issues that affect `IERC20-approve` also apply here -- Always verify signatures are using the latest nonce -- Be cautious of signature replay attacks -::: - +Sets `value` as the allowance of `spender` over `owner`'s tokens, +given `owner`'s signed approval. +IMPORTANT: The same issues `IERC20-approve` has related to transaction +ordering also apply here. Emits an `Approval` event. - -**Requirements:** -- `spender` cannot be the zero address -- `deadline` must be a timestamp in the future -- `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` over the EIP712-formatted function arguments -- The signature must use `owner`'s current nonce (see `nonces`) - -For more information, see [EIP-2612 Specification ↗](https://eips.ethereum.org/EIPS/eip-2612#specification). +Requirements: +- `spender` cannot be the zero address. +- `deadline` must be a timestamp in the future. +- `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` +over the EIP712-formatted function arguments. +- the signature must use `owner`'s current nonce (see `nonces`). +For more information on the signature format, see the +https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP +section]. +CAUTION: See Security Considerations above. ```solidity @@ -132,3 +170,39 @@ Returns the domain separator used in the encoding of the signature for `permit`, ```solidity function DOMAIN_SEPARATOR() public view override returns (bytes32); ``` + +### _computeDomainSeparator + +Computes the hash of the EIP712 typed data + +This function is used to compute the hash of the EIP712 typed data + + +```solidity +function _computeDomainSeparator() private view returns (bytes32); +``` + +### _transfer + +Moves `amount` of tokens from `from` to `to`. +Emits a `Transfer` event. + + +```solidity +function _transfer(address from, address to, uint256 amount) internal virtual; +``` + +### __ERC20Upgradeable_init + +Initializes the ERC20Upgradeable contract + + +```solidity +function __ERC20Upgradeable_init(string memory _name, string memory _symbol) internal onlyInitializing; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_name`|`string`|The name of the ERC20 token| +|`_symbol`|`string`|The symbol of the ERC20 token| diff --git a/docs/contracts/api/base/Multicall.md b/docs/contracts/api/base/Multicall.md index f4d1d070..fcd4979a 100644 --- a/docs/contracts/api/base/Multicall.md +++ b/docs/contracts/api/base/Multicall.md @@ -6,11 +6,11 @@ description: "Utility contract for batching multiple function calls into a singl # Multicall -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/base/Multicall.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/base/Multicall.sol) **Inherits:** IMulticall -Enables calling multiple methods in a single call to the contract. +Enables calling multiple methods in a single call to the contract ## Functions @@ -33,5 +33,3 @@ function multicall(bytes[] calldata data) external override returns (bytes[] mem |Name|Type|Description| |----|----|-----------| |`results`|`bytes[]`|The results from each of the calls passed in via data| - - diff --git a/docs/contracts/api/curators/BalancedCurator.md b/docs/contracts/api/curators/BalancedCurator.md index 2d672f80..b6c332d6 100644 --- a/docs/contracts/api/curators/BalancedCurator.md +++ b/docs/contracts/api/curators/BalancedCurator.md @@ -6,51 +6,13 @@ description: "Curator for evenly managing assets in sub-vaults" # BalancedCurator -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/curators/BalancedCurator.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/curators/BalancedCurator.sol) **Inherits:** ISubVaultsCurator Defines the functionality for evenly managing assets in sub-vaults. -## Structs -### Deposit -Struct for storing deposit data - - -```solidity -struct Deposit { - address vault; - uint256 assets; -} -``` - -**Properties** - -|Name|Type|Description| -|----|----|-----------| -|`vault`|`address`|The address of the vault| -|`assets`|`uint256`|The amount of assets to deposit| - -### ExitRequest -Struct for storing exit request data - - -```solidity -struct ExitRequest { - address vault; - uint256 assets; -} -``` - -**Properties** - -|Name|Type|Description| -|----|----|-----------| -|`vault`|`address`|The address of the vault| -|`assets`|`uint256`|The amount of assets to exit| - - ## Functions ### getDeposits @@ -60,7 +22,7 @@ Function to get the deposits to the sub-vaults ```solidity function getDeposits(uint256 assetsToDeposit, address[] calldata subVaults, address ejectingVault) external - pure + view override returns (Deposit[] memory deposits); ``` @@ -106,5 +68,3 @@ function getExitRequests( |Name|Type|Description| |----|----|-----------| |`exitRequests`|`ExitRequest[]`|An array of ExitRequest structs containing the vault addresses and the amounts to exit| - - diff --git a/docs/contracts/api/curators/CuratorsRegistry.md b/docs/contracts/api/curators/CuratorsRegistry.md index ec35182f..88d5e2ca 100644 --- a/docs/contracts/api/curators/CuratorsRegistry.md +++ b/docs/contracts/api/curators/CuratorsRegistry.md @@ -6,65 +6,37 @@ description: "Registry for managing curator addresses for sub-vaults" # CuratorsRegistry -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/curators/CuratorsRegistry.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/curators/CuratorsRegistry.sol) **Inherits:** [Ownable2Step ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol), ICuratorsRegistry Defines the registry functionality that keeps track of Curators for the sub-vaults. -## Events -### CuratorAdded -Emitted when a new curator is added - +## State Variables +### isCurator ```solidity -event CuratorAdded(address indexed sender, address indexed curator); +mapping(address curator => bool isCurator) public override isCurator ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`sender`|`address`|The address of the sender| -|`curator`|`address`|The address of the curator| - -### CuratorRemoved -Emitted when a curator is removed +### _initialized ```solidity -event CuratorRemoved(address indexed sender, address indexed curator); +bool private _initialized ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`sender`|`address`|The address of the sender| -|`curator`|`address`|The address of the curator| - ## Functions -### isCurator +### constructor -Checks if an address is a curator +Constructor ```solidity -function isCurator(address curator) external view returns (bool); +constructor() Ownable(msg.sender); ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`curator`|`address`|The address of the curator| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`bool`|True if the address is a curator, false otherwise| ### addCurator @@ -94,3 +66,18 @@ function removeCurator(address curator) external override onlyOwner; |Name|Type|Description| |----|----|-----------| |`curator`|`address`|The address of the curator to remove| + + +### initialize + +Initializes the CuratorsRegistry + + +```solidity +function initialize(address _owner) external override onlyOwner; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_owner`|`address`|The address of the owner| diff --git a/docs/contracts/api/index.md b/docs/contracts/api/index.md index d55429a4..860d7b9a 100644 --- a/docs/contracts/api/index.md +++ b/docs/contracts/api/index.md @@ -15,6 +15,7 @@ Welcome to the StakeWise Contracts API documentation. This section contains comp - **Keeper** - Oracle system for updating Vault rewards and approving validator registrations - **Libraries** - Shared utility libraries used across contracts - **Misc** - Reward distribution and fee splitting contracts +- **Nodes** - Delegation of validator operations to node operators - **Tokens** - Over-collateralized staking token (osToken) system and controllers - **Validators** - Validator lifecycle management, registry interfaces, and deposit data validation -- **Vaults** - Modular staking Vault contracts for Ethereum and Gnosis Chain +- **Vaults** - Modular staking Vault contracts for Ethereum and Gnosis Chain, including MetaVaults and the sub-vaults registry diff --git a/docs/contracts/api/keeper/KeeperContract.md b/docs/contracts/api/keeper/KeeperContract.md index 557fe954..7d846294 100644 --- a/docs/contracts/api/keeper/KeeperContract.md +++ b/docs/contracts/api/keeper/KeeperContract.md @@ -1,13 +1,67 @@ --- title: Keeper sidebar_position: 1 -description: "Oracle system for updating Vault rewards and approving validator registrations" +description: "Defines the functionality for updating Vaults' rewards and approving validators registrations" --- # Keeper -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/keeper/Keeper.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/keeper/Keeper.sol) -**Inherits:** [KeeperOracles →](./KeeperOracles), [KeeperRewards →](./KeeperRewards), [KeeperValidators →](./KeeperValidators), IKeeper +**Inherits:** [KeeperOracles](./KeeperOracles), [KeeperRewards](./KeeperRewards), [KeeperValidators](./KeeperValidators), IKeeper -Defines the functionality for updating Vaults' rewards and approving validators registrations. +Defines the functionality for updating Vaults' rewards and approving validators registrations + + +## State Variables +### _initialized + +```solidity +bool private _initialized +``` + + +## Functions +### constructor + +Constructor + + +```solidity +constructor( + address sharedMevEscrow, + IVaultsRegistry vaultsRegistry, + IOsTokenVaultController osTokenVaultController, + uint256 _rewardsDelay, + uint256 maxAvgRewardPerSecond, + IValidatorsRegistry validatorsRegistry +) + KeeperOracles() + KeeperRewards(sharedMevEscrow, vaultsRegistry, osTokenVaultController, _rewardsDelay, maxAvgRewardPerSecond) + KeeperValidators(validatorsRegistry); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`sharedMevEscrow`|`address`|The address of the shared MEV escrow contract| +|`vaultsRegistry`|`IVaultsRegistry`|The address of the VaultsRegistry contract| +|`osTokenVaultController`|`IOsTokenVaultController`|The address of the OsTokenVaultController contract| +|`_rewardsDelay`|`uint256`|The delay in seconds between rewards updates| +|`maxAvgRewardPerSecond`|`uint256`|The maximum possible average reward per second| +|`validatorsRegistry`|`IValidatorsRegistry`|The address of the beacon chain validators registry contract| + + +### initialize + +Initializes the Keeper contract. Can only be called once. + + +```solidity +function initialize(address _owner) external override onlyOwner; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_owner`|`address`|The address of the owner| diff --git a/docs/contracts/api/keeper/KeeperOracles.md b/docs/contracts/api/keeper/KeeperOracles.md index 25b5e450..36fac252 100644 --- a/docs/contracts/api/keeper/KeeperOracles.md +++ b/docs/contracts/api/keeper/KeeperOracles.md @@ -6,93 +6,53 @@ description: "Oracle signature verification system for off-chain oracle whitelis # KeeperOracles -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/keeper/KeeperOracles.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/keeper/KeeperOracles.sol) **Inherits:** [Ownable2Step ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol), [EIP712 ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol), IKeeperOracles -Defines the functionality for verifying signatures of the whitelisted off-chain oracles. +Defines the functionality for verifying signatures of the whitelisted off-chain oracles -## Events -### OracleAdded -Event emitted on the oracle addition - +## State Variables +### _signatureLength ```solidity -event OracleAdded(address indexed oracle); +uint256 internal constant _signatureLength = 65 ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`oracle`|`address`|The address of the added oracle| - -### OracleRemoved -Event emitted on the oracle removal +### _maxOracles ```solidity -event OracleRemoved(address indexed oracle); +uint256 private constant _maxOracles = 30 ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`oracle`|`address`|The address of the removed oracle| - -### ConfigUpdated -Event emitted on oracles config update +### isOracle ```solidity -event ConfigUpdated(string configIpfsHash); +mapping(address => bool) public override isOracle ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`configIpfsHash`|`string`|The IPFS hash of the new config| - - -## Functions -### isOracle -Function for verifying whether oracle is registered or not +### totalOracles +Total Oracles ```solidity -function isOracle(address oracle) external view returns (bool); +uint256 public override totalOracles ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`oracle`|`address`|The address of the oracle to check| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`bool`|`true` for the registered oracle, `false` otherwise| -### totalOracles +## Functions +### constructor -Total Oracles +Constructor ```solidity -function totalOracles() external view returns (uint256); +constructor() Ownable(msg.sender) EIP712("KeeperOracles", "1"); ``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`uint256`|The total number of oracles registered| - ### addOracle @@ -137,3 +97,20 @@ function updateConfig(string calldata configIpfsHash) external override onlyOwne |Name|Type|Description| |----|----|-----------| |`configIpfsHash`|`string`|The new config IPFS hash| + + +### _verifySignatures + +Internal function for verifying oracles' signatures + + +```solidity +function _verifySignatures(uint256 requiredSignatures, bytes32 message, bytes calldata signatures) internal view; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`requiredSignatures`|`uint256`|The number of signatures required for the verification to pass| +|`message`|`bytes32`|The message that was signed| +|`signatures`|`bytes`|The concatenation of the oracles' signatures| diff --git a/docs/contracts/api/keeper/KeeperRewards.md b/docs/contracts/api/keeper/KeeperRewards.md index 32464200..f6230a8b 100644 --- a/docs/contracts/api/keeper/KeeperRewards.md +++ b/docs/contracts/api/keeper/KeeperRewards.md @@ -6,291 +6,158 @@ description: "Rewards management system for updating Vault and osToken rewards" # KeeperRewards -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/keeper/KeeperRewards.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/keeper/KeeperRewards.sol) -**Inherits:** [KeeperOracles →](./KeeperOracles), IKeeperRewards +**Inherits:** [KeeperOracles](./KeeperOracles), IKeeperRewards -Defines the functionality for updating Vaults' and OsToken rewards. +Defines the functionality for updating Vaults' and OsToken rewards -## Structs -### Reward -A struct containing the last synced Vault's cumulative reward - +## State Variables +### _rewardsUpdateTypeHash ```solidity -struct Reward { - int192 assets; - uint64 nonce; -} +bytes32 private constant _rewardsUpdateTypeHash = keccak256( + "KeeperRewards(bytes32 rewardsRoot,string rewardsIpfsHash,uint256 avgRewardPerSecond,uint64 updateTimestamp,uint64 nonce)" +) ``` -**Properties** - -|Name|Type|Description| -|----|----|-----------| -|`assets`|`int192`|The Vault cumulative reward earned since the start. Can be negative in case of penalty/slashing.| -|`nonce`|`uint64`|The nonce of the last sync| - -### UnlockedMevReward -A struct containing the last unlocked Vault's cumulative execution reward that can be withdrawn from shared MEV escrow. Only used by shared MEV Vaults. +### _maxAvgRewardPerSecond ```solidity -struct UnlockedMevReward { - uint192 assets; - uint64 nonce; -} +uint256 private immutable _maxAvgRewardPerSecond ``` -**Properties** - -|Name|Type|Description| -|----|----|-----------| -|`assets`|`uint192`|The shared MEV Vault's cumulative execution reward that can be withdrawn| -|`nonce`|`uint64`|The nonce of the last sync| - -### RewardsUpdateParams -A struct containing parameters for rewards update +### _sharedMevEscrow ```solidity -struct RewardsUpdateParams { - bytes32 rewardsRoot; - uint256 avgRewardPerSecond; - uint64 updateTimestamp; - string rewardsIpfsHash; - bytes signatures; -} +address private immutable _sharedMevEscrow ``` -**Properties** - -|Name|Type|Description| -|----|----|-----------| -|`rewardsRoot`|`bytes32`|The new rewards merkle root| -|`avgRewardPerSecond`|`uint256`|The new average reward per second| -|`updateTimestamp`|`uint64`|The update timestamp used for rewards calculation| -|`rewardsIpfsHash`|`string`|The new IPFS hash with all the Vaults' rewards for the new root| -|`signatures`|`bytes`|The concatenation of the Oracles' signatures| - -### HarvestParams -A struct containing parameters for harvesting rewards. Can only be called by Vault. +### _osTokenVaultController ```solidity -struct HarvestParams { - bytes32 rewardsRoot; - int160 reward; - uint160 unlockedMevReward; - bytes32[] proof; -} +IOsTokenVaultController private immutable _osTokenVaultController ``` -**Properties** - -|Name|Type|Description| -|----|----|-----------| -|`rewardsRoot`|`bytes32`|The rewards merkle root| -|`reward`|`int160`|The Vault cumulative reward earned since the start. Can be negative in case of penalty/slashing.| -|`unlockedMevReward`|`uint160`|The Vault cumulative execution reward that can be withdrawn from shared MEV escrow. Only used by shared MEV Vaults.| -|`proof`|`bytes32[]`|The proof to verify that Vault's reward is correct| - - -## Events -### RewardsUpdated -Event emitted on rewards update +### _vaultsRegistry ```solidity -event RewardsUpdated( - address indexed caller, - bytes32 indexed rewardsRoot, - uint256 avgRewardPerSecond, - uint64 updateTimestamp, - uint64 nonce, - string rewardsIpfsHash -); +IVaultsRegistry internal immutable _vaultsRegistry ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the function caller| -|`rewardsRoot`|`bytes32`|The new rewards merkle tree root| -|`avgRewardPerSecond`|`uint256`|The new average reward per second| -|`updateTimestamp`|`uint64`|The update timestamp used for rewards calculation| -|`nonce`|`uint64`|The nonce used for verifying signatures| -|`rewardsIpfsHash`|`string`|The new rewards IPFS hash| - -### Harvested -Event emitted on Vault harvest +### rewardsDelay +The rewards delay ```solidity -event Harvested( - address indexed vault, bytes32 indexed rewardsRoot, int256 totalAssetsDelta, uint256 unlockedMevDelta -); +uint256 public immutable override rewardsDelay ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`vault`|`address`|The address of the Vault| -|`rewardsRoot`|`bytes32`|The rewards merkle tree root| -|`totalAssetsDelta`|`int256`|The Vault total assets delta since last sync. Can be negative in case of penalty/slashing.| -|`unlockedMevDelta`|`uint256`|The Vault execution reward that can be withdrawn from shared MEV escrow. Only used by shared MEV Vaults.| - -### RewardsMinOraclesUpdated -Event emitted on rewards min oracles number update +### rewards ```solidity -event RewardsMinOraclesUpdated(uint256 oracles); +mapping(address => Reward) public override rewards ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`oracles`|`uint256`|The new minimum number of oracles required to update rewards| +### unlockedMevRewards +```solidity +mapping(address => UnlockedMevReward) public override unlockedMevRewards +``` -## Functions -### prevRewardsRoot +### prevRewardsRoot Previous Rewards Root ```solidity -function prevRewardsRoot() external view returns (bytes32); +bytes32 public override prevRewardsRoot ``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`bytes32`|The previous merkle tree root of the rewards accumulated by the Vaults| ### rewardsRoot - Rewards Root ```solidity -function rewardsRoot() external view returns (bytes32); +bytes32 public override rewardsRoot ``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`bytes32`|The latest merkle tree root of the rewards accumulated by the Vaults| -### rewardsNonce - -Rewards Nonce +### rewardsMinOracles +The minimum number of oracles required to update rewards ```solidity -function rewardsNonce() external view returns (uint64); +uint256 public override rewardsMinOracles ``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`uint64`|The nonce used for updating rewards merkle tree root| ### lastRewardsTimestamp - The last rewards update ```solidity -function lastRewardsTimestamp() external view returns (uint64); +uint64 public override lastRewardsTimestamp ``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`uint64`|The timestamp of the last rewards update| - -### rewardsMinOracles - -The minimum number of oracles required to update rewards - -```solidity -function rewardsMinOracles() external view returns (uint256); -``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`uint256`|The minimum number of oracles| - - -### rewardsDelay - -The rewards delay +### rewardsNonce +Rewards Nonce ```solidity -function rewardsDelay() external view returns (uint256); +uint64 public override rewardsNonce ``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`uint256`|The delay in seconds between rewards updates| -### rewards +## Functions +### constructor -Get last synced Vault cumulative reward +Constructor ```solidity -function rewards(address vault) external view returns (int192 assets, uint64 nonce); +constructor( + address sharedMevEscrow, + IVaultsRegistry vaultsRegistry, + IOsTokenVaultController osTokenVaultController, + uint256 _rewardsDelay, + uint256 maxAvgRewardPerSecond +) ; ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`vault`|`address`|The address of the Vault| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|`assets`|`int192`|The last synced reward assets| -|`nonce`|`uint64`|The last synced reward nonce| +|`sharedMevEscrow`|`address`|The address of the shared MEV escrow contract| +|`vaultsRegistry`|`IVaultsRegistry`|The address of the VaultsRegistry contract| +|`osTokenVaultController`|`IOsTokenVaultController`|The address of the OsTokenVaultController contract| +|`_rewardsDelay`|`uint256`|The delay in seconds between rewards updates| +|`maxAvgRewardPerSecond`|`uint256`|The maximum possible average reward per second| -### unlockedMevRewards +### updateRewards -Get last unlocked shared MEV Vault cumulative reward +Update rewards data ```solidity -function unlockedMevRewards(address vault) external view returns (uint192 assets, uint64 nonce); +function updateRewards(RewardsUpdateParams calldata params) external override; ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`vault`|`address`|The address of the Vault| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|`assets`|`uint192`|The last synced reward assets| -|`nonce`|`uint64`|The last synced reward nonce| +|`params`|`RewardsUpdateParams`|The struct containing rewards update parameters| ### canUpdateRewards @@ -371,21 +238,6 @@ function isCollateralized(address vault) public view override returns (bool); |``|`bool`|`true` if Vault is collateralized, `false` otherwise| -### updateRewards - -Update rewards data - - -```solidity -function updateRewards(RewardsUpdateParams calldata params) external override; -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`params`|`RewardsUpdateParams`|The struct containing rewards update parameters| - - ### harvest Harvest rewards. Can be called only by Vault. @@ -425,3 +277,33 @@ function setRewardsMinOracles(uint256 _rewardsMinOracles) external override only |Name|Type|Description| |----|----|-----------| |`_rewardsMinOracles`|`uint256`|The new min number of oracles for confirming rewards update| + + +### _setRewardsMinOracles + +Internal function for updating rewardsMinOracles + + +```solidity +function _setRewardsMinOracles(uint256 _rewardsMinOracles) private; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_rewardsMinOracles`|`uint256`|The new value of rewardsMinOracles| + + +### _collateralize + +Collateralize Vault so that it must be harvested in future reward updates + + +```solidity +function _collateralize(address vault) internal; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vault`|`address`|The address of the Vault| diff --git a/docs/contracts/api/keeper/KeeperValidators.md b/docs/contracts/api/keeper/KeeperValidators.md index d39a3892..b636e487 100644 --- a/docs/contracts/api/keeper/KeeperValidators.md +++ b/docs/contracts/api/keeper/KeeperValidators.md @@ -6,124 +6,83 @@ description: "Validator registration approval and exit signature management syst # KeeperValidators -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/keeper/KeeperValidators.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/keeper/KeeperValidators.sol) -**Inherits:** [KeeperOracles →](./KeeperOracles), [KeeperRewards →](./KeeperRewards), IKeeperValidators +**Inherits:** [KeeperOracles](./KeeperOracles), [KeeperRewards](./KeeperRewards), IKeeperValidators -Defines the functionality for approving validators' registrations and updating exit signatures. +Defines the functionality for approving validators' registrations and updating exit signatures -## Structs -### ApprovalParams -Struct for approving registration of one or more validators - +## State Variables +### _registerValidatorsTypeHash ```solidity -struct ApprovalParams { - bytes32 validatorsRegistryRoot; - uint256 deadline; - bytes validators; - bytes signatures; - string exitSignaturesIpfsHash; -} +bytes32 private constant _registerValidatorsTypeHash = keccak256( + "KeeperValidators(bytes32 validatorsRegistryRoot,address vault,bytes validators,string exitSignaturesIpfsHash,uint256 deadline)" +) ``` -**Properties** -|Name|Type|Description| -|----|----|-----------| -|`validatorsRegistryRoot`|`bytes32`|The deposit data root used to verify that oracles approved validators| -|`deadline`|`uint256`|The deadline for submitting the approval| -|`validators`|`bytes`|The concatenation of the validators' public key, signature and deposit data root| -|`signatures`|`bytes`|The concatenation of Oracles' signatures| -|`exitSignaturesIpfsHash`|`string`|The IPFS hash with the validators' exit signatures| +### _updateExitSigTypeHash +```solidity +bytes32 private constant _updateExitSigTypeHash = + keccak256("KeeperValidators(address vault,string exitSignaturesIpfsHash,uint256 nonce,uint256 deadline)") +``` -## Events -### ValidatorsApproval -Event emitted on validators approval +### _validatorsRegistry ```solidity -event ValidatorsApproval(address indexed vault, string exitSignaturesIpfsHash); +IValidatorsRegistry private immutable _validatorsRegistry ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`vault`|`address`|The address of the Vault| -|`exitSignaturesIpfsHash`|`string`|The IPFS hash with the validators' exit signatures| - -### ExitSignaturesUpdated -Event emitted on exit signatures update +### exitSignaturesNonces ```solidity -event ExitSignaturesUpdated( - address indexed caller, address indexed vault, uint256 nonce, string exitSignaturesIpfsHash -); +mapping(address => uint256) public override exitSignaturesNonces ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the function caller| -|`vault`|`address`|The address of the Vault| -|`nonce`|`uint256`|The nonce used for verifying Oracles' signatures| -|`exitSignaturesIpfsHash`|`string`|The IPFS hash with the validators' exit signatures| -### ValidatorsMinOraclesUpdated -Event emitted on validators min oracles number update +### validatorsMinOracles +The minimum number of oracles required to update validators ```solidity -event ValidatorsMinOraclesUpdated(uint256 oracles); +uint256 public override validatorsMinOracles ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`oracles`|`uint256`|The new minimum number of oracles required to approve validators| - ## Functions -### exitSignaturesNonces +### constructor -Get nonce for the next vault exit signatures update +Constructor ```solidity -function exitSignaturesNonces(address vault) external view returns (uint256); +constructor(IValidatorsRegistry validatorsRegistry) ; ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`vault`|`address`|The address of the Vault to get the nonce for| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`uint256`|The nonce of the Vault for updating signatures| +|`validatorsRegistry`|`IValidatorsRegistry`|The address of the beacon chain validators registry contract| -### validatorsMinOracles +### setValidatorsMinOracles -The minimum number of oracles required to update validators +Function for updating validators min oracles number ```solidity -function validatorsMinOracles() external view returns (uint256); +function setValidatorsMinOracles(uint256 _validatorsMinOracles) external override onlyOwner; ``` -**Returns** +**Parameters** |Name|Type|Description| |----|----|-----------| -|``|`uint256`|The minimum number of oracles| +|`_validatorsMinOracles`|`uint256`|The new minimum number of oracles required to approve validators| ### approveValidators @@ -164,13 +123,13 @@ function updateExitSignatures( |`oraclesSignatures`|`bytes`|The concatenation of Oracles' signatures| -### setValidatorsMinOracles +### _setValidatorsMinOracles -Function for updating validators min oracles number +Internal function to set the minimum number of oracles required to approve validators ```solidity -function setValidatorsMinOracles(uint256 _validatorsMinOracles) external override onlyOwner; +function _setValidatorsMinOracles(uint256 _validatorsMinOracles) private; ``` **Parameters** diff --git a/docs/contracts/api/libraries/EIP712Utils.md b/docs/contracts/api/libraries/EIP712Utils.md index 6152d9bf..44d6fa0c 100644 --- a/docs/contracts/api/libraries/EIP712Utils.md +++ b/docs/contracts/api/libraries/EIP712Utils.md @@ -6,9 +6,25 @@ description: "Utility library for calculating EIP712 typed data hashes" # EIP712Utils -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/libraries/EIP712Utils.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/libraries/EIP712Utils.sol) -Includes functionality for calculating [EIP712 ↗](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md) hashes. +Includes functionality for calculating EIP712 hashes + + +## State Variables +### _domainTypeHash + +```solidity +bytes32 private constant _domainTypeHash = + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") +``` + + +### _versionHash + +```solidity +bytes32 private constant _versionHash = keccak256("1") +``` ## Functions diff --git a/docs/contracts/api/libraries/Errors.md b/docs/contracts/api/libraries/Errors.md index fe2ce7ea..91018c6e 100644 --- a/docs/contracts/api/libraries/Errors.md +++ b/docs/contracts/api/libraries/Errors.md @@ -6,9 +6,9 @@ description: "Custom error definitions used across contracts" # Errors -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/libraries/Errors.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/libraries/Errors.sol) -Contains all the custom errors. +Contains all the custom errors ## Errors @@ -330,14 +330,26 @@ error RewardsNonceIsHigher(); error InvalidRedeemablePositions(); ``` -### RedeemablePositionsProposed +### InvalidDelay ```solidity -error RedeemablePositionsProposed(); +error InvalidDelay(); ``` -### InvalidDelay +### InvalidTicket ```solidity -error InvalidDelay(); +error InvalidTicket(); +``` + +### LowBalance + +```solidity +error LowBalance(); +``` + +### InvalidMinBalancePercent + +```solidity +error InvalidMinBalancePercent(); ``` diff --git a/docs/contracts/api/libraries/ExitPositions.md b/docs/contracts/api/libraries/ExitPositions.md new file mode 100644 index 00000000..42dad8e8 --- /dev/null +++ b/docs/contracts/api/libraries/ExitPositions.md @@ -0,0 +1,88 @@ +--- +title: ExitPositions +sidebar_position: 3 +description: "Includes the common functionality for managing exit positions in queues" +--- + +# ExitPositions + +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/libraries/ExitPositions.sol) + +Includes the common functionality for managing exit positions in queues + + +## Functions +### peek + +Fetches the exit data from a mapping-based queue + + +```solidity +function peek(mapping(address vault => DoubleEndedQueue.Bytes32Deque) storage exits, address vault) + internal + view + returns (uint160 positionTicket, uint96 shares); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`exits`|`mapping(address vault => DoubleEndedQueue.Bytes32Deque)`|The mapping of exit queues| +|`vault`|`address`|The address of the vault| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`positionTicket`|`uint160`|The position ticket| +|`shares`|`uint96`|The shares to be exited| + + +### push + +Stores exit data in a mapping-based queue + + +```solidity +function push( + mapping(address vault => DoubleEndedQueue.Bytes32Deque) storage exits, + address vault, + uint160 positionTicket, + uint96 shares, + bool front +) internal; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`exits`|`mapping(address vault => DoubleEndedQueue.Bytes32Deque)`|The mapping of exit queues| +|`vault`|`address`|The address of the vault| +|`positionTicket`|`uint160`|The position ticket| +|`shares`|`uint96`|The shares to be exited| +|`front`|`bool`|Whether to insert the exit data at the front of the queue| + + +### pop + +Removes and returns exit data from a mapping-based queue + + +```solidity +function pop(mapping(address vault => DoubleEndedQueue.Bytes32Deque) storage exits, address vault) + internal + returns (uint160 positionTicket, uint96 shares); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`exits`|`mapping(address vault => DoubleEndedQueue.Bytes32Deque)`|The mapping of exit queues| +|`vault`|`address`|The address of the vault| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`positionTicket`|`uint160`|The position ticket| +|`shares`|`uint96`|The shares to be exited| diff --git a/docs/contracts/api/libraries/ExitQueue.md b/docs/contracts/api/libraries/ExitQueue.md index 9561503c..dc6a2c14 100644 --- a/docs/contracts/api/libraries/ExitQueue.md +++ b/docs/contracts/api/libraries/ExitQueue.md @@ -1,50 +1,14 @@ --- title: ExitQueue -sidebar_position: 3 +sidebar_position: 4 description: "Library for managing exit queue checkpoints of burned shares and exited assets" --- # ExitQueue -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/libraries/ExitQueue.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/libraries/ExitQueue.sol) -ExitQueue represent checkpoints of burned shares and exited assets. - - -## Structs -### Checkpoint -A struct containing checkpoint data - - -```solidity -struct Checkpoint { - uint160 totalTickets; - uint96 exitedAssets; -} -``` - -**Properties** - -|Name|Type|Description| -|----|----|-----------| -|`totalTickets`|`uint160`|The cumulative number of tickets (shares) exited| -|`exitedAssets`|`uint96`|The number of assets that exited in this checkpoint| - -### History -A struct containing the history of checkpoints data - - -```solidity -struct History { - Checkpoint[] checkpoints; -} -``` - -**Properties** - -|Name|Type|Description| -|----|----|-----------| -|`checkpoints`|`Checkpoint[]`|An array of checkpoints| +ExitQueue represent checkpoints of burned shares and exited assets ## Functions @@ -136,3 +100,46 @@ function push(History storage self, uint256 shares, uint256 assets) internal; |`self`|`History`|An array containing checkpoints| |`shares`|`uint256`|The number of shares to add to the latest checkpoint| |`assets`|`uint256`|The number of assets that were exited for this checkpoint| + + +### _unsafeAccess + + +```solidity +function _unsafeAccess(Checkpoint[] storage self, uint256 pos) private pure returns (Checkpoint storage result); +``` + +## Structs +### Checkpoint +A struct containing checkpoint data + + +```solidity +struct Checkpoint { + uint160 totalTickets; + uint96 exitedAssets; +} +``` + +**Properties** + +|Name|Type|Description| +|----|----|-----------| +|`totalTickets`|`uint160`|The cumulative number of tickets (shares) exited| +|`exitedAssets`|`uint96`|The number of assets that exited in this checkpoint| + +### History +A struct containing the history of checkpoints data + + +```solidity +struct History { + Checkpoint[] checkpoints; +} +``` + +**Properties** + +|Name|Type|Description| +|----|----|-----------| +|`checkpoints`|`Checkpoint[]`|An array of checkpoints| diff --git a/docs/contracts/api/libraries/OsTokenUtils.md b/docs/contracts/api/libraries/OsTokenUtils.md index ec3ea417..3d63d02c 100644 --- a/docs/contracts/api/libraries/OsTokenUtils.md +++ b/docs/contracts/api/libraries/OsTokenUtils.md @@ -1,40 +1,43 @@ --- title: OsTokenUtils -sidebar_position: 4 +sidebar_position: 5 description: "Utility library for handling osToken redemption calculations" --- # OsTokenUtils -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/libraries/OsTokenUtils.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/libraries/OsTokenUtils.sol) -Includes functionality for handling osToken redemptions. +Includes functionality for handling osToken redemptions -## Structs -### RedemptionData -Struct for storing redemption data +## State Variables +### _wad + +```solidity +uint256 private constant _wad = 1e18 +``` + +### _hfLiqThreshold ```solidity -struct RedemptionData { - uint256 mintedAssets; - uint256 depositedAssets; - uint256 redeemedOsTokenShares; - uint256 availableAssets; - bool isLiquidation; -} +uint256 private constant _hfLiqThreshold = 1e18 ``` -**Properties** -|Name|Type|Description| -|----|----|-----------| -|`mintedAssets`|`uint256`|The amount of minted assets| -|`depositedAssets`|`uint256`|The amount of deposited assets| -|`redeemedOsTokenShares`|`uint256`|The amount of redeemed osToken shares| -|`availableAssets`|`uint256`|The amount of available assets| -|`isLiquidation`|`bool`|Whether the redemption is a liquidation| +### _maxPercent + +```solidity +uint256 private constant _maxPercent = 1e18 +``` + + +### _disabledLiqThreshold + +```solidity +uint256 private constant _disabledLiqThreshold = type(uint64).max +``` ## Functions @@ -63,3 +66,29 @@ function calculateReceivedAssets( |Name|Type|Description| |----|----|-----------| |`receivedAssets`|`uint256`|The amount of received assets| + + +## Structs +### RedemptionData +Struct for storing redemption data + + +```solidity +struct RedemptionData { + uint256 mintedAssets; + uint256 depositedAssets; + uint256 redeemedOsTokenShares; + uint256 availableAssets; + bool isLiquidation; +} +``` + +**Properties** + +|Name|Type|Description| +|----|----|-----------| +|`mintedAssets`|`uint256`|The amount of minted assets| +|`depositedAssets`|`uint256`|The amount of deposited assets| +|`redeemedOsTokenShares`|`uint256`|The amount of redeemed osToken shares| +|`availableAssets`|`uint256`|The amount of available assets| +|`isLiquidation`|`bool`|Whether the redemption is a liquidation| diff --git a/docs/contracts/api/libraries/ValidatorUtils.md b/docs/contracts/api/libraries/ValidatorUtils.md index a5c1c63c..1c761425 100644 --- a/docs/contracts/api/libraries/ValidatorUtils.md +++ b/docs/contracts/api/libraries/ValidatorUtils.md @@ -1,26 +1,64 @@ --- title: ValidatorUtils -sidebar_position: 5 +sidebar_position: 6 description: "Utility library for managing validator operations and deposits" --- # ValidatorUtils -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/libraries/ValidatorUtils.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/libraries/ValidatorUtils.sol) -Includes functionality for managing the validators. +Includes functionality for managing the validators -## Structs -### ValidatorDeposit + +## State Variables +### _validatorsManagerTypeHash ```solidity -struct ValidatorDeposit { - bytes publicKey; - bytes signature; - bytes withdrawalCredentials; - bytes32 depositDataRoot; - uint256 depositAmount; -} +bytes32 private constant _validatorsManagerTypeHash = + keccak256("VaultValidators(bytes32 validatorsRegistryRoot,bytes validators)") +``` + + +### _validatorV1DepositLength + +```solidity +uint256 private constant _validatorV1DepositLength = 176 +``` + + +### _validatorV2DepositLength + +```solidity +uint256 private constant _validatorV2DepositLength = 184 +``` + + +### _validatorWithdrawalLength + +```solidity +uint256 private constant _validatorWithdrawalLength = 56 +``` + + +### _validatorConsolidationLength + +```solidity +uint256 private constant _validatorConsolidationLength = 96 +``` + + +### _validatorMinEffectiveBalance + +```solidity +uint256 private constant _validatorMinEffectiveBalance = 32 ether +``` + + +### _validatorMaxEffectiveBalance + +```solidity +uint256 private constant _validatorMaxEffectiveBalance = 2048 ether ``` @@ -184,3 +222,17 @@ function consolidateValidators( |`validators`|`bytes`|The validators data| |`consolidationsApproved`|`bool`|Whether the consolidations are approved| |`validatorsConsolidations`|`address`|The address of the validators consolidations contract| + + +## Structs +### ValidatorDeposit + +```solidity +struct ValidatorDeposit { + bytes publicKey; + bytes signature; + bytes withdrawalCredentials; + bytes32 depositDataRoot; + uint256 depositAmount; +} +``` diff --git a/docs/contracts/api/misc/EthRewardSplitter.md b/docs/contracts/api/misc/EthRewardSplitter.md index d1ad4d8c..4af1b465 100644 --- a/docs/contracts/api/misc/EthRewardSplitter.md +++ b/docs/contracts/api/misc/EthRewardSplitter.md @@ -6,14 +6,24 @@ description: "Ethereum-specific implementation of RewardSplitter for distributin # EthRewardSplitter -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/misc/EthRewardSplitter.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/misc/EthRewardSplitter.sol) -**Inherits:** [ReentrancyGuardUpgradeable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/utils/ReentrancyGuardUpgradeable.sol), [RewardSplitter →](./RewardSplitter) +**Inherits:** [ReentrancyGuardUpgradeable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/utils/ReentrancyGuardUpgradeable.sol), [RewardSplitter](./RewardSplitter) -The EthRewardSplitter can be used on Ethereum networks to split the rewards of the fee recipient of the Vault based on configured shares. +The EthRewardSplitter can be used on Ethereum networks +to split the rewards of the fee recipient of the vault based on configured shares ## Functions +### constructor + +Constructor for EthRewardSplitter + + +```solidity +constructor() RewardSplitter(); +``` + ### receive Allows to claim rewards from the vault and receive them to the reward splitter address @@ -22,3 +32,33 @@ Allows to claim rewards from the vault and receive them to the reward splitter a ```solidity receive() external payable; ``` + +### initialize + +Initializes the RewardSplitter contract + + +```solidity +function initialize(address _vault) external override initializer; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_vault`|`address`|The address of the vault to which the RewardSplitter will be connected| + + +### _transferRewards + +Transfers the specified amount of rewards to the shareholder + + +```solidity +function _transferRewards(address shareholder, uint256 amount) internal override nonReentrant; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`shareholder`|`address`|The address of the shareholder| +|`amount`|`uint256`|The amount of rewards to transfer| diff --git a/docs/contracts/api/misc/GnoRewardSplitter.md b/docs/contracts/api/misc/GnoRewardSplitter.md index 82edb018..6bbb8c47 100644 --- a/docs/contracts/api/misc/GnoRewardSplitter.md +++ b/docs/contracts/api/misc/GnoRewardSplitter.md @@ -6,8 +6,64 @@ description: "Gnosis-specific implementation of RewardSplitter for distributing # GnoRewardSplitter -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/misc/GnoRewardSplitter.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/misc/GnoRewardSplitter.sol) -**Inherits:** [RewardSplitter →](./RewardSplitter) +**Inherits:** [RewardSplitter](./RewardSplitter) -The GnoRewardSplitter can be used on Gnosis networks to split the rewards of the fee recipient of the Vault based on configures shares. +The GnoRewardSplitter can be used on Gnosis networks +to split the rewards of the fee recipient of the vault based on configures shares + + +## State Variables +### _gnoToken + +```solidity +IERC20 private immutable _gnoToken +``` + + +## Functions +### constructor + +Constructor for GnoRewardSplitter + + +```solidity +constructor(address gnoToken) RewardSplitter(); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`gnoToken`|`address`|The address of the GNO token| + + +### initialize + +Initializes the RewardSplitter contract + + +```solidity +function initialize(address _vault) external override initializer; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_vault`|`address`|The address of the vault to which the RewardSplitter will be connected| + + +### _transferRewards + +Transfers the specified amount of rewards to the shareholder + + +```solidity +function _transferRewards(address shareholder, uint256 amount) internal override; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`shareholder`|`address`|The address of the shareholder| +|`amount`|`uint256`|The amount of rewards to transfer| diff --git a/docs/contracts/api/misc/RewardSplitter.md b/docs/contracts/api/misc/RewardSplitter.md index c58fe0c1..c4e65f5a 100644 --- a/docs/contracts/api/misc/RewardSplitter.md +++ b/docs/contracts/api/misc/RewardSplitter.md @@ -6,159 +6,82 @@ description: "Abstract contract for splitting vault rewards among shareholders b # RewardSplitter -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/misc/RewardSplitter.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/misc/RewardSplitter.sol) -**Inherits:** IRewardSplitter, [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [Multicall →](../base/Multicall) +**Inherits:** IRewardSplitter, [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [Multicall](../base/Multicall) -The RewardSplitter can be used to split the rewards of the fee recipient of the Vault based on configured shares. +The RewardSplitter can be used to split the rewards of the fee recipient of the vault based on configured shares -## Structs -### ShareHolder -Structure for storing information about share holder - +## State Variables +### _wad ```solidity -struct ShareHolder { - uint128 shares; - uint128 rewardPerShare; -} +uint256 private constant _wad = 1e18 ``` -**Properties** - -|Name|Type|Description| -|----|----|-----------| -|`shares`|`uint128`|The amount of shares the account has| -|`rewardPerShare`|`uint128`|The last synced reward per share| - -## Events -### ClaimerUpdated -Event emitted when the claim on behalf flag is updated +### vault +The vault to which the RewardSplitter is connected ```solidity -event ClaimerUpdated(address indexed caller, address indexed claimer); +address public override vault ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the account that called the function| -|`claimer`|`address`|The address of the claimer that can claim rewards on behalf of shareholders| - -### SharesIncreased -Event emitted when the number of shares is increased for an account +### totalShares +The total number of shares in the splitter ```solidity -event SharesIncreased(address indexed account, uint256 amount); +uint256 public override totalShares ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`account`|`address`|The address of the account for which the shares were increased| -|`amount`|`uint256`|The amount of shares that were added| -### SharesDecreased -Event emitted when the number of shares is decreased for an account +### claimer +Returns the address of the claimer that can claim rewards on behalf of shareholders ```solidity -event SharesDecreased(address indexed account, uint256 amount); +address public override claimer ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`account`|`address`|The address of the account for which the shares were decreased| -|`amount`|`uint256`|The amount of shares that were deducted| - -### RewardsSynced -Event emitted when the rewards are synced from the vault. +### _shareHolders ```solidity -event RewardsSynced(uint256 totalRewards, uint256 rewardPerShare); +mapping(address => ShareHolder) private _shareHolders ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`totalRewards`|`uint256`|The new total amount of rewards| -|`rewardPerShare`|`uint256`|The new reward per share| - -### RewardsWithdrawn -Event emitted when the rewards are withdrawn from the splitter +### _unclaimedRewards ```solidity -event RewardsWithdrawn(address indexed account, uint256 amount); +mapping(address => uint256) private _unclaimedRewards ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`account`|`address`|The address of the account for which the rewards were withdrawn| -|`amount`|`uint256`|The amount of rewards that were withdrawn| - -### ExitQueueEnteredOnBehalf -Event emitted when the rewards are claimed on behalf +### exitPositions ```solidity -event ExitQueueEnteredOnBehalf(address indexed onBehalf, uint256 positionTicket, uint256 amount); +mapping(uint256 positionTicket => address onBehalf) public override exitPositions ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`onBehalf`|`address`|The address of the account on behalf of which the rewards were claimed| -|`positionTicket`|`uint256`|The position ticket in the exit queue| -|`amount`|`uint256`|The amount of rewards that were claimed| - -### ExitedAssetsClaimedOnBehalf -Event emitted when the exited assets are claimed on behalf +### _totalRewards ```solidity -event ExitedAssetsClaimedOnBehalf(address indexed onBehalf, uint256 positionTicket, uint256 amount); +uint128 private _totalRewards ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`onBehalf`|`address`|The address of the account on behalf of which the assets were claimed| -|`positionTicket`|`uint256`|The position ticket in the exit queue| -|`amount`|`uint256`|The amount of assets that were claimed| -## Errors -### NotHarvested +### _rewardPerShare ```solidity -error NotHarvested(); +uint128 private _rewardPerShare ``` -### InvalidAccount - -```solidity -error InvalidAccount(); -``` - -### InvalidAmount - -```solidity -error InvalidAmount(); -``` ## Functions ### onlyVaultAdmin @@ -170,6 +93,13 @@ Modifier to check if the caller is the vault admin modifier onlyVaultAdmin() ; ``` +### constructor + + +```solidity +constructor() ; +``` + ### setClaimer Sets the claimer address that can claim rewards on behalf of shareholders. @@ -377,6 +307,22 @@ function claimExitedAssetsOnBehalf(uint256 positionTicket, uint256 timestamp, ui |`exitQueueIndex`|`uint256`|The exit queue index of the exit request| +### _transferRewards + +Transfers the specified amount of rewards to the shareholder + + +```solidity +function _transferRewards(address shareholder, uint256 amount) internal virtual; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`shareholder`|`address`|The address of the shareholder| +|`amount`|`uint256`|The amount of rewards to transfer| + + ### syncRewards Syncs the rewards from the vault to the splitter. The vault state must be up-to-date. @@ -385,3 +331,39 @@ Syncs the rewards from the vault to the splitter. The vault state must be up-to- ```solidity function syncRewards() public override; ``` + +### _withdrawRewards + +Withdraws rewards for the given account + + +```solidity +function _withdrawRewards(address account, uint256 rewards) private returns (uint256); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`account`|`address`|The address of the account to withdraw rewards for| +|`rewards`|`uint256`|The amount of rewards to withdraw| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The actual amount of rewards withdrawn| + + +### __RewardSplitter_init + +Initializes the RewardSplitter contract + + +```solidity +function __RewardSplitter_init(address _vault) internal onlyInitializing; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_vault`|`address`|The address of the vault to which the RewardSplitter will be connected| diff --git a/docs/contracts/api/misc/RewardSplitterFactory.md b/docs/contracts/api/misc/RewardSplitterFactory.md index 6f90cd33..ae776314 100644 --- a/docs/contracts/api/misc/RewardSplitterFactory.md +++ b/docs/contracts/api/misc/RewardSplitterFactory.md @@ -6,46 +6,37 @@ description: "Factory contract for deploying RewardSplitter proxies" # RewardSplitterFactory -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/misc/RewardSplitterFactory.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/misc/RewardSplitterFactory.sol) **Inherits:** IRewardSplitterFactory -Factory for deploying the RewardSplitter contract. +Factory for deploying the RewardSplitter contract -## Events -### RewardSplitterCreated -Event emitted on a RewardSplitter creation +## State Variables +### implementation +The address of the RewardSplitter implementation contract used for proxy creation ```solidity -event RewardSplitterCreated(address owner, address vault, address rewardSplitter); +address public immutable override implementation ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`owner`|`address`|The address of the RewardSplitter owner| -|`vault`|`address`|The address of the connected vault| -|`rewardSplitter`|`address`|The address of the created RewardSplitter| - ## Functions +### constructor -### implementation - -The address of the RewardSplitter implementation contract used for proxy creation +Constructor ```solidity -function implementation() external view returns (address); +constructor(address _implementation) ; ``` -**Returns** +**Parameters** |Name|Type|Description| |----|----|-----------| -|``|`address`|The address of the RewardSplitter proxy contract| +|`_implementation`|`address`|The implementation address of RewardSplitter| ### createRewardSplitter diff --git a/docs/contracts/api/nodes/EthNodesManager.md b/docs/contracts/api/nodes/EthNodesManager.md new file mode 100644 index 00000000..62e3d634 --- /dev/null +++ b/docs/contracts/api/nodes/EthNodesManager.md @@ -0,0 +1,131 @@ +--- +title: EthNodesManager +sidebar_position: 1 +description: "Implements Ethereum specific functionality for the NodesManager contract" +--- + +# EthNodesManager + +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/nodes/EthNodesManager.sol) + +**Inherits:** [NodesManager](./NodesManager), IEthNodesManager + +Implements Ethereum specific functionality for the NodesManager contract + + +## Functions +### constructor + +Constructor + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(address _vault, address _keeper) NodesManager(_vault, _keeper); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_vault`|`address`|| +|`_keeper`|`address`|The address of the Keeper contract| + + +### initialize + +Initializes the EthNodesManager contract + + +```solidity +function initialize(address owner, uint256 _minDepositAssets, uint16 _minBalancePercent, uint256 _stateUpdateDelay) + external + initializer; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`owner`|`address`|The address of the contract owner| +|`_minDepositAssets`|`uint256`|The minimum deposit assets| +|`_minBalancePercent`|`uint16`|The minimum balance percent in BPS| +|`_stateUpdateDelay`|`uint256`|The delay in seconds between state updates| + + +### deposit + +Deposits ETH as bond assets + + +```solidity +function deposit() external payable override returns (uint256 shares); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The vault shares received for the deposit| + + +### _depositToVault + +Deposits assets to the vault and returns the shares received. +Must be implemented by network-specific contracts. + + +```solidity +function _depositToVault(uint256 assets) internal override returns (uint256 shares); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`assets`|`uint256`|The amount of assets to deposit| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The vault shares received| + + +### _transferAssets + +Transfers assets to the receiver. +Must be implemented by network-specific contracts. + + +```solidity +function _transferAssets(address receiver, uint256 assets) internal override; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address to transfer assets to| +|`assets`|`uint256`|The amount of assets to transfer| + + +### _donateAssets + +Donates assets back to the vault. +Must be implemented by network-specific contracts. + + +```solidity +function _donateAssets(uint256 assets) internal override; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`assets`|`uint256`|The amount of assets to donate| + + +### receive + + +```solidity +receive() external payable; +``` diff --git a/docs/contracts/api/nodes/NodesManager.md b/docs/contracts/api/nodes/NodesManager.md new file mode 100644 index 00000000..3fd1ce52 --- /dev/null +++ b/docs/contracts/api/nodes/NodesManager.md @@ -0,0 +1,647 @@ +--- +title: NodesManager +sidebar_position: 2 +description: "uint256 private constant _validatorChangeClaimDelay = 2" +--- + +# NodesManager + +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/nodes/NodesManager.sol) + +**Inherits:** [Ownable2StepUpgradeable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/access/Ownable2StepUpgradeable.sol), [EIP712Upgradeable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/utils/cryptography/EIP712Upgradeable.sol), [UUPSUpgradeable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/UUPSUpgradeable.sol), [ReentrancyGuardUpgradeable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/utils/ReentrancyGuardUpgradeable.sol), [Multicall](../base/Multicall), INodesManager + + +## State Variables +### _validatorChangeClaimDelay + +```solidity +uint256 private constant _validatorChangeClaimDelay = 2 +``` + + +### _wad + +```solidity +uint256 private constant _wad = 1e18 +``` + + +### _maxPercent + +```solidity +uint256 private constant _maxPercent = 10_000 +``` + + +### _validatorV2DepositLength + +```solidity +uint256 private constant _validatorV2DepositLength = 184 +``` + + +### _signatureLength + +```solidity +uint256 private constant _signatureLength = 65 +``` + + +### _fundValidatorsTypeHash + +```solidity +bytes32 private constant _fundValidatorsTypeHash = + keccak256("FundValidators(address operator,uint256 nonce,address vault,bytes validators)") +``` + + +### _registerValidatorsTypeHash + +```solidity +bytes32 private constant _registerValidatorsTypeHash = + keccak256("RegisterValidators(address operator,uint256 nonce,address vault,bytes validators)") +``` + + +### _updateStateTypeHash + +```solidity +bytes32 private constant _updateStateTypeHash = + keccak256("UpdateState(bytes32 stateRoot,string stateIpfsHash,uint64 updateTimestamp,uint256 nonce)") +``` + + +### _keeper + +```solidity +IKeeper private immutable _keeper +``` + + +### vault +The address of the vault the NodesManager is attached to + + +```solidity +address public immutable override vault +``` + + +### operatorStates + +```solidity +mapping(address operator => OperatorState state) public override operatorStates +``` + + +### stateData +The state data of the nodes manager + + +```solidity +StateData public override stateData +``` + + +### minDepositAssets +The minimum assets required for a deposit request + + +```solidity +uint256 public override minDepositAssets +``` + + +### withdrawalsManager +The address of the withdrawals manager + + +```solidity +address public override withdrawalsManager +``` + + +### minBalancePercent +The minimum balance percent in BPS (10000 = 100%) + + +```solidity +uint16 public override minBalancePercent +``` + + +### operatorNonces + +```solidity +mapping(address operator => mapping(OperatorNonceType nonceType => uint256 nonce)) public override operatorNonces +``` + + +### pendingPenaltyAssets + +```solidity +mapping(address operator => uint256 penaltyAssets) public override pendingPenaltyAssets +``` + + +### validatorsManagers + +```solidity +mapping(address operator => address manager) public override validatorsManagers +``` + + +### _exitPositions + +```solidity +mapping(uint256 positionTicket => address operator) private _exitPositions +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` + + +## Functions +### onlyWithdrawalsManager + +Modifier to restrict access to the withdrawals manager + + +```solidity +modifier onlyWithdrawalsManager() ; +``` + +### constructor + +Constructor sets the immutables + + +```solidity +constructor(address vault_, address keeper_) ; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vault_`|`address`|The address of the vault| +|`keeper_`|`address`|The address of the Keeper contract| + + +### __NodesManager_init + +Initializes the NodesManager contract + + +```solidity +function __NodesManager_init( + address _owner, + uint256 _minDepositAssets, + uint16 _minBalancePercent, + uint256 _stateUpdateDelay +) internal onlyInitializing; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_owner`|`address`|The address of the contract owner| +|`_minDepositAssets`|`uint256`|The minimum assets required for a deposit request| +|`_minBalancePercent`|`uint16`|The minimum balance percent in BPS| +|`_stateUpdateDelay`|`uint256`|The delay in seconds between state updates| + + +### setMinDepositAssets + +Updates the minimum deposit assets. Can only be called by the owner. + + +```solidity +function setMinDepositAssets(uint256 newMinDepositAssets) external override onlyOwner; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`newMinDepositAssets`|`uint256`|The new minimum deposit assets| + + +### setMinBalancePercent + +Updates the minimum balance percent. Can only be called by the owner. + + +```solidity +function setMinBalancePercent(uint16 newMinBalancePercent) external override onlyOwner; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`newMinBalancePercent`|`uint16`|The new minimum balance percent| + + +### setWithdrawalsManager + +Updates the withdrawals manager address. Can only be called by the owner. + + +```solidity +function setWithdrawalsManager(address newWithdrawalsManager) external override onlyOwner; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`newWithdrawalsManager`|`address`|The new withdrawals manager address| + + +### setValidatorsManager + +Sets the validators manager address for the calling operator + + +```solidity +function setValidatorsManager(address validatorsManager) external override; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`validatorsManager`|`address`|The new validators manager address| + + +### canUpdateState + +Checks whether state can be updated + + +```solidity +function canUpdateState() external view override returns (bool); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|`true` if state can be updated, `false` otherwise| + + +### updateState + +Update state data + + +```solidity +function updateState(StateUpdateParams calldata params) external override; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`StateUpdateParams`|The struct containing state update parameters| + + +### setStateUpdateDelay + +Updates the state update delay. Can only be called by the owner. + + +```solidity +function setStateUpdateDelay(uint256 newStateUpdateDelay) external override onlyOwner; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`newStateUpdateDelay`|`uint256`|The new state update delay in seconds| + + +### updateVaultState + +Updates the vault state by harvesting rewards + + +```solidity +function updateVaultState(IKeeperRewards.HarvestParams calldata harvestParams) external override; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`harvestParams`|`IKeeperRewards.HarvestParams`|The parameters for harvesting Keeper rewards| + + +### updateOperatorState + +Updates the operator state by verifying a merkle proof against the current state root + + +```solidity +function updateOperatorState(address operator, OperatorStateUpdateParams calldata params) external override; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`operator`|`address`|The address of the operator to update| +|`params`|`OperatorStateUpdateParams`|The parameters for updating the operator state| + + +### registerValidators + +Registers validators with oracle-approved signatures. Can only be called by the operator's validators manager. + + +```solidity +function registerValidators( + address operator, + IKeeperValidators.ApprovalParams calldata keeperParams, + bytes calldata signatures +) external override; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`operator`|`address`|The address of the operator| +|`keeperParams`|`IKeeperValidators.ApprovalParams`|The keeper approval parameters containing validator data| +|`signatures`|`bytes`|The concatenation of the oracles' signatures| + + +### fundValidators + +Funds validators with oracle-approved signatures. Can only be called by the operator's validators manager. + + +```solidity +function fundValidators(address operator, bytes calldata validators, bytes calldata signatures) external override; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`operator`|`address`|The address of the operator| +|`validators`|`bytes`|The concatenation of the validators' data| +|`signatures`|`bytes`|The concatenation of the oracles' signatures approving the funding| + + +### withdrawValidators + +Submits validator withdrawals. Can only be called by the withdrawals manager. + + +```solidity +function withdrawValidators(bytes calldata validators) + external + payable + override + nonReentrant + onlyWithdrawalsManager; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`validators`|`bytes`|The concatenation of the validators' data| + + +### enterExitQueue + +Enters the exit queue by locking operator shares in the vault's exit queue + + +```solidity +function enterExitQueue(uint256 shares) external override nonReentrant returns (uint256 positionTicket); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The number of shares to lock in the exit queue| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`positionTicket`|`uint256`|The position ticket of the exit queue| + + +### claimExitedAssets + +Claims exited assets from the vault's exit queue for the operator + + +```solidity +function claimExitedAssets(uint256 positionTicket, uint256 timestamp, uint256 exitQueueIndex) + external + override + nonReentrant; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`positionTicket`|`uint256`|The exit queue ticket received after the `enterExitQueue` call| +|`timestamp`|`uint256`|The timestamp when the shares entered the exit queue| +|`exitQueueIndex`|`uint256`|The exit queue index at which the shares were burned| + + +### _deposit + +Internal function to deposit assets to the vault and update the operator's shares balance + + +```solidity +function _deposit(uint256 assets) internal returns (uint256 addedShares); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`assets`|`uint256`|The amount of assets to deposit| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`addedShares`|`uint256`|The amount of shares received with penalty applied if any| + + +### _setMinDepositAssets + +Internal function for updating the minimum deposit assets + + +```solidity +function _setMinDepositAssets(uint256 newMinDepositAssets) private; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`newMinDepositAssets`|`uint256`|The new minimum deposit assets| + + +### _setStateUpdateDelay + +Internal function for updating the state update delay + + +```solidity +function _setStateUpdateDelay(uint256 newStateUpdateDelay) private; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`newStateUpdateDelay`|`uint256`|The new state update delay in seconds| + + +### _setMinBalancePercent + +Internal function for updating the minimum balance percent + + +```solidity +function _setMinBalancePercent(uint16 newMinBalancePercent) private; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`newMinBalancePercent`|`uint16`|The new minimum balance percent| + + +### _useOperatorNonce + +Returns the current nonce for an operator and nonce type, then increments it + + +```solidity +function _useOperatorNonce(address operator, OperatorNonceType nonceType) private returns (uint256 nonce); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`operator`|`address`|The address of the operator| +|`nonceType`|`OperatorNonceType`|The type of nonce to use| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`nonce`|`uint256`|The current nonce before incrementing| + + +### _verifySignatures + +Verifies that oracles have approved the action by checking their signatures + + +```solidity +function _verifySignatures(bytes32 digest, bytes calldata signatures) private view; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`digest`|`bytes32`|The EIP-712 typed data hash to verify signatures against| +|`signatures`|`bytes`|The concatenation of the oracles' signatures| + + +### _getValidatorsPublicKeys + +Internal function to extract the validators' public keys from the concatenated validators data + + +```solidity +function _getValidatorsPublicKeys(bytes calldata validators) internal pure returns (bytes memory publicKeys); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`validators`|`bytes`|The concatenation of the validators' data| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`publicKeys`|`bytes`|The concatenation of the validators' public keys extracted from the validators data| + + +### _authorizeUpgrade + +Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by +`upgradeToAndCall`. +Normally, this function will use an access control modifier such as `Ownable-onlyOwner`. +```solidity +function _authorizeUpgrade(address) internal onlyOwner {} +``` + + +```solidity +function _authorizeUpgrade(address) internal override onlyOwner; +``` + +### _depositToVault + +Deposits assets to the vault and returns the shares received. +Must be implemented by network-specific contracts. + + +```solidity +function _depositToVault(uint256 assets) internal virtual returns (uint256 shares); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`assets`|`uint256`|The amount of assets to deposit| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The vault shares received| + + +### _transferAssets + +Transfers assets to the receiver. +Must be implemented by network-specific contracts. + + +```solidity +function _transferAssets(address receiver, uint256 assets) internal virtual; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address to transfer assets to| +|`assets`|`uint256`|The amount of assets to transfer| + + +### _donateAssets + +Donates assets back to the vault. +Must be implemented by network-specific contracts. + + +```solidity +function _donateAssets(uint256 assets) internal virtual; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`assets`|`uint256`|The amount of assets to donate| diff --git a/docs/contracts/api/nodes/_category_.json b/docs/contracts/api/nodes/_category_.json new file mode 100644 index 00000000..c0a4eeea --- /dev/null +++ b/docs/contracts/api/nodes/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Nodes", + "position": 9, + "collapsed": true +} diff --git a/docs/contracts/api/tokens/EthOsTokenRedeemer.md b/docs/contracts/api/tokens/EthOsTokenRedeemer.md index 847a15a2..3e632919 100644 --- a/docs/contracts/api/tokens/EthOsTokenRedeemer.md +++ b/docs/contracts/api/tokens/EthOsTokenRedeemer.md @@ -6,14 +6,40 @@ description: "Ethereum-specific contract for redeeming osTokens for ETH" # EthOsTokenRedeemer -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/tokens/EthOsTokenRedeemer.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/tokens/EthOsTokenRedeemer.sol) -**Inherits:** IEthOsTokenRedeemer, [ReentrancyGuard ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/ReentrancyGuard.sol), [OsTokenRedeemer →](./OsTokenRedeemer) +**Inherits:** IEthOsTokenRedeemer, [ReentrancyGuard ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/ReentrancyGuard.sol), [OsTokenRedeemer](./OsTokenRedeemer) This contract is used to redeem OsTokens for the underlying asset. ## Functions +### constructor + +Constructor + + +```solidity +constructor( + address vaultsRegistry_, + address osToken_, + address osTokenVaultController_, + address owner_, + uint256 exitQueueUpdateDelay_ +) + ReentrancyGuard() + OsTokenRedeemer(vaultsRegistry_, osToken_, osTokenVaultController_, owner_, exitQueueUpdateDelay_); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vaultsRegistry_`|`address`|The address of the VaultsRegistry contract| +|`osToken_`|`address`|The address of the OsToken contract| +|`osTokenVaultController_`|`address`|The address of the OsTokenVaultController contract| +|`owner_`|`address`|The address of the owner| +|`exitQueueUpdateDelay_`|`uint256`|The delay in seconds for exit queue updates| + ### swapAssetsToOsTokenShares @@ -36,6 +62,43 @@ function swapAssetsToOsTokenShares(address receiver) external payable override r |`osTokenShares`|`uint256`|The amount of OsToken shares received| +### _getAssets + +Internal function that must be implemented to return the account assets + + +```solidity +function _getAssets(address account) internal view override returns (uint256); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`account`|`address`|The address of the account| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The amount of assets in the vault| + + +### _transferAssets + +Internal function for transferring assets to the receiver + + +```solidity +function _transferAssets(address receiver, uint256 assets) internal override nonReentrant; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive the assets| +|`assets`|`uint256`|The number of assets to transfer| + + ### receive Function for receiving redeemed assets. diff --git a/docs/contracts/api/tokens/EthOsTokenVaultEscrow.md b/docs/contracts/api/tokens/EthOsTokenVaultEscrow.md index 0564cf20..cb962e69 100644 --- a/docs/contracts/api/tokens/EthOsTokenVaultEscrow.md +++ b/docs/contracts/api/tokens/EthOsTokenVaultEscrow.md @@ -6,32 +6,45 @@ description: "Contract for initiating assets exits from the vault without burnin # EthOsTokenVaultEscrow -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/tokens/EthOsTokenVaultEscrow.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/tokens/EthOsTokenVaultEscrow.sol) -**Inherits:** [ReentrancyGuard ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/ReentrancyGuard.sol), [OsTokenVaultEscrow →](./OsTokenVaultEscrow) +**Inherits:** [ReentrancyGuard ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/ReentrancyGuard.sol), [OsTokenVaultEscrow](./OsTokenVaultEscrow) -Used for initiating assets exits from the Vault without burning osToken on Ethereum. +Used for initiating assets exits from the vault without burning osToken on Ethereum -## Events -### AssetsReceived -Event emitted on assets received by the escrow +## Functions +### constructor + +Constructor ```solidity -event AssetsReceived(address indexed sender, uint256 value); +constructor( + address osTokenVaultController, + address osTokenConfig, + address initialOwner, + address _authenticator, + uint64 _liqThresholdPercent, + uint256 _liqBonusPercent +) + ReentrancyGuard() + OsTokenVaultEscrow( + osTokenVaultController, osTokenConfig, initialOwner, _authenticator, _liqThresholdPercent, _liqBonusPercent + ); ``` - **Parameters** |Name|Type|Description| |----|----|-----------| -|`sender`|`address`|The address of the sender| -|`value`|`uint256`|The amount of assets received| +|`osTokenVaultController`|`address`|The address of the OsTokenVaultController contract| +|`osTokenConfig`|`address`|The address of the OsTokenConfig contract| +|`initialOwner`|`address`|The address of the contract owner| +|`_authenticator`|`address`|The address of the OsTokenVaultEscrowAuth contract| +|`_liqThresholdPercent`|`uint64`|The liquidation threshold percent| +|`_liqBonusPercent`|`uint256`|The liquidation bonus percent| -## Functions - ### receive Function for receiving assets from the vault @@ -40,3 +53,35 @@ Function for receiving assets from the vault ```solidity receive() external payable; ``` + +### _transferAssets + +Internal function for transferring assets from the Vault to the receiver + + +```solidity +function _transferAssets(address receiver, uint256 assets) internal override nonReentrant; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive the assets| +|`assets`|`uint256`|The number of assets to transfer| + + +## Events +### AssetsReceived +Event emitted on assets received by the escrow + + +```solidity +event AssetsReceived(address indexed sender, uint256 value); +``` + +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`sender`|`address`|The address of the sender| +|`value`|`uint256`|The amount of assets received| diff --git a/docs/contracts/api/tokens/GnoOsTokenRedeemer.md b/docs/contracts/api/tokens/GnoOsTokenRedeemer.md index 46446ec0..3fa4651b 100644 --- a/docs/contracts/api/tokens/GnoOsTokenRedeemer.md +++ b/docs/contracts/api/tokens/GnoOsTokenRedeemer.md @@ -6,14 +6,48 @@ description: "Gnosis-specific contract for redeeming osTokens for GNO" # GnoOsTokenRedeemer -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/tokens/GnoOsTokenRedeemer.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/tokens/GnoOsTokenRedeemer.sol) -**Inherits:** IGnoOsTokenRedeemer, [OsTokenRedeemer →](./OsTokenRedeemer) +**Inherits:** IGnoOsTokenRedeemer, [OsTokenRedeemer](./OsTokenRedeemer) This contract is used to redeem OsTokens for the underlying asset. +## State Variables +### _gnoToken + +```solidity +IERC20 private immutable _gnoToken +``` + + ## Functions +### constructor + +Constructor + + +```solidity +constructor( + address gnoToken_, + address vaultsRegistry_, + address osToken_, + address osTokenVaultController_, + address owner_, + uint256 exitQueueUpdateDelay_ +) OsTokenRedeemer(vaultsRegistry_, osToken_, osTokenVaultController_, owner_, exitQueueUpdateDelay_); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`gnoToken_`|`address`|The address of the GNO token contract| +|`vaultsRegistry_`|`address`|The address of the VaultsRegistry contract| +|`osToken_`|`address`|The address of the OsToken contract| +|`osTokenVaultController_`|`address`|The address of the OsTokenVaultController contract| +|`owner_`|`address`|The address of the owner| +|`exitQueueUpdateDelay_`|`uint256`|The delay in seconds for exit queue updates| + ### permitGnoToken @@ -57,3 +91,40 @@ function swapAssetsToOsTokenShares(address receiver, uint256 assets) |Name|Type|Description| |----|----|-----------| |`osTokenShares`|`uint256`|The amount of OsToken shares received| + + +### _getAssets + +Internal function that must be implemented to return the account assets + + +```solidity +function _getAssets(address account) internal view override returns (uint256); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`account`|`address`|The address of the account| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The amount of assets in the vault| + + +### _transferAssets + +Internal function for transferring assets to the receiver + + +```solidity +function _transferAssets(address receiver, uint256 assets) internal override; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive the assets| +|`assets`|`uint256`|The number of assets to transfer| diff --git a/docs/contracts/api/tokens/GnoOsTokenVaultEscrow.md b/docs/contracts/api/tokens/GnoOsTokenVaultEscrow.md index 77b140a8..19da61d7 100644 --- a/docs/contracts/api/tokens/GnoOsTokenVaultEscrow.md +++ b/docs/contracts/api/tokens/GnoOsTokenVaultEscrow.md @@ -6,8 +6,65 @@ description: "Contract for initiating assets exits from the vault without burnin # GnoOsTokenVaultEscrow -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/tokens/GnoOsTokenVaultEscrow.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/tokens/GnoOsTokenVaultEscrow.sol) -**Inherits:** [OsTokenVaultEscrow →](./OsTokenVaultEscrow) +**Inherits:** [OsTokenVaultEscrow](./OsTokenVaultEscrow) -Used for initiating assets exits from the vault without burning osToken on Gnosis. +Used for initiating assets exits from the vault without burning osToken on Gnosis + + +## State Variables +### _gnoToken + +```solidity +IERC20 private immutable _gnoToken +``` + + +## Functions +### constructor + +Constructor + + +```solidity +constructor( + address osTokenVaultController, + address osTokenConfig, + address initialOwner, + address _authenticator, + uint64 _liqThresholdPercent, + uint256 _liqBonusPercent, + address gnoToken +) + OsTokenVaultEscrow( + osTokenVaultController, osTokenConfig, initialOwner, _authenticator, _liqThresholdPercent, _liqBonusPercent + ); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`osTokenVaultController`|`address`|The address of the OsTokenVaultController contract| +|`osTokenConfig`|`address`|The address of the OsTokenConfig contract| +|`initialOwner`|`address`|The address of the contract owner| +|`_authenticator`|`address`|The address of the OsTokenVaultEscrowAuth contract| +|`_liqThresholdPercent`|`uint64`|The liquidation threshold percent| +|`_liqBonusPercent`|`uint256`|The liquidation bonus percent| +|`gnoToken`|`address`|The address of the GNO token| + + +### _transferAssets + +Internal function for transferring assets from the Vault to the receiver + + +```solidity +function _transferAssets(address receiver, uint256 assets) internal override; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive the assets| +|`assets`|`uint256`|The number of assets to transfer| diff --git a/docs/contracts/api/tokens/OsToken.md b/docs/contracts/api/tokens/OsToken.md index d27c3361..98466c30 100644 --- a/docs/contracts/api/tokens/OsToken.md +++ b/docs/contracts/api/tokens/OsToken.md @@ -6,51 +6,48 @@ description: "Over-collateralized staked token with controller-based minting and # OsToken -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/tokens/OsToken.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/tokens/OsToken.sol) **Inherits:** [Ownable2Step ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol), [ERC20Permit ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC20Permit.sol), IOsToken -OsToken is an over-collateralized staked token. +OsToken is an over-collateralized staked token -## Events -### ControllerUpdated -Emitted when a controller is updated - +## State Variables +### _vaultController ```solidity -event ControllerUpdated(address indexed controller, bool registered); +address private immutable _vaultController ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`controller`|`address`|The address of the controller| -|`registered`|`bool`|Whether the controller is registered or not| +### controllers + +```solidity +mapping(address controller => bool enabled) public override controllers +``` ## Functions +### constructor -### controllers - -Returns whether controller is registered or not +Constructor ```solidity -function controllers(address controller) external view returns (bool); +constructor(address _owner, address vaultController, string memory _name, string memory _symbol) + ERC20(_name, _symbol) + ERC20Permit(_name) + Ownable(_owner); ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`controller`|`address`|The address of the controller| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`bool`|Whether the controller is registered or not| +|`_owner`|`address`|The address of the contract owner| +|`vaultController`|`address`|The address of the OsTokenVaultController contract| +|`_name`|`string`|The name of the ERC20 token| +|`_symbol`|`string`|The symbol of the ERC20 token| ### onlyController diff --git a/docs/contracts/api/tokens/OsTokenConfig.md b/docs/contracts/api/tokens/OsTokenConfig.md index 0fba8bf0..279f5aae 100644 --- a/docs/contracts/api/tokens/OsTokenConfig.md +++ b/docs/contracts/api/tokens/OsTokenConfig.md @@ -6,82 +6,67 @@ description: "Configuration contract for OsToken minting and liquidation paramet # OsTokenConfig -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/tokens/OsTokenConfig.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/tokens/OsTokenConfig.sol) **Inherits:** [Ownable2Step ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol), IOsTokenConfig -Configuration for minting and liquidating OsToken shares. +Configuration for minting and liquidating OsToken shares -## Events -### OsTokenConfigUpdated -Emitted when OsToken minting and liquidating configuration values are updated - +## State Variables +### _maxPercent ```solidity -event OsTokenConfigUpdated(address vault, uint128 liqBonusPercent, uint64 liqThresholdPercent, uint64 ltvPercent); +uint256 private constant _maxPercent = 1e18 ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`vault`|`address`|The address of the vault to update the config for. Will be zero address if it is a default config.| -|`liqBonusPercent`|`uint128`|The new liquidation bonus percent value| -|`liqThresholdPercent`|`uint64`|The new liquidation threshold percent value| -|`ltvPercent`|`uint64`|The new loan-to-value (LTV) percent value| - -### RedeemerUpdated -Emitted when the OsToken redeemer address is updated +### _disabledLiqThreshold ```solidity -event RedeemerUpdated(address newRedeemer); +uint256 private constant _disabledLiqThreshold = type(uint64).max ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`newRedeemer`|`address`|The address of the new redeemer| +### redeemer +The address of the OsToken redeemer + -## Structs -### Config -The OsToken minting and liquidating configuration values +```solidity +address public override redeemer +``` +### _defaultConfig + ```solidity -struct Config { - uint128 liqBonusPercent; - uint64 liqThresholdPercent; - uint64 ltvPercent; -} +Config private _defaultConfig ``` -**Properties** -|Name|Type|Description| -|----|----|-----------| -|`liqBonusPercent`|`uint128`|The minimal bonus percent that liquidator earns on OsToken position liquidation| -|`liqThresholdPercent`|`uint64`|The liquidation threshold percent used to calculate health factor for OsToken position| -|`ltvPercent`|`uint64`|The percent used to calculate how much user can mint OsToken shares| +### _vaultConfigs +```solidity +mapping(address vault => Config config) private _vaultConfigs +``` -## Functions -### redeemer +## Functions +### constructor -The address of the OsToken redeemer +Constructor ```solidity -function redeemer() external view returns (address); +constructor(address _owner, Config memory defaultConfig, address _redeemer) Ownable(msg.sender); ``` -**Returns** +**Parameters** |Name|Type|Description| |----|----|-----------| -|``|`address`|The address of the redeemer| +|`_owner`|`address`|The address of the contract owner| +|`defaultConfig`|`Config`|The OsToken default configuration| +|`_redeemer`|`address`|The address of the redeemer| ### getConfig diff --git a/docs/contracts/api/tokens/OsTokenFlashLoans.md b/docs/contracts/api/tokens/OsTokenFlashLoans.md index 4c0928a2..df3643fd 100644 --- a/docs/contracts/api/tokens/OsTokenFlashLoans.md +++ b/docs/contracts/api/tokens/OsTokenFlashLoans.md @@ -6,31 +6,43 @@ description: "Flash loan contract for borrowing up to 100,000 osToken shares in # OsTokenFlashLoans -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/tokens/OsTokenFlashLoans.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/tokens/OsTokenFlashLoans.sol) **Inherits:** [ReentrancyGuard ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/ReentrancyGuard.sol), IOsTokenFlashLoans Mint and burn up to 100 000 osToken shares in single transaction. -## Events -### OsTokenFlashLoan -Event emitted on flash loan +## State Variables +### _maxFlashLoanAmount +```solidity +uint256 private constant _maxFlashLoanAmount = 100_000 ether +``` + + +### _osToken ```solidity -event OsTokenFlashLoan(address indexed caller, uint256 amount); +address private immutable _osToken ``` + +## Functions +### constructor + +Constructor + + +```solidity +constructor(address osToken) ReentrancyGuard(); +``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`caller`|`address`|The address of the caller| -|`amount`|`uint256`|The flashLoan osToken shares amount| - +|`osToken`|`address`|The address of the OsToken contract| -## Functions ### flashLoan diff --git a/docs/contracts/api/tokens/OsTokenRedeemer.md b/docs/contracts/api/tokens/OsTokenRedeemer.md index 9c7aab3a..f733b812 100644 --- a/docs/contracts/api/tokens/OsTokenRedeemer.md +++ b/docs/contracts/api/tokens/OsTokenRedeemer.md @@ -6,651 +6,576 @@ description: "Abstract contract for redeeming osTokens through exit queue or dir # OsTokenRedeemer -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/tokens/OsTokenRedeemer.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/tokens/OsTokenRedeemer.sol) -**Inherits:** [Ownable2Step ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol), [Multicall →](../base/Multicall), IOsTokenRedeemer +**Inherits:** [Ownable2Step ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol), [Multicall](../base/Multicall), IOsTokenRedeemer This contract is used to redeem OsTokens for the underlying asset. -## Events -### PositionsManagerUpdated -Event emitted when the positions manager is updated - +## State Variables +### _vaultsRegistry ```solidity -event PositionsManagerUpdated(address indexed positionsManager); +IVaultsRegistry private immutable _vaultsRegistry ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`positionsManager`|`address`|The address of the new positions manager| - -### RedeemablePositionsProposed -Event emitted when new redeemable positions are proposed +### _osToken ```solidity -event RedeemablePositionsProposed(bytes32 indexed merkleRoot, string ipfsHash); +IERC20 private immutable _osToken ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`merkleRoot`|`bytes32`|The Merkle root of the redeemable positions| -|`ipfsHash`|`string`|The IPFS hash of the redeemable positions| - -### RedeemablePositionsAccepted -Event emitted when the pending redeemable positions are accepted +### _osTokenVaultController ```solidity -event RedeemablePositionsAccepted(bytes32 indexed merkleRoot, string ipfsHash); +IOsTokenVaultController private immutable _osTokenVaultController ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`merkleRoot`|`bytes32`|The Merkle root of the accepted redeemable positions| -|`ipfsHash`|`string`|The IPFS hash of the accepted redeemable positions| - -### RedeemablePositionsDenied -Event emitted when the new redeemable positions are denied +### exitQueueUpdateDelay +The delay in seconds for the exit queue updates ```solidity -event RedeemablePositionsDenied(bytes32 indexed merkleRoot, string ipfsHash); +uint256 public immutable override exitQueueUpdateDelay ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`merkleRoot`|`bytes32`|The Merkle root of the denied redeemable positions| -|`ipfsHash`|`string`|The IPFS hash of the denied redeemable positions| - -### RedeemablePositionsRemoved -Event emitted when the redeemable positions are removed +### positionsManager +The address authorized to redeem OsToken positions ```solidity -event RedeemablePositionsRemoved(bytes32 indexed merkleRoot, string ipfsHash); +address public override positionsManager ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`merkleRoot`|`bytes32`|The Merkle root of the removed redeemable positions| -|`ipfsHash`|`string`|The IPFS hash of the removed redeemable positions| - -### ExitQueueEntered -Event emitted on shares added to the exit queue +### nonce +The current nonce for the redemptions ```solidity -event ExitQueueEntered(address indexed owner, address indexed receiver, uint256 positionTicket, uint256 shares); +uint256 public override nonce ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`owner`|`address`|The address that owns the shares| -|`receiver`|`address`|The address that will receive withdrawn assets| -|`positionTicket`|`uint256`|The exit queue ticket that was assigned to the position| -|`shares`|`uint256`|The number of shares that queued for the exit| -### ExitedAssetsClaimed -Event emitted on claim of the exited assets +### queuedShares +The number of queued OsToken shares ```solidity -event ExitedAssetsClaimed( - address indexed receiver, uint256 prevPositionTicket, uint256 newPositionTicket, uint256 withdrawnAssets -); +uint128 public override queuedShares ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`receiver`|`address`|The address that has received withdrawn assets| -|`prevPositionTicket`|`uint256`|The exit queue ticket received after the `enterExitQueue` call| -|`newPositionTicket`|`uint256`|The new exit queue ticket in case not all the shares were withdrawn. Otherwise 0.| -|`withdrawnAssets`|`uint256`|The total number of assets withdrawn| - -### OsTokenSharesSwapped -Event emitted on shares swapped for assets +### unclaimedAssets +The number of unclaimed assets in the exit queue ```solidity -event OsTokenSharesSwapped(address indexed sender, address indexed receiver, uint256 shares, uint256 assets); +uint128 public override unclaimedAssets ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`sender`|`address`|The address that initiated the swap| -|`receiver`|`address`|The address that will receive the shares| -|`shares`|`uint256`|The number of shares received| -|`assets`|`uint256`|The number of assets spent| -### CheckpointCreated -Event emitted on checkpoint creation +### redeemedShares +The number of redeemed OsToken shares ```solidity -event CheckpointCreated(uint256 shares, uint256 assets); +uint128 public override redeemedShares ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`shares`|`uint256`|The number of burned shares| -|`assets`|`uint256`|The amount of exited assets| - -### OsTokenPositionsRedeemed -Event emitted when OsToken positions are redeemed +### redeemedAssets +The number of redeemed assets ```solidity -event OsTokenPositionsRedeemed(uint256 shares, uint256 assets); +uint128 public override redeemedAssets ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`shares`|`uint256`|The number of shares redeemed| -|`assets`|`uint256`|The number of assets redeemed| - -## Structs -### RedeemablePositions -Struct to store the redeemable positions Merkle root and IPFS hash +### swappedShares +The number of swapped OsToken shares ```solidity -struct RedeemablePositions { - bytes32 merkleRoot; - string ipfsHash; -} +uint128 public override swappedShares ``` -**Properties** -|Name|Type|Description| -|----|----|-----------| -|`merkleRoot`|`bytes32`|The Merkle root of the redeemable positions| -|`ipfsHash`|`string`|The IPFS hash of the redeemable positions| - -### OsTokenPosition -Struct to store the redeemed OsToken position details +### swappedAssets +The number of swapped assets ```solidity -struct OsTokenPosition { - address vault; - address owner; - uint256 leafShares; - uint256 sharesToRedeem; -} +uint128 public override swappedAssets ``` -**Properties** - -|Name|Type|Description| -|----|----|-----------| -|`vault`|`address`|The address of the Vault| -|`owner`|`address`|The address of the position owner| -|`leafShares`|`uint256`|The amount of OsToken shares used to calculate the merkle leaf| -|`sharesToRedeem`|`uint256`|| - -## Functions +### leafToProcessedShares -### exitQueueUpdateDelay +```solidity +mapping(bytes32 leaf => uint256 processedShares) public override leafToProcessedShares +``` -The delay in seconds for the exit queue updates +### exitRequests ```solidity -function exitQueueUpdateDelay() external view returns (uint256); +mapping(bytes32 exitRequestHash => uint256 shares) public override exitRequests ``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`uint256`|The delay in seconds| -### queuedShares - -The number of queued OsToken shares +### exitQueueTimestamp +The timestamp when the exit queue was last updated ```solidity -function queuedShares() external view returns (uint128); +uint256 public override exitQueueTimestamp ``` -**Returns** -|Name|Type|Description| -|----|----|-----------| -|``|`uint128`|The number of queued shares| +### _redeemablePositions -### unclaimedAssets +```solidity +RedeemablePositions private _redeemablePositions +``` -The number of unclaimed assets in the exit queue +### _exitQueue ```solidity -function unclaimedAssets() external view returns (uint128); +ExitQueue.History private _exitQueue ``` -**Returns** -|Name|Type|Description| -|----|----|-----------| -|``|`uint128`|The number of unclaimed assets| +## Functions +### constructor -### redeemedShares - -The number of redeemed OsToken shares +Constructor ```solidity -function redeemedShares() external view returns (uint128); +constructor( + address vaultsRegistry_, + address osToken_, + address osTokenVaultController_, + address owner_, + uint256 exitQueueUpdateDelay_ +) Ownable(owner_); ``` -**Returns** +**Parameters** |Name|Type|Description| |----|----|-----------| -|``|`uint128`|The number of redeemed shares| +|`vaultsRegistry_`|`address`|The address of the VaultsRegistry contract| +|`osToken_`|`address`|The address of the OsToken contract| +|`osTokenVaultController_`|`address`|The address of the OsTokenVaultController contract| +|`owner_`|`address`|The address of the owner| +|`exitQueueUpdateDelay_`|`uint256`|The delay in seconds for exit queue updates| -### redeemedAssets +### getExitQueueData -The number of redeemed assets +Get the current exit queue data ```solidity -function redeemedAssets() external view returns (uint128); +function getExitQueueData() public view override returns (uint256, uint256, uint256); ``` **Returns** |Name|Type|Description| |----|----|-----------| -|``|`uint128`|The number of redeemed assets| +|``|`uint256`|queuedShares The total number of shares currently queued for exit| +|``|`uint256`|unclaimedAssets The total number of assets that have not been claimed yet| +|``|`uint256`|totalTickets The total number of tickets (shares) processed in the exit queue| -### swappedShares +### getExitQueueCumulativeTickets -The number of swapped OsToken shares +Gets the cumulative tickets in the exit queue ```solidity -function swappedShares() external view returns (uint128); +function getExitQueueCumulativeTickets() external view override returns (uint256); ``` **Returns** |Name|Type|Description| |----|----|-----------| -|``|`uint128`|The number of swapped shares| +|``|`uint256`|The cumulative tickets in the exit queue| -### swappedAssets +### redeemablePositions -The number of swapped assets +The current redeemable positions Merkle root and IPFS hash ```solidity -function swappedAssets() external view returns (uint128); +function redeemablePositions() external view override returns (bytes32 merkleRoot, string memory ipfsHash); ``` **Returns** |Name|Type|Description| |----|----|-----------| -|``|`uint128`|The number of swapped assets| +|`merkleRoot`|`bytes32`|The Merkle root of the redeemable positions| +|`ipfsHash`|`string`|The IPFS hash of the redeemable positions| -### leafToProcessedShares +### getExitQueueIndex -Maps a Merkle tree leaf to processed shares +Gets the index of the exit queue for a given position ticket. ```solidity -function leafToProcessedShares(bytes32 leaf) external view returns (uint256 processedShares); +function getExitQueueIndex(uint256 positionTicket) external view override returns (int256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`leaf`|`bytes32`|The leaf of the Merkle tree| +|`positionTicket`|`uint256`|The position ticket to search for| **Returns** |Name|Type|Description| |----|----|-----------| -|`processedShares`|`uint256`|The number of processed shares corresponding to the leaf| +|``|`int256`|The index of the exit queue or -1 if not found| -### exitRequests +### canProcessExitQueue -Maps a exit request hash to the number of exiting shares +Checks if the exit queue can be processed ```solidity -function exitRequests(bytes32 exitRequestHash) external view returns (uint256 shares); +function canProcessExitQueue() external view override returns (bool); ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`exitRequestHash`|`bytes32`|The hash of the exit request| - **Returns** |Name|Type|Description| |----|----|-----------| -|`shares`|`uint256`|The number of shares that are exiting for the given exit request hash| +|``|`bool`|True if the exit queue can be processed, false otherwise| -### exitQueueTimestamp +### calculateExitedAssets -The timestamp when the exit queue was last updated +Calculates the exited assets for a given position ticket and exit queue index. ```solidity -function exitQueueTimestamp() external view returns (uint256); +function calculateExitedAssets(address receiver, uint256 positionTicket, uint256 exitQueueIndex) + public + view + override + returns (uint256 leftTickets, uint256 exitedTickets, uint256 exitedAssets); ``` -**Returns** +**Parameters** |Name|Type|Description| |----|----|-----------| -|``|`uint256`|The timestamp of the last exit queue update| - - -### positionsManager - -The address that can propose redeemable OsToken positions - +|`receiver`|`address`|The address of the receiver| +|`positionTicket`|`uint256`|The position ticket to calculate exited assets for| +|`exitQueueIndex`|`uint256`|The index of the exit queue to calculate exited assets for| -```solidity -function positionsManager() external view returns (address); -``` **Returns** |Name|Type|Description| |----|----|-----------| -|``|`address`|The address of the positions manager| +|`leftTickets`|`uint256`|The number of tickets left in the exit queue| +|`exitedTickets`|`uint256`|The number of tickets that have exited| +|`exitedAssets`|`uint256`|The number of assets that have exited| -### nonce +### setPositionsManager -The current nonce for the redemptions +Update the address of the positions manager. Can only be called by the owner. ```solidity -function nonce() external view returns (uint256); +function setPositionsManager(address positionsManager_) external override onlyOwner; ``` -**Returns** +**Parameters** |Name|Type|Description| |----|----|-----------| -|``|`uint256`|The current nonce value| +|`positionsManager_`|`address`|The address of the new positions manager| -### getExitQueueData +### getExitQueueMissingAssets -Get the current exit queue data +Calculates the missing assets in the exit queue for a target cumulative tickets. ```solidity -function getExitQueueData() external view override returns (uint256, uint256, uint256); +function getExitQueueMissingAssets(uint256 targetCumulativeTickets) + external + view + override + returns (uint256 missingAssets); ``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`targetCumulativeTickets`|`uint256`|The target cumulative tickets in the exit queue| + **Returns** |Name|Type|Description| |----|----|-----------| -|``|`uint256`|queuedShares The total number of shares currently queued for exit| -|``|`uint256`|unclaimedAssets The total number of assets that have not been claimed yet| -|``|`uint256`|totalTickets The total number of tickets (shares) processed in the exit queue| +|`missingAssets`|`uint256`|The number of missing assets in the exit queue| -### redeemablePositions +### setRedeemablePositions -The current redeemable positions Merkle root and IPFS hash +Set new redeemable positions. Can only be called by the owner. ```solidity -function redeemablePositions() external view override returns (bytes32 merkleRoot, string memory ipfsHash); +function setRedeemablePositions(RedeemablePositions calldata newPositions) external override onlyOwner; ``` -**Returns** +**Parameters** |Name|Type|Description| |----|----|-----------| -|`merkleRoot`|`bytes32`|The Merkle root of the redeemable positions| -|`ipfsHash`|`string`|The IPFS hash of the redeemable positions| +|`newPositions`|`RedeemablePositions`|The new redeemable positions| -### pendingRedeemablePositions +### permitOsToken -The pending redeemable positions Merkle root and IPFS hash that is waiting to be accepted +Permit OsToken shares to be used for redemption. ```solidity -function pendingRedeemablePositions() external view override returns (bytes32 merkleRoot, string memory ipfsHash); +function permitOsToken(uint256 shares, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external override; ``` -**Returns** +**Parameters** |Name|Type|Description| |----|----|-----------| -|`merkleRoot`|`bytes32`|The Merkle root of the pending redeemable positions| -|`ipfsHash`|`string`|The IPFS hash of the pending redeemable positions| +|`shares`|`uint256`|The number of shares to permit| +|`deadline`|`uint256`|The deadline for the permit| +|`v`|`uint8`|The recovery byte of the signature| +|`r`|`bytes32`|The output of the ECDSA signature| +|`s`|`bytes32`|The output of the ECDSA signature| -### getExitQueueIndex +### enterExitQueue -Gets the index of the exit queue for a given position ticket. +Enters the exit queue with a given number of shares and receiver address. ```solidity -function getExitQueueIndex(uint256 positionTicket) external view override returns (int256); +function enterExitQueue(uint256 shares, address receiver) external override returns (uint256 positionTicket); ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`positionTicket`|`uint256`|The position ticket to search for| +|`shares`|`uint256`|The number of shares to enter the exit queue with| +|`receiver`|`address`|The address that will receive the assets after exit| **Returns** |Name|Type|Description| |----|----|-----------| -|``|`int256`|The index of the exit queue or -1 if not found| +|`positionTicket`|`uint256`|The position ticket for the entered exit queue| -### canProcessExitQueue +### claimExitedAssets -Checks if the exit queue can be processed +Claims exited assets for a given position ticket and exit queue index. ```solidity -function canProcessExitQueue() external view override returns (bool); +function claimExitedAssets(uint256 positionTicket, uint256 exitQueueIndex) external override; ``` -**Returns** +**Parameters** |Name|Type|Description| |----|----|-----------| -|``|`bool`|True if the exit queue can be processed, false otherwise| +|`positionTicket`|`uint256`|The position ticket to claim exited assets for| +|`exitQueueIndex`|`uint256`|The index of the exit queue to claim exited assets for| -### calculateExitedAssets +### redeemSubVaultsAssets -Calculates the exited assets for a given position ticket and exit queue index. +Redeem assets from the sub-vaults to the meta vault. Can only be called by the positions manager. ```solidity -function calculateExitedAssets(address receiver, uint256 positionTicket, uint256 exitQueueIndex) - public - view +function redeemSubVaultsAssets(address metaVault, uint256 assetsToRedeem) + external override - returns (uint256 leftTickets, uint256 exitedTickets, uint256 exitedAssets); + returns (uint256 totalRedeemedAssets); ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`receiver`|`address`|The address of the receiver| -|`positionTicket`|`uint256`|The position ticket to calculate exited assets for| -|`exitQueueIndex`|`uint256`|The index of the exit queue to calculate exited assets for| +|`metaVault`|`address`|The address of the meta vault| +|`assetsToRedeem`|`uint256`|The number of assets to redeem| **Returns** |Name|Type|Description| |----|----|-----------| -|`leftTickets`|`uint256`|The number of tickets left in the exit queue| -|`exitedTickets`|`uint256`|The number of tickets that have exited| -|`exitedAssets`|`uint256`|The number of assets that have exited| +|`totalRedeemedAssets`|`uint256`|The total number of redeemed assets| -### setPositionsManager +### redeemSubVaultOsToken -Update the address of the positions manager. Can only be called by the owner. +Redeem OsToken shares from a specific sub-vault. Can only be called by the meta vault. ```solidity -function setPositionsManager(address positionsManager_) external override onlyOwner; +function redeemSubVaultOsToken(address subVault, uint256 osTokenShares) external override returns (uint256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`positionsManager_`|`address`|The address of the new positions manager| +|`subVault`|`address`|The address of the sub-vault| +|`osTokenShares`|`uint256`|The number of OsToken shares to redeem| +**Returns** -### proposeRedeemablePositions +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The amount of redeemed assets| -Proposes new redeemable positions. Can only be called by the positions manager. + +### redeemOsTokenPositions + +Redeem OsToken shares from the vault positions. ```solidity -function proposeRedeemablePositions(RedeemablePositions calldata newPositions) external override; +function redeemOsTokenPositions( + OsTokenPosition[] memory positions, + bytes32[] calldata proof, + bool[] calldata proofFlags +) external override; ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`newPositions`|`RedeemablePositions`|The new redeemable positions to propose| +|`positions`|`OsTokenPosition[]`|The array of OsToken positions to redeem| +|`proof`|`bytes32[]`|The Merkle proof for the positions root| +|`proofFlags`|`bool[]`|The flags for the Merkle proof| -### acceptRedeemablePositions +### processExitQueue -Accepts the pending redeemable positions. Can only be called by the owner. +Process the exit queue and checkpoint swapped or redeemed shares. Can only be called once per `exitQueueUpdateDelay`. ```solidity -function acceptRedeemablePositions() external override onlyOwner; +function processExitQueue() external override; ``` -### denyRedeemablePositions +### updateVaultState -Denies the pending redeemable positions. Can only be called by the owner. +Updates the vault state. To be used in multicall to update state and redeem positions. ```solidity -function denyRedeemablePositions() external override onlyOwner; +function updateVaultState(address vault, IKeeperRewards.HarvestParams calldata harvestParams) external override; ``` +**Parameters** -### removeRedeemablePositions - -Removes the redeemable positions. Can only be called by the owner. - +|Name|Type|Description| +|----|----|-----------| +|`vault`|`address`|The address of the vault to update| +|`harvestParams`|`IKeeperRewards.HarvestParams`|The harvest parameters for the vault state update| -```solidity -function removeRedeemablePositions() external override onlyOwner; -``` -### permitOsToken +### _swapAssetsToOsTokenShares -Permit OsToken shares to be used for redemption. +Internal function to swap assets to OsToken shares ```solidity -function permitOsToken(uint256 shares, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external override; +function _swapAssetsToOsTokenShares(address receiver, uint256 assets) internal returns (uint256 osTokenShares); ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`shares`|`uint256`|The number of shares to permit| -|`deadline`|`uint256`|The deadline for the permit| -|`v`|`uint8`|The recovery byte of the signature| -|`r`|`bytes32`|The output of the ECDSA signature| -|`s`|`bytes32`|The output of the ECDSA signature| +|`receiver`|`address`|The address that will receive the OsToken shares| +|`assets`|`uint256`|The number of assets to swap| +**Returns** -### enterExitQueue +|Name|Type|Description| +|----|----|-----------| +|`osTokenShares`|`uint256`|The number of OsToken shares swapped| -Enters the exit queue with a given number of shares and receiver address. + +### _isMetaVault + +Internal function to check whether the caller is a meta vault ```solidity -function enterExitQueue(uint256 shares, address receiver) external override returns (uint256 positionTicket); +function _isMetaVault(address vault) private view returns (bool); ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`shares`|`uint256`|The number of shares to enter the exit queue with| -|`receiver`|`address`|The address that will receive the assets after exit| +|`vault`|`address`|The address of the vault to check| **Returns** |Name|Type|Description| |----|----|-----------| -|`positionTicket`|`uint256`|The position ticket for the entered exit queue| +|``|`bool`|True if the caller is a meta vault, false otherwise| -### claimExitedAssets +### _getAssets -Claims exited assets for a given position ticket and exit queue index. +Internal function that must be implemented to return the account assets ```solidity -function claimExitedAssets(uint256 positionTicket, uint256 exitQueueIndex) external override; +function _getAssets(address account) internal view virtual returns (uint256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`positionTicket`|`uint256`|The position ticket to claim exited assets for| -|`exitQueueIndex`|`uint256`|The index of the exit queue to claim exited assets for| +|`account`|`address`|The address of the account| +**Returns** -### redeemOsTokenPositions +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The amount of assets in the vault| -```solidity -function redeemOsTokenPositions( - OsTokenPosition[] memory positions, - bytes32[] calldata proof, - bool[] calldata proofFlags -) external override; -``` +### _transferAssets -### processExitQueue +Internal function for transferring assets to the receiver -Process the exit queue and checkpoint swapped or redeemed shares. Can only be called once per `exitQueueUpdateDelay`. +IMPORTANT: because control is transferred to the receiver, care must be +taken to not create reentrancy vulnerabilities. The Vault must follow the checks-effects-interactions pattern: +https://docs.soliditylang.org/en/v0.8.22/security-considerations.html#use-the-checks-effects-interactions-pattern ```solidity -function processExitQueue() external override; +function _transferAssets(address receiver, uint256 assets) internal virtual; ``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive the assets| +|`assets`|`uint256`|The number of assets to transfer| diff --git a/docs/contracts/api/tokens/OsTokenVaultController.md b/docs/contracts/api/tokens/OsTokenVaultController.md index 6957201c..78e58131 100644 --- a/docs/contracts/api/tokens/OsTokenVaultController.md +++ b/docs/contracts/api/tokens/OsTokenVaultController.md @@ -6,225 +6,143 @@ description: "Controller contract for managing osToken minting, burning, and rew # OsTokenVaultController -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/tokens/OsTokenVaultController.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/tokens/OsTokenVaultController.sol) **Inherits:** [Ownable2Step ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol), IOsTokenVaultController -Over-collateralized staked token controller. +Over-collateralized staked token controller -## Events -### Mint -Event emitted on minting shares - - -```solidity -event Mint(address indexed vault, address indexed receiver, uint256 assets, uint256 shares); -``` - -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`vault`|`address`|The address of the Vault| -|`receiver`|`address`|The address that received the shares| -|`assets`|`uint256`|The number of assets collateralized| -|`shares`|`uint256`|The number of tokens the owner received| - -### Burn -Event emitted on burning shares - - -```solidity -event Burn(address indexed vault, address indexed owner, uint256 assets, uint256 shares); -``` - -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`vault`|`address`|The address of the Vault| -|`owner`|`address`|The address that owns the shares| -|`assets`|`uint256`|The total number of assets withdrawn| -|`shares`|`uint256`|The total number of shares burned| - -### StateUpdated -Event emitted on state update - +## State Variables +### _wad ```solidity -event StateUpdated(uint256 profitAccrued, uint256 treasuryShares, uint256 treasuryAssets); +uint256 private constant _wad = 1e18 ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`profitAccrued`|`uint256`|The profit accrued since the last update| -|`treasuryShares`|`uint256`|The number of shares minted for the treasury| -|`treasuryAssets`|`uint256`|The number of assets minted for the treasury| - -### CapacityUpdated -Event emitted on capacity update +### _maxFeePercent ```solidity -event CapacityUpdated(uint256 capacity); +uint256 private constant _maxFeePercent = 10_000 ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`capacity`|`uint256`|The amount after which the OsToken stops accepting deposits| - -### TreasuryUpdated -Event emitted on treasury address update +### _registry ```solidity -event TreasuryUpdated(address indexed treasury); +address private immutable _registry ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`treasury`|`address`|The new treasury address| - -### FeePercentUpdated -Event emitted on fee percent update +### _osToken ```solidity -event FeePercentUpdated(uint16 feePercent); +address private immutable _osToken ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`feePercent`|`uint16`|The new fee percent| -### AvgRewardPerSecondUpdated -Event emitted on average reward per second update +### keeper +The address that can update avgRewardPerSecond ```solidity -event AvgRewardPerSecondUpdated(uint256 avgRewardPerSecond); +address public override keeper ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`avgRewardPerSecond`|`uint256`|The new average reward per second| -### KeeperUpdated -Event emitted on keeper address update +### capacity +The OsToken capacity ```solidity -event KeeperUpdated(address keeper); +uint256 public override capacity ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`keeper`|`address`|The new keeper address| - - -## Functions - -### capacity -The OsToken capacity +### avgRewardPerSecond +The average reward per second used to mint OsToken rewards ```solidity -function capacity() external view returns (uint256); +uint256 public override avgRewardPerSecond ``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`uint256`|The amount after which the OsToken stops accepting deposits| ### treasury - The DAO treasury address that receives OsToken fees ```solidity -function treasury() external view returns (address); +address public override treasury ``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`address`|The address of the treasury| ### feePercent - The fee percent (multiplied by 100) ```solidity -function feePercent() external view returns (uint64); +uint64 public override feePercent ``` -**Returns** -|Name|Type|Description| -|----|----|-----------| -|``|`uint64`|The fee percent applied by the OsToken on the rewards| +### _cumulativeFeePerShare -### keeper +```solidity +uint192 private _cumulativeFeePerShare = uint192(_wad) +``` -The address that can update avgRewardPerSecond +### _lastUpdateTimestamp ```solidity -function keeper() external view returns (address); +uint64 private _lastUpdateTimestamp ``` -**Returns** -|Name|Type|Description| -|----|----|-----------| -|``|`address`|The address of the keeper contract| +### _totalShares -### avgRewardPerSecond +```solidity +uint128 private _totalShares +``` -The average reward per second used to mint OsToken rewards +### _totalAssets ```solidity -function avgRewardPerSecond() external view returns (uint256); +uint128 private _totalAssets ``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`uint256`|The average reward per second earned by the Vaults| -### cumulativeFeePerShare +## Functions +### constructor -The fee per share used for calculating the fee for every position +Constructor ```solidity -function cumulativeFeePerShare() external view returns (uint256); +constructor( + address _keeper, + address registry, + address osToken, + address _treasury, + address _owner, + uint16 _feePercent, + uint256 _capacity +) Ownable(msg.sender); ``` -**Returns** +**Parameters** |Name|Type|Description| |----|----|-----------| -|``|`uint256`|The cumulative fee per share| +|`_keeper`|`address`|The address of the Keeper contract| +|`registry`|`address`|The address of the VaultsRegistry contract| +|`osToken`|`address`|The address of the OsToken contract| +|`_treasury`|`address`|The address of the DAO treasury| +|`_owner`|`address`|The address of the owner of the contract| +|`_feePercent`|`uint16`|The fee percent applied on the rewards| +|`_capacity`|`uint256`|The amount after which the osToken stops accepting deposits| ### totalShares @@ -418,6 +336,21 @@ function setKeeper(address _keeper) external override onlyOwner; |`_keeper`|`address`|The new keeper address| +### cumulativeFeePerShare + +The fee per share used for calculating the fee for every position + + +```solidity +function cumulativeFeePerShare() external view override returns (uint256); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The cumulative fee per share| + + ### updateState Updates rewards and treasury fee checkpoint for the OsToken @@ -426,3 +359,36 @@ Updates rewards and treasury fee checkpoint for the OsToken ```solidity function updateState() public override; ``` + +### _convertToShares + +Internal conversion function (from assets to shares) with support for rounding direction. + + +```solidity +function _convertToShares(uint256 assets, uint256 totalShares_, uint256 totalAssets_, Math.Rounding rounding) + internal + pure + returns (uint256 shares); +``` + +### _convertToAssets + +Internal conversion function (from shares to assets) with support for rounding direction. + + +```solidity +function _convertToAssets(uint256 shares, uint256 totalShares_, uint256 totalAssets_, Math.Rounding rounding) + internal + pure + returns (uint256); +``` + +### _unclaimedAssets + +Internal function for calculating assets accumulated since last update + + +```solidity +function _unclaimedAssets() internal view returns (uint256); +``` diff --git a/docs/contracts/api/tokens/OsTokenVaultEscrow.md b/docs/contracts/api/tokens/OsTokenVaultEscrow.md index 6ca3ce1c..60a1eb2d 100644 --- a/docs/contracts/api/tokens/OsTokenVaultEscrow.md +++ b/docs/contracts/api/tokens/OsTokenVaultEscrow.md @@ -6,231 +6,109 @@ description: "Abstract contract for initiating assets exits from the vault witho # OsTokenVaultEscrow -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/tokens/OsTokenVaultEscrow.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/tokens/OsTokenVaultEscrow.sol) -**Inherits:** [Ownable2Step ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol), [Multicall →](../base/Multicall), IOsTokenVaultEscrow +**Inherits:** [Ownable2Step ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol), [Multicall](../base/Multicall), IOsTokenVaultEscrow -Used for initiating assets exits from the Vault without burning osToken. +Used for initiating assets exits from the vault without burning osToken -## Structs -### Position -Struct to store the escrow position details +## State Variables +### _maxPercent ```solidity -struct Position { - address owner; - uint96 exitedAssets; - uint128 osTokenShares; - uint128 cumulativeFeePerShare; -} +uint256 private constant _maxPercent = 1e18 ``` -**Properties** - -|Name|Type|Description| -|----|----|-----------| -|`owner`|`address`|The address of the assets owner| -|`exitedAssets`|`uint96`|The amount of assets exited and ready to be claimed| -|`osTokenShares`|`uint128`|The amount of osToken shares| -|`cumulativeFeePerShare`|`uint128`|The cumulative fee per share used to calculate the osToken fee| - - -## Events -### PositionCreated -Event emitted on position creation +### _wad ```solidity -event PositionCreated( - address indexed vault, - uint256 indexed exitPositionTicket, - address owner, - uint256 osTokenShares, - uint256 cumulativeFeePerShare -); +uint256 private constant _wad = 1e18 ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`vault`|`address`|The address of the vault| -|`exitPositionTicket`|`uint256`|The exit position ticket| -|`owner`|`address`|The address of the assets owner| -|`osTokenShares`|`uint256`|The amount of osToken shares| -|`cumulativeFeePerShare`|`uint256`|The cumulative fee per share used to calculate the osToken fee| - -### ExitedAssetsProcessed -Event emitted on assets exit processing +### _hfLiqThreshold ```solidity -event ExitedAssetsProcessed( - address indexed vault, address indexed caller, uint256 indexed exitPositionTicket, uint256 exitedAssets -); +uint256 private constant _hfLiqThreshold = 1e18 ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`vault`|`address`|The address of the vault| -|`caller`|`address`|The address of the caller| -|`exitPositionTicket`|`uint256`|The exit position ticket| -|`exitedAssets`|`uint256`|The amount of exited assets claimed| - -### OsTokenLiquidated -Event emitted on osToken liquidation +### _osTokenVaultController ```solidity -event OsTokenLiquidated( - address indexed caller, - address indexed vault, - uint256 indexed exitPositionTicket, - address receiver, - uint256 osTokenShares, - uint256 receivedAssets -); +IOsTokenVaultController private immutable _osTokenVaultController ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the function caller| -|`vault`|`address`|The address of the vault| -|`exitPositionTicket`|`uint256`|The exit position ticket| -|`receiver`|`address`|The address of the receiver of the liquidated assets| -|`osTokenShares`|`uint256`|The amount of osToken shares to liquidate| -|`receivedAssets`|`uint256`|The amount of assets received| - -### OsTokenRedeemed -Event emitted on osToken redemption +### _osTokenConfig ```solidity -event OsTokenRedeemed( - address indexed caller, - address indexed vault, - uint256 indexed exitPositionTicket, - address receiver, - uint256 osTokenShares, - uint256 receivedAssets -); +IOsTokenConfig private immutable _osTokenConfig ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the function caller| -|`vault`|`address`|The address of the vault| -|`exitPositionTicket`|`uint256`|The exit position ticket| -|`receiver`|`address`|The address of the receiver of the redeemed assets| -|`osTokenShares`|`uint256`|The amount of osToken shares to redeem| -|`receivedAssets`|`uint256`|The amount of assets received| - -### ExitedAssetsClaimed -Event emitted on exited assets claim +### _positions ```solidity -event ExitedAssetsClaimed( - address indexed receiver, - address indexed vault, - uint256 indexed exitPositionTicket, - uint256 osTokenShares, - uint256 assets -); +mapping(address vault => mapping(uint256 positionTicket => Position)) private _positions ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`receiver`|`address`|The address of the receiver of the exited assets| -|`vault`|`address`|The address of the vault| -|`exitPositionTicket`|`uint256`|The exit position ticket| -|`osTokenShares`|`uint256`|The amount of osToken shares burned| -|`assets`|`uint256`|The amount of assets claimed| -### LiqConfigUpdated -Event emitted on liquidation configuration update +### liqBonusPercent +The liquidation bonus percent ```solidity -event LiqConfigUpdated(uint64 liqThresholdPercent, uint256 liqBonusPercent); +uint256 public override liqBonusPercent ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`liqThresholdPercent`|`uint64`|The liquidation threshold percent| -|`liqBonusPercent`|`uint256`|The liquidation bonus percent| - -### AuthenticatorUpdated -Event emitted on authenticator update +### authenticator +The address of the authenticator ```solidity -event AuthenticatorUpdated(address newAuthenticator); +address public override authenticator ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`newAuthenticator`|`address`|The address of the new authenticator| - - -## Functions ### liqThresholdPercent - The liquidation threshold percent ```solidity -function liqThresholdPercent() external view returns (uint64); +uint64 public override liqThresholdPercent ``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`uint64`|The liquidation threshold percent starting from which the osToken shares can be liquidated| - - -### liqBonusPercent - -The liquidation bonus percent -```solidity -function liqBonusPercent() external view returns (uint256); -``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`uint256`|The liquidation bonus percent paid for liquidating the osToken shares| - - -### authenticator +## Functions +### constructor -The address of the authenticator +Constructor ```solidity -function authenticator() external view returns (address); +constructor( + address osTokenVaultController, + address osTokenConfig, + address initialOwner, + address _authenticator, + uint64 _liqThresholdPercent, + uint256 _liqBonusPercent +) Ownable(msg.sender); ``` -**Returns** +**Parameters** |Name|Type|Description| |----|----|-----------| -|``|`address`|The address of the authenticator contract| +|`osTokenVaultController`|`address`|The address of the OsTokenVaultController contract| +|`osTokenConfig`|`address`|The address of the OsTokenConfig contract| +|`initialOwner`|`address`|The address of the contract owner| +|`_authenticator`|`address`|The address of the OsTokenVaultEscrowAuth contract| +|`_liqThresholdPercent`|`uint64`|The liquidation threshold percent| +|`_liqBonusPercent`|`uint256`|The liquidation bonus percent| ### getPosition @@ -392,3 +270,69 @@ function updateLiqConfig(uint64 _liqThresholdPercent, uint256 _liqBonusPercent) |----|----|-----------| |`_liqThresholdPercent`|`uint64`|The liquidation threshold percent| |`_liqBonusPercent`|`uint256`|The liquidation bonus percent| + + +### _redeemOsToken + +Internal function for redeeming osToken shares + + +```solidity +function _redeemOsToken( + address vault, + uint256 exitPositionTicket, + address receiver, + uint256 osTokenShares, + bool isLiquidation +) private returns (uint256 receivedAssets); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vault`|`address`|The address of the vault| +|`exitPositionTicket`|`uint256`|The position ticket of the exit queue| +|`receiver`|`address`|The address of the receiver of the redeemed assets| +|`osTokenShares`|`uint256`|The amount of osToken shares to redeem| +|`isLiquidation`|`bool`|Whether the redeem is a liquidation| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`receivedAssets`|`uint256`|The amount of assets received| + + +### _syncPositionFee + +Internal function for syncing the osToken fee + + +```solidity +function _syncPositionFee(Position memory position) private view; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`position`|`Position`|The position to sync the fee for| + + +### _transferAssets + +Internal function for transferring assets from the Vault to the receiver + +IMPORTANT: because control is transferred to the receiver, care must be +taken to not create reentrancy vulnerabilities. The Vault must follow the checks-effects-interactions pattern: +https://docs.soliditylang.org/en/v0.8.22/security-considerations.html#use-the-checks-effects-interactions-pattern + + +```solidity +function _transferAssets(address receiver, uint256 assets) internal virtual; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive the assets| +|`assets`|`uint256`|The number of assets to transfer| diff --git a/docs/contracts/api/tokens/PriceFeed.md b/docs/contracts/api/tokens/PriceFeed.md index 7c040568..36b17965 100644 --- a/docs/contracts/api/tokens/PriceFeed.md +++ b/docs/contracts/api/tokens/PriceFeed.md @@ -6,11 +6,11 @@ description: "Price feed oracle for osToken compatible with Balancer and Chainli # PriceFeed -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/tokens/PriceFeed.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/tokens/PriceFeed.sol) **Inherits:** IBalancerRateProvider, IChainlinkAggregator, IChainlinkV3Aggregator -Price feed for osToken (e.g osETH price in ETH). +Price feed for osToken (e.g osETH price in ETH) ## State Variables @@ -30,21 +30,30 @@ address public immutable osTokenVaultController ``` -## Functions - ### description - The description of the aggregator ```solidity -function description() external view returns (string memory); +string public override description ``` -**Returns** + + +## Functions +### constructor + +Constructor + + +```solidity +constructor(address _osTokenVaultController, string memory _description) ; +``` +**Parameters** |Name|Type|Description| |----|----|-----------| -|``|`string`|The description string| +|`_osTokenVaultController`|`address`|The address of the OsTokenVaultController contract| +|`_description`|`string`|The description of the price feed| ### getRate diff --git a/docs/contracts/api/validators/ConsolidationsChecker.md b/docs/contracts/api/validators/ConsolidationsChecker.md index 5a636a7b..babe9e26 100644 --- a/docs/contracts/api/validators/ConsolidationsChecker.md +++ b/docs/contracts/api/validators/ConsolidationsChecker.md @@ -6,14 +6,51 @@ description: "Contract for checking signatures of oracles for validators consoli # ConsolidationsChecker -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/validators/ConsolidationsChecker.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/validators/ConsolidationsChecker.sol) **Inherits:** [EIP712 ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol), IConsolidationsChecker -Defines the functionality for checking signatures of oracles for validators consolidations. +Defines the functionality for checking signatures of oracles for validators consolidations + + +## State Variables +### _signatureLength + +```solidity +uint256 private constant _signatureLength = 65 +``` + + +### _consolidationsCheckerTypeHash + +```solidity +bytes32 private constant _consolidationsCheckerTypeHash = + keccak256("ConsolidationsChecker(address vault,bytes validators)") +``` + + +### _keeper + +```solidity +IKeeper private immutable _keeper +``` ## Functions +### constructor + +Constructor + + +```solidity +constructor(address keeper) EIP712("ConsolidationsChecker", "1"); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`keeper`|`address`|The address of the Keeper contract| + ### verifySignatures @@ -60,3 +97,29 @@ function isValidSignatures(address vault, bytes calldata validators, bytes calld |Name|Type|Description| |----|----|-----------| |``|`bool`|`true` if the signatures are valid, `false` otherwise| + + +### _isValidSignatures + +Internal function for verifying oracles' signatures + + +```solidity +function _isValidSignatures(uint256 requiredSignatures, bytes32 message, bytes calldata signatures) + private + view + returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`requiredSignatures`|`uint256`|The number of signatures required for the verification to pass| +|`message`|`bytes32`|The message that was signed| +|`signatures`|`bytes`|The concatenation of the oracles' signatures| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|True if the signatures are valid, otherwise false| diff --git a/docs/contracts/api/validators/DepositDataRegistry.md b/docs/contracts/api/validators/DepositDataRegistry.md index eb639efa..dfc67f19 100644 --- a/docs/contracts/api/validators/DepositDataRegistry.md +++ b/docs/contracts/api/validators/DepositDataRegistry.md @@ -6,87 +6,57 @@ description: "Contract for managing Vault's deposit data and validator registrat # DepositDataRegistry -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/validators/DepositDataRegistry.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/validators/DepositDataRegistry.sol) -**Inherits:** [Multicall →](../base/Multicall), IDepositDataRegistry +**Inherits:** [Multicall](../base/Multicall), IDepositDataRegistry -Defines the functionality for the Vault's deposit data management. +Defines the functionality for the Vault's deposit data management -## Events -### DepositDataManagerUpdated -Event emitted on deposit data manager update - +## State Variables +### _vaultsRegistry ```solidity -event DepositDataManagerUpdated(address indexed vault, address depositDataManager); +IVaultsRegistry private immutable _vaultsRegistry ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`vault`|`address`|The address of the vault| -|`depositDataManager`|`address`|The address of the new deposit data manager| - -### DepositDataRootUpdated -Event emitted on deposit data root update +### depositDataIndexes ```solidity -event DepositDataRootUpdated(address indexed vault, bytes32 depositDataRoot); +mapping(address => uint256) public override depositDataIndexes ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`vault`|`address`|The address of the vault| -|`depositDataRoot`|`bytes32`|The new deposit data Merkle tree root| - -### DepositDataMigrated -Event emitted on deposit data migration +### depositDataRoots ```solidity -event DepositDataMigrated( - address indexed vault, bytes32 depositDataRoot, uint256 validatorIndex, address depositDataManager -); +mapping(address => bytes32) public override depositDataRoots ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`vault`|`address`|The address of the vault| -|`depositDataRoot`|`bytes32`|The deposit data root| -|`validatorIndex`|`uint256`|The index of the next validator to be registered| -|`depositDataManager`|`address`|The address of the deposit data manager| +### _depositDataManagers -## Functions -### onlyValidVault +```solidity +mapping(address => address) private _depositDataManagers +``` -Modifier to check if the caller is a valid vault +### _migrated ```solidity -modifier onlyValidVault(address vault) ; +mapping(address => bool) private _migrated ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`vault`|`address`|The address of the vault| +## Functions +### onlyValidVault -### depositDataIndexes - -The vault deposit data index +Modifier to check if the caller is a valid vault ```solidity -function depositDataIndexes(address vault) external view returns (uint256 validatorIndex); +modifier onlyValidVault(address vault) ; ``` **Parameters** @@ -94,32 +64,20 @@ function depositDataIndexes(address vault) external view returns (uint256 valida |----|----|-----------| |`vault`|`address`|The address of the vault| -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|`validatorIndex`|`uint256`|The index of the next validator to be registered| - -### depositDataRoots +### constructor -The vault deposit data root +Constructor ```solidity -function depositDataRoots(address vault) external view returns (bytes32 depositDataRoot); +constructor(address vaultsRegistry) ; ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`vault`|`address`|The address of the vault| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|`depositDataRoot`|`bytes32`|The deposit data root| +|`vaultsRegistry`|`address`|The address of the vaults registry contract| ### getDepositDataManager diff --git a/docs/contracts/api/validators/EthValidatorsChecker.md b/docs/contracts/api/validators/EthValidatorsChecker.md index 5e5dca2e..18fcfb09 100644 --- a/docs/contracts/api/validators/EthValidatorsChecker.md +++ b/docs/contracts/api/validators/EthValidatorsChecker.md @@ -6,8 +6,78 @@ description: "Contract for checking validators registration on Ethereum" # EthValidatorsChecker -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/validators/EthValidatorsChecker.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/validators/EthValidatorsChecker.sol) -**Inherits:** [ValidatorsChecker →](./ValidatorsChecker) +**Inherits:** [ValidatorsChecker](./ValidatorsChecker) -Defines functionality for checking validators registration on Ethereum. +Defines functionality for checking validators registration on Ethereum + + +## State Variables +### _GENESIS_VAULT_ID + +```solidity +bytes32 private constant _GENESIS_VAULT_ID = keccak256("EthGenesisVault") +``` + + +## Functions +### constructor + +Constructor + + +```solidity +constructor( + address validatorsRegistry, + address keeper, + address vaultsRegistry, + address depositDataRegistry, + address genesisVaultPoolEscrow +) ValidatorsChecker(validatorsRegistry, keeper, vaultsRegistry, depositDataRegistry, genesisVaultPoolEscrow); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`validatorsRegistry`|`address`|The address of the beacon chain validators registry contract| +|`keeper`|`address`|The address of the Keeper contract| +|`vaultsRegistry`|`address`|The address of the VaultsRegistry contract| +|`depositDataRegistry`|`address`|The address of the DepositDataRegistry contract| +|`genesisVaultPoolEscrow`|`address`|The address of the genesis vault pool escrow contract| + + +### _depositAmount + +Get the amount of assets required for validator deposit + + +```solidity +function _depositAmount() internal pure override returns (uint256); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The amount of assets required for deposit| + + +### _vaultAssets + +Get the amount of assets in the vault + + +```solidity +function _vaultAssets(address vault) internal view override returns (uint256); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vault`|`address`|The address of the vault| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The amount of assets in the vault| diff --git a/docs/contracts/api/validators/GnoValidatorsChecker.md b/docs/contracts/api/validators/GnoValidatorsChecker.md index da9bbe7a..e3cd5db8 100644 --- a/docs/contracts/api/validators/GnoValidatorsChecker.md +++ b/docs/contracts/api/validators/GnoValidatorsChecker.md @@ -6,8 +6,87 @@ description: "Contract for checking validators registration on Gnosis" # GnoValidatorsChecker -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/validators/GnoValidatorsChecker.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/validators/GnoValidatorsChecker.sol) -**Inherits:** [ValidatorsChecker →](./ValidatorsChecker) +**Inherits:** [ValidatorsChecker](./ValidatorsChecker) -Defines functionality for checking validators registration on Gnosis. +Defines functionality for checking validators registration on Gnosis + + +## State Variables +### _GENESIS_VAULT_ID + +```solidity +bytes32 private constant _GENESIS_VAULT_ID = keccak256("GnoGenesisVault") +``` + + +### _gnoToken + +```solidity +IERC20 private immutable _gnoToken +``` + + +## Functions +### constructor + +Constructor + + +```solidity +constructor( + address validatorsRegistry, + address keeper, + address vaultsRegistry, + address depositDataRegistry, + address genesisVaultPoolEscrow, + address gnoToken +) ValidatorsChecker(validatorsRegistry, keeper, vaultsRegistry, depositDataRegistry, genesisVaultPoolEscrow); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`validatorsRegistry`|`address`|The address of the beacon chain validators registry contract| +|`keeper`|`address`|The address of the Keeper contract| +|`vaultsRegistry`|`address`|The address of the VaultsRegistry contract| +|`depositDataRegistry`|`address`|The address of the DepositDataRegistry contract| +|`genesisVaultPoolEscrow`|`address`|The address of the genesis vault pool escrow contract| +|`gnoToken`|`address`|The address of the Gnosis token contract| + + +### _depositAmount + +Get the amount of assets required for validator deposit + + +```solidity +function _depositAmount() internal pure override returns (uint256); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The amount of assets required for deposit| + + +### _vaultAssets + +Get the amount of assets in the vault + + +```solidity +function _vaultAssets(address vault) internal view override returns (uint256 assets); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vault`|`address`|The address of the vault| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`assets`|`uint256`|The amount of assets in the vault| diff --git a/docs/contracts/api/validators/ValidatorsChecker.md b/docs/contracts/api/validators/ValidatorsChecker.md index 4828d44a..f8da8024 100644 --- a/docs/contracts/api/validators/ValidatorsChecker.md +++ b/docs/contracts/api/validators/ValidatorsChecker.md @@ -6,62 +6,76 @@ description: "Abstract contract for checking validators manager signature and de # ValidatorsChecker -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/validators/ValidatorsChecker.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/validators/ValidatorsChecker.sol) -**Inherits:** [Multicall →](../base/Multicall), IValidatorsChecker +**Inherits:** [Multicall](../base/Multicall), IValidatorsChecker Defines the functionality for: -- checking validators manager signature -- checking deposit data root +checking validators manager signature +checking deposit data root -## Structs -### DepositDataRootCheckParams -Struct for checking deposit data root +## State Variables +### _validatorsRegistry + +```solidity +IValidatorsRegistry internal immutable _validatorsRegistry +``` +### _keeper + ```solidity -struct DepositDataRootCheckParams { - address vault; - bytes32 validatorsRegistryRoot; - bytes validators; - bytes32[] proof; - bool[] proofFlags; - uint256[] proofIndexes; -} +IKeeper private immutable _keeper ``` -**Properties** -|Name|Type|Description| -|----|----|-----------| -|`vault`|`address`|The address of the vault| -|`validatorsRegistryRoot`|`bytes32`|The validators registry root| -|`validators`|`bytes`|The concatenation of the validators' public key, deposit signature, deposit root| -|`proof`|`bytes32[]`|The proof of the deposit data root| -|`proofFlags`|`bool[]`|The flags of the proof| -|`proofIndexes`|`uint256[]`|The indexes of the proof| +### _vaultsRegistry + +```solidity +IVaultsRegistry private immutable _vaultsRegistry +``` -## Enums -### Status +### _depositDataRegistry ```solidity -enum Status { - SUCCEEDED, - INVALID_VALIDATORS_REGISTRY_ROOT, - INVALID_VAULT, - INSUFFICIENT_ASSETS, - INVALID_SIGNATURE, - INVALID_VALIDATORS_MANAGER, - INVALID_VALIDATORS_COUNT, - INVALID_VALIDATORS_LENGTH, - INVALID_PROOF -} +IDepositDataRegistry private immutable _depositDataRegistry +``` + + +### _genesisVaultPoolEscrow + +```solidity +address internal immutable _genesisVaultPoolEscrow ``` ## Functions +### constructor + +Constructor + + +```solidity +constructor( + address validatorsRegistry, + address keeper, + address vaultsRegistry, + address depositDataRegistry, + address genesisVaultPoolEscrow +) ; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`validatorsRegistry`|`address`|The address of the beacon chain validators registry contract| +|`keeper`|`address`|The address of the Keeper contract| +|`vaultsRegistry`|`address`|The address of the VaultsRegistry contract| +|`depositDataRegistry`|`address`|The address of the DepositDataRegistry contract| +|`genesisVaultPoolEscrow`|`address`|The address of the genesis vault pool escrow contract| + ### updateVaultState @@ -106,11 +120,12 @@ Function for getting the exit queue missing assets ```solidity -function getExitQueueMissingAssets(address vault, uint256 withdrawingAssets, uint256 targetCumulativeTickets) - external - view - override - returns (uint256 missingAssets); +function getExitQueueMissingAssets( + address vault, + uint256 withdrawingAssets, + uint256 redemptionAssets, + uint256 targetCumulativeTickets +) external view override returns (uint256 missingAssets); ``` **Parameters** @@ -118,6 +133,7 @@ function getExitQueueMissingAssets(address vault, uint256 withdrawingAssets, uin |----|----|-----------| |`vault`|`address`|The address of the vault| |`withdrawingAssets`|`uint256`|The amount of assets currently being withdrawn from validators| +|`redemptionAssets`|`uint256`|The amount of assets to be redeemed| |`targetCumulativeTickets`|`uint256`|The target cumulative tickets| **Returns** @@ -181,3 +197,56 @@ function checkDepositDataRoot(DepositDataRootCheckParams calldata params) |----|----|-----------| |`blockNumber`|`uint256`|Current block number| |`status`|`Status`|The status of the verification| + + +### _computeVaultValidatorsDomain + +Computes the hash of the EIP712 typed data for the vault + +This function is used to compute the hash of the EIP712 typed data + + +```solidity +function _computeVaultValidatorsDomain(address vault) private view returns (bytes32); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bytes32`|The hash of the EIP712 typed data| + + +### _depositAmount + +Get the amount of assets required for validator deposit + + +```solidity +function _depositAmount() internal pure virtual returns (uint256); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The amount of assets required for deposit| + + +### _vaultAssets + +Get the amount of assets in the vault + + +```solidity +function _vaultAssets(address vault) internal view virtual returns (uint256); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vault`|`address`|The address of the vault| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The amount of assets in the vault| diff --git a/docs/contracts/api/vaults/SubVaultsRegistry.md b/docs/contracts/api/vaults/SubVaultsRegistry.md new file mode 100644 index 00000000..403e0942 --- /dev/null +++ b/docs/contracts/api/vaults/SubVaultsRegistry.md @@ -0,0 +1,800 @@ +--- +title: SubVaultsRegistry +sidebar_position: 1 +description: "Defines the functionality for managing the Vault sub-vaults. This contract is deployed per MetaVault." +--- + +# SubVaultsRegistry + +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/SubVaultsRegistry.sol) + +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [UUPSUpgradeable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/UUPSUpgradeable.sol), [ReentrancyGuardUpgradeable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/utils/ReentrancyGuardUpgradeable.sol), [Multicall](../base/Multicall), ISubVaultsRegistry + +Defines the functionality for managing the Vault sub-vaults. This contract is deployed per MetaVault. + + +## State Variables +### _maxSubVaults + +```solidity +uint256 private constant _maxSubVaults = 50 +``` + + +### _maxPercent + +```solidity +uint256 private constant _maxPercent = 1e18 +``` + + +### _curatorsRegistry + +```solidity +address private immutable _curatorsRegistry +``` + + +### _vaultsRegistry + +```solidity +address private immutable _vaultsRegistry +``` + + +### _keeper + +```solidity +address private immutable _keeper +``` + + +### _osTokenVaultController + +```solidity +IOsTokenVaultController private immutable _osTokenVaultController +``` + + +### _osTokenConfig + +```solidity +IOsTokenConfig private immutable _osTokenConfig +``` + + +### metaVault +The address of the meta vault + + +```solidity +address public override metaVault +``` + + +### subVaultsCurator +The address of the sub-vaults curator + + +```solidity +address public override subVaultsCurator +``` + + +### pendingMetaSubVault +Pending meta sub-vault waiting for approval + + +```solidity +address public override pendingMetaSubVault +``` + + +### subVaultsRewardsNonce +Function to get the rewards nonce of the sub-vaults + + +```solidity +uint128 public override subVaultsRewardsNonce +``` + + +### ejectingSubVault +The address of the sub-vault being ejected + + +```solidity +address public override ejectingSubVault +``` + + +### ejectingSubVaultShares +The number of shares of the ejecting sub-vault + + +```solidity +uint256 public override ejectingSubVaultShares +``` + + +### _subVaults + +```solidity +EnumerableSet.AddressSet private _subVaults +``` + + +### _subVaultsExits + +```solidity +mapping(address vault => DoubleEndedQueue.Bytes32Deque) private _subVaultsExits +``` + + +### _subVaultsStates + +```solidity +mapping(address vault => SubVaultState state) private _subVaultsStates +``` + + +### subVaultsTotalAssets +The total assets deposited to sub-vaults + + +```solidity +uint128 public override subVaultsTotalAssets +``` + + +### _totalProcessedExitQueueTickets + +```solidity +uint256 private _totalProcessedExitQueueTickets +``` + + +### _unaccountedExitedAssets + +```solidity +uint256 private _unaccountedExitedAssets +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` + + +## Functions +### onlyMetaVaultAdmin + +Modifier to check if the caller is the meta vault admin + + +```solidity +modifier onlyMetaVaultAdmin() ; +``` + +### constructor + +Constructor + + +```solidity +constructor( + address curatorsRegistry, + address vaultsRegistry, + address keeper, + address osTokenVaultController, + address osTokenConfig +) ; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`curatorsRegistry`|`address`|The address of the CuratorsRegistry contract| +|`vaultsRegistry`|`address`|The address of the VaultsRegistry contract| +|`keeper`|`address`|The address of the Keeper contract| +|`osTokenVaultController`|`address`|The address of the OsTokenVaultController contract| +|`osTokenConfig`|`address`|The address of the OsTokenConfig contract| + + +### _authorizeUpgrade + +Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by +`upgradeToAndCall`. +Normally, this function will use an access control modifier such as `Ownable-onlyOwner`. +```solidity +function _authorizeUpgrade(address) internal onlyOwner {} +``` + + +```solidity +function _authorizeUpgrade(address) internal view override; +``` + +### initialize + +Initializes the SubVaultsRegistry + + +```solidity +function initialize(address _metaVault, address curator) external override initializer; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_metaVault`|`address`|| +|`curator`|`address`|The address of initial sub-vaults curator| + + +### getSubVaults + +Returns the list of sub-vaults + + +```solidity +function getSubVaults() public view override returns (address[] memory); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`address[]`|The array of sub-vault addresses| + + +### isSubVault + +Checks if the given address is a sub-vault + + +```solidity +function isSubVault(address vault) public view override returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vault`|`address`|The address to check| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|True if the address is a sub-vault, false otherwise| + + +### setSubVaultsCurator + +Function to update the sub-vaults curator. Can only be called by the meta vault admin. + + +```solidity +function setSubVaultsCurator(address curator) external override onlyMetaVaultAdmin; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`curator`|`address`|The address of the new sub-vaults curator| + + +### addSubVault + +Function to add a new sub-vault. Can only be called by the meta vault admin. + + +```solidity +function addSubVault(address vault) external override onlyMetaVaultAdmin; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vault`|`address`|The address of the sub-vault to add| + + +### acceptMetaSubVault + +Function to accept a meta sub-vault. Can only be called by the VaultsRegistry owner. + + +```solidity +function acceptMetaSubVault(address metaSubVault) external override; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`metaSubVault`|`address`|The address of the meta sub-vault to accept| + + +### rejectMetaSubVault + +Function to reject a meta sub-vault. Can only be called by the VaultsRegistry owner or meta vault admin. + + +```solidity +function rejectMetaSubVault(address metaSubVault) external override; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`metaSubVault`|`address`|The address of the meta sub-vault to reject| + + +### ejectSubVault + +Function to eject a sub-vault. Can only be called by the meta vault admin. +All the sub-vault shares will be added to the exit queue. + + +```solidity +function ejectSubVault(address vault) external override onlyMetaVaultAdmin; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vault`|`address`|The address of the sub-vault to eject| + + +### subVaultsStates + +Returns the state of a sub-vault + + +```solidity +function subVaultsStates(address vault) external view override returns (SubVaultState memory); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vault`|`address`|The address of the sub-vault| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`SubVaultState`|The state of the sub-vault| + + +### subVaultsExits + +Returns the exits queue for a sub-vault + + +```solidity +function subVaultsExits(address vault) external view override returns (bytes32[] memory); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vault`|`address`|The address of the sub-vault| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bytes32[]`|The array of packed exit data (positionTicket: uint160, shares: uint96)| + + +### canUpdateState + +Checks whether the state can be updated + + +```solidity +function canUpdateState() external view override returns (bool); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|True if the state can be updated, false otherwise| + + +### isCollateralized + +Checks whether the meta vault is collateralized + + +```solidity +function isCollateralized() public view override returns (bool); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|True if the meta vault is collateralized, false otherwise| + + +### isStateUpdateRequired + +Checks whether the state update is required + + +```solidity +function isStateUpdateRequired() public view override returns (bool); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|True if the state update is required, false otherwise| + + +### depositToSubVaults + +Deposit available assets to the sub vaults + + +```solidity +function depositToSubVaults() external override nonReentrant; +``` + +### claimSubVaultsExitedAssets + +Claims exited assets from sub vaults + + +```solidity +function claimSubVaultsExitedAssets(SubVaultExitRequest[] calldata exitRequests) external override nonReentrant; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`exitRequests`|`SubVaultExitRequest[]`|The array of exit requests to claim| + + +### harvestSubVaultsAssets + +Harvests sub-vaults assets. Can only be called by the meta vault. + + +```solidity +function harvestSubVaultsAssets() external override returns (int256 totalAssetsDelta, bool harvested); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`totalAssetsDelta`|`int256`|The change in total assets after the harvest| +|`harvested`|`bool`|Whether the sub-vaults were harvested| + + +### enterSubVaultsExitQueue + +Enters the exit queue for sub-vaults. Can only be called by the meta vault. + + +```solidity +function enterSubVaultsExitQueue() external override nonReentrant; +``` + +### calculateSubVaultsRedemptions + +Calculates the required sub-vaults exit requests to fulfill the assets to redeem + + +```solidity +function calculateSubVaultsRedemptions(uint256 assetsToRedeem) + external + view + override + returns (ISubVaultsCurator.ExitRequest[] memory redeemRequests); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`assetsToRedeem`|`uint256`|The amount of assets to redeem| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`redeemRequests`|`ISubVaultsCurator.ExitRequest[]`|The array of sub-vaults exit requests| + + +### redeemSubVaultsAssets + +Redeems assets from sub-vaults to the meta vault. Can only be called by the redeemer. + + +```solidity +function redeemSubVaultsAssets(uint256 assetsToRedeem) + external + override + nonReentrant + returns (uint256 totalRedeemedAssets); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`assetsToRedeem`|`uint256`|The amount of assets to redeem to the meta vault| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`totalRedeemedAssets`|`uint256`|The total amount of assets redeemed from sub-vaults| + + +### _calculateSubVaultsRedemptions + +Internal function to calculate the required sub-vaults exit requests to fulfill the assets to redeem + + +```solidity +function _calculateSubVaultsRedemptions(uint256 assetsToRedeem, bool useEjectingSubVaultShares) + private + view + returns (ISubVaultsCurator.ExitRequest[] memory redeemRequests); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`assetsToRedeem`|`uint256`|The amount of assets to redeem| +|`useEjectingSubVaultShares`|`bool`|Whether to use ejecting sub-vault shares| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`redeemRequests`|`ISubVaultsCurator.ExitRequest[]`|The array of sub-vaults exit requests| + + +### _getSubVaultsBalances + +Returns the balances of the given sub-vaults + + +```solidity +function _getSubVaultsBalances(address[] memory vaults, bool calcNewTotalAssets) + private + view + returns (uint256[] memory balances, uint256 newTotalAssets, uint256 totalStakedAssets); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vaults`|`address[]`|The addresses of the sub-vaults| +|`calcNewTotalAssets`|`bool`|Whether to calculate the new total assets across all sub-vaults| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`balances`|`uint256[]`|The balances of the sub-vaults| +|`newTotalAssets`|`uint256`|The new total assets across all sub-vaults| +|`totalStakedAssets`|`uint256`|The sum of the staked balances across all sub-vaults| + + +### _checkHarvested + +Internal function to check whether the sub-vaults are harvested + + +```solidity +function _checkHarvested() private view; +``` + +### _checkSubVaultsExitClaims + +Internal function to check whether the sub vaults have claimed processed exit queue tickets + + +```solidity +function _checkSubVaultsExitClaims(address[] memory vaults) private view; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vaults`|`address[]`|The addresses of the sub vaults| + + +### _syncRewardsNonce + +Internal function to check whether the vaults are harvested + + +```solidity +function _syncRewardsNonce(address[] memory vaults) private returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vaults`|`address[]`|The addresses of the vaults| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|Whether the nonce has been updated| + + +### _consumeEjectingSubVaultAssets + +Internal function to consume ejecting sub-vault assets + + +```solidity +function _consumeEjectingSubVaultAssets(uint256 unprocessedAssets) private returns (uint256 processedAssets); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`unprocessedAssets`|`uint256`|The amount of unprocessed assets| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`processedAssets`|`uint256`|The amount of processed assets| + + +### _setSubVaultsCurator + +Internal function to set the sub-vaults curator + + +```solidity +function _setSubVaultsCurator(address curator) private; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`curator`|`address`|The address of the sub-vaults curator| + + +### _validateSubVault + +Internal function to validate the addition of a sub-vault + + +```solidity +function _validateSubVault(address vault) private view; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vault`|`address`|The address of the sub-vault to be added| + + +### _addSubVault + +Internal function to add a sub-vault + + +```solidity +function _addSubVault(address vault) private; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vault`|`address`|The address of the sub-vault to add| + + +### _isMetaVault + +Internal function to check whether the vault is a meta vault + + +```solidity +function _isMetaVault(address vault) private view returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vault`|`address`|The address of the vault| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|True if the vault is a meta vault, false otherwise| + + +### _isSubVaultCollateralized + +Internal function to check whether the sub-vault is collateralized + + +```solidity +function _isSubVaultCollateralized(address subVault) private view returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`subVault`|`address`|The address of the sub-vault| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|True if the sub-vault is collateralized, false otherwise| + + +### _getSubVaultRewardsNonce + +Internal function to get the rewards nonce of a sub-vault + + +```solidity +function _getSubVaultRewardsNonce(address subVault) private view returns (uint256); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`subVault`|`address`|The address of the sub-vault| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The rewards nonce of the sub-vault| + + +### _getCurrentRewardsNonce + +Internal function to get the current rewards nonce from the Keeper contract + + +```solidity +function _getCurrentRewardsNonce() private view returns (uint256); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The current rewards nonce| + + +### _processRedeemRequests + +Processes the given redeem requests + + +```solidity +function _processRedeemRequests(address redeemer, ISubVaultsCurator.ExitRequest[] memory redeemRequests) + private + returns (uint256 totalRedeemedAssets); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`redeemer`|`address`|The address of the redeemer| +|`redeemRequests`|`ISubVaultsCurator.ExitRequest[]`|The redeem requests to process| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`totalRedeemedAssets`|`uint256`|The total amount of redeemed assets| diff --git a/docs/contracts/api/vaults/SubVaultsRegistryFactory.md b/docs/contracts/api/vaults/SubVaultsRegistryFactory.md new file mode 100644 index 00000000..b7880638 --- /dev/null +++ b/docs/contracts/api/vaults/SubVaultsRegistryFactory.md @@ -0,0 +1,62 @@ +--- +title: SubVaultsRegistryFactory +sidebar_position: 2 +description: "Factory for deploying SubVaultsRegistry contracts" +--- + +# SubVaultsRegistryFactory + +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/SubVaultsRegistryFactory.sol) + +**Inherits:** ISubVaultsRegistryFactory + +Factory for deploying SubVaultsRegistry contracts + + +## State Variables +### _vaultsRegistry + +```solidity +IVaultsRegistry internal immutable _vaultsRegistry +``` + + +### implementation +The address of the SubVaultsRegistry implementation contract used for proxy creation + + +```solidity +address public immutable override implementation +``` + + +## Functions +### constructor + +Constructor + + +```solidity +constructor(address _implementation, IVaultsRegistry vaultsRegistry) ; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_implementation`|`address`|The implementation address of SubVaultsRegistry| +|`vaultsRegistry`|`IVaultsRegistry`|The address of the VaultsRegistry contract| + + +### createSubVaultsRegistry + +Creates a new SubVaultsRegistry contract + + +```solidity +function createSubVaultsRegistry() external override returns (address); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`address`|The address of the created SubVaultsRegistry contract| diff --git a/docs/contracts/api/vaults/VaultsRegistry.md b/docs/contracts/api/vaults/VaultsRegistry.md index 50da4f04..ee04a494 100644 --- a/docs/contracts/api/vaults/VaultsRegistry.md +++ b/docs/contracts/api/vaults/VaultsRegistry.md @@ -1,154 +1,56 @@ --- title: VaultsRegistry +sidebar_position: 3 description: VaultsRegistry contract API reference for tracking StakeWise vaults, factories, and vault upgrades on Ethereum. --- # VaultsRegistry -[Git Source](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/VaultsRegistry.sol) -**Inherits:** -[Ownable2Step ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol), IVaultsRegistry +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/VaultsRegistry.sol) -Defines the registry functionality that keeps track of Vaults, Factories and Vault upgrades. +**Inherits:** [Ownable2Step ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol), IVaultsRegistry +Defines the registry functionality that keeps track of Vaults, Factories and Vault upgrades -## Events -### VaultAdded -Event emitted on a Vault addition - - -```solidity -event VaultAdded(address indexed caller, address indexed vault); -``` - -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address that has added the Vault| -|`vault`|`address`|The address of the added Vault| - -### VaultImplAdded -Event emitted on adding Vault implementation contract +## State Variables +### vaults ```solidity -event VaultImplAdded(address indexed impl); +mapping(address => bool) public override vaults ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`impl`|`address`|The address of the new implementation contract| - -### VaultImplRemoved -Event emitted on removing Vault implementation contract +### factories ```solidity -event VaultImplRemoved(address indexed impl); +mapping(address => bool) public override factories ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`impl`|`address`|The address of the removed implementation contract| - -### FactoryAdded -Event emitted on whitelisting the factory +### vaultImpls ```solidity -event FactoryAdded(address indexed factory); +mapping(address => bool) public override vaultImpls ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`factory`|`address`|The address of the whitelisted factory| - -### FactoryRemoved -Event emitted on removing the factory from the whitelist +### _initialized ```solidity -event FactoryRemoved(address indexed factory); +bool private _initialized ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`factory`|`address`|The address of the factory removed from the whitelist| - ## Functions +### constructor -### vaults - -Registered Vaults - - -```solidity -function vaults(address vault) external view returns (bool); -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`vault`|`address`|The address of the vault to check whether it is registered| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`bool`|`true` for the registered Vault, `false` otherwise| - - -### vaultImpls - -Registered Vault implementations - - -```solidity -function vaultImpls(address impl) external view returns (bool); -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`impl`|`address`|The address of the vault implementation| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`bool`|`true` for the registered implementation, `false` otherwise| - - -### factories - -Registered Factories +Constructor ```solidity -function factories(address factory) external view returns (bool); +constructor() Ownable(msg.sender); ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`factory`|`address`|The address of the factory to check whether it is whitelisted| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`bool`|`true` for the whitelisted Factory, `false` otherwise| - ### addVault @@ -223,3 +125,18 @@ function removeFactory(address factory) external override onlyOwner; |Name|Type|Description| |----|----|-----------| |`factory`|`address`|The address of the factory to remove from the whitelist| + + +### initialize + +Function for initializing the registry. Can only be called once during the deployment. + + +```solidity +function initialize(address _owner) external override onlyOwner; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_owner`|`address`|The address of the owner of the contract| diff --git a/docs/contracts/api/vaults/ethereum/EthBlocklistErc20Vault.md b/docs/contracts/api/vaults/ethereum/EthBlocklistErc20Vault.md index db902ed9..78bfb380 100644 --- a/docs/contracts/api/vaults/ethereum/EthBlocklistErc20Vault.md +++ b/docs/contracts/api/vaults/ethereum/EthBlocklistErc20Vault.md @@ -6,13 +6,73 @@ description: "Ethereum staking vault with ERC-20 token and blocklist functionali # EthBlocklistErc20Vault -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/ethereum/EthBlocklistErc20Vault.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/ethereum/EthBlocklistErc20Vault.sol) -**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [EthErc20Vault →](./EthErc20Vault), [VaultBlocklist →](../modules/VaultBlocklist), IEthBlocklistErc20Vault +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [EthErc20Vault](./EthErc20Vault), [VaultBlocklist](../modules/VaultBlocklist), IEthBlocklistErc20Vault + +Defines the Ethereum staking Vault with blocking and ERC-20 functionality + + +## State Variables +### _version + +```solidity +uint8 private constant _version = 5 +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` -Defines the Ethereum staking Vault with blocking and [ERC20 ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol) functionality. ## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(EthErc20VaultConstructorArgs memory args) EthErc20Vault(args); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`args`|`EthErc20VaultConstructorArgs`|The arguments for initializing the EthErc20Vault contract| + + +### initialize + +Initializes or upgrades the EthErc20Vault contract. Must transfer security deposit during the deployment. + + +```solidity +function initialize(bytes calldata params) + external + payable + virtual + override(IEthErc20Vault, EthErc20Vault) + reinitializer(_version); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`bytes`|The encoded parameters for initializing the EthErc20Vault contract| + ### deposit @@ -77,29 +137,6 @@ function mintOsToken(address receiver, uint256 osTokenShares, address referrer) |`assets`|`uint256`|The number of assets minted to the receiver| -### transfer - -Moves `amount` tokens from the caller's account to `to`. - - -```solidity -function transfer(address to, uint256 amount) public virtual override(IERC20, ERC20Upgradeable) returns (bool); -``` - -### transferFrom - -Moves `amount` tokens from `from` to `to` using the allowance mechanism. -`amount` is then deducted from the caller's allowance. - - -```solidity -function transferFrom(address from, address to, uint256 amount) - public - virtual - override(IERC20, ERC20Upgradeable) - returns (bool); -``` - ### vaultId Vault Unique Identifier @@ -128,3 +165,14 @@ function version() public pure virtual override(IVaultVersion, EthErc20Vault) re |Name|Type|Description| |----|----|-----------| |``|`uint8`|The version of the Vault implementation contract| + + +### _transfer + +Moves `amount` of tokens from `from` to `to`. +Emits a `Transfer` event. + + +```solidity +function _transfer(address from, address to, uint256 amount) internal virtual override; +``` diff --git a/docs/contracts/api/vaults/ethereum/EthBlocklistVault.md b/docs/contracts/api/vaults/ethereum/EthBlocklistVault.md index da257646..89c53863 100644 --- a/docs/contracts/api/vaults/ethereum/EthBlocklistVault.md +++ b/docs/contracts/api/vaults/ethereum/EthBlocklistVault.md @@ -6,13 +6,73 @@ description: "Ethereum staking vault with blocklist functionality" # EthBlocklistVault -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/ethereum/EthBlocklistVault.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/ethereum/EthBlocklistVault.sol) -**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [EthVault →](./EthVault), [VaultBlocklist →](../modules/VaultBlocklist), IEthBlocklistVault +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [EthVault](./EthVault), [VaultBlocklist](../modules/VaultBlocklist), IEthBlocklistVault + +Defines the Ethereum staking Vault with blocking addresses functionality + + +## State Variables +### _version + +```solidity +uint8 private constant _version = 5 +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` -Defines the Ethereum staking Vault with blocking addresses functionality. ## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(EthVaultConstructorArgs memory args) EthVault(args); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`args`|`EthVaultConstructorArgs`|The arguments for initializing the EthVault contract| + + +### initialize + +Initializes or upgrades the EthVault contract. Must transfer security deposit during the deployment. + + +```solidity +function initialize(bytes calldata params) + external + payable + virtual + override(IEthVault, EthVault) + reinitializer(_version); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`bytes`|The encoded parameters for initializing the EthVault contract| + ### deposit diff --git a/docs/contracts/api/vaults/ethereum/EthErc20MetaVault.md b/docs/contracts/api/vaults/ethereum/EthErc20MetaVault.md new file mode 100644 index 00000000..d6284adc --- /dev/null +++ b/docs/contracts/api/vaults/ethereum/EthErc20MetaVault.md @@ -0,0 +1,515 @@ +--- +title: EthErc20MetaVault +sidebar_position: 3 +description: "Defines the Meta Vault functionality with ERC-20 token on Ethereum" +--- + +# EthErc20MetaVault + +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/ethereum/EthErc20MetaVault.sol) + +**Inherits:** [VaultImmutables](../modules/VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [ReentrancyGuardUpgradeable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/utils/ReentrancyGuardUpgradeable.sol), [VaultAdmin](../modules/VaultAdmin), [VaultVersion](../modules/VaultVersion), [VaultFee](../modules/VaultFee), [VaultState](../modules/VaultState), [VaultEnterExit](../modules/VaultEnterExit), [VaultOsToken](../modules/VaultOsToken), [VaultToken](../modules/VaultToken), [VaultSubVaults](../modules/VaultSubVaults), [Multicall](../../base/Multicall), IEthErc20MetaVault + +Defines the Meta Vault functionality with ERC-20 token on Ethereum + + +## State Variables +### _version + +```solidity +uint8 private constant _version = 7 +``` + + +### _securityDeposit + +```solidity +uint256 private constant _securityDeposit = 1e9 +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` + + +## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(EthErc20MetaVaultConstructorArgs memory args) + VaultImmutables(args.keeper, args.vaultsRegistry) + VaultEnterExit(args.exitingAssetsClaimDelay) + VaultOsToken(args.osTokenVaultController, args.osTokenConfig, args.osTokenVaultEscrow) + VaultSubVaults(args.subVaultsRegistryFactory); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`args`|`EthErc20MetaVaultConstructorArgs`|The arguments for initializing the EthErc20MetaVault contract| + + +### initialize + +Initializes or upgrades the EthErc20MetaVault contract. Must transfer security deposit during the deployment. + + +```solidity +function initialize(bytes calldata params) external payable virtual override reinitializer(_version); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`bytes`|The encoded parameters for initializing the EthErc20MetaVault contract| + + +### deposit + +Deposit ETH to the Vault + + +```solidity +function deposit(address receiver, address referrer) public payable virtual override returns (uint256 shares); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive Vault's shares| +|`referrer`|`address`|The address of the referrer. Set to zero address if not used.| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The number of shares minted| + + +### receive + +Function for depositing using fallback function + + +```solidity +receive() external payable virtual; +``` + +### updateStateAndDeposit + +Updates Vault state and deposits ETH to the Vault + + +```solidity +function updateStateAndDeposit( + address receiver, + address referrer, + IKeeperRewards.HarvestParams calldata harvestParams +) public payable virtual override returns (uint256 shares); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive Vault's shares| +|`referrer`|`address`|The address of the referrer. Set to zero address if not used.| +|`harvestParams`|`IKeeperRewards.HarvestParams`|The parameters for harvesting Keeper rewards| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The number of shares minted| + + +### depositAndMintOsToken + +Deposits assets to the vault and mints OsToken shares to the receiver + + +```solidity +function depositAndMintOsToken(address receiver, uint256 osTokenShares, address referrer) + public + payable + override + returns (uint256); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address to receive the OsToken| +|`osTokenShares`|`uint256`|The amount of OsToken shares to mint. If set to type(uint256).max, max OsToken shares will be minted.| +|`referrer`|`address`|The address of the referrer| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The amount of OsToken assets minted| + + +### updateStateAndDepositAndMintOsToken + +Updates the state, deposits assets to the vault and mints OsToken shares to the receiver + + +```solidity +function updateStateAndDepositAndMintOsToken( + address receiver, + uint256 osTokenShares, + address referrer, + IKeeperRewards.HarvestParams calldata harvestParams +) external payable override returns (uint256); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address to receive the OsToken| +|`osTokenShares`|`uint256`|The amount of OsToken shares to mint. If set to type(uint256).max, max OsToken shares will be minted.| +|`referrer`|`address`|The address of the referrer| +|`harvestParams`|`IKeeperRewards.HarvestParams`|The parameters for the harvest| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The amount of OsToken assets minted| + + +### donateAssets + +Donate assets to the Vault. Must transfer ETH together with the call. + + +```solidity +function donateAssets() external payable override; +``` + +### transfer + +Moves `amount` tokens from the caller's account to `to`. + + +```solidity +function transfer(address to, uint256 amount) public virtual override(IERC20, ERC20Upgradeable) returns (bool); +``` + +### transferFrom + +Moves `amount` tokens from `from` to `to` using the allowance mechanism. +`amount` is then deducted from the caller's allowance. + + +```solidity +function transferFrom(address from, address to, uint256 amount) + public + virtual + override(IERC20, ERC20Upgradeable) + returns (bool); +``` + +### transferOsTokenPositionToEscrow + +Transfers minted osToken shares to the OsTokenVaultEscrow contract, enters the exit queue for staked assets + + +```solidity +function transferOsTokenPositionToEscrow(uint256 osTokenShares) + public + virtual + override(IVaultOsToken, VaultOsToken) + returns (uint256 positionTicket); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`osTokenShares`|`uint256`|The number of osToken shares to transfer| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`positionTicket`|`uint256`|The exit position ticket| + + +### enterExitQueue + +Locks shares to the exit queue. The shares continue earning rewards until they will be burned by the Vault. + +For ERC-20 vault variants, balanceOf(vault) does not reflect queued exit shares despite the emitted Transfer event. + + +```solidity +function enterExitQueue(uint256 shares, address receiver) + public + virtual + override(IVaultEnterExit, VaultEnterExit, VaultOsToken) + returns (uint256 positionTicket); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The number of shares to lock| +|`receiver`|`address`|The address that will receive assets upon withdrawal| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`positionTicket`|`uint256`|The position ticket of the exit queue. Returns uint256 max if no ticket created.| + + +### donateShares + +Donates shares to the Vault by burning them from the caller, +increasing the value per share for remaining holders + + +```solidity +function donateShares(uint256 shares) public virtual override(IVaultState, VaultState, VaultOsToken); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The number of shares to donate| + + +### vaultId + +Vault Unique Identifier + + +```solidity +function vaultId() public pure virtual override(IVaultVersion, VaultVersion) returns (bytes32); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bytes32`|The unique identifier of the Vault| + + +### version + +Version + + +```solidity +function version() public pure virtual override(IVaultVersion, VaultVersion) returns (uint8); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint8`|The version of the Vault implementation contract| + + +### _depositToVault + +Internal function to deposit assets to the sub-vault + + +```solidity +function _depositToVault(address vault, uint256 assets) internal override returns (uint256); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vault`|`address`|The address of the vault| +|`assets`|`uint256`|The amount of assets to deposit| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The amount of vault shares received| + + +### _vaultAssets + +Internal function for retrieving the total assets stored in the Vault. +NB! Assets can be forcibly sent to the vault, the returned value must be used with caution + + +```solidity +function _vaultAssets() internal view virtual override returns (uint256); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The total amount of assets stored in the Vault| + + +### _transferVaultAssets + +Internal function for transferring assets from the Vault to the receiver + + +```solidity +function _transferVaultAssets(address receiver, uint256 assets) internal virtual override nonReentrant; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive the assets| +|`assets`|`uint256`|The number of assets to transfer| + + +### isStateUpdateRequired + +Check whether state update is required + + +```solidity +function isStateUpdateRequired() + public + view + virtual + override(IVaultState, VaultState, VaultSubVaults) + returns (bool); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|`true` if state update is required, `false` otherwise| + + +### updateState + +Updates the total amount of assets in the Vault and its exit queue + + +```solidity +function updateState(IKeeperRewards.HarvestParams calldata harvestParams) + public + virtual + override(IVaultState, VaultState, VaultSubVaults); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`harvestParams`|`IKeeperRewards.HarvestParams`|The parameters for harvesting Keeper rewards| + + +### _updateExitQueue + +Internal function that must be used to process exit queue + + +```solidity +function _updateExitQueue() internal virtual override(VaultState, VaultToken) returns (uint256 burnedShares); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`burnedShares`|`uint256`|The total amount of burned shares| + + +### _mintShares + +Internal function for minting shares + + +```solidity +function _mintShares(address owner, uint256 shares) internal virtual override(VaultState, VaultToken); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`owner`|`address`|The address of the owner to mint shares to| +|`shares`|`uint256`|The number of shares to mint| + + +### _burnShares + +Internal function for burning shares + + +```solidity +function _burnShares(address owner, uint256 shares) internal virtual override(VaultState, VaultToken); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`owner`|`address`|The address of the owner to burn shares for| +|`shares`|`uint256`|The number of shares to burn| + + +### _checkHarvested + +Internal method for checking whether the vault is harvested + + +```solidity +function _checkHarvested() internal view virtual override(VaultImmutables, VaultSubVaults); +``` + +### _isCollateralized + +Returns whether the vault is collateralized + + +```solidity +function _isCollateralized() internal view virtual override(VaultImmutables, VaultSubVaults) returns (bool); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|true if the vault is collateralized| + + +### __EthErc20MetaVault_upgrade + +Upgrades the EthErc20MetaVault contract + + +```solidity +function __EthErc20MetaVault_upgrade() internal onlyInitializing; +``` + +### __EthErc20MetaVault_init + +Initializes the EthErc20MetaVault contract + + +```solidity +function __EthErc20MetaVault_init(address _admin, EthErc20MetaVaultInitParams memory params) + internal + onlyInitializing; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_admin`|`address`|The address of the admin of the Vault| +|`params`|`EthErc20MetaVaultInitParams`|The parameters for initializing the EthErc20MetaVault contract| diff --git a/docs/contracts/api/vaults/ethereum/EthErc20Vault.md b/docs/contracts/api/vaults/ethereum/EthErc20Vault.md index 0b6c9947..24c96f06 100644 --- a/docs/contracts/api/vaults/ethereum/EthErc20Vault.md +++ b/docs/contracts/api/vaults/ethereum/EthErc20Vault.md @@ -1,81 +1,84 @@ --- title: EthErc20Vault -sidebar_position: 3 +sidebar_position: 4 description: "Ethereum staking vault with ERC-20 token functionality" --- # EthErc20Vault -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/ethereum/EthErc20Vault.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/ethereum/EthErc20Vault.sol) -**Inherits:** [VaultImmutables →](../modules/VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [VaultAdmin →](../modules/VaultAdmin), [VaultVersion →](../modules/VaultVersion), [VaultFee →](../modules/VaultFee), [VaultState →](../modules/VaultState), [VaultValidators →](../modules/VaultValidators), [VaultEnterExit →](../modules/VaultEnterExit), [VaultOsToken →](../modules/VaultOsToken), [VaultMev →](../modules/VaultMev), [VaultToken →](../modules/VaultToken), [VaultEthStaking →](../modules/VaultEthStaking), [Multicall →](../../base/Multicall), IEthErc20Vault +**Inherits:** [VaultImmutables](../modules/VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [VaultAdmin](../modules/VaultAdmin), [VaultVersion](../modules/VaultVersion), [VaultFee](../modules/VaultFee), [VaultState](../modules/VaultState), [VaultValidators](../modules/VaultValidators), [VaultEnterExit](../modules/VaultEnterExit), [VaultOsToken](../modules/VaultOsToken), [VaultMev](../modules/VaultMev), [VaultToken](../modules/VaultToken), [VaultEthStaking](../modules/VaultEthStaking), [Multicall](../../base/Multicall), IEthErc20Vault -Defines the Ethereum staking Vault with [ERC20 ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol) token. +Defines the Ethereum staking Vault with ERC-20 token -## Structs -### EthErc20VaultConstructorArgs -Struct for deploying the EthErc20Vault contract +## State Variables +### _version ```solidity -struct EthErc20VaultConstructorArgs { - address keeper; - address vaultsRegistry; - address validatorsRegistry; - address validatorsWithdrawals; - address validatorsConsolidations; - address consolidationsChecker; - address osTokenVaultController; - address osTokenConfig; - address osTokenVaultEscrow; - address sharedMevEscrow; - address depositDataRegistry; - uint64 exitingAssetsClaimDelay; -} +uint8 private constant _version = 5 ``` -**Properties** + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` + + +## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(EthErc20VaultConstructorArgs memory args) + VaultImmutables(args.keeper, args.vaultsRegistry) + VaultValidators( + args.depositDataRegistry, + args.validatorsRegistry, + args.validatorsWithdrawals, + args.validatorsConsolidations, + args.consolidationsChecker + ) + VaultEnterExit(args.exitingAssetsClaimDelay) + VaultOsToken(args.osTokenVaultController, args.osTokenConfig, args.osTokenVaultEscrow) + VaultMev(args.sharedMevEscrow); +``` +**Parameters** |Name|Type|Description| |----|----|-----------| -|`keeper`|`address`|The address of the Keeper contract| -|`vaultsRegistry`|`address`|The address of the VaultsRegistry contract| -|`validatorsRegistry`|`address`|The contract address used for registering validators in beacon chain| -|`validatorsWithdrawals`|`address`|The contract address used for withdrawing validators in beacon chain| -|`validatorsConsolidations`|`address`|The contract address used for consolidating validators in beacon chain| -|`consolidationsChecker`|`address`|The contract address used for checking consolidations| -|`osTokenVaultController`|`address`|The address of the OsTokenVaultController contract| -|`osTokenConfig`|`address`|The address of the OsTokenConfig contract| -|`osTokenVaultEscrow`|`address`|The address of the OsTokenVaultEscrow contract| -|`sharedMevEscrow`|`address`|The address of the shared MEV escrow| -|`depositDataRegistry`|`address`|The address of the DepositDataRegistry contract| -|`exitingAssetsClaimDelay`|`uint64`|The delay after which the assets can be claimed after exiting from staking| +|`args`|`EthErc20VaultConstructorArgs`|The arguments for initializing the EthErc20Vault contract| + -### EthErc20VaultInitParams -Struct for initializing the EthErc20Vault contract +### initialize + +Initializes or upgrades the EthErc20Vault contract. Must transfer security deposit during the deployment. ```solidity -struct EthErc20VaultInitParams { - uint256 capacity; - uint16 feePercent; - string name; - string symbol; - string metadataIpfsHash; -} +function initialize(bytes calldata params) external payable virtual override reinitializer(_version); ``` - -**Properties** +**Parameters** |Name|Type|Description| |----|----|-----------| -|`capacity`|`uint256`|The Vault stops accepting deposits after exceeding the capacity| -|`feePercent`|`uint16`|The fee percent that is charged by the Vault| -|`name`|`string`|The name of the ERC20 token| -|`symbol`|`string`|The symbol of the ERC20 token| -|`metadataIpfsHash`|`string`|The IPFS hash of the Vault's metadata file| +|`params`|`bytes`|The encoded parameters for initializing the EthErc20Vault contract| -## Functions ### depositAndMintOsToken @@ -156,10 +159,37 @@ function transferFrom(address from, address to, uint256 amount) returns (bool); ``` +### transferOsTokenPositionToEscrow + +Transfers minted osToken shares to the OsTokenVaultEscrow contract, enters the exit queue for staked assets + + +```solidity +function transferOsTokenPositionToEscrow(uint256 osTokenShares) + public + virtual + override(IVaultOsToken, VaultOsToken) + returns (uint256 positionTicket); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`osTokenShares`|`uint256`|The number of osToken shares to transfer| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`positionTicket`|`uint256`|The exit position ticket| + + ### enterExitQueue Locks shares to the exit queue. The shares continue earning rewards until they will be burned by the Vault. +For ERC-20 vault variants, balanceOf(vault) does not reflect queued exit shares despite the emitted Transfer event. + ```solidity function enterExitQueue(uint256 shares, address receiver) @@ -182,6 +212,22 @@ function enterExitQueue(uint256 shares, address receiver) |`positionTicket`|`uint256`|The position ticket of the exit queue. Returns uint256 max if no ticket created.| +### donateShares + +Donates shares to the Vault by burning them from the caller, +increasing the value per share for remaining holders + + +```solidity +function donateShares(uint256 shares) public virtual override(IVaultState, VaultState, VaultOsToken); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The number of shares to donate| + + ### vaultId Vault Unique Identifier @@ -210,3 +256,78 @@ function version() public pure virtual override(IVaultVersion, VaultVersion) ret |Name|Type|Description| |----|----|-----------| |``|`uint8`|The version of the Vault implementation contract| + + +### _updateExitQueue + +Internal function that must be used to process exit queue + + +```solidity +function _updateExitQueue() internal virtual override(VaultState, VaultToken) returns (uint256 burnedShares); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`burnedShares`|`uint256`|The total amount of burned shares| + + +### _mintShares + +Internal function for minting shares + + +```solidity +function _mintShares(address owner, uint256 shares) internal virtual override(VaultState, VaultToken); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`owner`|`address`|The address of the owner to mint shares to| +|`shares`|`uint256`|The number of shares to mint| + + +### _burnShares + +Internal function for burning shares + + +```solidity +function _burnShares(address owner, uint256 shares) internal virtual override(VaultState, VaultToken); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`owner`|`address`|The address of the owner to burn shares for| +|`shares`|`uint256`|The number of shares to burn| + + +### __EthErc20Vault_upgrade + +Upgrades the EthErc20Vault contract + + +```solidity +function __EthErc20Vault_upgrade() internal; +``` + +### __EthErc20Vault_init + +Initializes the EthErc20Vault contract + + +```solidity +function __EthErc20Vault_init(address admin, address ownMevEscrow, EthErc20VaultInitParams memory params) + internal + onlyInitializing; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`admin`|`address`|The address of the admin of the Vault| +|`ownMevEscrow`|`address`|The address of the MEV escrow owned by the Vault. Zero address if shared MEV escrow is used.| +|`params`|`EthErc20VaultInitParams`|The decoded parameters for initializing the EthErc20Vault contract| diff --git a/docs/contracts/api/vaults/ethereum/EthGenesisVault.md b/docs/contracts/api/vaults/ethereum/EthGenesisVault.md index 51614ce5..1fa89090 100644 --- a/docs/contracts/api/vaults/ethereum/EthGenesisVault.md +++ b/docs/contracts/api/vaults/ethereum/EthGenesisVault.md @@ -1,54 +1,96 @@ --- title: EthGenesisVault -sidebar_position: 4 +sidebar_position: 5 description: "Genesis vault for Ethereum staking migrated from StakeWise Legacy" --- # EthGenesisVault -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/ethereum/EthGenesisVault.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/ethereum/EthGenesisVault.sol) -**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [EthVault →](./EthVault), IEthGenesisVault +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [EthVault](./EthVault), IEthGenesisVault -Defines the Genesis Vault for Ethereum staking migrated from StakeWise Legacy. +Defines the Genesis Vault for Ethereum staking migrated from StakeWise Legacy -## Events -### Migrated -Event emitted on migration from StakeWise Legacy +## State Variables +### _version ```solidity -event Migrated(address receiver, uint256 assets, uint256 shares); +uint8 private constant _version = 5 ``` + +### _poolEscrow +**Note:** +oz-upgrades-unsafe-allow: state-variable-immutable + + +```solidity +IEthPoolEscrow private immutable _poolEscrow +``` + + +### _rewardEthToken +**Note:** +oz-upgrades-unsafe-allow: state-variable-immutable + + +```solidity +IRewardEthToken private immutable _rewardEthToken +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` + + +## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(EthVaultConstructorArgs memory args, address poolEscrow, address rewardEthToken) EthVault(args); +``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`receiver`|`address`|The address of the shares receiver| -|`assets`|`uint256`|The amount of assets migrated| -|`shares`|`uint256`|The amount of shares migrated| +|`args`|`EthVaultConstructorArgs`|The arguments for initializing the EthVault contract| +|`poolEscrow`|`address`|The address of the pool escrow contract| +|`rewardEthToken`|`address`|The address of the reward ETH token contract| -### GenesisVaultCreated -Event emitted on EthGenesisVault creation (deprecated) + +### initialize + +Initializes or upgrades the EthVault contract. Must transfer security deposit during the deployment. ```solidity -event GenesisVaultCreated(address admin, uint256 capacity, uint16 feePercent, string metadataIpfsHash); +function initialize(bytes calldata) external payable virtual override(IEthVault, EthVault) reinitializer(_version); ``` - **Parameters** |Name|Type|Description| |----|----|-----------| -|`admin`|`address`|The address of the Vault admin| -|`capacity`|`uint256`|The capacity of the Vault| -|`feePercent`|`uint16`|The fee percent of the Vault| -|`metadataIpfsHash`|`string`|The IPFS hash of the Vault metadata| +|``|`bytes`|| -## Functions - ### vaultId Vault Unique Identifier @@ -109,3 +151,87 @@ Function for depositing using fallback function ```solidity receive() external payable virtual override; ``` + +### _calcMaxMintOsTokenShares + +Internal function for calculating the maximum amount of osToken shares that can be minted +based on the current user balance + + +```solidity +function _calcMaxMintOsTokenShares(address user) private view returns (uint256); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`user`|`address`|The address of the user| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The maximum amount of osToken shares that can be minted| + + +### _transferVaultAssets + +Internal function for transferring assets from the Vault to the receiver + + +```solidity +function _transferVaultAssets(address receiver, uint256 assets) + internal + virtual + override(VaultEnterExit, VaultEthStaking); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive the assets| +|`assets`|`uint256`|The number of assets to transfer| + + +### _vaultAssets + +Internal function for retrieving the total assets stored in the Vault. +NB! Assets can be forcibly sent to the vault, the returned value must be used with caution + + +```solidity +function _vaultAssets() internal view virtual override(VaultState, VaultEthStaking) returns (uint256); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The total amount of assets stored in the Vault| + + +### _registerValidators + +Internal function for registering validators + + +```solidity +function _registerValidators(ValidatorUtils.ValidatorDeposit[] memory deposits) + internal + virtual + override(VaultValidators, VaultEthStaking); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`deposits`|`ValidatorUtils.ValidatorDeposit[]`|The validators registration data| + + +### _pullWithdrawals + +Pulls assets from pool escrow + + +```solidity +function _pullWithdrawals() private; +``` diff --git a/docs/contracts/api/vaults/ethereum/EthMetaVault.md b/docs/contracts/api/vaults/ethereum/EthMetaVault.md new file mode 100644 index 00000000..beca5b55 --- /dev/null +++ b/docs/contracts/api/vaults/ethereum/EthMetaVault.md @@ -0,0 +1,410 @@ +--- +title: EthMetaVault +sidebar_position: 6 +description: "Defines the Meta Vault functionality on Ethereum" +--- + +# EthMetaVault + +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/ethereum/EthMetaVault.sol) + +**Inherits:** [VaultImmutables](../modules/VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [ReentrancyGuardUpgradeable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/utils/ReentrancyGuardUpgradeable.sol), [VaultAdmin](../modules/VaultAdmin), [VaultVersion](../modules/VaultVersion), [VaultFee](../modules/VaultFee), [VaultState](../modules/VaultState), [VaultEnterExit](../modules/VaultEnterExit), [VaultOsToken](../modules/VaultOsToken), [VaultSubVaults](../modules/VaultSubVaults), [Multicall](../../base/Multicall), IEthMetaVault + +Defines the Meta Vault functionality on Ethereum + + +## State Variables +### _version + +```solidity +uint8 private constant _version = 7 +``` + + +### _securityDeposit + +```solidity +uint256 private constant _securityDeposit = 1e9 +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` + + +## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(EthMetaVaultConstructorArgs memory args) + VaultImmutables(args.keeper, args.vaultsRegistry) + VaultEnterExit(args.exitingAssetsClaimDelay) + VaultOsToken(args.osTokenVaultController, args.osTokenConfig, args.osTokenVaultEscrow) + VaultSubVaults(args.subVaultsRegistryFactory); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`args`|`EthMetaVaultConstructorArgs`|The arguments for initializing the EthMetaVault contract| + + +### initialize + +Initializes or upgrades the EthMetaVault contract. Must transfer security deposit during the deployment. + + +```solidity +function initialize(bytes calldata params) external payable virtual override reinitializer(_version); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`bytes`|The encoded parameters for initializing the EthMetaVault contract| + + +### deposit + +Deposit ETH to the Vault + + +```solidity +function deposit(address receiver, address referrer) public payable virtual override returns (uint256 shares); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive Vault's shares| +|`referrer`|`address`|The address of the referrer. Set to zero address if not used.| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The number of shares minted| + + +### receive + +Function for depositing using fallback function + + +```solidity +receive() external payable virtual; +``` + +### updateStateAndDeposit + +Updates Vault state and deposits ETH to the Vault + + +```solidity +function updateStateAndDeposit( + address receiver, + address referrer, + IKeeperRewards.HarvestParams calldata harvestParams +) public payable virtual override returns (uint256 shares); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive Vault's shares| +|`referrer`|`address`|The address of the referrer. Set to zero address if not used.| +|`harvestParams`|`IKeeperRewards.HarvestParams`|The parameters for harvesting Keeper rewards| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The number of shares minted| + + +### depositAndMintOsToken + +Deposits assets to the vault and mints OsToken shares to the receiver + + +```solidity +function depositAndMintOsToken(address receiver, uint256 osTokenShares, address referrer) + public + payable + override + returns (uint256); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address to receive the OsToken| +|`osTokenShares`|`uint256`|The amount of OsToken shares to mint. If set to type(uint256).max, max OsToken shares will be minted.| +|`referrer`|`address`|The address of the referrer| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The amount of OsToken assets minted| + + +### updateStateAndDepositAndMintOsToken + +Updates the state, deposits assets to the vault and mints OsToken shares to the receiver + + +```solidity +function updateStateAndDepositAndMintOsToken( + address receiver, + uint256 osTokenShares, + address referrer, + IKeeperRewards.HarvestParams calldata harvestParams +) external payable override returns (uint256); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address to receive the OsToken| +|`osTokenShares`|`uint256`|The amount of OsToken shares to mint. If set to type(uint256).max, max OsToken shares will be minted.| +|`referrer`|`address`|The address of the referrer| +|`harvestParams`|`IKeeperRewards.HarvestParams`|The parameters for the harvest| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The amount of OsToken assets minted| + + +### donateAssets + +Donate assets to the Vault. Must transfer ETH together with the call. + + +```solidity +function donateAssets() external payable override; +``` + +### vaultId + + +```solidity +function vaultId() public pure virtual override(IVaultVersion, VaultVersion) returns (bytes32); +``` + +### version + +Version + + +```solidity +function version() public pure virtual override(IVaultVersion, VaultVersion) returns (uint8); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint8`|The version of the Vault implementation contract| + + +### _depositToVault + +Internal function to deposit assets to the sub-vault + + +```solidity +function _depositToVault(address vault, uint256 assets) internal override returns (uint256); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vault`|`address`|The address of the vault| +|`assets`|`uint256`|The amount of assets to deposit| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The amount of vault shares received| + + +### _vaultAssets + +Internal function for retrieving the total assets stored in the Vault. +NB! Assets can be forcibly sent to the vault, the returned value must be used with caution + + +```solidity +function _vaultAssets() internal view virtual override returns (uint256); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The total amount of assets stored in the Vault| + + +### _transferVaultAssets + +Internal function for transferring assets from the Vault to the receiver + + +```solidity +function _transferVaultAssets(address receiver, uint256 assets) internal virtual override nonReentrant; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive the assets| +|`assets`|`uint256`|The number of assets to transfer| + + +### isStateUpdateRequired + +Check whether state update is required + + +```solidity +function isStateUpdateRequired() + public + view + virtual + override(IVaultState, VaultState, VaultSubVaults) + returns (bool); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|`true` if state update is required, `false` otherwise| + + +### updateState + +Updates the total amount of assets in the Vault and its exit queue + + +```solidity +function updateState(IKeeperRewards.HarvestParams calldata harvestParams) + public + virtual + override(IVaultState, VaultState, VaultSubVaults); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`harvestParams`|`IKeeperRewards.HarvestParams`|The parameters for harvesting Keeper rewards| + + +### enterExitQueue + +Locks shares to the exit queue. The shares continue earning rewards until they will be burned by the Vault. + +For ERC-20 vault variants, balanceOf(vault) does not reflect queued exit shares despite the emitted Transfer event. + + +```solidity +function enterExitQueue(uint256 shares, address receiver) + public + virtual + override(IVaultEnterExit, VaultEnterExit, VaultOsToken) + returns (uint256 positionTicket); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The number of shares to lock| +|`receiver`|`address`|The address that will receive assets upon withdrawal| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`positionTicket`|`uint256`|The position ticket of the exit queue. Returns uint256 max if no ticket created.| + + +### donateShares + +Donates shares to the Vault by burning them from the caller, +increasing the value per share for remaining holders + + +```solidity +function donateShares(uint256 shares) public virtual override(IVaultState, VaultState, VaultOsToken); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The number of shares to donate| + + +### _checkHarvested + +Internal method for checking whether the vault is harvested + + +```solidity +function _checkHarvested() internal view virtual override(VaultImmutables, VaultSubVaults); +``` + +### _isCollateralized + +Returns whether the vault is collateralized + + +```solidity +function _isCollateralized() internal view virtual override(VaultImmutables, VaultSubVaults) returns (bool); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|true if the vault is collateralized| + + +### __EthMetaVault_upgrade + +Upgrades the EthMetaVault contract + + +```solidity +function __EthMetaVault_upgrade() internal onlyInitializing; +``` + +### __EthMetaVault_init + +Initializes the EthMetaVault contract + + +```solidity +function __EthMetaVault_init(address _admin, EthMetaVaultInitParams memory params) internal onlyInitializing; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_admin`|`address`|The address of the admin of the Vault| +|`params`|`EthMetaVaultInitParams`|The parameters for initializing the EthMetaVault contract| diff --git a/docs/contracts/api/vaults/ethereum/EthMetaVaultFactory.md b/docs/contracts/api/vaults/ethereum/EthMetaVaultFactory.md new file mode 100644 index 00000000..09f08a19 --- /dev/null +++ b/docs/contracts/api/vaults/ethereum/EthMetaVaultFactory.md @@ -0,0 +1,71 @@ +--- +title: EthMetaVaultFactory +sidebar_position: 7 +description: "Factory for deploying Ethereum meta Vaults" +--- + +# EthMetaVaultFactory + +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/ethereum/EthMetaVaultFactory.sol) + +**Inherits:** IEthMetaVaultFactory + +Factory for deploying Ethereum meta Vaults + + +## State Variables +### _vaultsRegistry + +```solidity +IVaultsRegistry internal immutable _vaultsRegistry +``` + + +### implementation +The address of the Vault implementation contract used for proxy creation + + +```solidity +address public immutable override implementation +``` + + +### vaultAdmin +The address of the Vault admin used for Vault creation + + +```solidity +address public override vaultAdmin +``` + + +## Functions +### constructor + +Constructor + + +```solidity +constructor(address _implementation, IVaultsRegistry vaultsRegistry) ; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_implementation`|`address`|The implementation address of Vault| +|`vaultsRegistry`|`IVaultsRegistry`|The address of the VaultsRegistry contract| + + +### createVault + +Create Vault. Must transfer security deposit together with a call. + + +```solidity +function createVault(bytes calldata params) external payable override returns (address vault); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`bytes`|The encoded parameters for initializing the Vault contract| diff --git a/docs/contracts/api/vaults/ethereum/EthPrivErc20MetaVault.md b/docs/contracts/api/vaults/ethereum/EthPrivErc20MetaVault.md new file mode 100644 index 00000000..dcf24d9a --- /dev/null +++ b/docs/contracts/api/vaults/ethereum/EthPrivErc20MetaVault.md @@ -0,0 +1,178 @@ +--- +title: EthPrivErc20MetaVault +sidebar_position: 8 +description: "Defines the Meta Vault functionality with whitelist and ERC-20 token on Ethereum" +--- + +# EthPrivErc20MetaVault + +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/ethereum/EthPrivErc20MetaVault.sol) + +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [EthErc20MetaVault](./EthErc20MetaVault), [VaultWhitelist](../modules/VaultWhitelist), IEthPrivErc20MetaVault + +Defines the Meta Vault functionality with whitelist and ERC-20 token on Ethereum + + +## State Variables +### _version + +```solidity +uint8 private constant _version = 7 +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` + + +## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(EthErc20MetaVaultConstructorArgs memory args) EthErc20MetaVault(args); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`args`|`EthErc20MetaVaultConstructorArgs`|The arguments for initializing the EthErc20MetaVault contract| + + +### initialize + +Initializes or upgrades the EthErc20MetaVault contract. Must transfer security deposit during the deployment. + + +```solidity +function initialize(bytes calldata params) + external + payable + virtual + override(IEthErc20MetaVault, EthErc20MetaVault) + reinitializer(_version); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`bytes`|The encoded parameters for initializing the EthErc20MetaVault contract| + + +### deposit + +Deposit ETH to the Vault + + +```solidity +function deposit(address receiver, address referrer) + public + payable + virtual + override(IEthErc20MetaVault, EthErc20MetaVault) + returns (uint256 shares); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive Vault's shares| +|`referrer`|`address`|The address of the referrer. Set to zero address if not used.| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The number of shares minted| + + +### receive + +Function for depositing using fallback function + + +```solidity +receive() external payable virtual override; +``` + +### mintOsToken + +Mints OsToken shares + + +```solidity +function mintOsToken(address receiver, uint256 osTokenShares, address referrer) + public + virtual + override(IVaultOsToken, VaultOsToken) + returns (uint256 assets); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive the minted OsToken shares| +|`osTokenShares`|`uint256`|The number of OsToken shares to mint to the receiver. To mint the maximum amount of shares, use 2^256 - 1.| +|`referrer`|`address`|The address of the referrer| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`assets`|`uint256`|The number of assets minted to the receiver| + + +### vaultId + +Vault Unique Identifier + + +```solidity +function vaultId() public pure virtual override(IVaultVersion, EthErc20MetaVault) returns (bytes32); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bytes32`|The unique identifier of the Vault| + + +### version + +Version + + +```solidity +function version() public pure virtual override(IVaultVersion, EthErc20MetaVault) returns (uint8); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint8`|The version of the Vault implementation contract| + + +### _transfer + +Moves `amount` of tokens from `from` to `to`. +Emits a `Transfer` event. + + +```solidity +function _transfer(address from, address to, uint256 amount) internal virtual override; +``` diff --git a/docs/contracts/api/vaults/ethereum/EthPrivErc20Vault.md b/docs/contracts/api/vaults/ethereum/EthPrivErc20Vault.md index 69a8a61d..063ea30e 100644 --- a/docs/contracts/api/vaults/ethereum/EthPrivErc20Vault.md +++ b/docs/contracts/api/vaults/ethereum/EthPrivErc20Vault.md @@ -1,18 +1,78 @@ --- title: EthPrivErc20Vault -sidebar_position: 5 +sidebar_position: 9 description: "Ethereum staking vault with ERC-20 token and whitelist functionality" --- # EthPrivErc20Vault -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/ethereum/EthPrivErc20Vault.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/ethereum/EthPrivErc20Vault.sol) -**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [EthErc20Vault →](./EthErc20Vault), [VaultWhitelist →](../modules/VaultWhitelist), IEthPrivErc20Vault +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [EthErc20Vault](./EthErc20Vault), [VaultWhitelist](../modules/VaultWhitelist), IEthPrivErc20Vault + +Defines the Ethereum staking Vault with whitelist and ERC-20 token + + +## State Variables +### _version + +```solidity +uint8 private constant _version = 5 +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` -Defines the Ethereum staking Vault with whitelist and [ERC20 ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol) token. ## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(EthErc20VaultConstructorArgs memory args) EthErc20Vault(args); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`args`|`EthErc20VaultConstructorArgs`|The arguments for initializing the EthErc20Vault contract| + + +### initialize + +Initializes or upgrades the EthErc20Vault contract. Must transfer security deposit during the deployment. + + +```solidity +function initialize(bytes calldata params) + external + payable + virtual + override(IEthErc20Vault, EthErc20Vault) + reinitializer(_version); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`bytes`|The encoded parameters for initializing the EthErc20Vault contract| + ### vaultId @@ -105,3 +165,14 @@ function mintOsToken(address receiver, uint256 osTokenShares, address referrer) |Name|Type|Description| |----|----|-----------| |`assets`|`uint256`|The number of assets minted to the receiver| + + +### _transfer + +Moves `amount` of tokens from `from` to `to`. +Emits a `Transfer` event. + + +```solidity +function _transfer(address from, address to, uint256 amount) internal virtual override; +``` diff --git a/docs/contracts/api/vaults/ethereum/EthPrivMetaVault.md b/docs/contracts/api/vaults/ethereum/EthPrivMetaVault.md new file mode 100644 index 00000000..88a76b1a --- /dev/null +++ b/docs/contracts/api/vaults/ethereum/EthPrivMetaVault.md @@ -0,0 +1,167 @@ +--- +title: EthPrivMetaVault +sidebar_position: 10 +description: "Defines the Meta Vault functionality with whitelist on Ethereum" +--- + +# EthPrivMetaVault + +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/ethereum/EthPrivMetaVault.sol) + +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [EthMetaVault](./EthMetaVault), [VaultWhitelist](../modules/VaultWhitelist), IEthPrivMetaVault + +Defines the Meta Vault functionality with whitelist on Ethereum + + +## State Variables +### _version + +```solidity +uint8 private constant _version = 7 +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` + + +## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(EthMetaVaultConstructorArgs memory args) EthMetaVault(args); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`args`|`EthMetaVaultConstructorArgs`|The arguments for initializing the EthMetaVault contract| + + +### initialize + +Initializes or upgrades the EthMetaVault contract. Must transfer security deposit during the deployment. + + +```solidity +function initialize(bytes calldata params) + external + payable + virtual + override(IEthMetaVault, EthMetaVault) + reinitializer(_version); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`bytes`|The encoded parameters for initializing the EthMetaVault contract| + + +### deposit + +Deposit ETH to the Vault + + +```solidity +function deposit(address receiver, address referrer) + public + payable + virtual + override(IEthMetaVault, EthMetaVault) + returns (uint256 shares); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive Vault's shares| +|`referrer`|`address`|The address of the referrer. Set to zero address if not used.| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The number of shares minted| + + +### receive + +Function for depositing using fallback function + + +```solidity +receive() external payable virtual override; +``` + +### mintOsToken + +Mints OsToken shares + + +```solidity +function mintOsToken(address receiver, uint256 osTokenShares, address referrer) + public + virtual + override(IVaultOsToken, VaultOsToken) + returns (uint256 assets); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive the minted OsToken shares| +|`osTokenShares`|`uint256`|The number of OsToken shares to mint to the receiver. To mint the maximum amount of shares, use 2^256 - 1.| +|`referrer`|`address`|The address of the referrer| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`assets`|`uint256`|The number of assets minted to the receiver| + + +### vaultId + +Vault Unique Identifier + + +```solidity +function vaultId() public pure virtual override(IVaultVersion, EthMetaVault) returns (bytes32); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bytes32`|The unique identifier of the Vault| + + +### version + +Version + + +```solidity +function version() public pure virtual override(IVaultVersion, EthMetaVault) returns (uint8); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint8`|The version of the Vault implementation contract| diff --git a/docs/contracts/api/vaults/ethereum/EthPrivVault.md b/docs/contracts/api/vaults/ethereum/EthPrivVault.md index 8d5f9d1f..4fe996dd 100644 --- a/docs/contracts/api/vaults/ethereum/EthPrivVault.md +++ b/docs/contracts/api/vaults/ethereum/EthPrivVault.md @@ -1,18 +1,78 @@ --- title: EthPrivVault -sidebar_position: 6 +sidebar_position: 11 description: "Ethereum staking vault with whitelist functionality" --- # EthPrivVault -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/ethereum/EthPrivVault.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/ethereum/EthPrivVault.sol) -**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [EthVault →](./EthVault), [VaultWhitelist →](../modules/VaultWhitelist), IEthPrivVault +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [EthVault](./EthVault), [VaultWhitelist](../modules/VaultWhitelist), IEthPrivVault + +Defines the Ethereum staking Vault with whitelist + + +## State Variables +### _version + +```solidity +uint8 private constant _version = 5 +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` -Defines the Ethereum staking Vault with whitelist. ## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(EthVaultConstructorArgs memory args) EthVault(args); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`args`|`EthVaultConstructorArgs`|The arguments for initializing the EthVault contract| + + +### initialize + +Initializes or upgrades the EthVault contract. Must transfer security deposit during the deployment. + + +```solidity +function initialize(bytes calldata params) + external + payable + virtual + override(IEthVault, EthVault) + reinitializer(_version); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`bytes`|The encoded parameters for initializing the EthVault contract| + ### deposit diff --git a/docs/contracts/api/vaults/ethereum/EthVault.md b/docs/contracts/api/vaults/ethereum/EthVault.md index 18268707..ed0e04c8 100644 --- a/docs/contracts/api/vaults/ethereum/EthVault.md +++ b/docs/contracts/api/vaults/ethereum/EthVault.md @@ -1,78 +1,84 @@ --- title: EthVault -sidebar_position: 7 +sidebar_position: 12 description: "Core Ethereum staking vault implementation" --- # EthVault -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/ethereum/EthVault.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/ethereum/EthVault.sol) -**Inherits:** [VaultImmutables →](../modules/VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [VaultAdmin →](../modules/VaultAdmin), [VaultVersion →](../modules/VaultVersion), [VaultFee →](../modules/VaultFee), [VaultState →](../modules/VaultState), [VaultValidators →](../modules/VaultValidators), [VaultEnterExit →](../modules/VaultEnterExit), [VaultOsToken →](../modules/VaultOsToken), [VaultMev →](../modules/VaultMev), [VaultEthStaking →](../modules/VaultEthStaking), [Multicall →](../../base/Multicall), IEthVault +**Inherits:** [VaultImmutables](../modules/VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [VaultAdmin](../modules/VaultAdmin), [VaultVersion](../modules/VaultVersion), [VaultFee](../modules/VaultFee), [VaultState](../modules/VaultState), [VaultValidators](../modules/VaultValidators), [VaultEnterExit](../modules/VaultEnterExit), [VaultOsToken](../modules/VaultOsToken), [VaultMev](../modules/VaultMev), [VaultEthStaking](../modules/VaultEthStaking), [Multicall](../../base/Multicall), IEthVault -Defines the Ethereum staking Vault. +Defines the Ethereum staking Vault -## Structs -### EthVaultConstructorArgs -Struct for deploying the EthVault contract +## State Variables +### _version ```solidity -struct EthVaultConstructorArgs { - address keeper; - address vaultsRegistry; - address validatorsRegistry; - address validatorsWithdrawals; - address validatorsConsolidations; - address consolidationsChecker; - address osTokenVaultController; - address osTokenConfig; - address osTokenVaultEscrow; - address sharedMevEscrow; - address depositDataRegistry; - uint64 exitingAssetsClaimDelay; -} +uint8 private constant _version = 5 ``` -**Properties** -|Name|Type|Description| -|----|----|-----------| -|`keeper`|`address`|The address of the Keeper contract| -|`vaultsRegistry`|`address`|The address of the VaultsRegistry contract| -|`validatorsRegistry`|`address`|The contract address used for registering validators in beacon chain| -|`validatorsWithdrawals`|`address`|The contract address used for withdrawing validators in beacon chain| -|`validatorsConsolidations`|`address`|The contract address used for consolidating validators in beacon chain| -|`consolidationsChecker`|`address`|The contract address used for checking consolidations| -|`osTokenVaultController`|`address`|The address of the OsTokenVaultController contract| -|`osTokenConfig`|`address`|The address of the OsTokenConfig contract| -|`osTokenVaultEscrow`|`address`|The address of the OsTokenVaultEscrow contract| -|`sharedMevEscrow`|`address`|The address of the shared MEV escrow| -|`depositDataRegistry`|`address`|The address of the DepositDataRegistry contract| -|`exitingAssetsClaimDelay`|`uint64`|The delay after which the assets can be claimed after exiting from staking| - -### EthVaultInitParams -Struct for initializing the EthVault contract +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps ```solidity -struct EthVaultInitParams { - uint256 capacity; - uint16 feePercent; - string metadataIpfsHash; -} +uint256[50] private __gap ``` -**Properties** + +## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(EthVaultConstructorArgs memory args) + VaultImmutables(args.keeper, args.vaultsRegistry) + VaultValidators( + args.depositDataRegistry, + args.validatorsRegistry, + args.validatorsWithdrawals, + args.validatorsConsolidations, + args.consolidationsChecker + ) + VaultEnterExit(args.exitingAssetsClaimDelay) + VaultOsToken(args.osTokenVaultController, args.osTokenConfig, args.osTokenVaultEscrow) + VaultMev(args.sharedMevEscrow); +``` +**Parameters** |Name|Type|Description| |----|----|-----------| -|`capacity`|`uint256`|The Vault stops accepting deposits after exceeding the capacity| -|`feePercent`|`uint16`|The fee percent that is charged by the Vault| -|`metadataIpfsHash`|`string`|The IPFS hash of the Vault's metadata file| +|`args`|`EthVaultConstructorArgs`|The arguments for initializing the EthVault contract| -## Functions +### initialize + +Initializes or upgrades the EthVault contract. Must transfer security deposit during the deployment. + + +```solidity +function initialize(bytes calldata params) external payable virtual override reinitializer(_version); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`bytes`|The encoded parameters for initializing the EthVault contract| + ### depositAndMintOsToken @@ -134,6 +140,8 @@ function updateStateAndDepositAndMintOsToken( Locks shares to the exit queue. The shares continue earning rewards until they will be burned by the Vault. +For ERC-20 vault variants, balanceOf(vault) does not reflect queued exit shares despite the emitted Transfer event. + ```solidity function enterExitQueue(uint256 shares, address receiver) @@ -156,6 +164,22 @@ function enterExitQueue(uint256 shares, address receiver) |`positionTicket`|`uint256`|The position ticket of the exit queue. Returns uint256 max if no ticket created.| +### donateShares + +Donates shares to the Vault by burning them from the caller, +increasing the value per share for remaining holders + + +```solidity +function donateShares(uint256 shares) public virtual override(IVaultState, VaultState, VaultOsToken); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The number of shares to donate| + + ### vaultId @@ -176,3 +200,31 @@ function version() public pure virtual override(IVaultVersion, VaultVersion) ret |Name|Type|Description| |----|----|-----------| |``|`uint8`|The version of the Vault implementation contract| + + +### __EthVault_upgrade + +Upgrades the EthVault contract + + +```solidity +function __EthVault_upgrade() internal; +``` + +### __EthVault_init + +Initializes the EthVault contract + + +```solidity +function __EthVault_init(address admin, address ownMevEscrow, EthVaultInitParams memory params) + internal + onlyInitializing; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`admin`|`address`|The address of the admin of the Vault| +|`ownMevEscrow`|`address`|The address of the MEV escrow owned by the Vault. Zero address if shared MEV escrow is used.| +|`params`|`EthVaultInitParams`|The decoded parameters for initializing the EthVault contract| diff --git a/docs/contracts/api/vaults/ethereum/EthVaultFactory.md b/docs/contracts/api/vaults/ethereum/EthVaultFactory.md index 3a19800a..d6176b1e 100644 --- a/docs/contracts/api/vaults/ethereum/EthVaultFactory.md +++ b/docs/contracts/api/vaults/ethereum/EthVaultFactory.md @@ -1,81 +1,68 @@ --- title: EthVaultFactory -sidebar_position: 8 +sidebar_position: 13 description: "Factory for deploying Ethereum staking vaults" --- # EthVaultFactory -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/ethereum/EthVaultFactory.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/ethereum/EthVaultFactory.sol) **Inherits:** IEthVaultFactory -Factory for deploying Ethereum staking Vaults. +Factory for deploying Ethereum staking Vaults -## Events -### VaultCreated -Event emitted on a Vault creation - +## State Variables +### _vaultsRegistry ```solidity -event VaultCreated(address indexed admin, address indexed vault, address ownMevEscrow, bytes params); +IVaultsRegistry internal immutable _vaultsRegistry ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`admin`|`address`|The address of the Vault admin| -|`vault`|`address`|The address of the created Vault| -|`ownMevEscrow`|`address`|The address of the own MEV escrow contract. Zero address if shared MEV escrow is used.| -|`params`|`bytes`|The encoded parameters for initializing the Vault contract| - -## Functions ### implementation - The address of the Vault implementation contract used for proxy creation ```solidity -function implementation() external view returns (address); +address public immutable override implementation ``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`address`|The address of the Vault implementation contract| ### ownMevEscrow - The address of the own MEV escrow contract used for Vault creation ```solidity -function ownMevEscrow() external view returns (address); +address public override ownMevEscrow ``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`address`|The address of the MEV escrow contract| ### vaultAdmin - The address of the Vault admin used for Vault creation ```solidity -function vaultAdmin() external view returns (address); +address public override vaultAdmin ``` -**Returns** + + +## Functions +### constructor + +Constructor + + +```solidity +constructor(address _implementation, IVaultsRegistry vaultsRegistry) ; +``` +**Parameters** |Name|Type|Description| |----|----|-----------| -|``|`address`|The address of the Vault admin| +|`_implementation`|`address`|The implementation address of Vault| +|`vaultsRegistry`|`IVaultsRegistry`|The address of the VaultsRegistry contract| ### createVault diff --git a/docs/contracts/api/vaults/ethereum/custom/EthCommunityVault.md b/docs/contracts/api/vaults/ethereum/custom/EthCommunityVault.md new file mode 100644 index 00000000..f3221fdb --- /dev/null +++ b/docs/contracts/api/vaults/ethereum/custom/EthCommunityVault.md @@ -0,0 +1,144 @@ +--- +title: EthCommunityVault +sidebar_position: 1 +description: "Defines the Ethereum staking Vault with ERC-20 token and NodesManager as fee recipient and validators manager." +--- + +# EthCommunityVault + +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/ethereum/custom/EthCommunityVault.sol) + +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [EthErc20Vault](../EthErc20Vault), IEthCommunityVault + +Defines the Ethereum staking Vault with ERC-20 token and NodesManager as fee recipient and validators manager. + + +## State Variables +### _version + +```solidity +uint8 private constant _version = 6 +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` + + +## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy's storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(EthErc20VaultConstructorArgs memory args) EthErc20Vault(args); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`args`|`EthErc20VaultConstructorArgs`|The arguments for initializing the EthErc20Vault contract| + + +### initialize + +Initializes or upgrades the EthErc20Vault contract. Must transfer security deposit during the deployment. + + +```solidity +function initialize(bytes calldata params) + external + payable + virtual + override(IEthErc20Vault, EthErc20Vault) + reinitializer(_version); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`bytes`|The encoded parameters for initializing the EthErc20Vault contract| + + +### setFeeRecipient + +Function for updating the fee recipient address. Can only be called by the admin. + + +```solidity +function setFeeRecipient(address) external virtual override(IVaultFee, VaultFee); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|``|`address`|| + + +### _setFeeRecipient + +No-op: prevents __VaultFee_init from setting feeRecipient to admin. +The fee recipient is set directly to nodesManager in initialize() and cannot be changed later. + + +```solidity +function _setFeeRecipient(address) internal virtual override; +``` + +### setValidatorsManager + +Function for updating the validators manager. Can only be called by the admin. Default is the DepositDataRegistry contract. + + +```solidity +function setValidatorsManager(address) external virtual override(IVaultValidators, VaultValidators); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|``|`address`|| + + +### vaultId + +Vault Unique Identifier + + +```solidity +function vaultId() public pure virtual override(IVaultVersion, EthErc20Vault) returns (bytes32); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bytes32`|The unique identifier of the Vault| + + +### version + +Version + + +```solidity +function version() public pure virtual override(IVaultVersion, EthErc20Vault) returns (uint8); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint8`|The version of the Vault implementation contract| diff --git a/docs/contracts/api/vaults/ethereum/custom/EthFoxVault.md b/docs/contracts/api/vaults/ethereum/custom/EthFoxVault.md index 1ae10353..1544e606 100644 --- a/docs/contracts/api/vaults/ethereum/custom/EthFoxVault.md +++ b/docs/contracts/api/vaults/ethereum/custom/EthFoxVault.md @@ -1,114 +1,83 @@ --- title: EthFoxVault -sidebar_position: 1 +sidebar_position: 2 description: "Custom Ethereum non-ERC20 vault with blocklist, own MEV and without osToken minting" --- # EthFoxVault -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/ethereum/custom/EthFoxVault.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/ethereum/custom/EthFoxVault.sol) -**Inherits:** [VaultImmutables →](../../modules/VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [VaultAdmin →](../../modules/VaultAdmin), [VaultVersion →](../../modules/VaultVersion), [VaultFee →](../../modules/VaultFee), [VaultState →](../../modules/VaultState), [VaultValidators →](../../modules/VaultValidators), [VaultEnterExit →](../../modules/VaultEnterExit), [VaultMev →](../../modules/VaultMev), [VaultEthStaking →](../../modules/VaultEthStaking), [VaultBlocklist →](../../modules/VaultBlocklist), [Multicall →](../../../base/Multicall), IEthFoxVault +**Inherits:** [VaultImmutables](../../modules/VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [VaultAdmin](../../modules/VaultAdmin), [VaultVersion](../../modules/VaultVersion), [VaultFee](../../modules/VaultFee), [VaultState](../../modules/VaultState), [VaultValidators](../../modules/VaultValidators), [VaultEnterExit](../../modules/VaultEnterExit), [VaultMev](../../modules/VaultMev), [VaultEthStaking](../../modules/VaultEthStaking), [VaultBlocklist](../../modules/VaultBlocklist), [Multicall](../../../base/Multicall), IEthFoxVault Custom Ethereum non-ERC20 vault with blocklist, own MEV and without osToken minting. -## Structs -### EthFoxVaultConstructorArgs -Struct for deploying the EthFoxVault contract - +## State Variables +### _version ```solidity -struct EthFoxVaultConstructorArgs { - address keeper; - address vaultsRegistry; - address validatorsRegistry; - address validatorsWithdrawals; - address validatorsConsolidations; - address consolidationsChecker; - address sharedMevEscrow; - address depositDataRegistry; - uint64 exitingAssetsClaimDelay; -} +uint8 private constant _version = 2 ``` -**Properties** - -|Name|Type|Description| -|----|----|-----------| -|`keeper`|`address`|The address of the Keeper contract| -|`vaultsRegistry`|`address`|The address of the VaultsRegistry contract| -|`validatorsRegistry`|`address`|The contract address used for registering validators in beacon chain| -|`validatorsWithdrawals`|`address`|The contract address used for withdrawing validators in beacon chain| -|`validatorsConsolidations`|`address`|The contract address used for consolidating validators in beacon chain| -|`consolidationsChecker`|`address`|The contract address used for checking consolidations| -|`sharedMevEscrow`|`address`|The address of the shared MEV escrow| -|`depositDataRegistry`|`address`|The address of the DepositDataRegistry contract| -|`exitingAssetsClaimDelay`|`uint64`|The delay after which the assets can be claimed after exiting from staking| -### EthFoxVaultInitParams -Struct for initializing the EthFoxVault contract +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps ```solidity -struct EthFoxVaultInitParams { - address admin; - address ownMevEscrow; - uint256 capacity; - uint16 feePercent; - string metadataIpfsHash; -} +uint256[50] private __gap ``` -**Properties** -|Name|Type|Description| -|----|----|-----------| -|`admin`|`address`|The address of the Vault admin| -|`ownMevEscrow`|`address`|The address of the MEV escrow contract| -|`capacity`|`uint256`|The Vault stops accepting deposits after exceeding the capacity| -|`feePercent`|`uint16`|The fee percent that is charged by the Vault| -|`metadataIpfsHash`|`string`|The IPFS hash of the Vault's metadata file| +## Functions +### constructor +Constructor -## Events -### UserEjected -Event emitted when a user is ejected from the Vault +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor ```solidity -event UserEjected(address user, uint256 shares); +constructor(EthFoxVaultConstructorArgs memory args) + VaultImmutables(args.keeper, args.vaultsRegistry) + VaultValidators( + args.depositDataRegistry, + args.validatorsRegistry, + args.validatorsWithdrawals, + args.validatorsConsolidations, + args.consolidationsChecker + ) + VaultEnterExit(args.exitingAssetsClaimDelay) + VaultMev(args.sharedMevEscrow); ``` - **Parameters** |Name|Type|Description| |----|----|-----------| -|`user`|`address`|The address of the user| -|`shares`|`uint256`|The amount of shares ejected| +|`args`|`EthFoxVaultConstructorArgs`|The arguments for initializing the EthFoxVault contract| + + +### initialize -### EthFoxVaultCreated -Event emitted on EthFoxVault creation +Initializes or upgrades the EthFoxVault contract. Must transfer security deposit during the deployment. ```solidity -event EthFoxVaultCreated( - address admin, address ownMevEscrow, uint256 capacity, uint16 feePercent, string metadataIpfsHash -); +function initialize(bytes calldata) external payable virtual override reinitializer(_version); ``` - **Parameters** |Name|Type|Description| |----|----|-----------| -|`admin`|`address`|The address of the Vault admin| -|`ownMevEscrow`|`address`|The address of the MEV escrow contract| -|`capacity`|`uint256`|The capacity of the Vault| -|`feePercent`|`uint16`|The fee percent of the Vault| -|`metadataIpfsHash`|`string`|The IPFS hash of the Vault metadata| - +|``|`bytes`|| -## Functions ### deposit diff --git a/docs/contracts/api/vaults/ethereum/custom/EthMetaVault.md b/docs/contracts/api/vaults/ethereum/custom/EthMetaVault.md deleted file mode 100644 index c8820adb..00000000 --- a/docs/contracts/api/vaults/ethereum/custom/EthMetaVault.md +++ /dev/null @@ -1,261 +0,0 @@ ---- -title: EthMetaVault -sidebar_position: 2 -description: "Meta vault that delegates stake to sub-vaults on Ethereum" ---- - -# EthMetaVault - -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/ethereum/custom/EthMetaVault.sol) - -**Inherits:** [VaultImmutables →](../../modules/VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [VaultAdmin →](../../modules/VaultAdmin), [VaultVersion →](../../modules/VaultVersion), [VaultFee →](../../modules/VaultFee), [VaultState →](../../modules/VaultState), [VaultEnterExit →](../../modules/VaultEnterExit), [VaultOsToken →](../../modules/VaultOsToken), [VaultSubVaults →](../../modules/VaultSubVaults), [Multicall →](../../../base/Multicall), IEthMetaVault - -Defines the Meta Vault that delegates stake to the sub vaults on Ethereum. - -## Structs -### EthMetaVaultConstructorArgs -Struct for deploying the EthMetaVault contract - - -```solidity -struct EthMetaVaultConstructorArgs { - address keeper; - address vaultsRegistry; - address osTokenVaultController; - address osTokenConfig; - address osTokenVaultEscrow; - address curatorsRegistry; - uint64 exitingAssetsClaimDelay; -} -``` - -**Properties** - -|Name|Type|Description| -|----|----|-----------| -|`keeper`|`address`|The address of the Keeper contract| -|`vaultsRegistry`|`address`|The address of the VaultsRegistry contract| -|`osTokenVaultController`|`address`|The address of the OsTokenVaultController contract| -|`osTokenConfig`|`address`|The address of the OsTokenConfig contract| -|`osTokenVaultEscrow`|`address`|The address of the OsTokenVaultEscrow contract| -|`curatorsRegistry`|`address`|The address of the CuratorsRegistry contract| -|`exitingAssetsClaimDelay`|`uint64`|The delay after which the assets can be claimed after exiting from staking| - -### EthMetaVaultInitParams -Struct for initializing the EthMetaVault contract - - -```solidity -struct EthMetaVaultInitParams { - address subVaultsCurator; - uint256 capacity; - uint16 feePercent; - string metadataIpfsHash; -} -``` - -**Properties** - -|Name|Type|Description| -|----|----|-----------| -|`subVaultsCurator`|`address`|The address of the initial sub-vaults curator| -|`capacity`|`uint256`|The Vault stops accepting deposits after exceeding the capacity| -|`feePercent`|`uint16`|The fee percent that is charged by the Vault| -|`metadataIpfsHash`|`string`|The IPFS hash of the Vault's metadata file| - - -## Functions - -### isStateUpdateRequired - -Check whether state update is required - - -```solidity -function isStateUpdateRequired() public view override(IVaultState, VaultState, VaultSubVaults) returns (bool); -``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`bool`|`true` if state update is required, `false` otherwise| - - -### deposit - -Deposit ETH to the Vault - - -```solidity -function deposit(address receiver, address referrer) public payable virtual override returns (uint256 shares); -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`receiver`|`address`|The address that will receive Vault's shares| -|`referrer`|`address`|The address of the referrer. Set to zero address if not used.| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|`shares`|`uint256`|The number of shares minted| - - -### receive - -Function for depositing using fallback function - - -```solidity -receive() external payable virtual; -``` - -### updateState - - -```solidity -function updateState(IKeeperRewards.HarvestParams calldata harvestParams) - public - override(IVaultState, VaultState, VaultSubVaults); -``` - -### enterExitQueue - -Locks shares to the exit queue. The shares continue earning rewards until they will be burned by the Vault. - - -```solidity -function enterExitQueue(uint256 shares, address receiver) - public - virtual - override(IVaultEnterExit, VaultEnterExit, VaultOsToken) - returns (uint256 positionTicket); -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`shares`|`uint256`|The number of shares to lock| -|`receiver`|`address`|The address that will receive assets upon withdrawal| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|`positionTicket`|`uint256`|The position ticket of the exit queue. Returns uint256 max if no ticket created.| - - -### updateStateAndDeposit - -Updates Vault state and deposits ETH to the Vault - - -```solidity -function updateStateAndDeposit( - address receiver, - address referrer, - IKeeperRewards.HarvestParams calldata harvestParams -) public payable virtual override returns (uint256 shares); -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`receiver`|`address`|The address that will receive Vault's shares| -|`referrer`|`address`|The address of the referrer. Set to zero address if not used.| -|`harvestParams`|`IKeeperRewards.HarvestParams`|The parameters for harvesting Keeper rewards| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|`shares`|`uint256`|The number of shares minted| - - -### depositAndMintOsToken - -Deposits assets to the vault and mints OsToken shares to the receiver - - -```solidity -function depositAndMintOsToken(address receiver, uint256 osTokenShares, address referrer) - public - payable - override - returns (uint256); -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`receiver`|`address`|The address to receive the OsToken| -|`osTokenShares`|`uint256`|The amount of OsToken shares to mint. If set to type(uint256).max, max OsToken shares will be minted.| -|`referrer`|`address`|The address of the referrer| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`uint256`|The amount of OsToken assets minted| - - -### updateStateAndDepositAndMintOsToken - -Updates the state, deposits assets to the vault and mints OsToken shares to the receiver - - -```solidity -function updateStateAndDepositAndMintOsToken( - address receiver, - uint256 osTokenShares, - address referrer, - IKeeperRewards.HarvestParams calldata harvestParams -) external payable override returns (uint256); -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`receiver`|`address`|The address to receive the OsToken| -|`osTokenShares`|`uint256`|The amount of OsToken shares to mint. If set to type(uint256).max, max OsToken shares will be minted.| -|`referrer`|`address`|The address of the referrer| -|`harvestParams`|`IKeeperRewards.HarvestParams`|The parameters for the harvest| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`uint256`|The amount of OsToken assets minted| - - -### donateAssets - -Donate assets to the Vault. Must transfer ETH together with the call. - - -```solidity -function donateAssets() external payable override; -``` - -### vaultId - - -```solidity -function vaultId() public pure virtual override(IVaultVersion, VaultVersion) returns (bytes32); -``` - -### version - -Version - - -```solidity -function version() public pure virtual override(IVaultVersion, VaultVersion) returns (uint8); -``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`uint8`|The version of the Vault implementation contract| diff --git a/docs/contracts/api/vaults/ethereum/custom/EthMetaVaultFactory.md b/docs/contracts/api/vaults/ethereum/custom/EthMetaVaultFactory.md deleted file mode 100644 index adda5574..00000000 --- a/docs/contracts/api/vaults/ethereum/custom/EthMetaVaultFactory.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -title: EthMetaVaultFactory -sidebar_position: 3 -description: "Factory for deploying Ethereum meta vaults" ---- - -# EthMetaVaultFactory - -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/ethereum/custom/EthMetaVaultFactory.sol) - -**Inherits:** [Ownable2Step ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol), IEthMetaVaultFactory - -Factory for deploying Ethereum meta Vaults. - - -## Events -### MetaVaultCreated -Event emitted on a MetaVault creation - - -```solidity -event MetaVaultCreated(address indexed caller, address indexed admin, address indexed vault, bytes params); -``` - -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the factory caller| -|`admin`|`address`|The address of the Vault admin| -|`vault`|`address`|The address of the created Vault| -|`params`|`bytes`|The encoded parameters for initializing the Vault contract| - - -## Functions - -### implementation - -The address of the Vault implementation contract used for proxy creation - - -```solidity -function implementation() external view returns (address); -``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`address`|The address of the Vault implementation contract| - - -### vaultAdmin - -The address of the Vault admin used for Vault creation - - -```solidity -function vaultAdmin() external view returns (address); -``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`address`|The address of the Vault admin| - - -### createVault - -Create Vault. Must transfer security deposit together with a call. - - -```solidity -function createVault(address admin, bytes calldata params) - external - payable - override - onlyOwner - returns (address vault); -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`admin`|`address`|The address of the Vault admin| -|`params`|`bytes`|The encoded parameters for initializing the Vault contract| diff --git a/docs/contracts/api/vaults/ethereum/custom/_category_.json b/docs/contracts/api/vaults/ethereum/custom/_category_.json new file mode 100644 index 00000000..5f112be7 --- /dev/null +++ b/docs/contracts/api/vaults/ethereum/custom/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Custom", + "position": 1, + "collapsed": true +} diff --git a/docs/contracts/api/vaults/ethereum/mev/OwnMevEscrow.md b/docs/contracts/api/vaults/ethereum/mev/OwnMevEscrow.md index a15e1c1d..fc6d1211 100644 --- a/docs/contracts/api/vaults/ethereum/mev/OwnMevEscrow.md +++ b/docs/contracts/api/vaults/ethereum/mev/OwnMevEscrow.md @@ -6,69 +6,39 @@ description: "MEV escrow owned by individual vault" # OwnMevEscrow -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/ethereum/mev/OwnMevEscrow.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/ethereum/mev/OwnMevEscrow.sol) **Inherits:** IOwnMevEscrow Accumulates received MEV. The escrow is owned by the Vault. -## Events -### MevReceived -Event emitted on received MEV - - -```solidity -event MevReceived(uint256 assets); -``` - -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`assets`|`uint256`|The amount of MEV assets received| - -### Harvested -Event emitted on harvest - +## State Variables +### vault ```solidity -event Harvested(uint256 assets); +address payable public immutable override vault ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`assets`|`uint256`|The amount of assets withdrawn| - ## Functions +### constructor -### vault - -Vault address +Constructor ```solidity -function vault() external view returns (address payable); +constructor(address _vault) ; ``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`address payable`|The address of the vault that owns the escrow| - ### harvest Withdraws MEV accumulated in the escrow. Can be called only by the Vault. -:::custom-warning[Important] -Because control is transferred to the Vault, care must be +IMPORTANT: because control is transferred to the Vault, care must be taken to not create reentrancy vulnerabilities. The Vault must follow the checks-effects-interactions pattern: https://docs.soliditylang.org/en/v0.8.22/security-considerations.html#use-the-checks-effects-interactions-pattern -::: + ```solidity function harvest() external returns (uint256 assets); diff --git a/docs/contracts/api/vaults/ethereum/mev/SharedMevEscrow.md b/docs/contracts/api/vaults/ethereum/mev/SharedMevEscrow.md index 87bd38d7..bb946bef 100644 --- a/docs/contracts/api/vaults/ethereum/mev/SharedMevEscrow.md +++ b/docs/contracts/api/vaults/ethereum/mev/SharedMevEscrow.md @@ -6,56 +6,39 @@ description: "Shared MEV escrow for multiple vaults" # SharedMevEscrow -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/ethereum/mev/SharedMevEscrow.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/ethereum/mev/SharedMevEscrow.sol) **Inherits:** ISharedMevEscrow Accumulates received MEV. The rewards are shared by multiple Vaults. -## Events -### MevReceived -Event emitted on received MEV - +## State Variables +### _vaultsRegistry ```solidity -event MevReceived(uint256 assets); +IVaultsRegistry private immutable _vaultsRegistry ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`assets`|`uint256`|The amount of MEV assets received| +## Functions +### constructor -### Harvested -Event emitted on harvest +Constructor ```solidity -event Harvested(address indexed caller, uint256 assets); +constructor(address vaultsRegistry) ; ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The function caller| -|`assets`|`uint256`|The amount of assets withdrawn| - - -## Functions - ### harvest Withdraws MEV accumulated in the escrow. Can be called only by the Vault. - -:::custom-warning[Important] IMPORTANT: because control is transferred to the Vault, care must be taken to not create reentrancy vulnerabilities. The Vault must follow the checks-effects-interactions pattern: https://docs.soliditylang.org/en/v0.8.22/security-considerations.html#use-the-checks-effects-interactions-pattern -::: + ```solidity function harvest(uint256 assets) external override; diff --git a/docs/contracts/api/vaults/gnosis/GnoBlocklistErc20Vault.md b/docs/contracts/api/vaults/gnosis/GnoBlocklistErc20Vault.md index 3a5bd354..c9ddf65c 100644 --- a/docs/contracts/api/vaults/gnosis/GnoBlocklistErc20Vault.md +++ b/docs/contracts/api/vaults/gnosis/GnoBlocklistErc20Vault.md @@ -6,13 +6,72 @@ description: "Gnosis staking vault with blocklist and ERC-20 token functionality # GnoBlocklistErc20Vault -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/gnosis/GnoBlocklistErc20Vault.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/gnosis/GnoBlocklistErc20Vault.sol) -**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [GnoErc20Vault →](./GnoErc20Vault), [VaultBlocklist →](../modules/VaultBlocklist), IGnoBlocklistErc20Vault +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [GnoErc20Vault](./GnoErc20Vault), [VaultBlocklist](../modules/VaultBlocklist), IGnoBlocklistErc20Vault + +Defines the Gnosis staking Vault with blocking and ERC-20 functionality + + +## State Variables +### _version + +```solidity +uint8 private constant _version = 3 +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` -Defines the Gnosis staking Vault with blocking and [ERC20 ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol) functionality. ## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(GnoErc20VaultConstructorArgs memory args) GnoErc20Vault(args); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`args`|`GnoErc20VaultConstructorArgs`|The arguments for initializing the GnoErc20Vault contract| + + +### initialize + +Initializes or upgrades the GnoErc20Vault contract. Must transfer security deposit during the deployment. + + +```solidity +function initialize(bytes calldata params) + external + virtual + override(IGnoErc20Vault, GnoErc20Vault) + reinitializer(_version); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`bytes`|The encoded parameters for initializing the GnoErc20Vault contract| + ### deposit @@ -96,3 +155,14 @@ function version() public pure virtual override(IVaultVersion, GnoErc20Vault) re |Name|Type|Description| |----|----|-----------| |``|`uint8`|The version of the Vault implementation contract| + + +### _transfer + +Moves `amount` of tokens from `from` to `to`. +Emits a `Transfer` event. + + +```solidity +function _transfer(address from, address to, uint256 amount) internal virtual override; +``` diff --git a/docs/contracts/api/vaults/gnosis/GnoBlocklistVault.md b/docs/contracts/api/vaults/gnosis/GnoBlocklistVault.md index d2d17689..2438def1 100644 --- a/docs/contracts/api/vaults/gnosis/GnoBlocklistVault.md +++ b/docs/contracts/api/vaults/gnosis/GnoBlocklistVault.md @@ -6,13 +6,68 @@ description: "Gnosis staking vault with blocklist functionality" # GnoBlocklistVault -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/gnosis/GnoBlocklistVault.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/gnosis/GnoBlocklistVault.sol) -**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [GnoVault →](./GnoVault), [VaultBlocklist →](../modules/VaultBlocklist), IGnoBlocklistVault +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [GnoVault](./GnoVault), [VaultBlocklist](../modules/VaultBlocklist), IGnoBlocklistVault + +Defines the Gnosis staking Vault with blocking addresses functionality + + +## State Variables +### _version + +```solidity +uint8 private constant _version = 3 +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` -Defines the Gnosis staking Vault with blocking addresses functionality. ## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(GnoVaultConstructorArgs memory args) GnoVault(args); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`args`|`GnoVaultConstructorArgs`|The arguments for initializing the GnoVault contract| + + +### initialize + +Initializes or upgrades the GnoVault contract. Must transfer security deposit during the deployment. + + +```solidity +function initialize(bytes calldata params) external virtual override(IGnoVault, GnoVault) reinitializer(_version); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`bytes`|The encoded parameters for initializing the GnoVault contract| + ### deposit diff --git a/docs/contracts/api/vaults/gnosis/GnoErc20Vault.md b/docs/contracts/api/vaults/gnosis/GnoErc20Vault.md index bc19a19e..6368df6a 100644 --- a/docs/contracts/api/vaults/gnosis/GnoErc20Vault.md +++ b/docs/contracts/api/vaults/gnosis/GnoErc20Vault.md @@ -6,83 +6,81 @@ description: "Gnosis staking vault with ERC-20 token" # GnoErc20Vault -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/gnosis/GnoErc20Vault.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/gnosis/GnoErc20Vault.sol) -**Inherits:** [VaultImmutables →](../modules/VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [VaultAdmin →](../modules/VaultAdmin), [VaultVersion →](../modules/VaultVersion), [VaultFee →](../modules/VaultFee), [VaultState →](../modules/VaultState), [VaultValidators →](../modules/VaultValidators), [VaultEnterExit →](../modules/VaultEnterExit), [VaultOsToken →](../modules/VaultOsToken), [VaultMev →](../modules/VaultMev), [VaultToken →](../modules/VaultToken), [VaultGnoStaking →](../modules/VaultGnoStaking), [Multicall →](../../base/Multicall), IGnoErc20Vault +**Inherits:** [VaultImmutables](../modules/VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [VaultAdmin](../modules/VaultAdmin), [VaultVersion](../modules/VaultVersion), [VaultFee](../modules/VaultFee), [VaultState](../modules/VaultState), [VaultValidators](../modules/VaultValidators), [VaultEnterExit](../modules/VaultEnterExit), [VaultOsToken](../modules/VaultOsToken), [VaultMev](../modules/VaultMev), [VaultToken](../modules/VaultToken), [VaultGnoStaking](../modules/VaultGnoStaking), [Multicall](../../base/Multicall), IGnoErc20Vault -Defines the Gnosis staking Vault with [ERC20 ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol) token. +Defines the Gnosis staking Vault with ERC-20 token -## Structs -### GnoErc20VaultConstructorArgs -Struct for initializing the GnoErc20Vault contract +## State Variables +### _version + +```solidity +uint8 private constant _version = 3 +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps ```solidity -struct GnoErc20VaultConstructorArgs { - address keeper; - address vaultsRegistry; - address validatorsRegistry; - address validatorsWithdrawals; - address validatorsConsolidations; - address consolidationsChecker; - address osTokenVaultController; - address osTokenConfig; - address osTokenVaultEscrow; - address sharedMevEscrow; - address depositDataRegistry; - address gnoToken; - address tokensConverterFactory; - uint256 exitingAssetsClaimDelay; -} +uint256[50] private __gap ``` -**Properties** + +## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(GnoErc20VaultConstructorArgs memory args) + VaultImmutables(args.keeper, args.vaultsRegistry) + VaultValidators( + args.depositDataRegistry, + args.validatorsRegistry, + args.validatorsWithdrawals, + args.validatorsConsolidations, + args.consolidationsChecker + ) + VaultEnterExit(args.exitingAssetsClaimDelay) + VaultOsToken(args.osTokenVaultController, args.osTokenConfig, args.osTokenVaultEscrow) + VaultMev(args.sharedMevEscrow) + VaultGnoStaking(args.gnoToken, args.tokensConverterFactory); +``` +**Parameters** |Name|Type|Description| |----|----|-----------| -|`keeper`|`address`|The address of the Keeper contract| -|`vaultsRegistry`|`address`|The address of the VaultsRegistry contract| -|`validatorsRegistry`|`address`|The contract address used for registering validators in beacon chain| -|`validatorsWithdrawals`|`address`|The contract address used for withdrawing validators in beacon chain| -|`validatorsConsolidations`|`address`|The contract address used for consolidating validators in beacon chain| -|`consolidationsChecker`|`address`|The contract address used for checking consolidations| -|`osTokenVaultController`|`address`|The address of the OsTokenVaultController contract| -|`osTokenConfig`|`address`|The address of the OsTokenConfig contract| -|`osTokenVaultEscrow`|`address`|The address of the OsTokenVaultEscrow contract| -|`sharedMevEscrow`|`address`|The address of the shared MEV escrow| -|`depositDataRegistry`|`address`|The address of the DepositDataRegistry contract| -|`gnoToken`|`address`|The address of the GNO token| -|`tokensConverterFactory`|`address`|The address of the TokensConverterFactory contract| -|`exitingAssetsClaimDelay`|`uint256`|The delay after which the assets can be claimed after exiting from staking| +|`args`|`GnoErc20VaultConstructorArgs`|The arguments for initializing the GnoErc20Vault contract| -### GnoErc20VaultInitParams -Struct for initializing the GnoErc20Vault contract + +### initialize + +Initializes or upgrades the GnoErc20Vault contract. Must transfer security deposit during the deployment. ```solidity -struct GnoErc20VaultInitParams { - uint256 capacity; - uint16 feePercent; - string name; - string symbol; - string metadataIpfsHash; -} +function initialize(bytes calldata params) external virtual override reinitializer(_version); ``` - -**Properties** +**Parameters** |Name|Type|Description| |----|----|-----------| -|`capacity`|`uint256`|The Vault stops accepting deposits after exceeding the capacity| -|`feePercent`|`uint16`|The fee percent that is charged by the Vault| -|`name`|`string`|The name of the ERC20 token| -|`symbol`|`string`|The symbol of the ERC20 token| -|`metadataIpfsHash`|`string`|The IPFS hash of the Vault's metadata file| +|`params`|`bytes`|The encoded parameters for initializing the GnoErc20Vault contract| -## Functions - ### transfer Moves `amount` tokens from the caller's account to `to`. @@ -106,10 +104,37 @@ function transferFrom(address from, address to, uint256 amount) returns (bool); ``` +### transferOsTokenPositionToEscrow + +Transfers minted osToken shares to the OsTokenVaultEscrow contract, enters the exit queue for staked assets + + +```solidity +function transferOsTokenPositionToEscrow(uint256 osTokenShares) + public + virtual + override(IVaultOsToken, VaultOsToken) + returns (uint256 positionTicket); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`osTokenShares`|`uint256`|The number of osToken shares to transfer| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`positionTicket`|`uint256`|The exit position ticket| + + ### enterExitQueue Locks shares to the exit queue. The shares continue earning rewards until they will be burned by the Vault. +For ERC-20 vault variants, balanceOf(vault) does not reflect queued exit shares despite the emitted Transfer event. + ```solidity function enterExitQueue(uint256 shares, address receiver) @@ -132,6 +157,22 @@ function enterExitQueue(uint256 shares, address receiver) |`positionTicket`|`uint256`|The position ticket of the exit queue. Returns uint256 max if no ticket created.| +### donateShares + +Donates shares to the Vault by burning them from the caller, +increasing the value per share for remaining holders + + +```solidity +function donateShares(uint256 shares) public virtual override(IVaultState, VaultState, VaultOsToken); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The number of shares to donate| + + ### vaultId Vault Unique Identifier @@ -160,3 +201,95 @@ function version() public pure virtual override(IVaultVersion, VaultVersion) ret |Name|Type|Description| |----|----|-----------| |``|`uint8`|The version of the Vault implementation contract| + + +### _harvestAssets + +Internal function for harvesting Vaults' new assets + + +```solidity +function _harvestAssets(IKeeperRewards.HarvestParams calldata harvestParams) + internal + override(VaultState, VaultMev) + returns (int256 totalAssetsDelta, bool harvested); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`totalAssetsDelta`|`int256`|The total assets delta after harvest| +|`harvested`|`bool`|`true` when the rewards were harvested, `false` otherwise| + + +### _updateExitQueue + +Internal function that must be used to process exit queue + + +```solidity +function _updateExitQueue() internal virtual override(VaultState, VaultToken) returns (uint256 burnedShares); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`burnedShares`|`uint256`|The total amount of burned shares| + + +### _mintShares + +Internal function for minting shares + + +```solidity +function _mintShares(address owner, uint256 shares) internal virtual override(VaultState, VaultToken); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`owner`|`address`|The address of the owner to mint shares to| +|`shares`|`uint256`|The number of shares to mint| + + +### _burnShares + +Internal function for burning shares + + +```solidity +function _burnShares(address owner, uint256 shares) internal virtual override(VaultState, VaultToken); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`owner`|`address`|The address of the owner to burn shares for| +|`shares`|`uint256`|The number of shares to burn| + + +### __GnoErc20Vault_upgrade + +Upgrades the GnoErc20Vault contract + + +```solidity +function __GnoErc20Vault_upgrade() internal; +``` + +### __GnoErc20Vault_init + +Initializes the GnoErc20Vault contract + + +```solidity +function __GnoErc20Vault_init(address admin, address ownMevEscrow, GnoErc20VaultInitParams memory params) internal; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`admin`|`address`|The address of the admin of the Vault| +|`ownMevEscrow`|`address`|The address of the MEV escrow owned by the Vault. Zero address if shared MEV escrow is used.| +|`params`|`GnoErc20VaultInitParams`|The decoded parameters for initializing the GnoErc20Vault contract| diff --git a/docs/contracts/api/vaults/gnosis/GnoGenesisVault.md b/docs/contracts/api/vaults/gnosis/GnoGenesisVault.md index 657a9ec0..7b185a68 100644 --- a/docs/contracts/api/vaults/gnosis/GnoGenesisVault.md +++ b/docs/contracts/api/vaults/gnosis/GnoGenesisVault.md @@ -6,49 +6,90 @@ description: "Genesis vault for Gnosis staking migrated from StakeWise Legacy" # GnoGenesisVault -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/gnosis/GnoGenesisVault.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/gnosis/GnoGenesisVault.sol) -**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [GnoVault →](./GnoVault), IGnoGenesisVault +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [GnoVault](./GnoVault), IGnoGenesisVault -Defines the Genesis Vault for Gnosis staking migrated from StakeWise Legacy. +Defines the Genesis Vault for Gnosis staking migrated from StakeWise Legacy -## Events -### Migrated -Event emitted on migration from StakeWise Legacy +## State Variables +### _version + +```solidity +uint8 private constant _version = 4 +``` + + +### _poolEscrow +**Note:** +oz-upgrades-unsafe-allow: state-variable-immutable + + +```solidity +IGnoPoolEscrow private immutable _poolEscrow +``` + + +### _rewardGnoToken +**Note:** +oz-upgrades-unsafe-allow: state-variable-immutable ```solidity -event Migrated(address receiver, uint256 assets, uint256 shares); +IRewardGnoToken private immutable _rewardGnoToken ``` + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` + + +## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(GnoVaultConstructorArgs memory args, address poolEscrow, address rewardGnoToken) GnoVault(args); +``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`receiver`|`address`|The address of the shares receiver| -|`assets`|`uint256`|The amount of assets migrated| -|`shares`|`uint256`|The amount of shares migrated| +|`args`|`GnoVaultConstructorArgs`|The arguments for initializing the GnoVault contract| +|`poolEscrow`|`address`|The address of the pool escrow from StakeWise Legacy| +|`rewardGnoToken`|`address`|The address of the rGNO token from StakeWise Legacy| -### GenesisVaultCreated -Event emitted on GnoGenesisVault creation (deprecated) + +### initialize + +Initializes or upgrades the GnoVault contract. Must transfer security deposit during the deployment. ```solidity -event GenesisVaultCreated(address admin, uint256 capacity, uint16 feePercent, string metadataIpfsHash); +function initialize(bytes calldata) external virtual override(IGnoVault, GnoVault) reinitializer(_version); ``` - **Parameters** |Name|Type|Description| |----|----|-----------| -|`admin`|`address`|The address of the Vault admin| -|`capacity`|`uint256`|The capacity of the Vault| -|`feePercent`|`uint16`|The fee percent of the Vault| -|`metadataIpfsHash`|`string`|The IPFS hash of the Vault metadata| - +|``|`bytes`|| -## Functions ### vaultId @@ -100,3 +141,58 @@ function migrate(address receiver, uint256 assets) external override returns (ui |Name|Type|Description| |----|----|-----------| |`shares`|`uint256`|The amount of shares minted| + + +### _calcMaxMintOsTokenShares + +Internal function for calculating the maximum amount of osToken shares that can be minted +based on the current user balance + + +```solidity +function _calcMaxMintOsTokenShares(address user) private view returns (uint256); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`user`|`address`|The address of the user| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The maximum amount of osToken shares that can be minted| + + +### _vaultAssets + +Internal function for retrieving the total assets stored in the Vault. +NB! Assets can be forcibly sent to the vault, the returned value must be used with caution + + +```solidity +function _vaultAssets() internal view virtual override(VaultState, VaultGnoStaking) returns (uint256); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The total amount of assets stored in the Vault| + + +### _pullWithdrawals + +Pulls assets from withdrawal contract + + +```solidity +function _pullWithdrawals() internal override; +``` + +## Errors +### InvalidInitialHarvest + +```solidity +error InvalidInitialHarvest(); +``` diff --git a/docs/contracts/api/vaults/gnosis/GnoMetaVault.md b/docs/contracts/api/vaults/gnosis/GnoMetaVault.md new file mode 100644 index 00000000..9a3926a5 --- /dev/null +++ b/docs/contracts/api/vaults/gnosis/GnoMetaVault.md @@ -0,0 +1,337 @@ +--- +title: GnoMetaVault +sidebar_position: 5 +description: "Defines the Meta Vault functionality on Gnosis" +--- + +# GnoMetaVault + +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/gnosis/GnoMetaVault.sol) + +**Inherits:** [VaultImmutables](../modules/VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [ReentrancyGuardUpgradeable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/utils/ReentrancyGuardUpgradeable.sol), [VaultAdmin](../modules/VaultAdmin), [VaultVersion](../modules/VaultVersion), [VaultFee](../modules/VaultFee), [VaultState](../modules/VaultState), [VaultEnterExit](../modules/VaultEnterExit), [VaultOsToken](../modules/VaultOsToken), [VaultSubVaults](../modules/VaultSubVaults), [Multicall](../../base/Multicall), IGnoMetaVault + +Defines the Meta Vault functionality on Gnosis + + +## State Variables +### _version + +```solidity +uint8 private constant _version = 5 +``` + + +### _securityDeposit + +```solidity +uint256 private constant _securityDeposit = 1e9 +``` + + +### _gnoToken + +```solidity +IERC20 private immutable _gnoToken +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` + + +## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(address gnoToken, GnoMetaVaultConstructorArgs memory args) + VaultImmutables(args.keeper, args.vaultsRegistry) + VaultEnterExit(args.exitingAssetsClaimDelay) + VaultOsToken(args.osTokenVaultController, args.osTokenConfig, args.osTokenVaultEscrow) + VaultSubVaults(args.subVaultsRegistryFactory); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`gnoToken`|`address`|The address of the GNO token contract| +|`args`|`GnoMetaVaultConstructorArgs`|The arguments for initializing the GnoMetaVault contract| + + +### initialize + +Initializes or upgrades the GnoMetaVault contract. Must transfer security deposit during the deployment. + + +```solidity +function initialize(bytes calldata params) external virtual override reinitializer(_version); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`bytes`|The encoded parameters for initializing the GnoMetaVault contract| + + +### deposit + +Deposit GNO to the Vault + + +```solidity +function deposit(uint256 assets, address receiver, address referrer) + public + virtual + override + returns (uint256 shares); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`assets`|`uint256`|The amount of GNO to deposit| +|`receiver`|`address`|The address that will receive Vault's shares| +|`referrer`|`address`|The address of the referrer. Set to zero address if not used.| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The number of shares minted| + + +### donateAssets + +Donate assets to the Vault. Must approve GNO transfer before the call. + + +```solidity +function donateAssets(uint256 amount) external override nonReentrant; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`amount`|`uint256`|The amount of GNO to donate| + + +### vaultId + + +```solidity +function vaultId() public pure virtual override(IVaultVersion, VaultVersion) returns (bytes32); +``` + +### version + +Version + + +```solidity +function version() public pure virtual override(IVaultVersion, VaultVersion) returns (uint8); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint8`|The version of the Vault implementation contract| + + +### _depositToVault + +Internal function to deposit assets to the sub-vault + + +```solidity +function _depositToVault(address vault, uint256 assets) internal override returns (uint256); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vault`|`address`|The address of the vault| +|`assets`|`uint256`|The amount of assets to deposit| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The amount of vault shares received| + + +### _vaultAssets + +Internal function for retrieving the total assets stored in the Vault. +NB! Assets can be forcibly sent to the vault, the returned value must be used with caution + + +```solidity +function _vaultAssets() internal view virtual override returns (uint256); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The total amount of assets stored in the Vault| + + +### _transferVaultAssets + +Internal function for transferring assets from the Vault to the receiver + + +```solidity +function _transferVaultAssets(address receiver, uint256 assets) internal virtual override nonReentrant; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive the assets| +|`assets`|`uint256`|The number of assets to transfer| + + +### isStateUpdateRequired + +Check whether state update is required + + +```solidity +function isStateUpdateRequired() + public + view + virtual + override(IVaultState, VaultState, VaultSubVaults) + returns (bool); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|`true` if state update is required, `false` otherwise| + + +### updateState + +Updates the total amount of assets in the Vault and its exit queue + + +```solidity +function updateState(IKeeperRewards.HarvestParams calldata harvestParams) + public + virtual + override(IVaultState, VaultState, VaultSubVaults); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`harvestParams`|`IKeeperRewards.HarvestParams`|The parameters for harvesting Keeper rewards| + + +### enterExitQueue + +Locks shares to the exit queue. The shares continue earning rewards until they will be burned by the Vault. + +For ERC-20 vault variants, balanceOf(vault) does not reflect queued exit shares despite the emitted Transfer event. + + +```solidity +function enterExitQueue(uint256 shares, address receiver) + public + virtual + override(IVaultEnterExit, VaultEnterExit, VaultOsToken) + returns (uint256 positionTicket); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The number of shares to lock| +|`receiver`|`address`|The address that will receive assets upon withdrawal| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`positionTicket`|`uint256`|The position ticket of the exit queue. Returns uint256 max if no ticket created.| + + +### donateShares + +Donates shares to the Vault by burning them from the caller, +increasing the value per share for remaining holders + + +```solidity +function donateShares(uint256 shares) public virtual override(IVaultState, VaultState, VaultOsToken); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The number of shares to donate| + + +### _checkHarvested + +Internal method for checking whether the vault is harvested + + +```solidity +function _checkHarvested() internal view virtual override(VaultImmutables, VaultSubVaults); +``` + +### _isCollateralized + +Returns whether the vault is collateralized + + +```solidity +function _isCollateralized() internal view virtual override(VaultImmutables, VaultSubVaults) returns (bool); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|true if the vault is collateralized| + + +### __GnoMetaVault_upgrade + +Upgrades the GnoMetaVault contract + + +```solidity +function __GnoMetaVault_upgrade() internal onlyInitializing; +``` + +### __GnoMetaVault_init + +Initializes the GnoMetaVault contract + + +```solidity +function __GnoMetaVault_init(address _admin, GnoMetaVaultInitParams memory params) internal onlyInitializing; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_admin`|`address`|The address of the admin of the Vault| +|`params`|`GnoMetaVaultInitParams`|The parameters for initializing the GnoMetaVault contract| diff --git a/docs/contracts/api/vaults/gnosis/GnoMetaVaultFactory.md b/docs/contracts/api/vaults/gnosis/GnoMetaVaultFactory.md new file mode 100644 index 00000000..2d36e4eb --- /dev/null +++ b/docs/contracts/api/vaults/gnosis/GnoMetaVaultFactory.md @@ -0,0 +1,86 @@ +--- +title: GnoMetaVaultFactory +sidebar_position: 6 +description: "Factory for deploying Gnosis meta Vaults" +--- + +# GnoMetaVaultFactory + +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/gnosis/GnoMetaVaultFactory.sol) + +**Inherits:** IGnoMetaVaultFactory + +Factory for deploying Gnosis meta Vaults + + +## State Variables +### _securityDeposit + +```solidity +uint256 private constant _securityDeposit = 1e9 +``` + + +### _vaultsRegistry + +```solidity +IVaultsRegistry internal immutable _vaultsRegistry +``` + + +### _gnoToken + +```solidity +IERC20 internal immutable _gnoToken +``` + + +### implementation +The address of the Vault implementation contract used for proxy creation + + +```solidity +address public immutable override implementation +``` + + +### vaultAdmin +The address of the Vault admin used for Vault creation + + +```solidity +address public override vaultAdmin +``` + + +## Functions +### constructor + +Constructor + + +```solidity +constructor(address _implementation, IVaultsRegistry vaultsRegistry, address gnoToken) ; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_implementation`|`address`|The implementation address of Vault| +|`vaultsRegistry`|`IVaultsRegistry`|The address of the VaultsRegistry contract| +|`gnoToken`|`address`|The address of the GNO token contract| + + +### createVault + +Create Vault. Must transfer security deposit together with a call. + + +```solidity +function createVault(bytes calldata params) external override returns (address vault); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`bytes`|The encoded parameters for initializing the Vault contract| diff --git a/docs/contracts/api/vaults/gnosis/GnoPrivErc20Vault.md b/docs/contracts/api/vaults/gnosis/GnoPrivErc20Vault.md index d13c2fe5..54e714e1 100644 --- a/docs/contracts/api/vaults/gnosis/GnoPrivErc20Vault.md +++ b/docs/contracts/api/vaults/gnosis/GnoPrivErc20Vault.md @@ -1,19 +1,77 @@ --- title: GnoPrivErc20Vault -sidebar_position: 5 +sidebar_position: 7 description: "Gnosis staking vault with whitelist and ERC-20 token functionality" --- # GnoPrivErc20Vault -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/gnosis/GnoPrivErc20Vault.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/gnosis/GnoPrivErc20Vault.sol) -**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [GnoErc20Vault →](./GnoErc20Vault), [VaultWhitelist →](../modules/VaultWhitelist), IGnoPrivErc20Vault +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [GnoErc20Vault](./GnoErc20Vault), [VaultWhitelist](../modules/VaultWhitelist), IGnoPrivErc20Vault -Defines the Gnosis staking Vault with whitelist and [ERC20 ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol) token. +Defines the Gnosis staking Vault with whitelist and ERC-20 token + + +## State Variables +### _version + +```solidity +uint8 private constant _version = 3 +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` ## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(GnoErc20VaultConstructorArgs memory args) GnoErc20Vault(args); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`args`|`GnoErc20VaultConstructorArgs`|The arguments for initializing the GnoErc20Vault contract| + + +### initialize + +Initializes or upgrades the GnoErc20Vault contract. Must transfer security deposit during the deployment. + + +```solidity +function initialize(bytes calldata params) + external + virtual + override(IGnoErc20Vault, GnoErc20Vault) + reinitializer(_version); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`bytes`|The encoded parameters for initializing the GnoErc20Vault contract| + ### deposit @@ -97,3 +155,14 @@ function version() public pure virtual override(IVaultVersion, GnoErc20Vault) re |Name|Type|Description| |----|----|-----------| |``|`uint8`|The version of the Vault implementation contract| + + +### _transfer + +Moves `amount` of tokens from `from` to `to`. +Emits a `Transfer` event. + + +```solidity +function _transfer(address from, address to, uint256 amount) internal virtual override; +``` diff --git a/docs/contracts/api/vaults/gnosis/GnoPrivVault.md b/docs/contracts/api/vaults/gnosis/GnoPrivVault.md index 384b7acf..102c93f6 100644 --- a/docs/contracts/api/vaults/gnosis/GnoPrivVault.md +++ b/docs/contracts/api/vaults/gnosis/GnoPrivVault.md @@ -1,18 +1,73 @@ --- title: GnoPrivVault -sidebar_position: 6 +sidebar_position: 8 description: "Gnosis staking vault with whitelist functionality" --- # GnoPrivVault -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/gnosis/GnoPrivVault.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/gnosis/GnoPrivVault.sol) -**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [GnoVault →](./GnoVault), [VaultWhitelist →](../modules/VaultWhitelist), IGnoPrivVault +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [GnoVault](./GnoVault), [VaultWhitelist](../modules/VaultWhitelist), IGnoPrivVault + +Defines the Gnosis staking Vault with whitelist + + +## State Variables +### _version + +```solidity +uint8 private constant _version = 3 +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` -Defines the Gnosis staking Vault with whitelist. ## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(GnoVaultConstructorArgs memory args) GnoVault(args); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`args`|`GnoVaultConstructorArgs`|The arguments for initializing the GnoVault contract| + + +### initialize + +Initializes or upgrades the GnoVault contract. Must transfer security deposit during the deployment. + + +```solidity +function initialize(bytes calldata params) external virtual override(IGnoVault, GnoVault) reinitializer(_version); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`bytes`|The encoded parameters for initializing the GnoVault contract| + ### deposit diff --git a/docs/contracts/api/vaults/gnosis/GnoVault.md b/docs/contracts/api/vaults/gnosis/GnoVault.md index 9579cd9b..ef4eee0c 100644 --- a/docs/contracts/api/vaults/gnosis/GnoVault.md +++ b/docs/contracts/api/vaults/gnosis/GnoVault.md @@ -1,87 +1,92 @@ --- title: GnoVault -sidebar_position: 7 +sidebar_position: 9 description: "Core Gnosis staking vault implementation" --- # GnoVault -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/gnosis/GnoVault.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/gnosis/GnoVault.sol) -**Inherits:** [VaultImmutables →](../modules/VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [VaultAdmin →](../modules/VaultAdmin), [VaultVersion →](../modules/VaultVersion), [VaultFee →](../modules/VaultFee), [VaultState →](../modules/VaultState), [VaultValidators →](../modules/VaultValidators), [VaultEnterExit →](../modules/VaultEnterExit), [VaultOsToken →](../modules/VaultOsToken), [VaultMev →](../modules/VaultMev), [VaultGnoStaking →](../modules/VaultGnoStaking), [Multicall →](../../base/Multicall), IGnoVault +**Inherits:** [VaultImmutables](../modules/VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [VaultAdmin](../modules/VaultAdmin), [VaultVersion](../modules/VaultVersion), [VaultFee](../modules/VaultFee), [VaultState](../modules/VaultState), [VaultValidators](../modules/VaultValidators), [VaultEnterExit](../modules/VaultEnterExit), [VaultOsToken](../modules/VaultOsToken), [VaultMev](../modules/VaultMev), [VaultGnoStaking](../modules/VaultGnoStaking), [Multicall](../../base/Multicall), IGnoVault -Defines the Gnosis staking Vault. +Defines the Gnosis staking Vault -## Structs -### GnoVaultConstructorArgs -Struct for initializing the GnoVault contract +## State Variables +### _version + +```solidity +uint8 private constant _version = 3 +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps ```solidity -struct GnoVaultConstructorArgs { - address keeper; - address vaultsRegistry; - address validatorsRegistry; - address validatorsWithdrawals; - address validatorsConsolidations; - address consolidationsChecker; - address osTokenVaultController; - address osTokenConfig; - address osTokenVaultEscrow; - address sharedMevEscrow; - address depositDataRegistry; - address gnoToken; - address tokensConverterFactory; - uint256 exitingAssetsClaimDelay; -} +uint256[50] private __gap ``` -**Properties** + +## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(GnoVaultConstructorArgs memory args) + VaultImmutables(args.keeper, args.vaultsRegistry) + VaultValidators( + args.depositDataRegistry, + args.validatorsRegistry, + args.validatorsWithdrawals, + args.validatorsConsolidations, + args.consolidationsChecker + ) + VaultEnterExit(args.exitingAssetsClaimDelay) + VaultOsToken(args.osTokenVaultController, args.osTokenConfig, args.osTokenVaultEscrow) + VaultMev(args.sharedMevEscrow) + VaultGnoStaking(args.gnoToken, args.tokensConverterFactory); +``` +**Parameters** |Name|Type|Description| |----|----|-----------| -|`keeper`|`address`|The address of the Keeper contract| -|`vaultsRegistry`|`address`|The address of the VaultsRegistry contract| -|`validatorsRegistry`|`address`|The contract address used for registering validators in beacon chain| -|`validatorsWithdrawals`|`address`|The contract address used for withdrawing validators in beacon chain| -|`validatorsConsolidations`|`address`|The contract address used for consolidating validators in beacon chain| -|`consolidationsChecker`|`address`|The contract address used for checking consolidations| -|`osTokenVaultController`|`address`|The address of the OsTokenVaultController contract| -|`osTokenConfig`|`address`|The address of the OsTokenConfig contract| -|`osTokenVaultEscrow`|`address`|The address of the OsTokenVaultEscrow contract| -|`sharedMevEscrow`|`address`|The address of the shared MEV escrow| -|`depositDataRegistry`|`address`|The address of the DepositDataRegistry contract| -|`gnoToken`|`address`|The address of the GNO token| -|`tokensConverterFactory`|`address`|The address of the TokensConverterFactory contract| -|`exitingAssetsClaimDelay`|`uint256`|The delay after which the assets can be claimed after exiting from staking| - -### GnoVaultInitParams -Struct for initializing the GnoVault contract +|`args`|`GnoVaultConstructorArgs`|The arguments for initializing the GnoVault contract| + + +### initialize + +Initializes or upgrades the GnoVault contract. Must transfer security deposit during the deployment. ```solidity -struct GnoVaultInitParams { - uint256 capacity; - uint16 feePercent; - string metadataIpfsHash; -} +function initialize(bytes calldata params) external virtual override reinitializer(_version); ``` - -**Properties** +**Parameters** |Name|Type|Description| |----|----|-----------| -|`capacity`|`uint256`|The Vault stops accepting deposits after exceeding the capacity| -|`feePercent`|`uint16`|The fee percent that is charged by the Vault| -|`metadataIpfsHash`|`string`|The IPFS hash of the Vault's metadata file| +|`params`|`bytes`|The encoded parameters for initializing the GnoVault contract| -## Functions ### enterExitQueue Locks shares to the exit queue. The shares continue earning rewards until they will be burned by the Vault. +For ERC-20 vault variants, balanceOf(vault) does not reflect queued exit shares despite the emitted Transfer event. + ```solidity function enterExitQueue(uint256 shares, address receiver) @@ -104,6 +109,22 @@ function enterExitQueue(uint256 shares, address receiver) |`positionTicket`|`uint256`|The position ticket of the exit queue. Returns uint256 max if no ticket created.| +### donateShares + +Donates shares to the Vault by burning them from the caller, +increasing the value per share for remaining holders + + +```solidity +function donateShares(uint256 shares) public virtual override(IVaultState, VaultState, VaultOsToken); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The number of shares to donate| + + ### vaultId @@ -124,3 +145,50 @@ function version() public pure virtual override(IVaultVersion, VaultVersion) ret |Name|Type|Description| |----|----|-----------| |``|`uint8`|The version of the Vault implementation contract| + + +### _harvestAssets + +Internal function for harvesting Vaults' new assets + + +```solidity +function _harvestAssets(IKeeperRewards.HarvestParams calldata harvestParams) + internal + override(VaultState, VaultMev) + returns (int256 totalAssetsDelta, bool harvested); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`totalAssetsDelta`|`int256`|The total assets delta after harvest| +|`harvested`|`bool`|`true` when the rewards were harvested, `false` otherwise| + + +### __GnoVault_upgrade + +Upgrades the GnoVault contract + + +```solidity +function __GnoVault_upgrade() internal; +``` + +### __GnoVault_init + +Initializes the GnoVault contract + + +```solidity +function __GnoVault_init(address admin, address ownMevEscrow, GnoVaultInitParams memory params) + internal + onlyInitializing; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`admin`|`address`|The address of the admin of the Vault| +|`ownMevEscrow`|`address`|The address of the MEV escrow owned by the Vault. Zero address if shared MEV escrow is used.| +|`params`|`GnoVaultInitParams`|The decoded parameters for initializing the GnoVault contract| diff --git a/docs/contracts/api/vaults/gnosis/GnoVaultFactory.md b/docs/contracts/api/vaults/gnosis/GnoVaultFactory.md index bd4c2b41..ef78e111 100644 --- a/docs/contracts/api/vaults/gnosis/GnoVaultFactory.md +++ b/docs/contracts/api/vaults/gnosis/GnoVaultFactory.md @@ -1,82 +1,83 @@ --- title: GnoVaultFactory -sidebar_position: 8 +sidebar_position: 10 description: "Factory for deploying Gnosis staking Vaults" --- # GnoVaultFactory -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/gnosis/GnoVaultFactory.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/gnosis/GnoVaultFactory.sol) **Inherits:** IGnoVaultFactory -Factory for deploying Gnosis staking Vaults. +Factory for deploying Gnosis staking Vaults -## Events -### VaultCreated -Event emitted on a Vault creation +## State Variables +### _securityDeposit + +```solidity +uint256 private constant _securityDeposit = 1e9 +``` +### _vaultsRegistry + ```solidity -event VaultCreated(address indexed admin, address indexed vault, address ownMevEscrow, bytes params); +IVaultsRegistry internal immutable _vaultsRegistry ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`admin`|`address`|The address of the Vault admin| -|`vault`|`address`|The address of the created Vault| -|`ownMevEscrow`|`address`|The address of the own MEV escrow contract. Zero address if shared MEV escrow is used.| -|`params`|`bytes`|The encoded parameters for initializing the Vault contract| +### _gnoToken +```solidity +IERC20 internal immutable _gnoToken +``` -## Functions ### implementation - The address of the Vault implementation contract used for proxy creation ```solidity -function implementation() external view returns (address); +address public immutable override implementation ``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`address`|The address of the Vault implementation contract| ### ownMevEscrow - The address of the own MEV escrow contract used for Vault creation ```solidity -function ownMevEscrow() external view returns (address); +address public override ownMevEscrow ``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`address`|The address of the MEV escrow contract| ### vaultAdmin - The address of the Vault admin used for Vault creation ```solidity -function vaultAdmin() external view returns (address); +address public override vaultAdmin ``` -**Returns** + + +## Functions +### constructor + +Constructor + + +```solidity +constructor(address _implementation, IVaultsRegistry vaultsRegistry, address gnoToken) ; +``` +**Parameters** |Name|Type|Description| |----|----|-----------| -|``|`address`|The address of the Vault admin| +|`_implementation`|`address`|The implementation address of Vault| +|`vaultsRegistry`|`IVaultsRegistry`|The address of the VaultsRegistry contract| +|`gnoToken`|`address`|The address of the GNO token| ### createVault diff --git a/docs/contracts/api/vaults/gnosis/mev/GnoOwnMevEscrow.md b/docs/contracts/api/vaults/gnosis/mev/GnoOwnMevEscrow.md new file mode 100644 index 00000000..b54f4a7c --- /dev/null +++ b/docs/contracts/api/vaults/gnosis/mev/GnoOwnMevEscrow.md @@ -0,0 +1,68 @@ +--- +title: GnoOwnMevEscrow +sidebar_position: 1 +description: "Accumulates received MEV. The escrow is owned by the Vault." +--- + +# GnoOwnMevEscrow + +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/gnosis/mev/GnoOwnMevEscrow.sol) + +**Inherits:** IOwnMevEscrow + +Accumulates received MEV. The escrow is owned by the Vault. + + +## State Variables +### vault +Vault address + + +```solidity +address payable public immutable override vault +``` + + +## Functions +### constructor + +Constructor + + +```solidity +constructor(address _vault) ; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_vault`|`address`|The address of the Vault contract| + + +### harvest + +Withdraws MEV accumulated in the escrow. Can be called only by the Vault. + +IMPORTANT: because control is transferred to the Vault, care must be +taken to not create reentrancy vulnerabilities. The Vault must follow the checks-effects-interactions pattern: +https://docs.soliditylang.org/en/v0.8.22/security-considerations.html#use-the-checks-effects-interactions-pattern + + +```solidity +function harvest() external returns (uint256); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|assets The amount of assets withdrawn| + + +### receive + +Function for receiving MEV + + +```solidity +receive() external payable; +``` diff --git a/docs/contracts/api/vaults/gnosis/mev/GnoSharedMevEscrow.md b/docs/contracts/api/vaults/gnosis/mev/GnoSharedMevEscrow.md new file mode 100644 index 00000000..52eb4ab2 --- /dev/null +++ b/docs/contracts/api/vaults/gnosis/mev/GnoSharedMevEscrow.md @@ -0,0 +1,66 @@ +--- +title: GnoSharedMevEscrow +sidebar_position: 2 +description: "Accumulates received MEV. The rewards are shared by multiple Vaults." +--- + +# GnoSharedMevEscrow + +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/gnosis/mev/GnoSharedMevEscrow.sol) + +**Inherits:** ISharedMevEscrow + +Accumulates received MEV. The rewards are shared by multiple Vaults. + + +## State Variables +### _vaultsRegistry + +```solidity +IVaultsRegistry private immutable _vaultsRegistry +``` + + +## Functions +### constructor + +Constructor + + +```solidity +constructor(address vaultsRegistry) ; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vaultsRegistry`|`address`|The address of the VaultsRegistry contract| + + +### harvest + +Withdraws MEV accumulated in the escrow. Can be called only by the Vault. + +IMPORTANT: because control is transferred to the Vault, care must be +taken to not create reentrancy vulnerabilities. The Vault must follow the checks-effects-interactions pattern: +https://docs.soliditylang.org/en/v0.8.22/security-considerations.html#use-the-checks-effects-interactions-pattern + + +```solidity +function harvest(uint256 assets) external override; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`assets`|`uint256`|The amount of assets to withdraw| + + +### receive + +Function for receiving MEV + + +```solidity +receive() external payable; +``` diff --git a/docs/contracts/api/vaults/gnosis/mev/_category_.json b/docs/contracts/api/vaults/gnosis/mev/_category_.json new file mode 100644 index 00000000..dc7e81ac --- /dev/null +++ b/docs/contracts/api/vaults/gnosis/mev/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "MEV", + "position": 1, + "collapsed": true +} diff --git a/docs/contracts/api/vaults/modules/VaultAdmin.md b/docs/contracts/api/vaults/modules/VaultAdmin.md index 92d32527..3b7cc7a1 100644 --- a/docs/contracts/api/vaults/modules/VaultAdmin.md +++ b/docs/contracts/api/vaults/modules/VaultAdmin.md @@ -6,86 +6,100 @@ description: "Abstract contract defining admin functionality for vaults" # VaultAdmin -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/modules/VaultAdmin.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/modules/VaultAdmin.sol) -**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), IVaultAdmin +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), IVaultAdmin -Defines the admin functionality for the Vault. +Defines the admin functionality for the Vault -## Events -### MetadataUpdated -Event emitted on metadata ipfs hash update +## State Variables +### admin +The Vault admin + + +```solidity +address public override admin +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps ```solidity -event MetadataUpdated(address indexed caller, string metadataIpfsHash); +uint256[50] private __gap ``` + +## Functions +### setMetadata + +Function for updating the metadata IPFS hash. Can only be called by Vault admin. + + +```solidity +function setMetadata(string calldata metadataIpfsHash) external override; +``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`caller`|`address`|The address of the function caller| |`metadataIpfsHash`|`string`|The new metadata IPFS hash| -### AdminUpdated -Event emitted on admin update + +### setAdmin + +Function for updating the admin address. Can only be called by Vault current admin. ```solidity -event AdminUpdated(address indexed caller, address indexed newAdmin); +function setAdmin(address newAdmin) external override; ``` - **Parameters** |Name|Type|Description| |----|----|-----------| -|`caller`|`address`|The address of the function caller| |`newAdmin`|`address`|The new admin address| -## Functions -### admin +### _checkAdmin -The Vault admin +Internal method for checking whether the caller is admin ```solidity -function admin() external view returns (address); +function _checkAdmin() internal view; ``` -**Returns** -|Name|Type|Description| -|----|----|-----------| -|``|`address`|The address of the Vault admin| +### _setAdmin - -### setMetadata - -Function for updating the metadata IPFS hash. Can only be called by Vault admin. +Internal method for updating the admin ```solidity -function setMetadata(string calldata metadataIpfsHash) external override; +function _setAdmin(address newAdmin) private; ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`metadataIpfsHash`|`string`|The new metadata IPFS hash| +|`newAdmin`|`address`|The address of the new admin| -### setAdmin +### __VaultAdmin_init -Function for updating the admin address. Can only be called by Vault current admin. +Initializes the VaultAdmin contract ```solidity -function setAdmin(address newAdmin) external override; +function __VaultAdmin_init(address _admin, string memory metadataIpfsHash) internal onlyInitializing; ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`newAdmin`|`address`|The new admin address| +|`_admin`|`address`|The address of the Vault admin| +|`metadataIpfsHash`|`string`|| diff --git a/docs/contracts/api/vaults/modules/VaultBlocklist.md b/docs/contracts/api/vaults/modules/VaultBlocklist.md index 73a05170..669d3565 100644 --- a/docs/contracts/api/vaults/modules/VaultBlocklist.md +++ b/docs/contracts/api/vaults/modules/VaultBlocklist.md @@ -6,109 +6,113 @@ description: "Abstract contract defining blocklisting functionality for vaults" # VaultBlocklist -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/modules/VaultBlocklist.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/modules/VaultBlocklist.sol) -**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [VaultAdmin →](./VaultAdmin), IVaultBlocklist +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [VaultAdmin](./VaultAdmin), IVaultBlocklist -Defines the functionality for blocking addresses for the Vault. +Defines the functionality for blocking addresses for the Vault -## Events -### BlocklistUpdated -Event emitted on blocklist update +## State Variables +### blocklistManager +Blocklist manager address ```solidity -event BlocklistUpdated(address indexed caller, address indexed account, bool isBlocked); +address public override blocklistManager ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the function caller| -|`account`|`address`|The address of the account updated| -|`isBlocked`|`bool`|Whether account is blocked or not| - -### BlocklistManagerUpdated -Event emitted when blocklist manager address is updated +### blockedAccounts ```solidity -event BlocklistManagerUpdated(address indexed caller, address indexed blocklistManager); +mapping(address => bool) public override blockedAccounts ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the function caller| -|`blocklistManager`|`address`|The address of the new blocklist manager| +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` ## Functions -### blocklistManager +### updateBlocklist -Blocklist manager address +Add or remove account from the blocklist. Can only be called by the blocklist manager. ```solidity -function blocklistManager() external view returns (address); +function updateBlocklist(address account, bool isBlocked) public virtual override; ``` -**Returns** +**Parameters** |Name|Type|Description| |----|----|-----------| -|``|`address`|The address of the blocklist manager| +|`account`|`address`|The account to add or remove to the blocklist| +|`isBlocked`|`bool`|Whether account should be blocked or not| -### blockedAccounts +### setBlocklistManager -Checks whether account is blocked or not +Used to update the blocklist manager. Can only be called by the Vault admin. ```solidity -function blockedAccounts(address account) external view returns (bool); +function setBlocklistManager(address _blocklistManager) external override; ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`account`|`address`|The account to check| +|`_blocklistManager`|`address`|The address of the new blocklist manager| + -**Returns** +### _checkBlocklist + +Internal function for checking blocklist + + +```solidity +function _checkBlocklist(address account) internal view; +``` +**Parameters** |Name|Type|Description| |----|----|-----------| -|``|`bool`|`true` for the blocked account, `false` otherwise| +|`account`|`address`|The address of the account to check| -### updateBlocklist +### _setBlocklistManager -Add or remove account from the blocklist. Can only be called by the blocklist manager. +Internal function for updating the blocklist manager externally or from the initializer ```solidity -function updateBlocklist(address account, bool isBlocked) public virtual override; +function _setBlocklistManager(address _blocklistManager) private; ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`account`|`address`|The account to add or remove to the blocklist| -|`isBlocked`|`bool`|Whether account should be blocked or not| +|`_blocklistManager`|`address`|The address of the new blocklist manager| -### setBlocklistManager +### __VaultBlocklist_init -Used to update the blocklist manager. Can only be called by the Vault admin. +Initializes the VaultBlocklist contract ```solidity -function setBlocklistManager(address _blocklistManager) external override; +function __VaultBlocklist_init(address _blocklistManager) internal onlyInitializing; ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`_blocklistManager`|`address`|The address of the new blocklist manager| +|`_blocklistManager`|`address`|The address of the blocklist manager| diff --git a/docs/contracts/api/vaults/modules/VaultEnterExit.md b/docs/contracts/api/vaults/modules/VaultEnterExit.md index 8d197959..7fd6ea4a 100644 --- a/docs/contracts/api/vaults/modules/VaultEnterExit.md +++ b/docs/contracts/api/vaults/modules/VaultEnterExit.md @@ -6,87 +6,56 @@ description: "Abstract contract defining enter and exit functionality for vaults # VaultEnterExit -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/modules/VaultEnterExit.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/modules/VaultEnterExit.sol) -**Inherits:** [VaultImmutables →](./VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [VaultState →](./VaultState), IVaultEnterExit +**Inherits:** [VaultImmutables](./VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [VaultState](./VaultState), IVaultEnterExit -Defines the functionality for entering and exiting the Vault. +Defines the functionality for entering and exiting the Vault -## Events -### Deposited -Event emitted on deposit +## State Variables +### _exitingAssetsClaimDelay +**Note:** +oz-upgrades-unsafe-allow: state-variable-immutable ```solidity -event Deposited(address indexed caller, address indexed receiver, uint256 assets, uint256 shares, address referrer); +uint256 private immutable _exitingAssetsClaimDelay ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address that called the deposit function| -|`receiver`|`address`|The address that received the shares| -|`assets`|`uint256`|The number of assets deposited by the caller| -|`shares`|`uint256`|The number of shares received| -|`referrer`|`address`|The address of the referrer| -### Redeemed -Event emitted on redeem +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps ```solidity -event Redeemed(address indexed owner, address indexed receiver, uint256 assets, uint256 shares); +uint256[50] private __gap ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`owner`|`address`|The address that owns the shares| -|`receiver`|`address`|The address that received withdrawn assets| -|`assets`|`uint256`|The total number of withdrawn assets| -|`shares`|`uint256`|The total number of withdrawn shares| - -### ExitQueueEntered -Event emitted on shares added to the exit queue - -```solidity -event ExitQueueEntered(address indexed owner, address indexed receiver, uint256 positionTicket, uint256 shares); -``` +## Functions +### constructor -**Parameters** +Constructor -|Name|Type|Description| -|----|----|-----------| -|`owner`|`address`|The address that owns the shares| -|`receiver`|`address`|The address that will receive withdrawn assets| -|`positionTicket`|`uint256`|The exit queue ticket that was assigned to the position| -|`shares`|`uint256`|The number of shares queued for exit| +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. -### ExitedAssetsClaimed -Event emitted on claim of the exited assets +**Note:** +oz-upgrades-unsafe-allow: constructor ```solidity -event ExitedAssetsClaimed( - address indexed receiver, uint256 prevPositionTicket, uint256 newPositionTicket, uint256 withdrawnAssets -); +constructor(uint256 exitingAssetsClaimDelay) ; ``` - **Parameters** |Name|Type|Description| |----|----|-----------| -|`receiver`|`address`|The address that has received withdrawn assets| -|`prevPositionTicket`|`uint256`|The exit queue ticket received after the enterExitQueue call| -|`newPositionTicket`|`uint256`|The new exit queue ticket in case not all the shares were withdrawn| -|`withdrawnAssets`|`uint256`|The total number of assets withdrawn| - +|`exitingAssetsClaimDelay`|`uint256`|The minimum delay after which the assets can be claimed after joining the exit queue| -## Functions ### getExitQueueIndex @@ -113,6 +82,8 @@ function getExitQueueIndex(uint256 positionTicket) external view override return Locks shares to the exit queue. The shares continue earning rewards until they will be burned by the Vault. +For ERC-20 vault variants, balanceOf(vault) does not reflect queued exit shares despite the emitted Transfer event. + ```solidity function enterExitQueue(uint256 shares, address receiver) public virtual override returns (uint256 positionTicket); @@ -148,9 +119,9 @@ function calculateExitedAssets(address receiver, uint256 positionTicket, uint256 |Name|Type|Description| |----|----|-----------| |`receiver`|`address`|The address that will receive assets upon withdrawal| -|`positionTicket`|`uint256`|The exit queue ticket received after the enterExitQueue call| +|`positionTicket`|`uint256`|The exit queue ticket received after the `enterExitQueue` call| |`timestamp`|`uint256`|The timestamp when the shares entered the exit queue| -|`exitQueueIndex`|`uint256`|The exit queue index at which the shares were burned| +|`exitQueueIndex`|`uint256`|The exit queue index at which the shares were burned. It can be looked up by calling `getExitQueueIndex`.| **Returns** @@ -163,7 +134,7 @@ function calculateExitedAssets(address receiver, uint256 positionTicket, uint256 ### claimExitedAssets -Claims assets that were withdrawn by the Vault. It can be called only after the enterExitQueue call by the receiver. +Claims assets that were withdrawn by the Vault. It can be called only after the `enterExitQueue` call by the `receiver`. ```solidity @@ -173,17 +144,86 @@ function claimExitedAssets(uint256 positionTicket, uint256 timestamp, uint256 ex |Name|Type|Description| |----|----|-----------| -|`positionTicket`|`uint256`|The exit queue ticket received after the enterExitQueue call| +|`positionTicket`|`uint256`|The exit queue ticket received after the `enterExitQueue` call| |`timestamp`|`uint256`|The timestamp when the assets entered the exit queue| -|`exitQueueIndex`|`uint256`|The exit queue index at which the shares were burned| +|`exitQueueIndex`|`uint256`|The exit queue index at which the shares were burned. It can be looked up by calling `getExitQueueIndex`.| ### rescueAssets Rescue any assets that are not backing shares when the vault is not collateralized. + Can be called only by the admin when the vault is not collateralized. ```solidity function rescueAssets() external override; ``` + +### _deposit + +Internal function that must be used to process user deposits + + +```solidity +function _deposit(address to, uint256 assets, address referrer) internal virtual returns (uint256 shares); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`to`|`address`|The address to mint shares to| +|`assets`|`uint256`|The number of assets deposited| +|`referrer`|`address`|The address of the referrer. Set to zero address if not used.| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The total amount of shares minted| + + +### _enterExitQueue + +Internal function for sending user shares to the exit queue + + +```solidity +function _enterExitQueue(address user, uint256 shares, address receiver) + internal + virtual + returns (uint256 positionTicket); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`user`|`address`|The address of the user| +|`shares`|`uint256`|The number of shares to send to exit queue| +|`receiver`|`address`|The address that will receive the assets| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`positionTicket`|`uint256`|The position ticket in the exit queue. Returns max uint256 if no ticket is created.| + + +### _transferVaultAssets + +Internal function for transferring assets from the Vault to the receiver + +IMPORTANT: because control is transferred to the receiver, care must be +taken to not create reentrancy vulnerabilities. The Vault must follow the checks-effects-interactions pattern: +https://docs.soliditylang.org/en/v0.8.22/security-considerations.html#use-the-checks-effects-interactions-pattern + + +```solidity +function _transferVaultAssets(address receiver, uint256 assets) internal virtual; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive the assets| +|`assets`|`uint256`|The number of assets to transfer| diff --git a/docs/contracts/api/vaults/modules/VaultEthStaking.md b/docs/contracts/api/vaults/modules/VaultEthStaking.md index 9a11118b..3213fd38 100644 --- a/docs/contracts/api/vaults/modules/VaultEthStaking.md +++ b/docs/contracts/api/vaults/modules/VaultEthStaking.md @@ -6,11 +6,30 @@ description: "Abstract contract defining Ethereum staking functionality for vaul # VaultEthStaking -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/modules/VaultEthStaking.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/modules/VaultEthStaking.sol) -**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [VaultState →](./VaultState), [VaultValidators →](./VaultValidators), [VaultEnterExit →](./VaultEnterExit), [VaultMev →](./VaultMev), IVaultEthStaking +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [VaultState](./VaultState), [VaultValidators](./VaultValidators), [VaultEnterExit](./VaultEnterExit), [VaultMev](./VaultMev), IVaultEthStaking -Defines the Ethereum staking functionality for the Vault. +Defines the Ethereum staking functionality for the Vault + + +## State Variables +### _securityDeposit + +```solidity +uint256 private constant _securityDeposit = 1e9 +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` ## Functions @@ -89,3 +108,59 @@ Donate assets to the Vault. Must transfer ETH together with the call. ```solidity function donateAssets() external payable override; ``` + +### _registerValidators + +Internal function for registering validators + + +```solidity +function _registerValidators(ValidatorUtils.ValidatorDeposit[] memory deposits) internal virtual override; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`deposits`|`ValidatorUtils.ValidatorDeposit[]`|The validators registration data| + + +### _vaultAssets + +Internal function for retrieving the total assets stored in the Vault. +NB! Assets can be forcibly sent to the vault, the returned value must be used with caution + + +```solidity +function _vaultAssets() internal view virtual override returns (uint256); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The total amount of assets stored in the Vault| + + +### _transferVaultAssets + +Internal function for transferring assets from the Vault to the receiver + + +```solidity +function _transferVaultAssets(address receiver, uint256 assets) internal virtual override nonReentrant; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive the assets| +|`assets`|`uint256`|The number of assets to transfer| + + +### __VaultEthStaking_init + +Initializes the VaultEthStaking contract + + +```solidity +function __VaultEthStaking_init() internal onlyInitializing; +``` diff --git a/docs/contracts/api/vaults/modules/VaultFee.md b/docs/contracts/api/vaults/modules/VaultFee.md index 29f1b388..d900bdc4 100644 --- a/docs/contracts/api/vaults/modules/VaultFee.md +++ b/docs/contracts/api/vaults/modules/VaultFee.md @@ -6,83 +6,86 @@ description: "Abstract contract defining fee functionality for vaults" # VaultFee -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/modules/VaultFee.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/modules/VaultFee.sol) -**Inherits:** [VaultImmutables →](./VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [VaultAdmin →](./VaultAdmin), IVaultFee +**Inherits:** [VaultImmutables](./VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [VaultAdmin](./VaultAdmin), IVaultFee -Defines the fee functionality for the Vault. +Defines the fee functionality for the Vault -## Events -### FeeRecipientUpdated -Event emitted on fee recipient update - +## State Variables +### _maxFeePercent ```solidity -event FeeRecipientUpdated(address indexed caller, address indexed feeRecipient); +uint256 internal constant _maxFeePercent = 10_000 ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the function caller| -|`feeRecipient`|`address`|The address of the new fee recipient| +### _feeUpdateDelay + +```solidity +uint256 private constant _feeUpdateDelay = 3 days +``` -### FeePercentUpdated -Event emitted on fee percent update +### _feeUpdateMultiplier ```solidity -event FeePercentUpdated(address indexed caller, uint16 feePercent); +uint256 private constant _feeUpdateMultiplier = 120 ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the function caller| -|`feePercent`|`uint16`|The new fee percent| +### _feeUpdateBase +```solidity +uint256 private constant _feeUpdateBase = 100 +``` -## Functions -### feeRecipient +### feeRecipient The Vault's fee recipient ```solidity -function feeRecipient() external view returns (address); +address public override feeRecipient ``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`address`|The address of the Vault's fee recipient| ### feePercent - The Vault's fee percent in BPS ```solidity -function feePercent() external view returns (uint16); +uint16 public override feePercent +``` + + +### _lastUpdateTimestamp + +```solidity +uint64 private _lastUpdateTimestamp ``` -**Returns** -|Name|Type|Description| -|----|----|-----------| -|``|`uint16`|The fee percent applied by the Vault on the rewards| +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + +```solidity +uint256[50] private __gap +``` + + +## Functions ### setFeeRecipient Function for updating the fee recipient address. Can only be called by the admin. ```solidity -function setFeeRecipient(address _feeRecipient) external override; +function setFeeRecipient(address _feeRecipient) external virtual override; ``` **Parameters** @@ -104,3 +107,50 @@ function setFeePercent(uint16 _feePercent) external override; |Name|Type|Description| |----|----|-----------| |`_feePercent`|`uint16`|The new fee percent| + + +### _setFeeRecipient + +Internal function for updating the fee recipient externally or from the initializer + + +```solidity +function _setFeeRecipient(address _feeRecipient) internal virtual; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_feeRecipient`|`address`|The address of the new fee recipient| + + +### _setFeePercent + +Internal function for updating the fee percent + + +```solidity +function _setFeePercent(uint16 _feePercent, bool isVaultCreation) private; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_feePercent`|`uint16`|The new fee percent| +|`isVaultCreation`|`bool`|Flag indicating whether the fee percent is set during the vault creation| + + +### __VaultFee_init + +Initializes the VaultFee contract + + +```solidity +function __VaultFee_init(address _feeRecipient, uint16 _feePercent) internal onlyInitializing; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_feeRecipient`|`address`|The address of the fee recipient| +|`_feePercent`|`uint16`|The fee percent that is charged by the Vault| diff --git a/docs/contracts/api/vaults/modules/VaultGnoStaking.md b/docs/contracts/api/vaults/modules/VaultGnoStaking.md index 0a0b7209..d0d80cd1 100644 --- a/docs/contracts/api/vaults/modules/VaultGnoStaking.md +++ b/docs/contracts/api/vaults/modules/VaultGnoStaking.md @@ -6,31 +6,75 @@ description: "Abstract contract defining Gnosis staking functionality for vaults # VaultGnoStaking -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/modules/VaultGnoStaking.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/modules/VaultGnoStaking.sol) -**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [VaultAdmin →](./VaultAdmin), [VaultState →](./VaultState), [VaultValidators →](./VaultValidators), [VaultEnterExit →](./VaultEnterExit), IVaultGnoStaking +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [VaultAdmin](./VaultAdmin), [VaultState](./VaultState), [VaultValidators](./VaultValidators), [VaultEnterExit](./VaultEnterExit), IVaultGnoStaking -Defines the Gnosis staking functionality for the Vault. +Defines the Gnosis staking functionality for the Vault -## Events -### XdaiSwapped -Emitted when xDAI is swapped to GNO (deprecated) +## State Variables +### _securityDeposit +```solidity +uint256 private constant _securityDeposit = 1e9 +``` + + +### _gnoToken + +```solidity +IERC20 internal immutable _gnoToken +``` + + +### _tokensConverterFactory + +```solidity +ITokensConverterFactory private immutable _tokensConverterFactory +``` + + +### _tokensConverter ```solidity -event XdaiSwapped(uint256 amount, uint256 assets); +IGnoTokensConverter internal _tokensConverter ``` + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[49] private __gap +``` + + +## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(address gnoToken, address tokensConverterFactory) ; +``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`amount`|`uint256`|The amount of xDAI swapped| -|`assets`|`uint256`|The amount of GNO received| - +|`gnoToken`|`address`|The address of the GNO token| +|`tokensConverterFactory`|`address`|The address of the tokens converter factory| -## Functions ### deposit @@ -82,3 +126,86 @@ Function for receiving xDAI and forwarding it to the tokens converter ```solidity receive() external payable; ``` + +### _registerValidators + +Internal function for registering validators + + +```solidity +function _registerValidators(ValidatorUtils.ValidatorDeposit[] memory deposits) internal virtual override; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`deposits`|`ValidatorUtils.ValidatorDeposit[]`|The validators registration data| + + +### _vaultAssets + +Internal function for retrieving the total assets stored in the Vault. +NB! Assets can be forcibly sent to the vault, the returned value must be used with caution + + +```solidity +function _vaultAssets() internal view virtual override returns (uint256); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The total amount of assets stored in the Vault| + + +### _transferVaultAssets + +Internal function for transferring assets from the Vault to the receiver + + +```solidity +function _transferVaultAssets(address receiver, uint256 assets) internal virtual override nonReentrant; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`receiver`|`address`|The address that will receive the assets| +|`assets`|`uint256`|The number of assets to transfer| + + +### _pullWithdrawals + +Pulls assets from withdrawal contract + + +```solidity +function _pullWithdrawals() internal virtual; +``` + +### __VaultGnoStaking_upgrade + +Upgrades the VaultGnoStaking contract + + +```solidity +function __VaultGnoStaking_upgrade() internal onlyInitializing; +``` + +### __VaultGnoStaking_init + +Initializes the VaultGnoStaking contract + + +```solidity +function __VaultGnoStaking_init() internal onlyInitializing; +``` + +### __VaultGnoStaking_init_common + +Common initialization for gas optimization + + +```solidity +function __VaultGnoStaking_init_common() private; +``` diff --git a/docs/contracts/api/vaults/modules/VaultImmutables.md b/docs/contracts/api/vaults/modules/VaultImmutables.md index eb5541bd..5fc9e81a 100644 --- a/docs/contracts/api/vaults/modules/VaultImmutables.md +++ b/docs/contracts/api/vaults/modules/VaultImmutables.md @@ -6,6 +6,83 @@ description: "Abstract contract defining common immutable variables and check fu # VaultImmutables -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/modules/VaultImmutables.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/modules/VaultImmutables.sol) Defines the Vault common immutable variables and check functions. + + +## State Variables +### _keeper +**Note:** +oz-upgrades-unsafe-allow: state-variable-immutable + + +```solidity +address internal immutable _keeper +``` + + +### _vaultsRegistry +**Note:** +oz-upgrades-unsafe-allow: state-variable-immutable + + +```solidity +address internal immutable _vaultsRegistry +``` + + +## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(address keeper, address vaultsRegistry) ; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`keeper`|`address`|The address of the Keeper contract| +|`vaultsRegistry`|`address`|The address of the VaultsRegistry contract| + + +### _checkHarvested + +Internal method for checking whether the vault is harvested + + +```solidity +function _checkHarvested() internal view virtual; +``` + +### _checkCollateralized + +Internal method for checking whether the vault is collateralized + + +```solidity +function _checkCollateralized() internal view; +``` + +### _isCollateralized + +Returns whether the vault is collateralized + + +```solidity +function _isCollateralized() internal view virtual returns (bool); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|true if the vault is collateralized| diff --git a/docs/contracts/api/vaults/modules/VaultMev.md b/docs/contracts/api/vaults/modules/VaultMev.md index 6efb08ee..6fde2c41 100644 --- a/docs/contracts/api/vaults/modules/VaultMev.md +++ b/docs/contracts/api/vaults/modules/VaultMev.md @@ -6,14 +6,63 @@ description: "Abstract contract defining MEV escrow functionality for vaults" # VaultMev -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/modules/VaultMev.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/modules/VaultMev.sol) -**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [VaultState →](./VaultState), IVaultMev +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [VaultState](./VaultState), IVaultMev -Defines the Vaults' MEV functionality. +Defines the Vaults' MEV functionality + + +## State Variables +### _sharedMevEscrow +**Note:** +oz-upgrades-unsafe-allow: state-variable-immutable + + +```solidity +address private immutable _sharedMevEscrow +``` + + +### _ownMevEscrow + +```solidity +address private _ownMevEscrow +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` ## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor + + +```solidity +constructor(address sharedMevEscrow) ; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`sharedMevEscrow`|`address`|The address of the shared MEV escrow| + ### mevEscrow @@ -28,3 +77,38 @@ function mevEscrow() public view override returns (address); |Name|Type|Description| |----|----|-----------| |``|`address`|The MEV escrow contract address| + + +### _harvestAssets + +Internal function for harvesting Vaults' new assets + + +```solidity +function _harvestAssets(IKeeperRewards.HarvestParams calldata harvestParams) + internal + virtual + override + returns (int256 totalAssetsDelta, bool harvested); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`totalAssetsDelta`|`int256`|The total assets delta after harvest| +|`harvested`|`bool`|`true` when the rewards were harvested, `false` otherwise| + + +### __VaultMev_init + +Initializes the VaultMev contract + + +```solidity +function __VaultMev_init(address ownMevEscrow) internal onlyInitializing; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`ownMevEscrow`|`address`|The address of the own MEV escrow contract| diff --git a/docs/contracts/api/vaults/modules/VaultOsToken.md b/docs/contracts/api/vaults/modules/VaultOsToken.md index 18a6c522..b0bc6974 100644 --- a/docs/contracts/api/vaults/modules/VaultOsToken.md +++ b/docs/contracts/api/vaults/modules/VaultOsToken.md @@ -6,118 +6,92 @@ description: "Abstract contract defining OsToken minting functionality for vault # VaultOsToken -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/modules/VaultOsToken.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/modules/VaultOsToken.sol) -**Inherits:** [VaultImmutables →](./VaultImmutables), [VaultState →](./VaultState), [VaultEnterExit →](./VaultEnterExit), IVaultOsToken +**Inherits:** [VaultImmutables](./VaultImmutables), [VaultState](./VaultState), [VaultEnterExit](./VaultEnterExit), IVaultOsToken -Defines the functionality for minting OsToken. +Defines the functionality for minting OsToken -## Structs -### OsTokenPosition -Struct of osToken position + +## State Variables +### _maxPercent ```solidity -struct OsTokenPosition { - uint128 shares; - uint128 cumulativeFeePerShare; -} +uint256 private constant _maxPercent = 1e18 ``` -**Properties** - -|Name|Type|Description| -|----|----|-----------| -|`shares`|`uint128`|The total number of minted osToken shares. Will increase based on the treasury fee.| -|`cumulativeFeePerShare`|`uint128`|The cumulative fee per share| -## Events -### OsTokenMinted -Event emitted on minting osToken +### _osTokenVaultController +**Note:** +oz-upgrades-unsafe-allow: state-variable-immutable ```solidity -event OsTokenMinted(address indexed caller, address receiver, uint256 assets, uint256 shares, address referrer); +IOsTokenVaultController private immutable _osTokenVaultController ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the function caller| -|`receiver`|`address`|The address of the osToken receiver| -|`assets`|`uint256`|The amount of minted assets| -|`shares`|`uint256`|The amount of minted shares| -|`referrer`|`address`|The address of the referrer| +### _osTokenConfig +**Note:** +oz-upgrades-unsafe-allow: state-variable-immutable -### OsTokenBurned -Event emitted on burning OsToken + +```solidity +IOsTokenConfig private immutable _osTokenConfig +``` + + +### _osTokenVaultEscrow +**Note:** +oz-upgrades-unsafe-allow: state-variable-immutable ```solidity -event OsTokenBurned(address indexed caller, uint256 assets, uint256 shares); +IOsTokenVaultEscrow private immutable _osTokenVaultEscrow ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the function caller| -|`assets`|`uint256`|The amount of burned assets| -|`shares`|`uint256`|The amount of burned shares| +### _positions -### OsTokenLiquidated -Event emitted on osToken position liquidation +```solidity +mapping(address => OsTokenPosition) private _positions +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps ```solidity -event OsTokenLiquidated( - address indexed caller, - address indexed user, - address receiver, - uint256 osTokenShares, - uint256 shares, - uint256 receivedAssets -); +uint256[50] private __gap ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the function caller| -|`user`|`address`|The address of the user liquidated| -|`receiver`|`address`|The address of the receiver of the liquidated assets| -|`osTokenShares`|`uint256`|The amount of osToken shares to liquidate| -|`shares`|`uint256`|The amount of vault shares burned| -|`receivedAssets`|`uint256`|The amount of assets received| +## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. -### OsTokenRedeemed -Event emitted on osToken position redemption +**Note:** +oz-upgrades-unsafe-allow: constructor ```solidity -event OsTokenRedeemed( - address indexed caller, - address indexed user, - address receiver, - uint256 osTokenShares, - uint256 shares, - uint256 assets -); +constructor(address osTokenVaultController, address osTokenConfig, address osTokenVaultEscrow) ; ``` - **Parameters** |Name|Type|Description| |----|----|-----------| -|`caller`|`address`|The address of the function caller| -|`user`|`address`|The address of the position owner to redeem from| -|`receiver`|`address`|The address of the receiver of the redeemed assets| -|`osTokenShares`|`uint256`|The amount of osToken shares to redeem| -|`shares`|`uint256`|The amount of vault shares burned| -|`assets`|`uint256`|The amount of assets received| +|`osTokenVaultController`|`address`|The address of the OsTokenVaultController contract| +|`osTokenConfig`|`address`|The address of the OsTokenConfig contract| +|`osTokenVaultEscrow`|`address`|The address of the OsTokenVaultEscrow contract| -## Functions ### osTokenPositions @@ -229,7 +203,11 @@ Transfers minted osToken shares to the OsTokenVaultEscrow contract, enters the e ```solidity -function transferOsTokenPositionToEscrow(uint256 osTokenShares) external override returns (uint256 positionTicket); +function transferOsTokenPositionToEscrow(uint256 osTokenShares) + public + virtual + override + returns (uint256 positionTicket); ``` **Parameters** @@ -244,10 +222,28 @@ function transferOsTokenPositionToEscrow(uint256 osTokenShares) external overrid |`positionTicket`|`uint256`|The exit position ticket| +### donateShares + +Donates shares to the Vault by burning them from the caller, +increasing the value per share for remaining holders + + +```solidity +function donateShares(uint256 shares) public virtual override(IVaultState, VaultState); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The number of shares to donate| + + ### enterExitQueue Locks shares to the exit queue. The shares continue earning rewards until they will be burned by the Vault. +For ERC-20 vault variants, balanceOf(vault) does not reflect queued exit shares despite the emitted Transfer event. + ```solidity function enterExitQueue(uint256 shares, address receiver) @@ -268,3 +264,107 @@ function enterExitQueue(uint256 shares, address receiver) |Name|Type|Description| |----|----|-----------| |`positionTicket`|`uint256`|The position ticket of the exit queue. Returns uint256 max if no ticket created.| + + +### _mintOsToken + +Internal function for minting osToken shares + + +```solidity +function _mintOsToken(address owner, address receiver, uint256 osTokenShares, address referrer) + internal + returns (uint256 assets); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`owner`|`address`|The owner of the osToken position| +|`receiver`|`address`|The receiver of the osToken shares| +|`osTokenShares`|`uint256`|The amount of osToken shares to mint| +|`referrer`|`address`|The address of the referrer| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`assets`|`uint256`|The amount of assets minted| + + +### _redeemOsToken + +Internal function for redeeming and liquidating osToken shares + + +```solidity +function _redeemOsToken(address owner, address receiver, uint256 osTokenShares, bool isLiquidation) + private + returns (uint256 burnedShares, uint256 receivedAssets); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`owner`|`address`|The minter of the osToken shares| +|`receiver`|`address`|The receiver of the assets| +|`osTokenShares`|`uint256`|The amount of osToken shares to redeem or liquidate| +|`isLiquidation`|`bool`|Whether the liquidation or redemption is being performed| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`burnedShares`|`uint256`|The amount of shares burned| +|`receivedAssets`|`uint256`|The amount of assets received| + + +### _syncPositionFee + +Internal function for syncing the osToken fee + + +```solidity +function _syncPositionFee(OsTokenPosition memory position) private view; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`position`|`OsTokenPosition`|The position to sync the fee for| + + +### _checkOsTokenPosition + +Internal function for checking position validity. Reverts if it is invalid. + + +```solidity +function _checkOsTokenPosition(address user) internal view; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`user`|`address`|The address of the user| + + +### _calcMaxOsTokenShares + +Internal function for calculating the maximum amount of osToken shares that can be minted + + +```solidity +function _calcMaxOsTokenShares(uint256 assets) internal view returns (uint256); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`assets`|`uint256`|The amount of assets to convert to osToken shares| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|maxOsTokenShares The maximum amount of osToken shares that can be minted| diff --git a/docs/contracts/api/vaults/modules/VaultState.md b/docs/contracts/api/vaults/modules/VaultState.md index 95394ac0..86501bc8 100644 --- a/docs/contracts/api/vaults/modules/VaultState.md +++ b/docs/contracts/api/vaults/modules/VaultState.md @@ -6,73 +6,107 @@ description: "Abstract contract defining state manipulation functionality for va # VaultState -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/modules/VaultState.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/modules/VaultState.sol) -**Inherits:** [VaultImmutables →](./VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [VaultFee →](./VaultFee), IVaultState +**Inherits:** [VaultImmutables](./VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [VaultFee](./VaultFee), IVaultState -Defines Vault's state manipulation. +Defines Vault's state manipulation -## Events -### CheckpointCreated -Event emitted on checkpoint creation +## State Variables +### _totalShares + +```solidity +uint128 internal _totalShares +``` +### _totalAssets + ```solidity -event CheckpointCreated(uint256 shares, uint256 assets); +uint128 internal _totalAssets ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`shares`|`uint256`|The number of burned shares| -|`assets`|`uint256`|The amount of exited assets| +### _queuedShares + +```solidity +uint128 internal _queuedShares +``` -### FeeSharesMinted -Event emitted on minting fee recipient shares +### _unclaimedAssets ```solidity -event FeeSharesMinted(address receiver, uint256 shares, uint256 assets); +uint128 internal _unclaimedAssets ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`receiver`|`address`|The address of the fee recipient| -|`shares`|`uint256`|The number of minted shares| -|`assets`|`uint256`|The amount of minted assets| +### _exitQueue -### ExitingAssetsPenalized -Event emitted when exiting assets are penalized (deprecated) +```solidity +ExitQueue.History internal _exitQueue +``` +### _exitRequests + ```solidity -event ExitingAssetsPenalized(uint256 penalty); +mapping(bytes32 => uint256) internal _exitRequests ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`penalty`|`uint256`|The total penalty amount| +### _balances + +```solidity +mapping(address => uint256) internal _balances +``` -### AssetsDonated -Event emitted when the assets are donated to the Vault +### _capacity ```solidity -event AssetsDonated(address sender, uint256 assets); +uint256 private _capacity ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`sender`|`address`|The address of the sender| -|`assets`|`uint256`|The amount of donated assets| +### _totalExitingAssets + +```solidity +uint128 internal _totalExitingAssets +``` + + +### _totalExitingTickets + +```solidity +uint128 internal _totalExitingTickets +``` + + +### _totalExitedTickets + +```solidity +uint256 internal _totalExitedTickets +``` + + +### _donatedAssets + +```solidity +uint256 internal _donatedAssets +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[47] private __gap +``` ## Functions @@ -198,6 +232,22 @@ function convertToAssets(uint256 shares) public view override returns (uint256 a |`assets`|`uint256`|The amount of assets that the Vault would exchange for the amount of shares provided| +### donateShares + +Donates shares to the Vault by burning them from the caller, +increasing the value per share for remaining holders + + +```solidity +function donateShares(uint256 shares) public virtual override; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`shares`|`uint256`|The number of shares to donate| + + ### capacity The Vault's capacity @@ -256,3 +306,137 @@ function updateState(IKeeperRewards.HarvestParams calldata harvestParams) public |Name|Type|Description| |----|----|-----------| |`harvestParams`|`IKeeperRewards.HarvestParams`|The parameters for harvesting Keeper rewards| + + +### _processTotalAssetsDelta + +Internal function for processing rewards and penalties + + +```solidity +function _processTotalAssetsDelta(int256 totalAssetsDelta) internal virtual; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`totalAssetsDelta`|`int256`|The number of assets earned or lost| + + +### _updateExitQueue + +Internal function that must be used to process exit queue + +Make sure that sufficient time passed between exit queue updates (at least 12 hours). +Currently it's restricted by the keeper's harvest interval + + +```solidity +function _updateExitQueue() internal virtual returns (uint256 burnedShares); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`burnedShares`|`uint256`|The total amount of burned shares| + + +### _mintShares + +Internal function for minting shares + + +```solidity +function _mintShares(address owner, uint256 shares) internal virtual; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`owner`|`address`|The address of the owner to mint shares to| +|`shares`|`uint256`|The number of shares to mint| + + +### _burnShares + +Internal function for burning shares + + +```solidity +function _burnShares(address owner, uint256 shares) internal virtual; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`owner`|`address`|The address of the owner to burn shares for| +|`shares`|`uint256`|The number of shares to burn| + + +### _convertToShares + +Internal conversion function (from assets to shares) with support for rounding direction. + + +```solidity +function _convertToShares(uint256 assets, Math.Rounding rounding) internal view returns (uint256 shares); +``` + +### _harvestAssets + +Internal function for harvesting Vaults' new assets + + +```solidity +function _harvestAssets(IKeeperRewards.HarvestParams calldata harvestParams) + internal + virtual + returns (int256 totalAssetsDelta, bool harvested); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`totalAssetsDelta`|`int256`|The total assets delta after harvest| +|`harvested`|`bool`|`true` when the rewards were harvested, `false` otherwise| + + +### _vaultAssets + +Internal function for retrieving the total assets stored in the Vault. +NB! Assets can be forcibly sent to the vault, the returned value must be used with caution + + +```solidity +function _vaultAssets() internal view virtual returns (uint256); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The total amount of assets stored in the Vault| + + +### __VaultState_init + +Initializes the VaultState contract + + +```solidity +function __VaultState_init(uint256 capacity_) internal onlyInitializing; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`capacity_`|`uint256`|The amount after which the Vault stops accepting deposits| + + +### __VaultState_upgrade + +Upgrades the VaultState contract + + +```solidity +function __VaultState_upgrade() internal onlyInitializing; +``` diff --git a/docs/contracts/api/vaults/modules/VaultSubVaults.md b/docs/contracts/api/vaults/modules/VaultSubVaults.md index bc46f1da..11db5073 100644 --- a/docs/contracts/api/vaults/modules/VaultSubVaults.md +++ b/docs/contracts/api/vaults/modules/VaultSubVaults.md @@ -6,263 +6,252 @@ description: "Abstract contract defining sub-vaults management functionality for # VaultSubVaults -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/modules/VaultSubVaults.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/modules/VaultSubVaults.sol) -**Inherits:** [VaultImmutables →](./VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [ReentrancyGuardUpgradeable ↗](https://github.com/Badger-Finance/badger-system/blob/master/deps/@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol), [VaultAdmin →](./VaultAdmin), [VaultState →](./VaultState), IVaultSubVaults +**Inherits:** [VaultImmutables](./VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [VaultState](./VaultState), IVaultSubVaults -Defines the functionality for managing the Vault sub-vaults. +Defines the functionality for managing the Vault sub-vaults -## Structs -### SubVaultState -Struct for sub vault state + +## State Variables +### _subVaultsRegistryFactory +**Note:** +oz-upgrades-unsafe-allow: state-variable-immutable ```solidity -struct SubVaultState { - uint128 stakedShares; - uint128 queuedShares; -} +address private immutable _subVaultsRegistryFactory ``` -**Properties** -|Name|Type|Description| -|----|----|-----------| -|`stakedShares`|`uint128`|The number of shares staked in the sub vault| -|`queuedShares`|`uint128`|The number of shares queued for exit in the sub vault| +### __deprecated__subVaultsCurator +Deprecated: moved to SubVaultsRegistry -### SubVaultExitRequest -Struct for submitting sub vault exit request ```solidity -struct SubVaultExitRequest { - uint256 exitQueueIndex; - address vault; - uint64 timestamp; -} +address private __deprecated__subVaultsCurator ``` -**Properties** -|Name|Type|Description| -|----|----|-----------| -|`exitQueueIndex`|`uint256`|The index of the exit queue| -|`vault`|`address`|The address of the vault| -|`timestamp`|`uint64`|The timestamp of the exit request| +### __deprecated__ejectingSubVault +Deprecated: moved to SubVaultsRegistry -## Events -### RewardsNonceUpdated -Emitted when the rewards nonce is updated ```solidity -event RewardsNonceUpdated(uint256 rewardsNonce); +address private __deprecated__ejectingSubVault ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`rewardsNonce`|`uint256`|The new rewards nonce| +### __deprecated__subVaults +Deprecated: moved to SubVaultsRegistry -### SubVaultsHarvested -Emitted when the sub vaults are harvested ```solidity -event SubVaultsHarvested(int256 totalAssetsDelta); +EnumerableSet.AddressSet private __deprecated__subVaults ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`totalAssetsDelta`|`int256`|The change in total assets after the harvest| +### __deprecated__subVaultsExits +Deprecated: moved to SubVaultsRegistry -### SubVaultAdded -Emitted when the new sub-vault is added ```solidity -event SubVaultAdded(address indexed caller, address indexed vault); +mapping(address vault => DoubleEndedQueue.Bytes32Deque) private __deprecated__subVaultsExits ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the caller| -|`vault`|`address`|The address of the sub-vault| - -### SubVaultEjecting -Emitted when the sub-vault is ejecting +### __deprecated__subVaultsStates +Deprecated: moved to SubVaultsRegistry ```solidity -event SubVaultEjecting(address indexed caller, address indexed vault); +mapping(address vault => ISubVaultsRegistry.SubVaultState state) private __deprecated__subVaultsStates ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the caller| -|`vault`|`address`|The address of the sub-vault| - -### SubVaultEjected -Emitted when the sub-vault is ejected +### __deprecated__subVaultsRewardsNonce +Deprecated: moved to SubVaultsRegistry ```solidity -event SubVaultEjected(address indexed caller, address indexed vault); +uint128 private __deprecated__subVaultsRewardsNonce ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the caller| -|`vault`|`address`|The address of the sub-vault| - -### SubVaultsCuratorUpdated -Emitted when the sub-vaults curator is updated +### __deprecated__subVaultsTotalAssets +Deprecated: moved to SubVaultsRegistry ```solidity -event SubVaultsCuratorUpdated(address indexed caller, address indexed curator); +uint128 private __deprecated__subVaultsTotalAssets ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the caller| -|`curator`|`address`|The address of the new sub-vaults curator| +### __deprecated__totalProcessedExitQueueTickets +Deprecated: moved to SubVaultsRegistry -## Functions -### subVaultsCurator +```solidity +uint256 private __deprecated__totalProcessedExitQueueTickets +``` + -Sub-vaults curator contract +### __deprecated__ejectingSubVaultShares +Deprecated: moved to SubVaultsRegistry ```solidity -function subVaultsCurator() external view returns (address); +uint256 private __deprecated__ejectingSubVaultShares ``` -**Returns** -|Name|Type|Description| -|----|----|-----------| -|``|`address`|The address of the Sub-vaults curator contract| +### subVaultsRegistry +Returns the address of the SubVaultsRegistry contract -### ejectingSubVault -Ejecting sub-vault +```solidity +address public override subVaultsRegistry +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps ```solidity -function ejectingSubVault() external view returns (address); +uint256[49] private __gap ``` -**Returns** -|Name|Type|Description| -|----|----|-----------| -|``|`address`|The address of the ejecting sub-vault| +## Functions +### constructor + +Constructor -### subVaultsRewardsNonce +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. -Function to get the rewards nonce of the sub-vaults +**Note:** +oz-upgrades-unsafe-allow: constructor ```solidity -function subVaultsRewardsNonce() external view returns (uint128); +constructor(address subVaultsRegistryFactory) ; ``` -**Returns** +**Parameters** |Name|Type|Description| |----|----|-----------| -|``|`uint128`|The rewards nonce| +|`subVaultsRegistryFactory`|`address`|The address of the factory used to deploy SubVaultsRegistry contract| -### subVaultsStates +### depositToSubVault -Function to get the state of a sub-vault +Function to deposit assets to a sub vault. Can only be called by SubVaultsRegistry contract. ```solidity -function subVaultsStates(address vault) external view override returns (SubVaultState memory); +function depositToSubVault(address vault, uint256 assets) external override returns (uint256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`vault`|`address`|The address of the sub-vault| +|`assets`|`uint256`|The amount of assets to deposit| **Returns** |Name|Type|Description| |----|----|-----------| -|``|`SubVaultState`|The state of the sub-vault| +|``|`uint256`|shares The amount of vault shares received| -### getSubVaults +### enterSubVaultExitQueue -Function to get the list sub-vaults +Function to enter sub-vault exit queue. Can only be called by SubVaultsRegistry contract. ```solidity -function getSubVaults() public view override returns (address[] memory); +function enterSubVaultExitQueue(address vault, uint256 shares) external override returns (uint256 positionTicket); ``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`vault`|`address`|The address of the sub-vault| +|`shares`|`uint256`|The amount of shares to exit| + **Returns** |Name|Type|Description| |----|----|-----------| -|``|`address[]`|An array of addresses of the sub-vaults| +|`positionTicket`|`uint256`|The position ticket in the exit queue| -### setSubVaultsCurator +### claimSubVaultExitedAssets -Function to update the the sub-vaults curator. Can only be called by the admin. +Function to claim exited assets from a sub-vault. Can only be called by SubVaultsRegistry contract. ```solidity -function setSubVaultsCurator(address curator) external override; +function claimSubVaultExitedAssets(address vault, uint256 positionTicket, uint256 timestamp, uint256 exitQueueIndex) + external + override; ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`curator`|`address`|The address of the new sub-vaults curator| +|`vault`|`address`|The address of the sub-vault| +|`positionTicket`|`uint256`|The position ticket in the exit queue| +|`timestamp`|`uint256`|The timestamp of the exit request| +|`exitQueueIndex`|`uint256`|The index of the exit queue| -### addSubVault +### mintSubVaultOsToken -Function to add a new sub-vault. Can only be called by the admin. +Function to mint osToken for a sub-vault. Can only be called by SubVaultsRegistry contract. ```solidity -function addSubVault(address vault) public virtual override; +function mintSubVaultOsToken(address vault, address receiver, uint256 osTokenShares) external override; ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`vault`|`address`|The address of the sub-vault to add| +|`vault`|`address`|The address of the sub-vault| +|`receiver`|`address`|The address that will receive the minted osToken shares| +|`osTokenShares`|`uint256`|The amount of osToken shares to mint| -### ejectSubVault +### redeemSubVaultOsToken -Function to remove a sub-vault. Can only be called by the admin. -All the sub-vault shares will be added to the exit queue. +Function to redeem osToken from a sub-vault. Can only be called by SubVaultsRegistry contract. ```solidity -function ejectSubVault(address vault) public virtual override; +function redeemSubVaultOsToken(address vault, address redeemer, uint256 osTokenShares) + external + override + returns (uint256 assets); ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`vault`|`address`|The address of the sub-vault to remove| +|`vault`|`address`|The address of the sub-vault| +|`redeemer`|`address`|The address of the OsToken redeemer| +|`osTokenShares`|`uint256`|The amount of osToken shares to redeem| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`assets`|`uint256`|The amount of assets redeemed| ### isStateUpdateRequired @@ -271,7 +260,7 @@ Check whether state update is required ```solidity -function isStateUpdateRequired() public view virtual override returns (bool); +function isStateUpdateRequired() public view virtual override(IVaultState, VaultState) returns (bool); ``` **Returns** @@ -280,70 +269,116 @@ function isStateUpdateRequired() public view virtual override returns (bool); |``|`bool`|`true` if state update is required, `false` otherwise| -### canUpdateState +### updateState -Checks whether the meta vault can be updated +Updates the total amount of assets in the Vault and its exit queue ```solidity -function canUpdateState() external view override returns (bool); +function updateState(IKeeperRewards.HarvestParams calldata) public virtual override(IVaultState, VaultState); ``` -**Returns** +**Parameters** |Name|Type|Description| |----|----|-----------| -|``|`bool`|`true` if the meta vault can be updated, `false` otherwise| +|``|`IKeeperRewards.HarvestParams`|| -### isCollateralized +### _harvestAssets -Checks whether the vault is collateralized +Internal function for harvesting Vaults' new assets ```solidity -function isCollateralized() external view override returns (bool); +function _harvestAssets(IKeeperRewards.HarvestParams calldata) + internal + pure + override + returns (int256 totalAssetsDelta, bool harvested); ``` **Returns** |Name|Type|Description| |----|----|-----------| -|``|`bool`|`true` if the vault is collateralized, `false` otherwise| +|`totalAssetsDelta`|`int256`|The total assets delta after harvest| +|`harvested`|`bool`|`true` when the rewards were harvested, `false` otherwise| -### depositToSubVaults +### _checkHarvested -Deposit available assets to the sub vaults +Internal method for checking whether the vault is harvested ```solidity -function depositToSubVaults() external override nonReentrant; +function _checkHarvested() internal view virtual override; ``` -### claimSubVaultsExitedAssets +### _isCollateralized -Claim the exited assets from the sub vaults +Returns whether the vault is collateralized ```solidity -function claimSubVaultsExitedAssets(SubVaultExitRequest[] calldata exitRequests) external override; +function _isCollateralized() internal view virtual override returns (bool); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|true if the vault is collateralized| + + +### _depositToVault + +Internal function to deposit assets to the sub-vault + + +```solidity +function _depositToVault(address vault, uint256 assets) internal virtual returns (uint256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`exitRequests`|`SubVaultExitRequest[]`|The array of exit requests to claim| +|`vault`|`address`|The address of the vault| +|`assets`|`uint256`|The amount of assets to deposit| +**Returns** -### updateState +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The amount of vault shares received| -Updates the total amount of assets in the Vault and its exit queue + +### _checkSubVaultsRegistry + +Internal function to check if the caller is the SubVaultsRegistry ```solidity -function updateState(IKeeperRewards.HarvestParams calldata) public virtual override; +function _checkSubVaultsRegistry() private view; +``` + +### __VaultSubVaults_init + +Initializes the VaultSubVaults contract + + +```solidity +function __VaultSubVaults_init(address curator) internal onlyInitializing; ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|``|`IKeeperRewards.HarvestParams`|| +|`curator`|`address`|The address of initial sub-vaults curator| + + +### __VaultSubVaults_upgrade + +Upgrades the VaultSubVaults contract by upgrading the SubVaultsRegistry proxy to the latest implementation + + +```solidity +function __VaultSubVaults_upgrade() internal onlyInitializing; +``` diff --git a/docs/contracts/api/vaults/modules/VaultToken.md b/docs/contracts/api/vaults/modules/VaultToken.md index 3074161a..dfe28d88 100644 --- a/docs/contracts/api/vaults/modules/VaultToken.md +++ b/docs/contracts/api/vaults/modules/VaultToken.md @@ -6,11 +6,23 @@ description: "Abstract contract defining ERC20 token functionality for vaults" # VaultToken -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/modules/VaultToken.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/modules/VaultToken.sol) -**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [ERC20Upgradeable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/token/ERC20/ERC20Upgradeable.sol), [VaultState →](./VaultState), IVaultToken +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [ERC20Upgradeable](../../base/ERC20Upgradeable), [VaultState](./VaultState), IVaultToken -Defines the token functionality for the Vault. +Defines the token functionality for the Vault + + +## State Variables +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` ## Functions @@ -31,3 +43,75 @@ Returns the amount of tokens owned by `account`. ```solidity function balanceOf(address account) external view returns (uint256); ``` + +### _mintShares + +Internal function for minting shares + + +```solidity +function _mintShares(address owner, uint256 shares) internal virtual override; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`owner`|`address`|The address of the owner to mint shares to| +|`shares`|`uint256`|The number of shares to mint| + + +### _burnShares + +Internal function for burning shares + + +```solidity +function _burnShares(address owner, uint256 shares) internal virtual override; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`owner`|`address`|The address of the owner to burn shares for| +|`shares`|`uint256`|The number of shares to burn| + + +### _updateExitQueue + +Internal function that must be used to process exit queue + + +```solidity +function _updateExitQueue() internal virtual override returns (uint256 burnedShares); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`burnedShares`|`uint256`|The total amount of burned shares| + + +### _transfer + +Moves `amount` of tokens from `from` to `to`. +Emits a `Transfer` event. + + +```solidity +function _transfer(address from, address to, uint256 amount) internal virtual override; +``` + +### __VaultToken_init + +Initializes the VaultToken contract + + +```solidity +function __VaultToken_init(string memory _name, string memory _symbol) internal onlyInitializing; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_name`|`string`|The name of the ERC20 token| +|`_symbol`|`string`|The symbol of the ERC20 token| diff --git a/docs/contracts/api/vaults/modules/VaultValidators.md b/docs/contracts/api/vaults/modules/VaultValidators.md index 617966d5..3309f828 100644 --- a/docs/contracts/api/vaults/modules/VaultValidators.md +++ b/docs/contracts/api/vaults/modules/VaultValidators.md @@ -6,187 +6,165 @@ description: "Abstract contract defining validators management functionality for # VaultValidators -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/modules/VaultValidators.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/modules/VaultValidators.sol) -**Inherits:** [VaultImmutables →](./VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [ReentrancyGuardUpgradeable ↗](https://github.com/Badger-Finance/badger-system/blob/master/deps/@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol), [VaultAdmin →](./VaultAdmin), [VaultState →](./VaultState), IVaultValidators +**Inherits:** [VaultImmutables](./VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [ReentrancyGuardUpgradeable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/utils/ReentrancyGuardUpgradeable.sol), [VaultAdmin](./VaultAdmin), [VaultState](./VaultState), IVaultValidators -Defines the validators functionality for the Vault. +Defines the validators functionality for the Vault -## Events -### ValidatorRegistered -Event emitted on V1 validator registration +## State Variables +### _depositDataRegistry +**Note:** +oz-upgrades-unsafe-allow: state-variable-immutable ```solidity -event ValidatorRegistered(bytes publicKey); +address private immutable _depositDataRegistry ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`publicKey`|`bytes`|The public key of the validator that was registered| -### V2ValidatorRegistered -Event emitted on V2 validator registration +### _initialChainId +**Note:** +oz-upgrades-unsafe-allow: state-variable-immutable ```solidity -event V2ValidatorRegistered(bytes publicKey, uint256 amount); +uint256 private immutable _initialChainId ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`publicKey`|`bytes`|The public key of the validator that was registered| -|`amount`|`uint256`|The amount of assets that was registered| -### ValidatorWithdrawalSubmitted -Event emitted on validator withdrawal +### _validatorsRegistry +**Note:** +oz-upgrades-unsafe-allow: state-variable-immutable ```solidity -event ValidatorWithdrawalSubmitted(bytes publicKey, uint256 amount, uint256 feePaid); +address internal immutable _validatorsRegistry ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`publicKey`|`bytes`|The public key of the validator that was withdrawn| -|`amount`|`uint256`|The amount of assets that was withdrawn| -|`feePaid`|`uint256`|The amount of fee that was paid| -### ValidatorFunded -Event emitted on validator balance top-up +### _validatorsWithdrawals +**Note:** +oz-upgrades-unsafe-allow: state-variable-immutable ```solidity -event ValidatorFunded(bytes publicKey, uint256 amount); +address private immutable _validatorsWithdrawals ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`publicKey`|`bytes`|The public key of the validator that was funded| -|`amount`|`uint256`|The amount of assets that was funded| -### ValidatorConsolidationSubmitted -Event emitted on validators consolidation +### _validatorsConsolidations +**Note:** +oz-upgrades-unsafe-allow: state-variable-immutable ```solidity -event ValidatorConsolidationSubmitted(bytes fromPublicKey, bytes toPublicKey, uint256 feePaid); +address private immutable _validatorsConsolidations ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`fromPublicKey`|`bytes`|The public key of the validator that was consolidated| -|`toPublicKey`|`bytes`|The public key of the validator that was consolidated to| -|`feePaid`|`uint256`|The amount of fee that was paid| -### KeysManagerUpdated -Event emitted on keys manager address update (deprecated) +### _consolidationsChecker +**Note:** +oz-upgrades-unsafe-allow: state-variable-immutable ```solidity -event KeysManagerUpdated(address indexed caller, address indexed keysManager); +address private immutable _consolidationsChecker ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the function caller| -|`keysManager`|`address`|The address of the new keys manager| -### ValidatorsRootUpdated -Event emitted on validators merkle tree root update (deprecated) +### __deprecated__validatorsRoot +deprecated. Deposit data management is moved to DepositDataRegistry contract ```solidity -event ValidatorsRootUpdated(address indexed caller, bytes32 indexed validatorsRoot); +bytes32 private __deprecated__validatorsRoot ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the function caller| -|`validatorsRoot`|`bytes32`|The new validators merkle tree root| -### ValidatorsManagerUpdated -Event emitted on validators manager address update +### __deprecated__validatorIndex +deprecated. Deposit data management is moved to DepositDataRegistry contract ```solidity -event ValidatorsManagerUpdated(address indexed caller, address indexed validatorsManager); +uint256 private __deprecated__validatorIndex ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the function caller| -|`validatorsManager`|`address`|The address of the new validators manager| +### validatorsManager +The Vault validators manager address -## Functions -### validatorsManager +```solidity +address public override validatorsManager +``` -The Vault validators manager address +### _initialDomainSeparator ```solidity -function validatorsManager() external view returns (address); +bytes32 private _initialDomainSeparator ``` -**Returns** -|Name|Type|Description| -|----|----|-----------| -|``|`address`|The address that can register validators| +### v2Validators -### validatorsManagerNonce +```solidity +mapping(bytes32 publicKeyHash => bool isRegistered) public override v2Validators +``` + +### validatorsManagerNonce The nonce for the validators manager used for signing ```solidity -function validatorsManagerNonce() external view returns (uint256); +uint256 public override validatorsManagerNonce ``` -**Returns** -|Name|Type|Description| -|----|----|-----------| -|``|`uint256`|The nonce for the validators manager| +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps -### v2Validators -Function for checking if the validator is tracked V2 validator +```solidity +uint256[47] private __gap +``` + + +## Functions +### constructor + +Constructor + +Since the immutable variable value is stored in the bytecode, +its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + +**Note:** +oz-upgrades-unsafe-allow: constructor ```solidity -function v2Validators(bytes32 publicKeyHash) external view returns (bool); +constructor( + address depositDataRegistry, + address validatorsRegistry, + address validatorsWithdrawals, + address validatorsConsolidations, + address consolidationsChecker +) ; ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`publicKeyHash`|`bytes32`|The keccak256 hash of the public key of the validator| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`bool`|Whether the validator is tracked V2 validator| +|`depositDataRegistry`|`address`|The address of the deposit data registry contract| +|`validatorsRegistry`|`address`|The contract address used for registering validators in beacon chain| +|`validatorsWithdrawals`|`address`|The contract address used for withdrawing validators in beacon chain| +|`validatorsConsolidations`|`address`|The contract address used for consolidating validators in beacon chain| +|`consolidationsChecker`|`address`|The contract address used for verifying consolidation approvals| ### registerValidators @@ -271,10 +249,96 @@ Function for updating the validators manager. Can only be called by the admin. D ```solidity -function setValidatorsManager(address _validatorsManager) external override; +function setValidatorsManager(address _validatorsManager) external virtual override; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`_validatorsManager`|`address`|The new validators manager address| + + +### _registerValidators + +Internal function for registering validators + + +```solidity +function _registerValidators(ValidatorUtils.ValidatorDeposit[] memory deposits) internal virtual; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`deposits`|`ValidatorUtils.ValidatorDeposit[]`|The validators registration data| + + +### _isValidatorsManager + +Internal function for checking whether the caller is the validators manager. +If the valid signature is provided, update the nonce. + + +```solidity +function _isValidatorsManager(bytes calldata validators, bytes32 nonce, bytes calldata validatorsManagerSignature) + internal + returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`validators`|`bytes`|The concatenated validators data| +|`nonce`|`bytes32`|The nonce of the signature| +|`validatorsManagerSignature`|`bytes`|The optional signature from the validators manager| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|true if the caller is the validators manager| + + +### _computeVaultValidatorsDomain + +Computes the hash of the EIP712 typed data + +This function is used to compute the hash of the EIP712 typed data + + +```solidity +function _computeVaultValidatorsDomain() private view returns (bytes32); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bytes32`|The hash of the EIP712 typed data| + + +### __VaultValidators_upgrade + +Upgrades the VaultValidators contract + + +```solidity +function __VaultValidators_upgrade() internal onlyInitializing; +``` + +### __VaultValidators_init + +Initializes the VaultValidators contract + + +```solidity +function __VaultValidators_init() internal onlyInitializing; +``` + +### __VaultValidators_init_common + +Common initialization for gas optimization + + +```solidity +function __VaultValidators_init_common() private; +``` diff --git a/docs/contracts/api/vaults/modules/VaultVersion.md b/docs/contracts/api/vaults/modules/VaultVersion.md index 2bdde100..73179cce 100644 --- a/docs/contracts/api/vaults/modules/VaultVersion.md +++ b/docs/contracts/api/vaults/modules/VaultVersion.md @@ -6,11 +6,30 @@ description: "Abstract contract defining versioning and upgrade functionality fo # VaultVersion -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/modules/VaultVersion.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/modules/VaultVersion.sol) -**Inherits:** [VaultImmutables →](./VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [UUPSUpgradeable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/UUPSUpgradeable.sol), [VaultAdmin →](./VaultAdmin), IVaultVersion +**Inherits:** [VaultImmutables](./VaultImmutables), [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [UUPSUpgradeable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/UUPSUpgradeable.sol), [VaultAdmin](./VaultAdmin), IVaultVersion -Defines the versioning functionality for the Vault. +Defines the versioning functionality for the Vault + + +## State Variables +### _initSelector + +```solidity +bytes4 private constant _initSelector = bytes4(keccak256("initialize(bytes)")) +``` + + +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` ## Functions @@ -33,16 +52,31 @@ function implementation() external view override returns (address); Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. +Calls `_authorizeUpgrade`. +Emits an `Upgraded` event. -:::custom-notes[Note] +**Note:** oz-upgrades-unsafe-allow-reachable: delegatecall -::: ```solidity function upgradeToAndCall(address newImplementation, bytes memory data) public payable override onlyProxy; ``` +### _authorizeUpgrade + +Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by +`upgradeToAndCall`. +Normally, this function will use an access control modifier such as `Ownable-onlyOwner`. +```solidity +function _authorizeUpgrade(address) internal onlyOwner {} +``` + + +```solidity +function _authorizeUpgrade(address newImplementation) internal view override; +``` + ### vaultId Vault Unique Identifier diff --git a/docs/contracts/api/vaults/modules/VaultWhitelist.md b/docs/contracts/api/vaults/modules/VaultWhitelist.md index 21eb144c..23e92c91 100644 --- a/docs/contracts/api/vaults/modules/VaultWhitelist.md +++ b/docs/contracts/api/vaults/modules/VaultWhitelist.md @@ -6,109 +6,113 @@ description: "Abstract contract defining whitelisting functionality for vaults" # VaultWhitelist -[Git Source ↗](https://github.com/stakewise/v3-core/blob/c511cd912cb881f60cf2a32d6c5d5f533e5d04b5/contracts/vaults/modules/VaultWhitelist.sol) +[Git Source ↗](https://github.com/stakewise/v3-core/blob/fc70cbe1b3d41bc5f78434830d837aa270ca33bc/contracts/vaults/modules/VaultWhitelist.sol) -**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol), [VaultAdmin →](./VaultAdmin), IVaultWhitelist +**Inherits:** [Initializable ↗](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol), [VaultAdmin](./VaultAdmin), IVaultWhitelist -Defines the whitelisting functionality for the Vault. +Defines the whitelisting functionality for the Vault -## Events -### WhitelistUpdated -Event emitted on whitelist update +## State Variables +### whitelister +Whitelister address ```solidity -event WhitelistUpdated(address indexed caller, address indexed account, bool approved); +address public override whitelister ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the function caller| -|`account`|`address`|The address of the account updated| -|`approved`|`bool`|Whether account is approved or not| - -### WhitelisterUpdated -Event emitted when whitelister address is updated +### whitelistedAccounts ```solidity -event WhitelisterUpdated(address indexed caller, address indexed whitelister); +mapping(address => bool) public override whitelistedAccounts ``` -**Parameters** -|Name|Type|Description| -|----|----|-----------| -|`caller`|`address`|The address of the function caller| -|`whitelister`|`address`|The address of the new whitelister| +### __gap +This empty reserved space is put in place to allow future versions to add new +variables without shifting down storage in the inheritance chain. +See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + + +```solidity +uint256[50] private __gap +``` ## Functions -### whitelister +### updateWhitelist -Whitelister address +Add or remove account from the whitelist. Can only be called by the whitelister. ```solidity -function whitelister() external view returns (address); +function updateWhitelist(address account, bool approved) external override; ``` -**Returns** +**Parameters** |Name|Type|Description| |----|----|-----------| -|``|`address`|The address of the whitelister| +|`account`|`address`|The account to add or remove from the whitelist| +|`approved`|`bool`|Whether account should be whitelisted or not| -### whitelistedAccounts +### setWhitelister -Checks whether account is whitelisted or not +Used to update the whitelister. Can only be called by the Vault admin. ```solidity -function whitelistedAccounts(address account) external view returns (bool); +function setWhitelister(address _whitelister) external override; ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`account`|`address`|The account to check| +|`_whitelister`|`address`|The address of the new whitelister| + -**Returns** +### _checkWhitelist + +Internal function for checking whether account is in the whitelist + + +```solidity +function _checkWhitelist(address account) internal view; +``` +**Parameters** |Name|Type|Description| |----|----|-----------| -|``|`bool`|`true` for the whitelisted account, `false` otherwise| +|`account`|`address`|The address of the account to check| -### updateWhitelist +### _setWhitelister -Add or remove account from the whitelist. Can only be called by the whitelister. +Internal function for updating the whitelister externally or from the initializer ```solidity -function updateWhitelist(address account, bool approved) external override; +function _setWhitelister(address _whitelister) private; ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`account`|`address`|The account to add or remove from the whitelist| -|`approved`|`bool`|Whether account should be whitelisted or not| +|`_whitelister`|`address`|The address of the new whitelister| -### setWhitelister +### __VaultWhitelist_init -Used to update the whitelister. Can only be called by the Vault admin. +Initializes the VaultWhitelist contract ```solidity -function setWhitelister(address _whitelister) external override; +function __VaultWhitelist_init(address _whitelister) internal onlyInitializing; ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`_whitelister`|`address`|The address of the new whitelister| +|`_whitelister`|`address`|The address of the whitelister| diff --git a/docs/contracts/audits.mdx b/docs/contracts/audits.mdx index b5c1f16a..a98523b2 100644 --- a/docs/contracts/audits.mdx +++ b/docs/contracts/audits.mdx @@ -1,6 +1,6 @@ --- title: Audits -description: Security audits of StakeWise V3 smart contracts by Halborn, Sigma Prime, Consensys Diligence, and ABDK. +description: Security audits of StakeWise V3 smart contracts by Statemind, ABDK, Sigma Prime, Consensys Diligence, and Halborn. --- # Audits @@ -9,6 +9,7 @@ Security is central to how StakeWise is built. Every new feature and contract up | Date | Auditor | Report | |------|---------|--------| +| Apr 2026 | [Statemind ↗](https://statemind.io/) | [2026-04-Statemind.pdf ↗](https://github.com/stakewise/v3-core/blob/main/audits/2026-04-Statemind.pdf) | | Sep 2025 | [ABDK ↗](https://abdk.consulting/) | [2025-09-ABDK.pdf ↗](https://github.com/stakewise/v3-core/blob/main/audits/2025-09-ABDK.pdf) | | Sep 2024 | [Sigma Prime ↗](https://sigmaprime.io/) | [2024-09-Sigma-Prime.pdf ↗](https://github.com/stakewise/v3-core/blob/main/audits/2024-09-Sigma-Prime.pdf) | | Jun 2024 | [Sigma Prime ↗](https://sigmaprime.io/) | [2024-06-Sigma-Prime.pdf ↗](https://github.com/stakewise/v3-core/blob/main/audits/2024-06-Sigma-Prime.pdf) | diff --git a/docs/contracts/networks/Gnosis.mdx b/docs/contracts/networks/Gnosis.mdx index e990197c..a40aebd1 100644 --- a/docs/contracts/networks/Gnosis.mdx +++ b/docs/contracts/networks/Gnosis.mdx @@ -24,7 +24,6 @@ View contracts on [Gnosisscan ↗](https://gnosisscan.io) | LegacyPoolEscrow | [`0xfc9B67b6034F6B306EA9Bd8Ec1baf3eFA2490394` ↗](https://gnosisscan.io/address/0xfc9B67b6034F6B306EA9Bd8Ec1baf3eFA2490394) | | LegacyRewardToken | [`0x6aC78efae880282396a335CA2F79863A1e6831D4` ↗](https://gnosisscan.io/address/0x6aC78efae880282396a335CA2F79863A1e6831D4) | | MerkleDistributor | [`0xFBceefdBB0ca25a4043b35EF49C2810425243710` ↗](https://gnosisscan.io/address/0xFBceefdBB0ca25a4043b35EF49C2810425243710) | -| MetaVaultFactory | [`0x75E6f7640137c128c91dE4Ee62F6f30b542e5f1B` ↗](https://gnosisscan.io/address/0x75E6f7640137c128c91dE4Ee62F6f30b542e5f1B) | | OsToken | [`0xF490c80aAE5f2616d3e3BDa2483E30C4CB21d1A0` ↗](https://gnosisscan.io/address/0xF490c80aAE5f2616d3e3BDa2483E30C4CB21d1A0) | | OsTokenConfig | [`0xd6672fbE1D28877db598DC0ac2559A15745FC3ec` ↗](https://gnosisscan.io/address/0xd6672fbE1D28877db598DC0ac2559A15745FC3ec) | | OsTokenFlashLoans | [`0xe84183EfFbcc76D022Cccc31b95EAa332bB5Bb11` ↗](https://gnosisscan.io/address/0xe84183EfFbcc76D022Cccc31b95EAa332bB5Bb11) | @@ -36,7 +35,7 @@ View contracts on [Gnosisscan ↗](https://gnosisscan.io) | PrivVaultFactory | [`0x4C958642F1CD735F13aed02A4FB015153edDf8Fd` ↗](https://gnosisscan.io/address/0x4C958642F1CD735F13aed02A4FB015153edDf8Fd) | | RewardSplitterFactory | [`0xd12Df8543e0522CCbF12d231e822B7264c634775` ↗](https://gnosisscan.io/address/0xd12Df8543e0522CCbF12d231e822B7264c634775) | | SharedMevEscrow | [`0x30db0d10d3774e78f8cB214b9e8B72D4B402488a` ↗](https://gnosisscan.io/address/0x30db0d10d3774e78f8cB214b9e8B72D4B402488a) | -| SubVaultsRegistryFactory | [`0x37Bf0883c27365CffCd0C4202918df930989891f` ↗](https://gnosisscan.io/address/0x37Bf0883c27365CffCd0C4202918df930989891f) | +| SubVaultsRegistryFactory | [`0x45EA1d8Cb97fD04e064F92eB56305610900928DC` ↗](https://gnosisscan.io/address/0x45EA1d8Cb97fD04e064F92eB56305610900928DC) | | ValidatorsChecker | [`0x80353898B72417AC5701a9809A9eF63F691BdE86` ↗](https://gnosisscan.io/address/0x80353898B72417AC5701a9809A9eF63F691BdE86) | | VaultFactory | [`0x7A8cbBf690084E43De778173cfAcf7313c9122DD` ↗](https://gnosisscan.io/address/0x7A8cbBf690084E43De778173cfAcf7313c9122DD) | | VaultsRegistry | [`0x7d014B3C6ee446563d4e0cB6fBD8C3D0419867cB` ↗](https://gnosisscan.io/address/0x7d014B3C6ee446563d4e0cB6fBD8C3D0419867cB) | diff --git a/docs/contracts/networks/Hoodi.mdx b/docs/contracts/networks/Hoodi.mdx index 1ac84414..b034997d 100644 --- a/docs/contracts/networks/Hoodi.mdx +++ b/docs/contracts/networks/Hoodi.mdx @@ -19,7 +19,7 @@ View contracts on [Hoodi Etherscan ↗](https://hoodi.etherscan.io) | ConsolidationsChecker | [`0x033E5BaE5bdc459CBb7d388b41a9d62020Be810F` ↗](https://hoodi.etherscan.io/address/0x033E5BaE5bdc459CBb7d388b41a9d62020Be810F) | | CuratorsRegistry | [`0xa23F7c8d25f4503cA4cEd84d9CC2428e8745933C` ↗](https://hoodi.etherscan.io/address/0xa23F7c8d25f4503cA4cEd84d9CC2428e8745933C) | | DepositDataRegistry | [`0x93a3f880E07B27dacA6Ef2d3C23E77DBd6294487` ↗](https://hoodi.etherscan.io/address/0x93a3f880E07B27dacA6Ef2d3C23E77DBd6294487) | -| Erc20MetaVaultFactory | [`0xd21e8401c2DBbb180C621E8B3B22a842Ea73e487` ↗](https://hoodi.etherscan.io/address/0xd21e8401c2DBbb180C621E8B3B22a842Ea73e487) | +| Erc20MetaVaultFactory | [`0x57582d566be7ebf9046CA015c8059132DAb85FD8` ↗](https://hoodi.etherscan.io/address/0x57582d566be7ebf9046CA015c8059132DAb85FD8) | | Erc20VaultFactory | [`0x97795DA27138BD8d79204D37F3A2e80fA4d30488` ↗](https://hoodi.etherscan.io/address/0x97795DA27138BD8d79204D37F3A2e80fA4d30488) | | FoxVault | [`0xE96aC18cFE5A7AF8FE1FE7Bc37FF110d88bc67fF` ↗](https://hoodi.etherscan.io/address/0xE96aC18cFE5A7AF8FE1FE7Bc37FF110d88bc67fF) | | GenesisVault | [`0xba447498DC4c169f2b4f427B2c4D532320457E89` ↗](https://hoodi.etherscan.io/address/0xba447498DC4c169f2b4f427B2c4D532320457E89) | @@ -27,7 +27,7 @@ View contracts on [Hoodi Etherscan ↗](https://hoodi.etherscan.io) | LegacyPoolEscrow | [`0x291Fa5849215847081B475450cBE5De46CfD4fAE` ↗](https://hoodi.etherscan.io/address/0x291Fa5849215847081B475450cBE5De46CfD4fAE) | | LegacyRewardToken | [`0x75c57bd50A3EB7291Da3429956D3566E0153A38f` ↗](https://hoodi.etherscan.io/address/0x75c57bd50A3EB7291Da3429956D3566E0153A38f) | | MerkleDistributor | [`0xc61847D6Fc1F64162fF9F1d06205D9c4cDb2f239` ↗](https://hoodi.etherscan.io/address/0xc61847D6Fc1F64162fF9F1d06205D9c4cDb2f239) | -| MetaVaultFactory | [`0xFC10dB96A7724Fa3Efb47C5304b29d3C01004b47` ↗](https://hoodi.etherscan.io/address/0xFC10dB96A7724Fa3Efb47C5304b29d3C01004b47) | +| MetaVaultFactory | [`0xB7cB12e10f4eb2E118c57cd67BBeAE027A1DA53d` ↗](https://hoodi.etherscan.io/address/0xB7cB12e10f4eb2E118c57cd67BBeAE027A1DA53d) | | NodesManager | [`0x7FaE5772fEe74e71077D3B47e629DD7a9Adb7149` ↗](https://hoodi.etherscan.io/address/0x7FaE5772fEe74e71077D3B47e629DD7a9Adb7149) | | OsToken | [`0x7345fC8268459413beE9e9dd327f31283C65Ee7e` ↗](https://hoodi.etherscan.io/address/0x7345fC8268459413beE9e9dd327f31283C65Ee7e) | | OsTokenConfig | [`0x5b817621EBE00622b9a71b53c942b392751c8197` ↗](https://hoodi.etherscan.io/address/0x5b817621EBE00622b9a71b53c942b392751c8197) | @@ -36,13 +36,13 @@ View contracts on [Hoodi Etherscan ↗](https://hoodi.etherscan.io) | OsTokenVaultController | [`0x140Fc69Eabd77fFF91d9852B612B2323256f7Ac1` ↗](https://hoodi.etherscan.io/address/0x140Fc69Eabd77fFF91d9852B612B2323256f7Ac1) | | OsTokenVaultEscrow | [`0xdC1347cC04d4a8945b98A09C3c5585286bbA5C2B` ↗](https://hoodi.etherscan.io/address/0xdC1347cC04d4a8945b98A09C3c5585286bbA5C2B) | | PriceFeed | [`0xe8a222D887b468a71Ee8a27df4fa3b886A4B7BA1` ↗](https://hoodi.etherscan.io/address/0xe8a222D887b468a71Ee8a27df4fa3b886A4B7BA1) | -| PrivErc20MetaVaultFactory | [`0xAc31856e2A81B9Af8b83424817C3bc0770F40a01` ↗](https://hoodi.etherscan.io/address/0xAc31856e2A81B9Af8b83424817C3bc0770F40a01) | +| PrivErc20MetaVaultFactory | [`0xe3fc7aBcFFe4E313bDB5DAE7d1e9B1054d417A34` ↗](https://hoodi.etherscan.io/address/0xe3fc7aBcFFe4E313bDB5DAE7d1e9B1054d417A34) | | PrivErc20VaultFactory | [`0x1831834dC4Bf88B9d9183015e1285B105Ec2FdC9` ↗](https://hoodi.etherscan.io/address/0x1831834dC4Bf88B9d9183015e1285B105Ec2FdC9) | -| PrivMetaVaultFactory | [`0xD13BB2fD02fA060Dc82c439724dC9c4bc59957B7` ↗](https://hoodi.etherscan.io/address/0xD13BB2fD02fA060Dc82c439724dC9c4bc59957B7) | +| PrivMetaVaultFactory | [`0x40471cE713EfB3CB83fF25A754b8BfA28D322A1b` ↗](https://hoodi.etherscan.io/address/0x40471cE713EfB3CB83fF25A754b8BfA28D322A1b) | | PrivVaultFactory | [`0x4C958642F1CD735F13aed02A4FB015153edDf8Fd` ↗](https://hoodi.etherscan.io/address/0x4C958642F1CD735F13aed02A4FB015153edDf8Fd) | | RewardSplitterFactory | [`0xd12Df8543e0522CCbF12d231e822B7264c634775` ↗](https://hoodi.etherscan.io/address/0xd12Df8543e0522CCbF12d231e822B7264c634775) | | SharedMevEscrow | [`0x51FD45BAEfB12f54766B5C4d639b360Ea50063bd` ↗](https://hoodi.etherscan.io/address/0x51FD45BAEfB12f54766B5C4d639b360Ea50063bd) | -| SubVaultsRegistryFactory | [`0xA1A0d4A01CE470D11d264Fb966Bc7cC87268d560` ↗](https://hoodi.etherscan.io/address/0xA1A0d4A01CE470D11d264Fb966Bc7cC87268d560) | +| SubVaultsRegistryFactory | [`0x2D4B9AAFDD3eF81B869AA6b8EadC6Ef8fB11eCe1` ↗](https://hoodi.etherscan.io/address/0x2D4B9AAFDD3eF81B869AA6b8EadC6Ef8fB11eCe1) | | ValidatorsChecker | [`0xF1b78B96212DCcbf8F34A83535e58864311Fb9eB` ↗](https://hoodi.etherscan.io/address/0xF1b78B96212DCcbf8F34A83535e58864311Fb9eB) | | VaultFactory | [`0x7A8cbBf690084E43De778173cfAcf7313c9122DD` ↗](https://hoodi.etherscan.io/address/0x7A8cbBf690084E43De778173cfAcf7313c9122DD) | | VaultsRegistry | [`0xf16fea93D3253A401C3f73B0De890C6586740B25` ↗](https://hoodi.etherscan.io/address/0xf16fea93D3253A401C3f73B0De890C6586740B25) | diff --git a/docs/contracts/networks/Mainnet.mdx b/docs/contracts/networks/Mainnet.mdx index 814856c6..1e65a4cd 100644 --- a/docs/contracts/networks/Mainnet.mdx +++ b/docs/contracts/networks/Mainnet.mdx @@ -19,7 +19,7 @@ View contracts on [Etherscan ↗](https://etherscan.io) | ConsolidationsChecker | [`0x033E5BaE5bdc459CBb7d388b41a9d62020Be810F` ↗](https://etherscan.io/address/0x033E5BaE5bdc459CBb7d388b41a9d62020Be810F) | | CuratorsRegistry | [`0xa23F7c8d25f4503cA4cEd84d9CC2428e8745933C` ↗](https://etherscan.io/address/0xa23F7c8d25f4503cA4cEd84d9CC2428e8745933C) | | DepositDataRegistry | [`0x75AB6DdCe07556639333d3Df1eaa684F5735223e` ↗](https://etherscan.io/address/0x75AB6DdCe07556639333d3Df1eaa684F5735223e) | -| Erc20MetaVaultFactory | [`0x4E3dE90882B3d10D067b8954909D4A4b0Bb390D0` ↗](https://etherscan.io/address/0x4E3dE90882B3d10D067b8954909D4A4b0Bb390D0) | +| Erc20MetaVaultFactory | [`0xCd191DfDF52ae7aa2B1AD7B4a6A9D4d4FFB837B5` ↗](https://etherscan.io/address/0xCd191DfDF52ae7aa2B1AD7B4a6A9D4d4FFB837B5) | | Erc20VaultFactory | [`0x97795DA27138BD8d79204D37F3A2e80fA4d30488` ↗](https://etherscan.io/address/0x97795DA27138BD8d79204D37F3A2e80fA4d30488) | | FoxVault | [`0x4FEF9D741011476750A243aC70b9789a63dd47Df` ↗](https://etherscan.io/address/0x4FEF9D741011476750A243aC70b9789a63dd47Df) | | GenesisVault | [`0xAC0F906E433d58FA868F936E8A43230473652885` ↗](https://etherscan.io/address/0xAC0F906E433d58FA868F936E8A43230473652885) | @@ -27,7 +27,7 @@ View contracts on [Etherscan ↗](https://etherscan.io) | LegacyPoolEscrow | [`0x2296e122c1a20Fca3CAc3371357BdAd3be0dF079` ↗](https://etherscan.io/address/0x2296e122c1a20Fca3CAc3371357BdAd3be0dF079) | | LegacyRewardToken | [`0x20BC832ca081b91433ff6c17f85701B6e92486c5` ↗](https://etherscan.io/address/0x20BC832ca081b91433ff6c17f85701B6e92486c5) | | MerkleDistributor | [`0xa9dc250dF4EE9273D09CFa455da41FB1cAC78d34` ↗](https://etherscan.io/address/0xa9dc250dF4EE9273D09CFa455da41FB1cAC78d34) | -| MetaVaultFactory | [`0x76D90928645065b4D4212eE62ce1ba8f90718f14` ↗](https://etherscan.io/address/0x76D90928645065b4D4212eE62ce1ba8f90718f14) | +| MetaVaultFactory | [`0xE51eE4200510498d5964627B25FE7D4685CB858c` ↗](https://etherscan.io/address/0xE51eE4200510498d5964627B25FE7D4685CB858c) | | NodesManager | [`0x5674848102D29d0652f03AcDc548C342ecA13592` ↗](https://etherscan.io/address/0x5674848102D29d0652f03AcDc548C342ecA13592) | | OsToken | [`0xf1C9acDc66974dFB6dEcB12aA385b9cD01190E38` ↗](https://etherscan.io/address/0xf1C9acDc66974dFB6dEcB12aA385b9cD01190E38) | | OsTokenConfig | [`0x287d1e2A8dE183A8bf8f2b09Fa1340fBd766eb59` ↗](https://etherscan.io/address/0x287d1e2A8dE183A8bf8f2b09Fa1340fBd766eb59) | @@ -36,13 +36,13 @@ View contracts on [Etherscan ↗](https://etherscan.io) | OsTokenVaultController | [`0x2A261e60FB14586B474C208b1B7AC6D0f5000306` ↗](https://etherscan.io/address/0x2A261e60FB14586B474C208b1B7AC6D0f5000306) | | OsTokenVaultEscrow | [`0x09e84205DF7c68907e619D07aFD90143c5763605` ↗](https://etherscan.io/address/0x09e84205DF7c68907e619D07aFD90143c5763605) | | PriceFeed | [`0x8023518b2192FB5384DAdc596765B3dD1cdFe471` ↗](https://etherscan.io/address/0x8023518b2192FB5384DAdc596765B3dD1cdFe471) | -| PrivErc20MetaVaultFactory | [`0xE14FA9bBdb7813025309f71DdC0FA8fAae1B9141` ↗](https://etherscan.io/address/0xE14FA9bBdb7813025309f71DdC0FA8fAae1B9141) | +| PrivErc20MetaVaultFactory | [`0x8f347eb308707DC1FA1acCF3ea889CF554b6B8A5` ↗](https://etherscan.io/address/0x8f347eb308707DC1FA1acCF3ea889CF554b6B8A5) | | PrivErc20VaultFactory | [`0x1831834dC4Bf88B9d9183015e1285B105Ec2FdC9` ↗](https://etherscan.io/address/0x1831834dC4Bf88B9d9183015e1285B105Ec2FdC9) | -| PrivMetaVaultFactory | [`0x1e86e620567bb877F5ED13607A1a7B7DBcb6BE66` ↗](https://etherscan.io/address/0x1e86e620567bb877F5ED13607A1a7B7DBcb6BE66) | +| PrivMetaVaultFactory | [`0x42427acFf6DdC93f73Fcb53d1a03AA26A292bA1A` ↗](https://etherscan.io/address/0x42427acFf6DdC93f73Fcb53d1a03AA26A292bA1A) | | PrivVaultFactory | [`0x4C958642F1CD735F13aed02A4FB015153edDf8Fd` ↗](https://etherscan.io/address/0x4C958642F1CD735F13aed02A4FB015153edDf8Fd) | | RewardSplitterFactory | [`0xd12Df8543e0522CCbF12d231e822B7264c634775` ↗](https://etherscan.io/address/0xd12Df8543e0522CCbF12d231e822B7264c634775) | | SharedMevEscrow | [`0x48319f97E5Da1233c21c48b80097c0FB7a20Ff86` ↗](https://etherscan.io/address/0x48319f97E5Da1233c21c48b80097c0FB7a20Ff86) | -| SubVaultsRegistryFactory | [`0x3cc1bde89640E0D32b2D2D66D3098d9Bc11b17Ee` ↗](https://etherscan.io/address/0x3cc1bde89640E0D32b2D2D66D3098d9Bc11b17Ee) | +| SubVaultsRegistryFactory | [`0xBB3aa7F8D85dB3e39aD859D8e4f295e0E8bDD647` ↗](https://etherscan.io/address/0xBB3aa7F8D85dB3e39aD859D8e4f295e0E8bDD647) | | ValidatorsChecker | [`0x508e82B5119CCfB923C387d62D2Ae7B56Df79906` ↗](https://etherscan.io/address/0x508e82B5119CCfB923C387d62D2Ae7B56Df79906) | | VaultFactory | [`0x7A8cbBf690084E43De778173cfAcf7313c9122DD` ↗](https://etherscan.io/address/0x7A8cbBf690084E43De778173cfAcf7313c9122DD) | | VaultsRegistry | [`0x3a0008a588772446f6e656133C2D5029CC4FC20E` ↗](https://etherscan.io/address/0x3a0008a588772446f6e656133C2D5029CC4FC20E) | diff --git a/docs/contracts/networks/index.mdx b/docs/contracts/networks/index.mdx index 18d80639..9e25b90f 100644 --- a/docs/contracts/networks/index.mdx +++ b/docs/contracts/networks/index.mdx @@ -6,16 +6,53 @@ description: StakeWise V3 smart contract addresses deployed on Ethereum Mainnet, # Networks -This section provides the complete list of StakeWise V3 smart contract addresses deployed across 3 supported networks: [Mainnet →](./Mainnet.mdx), [Gnosis →](./Gnosis.mdx), and [Hoodi →](./Hoodi.mdx). -Each network page contains a table of all core protocol contracts including: -- Different Vault factory types (ERC20, Blocklist, MetaVault) -- Vault registry keeping track of all registered Vaults -- osToken contracts with minting, liquidation, and redemption mechanisms -- Vault deposit data management -- Functionality for updating Vaults' rewards and approving validator registrations -- Curator strategy for managing assets in sub-vaults - -Use these addresses when integrating with StakeWise V4 contracts or verifying deployments on each respective network. +This section provides the complete list of StakeWise V3 smart contract addresses deployed across 3 supported networks: [Mainnet](./Mainnet.mdx), [Gnosis](./Gnosis.mdx), and [Hoodi](./Hoodi.mdx). +Each network page lists every core protocol contract. What they do: + +## Vaults + +| Contract | Purpose | +|----------|---------| +| `VaultFactory`, `Erc20VaultFactory`, `PrivVaultFactory`, `BlocklistVaultFactory`, `BlocklistErc20VaultFactory`, `PrivErc20VaultFactory` | Deploy new Vaults of each [Vault type](/docs/vaults/vault-types) and register them in the `VaultsRegistry` | +| `MetaVaultFactory`, `Erc20MetaVaultFactory`, `PrivMetaVaultFactory`, `PrivErc20MetaVaultFactory` | Deploy [MetaVaults](/docs/vaults/vault-types#metavault), which delegate staking to sub-vaults | +| `VaultsRegistry` | The canonical on-chain list of valid Vaults and approved Vault implementations | +| `SubVaultsRegistryFactory` | Deploys the per-MetaVault registry that holds sub-vault positions, exit queues and capacities | +| `CuratorsRegistry`, `BalancedCurator` | DAO-approved allocation strategies that decide how a MetaVault spreads assets across its sub-vaults | +| `GenesisVault` | The Vault that succeeded StakeWise V2, holding stake migrated from the legacy pool | +| `CommunityVault`, `FoxVault` | Custom Vaults deployed for specific partners, built on the same modules as standard Vaults | +| `RewardSplitterFactory` | Deploys the `RewardSplitter` contracts that divide a Vault's fees among several [shareholders](/operator/manage-vault/fee-claiming) | + +## osToken + +| Contract | Purpose | +|----------|---------| +| `OsToken` | The osETH / osGNO ERC-20 token itself | +| `OsTokenVaultController` | Tracks total osToken supply and backing assets, and so sets the [exchange rate](/docs/ostoken/how-ostoken-works#exchange-rate) | +| `OsTokenConfig` | Per-Vault [LTV, liquidation threshold and liquidation bonus](/docs/ostoken/how-ostoken-works#ltv-ratio) | +| `OsTokenRedeemer` | The queue through which osToken is [redeemed](/docs/ostoken/ostoken-redemptions) for ETH or GNO at the protocol rate | +| `OsTokenVaultEscrow` | Holds an osToken position alongside its stake while a user exits a Vault with osToken still minted, so the token stays backed throughout | +| `OsTokenFlashLoans` | Uncollateralised same-transaction osToken loans, capped at 100,000 osToken | +| `PriceFeed` | Reports the osToken exchange rate to external protocols — this is the feed [Aave uses to value Boost collateral](/docs/vaults/boost#safety) | + +## Validators and rewards + +| Contract | Purpose | +|----------|---------| +| `Keeper` | Verifies [Oracle](/docs/oracles/intro) signatures, stores the rewards Merkle root, and approves validator registrations | +| `ConsolidationsChecker` | Verifies Oracle signatures for [validator consolidations](/operator/manage-validators/consolidate-validators) | +| `ValidatorsChecker` | Read-only helper that reports whether a Vault's validator registration would currently succeed | +| `DepositDataRegistry` | Stores the deposit data root pre-approving which validator keys a Vault may register | +| `SharedMevEscrow` | The [Smoothing Pool](/docs/vaults/configuration#smoothing-pool) — shared MEV escrow for participating Vaults | +| `NodesManager` | Delegates a Vault's validator operations to registered node operators | +| `MerkleDistributor` | Distributes [extra token rewards](/operator/manage-vault/add-extra-rewards) to Vault stakers | + +## Legacy + +| Contract | Purpose | +|----------|---------| +| `LegacyPoolEscrow`, `LegacyRewardToken` | StakeWise V2 contracts retained for [migration](/staker/stakewise-v2/migrate-to-stakewise-v3-on-ethereum) into the Genesis Vault | + +Use these addresses when integrating with StakeWise V3 contracts or verifying deployments on each respective network. :::custom-info[Usage] To find any contract, copy its address and paste it into the appropriate block explorer for that network. diff --git a/docs/contracts/verify-vault-contract.mdx b/docs/contracts/verify-vault-contract.mdx index 888509dc..f7a8e306 100644 --- a/docs/contracts/verify-vault-contract.mdx +++ b/docs/contracts/verify-vault-contract.mdx @@ -30,7 +30,7 @@ To interact with your Vault on Etherscan — call functions, view the ABI, or in 1. **Find Your Vault Contract Address** - If you need help finding your Vault contract address, see [Where to Find Your Vault Address →](/operator/create-regular-vault#where-to-find-your-vault-address). + If you need help finding your Vault contract address, see [Where to Find Your Vault Address](/operator/create-regular-vault#where-to-find-your-vault-address). 2. **Open the Contract on Block Explorer** diff --git a/docs/docs/glossary.mdx b/docs/docs/glossary.mdx index 3249181a..bfd30ffa 100644 --- a/docs/docs/glossary.mdx +++ b/docs/docs/glossary.mdx @@ -5,11 +5,17 @@ description: Key terms and concepts in the StakeWise protocol # Glossary +### Automatic Unboost Automatic Unboost +A safety mechanism in Boost that automatically unboosts positions approaching the liquidation threshold. When any position reaches 94.5% LTV, anyone can trigger an automatic unboosting transaction to protect the user, with all funds remaining under the original owner's control. + +### Blocklist Manager Blocklist Manager +A Vault role that controls who is barred from depositing into a Blocklist Vault. Can add or remove blocked addresses. Only present in Blocklist Vaults. + ### Blocklist Vault Blocklist Vault -A Vault type that accepts deposits from any wallet except those on a specified blocklist. The blocklist is managed by the Whitelist/Blocklist Manager role. +A Vault type that accepts deposits from any wallet except those on a specified blocklist. The blocklist is managed by the Blocklist Manager role. ### Boost Boost -A StakeWise feature that amplifies staking rewards by using osTokens as collateral on Aave to borrow additional assets, which are then staked again. This creates a leverage loop that can provide up to 3x boost in staking rewards compared to normal staking. +A StakeWise feature, available on Ethereum, that amplifies staking rewards by using osETH as collateral on Aave to borrow additional ETH, which is then staked again. The loop multiplies the staked position roughly 6x in a 90% LTV Vault and up to 14x in a near-100% LTV Vault, historically adding 1–3 percentage points of APY on top of the base staking rate. ### Burning Burning The process of returning minted osTokens to a Vault in exchange for unlocking the staked collateral that backs them. Users must burn all minted osTokens plus accrued protocol fees to fully unstake. Partial burns are possible to reduce debt or improve position health. @@ -23,19 +29,16 @@ The process of merging multiple legacy validators (0x01) into compound validator ### DAO-Approved Vault DAO-Approved Vault Vaults that meet stringent DAO criteria (defined in [SWIP-24 ↗](https://forum.stakewise.io/t/swip-24-establish-a-policy-for-enabling-100-oseth-minting-in-select-vaults/1724)) and qualify for a near-100% minting threshold (LTV) for osETH/osGNO—enabling automatic liquid staking token issuance without overcollateralization. This maximizes capital efficiency and unlocks advanced use cases like consumer liquid staking, exchange integrations, and leveraged DeFi strategies. -Requirements include substantial stake (≥10k ETH / ≥5k GNO), fee caps (≤5% / ≤15%), above-median performance, Vault v3, significant SWISE bond (5M / 1M), and DAO governance approval. +Requirements include substantial stake (≥10k ETH / ≥5k GNO), fee caps (≤5% / ≤15%), above-median performance, the latest Vault version, a significant SWISE bond (5M / 1M), and DAO governance approval. ### ERC-20 Vault ERC-20 Vault A Vault type that issues transferable ERC-20 tokens representing stake in the Vault. Operators set the token's name and symbol, enabling custom DeFi ecosystems around the Vault's token. Users can transfer their stake as long as they don't have osToken minted against it. -### Automatic Unboost Automatic Unboost -A safety mechanism in Boost that automatically unboosts positions approaching the liquidation threshold. When any position reaches 94.5% LTV, anyone can trigger an automatic unboosting transaction to protect the user, with all funds remaining under the original owner's control. - ### Exit Queue Exit Queue A queue system that manages withdrawal requests from a Vault. When users request to withdraw, they receive a position ticket tracking their place in line. As ETH becomes available through unbonded assets or validator exits, users can claim their funds according to their queue position. -### osToken Exchange Rate OsToken Exchange Rate -The amount of underlying assets (ETH or GNO) that can be received when redeeming osToken within the protocol. This rate increases over time as staking rewards accrue, reflected through the `convertToAssets` and `convertToShares` functions in the osTokenVaultController contract. +### Exit Queue Ticket Exit Queue Ticket +A record issued when a user enters the exit queue, tracking their place in line and the amount they've requested to withdraw. Users can claim available funds according to their ticket number as ETH becomes available in the Vault. ### Fee Recipient Fee Recipient The address designated to receive Vault fees. Set by the Vault Admin and can be updated over time. Typically the Vault operator or a multisig controlled by the operator team. @@ -53,10 +56,10 @@ The original validator type with a fixed stake of 32 ETH (or 1 GNO on Gnosis Cha In 90% LTV Vaults, the process of closing an unhealthy osToken position that exceeds 92% LTV. Anyone can liquidate by burning all minted osTokens against the collateral. The liquidator receives the underlying collateral value plus a 1% premium, with the penalty deducted from the staker's collateral. Positions in 99.99% LTV Vaults are exempt from liquidation. ### LTV (Loan-to-Value) LTV (Loan-to-Value) -The ratio of minted osToken value to staked collateral value, expressed as a percentage. Standard Vaults have 90% LTV, while DAO-approved Vaults can have up to 99.99% LTV (osETH) or 99.95% LTV (osGNO). This determines the maximum amount of osToken that can be minted against staked assets. +The ratio of minted osToken value to staked collateral value, expressed as a percentage. Standard Vaults have 90% LTV, while DAO-approved Vaults can have up to 99.99% LTV (osETH) or 99.975% LTV (osGNO). This determines the maximum amount of osToken that can be minted against staked assets. ### MetaVault MetaVault -A Vault type that does not register validators directly, instead delegating accumulated assets to sub-Vaults managed by the Vault Admin. Deposits are distributed across underlying sub-Vaults (up to 50 maximum) according to curator-defined allocation logic. +A Vault type that does not register validators directly, instead delegating accumulated assets to sub-Vaults. The Vault Admin chooses the sub-Vault set; a DAO-approved Curator contract decides how deposits and withdrawals are distributed across them (up to 50 sub-Vaults). A sub-Vault can itself be a MetaVault, so MetaVaults can be nested. ### Minting Minting The process of issuing osTokens against staked assets held in a Vault. Minting limits are determined by the Vault's LTV ratio to ensure overcollateralization. Users can mint osTokens to access liquidity while their stake continues earning rewards. @@ -67,35 +70,38 @@ Specialized software that node operators run alongside their Ethereum consensus ### Oracle Network Oracle Network StakeWise's decentralized network of Oracle nodes that connects smart contracts to the Beacon Chain. Manages validator registration approvals, calculates rewards and penalties, maintains osToken exchange rates, and enforces validator exits when needed. -### osToken osToken -Short for Overcollateralized Staked Token. StakeWise's liquid staking tokens (osETH on Ethereum, osGNO on Gnosis Chain) that represent staked assets plus accrued rewards. The value of staked assets backing osToken exceeds the token's value, providing a safety buffer against slashing and validator underperformance. - ### osETH osETH The overcollateralized liquid staking token on Ethereum. A repricing ERC-20 token that represents staked ETH plus accumulated rewards, with its value increasing over time as staking rewards accrue. ### osGNO osGNO The overcollateralized liquid staking token on Gnosis Chain. A repricing ERC-20 token that represents staked GNO plus accumulated rewards, with its value increasing over time as staking rewards accrue. +### osToken osToken +Short for Overcollateralized Staked Token. StakeWise's liquid staking tokens (osETH on Ethereum, osGNO on Gnosis Chain) that represent staked assets plus accrued rewards. The value of staked assets backing osToken exceeds the token's value, providing a safety buffer against slashing and validator underperformance. + +### osToken Exchange Rate osToken Exchange Rate +The amount of underlying assets (ETH or GNO) that can be received when redeeming osToken within the protocol. This rate increases over time as staking rewards accrue, reflected through the `convertToAssets` and `convertToShares` functions in the osTokenVaultController contract. + ### osToken Fee osToken Fee A 5% fee charged by StakeWise DAO on rewards accumulated by osToken. The fee is applied continuously to users' osToken debt (the amount they must burn to exit), with collected fees going to the DAO Treasury. -### Partial Withdrawal Partial Withdrawal -A withdrawal of rewards from a compound validator (0x02) without fully exiting the validator. Enabled by EIP-7002, partial withdrawals allow Vaults to access accumulated rewards above the minimum stake requirement while keeping validators active. - -### OsToken Position OsToken Position +### osToken Position osToken Position An osToken minting position representing the relationship between a user's staked collateral in a Vault and their minted osToken debt. Position health is tracked through LTV ratios. -### Exit Queue Ticket Exit Queue Ticket -A record issued when a user enters the exit queue, tracking their place in line and the amount they've requested to withdraw. Users can claim available funds according to their ticket number as ETH becomes available in the Vault. +### Own Escrow Own Escrow +MEV strategy where block proposal rewards are collected solely by individual Vaults. Own Escrow allows a Vault to choose an independent approach to earning block production rewards, like using any relay of choice, because it does not rely on sharing such rewards with other Vaults. Own Escrow targets maximum value capture but with increased variability. + +### Partial Withdrawal Partial Withdrawal +A withdrawal of rewards from a compound validator (0x02) without fully exiting the validator. Enabled by EIP-7002, partial withdrawals allow Vaults to access accumulated rewards above the minimum stake requirement while keeping validators active. ### Private Vault Private Vault -A Vault type that only accepts deposits from wallets that have been whitelisted by the Vault Admin. Access control is managed by the Whitelist/Blocklist Manager role. +A Vault type that only accepts deposits from wallets that have been whitelisted. Access control is managed by the Whitelist Manager role. ### Repricing Token Repricing Token A token whose redemption value increases over time rather than its quantity. osToken is a repricing token—each osETH or osGNO becomes worth more of the underlying asset as staking rewards accrue, reflected through an increasing exchange rate. ### Smoothing Pool Smoothing Pool -A mechanism for distributing block proposal rewards (MEV and priority fees) evenly across all participating Vaults. Instead of keeping volatile block rewards, Vaults contribute them to a pool that provides stable, predictable returns. Vaults using the smoothing pool must operate [DAO-approved MEV relays →](/operator/smoothing-pool-relays). +A mechanism for distributing block proposal rewards (MEV and priority fees) across all participating Vaults in proportion to the attestation rewards their validators earned. Instead of keeping volatile block rewards, Vaults contribute them to a pool that provides stable, predictable returns. Vaults using the smoothing pool must operate [DAO-approved MEV relays](/operator/smoothing-pool-relays). ### Standard Vault Standard Vault The most common Vault type that accepts deposits from any wallet and issues non-transferable shares that earn rewards. Users receive Vault shares representing their proportional stake in the Vault. @@ -109,6 +115,9 @@ ETH in a Vault that is not locked in validators. This includes excess deposits, ### Unboost Queue Unboost Queue A queue system that manages withdrawal requests from Boost positions. When users request to unboost, they enter the queue and can claim their assets as they will exit from the validators. +### Validators Manager Validators Manager +A critical Vault role that grants permission to execute validator operations including registration, funding, consolidation, and withdrawals. The Operator Service requires this role to automatically manage the Vault's validators. + ### Vault Vault An isolated, customizable smart contract that functions as an individual staking pool. Vault operators accept deposits from stakers, register and manage validators, and offer tailored staking services. Each Vault operates independently with its own fee structure, MEV strategy, and risk profile. @@ -116,10 +125,7 @@ An isolated, customizable smart contract that functions as an individual staking The primary controller of a Vault, responsible for overall configuration. The Admin can manage all roles and permissions, modify fees and fee recipients, update branding, and configure Vault settings. Can be a single wallet, multisig, or DAO. ### Vault Capacity Vault Capacity -An immutable deposit limit set during Vault creation that controls the maximum amount of ETH the Vault can accept. When left unset, Vaults have unlimited capacity by default. Helps operators control validator set size based on infrastructure capabilities. - -### Own Escrow Own Escrow -MEV strategy where block proposal rewards are collected solely by individual Vaults. Own Escrow allows a Vault to choose an independent approach to earning block production rewards, like using any relay of choice, because it does not rely on sharing such rewards with other Vaults. Own Escrow targets maximum value capture but with increased variability. +An immutable deposit limit set during Vault creation that controls the maximum amount of ETH or GNO the Vault can accept. When left unset, Vaults have unlimited capacity by default. Helps operators control validator set size based on infrastructure capabilities. ### Vault Fee Vault Fee A percentage fee charged by Vault operators on staking rewards, ranging from 0% to 100%. The fee is automatically deducted when Vault state is updated and the vault shares are minted to the fee recipient. Fees can only be increased by 20% at a time with a 3-day delay between updates. @@ -133,8 +139,5 @@ Entities or individuals who run validators for a Vault. They operate the infrast ### Vault Shares Vault Shares The accounting mechanism used by all Vaults to represent a user's proportional stake in the Vault. Shares accrue rewards over time and can be used as collateral for minting osTokens. In Standard, Private, and Blocklist Vaults, shares are non-transferable. In ERC-20 Vaults, shares are represented as transferable ERC-20 tokens. -### Validators Manager Validators Manager -A critical Vault role that grants permission to execute validator operations including registration, funding, consolidation, and withdrawals. The Operator Service requires this role to automatically manage the Vault's validators. - -### Whitelist/Blocklist Manager Whitelist/Blocklist Manager -A Vault role that controls deposit access permissions. Can add or remove accounts authorized to deposit (whitelist) or prohibited from depositing (blocklist). Only appears in Private Vaults or Vaults with blocklist functionality enabled. +### Whitelist Manager Whitelist Manager +A Vault role that controls who may deposit into a Private Vault. Can add or remove the addresses authorized to deposit. Only present in Private Vaults. diff --git a/docs/docs/oracles/intro.mdx b/docs/docs/oracles/intro.mdx index 45c304c5..f9217b1b 100644 --- a/docs/docs/oracles/intro.mdx +++ b/docs/docs/oracles/intro.mdx @@ -1,6 +1,6 @@ --- title: Oracles -description: Meet the 11 independent operators that form the Oracle network bridging the Beacon Chain and Vault contracts +description: Meet the independent operators that form the Oracle network bridging the Beacon Chain and Vault contracts --- Oracles are specialized entities that connect blockchains to external data sources, enabling smart contracts to access real-world information. @@ -13,7 +13,9 @@ Each Oracle is operated by an independent entity [approved ↗](https://v1.snaps ## Oracle Network Participants -The protocol currently operates with **11 Oracles**, listed below: +Each chain runs its own set of **11 Oracles**, and the two rosters are not identical. + +### Ethereum 1. [Chorus One ↗](https://chorus.one/) 2. [Stake.fish ↗](https://stake.fish/) @@ -26,3 +28,19 @@ The protocol currently operates with **11 Oracles**, listed below: 9. [P2P ↗](https://p2p.org/) 10. [DSRV ↗](https://www.dsrvlabs.com/) 11. [StakeWise Labs ↗](https://x.com/stakewise_io) (the development team) + +### Gnosis Chain + +Gnosis Chain also has 11 Oracles, but they are run by nine organizations — the Gnosis Chain team and StakeWise Labs each operate two: + +1. [Chorus One ↗](https://chorus.one/) +2. [Finoa Consensus Services ↗](https://www.finoa.io/) +3. [Bitfly ↗](https://gobitfly.com/) +4. [SenseiNode ↗](https://www.senseinode.com/) +5. [Gateway.fm ↗](https://gateway.fm/) +6. [Axol.io ↗](https://axol.io/) +7. [DSRV ↗](https://www.dsrvlabs.com/) +8. [Gnosis Chain ↗](https://www.gnosis.io/) team (two Oracles) +9. [StakeWise Labs ↗](https://x.com/stakewise_io) (two Oracles) + +Stake.fish, Telekom and P2P run Oracles on Ethereum only. diff --git a/docs/docs/ostoken/how-ostoken-works.mdx b/docs/docs/ostoken/how-ostoken-works.mdx index 56ae31c8..9d8cdb11 100644 --- a/docs/docs/ostoken/how-ostoken-works.mdx +++ b/docs/docs/ostoken/how-ostoken-works.mdx @@ -44,7 +44,7 @@ LTV limits vary by Vault type: | Vault Type | Ethereum (osETH) | Gnosis (osGNO) | Safety Mechanism | |----------------|------------------|----------------|-----------------------------------------------| | Standard | 90% LTV | 90% LTV | 10% overcollateralization buffer | -| DAO-approved | 99.99% LTV | 99.95% LTV | 5M SWISE operator bond | +| DAO-approved | 99.99% LTV | 99.975% LTV | 5M SWISE operator bond | To qualify for the 99.99% LTV, Vaults must meet strict performance criteria1 and post a SWISE operator bond. StakeWise DAO can adjust any individual Vault's LTV based on its risk profile. @@ -166,7 +166,7 @@ The amount of ETH that can be unstaked at any moment is determined by maintainin - **90%** of staked ETH in a **90% LTV Vault** - **99.99%** of staked ETH in a **99.99% LTV Vault** -The protocol calculates the **maximum unstakable ETH** as: +The protocol calculates the **maximum unstakable ETH** as (the examples below use Ethereum's 99.99% cap; substitute 0.99975 on Gnosis Chain): :::custom-notes[Formula: 90% LTV] $$ @@ -200,7 +200,7 @@ Alice stakes **100 ETH** and mints **50 osToken** at the same **1.05** rate. After unstaking **47.495 ETH**, Alice's LTV = **99.99%**. ::: -When a user exits a Vault while still holding osToken debt, the Vault may not have liquid ETH immediately available, since validators must go through the Ethereum exit queue. An escrow contract holds the osToken position alongside the underlying stake until ETH is freed, then unwinds both in lockstep. This keeps every osToken fully backed throughout the exit process. +When a user exits a Vault while still holding osToken debt, the Vault may not have liquid ETH immediately available, since validators must go through the Ethereum exit queue. The [`OsTokenVaultEscrow`](../../contracts/networks/) contract holds the osToken position alongside the underlying stake until ETH is freed, then unwinds both in lockstep. This keeps every osToken fully backed throughout the exit process. Positions inside the escrow have their own liquidation parameters, set separately from the Vault-level values above.
1. DAO-approved Vault requirements: minimum 10,000 ETH staked, maximum 5% Vault fee, consistently above-median network performance, latest Vault version deployed, and 5M SWISE tokens locked as slashing insurance. diff --git a/docs/docs/ostoken/ostoken-redemptions.mdx b/docs/docs/ostoken/ostoken-redemptions.mdx index e3956a04..253314e0 100644 --- a/docs/docs/ostoken/ostoken-redemptions.mdx +++ b/docs/docs/ostoken/ostoken-redemptions.mdx @@ -115,5 +115,5 @@ If only some of the ticket is covered and the rest is still queued, the contract The ticket holder calls `claimExitedAssets` with their ticket and the matching checkpoint index, and the contract pays out the corresponding ETH/GNO. If the ticket spans more than one checkpoint, the covered portion is paid out and the remainder rolls into a new ticket that can be claimed after a later checkpoint. :::custom-notes[Deep Dive] -For more details on how Oracles distribute rewards and enforce validator exits, see [Oracle Duties →](/docs/oracles/oracle-duties). +For more details on how Oracles distribute rewards and enforce validator exits, see [Oracle Duties](/docs/oracles/oracle-duties). ::: diff --git a/docs/docs/stakewise-protocol/understanding-liquid-staking.mdx b/docs/docs/stakewise-protocol/understanding-liquid-staking.mdx index c65b5e80..020ea3ad 100644 --- a/docs/docs/stakewise-protocol/understanding-liquid-staking.mdx +++ b/docs/docs/stakewise-protocol/understanding-liquid-staking.mdx @@ -54,13 +54,13 @@ StakeWise addresses these gaps with a different approach to liquid staking.
2. - Receiving a penalty is not the same as being slashed. Penalties are small deductions from a validator's Beacon Chain balance for failing to perform its duties, and are effectively burned. Slashing is a severe punishment reserved for specific misbehaviours that could form part of an attack on the chain — it removes some or all of the validator's stake and ejects it from the protocol. + Receiving a penalty is not the same as being slashed. Penalties are small deductions from a validator's Beacon Chain balance for failing to perform its duties, and are effectively burned. Slashing is a severe punishment reserved for specific misbehaviors that could form part of an attack on the chain — it removes some or all of the validator's stake and ejects it from the protocol.
3. For detailed reward calculations and formulas, see: - eth2book ↗ + [eth2book ↗](https://eth2book.info/capella/part2/incentives/rewards/)
diff --git a/docs/docs/vaults/boost.mdx b/docs/docs/vaults/boost.mdx index 91e9f6db..36a84cb9 100644 --- a/docs/docs/vaults/boost.mdx +++ b/docs/docs/vaults/boost.mdx @@ -1,5 +1,5 @@ --- -title: Boost +title: How Boost Works description: Amplify staking rewards with StakeWise Boost. Learn how osETH looping on Aave works and the safety mechanisms behind it. --- diff --git a/docs/docs/vaults/configuration.mdx b/docs/docs/vaults/configuration.mdx index b3c1c7c7..80ba7794 100644 --- a/docs/docs/vaults/configuration.mdx +++ b/docs/docs/vaults/configuration.mdx @@ -77,7 +77,7 @@ Primary controller of the Vault, and its creator by default. An Admin can be a s Triggers [claims of accumulated Vault fees](/operator/manage-vault/fee-claiming) on behalf of shareholders. -By default a Vault's fees go to a single recipient. To split them among several shareholders, the Admin sets a **Fee Splitter** — a contract that divides incoming fees according to configured proportions — as the Vault's fee recipient, under **Settings → Vault fee** on the Vault page. +By default a Vault's fees go to a single recipient. To split them among several shareholders, the Admin sets a **Fee Splitter** — on-chain, a `RewardSplitter` contract deployed by the `RewardSplitterFactory`, which divides incoming fees according to configured proportions — as the Vault's fee recipient, under **Settings → Vault fee** on the Vault page. ### Whitelist Manager @@ -90,3 +90,17 @@ Adds or removes the addresses blocked from depositing. Only present in Blocklist ### Validators Manager Authorizes the Vault's core validator operations — registration, funding, consolidation, and withdrawals. The [Operator Service automates most of the validator lifecycle](/operator/launch-operator-service#core-functions), and each on-chain action it submits must be signed by the address assigned to this role. + +### Deposit Data Manager + +Sets the Vault's deposit data root in the `DepositDataRegistry`, which is how Vaults on the older deposit-data flow pre-approve the validator keys that may be registered. Defaults to the Admin when unset, and only the Admin can reassign it. Vaults using a [Validators Manager](#validators-manager) do not need this role. + +## MetaVault Configuration + +A [MetaVault](./vault-types#metavault) has the same capacity, fee, branding and role settings as any other Vault, plus its own sub-vault set. That set lives in a `SubVaultsRegistry` contract deployed alongside the MetaVault, and only the MetaVault's Admin can change it: + +- **Curator** — the contract that decides how deposits and withdrawals are spread across sub-vaults. Must be DAO-approved and registered in the `CuratorsRegistry`. +- **Add a sub-vault** — adds a Vault to the set, up to 50. Adding a MetaVault as a sub-vault requires that MetaVault to accept the link. +- **Eject a sub-vault** — removes a Vault from the set and queues an exit for the assets held in it. + +MetaVaults do not run validators, so they have no Validators Manager or Deposit Data Manager role. diff --git a/docs/docs/vaults/how-vaults-work.mdx b/docs/docs/vaults/how-vaults-work.mdx index ceb1f479..cb53f74e 100644 --- a/docs/docs/vaults/how-vaults-work.mdx +++ b/docs/docs/vaults/how-vaults-work.mdx @@ -63,7 +63,7 @@ Once active, validators earn: - **Consensus Layer rewards** — for block proposals, attestations, and sync-committee participation. - **Execution Layer rewards** — [MEV ↗](https://ethereum.org/developers/docs/mev/) and transaction priority fees. -These rewards are [calculated off-chain](../oracles/oracle-duties#reward-distribution) by the StakeWise [Oracle network](../oracles/intro) every **12 hours**: Oracles publish per-Vault rewards to [IPFS ↗](https://ipfs.stakewise.io/ipfs/bafkreicoxcsrs652goyanvyjjhgljn6pqpf6zqweu7sobaeybdjrhqomfq), build a Merkle tree over that data, and submit a signed Merkle root to the [Keeper contract ↗](https://etherscan.io/address/0x6B5815467da09DaA7DC83Db21c9239d98Bb487b5), which verifies the signatures and stores the root. +These rewards are [calculated off-chain](../oracles/oracle-duties#reward-distribution) by the StakeWise [Oracle network](../oracles/intro) every **12 hours**: Oracles publish per-Vault rewards to [IPFS ↗](https://ipfs.tech/), build a Merkle tree over that data, and submit a signed Merkle root to the [Keeper contract ↗](https://etherscan.io/address/0x6B5815467da09DaA7DC83Db21c9239d98Bb487b5), which verifies the signatures and stores the root. Each Vault then pulls in its own rewards through a **harvest**. @@ -124,6 +124,6 @@ Staked assets continue earning rewards until validators complete the exit. Once
3. - BLS (Boneh–Lynn–Shacham) is a digital signature scheme introduced in 2001. It relies on the hardness of the Computational Diffie–Hellman (CDH) problem to prevent forgery. A user selects a secret key and derives the corresponding public key on an elliptic curve. To sign, the message is hashed to a curve point and multiplied by the secret key, producing the signature. Verification uses a bilinear pairing to confirm validity. Learn more about BLS signatures here. + BLS (Boneh–Lynn–Shacham) is a digital signature scheme introduced in 2001. It relies on the hardness of the [Computational Diffie–Hellman (CDH) problem ↗](https://www.iacr.org/archive/asiacrypt2001/22480516.pdf) to prevent forgery. A user selects a secret key and derives the corresponding public key on an elliptic curve. To sign, the message is hashed to a curve point and multiplied by the secret key, producing the signature. Verification uses a bilinear pairing to confirm validity.
diff --git a/docs/docs/vaults/intro.mdx b/docs/docs/vaults/intro.mdx index e7e78ddb..6d29bd58 100644 --- a/docs/docs/vaults/intro.mdx +++ b/docs/docs/vaults/intro.mdx @@ -10,7 +10,7 @@ Vaults do not socialize risk: each deposit can only fund that Vault's validators Anyone — from solo stakers to DAOs and institutions — can [create a Vault](/operator/create-regular-vault) and run it on their own terms: bespoke fee, MEV strategy, and an optional ERC-20 Vault token. [MetaMask ↗](https://blog.stakewise.io/caseStudy/how-metamask-launched-pooled-staking-with-stakewise), [Chorus One ↗](https://blog.stakewise.io/caseStudy/how-chorus-one-launched-retail-staking-with-stakewise), and [NodeSet ↗](https://blog.stakewise.io/caseStudy/how-nodeset-launched-decentralized-pooled-staking-with-stakewise) use StakeWise Vaults to power their staking products. -Every Vault lets depositors mint [osToken](../ostoken/intro) against their staked position as collateral. osToken is an overcollateralized, liquid token that unlocks [DeFi yield opportunities](https://app.stakewise.io/ecosystem) while continuing to earn staking rewards. +Every Vault lets depositors mint [osToken](../ostoken/intro) against their staked position as collateral. osToken is an overcollateralized, liquid token that unlocks [DeFi yield opportunities ↗](https://app.stakewise.io/ecosystem) while continuing to earn staking rewards. Beyond base staking rewards, StakeWise Vaults on Ethereum include access to [Boost](./boost), a built-in one-click strategy that uses osETH as collateral to borrow ETH on Aave and restake it in the Vault, potentially leading to more rewards. diff --git a/docs/docs/vaults/technical-architecture.mdx b/docs/docs/vaults/technical-architecture.mdx index 965a16f1..15090efd 100644 --- a/docs/docs/vaults/technical-architecture.mdx +++ b/docs/docs/vaults/technical-architecture.mdx @@ -13,7 +13,7 @@ A Vault is a self-contained on-chain system: a proxy that holds state, an implem Each Vault is deployed as an upgradeable [ERC-1967 ↗](https://eips.ethereum.org/EIPS/eip-1967) proxy contract. The proxy is what users interact with — it holds all persistent state, including user balances and the Vault-specific configuration set during initialization. Logic lives in a separate implementation contract, so new Vault versions can be released without redeploying each Vault or migrating user data. -Vaults are created through network-specific factory contracts. On Ethereum, an [`EthVaultFactory` ↗](https://etherscan.io/address/0x7a8cbbf690084e43de778173cfacf7313c9122dd#code) is deployed for each Vault variant (Standard, ERC-20, Private, Blocklist); on Gnosis, the equivalent [`GnoVaultFactory` ↗](https://gnosisscan.io/address/0x7A8cbBf690084E43De778173cfAcf7313c9122DD#code) plays the same role. Each call to `createVault`: +Vaults are created through network-specific factory contracts — one per Vault variant. On Ethereum, an [`EthVaultFactory` ↗](https://etherscan.io/address/0x7a8cbbf690084e43de778173cfacf7313c9122dd#code) is deployed for each of the Standard, ERC-20, Private and Blocklist variants, alongside separate factories for the [MetaVault](./vault-types#metavault) variants; on Gnosis, the equivalent [`GnoVaultFactory` ↗](https://gnosisscan.io/address/0x7A8cbBf690084E43De778173cfAcf7313c9122DD#code) plays the same role. The full set is listed in the [contract addresses](../../contracts/networks/) for each network. Each call to `createVault`: 1. Deploys a new ERC-1967 proxy pointing to the shared implementation contract. 2. Calls the proxy's initializer to set the Vault's initial configuration. @@ -48,3 +48,9 @@ Modules combine in different ways to produce the [Vault types](./vault-types). E Upgrades are gated behind the Vault's own [Admin](./configuration#admin): `upgradeToAndCall` reverts for anyone else, so the StakeWise DAO cannot change or upgrade a Vault's contracts unilaterally. The DAO releases new Vault versions to improve efficiency or safety, and each release goes through community governance approval — but adopting one is always the Admin's decision, and a Vault can stay on its current version indefinitely. Because the proxy pattern separates storage from logic, an upgrade swaps only the implementation; the proxy holding all state and user balances is untouched. + +Three further rules are enforced on-chain, so an Admin cannot upgrade to arbitrary code: + +- The new implementation must be **registered in the `VaultsRegistry`** by the DAO. +- It must be the **same Vault type** — a Standard Vault cannot become an ERC-20 Vault. +- It must be **exactly one version higher**. Versions cannot be skipped, so a Vault that has sat out several releases upgrades through them one at a time. diff --git a/docs/docs/vaults/vault-types.mdx b/docs/docs/vaults/vault-types.mdx index b8785113..f1159cc7 100644 --- a/docs/docs/vaults/vault-types.mdx +++ b/docs/docs/vaults/vault-types.mdx @@ -37,8 +37,8 @@ Open to any wallet by default, but the Blocklist Manager can block specific addr A MetaVault is a specialized Vault that doesn't run validators itself. Instead, it accepts deposits and routes them across a set of underlying Vaults — called *sub-vaults* — which handle validator operations. Just like with a Standard Vault, stakers can mint osToken against their position and burn it to unlock their stake at any time. -:::custom-notes[Ethereum Only] -MetaVaults are currently available on Ethereum only — no MetaVault factory is deployed on Gnosis Chain. +:::custom-notes[New MetaVaults: Ethereum Only] +MetaVaults run on both Ethereum and Gnosis Chain, but only Ethereum has a registered MetaVault factory, so new MetaVaults can currently be created on Ethereum only. Existing Gnosis MetaVaults continue to operate normally. ::: ### Use Cases @@ -53,3 +53,7 @@ Any third party can deploy a MetaVault permissionlessly, which opens up modular, Allocation is handled by a **Curator** — a contract that decides how deposited assets are distributed to and withdrawn from sub-vaults. Curator contracts must be approved by the DAO and registered in the [`CuratorsRegistry` ↗](https://etherscan.io/address/0xa23F7c8d25f4503cA4cEd84d9CC2428e8745933C#code). Currently, one Curator is available: the [`BalancedCurator` ↗](https://etherscan.io/address/0xe01351f866C118FbD04d222f9262A470F1d44d90#code), which spreads deposits and withdrawals evenly across sub-vaults. Before routing, the `BalancedCurator` checks each sub-vault's remaining capacity and distributes within those limits, filling each sub-vault up to its cap and skipping any that are already full. This means deposits continue to succeed even when one sub-vault in the set has reached its cap, instead of the entire deposit reverting. A MetaVault can route across up to 50 sub-vaults. + +Each MetaVault owns a `SubVaultsRegistry` contract that holds all of its sub-vault accounting — positions, exit queues, and capacities. The MetaVault's [Admin](./configuration#admin) manages the set through that registry: appointing the Curator, adding a sub-vault, and ejecting one. + +A sub-vault can itself be a MetaVault, so MetaVaults can be **nested**. A parent resolves its state bottom-up, requiring every sub-vault to be harvested against the latest Oracle-signed root before recomputing its own total assets. Nesting is used in production today — the Vault behind [Simple Staking](/staker/simple-staking) is a MetaVault whose only sub-vault is another MetaVault, which in turn routes to the Genesis Vault. diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 2f79bfeb..5a10cb2b 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -76,6 +76,7 @@ const config: Config = { [ '@docusaurus/preset-classic', { + blog: false, sitemap: { lastmod: 'date', changefreq: 'weekly', diff --git a/operator/alternative-key-management/api-mode.mdx b/operator/alternative-key-management/api-mode.mdx index c978e0d4..c2c365a7 100644 --- a/operator/alternative-key-management/api-mode.mdx +++ b/operator/alternative-key-management/api-mode.mdx @@ -17,8 +17,8 @@ The Operator communicates with the Vault contract using data and signatures prov Complete the following steps before proceeding: -1. [Installation →](/operator/launch-operator-service#installation) completed -2. [Validators Manager →](/operator/validators-manager) set to the wallet address of the Relayer. +1. [Installation](/operator/launch-operator-service#installation) completed +2. [Validators Manager](/operator/validators-manager) set to the wallet address of the Relayer. ## Start Operator Service @@ -47,7 +47,7 @@ For additional parameter information, use the `--help` flag: ``` :::custom-info[Docker Configuration] -For Docker-specific setup instructions, see the Docker Image tab in [Installation →](../launch-operator-service#installation) +For Docker-specific setup instructions, see the Docker Image tab in [Installation](../launch-operator-service#installation) ::: :::custom-info[Technical Implementation] @@ -198,10 +198,15 @@ Response: Response: ```json { - "network": "mainnet" + "network": "mainnet", + "validators_manager_address": "0x1e3d..." } ``` +:::custom-warning[`validators_manager_address` Is Checked at Startup] +The Operator Service compares this value against the Vault's on-chain Validators Manager and **refuses to start** if they differ. If the field is missing it logs a warning and continues, so return it to make misconfiguration obvious. +::: + :::custom-info[Multiple Relayers] Multiple relayers are possible for various use-cases. You can implement your own relayer using the API specification provided above to meet your specific requirements. ::: diff --git a/operator/alternative-key-management/dvt/img/Distributed Validator Technology.png b/operator/alternative-key-management/dvt/img/Distributed Validator Technology.png deleted file mode 100644 index df65e77de280db6660a4e83869b8201067160c6e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118705 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
Example Output ``` -Enter the network name (mainnet, hoodi, gnosis) [mainnet]: hoodi +Enter the network name (mainnet, hoodi, gnosis, chiado) [mainnet]: hoodi Enter your vault address: 0xf27...10ad9 Choose your mnemonic language (chinese_simplified, chinese_traditional, czech, english, italian, korean, portuguese, spanish) [english]: english @@ -89,7 +89,7 @@ Deposit data saved to /Users/user/.stakewise/0xf27...10ad9/deposit_data.json fil This command generates the following files in your Vault directory (`~/.stakewise/[vault address]/`): 1. `config.json` - Configuration file for your Vault. -2. `deposit_data.json` - Contains validator registration information including public keys, withdrawal credentials, and cryptographic signatures. This file must be uploaded to your Vault ([Step 5: Upload Deposit Data File →](./prepare-operator#step-5-upload-deposit-data-file-to-vault)) to authorize the Operator Service to register your validators. +2. `deposit_data.json` - Contains validator registration information including public keys, withdrawal credentials, and cryptographic signatures. This file must be uploaded to your Vault ([Step 5: Upload Deposit Data File](./prepare-operator#step-5-upload-deposit-data-file-to-vault)) to authorize the Operator Service to register your validators. 3. `keystores/` directory containing: validator keystores and `password.txt` for all generated keystores. :::custom-notes[Per-Keystore Passwords] @@ -105,7 +105,7 @@ This command generates the following files in your Vault directory (`~/.stakewis You can always add more validator keys to your Vault: 1. Generate new validator keys and deposit data as described in the step above -2. Upload the deposit data file to your Vault as described in [Step 5: Upload Deposit Data File →](./prepare-operator#step-5-upload-deposit-data-file-to-vault) +2. Upload the deposit data file to your Vault as described in [Step 5: Upload Deposit Data File](./prepare-operator#step-5-upload-deposit-data-file-to-vault) :::custom-warning[Important] Uploading a new deposit data file will overwrite the existing file and consequently overwrite previously unused validator keys. @@ -116,9 +116,9 @@ This can be done at any point, but only by the Vault Admin or Keys Manager. Validator keystores don't need to be stored locally. You can instead use: -- **[Remote Signer →](../alternative-key-management/remote-signer)** — Sign deposit/exit messages via a remote signer -- **[HashiCorp Vault →](../alternative-key-management/hashicorp-vault)** — Load keys from a remote Vault instance -- **[API Mode →](../alternative-key-management/api-mode)** — Run Operator as API service with external key management +- **[Remote Signer](../alternative-key-management/remote-signer)** — Sign deposit/exit messages via a remote signer +- **[HashiCorp Vault](../alternative-key-management/hashicorp-vault)** — Load keys from a remote Vault instance +- **[API Mode](../alternative-key-management/api-mode)** — Run Operator as API service with external key management ## Step 3: Import Validator Keys diff --git a/operator/operator-service-v3/run-operator-service-v3/start-operator.mdx b/operator/operator-service-v3/run-operator-service-v3/start-operator.mdx index 62f5c037..37d66e42 100644 --- a/operator/operator-service-v3/run-operator-service-v3/start-operator.mdx +++ b/operator/operator-service-v3/run-operator-service-v3/start-operator.mdx @@ -25,7 +25,7 @@ Run the Operator Service using the `start` command with your Vault address and n -u $(id -u):$(id -g) \\ -v ~/.stakewise/:/data \\ europe-west4-docker.pkg.dev/stakewiselabs/public/v3-operator:v3.1.10 \\ -src/main.py start-local \\ +src/main.py start \\ --vault={{0xf27...10ad9}} \\ --data-dir=/data \\ --consensus-endpoints={{http://localhost:5052}} \\ @@ -33,7 +33,7 @@ src/main.py start-local \\ - @@ -51,7 +51,7 @@ export $(grep -v '^#' .env | xargs) | Flag | Description | |------|-------------| -| `--network` | Network of the Vault: `mainnet`, `hoodi`, or `gnosis`. Default: network specified at `init` command | +| `--network` | Network of the Vault: `mainnet`, `hoodi`, `gnosis`, or `chiado`. Default: network specified at `init` command | | `--data-dir` | Path where the Vault data will be placed. Default: `~/.stakewise` | | `--database-dir` | Directory where the database will be created or read from. Default: `~/.stakewise/` | | `--execution-jwt-secret` | JWT secret key used for signing and verifying JSON Web Tokens when connecting to execution nodes | diff --git a/operator/operator-service-v3/v4-upgrade-guide.mdx b/operator/operator-service-v3/v4-upgrade-guide.mdx index d7f030d6..ef0a57f8 100644 --- a/operator/operator-service-v3/v4-upgrade-guide.mdx +++ b/operator/operator-service-v3/v4-upgrade-guide.mdx @@ -37,10 +37,10 @@ On Ethereum, the upgraded Vault is backwards compatible with the V3 operator — ### Step 3: Set up Validators Manager -If you were using deposit data files for validator registration, you need to switch to the **Validators Manager** role, which authorizes validator operations (registrations, funding, consolidations, withdrawals) on behalf of your Vault. If you already have a Validators Manager assigned, skip this step. Follow the [setup guide →](/operator/validators-manager). +If you were using deposit data files for validator registration, you need to switch to the **Validators Manager** role, which authorizes validator operations (registrations, funding, consolidations, withdrawals) on behalf of your Vault. If you already have a Validators Manager assigned, skip this step. Follow the [setup guide](/operator/validators-manager). ### Step 4: Start the Operator Service -The launch process has been simplified with dedicated commands for each key management mode (local keystores, Remote Signer, HashiCorp Vault, Relayer). See [Start Operator Service →](/operator/start-operator) for the updated commands and full configuration options. +The launch process has been simplified with dedicated commands for each key management mode (local keystores, Remote Signer, HashiCorp Vault, Relayer). See [Start Operator Service](/operator/start-operator) for the updated commands and full configuration options. -For the full V4 documentation, see the [main Operator docs →](/operator/introduction). +For the full V4 documentation, see the [main Operator docs](/operator/introduction). diff --git a/operator/smoothing-pool-relays.mdx b/operator/smoothing-pool-relays.mdx index 30c7bb29..a53fa688 100644 --- a/operator/smoothing-pool-relays.mdx +++ b/operator/smoothing-pool-relays.mdx @@ -5,19 +5,30 @@ description: Configure DAO-approved MEV relays for StakeWise Smoothing Pool Vaul # Smoothing Pool Relays -The Smoothing Pool is a shared MEV escrow where execution-layer rewards from all participating Vaults are pooled and redistributed proportionally based on each Vault's stake. This smooths out the inherent variance of MEV — instead of receiving large, unpredictable payouts only when your validators propose blocks, your Vault earns a steady, proportional share of the total pool. +The Smoothing Pool is a shared MEV escrow where execution-layer rewards from all participating Vaults are pooled and redistributed in proportion to the Consensus Layer attestation rewards each Vault's validators earned over the period. This smooths out the inherent variance of MEV — instead of receiving large, unpredictable payouts only when your validators propose blocks, your Vault earns a steady share of the total pool. If you selected the **Smoothing Pool** during Vault creation, you must use one or multiple StakeWise DAO-approved MEV relays. :::custom-warning[Required Fee Recipient] -You must specify the Smoothing Pool address as the fee recipient for your Vault validators: -**`0x48319f97E5Da1233c21c48b80097c0FB7a20Ff86`** +You must set the Smoothing Pool address **for your network** as the fee recipient for your Vault validators: + +| Network | Smoothing Pool (`SharedMevEscrow`) | +|---------|------------------------------------| +| Ethereum Mainnet | [`0x48319f97E5Da1233c21c48b80097c0FB7a20Ff86` ↗](https://etherscan.io/address/0x48319f97E5Da1233c21c48b80097c0FB7a20Ff86) | +| Gnosis Chain | [`0x30db0d10d3774e78f8cB214b9e8B72D4B402488a` ↗](https://gnosisscan.io/address/0x30db0d10d3774e78f8cB214b9e8B72D4B402488a) | +| Hoodi | [`0x51FD45BAEfB12f54766B5C4d639b360Ea50063bd` ↗](https://hoodi.etherscan.io/address/0x51FD45BAEfB12f54766B5C4d639b360Ea50063bd) | + +Setting the wrong address forfeits your Vault's locked share of the pool. ::: One instance of MEV-Boost can connect to multiple relays. Use the `-relays` flag with comma-separated relay URLs (e.g., `-relays https://relay1,https://relay2`), or use the `-relay` flag multiple times for individual relays. ## Approved relay endpoints +:::custom-info[Ethereum Only] +The approved-relay requirement applies to Ethereum. Gnosis Chain has no relay allowlist, so Gnosis Smoothing Pool Vaults are not restricted to a specific set of relays. +::: + **Flashbots** ``` https://0xac6e77dfe25ecd6110b8e780608cce0dab71fdd5ebea22a16c0205200f2f8e2e3ad3b71d3499c54ad14d6c21b41a37ae@boost-relay.flashbots.net @@ -51,4 +62,14 @@ https://0xa15b52576bcbf1072f4a011c0f99f9fb6c66f3e1ff321f11f461d15e31b1cb359caa09 **Titan Relay** ``` https://0x8c4ed5e24fe5c6ae21018437bde147693f68cda427cd1122cf20819c30eda7ed74f72dece09bb313f2a1855595ab677d@titanrelay.xyz +``` + +**Eden Network** +``` +https://0xb3ee7afcf27f1f1259ac1787876318c6584ee353097a50ed84f51a1f21a323b3736f271a895c7ce918c038e4265918be@relay.edennetwork.io +``` + +**SecureRPC** +``` +https://0x98650451ba02064f7b000f5768cf0cf4d4e492317d82871bdc87ef841a0743f69f0f1eea11168503240ac35d101c9135@mainnet-relay.securerpc.com ``` \ No newline at end of file diff --git a/operator/staking-nodes.mdx b/operator/staking-nodes.mdx index d2d577ed..ce035824 100644 --- a/operator/staking-nodes.mdx +++ b/operator/staking-nodes.mdx @@ -11,7 +11,7 @@ import TabItem from '@theme/TabItem'; Your Vault is created but not yet operational. You need to set up the following node infrastructure before installing the Operator Service. :::custom-tips[Automated Node Setup Available] -A new experimental feature allows the Operator Service to automatically set up execution and consensus clients (Reth and Lighthouse). See [Automated Node Setup →](/operator/manage-validators/automated-node-setup) for details. +A new experimental feature allows the Operator Service to automatically set up execution and consensus clients (Reth and Lighthouse). See [Automated Node Setup](/operator/manage-validators/automated-node-setup) for details. ::: ## Execution Client diff --git a/operator/start-operator.mdx b/operator/start-operator.mdx index 5fa20aee..5d7a54ba 100644 --- a/operator/start-operator.mdx +++ b/operator/start-operator.mdx @@ -63,17 +63,21 @@ src/main.py start \\ | Flag | Description | |------|-------------| +| `--data-dir` | Path where keystores and config data are placed. Required for Docker (`--data-dir=/data`). Default: `~/.stakewise` | +| `--database-dir` | Directory where the database is created or read from. Must already exist; map it to a host directory or Docker volume. Default: the Vault's data directory | +| `--graph-endpoint` | API endpoint for the StakeWise subgraph node | +| `--execution-jwt-secret` | JWT secret used to authenticate against the execution node | | `--keystores-dir` | Directory with validator keys in [EIP-2335 ↗](https://eips.ethereum.org/EIPS/eip-2335) format. Must contain a single `password.txt` or per-keystore password files (e.g., `keystore1.json`, `keystore1.txt`) | | `--keystores-password-file` | Path to the password file for decrypting keystores | | `--wallet-file` | Path to the wallet file. Default: file generated by `create-wallet` | | `--wallet-password-file` | Path to the wallet password file. Default: file generated by `create-wallet` | | `--harvest-vault` | Keeps your Vault synced by calling `updateState` every 12 hours, reducing gas costs for users. The Vault state is also updated whenever users interact with it (deposits, withdrawals, etc.) | -| `--claim-fee-splitter` | Periodically claims fees to shareholder addresses. See [Fee Claiming →](/operator/manage-vault/fee-claiming) | -| `--run-nodes` | Starts and manages local Reth (execution), Lighthouse (consensus), and validator nodes alongside the Operator Service. Requires `node-install` first. See [Automated Node Setup →](/operator/manage-validators/automated-node-setup) | +| `--claim-fee-splitter` | Periodically claims fees to shareholder addresses. See [Fee Claiming](/operator/manage-vault/fee-claiming) | +| `--run-nodes` | Starts and manages local Reth (execution), Lighthouse (consensus), and validator nodes alongside the Operator Service. Requires `node-install` first. See [Automated Node Setup](/operator/manage-validators/automated-node-setup) | | `--validator-type` | Register `0x01` instead of default `0x02` validators | | `--min-deposit-amount-gwei` | Minimum Vault balance to trigger registration or top-up. Minimum allowed: 1 ETH/GNO. Default: 10 ETH/GNO | | `--vault-min-balance-gwei` | ETH to keep in the Vault and not stake. Default: 0 | -| `--max-validator-balance-gwei` | Maximum validator balance for top-ups. Default: 1945 ETH (Ethereum), 1800 GNO (Gnosis) | +| `--max-validator-balance-gwei` | Maximum validator balance for top-ups. Default: 300 ETH (Ethereum), 1800 GNO (Gnosis). The protocol ceiling is higher (2048 ETH), but the service stops topping up at this value | | `--min-deposit-delay` | Minimum delay between registration or top-up transactions. Default: 3600 seconds (1 hour) | | `--max-withdrawal-request-fee-gwei` | Maximum fee per withdrawal request. Default: 1000 Gwei | | `--disable-withdrawals` | Disables submitting partial and full withdrawals. This reduces gas costs but increases withdrawal times for users. Oracles will instead handle exits using signatures from registration | @@ -81,5 +85,12 @@ src/main.py start \\ | `--concurrency` | Number of processes in a pool. For optimal performance, set to half your CPU cores (e.g., `--concurrency=4` for 8-core systems) to prevent overloading during keystore operations | | `--disable-validators-registration` | Disables registering new validators | | `--disable-validators-funding` | Disables topping up existing `0x02` validators | -| `--verbose` | Enable debug mode, prints full output on error | -| `--log-level` | Set logging level: ERROR, WARNING, INFO, DEBUG | +| `-v`, `--verbose` | Enable debug mode, prints full output on error | +| `--log-level` | Set logging level: ERROR, WARNING, INFO, DEBUG. Default: INFO | +| `--log-format` | Log record format: `plain` or `json`. Default: `plain` | + +Every flag above can also be supplied as an environment variable — `--max-fee-per-gas-gwei` as `MAX_FEE_PER_GAS_GWEI`, and so on. + +:::custom-tips[Advanced Environment Variables] +Beyond the flags above, the Operator Service reads a set of advanced environment variables for fine tuning — IPFS gateways, event-fetching batch sizes, Sentry reporting, and more. They have no CLI equivalent. Run `./operator env-vars` for the current list, or `./operator env-vars --format=markdown` to get it as a table. +::: diff --git a/operator/validator-keys.mdx b/operator/validator-keys.mdx index ccbba06a..8bc5cb5a 100644 --- a/operator/validator-keys.mdx +++ b/operator/validator-keys.mdx @@ -24,7 +24,7 @@ For example, if running Operator Service from binary: ./operator init ``` -Follow the command prompts. You will be asked to enter your Vault address during this step (see [Where to Find Your Vault Address →](/operator/create-regular-vault#where-to-find-your-vault-address)). +Follow the command prompts. You will be asked to enter your Vault address during this step (see [Where to Find Your Vault Address](/operator/create-regular-vault#where-to-find-your-vault-address)).
Example Output @@ -101,6 +101,6 @@ Upload your keystores into your validator client: Validator keystores don't need to be stored locally. You can instead use: -- [Remote Signer →](/operator/alternative-key-management/remote-signer) — Sign deposit/exit messages via a remote signer -- [HashiCorp Vault →](/operator/alternative-key-management/hashicorp-vault) — Load keys from a remote Vault instance -- [API Mode →](/operator/alternative-key-management/api-mode) — Run Operator as API service with external key management +- [Remote Signer](/operator/alternative-key-management/remote-signer) — Sign deposit/exit messages via a remote signer +- [HashiCorp Vault](/operator/alternative-key-management/hashicorp-vault) — Load keys from a remote Vault instance +- [API Mode](/operator/alternative-key-management/api-mode) — Run Operator as API service with external key management diff --git a/operator/validators-manager.mdx b/operator/validators-manager.mdx index 7af47fd7..dc0d24cc 100644 --- a/operator/validators-manager.mdx +++ b/operator/validators-manager.mdx @@ -12,7 +12,7 @@ After generating your validator keys, you need to set up a wallet that will auth The Operator Service can generate this wallet, or you can use an existing one. -If your Validators Manager wallet is managed externally, see [Relayer (API Mode) →](/operator/alternative-key-management/api-mode). +If your Validators Manager wallet is managed externally, see [Relayer (API Mode)](/operator/alternative-key-management/api-mode). validator-manager-setup @@ -31,7 +31,7 @@ You need a wallet to authorize validator operations and pay gas fees. You can cr ``` Enter the vault address: 0xF82f6E46d0d0a9536b9CA4bc480372EeaFcd9E6c Enter the mnemonic for generating the wallet: fish monster write banner tired laptop slender ... -Done. The wallet and password saved to ~/.stakewise/0xF82f6E46d0d0a9536b9CA4bc480372EeaFcd9E6c/wallet directory. The wallet address is: 0x59d58...B48C4 +Done. The wallet and password saved to ~/.stakewise/0xf82f6e46d0d0a9536b9ca4bc480372eeafcd9e6c/wallet directory. The wallet address is: 0x59d58...B48C4 ```
diff --git a/operator/vault-interface.mdx b/operator/vault-interface.mdx index 8be436d7..e73708d0 100644 --- a/operator/vault-interface.mdx +++ b/operator/vault-interface.mdx @@ -12,7 +12,7 @@ If you want stakers to deposit through your own site instead of [app.stakewise.i The UI supports Ethereum, Gnosis Chain, and Hoodi Testnet, and offers the same style and set of actions stakers can take on [app.stakewise.io ↗](https://app.stakewise.io/): deposit, withdraw, mint and burn [osToken](/docs/ostoken/intro), and [boost and unboost](/docs/vaults/boost)1. They can also see stats, unstake/unboost progress, and claim assets. -Building a staking interface from scratch takes time and engineering resources. The template gives you a production-ready UI. StakeWise keeps the template in sync with the latest released features and constantly improves it. If you need something the template doesn't cover, you can build a fully custom frontend on top of the [StakeWise SDK →](/sdk). +Building a staking interface from scratch takes time and engineering resources. The template gives you a production-ready UI. StakeWise keeps the template in sync with the latest released features and constantly improves it. If you need something the template doesn't cover, you can build a fully custom frontend on top of the [StakeWise SDK](/sdk). There are two ways to set it up: diff --git a/package.json b/package.json index 9eaefc9b..b3775103 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "write-heading-ids": "docusaurus write-heading-ids", "typecheck": "tsc", "prepare": "husky", - "checkRedirects": "tsx scripts/checkRedirects/index.ts" + "checkRedirects": "tsx scripts/checkRedirects/index.ts", + "generateContractsApi": "tsx scripts/generateContractsApi/index.ts" }, "dependencies": { "@docusaurus/core": "3.10.0", diff --git a/redirects.ts b/redirects.ts index 69c37c7d..bba0e8c8 100644 --- a/redirects.ts +++ b/redirects.ts @@ -51,4 +51,12 @@ export default [ from: '/docs/stakewise-protocol/governance-swise-token', to: '/docs/governance/intro', }, + { + from: '/contracts/api/vaults/ethereum/custom/EthMetaVault', + to: '/contracts/api/vaults/ethereum/EthMetaVault', + }, + { + from: '/contracts/api/vaults/ethereum/custom/EthMetaVaultFactory', + to: '/contracts/api/vaults/ethereum/EthMetaVaultFactory', + }, ] diff --git a/scripts/checkRedirects/util/contentRoots.ts b/scripts/checkRedirects/util/contentRoots.ts index 97ec2443..8c70c996 100644 --- a/scripts/checkRedirects/util/contentRoots.ts +++ b/scripts/checkRedirects/util/contentRoots.ts @@ -13,9 +13,9 @@ export const contentRoots: ContentRoot[] = [ ] export const pathToUrl = (filePath: string): string => { - const stripped = filePath - .replace(/\.(mdx|md)$/, '') - .toLowerCase() + // Docusaurus preserves path casing in routes (e.g. /contracts/networks/Mainnet), + // so the URL must not be lowercased here. + const stripped = filePath.replace(/\.(mdx|md)$/, '') const root = contentRoots.find(({ dir }) => stripped.startsWith(dir + '/')) diff --git a/scripts/checkRedirects/util/parser.ts b/scripts/checkRedirects/util/parser.ts index 13b905a4..9fa06951 100644 --- a/scripts/checkRedirects/util/parser.ts +++ b/scripts/checkRedirects/util/parser.ts @@ -4,8 +4,9 @@ import { errorLabel, bold, cyan } from './colors' import { log } from './log' +// Routes are case-sensitive in Docusaurus, so `from` values are compared as written. const normalizeFrom = (value: string): string => - value.replace(/#.*$/, '').replace(/\/$/, '').toLowerCase() + value.replace(/#.*$/, '').replace(/\/$/, '') export const readRedirectsSource = (): string => { try { diff --git a/scripts/generateContractsApi/index.ts b/scripts/generateContractsApi/index.ts new file mode 100644 index 00000000..652f5fb1 --- /dev/null +++ b/scripts/generateContractsApi/index.ts @@ -0,0 +1,194 @@ +import { execFileSync } from 'child_process' +import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'fs' +import { tmpdir } from 'os' +import { dirname, join, relative, resolve } from 'path' + +import { categories } from './util/categories' +import { transform, firstProseLine } from './util/transform' + +import type { PageRef } from './util/transform' + + +const API_DIR = 'docs/contracts/api' +const SKIP_DIRS = [ 'interfaces', 'mocks' ] +// Contracts are documented; interfaces declared inside a contract file are not. +const PAGE_KINDS = [ 'contract', 'abstract', 'library' ] + +type Generated = { + name: string + docPath: string + sourcePath: string + body: string +} + +const walk = (dir: string): string[] => + readdirSync(dir).flatMap((entry) => { + const full = join(dir, entry) + + return statSync(full).isDirectory() ? walk(full) : [ full ] + }) + +const readFrontmatter = (file: string): Record => { + if (!existsSync(file)) { + return {} + } + + const match = readFileSync(file, 'utf8').match(/^---\n([\s\S]*?)\n---\n/) + + if (!match) { + return {} + } + + return Object.fromEntries( + match[1] + .split('\n') + .map((line) => line.match(/^([a-zA-Z_]+):\s*(.*)$/)) + .filter((m): m is RegExpMatchArray => Boolean(m)) + .map((m) => [ m[1], m[2].trim() ]), + ) +} + +const quote = (value: string): string => `"${value.replace(/"/g, '\\"')}"` + +const collect = (docsRoot: string): Generated[] => { + const contractsRoot = join(docsRoot, 'src', 'contracts') + + return walk(contractsRoot) + .filter((file) => file.endsWith('.md') && !file.endsWith('README.md')) + .flatMap((file) => { + const rel = relative(contractsRoot, file) + const parts = rel.split('/') + const fileName = parts.pop() as string + const solFile = parts.pop() as string + const dir = parts.join('/') + + if (parts.some((part) => SKIP_DIRS.includes(part))) { + return [] + } + + const [ kind, name ] = fileName.replace(/\.md$/, '').split('.') + + if (!PAGE_KINDS.includes(kind)) { + return [] + } + + // Docusaurus turns a doc whose filename matches its folder into that + // category's index page, which removes its own route. Suffix those so + // e.g. keeper/Keeper.sol becomes keeper/KeeperContract.md. + const parent = parts[parts.length - 1] ?? '' + const slug = parent.toLowerCase() === name.toLowerCase() ? `${name}Contract` : name + + return [ { + name, + docPath: dir ? `${dir}/${slug}` : slug, + sourcePath: dir ? `contracts/${dir}/${solFile}` : `contracts/${solFile}`, + body: readFileSync(file, 'utf8'), + } ] + }) + .sort((a, b) => a.docPath.localeCompare(b.docPath)) +} + +const writeCategories = (dirs: Set): void => { + for (const dir of dirs) { + if (!dir) { + continue + } + + const file = join(API_DIR, dir, '_category_.json') + + if (existsSync(file)) { + continue + } + + const known = categories[dir] + const label = known?.label ?? (dir.split('/').pop() as string) + const payload: Record = { label } + + if (known?.position !== undefined) { + payload.position = known.position + } + + if (known?.collapsed) { + payload.collapsed = true + } + + writeFileSync(file, `${JSON.stringify(payload, null, 2)}\n`) + } +} + +const main = (): void => { + const repo = resolve(process.env.V3_CORE_PATH ?? '../eth-core') + + if (!existsSync(join(repo, 'foundry.toml'))) { + console.error(`No foundry project at ${repo}. Set V3_CORE_PATH to your v3-core checkout.`) + process.exit(1) + } + + const commit = execFileSync('git', [ '-C', repo, 'rev-parse', 'HEAD' ], { encoding: 'utf8' }).trim() + const out = mkdtempSync(join(tmpdir(), 'contracts-api-')) + + console.log(`Generating from ${repo} @ ${commit.slice(0, 10)}`) + + try { + // The default profile builds with via_ir, which currently fails to compile; + // the test profile does not and forge doc only needs the parsed sources. + execFileSync('forge', [ 'doc', '--root', repo, '--out', out ], { + env: { ...process.env, FOUNDRY_PROFILE: 'test' }, + stdio: 'inherit', + }) + + const generated = collect(out) + const pages = new Map(generated.map(({ name, docPath }) => [ name, { docPath } ])) + + const positions = new Map() + const dirs = new Set() + + for (const page of generated) { + const dir = dirname(page.docPath) === '.' ? '' : dirname(page.docPath) + const next = (positions.get(dir) ?? 0) + 1 + + positions.set(dir, next) + dirs.add(dir) + + const target = join(API_DIR, `${page.docPath}.md`) + const existing = readFrontmatter(target) + const body = transform({ body: page.body, docPath: page.docPath, commit, pages }) + const description = existing.description ?? quote(firstProseLine(body)) + + const frontmatter = [ + '---', + `title: ${existing.title ?? page.name}`, + `sidebar_position: ${next}`, + `description: ${description}`, + '---', + '', + ].join('\n') + + mkdirSync(dirname(target), { recursive: true }) + writeFileSync(target, `${frontmatter}\n${body}`) + } + + writeCategories(dirs) + + const kept = new Set(generated.map((page) => join(API_DIR, `${page.docPath}.md`))) + const stale = walk(API_DIR) + .filter((file) => file.endsWith('.md') && !file.endsWith('index.md') && !kept.has(file)) + + for (const file of stale) { + rmSync(file) + } + + console.log(`Wrote ${generated.length} pages.`) + + if (stale.length) { + console.log('\nRemoved pages with no matching contract — add redirects for these:') + stale.forEach((file) => console.log(` ${file}`)) + } + } + finally { + rmSync(out, { recursive: true, force: true }) + } +} + + +main() diff --git a/scripts/generateContractsApi/util/categories.ts b/scripts/generateContractsApi/util/categories.ts new file mode 100644 index 00000000..a356b204 --- /dev/null +++ b/scripts/generateContractsApi/util/categories.ts @@ -0,0 +1,25 @@ +type Category = { + label: string + position?: number + collapsed?: boolean +} + +// Directory under contracts/ -> sidebar category. Keeping this explicit means the +// sidebar order does not shuffle when a new contract directory appears upstream. +export const categories: Record = { + 'base': { label: 'Base', position: 1, collapsed: true }, + 'curators': { label: 'Curators', position: 2, collapsed: true }, + 'keeper': { label: 'Keeper', position: 3, collapsed: true }, + 'libraries': { label: 'Libraries', position: 4, collapsed: true }, + 'misc': { label: 'Misc', position: 5 }, + 'tokens': { label: 'Tokens', position: 6 }, + 'validators': { label: 'Validators', position: 7 }, + 'vaults': { label: 'Vaults', position: 8 }, + 'nodes': { label: 'Nodes', position: 9, collapsed: true }, + 'vaults/ethereum': { label: 'Ethereum', position: 1, collapsed: true }, + 'vaults/gnosis': { label: 'Gnosis', position: 2, collapsed: true }, + 'vaults/modules': { label: 'Modules', position: 3 }, + 'vaults/ethereum/custom': { label: 'Custom', position: 1, collapsed: true }, + 'vaults/ethereum/mev': { label: 'MEV', position: 2, collapsed: true }, + 'vaults/gnosis/mev': { label: 'MEV', position: 1, collapsed: true }, +} diff --git a/scripts/generateContractsApi/util/externalLinks.ts b/scripts/generateContractsApi/util/externalLinks.ts new file mode 100644 index 00000000..5b402815 --- /dev/null +++ b/scripts/generateContractsApi/util/externalLinks.ts @@ -0,0 +1,20 @@ +const OZ = 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts' +const OZ_UPGRADEABLE = + 'https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts' + +// Base contracts pulled in from lib/. forge doc emits these as plain text because +// they live outside contracts/, so we link them by hand. +export const externalLinks: Record = { + EIP712: `${OZ}/utils/cryptography/EIP712.sol`, + ERC20Permit: `${OZ}/token/ERC20/extensions/ERC20Permit.sol`, + IERC20: `${OZ}/token/ERC20/IERC20.sol`, + IERC20Metadata: `${OZ}/token/ERC20/extensions/IERC20Metadata.sol`, + IERC20Permit: `${OZ}/token/ERC20/extensions/IERC20Permit.sol`, + Ownable2Step: `${OZ}/access/Ownable2Step.sol`, + ReentrancyGuard: `${OZ}/utils/ReentrancyGuard.sol`, + EIP712Upgradeable: `${OZ_UPGRADEABLE}/utils/cryptography/EIP712Upgradeable.sol`, + Initializable: `${OZ_UPGRADEABLE}/proxy/utils/Initializable.sol`, + Ownable2StepUpgradeable: `${OZ_UPGRADEABLE}/access/Ownable2StepUpgradeable.sol`, + ReentrancyGuardUpgradeable: `${OZ_UPGRADEABLE}/utils/ReentrancyGuardUpgradeable.sol`, + UUPSUpgradeable: `${OZ_UPGRADEABLE}/proxy/utils/UUPSUpgradeable.sol`, +} diff --git a/scripts/generateContractsApi/util/transform.ts b/scripts/generateContractsApi/util/transform.ts new file mode 100644 index 00000000..c211b8ed --- /dev/null +++ b/scripts/generateContractsApi/util/transform.ts @@ -0,0 +1,136 @@ +import { externalLinks } from './externalLinks' + + +export type PageRef = { + // Path of the generated page relative to docs/contracts/api, without extension. + docPath: string +} + +type TransformInput = { + body: string + docPath: string + commit: string + pages: Map +} + +const SOURCE_REPO = 'https://github.com/stakewise/v3-core' + +const relativeLink = (from: string, to: string): string => { + const fromParts = from.split('/').slice(0, -1) + const toParts = to.split('/') + const name = toParts.pop() as string + + let shared = 0 + + while (shared < fromParts.length && shared < toParts.length && fromParts[shared] === toParts[shared]) { + shared += 1 + } + + const up = fromParts.slice(shared).map(() => '..') + const down = toParts.slice(shared) + const segments = [ ...up, ...down, name ] + + return up.length ? segments.join('/') : `./${segments.join('/')}` +} + +// forge doc links every contract as /contracts//.sol/..md. +// Rewrite those to relative doc paths, drop links to pages we do not publish +// (interfaces, mocks), and link known lib/ base contracts by hand. +const rewriteLinks = (body: string, docPath: string, pages: Map): string => + body.replace( + /\[([^\]]+)\]\(\/contracts\/[^)]*?\/(?:contract|abstract|library|interface|struct|enum)\.([A-Za-z0-9_]+)\.md\)/g, + (_match, label: string, name: string) => { + const page = pages.get(name) + + if (page) { + return `[${label}](${relativeLink(docPath, page.docPath)})` + } + + const external = externalLinks[name] + + return external ? `[${label} ↗](${external})` : label + }, + ) + +// Solidity NatSpec inherited from OpenZeppelin carries AsciiDoc artefacts: +// `{Foo}` cross-references, which MDX would evaluate as JSX expressions, and +// ``double backticks``. Rewrite both to plain inline code, skipping code fences. +const sanitizeProse = (body: string): string => { + let inFence = false + + return body + .split('\n') + .map((line) => { + if (line.trimStart().startsWith('```')) { + inFence = !inFence + + return line + } + + if (inFence) { + return line + } + + return line + .replace(/``([^`]+)``/g, '`$1`') + .replace(/\{([A-Za-z_][A-Za-z0-9_-]*)\}/g, '`$1`') + .replace(/xref:[^\s[]+\[([^\]]*)\]/g, '$1') + }) + .join('\n') +} + +export const transform = ({ body, docPath, commit, pages }: TransformInput): string => { + let out = body.trimStart() + + // forge doc puts the Git Source link straight under the heading with no blank line. + out = out.replace(/^(# .+)\n(\[Git Source\])/m, '$1\n\n$2') + + out = out.replace( + /\[Git Source\]\(https:\/\/github\.com\/[^/]+\/[^/]+\/blob\/[0-9a-f]+\//, + `[Git Source ↗](${SOURCE_REPO}/blob/${commit}/`, + ) + + // "**Title:**\nName" and "**Author:**\nStakeWise" duplicate the heading. + out = out.replace(/\*\*Title:\*\*\n[^\n]*\n\n/g, '') + out = out.replace(/\*\*Author:\*\*\n[^\n]*\n\n/g, '') + + // Collapse the inherits list onto one line, as the rest of the site does, and + // link the lib/ base contracts that forge doc leaves as bare names. + out = out.replace(/\*\*Inherits:\*\*\n([^\n]+)/, (_match, list: string) => { + const linked = list + .split(', ') + .map((entry) => { + const external = externalLinks[entry.trim()] + + return external ? `[${entry.trim()} ↗](${external})` : entry + }) + .join(', ') + + return `**Inherits:** ${linked}` + }) + + out = rewriteLinks(out, docPath, pages) + out = sanitizeProse(out) + + return `${out.trimEnd()}\n` +} + +export const firstProseLine = (body: string): string => { + const lines = body.split('\n') + let index = 0 + + const skip = (line: string): boolean => + !line.trim() + || line.startsWith('#') + || line.startsWith('[Git Source') + || line.startsWith('**') + || line.startsWith('```') + + while (index < lines.length && skip(lines[index])) { + index += 1 + } + + const line = (lines[index] ?? '').trim() + + return line.startsWith('*') || line.startsWith('|') ? '' : line +} diff --git a/src/components/Tooltip/TooltipTrigger/TooltipTrigger.tsx b/src/components/Tooltip/TooltipTrigger/TooltipTrigger.tsx index 2266cb6b..57559e7b 100644 --- a/src/components/Tooltip/TooltipTrigger/TooltipTrigger.tsx +++ b/src/components/Tooltip/TooltipTrigger/TooltipTrigger.tsx @@ -9,8 +9,8 @@ type TooltipTriggerProps = { data: TooltipData } -type ExtendedProps = React.HTMLAttributes & { - 'data-state'?: string; +type ExtendedProps = React.HTMLAttributes & { + 'data-state'?: string } const TooltipTrigger: React.FC = (props) => { @@ -18,14 +18,15 @@ const TooltipTrigger: React.FC = (props) => { const ref = data.refs.setReference + // A span, not a div: triggers are used inline inside paragraphs, and a block + // element there closes the

early and breaks hydration. return React.createElement( - 'div', + 'span', data.getReferenceProps({ ref, ...otherProps, className: s.trigger, tabIndex: 0, - role: 'button', 'data-state': data.context.open ? 'open' : 'closed', } as ExtendedProps), children diff --git a/src/css/admonitions.scss b/src/css/admonitions.scss index 26287354..6ac81e60 100644 --- a/src/css/admonitions.scss +++ b/src/css/admonitions.scss @@ -1,8 +1,11 @@ @use 'mixins' as *; .theme-admonition { + --admonition-accent: var(--lavender-rgb); + background: var(--fog); border-radius: 0.5rem; + border-left: 4px solid rgb(var(--admonition-accent)); padding: 1rem; margin: 1.5rem 0; @@ -18,6 +21,39 @@ } } +// Per-type accents. Without these every custom admonition renders identically, +// so a warning reads the same as a note. +.theme-admonition-info { + --admonition-accent: 74, 144, 226; +} + +.theme-admonition-tips { + --admonition-accent: 56, 161, 105; +} + +.theme-admonition-warning { + --admonition-accent: 221, 141, 42; +} + +.theme-admonition-notes, +.theme-admonition-stakewise { + --admonition-accent: var(--lavender-rgb); +} + +.theme-admonition-info, +.theme-admonition-tips, +.theme-admonition-warning { + background: + linear-gradient(rgba(var(--admonition-accent), 0.06), rgba(var(--admonition-accent), 0.06)), + var(--fog); + + @include dark-theme { + background: + linear-gradient(rgba(var(--admonition-accent), 0.14), rgba(var(--admonition-accent), 0.14)), + var(--fog); + } +} + .admonition-heading { margin-bottom: 0.75rem; @@ -25,13 +61,24 @@ margin: 0; font-size: 1rem; font-weight: 600; - color: var(--lavender); + color: rgb(var(--admonition-accent)); display: flex; align-items: center; gap: 0.5rem; } } +.theme-admonition-info, +.theme-admonition-tips, +.theme-admonition-warning { + @include dark-theme { + .admonition-heading h5 { + // Lighten the accent so the heading keeps contrast on the dark surface. + color: color-mix(in srgb, rgb(var(--admonition-accent)) 65%, white); + } + } +} + .admonition-icon { width: 25px; height: 25px; diff --git a/src/theme/Admonition/Types.js b/src/theme/Admonition/Types.js index 7bf20c9f..8b05287e 100644 --- a/src/theme/Admonition/Types.js +++ b/src/theme/Admonition/Types.js @@ -1,35 +1,42 @@ -import React from 'react'; -import DefaultAdmonitionTypes from '@theme-original/Admonition/Types'; +import React from 'react' +import clsx from 'clsx' +import DefaultAdmonitionTypes from '@theme-original/Admonition/Types' -function CustomAdmonition({ iconSrc, ...props }) { + +function CustomAdmonition({ iconSrc, type, title, children, className }) { return ( -

- {props.title && ( -
-
- Icon - {props.title} -
-
- )} +
+
+
+ + {title} +
+
- {props.children} + {children}
- ); + ) +} + +const custom = (type, iconSrc) => { + const Admonition = (props) => ( + + ) + + Admonition.displayName = `Admonition(${type})` + + return Admonition } const AdmonitionTypes = { ...DefaultAdmonitionTypes, - 'custom-info': (props) => , - 'custom-notes': (props) => , - 'custom-tips': (props) => , - 'custom-warning': (props) => , - 'custom-stakewise': (props) => , -}; - -export default AdmonitionTypes; + 'custom-info': custom('info', '/icons/stakewise/info.png'), + 'custom-notes': custom('notes', '/icons/stakewise/notes.png'), + 'custom-tips': custom('tips', '/icons/stakewise/tips.png'), + 'custom-warning': custom('warning', '/icons/stakewise/warning.png'), + 'custom-stakewise': custom('stakewise', '/icons/stakewise/sw_logo.gif'), +} + + +export default AdmonitionTypes diff --git a/src/views/HomeView/CommonQuestions/questions.ts b/src/views/HomeView/CommonQuestions/questions.ts index 8d642847..db8792ef 100644 --- a/src/views/HomeView/CommonQuestions/questions.ts +++ b/src/views/HomeView/CommonQuestions/questions.ts @@ -1,30 +1,30 @@ export default [ { question: 'How to stake with StakeWise?', - answer: `Simply head to the Stake page in the StakeWise dApp, enter the amount of ETH or GNO you want to stake, and press the Stake button. You will receive osETH or osGNO tokens in return to keep your stake liquid.`, + answer: `Head to the Stake page in the StakeWise dApp, enter the amount of ETH or GNO you want to stake, and choose whether to receive osETH or osGNO alongside your stake. Press Stake and confirm the transaction — the liquid staking token keeps your stake usable across DeFi while it earns rewards.`, }, { question: 'What are the advantages of staking on StakeWise?', - answer: `StakeWise makes it very easy to earn rewards on your ETH or GNO and keep your assets safe. We serve a diverse group of customers, from ordinary stakers to whales, DAOs, solo stakers, wallets, professional node operators, and institutions. StakeWise has been offering its services from 2021, and has invested ~$1M into security since inception.`, + answer: `StakeWise makes it very easy to earn rewards on your ETH or GNO and keep your assets safe. We serve a diverse group of customers, from ordinary stakers to whales, DAOs, solo stakers, wallets, professional node operators, and institutions. StakeWise has been offering its services from 2021, and has invested ~$1M into security since inception.`, }, { question: 'What is StakeWise, and what does it offer to users?', - answer: `StakeWise is a liquid staking network that enables users to easily stake any amount of ETH or GNO. Users receive osTokens to represent their stake, which they can use to access liquidity or increase their yield. `, + answer: `StakeWise is a liquid staking network that enables users to easily stake any amount of ETH or GNO. Users receive osTokens to represent their stake, which they can use to access liquidity or increase their yield.`, }, { question: 'How does StakeWise Boost work?', - answer: `StakeWise Boost uses osETH (on Ethereum) or osGNO (on Gnosis Chain) tokens to borrow more ETH or GNO on Aave, stake it, mint osETH or osGNO, and repeat the process enough times to maximize your yield. The extra yield comes from the difference between the cost to borrow extra ETH or GNO, and the staking rewards earned from staking the borrowed amount. `, + answer: `StakeWise Boost, available on Ethereum, uses osETH as collateral to borrow more ETH on Aave, stakes it, mints osETH, and repeats the process enough times to maximize your yield. The extra yield comes from the difference between the cost to borrow extra ETH and the staking rewards earned from staking the borrowed amount.`, }, { question: 'Is StakeWise Boost safe to use?', - answer: `StakeWise Boost is safe to use, and has been designed with safety in mind. The collateral supplied by Boost on Aave is near perfectly correlated with assets borrowed, allowing to maintain a stable Loan to Value in all circumstances. Boosted positions are not affected by osETH (on Ethereum) or osGNO (on Gnosis Chain) depegs, allowing to use leverage safely. Boost uses a sufficiently large buffer to the Liquidation Threshold to allow the strategy to remain deeply unprofitable (-10% to -25%) for over 1 year without triggering a liquidation. However, StakeWise doesn’t recommend using Boost when its APY is consistently negative, and will trigger forced exits for deeply unprofitable positions before they are liquidated.`, + answer: `StakeWise Boost is designed with safety in mind. Aave values osETH using StakeWise's own price feed rather than secondary-market prices, so boosted positions are not exposed to an osETH depeg. Aave's ETH-correlated market allows borrowing up to 93% LTV and liquidates at 95%, and that 2% buffer gives a position room to absorb a sustained negative spread. Above 94.5% LTV anyone can unboost a position on the holder's behalf, and StakeWise monitors positions to do so before they reach liquidation. StakeWise doesn't recommend using Boost while its APY is consistently negative.`, }, { question: 'How does the redemption mechanism for osETH work?', - answer: `osETH can be redeemed for ETH at its fair exchange rate. If there is enough unbonded ETH in the protocol, redemption is instant; otherwise, validators will be exited to provide the ETH required for full redemption.`, + answer: `osETH can be redeemed for ETH at its fair exchange rate. Redemptions run through a queue: you hand osETH to the redeemer contract and receive a ticket, the redemption is settled against minters' positions, and once the batch is checkpointed you claim your ETH. If a Vault has staked everything, validators are exited to provide the ETH required.`, }, { question: 'What is the purpose of osTokens in the StakeWise ecosystem?', - answer: ` osTokens are liquid representations of assets staked in StakeWise - osETH on Ethereum, and osGNO on Gnosis Chain. osTokens allow users to keep their stake liquid and accrue staking rewards. osTokens can be converted back to ETH or GNO to access your initial deposit and the accumulated rewards.`, + answer: `osTokens are liquid representations of assets staked in StakeWise — osETH on Ethereum, and osGNO on Gnosis Chain. osTokens allow users to keep their stake liquid and accrue staking rewards. osTokens can be converted back to ETH or GNO to access your initial deposit and the accumulated rewards.`, }, ] as const diff --git a/staker/boost.mdx b/staker/boost.mdx index 64ccac19..17342232 100644 --- a/staker/boost.mdx +++ b/staker/boost.mdx @@ -1,18 +1,22 @@ --- -title: Boost +title: Using Boost description: Amplify your staking yield with Boost — use osETH as collateral to borrow and restake more ETH. Historically 4–6% APY. --- import Image from '@theme/IdealImage'; -# Boost +# Using Boost -[Boost](/docs/vaults/boost) is a yield amplification strategy built into every Vault. It uses your osETH as collateral on Aave to borrow additional ETH, which is then staked back into the Vault on your behalf. This looping process repeats automatically, amplifying your staking position with a single click — no manual DeFi steps required. Over the mid-term, Boost historically delivers a total APY of approximately **4–6%**. +[Boost](/docs/vaults/boost) is a yield amplification strategy available on **Ethereum**. It uses your osETH as collateral on Aave to borrow additional ETH, which is then staked back into the Vault on your behalf. This looping process repeats automatically, amplifying your staking position with a single click — no manual DeFi steps required. + +How far the loop goes depends on the Vault's osETH LTV: roughly **6x** in a 90% LTV Vault and up to **14x** in a Vault with near-100% LTV. Over the mid-term, Boost historically delivers a total APY of approximately **4–6%**. Boost flow diagram :::custom-info[Prerequisites] -To use Boost, you need osETH in your wallet. +Boost is available on Ethereum only, and starts from **0.05 ETH**. + +You can either boost osETH you already hold, or go straight from ETH to a boosted position in one transaction by picking **Receive and boost osETH** on the [Stake page](/staker/simple-staking#how-to-stake). ::: ## How to Boost @@ -43,6 +47,12 @@ To use Boost, you need osETH in your wallet. :::custom-warning[Monitor Your Position] Periodically check on your position to ensure the Boost APY is positive. If you see a negative Boost APY for longer than 7 days in a row, consider unboosting your position. Stay informed for real-time updates via the [StakeWise Discord ↗](https://discord.com/invite/2BSdr2g). + +The Vault page also shows a **borrow status** for the position — *Healthy* up to 93.8% LTV, *Moderate* up to 94.5%, and *Risky* above that. See [Boost safety](/docs/vaults/boost#safety) for what those thresholds mean. +::: + +:::custom-notes[Wallet Warning About an EOA] +When boosting, your wallet may warn you that you are transacting with an externally owned account. This is expected — it is the deterministically calculated address of your own Boost position contract, which does not exist on-chain until the first transaction. ::: ## How to Unboost @@ -54,8 +64,8 @@ Periodically check on your position to ensure the Boost APY is positive. If you 1. Open [app.stakewise.io ↗](https://app.stakewise.io) and connect your wallet. 2. Select the **Unboost** tab. 3. Enter the percentage of the position you want to exit. -4. Confirm the transaction. Your request enters the unboost queue, where processing time depends on the network exit queue. Only one unboost request can be active at a time. -5. Once processing is complete, click the **Claim** button to receive your assets. Claim as soon as they become available to maximize your rewards. +4. Confirm the transaction. Your request enters the unboost queue, where processing time depends on the network exit queue. Only one unboost request can be active at a time, and it must be claimed before you can boost or unboost again. +5. Once processing is complete, click the **Claim** button to receive your assets. Claim as soon as they become claimable — leaving a completed request unclaimed incurs penalties.
@@ -66,11 +76,11 @@ Periodically check on your position to ensure the Boost APY is positive. If you 1. Head to the Vault page. 2. Click the minus **(–)** button to start the unboost process. 3. Enter the percentage of the position you want to exit. -4. Confirm the transaction. Your request enters the unboost queue, where processing time depends on the network exit queue. Only one unboost request can be active at a time. -5. Once processing is complete, click the **Claim** button on the Vault page. Claim as soon as assets become available to maximize your rewards. +4. Confirm the transaction. Your request enters the unboost queue, where processing time depends on the network exit queue. Only one unboost request can be active at a time, and it must be claimed before you can boost or unboost again. +5. Once processing is complete, click the **Claim** button on the Vault page. Claim as soon as assets become claimable — leaving a completed request unclaimed incurs penalties.

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 diff --git a/operator/alternative-key-management/hashicorp-vault.mdx b/operator/alternative-key-management/hashicorp-vault.mdx index 374ed92a..417b8999 100644 --- a/operator/alternative-key-management/hashicorp-vault.mdx +++ b/operator/alternative-key-management/hashicorp-vault.mdx @@ -13,8 +13,8 @@ The Operator supports loading signing keys from a remote [HashiCorp Vault ↗](h Complete the following steps before proceeding: -1. [Installation →](/operator/launch-operator-service#installation) completed -2. [Validators Manager →](/operator/validators-manager) set +1. [Installation](/operator/launch-operator-service#installation) completed +2. [Validators Manager](/operator/validators-manager) set 3. Validator keys stored in your [HashiCorp Vault ↗](https://developer.hashicorp.com/vault/docs) instance ## Key Storage Format @@ -58,4 +58,8 @@ Passing the following options to the `start-hashi-vault` command will enable loa | `--hashi-vault-key-prefix` | Key prefix(es) in the K/V secret engine. Can be repeated for multiple prefixes | | `--hashi-vault-parallelism` | Number of parallel requests to K/V secrets engine | +:::custom-warning[Secrets Engine Must Be Mounted at `secret`] +Keys are read from `/v1/secret/data/`, and the engine name is not configurable in Operator Service v4 — the `--hashi-vault-engine-name` option available in v3 was removed. If your K/V engine is mounted somewhere other than `secret`, mount a copy at `secret` for the Operator Service to use. +::: + For additional configuration, see the full list of [optional flags](../start-operator#optional-flags). diff --git a/operator/alternative-key-management/remote-signer.mdx b/operator/alternative-key-management/remote-signer.mdx index b18ae0d3..01a36432 100644 --- a/operator/alternative-key-management/remote-signer.mdx +++ b/operator/alternative-key-management/remote-signer.mdx @@ -13,8 +13,8 @@ The Remote Signer mode lets you isolate validator signing keys from the Operator Complete the following steps before proceeding: -1. [Installation →](../launch-operator-service#installation) completed -2. [Validators Manager →](../validators-manager) set +1. [Installation](../launch-operator-service#installation) completed +2. [Validators Manager](../validators-manager) set 3. [Web3Signer ↗](https://docs.web3signer.consensys.io/) running and accessible at your specified URL ## Setup Remote Signer diff --git a/operator/introduction.mdx b/operator/introduction.mdx index 108d4e31..db22680f 100644 --- a/operator/introduction.mdx +++ b/operator/introduction.mdx @@ -35,7 +35,7 @@ A Vault with 1,000 ETH staked at ~3.5% APY and a 5% fee earns approximately 1.75 Costs depend on your Vault type (Regular or Meta) and whether you run the node yourself or delegate to a third party. Since MetaVaults don't run validators, they skip the infrastructure cost, but they still need a funded operator wallet to cover gas for their operations. -- **Infrastructure** — As of March 2026, running a node requires a dedicated machine with at least 64 GB of RAM, a 4TB SSD, and a stable internet connection. You can use pre-built staking hardware like [Home x StakeWise Pro ↗](https://dappnode.com/collections/staking-collection/products/dappnode-home-x-stakewise-pro), rent a cloud server, or delegate node operations to a third party — in which case your costs depend on your arrangement with the provider. +- **Infrastructure** — Running a node requires a dedicated machine and a stable internet connection. The Operator Service checks for at least **16 GB of RAM** and **2 TB** of free disk space at startup; more headroom is advisable, since an execution client's storage needs grow over time. You can use pre-built staking hardware like [Home x StakeWise Pro ↗](https://dappnode.com/collections/staking-collection/products/dappnode-home-x-stakewise-pro), rent a cloud server, or delegate node operations to a third party — in which case your costs depend on your arrangement with the provider. - **Gas** — The Operator Service submits on-chain transactions funded from your operator wallet. Gas costs are minimal during normal network conditions and fluctuate with congestion. Check the [Ethereum Gas Tracker ↗](https://etherscan.io/gastracker) for current rates. ## What Are the Responsibilities? diff --git a/operator/launch-operator-service.mdx b/operator/launch-operator-service.mdx index a6b3ee51..ae65a3e2 100644 --- a/operator/launch-operator-service.mdx +++ b/operator/launch-operator-service.mdx @@ -25,7 +25,7 @@ The Operator Service connects to your Execution and Consensus nodes to read on-c Once started, the Operator Service automatically handles: - **Validator Registration** — Monitors Vault balance, prepares deposit data, obtains oracle approval, and submits registration transactions (requires at least 32 ETH in the Vault). -- **Validator Funding** — Tops up existing `0x02` compound validators1 (up to 2048 ETH effective balance) before registering new ones. +- **Validator Funding** — Tops up existing `0x02` compound validators1 before registering new ones, up to [`--max-validator-balance-gwei`](./start-operator#optional-flags) (300 ETH on Ethereum, 1800 GNO on Gnosis by default). - **Withdrawals** — Processes exit queue requests via partial withdrawals from `0x02` validators first, then full exits if needed. ## Additional Capabilities @@ -37,7 +37,7 @@ The Operator Service also includes commands and flags to automate additional tas - **Automated Fee Claims** — Use `--claim-fee-splitter` to claim earned fees and send them to fee recipients. :::custom-notes[Deep Dive] -For a deeper understanding of how Vaults work under the hood — validator registration, withdrawals, rewards, MEV, and more — see the [Concepts →](/docs/overview) section. +For a deeper understanding of how Vaults work under the hood — validator registration, withdrawals, rewards, MEV, and more — see the [Concepts](/docs/overview) section. ::: ## Installation @@ -131,7 +131,7 @@ The Operator Service can be installed as a binary, via Docker, or built from sou Requirements: - - [Python 3.12+ ↗](https://www.python.org/downloads/) + - [Python 3.12 ↗](https://www.python.org/downloads/) (3.13 is not supported) - [Poetry ↗](https://python-poetry.org/docs/) - [Git ↗](https://git-scm.com/) - [Cargo ↗](https://doc.rust-lang.org/cargo/getting-started/installation.html) (for sw-milagro-bls-binding dependency) diff --git a/operator/manage-validators/automated-node-setup.mdx b/operator/manage-validators/automated-node-setup.mdx index 8dd78901..a3610491 100644 --- a/operator/manage-validators/automated-node-setup.mdx +++ b/operator/manage-validators/automated-node-setup.mdx @@ -25,10 +25,10 @@ Installs Reth and Lighthouse binaries to the data directory. | Flag | Description | |------|-------------| -| `--network` | Network for your vault: `mainnet`, `hoodi`, or `gnosis` (required) | -| `--data-dir` | Path where node data will be placed. Default: `~/.stakewise` | -| `--lighthouse-version` | Lighthouse version to install. Default: `v8.0.0` | -| `--reth-version` | Reth version to install. Default: `v1.8.2` | +| `--network` | Network for your vault: `mainnet`, `hoodi`, or `gnosis`. Prompted if not supplied. Default: `mainnet` | +| `--data-dir` | Path where node data will be placed — the directory must already exist. Default: `~/.stakewise` | +| `--lighthouse-version` | Lighthouse version to install. Default: `v8.0.1` | +| `--reth-version` | Reth version to install. Default: `v1.9.3` | ## `node-start` @@ -39,7 +39,7 @@ To start the node services and begin syncing: | Flag | Description | |------|-------------| | `--vault` | Your Vault contract address (required) | -| `--data-dir` | Path where node data will be placed. Default: `~/.stakewise` | +| `--data-dir` | Path where node data will be placed — the directory must already exist. Default: `~/.stakewise` | | `--print-execution-logs` | Print execution node logs | | `--print-consensus-logs` | Print consensus node logs | | `--print-validator-logs` | Print validator node logs | diff --git a/operator/manage-validators/consolidate-validators.mdx b/operator/manage-validators/consolidate-validators.mdx index 5872e297..531921a5 100644 --- a/operator/manage-validators/consolidate-validators.mdx +++ b/operator/manage-validators/consolidate-validators.mdx @@ -11,7 +11,7 @@ The Operator Service supports consolidation of legacy `0x01` validators to `0x02 This reduces the validator count and infrastructure overhead by merging multiple 32 ETH validators into fewer, higher-balance validators. :::custom-warning[Validators Manager Required] - The wallet executing this command must be set as the [Validators Manager →](/operator/validators-manager) in the Vault settings. If the Validators Manager is set to the wallet address of the Relayer, you have to add the `--relayer-endpoint` parameter. + The wallet executing this command must be set as the [Validators Manager](/operator/validators-manager) in the Vault settings. If the Validators Manager is set to the wallet address of the Relayer, you have to add the `--relayer-endpoint` parameter. ::: ## Consolidate Specific Validators @@ -86,5 +86,7 @@ The consolidation process requires Oracle signatures in case of switching from ` ## Optional Flags -- `--max-validator-balance-gwei` - Maximum balance for a single validator in Gwei (default: 1945000000000 Gwei for Ethereum, 1800000000000 Gwei for Gnosis). You can override this value to set a custom maximum balance. +- `--max-validator-balance-gwei` - Maximum balance for a single validator in Gwei (default: 300000000000 Gwei for Ethereum, 1800000000000 Gwei for Gnosis). You can override this value to set a custom maximum balance. - `--max-consolidation-request-fee-gwei` - Maximum fee (in Gwei) for submitting a consolidation request per validator (default: 1000 Gwei). You can override this value to set a custom maximum fee. +- `--no-switch-consolidation` - Disables switching a `0x01` validator to `0x02` when no public keys are provided. Without this flag, a lone `0x01` validator is switched automatically so it can act as a consolidation target. +- `--vault-first-block` - Block the Vault was created at. Speeds up event fetching considerably on long-lived Vaults. diff --git a/operator/manage-validators/manually-exit-validators.mdx b/operator/manage-validators/manually-exit-validators.mdx index 5af0a205..9e59e0f1 100644 --- a/operator/manage-validators/manually-exit-validators.mdx +++ b/operator/manage-validators/manually-exit-validators.mdx @@ -8,7 +8,7 @@ description: Trigger manual validator exits by index or count for emergency, mai Manual exits should only be used in specific scenarios: emergency, maintenance, or testing. :::custom-warning[Validators Manager Required] -The wallet executing this command must be set as the [Validators Manager →](/operator/validators-manager) in the Vault settings. If the Validators Manager is set to the wallet address of the Relayer, you have to add the `--relayer-endpoint` parameter. +The wallet executing this command must be set as the [Validators Manager](/operator/validators-manager) in the Vault settings. If the Validators Manager is set to the wallet address of the Relayer, you have to add the `--relayer-endpoint` parameter. ::: ```bash @@ -36,3 +36,9 @@ Exits for validators with index(es) 513571, 513572, 513861 are successfully init | `--count` | Number of validators to exit (default: all active). Cannot be used with `--indexes` | | `--relayer-endpoint` | Relayer endpoint URL for API mode operations | | `--no-confirm` | Skips confirmation prompts | +| `--max-withdrawal-request-fee-gwei` | Maximum fee per withdrawal request. Default: 1000 Gwei | +| `--vault-first-block` | Block the Vault was created at. Speeds up event fetching considerably on long-lived Vaults | +| `--network` | Network of the Vault. Default: the network chosen during `init` | +| `--data-dir` | Path where keystores and config data are placed. Default: `~/.stakewise` | +| `-v`, `--verbose` | Enable debug mode, prints full output on error | +| `--log-level` | Set logging level: ERROR, WARNING, INFO, DEBUG. Default: INFO | diff --git a/operator/manage-validators/recover-validators.mdx b/operator/manage-validators/recover-validators.mdx index 9d379362..e8e9c4ce 100644 --- a/operator/manage-validators/recover-validators.mdx +++ b/operator/manage-validators/recover-validators.mdx @@ -36,3 +36,20 @@ Vault has 1 registered validator(s), recover active keystores from provided mnem Successfully recovered 1 keystores for vaults 0x001... ``` + +## `recover` flags + +The command prompts for anything it needs, so all flags are optional. + +| Flag | Description | +|------|-------------| +| `--mnemonic` | Mnemonic used to generate the validator keys. Prompted if omitted | +| `--vault` | Address of the Vault to recover keystores for. Prompted if omitted | +| `--per-keystore-password` | Creates a separate password file for each keystore instead of one shared `password.txt` | +| `--vault-first-block` | Block the Vault was created at. Without it, `recover` scans from the Keeper's genesis block, which can take a long time | +| `--no-confirm` | Skips confirmation prompts | +| `--execution-endpoints` | Comma-separated API endpoints for execution nodes. Prompted if omitted | +| `--consensus-endpoints` | Comma-separated API endpoints for consensus nodes. Prompted if omitted | +| `--network` | Network of the Vault. Default: the network chosen during `init` | +| `--data-dir` | Path where keystores and config data are placed. Default: `~/.stakewise` | +| `--log-level` | Set logging level: ERROR, WARNING, INFO, DEBUG. Default: INFO | diff --git a/operator/manage-vault/fee-claiming.mdx b/operator/manage-vault/fee-claiming.mdx index ab7741f2..f9efb168 100644 --- a/operator/manage-vault/fee-claiming.mdx +++ b/operator/manage-vault/fee-claiming.mdx @@ -24,7 +24,7 @@ A **Fee Splitter** is a smart contract that can take the place of that fee recip :::custom-notes[Under the Hood] Saving runs three on-chain actions, each requiring a separate wallet confirmation: -1. **Create reward splitter** — deploys the [Fee Splitter ↗](https://etherscan.io/address/0xDA7F4d74649d042b871e479835C8D262C7a1515f) contract for your Vault. +1. **Create reward splitter** — deploys the [`RewardSplitter` ↗](https://etherscan.io/address/0xd12Df8543e0522CCbF12d231e822B7264c634775) contract for your Vault (the address shown is the factory that deploys one splitter per Vault). 2. **Apply new settings** — points the Vault's fee recipient at the Splitter so future fees flow into it. 3. **Update fee splitter** — registers shareholders and their shares on the Splitter. ::: diff --git a/operator/manage-vault/overview.mdx b/operator/manage-vault/overview.mdx index a0476297..c5792bf8 100644 --- a/operator/manage-vault/overview.mdx +++ b/operator/manage-vault/overview.mdx @@ -9,6 +9,6 @@ Once your Vault is live, [vault administration tasks](./vault-administration) If your Vault has multiple fee shareholders, the [fee claiming](./fee-claiming) system lets you configure percentage-based splits and automate periodic claiming through the Operator Service — harvesting rewards, withdrawing each shareholder's share, and claiming exited assets in a single transaction. -You can also distribute [extra token rewards](./add-extra-rewards) (SWISE, osETH, SSV, Obol, WETH, USDC, LYX) to your stakers beyond standard staking yield — either periodically over a defined time window, as a one-time allocation, or via a custom IPFS-based distribution. Extra rewards are reflected in both the Vault APY and user reward statistics (Ethereum only). +You can also distribute [extra token rewards](./add-extra-rewards) (SWISE, osETH, SSV, Obol, WETH, USDC) to your stakers beyond standard staking yield — either periodically over a defined time window, as a one-time allocation, or via a custom IPFS-based distribution. Extra rewards are reflected in both the Vault APY and user reward statistics (Ethereum only). If you operate a MetaVault, the [`process-meta-vaults`](/operator/meta-vault/operate-meta-vault#3-run-process-meta-vaults) command triggers transactions to update states, process deposits into sub-vaults, and handle exits. diff --git a/operator/manage-vault/vault-administration.mdx b/operator/manage-vault/vault-administration.mdx index 992091ec..48742831 100644 --- a/operator/manage-vault/vault-administration.mdx +++ b/operator/manage-vault/vault-administration.mdx @@ -33,7 +33,7 @@ Once your Vault is created, you can manage all settings through the **Settings** **Manage fees:** - Set rates: 0-100% of staking rewards - Update frequency: Once every 3 days, limited to 20% increase per update. If the initial fee is 0%, first increase is limited to 1% - - Configure multiple recipients with percentage splits and automatic periodic claims on their behalf ([Learn more →](/operator/manage-vault/fee-claiming)) + - Configure multiple recipients with percentage splits and automatic periodic claims on their behalf ([Learn more](/operator/manage-vault/fee-claiming)) diff --git a/operator/operator-service-v3/alternative-key-management/api-mode.mdx b/operator/operator-service-v3/alternative-key-management/api-mode.mdx index c4d63b90..2fc19c48 100644 --- a/operator/operator-service-v3/alternative-key-management/api-mode.mdx +++ b/operator/operator-service-v3/alternative-key-management/api-mode.mdx @@ -15,7 +15,7 @@ In the standard setup, the Operator Service holds validator keystores locally an Every time a validator is registered, the Vault contract needs to verify that the registration is authorized. There are two ways this can work: -**Default (deposit data file)** — You generate validator keys and a deposit data file with the Operator Service, then upload it to your Vault. On each registration, the Vault contract checks that the submitted validator was pre-approved in the uploaded file (using a Merkle proof — a compact cryptographic check against the file contents). This is the standard setup described in [Prepare Operator →](../run-operator-service-v3/prepare-operator). +**Default (deposit data file)** — You generate validator keys and a deposit data file with the Operator Service, then upload it to your Vault. On each registration, the Vault contract checks that the submitted validator was pre-approved in the uploaded file (using a Merkle proof — a compact cryptographic check against the file contents). This is the standard setup described in [Prepare Operator](../run-operator-service-v3/prepare-operator). **Validators Manager signature (required for API mode)** — Instead of checking a deposit data file, the Vault contract verifies a cryptographic signature from a designated wallet called the **Validators Manager**. The Relayer signs each registration with this wallet using the [EIP-712 ↗](https://eips.ethereum.org/EIPS/eip-712) standard (a structured way to sign typed data on Ethereum). If the signature is valid, the registration is approved. Since the Relayer provides all data and signatures on demand, you don't need to generate or upload a deposit data file. @@ -42,8 +42,8 @@ To produce exit signatures, the Relayer must use the validator index passed in t Complete the following steps before proceeding: -1. [Installation completed →](../run-operator-service-v3/installation) -2. [Operator Service prepared →](../run-operator-service-v3/prepare-operator) +1. [Installation completed](../run-operator-service-v3/installation) +2. [Operator Service prepared](../run-operator-service-v3/prepare-operator) ### Configure Validators Manager @@ -79,7 +79,7 @@ For additional parameter information, use the `--help` flag: ``` :::custom-info[Docker Configuration] -For Docker-specific setup instructions, see the Docker Image tab in [Installation →](../run-operator-service-v3/installation) +For Docker-specific setup instructions, see the Docker Image tab in [Installation](../run-operator-service-v3/installation) ::: ## API Specification diff --git a/operator/operator-service-v3/alternative-key-management/dvt/obol-setup.mdx b/operator/operator-service-v3/alternative-key-management/dvt/obol-setup.mdx index 82988758..03b2b59e 100644 --- a/operator/operator-service-v3/alternative-key-management/dvt/obol-setup.mdx +++ b/operator/operator-service-v3/alternative-key-management/dvt/obol-setup.mdx @@ -24,8 +24,8 @@ Before proceeding, ensure you have the following: docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ``` -3. [Create Vault →](/operator/create-regular-vault) -4. [Setup Operator Service →](/operator/operator-service-v3/run-operator-service-v3/installation) +3. [Create Vault](/operator/create-regular-vault) +4. [Setup Operator Service](/operator/operator-service-v3/run-operator-service-v3/installation) :::custom-warning[Important - Keystore Password Configuration] When using the `create-keys` command, add the `--per-keystore-password` flag to generate a keystore with a separate password file for each keystore. This is necessary for the normal operation of the Charon CLI, especially when splitting keys. diff --git a/operator/operator-service-v3/alternative-key-management/dvt/ssv-setup.mdx b/operator/operator-service-v3/alternative-key-management/dvt/ssv-setup.mdx index 73245715..fc548d15 100644 --- a/operator/operator-service-v3/alternative-key-management/dvt/ssv-setup.mdx +++ b/operator/operator-service-v3/alternative-key-management/dvt/ssv-setup.mdx @@ -25,8 +25,8 @@ Before proceeding, ensure you have the following: docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ``` -3. [Create Vault →](/operator/create-regular-vault) -4. [Setup Operator Service →](/operator/operator-service-v3/run-operator-service-v3/installation) +3. [Create Vault](/operator/create-regular-vault) +4. [Setup Operator Service](/operator/operator-service-v3/run-operator-service-v3/installation) :::custom-notes[Hardware Requirements] SSV node requires (in addition to your beacon/execution nodes): **2 cores**, **2GB RAM**, **20GB storage**, **>8K IOPS**. See [full requirements ↗](https://docs.ssv.network/operators/operator-node/node-setup/hardware-requirements). diff --git a/operator/operator-service-v3/alternative-key-management/hashicorp-vault.mdx b/operator/operator-service-v3/alternative-key-management/hashicorp-vault.mdx index 8703035c..63b4b13f 100644 --- a/operator/operator-service-v3/alternative-key-management/hashicorp-vault.mdx +++ b/operator/operator-service-v3/alternative-key-management/hashicorp-vault.mdx @@ -13,8 +13,8 @@ The Operator supports loading signing keys from a remote [HashiCorp Vault ↗](h Complete the following steps before proceeding: -1. [Installation completed →](../run-operator-service-v3/installation) -2. [Prepare Operator →](../run-operator-service-v3/prepare-operator) — keys generated and deposit data uploaded +1. [Installation completed](../run-operator-service-v3/installation) +2. [Prepare Operator](../run-operator-service-v3/prepare-operator) — keys generated and deposit data uploaded 3. Validator keys stored in your [HashiCorp Vault ↗](https://developer.hashicorp.com/vault/docs) instance ## Key Storage Format diff --git a/operator/operator-service-v3/alternative-key-management/overview.mdx b/operator/operator-service-v3/alternative-key-management/overview.mdx index 743f96ad..685f5149 100644 --- a/operator/operator-service-v3/alternative-key-management/overview.mdx +++ b/operator/operator-service-v3/alternative-key-management/overview.mdx @@ -6,5 +6,5 @@ description: Manage validator keys externally using Remote Signer, HashiCorp Vau # Alternative Key Management :::custom-warning[Operator V3 Documentation] -This page covers Operator Service V3. If your Vault version is **5+ on Ethereum** or **3+ on Gnosis**, refer to the [main documentation →](/operator/introduction). +This page covers Operator Service V3. If your Vault version is **5+ on Ethereum** or **3+ on Gnosis**, refer to the [main documentation](/operator/introduction). ::: diff --git a/operator/operator-service-v3/alternative-key-management/remote-signer.mdx b/operator/operator-service-v3/alternative-key-management/remote-signer.mdx index 0428d3ef..f8e00f32 100644 --- a/operator/operator-service-v3/alternative-key-management/remote-signer.mdx +++ b/operator/operator-service-v3/alternative-key-management/remote-signer.mdx @@ -13,8 +13,8 @@ The Remote Signer mode lets you isolate validator signing keys from the Operator Complete the following steps before proceeding: -1. [Installation completed →](../run-operator-service-v3/installation) -2. [Operator Service prepared →](../run-operator-service-v3/prepare-operator) +1. [Installation completed](../run-operator-service-v3/installation) +2. [Operator Service prepared](../run-operator-service-v3/prepare-operator) 3. [Web3Signer ↗](https://docs.web3signer.consensys.io/) running and accessible at your specified URL ## Setup Remote Signer diff --git a/operator/operator-service-v3/img/architecture_overview.png b/operator/operator-service-v3/img/architecture_overview.png deleted file mode 100644 index 83d6e216a9949a2da81bf121e95a60af0432d9a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99640 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 diff --git a/operator/operator-service-v3/overview.mdx b/operator/operator-service-v3/overview.mdx index 73451f33..9aaf3ecb 100644 --- a/operator/operator-service-v3/overview.mdx +++ b/operator/operator-service-v3/overview.mdx @@ -6,9 +6,9 @@ description: Operator Service V3 documentation for Vaults below version 5 on Eth # Operator Service V3 :::custom-warning[Operator V3 Documentation] -You must run Operator Service v3.1.10 and follow this documentation page if your **Vault version is below 5** on Ethereum or **below 3** on Gnosis. You can check your Vault contract version in the **Details** section of the Vault page or on the contract page in the block explorer. If your Vault version is higher, please refer to the [main documentation →](/operator/introduction). +You must run Operator Service v3.1.10 and follow this documentation page if your **Vault version is below 5** on Ethereum or **below 3** on Gnosis. You can check your Vault contract version in the **Details** section of the Vault page or on the contract page in the block explorer. If your Vault version is higher, please refer to the [main documentation](/operator/introduction). ::: -We recommend upgrading to enjoy the full set of features. See the [V4 Upgrade Guide →](./v4-upgrade-guide). +We recommend upgrading to enjoy the full set of features. See the [V4 Upgrade Guide](./v4-upgrade-guide). This section covers V3-specific commands only. diff --git a/operator/operator-service-v3/run-operator-service-v3/installation.mdx b/operator/operator-service-v3/run-operator-service-v3/installation.mdx index e8e3a28c..17af0ad8 100644 --- a/operator/operator-service-v3/run-operator-service-v3/installation.mdx +++ b/operator/operator-service-v3/run-operator-service-v3/installation.mdx @@ -73,7 +73,7 @@ Choose your preferred method and follow the instructions below. Requirements: - - [Python 3.10+ ↗](https://www.python.org/downloads/) + - [Python 3.12 ↗](https://www.python.org/downloads/) (3.13 is not supported) - [Poetry ↗](https://python-poetry.org/docs/) Run the setup command: diff --git a/operator/operator-service-v3/run-operator-service-v3/prepare-operator.mdx b/operator/operator-service-v3/run-operator-service-v3/prepare-operator.mdx index 09f33f03..a53e0950 100644 --- a/operator/operator-service-v3/run-operator-service-v3/prepare-operator.mdx +++ b/operator/operator-service-v3/run-operator-service-v3/prepare-operator.mdx @@ -37,7 +37,7 @@ Follow the command prompts.

:::custom-tips[Get a Claim Alert] -You can add your email address to receive a notification as soon as your unboosted position is ready to be claimed. Click your wallet address in the top-right corner, then click the alert button alert button icon next to your address. In the **Configure alerts** pop-up, enter your email and click **Save**. +You can set up an email or browser notification to be alerted as soon as your unboosted position is ready to be claimed. Click your wallet address in the top-right corner, then click the alert button alert button icon next to your address. In the **Configure alerts** pop-up, enter your email and click **Save**, or enable browser notifications. ::: diff --git a/staker/introduction.mdx b/staker/introduction.mdx index 2434212f..c9092ef8 100644 --- a/staker/introduction.mdx +++ b/staker/introduction.mdx @@ -11,12 +11,12 @@ description: Stake ETH with StakeWise — earn rewards, mint osETH for DeFi, and 1. **Stake** any amount of ETH and start earning rewards — no 32 ETH minimum, no hardware to run, no technical knowledge required. [Rewards](/staker/rewards) accrue automatically. 2. **Mint** [osETH](/docs/ostoken/intro) — a liquid token representing your stake plus accumulated rewards — to use it across DeFi protocols such as Aave, Balancer, Fluid, Uniswap, and [more ↗](https://app.stakewise.io/ecosystem) -3. [**Boost**](/staker/boost) your position with a single click — use osETH as collateral on Aave to borrow and restake more ETH, for a historical 4–6% APY. +3. [**Boost**](/staker/boost) your position with a single click — use osETH as collateral on Aave to borrow and restake more ETH, for a historical 4–6% APY. Available on Ethereum. 4. **Withdraw** [at any time](/docs/vaults/how-vaults-work#withdrawals) — your ETH stays yours, with no lock-ups or penalties. You stay in full control — your funds never leave the smart contracts, while professional operators handle all the validator infrastructure for you. Your only [costs](/docs/fees/intro) are Ethereum gas for transactions and a fee on rewards — set by the Vault operator (typically 3–5%, varies by Vault). If you mint osETH, the [StakeWise DAO charges an additional 5%](/docs/fees/intro#ostoken-fees) of the rewards accrued on your osETH. -Everything runs on [regularly audited](/contracts/audits) smart contracts that secure over $1B in staked assets. +Everything runs on [regularly audited](/contracts/audits) smart contracts securing hundreds of millions of US dollars' worth of stake. ## How to Start diff --git a/staker/risks.mdx b/staker/risks.mdx index 251cd668..1d179689 100644 --- a/staker/risks.mdx +++ b/staker/risks.mdx @@ -15,7 +15,7 @@ Each Vault is fully isolated — poor performance in one Vault has no effect on ## Smart Contract Smart Contract -All DeFi protocols carry smart contract risk. StakeWise runs on battle-tested, [regularly audited](/contracts/audits) contracts that secure over $1B in staked assets. The protocol has been live since 2021 with no security incidents. +All DeFi protocols carry smart contract risk. StakeWise runs on battle-tested, [regularly audited](/contracts/audits) contracts securing hundreds of millions of US dollars' worth of stake. The protocol has been live since 2021 with no security incidents. ## osETH Depeg osETH Depeg @@ -23,4 +23,13 @@ osETH could temporarily trade below its fair value on secondary markets. The pro ## Boost Boost -As with any leveraged strategy, [Boost](/docs/vaults/boost) carries liquidation and penalty risks. If Aave's borrow rate exceeds the staking rate for too long, your Boost APY turns negative and your position loses value every day it stays open, while your LTV drifts toward Aave's liquidation threshold. A [2% safety buffer](/docs/vaults/boost#safety) between max borrow LTV and the liquidation threshold gives your position room to absorb this drift, and StakeWise automatically unboosts as you approach the threshold. Stay updated via [StakeWise Discord ↗](https://discord.com/invite/2BSdr2g). +As with any leveraged strategy, [Boost](/docs/vaults/boost) carries liquidation and penalty risks. If Aave's borrow rate exceeds the staking rate for too long, your Boost APY turns negative and your position loses value every day it stays open, while your LTV drifts toward Aave's liquidation threshold. A [2% safety buffer](/docs/vaults/boost#safety) between max borrow LTV and the liquidation threshold gives your position room to absorb this drift. Above 94.5% LTV anyone can unboost the position on your behalf, and the StakeWise team monitors positions to do so when needed — but this is a permissionless safeguard, not a protocol guarantee. The Vault page shows a borrow status (*Healthy* / *Moderate* / *Risky*) so you can see where your position sits. Stay updated via [StakeWise Discord ↗](https://discord.com/invite/2BSdr2g). + +## Unstaking Unstaking + +Unstaking is not instant. Your request joins the Vault's [exit queue](/docs/vaults/how-vaults-work#withdrawals), which is served first from assets already liquid in the Vault and then from validator exits. Two things set the pace: + +- **A fixed claim delay.** Assets cannot be claimed until 15 hours after you entered the queue, even if the Vault could cover the request immediately. +- **The network's validator exit queue.** If validators have to exit to fund your request, you also wait for Ethereum to process them. This varies with network demand — the app shows a live estimate and the average over the past month. + +Your assets keep earning rewards for the whole wait, and StakeWise Oracles force validator exits if a Vault's operator does not free up assets within 24 hours, so a request cannot be stalled indefinitely by an unresponsive operator. Vaults that hold most of their assets in active validators will still be slower to exit than Vaults holding a liquidity buffer. diff --git a/staker/simple-staking.mdx b/staker/simple-staking.mdx index 026acfb1..218d65d8 100644 --- a/staker/simple-staking.mdx +++ b/staker/simple-staking.mdx @@ -8,7 +8,7 @@ import Tooltip from '@site/src/components/Tooltip/Tooltip'; # Simple Staking -The simplest way to stake with StakeWise is through the [StakeWise App ↗](https://app.stakewise.io) — deposit any amount of ETH and stake it in a single click. Your assets are routed through a [MetaVault](/docs/vaults/vault-types#metavault) that distributes them across the best-performing sub-vaults — currently just the [Genesis Vault ↗](https://app.stakewise.io/vault/mainnet/0xac0f906e433d58fa868f936e8a43230473652885), with more to come. +The simplest way to stake with StakeWise is through the [StakeWise App ↗](https://app.stakewise.io) — deposit any amount of ETH and stake it in a single click. Your assets are routed through a [MetaVault](/docs/vaults/vault-types#metavault) that distributes them across the best-performing sub-vaults. Today that chain resolves to the [Genesis Vault ↗](https://app.stakewise.io/vault/mainnet/0xac0f906e433d58fa868f936e8a43230473652885), with more sub-vaults to come. When staking, you'll automatically receive [osETH](/docs/ostoken/intro), which represents your staked assets plus accumulated rewards. osETH can be used [across DeFi to boost your yield ↗](https://app.stakewise.io/ecosystem). @@ -22,12 +22,16 @@ Stake your ETH and start earning rewards immediately. 1. Open [app.stakewise.io ↗](https://app.stakewise.io) and connect your wallet. 2. Select the **Stake** tab and enter the amount of ETH you want to stake. -3. Review the details. -4. Click **Stake** and confirm the transaction in your wallet. -5. Once confirmed, osETH will appear in your wallet and immediately start earning rewards. +3. Choose what happens to the stake: + - **Stake only** — your ETH is staked and no osETH is minted. + - **Receive osETH** — osETH is minted against your stake. This is the default. + - **Receive and boost osETH** — the newly minted osETH is locked as [Boost](/staker/boost) collateral in the same transaction. Available from 0.05 ETH. +4. Review the details. +5. Click **Stake** and confirm the transaction in your wallet. +6. Once confirmed, your stake immediately starts earning rewards. Unless you chose **Stake only**, osETH appears in your wallet. :::custom-tips[Track Your Position Anytime] -In the **Balance** tab you can view your APY, total staked, and total boosted amounts. +In the **Balance** tab you can view your total staked amount and your APY. If you have an active Boost position, the APY figure reflects it. ::: ## Boost @@ -45,13 +49,12 @@ Before unstaking, make sure the amount of osETH you want to unstake is in your w Unstaking flow explanation diagram 1. Open [app.stakewise.io ↗](https://app.stakewise.io) and connect your wallet. -2. Click the **flip** button flip button icon to switch from staking to unstaking mode. -3. Select the **Unstake** tab and enter the amount of osETH you want to convert back to ETH. -4. Review the details. -5. Click **Unstake** and confirm the transaction in your wallet. -6. Your request enters the Vault's [exit queue](/docs/vaults/how-vaults-work#withdrawals). Waiting time is at least 24 hours and depends on [current network conditions ↗](https://www.validatorqueue.com/). Your assets keep earning rewards until the exit completes. -7. Once the request is processed, click **Claim** and confirm the transaction to receive your ETH (original stake plus accumulated rewards). +2. Select the **Unstake** tab and enter the amount of osETH you want to convert back to ETH. +3. Review the details. +4. Click **Unstake** and confirm the transaction in your wallet. +5. Your request enters the Vault's [exit queue](/docs/vaults/how-vaults-work#withdrawals). The app shows a live estimate of how long it will take, along with the average over the past month — how long it actually takes depends on the network's validator exit queue. Your assets keep earning rewards until the exit completes. +6. Once the request is processed, click **Claim** and confirm the transaction to receive your ETH (original stake plus accumulated rewards). :::custom-tips[Get a Claim Alert] -You can add your email address to receive a notification as soon as your unstaked assets are ready to be claimed. Click your wallet address in the top-right corner, then click the alert button alert button icon next to your address. In the **Configure alerts** pop-up, enter your email and click **Save**. +You can set up an email or browser notification to be alerted as soon as your unstaked assets are ready to be claimed. Click your wallet address in the top-right corner, then click the alert button alert button icon next to your address. In the **Configure alerts** pop-up, enter your email and click **Save**, or enable browser notifications. ::: diff --git a/staker/stakewise-v2/01-migrate-to-stakewise-v3-on-ethereum.mdx b/staker/stakewise-v2/01-migrate-to-stakewise-v3-on-ethereum.mdx index 1b6a2ed4..29625433 100644 --- a/staker/stakewise-v2/01-migrate-to-stakewise-v3-on-ethereum.mdx +++ b/staker/stakewise-v2/01-migrate-to-stakewise-v3-on-ethereum.mdx @@ -36,6 +36,10 @@ Remember that in order to migrate, you will need to connect with the wallet that Once your wallet is connected to the StakeWise V3 interface, you will find the Migrate section at the top of your screen. +:::custom-notes[Don't See the Migrate Option?] +The Migrate entry point only appears when the connected wallet holds a non-zero StakeWise V2 balance. If it isn't there, check that you are connected with the wallet that holds the V2 tokens and are on the right network. +::: + Navigate to the Migrate interface Press the `Migrate` button to open the Migrate interface. You should see a floating module with blank fields for the amount of sETH2 and rETH2 tokens you would like to migrate to StakeWise V3. @@ -50,7 +54,7 @@ Once you've entered the amounts, press the `Migrate` button and confirm the tran Confirm the transaction -Once the transaction is finalized, you should see the total amount of tokens you chose to migrate as ETH staked in the [Genesis Vault ↗](https://app.stakewise.io/vault/0xAC0F906E433d58FA868F936E8A43230473652885) on StakeWise V3. +Once the transaction is finalized, you should see the total amount of tokens you chose to migrate as ETH staked in the [Genesis Vault ↗](https://app.stakewise.io/vault/mainnet/0xac0f906e433d58fa868f936e8a43230473652885) on StakeWise V3. Migration complete diff --git a/staker/stakewise-v2/02-migrate-to-stakewise-v3-on-gnosis-chain.mdx b/staker/stakewise-v2/02-migrate-to-stakewise-v3-on-gnosis-chain.mdx index deb7db46..f3bc2a09 100644 --- a/staker/stakewise-v2/02-migrate-to-stakewise-v3-on-gnosis-chain.mdx +++ b/staker/stakewise-v2/02-migrate-to-stakewise-v3-on-gnosis-chain.mdx @@ -42,6 +42,10 @@ After connecting your wallet, select `Gnosis Chain` network at the top right-han Once your wallet is connected to the StakeWise V3 interface, you will find the Migrate section at the top of your screen. +:::custom-notes[Don't See the Migrate Option?] +The Migrate entry point only appears when the connected wallet holds a non-zero StakeWise V2 balance. If it isn't there, check that you are connected with the wallet that holds the V2 tokens and are on the right network. +::: + Navigate to the Migrate interface Press the `Migrate` button to open the Migrate interface. You should see a floating module with blank fields for the amount of sGNO and rGNO tokens you would like to migrate to StakeWise V3. diff --git a/staker/vault-staking.mdx b/staker/vault-staking.mdx index a0b79807..d70c6e9d 100644 --- a/staker/vault-staking.mdx +++ b/staker/vault-staking.mdx @@ -22,9 +22,10 @@ Pick a Vault that matches your preferences and start staking in just a few steps 1. Open [app.stakewise.io ↗](https://app.stakewise.io) and connect your wallet. 2. Navigate to **Vaults** and browse the available options. -3. Select a Vault and review its details, including MEV strategy, TVL, APY, performance, and staking fee. -4. Click the **(+)** button next to **Staked ETH**, enter the amount of ETH you want to stake, and confirm the transaction in your wallet. -5. (Optional) Click the **(+)** button next to **Minted osETH** to mint osETH and stay liquid while your ETH keeps earning. +3. Use the **Verified** filter to limit the list to Vaults the StakeWise team has reviewed, and compare the APY, TVL and uptime shown on each card. +4. Open a Vault and review its **Details** — Vault type, capacity, contract address, admin, fee recipient, **MEV recipient**, **Staking fee**, and version. +5. Click the **(+)** button next to **Staked ETH**, enter the amount of ETH you want to stake, and confirm the transaction in your wallet. +6. (Optional) Click the **(+)** button next to **Minted osETH** to mint osETH and stay liquid while your ETH keeps earning. Your stake starts earning rewards as soon as the Vault's validators are active. @@ -48,11 +49,11 @@ Before unstaking, make sure the amount of osETH you want to burn is in your wall Unstaking flow with a Vault explanation diagram -1. If you've minted osETH, click the **(–)** button next to **Minted osETH**, enter the amount to burn (or click **Max**), and confirm the transaction. +1. If you've minted osETH, click the **(–)** button next to **Minted osETH**, enter the amount to burn (or click **Max**), and confirm the transaction. The app also offers a combined **Burn & Unstake** option that does both in one step. 2. Click the **(–)** button next to **Staked ETH**, enter the amount you want to withdraw, and click **Unstake**. -3. Your request enters the Vault's [exit queue](/docs/vaults/how-vaults-work#withdrawals). Waiting time is at least 24 hours and depends on [current network conditions ↗](https://www.validatorqueue.com/). Your assets keep earning rewards until the exit completes. +3. Your request enters the Vault's [exit queue](/docs/vaults/how-vaults-work#withdrawals). The app shows a live estimate of how long it will take, along with the average over the past month — how long it actually takes depends on the network's validator exit queue. Your assets keep earning rewards until the exit completes. 4. Once your assets have exited, click **Claim** to receive your ETH. :::custom-tips[Get a Claim Alert] -You can add your email address to receive a notification as soon as your unstaked assets are ready to be claimed. Click your wallet address in the top-right corner, then click the alert button alert button icon next to your address. In the **Configure alerts** pop-up, enter your email and click **Save**. +You can set up an email or browser notification to be alerted as soon as your unstaked assets are ready to be claimed. Click your wallet address in the top-right corner, then click the alert button alert button icon next to your address. In the **Configure alerts** pop-up, enter your email and click **Save**, or enable browser notifications. ::: diff --git a/static/icons/aave.svg b/static/icons/aave.svg deleted file mode 100644 index 8cb75580..00000000 --- a/static/icons/aave.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/static/icons/code.svg b/static/icons/code.svg deleted file mode 100644 index 7c4b6857..00000000 --- a/static/icons/code.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/static/icons/file.svg b/static/icons/file.svg deleted file mode 100644 index 5893bc48..00000000 --- a/static/icons/file.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/static/icons/safe.png b/static/icons/safe.png deleted file mode 100644 index 52b7018419987ac737e1cfc1912c8b5bbd06129d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 378 zcmV-=0fqjFP)f_1qO(PFh+P;DWiwU$fjN<@1} zu)j&H8fXon^|h#gn}|7}hj)njKUWTt_RKzgCpF~BNz<6K~3+Pk~ck|!};pQGwu_t3Y#0@=rDmD>m{Kpuc zMCP=ZjAl+AhBRKD#E#)ziuoey{L YALMT*%beuXuK)l507*qoM6N<$f-fwjA^-pY diff --git a/static/icons/stakewise/asterisk.png b/static/icons/stakewise/asterisk.png deleted file mode 100644 index b7eb83aca3de50a77721245e79ffdaf835cae07c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17840 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

b-V50Kmg20^7I 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 diff --git a/static/icons/stakewise/brush.png b/static/icons/stakewise/brush.png deleted file mode 100644 index d7b291857d111f3d83786b47cada5e76028ec052..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2381 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�$^R#SS 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 QPa{B9Qc%sBY}0FYkSEdT%j diff --git a/static/icons/stakewise/eth_coin.png b/static/icons/stakewise/eth_coin.png deleted file mode 100644 index 8147ae388f45835a08ca1acab2849fcaec8ff080..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18142 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 diff --git a/static/icons/stakewise/exclamation_mark.png b/static/icons/stakewise/exclamation_mark.png deleted file mode 100644 index 2388a05b6c490386fb6fa371fdf9dc68cb563ba8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6338 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 diff --git a/static/icons/stakewise/flip_button.png b/static/icons/stakewise/flip_button.png deleted file mode 100644 index e0b3418d7f350a33047157d656b17e4fe9e003ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4831 zcmV<55+Ln~P)c0gHry$G}KI zW(iV4q$mm_W3Y`eW+JdjL_ivdF;Yf}kx-(@Vq_L?ZRl#d-L)@sez*MWa$~obs#jHw ztK9#(%e(KKbI<>t`|iE3Y z|JBzOfQ-Fgj8u+d-`>whHt*Rz^8LoSy0wy~1=0KV?He@5zYie?_oXVMBZn)oSkIc? z-kysSiG)mrzP>)03f{f7ZLDriZ=!ceZ=&bFQwPT@U+*7tgJJLI2iAW2>8GLAQg&|I z+nQ*LvOObvM=PN~Y$O(sUDMOkBOn%wNjMyqU@$0wK%g$D>aqK~y1E2(cXx}2NF*ZY zi^t;*AXl~OY)CB@ zivl1S$3g&UJ@91I4hB!2s=8|$vgoA-QvV2?Fx+rPFTde80y zR}me(y}f-z4AG;i;t`7XR8>?@1oia-f{~$mTsCFEH4l_kzum@&c7$8B4?*}>zdmpY zZBK!gRkxgId)~bpiAJ_VF3&|(b<-stA#zCD84N~Mb&nNDkM%@%8sYD^o6Uh%8f4G6 ziaF}xMl_D#8Ej5*PEu9p7N$xvq=0-o zL$x{sVJl!3qE8H5wq@;NdrOFhWQ%0CMN+L2%oGT4jHW}+)!9u{0|;*yvYSe@?T<&S z<8h`n)#FoMt&_;tI*xL?L$yku@Zg91CbhOPB$7Z=fT^b@qRdBc0VcI^rut!i50JJI{T9C_M0=MEHb9uD_< zSDW|h8l7e`1xF;hk&P{h{+Pt$QGsM;8KN0mR8_)}E}FT45Dtf2zENN3w#S#mf{Bfd zl6j_b0%zLrT-ybMp-1Q-&P0aiP}LKsIIj5x$RuI`Iu4~KEXs@S?H6a~nM}q8V$cnU zBjZ_##Ud`B&lhN37rg#@LhwZ}6kND#*Eb2Tt{JJXM1S@9fz3D>lIbv0Bb`!JH>FWW z*-v=nq67WmaL7mv*&e=IKKbMmi9|w<@JuG_p4$*_FT5PlwCBEhKp$1ri9jAROW0nq z#>1tZuChsAz7ib{g_baa=?k5t)8svBD5;^=J06C#hw_7cn14RNADND(v6)j7ll$Y*M*HJP>x`F|-uOOFP^1JfnldEOj zy0sSFn?%cUk-hTUSmv1x&CnJ0R?)?`vTF+#8 zz7ehJKgo24Df+Fq-ja&p77|PsYf>X?(S|Ws4O7wCmK$) z9eG}d@qQXzD?1zxx+42u9GFh$-E4AXJYy$ZF_$OmPX%mcu&iCXR^EK`O}XQaJA^45 zpBFQi<#mAs#5IMFuW{luQpZWQ}zxF`GYk#Wfvb5gdIGArg##E56b zZ7W;IWwACDip8>v?9H3E$o1FXAh+Int6RBqZ`NLTXeA)P(|5Khx+mY`kxV@UO;mn| zyq^d(T(}1QSXCP?_EZA-c`&4~=f$a{Y`2JTQdJezQ)APnP4dbsugLP{%jNF7?{?F* zs=Aqg?T^&?fmCoRG1zX_^_=Q@qv{(@v~?~GEqdCDDhHNwMnKTOp(;l zhN+qql6j8bd+)unbm>ytSC<^Y*j_5w^S7)ts|$`uv!Paq>T$W)(hNT}oM<|vTJ1

v?U zz43$`Ih>L1Kv448g5}e)^{s94r+;#t49^{wa#7r~xJ$cTDoy&Rs?(&Z6_@eptKmfZ zWalZx@|FpLX~5#?jW^zqZQHiVZMWU#2<947HqlDtGbm!QsPy#2T*Na#vi3#`IZWBW z8>HrZ#QKhA=<$Q-Q5*$)_mC;5E||);|1App>2>SY$&w{Y93YP=otGNC!D1^Gi$&~c zGh@@c2qb$>W&=~BO$K8zemQ$CuCwG<9 zl8a{SAa7f47vYtPx*sKY1EkCa@%Q71mhxtQwMgUt)7QDbHMkWg103vU2RO*sWcy%V zL=efh-+sHB!XXMUbrY?m*aOMfLz!ut*?{Y`^=u`jEntw)-cK-?=Ix%l%1<50Zg@l^ zl*JDYW3*DP7ZBl*LW(>VRh(bHW<-m2$t$mJkQ;8iNtWBxebd5)0+!g`m~^R~VJ}*` zNN!qulPp-YK$hLQOm19wqby#w*v_;U+VgyoEL^z2f%9&h4;TdKst3qXRkxRC@F0^E z-!PC^JLol9tVhS0-0KU`*13YgO_gV2#8ho?h$kx$P=R#X_sxjgk>&Mr#T8e$Y53-w zZc1^KT*#oPmj_Z9)XAs zf)L9#aXNr#97H6WlU!6YwUg%u_;=lP zm)l3LTD3|(_}~LOzYe=8pL?eQ0|hzQiVmi3FHc$zAXQb5iD1Cd2YfSk-r&Ho3qEIj zBO1F4#ljeAJ-{hA2QraE6cgPLqiPN;iG;dB5(!2mm(IzGTUN-@MN8%Vci)%vcviYj z^A9)_XVZRg&Wssn)Ba>_8Trv@ z$W6gG2f_g);6&gNMD#-xD{!_+lustJwhoyP%`DN~-Q`GNzs+DmR&CpJ$zz|Y4alQ6 zV=K0o%lRvhRy8H@m1ufkM~%C17|v8xHpxLmHfu=8Y)*4~Repjve(*Dt>K z;$u-%+jerEw%7-;g+kG7-zY-{q)cvqwq@iaOI42zRvTQEITy_NSep3po#payeG1Pxo!2qPs0K)Ob;M^I$`+c%Mo1)M<_tyren+~f^bAnw@&g=_vy_P(3 z^7&j-ez5Q9^wOpfogA9^v=M#Si)vMM(<-CgGvvFPs#g1I{de!bXAjT1trdN33ej|I zV0K@hJ)7KOhqCJV5!I|Qh-waG>@n7|PB)Ec$Sq}a7cjEvxT?A%LF&KDVAf*FE){c^ zwz8j1C3@DpS$p!?+;T6naY8FQ-wfSs+iY9wKsXNc9L#k;QoIfWy&XQQUb zHh(Q6+VjAS!QP;a{wHyms=8=aJsu@EPxaeAQ~h?GoWn=;L^+TLJW(gp&vg%~>INxg zNX`^W!!weJ%f0*CC3Qr%+8En<{WO*c1ueRFdxYa)>>^cFTY5CI^vM2swBsNZa@XPnQ;IFPoyW-^z=t`a4^1?>+OcNAR4ErYSE^)$ z8=^f2C6mb`M)-xcSnJJXvo96x1KBti;yqCI?rBsyS*E8Kask(=vj`r^WYdcxJ>8m^ z8ClsV{eH@6JEFbLI>HCy_w>$)@(jN^pUZ8r{ddNAcc*Ja8SvVlYwF2>L{A2FJVL6|hlzps5}P7p=|hJvNN2Lk3i-mD z<#LIi;#6we)|)qCE9Fu-Q?8W%&xrn6HkA&B=q!Pqt;Vz`y44PxF8Zf6(bL7r?O%bck+(liNW>hv;@-p$Rs0h;D+DC#z^hxkGd-yxszx9im&{^j4_s z5Zwx|w?Joy=oUD=6)JxN00960SLYln00006Nklt*;A1WpqK1NA$jG|FnVLLS>U1@PTH6}?yFJ*Ux zUR6GHi=9T|As0F{B!jNk9UT~AP$+a@FUaTp#^wEt zx8I1f+kUCj4G<7|rOr=9D{-5}kh|g)78J|r{%BS!Nj)G|N-TDu%D39~!sGlhEFNK5 zL0V5TduB6AK`JdSDQj9VZI-}fjJD70{<_`yR74>wC>|svAG6o>Lp>}A3kx_hAz)QE zn8N3;)bhUI`jx-rYF|KIOCvowDyq@(H8n1v$?T=g@0`ZzV27}1k-A7lH*aN0K{g+S zZZ=whs!&TgSyVwmNl;l{UOYZIQC3$kH$72wnl(N}VqQ&gYgR>1RwpkuR(qp7L`i3H zczSJ0m9))rczd6ytbUxelb@!1k)cgpYp1ffxW&t?zR6}|Ra$0mVPpVDS2! zkWk1;OUQ?B`BKT5&CSfWw?A{WT)g~4A`@*2LIELXkccOh%Z0)gW#UOzoCiVZNlcV0+ z%Oqm4Sa2#e^J?jK?PsKpn-At&ol`!iXB8Nrprr!Q31P8V91e#?3un|~)@bAhGx3dB z*y~Gdt~|I|^SQ*_TkGW%az?Q9|1oF^f-pL&CNeroJ{`4~G;%(lf3T8>t$L{}BW4KM*V?ler=$3uy4Q*`U$zhqaqa8yiJ$YHzdQ@@L@kH8;O4 zF9O4}8fA6ydIli`1IhpeUb99n=i^0dhllxmxyAq(d=n%Fplq(RY{Y!Q%&V49uagV6 zrkhC)&o@LF(8e2#iZAfB{rw#sj~+dGLgZ0Lzjm0fF@wXP0hC<(XfwXC8gyn}HGFE4 zi?_?+kSpkkC8AxwX@gjlNi$3sIy(CMwGaTE()16@H4x~@V9-ALWu^)wGq0*{e%h#e zcRJ_E2lG|)oxDMsgJ=U7X1TWG5mYd2n43=|o12@HNc4u`{zs=MMEFpS93(9b!QjE> zgW6BZv7XB3J+YQ3O4nz2IzTBkg z_+=&D63T6^+-v-F;p*FUS1j*Zg;@=bY2l3@+N4rVsI@mE!sKMlvtZ=1sn`FvF(${Qai936*4doi44#%vu^kPwx<6P&7$S=wZnvD$rV~zxmW*bIGR>l z!r_KQCpbXD`%N7O>EkSvL8(9y0fdCfWka|^Xohl74y*INWw?U~A?eV{`*D^Je$5zW-gWwnf2i(Jq4EW^W#e&135;EJN z3yFj~A)qnZXrXZg3Z2d2@XtD{O~Y?UG@65zxNr5N`+FTY8p5j^gablzz&*gRavY%i zDlVL(w^zdK6G$Oqh!ildX_P{njgXL)2&6_kVs9i8vFYr3@oUA1x%fub?Hsho=_&>!{4JuA z@T=pGsgn9{3(XCyEgRF_;}woBxCfs^-I+sw*3Tsz?UG*Rgj7#OUV;fwy}dfTc&qF+ z$?~$z?_T7o>0phTrGIPdU?%K?mwY^xG~O-5T3X^&)o(Z$EK`MJ&7Y+z9KDj>34w5V zK*Ig%>uWb|T>Ji~2?bjhu_-FnB^JJ1wmG~nf0NggPkJ{{-L99nRs4;L0FAyd-+YdcdS5vd}& zJ2z@C{k|o9O{wQbY+_m^MjedHw>ZclF#;;l?nN^*C+_%W_9Ionm#f3+csdS!{DEVq z!qH1}^umm&um9Lcyma+@r9!1M0z@iSL<0U<-?MHc(f&vH@A>o5%+hTB)$x^&6L^#1 zL^`vSM7$y#J*Rf{WOL^*5TLjfj&=z+gxCMXZ5wYG^>)hGCIyK-;CDF}hdNDiE$t?m z`Sj9)?;|Be1gykTCeyr&xC;){?VXki$8jKg$}!g7%eF=C{CNxU(v_d|R)<)I>|&&{ zE|2pWj~Le@$XVJP!^{jgXnhQUgYkv~RmoU|gDJ8_zW$3>Q$t9E zLMc+D*zI?voJWHU3tgc-(Z-YUomtnblk>N&np>@_G)E83G58zD$!{FU=Q`W9`oI1m z*YygALTnU^jk7M-;^NcpQPeg7d6aHxSPHmTmd-zJ-K04hrg`Ik;CQk2hNG7$(%EiY z{5!SY&|4`Z0dcxqsm13!cnN)$&osn#4&5(aebac}4)S0%J(F%oo^tSL4*2QLi$jX4 za4?y@Y)W_i@7=ZjD}~((mACGgZ1ynVj;|n-oTp(WXI5t5Ag?(X1}!-Dc3#*B#{l6FvQ^(){m)yx zowaxD4x>1qX({gCAL?dsETrZ5%P;Ay>u}j!wfRl`c}A?}L_D2{5spbXdN;$2%3^bG zXD2}G0}X~=AzSxN-P`4(uZ23j*po>Vj}ZLOdNB_JZxeLT8jXI^kGTy*mfws(slZb|=!S`#sxqkCjhAW(bJGy0UiV98*X- zo`{8~3CB|yRwwC-t}N}GoO1NO3kRgmzD9}N&6(zBPzV7Tu7KDhYYz9);q~*YP2%Zv ztXMq=j>+*J9x<#Z;Mmc@Kj@PzTcrEu?TbBl`=MX#UQH*Tb_3)$)3OHEDC5rYxq;L+ zEX5P~LeCI5o`Qpenu2gdRP3?dcjthH0b+E==BLNI87vy)37kC>aN7X#;@m)LZsROf z%vX=o3V~rE@@_6I?W{@IBg|fKh!pR}QB&(UAt2G+9v%T1HEU@Qn_hQFiO&zD4hm`T z6b7LX;_0NsdIXL`9UO$&3l2Rv-Yu9rUQkABY_0;rQX6QHw9TkHbckQ9T|YmC#Pcqq zkmr+wFf4fb!Oqf&O(+_JLcl@6&)-R|_)qL^>*^e@q7k*GA0UqsI`Mgez(dlB23HBS zB2h>;!$cIa^CF_^ZFNPv5di@*YM^VzOvEa- zMfAoOj@t7{>WMT_2x9N4Li(YQ!(~b@!t91{T)*_LKk|zIIN+XLjq-SK5rD}1p^=Wb z&26;l#LkmDjptHj{SZ&Ic#6}5L?Jv9vM?z-S&oPlBG?xCpg-bV`>vL?@WSJCl^L)s z0%95_E~2f{>Gd{;)OGmvg>%`^s7fbRJp~@A76)+(G2>V|v$m|#D-{p>_lvsoGaLDg^Ge> zoC@BJes%J#a2}J8`RZvarLUDM02pX-7`-2O-Nty$>woHrGQvP zcItY~yDKDnauRaELx#pHlP+2z+A7K|whP7X!#n5V>zB!K%G1Qa370f_Iiyc;ux7Jc zVOdryb%xeW&nwi?9?{tuYfFVx?} zhW@vZ+f|8ll`BkEe~^Un_F<3?7s0IDsu0;#XJ`uEjGa`4T&3vX)2Fnn)7vSVaP?WY z*r*p9*FKnKUA%egZn8W#H;1j`y;wXxv*Bsxbn)oi#jy~O7dDErTG5PFy}Mi1{!rBt z^yKDyo=%R_XAuK($Yw|Xy5DXT>y6Uc4`f-FYHvL#N7d@+^!?J&Q7RZqrWW4J0f)or z_UnY-yyK?p<<(f&=L&-Z$(1bHk^22$raxNrIF0C{%CcF+^Y`CDo||i3Bpq3oA0PQ} zY+-h(Wof~cFmXD`;_)fSFFVTtYF2E;@O*jUownO=(j2+nq34rBw1&;#c(SxN>2Ses7u%P0qU=WO z-aGV89?%@qL(iW--EmwHSwc%Mf(3mVhQ4=Bc#LCGU zK&*#4g;eG(C01{}Rlux$&=B@{@-zpX(k+Bz^TnRJ$usNz5DI}=HATy*#V~Bjvs%W0 zkQBN&K+QX2>HLN#b}&p9j|ql_bfBJWzNpGtrS3yYH0zm~N|lqh8{Z~@tQUNqViFwB z$KP;t?97;|T?oBIA@287$i-XL(Q0R^=ql!8>$~Ih(VWb~_^ZBH+Etv59rSCluxGXq z6cZA0$IdYUS)=s%?bhIwbGm%H{;fEi#pF0R;OLc_7mmJU=*|(tQeYMr@IxW*;~u-w zCTFfx)v!^8o+w%+&xg9m%y=*F4X5+2(857SKbH37W*P)DnMX?}fmx}0ff5I|Injd8 z+Zw(5_arsoXbL%Vt>7Sz&Y(+0G)b5EjIbY`E`nuQm%pliuND36Xl}98oNU=>nI_sG zny2G?CO;L9!+Zfk2Kri!dXxsi_RNcAzgVBmO5M|#2~0b^O&O5 z;dlg&mGl9pfGR-`4x@3w{vJskzhb+*xZm2GSdFcd4&3u+&j-8Iq=QL7a`BMUouxsr zLusKw;Oz(4b1)h`3o>IXDTvD+Z*%%?rt*4Ac%ga-Rz)HT!`zR$8hS5!J#lBq=*VD6AB%2$0z#(y9om_b zHMiKkAr+U%hO8m#@+H%^FaG5(d5|mw|G@DJ;aHk$^7|cZEe0;9D`uBDfB%>Y3n(LU;jN?c;`hEbN7jY9@B`PdQpsPFFZtO>=b9 z9K>#SmZBwau!X%s30v$~KD%LE0!Jw~UoJQ03&gHSfbUXkpB%(r%oxZ$qB)pO*e}K& zkC4j7hb`j_l1Z>Q!iSmdyeP`1V&bXPHcM^dsQCv8Um>T%_;nLiExlmk1E~hydrv68JFHYj~bJ6YT^@a_1+w6iE z>uy%cwlHVrO?KkG7ve9>u0NA*G0O5znQxjkYBnQHA5?i0*a`pR`J=jN(OE=5gub7n zlaIJd&uP;tJb)+a-E9$eB}N4LJ4-bLDa=96RM0XPC78nUmv%tlxvZZ(AVX zmbn*<*gD3tm@O7%?N6eV$Hto$|Jz$y^0bBWvZ(?G_be=(2L4{&nfb#4^2Hyko1CFA z@+{#Pf6eh|>BZdc8;*7bQaT*uJvdNdmG{H3X_{klyc6ahZCIcj=vN7aqcfU2dzP_J}O^?$YgXm7n=Qpx;yGSHN zDA7rTc7;^DV8`CEOu?o6+12Xc;!}EYWY{n~jGOl!Suj5pTIUdwQMgG%sQi25s=ZfT z3WZ`->qE3%(HtE#M>Of8Y`O{uTlzkZeiIzYxpER$%?U>(ee`Rl1C&T4;xf5hXk5is zkjmQO##*RRD0KLmtl7&g&aA$PTy^)V_r0Nw z=IW<(n(eOCB}jwmxn{o~tNu*FA(UE|&+O<(wcFujbiI%Yzvkd!P-|$;qUz!HO2)9; z-pf{q6iTUaAv-%OZj-WAc8A}!?+ZQaVPREQYvA)|meNjH&NUUBXCb{C_@f$490~q( zmHNV!{pp-HxKZ_nqcWG;MEY0-hJe1WPD`x$H1xLUR+fs4uj;zP&U7X~h-0H`jwXGSb#252=lblYJ!iG~RW6MF`PL%-&@apA1>E;y3WWN9&n zsR!X0>KYyHx5RW-C7Ve&2HM$u7~zO?0UKqVwL1dRHYHodw)(R{pKpJ1+kix37}jj1 zXPi^G0-a-ZGC-o?8xAtTrp$wSoYa13tzPuHVud#xtP00crd*^bU2lbhD^M;wu3bFy zox9yqQ#2V~EE0~V&z_BUsiBV;rI;XV41{BZ%VaAk8)X;$^c5Pi0V{EL3EL6yhrGdO z&pC43U6J$6nVD6Yt2pJHr$IP;`IfvT7;U+C`EGK$;0qNOnrM!v#I8(aNlrU6m&{X? z1RUhL4_7H(rmi)fc}IV@&=g%S1#>jVb8yghMd`uO-a7&gZZEUX9?{u7n zkhKXEsEKTWS+DPC|32ASh61*h;$GhdY>TwLSV)TUt-V}RI+iPx7QMmPMzZJW^T`T_ zncThV--@m-P%3cHi|1UW0PEUk<+yNrx48sAcjm&sgw{nQ{4m~Y zA}S*M>G~U$*VNn)XQ2|QUdfh3Y<8nB7<@K5tj+AqnK^D&Z->j}g>i)aPhvGpGhMI$Hks1nQxa-xod1O7}VS0 z@M<%(O>>O%X!iieLE^X}YyFKwAPDHrtdE*{1m5*G978B~p^v!*O3xc)pgzwt4w zOQz(uMzoRR*29_DPl_4%LK$Kbr!|GG7W;DC6}W`OhyoZ zqHkXDqb&;#5ip3C6X{h5u=9&L@$4;=`So0;HRLID@}Q8(9>gntV%e?`j?o<0AG0s3 z{`vw-%?-GTh>*}IeeLbIAxR0XGLK8q1w zJeEmC?Goe^wx}&{m8C{G#Ih^~z7~Bv@;nFaDMr&QjEZ3N!lvk`DD~SAP2T4kGI1o+VLmI!G^Q;!V!D6%@B2_%C5OAPxZYJ9)_%1k&P3x$Hy z3~b*Gl7PjBgD51zzCPy5cI;j0RD|QLNE8B^Q!nrk@Le)FcH}-OXjXgN( z?GBuc*Q6j3%O3ytzdt`#wo9bqS(&(Ou%Ptin2BY<+xdoQ*bWR8s?c6wKu9gBG{QaL z=I^%!Hxu+z?vQ7mezYCxI6fT2%7tN#{W>s$K9=rn4{8^$f2YS9ij} z9t8+_a0TWKTE1c11q>mI1!O>)V}J>DSn1&Yeo_~2aA)1_P&2EG(be5)8rGWUmno5C z3>?3rZfD!xet383>O+uN^|GITmI^0iPMNIQ3>V>`tilB7;DE1*y4Z;6Bg8joX=k$g zluAFc$A$M^OH6wmcB9)B?PNeOAC!+C!mw0*D6q!**b3XbM8ulfYeuWxYF9oMu-lB{ zwjjrBq0hMh0f*cYwo@=Fl({d7lS_axAy6P%)%~aDeHD_3868kal7nUkgJsfc%}D8X zi3l7_!Xdo+ZaL}7Lxp|TuCOZGaHG4`V&;&vj$D4Q!k3$y?K%|~5trtWjEVY)I&62Y zU;GezqCTtL?e@%d62XyBK6mK1;>yGTI7AAE?VUS)^*4S}*eSczC~aHpghBvflB4&y zCpVP+lnM!uV6^vgMST-WrQK$~_932;S>LcKhEcndqhv}3!_pcO0XroWNq%LD`j83F z(jlw8@sl3!PMf$|ZGz`9IH(tAW;BK<+CAvT(5``+0UTDF>Z_|CWGU`eI%E_n^qx5u zVkFkkp^!CKyX^$LN~&)5ukANM^y!k38D>P zTyOk97w=lcdWBxUY9UYPGh_Z|s>;sVxXfRBiK7d3XKsaGk+y|)EtbwMMmNW7w#=os z4CO#THNuqi0z)Yfz-oa}|DmqqB=S;)!f2gWlV|i{XeKW&%wjl!Hx0|}ipaIIs{P{k zvZ+>dx2K>n?oA%LXlC zrCw>QGMLN;Idax86ym4Afyj$gc;?x}Rk19$ZLxr3u*YO!>{d0*p(VPeB4Z+@9r3pI z0uslEliHIa8f!{X+EC%>Fsx!_QHMkYg(!7rzTaE>lgv3iY9YnjQ0@zX^^G`FbvuUs$G4!Q&U6b*J;fk0EcybEQQmQ z^^BWdHCKY66dVNW*@Kfj3uk=aJ)14KY&Iki$yhJODIfkS%heMPTtuLVcl6VZ4NGBh zG1WSk2slu+%-*R#+Yb#o7pFVn=se!jLAAQ46tZTE9?FJxNjp=*wUZ694=q8##X6GtB!;;6fyJKg>)rP8T-d)on`$U@N{ zT55hgF@i%Niqjl&6BKfE#4~JdINU}zb))7i9N#SZT6a0%z>|V;^Ct{^QDixiO#+k;ny%pq7y=MWb{UPTq zRWrcR#T)7w?HWqC9h9954yc4YCR4F&$4@;6kYkw?+dX>8vD&;{NQHa=dCHO-(qXT! z=)e5#%LXOye)Tq8@s9V5GKNw{2Ld@Ncc8QnU;>n-P)>h%ssE;`jXY9tLKP|O7WNkb zvVSzB=4&!lK3~Fh<()a2e5LVj7Nf$^IkafBS2&oJXXuISWcBKNGy;srZ5VxIbKx*< z966so8#M9dbBW+oJpW_OJ94!C#xcn09H%)(M#kt_Ej*pAAS+AfA@u+u0}UlR67b}5 zLGM(E2H~sI;i>fvJNfzaA~+c6nJ|9OF#-;H9L1gR+s?PY;VRbmXpZz|m>LVoq2iit{ln^0c87 z>Ca^D*T;<<`e$KUQ>n=wtiWMMDdtVKy#3A2s}BfAbvFY81RjsmIe6q2sn~rZW5huu z=v6cIie)yAA?G8Pt{v+uAZ*>?+N{$zH5Gi$Gs`g^F~mcQ(R}UOP9U4AQx%R*4zGKZ z1r7x`#s~*8A!ez4n4KdPo|oKMo2^V`MpTi*h&2Q!c}{luFdECmrqn;jHCg^0hgUCTPXGiQOv1qyIBa*|=;tT(KOMIbmB=QTP{~V&)jQ=J?5y-5 zv>BJToV;H1kKwARP|7t-cku{E7q({h-T%X3kBF{cI!}-b-yiD9m`tW*3b`*2ZA2ya z878u(|D57PH|~3A$&=9!s+{}LT;1RPO{abADCV27hXal-hK1E#s&MoXjxqT8 z0G3N4T(tDhSFT#uekLFS_I;j>B-ZVa&%0O_4&y%J72)V39KAG$@ejK==amv|4%)URCi=KSENjpz zVIgc0KN;r7W8PA1xH#UNpHGq6BRYpDz2gVr7`qwJKHr~r#p~5(|->K>7ki+HWhOP8!DAYRNbnkNG zrPo?)#GhttZA$q<`HiNh&uI<@rs*^X>cl>p1N!LIOO5o--A9odb{pIs6)*zkh^jrp zwHuAzr)r`S3@{BQQ@X(wO0CaNKe&DC^36K@xzgJ|G_5zKydl@ZYAbHM(iN{69JF1D zFjG6#NAHAGANXh-*xHQGio5Vy#2q7{mdcV=#|y7m&|SIbfuy+reSuel!layB(>7ko1uxrW#<|-QKCi zE)81q05M?N)VaPI^Z2|zXDWAev>5W@Ggls02Vqu|%_$i&15IC{xB zN~ZsKc15J_yMPoR2*DuX;-Y{8Q}?MO7XJyZ7|F4?H_Bq{w#JHik24hVg+fkGK3`l- zP7l#nCU8~FEH{ttrf_5Ilw*W+e%MNx;$!NA-gULYOX$eeH$jYBA_9qbe^mQK3le%U zlmV1NxL7PM6pP{2CiZN%(Gm+wOw)xxXe=yW;@{!d;$(^ zi0UJ40I85*TQZ`uT4|jOlWjM62~^T``iCU3@`$BiK#UL4&qn6U)zc}X5*(PFBNq`4 zCO8~AF1GqUvNS#vQa}HJgy`gmg*%>n%_#{OPGu1PCq+BJDuovB$%>GggTPqcg@2ziDi^UP~J#o{S_FU_nk(C2jgO1iarGgt``YxNG3FibfS;VRSoDQ5uw}YyU)QbPoS98K!MM1Ga%&YL|by~Ug1_N zFxcFYu@Qj)4uWnXN_eT!reb5u@)HOO6}?Ud5_%WT%Vh+{UTJagG@o9+{Cm z#fAQBm}-+j0~e1@?+1-~5pp1wwF$>qJK0=PN)-Vi8&@+vjnqCAQ5A;=ia^;X;v%O- zB0{6sn|ivfHfU%Vh(QAgjU_r=NcqILA;u;r4<$JI5VyzJa1VP#_}fzWr*IQC)doK~ zU2dNMiUcS+kytwAB%=cC+-z+bwze#z(e3I|D&!U8de{hJF4RGDv`e_`2?5$ALhLns zLb>uCS0|%MPEly5|23i_4~g-H7LWD|_wTpfzhBxrcBA0nzxu4LB=3!eBhV>h7=r3HoK3whl)+MUOzR7 zLZ>{9!fmH0A{1Xp4G2cqg#rKsYUm^DZvt@OwL>Pxm?Pj42pkci2y2#~T57I-H$kx< zp9U`?GAViM5ns8&F(M>i00KG?0E2ucqm2*(Ful#jCCk*G9^^lD4+Sa}!Z1)k@`mMi zq67p482DV;2EnKF7ihxa+AFC_J_nN8@1-K09_CfCuQxEz00@Z4$kzZ7fP#pWCB?WbS53~AwSL)jRKZGm!I+R z21F*X+bA}-fWr{}!pKzk1{Q z?~n_DqpS$Y@!x%a?Fx3n|7CKq{_6E>-#>i#9g&CMe}C=z)!K`2jDM$Gy4YA>U*A~Y xc;ONl{=HK3yU)JJ7x^Mzhes1ouLVyA`)!#ih7IvEo)dXo2FcMS{D#!}T8i zxqG?C+nL>Ob!H!Sb{{qzsw{(pNsb8s0C41FA!`3n{~w#8qx_SIp4O>jD5^ z5&SnJ0n#%`0RZHCsG^4Szo6$=53rQFwjNZ^RPB?Al9`o~q2Wh;9dQkHA(Jm+1_nYd zj!*|nMXN7RQ$salZ53@*X^YRs(z3!Ta#A8U`GvY-(U- zZ{Z&16c}gj?5u65rlzf=s4A_lC8?nNL0XpA+1e_^KOizZsx-H>zPw_6YJPKW{do0g zcVT;Jbb6|{dttC|rn|bU+O4k0wX^nnPs5L2#ox-ZU5|Gj4mR(0*KW2DS8I#s%k#$z z(+6`CJ7YtuL%s7|ZDXxXea*i*>nh<@KP##V3KOFvB7*}m64Nr{!{ffWd3|-(*D+94 z(fBB@CM7NRQ4t~z`5-LL$Nzz!i=U5!o0Wx)qw<4-Gk@ea7FP8q z{p^lTuL@1d3yJ&g7wYHZ=jh^U;^b&#Yol*!uICkz;pzWV^L6KRZ(G6S7SBQ z5Xaj~NBgVi=eyU_1JlPFXKQo2OA||bh@+L6jrFj+gox-zAt9L$f)Fll zQ8rdSkdTyT4ev|1;I!IW^MV-&RvI@n^!t z_4~@d>3{yJrmPM?u~{#Be)$jmkM0Tvm1?s%+Fe+#6j44yA^kc;p_vH)AgPgqNN9L0 z6fRhN)zD5qKHFJ-(2%25Ko7$BNSkU3?4n2d7>50(>1|P4i?8U0O)Zifj8+m~5>GD7 zw)0rnmvQNn`)t-;1GkWztSl`$7wAs_c0-Ls08_~*VvM~S{xj&vZR~MhzoPWTb56`# z({~CUdELmZ}2S= zrg(o7j?d8vuZ-+o*v|`gX;cSN62D5KQNraIunZT2sN8_~PpW0VCAW<_JJenD{ zfpBpMzJJa2wpib{-5`#Ov-)Te{`bvfW8&g;o6W(9 zsh^A@B$Mwi*IpIn{Evhe#bnpQSpa9Z5*Y~2F>A+)qXY!9}pc|P5$IVPQVHbpq4YXCi$f!g`j~X zwi5Z&NCj1`)XQN?-1P5L>!0x2U9IkS`By2B#%M>hhJNfJBQ1uK0PRwk$hO|T)h5KU zuOYm}FnjLtC**ew2e6yLcF-1g7TH%K>WrRR`M4Pm^8^4mkr0VaU_h4DMTqj4n)o~XuuJ+8%W2P(w6NWYp|fg^slysV$n<+Kxe_J*I3Bj`FC=@WnfB>h7d zm0?j)t%iaf*vJe561Nj$YFp){G#{ z&iI}~e8tW>ehi@j5F@kW@kbFb7#*VnNa4+1Y_Fsmg{Ou4oZY>qOq9+){kTY zcH4t>!&_vFu`%SLW?qA-s`)wO6huVzCJ!fm!agf8S#bH+M@l)ZPph}0-ZytEh-QI2 zYp|xYr>?GN@5J&(Uo@Gf?pe>TBjHOiH6o`?l#8D({$Kfv;fzg%EJ>sVHu~m(_*wJF z59jnm9*s?#0&+iaFq-2`B`VeVF5aG57MjW#xTP{N)LzL4UMgi+S){#x48Lz}ZGC*a ztUKzXbyrjnAte*CzDy0xYZy^TCq2sXcepR0)0D}3bGYJ7bANTVcym18>@r*|`-0X7 ziC+2TefYh!JLy7-8fWFECpX|`n0m~Ry`*G_chD>`<~u)W0x}Z1cp<{Kd`72G`v;^K z7&y!pGOeJtd4>!OxMcv{^Oz&s>lQ3RQ3ojhUdq^E?4h%r;xz9MBqycc0nq_?g&IxZ zyg$tQJb12jy2%ngK~3kCr+z>_n*&3ic;*N-uP;ILt5 zfBO^mY_fIPD|fq#tI3|=0@FKMgC83^cpCdeGlpQj_|FX59A_ua1!|D3NMqer;)P07 zssMB(c<)?d-@9`Vq<{Q^7F*%v`fsbE%@HY)DDBxxRfdU4^N7w72LNNv&u7Ppx^r7z2iLB+T%wQxj zGndB?Eh(clNHYdre%7Ph6md~#mQxwgb@@@RPE*a?0$;Py#F+*x`AONZ!z69GzU+Mb zQ~gxhrR>3nOES~l02jzFDVgv9o}9m2eN{@f`o5A#1+PQLjhied%=ZXrC3Q$4$7fQb zbtwzp&zHL5=dNxWUs8GpS<9=)CoScvFDSlt_I{k9{SN z!)SnuJR@Pe(vXS3kPuwF=Ak<9)*~2J zAdO=`^pz15UHhIx`Z9|s+N*47_`7{o(mAXw5#9FRNfnZI5<(h?XR@2$RbX!~L)mws zn-Js{^;H?W-s;PKbRAD^02%@dXu6qbmDgYJ%0#>{0(1thmIypX4pvb2k5hcwc~auo zS$?^qAY`Lp$(4gKNw7u>@|y!tBk9Nrf)9>=o;Bn{;XU!P3Tgk=i_?t?^S)s&XtE;Q zNIl5JGPctq!MX{D-+t`kSnOzv21HB^qUBg7O2J=shBz9?swUI0fySKLVytHA+1<+& zF*_GPdC}jvPmkZO*p@v`omahO)zv{-iZ6s7yjGNjLcBaH+S+)6<_&GC*vlbH@~V=V zB%77a*hn>(3jMmRL=tqNdPus9fw#o5&toq{_cJYbT=J#mh7Go9VKRw9wpqt=LnZEz zuWqAM(tAdSWQ#@Bq`A~By>p{ZsK^~XPXji9IuSju=FJE@2Gn5rVgnRp9vfJTa?aSC zPM`!1uR~mXLp&u5j9hFz3E;px7*-@Wms)^&8)9TYm8#E+^-DT9ZrQ!PncHSUc2Rjj zlfW+z9rii5wYhT2RT`g$pgPrO{Y@<(0^pUJP!|rB5uKorqo9>TrK#i`XO$FPG~K+j zZI&BEH;&AAE*Gc5f2Ct!Sq9jf(weY{9^-TS4mk0;XAovUMx8gwV1i*gMAXpyZD5CC z1uu0Nt*T7Hoh^UmrW;xb&0?bAlrp{|t|O$6ZgozoCb1#Jwqk#km*oa!aVQRhb?0Q3 z0d9E5ns8tMd^DcyX^Fhtx3R{Q4X>N|`pMT=Ftjjf<%2Q8laZT%8>gg?mxZVFj~`5~ zp|Uqn%C}fST|(&W)QH3mrg;VoC}yhmlxcdmARm`AE`Dki`}qAX6!Snz1og9VuJ1T{ z(C@f+JYSizXEfS+SPH4|Ajm)roQnHl^}ntgCt7;AhLs$(v#-$@mpe{iTpv*Zf8AOk z|944jkabu-~(3 z4r9ap+z>r4EUzGLkJ~}|GJQPPSWA4MRHyJx#TTdqbNty;06rE{ZX#^CJdS(UPS^SfzE9JT=Ogei+vZ1FE$f#~h2L zFc^2yPOp5Kxn8@#I7#CP>gguagwuTUslm||0N_ixkC5tn-wejgYlLIB||Km z)y$Uh_RAUtXwQwVWWxvEmcl_}Y>B4dY@TF);;$d*G|DA$~Ob zl!C!I2KJnDR2Eo3*9x5I^<})|9qe~NJjq~Ggz=x9xn`fC_GGW$6|fn?)zdJ?{%Kai zVj$Yr0WXIR;aqUnq(hl)L*PhqAgC)ZDLLdFsrgZ<<}2K17L9iF)zh})s*^~=@Fams z2j7Ek1;F+Vj~82_!fvCer@rH|H&Pm9X0i*{3-EBFxE(?*NPB8c(&BCsy}dCosYsnIqO zUpx$-#h40dk{spA_|RnlDvv)rHZzCSSjXCKQ1^ybJ_mvh%==T>Pdp*&Hcu>%{APBD zO-kx>#j1N^T6fJD;qp#{?=BRtjPQ znHCNOPvkjRv?sXpF;c_ z7=0eYmE?gkQ7#6V36%zltV8-EB#MDXk9mTlTr==dbH%hYO)LCcQhW-0+-%sDDRbhK zl^ToYvRgVh%yxKT{-$%Yk@u*v=W5G>&* zb0le1DT%`PV4-s2c_LugELHPJ_XOMkKq=%^x5||ZAWyG7Kkr6oMgh;>`m`r=pu^NI z&Bq_y*7k4(Z%9m9=%7}N$e6KM0Oi2ER2LWi9z2T+7ImsN7ZN!J+S9%Y9fjmbbR89N zxZN6vf4LDVIrRJCm4Lu!H|-o74&5AKS!GuMp|Qy6FTITVHnDKLFycZ?Uo#0e?@cDv z=UUljSHs5PoY!GYl=xfdQB@PNQXCtm=_$=JewAuJSPCRkO=38 z==>m{wS-@vY-RvS!6mxEZ5_Dkq#4g0YK0!jwb18@tkiom8)rfkihE6-$h3!D@OZZ;7xrVao(_2Y zROloE>8bD;lEeA>cD8=h?LP0BzwP=W1GVG)G2tZhW{LK@(FPpkYn3k(OSOS$NB-O6 zr0v`eO#JdPV3XH^uwucQZ(LrzpN#|G*JR^?ts(ZH^iF%~B0(`eJ{9w&<6fpa% zjO^?Itah`egVDU+>=R;0{v3J`wR}8?0t54HaoxBdH;sx|2a#tm zRJycdV~0)G<|yjugcMVN6)m1ZLGALrT|s$yoHE!V(cWNH?4vG`1`F&te59L($i=`6uH0KDBL#^?wr#mj^p^Ro^ zbR!*0lvr2QLCo~|#CuVXZxN0~3qZw{mFuBdco_DEdJP1>BdTebW)IGeQdvA?`=tQ$<37#_Av5R8nksF~4ePt4 zOR00O{P_g#JsCcHAP+6zmljuDFP9>iV_mNQRsTm;>N5)EU;8x~80xh)u5EVz$@TkO zR7(FPZfop2jH{eF=A9^RRrD68)|&fnzo(=Vdb=~Pg-4$hjiOym<=BN|d|cqtoY!~^ zJp;hEk~=w3YLPQWL*^mjyv+VK0n}p1%E&(A!9o_>&gu0Ug$i#WBUzsL;kNB9d;U!m zJ(B+i<8ilUI;fy6g<*oB1E;}*B`YIeLphZLoS z>+xGnX%tRqFH)kY)4vgHuZjk3B?oJi(XJ)z|usx6OaomTxmb46xTcV=$BIOhnulxRYESOX>6wN=7=H zgch&0fUW2-gDu~&$PAu>V#7FaDgNl&5ih;>E5-?)&fG&2JPvM4c=M*x?`-P z9*u@GL)AO;Xj<9&@!yhv!LptG8SWqSFC0{&X;xJ(WX3p0dJ6@yg}tg6$!A=yFV0!) z!}Y2v?J`9}^0P4ciEHOaUia9;AeR`>x^)*{e5ZSDS9-rp)ekf@)rNgqobA&MGHbld z>OT*cK^4_9n@DSG#tP3w~r3@Y+I#J)hn-<#aS#UvR|G=Ep-|SU$ zQ|(e^RHEJ@o6uh(FMpUO{Vzn8YeO^c$PogI~&Zji8w^$n&F1*_2$_k2qL0fxKX14Hv^n{~rxWUM^N zO6hSt@R{VshS!9I)|!zyl|`c)duQ%P-4{8^`prD>CdZk&l7xRXON}SB)6>&tX$dPU zaCT2GiO$6gfsVI=8^^kjN}GWMh>b#-PfYs>X=&<|<8;-<)vL{W;p#sX)6x?KD^zlp zGM7aBy=|bUSt{0NjXV|yUvJ!wjL`R%8O3ReWjs7?QXVr3ycz#+7=yRLd&9f_E$?GDI!YUgp0}@dgj>1t zi*s{}7b7`a=Sy5ts?oecMErM_{^~v6q+XEh9~}EUo?Y;o2a9lZzjXHY<@i1m{0{6; sUd<+A@U<3s#0Q2c%tZP>dB0H0wP=^_IXNl+dt(6Pq?I9+lE#7m1s~K~cmMzZ diff --git a/static/icons/stakewise/rewards.png b/static/icons/stakewise/rewards.png deleted file mode 100644 index b4785396e155f06b1731922b6dd62375df89536c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5957 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 T`YR8n00000NkvXXu0mjf3EuDd diff --git a/static/icons/stakewise/sparks.png b/static/icons/stakewise/sparks.png deleted file mode 100644 index 79162981e0acc059d7eec8961491cca35205d83b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8109 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 diff --git a/static/icons/tokens/ethereum.png b/static/icons/tokens/ethereum.png deleted file mode 100644 index 3d925e3306b1ad133b6bee0932e4786c09c6e2af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 522 zcmV+l0`>igP)3J#(j&-pQzf6nA3TN%VTlFTx-BC)Gtf`000$q zQchC<1qc!!F-cW%g@l)*yTQoW=<)gb>R)Du0003&Nklco3`M;lO2s20 zH2VKPdWM#ZrDEb!^VUw*xVK66SpBYgaj(BshTQ^23v)%Uy0aEwxB(~!n3fC(>j1i_ zPQL-*L}0QWFg2-vumJ!`>1R3t(9r}XU?J>;Jpv%`o%jR{LwDi`;KS%_4#0ryLI_Ld&*PjRkDjcu}$#IxD3(xCNF2P;9m`_*4u zv+BJSC*D6#UZ3amwz55fqXoUUu|2*s!|($NR^kES7om8V`%Friba6|K0j>4`Imx&O zG6SfMXaKJ!rFTc#RC&HkSAbFJgSXSd=<}6{_3OUAKzXxn2m5@4*&oF M07*qoM6N<$f*(-m@&Et; diff --git a/static/icons/tokens/ethereum.svg b/static/icons/tokens/ethereum.svg deleted file mode 100644 index 46d0eb79..00000000 --- a/static/icons/tokens/ethereum.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/static/icons/tokens/osETH.svg b/static/icons/tokens/osETH.svg deleted file mode 100644 index aa71c24d..00000000 --- a/static/icons/tokens/osETH.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file