diff --git a/contracts/deploy/holesky/003_deposit_to_native_strategy.js b/contracts/deploy/holesky/003_deposit_to_native_strategy.js new file mode 100644 index 0000000000..b4c5768ed3 --- /dev/null +++ b/contracts/deploy/holesky/003_deposit_to_native_strategy.js @@ -0,0 +1,69 @@ +const hre = require("hardhat"); +const { utils, BigNumber } = require("ethers"); +const { + withConfirmation, +} = require("../../utils/deploy"); + +const mainExport = async () => { + console.log("Running 003 deployment on Holesky..."); + const { governorAddr } = await getNamedAccounts(); + const sGovernor = await ethers.provider.getSigner(governorAddr); + const { + getAssetAddresses, + } = require("../../test/helpers.js"); + + + const strategyProxy = await ethers.getContract("NativeStakingSSVStrategyProxy"); + const strategy = await ethers.getContractAt("NativeStakingSSVStrategy", strategyProxy.address); + const cOETHVaultProxy = await ethers.getContract("OETHVaultProxy"); + const cOETHVault = await ethers.getContractAt("OETHVault", cOETHVaultProxy.address); + const assetAddresses = await getAssetAddresses(); + + await withConfirmation( + cOETHVault + .connect(sGovernor) + .depositToStrategy( + strategyProxy.address, // strategy address + [assetAddresses.WETH], // assets + [utils.parseEther("32")], // amounts + ) + ); + + await withConfirmation( + strategy + .registerSsvValidator( + "0xa023587296851ae359280e2bff7ac8ec840b9192dddc9a8b817fd55d23b0034b4a4856d897d3aec58d99c6f6a9ba6032", //pubkey + [119,139,230,252], // operatorIds + // sharesData + "0x8a19414d6eaaadb19f3e5362de068bac9bf1cd1561a0c592772d3dcbf29a3dd515e1ab1cbddca45262d5c8bc89cac22307a561f00246e251c40bcfc7152cb6b848d1d4399bdf22005214aba83c8ac507a1003922d84f110d75565fb498802b568c3c4f626206855906e32335d22cddec651fe14fb9db68470ce2ed90b7272b75aef523cd2e0ee350f5f2ebbc737edd45a9afe797cd1a3836ea968f1b428868b238aa6d0784d9042b7065f401986131ac66d574c16a359438e9b9fb8230422f98a3cfd2b89ac20cda7f7b324b26829f27304c5f03e3dd36028a804e8d2241406e6f8d8b3de92749d2b30bf99bb3f26cad97f655d0aac104c0b412f18357350393b9bbeb5f14bbf57464207e38c7e9721823ec459b0a6584a5eabe110a9bdeacff1fafd25981f0b1443e8327323ff34b78f4d04eb4158c3ffe2efe289a83cf23388af467aa782dd5b81994935b2925c1c61a9bd3b3dd4781c03a4473b035f019d72eaa28feceef3b564d5d3659c78f54edf8274d997f5f505f32ec17a72a6b53903f5cbd0e17824cfd94c7fb957fbd19f448e20c4fc81e6708ebb49f666c844199253de6d8d4776b0d9645a95b4ea1bd12a80be5b14532749c5c4d9d6c8cdbbef05fdab373ddff1ed59642152661c4d3e47af55ed7bec280d8a3782ad94682b47d7135cf56681716380aa3d95e8915f692db482caddf70ccb48273681e0516d54ac0d85cd19e9db98a89b94683181fcfea87df3b30377b5f37acb3c386de9db2b571675036d420259ea0496acd2821541b72c8d1636b86c79acfaa46fe3021414e8291916778f6a37c86f02b43ccd4b8a5e9a02dea5552413629b2e835320d7c84fad8be64971f3ee24ea94ae95cbde50706e9b5149473fdfe2fc425d553a4219810cda5eb10263fe42bf2cc83060e26e9bf56b1da4b6d70e471a9705cf77f91f98282b10697e3a028d42fca9d4b494b36481443c247dece2a3bda8e5bbaef6524a9527b3f51f26cbf46e5310fc03d29d6cf6cd6685ec3367ca210f1f39d9c050fb7581f1de234959817383566d528afe44c3ff45f43c115679f2b4d93687a8c7a653b1478126b5eb933fa52a52b37bee47ee88d378fe553899f7e7ea7dd4af67b188d43934ab004709b9715753301bcb9c20c1a3d933d793dc73ba6736c164d9c8fbba393763145bd3250256acf17b0706aade1356084ef18a91807449fb56a3a11f85a804d8e20e6d3486e5e30da4a8f83fb15d3651ddcd58d7c7a0569ccaab38c978f5357d44529bfb88bf778dcc9222c3c6b6f03f72727eec267420264edefd8d68451424e5e077eb8cc5718365911f9b23ed7f87bbc84b4b38037eec25615b0bddbaa2047f6a84ccea6dad0b3c222d4e618603c02f0d63733b76113d97c682f16147da0de37f9021b9a55163eeca2668ec1332146b7adf58639505bd186d8876c4b6a95658bbc3a65b61aa9671c9c9bc75c3bc9b809680f51864dd4eeb43ab99564719f782c3d595c03b64af22b62b115efe1eb93e09cf67c0b4162429ab074cee64e072581d6b9d6120d9b01ad0bcfe5f6d9fed8a79fdc03d33104049202e8cc11bef16e4825352988496522e92ca870121c5b02b6148e40477ef48a80e5edd257e6264a5c7b849d060d06a5be079fb7a546859d1cc90c8688d8e7d5b4b1b5fa742870745abf908962ce81dda9ba97060d1925117143f64aec125c0fef93ad33af0be63e4c0617d1afe4f148e218a4b312aa33c193e92931bac3ad55e967bd847a2a23eea22d1e8ca25c86385b079dd18a7984ede1e24c435fd066fe5f890027fc37006348b570ece5eb67dcb6b3656c64f9dfa8d36ab5c17ce5ea00e459", + BigNumber.from("1801362952000000000"), + [0,0,0,true,0] // cluster + ) + ); + + console.log("Validator registered"); + + await withConfirmation( + strategy + .stakeEth([ + [ + "0xa023587296851ae359280e2bff7ac8ec840b9192dddc9a8b817fd55d23b0034b4a4856d897d3aec58d99c6f6a9ba6032", //pubkey + // signature + "0xb59d5d21392a1d03d6bd40558ab3eb9fad1e8ced748b4e9fcf379c304317d9e2189c0b8f321ecc3a927c8cfef9f92046017f92456ae0a16e564d6db15541e468ab696cb0409d4e5b30cefaf835a739533f4810c677837e9d9ecbfb5bbb534944", + // deposit data root + "0xfc3f49c04eaf3437df6f6af7e66eb777858c60eddafec63b3a530e32d33724be", + ] + ]) + ); + console.log("Validator registered"); + + console.log("Running 003 deployment done"); + return true; +}; + +mainExport.id = "003_deposit_to_native_strategy"; +mainExport.tags = []; +mainExport.dependencies = []; +mainExport.skip = () => false; + +module.exports = mainExport; \ No newline at end of file diff --git a/contracts/deployments/holesky/.migrations.json b/contracts/deployments/holesky/.migrations.json index 85c94b3f6e..d45ad24364 100644 --- a/contracts/deployments/holesky/.migrations.json +++ b/contracts/deployments/holesky/.migrations.json @@ -1,4 +1,5 @@ { "001_core": 1714168010, - "002_upgrade_strategy": 1714222265 + "002_upgrade_strategy": 1714233842, + "003_deposit_to_native_strategy": 1714307581 } \ No newline at end of file diff --git a/contracts/deployments/holesky/solcInputs/ea04f995873c65a558ae1a10ef794843.json b/contracts/deployments/holesky/solcInputs/ea04f995873c65a558ae1a10ef794843.json new file mode 100644 index 0000000000..2b21e8f479 --- /dev/null +++ b/contracts/deployments/holesky/solcInputs/ea04f995873c65a558ae1a10ef794843.json @@ -0,0 +1,122 @@ +{ + "language": "Solidity", + "sources": { + "@openzeppelin/contracts/security/Pausable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which allows children to implement an emergency stop\n * mechanism that can be triggered by an authorized account.\n *\n * This module is used through inheritance. It will make available the\n * modifiers `whenNotPaused` and `whenPaused`, which can be applied to\n * the functions of your contract. Note that they will not be pausable by\n * simply including this module, only once the modifiers are put in place.\n */\nabstract contract Pausable is Context {\n /**\n * @dev Emitted when the pause is triggered by `account`.\n */\n event Paused(address account);\n\n /**\n * @dev Emitted when the pause is lifted by `account`.\n */\n event Unpaused(address account);\n\n bool private _paused;\n\n /**\n * @dev Initializes the contract in unpaused state.\n */\n constructor() {\n _paused = false;\n }\n\n /**\n * @dev Returns true if the contract is paused, and false otherwise.\n */\n function paused() public view virtual returns (bool) {\n return _paused;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is not paused.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n modifier whenNotPaused() {\n require(!paused(), \"Pausable: paused\");\n _;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is paused.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n modifier whenPaused() {\n require(paused(), \"Pausable: not paused\");\n _;\n }\n\n /**\n * @dev Triggers stopped state.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n function _pause() internal virtual whenNotPaused {\n _paused = true;\n emit Paused(_msgSender());\n }\n\n /**\n * @dev Returns to normal state.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n function _unpause() internal virtual whenPaused {\n _paused = false;\n emit Unpaused(_msgSender());\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address recipient, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) external returns (bool);\n\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\nimport \"../../../utils/Address.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n using Address for address;\n\n function safeTransfer(\n IERC20 token,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\n }\n\n function safeTransferFrom(\n IERC20 token,\n address from,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\n }\n\n /**\n * @dev Deprecated. This function has issues similar to the ones found in\n * {IERC20-approve}, and its usage is discouraged.\n *\n * Whenever possible, use {safeIncreaseAllowance} and\n * {safeDecreaseAllowance} instead.\n */\n function safeApprove(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n // safeApprove should only be called when setting an initial allowance,\n // or when resetting it to zero. To increase and decrease it, use\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\n require(\n (value == 0) || (token.allowance(address(this), spender) == 0),\n \"SafeERC20: approve from non-zero to non-zero allowance\"\n );\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\n }\n\n function safeIncreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n uint256 newAllowance = token.allowance(address(this), spender) + value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n\n function safeDecreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n unchecked {\n uint256 oldAllowance = token.allowance(address(this), spender);\n require(oldAllowance >= value, \"SafeERC20: decreased allowance below zero\");\n uint256 newAllowance = oldAllowance - value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n */\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that\n // the target address contains contract code and also asserts for success in the low-level call.\n\n bytes memory returndata = address(token).functionCall(data, \"SafeERC20: low-level call failed\");\n if (returndata.length > 0) {\n // Return data is optional\n require(abi.decode(returndata, (bool)), \"SafeERC20: ERC20 operation did not succeed\");\n }\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Address.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Address.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize, which returns 0 for contracts in\n // construction, since the code is only stored at the end of the\n // constructor execution.\n\n uint256 size;\n assembly {\n size := extcodesize(account)\n }\n return size > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCall(target, data, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n require(isContract(target), \"Address: call to non-contract\");\n\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n require(isContract(target), \"Address: static call to non-contract\");\n\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(isContract(target), \"Address: delegate call to non-contract\");\n\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n" + }, + "@openzeppelin/contracts/utils/math/SafeCast.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/math/SafeCast.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow\n * checks.\n *\n * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can\n * easily result in undesired exploitation or bugs, since developers usually\n * assume that overflows raise errors. `SafeCast` restores this intuition by\n * reverting the transaction when such an operation overflows.\n *\n * Using this library instead of the unchecked operations eliminates an entire\n * class of bugs, so it's recommended to use it always.\n *\n * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing\n * all math on `uint256` and `int256` and then downcasting.\n */\nlibrary SafeCast {\n /**\n * @dev Returns the downcasted uint224 from uint256, reverting on\n * overflow (when the input is greater than largest uint224).\n *\n * Counterpart to Solidity's `uint224` operator.\n *\n * Requirements:\n *\n * - input must fit into 224 bits\n */\n function toUint224(uint256 value) internal pure returns (uint224) {\n require(value <= type(uint224).max, \"SafeCast: value doesn't fit in 224 bits\");\n return uint224(value);\n }\n\n /**\n * @dev Returns the downcasted uint128 from uint256, reverting on\n * overflow (when the input is greater than largest uint128).\n *\n * Counterpart to Solidity's `uint128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n */\n function toUint128(uint256 value) internal pure returns (uint128) {\n require(value <= type(uint128).max, \"SafeCast: value doesn't fit in 128 bits\");\n return uint128(value);\n }\n\n /**\n * @dev Returns the downcasted uint96 from uint256, reverting on\n * overflow (when the input is greater than largest uint96).\n *\n * Counterpart to Solidity's `uint96` operator.\n *\n * Requirements:\n *\n * - input must fit into 96 bits\n */\n function toUint96(uint256 value) internal pure returns (uint96) {\n require(value <= type(uint96).max, \"SafeCast: value doesn't fit in 96 bits\");\n return uint96(value);\n }\n\n /**\n * @dev Returns the downcasted uint64 from uint256, reverting on\n * overflow (when the input is greater than largest uint64).\n *\n * Counterpart to Solidity's `uint64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n */\n function toUint64(uint256 value) internal pure returns (uint64) {\n require(value <= type(uint64).max, \"SafeCast: value doesn't fit in 64 bits\");\n return uint64(value);\n }\n\n /**\n * @dev Returns the downcasted uint32 from uint256, reverting on\n * overflow (when the input is greater than largest uint32).\n *\n * Counterpart to Solidity's `uint32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n */\n function toUint32(uint256 value) internal pure returns (uint32) {\n require(value <= type(uint32).max, \"SafeCast: value doesn't fit in 32 bits\");\n return uint32(value);\n }\n\n /**\n * @dev Returns the downcasted uint16 from uint256, reverting on\n * overflow (when the input is greater than largest uint16).\n *\n * Counterpart to Solidity's `uint16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n */\n function toUint16(uint256 value) internal pure returns (uint16) {\n require(value <= type(uint16).max, \"SafeCast: value doesn't fit in 16 bits\");\n return uint16(value);\n }\n\n /**\n * @dev Returns the downcasted uint8 from uint256, reverting on\n * overflow (when the input is greater than largest uint8).\n *\n * Counterpart to Solidity's `uint8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits.\n */\n function toUint8(uint256 value) internal pure returns (uint8) {\n require(value <= type(uint8).max, \"SafeCast: value doesn't fit in 8 bits\");\n return uint8(value);\n }\n\n /**\n * @dev Converts a signed int256 into an unsigned uint256.\n *\n * Requirements:\n *\n * - input must be greater than or equal to 0.\n */\n function toUint256(int256 value) internal pure returns (uint256) {\n require(value >= 0, \"SafeCast: value must be positive\");\n return uint256(value);\n }\n\n /**\n * @dev Returns the downcasted int128 from int256, reverting on\n * overflow (when the input is less than smallest int128 or\n * greater than largest int128).\n *\n * Counterpart to Solidity's `int128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n *\n * _Available since v3.1._\n */\n function toInt128(int256 value) internal pure returns (int128) {\n require(value >= type(int128).min && value <= type(int128).max, \"SafeCast: value doesn't fit in 128 bits\");\n return int128(value);\n }\n\n /**\n * @dev Returns the downcasted int64 from int256, reverting on\n * overflow (when the input is less than smallest int64 or\n * greater than largest int64).\n *\n * Counterpart to Solidity's `int64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n *\n * _Available since v3.1._\n */\n function toInt64(int256 value) internal pure returns (int64) {\n require(value >= type(int64).min && value <= type(int64).max, \"SafeCast: value doesn't fit in 64 bits\");\n return int64(value);\n }\n\n /**\n * @dev Returns the downcasted int32 from int256, reverting on\n * overflow (when the input is less than smallest int32 or\n * greater than largest int32).\n *\n * Counterpart to Solidity's `int32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n *\n * _Available since v3.1._\n */\n function toInt32(int256 value) internal pure returns (int32) {\n require(value >= type(int32).min && value <= type(int32).max, \"SafeCast: value doesn't fit in 32 bits\");\n return int32(value);\n }\n\n /**\n * @dev Returns the downcasted int16 from int256, reverting on\n * overflow (when the input is less than smallest int16 or\n * greater than largest int16).\n *\n * Counterpart to Solidity's `int16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n *\n * _Available since v3.1._\n */\n function toInt16(int256 value) internal pure returns (int16) {\n require(value >= type(int16).min && value <= type(int16).max, \"SafeCast: value doesn't fit in 16 bits\");\n return int16(value);\n }\n\n /**\n * @dev Returns the downcasted int8 from int256, reverting on\n * overflow (when the input is less than smallest int8 or\n * greater than largest int8).\n *\n * Counterpart to Solidity's `int8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits.\n *\n * _Available since v3.1._\n */\n function toInt8(int256 value) internal pure returns (int8) {\n require(value >= type(int8).min && value <= type(int8).max, \"SafeCast: value doesn't fit in 8 bits\");\n return int8(value);\n }\n\n /**\n * @dev Converts an unsigned uint256 into a signed int256.\n *\n * Requirements:\n *\n * - input must be less than or equal to maxInt256.\n */\n function toInt256(uint256 value) internal pure returns (int256) {\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\n require(value <= uint256(type(int256).max), \"SafeCast: value doesn't fit in an int256\");\n return int256(value);\n }\n}\n" + }, + "@openzeppelin/contracts/utils/math/SafeMath.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/math/SafeMath.sol)\n\npragma solidity ^0.8.0;\n\n// CAUTION\n// This version of SafeMath should only be used with Solidity 0.8 or later,\n// because it relies on the compiler's built in overflow checks.\n\n/**\n * @dev Wrappers over Solidity's arithmetic operations.\n *\n * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler\n * now has built in overflow checking.\n */\nlibrary SafeMath {\n /**\n * @dev Returns the addition of two unsigned integers, with an overflow flag.\n *\n * _Available since v3.4._\n */\n function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n unchecked {\n uint256 c = a + b;\n if (c < a) return (false, 0);\n return (true, c);\n }\n }\n\n /**\n * @dev Returns the substraction of two unsigned integers, with an overflow flag.\n *\n * _Available since v3.4._\n */\n function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n unchecked {\n if (b > a) return (false, 0);\n return (true, a - b);\n }\n }\n\n /**\n * @dev Returns the multiplication of two unsigned integers, with an overflow flag.\n *\n * _Available since v3.4._\n */\n function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n unchecked {\n // Gas optimization: this is cheaper than requiring 'a' not being zero, but the\n // benefit is lost if 'b' is also tested.\n // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522\n if (a == 0) return (true, 0);\n uint256 c = a * b;\n if (c / a != b) return (false, 0);\n return (true, c);\n }\n }\n\n /**\n * @dev Returns the division of two unsigned integers, with a division by zero flag.\n *\n * _Available since v3.4._\n */\n function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n unchecked {\n if (b == 0) return (false, 0);\n return (true, a / b);\n }\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.\n *\n * _Available since v3.4._\n */\n function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n unchecked {\n if (b == 0) return (false, 0);\n return (true, a % b);\n }\n }\n\n /**\n * @dev Returns the addition of two unsigned integers, reverting on\n * overflow.\n *\n * Counterpart to Solidity's `+` operator.\n *\n * Requirements:\n *\n * - Addition cannot overflow.\n */\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\n return a + b;\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, reverting on\n * overflow (when the result is negative).\n *\n * Counterpart to Solidity's `-` operator.\n *\n * Requirements:\n *\n * - Subtraction cannot overflow.\n */\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\n return a - b;\n }\n\n /**\n * @dev Returns the multiplication of two unsigned integers, reverting on\n * overflow.\n *\n * Counterpart to Solidity's `*` operator.\n *\n * Requirements:\n *\n * - Multiplication cannot overflow.\n */\n function mul(uint256 a, uint256 b) internal pure returns (uint256) {\n return a * b;\n }\n\n /**\n * @dev Returns the integer division of two unsigned integers, reverting on\n * division by zero. The result is rounded towards zero.\n *\n * Counterpart to Solidity's `/` operator.\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function div(uint256 a, uint256 b) internal pure returns (uint256) {\n return a / b;\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\n * reverting when dividing by zero.\n *\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\n * opcode (which leaves remaining gas untouched) while Solidity uses an\n * invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function mod(uint256 a, uint256 b) internal pure returns (uint256) {\n return a % b;\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, reverting with custom message on\n * overflow (when the result is negative).\n *\n * CAUTION: This function is deprecated because it requires allocating memory for the error\n * message unnecessarily. For custom revert reasons use {trySub}.\n *\n * Counterpart to Solidity's `-` operator.\n *\n * Requirements:\n *\n * - Subtraction cannot overflow.\n */\n function sub(\n uint256 a,\n uint256 b,\n string memory errorMessage\n ) internal pure returns (uint256) {\n unchecked {\n require(b <= a, errorMessage);\n return a - b;\n }\n }\n\n /**\n * @dev Returns the integer division of two unsigned integers, reverting with custom message on\n * division by zero. The result is rounded towards zero.\n *\n * Counterpart to Solidity's `/` operator. Note: this function uses a\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\n * uses an invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function div(\n uint256 a,\n uint256 b,\n string memory errorMessage\n ) internal pure returns (uint256) {\n unchecked {\n require(b > 0, errorMessage);\n return a / b;\n }\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\n * reverting with custom message when dividing by zero.\n *\n * CAUTION: This function is deprecated because it requires allocating memory for the error\n * message unnecessarily. For custom revert reasons use {tryMod}.\n *\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\n * opcode (which leaves remaining gas untouched) while Solidity uses an\n * invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function mod(\n uint256 a,\n uint256 b,\n string memory errorMessage\n ) internal pure returns (uint256) {\n unchecked {\n require(b > 0, errorMessage);\n return a % b;\n }\n }\n}\n" + }, + "contracts/governance/Governable.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n/**\n * @title Base for contracts that are managed by the Origin Protocol's Governor.\n * @dev Copy of the openzeppelin Ownable.sol contract with nomenclature change\n * from owner to governor and renounce methods removed. Does not use\n * Context.sol like Ownable.sol does for simplification.\n * @author Origin Protocol Inc\n */\ncontract Governable {\n // Storage position of the owner and pendingOwner of the contract\n // keccak256(\"OUSD.governor\");\n bytes32 private constant governorPosition =\n 0x7bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4a;\n\n // keccak256(\"OUSD.pending.governor\");\n bytes32 private constant pendingGovernorPosition =\n 0x44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db;\n\n // keccak256(\"OUSD.reentry.status\");\n bytes32 private constant reentryStatusPosition =\n 0x53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac4535;\n\n // See OpenZeppelin ReentrancyGuard implementation\n uint256 constant _NOT_ENTERED = 1;\n uint256 constant _ENTERED = 2;\n\n event PendingGovernorshipTransfer(\n address indexed previousGovernor,\n address indexed newGovernor\n );\n\n event GovernorshipTransferred(\n address indexed previousGovernor,\n address indexed newGovernor\n );\n\n /**\n * @dev Initializes the contract setting the deployer as the initial Governor.\n */\n constructor() {\n _setGovernor(msg.sender);\n emit GovernorshipTransferred(address(0), _governor());\n }\n\n /**\n * @notice Returns the address of the current Governor.\n */\n function governor() public view returns (address) {\n return _governor();\n }\n\n /**\n * @dev Returns the address of the current Governor.\n */\n function _governor() internal view returns (address governorOut) {\n bytes32 position = governorPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n governorOut := sload(position)\n }\n }\n\n /**\n * @dev Returns the address of the pending Governor.\n */\n function _pendingGovernor()\n internal\n view\n returns (address pendingGovernor)\n {\n bytes32 position = pendingGovernorPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n pendingGovernor := sload(position)\n }\n }\n\n /**\n * @dev Throws if called by any account other than the Governor.\n */\n modifier onlyGovernor() {\n require(isGovernor(), \"Caller is not the Governor\");\n _;\n }\n\n /**\n * @notice Returns true if the caller is the current Governor.\n */\n function isGovernor() public view returns (bool) {\n return msg.sender == _governor();\n }\n\n function _setGovernor(address newGovernor) internal {\n bytes32 position = governorPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, newGovernor)\n }\n }\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n * Calling a `nonReentrant` function from another `nonReentrant`\n * function is not supported. It is possible to prevent this from happening\n * by making the `nonReentrant` function external, and make it call a\n * `private` function that does the actual work.\n */\n modifier nonReentrant() {\n bytes32 position = reentryStatusPosition;\n uint256 _reentry_status;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n _reentry_status := sload(position)\n }\n\n // On the first call to nonReentrant, _notEntered will be true\n require(_reentry_status != _ENTERED, \"Reentrant call\");\n\n // Any calls to nonReentrant after this point will fail\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, _ENTERED)\n }\n\n _;\n\n // By storing the original value once again, a refund is triggered (see\n // https://eips.ethereum.org/EIPS/eip-2200)\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, _NOT_ENTERED)\n }\n }\n\n function _setPendingGovernor(address newGovernor) internal {\n bytes32 position = pendingGovernorPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, newGovernor)\n }\n }\n\n /**\n * @notice Transfers Governance of the contract to a new account (`newGovernor`).\n * Can only be called by the current Governor. Must be claimed for this to complete\n * @param _newGovernor Address of the new Governor\n */\n function transferGovernance(address _newGovernor) external onlyGovernor {\n _setPendingGovernor(_newGovernor);\n emit PendingGovernorshipTransfer(_governor(), _newGovernor);\n }\n\n /**\n * @notice Claim Governance of the contract to a new account (`newGovernor`).\n * Can only be called by the new Governor.\n */\n function claimGovernance() external {\n require(\n msg.sender == _pendingGovernor(),\n \"Only the pending Governor can complete the claim\"\n );\n _changeGovernor(msg.sender);\n }\n\n /**\n * @dev Change Governance of the contract to a new account (`newGovernor`).\n * @param _newGovernor Address of the new Governor\n */\n function _changeGovernor(address _newGovernor) internal {\n require(_newGovernor != address(0), \"New Governor is address(0)\");\n emit GovernorshipTransferred(_governor(), _newGovernor);\n _setGovernor(_newGovernor);\n }\n}\n" + }, + "contracts/interfaces/chainlink/AggregatorV3Interface.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface AggregatorV3Interface {\n function decimals() external view returns (uint8);\n\n function description() external view returns (string memory);\n\n function version() external view returns (uint256);\n\n // getRoundData and latestRoundData should both raise \"No data present\"\n // if they do not have data to report, instead of returning unset values\n // which could be misinterpreted as actual reported values.\n function getRoundData(uint80 _roundId)\n external\n view\n returns (\n uint80 roundId,\n int256 answer,\n uint256 startedAt,\n uint256 updatedAt,\n uint80 answeredInRound\n );\n\n function latestRoundData()\n external\n view\n returns (\n uint80 roundId,\n int256 answer,\n uint256 startedAt,\n uint256 updatedAt,\n uint80 answeredInRound\n );\n}\n" + }, + "contracts/interfaces/IBasicToken.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IBasicToken {\n function symbol() external view returns (string memory);\n\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/IDepositContract.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IDepositContract {\n /// @notice A processed deposit event.\n event DepositEvent(\n bytes pubkey,\n bytes withdrawal_credentials,\n bytes amount,\n bytes signature,\n bytes index\n );\n\n /// @notice Submit a Phase 0 DepositData object.\n /// @param pubkey A BLS12-381 public key.\n /// @param withdrawal_credentials Commitment to a public key for withdrawals.\n /// @param signature A BLS12-381 signature.\n /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object.\n /// Used as a protection against malformed input.\n function deposit(\n bytes calldata pubkey,\n bytes calldata withdrawal_credentials,\n bytes calldata signature,\n bytes32 deposit_data_root\n ) external payable;\n\n /// @notice Query the current deposit root hash.\n /// @return The deposit root hash.\n function get_deposit_root() external view returns (bytes32);\n\n /// @notice Query the current deposit count.\n /// @return The deposit count encoded as a little endian 64-bit number.\n function get_deposit_count() external view returns (bytes memory);\n}\n" + }, + "contracts/interfaces/IOracle.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IOracle {\n /**\n * @dev returns the asset price in USD, in 8 decimal digits.\n *\n * The version of priceProvider deployed for OETH has 18 decimal digits\n */\n function price(address asset) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/ISSVNetwork.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nstruct Cluster {\n uint32 validatorCount;\n uint64 networkFeeIndex;\n uint64 index;\n bool active;\n uint256 balance;\n}\n\ninterface ISSVNetwork {\n error ApprovalNotWithinTimeframe();\n error CallerNotOwner();\n error CallerNotWhitelisted();\n error ClusterAlreadyEnabled();\n error ClusterDoesNotExists();\n error ClusterIsLiquidated();\n error ClusterNotLiquidatable();\n error ExceedValidatorLimit();\n error FeeExceedsIncreaseLimit();\n error FeeIncreaseNotAllowed();\n error FeeTooHigh();\n error FeeTooLow();\n error IncorrectClusterState();\n error IncorrectValidatorState();\n error InsufficientBalance();\n error InvalidOperatorIdsLength();\n error InvalidPublicKeyLength();\n error MaxValueExceeded();\n error NewBlockPeriodIsBelowMinimum();\n error NoFeeDeclared();\n error NotAuthorized();\n error OperatorAlreadyExists();\n error OperatorDoesNotExist();\n error OperatorsListNotUnique();\n error SameFeeChangeNotAllowed();\n error TargetModuleDoesNotExist();\n error TokenTransferFailed();\n error UnsortedOperatorsList();\n error ValidatorAlreadyExists();\n error ValidatorDoesNotExist();\n\n event AdminChanged(address previousAdmin, address newAdmin);\n event BeaconUpgraded(address indexed beacon);\n event ClusterDeposited(\n address indexed owner,\n uint64[] operatorIds,\n uint256 value,\n Cluster cluster\n );\n event ClusterLiquidated(\n address indexed owner,\n uint64[] operatorIds,\n Cluster cluster\n );\n event ClusterReactivated(\n address indexed owner,\n uint64[] operatorIds,\n Cluster cluster\n );\n event ClusterWithdrawn(\n address indexed owner,\n uint64[] operatorIds,\n uint256 value,\n Cluster cluster\n );\n event DeclareOperatorFeePeriodUpdated(uint64 value);\n event ExecuteOperatorFeePeriodUpdated(uint64 value);\n event FeeRecipientAddressUpdated(\n address indexed owner,\n address recipientAddress\n );\n event Initialized(uint8 version);\n event LiquidationThresholdPeriodUpdated(uint64 value);\n event MinimumLiquidationCollateralUpdated(uint256 value);\n event NetworkEarningsWithdrawn(uint256 value, address recipient);\n event NetworkFeeUpdated(uint256 oldFee, uint256 newFee);\n event OperatorAdded(\n uint64 indexed operatorId,\n address indexed owner,\n bytes publicKey,\n uint256 fee\n );\n event OperatorFeeDeclarationCancelled(\n address indexed owner,\n uint64 indexed operatorId\n );\n event OperatorFeeDeclared(\n address indexed owner,\n uint64 indexed operatorId,\n uint256 blockNumber,\n uint256 fee\n );\n event OperatorFeeExecuted(\n address indexed owner,\n uint64 indexed operatorId,\n uint256 blockNumber,\n uint256 fee\n );\n event OperatorFeeIncreaseLimitUpdated(uint64 value);\n event OperatorMaximumFeeUpdated(uint64 maxFee);\n event OperatorRemoved(uint64 indexed operatorId);\n event OperatorWhitelistUpdated(\n uint64 indexed operatorId,\n address whitelisted\n );\n event OperatorWithdrawn(\n address indexed owner,\n uint64 indexed operatorId,\n uint256 value\n );\n event OwnershipTransferStarted(\n address indexed previousOwner,\n address indexed newOwner\n );\n event OwnershipTransferred(\n address indexed previousOwner,\n address indexed newOwner\n );\n event Upgraded(address indexed implementation);\n event ValidatorAdded(\n address indexed owner,\n uint64[] operatorIds,\n bytes publicKey,\n bytes shares,\n Cluster cluster\n );\n event ValidatorExited(\n address indexed owner,\n uint64[] operatorIds,\n bytes publicKey\n );\n event ValidatorRemoved(\n address indexed owner,\n uint64[] operatorIds,\n bytes publicKey,\n Cluster cluster\n );\n\n fallback() external;\n\n function acceptOwnership() external;\n\n function cancelDeclaredOperatorFee(uint64 operatorId) external;\n\n function declareOperatorFee(uint64 operatorId, uint256 fee) external;\n\n function deposit(\n address clusterOwner,\n uint64[] memory operatorIds,\n uint256 amount,\n Cluster memory cluster\n ) external;\n\n function executeOperatorFee(uint64 operatorId) external;\n\n function exitValidator(bytes memory publicKey, uint64[] memory operatorIds)\n external;\n\n function getVersion() external pure returns (string memory version);\n\n function initialize(\n address token_,\n address ssvOperators_,\n address ssvClusters_,\n address ssvDAO_,\n address ssvViews_,\n uint64 minimumBlocksBeforeLiquidation_,\n uint256 minimumLiquidationCollateral_,\n uint32 validatorsPerOperatorLimit_,\n uint64 declareOperatorFeePeriod_,\n uint64 executeOperatorFeePeriod_,\n uint64 operatorMaxFeeIncrease_\n ) external;\n\n function liquidate(\n address clusterOwner,\n uint64[] memory operatorIds,\n Cluster memory cluster\n ) external;\n\n function owner() external view returns (address);\n\n function pendingOwner() external view returns (address);\n\n function proxiableUUID() external view returns (bytes32);\n\n function reactivate(\n uint64[] memory operatorIds,\n uint256 amount,\n Cluster memory cluster\n ) external;\n\n function reduceOperatorFee(uint64 operatorId, uint256 fee) external;\n\n function registerOperator(bytes memory publicKey, uint256 fee)\n external\n returns (uint64 id);\n\n function registerValidator(\n bytes memory publicKey,\n uint64[] memory operatorIds,\n bytes memory sharesData,\n uint256 amount,\n Cluster memory cluster\n ) external;\n\n function removeOperator(uint64 operatorId) external;\n\n function removeValidator(\n bytes memory publicKey,\n uint64[] memory operatorIds,\n Cluster memory cluster\n ) external;\n\n function renounceOwnership() external;\n\n function setFeeRecipientAddress(address recipientAddress) external;\n\n function setOperatorWhitelist(uint64 operatorId, address whitelisted)\n external;\n\n function transferOwnership(address newOwner) external;\n\n function updateDeclareOperatorFeePeriod(uint64 timeInSeconds) external;\n\n function updateExecuteOperatorFeePeriod(uint64 timeInSeconds) external;\n\n function updateLiquidationThresholdPeriod(uint64 blocks) external;\n\n function updateMaximumOperatorFee(uint64 maxFee) external;\n\n function updateMinimumLiquidationCollateral(uint256 amount) external;\n\n function updateModule(uint8 moduleId, address moduleAddress) external;\n\n function updateNetworkFee(uint256 fee) external;\n\n function updateOperatorFeeIncreaseLimit(uint64 percentage) external;\n\n function upgradeTo(address newImplementation) external;\n\n function upgradeToAndCall(address newImplementation, bytes memory data)\n external\n payable;\n\n function withdraw(\n uint64[] memory operatorIds,\n uint256 amount,\n Cluster memory cluster\n ) external;\n\n function withdrawAllOperatorEarnings(uint64 operatorId) external;\n\n function withdrawNetworkEarnings(uint256 amount) external;\n\n function withdrawOperatorEarnings(uint64 operatorId, uint256 amount)\n external;\n}\n" + }, + "contracts/interfaces/IStrategy.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n/**\n * @title Platform interface to integrate with lending platform like Compound, AAVE etc.\n */\ninterface IStrategy {\n /**\n * @dev Deposit the given asset to platform\n * @param _asset asset address\n * @param _amount Amount to deposit\n */\n function deposit(address _asset, uint256 _amount) external;\n\n /**\n * @dev Deposit the entire balance of all supported assets in the Strategy\n * to the platform\n */\n function depositAll() external;\n\n /**\n * @dev Withdraw given asset from Lending platform\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external;\n\n /**\n * @dev Liquidate all assets in strategy and return them to Vault.\n */\n function withdrawAll() external;\n\n /**\n * @dev Returns the current balance of the given asset.\n */\n function checkBalance(address _asset)\n external\n view\n returns (uint256 balance);\n\n /**\n * @dev Returns bool indicating whether strategy supports asset.\n */\n function supportsAsset(address _asset) external view returns (bool);\n\n /**\n * @dev Collect reward tokens from the Strategy.\n */\n function collectRewardTokens() external;\n\n /**\n * @dev The address array of the reward tokens for the Strategy.\n */\n function getRewardTokenAddresses() external view returns (address[] memory);\n}\n" + }, + "contracts/interfaces/IVault.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport { VaultStorage } from \"../vault/VaultStorage.sol\";\n\ninterface IVault {\n event AssetSupported(address _asset);\n event AssetDefaultStrategyUpdated(address _asset, address _strategy);\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\n event StrategyApproved(address _addr);\n event StrategyRemoved(address _addr);\n event Mint(address _addr, uint256 _value);\n event Redeem(address _addr, uint256 _value);\n event CapitalPaused();\n event CapitalUnpaused();\n event RebasePaused();\n event RebaseUnpaused();\n event VaultBufferUpdated(uint256 _vaultBuffer);\n event RedeemFeeUpdated(uint256 _redeemFeeBps);\n event PriceProviderUpdated(address _priceProvider);\n event AllocateThresholdUpdated(uint256 _threshold);\n event RebaseThresholdUpdated(uint256 _threshold);\n event StrategistUpdated(address _address);\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\n event TrusteeFeeBpsChanged(uint256 _basis);\n event TrusteeAddressChanged(address _address);\n event SwapperChanged(address _address);\n event SwapAllowedUndervalueChanged(uint256 _basis);\n event SwapSlippageChanged(address _asset, uint256 _basis);\n event Swapped(\n address indexed _fromAsset,\n address indexed _toAsset,\n uint256 _fromAssetAmount,\n uint256 _toAssetAmount\n );\n\n // Governable.sol\n function transferGovernance(address _newGovernor) external;\n\n function claimGovernance() external;\n\n function governor() external view returns (address);\n\n // VaultAdmin.sol\n function setPriceProvider(address _priceProvider) external;\n\n function priceProvider() external view returns (address);\n\n function setRedeemFeeBps(uint256 _redeemFeeBps) external;\n\n function redeemFeeBps() external view returns (uint256);\n\n function setVaultBuffer(uint256 _vaultBuffer) external;\n\n function vaultBuffer() external view returns (uint256);\n\n function setAutoAllocateThreshold(uint256 _threshold) external;\n\n function autoAllocateThreshold() external view returns (uint256);\n\n function setRebaseThreshold(uint256 _threshold) external;\n\n function rebaseThreshold() external view returns (uint256);\n\n function setStrategistAddr(address _address) external;\n\n function strategistAddr() external view returns (address);\n\n function setMaxSupplyDiff(uint256 _maxSupplyDiff) external;\n\n function maxSupplyDiff() external view returns (uint256);\n\n function setTrusteeAddress(address _address) external;\n\n function trusteeAddress() external view returns (address);\n\n function setTrusteeFeeBps(uint256 _basis) external;\n\n function trusteeFeeBps() external view returns (uint256);\n\n function ousdMetaStrategy() external view returns (address);\n\n function setSwapper(address _swapperAddr) external;\n\n function setSwapAllowedUndervalue(uint16 _percentageBps) external;\n\n function setOracleSlippage(address _asset, uint16 _allowedOracleSlippageBps)\n external;\n\n function supportAsset(address _asset, uint8 _supportsAsset) external;\n\n function approveStrategy(address _addr) external;\n\n function removeStrategy(address _addr) external;\n\n function setAssetDefaultStrategy(address _asset, address _strategy)\n external;\n\n function assetDefaultStrategies(address _asset)\n external\n view\n returns (address);\n\n function pauseRebase() external;\n\n function unpauseRebase() external;\n\n function rebasePaused() external view returns (bool);\n\n function pauseCapital() external;\n\n function unpauseCapital() external;\n\n function capitalPaused() external view returns (bool);\n\n function transferToken(address _asset, uint256 _amount) external;\n\n function priceUnitMint(address asset) external view returns (uint256);\n\n function priceUnitRedeem(address asset) external view returns (uint256);\n\n function withdrawAllFromStrategy(address _strategyAddr) external;\n\n function withdrawAllFromStrategies() external;\n\n function withdrawFromStrategy(\n address _strategyFromAddress,\n address[] calldata _assets,\n uint256[] calldata _amounts\n ) external;\n\n function depositToStrategy(\n address _strategyToAddress,\n address[] calldata _assets,\n uint256[] calldata _amounts\n ) external;\n\n // VaultCore.sol\n function mint(\n address _asset,\n uint256 _amount,\n uint256 _minimumOusdAmount\n ) external;\n\n function mintForStrategy(uint256 _amount) external;\n\n function redeem(uint256 _amount, uint256 _minimumUnitAmount) external;\n\n function burnForStrategy(uint256 _amount) external;\n\n function redeemAll(uint256 _minimumUnitAmount) external;\n\n function allocate() external;\n\n function rebase() external;\n\n function swapCollateral(\n address fromAsset,\n address toAsset,\n uint256 fromAssetAmount,\n uint256 minToAssetAmount,\n bytes calldata data\n ) external returns (uint256 toAssetAmount);\n\n function totalValue() external view returns (uint256 value);\n\n function checkBalance(address _asset) external view returns (uint256);\n\n function calculateRedeemOutputs(uint256 _amount)\n external\n view\n returns (uint256[] memory);\n\n function getAssetCount() external view returns (uint256);\n\n function getAssetConfig(address _asset)\n external\n view\n returns (VaultStorage.Asset memory config);\n\n function getAllAssets() external view returns (address[] memory);\n\n function getStrategyCount() external view returns (uint256);\n\n function swapper() external view returns (address);\n\n function allowedSwapUndervalue() external view returns (uint256);\n\n function getAllStrategies() external view returns (address[] memory);\n\n function isSupportedAsset(address _asset) external view returns (bool);\n\n function netOusdMintForStrategyThreshold() external view returns (uint256);\n\n function setOusdMetaStrategy(address _ousdMetaStrategy) external;\n\n function setNetOusdMintForStrategyThreshold(uint256 _threshold) external;\n\n function netOusdMintedForStrategy() external view returns (int256);\n\n function weth() external view returns (address);\n\n function cacheWETHAssetIndex() external;\n\n function wethAssetIndex() external view returns (uint256);\n\n function initialize(address, address) external;\n\n function setAdminImpl(address) external;\n}\n" + }, + "contracts/interfaces/IWETH9.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IWETH9 {\n event Approval(address indexed src, address indexed guy, uint256 wad);\n event Deposit(address indexed dst, uint256 wad);\n event Transfer(address indexed src, address indexed dst, uint256 wad);\n event Withdrawal(address indexed src, uint256 wad);\n\n function allowance(address, address) external view returns (uint256);\n\n function approve(address guy, uint256 wad) external returns (bool);\n\n function balanceOf(address) external view returns (uint256);\n\n function decimals() external view returns (uint8);\n\n function deposit() external payable;\n\n function name() external view returns (string memory);\n\n function symbol() external view returns (string memory);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address dst, uint256 wad) external returns (bool);\n\n function transferFrom(\n address src,\n address dst,\n uint256 wad\n ) external returns (bool);\n\n function withdraw(uint256 wad) external;\n}\n" + }, + "contracts/oracle/OETHFixedOracle.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport { OETHOracleRouter } from \"./OETHOracleRouter.sol\";\n\n// @notice Oracle Router that returns 1e18 for all prices\n// used solely for deployment to testnets\ncontract OETHFixedOracle is OETHOracleRouter {\n constructor(address _auraPriceFeed) OETHOracleRouter(_auraPriceFeed) {}\n\n /**\n * @dev The price feed contract to use for a particular asset along with\n * maximum data staleness\n * @param asset address of the asset\n * @return feedAddress address of the price feed for the asset\n * @return maxStaleness maximum acceptable data staleness duration\n */\n function feedMetadata(address asset)\n internal\n view\n virtual\n override\n returns (address feedAddress, uint256 maxStaleness)\n {\n // fixes price for all of the assets\n feedAddress = FIXED_PRICE;\n maxStaleness = 0;\n }\n}\n" + }, + "contracts/oracle/OETHOracleRouter.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"../interfaces/chainlink/AggregatorV3Interface.sol\";\nimport { OracleRouterBase } from \"./OracleRouterBase.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\n\n// @notice Oracle Router that denominates all prices in ETH\ncontract OETHOracleRouter is OracleRouterBase {\n using StableMath for uint256;\n\n address public immutable auraPriceFeed;\n\n constructor(address _auraPriceFeed) {\n auraPriceFeed = _auraPriceFeed;\n }\n\n /**\n * @notice Returns the total price in 18 digit units for a given asset.\n * This implementation does not (!) do range checks as the\n * parent OracleRouter does.\n * @param asset address of the asset\n * @return uint256 unit price for 1 asset unit, in 18 decimal fixed\n */\n function price(address asset)\n external\n view\n virtual\n override\n returns (uint256)\n {\n (address _feed, uint256 maxStaleness) = feedMetadata(asset);\n if (_feed == FIXED_PRICE) {\n return 1e18;\n }\n require(_feed != address(0), \"Asset not available\");\n\n // slither-disable-next-line unused-return\n (, int256 _iprice, , uint256 updatedAt, ) = AggregatorV3Interface(_feed)\n .latestRoundData();\n\n require(\n updatedAt + maxStaleness >= block.timestamp,\n \"Oracle price too old\"\n );\n\n uint8 decimals = getDecimals(_feed);\n uint256 _price = uint256(_iprice).scaleBy(18, decimals);\n return _price;\n }\n\n /**\n * @dev The price feed contract to use for a particular asset along with\n * maximum data staleness\n * @param asset address of the asset\n * @return feedAddress address of the price feed for the asset\n * @return maxStaleness maximum acceptable data staleness duration\n */\n function feedMetadata(address asset)\n internal\n view\n virtual\n override\n returns (address feedAddress, uint256 maxStaleness)\n {\n if (asset == 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) {\n // FIXED_PRICE: WETH/ETH\n feedAddress = FIXED_PRICE;\n maxStaleness = 0;\n } else if (asset == 0x5E8422345238F34275888049021821E8E08CAa1f) {\n // frxETH/ETH\n feedAddress = 0xC58F3385FBc1C8AD2c0C9a061D7c13b141D7A5Df;\n maxStaleness = 18 hours + STALENESS_BUFFER;\n } else if (asset == 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84) {\n // https://data.chain.link/ethereum/mainnet/crypto-eth/steth-eth\n // Chainlink: stETH/ETH\n feedAddress = 0x86392dC19c0b719886221c78AB11eb8Cf5c52812;\n maxStaleness = 1 days + STALENESS_BUFFER;\n } else if (asset == 0xae78736Cd615f374D3085123A210448E74Fc6393) {\n // https://data.chain.link/ethereum/mainnet/crypto-eth/reth-eth\n // Chainlink: rETH/ETH\n feedAddress = 0x536218f9E9Eb48863970252233c8F271f554C2d0;\n maxStaleness = 1 days + STALENESS_BUFFER;\n } else if (asset == 0xD533a949740bb3306d119CC777fa900bA034cd52) {\n // https://data.chain.link/ethereum/mainnet/crypto-eth/crv-eth\n // Chainlink: CRV/ETH\n feedAddress = 0x8a12Be339B0cD1829b91Adc01977caa5E9ac121e;\n maxStaleness = 1 days + STALENESS_BUFFER;\n } else if (asset == 0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B) {\n // https://data.chain.link/ethereum/mainnet/crypto-eth/cvx-eth\n // Chainlink: CVX/ETH\n feedAddress = 0xC9CbF687f43176B302F03f5e58470b77D07c61c6;\n maxStaleness = 1 days + STALENESS_BUFFER;\n } else if (asset == 0xBe9895146f7AF43049ca1c1AE358B0541Ea49704) {\n // https://data.chain.link/ethereum/mainnet/crypto-eth/cbeth-eth\n // Chainlink: cbETH/ETH\n feedAddress = 0xF017fcB346A1885194689bA23Eff2fE6fA5C483b;\n maxStaleness = 1 days + STALENESS_BUFFER;\n } else if (asset == 0xba100000625a3754423978a60c9317c58a424e3D) {\n // https://data.chain.link/ethereum/mainnet/crypto-eth/bal-eth\n // Chainlink: BAL/ETH\n feedAddress = 0xC1438AA3823A6Ba0C159CfA8D98dF5A994bA120b;\n maxStaleness = 1 days + STALENESS_BUFFER;\n } else if (asset == 0xC0c293ce456fF0ED870ADd98a0828Dd4d2903DBF) {\n // AURA/ETH\n feedAddress = auraPriceFeed;\n maxStaleness = 0;\n } else {\n revert(\"Asset not available\");\n }\n }\n}\n" + }, + "contracts/oracle/OracleRouterBase.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"../interfaces/chainlink/AggregatorV3Interface.sol\";\nimport { IOracle } from \"../interfaces/IOracle.sol\";\nimport { Helpers } from \"../utils/Helpers.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\n// @notice Abstract functionality that is shared between various Oracle Routers\nabstract contract OracleRouterBase is IOracle {\n using StableMath for uint256;\n using SafeCast for int256;\n\n uint256 internal constant MIN_DRIFT = 0.7e18;\n uint256 internal constant MAX_DRIFT = 1.3e18;\n address internal constant FIXED_PRICE =\n 0x0000000000000000000000000000000000000001;\n // Maximum allowed staleness buffer above normal Oracle maximum staleness\n uint256 internal constant STALENESS_BUFFER = 1 days;\n mapping(address => uint8) internal decimalsCache;\n\n /**\n * @dev The price feed contract to use for a particular asset along with\n * maximum data staleness\n * @param asset address of the asset\n * @return feedAddress address of the price feed for the asset\n * @return maxStaleness maximum acceptable data staleness duration\n */\n function feedMetadata(address asset)\n internal\n view\n virtual\n returns (address feedAddress, uint256 maxStaleness);\n\n /**\n * @notice Returns the total price in 18 digit unit for a given asset.\n * @param asset address of the asset\n * @return uint256 unit price for 1 asset unit, in 18 decimal fixed\n */\n function price(address asset)\n external\n view\n virtual\n override\n returns (uint256)\n {\n (address _feed, uint256 maxStaleness) = feedMetadata(asset);\n require(_feed != address(0), \"Asset not available\");\n require(_feed != FIXED_PRICE, \"Fixed price feeds not supported\");\n\n // slither-disable-next-line unused-return\n (, int256 _iprice, , uint256 updatedAt, ) = AggregatorV3Interface(_feed)\n .latestRoundData();\n\n require(\n updatedAt + maxStaleness >= block.timestamp,\n \"Oracle price too old\"\n );\n\n uint8 decimals = getDecimals(_feed);\n\n uint256 _price = _iprice.toUint256().scaleBy(18, decimals);\n if (shouldBePegged(asset)) {\n require(_price <= MAX_DRIFT, \"Oracle: Price exceeds max\");\n require(_price >= MIN_DRIFT, \"Oracle: Price under min\");\n }\n return _price;\n }\n\n function getDecimals(address _feed) internal view virtual returns (uint8) {\n uint8 decimals = decimalsCache[_feed];\n require(decimals > 0, \"Oracle: Decimals not cached\");\n return decimals;\n }\n\n /**\n * @notice Before an asset/feed price is fetches for the first time the\n * decimals need to be cached. This is a gas optimization\n * @param asset address of the asset\n * @return uint8 corresponding asset decimals\n */\n function cacheDecimals(address asset) external returns (uint8) {\n (address _feed, ) = feedMetadata(asset);\n require(_feed != address(0), \"Asset not available\");\n require(_feed != FIXED_PRICE, \"Fixed price feeds not supported\");\n\n uint8 decimals = AggregatorV3Interface(_feed).decimals();\n decimalsCache[_feed] = decimals;\n return decimals;\n }\n\n function shouldBePegged(address _asset) internal view returns (bool) {\n string memory symbol = Helpers.getSymbol(_asset);\n bytes32 symbolHash = keccak256(abi.encodePacked(symbol));\n return\n symbolHash == keccak256(abi.encodePacked(\"DAI\")) ||\n symbolHash == keccak256(abi.encodePacked(\"USDC\")) ||\n symbolHash == keccak256(abi.encodePacked(\"USDT\"));\n }\n}\n" + }, + "contracts/strategies/NativeStaking/FeeAccumulator.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport { Address } from \"@openzeppelin/contracts/utils/Address.sol\";\n\nimport { Governable } from \"../../governance/Governable.sol\";\n\n/**\n * @title Fee Accumulator for Native Staking SSV Strategy\n * @notice Receives execution rewards which includes tx fees and\n * MEV rewards like tx priority and tx ordering.\n * It does NOT include swept ETH from beacon chain consensus rewards or full validator withdrawals.\n * @author Origin Protocol Inc\n */\ncontract FeeAccumulator is Governable {\n /// @notice The address of the Native Staking Strategy\n address public immutable STRATEGY;\n\n /**\n * @param _strategy Address of the Native Staking Strategy\n */\n constructor(address _strategy) {\n STRATEGY = _strategy;\n }\n\n /**\n * @notice sends all ETH in this FeeAccumulator contract to the Native Staking Strategy.\n * @return eth The amount of execution rewards that were sent to the Native Staking Strategy\n */\n function collect() external returns (uint256 eth) {\n require(msg.sender == STRATEGY, \"Caller is not the Strategy\");\n\n eth = address(this).balance;\n if (eth > 0) {\n // Send the ETH to the Native Staking Strategy\n Address.sendValue(payable(STRATEGY), eth);\n }\n }\n}\n" + }, + "contracts/strategies/NativeStaking/NativeStakingSSVStrategy.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { ISSVNetwork, Cluster } from \"../../interfaces/ISSVNetwork.sol\";\nimport { IWETH9 } from \"../../interfaces/IWETH9.sol\";\nimport { FeeAccumulator } from \"./FeeAccumulator.sol\";\nimport { ValidatorAccountant } from \"./ValidatorAccountant.sol\";\nimport { Cluster } from \"../../interfaces/ISSVNetwork.sol\";\n\nstruct ValidatorStakeData {\n bytes pubkey;\n bytes signature;\n bytes32 depositDataRoot;\n}\n\n/// @title Native Staking SSV Strategy\n/// @notice Strategy to deploy funds into DVT validators powered by the SSV Network\n/// @author Origin Protocol Inc\ncontract NativeStakingSSVStrategy is\n ValidatorAccountant,\n InitializableAbstractStrategy\n{\n using SafeERC20 for IERC20;\n\n /// @notice SSV ERC20 token that serves as a payment for operating SSV validators\n address public immutable SSV_TOKEN_ADDRESS;\n /// @notice Fee collector address\n /// @dev this address will receive Execution layer rewards - These are rewards earned for\n /// executing transactions on the Ethereum network as part of block proposals. They include\n /// priority fees (fees paid by users for their transactions to be included) and MEV rewards\n /// (rewards for arranging transactions in a way that benefits the validator).\n address public immutable FEE_ACCUMULATOR_ADDRESS;\n\n // For future use\n uint256[50] private __gap;\n\n /// @param _baseConfig Base strategy config with platformAddress (ERC-4626 Vault contract), eg sfrxETH or sDAI,\n /// and vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _ssvToken Address of the Erc20 SSV Token contract\n /// @param _ssvNetwork Address of the SSV Network contract\n /// @param _feeAccumulator Address of the fee accumulator receiving execution layer validator rewards\n /// @param _beaconChainDepositContract Address of the beacon chain deposit contract\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _wethAddress,\n address _ssvToken,\n address _ssvNetwork,\n address _feeAccumulator,\n address _beaconChainDepositContract\n )\n InitializableAbstractStrategy(_baseConfig)\n ValidatorAccountant(\n _wethAddress,\n _baseConfig.vaultAddress,\n _beaconChainDepositContract,\n _ssvNetwork\n )\n {\n SSV_TOKEN_ADDRESS = _ssvToken;\n FEE_ACCUMULATOR_ADDRESS = _feeAccumulator;\n }\n\n /// @notice initialize function, to set up initial internal state\n /// @param _rewardTokenAddresses Address of reward token for platform\n /// @param _assets Addresses of initial supported assets\n /// @param _pTokens Platform Token corresponding addresses\n function initialize(\n address[] memory _rewardTokenAddresses,\n address[] memory _assets,\n address[] memory _pTokens\n ) external onlyGovernor initializer {\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n }\n\n /// @dev Convert accumulated ETH to WETH and send to the Harvester.\n /// Will revert if the strategy is paused for accounting.\n function _collectRewardTokens() internal override whenNotPaused {\n // collect ETH from execution rewards from the fee accumulator\n uint256 executionRewards = FeeAccumulator(FEE_ACCUMULATOR_ADDRESS)\n .collect();\n\n // total ETH rewards to be harvested = execution rewards + consensus rewards\n uint256 ethRewards = executionRewards + consensusRewards;\n\n require(\n address(this).balance >= ethRewards,\n \"insufficient eth balance\"\n );\n\n if (ethRewards > 0) {\n // reset the counter keeping track of beacon chain consensus rewards\n consensusRewards = 0;\n\n // Convert ETH rewards to WETH\n IWETH9(WETH_TOKEN_ADDRESS).deposit{ value: ethRewards }();\n\n emit RewardTokenCollected(\n harvesterAddress,\n WETH_TOKEN_ADDRESS,\n ethRewards\n );\n IERC20(WETH_TOKEN_ADDRESS).safeTransfer(\n harvesterAddress,\n ethRewards\n );\n }\n }\n\n /// @notice Unlike other strategies, this does not deposit assets into the underlying platform.\n /// It just checks the asset is WETH and emits the Deposit event.\n /// To deposit WETH into validators `registerSsvValidator` and `stakeEth` must be used.\n /// Will NOT revert if the strategy is paused from an accounting failure.\n /// @param _asset Address of asset to deposit. Has to be WETH.\n /// @param _amount Amount of assets that were transferred to the strategy by the vault.\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n require(_asset == WETH_TOKEN_ADDRESS, \"Unsupported asset\");\n _deposit(_asset, _amount);\n }\n\n /// @dev Deposit WETH to this strategy so it can later be staked into a validator.\n /// @param _asset Address of WETH\n /// @param _amount Amount of WETH to deposit\n function _deposit(address _asset, uint256 _amount) internal {\n require(_amount > 0, \"Must deposit something\");\n /*\n * We could do a check here that would revert when \"_amount % 32 ether != 0\". With the idea of\n * not allowing deposits that will result in WETH sitting on the strategy after all the possible batches\n * of 32ETH have been staked.\n * But someone could mess with our strategy by sending some WETH to it. And we might want to deposit just\n * enough WETH to add it up to 32 so it can be staked. For that reason the check is left out.\n *\n * WETH sitting on the strategy won't interfere with the accounting since accounting only operates on ETH.\n */\n emit Deposit(_asset, address(0), _amount);\n }\n\n /// @notice Unlike other strategies, this does not deposit assets into the underlying platform.\n /// It just emits the Deposit event.\n /// To deposit WETH into validators `registerSsvValidator` and `stakeEth` must be used.\n /// Will NOT revert if the strategy is paused from an accounting failure.\n function depositAll() external override onlyVault nonReentrant {\n uint256 wethBalance = IERC20(WETH_TOKEN_ADDRESS).balanceOf(\n address(this)\n );\n if (wethBalance > 0) {\n _deposit(WETH_TOKEN_ADDRESS, wethBalance);\n }\n }\n\n /// @notice Withdraw WETH from this contract. Used only if some WETH for is lingering on the contract. That\n /// can happen when:\n /// - the deposit was not a multiple of 32 WETH\n /// - someone sent WETH directly to this contract\n /// Will NOT revert if the strategy is paused from an accounting failure.\n /// @param _recipient Address to receive withdrawn assets\n /// @param _asset WETH to withdraw\n /// @param _amount Amount of WETH to withdraw\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n _withdraw(_recipient, _asset, _amount);\n }\n\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n\n emit Withdrawal(_asset, address(0), _amount);\n IERC20(_asset).safeTransfer(_recipient, _amount);\n }\n\n /// @notice transfer all WETH deposits back to the vault.\n /// This does not withdraw from the validators. That has to be done separately with the\n /// `exitSsvValidator` and `removeSsvValidator` operations.\n /// This does not withdraw any execution rewards from the FeeAccumulator or\n /// consensus rewards in this strategy.\n /// Any ETH in this strategy that was swept from a full validator withdrawal will not be withdrawn.\n /// ETH from full validator withdrawals is sent to the Vault using `doAccounting`.\n /// Will NOT revert if the strategy is paused from an accounting failure.\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 wethBalance = IERC20(WETH_TOKEN_ADDRESS).balanceOf(\n address(this)\n );\n if (wethBalance > 0) {\n _withdraw(vaultAddress, WETH_TOKEN_ADDRESS, wethBalance);\n }\n }\n\n function _abstractSetPToken(address _asset, address) internal override {}\n\n /// @notice Returns the total value of (W)ETH that is staked to the validators\n /// and WETH deposits that are still to be staked.\n /// This does not include ETH from consensus rewards sitting in this strategy\n /// or ETH from MEV rewards in the FeeAccumulator. These rewards are harvested\n /// and sent to the Dripper so will eventually be sent to the Vault as WETH.\n /// @param _asset Address of weth asset\n /// @return balance Total value of (W)ETH\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n require(_asset == WETH_TOKEN_ADDRESS, \"Unsupported asset\");\n\n balance =\n // add the ETH that has been staked in validators\n activeDepositedValidators *\n 32 ether +\n // add the WETH in the strategy from deposits that are still to be staked\n IERC20(WETH_TOKEN_ADDRESS).balanceOf(address(this));\n }\n\n function pause() external onlyStrategist {\n _pause();\n }\n\n /// @notice Returns bool indicating whether asset is supported by strategy.\n /// @param _asset The address of the asset token.\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == WETH_TOKEN_ADDRESS;\n }\n\n /// @notice Approves the SSV Network contract to transfer SSV tokens for deposits\n function safeApproveAllTokens() external override {\n /// @dev Approves the SSV Network contract to transfer SSV tokens for deposits\n IERC20(SSV_TOKEN_ADDRESS).approve(\n SSV_NETWORK_ADDRESS,\n type(uint256).max\n );\n }\n\n /**\n * @notice Only accept ETH from the FeeAccumulator and the WETH contract - required when \n * unwrapping WETH just before staking it to the validator\n * @dev don't want to receive donations from anyone else as this will\n * mess with the accounting of the consensus rewards and validator full withdrawals\n */\n receive() external payable {\n require(\n msg.sender == FEE_ACCUMULATOR_ADDRESS || msg.sender == WETH_TOKEN_ADDRESS,\n \"eth not from allowed contracts\"\n );\n }\n}\n" + }, + "contracts/strategies/NativeStaking/ValidatorAccountant.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport { Pausable } from \"@openzeppelin/contracts/security/Pausable.sol\";\nimport { ValidatorRegistrator } from \"./ValidatorRegistrator.sol\";\nimport { IWETH9 } from \"../../interfaces/IWETH9.sol\";\n\n/// @title Validator Accountant\n/// @notice Attributes the ETH swept from beacon chain validators to this strategy contract\n/// as either full or partial withdrawals. Partial withdrawals being consensus rewards.\n/// Full withdrawals are from exited validators.\n/// @author Origin Protocol Inc\nabstract contract ValidatorAccountant is ValidatorRegistrator {\n /// @notice The maximum amount of ETH that can be staked by a validator\n /// @dev this can change in the future with EIP-7251, Increase the MAX_EFFECTIVE_BALANCE\n uint256 public constant MAX_STAKE = 32 ether;\n\n /// @notice Keeps track of the total consensus rewards swept from the beacon chain\n uint256 public consensusRewards = 0;\n\n /// @notice start of fuse interval\n uint256 public fuseIntervalStart = 0;\n /// @notice end of fuse interval\n uint256 public fuseIntervalEnd = 0;\n /// @notice Governor that can manually correct the accounting\n address public accountingGovernor;\n\n uint256[50] private __gap;\n\n event FuseIntervalUpdated(uint256 start, uint256 end);\n event AccountingFullyWithdrawnValidator(\n uint256 noOfValidators,\n uint256 remainingValidators,\n uint256 wethSentToVault\n );\n event AccountingValidatorSlashed(\n uint256 remainingValidators,\n uint256 wethSentToVault\n );\n event AccountingGovernorChanged(address newAddress);\n event AccountingConsensusRewards(uint256 amount);\n\n event AccountingManuallyFixed(\n uint256 oldActiveDepositedValidators,\n uint256 activeDepositedValidators,\n uint256 oldBeaconChainRewards,\n uint256 beaconChainRewards,\n uint256 ethToWeth,\n uint256 wethToBeSentToVault\n );\n\n /// @dev Throws if called by any account other than the Accounting Governor\n modifier onlyAccountingGovernor() {\n require(\n msg.sender == accountingGovernor,\n \"Caller is not the Accounting Governor\"\n );\n _;\n }\n\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _vaultAddress Address of the Vault\n /// @param _beaconChainDepositContract Address of the beacon chain deposit contract\n /// @param _ssvNetwork Address of the SSV Network contract\n constructor(\n address _wethAddress,\n address _vaultAddress,\n address _beaconChainDepositContract,\n address _ssvNetwork\n )\n ValidatorRegistrator(\n _wethAddress,\n _vaultAddress,\n _beaconChainDepositContract,\n _ssvNetwork\n )\n {}\n\n function setAccountingGovernor(address _address) external onlyGovernor {\n emit AccountingGovernorChanged(_address);\n accountingGovernor = _address;\n }\n\n /// @notice set fuse interval values\n function setFuseInterval(\n uint256 _fuseIntervalStart,\n uint256 _fuseIntervalEnd\n ) external onlyGovernor {\n require(\n _fuseIntervalStart < _fuseIntervalEnd &&\n _fuseIntervalStart < 32 ether &&\n _fuseIntervalEnd < 32 ether &&\n _fuseIntervalEnd - _fuseIntervalStart >= 4 ether,\n \"incorrect fuse interval\"\n );\n\n emit FuseIntervalUpdated(_fuseIntervalStart, _fuseIntervalEnd);\n\n fuseIntervalStart = _fuseIntervalStart;\n fuseIntervalEnd = _fuseIntervalEnd;\n }\n\n /* solhint-disable max-line-length */\n /// This notion page offers a good explanation of how the accounting functions\n /// https://www.notion.so/originprotocol/Limited-simplified-native-staking-accounting-67a217c8420d40678eb943b9da0ee77d\n /// In short, after dividing by 32, if the ETH remaining on the contract falls between 0 and fuseIntervalStart,\n /// the accounting function will treat that ETH as Beacon chain consensus rewards.\n /// On the contrary, if after dividing by 32, the ETH remaining on the contract falls between fuseIntervalEnd and 32,\n /// the accounting function will treat that as a validator slashing.\n /// @notice Perform the accounting attributing beacon chain ETH to either full or partial withdrawals. Returns true when\n /// accounting is valid and fuse isn't \"blown\". Returns false when fuse is blown.\n /// @dev This function could in theory be permission-less but lets allow only the Registrator (Defender Action) to call it\n /// for now.\n /* solhint-enable max-line-length */\n function doAccounting()\n external\n onlyRegistrator\n whenNotPaused\n returns (bool accountingValid)\n {\n if (address(this).balance < consensusRewards) {\n // pause and fail the accounting\n _pause();\n return false;\n }\n\n // Calculate all the new ETH that has been swept to the contract since the last accounting\n uint256 newSweptETH = address(this).balance - consensusRewards;\n accountingValid = true;\n\n // send the ETH that is from fully withdrawn validators to the Vault\n if (newSweptETH >= MAX_STAKE) {\n uint256 fullyWithdrawnValidators = newSweptETH / MAX_STAKE;\n activeDepositedValidators -= fullyWithdrawnValidators;\n\n uint256 wethToVault = MAX_STAKE * fullyWithdrawnValidators;\n IWETH9(WETH_TOKEN_ADDRESS).deposit{ value: wethToVault }();\n IWETH9(WETH_TOKEN_ADDRESS).transfer(VAULT_ADDRESS, wethToVault);\n\n emit AccountingFullyWithdrawnValidator(\n fullyWithdrawnValidators,\n activeDepositedValidators,\n wethToVault\n );\n }\n\n uint256 ethRemaining = address(this).balance - consensusRewards;\n // should be less than a whole validator stake\n require(ethRemaining < 32 ether, \"unexpected accounting\");\n\n // If no Beacon chain consensus rewards swept\n if (ethRemaining == 0) {\n // do nothing\n return accountingValid;\n }\n // Beacon chain consensus rewards swept (partial validator withdrawals)\n else if (ethRemaining < fuseIntervalStart) {\n // solhint-disable-next-line reentrancy\n consensusRewards += ethRemaining;\n emit AccountingConsensusRewards(ethRemaining);\n }\n // Beacon chain consensus rewards swept but also a slashed validator fully exited\n else if (ethRemaining > fuseIntervalEnd) {\n IWETH9(WETH_TOKEN_ADDRESS).deposit{ value: ethRemaining }();\n IWETH9(WETH_TOKEN_ADDRESS).transfer(VAULT_ADDRESS, ethRemaining);\n activeDepositedValidators -= 1;\n\n emit AccountingValidatorSlashed(\n activeDepositedValidators,\n ethRemaining\n );\n }\n // Oh no... Fuse is blown. The governor (Multisig not OGV Governor) needs to set the\n // record straight by manually set the accounting values.\n else {\n // will emit a paused event\n _pause();\n accountingValid = false;\n }\n }\n\n /// @dev allow the accounting governor to fix the accounting of this strategy and unpause\n /// @param _activeDepositedValidators the override value of activeDepositedValidators\n /// @param _ethToWeth the amount of ETH to be converted to WETH\n /// @param _wethToBeSentToVault the amount of WETH to be sent to the Vault\n /// @param _consensusRewards the override value for consensusRewards\n /// @param _ethThresholdCheck maximum allowed ETH balance on the contract for the function to run\n /// @param _wethThresholdCheck maximum allowed WETH balance on the contract for the function to run\n /// the above 2 checks are done so transaction doesn't get front run and cause\n /// unexpected behaviour\n function manuallyFixAccounting(\n uint256 _activeDepositedValidators,\n uint256 _ethToWeth,\n uint256 _wethToBeSentToVault,\n uint256 _consensusRewards,\n uint256 _ethThresholdCheck,\n uint256 _wethThresholdCheck\n ) external onlyAccountingGovernor whenPaused {\n uint256 ethBalance = address(this).balance;\n uint256 wethBalance = IWETH9(WETH_TOKEN_ADDRESS).balanceOf(\n address(this)\n );\n\n require(\n ethBalance <= _ethThresholdCheck &&\n wethBalance <= _wethThresholdCheck,\n \"over accounting threshold\"\n );\n\n emit AccountingManuallyFixed(\n activeDepositedValidators,\n _activeDepositedValidators,\n consensusRewards,\n _consensusRewards,\n _ethToWeth,\n _wethToBeSentToVault\n );\n\n activeDepositedValidators = _activeDepositedValidators;\n consensusRewards = _consensusRewards;\n if (_ethToWeth > 0) {\n require(_ethToWeth <= ethBalance, \"insufficient ETH\");\n\n IWETH9(WETH_TOKEN_ADDRESS).deposit{ value: _ethToWeth }();\n }\n if (_wethToBeSentToVault > 0) {\n IWETH9(WETH_TOKEN_ADDRESS).transfer(\n VAULT_ADDRESS,\n _wethToBeSentToVault\n );\n }\n\n _unpause();\n }\n}\n" + }, + "contracts/strategies/NativeStaking/ValidatorRegistrator.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport { Pausable } from \"@openzeppelin/contracts/security/Pausable.sol\";\nimport { Governable } from \"../../governance/Governable.sol\";\nimport { IDepositContract } from \"../../interfaces/IDepositContract.sol\";\nimport { IVault } from \"../../interfaces/IVault.sol\";\nimport { IWETH9 } from \"../../interfaces/IWETH9.sol\";\nimport { ISSVNetwork, Cluster } from \"../../interfaces/ISSVNetwork.sol\";\n\nstruct ValidatorStakeData {\n bytes pubkey;\n bytes signature;\n bytes32 depositDataRoot;\n}\n\n/**\n * @title Registrator of the validators\n * @notice This contract implements all the required functionality to register, exit and remove validators.\n * @author Origin Protocol Inc\n */\nabstract contract ValidatorRegistrator is Governable, Pausable {\n /// @notice The address of the Wrapped ETH (WETH) token contract\n address public immutable WETH_TOKEN_ADDRESS;\n /// @notice The address of the beacon chain deposit contract\n address public immutable BEACON_CHAIN_DEPOSIT_CONTRACT;\n /// @notice The address of the SSV Network contract used to interface with\n address public immutable SSV_NETWORK_ADDRESS;\n /// @notice Address of the OETH Vault proxy contract\n address public immutable VAULT_ADDRESS;\n\n /// @notice Address of the registrator - allowed to register, exit and remove validators\n address public validatorRegistrator;\n /// @notice The number of validators that have 32 (!) ETH actively deposited. When a new deposit\n /// to a validator happens this number increases, when a validator exit is detected this number\n /// decreases.\n uint256 activeDepositedValidators;\n /// @notice State of the validators keccak256(pubKey) => state\n mapping(bytes32 => VALIDATOR_STATE) public validatorsStates;\n\n // For future use\n uint256[50] private __gap;\n\n enum VALIDATOR_STATE {\n REGISTERED, // validator is registered on the SSV network\n STAKED, // validator has funds staked\n EXITING, // exit message has been posted and validator is in the process of exiting\n EXIT_COMPLETE // validator has funds withdrawn to the EigenPod and is removed from the SSV\n }\n\n event RegistratorChanged(address newAddress);\n event ETHStaked(bytes pubkey, uint256 amount, bytes withdrawal_credentials);\n event SSVValidatorRegistered(bytes pubkey, uint64[] operatorIds);\n event SSVValidatorExitInitiated(bytes pubkey, uint64[] operatorIds);\n event SSVValidatorExitCompleted(bytes pubkey, uint64[] operatorIds);\n\n /// @dev Throws if called by any account other than the Registrator\n modifier onlyRegistrator() {\n require(\n msg.sender == validatorRegistrator,\n \"Caller is not the Registrator\"\n );\n _;\n }\n\n /// @dev Throws if called by any account other than the Strategist\n modifier onlyStrategist() {\n require(\n msg.sender == IVault(VAULT_ADDRESS).strategistAddr(),\n \"Caller is not the Strategist\"\n );\n _;\n }\n\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _vaultAddress Address of the Vault\n /// @param _beaconChainDepositContract Address of the beacon chain deposit contract\n /// @param _ssvNetwork Address of the SSV Network contract\n constructor(\n address _wethAddress,\n address _vaultAddress,\n address _beaconChainDepositContract,\n address _ssvNetwork\n ) {\n WETH_TOKEN_ADDRESS = _wethAddress;\n BEACON_CHAIN_DEPOSIT_CONTRACT = _beaconChainDepositContract;\n SSV_NETWORK_ADDRESS = _ssvNetwork;\n VAULT_ADDRESS = _vaultAddress;\n }\n\n /// @notice Set the address of the registrator which can register, exit and remove validators\n function setRegistrator(address _address) external onlyGovernor {\n emit RegistratorChanged(_address);\n validatorRegistrator = _address;\n }\n\n /// @notice Stakes WETH to the node validators\n /// @param validators A list of validator data needed to stake.\n /// The `ValidatorStakeData` struct contains the pubkey, signature and depositDataRoot.\n /// Only the registrator can call this function.\n function stakeEth(ValidatorStakeData[] calldata validators)\n external\n onlyRegistrator\n whenNotPaused\n {\n uint256 requiredETH = validators.length * 32 ether;\n\n // Check there is enough WETH from the deposits sitting in this strategy contract\n require(\n requiredETH <= IWETH9(WETH_TOKEN_ADDRESS).balanceOf(address(this)),\n \"insufficient WETH\"\n );\n\n // Convert required ETH from WETH\n IWETH9(WETH_TOKEN_ADDRESS).withdraw(requiredETH);\n\n // For each validator\n for (uint256 i = 0; i < validators.length; ) {\n bytes32 pubkeyHash = keccak256(validators[i].pubkey);\n VALIDATOR_STATE currentState = validatorsStates[pubkeyHash];\n\n require(\n currentState == VALIDATOR_STATE.REGISTERED,\n \"Validator not registered\"\n );\n\n /* 0x01 to indicate that withdrawal credentials will contain an EOA address that the sweeping function\n * can sweep funds to.\n * bytes11(0) to fill up the required zeros\n * remaining bytes20 are for the address\n */\n bytes memory withdrawal_credentials = abi.encodePacked(\n bytes1(0x01),\n bytes11(0),\n address(this)\n );\n IDepositContract(BEACON_CHAIN_DEPOSIT_CONTRACT).deposit{ value: 32 ether }(\n validators[i].pubkey,\n withdrawal_credentials,\n validators[i].signature,\n validators[i].depositDataRoot\n );\n\n activeDepositedValidators += 1;\n emit ETHStaked(\n validators[i].pubkey,\n 32 ether,\n withdrawal_credentials\n );\n\n validatorsStates[pubkeyHash] = VALIDATOR_STATE.STAKED;\n\n unchecked {\n ++i;\n }\n }\n }\n\n /// @notice Registers a new validator in the SSV Cluster.\n /// Only the registrator can call this function.\n function registerSsvValidator(\n bytes calldata publicKey,\n uint64[] calldata operatorIds,\n bytes calldata sharesData,\n uint256 amount,\n Cluster calldata cluster\n ) external onlyRegistrator whenNotPaused {\n ISSVNetwork(SSV_NETWORK_ADDRESS).registerValidator(\n publicKey,\n operatorIds,\n sharesData,\n amount,\n cluster\n );\n validatorsStates[keccak256(publicKey)] = VALIDATOR_STATE.REGISTERED;\n emit SSVValidatorRegistered(publicKey, operatorIds);\n }\n\n /// @notice Exit a validator from the Beacon chain.\n /// The staked ETH will eventually swept to this native staking strategy.\n /// Only the registrator can call this function.\n function exitSsvValidator(\n bytes calldata publicKey,\n uint64[] calldata operatorIds\n ) external onlyRegistrator whenNotPaused {\n VALIDATOR_STATE currentState = validatorsStates[keccak256(publicKey)];\n require(currentState == VALIDATOR_STATE.STAKED, \"Validator not staked\");\n\n ISSVNetwork(SSV_NETWORK_ADDRESS).exitValidator(publicKey, operatorIds);\n emit SSVValidatorExitInitiated(publicKey, operatorIds);\n\n validatorsStates[keccak256(publicKey)] = VALIDATOR_STATE.EXITING;\n }\n\n /// @notice Remove a validator from the SSV Cluster.\n /// Make sure `exitSsvValidator` is called before and the validate has exited the Beacon chain.\n /// If removed before the validator has exited the beacon chain will result in the validator being slashed.\n /// Only the registrator can call this function.\n function removeSsvValidator(\n bytes calldata publicKey,\n uint64[] calldata operatorIds,\n Cluster calldata cluster\n ) external onlyRegistrator whenNotPaused {\n VALIDATOR_STATE currentState = validatorsStates[keccak256(publicKey)];\n require(\n currentState == VALIDATOR_STATE.EXITING,\n \"Validator not exiting\"\n );\n\n ISSVNetwork(SSV_NETWORK_ADDRESS).removeValidator(\n publicKey,\n operatorIds,\n cluster\n );\n emit SSVValidatorExitCompleted(publicKey, operatorIds);\n\n validatorsStates[keccak256(publicKey)] = VALIDATOR_STATE.EXIT_COMPLETE;\n }\n\n /// @notice Deposits more SSV Tokens to the SSV Network contract which is used to pay the SSV Operators.\n /// @dev A SSV cluster is defined by the SSVOwnerAddress and the set of operatorIds.\n /// uses \"onlyStrategist\" modifier so continuous front-running can't DOS our maintenance service\n /// that tries to top up SSV tokens.\n /// @param cluster The SSV cluster details that must be derived from emitted events from the SSVNetwork contract.\n function depositSSV(\n uint64[] memory operatorIds,\n uint256 amount,\n Cluster memory cluster\n ) external onlyStrategist {\n ISSVNetwork(SSV_NETWORK_ADDRESS).deposit(\n address(this),\n operatorIds,\n amount,\n cluster\n );\n }\n}\n" + }, + "contracts/token/OUSD.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n/**\n * @title OUSD Token Contract\n * @dev ERC20 compatible contract for OUSD\n * @dev Implements an elastic supply\n * @author Origin Protocol Inc\n */\nimport { SafeMath } from \"@openzeppelin/contracts/utils/math/SafeMath.sol\";\nimport { Address } from \"@openzeppelin/contracts/utils/Address.sol\";\n\nimport { Initializable } from \"../utils/Initializable.sol\";\nimport { InitializableERC20Detailed } from \"../utils/InitializableERC20Detailed.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { Governable } from \"../governance/Governable.sol\";\n\n/**\n * NOTE that this is an ERC20 token but the invariant that the sum of\n * balanceOf(x) for all x is not >= totalSupply(). This is a consequence of the\n * rebasing design. Any integrations with OUSD should be aware.\n */\n\ncontract OUSD is Initializable, InitializableERC20Detailed, Governable {\n using SafeMath for uint256;\n using StableMath for uint256;\n\n event TotalSupplyUpdatedHighres(\n uint256 totalSupply,\n uint256 rebasingCredits,\n uint256 rebasingCreditsPerToken\n );\n event AccountRebasingEnabled(address account);\n event AccountRebasingDisabled(address account);\n\n enum RebaseOptions {\n NotSet,\n OptOut,\n OptIn\n }\n\n uint256 private constant MAX_SUPPLY = ~uint128(0); // (2^128) - 1\n uint256 public _totalSupply;\n mapping(address => mapping(address => uint256)) private _allowances;\n address public vaultAddress = address(0);\n mapping(address => uint256) private _creditBalances;\n uint256 private _rebasingCredits;\n uint256 private _rebasingCreditsPerToken;\n // Frozen address/credits are non rebasing (value is held in contracts which\n // do not receive yield unless they explicitly opt in)\n uint256 public nonRebasingSupply;\n mapping(address => uint256) public nonRebasingCreditsPerToken;\n mapping(address => RebaseOptions) public rebaseState;\n mapping(address => uint256) public isUpgraded;\n\n uint256 private constant RESOLUTION_INCREASE = 1e9;\n\n function initialize(\n string calldata _nameArg,\n string calldata _symbolArg,\n address _vaultAddress,\n uint256 _initialCreditsPerToken\n ) external onlyGovernor initializer {\n InitializableERC20Detailed._initialize(_nameArg, _symbolArg, 18);\n _rebasingCreditsPerToken = _initialCreditsPerToken;\n vaultAddress = _vaultAddress;\n }\n\n /**\n * @dev Verifies that the caller is the Vault contract\n */\n modifier onlyVault() {\n require(vaultAddress == msg.sender, \"Caller is not the Vault\");\n _;\n }\n\n /**\n * @return The total supply of OUSD.\n */\n function totalSupply() public view override returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @return Low resolution rebasingCreditsPerToken\n */\n function rebasingCreditsPerToken() public view returns (uint256) {\n return _rebasingCreditsPerToken / RESOLUTION_INCREASE;\n }\n\n /**\n * @return Low resolution total number of rebasing credits\n */\n function rebasingCredits() public view returns (uint256) {\n return _rebasingCredits / RESOLUTION_INCREASE;\n }\n\n /**\n * @return High resolution rebasingCreditsPerToken\n */\n function rebasingCreditsPerTokenHighres() public view returns (uint256) {\n return _rebasingCreditsPerToken;\n }\n\n /**\n * @return High resolution total number of rebasing credits\n */\n function rebasingCreditsHighres() public view returns (uint256) {\n return _rebasingCredits;\n }\n\n /**\n * @dev Gets the balance of the specified address.\n * @param _account Address to query the balance of.\n * @return A uint256 representing the amount of base units owned by the\n * specified address.\n */\n function balanceOf(address _account)\n public\n view\n override\n returns (uint256)\n {\n if (_creditBalances[_account] == 0) return 0;\n return\n _creditBalances[_account].divPrecisely(_creditsPerToken(_account));\n }\n\n /**\n * @dev Gets the credits balance of the specified address.\n * @dev Backwards compatible with old low res credits per token.\n * @param _account The address to query the balance of.\n * @return (uint256, uint256) Credit balance and credits per token of the\n * address\n */\n function creditsBalanceOf(address _account)\n public\n view\n returns (uint256, uint256)\n {\n uint256 cpt = _creditsPerToken(_account);\n if (cpt == 1e27) {\n // For a period before the resolution upgrade, we created all new\n // contract accounts at high resolution. Since they are not changing\n // as a result of this upgrade, we will return their true values\n return (_creditBalances[_account], cpt);\n } else {\n return (\n _creditBalances[_account] / RESOLUTION_INCREASE,\n cpt / RESOLUTION_INCREASE\n );\n }\n }\n\n /**\n * @dev Gets the credits balance of the specified address.\n * @param _account The address to query the balance of.\n * @return (uint256, uint256, bool) Credit balance, credits per token of the\n * address, and isUpgraded\n */\n function creditsBalanceOfHighres(address _account)\n public\n view\n returns (\n uint256,\n uint256,\n bool\n )\n {\n return (\n _creditBalances[_account],\n _creditsPerToken(_account),\n isUpgraded[_account] == 1\n );\n }\n\n /**\n * @dev Transfer tokens to a specified address.\n * @param _to the address to transfer to.\n * @param _value the amount to be transferred.\n * @return true on success.\n */\n function transfer(address _to, uint256 _value)\n public\n override\n returns (bool)\n {\n require(_to != address(0), \"Transfer to zero address\");\n require(\n _value <= balanceOf(msg.sender),\n \"Transfer greater than balance\"\n );\n\n _executeTransfer(msg.sender, _to, _value);\n\n emit Transfer(msg.sender, _to, _value);\n\n return true;\n }\n\n /**\n * @dev Transfer tokens from one address to another.\n * @param _from The address you want to send tokens from.\n * @param _to The address you want to transfer to.\n * @param _value The amount of tokens to be transferred.\n */\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n ) public override returns (bool) {\n require(_to != address(0), \"Transfer to zero address\");\n require(_value <= balanceOf(_from), \"Transfer greater than balance\");\n\n _allowances[_from][msg.sender] = _allowances[_from][msg.sender].sub(\n _value\n );\n\n _executeTransfer(_from, _to, _value);\n\n emit Transfer(_from, _to, _value);\n\n return true;\n }\n\n /**\n * @dev Update the count of non rebasing credits in response to a transfer\n * @param _from The address you want to send tokens from.\n * @param _to The address you want to transfer to.\n * @param _value Amount of OUSD to transfer\n */\n function _executeTransfer(\n address _from,\n address _to,\n uint256 _value\n ) internal {\n bool isNonRebasingTo = _isNonRebasingAccount(_to);\n bool isNonRebasingFrom = _isNonRebasingAccount(_from);\n\n // Credits deducted and credited might be different due to the\n // differing creditsPerToken used by each account\n uint256 creditsCredited = _value.mulTruncate(_creditsPerToken(_to));\n uint256 creditsDeducted = _value.mulTruncate(_creditsPerToken(_from));\n\n _creditBalances[_from] = _creditBalances[_from].sub(\n creditsDeducted,\n \"Transfer amount exceeds balance\"\n );\n _creditBalances[_to] = _creditBalances[_to].add(creditsCredited);\n\n if (isNonRebasingTo && !isNonRebasingFrom) {\n // Transfer to non-rebasing account from rebasing account, credits\n // are removed from the non rebasing tally\n nonRebasingSupply = nonRebasingSupply.add(_value);\n // Update rebasingCredits by subtracting the deducted amount\n _rebasingCredits = _rebasingCredits.sub(creditsDeducted);\n } else if (!isNonRebasingTo && isNonRebasingFrom) {\n // Transfer to rebasing account from non-rebasing account\n // Decreasing non-rebasing credits by the amount that was sent\n nonRebasingSupply = nonRebasingSupply.sub(_value);\n // Update rebasingCredits by adding the credited amount\n _rebasingCredits = _rebasingCredits.add(creditsCredited);\n }\n }\n\n /**\n * @dev Function to check the amount of tokens that _owner has allowed to\n * `_spender`.\n * @param _owner The address which owns the funds.\n * @param _spender The address which will spend the funds.\n * @return The number of tokens still available for the _spender.\n */\n function allowance(address _owner, address _spender)\n public\n view\n override\n returns (uint256)\n {\n return _allowances[_owner][_spender];\n }\n\n /**\n * @dev Approve the passed address to spend the specified amount of tokens\n * on behalf of msg.sender. This method is included for ERC20\n * compatibility. `increaseAllowance` and `decreaseAllowance` should be\n * used instead.\n *\n * Changing an allowance with this method brings the risk that someone\n * may transfer both the old and the new allowance - if they are both\n * greater than zero - if a transfer transaction is mined before the\n * later approve() call is mined.\n * @param _spender The address which will spend the funds.\n * @param _value The amount of tokens to be spent.\n */\n function approve(address _spender, uint256 _value)\n public\n override\n returns (bool)\n {\n _allowances[msg.sender][_spender] = _value;\n emit Approval(msg.sender, _spender, _value);\n return true;\n }\n\n /**\n * @dev Increase the amount of tokens that an owner has allowed to\n * `_spender`.\n * This method should be used instead of approve() to avoid the double\n * approval vulnerability described above.\n * @param _spender The address which will spend the funds.\n * @param _addedValue The amount of tokens to increase the allowance by.\n */\n function increaseAllowance(address _spender, uint256 _addedValue)\n public\n returns (bool)\n {\n _allowances[msg.sender][_spender] = _allowances[msg.sender][_spender]\n .add(_addedValue);\n emit Approval(msg.sender, _spender, _allowances[msg.sender][_spender]);\n return true;\n }\n\n /**\n * @dev Decrease the amount of tokens that an owner has allowed to\n `_spender`.\n * @param _spender The address which will spend the funds.\n * @param _subtractedValue The amount of tokens to decrease the allowance\n * by.\n */\n function decreaseAllowance(address _spender, uint256 _subtractedValue)\n public\n returns (bool)\n {\n uint256 oldValue = _allowances[msg.sender][_spender];\n if (_subtractedValue >= oldValue) {\n _allowances[msg.sender][_spender] = 0;\n } else {\n _allowances[msg.sender][_spender] = oldValue.sub(_subtractedValue);\n }\n emit Approval(msg.sender, _spender, _allowances[msg.sender][_spender]);\n return true;\n }\n\n /**\n * @dev Mints new tokens, increasing totalSupply.\n */\n function mint(address _account, uint256 _amount) external onlyVault {\n _mint(_account, _amount);\n }\n\n /**\n * @dev Creates `_amount` tokens and assigns them to `_account`, increasing\n * the total supply.\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * Requirements\n *\n * - `to` cannot be the zero address.\n */\n function _mint(address _account, uint256 _amount) internal nonReentrant {\n require(_account != address(0), \"Mint to the zero address\");\n\n bool isNonRebasingAccount = _isNonRebasingAccount(_account);\n\n uint256 creditAmount = _amount.mulTruncate(_creditsPerToken(_account));\n _creditBalances[_account] = _creditBalances[_account].add(creditAmount);\n\n // If the account is non rebasing and doesn't have a set creditsPerToken\n // then set it i.e. this is a mint from a fresh contract\n if (isNonRebasingAccount) {\n nonRebasingSupply = nonRebasingSupply.add(_amount);\n } else {\n _rebasingCredits = _rebasingCredits.add(creditAmount);\n }\n\n _totalSupply = _totalSupply.add(_amount);\n\n require(_totalSupply < MAX_SUPPLY, \"Max supply\");\n\n emit Transfer(address(0), _account, _amount);\n }\n\n /**\n * @dev Burns tokens, decreasing totalSupply.\n */\n function burn(address account, uint256 amount) external onlyVault {\n _burn(account, amount);\n }\n\n /**\n * @dev Destroys `_amount` tokens from `_account`, reducing the\n * total supply.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * Requirements\n *\n * - `_account` cannot be the zero address.\n * - `_account` must have at least `_amount` tokens.\n */\n function _burn(address _account, uint256 _amount) internal nonReentrant {\n require(_account != address(0), \"Burn from the zero address\");\n if (_amount == 0) {\n return;\n }\n\n bool isNonRebasingAccount = _isNonRebasingAccount(_account);\n uint256 creditAmount = _amount.mulTruncate(_creditsPerToken(_account));\n uint256 currentCredits = _creditBalances[_account];\n\n // Remove the credits, burning rounding errors\n if (\n currentCredits == creditAmount || currentCredits - 1 == creditAmount\n ) {\n // Handle dust from rounding\n _creditBalances[_account] = 0;\n } else if (currentCredits > creditAmount) {\n _creditBalances[_account] = _creditBalances[_account].sub(\n creditAmount\n );\n } else {\n revert(\"Remove exceeds balance\");\n }\n\n // Remove from the credit tallies and non-rebasing supply\n if (isNonRebasingAccount) {\n nonRebasingSupply = nonRebasingSupply.sub(_amount);\n } else {\n _rebasingCredits = _rebasingCredits.sub(creditAmount);\n }\n\n _totalSupply = _totalSupply.sub(_amount);\n\n emit Transfer(_account, address(0), _amount);\n }\n\n /**\n * @dev Get the credits per token for an account. Returns a fixed amount\n * if the account is non-rebasing.\n * @param _account Address of the account.\n */\n function _creditsPerToken(address _account)\n internal\n view\n returns (uint256)\n {\n if (nonRebasingCreditsPerToken[_account] != 0) {\n return nonRebasingCreditsPerToken[_account];\n } else {\n return _rebasingCreditsPerToken;\n }\n }\n\n /**\n * @dev Is an account using rebasing accounting or non-rebasing accounting?\n * Also, ensure contracts are non-rebasing if they have not opted in.\n * @param _account Address of the account.\n */\n function _isNonRebasingAccount(address _account) internal returns (bool) {\n bool isContract = Address.isContract(_account);\n if (isContract && rebaseState[_account] == RebaseOptions.NotSet) {\n _ensureRebasingMigration(_account);\n }\n return nonRebasingCreditsPerToken[_account] > 0;\n }\n\n /**\n * @dev Ensures internal account for rebasing and non-rebasing credits and\n * supply is updated following deployment of frozen yield change.\n */\n function _ensureRebasingMigration(address _account) internal {\n if (nonRebasingCreditsPerToken[_account] == 0) {\n emit AccountRebasingDisabled(_account);\n if (_creditBalances[_account] == 0) {\n // Since there is no existing balance, we can directly set to\n // high resolution, and do not have to do any other bookkeeping\n nonRebasingCreditsPerToken[_account] = 1e27;\n } else {\n // Migrate an existing account:\n\n // Set fixed credits per token for this account\n nonRebasingCreditsPerToken[_account] = _rebasingCreditsPerToken;\n // Update non rebasing supply\n nonRebasingSupply = nonRebasingSupply.add(balanceOf(_account));\n // Update credit tallies\n _rebasingCredits = _rebasingCredits.sub(\n _creditBalances[_account]\n );\n }\n }\n }\n\n /**\n * @notice Enable rebasing for an account.\n * @dev Add a contract address to the non-rebasing exception list. The\n * address's balance will be part of rebases and the account will be exposed\n * to upside and downside.\n * @param _account Address of the account.\n */\n function governanceRebaseOptIn(address _account)\n public\n nonReentrant\n onlyGovernor\n {\n _rebaseOptIn(_account);\n }\n\n /**\n * @dev Add a contract address to the non-rebasing exception list. The\n * address's balance will be part of rebases and the account will be exposed\n * to upside and downside.\n */\n function rebaseOptIn() public nonReentrant {\n _rebaseOptIn(msg.sender);\n }\n\n function _rebaseOptIn(address _account) internal {\n require(_isNonRebasingAccount(_account), \"Account has not opted out\");\n\n // Convert balance into the same amount at the current exchange rate\n uint256 newCreditBalance = _creditBalances[_account]\n .mul(_rebasingCreditsPerToken)\n .div(_creditsPerToken(_account));\n\n // Decreasing non rebasing supply\n nonRebasingSupply = nonRebasingSupply.sub(balanceOf(_account));\n\n _creditBalances[_account] = newCreditBalance;\n\n // Increase rebasing credits, totalSupply remains unchanged so no\n // adjustment necessary\n _rebasingCredits = _rebasingCredits.add(_creditBalances[_account]);\n\n rebaseState[_account] = RebaseOptions.OptIn;\n\n // Delete any fixed credits per token\n delete nonRebasingCreditsPerToken[_account];\n emit AccountRebasingEnabled(_account);\n }\n\n /**\n * @dev Explicitly mark that an address is non-rebasing.\n */\n function rebaseOptOut() public nonReentrant {\n require(!_isNonRebasingAccount(msg.sender), \"Account has not opted in\");\n\n // Increase non rebasing supply\n nonRebasingSupply = nonRebasingSupply.add(balanceOf(msg.sender));\n // Set fixed credits per token\n nonRebasingCreditsPerToken[msg.sender] = _rebasingCreditsPerToken;\n\n // Decrease rebasing credits, total supply remains unchanged so no\n // adjustment necessary\n _rebasingCredits = _rebasingCredits.sub(_creditBalances[msg.sender]);\n\n // Mark explicitly opted out of rebasing\n rebaseState[msg.sender] = RebaseOptions.OptOut;\n emit AccountRebasingDisabled(msg.sender);\n }\n\n /**\n * @dev Modify the supply without minting new tokens. This uses a change in\n * the exchange rate between \"credits\" and OUSD tokens to change balances.\n * @param _newTotalSupply New total supply of OUSD.\n */\n function changeSupply(uint256 _newTotalSupply)\n external\n onlyVault\n nonReentrant\n {\n require(_totalSupply > 0, \"Cannot increase 0 supply\");\n\n if (_totalSupply == _newTotalSupply) {\n emit TotalSupplyUpdatedHighres(\n _totalSupply,\n _rebasingCredits,\n _rebasingCreditsPerToken\n );\n return;\n }\n\n _totalSupply = _newTotalSupply > MAX_SUPPLY\n ? MAX_SUPPLY\n : _newTotalSupply;\n\n _rebasingCreditsPerToken = _rebasingCredits.divPrecisely(\n _totalSupply.sub(nonRebasingSupply)\n );\n\n require(_rebasingCreditsPerToken > 0, \"Invalid change in supply\");\n\n _totalSupply = _rebasingCredits\n .divPrecisely(_rebasingCreditsPerToken)\n .add(nonRebasingSupply);\n\n emit TotalSupplyUpdatedHighres(\n _totalSupply,\n _rebasingCredits,\n _rebasingCreditsPerToken\n );\n }\n}\n" + }, + "contracts/utils/Helpers.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport { IBasicToken } from \"../interfaces/IBasicToken.sol\";\n\nlibrary Helpers {\n /**\n * @notice Fetch the `symbol()` from an ERC20 token\n * @dev Grabs the `symbol()` from a contract\n * @param _token Address of the ERC20 token\n * @return string Symbol of the ERC20 token\n */\n function getSymbol(address _token) internal view returns (string memory) {\n string memory symbol = IBasicToken(_token).symbol();\n return symbol;\n }\n\n /**\n * @notice Fetch the `decimals()` from an ERC20 token\n * @dev Grabs the `decimals()` from a contract and fails if\n * the decimal value does not live within a certain range\n * @param _token Address of the ERC20 token\n * @return uint256 Decimals of the ERC20 token\n */\n function getDecimals(address _token) internal view returns (uint256) {\n uint256 decimals = IBasicToken(_token).decimals();\n require(\n decimals >= 4 && decimals <= 18,\n \"Token must have sufficient decimal places\"\n );\n\n return decimals;\n }\n}\n" + }, + "contracts/utils/Initializable.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n/**\n * @title Base contract any contracts that need to initialize state after deployment.\n * @author Origin Protocol Inc\n */\nabstract contract Initializable {\n /**\n * @dev Indicates that the contract has been initialized.\n */\n bool private initialized;\n\n /**\n * @dev Indicates that the contract is in the process of being initialized.\n */\n bool private initializing;\n\n /**\n * @dev Modifier to protect an initializer function from being invoked twice.\n */\n modifier initializer() {\n require(\n initializing || !initialized,\n \"Initializable: contract is already initialized\"\n );\n\n bool isTopLevelCall = !initializing;\n if (isTopLevelCall) {\n initializing = true;\n initialized = true;\n }\n\n _;\n\n if (isTopLevelCall) {\n initializing = false;\n }\n }\n\n uint256[50] private ______gap;\n}\n" + }, + "contracts/utils/InitializableAbstractStrategy.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n/**\n * @title Base contract for vault strategies.\n * @author Origin Protocol Inc\n */\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { Initializable } from \"../utils/Initializable.sol\";\nimport { Governable } from \"../governance/Governable.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\n\nabstract contract InitializableAbstractStrategy is Initializable, Governable {\n using SafeERC20 for IERC20;\n\n event PTokenAdded(address indexed _asset, address _pToken);\n event PTokenRemoved(address indexed _asset, address _pToken);\n event Deposit(address indexed _asset, address _pToken, uint256 _amount);\n event Withdrawal(address indexed _asset, address _pToken, uint256 _amount);\n event RewardTokenCollected(\n address recipient,\n address rewardToken,\n uint256 amount\n );\n event RewardTokenAddressesUpdated(\n address[] _oldAddresses,\n address[] _newAddresses\n );\n event HarvesterAddressesUpdated(\n address _oldHarvesterAddress,\n address _newHarvesterAddress\n );\n\n /// @notice Address of the underlying platform\n address public immutable platformAddress;\n /// @notice Address of the OToken vault\n address public immutable vaultAddress;\n\n /// @dev Replaced with an immutable variable\n // slither-disable-next-line constable-states\n address private _deprecated_platformAddress;\n\n /// @dev Replaced with an immutable\n // slither-disable-next-line constable-states\n address private _deprecated_vaultAddress;\n\n /// @notice asset => pToken (Platform Specific Token Address)\n mapping(address => address) public assetToPToken;\n\n /// @notice Full list of all assets supported by the strategy\n address[] internal assetsMapped;\n\n // Deprecated: Reward token address\n // slither-disable-next-line constable-states\n address private _deprecated_rewardTokenAddress;\n\n // Deprecated: now resides in Harvester's rewardTokenConfigs\n // slither-disable-next-line constable-states\n uint256 private _deprecated_rewardLiquidationThreshold;\n\n /// @notice Address of the Harvester contract allowed to collect reward tokens\n address public harvesterAddress;\n\n /// @notice Address of the reward tokens. eg CRV, BAL, CVX, AURA\n address[] public rewardTokenAddresses;\n\n /* Reserved for future expansion. Used to be 100 storage slots\n * and has decreased to accommodate:\n * - harvesterAddress\n * - rewardTokenAddresses\n */\n int256[98] private _reserved;\n\n struct BaseStrategyConfig {\n address platformAddress; // Address of the underlying platform\n address vaultAddress; // Address of the OToken's Vault\n }\n\n /**\n * @param _config The platform and OToken vault addresses\n */\n constructor(BaseStrategyConfig memory _config) {\n platformAddress = _config.platformAddress;\n vaultAddress = _config.vaultAddress;\n }\n\n /**\n * @dev Internal initialize function, to set up initial internal state\n * @param _rewardTokenAddresses Address of reward token for platform\n * @param _assets Addresses of initial supported assets\n * @param _pTokens Platform Token corresponding addresses\n */\n function _initialize(\n address[] memory _rewardTokenAddresses,\n address[] memory _assets,\n address[] memory _pTokens\n ) internal {\n rewardTokenAddresses = _rewardTokenAddresses;\n\n uint256 assetCount = _assets.length;\n require(assetCount == _pTokens.length, \"Invalid input arrays\");\n for (uint256 i = 0; i < assetCount; ++i) {\n _setPTokenAddress(_assets[i], _pTokens[i]);\n }\n }\n\n /**\n * @notice Collect accumulated reward token and send to Vault.\n */\n function collectRewardTokens() external virtual onlyHarvester nonReentrant {\n _collectRewardTokens();\n }\n\n /**\n * @dev Default implementation that transfers reward tokens to the Harvester\n * Implementing strategies need to add custom logic to collect the rewards.\n */\n function _collectRewardTokens() internal virtual {\n uint256 rewardTokenCount = rewardTokenAddresses.length;\n for (uint256 i = 0; i < rewardTokenCount; ++i) {\n IERC20 rewardToken = IERC20(rewardTokenAddresses[i]);\n uint256 balance = rewardToken.balanceOf(address(this));\n if (balance > 0) {\n emit RewardTokenCollected(\n harvesterAddress,\n address(rewardToken),\n balance\n );\n rewardToken.safeTransfer(harvesterAddress, balance);\n }\n }\n }\n\n /**\n * @dev Verifies that the caller is the Vault.\n */\n modifier onlyVault() {\n require(msg.sender == vaultAddress, \"Caller is not the Vault\");\n _;\n }\n\n /**\n * @dev Verifies that the caller is the Harvester.\n */\n modifier onlyHarvester() {\n require(msg.sender == harvesterAddress, \"Caller is not the Harvester\");\n _;\n }\n\n /**\n * @dev Verifies that the caller is the Vault or Governor.\n */\n modifier onlyVaultOrGovernor() {\n require(\n msg.sender == vaultAddress || msg.sender == governor(),\n \"Caller is not the Vault or Governor\"\n );\n _;\n }\n\n /**\n * @dev Verifies that the caller is the Vault, Governor, or Strategist.\n */\n modifier onlyVaultOrGovernorOrStrategist() {\n require(\n msg.sender == vaultAddress ||\n msg.sender == governor() ||\n msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Vault, Governor, or Strategist\"\n );\n _;\n }\n\n /**\n * @notice Set the reward token addresses. Any old addresses will be overwritten.\n * @param _rewardTokenAddresses Array of reward token addresses\n */\n function setRewardTokenAddresses(address[] calldata _rewardTokenAddresses)\n external\n onlyGovernor\n {\n uint256 rewardTokenCount = _rewardTokenAddresses.length;\n for (uint256 i = 0; i < rewardTokenCount; ++i) {\n require(\n _rewardTokenAddresses[i] != address(0),\n \"Can not set an empty address as a reward token\"\n );\n }\n\n emit RewardTokenAddressesUpdated(\n rewardTokenAddresses,\n _rewardTokenAddresses\n );\n rewardTokenAddresses = _rewardTokenAddresses;\n }\n\n /**\n * @notice Get the reward token addresses.\n * @return address[] the reward token addresses.\n */\n function getRewardTokenAddresses()\n external\n view\n returns (address[] memory)\n {\n return rewardTokenAddresses;\n }\n\n /**\n * @notice Provide support for asset by passing its pToken address.\n * This method can only be called by the system Governor\n * @param _asset Address for the asset\n * @param _pToken Address for the corresponding platform token\n */\n function setPTokenAddress(address _asset, address _pToken)\n external\n virtual\n onlyGovernor\n {\n _setPTokenAddress(_asset, _pToken);\n }\n\n /**\n * @notice Remove a supported asset by passing its index.\n * This method can only be called by the system Governor\n * @param _assetIndex Index of the asset to be removed\n */\n function removePToken(uint256 _assetIndex) external virtual onlyGovernor {\n require(_assetIndex < assetsMapped.length, \"Invalid index\");\n address asset = assetsMapped[_assetIndex];\n address pToken = assetToPToken[asset];\n\n if (_assetIndex < assetsMapped.length - 1) {\n assetsMapped[_assetIndex] = assetsMapped[assetsMapped.length - 1];\n }\n assetsMapped.pop();\n assetToPToken[asset] = address(0);\n\n emit PTokenRemoved(asset, pToken);\n }\n\n /**\n * @notice Provide support for asset by passing its pToken address.\n * Add to internal mappings and execute the platform specific,\n * abstract method `_abstractSetPToken`\n * @param _asset Address for the asset\n * @param _pToken Address for the corresponding platform token\n */\n function _setPTokenAddress(address _asset, address _pToken) internal {\n require(assetToPToken[_asset] == address(0), \"pToken already set\");\n require(\n _asset != address(0) && _pToken != address(0),\n \"Invalid addresses\"\n );\n\n assetToPToken[_asset] = _pToken;\n assetsMapped.push(_asset);\n\n emit PTokenAdded(_asset, _pToken);\n\n _abstractSetPToken(_asset, _pToken);\n }\n\n /**\n * @notice Transfer token to governor. Intended for recovering tokens stuck in\n * strategy contracts, i.e. mistaken sends.\n * @param _asset Address for the asset\n * @param _amount Amount of the asset to transfer\n */\n function transferToken(address _asset, uint256 _amount)\n public\n onlyGovernor\n {\n require(!supportsAsset(_asset), \"Cannot transfer supported asset\");\n IERC20(_asset).safeTransfer(governor(), _amount);\n }\n\n /**\n * @notice Set the Harvester contract that can collect rewards.\n * @param _harvesterAddress Address of the harvester contract.\n */\n function setHarvesterAddress(address _harvesterAddress)\n external\n onlyGovernor\n {\n emit HarvesterAddressesUpdated(harvesterAddress, _harvesterAddress);\n harvesterAddress = _harvesterAddress;\n }\n\n /***************************************\n Abstract\n ****************************************/\n\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n virtual;\n\n function safeApproveAllTokens() external virtual;\n\n /**\n * @notice Deposit an amount of assets into the platform\n * @param _asset Address for the asset\n * @param _amount Units of asset to deposit\n */\n function deposit(address _asset, uint256 _amount) external virtual;\n\n /**\n * @notice Deposit all supported assets in this strategy contract to the platform\n */\n function depositAll() external virtual;\n\n /**\n * @notice Withdraw an `amount` of assets from the platform and\n * send to the `_recipient`.\n * @param _recipient Address to which the asset should be sent\n * @param _asset Address of the asset\n * @param _amount Units of asset to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external virtual;\n\n /**\n * @notice Withdraw all supported assets from platform and\n * sends to the OToken's Vault.\n */\n function withdrawAll() external virtual;\n\n /**\n * @notice Get the total asset value held in the platform.\n * This includes any interest that was generated since depositing.\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n virtual\n returns (uint256 balance);\n\n /**\n * @notice Check if an asset is supported.\n * @param _asset Address of the asset\n * @return bool Whether asset is supported\n */\n function supportsAsset(address _asset) public view virtual returns (bool);\n}\n" + }, + "contracts/utils/InitializableERC20Detailed.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\n/**\n * @dev Optional functions from the ERC20 standard.\n * Converted from openzeppelin/contracts/token/ERC20/ERC20Detailed.sol\n * @author Origin Protocol Inc\n */\nabstract contract InitializableERC20Detailed is IERC20 {\n // Storage gap to skip storage from prior to OUSD reset\n uint256[100] private _____gap;\n\n string private _name;\n string private _symbol;\n uint8 private _decimals;\n\n /**\n * @dev Sets the values for `name`, `symbol`, and `decimals`. All three of\n * these values are immutable: they can only be set once during\n * construction.\n * @notice To avoid variable shadowing appended `Arg` after arguments name.\n */\n function _initialize(\n string memory nameArg,\n string memory symbolArg,\n uint8 decimalsArg\n ) internal {\n _name = nameArg;\n _symbol = symbolArg;\n _decimals = decimalsArg;\n }\n\n /**\n * @notice Returns the name of the token.\n */\n function name() public view returns (string memory) {\n return _name;\n }\n\n /**\n * @notice Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view returns (string memory) {\n return _symbol;\n }\n\n /**\n * @notice Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5,05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei.\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view returns (uint8) {\n return _decimals;\n }\n}\n" + }, + "contracts/utils/StableMath.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport { SafeMath } from \"@openzeppelin/contracts/utils/math/SafeMath.sol\";\n\n// Based on StableMath from Stability Labs Pty. Ltd.\n// https://github.com/mstable/mStable-contracts/blob/master/contracts/shared/StableMath.sol\n\nlibrary StableMath {\n using SafeMath for uint256;\n\n /**\n * @dev Scaling unit for use in specific calculations,\n * where 1 * 10**18, or 1e18 represents a unit '1'\n */\n uint256 private constant FULL_SCALE = 1e18;\n\n /***************************************\n Helpers\n ****************************************/\n\n /**\n * @dev Adjust the scale of an integer\n * @param to Decimals to scale to\n * @param from Decimals to scale from\n */\n function scaleBy(\n uint256 x,\n uint256 to,\n uint256 from\n ) internal pure returns (uint256) {\n if (to > from) {\n x = x.mul(10**(to - from));\n } else if (to < from) {\n // slither-disable-next-line divide-before-multiply\n x = x.div(10**(from - to));\n }\n return x;\n }\n\n /***************************************\n Precise Arithmetic\n ****************************************/\n\n /**\n * @dev Multiplies two precise units, and then truncates by the full scale\n * @param x Left hand input to multiplication\n * @param y Right hand input to multiplication\n * @return Result after multiplying the two inputs and then dividing by the shared\n * scale unit\n */\n function mulTruncate(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulTruncateScale(x, y, FULL_SCALE);\n }\n\n /**\n * @dev Multiplies two precise units, and then truncates by the given scale. For example,\n * when calculating 90% of 10e18, (10e18 * 9e17) / 1e18 = (9e36) / 1e18 = 9e18\n * @param x Left hand input to multiplication\n * @param y Right hand input to multiplication\n * @param scale Scale unit\n * @return Result after multiplying the two inputs and then dividing by the shared\n * scale unit\n */\n function mulTruncateScale(\n uint256 x,\n uint256 y,\n uint256 scale\n ) internal pure returns (uint256) {\n // e.g. assume scale = fullScale\n // z = 10e18 * 9e17 = 9e36\n uint256 z = x.mul(y);\n // return 9e36 / 1e18 = 9e18\n return z.div(scale);\n }\n\n /**\n * @dev Multiplies two precise units, and then truncates by the full scale, rounding up the result\n * @param x Left hand input to multiplication\n * @param y Right hand input to multiplication\n * @return Result after multiplying the two inputs and then dividing by the shared\n * scale unit, rounded up to the closest base unit.\n */\n function mulTruncateCeil(uint256 x, uint256 y)\n internal\n pure\n returns (uint256)\n {\n // e.g. 8e17 * 17268172638 = 138145381104e17\n uint256 scaled = x.mul(y);\n // e.g. 138145381104e17 + 9.99...e17 = 138145381113.99...e17\n uint256 ceil = scaled.add(FULL_SCALE.sub(1));\n // e.g. 13814538111.399...e18 / 1e18 = 13814538111\n return ceil.div(FULL_SCALE);\n }\n\n /**\n * @dev Precisely divides two units, by first scaling the left hand operand. Useful\n * for finding percentage weightings, i.e. 8e18/10e18 = 80% (or 8e17)\n * @param x Left hand input to division\n * @param y Right hand input to division\n * @return Result after multiplying the left operand by the scale, and\n * executing the division on the right hand input.\n */\n function divPrecisely(uint256 x, uint256 y)\n internal\n pure\n returns (uint256)\n {\n // e.g. 8e18 * 1e18 = 8e36\n uint256 z = x.mul(FULL_SCALE);\n // e.g. 8e36 / 10e18 = 8e17\n return z.div(y);\n }\n}\n" + }, + "contracts/vault/VaultStorage.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n/**\n * @title OToken VaultStorage contract\n * @notice The VaultStorage contract defines the storage for the Vault contracts\n * @author Origin Protocol Inc\n */\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { Address } from \"@openzeppelin/contracts/utils/Address.sol\";\n\nimport { IStrategy } from \"../interfaces/IStrategy.sol\";\nimport { Governable } from \"../governance/Governable.sol\";\nimport { OUSD } from \"../token/OUSD.sol\";\nimport { Initializable } from \"../utils/Initializable.sol\";\nimport \"../utils/Helpers.sol\";\n\ncontract VaultStorage is Initializable, Governable {\n using SafeERC20 for IERC20;\n\n event AssetSupported(address _asset);\n event AssetDefaultStrategyUpdated(address _asset, address _strategy);\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\n event StrategyApproved(address _addr);\n event StrategyRemoved(address _addr);\n event Mint(address _addr, uint256 _value);\n event Redeem(address _addr, uint256 _value);\n event CapitalPaused();\n event CapitalUnpaused();\n event RebasePaused();\n event RebaseUnpaused();\n event VaultBufferUpdated(uint256 _vaultBuffer);\n event OusdMetaStrategyUpdated(address _ousdMetaStrategy);\n event RedeemFeeUpdated(uint256 _redeemFeeBps);\n event PriceProviderUpdated(address _priceProvider);\n event AllocateThresholdUpdated(uint256 _threshold);\n event RebaseThresholdUpdated(uint256 _threshold);\n event StrategistUpdated(address _address);\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\n event TrusteeFeeBpsChanged(uint256 _basis);\n event TrusteeAddressChanged(address _address);\n event NetOusdMintForStrategyThresholdChanged(uint256 _threshold);\n event SwapperChanged(address _address);\n event SwapAllowedUndervalueChanged(uint256 _basis);\n event SwapSlippageChanged(address _asset, uint256 _basis);\n event Swapped(\n address indexed _fromAsset,\n address indexed _toAsset,\n uint256 _fromAssetAmount,\n uint256 _toAssetAmount\n );\n\n // Assets supported by the Vault, i.e. Stablecoins\n enum UnitConversion {\n DECIMALS,\n GETEXCHANGERATE\n }\n // Changed to fit into a single storage slot so the decimals needs to be recached\n struct Asset {\n // Note: OETHVaultCore doesn't use `isSupported` when minting,\n // redeeming or checking balance of assets.\n bool isSupported;\n UnitConversion unitConversion;\n uint8 decimals;\n // Max allowed slippage from the Oracle price when swapping collateral assets in basis points.\n // For example 40 == 0.4% slippage\n uint16 allowedOracleSlippageBps;\n }\n\n /// @dev mapping of supported vault assets to their configuration\n // slither-disable-next-line uninitialized-state\n mapping(address => Asset) internal assets;\n /// @dev list of all assets supported by the vault.\n // slither-disable-next-line uninitialized-state\n address[] internal allAssets;\n\n // Strategies approved for use by the Vault\n struct Strategy {\n bool isSupported;\n uint256 _deprecated; // Deprecated storage slot\n }\n /// @dev mapping of strategy contracts to their configiration\n mapping(address => Strategy) internal strategies;\n /// @dev list of all vault strategies\n address[] internal allStrategies;\n\n /// @notice Address of the Oracle price provider contract\n // slither-disable-next-line uninitialized-state\n address public priceProvider;\n /// @notice pause rebasing if true\n bool public rebasePaused = false;\n /// @notice pause operations that change the OToken supply.\n /// eg mint, redeem, allocate, mint/burn for strategy\n bool public capitalPaused = true;\n /// @notice Redemption fee in basis points. eg 50 = 0.5%\n uint256 public redeemFeeBps;\n /// @notice Percentage of assets to keep in Vault to handle (most) withdrawals. 100% = 1e18.\n uint256 public vaultBuffer;\n /// @notice OToken mints over this amount automatically allocate funds. 18 decimals.\n uint256 public autoAllocateThreshold;\n /// @notice OToken mints over this amount automatically rebase. 18 decimals.\n uint256 public rebaseThreshold;\n\n /// @dev Address of the OToken token. eg OUSD or OETH.\n // slither-disable-next-line uninitialized-state\n OUSD internal oUSD;\n\n //keccak256(\"OUSD.vault.governor.admin.impl\");\n bytes32 constant adminImplPosition =\n 0xa2bd3d3cf188a41358c8b401076eb59066b09dec5775650c0de4c55187d17bd9;\n\n // Address of the contract responsible for post rebase syncs with AMMs\n address private _deprecated_rebaseHooksAddr = address(0);\n\n // Deprecated: Address of Uniswap\n // slither-disable-next-line constable-states\n address private _deprecated_uniswapAddr = address(0);\n\n /// @notice Address of the Strategist\n address public strategistAddr = address(0);\n\n /// @notice Mapping of asset address to the Strategy that they should automatically\n // be allocated to\n // slither-disable-next-line uninitialized-state\n mapping(address => address) public assetDefaultStrategies;\n\n /// @notice Max difference between total supply and total value of assets. 18 decimals.\n // slither-disable-next-line uninitialized-state\n uint256 public maxSupplyDiff;\n\n /// @notice Trustee contract that can collect a percentage of yield\n address public trusteeAddress;\n\n /// @notice Amount of yield collected in basis points. eg 2000 = 20%\n uint256 public trusteeFeeBps;\n\n /// @dev Deprecated: Tokens that should be swapped for stablecoins\n address[] private _deprecated_swapTokens;\n\n uint256 constant MINT_MINIMUM_UNIT_PRICE = 0.998e18;\n\n /// @notice Metapool strategy that is allowed to mint/burn OTokens without changing collateral\n address public ousdMetaStrategy = address(0);\n\n /// @notice How much OTokens are currently minted by the strategy\n int256 public netOusdMintedForStrategy = 0;\n\n /// @notice How much net total OTokens are allowed to be minted by all strategies\n uint256 public netOusdMintForStrategyThreshold = 0;\n\n uint256 constant MIN_UNIT_PRICE_DRIFT = 0.7e18;\n uint256 constant MAX_UNIT_PRICE_DRIFT = 1.3e18;\n\n /// @notice Collateral swap configuration.\n /// @dev is packed into a single storage slot to save gas.\n struct SwapConfig {\n // Contract that swaps the vault's collateral assets\n address swapper;\n // Max allowed percentage the total value can drop below the total supply in basis points.\n // For example 100 == 1%\n uint16 allowedUndervalueBps;\n }\n SwapConfig internal swapConfig = SwapConfig(address(0), 0);\n\n // For future use\n uint256[50] private __gap;\n\n /**\n * @notice set the implementation for the admin, this needs to be in a base class else we cannot set it\n * @param newImpl address of the implementation\n */\n function setAdminImpl(address newImpl) external onlyGovernor {\n require(\n Address.isContract(newImpl),\n \"new implementation is not a contract\"\n );\n bytes32 position = adminImplPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, newImpl)\n }\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.gasEstimates" + ], + "": [ + "ast" + ] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} \ No newline at end of file diff --git a/contracts/test/strategies/nativeSsvStaking.holesky.fork-test.js b/contracts/test/strategies/nativeSsvStaking.holesky.fork-test.js index 8efa3605df..5dbfcfb5dd 100644 --- a/contracts/test/strategies/nativeSsvStaking.holesky.fork-test.js +++ b/contracts/test/strategies/nativeSsvStaking.holesky.fork-test.js @@ -47,7 +47,7 @@ describe("Holesky ForkTest: Native SSV Staking Strategy", function () { await mintTest(fixture, josh, weth, "32"); }); - it.only("Should mint using WETH and deposit to a validator", async () => { + it("Should mint using WETH and deposit to a validator", async () => { const { nativeStakingSSVStrategy, josh, weth } = fixture; await mintTest(fixture, josh, weth, "32");