From 51ae54dc428ba73186893b50197c299f4258609b Mon Sep 17 00:00:00 2001 From: Evgenii Date: Thu, 26 Dec 2024 17:26:33 +0400 Subject: [PATCH 01/24] feat: add script that allows pass pattern of tes files, for integration with depot repo --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index bb806bd40..872640ff9 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "test:integration:scratch:fulltrace": "INTEGRATION_WITH_CSM=off INTEGRATION_WITH_SCRATCH_DEPLOY=on hardhat test test/integration/**/*.ts --fulltrace --disabletracer", "test:integration:fork:local": "hardhat test test/integration/**/*.ts --network local", "test:integration:fork:mainnet": "hardhat test test/integration/**/*.ts --network mainnet-fork", + "test:integration:fork:mainnet:custom": "hardhat test --network mainnet-fork", "typecheck": "tsc --noEmit", "prepare": "husky", "abis:extract": "hardhat abis:extract", From 590460964e84fca903514604070b338425468f69 Mon Sep 17 00:00:00 2001 From: Aleksei Potapkin Date: Thu, 2 Jan 2025 17:30:37 +0200 Subject: [PATCH 02/24] fix: get rig of double division on shares to eth conversion --- contracts/0.4.24/Lido.sol | 15 +++++++++ contracts/0.4.24/StETH.sol | 34 ++++++++++++++------ test/0.4.24/lido/lido.externalShares.test.ts | 20 ++++++------ 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index b217aad2e..4668bff76 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -947,6 +947,21 @@ contract Lido is Versioned, StETHPermit, AragonApp { return internalEther.add(_getExternalEther(internalEther)); } + /// @dev the numerator (in ether) of the share rate for StETH conversion between shares and ether and vice versa. + /// using the numerator and denominator different from totalShares and totalPooledEther allows to: + /// - avoid double precision loss on additional division on external ether calculations + /// - optimize gas cost of conversions between shares and ether + function _getShareRateNumerator() internal view returns (uint256) { + return _getInternalEther(); + } + + /// @dev the denominator (in shares) of the share rate for StETH conversion between shares and ether and vice versa. + function _getShareRateDenominator() internal view returns (uint256) { + uint256 externalShares = EXTERNAL_SHARES_POSITION.getStorageUint256(); + uint256 internalShares = _getTotalShares() - externalShares; + return internalShares; + } + /// @notice Calculate the maximum amount of external shares that can be minted while maintaining /// maximum allowed external ratio limits /// @return Maximum amount of external shares that can be minted diff --git a/contracts/0.4.24/StETH.sol b/contracts/0.4.24/StETH.sol index 32e384605..3c5b6c610 100644 --- a/contracts/0.4.24/StETH.sol +++ b/contracts/0.4.24/StETH.sol @@ -303,8 +303,8 @@ contract StETH is IERC20, Pausable { */ function getSharesByPooledEth(uint256 _ethAmount) public view returns (uint256) { return _ethAmount - .mul(_getTotalShares()) - .div(_getTotalPooledEther()); + .mul(_getShareRateDenominator()) // denominator in shares + .div(_getShareRateNumerator()); // numerator in ether } /** @@ -312,8 +312,8 @@ contract StETH is IERC20, Pausable { */ function getPooledEthByShares(uint256 _sharesAmount) public view returns (uint256) { return _sharesAmount - .mul(_getTotalPooledEther()) - .div(_getTotalShares()); + .mul(_getShareRateNumerator()) // numerator in ether + .div(_getShareRateDenominator()); // denominator in shares } /** @@ -322,14 +322,14 @@ contract StETH is IERC20, Pausable { * for `shareRate >= 0.5`, `getSharesByPooledEth(getPooledEthBySharesRoundUp(1))` will be 1. */ function getPooledEthBySharesRoundUp(uint256 _sharesAmount) public view returns (uint256 etherAmount) { - uint256 totalEther = _getTotalPooledEther(); - uint256 totalShares = _getTotalShares(); + uint256 numeratorInEther = _getShareRateNumerator(); + uint256 denominatorInShares = _getShareRateDenominator(); etherAmount = _sharesAmount - .mul(totalEther) - .div(totalShares); + .mul(numeratorInEther) + .div(denominatorInShares); - if (etherAmount.mul(totalShares) != _sharesAmount.mul(totalEther)) { + if (_sharesAmount.mul(numeratorInEther) != etherAmount.mul(denominatorInShares)) { ++etherAmount; } } @@ -389,6 +389,22 @@ contract StETH is IERC20, Pausable { */ function _getTotalPooledEther() internal view returns (uint256); + /** + * @return the numerator of the protocol's share rate (in ether). + * @dev used to convert shares to tokens and vice versa. + */ + function _getShareRateNumerator() internal view returns (uint256) { + return _getTotalPooledEther(); + } + + /** + * @return the denominator of the protocol's share rate (in shares). + * @dev used to convert shares to tokens and vice versa. + */ + function _getShareRateDenominator() internal view returns (uint256) { + return _getTotalShares(); + } + /** * @notice Moves `_amount` tokens from `_sender` to `_recipient`. * Emits a `Transfer` event. diff --git a/test/0.4.24/lido/lido.externalShares.test.ts b/test/0.4.24/lido/lido.externalShares.test.ts index 5910e97c5..c98eb15a1 100644 --- a/test/0.4.24/lido/lido.externalShares.test.ts +++ b/test/0.4.24/lido/lido.externalShares.test.ts @@ -46,12 +46,12 @@ describe("Lido.sol:externalShares", () => { accountingSigner = await impersonate(await locator.accounting(), ether("1")); // Add some ether to the protocol - await lido.connect(whale).submit(ZeroAddress, { value: 1000n }); + await lido.connect(whale).submit(ZeroAddress, { value: ether("1000") }); // Burn some shares to make share rate fractional const burner = await impersonate(await locator.burner(), ether("1")); - await lido.connect(whale).transfer(burner, 500n); - await lido.connect(burner).burnShares(500n); + await lido.connect(whale).transfer(burner, ether("500")); + await lido.connect(burner).burnShares(ether("500")); }); beforeEach(async () => (originalState = await Snapshot.take())); @@ -199,16 +199,16 @@ describe("Lido.sol:externalShares", () => { // Increase the external ether limit to 10% await lido.setMaxExternalRatioBP(maxExternalRatioBP); - const amountToMint = await lido.getMaxMintableExternalShares(); - const etherToMint = await lido.getPooledEthByShares(amountToMint); + const sharesToMint = 1n; + const etherToMint = await lido.getPooledEthByShares(sharesToMint); - await expect(lido.connect(accountingSigner).mintExternalShares(whale, amountToMint)) + await expect(lido.connect(accountingSigner).mintExternalShares(whale, sharesToMint)) .to.emit(lido, "Transfer") .withArgs(ZeroAddress, whale, etherToMint) .to.emit(lido, "TransferShares") - .withArgs(ZeroAddress, whale, amountToMint) + .withArgs(ZeroAddress, whale, sharesToMint) .to.emit(lido, "ExternalSharesMinted") - .withArgs(whale, amountToMint, etherToMint); + .withArgs(whale, sharesToMint, etherToMint); // Verify external balance was increased const externalEther = await lido.getExternalEther(); @@ -280,11 +280,11 @@ describe("Lido.sol:externalShares", () => { // Burn partial amount await lido.connect(accountingSigner).burnExternalShares(150n); - expect(await lido.getExternalEther()).to.equal(150n); + expect(await lido.getExternalShares()).to.equal(150n); // Burn remaining await lido.connect(accountingSigner).burnExternalShares(150n); - expect(await lido.getExternalEther()).to.equal(0n); + expect(await lido.getExternalShares()).to.equal(0n); }); }); From 4e18178357be057ef3cea14e1b069973abe47cd2 Mon Sep 17 00:00:00 2001 From: Aleksei Potapkin Date: Fri, 3 Jan 2025 17:45:37 +0200 Subject: [PATCH 03/24] feat: make VaultHub pausable --- contracts/0.8.25/utils/OZPausableUntil.sol | 41 +++++++++ contracts/0.8.25/vaults/VaultHub.sol | 12 ++- contracts/common/lib/UnstructuredStorage.sol | 38 ++++++++ contracts/common/utils/PausableUntil.sol | 97 ++++++++++++++++++++ 4 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 contracts/0.8.25/utils/OZPausableUntil.sol create mode 100644 contracts/common/lib/UnstructuredStorage.sol create mode 100644 contracts/common/utils/PausableUntil.sol diff --git a/contracts/0.8.25/utils/OZPausableUntil.sol b/contracts/0.8.25/utils/OZPausableUntil.sol new file mode 100644 index 000000000..2ea479bb2 --- /dev/null +++ b/contracts/0.8.25/utils/OZPausableUntil.sol @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +// See contracts/COMPILERS.md +pragma solidity 0.8.25; + +import {PausableUntil} from "contracts/common/utils/PausableUntil.sol"; +import {AccessControlEnumerableUpgradeable} from "contracts/openzeppelin/5.0.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; + +/// @title PausableAccessControlEnumerableUpgradeable aka PausableACEU +/// @author folkyatina +abstract contract OZPausableUntil is PausableUntil, AccessControlEnumerableUpgradeable { + /// @notice role that allows to pause the hub + bytes32 public constant PAUSE_ROLE = keccak256("OZPausableUntil.PauseRole"); + /// @notice role that allows to resume the hub + bytes32 public constant RESUME_ROLE = keccak256("OZPausableUntil.ResumeRole"); + + /// @notice Resume withdrawal requests placement and finalization + /// @dev Contract is deployed in paused state and should be resumed explicitly + function resume() external onlyRole(RESUME_ROLE) { + _resume(); + } + + /// @notice Pause withdrawal requests placement and finalization. Claiming finalized requests will still be available + /// @param _duration pause duration in seconds (use `PAUSE_INFINITELY` for unlimited) + /// @dev Reverts if contract is already paused + /// @dev Reverts reason if sender has no `PAUSE_ROLE` + /// @dev Reverts if zero duration is passed + function pauseFor(uint256 _duration) external onlyRole(PAUSE_ROLE) { + _pauseFor(_duration); + } + + /// @notice Pause withdrawal requests placement and finalization. Claiming finalized requests will still be available + /// @param _pauseUntilInclusive the last second to pause until inclusive + /// @dev Reverts if the timestamp is in the past + /// @dev Reverts if sender has no `PAUSE_ROLE` + /// @dev Reverts if contract is already paused + function pauseUntil(uint256 _pauseUntilInclusive) external onlyRole(PAUSE_ROLE) { + _pauseUntil(_pauseUntilInclusive); + } +} diff --git a/contracts/0.8.25/vaults/VaultHub.sol b/contracts/0.8.25/vaults/VaultHub.sol index b8e6af96d..ab3e9ff93 100644 --- a/contracts/0.8.25/vaults/VaultHub.sol +++ b/contracts/0.8.25/vaults/VaultHub.sol @@ -12,6 +12,8 @@ import {IStakingVault} from "./interfaces/IStakingVault.sol"; import {ILido as IStETH} from "../interfaces/ILido.sol"; import {IBeaconProxy} from "./interfaces/IBeaconProxy.sol"; +import {OZPausableUntil} from "../utils/OZPausableUntil.sol"; + import {Math256} from "contracts/common/lib/Math256.sol"; /// @notice VaultHub is a contract that manages vaults connected to the Lido protocol @@ -19,7 +21,7 @@ import {Math256} from "contracts/common/lib/Math256.sol"; /// It also allows to force rebalance of the vaults /// Also, it passes the report from the accounting oracle to the vaults and charges fees /// @author folkyatina -abstract contract VaultHub is AccessControlEnumerableUpgradeable { +abstract contract VaultHub is AccessControlEnumerableUpgradeable, OZPausableUntil { /// @custom:storage-location erc7201:VaultHub struct VaultHubStorage { /// @notice vault sockets with vaults connected to the hub @@ -217,7 +219,7 @@ abstract contract VaultHub is AccessControlEnumerableUpgradeable { /// @param _vault vault address /// @dev msg.sender should be vault's owner /// @dev vault's `mintedShares` should be zero - function voluntaryDisconnect(address _vault) external { + function voluntaryDisconnect(address _vault) external whenResumed { if (_vault == address(0)) revert ZeroArgument("_vault"); _vaultAuth(_vault, "disconnect"); @@ -229,7 +231,7 @@ abstract contract VaultHub is AccessControlEnumerableUpgradeable { /// @param _recipient address of the receiver /// @param _amountOfShares amount of stETH shares to mint /// @dev msg.sender should be vault's owner - function mintSharesBackedByVault(address _vault, address _recipient, uint256 _amountOfShares) external { + function mintSharesBackedByVault(address _vault, address _recipient, uint256 _amountOfShares) external whenResumed { if (_vault == address(0)) revert ZeroArgument("_vault"); if (_recipient == address(0)) revert ZeroArgument("_recipient"); if (_amountOfShares == 0) revert ZeroArgument("_amountOfShares"); @@ -268,7 +270,7 @@ abstract contract VaultHub is AccessControlEnumerableUpgradeable { /// @param _amountOfShares amount of shares to burn /// @dev msg.sender should be vault's owner /// @dev VaultHub must have all the stETH on its balance - function burnSharesBackedByVault(address _vault, uint256 _amountOfShares) public { + function burnSharesBackedByVault(address _vault, uint256 _amountOfShares) public whenResumed { if (_vault == address(0)) revert ZeroArgument("_vault"); if (_amountOfShares == 0) revert ZeroArgument("_amountOfShares"); _vaultAuth(_vault, "burn"); @@ -334,7 +336,7 @@ abstract contract VaultHub is AccessControlEnumerableUpgradeable { /// @notice rebalances the vault by writing off the amount of ether equal /// to `msg.value` from the vault's minted stETH /// @dev msg.sender should be vault's contract - function rebalance() external payable { + function rebalance() external payable whenResumed { if (msg.value == 0) revert ZeroArgument("msg.value"); VaultSocket storage socket = _connectedSocket(msg.sender); diff --git a/contracts/common/lib/UnstructuredStorage.sol b/contracts/common/lib/UnstructuredStorage.sol new file mode 100644 index 000000000..e2d835f1e --- /dev/null +++ b/contracts/common/lib/UnstructuredStorage.sol @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2023 Lido , Aragon +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +library UnstructuredStorage { + function getStorageBool(bytes32 position) internal view returns (bool data) { + assembly { data := sload(position) } + } + + function getStorageAddress(bytes32 position) internal view returns (address data) { + assembly { data := sload(position) } + } + + function getStorageBytes32(bytes32 position) internal view returns (bytes32 data) { + assembly { data := sload(position) } + } + + function getStorageUint256(bytes32 position) internal view returns (uint256 data) { + assembly { data := sload(position) } + } + + function setStorageBool(bytes32 position, bool data) internal { + assembly { sstore(position, data) } + } + + function setStorageAddress(bytes32 position, address data) internal { + assembly { sstore(position, data) } + } + + function setStorageBytes32(bytes32 position, bytes32 data) internal { + assembly { sstore(position, data) } + } + + function setStorageUint256(bytes32 position, uint256 data) internal { + assembly { sstore(position, data) } + } +} diff --git a/contracts/common/utils/PausableUntil.sol b/contracts/common/utils/PausableUntil.sol new file mode 100644 index 000000000..20aa47c01 --- /dev/null +++ b/contracts/common/utils/PausableUntil.sol @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.9; + +import {UnstructuredStorage} from "contracts/common/lib/UnstructuredStorage.sol"; + + +abstract contract PausableUntil { + using UnstructuredStorage for bytes32; + + /// Contract resume/pause control storage slot + bytes32 internal constant RESUME_SINCE_TIMESTAMP_POSITION = keccak256("lido.PausableUntil.resumeSinceTimestamp"); + /// Special value for the infinite pause + uint256 public constant PAUSE_INFINITELY = type(uint256).max; + + /// @notice Emitted when paused by the `pauseFor` or `pauseUntil` call + event Paused(uint256 duration); + /// @notice Emitted when resumed by the `resume` call + event Resumed(); + + error ZeroPauseDuration(); + error PausedExpected(); + error ResumedExpected(); + error PauseUntilMustBeInFuture(); + + /// @notice Reverts when paused + modifier whenResumed() { + _checkResumed(); + _; + } + + function _checkPaused() internal view { + if (!isPaused()) { + revert PausedExpected(); + } + } + + function _checkResumed() internal view { + if (isPaused()) { + revert ResumedExpected(); + } + } + + /// @notice Returns whether the contract is paused + function isPaused() public view returns (bool) { + return block.timestamp < RESUME_SINCE_TIMESTAMP_POSITION.getStorageUint256(); + } + + /// @notice Returns one of: + /// - PAUSE_INFINITELY if paused infinitely returns + /// - first second when get contract get resumed if paused for specific duration + /// - some timestamp in past if not paused + function getResumeSinceTimestamp() external view returns (uint256) { + return RESUME_SINCE_TIMESTAMP_POSITION.getStorageUint256(); + } + + function _resume() internal { + _checkPaused(); + RESUME_SINCE_TIMESTAMP_POSITION.setStorageUint256(block.timestamp); + emit Resumed(); + } + + function _pauseFor(uint256 _duration) internal { + _checkResumed(); + if (_duration == 0) revert ZeroPauseDuration(); + + uint256 resumeSince; + if (_duration == PAUSE_INFINITELY) { + resumeSince = PAUSE_INFINITELY; + } else { + resumeSince = block.timestamp + _duration; + } + _setPausedState(resumeSince); + } + + function _pauseUntil(uint256 _pauseUntilInclusive) internal { + _checkResumed(); + if (_pauseUntilInclusive < block.timestamp) revert PauseUntilMustBeInFuture(); + + uint256 resumeSince; + if (_pauseUntilInclusive != PAUSE_INFINITELY) { + resumeSince = _pauseUntilInclusive + 1; + } else { + resumeSince = PAUSE_INFINITELY; + } + _setPausedState(resumeSince); + } + + function _setPausedState(uint256 _resumeSince) internal { + RESUME_SINCE_TIMESTAMP_POSITION.setStorageUint256(_resumeSince); + if (_resumeSince == PAUSE_INFINITELY) { + emit Paused(PAUSE_INFINITELY); + } else { + emit Paused(_resumeSince - block.timestamp); + } + } +} From a48c18a62a37047817da8a4e378f298f38d5c1ff Mon Sep 17 00:00:00 2001 From: Aleksei Potapkin Date: Fri, 3 Jan 2025 17:52:49 +0200 Subject: [PATCH 04/24] chore: some solhint fixes --- contracts/common/lib/UnstructuredStorage.sol | 1 + contracts/common/utils/PausableUntil.sol | 1 + package.json | 2 +- yarn.lock | 12 ++++++------ 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/contracts/common/lib/UnstructuredStorage.sol b/contracts/common/lib/UnstructuredStorage.sol index e2d835f1e..04d9cbb6f 100644 --- a/contracts/common/lib/UnstructuredStorage.sol +++ b/contracts/common/lib/UnstructuredStorage.sol @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2023 Lido , Aragon // SPDX-License-Identifier: MIT +// solhint-disable-next-line lido/fixed-compiler-version pragma solidity ^0.8.9; library UnstructuredStorage { diff --git a/contracts/common/utils/PausableUntil.sol b/contracts/common/utils/PausableUntil.sol index 20aa47c01..024028400 100644 --- a/contracts/common/utils/PausableUntil.sol +++ b/contracts/common/utils/PausableUntil.sol @@ -1,5 +1,6 @@ // SPDX-FileCopyrightText: 2023 Lido // SPDX-License-Identifier: GPL-3.0 +// solhint-disable-next-line lido/fixed-compiler-version pragma solidity ^0.8.9; import {UnstructuredStorage} from "contracts/common/lib/UnstructuredStorage.sol"; diff --git a/package.json b/package.json index a8711c17c..e5eea655b 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "lint-staged": "15.2.10", "prettier": "3.4.1", "prettier-plugin-solidity": "1.4.1", - "solhint": "5.0.3", + "solhint": "^5.0.4", "solhint-plugin-lido": "0.0.4", "solidity-coverage": "0.8.14", "ts-node": "10.9.2", diff --git a/yarn.lock b/yarn.lock index c910ac91b..2bed96f03 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8095,7 +8095,7 @@ __metadata: openzeppelin-solidity: "npm:2.0.0" prettier: "npm:3.4.1" prettier-plugin-solidity: "npm:1.4.1" - solhint: "npm:5.0.3" + solhint: "npm:^5.0.4" solhint-plugin-lido: "npm:0.0.4" solidity-coverage: "npm:0.8.14" ts-node: "npm:10.9.2" @@ -10638,11 +10638,11 @@ __metadata: languageName: node linkType: hard -"solhint@npm:5.0.3": - version: 5.0.3 - resolution: "solhint@npm:5.0.3" +"solhint@npm:^5.0.4": + version: 5.0.4 + resolution: "solhint@npm:5.0.4" dependencies: - "@solidity-parser/parser": "npm:^0.18.0" + "@solidity-parser/parser": "npm:^0.19.0" ajv: "npm:^6.12.6" antlr4: "npm:^4.13.1-patch-1" ast-parents: "npm:^0.0.1" @@ -10666,7 +10666,7 @@ __metadata: optional: true bin: solhint: solhint.js - checksum: 10c0/262e86a8932d7d4d6ebae2a9d7317749e5068092e7cdf4caf07ac39fc72bd2c94f3907daaedcad37592ec001b57caed6dc5ed7c3fd6cd18b6443182f38c1715e + checksum: 10c0/70058b23c8746762fc88d48b571c4571719913ca7f3c582a55c123ad9ba38976a2338782025fbb9643bb75bfad18bf3dce1b71e500df6d99589e9814fbcce1d7 languageName: node linkType: hard From b2426701d60e7717e665109d420b833254886297 Mon Sep 17 00:00:00 2001 From: Aleksei Potapkin Date: Fri, 3 Jan 2025 17:56:52 +0200 Subject: [PATCH 05/24] chore: fix some comments and imports --- contracts/0.8.25/utils/OZPausableUntil.sol | 2 +- contracts/0.8.25/vaults/VaultHub.sol | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/0.8.25/utils/OZPausableUntil.sol b/contracts/0.8.25/utils/OZPausableUntil.sol index 2ea479bb2..bb1df6020 100644 --- a/contracts/0.8.25/utils/OZPausableUntil.sol +++ b/contracts/0.8.25/utils/OZPausableUntil.sol @@ -7,7 +7,7 @@ pragma solidity 0.8.25; import {PausableUntil} from "contracts/common/utils/PausableUntil.sol"; import {AccessControlEnumerableUpgradeable} from "contracts/openzeppelin/5.0.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; -/// @title PausableAccessControlEnumerableUpgradeable aka PausableACEU +/// @title OZPausableUntil is a PausableUntil reference implementation using OpenZeppelin's AccessControlEnumerableUpgradeable /// @author folkyatina abstract contract OZPausableUntil is PausableUntil, AccessControlEnumerableUpgradeable { /// @notice role that allows to pause the hub diff --git a/contracts/0.8.25/vaults/VaultHub.sol b/contracts/0.8.25/vaults/VaultHub.sol index ab3e9ff93..11ecbf8e7 100644 --- a/contracts/0.8.25/vaults/VaultHub.sol +++ b/contracts/0.8.25/vaults/VaultHub.sol @@ -5,7 +5,6 @@ pragma solidity 0.8.25; import {IBeacon} from "@openzeppelin/contracts-v5.0.2/proxy/beacon/IBeacon.sol"; -import {AccessControlEnumerableUpgradeable} from "contracts/openzeppelin/5.0.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; import {OwnableUpgradeable} from "contracts/openzeppelin/5.0.2/upgradeable/access/OwnableUpgradeable.sol"; import {IStakingVault} from "./interfaces/IStakingVault.sol"; @@ -21,7 +20,7 @@ import {Math256} from "contracts/common/lib/Math256.sol"; /// It also allows to force rebalance of the vaults /// Also, it passes the report from the accounting oracle to the vaults and charges fees /// @author folkyatina -abstract contract VaultHub is AccessControlEnumerableUpgradeable, OZPausableUntil { +abstract contract VaultHub is OZPausableUntil { /// @custom:storage-location erc7201:VaultHub struct VaultHubStorage { /// @notice vault sockets with vaults connected to the hub From 9822b47c2c4951b3f50268fd3eb384363ee8008f Mon Sep 17 00:00:00 2001 From: Aleksei Potapkin Date: Tue, 7 Jan 2025 15:08:20 +0200 Subject: [PATCH 06/24] chore: comments and naming --- contracts/0.8.25/utils/OZPausableUntil.sol | 41 --------------- .../0.8.25/utils/PausableUntilWithRoles.sol | 52 +++++++++++++++++++ contracts/0.8.25/vaults/VaultHub.sol | 4 +- 3 files changed, 54 insertions(+), 43 deletions(-) delete mode 100644 contracts/0.8.25/utils/OZPausableUntil.sol create mode 100644 contracts/0.8.25/utils/PausableUntilWithRoles.sol diff --git a/contracts/0.8.25/utils/OZPausableUntil.sol b/contracts/0.8.25/utils/OZPausableUntil.sol deleted file mode 100644 index bb1df6020..000000000 --- a/contracts/0.8.25/utils/OZPausableUntil.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Lido -// SPDX-License-Identifier: GPL-3.0 - -// See contracts/COMPILERS.md -pragma solidity 0.8.25; - -import {PausableUntil} from "contracts/common/utils/PausableUntil.sol"; -import {AccessControlEnumerableUpgradeable} from "contracts/openzeppelin/5.0.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; - -/// @title OZPausableUntil is a PausableUntil reference implementation using OpenZeppelin's AccessControlEnumerableUpgradeable -/// @author folkyatina -abstract contract OZPausableUntil is PausableUntil, AccessControlEnumerableUpgradeable { - /// @notice role that allows to pause the hub - bytes32 public constant PAUSE_ROLE = keccak256("OZPausableUntil.PauseRole"); - /// @notice role that allows to resume the hub - bytes32 public constant RESUME_ROLE = keccak256("OZPausableUntil.ResumeRole"); - - /// @notice Resume withdrawal requests placement and finalization - /// @dev Contract is deployed in paused state and should be resumed explicitly - function resume() external onlyRole(RESUME_ROLE) { - _resume(); - } - - /// @notice Pause withdrawal requests placement and finalization. Claiming finalized requests will still be available - /// @param _duration pause duration in seconds (use `PAUSE_INFINITELY` for unlimited) - /// @dev Reverts if contract is already paused - /// @dev Reverts reason if sender has no `PAUSE_ROLE` - /// @dev Reverts if zero duration is passed - function pauseFor(uint256 _duration) external onlyRole(PAUSE_ROLE) { - _pauseFor(_duration); - } - - /// @notice Pause withdrawal requests placement and finalization. Claiming finalized requests will still be available - /// @param _pauseUntilInclusive the last second to pause until inclusive - /// @dev Reverts if the timestamp is in the past - /// @dev Reverts if sender has no `PAUSE_ROLE` - /// @dev Reverts if contract is already paused - function pauseUntil(uint256 _pauseUntilInclusive) external onlyRole(PAUSE_ROLE) { - _pauseUntil(_pauseUntilInclusive); - } -} diff --git a/contracts/0.8.25/utils/PausableUntilWithRoles.sol b/contracts/0.8.25/utils/PausableUntilWithRoles.sol new file mode 100644 index 000000000..e2e0a7371 --- /dev/null +++ b/contracts/0.8.25/utils/PausableUntilWithRoles.sol @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +// See contracts/COMPILERS.md +pragma solidity 0.8.25; + +import {PausableUntil} from "contracts/common/utils/PausableUntil.sol"; +import {AccessControlEnumerableUpgradeable} from "contracts/openzeppelin/5.0.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; + +/** + * @title PausableUntilWithRoles + * @author folkyatina + * @notice a `PausableUntil` reference implementation using OpenZeppelin's `AccessControlEnumerableUpgradeable` + * @dev This contract is abstract and should be inherited by the actual contract that is using `whenNotPaused` modifier + * to actually block some functions on pause + */ +abstract contract PausableUntilWithRoles is PausableUntil, AccessControlEnumerableUpgradeable { + /// @notice role that allows to pause the contract + bytes32 public constant PAUSE_ROLE = keccak256("PausableUntilWithRoles.PauseRole"); + /// @notice role that allows to resume the contract + bytes32 public constant RESUME_ROLE = keccak256("PausableUntilWithRoles.ResumeRole"); + + /** + * @notice Resume the contract + * @dev Contract is deployed in paused state and should be resumed explicitly + */ + function resume() external onlyRole(RESUME_ROLE) { + _resume(); + } + + /** + * @notice Pause the contract + * @param _duration pause duration in seconds (use `PAUSE_INFINITELY` for unlimited) + * @dev Reverts if contract is already paused + * @dev Reverts reason if sender has no `PAUSE_ROLE` + * @dev Reverts if zero duration is passed + */ + function pauseFor(uint256 _duration) external onlyRole(PAUSE_ROLE) { + _pauseFor(_duration); + } + + /** + * @notice Pause the contract until a specific timestamp + * @param _pauseUntilInclusive the last second to pause until inclusive + * @dev Reverts if the timestamp is in the past + * @dev Reverts if sender has no `PAUSE_ROLE` + * @dev Reverts if contract is already paused + */ + function pauseUntil(uint256 _pauseUntilInclusive) external onlyRole(PAUSE_ROLE) { + _pauseUntil(_pauseUntilInclusive); + } +} diff --git a/contracts/0.8.25/vaults/VaultHub.sol b/contracts/0.8.25/vaults/VaultHub.sol index 11ecbf8e7..8ce4527fd 100644 --- a/contracts/0.8.25/vaults/VaultHub.sol +++ b/contracts/0.8.25/vaults/VaultHub.sol @@ -11,7 +11,7 @@ import {IStakingVault} from "./interfaces/IStakingVault.sol"; import {ILido as IStETH} from "../interfaces/ILido.sol"; import {IBeaconProxy} from "./interfaces/IBeaconProxy.sol"; -import {OZPausableUntil} from "../utils/OZPausableUntil.sol"; +import {PausableUntilWithRoles} from "../utils/PausableUntilWithRoles.sol"; import {Math256} from "contracts/common/lib/Math256.sol"; @@ -20,7 +20,7 @@ import {Math256} from "contracts/common/lib/Math256.sol"; /// It also allows to force rebalance of the vaults /// Also, it passes the report from the accounting oracle to the vaults and charges fees /// @author folkyatina -abstract contract VaultHub is OZPausableUntil { +abstract contract VaultHub is PausableUntilWithRoles { /// @custom:storage-location erc7201:VaultHub struct VaultHubStorage { /// @notice vault sockets with vaults connected to the hub From a36d64b9db696a406d47714d679d57a76320fbb1 Mon Sep 17 00:00:00 2001 From: Aleksei Potapkin Date: Tue, 7 Jan 2025 18:13:47 +0200 Subject: [PATCH 07/24] chore: fix dependency --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e5eea655b..069c8e125 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "lint-staged": "15.2.10", "prettier": "3.4.1", "prettier-plugin-solidity": "1.4.1", - "solhint": "^5.0.4", + "solhint": "5.0.4", "solhint-plugin-lido": "0.0.4", "solidity-coverage": "0.8.14", "ts-node": "10.9.2", From 46d1211da1dd2a9292b4756c7c01a5a9615d349b Mon Sep 17 00:00:00 2001 From: Aleksei Potapkin Date: Tue, 7 Jan 2025 18:16:03 +0200 Subject: [PATCH 08/24] chore: update lockfile --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2bed96f03..a8657fefc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8095,7 +8095,7 @@ __metadata: openzeppelin-solidity: "npm:2.0.0" prettier: "npm:3.4.1" prettier-plugin-solidity: "npm:1.4.1" - solhint: "npm:^5.0.4" + solhint: "npm:5.0.4" solhint-plugin-lido: "npm:0.0.4" solidity-coverage: "npm:0.8.14" ts-node: "npm:10.9.2" @@ -10638,7 +10638,7 @@ __metadata: languageName: node linkType: hard -"solhint@npm:^5.0.4": +"solhint@npm:5.0.4": version: 5.0.4 resolution: "solhint@npm:5.0.4" dependencies: From 6ed8363a5780b3d26e4aef9495bbbeda9b25f04d Mon Sep 17 00:00:00 2001 From: Aleksei Potapkin Date: Wed, 8 Jan 2025 15:27:11 +0200 Subject: [PATCH 09/24] test: tests for VaultHub pausability --- hardhat.config.ts | 9 +- package.json | 2 +- .../vaults/vaulthub/vaulthub.pausable.test.ts | 187 ++++++++++++++++++ 3 files changed, 194 insertions(+), 4 deletions(-) create mode 100644 test/0.8.25/vaults/vaulthub/vaulthub.pausable.test.ts diff --git a/hardhat.config.ts b/hardhat.config.ts index a8a1af019..15aa0a7f4 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -52,7 +52,7 @@ function loadAccounts(networkName: string) { const config: HardhatUserConfig = { defaultNetwork: "hardhat", gasReporter: { - enabled: true, + enabled: process.env.SKIP_GAS_REPORT ? false : true, }, networks: { "hardhat": { @@ -198,7 +198,10 @@ const config: HardhatUserConfig = { }, watcher: { test: { - tasks: [{ command: "test", params: { testFiles: ["{path}"] } }], + tasks: [ + { command: "compile", params: { quiet: true } }, + { command: "test", params: { noCompile: true, testFiles: ["{path}"] } }, + ], files: ["./test/**/*"], clearOnStart: true, start: "echo Running tests...", @@ -225,7 +228,7 @@ const config: HardhatUserConfig = { contractSizer: { alphaSort: false, disambiguatePaths: false, - runOnCompile: true, + runOnCompile: process.env.SKIP_CONTRACT_SIZE ? false : true, strict: true, except: ["template", "mocks", "@aragon", "openzeppelin", "test"], }, diff --git a/package.json b/package.json index 069c8e125..a276bfcf9 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "test:sequential": "hardhat test test/**/*.test.ts", "test:trace": "hardhat test test/**/*.test.ts --trace --disabletracer", "test:fulltrace": "hardhat test test/**/*.test.ts --fulltrace --disabletracer", - "test:watch": "hardhat watch test", + "test:watch": "SKIP_GAS_REPORT=true SKIP_CONTRACT_SIZE=true hardhat watch test", "test:integration": "hardhat test test/integration/**/*.ts", "test:integration:trace": "hardhat test test/integration/**/*.ts --trace --disabletracer", "test:integration:fulltrace": "hardhat test test/integration/**/*.ts --fulltrace --disabletracer", diff --git a/test/0.8.25/vaults/vaulthub/vaulthub.pausable.test.ts b/test/0.8.25/vaults/vaulthub/vaulthub.pausable.test.ts new file mode 100644 index 000000000..feb145fa0 --- /dev/null +++ b/test/0.8.25/vaults/vaulthub/vaulthub.pausable.test.ts @@ -0,0 +1,187 @@ +import { expect } from "chai"; +import { ethers } from "hardhat"; + +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; +import { time } from "@nomicfoundation/hardhat-network-helpers"; + +import { StETH__HarnessForVaultHub, VaultHub } from "typechain-types"; + +import { ether, MAX_UINT256 } from "lib"; + +import { deployLidoLocator } from "test/deploy"; +import { Snapshot } from "test/suite"; + +describe("VaultHub.sol:pausableUntil", () => { + let deployer: HardhatEthersSigner; + let user: HardhatEthersSigner; + let stranger: HardhatEthersSigner; + + let vaultHub: VaultHub; + let steth: StETH__HarnessForVaultHub; + + let originalState: string; + + before(async () => { + [deployer, user, stranger] = await ethers.getSigners(); + + const locator = await deployLidoLocator(); + steth = await ethers.deployContract("StETH__HarnessForVaultHub", [user], { value: ether("1.0") }); + + const vaultHubImpl = await ethers.deployContract("Accounting", [locator, steth]); + const proxy = await ethers.deployContract("OssifiableProxy", [vaultHubImpl, deployer, new Uint8Array()]); + + const accounting = await ethers.getContractAt("Accounting", proxy); + await accounting.initialize(deployer); + + vaultHub = await ethers.getContractAt("Accounting", proxy, user); + await accounting.grantRole(await vaultHub.PAUSE_ROLE(), user); + await accounting.grantRole(await vaultHub.RESUME_ROLE(), user); + }); + + beforeEach(async () => (originalState = await Snapshot.take())); + + afterEach(async () => await Snapshot.restore(originalState)); + + context("Constants", () => { + it("Returns the PAUSE_INFINITELY variable", async () => { + expect(await vaultHub.PAUSE_INFINITELY()).to.equal(MAX_UINT256); + }); + }); + + context("initialState", () => { + it("isPaused returns false", async () => { + expect(await vaultHub.isPaused()).to.equal(false); + }); + + it("getResumeSinceTimestamp returns 0", async () => { + expect(await vaultHub.getResumeSinceTimestamp()).to.equal(0); + }); + }); + + context("pauseFor", () => { + it("reverts if no PAUSE_ROLE", async () => { + await expect(vaultHub.connect(stranger).pauseFor(1000n)) + .to.be.revertedWithCustomError(vaultHub, "AccessControlUnauthorizedAccount") + .withArgs(stranger, await vaultHub.PAUSE_ROLE()); + }); + + it("reverts if zero pause duration", async () => { + await expect(vaultHub.pauseFor(0n)).to.be.revertedWithCustomError(vaultHub, "ZeroPauseDuration"); + }); + + it("reverts if paused", async () => { + await expect(vaultHub.pauseFor(1000n)).to.emit(vaultHub, "Paused"); + + await expect(vaultHub.pauseFor(1000n)).to.be.revertedWithCustomError(vaultHub, "ResumedExpected"); + }); + + it("emits Paused event and change state", async () => { + await expect(vaultHub.pauseFor(1000n)).to.emit(vaultHub, "Paused").withArgs(1000n); + + expect(await vaultHub.isPaused()).to.equal(true); + expect(await vaultHub.getResumeSinceTimestamp()).to.equal((await time.latest()) + 1000); + }); + + it("works for MAX_UINT256 duration", async () => { + await expect(vaultHub.pauseFor(MAX_UINT256)).to.emit(vaultHub, "Paused").withArgs(MAX_UINT256); + + expect(await vaultHub.isPaused()).to.equal(true); + expect(await vaultHub.getResumeSinceTimestamp()).to.equal(MAX_UINT256); + }); + }); + + context("pauseUntil", () => { + it("reverts if no PAUSE_ROLE", async () => { + await expect(vaultHub.connect(stranger).pauseUntil(1000n)) + .to.be.revertedWithCustomError(vaultHub, "AccessControlUnauthorizedAccount") + .withArgs(stranger, await vaultHub.PAUSE_ROLE()); + }); + + it("reverts if timestamp is in the past", async () => { + await expect(vaultHub.pauseUntil(0)).to.be.revertedWithCustomError(vaultHub, "PauseUntilMustBeInFuture"); + }); + + it("emits Paused event and change state", async () => { + const timestamp = await time.latest(); + + await expect(vaultHub.pauseUntil(timestamp + 1000)).to.emit(vaultHub, "Paused"); + // .withArgs(timestamp + 1000 - await time.latest()); // how to use last block timestamp in assertions + + expect(await vaultHub.isPaused()).to.equal(true); + expect(await vaultHub.getResumeSinceTimestamp()).to.greaterThanOrEqual((await time.latest()) + 1000); + }); + + it("works for MAX_UINT256 timestamp", async () => { + await expect(vaultHub.pauseUntil(MAX_UINT256)).to.emit(vaultHub, "Paused").withArgs(MAX_UINT256); + + expect(await vaultHub.isPaused()).to.equal(true); + expect(await vaultHub.getResumeSinceTimestamp()).to.equal(MAX_UINT256); + }); + }); + + context("resume", () => { + it("reverts if no RESUME_ROLE", async () => { + await expect(vaultHub.connect(stranger).resume()) + .to.be.revertedWithCustomError(vaultHub, "AccessControlUnauthorizedAccount") + .withArgs(stranger, await vaultHub.RESUME_ROLE()); + }); + + it("reverts if not paused", async () => { + await expect(vaultHub.resume()).to.be.revertedWithCustomError(vaultHub, "PausedExpected"); + }); + + it("reverts if already resumed", async () => { + await expect(vaultHub.pauseFor(1000n)).to.emit(vaultHub, "Paused"); + await expect(vaultHub.resume()).to.emit(vaultHub, "Resumed"); + + await expect(vaultHub.resume()).to.be.revertedWithCustomError(vaultHub, "PausedExpected"); + }); + + it("emits Resumed event and change state", async () => { + await expect(vaultHub.pauseFor(1000n)).to.emit(vaultHub, "Paused"); + + await expect(vaultHub.resume()).to.emit(vaultHub, "Resumed"); + + expect(await vaultHub.isPaused()).to.equal(false); + expect(await vaultHub.getResumeSinceTimestamp()).to.equal(await time.latest()); + }); + }); + + context("isPaused", () => { + beforeEach(async () => { + await expect(vaultHub.pauseFor(1000n)).to.emit(vaultHub, "Paused"); + expect(await vaultHub.isPaused()).to.equal(true); + }); + + it("reverts voluntaryDisconnect() if paused", async () => { + await expect(vaultHub.voluntaryDisconnect(user)).to.be.revertedWithCustomError(vaultHub, "ResumedExpected"); + }); + + it("reverts mintSharesBackedByVault() if paused", async () => { + await expect(vaultHub.mintSharesBackedByVault(stranger, user, 1000n)).to.be.revertedWithCustomError( + vaultHub, + "ResumedExpected", + ); + }); + + it("reverts burnSharesBackedByVault() if paused", async () => { + await expect(vaultHub.burnSharesBackedByVault(stranger, 1000n)).to.be.revertedWithCustomError( + vaultHub, + "ResumedExpected", + ); + }); + + it("reverts rebalance() if paused", async () => { + await expect(vaultHub.rebalance()).to.be.revertedWithCustomError(vaultHub, "ResumedExpected"); + }); + + it("reverts transferAndBurnSharesBackedByVault() if paused", async () => { + await steth.connect(user).approve(vaultHub, 1000n); + + await expect(vaultHub.transferAndBurnSharesBackedByVault(stranger, 1000n)).to.be.revertedWithCustomError( + vaultHub, + "ResumedExpected", + ); + }); + }); +}); From 8efb94ee1eaa17156a8ec18d6aedbc1986e203c4 Mon Sep 17 00:00:00 2001 From: Aleksei Potapkin Date: Wed, 8 Jan 2025 15:34:11 +0200 Subject: [PATCH 10/24] chore: better comments --- .../0.8.25/utils/PausableUntilWithRoles.sol | 9 ++--- contracts/common/utils/PausableUntil.sol | 35 ++++++++++--------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/contracts/0.8.25/utils/PausableUntilWithRoles.sol b/contracts/0.8.25/utils/PausableUntilWithRoles.sol index e2e0a7371..2fbce151a 100644 --- a/contracts/0.8.25/utils/PausableUntilWithRoles.sol +++ b/contracts/0.8.25/utils/PausableUntilWithRoles.sol @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 Lido +// SPDX-FileCopyrightText: 2025 Lido // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md @@ -9,10 +9,8 @@ import {AccessControlEnumerableUpgradeable} from "contracts/openzeppelin/5.0.2/u /** * @title PausableUntilWithRoles - * @author folkyatina - * @notice a `PausableUntil` reference implementation using OpenZeppelin's `AccessControlEnumerableUpgradeable` - * @dev This contract is abstract and should be inherited by the actual contract that is using `whenNotPaused` modifier - * to actually block some functions on pause + * @notice a `PausableUntil` implementation using OpenZeppelin's `AccessControlEnumerableUpgradeable` + * @dev the inheriting contract must use `whenNotPaused` modifier from `PausableUntil` to block some functions on pause */ abstract contract PausableUntilWithRoles is PausableUntil, AccessControlEnumerableUpgradeable { /// @notice role that allows to pause the contract @@ -22,7 +20,6 @@ abstract contract PausableUntilWithRoles is PausableUntil, AccessControlEnumerab /** * @notice Resume the contract - * @dev Contract is deployed in paused state and should be resumed explicitly */ function resume() external onlyRole(RESUME_ROLE) { _resume(); diff --git a/contracts/common/utils/PausableUntil.sol b/contracts/common/utils/PausableUntil.sol index 024028400..4ef0988a7 100644 --- a/contracts/common/utils/PausableUntil.sol +++ b/contracts/common/utils/PausableUntil.sol @@ -1,11 +1,14 @@ -// SPDX-FileCopyrightText: 2023 Lido +// SPDX-FileCopyrightText: 2025 Lido // SPDX-License-Identifier: GPL-3.0 // solhint-disable-next-line lido/fixed-compiler-version pragma solidity ^0.8.9; import {UnstructuredStorage} from "contracts/common/lib/UnstructuredStorage.sol"; - +/** + * @title PausableUntil + * @notice allows to pause the contract for a specific duration or indefinitely + */ abstract contract PausableUntil { using UnstructuredStorage for bytes32; @@ -24,24 +27,12 @@ abstract contract PausableUntil { error ResumedExpected(); error PauseUntilMustBeInFuture(); - /// @notice Reverts when paused + /// @notice Reverts if paused modifier whenResumed() { _checkResumed(); _; } - function _checkPaused() internal view { - if (!isPaused()) { - revert PausedExpected(); - } - } - - function _checkResumed() internal view { - if (isPaused()) { - revert ResumedExpected(); - } - } - /// @notice Returns whether the contract is paused function isPaused() public view returns (bool) { return block.timestamp < RESUME_SINCE_TIMESTAMP_POSITION.getStorageUint256(); @@ -49,12 +40,24 @@ abstract contract PausableUntil { /// @notice Returns one of: /// - PAUSE_INFINITELY if paused infinitely returns - /// - first second when get contract get resumed if paused for specific duration + /// - the timestamp when the contract get resumed if paused for specific duration /// - some timestamp in past if not paused function getResumeSinceTimestamp() external view returns (uint256) { return RESUME_SINCE_TIMESTAMP_POSITION.getStorageUint256(); } + function _checkPaused() internal view { + if (!isPaused()) { + revert PausedExpected(); + } + } + + function _checkResumed() internal view { + if (isPaused()) { + revert ResumedExpected(); + } + } + function _resume() internal { _checkPaused(); RESUME_SINCE_TIMESTAMP_POSITION.setStorageUint256(block.timestamp); From 9cf4bb936e109a29f25915c9a2b11adc77682b9c Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Thu, 9 Jan 2025 11:32:38 +0000 Subject: [PATCH 11/24] chore: update develop from master --- .../workflows/tests-integration-mainnet.yml | 2 +- .../workflows/tests-integration-scratch.yml | 2 +- README.md | 6 +- package.json | 2 +- poetry.lock | 458 +++++++++++------- yarn.lock | 26 +- 6 files changed, 290 insertions(+), 206 deletions(-) diff --git a/.github/workflows/tests-integration-mainnet.yml b/.github/workflows/tests-integration-mainnet.yml index 37986f198..dfbb6d082 100644 --- a/.github/workflows/tests-integration-mainnet.yml +++ b/.github/workflows/tests-integration-mainnet.yml @@ -10,7 +10,7 @@ jobs: services: hardhat-node: - image: ghcr.io/lidofinance/hardhat-node:2.22.12 + image: ghcr.io/lidofinance/hardhat-node:2.22.17 ports: - 8545:8545 env: diff --git a/.github/workflows/tests-integration-scratch.yml b/.github/workflows/tests-integration-scratch.yml index 670afcc53..6201d2257 100644 --- a/.github/workflows/tests-integration-scratch.yml +++ b/.github/workflows/tests-integration-scratch.yml @@ -10,7 +10,7 @@ jobs: services: hardhat-node: - image: ghcr.io/lidofinance/hardhat-node:2.22.12-scratch + image: ghcr.io/lidofinance/hardhat-node:2.22.17-scratch ports: - 8555:8545 diff --git a/README.md b/README.md index f62def5bc..205f0acb7 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This is the core repository of the Lido on Ethereum protocol. The entire codebas ---
- Lido + Lido GitHub license Solidity Aragon OS @@ -14,7 +14,9 @@ This is the core repository of the Lido on Ethereum protocol. The entire codebas Hardhat Ethers
- GitHub tests + GitHub unit tests + GitHub integration tests (scratch) + GitHub integration tests (mainnet-fork) GitHub linters GitHub code analysis
diff --git a/package.json b/package.json index 872640ff9..3e42dd7e1 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "ethers": "^6.13.4", "glob": "^11.0.0", "globals": "^15.9.0", - "hardhat": "^2.22.16", + "hardhat": "^2.22.17", "hardhat-contract-sizer": "^2.10.0", "hardhat-gas-reporter": "^1.0.10", "hardhat-ignore-warnings": "^0.2.12", diff --git a/poetry.lock b/poetry.lock index d7b3cfb7a..9b9f6fa83 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -13,102 +13,102 @@ files = [ [[package]] name = "aiohttp" -version = "3.10.5" +version = "3.10.11" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:18a01eba2574fb9edd5f6e5fb25f66e6ce061da5dab5db75e13fe1558142e0a3"}, - {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:94fac7c6e77ccb1ca91e9eb4cb0ac0270b9fb9b289738654120ba8cebb1189c6"}, - {file = "aiohttp-3.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f1f1c75c395991ce9c94d3e4aa96e5c59c8356a15b1c9231e783865e2772699"}, - {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7acae3cf1a2a2361ec4c8e787eaaa86a94171d2417aae53c0cca6ca3118ff6"}, - {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94c4381ffba9cc508b37d2e536b418d5ea9cfdc2848b9a7fea6aebad4ec6aac1"}, - {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c31ad0c0c507894e3eaa843415841995bf8de4d6b2d24c6e33099f4bc9fc0d4f"}, - {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0912b8a8fadeb32ff67a3ed44249448c20148397c1ed905d5dac185b4ca547bb"}, - {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d93400c18596b7dc4794d48a63fb361b01a0d8eb39f28800dc900c8fbdaca91"}, - {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3c5e0d764a5c9aa5a62d99728c56d455310bcc288a79cab10157b3af426f"}, - {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d742c36ed44f2798c8d3f4bc511f479b9ceef2b93f348671184139e7d708042c"}, - {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:814375093edae5f1cb31e3407997cf3eacefb9010f96df10d64829362ae2df69"}, - {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8224f98be68a84b19f48e0bdc14224b5a71339aff3a27df69989fa47d01296f3"}, - {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9a487ef090aea982d748b1b0d74fe7c3950b109df967630a20584f9a99c0683"}, - {file = "aiohttp-3.10.5-cp310-cp310-win32.whl", hash = "sha256:d9ef084e3dc690ad50137cc05831c52b6ca428096e6deb3c43e95827f531d5ef"}, - {file = "aiohttp-3.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:66bf9234e08fe561dccd62083bf67400bdbf1c67ba9efdc3dac03650e97c6088"}, - {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c6a4e5e40156d72a40241a25cc226051c0a8d816610097a8e8f517aeacd59a2"}, - {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c634a3207a5445be65536d38c13791904fda0748b9eabf908d3fe86a52941cf"}, - {file = "aiohttp-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4aff049b5e629ef9b3e9e617fa6e2dfeda1bf87e01bcfecaf3949af9e210105e"}, - {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1942244f00baaacaa8155eca94dbd9e8cc7017deb69b75ef67c78e89fdad3c77"}, - {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04a1f2a65ad2f93aa20f9ff9f1b672bf912413e5547f60749fa2ef8a644e061"}, - {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f2bfc0032a00405d4af2ba27f3c429e851d04fad1e5ceee4080a1c570476697"}, - {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:424ae21498790e12eb759040bbb504e5e280cab64693d14775c54269fd1d2bb7"}, - {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:975218eee0e6d24eb336d0328c768ebc5d617609affaca5dbbd6dd1984f16ed0"}, - {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4120d7fefa1e2d8fb6f650b11489710091788de554e2b6f8347c7a20ceb003f5"}, - {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b90078989ef3fc45cf9221d3859acd1108af7560c52397ff4ace8ad7052a132e"}, - {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ba5a8b74c2a8af7d862399cdedce1533642fa727def0b8c3e3e02fcb52dca1b1"}, - {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:02594361128f780eecc2a29939d9dfc870e17b45178a867bf61a11b2a4367277"}, - {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4fc029e135859f533025bc82047334e24b0d489e75513144f25408ecaf058"}, - {file = "aiohttp-3.10.5-cp311-cp311-win32.whl", hash = "sha256:e1ca1ef5ba129718a8fc827b0867f6aa4e893c56eb00003b7367f8a733a9b072"}, - {file = "aiohttp-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:349ef8a73a7c5665cca65c88ab24abe75447e28aa3bc4c93ea5093474dfdf0ff"}, - {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:305be5ff2081fa1d283a76113b8df7a14c10d75602a38d9f012935df20731487"}, - {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3a1c32a19ee6bbde02f1cb189e13a71b321256cc1d431196a9f824050b160d5a"}, - {file = "aiohttp-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61645818edd40cc6f455b851277a21bf420ce347baa0b86eaa41d51ef58ba23d"}, - {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c225286f2b13bab5987425558baa5cbdb2bc925b2998038fa028245ef421e75"}, - {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ba01ebc6175e1e6b7275c907a3a36be48a2d487549b656aa90c8a910d9f3178"}, - {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8eaf44ccbc4e35762683078b72bf293f476561d8b68ec8a64f98cf32811c323e"}, - {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c43eb1ab7cbf411b8e387dc169acb31f0ca0d8c09ba63f9eac67829585b44f"}, - {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de7a5299827253023c55ea549444e058c0eb496931fa05d693b95140a947cb73"}, - {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4790f0e15f00058f7599dab2b206d3049d7ac464dc2e5eae0e93fa18aee9e7bf"}, - {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:44b324a6b8376a23e6ba25d368726ee3bc281e6ab306db80b5819999c737d820"}, - {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0d277cfb304118079e7044aad0b76685d30ecb86f83a0711fc5fb257ffe832ca"}, - {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:54d9ddea424cd19d3ff6128601a4a4d23d54a421f9b4c0fff740505813739a91"}, - {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f1c9866ccf48a6df2b06823e6ae80573529f2af3a0992ec4fe75b1a510df8a6"}, - {file = "aiohttp-3.10.5-cp312-cp312-win32.whl", hash = "sha256:dc4826823121783dccc0871e3f405417ac116055bf184ac04c36f98b75aacd12"}, - {file = "aiohttp-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:22c0a23a3b3138a6bf76fc553789cb1a703836da86b0f306b6f0dc1617398abc"}, - {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7f6b639c36734eaa80a6c152a238242bedcee9b953f23bb887e9102976343092"}, - {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29930bc2921cef955ba39a3ff87d2c4398a0394ae217f41cb02d5c26c8b1b77"}, - {file = "aiohttp-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f489a2c9e6455d87eabf907ac0b7d230a9786be43fbe884ad184ddf9e9c1e385"}, - {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:123dd5b16b75b2962d0fff566effb7a065e33cd4538c1692fb31c3bda2bfb972"}, - {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b98e698dc34966e5976e10bbca6d26d6724e6bdea853c7c10162a3235aba6e16"}, - {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3b9162bab7e42f21243effc822652dc5bb5e8ff42a4eb62fe7782bcbcdfacf6"}, - {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1923a5c44061bffd5eebeef58cecf68096e35003907d8201a4d0d6f6e387ccaa"}, - {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55f011da0a843c3d3df2c2cf4e537b8070a419f891c930245f05d329c4b0689"}, - {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:afe16a84498441d05e9189a15900640a2d2b5e76cf4efe8cbb088ab4f112ee57"}, - {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8112fb501b1e0567a1251a2fd0747baae60a4ab325a871e975b7bb67e59221f"}, - {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1e72589da4c90337837fdfe2026ae1952c0f4a6e793adbbfbdd40efed7c63599"}, - {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4d46c7b4173415d8e583045fbc4daa48b40e31b19ce595b8d92cf639396c15d5"}, - {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33e6bc4bab477c772a541f76cd91e11ccb6d2efa2b8d7d7883591dfb523e5987"}, - {file = "aiohttp-3.10.5-cp313-cp313-win32.whl", hash = "sha256:c58c6837a2c2a7cf3133983e64173aec11f9c2cd8e87ec2fdc16ce727bcf1a04"}, - {file = "aiohttp-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:38172a70005252b6893088c0f5e8a47d173df7cc2b2bd88650957eb84fcf5022"}, - {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f6f18898ace4bcd2d41a122916475344a87f1dfdec626ecde9ee802a711bc569"}, - {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5ede29d91a40ba22ac1b922ef510aab871652f6c88ef60b9dcdf773c6d32ad7a"}, - {file = "aiohttp-3.10.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:673f988370f5954df96cc31fd99c7312a3af0a97f09e407399f61583f30da9bc"}, - {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58718e181c56a3c02d25b09d4115eb02aafe1a732ce5714ab70326d9776457c3"}, - {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b38b1570242fbab8d86a84128fb5b5234a2f70c2e32f3070143a6d94bc854cf"}, - {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:074d1bff0163e107e97bd48cad9f928fa5a3eb4b9d33366137ffce08a63e37fe"}, - {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd31f176429cecbc1ba499d4aba31aaccfea488f418d60376b911269d3b883c5"}, - {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7384d0b87d4635ec38db9263e6a3f1eb609e2e06087f0aa7f63b76833737b471"}, - {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8989f46f3d7ef79585e98fa991e6ded55d2f48ae56d2c9fa5e491a6e4effb589"}, - {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c83f7a107abb89a227d6c454c613e7606c12a42b9a4ca9c5d7dad25d47c776ae"}, - {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cde98f323d6bf161041e7627a5fd763f9fd829bcfcd089804a5fdce7bb6e1b7d"}, - {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:676f94c5480d8eefd97c0c7e3953315e4d8c2b71f3b49539beb2aa676c58272f"}, - {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2d21ac12dc943c68135ff858c3a989f2194a709e6e10b4c8977d7fcd67dfd511"}, - {file = "aiohttp-3.10.5-cp38-cp38-win32.whl", hash = "sha256:17e997105bd1a260850272bfb50e2a328e029c941c2708170d9d978d5a30ad9a"}, - {file = "aiohttp-3.10.5-cp38-cp38-win_amd64.whl", hash = "sha256:1c19de68896747a2aa6257ae4cf6ef59d73917a36a35ee9d0a6f48cff0f94db8"}, - {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7e2fe37ac654032db1f3499fe56e77190282534810e2a8e833141a021faaab0e"}, - {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5bf3ead3cb66ab990ee2561373b009db5bc0e857549b6c9ba84b20bc462e172"}, - {file = "aiohttp-3.10.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b2c16a919d936ca87a3c5f0e43af12a89a3ce7ccbce59a2d6784caba945b68b"}, - {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad146dae5977c4dd435eb31373b3fe9b0b1bf26858c6fc452bf6af394067e10b"}, - {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c5c6fa16412b35999320f5c9690c0f554392dc222c04e559217e0f9ae244b92"}, - {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95c4dc6f61d610bc0ee1edc6f29d993f10febfe5b76bb470b486d90bbece6b22"}, - {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da452c2c322e9ce0cfef392e469a26d63d42860f829026a63374fde6b5c5876f"}, - {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:898715cf566ec2869d5cb4d5fb4be408964704c46c96b4be267442d265390f32"}, - {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:391cc3a9c1527e424c6865e087897e766a917f15dddb360174a70467572ac6ce"}, - {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:380f926b51b92d02a34119d072f178d80bbda334d1a7e10fa22d467a66e494db"}, - {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce91db90dbf37bb6fa0997f26574107e1b9d5ff939315247b7e615baa8ec313b"}, - {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9093a81e18c45227eebe4c16124ebf3e0d893830c6aca7cc310bfca8fe59d857"}, - {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ee40b40aa753d844162dcc80d0fe256b87cba48ca0054f64e68000453caead11"}, - {file = "aiohttp-3.10.5-cp39-cp39-win32.whl", hash = "sha256:03f2645adbe17f274444953bdea69f8327e9d278d961d85657cb0d06864814c1"}, - {file = "aiohttp-3.10.5-cp39-cp39-win_amd64.whl", hash = "sha256:d17920f18e6ee090bdd3d0bfffd769d9f2cb4c8ffde3eb203777a3895c128862"}, - {file = "aiohttp-3.10.5.tar.gz", hash = "sha256:f071854b47d39591ce9a17981c46790acb30518e2f83dfca8db2dfa091178691"}, + {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5077b1a5f40ffa3ba1f40d537d3bec4383988ee51fbba6b74aa8fb1bc466599e"}, + {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d6a14a4d93b5b3c2891fca94fa9d41b2322a68194422bef0dd5ec1e57d7d298"}, + {file = "aiohttp-3.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffbfde2443696345e23a3c597049b1dd43049bb65337837574205e7368472177"}, + {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20b3d9e416774d41813bc02fdc0663379c01817b0874b932b81c7f777f67b217"}, + {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b943011b45ee6bf74b22245c6faab736363678e910504dd7531a58c76c9015a"}, + {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48bc1d924490f0d0b3658fe5c4b081a4d56ebb58af80a6729d4bd13ea569797a"}, + {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e12eb3f4b1f72aaaf6acd27d045753b18101524f72ae071ae1c91c1cd44ef115"}, + {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f14ebc419a568c2eff3c1ed35f634435c24ead2fe19c07426af41e7adb68713a"}, + {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:72b191cdf35a518bfc7ca87d770d30941decc5aaf897ec8b484eb5cc8c7706f3"}, + {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5ab2328a61fdc86424ee540d0aeb8b73bbcad7351fb7cf7a6546fc0bcffa0038"}, + {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:aa93063d4af05c49276cf14e419550a3f45258b6b9d1f16403e777f1addf4519"}, + {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:30283f9d0ce420363c24c5c2421e71a738a2155f10adbb1a11a4d4d6d2715cfc"}, + {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e5358addc8044ee49143c546d2182c15b4ac3a60be01c3209374ace05af5733d"}, + {file = "aiohttp-3.10.11-cp310-cp310-win32.whl", hash = "sha256:e1ffa713d3ea7cdcd4aea9cddccab41edf6882fa9552940344c44e59652e1120"}, + {file = "aiohttp-3.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:778cbd01f18ff78b5dd23c77eb82987ee4ba23408cbed233009fd570dda7e674"}, + {file = "aiohttp-3.10.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:80ff08556c7f59a7972b1e8919f62e9c069c33566a6d28586771711e0eea4f07"}, + {file = "aiohttp-3.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c8f96e9ee19f04c4914e4e7a42a60861066d3e1abf05c726f38d9d0a466e695"}, + {file = "aiohttp-3.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fb8601394d537da9221947b5d6e62b064c9a43e88a1ecd7414d21a1a6fba9c24"}, + {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ea224cf7bc2d8856d6971cea73b1d50c9c51d36971faf1abc169a0d5f85a382"}, + {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db9503f79e12d5d80b3efd4d01312853565c05367493379df76d2674af881caa"}, + {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0f449a50cc33f0384f633894d8d3cd020e3ccef81879c6e6245c3c375c448625"}, + {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82052be3e6d9e0c123499127782a01a2b224b8af8c62ab46b3f6197035ad94e9"}, + {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20063c7acf1eec550c8eb098deb5ed9e1bb0521613b03bb93644b810986027ac"}, + {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:489cced07a4c11488f47aab1f00d0c572506883f877af100a38f1fedaa884c3a"}, + {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ea9b3bab329aeaa603ed3bf605f1e2a6f36496ad7e0e1aa42025f368ee2dc07b"}, + {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ca117819d8ad113413016cb29774b3f6d99ad23c220069789fc050267b786c16"}, + {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2dfb612dcbe70fb7cdcf3499e8d483079b89749c857a8f6e80263b021745c730"}, + {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9b615d3da0d60e7d53c62e22b4fd1c70f4ae5993a44687b011ea3a2e49051b8"}, + {file = "aiohttp-3.10.11-cp311-cp311-win32.whl", hash = "sha256:29103f9099b6068bbdf44d6a3d090e0a0b2be6d3c9f16a070dd9d0d910ec08f9"}, + {file = "aiohttp-3.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:236b28ceb79532da85d59aa9b9bf873b364e27a0acb2ceaba475dc61cffb6f3f"}, + {file = "aiohttp-3.10.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7480519f70e32bfb101d71fb9a1f330fbd291655a4c1c922232a48c458c52710"}, + {file = "aiohttp-3.10.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f65267266c9aeb2287a6622ee2bb39490292552f9fbf851baabc04c9f84e048d"}, + {file = "aiohttp-3.10.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7400a93d629a0608dc1d6c55f1e3d6e07f7375745aaa8bd7f085571e4d1cee97"}, + {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f34b97e4b11b8d4eb2c3a4f975be626cc8af99ff479da7de49ac2c6d02d35725"}, + {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e7b825da878464a252ccff2958838f9caa82f32a8dbc334eb9b34a026e2c636"}, + {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9f92a344c50b9667827da308473005f34767b6a2a60d9acff56ae94f895f385"}, + {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f1ab987a27b83c5268a17218463c2ec08dbb754195113867a27b166cd6087"}, + {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1dc0f4ca54842173d03322793ebcf2c8cc2d34ae91cc762478e295d8e361e03f"}, + {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7ce6a51469bfaacff146e59e7fb61c9c23006495d11cc24c514a455032bcfa03"}, + {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:aad3cd91d484d065ede16f3cf15408254e2469e3f613b241a1db552c5eb7ab7d"}, + {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f4df4b8ca97f658c880fb4b90b1d1ec528315d4030af1ec763247ebfd33d8b9a"}, + {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2e4e18a0a2d03531edbc06c366954e40a3f8d2a88d2b936bbe78a0c75a3aab3e"}, + {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ce66780fa1a20e45bc753cda2a149daa6dbf1561fc1289fa0c308391c7bc0a4"}, + {file = "aiohttp-3.10.11-cp312-cp312-win32.whl", hash = "sha256:a919c8957695ea4c0e7a3e8d16494e3477b86f33067478f43106921c2fef15bb"}, + {file = "aiohttp-3.10.11-cp312-cp312-win_amd64.whl", hash = "sha256:b5e29706e6389a2283a91611c91bf24f218962717c8f3b4e528ef529d112ee27"}, + {file = "aiohttp-3.10.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:703938e22434d7d14ec22f9f310559331f455018389222eed132808cd8f44127"}, + {file = "aiohttp-3.10.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9bc50b63648840854e00084c2b43035a62e033cb9b06d8c22b409d56eb098413"}, + {file = "aiohttp-3.10.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f0463bf8b0754bc744e1feb61590706823795041e63edf30118a6f0bf577461"}, + {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6c6dec398ac5a87cb3a407b068e1106b20ef001c344e34154616183fe684288"}, + {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcaf2d79104d53d4dcf934f7ce76d3d155302d07dae24dff6c9fffd217568067"}, + {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25fd5470922091b5a9aeeb7e75be609e16b4fba81cdeaf12981393fb240dd10e"}, + {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbde2ca67230923a42161b1f408c3992ae6e0be782dca0c44cb3206bf330dee1"}, + {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:249c8ff8d26a8b41a0f12f9df804e7c685ca35a207e2410adbd3e924217b9006"}, + {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:878ca6a931ee8c486a8f7b432b65431d095c522cbeb34892bee5be97b3481d0f"}, + {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8663f7777ce775f0413324be0d96d9730959b2ca73d9b7e2c2c90539139cbdd6"}, + {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6cd3f10b01f0c31481fba8d302b61603a2acb37b9d30e1d14e0f5a58b7b18a31"}, + {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e8d8aad9402d3aa02fdc5ca2fe68bcb9fdfe1f77b40b10410a94c7f408b664d"}, + {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:38e3c4f80196b4f6c3a85d134a534a56f52da9cb8d8e7af1b79a32eefee73a00"}, + {file = "aiohttp-3.10.11-cp313-cp313-win32.whl", hash = "sha256:fc31820cfc3b2863c6e95e14fcf815dc7afe52480b4dc03393c4873bb5599f71"}, + {file = "aiohttp-3.10.11-cp313-cp313-win_amd64.whl", hash = "sha256:4996ff1345704ffdd6d75fb06ed175938c133425af616142e7187f28dc75f14e"}, + {file = "aiohttp-3.10.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:74baf1a7d948b3d640badeac333af581a367ab916b37e44cf90a0334157cdfd2"}, + {file = "aiohttp-3.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:473aebc3b871646e1940c05268d451f2543a1d209f47035b594b9d4e91ce8339"}, + {file = "aiohttp-3.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c2f746a6968c54ab2186574e15c3f14f3e7f67aef12b761e043b33b89c5b5f95"}, + {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d110cabad8360ffa0dec8f6ec60e43286e9d251e77db4763a87dcfe55b4adb92"}, + {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0099c7d5d7afff4202a0c670e5b723f7718810000b4abcbc96b064129e64bc7"}, + {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0316e624b754dbbf8c872b62fe6dcb395ef20c70e59890dfa0de9eafccd2849d"}, + {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a5f7ab8baf13314e6b2485965cbacb94afff1e93466ac4d06a47a81c50f9cca"}, + {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c891011e76041e6508cbfc469dd1a8ea09bc24e87e4c204e05f150c4c455a5fa"}, + {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9208299251370ee815473270c52cd3f7069ee9ed348d941d574d1457d2c73e8b"}, + {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:459f0f32c8356e8125f45eeff0ecf2b1cb6db1551304972702f34cd9e6c44658"}, + {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:14cdc8c1810bbd4b4b9f142eeee23cda528ae4e57ea0923551a9af4820980e39"}, + {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:971aa438a29701d4b34e4943e91b5e984c3ae6ccbf80dd9efaffb01bd0b243a9"}, + {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9a309c5de392dfe0f32ee57fa43ed8fc6ddf9985425e84bd51ed66bb16bce3a7"}, + {file = "aiohttp-3.10.11-cp38-cp38-win32.whl", hash = "sha256:9ec1628180241d906a0840b38f162a3215114b14541f1a8711c368a8739a9be4"}, + {file = "aiohttp-3.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:9c6e0ffd52c929f985c7258f83185d17c76d4275ad22e90aa29f38e211aacbec"}, + {file = "aiohttp-3.10.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cdc493a2e5d8dc79b2df5bec9558425bcd39aff59fc949810cbd0832e294b106"}, + {file = "aiohttp-3.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3e70f24e7d0405be2348da9d5a7836936bf3a9b4fd210f8c37e8d48bc32eca6"}, + {file = "aiohttp-3.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968b8fb2a5eee2770eda9c7b5581587ef9b96fbdf8dcabc6b446d35ccc69df01"}, + {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deef4362af9493d1382ef86732ee2e4cbc0d7c005947bd54ad1a9a16dd59298e"}, + {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:686b03196976e327412a1b094f4120778c7c4b9cff9bce8d2fdfeca386b89829"}, + {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3bf6d027d9d1d34e1c2e1645f18a6498c98d634f8e373395221121f1c258ace8"}, + {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:099fd126bf960f96d34a760e747a629c27fb3634da5d05c7ef4d35ef4ea519fc"}, + {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c73c4d3dae0b4644bc21e3de546530531d6cdc88659cdeb6579cd627d3c206aa"}, + {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0c5580f3c51eea91559db3facd45d72e7ec970b04528b4709b1f9c2555bd6d0b"}, + {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fdf6429f0caabfd8a30c4e2eaecb547b3c340e4730ebfe25139779b9815ba138"}, + {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d97187de3c276263db3564bb9d9fad9e15b51ea10a371ffa5947a5ba93ad6777"}, + {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0acafb350cfb2eba70eb5d271f55e08bd4502ec35e964e18ad3e7d34d71f7261"}, + {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c13ed0c779911c7998a58e7848954bd4d63df3e3575f591e321b19a2aec8df9f"}, + {file = "aiohttp-3.10.11-cp39-cp39-win32.whl", hash = "sha256:22b7c540c55909140f63ab4f54ec2c20d2635c0289cdd8006da46f3327f971b9"}, + {file = "aiohttp-3.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:7b26b1551e481012575dab8e3727b16fe7dd27eb2711d2e63ced7368756268fb"}, + {file = "aiohttp-3.10.11.tar.gz", hash = "sha256:9dc2b8f3dcab2e39e0fa309c8da50c3b55e6f34ab25f1a71d3288f24924d33a7"}, ] [package.dependencies] @@ -117,7 +117,7 @@ aiosignal = ">=1.1.2" attrs = ">=17.3.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" -yarl = ">=1.0,<2.0" +yarl = ">=1.12.0,<2.0" [package.extras] speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] @@ -1245,6 +1245,97 @@ wcwidth = "*" [package.extras] tests = ["pytest", "pytest-cov", "pytest-lazy-fixtures"] +[[package]] +name = "propcache" +version = "0.2.1" +description = "Accelerated property cache" +optional = false +python-versions = ">=3.9" +files = [ + {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6"}, + {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2"}, + {file = "propcache-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6445804cf4ec763dc70de65a3b0d9954e868609e83850a47ca4f0cb64bd79fea"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9479aa06a793c5aeba49ce5c5692ffb51fcd9a7016e017d555d5e2b0045d212"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9631c5e8b5b3a0fda99cb0d29c18133bca1e18aea9effe55adb3da1adef80d3"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3156628250f46a0895f1f36e1d4fbe062a1af8718ec3ebeb746f1d23f0c5dc4d"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6fb63ae352e13748289f04f37868099e69dba4c2b3e271c46061e82c745634"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:887d9b0a65404929641a9fabb6452b07fe4572b269d901d622d8a34a4e9043b2"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a96dc1fa45bd8c407a0af03b2d5218392729e1822b0c32e62c5bf7eeb5fb3958"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a7e65eb5c003a303b94aa2c3852ef130230ec79e349632d030e9571b87c4698c"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:999779addc413181912e984b942fbcc951be1f5b3663cd80b2687758f434c583"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:19a0f89a7bb9d8048d9c4370c9c543c396e894c76be5525f5e1ad287f1750ddf"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1ac2f5fe02fa75f56e1ad473f1175e11f475606ec9bd0be2e78e4734ad575034"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:574faa3b79e8ebac7cb1d7930f51184ba1ccf69adfdec53a12f319a06030a68b"}, + {file = "propcache-0.2.1-cp310-cp310-win32.whl", hash = "sha256:03ff9d3f665769b2a85e6157ac8b439644f2d7fd17615a82fa55739bc97863f4"}, + {file = "propcache-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2d3af2e79991102678f53e0dbf4c35de99b6b8b58f29a27ca0325816364caaba"}, + {file = "propcache-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ffc3cca89bb438fb9c95c13fc874012f7b9466b89328c3c8b1aa93cdcfadd16"}, + {file = "propcache-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f174bbd484294ed9fdf09437f889f95807e5f229d5d93588d34e92106fbf6717"}, + {file = "propcache-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:70693319e0b8fd35dd863e3e29513875eb15c51945bf32519ef52927ca883bc3"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b480c6a4e1138e1aa137c0079b9b6305ec6dcc1098a8ca5196283e8a49df95a9"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d27b84d5880f6d8aa9ae3edb253c59d9f6642ffbb2c889b78b60361eed449787"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:857112b22acd417c40fa4595db2fe28ab900c8c5fe4670c7989b1c0230955465"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf6c4150f8c0e32d241436526f3c3f9cbd34429492abddbada2ffcff506c51af"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d4cfda1d8ed687daa4bc0274fcfd5267873db9a5bc0418c2da19273040eeb7"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2f992c07c0fca81655066705beae35fc95a2fa7366467366db627d9f2ee097f"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4a571d97dbe66ef38e472703067021b1467025ec85707d57e78711c085984e54"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bb6178c241278d5fe853b3de743087be7f5f4c6f7d6d22a3b524d323eecec505"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ad1af54a62ffe39cf34db1aa6ed1a1873bd548f6401db39d8e7cd060b9211f82"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e7048abd75fe40712005bcfc06bb44b9dfcd8e101dda2ecf2f5aa46115ad07ca"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:160291c60081f23ee43d44b08a7e5fb76681221a8e10b3139618c5a9a291b84e"}, + {file = "propcache-0.2.1-cp311-cp311-win32.whl", hash = "sha256:819ce3b883b7576ca28da3861c7e1a88afd08cc8c96908e08a3f4dd64a228034"}, + {file = "propcache-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:edc9fc7051e3350643ad929df55c451899bb9ae6d24998a949d2e4c87fb596d3"}, + {file = "propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a"}, + {file = "propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0"}, + {file = "propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518"}, + {file = "propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246"}, + {file = "propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1"}, + {file = "propcache-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aca405706e0b0a44cc6bfd41fbe89919a6a56999157f6de7e182a990c36e37bc"}, + {file = "propcache-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12d1083f001ace206fe34b6bdc2cb94be66d57a850866f0b908972f90996b3e9"}, + {file = "propcache-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d93f3307ad32a27bda2e88ec81134b823c240aa3abb55821a8da553eed8d9439"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba278acf14471d36316159c94a802933d10b6a1e117b8554fe0d0d9b75c9d536"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e6281aedfca15301c41f74d7005e6e3f4ca143584ba696ac69df4f02f40d629"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b750a8e5a1262434fb1517ddf64b5de58327f1adc3524a5e44c2ca43305eb0b"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf72af5e0fb40e9babf594308911436c8efde3cb5e75b6f206c34ad18be5c052"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2d0a12018b04f4cb820781ec0dffb5f7c7c1d2a5cd22bff7fb055a2cb19ebce"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e800776a79a5aabdb17dcc2346a7d66d0777e942e4cd251defeb084762ecd17d"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4160d9283bd382fa6c0c2b5e017acc95bc183570cd70968b9202ad6d8fc48dce"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b43e74f1359353341a7adb783c8f1b1c676367b011709f466f42fda2045e95"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:58791550b27d5488b1bb52bc96328456095d96206a250d28d874fafe11b3dfaf"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f022d381747f0dfe27e99d928e31bc51a18b65bb9e481ae0af1380a6725dd1f"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:297878dc9d0a334358f9b608b56d02e72899f3b8499fc6044133f0d319e2ec30"}, + {file = "propcache-0.2.1-cp313-cp313-win32.whl", hash = "sha256:ddfab44e4489bd79bda09d84c430677fc7f0a4939a73d2bba3073036f487a0a6"}, + {file = "propcache-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:556fc6c10989f19a179e4321e5d678db8eb2924131e64652a51fe83e4c3db0e1"}, + {file = "propcache-0.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6a9a8c34fb7bb609419a211e59da8887eeca40d300b5ea8e56af98f6fbbb1541"}, + {file = "propcache-0.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae1aa1cd222c6d205853b3013c69cd04515f9d6ab6de4b0603e2e1c33221303e"}, + {file = "propcache-0.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:accb6150ce61c9c4b7738d45550806aa2b71c7668c6942f17b0ac182b6142fd4"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eee736daafa7af6d0a2dc15cc75e05c64f37fc37bafef2e00d77c14171c2097"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7a31fc1e1bd362874863fdeed71aed92d348f5336fd84f2197ba40c59f061bd"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba4cfa1052819d16699e1d55d18c92b6e094d4517c41dd231a8b9f87b6fa681"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f089118d584e859c62b3da0892b88a83d611c2033ac410e929cb6754eec0ed16"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:781e65134efaf88feb447e8c97a51772aa75e48b794352f94cb7ea717dedda0d"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31f5af773530fd3c658b32b6bdc2d0838543de70eb9a2156c03e410f7b0d3aae"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a7a078f5d37bee6690959c813977da5291b24286e7b962e62a94cec31aa5188b"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cea7daf9fc7ae6687cf1e2c049752f19f146fdc37c2cc376e7d0032cf4f25347"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:8b3489ff1ed1e8315674d0775dc7d2195fb13ca17b3808721b54dbe9fd020faf"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9403db39be1393618dd80c746cb22ccda168efce239c73af13c3763ef56ffc04"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5d97151bc92d2b2578ff7ce779cdb9174337390a535953cbb9452fb65164c587"}, + {file = "propcache-0.2.1-cp39-cp39-win32.whl", hash = "sha256:9caac6b54914bdf41bcc91e7eb9147d331d29235a7c967c150ef5df6464fd1bb"}, + {file = "propcache-0.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:92fc4500fcb33899b05ba73276dfb684a20d31caa567b7cb5252d48f896a91b1"}, + {file = "propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54"}, + {file = "propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64"}, +] + [[package]] name = "protobuf" version = "5.28.2" @@ -1864,108 +1955,99 @@ files = [ [[package]] name = "yarl" -version = "1.11.1" +version = "1.18.3" description = "Yet another URL library" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "yarl-1.11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:400cd42185f92de559d29eeb529e71d80dfbd2f45c36844914a4a34297ca6f00"}, - {file = "yarl-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8258c86f47e080a258993eed877d579c71da7bda26af86ce6c2d2d072c11320d"}, - {file = "yarl-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2164cd9725092761fed26f299e3f276bb4b537ca58e6ff6b252eae9631b5c96e"}, - {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08ea567c16f140af8ddc7cb58e27e9138a1386e3e6e53982abaa6f2377b38cc"}, - {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:768ecc550096b028754ea28bf90fde071c379c62c43afa574edc6f33ee5daaec"}, - {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2909fa3a7d249ef64eeb2faa04b7957e34fefb6ec9966506312349ed8a7e77bf"}, - {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01a8697ec24f17c349c4f655763c4db70eebc56a5f82995e5e26e837c6eb0e49"}, - {file = "yarl-1.11.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e286580b6511aac7c3268a78cdb861ec739d3e5a2a53b4809faef6b49778eaff"}, - {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4179522dc0305c3fc9782549175c8e8849252fefeb077c92a73889ccbcd508ad"}, - {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:27fcb271a41b746bd0e2a92182df507e1c204759f460ff784ca614e12dd85145"}, - {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f61db3b7e870914dbd9434b560075e0366771eecbe6d2b5561f5bc7485f39efd"}, - {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:c92261eb2ad367629dc437536463dc934030c9e7caca861cc51990fe6c565f26"}, - {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d95b52fbef190ca87d8c42f49e314eace4fc52070f3dfa5f87a6594b0c1c6e46"}, - {file = "yarl-1.11.1-cp310-cp310-win32.whl", hash = "sha256:489fa8bde4f1244ad6c5f6d11bb33e09cf0d1d0367edb197619c3e3fc06f3d91"}, - {file = "yarl-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:476e20c433b356e16e9a141449f25161e6b69984fb4cdbd7cd4bd54c17844998"}, - {file = "yarl-1.11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:946eedc12895873891aaceb39bceb484b4977f70373e0122da483f6c38faaa68"}, - {file = "yarl-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:21a7c12321436b066c11ec19c7e3cb9aec18884fe0d5b25d03d756a9e654edfe"}, - {file = "yarl-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c35f493b867912f6fda721a59cc7c4766d382040bdf1ddaeeaa7fa4d072f4675"}, - {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25861303e0be76b60fddc1250ec5986c42f0a5c0c50ff57cc30b1be199c00e63"}, - {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4b53f73077e839b3f89c992223f15b1d2ab314bdbdf502afdc7bb18e95eae27"}, - {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:327c724b01b8641a1bf1ab3b232fb638706e50f76c0b5bf16051ab65c868fac5"}, - {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4307d9a3417eea87715c9736d050c83e8c1904e9b7aada6ce61b46361b733d92"}, - {file = "yarl-1.11.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48a28bed68ab8fb7e380775f0029a079f08a17799cb3387a65d14ace16c12e2b"}, - {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:067b961853c8e62725ff2893226fef3d0da060656a9827f3f520fb1d19b2b68a"}, - {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8215f6f21394d1f46e222abeb06316e77ef328d628f593502d8fc2a9117bde83"}, - {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:498442e3af2a860a663baa14fbf23fb04b0dd758039c0e7c8f91cb9279799bff"}, - {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:69721b8effdb588cb055cc22f7c5105ca6fdaa5aeb3ea09021d517882c4a904c"}, - {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e969fa4c1e0b1a391f3fcbcb9ec31e84440253325b534519be0d28f4b6b533e"}, - {file = "yarl-1.11.1-cp311-cp311-win32.whl", hash = "sha256:7d51324a04fc4b0e097ff8a153e9276c2593106a811704025bbc1d6916f45ca6"}, - {file = "yarl-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:15061ce6584ece023457fb8b7a7a69ec40bf7114d781a8c4f5dcd68e28b5c53b"}, - {file = "yarl-1.11.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a4264515f9117be204935cd230fb2a052dd3792789cc94c101c535d349b3dab0"}, - {file = "yarl-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f41fa79114a1d2eddb5eea7b912d6160508f57440bd302ce96eaa384914cd265"}, - {file = "yarl-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:02da8759b47d964f9173c8675710720b468aa1c1693be0c9c64abb9d8d9a4867"}, - {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9361628f28f48dcf8b2f528420d4d68102f593f9c2e592bfc842f5fb337e44fd"}, - {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b91044952da03b6f95fdba398d7993dd983b64d3c31c358a4c89e3c19b6f7aef"}, - {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74db2ef03b442276d25951749a803ddb6e270d02dda1d1c556f6ae595a0d76a8"}, - {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e975a2211952a8a083d1b9d9ba26472981ae338e720b419eb50535de3c02870"}, - {file = "yarl-1.11.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aef97ba1dd2138112890ef848e17d8526fe80b21f743b4ee65947ea184f07a2"}, - {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a7915ea49b0c113641dc4d9338efa9bd66b6a9a485ffe75b9907e8573ca94b84"}, - {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:504cf0d4c5e4579a51261d6091267f9fd997ef58558c4ffa7a3e1460bd2336fa"}, - {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3de5292f9f0ee285e6bd168b2a77b2a00d74cbcfa420ed078456d3023d2f6dff"}, - {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a34e1e30f1774fa35d37202bbeae62423e9a79d78d0874e5556a593479fdf239"}, - {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66b63c504d2ca43bf7221a1f72fbe981ff56ecb39004c70a94485d13e37ebf45"}, - {file = "yarl-1.11.1-cp312-cp312-win32.whl", hash = "sha256:a28b70c9e2213de425d9cba5ab2e7f7a1c8ca23a99c4b5159bf77b9c31251447"}, - {file = "yarl-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:17b5a386d0d36fb828e2fb3ef08c8829c1ebf977eef88e5367d1c8c94b454639"}, - {file = "yarl-1.11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1fa2e7a406fbd45b61b4433e3aa254a2c3e14c4b3186f6e952d08a730807fa0c"}, - {file = "yarl-1.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:750f656832d7d3cb0c76be137ee79405cc17e792f31e0a01eee390e383b2936e"}, - {file = "yarl-1.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b8486f322d8f6a38539136a22c55f94d269addb24db5cb6f61adc61eabc9d93"}, - {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fce4da3703ee6048ad4138fe74619c50874afe98b1ad87b2698ef95bf92c96d"}, - {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed653638ef669e0efc6fe2acb792275cb419bf9cb5c5049399f3556995f23c7"}, - {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18ac56c9dd70941ecad42b5a906820824ca72ff84ad6fa18db33c2537ae2e089"}, - {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:688654f8507464745ab563b041d1fb7dab5d9912ca6b06e61d1c4708366832f5"}, - {file = "yarl-1.11.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4973eac1e2ff63cf187073cd4e1f1148dcd119314ab79b88e1b3fad74a18c9d5"}, - {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:964a428132227edff96d6f3cf261573cb0f1a60c9a764ce28cda9525f18f7786"}, - {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6d23754b9939cbab02c63434776df1170e43b09c6a517585c7ce2b3d449b7318"}, - {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c2dc4250fe94d8cd864d66018f8344d4af50e3758e9d725e94fecfa27588ff82"}, - {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09696438cb43ea6f9492ef237761b043f9179f455f405279e609f2bc9100212a"}, - {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:999bfee0a5b7385a0af5ffb606393509cfde70ecca4f01c36985be6d33e336da"}, - {file = "yarl-1.11.1-cp313-cp313-win32.whl", hash = "sha256:ce928c9c6409c79e10f39604a7e214b3cb69552952fbda8d836c052832e6a979"}, - {file = "yarl-1.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:501c503eed2bb306638ccb60c174f856cc3246c861829ff40eaa80e2f0330367"}, - {file = "yarl-1.11.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dae7bd0daeb33aa3e79e72877d3d51052e8b19c9025ecf0374f542ea8ec120e4"}, - {file = "yarl-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3ff6b1617aa39279fe18a76c8d165469c48b159931d9b48239065767ee455b2b"}, - {file = "yarl-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3257978c870728a52dcce8c2902bf01f6c53b65094b457bf87b2644ee6238ddc"}, - {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f351fa31234699d6084ff98283cb1e852270fe9e250a3b3bf7804eb493bd937"}, - {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8aef1b64da41d18026632d99a06b3fefe1d08e85dd81d849fa7c96301ed22f1b"}, - {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7175a87ab8f7fbde37160a15e58e138ba3b2b0e05492d7351314a250d61b1591"}, - {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba444bdd4caa2a94456ef67a2f383710928820dd0117aae6650a4d17029fa25e"}, - {file = "yarl-1.11.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ea9682124fc062e3d931c6911934a678cb28453f957ddccf51f568c2f2b5e05"}, - {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8418c053aeb236b20b0ab8fa6bacfc2feaaf7d4683dd96528610989c99723d5f"}, - {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:61a5f2c14d0a1adfdd82258f756b23a550c13ba4c86c84106be4c111a3a4e413"}, - {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f3a6d90cab0bdf07df8f176eae3a07127daafcf7457b997b2bf46776da2c7eb7"}, - {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:077da604852be488c9a05a524068cdae1e972b7dc02438161c32420fb4ec5e14"}, - {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:15439f3c5c72686b6c3ff235279630d08936ace67d0fe5c8d5bbc3ef06f5a420"}, - {file = "yarl-1.11.1-cp38-cp38-win32.whl", hash = "sha256:238a21849dd7554cb4d25a14ffbfa0ef380bb7ba201f45b144a14454a72ffa5a"}, - {file = "yarl-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:67459cf8cf31da0e2cbdb4b040507e535d25cfbb1604ca76396a3a66b8ba37a6"}, - {file = "yarl-1.11.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:884eab2ce97cbaf89f264372eae58388862c33c4f551c15680dd80f53c89a269"}, - {file = "yarl-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a336eaa7ee7e87cdece3cedb395c9657d227bfceb6781295cf56abcd3386a26"}, - {file = "yarl-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87f020d010ba80a247c4abc335fc13421037800ca20b42af5ae40e5fd75e7909"}, - {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:637c7ddb585a62d4469f843dac221f23eec3cbad31693b23abbc2c366ad41ff4"}, - {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48dfd117ab93f0129084577a07287376cc69c08138694396f305636e229caa1a"}, - {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e0ae31fb5ccab6eda09ba1494e87eb226dcbd2372dae96b87800e1dcc98804"}, - {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f46f81501160c28d0c0b7333b4f7be8983dbbc161983b6fb814024d1b4952f79"}, - {file = "yarl-1.11.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04293941646647b3bfb1719d1d11ff1028e9c30199509a844da3c0f5919dc520"}, - {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:250e888fa62d73e721f3041e3a9abf427788a1934b426b45e1b92f62c1f68366"}, - {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e8f63904df26d1a66aabc141bfd258bf738b9bc7bc6bdef22713b4f5ef789a4c"}, - {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:aac44097d838dda26526cffb63bdd8737a2dbdf5f2c68efb72ad83aec6673c7e"}, - {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:267b24f891e74eccbdff42241c5fb4f974de2d6271dcc7d7e0c9ae1079a560d9"}, - {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6907daa4b9d7a688063ed098c472f96e8181733c525e03e866fb5db480a424df"}, - {file = "yarl-1.11.1-cp39-cp39-win32.whl", hash = "sha256:14438dfc5015661f75f85bc5adad0743678eefee266ff0c9a8e32969d5d69f74"}, - {file = "yarl-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:94d0caaa912bfcdc702a4204cd5e2bb01eb917fc4f5ea2315aa23962549561b0"}, - {file = "yarl-1.11.1-py3-none-any.whl", hash = "sha256:72bf26f66456baa0584eff63e44545c9f0eaed9b73cb6601b647c91f14c11f38"}, - {file = "yarl-1.11.1.tar.gz", hash = "sha256:1bb2d9e212fb7449b8fb73bc461b51eaa17cc8430b4a87d87be7b25052d92f53"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690"}, + {file = "yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6"}, + {file = "yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a"}, + {file = "yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1"}, + {file = "yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285"}, + {file = "yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2"}, + {file = "yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8"}, + {file = "yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d"}, + {file = "yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:61e5e68cb65ac8f547f6b5ef933f510134a6bf31bb178be428994b0cb46c2a04"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe57328fbc1bfd0bd0514470ac692630f3901c0ee39052ae47acd1d90a436719"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a440a2a624683108a1b454705ecd7afc1c3438a08e890a1513d468671d90a04e"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c7907c8548bcd6ab860e5f513e727c53b4a714f459b084f6580b49fa1b9cee"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4f6450109834af88cb4cc5ecddfc5380ebb9c228695afc11915a0bf82116789"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9ca04806f3be0ac6d558fffc2fdf8fcef767e0489d2684a21912cc4ed0cd1b8"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77a6e85b90a7641d2e07184df5557132a337f136250caafc9ccaa4a2a998ca2c"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6333c5a377c8e2f5fae35e7b8f145c617b02c939d04110c76f29ee3676b5f9a5"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0b3c92fa08759dbf12b3a59579a4096ba9af8dd344d9a813fc7f5070d86bbab1"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ac515b860c36becb81bb84b667466885096b5fc85596948548b667da3bf9f24"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:045b8482ce9483ada4f3f23b3774f4e1bf4f23a2d5c912ed5170f68efb053318"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a4bb030cf46a434ec0225bddbebd4b89e6471814ca851abb8696170adb163985"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:54d6921f07555713b9300bee9c50fb46e57e2e639027089b1d795ecd9f7fa910"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1d407181cfa6e70077df3377938c08012d18893f9f20e92f7d2f314a437c30b1"}, + {file = "yarl-1.18.3-cp39-cp39-win32.whl", hash = "sha256:ac36703a585e0929b032fbaab0707b75dc12703766d0b53486eabd5139ebadd5"}, + {file = "yarl-1.18.3-cp39-cp39-win_amd64.whl", hash = "sha256:ba87babd629f8af77f557b61e49e7c7cac36f22f871156b91e10a6e9d4f829e9"}, + {file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b"}, + {file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1"}, ] [package.dependencies] idna = ">=2.0" multidict = ">=4.0" +propcache = ">=0.2.0" [metadata] lock-version = "2.0" diff --git a/yarn.lock b/yarn.lock index 1b7ce6f34..9ebdbbfd3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -573,11 +573,11 @@ __metadata: linkType: hard "@eslint/plugin-kit@npm:^0.2.0": - version: 0.2.0 - resolution: "@eslint/plugin-kit@npm:0.2.0" + version: 0.2.4 + resolution: "@eslint/plugin-kit@npm:0.2.4" dependencies: levn: "npm:^0.4.1" - checksum: 10c0/00b92bc52ad09b0e2bbbb30591c02a895f0bec3376759562590e8a57a13d096b22f8c8773b6bf791a7cf2ea614123b3d592fd006c51ac5fd0edbb90ea6d8760c + checksum: 10c0/1bcfc0a30b1df891047c1d8b3707833bded12a057ba01757a2a8591fdc8d8fe0dbb8d51d4b0b61b2af4ca1d363057abd7d2fb4799f1706b105734f4d3fa0dbf1 languageName: node linkType: hard @@ -1283,7 +1283,7 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr@npm:^0.6.4": +"@nomicfoundation/edr@npm:^0.6.5": version: 0.6.5 resolution: "@nomicfoundation/edr@npm:0.6.5" dependencies: @@ -4451,13 +4451,13 @@ __metadata: linkType: hard "cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": - version: 7.0.3 - resolution: "cross-spawn@npm:7.0.3" + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" dependencies: path-key: "npm:^3.1.0" shebang-command: "npm:^2.0.0" which: "npm:^2.0.1" - checksum: 10c0/5738c312387081c98d69c98e105b6327b069197f864a60593245d64c8089c8a0a744e16349281210d56835bb9274130d825a78b2ad6853ca13cfbeffc0c31750 + checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 languageName: node linkType: hard @@ -6628,13 +6628,13 @@ __metadata: languageName: node linkType: hard -"hardhat@npm:^2.22.16": - version: 2.22.16 - resolution: "hardhat@npm:2.22.16" +"hardhat@npm:^2.22.17": + version: 2.22.17 + resolution: "hardhat@npm:2.22.17" dependencies: "@ethersproject/abi": "npm:^5.1.2" "@metamask/eth-sig-util": "npm:^4.0.0" - "@nomicfoundation/edr": "npm:^0.6.4" + "@nomicfoundation/edr": "npm:^0.6.5" "@nomicfoundation/ethereumjs-common": "npm:4.0.4" "@nomicfoundation/ethereumjs-tx": "npm:5.0.4" "@nomicfoundation/ethereumjs-util": "npm:9.0.4" @@ -6686,7 +6686,7 @@ __metadata: optional: true bin: hardhat: internal/cli/bootstrap.js - checksum: 10c0/d193d8dbd02aba9875fc4df23c49fe8cf441afb63382c9e248c776c75aca6e081e9b7b75fb262739f20bff152f9e0e4112bb22e3609dfa63ed4469d3ea46c0ca + checksum: 10c0/d64419a36bfdeb6b4b623d68dcbbb31c724b54999fde5be64c6c102d2f94f98d37ff3964e0293e64c5b436bc194349b09c0874946c687d362bb7a24f989ca685 languageName: node linkType: hard @@ -8005,7 +8005,7 @@ __metadata: ethers: "npm:^6.13.4" glob: "npm:^11.0.0" globals: "npm:^15.9.0" - hardhat: "npm:^2.22.16" + hardhat: "npm:^2.22.17" hardhat-contract-sizer: "npm:^2.10.0" hardhat-gas-reporter: "npm:^1.0.10" hardhat-ignore-warnings: "npm:^0.2.12" From c191ac24cbce17cf1d4aa8de3fa4960e358ee188 Mon Sep 17 00:00:00 2001 From: failingtwice Date: Tue, 14 Jan 2025 13:48:12 +0500 Subject: [PATCH 12/24] fix(StakingVault): rename operator -> nodeOperator --- contracts/0.8.25/vaults/StakingVault.sol | 22 +++++++++---------- .../vaults/interfaces/IStakingVault.sol | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/contracts/0.8.25/vaults/StakingVault.sol b/contracts/0.8.25/vaults/StakingVault.sol index bc6e585d9..79b6179ac 100644 --- a/contracts/0.8.25/vaults/StakingVault.sol +++ b/contracts/0.8.25/vaults/StakingVault.sol @@ -59,13 +59,13 @@ contract StakingVault is IStakingVault, IBeaconProxy, BeaconChainDepositLogistic * @custom:report Latest report containing valuation and inOutDelta * @custom:locked Amount of ether locked on StakingVault by VaultHub and cannot be withdrawn by owner * @custom:inOutDelta Net difference between ether funded and withdrawn from StakingVault - * @custom:operator Address of the node operator + * @custom:nodeOperator Address of the node operator */ struct ERC7201Storage { Report report; uint128 locked; int128 inOutDelta; - address operator; + address nodeOperator; } /** @@ -115,14 +115,14 @@ contract StakingVault is IStakingVault, IBeaconProxy, BeaconChainDepositLogistic } /** - * @notice Initializes `StakingVault` with an owner, operator, and optional parameters + * @notice Initializes `StakingVault` with an owner, node operator, and optional parameters * @param _owner Address that will own the vault - * @param _operator Address of the node operator + * @param _nodeOperator Address of the node operator * @param - Additional initialization parameters */ - function initialize(address _owner, address _operator, bytes calldata /* _params */ ) external onlyBeacon initializer { + function initialize(address _owner, address _nodeOperator, bytes calldata /* _params */ ) external onlyBeacon initializer { __Ownable_init(_owner); - _getStorage().operator = _operator; + _getStorage().nodeOperator = _nodeOperator; } /** @@ -242,8 +242,8 @@ contract StakingVault is IStakingVault, IBeaconProxy, BeaconChainDepositLogistic * Node operator address is set in the initialization and can never be changed. * @return Address of the node operator */ - function operator() external view returns (address) { - return _getStorage().operator; + function nodeOperator() external view returns (address) { + return _getStorage().nodeOperator; } /** @@ -316,7 +316,7 @@ contract StakingVault is IStakingVault, IBeaconProxy, BeaconChainDepositLogistic ) external { if (_numberOfDeposits == 0) revert ZeroArgument("_numberOfDeposits"); if (!isBalanced()) revert Unbalanced(); - if (msg.sender != _getStorage().operator) revert NotAuthorized("depositToBeaconChain", msg.sender); + if (msg.sender != _getStorage().nodeOperator) revert NotAuthorized("depositToBeaconChain", msg.sender); _makeBeaconChainDeposits32ETH(_numberOfDeposits, bytes.concat(withdrawalCredentials()), _pubkeys, _signatures); emit DepositedToBeaconChain(msg.sender, _numberOfDeposits, _numberOfDeposits * 32 ether); @@ -325,7 +325,7 @@ contract StakingVault is IStakingVault, IBeaconProxy, BeaconChainDepositLogistic /** * @notice Requests validator exit from the beacon chain * @param _pubkeys Concatenated validator public keys - * @dev Signals the operator to eject the specified validators from the beacon chain + * @dev Signals the node operator to eject the specified validators from the beacon chain */ function requestValidatorExit(bytes calldata _pubkeys) external onlyOwner { emit ValidatorsExitRequest(msg.sender, _pubkeys); @@ -422,7 +422,7 @@ contract StakingVault is IStakingVault, IBeaconProxy, BeaconChainDepositLogistic /** * @notice Emitted when a validator exit request is made - * @dev Signals `operator` to exit the validator + * @dev Signals `nodeOperator` to exit the validator * @param sender Address that requested the validator exit * @param pubkey Public key of the validator requested to exit */ diff --git a/contracts/0.8.25/vaults/interfaces/IStakingVault.sol b/contracts/0.8.25/vaults/interfaces/IStakingVault.sol index 54d597073..51ebe61c5 100644 --- a/contracts/0.8.25/vaults/interfaces/IStakingVault.sol +++ b/contracts/0.8.25/vaults/interfaces/IStakingVault.sol @@ -23,7 +23,7 @@ interface IStakingVault { function initialize(address _owner, address _operator, bytes calldata _params) external; function getInitializedVersion() external view returns (uint64); function vaultHub() external view returns (address); - function operator() external view returns (address); + function nodeOperator() external view returns (address); function locked() external view returns (uint256); function valuation() external view returns (uint256); function isBalanced() external view returns (bool); From d6caa0397cf560b30118242a0b4cd6c6f886069e Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Tue, 14 Jan 2025 15:54:21 +0000 Subject: [PATCH 13/24] fix: refactor hardhat config file --- .env.example | 2 +- CONTRIBUTING.md | 2 +- hardhat.config.ts | 47 +++--------------------------------- hardhat.helpers.ts | 32 ++++++++++++++++++++++++ tasks/index.ts | 1 + tasks/solidity-get-source.ts | 18 ++++++++++++++ 6 files changed, 57 insertions(+), 45 deletions(-) create mode 100644 hardhat.helpers.ts create mode 100644 tasks/solidity-get-source.ts diff --git a/.env.example b/.env.example index b654199fd..f21c1c63a 100644 --- a/.env.example +++ b/.env.example @@ -28,7 +28,7 @@ MAINNET_RPC_URL=http://localhost:8545 # RPC URL for Hardhat Network forking, required for running tests on mainnet fork with tracing (Infura, Alchemy, etc.) # https://hardhat.org/hardhat-network/docs/guides/forking-other-networks#forking-other-networks -HARDHAT_FORKING_URL= +HARDHAT_FORKING_URL=https://eth.drpc.org # https://docs.lido.fi/deployed-contracts MAINNET_LOCATOR_ADDRESS=0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b95f36970..a93d16690 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -328,7 +328,7 @@ mainnet environment, allowing you to run integration tests with trace logging. > [!NOTE] > Ensure that `HARDHAT_FORKING_URL` is set to Ethereum Mainnet RPC and `MAINNET_*` environment variables are set in the -> `.env` file (refer to `.env.example` for guidance). +> `.env` file (refer to `.env.example` for guidance). Otherwise, the tests will run against the Scratch deployment. ```bash # Run all integration tests diff --git a/hardhat.config.ts b/hardhat.config.ts index 76ccd1b46..e046e8fdb 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -1,5 +1,4 @@ -import { existsSync, readFileSync } from "node:fs"; -import path from "node:path"; +import * as process from "node:process"; import "@nomicfoundation/hardhat-chai-matchers"; import "@nomicfoundation/hardhat-toolbox"; @@ -12,41 +11,15 @@ import "hardhat-tracer"; import "hardhat-watcher"; import "hardhat-ignore-warnings"; import "hardhat-contract-sizer"; -import { globSync } from "glob"; -import { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } from "hardhat/builtin-tasks/task-names"; -import { HardhatUserConfig, subtask } from "hardhat/config"; +import { HardhatUserConfig } from "hardhat/config"; import { mochaRootHooks } from "test/hooks"; import "./tasks"; -const RPC_URL: string = process.env.RPC_URL || ""; -const ACCOUNTS_PATH = "./accounts.json"; - -const HARDHAT_FORKING_URL = process.env.HARDHAT_FORKING_URL || ""; - -const INTEGRATION_WITH_SCRATCH_DEPLOY = process.env.INTEGRATION_WITH_SCRATCH_DEPLOY || "off"; - -/* Determines the forking configuration for Hardhat */ -function getHardhatForkingConfig() { - if (INTEGRATION_WITH_SCRATCH_DEPLOY === "on" || !HARDHAT_FORKING_URL) { - return undefined; - } - return { url: HARDHAT_FORKING_URL }; -} +import { getHardhatForkingConfig, loadAccounts } from "./hardhat.helpers"; -function loadAccounts(networkName: string) { - // TODO: this plaintext accounts.json private keys management is a subject - // of rework to a solution with the keys stored encrypted - if (!existsSync(ACCOUNTS_PATH)) { - return []; - } - const content = JSON.parse(readFileSync(ACCOUNTS_PATH, "utf-8")); - if (!content.eth) { - return []; - } - return content.eth[networkName] || []; -} +const RPC_URL: string = process.env.RPC_URL || ""; const config: HardhatUserConfig = { defaultNetwork: "hardhat", @@ -185,16 +158,4 @@ const config: HardhatUserConfig = { }, }; -// a workaround for having an additional source directory for compilation -// see, https://github.com/NomicFoundation/hardhat/issues/776#issuecomment-1713584386 -subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS).setAction(async (_, hre, runSuper) => { - const paths = await runSuper(); - - const otherDirectoryGlob = path.join(hre.config.paths.root, "test", "**", "*.sol"); - // Don't need to compile test, helper and script files that are not part of the contracts for Hardhat. - const otherPaths = globSync(otherDirectoryGlob).filter((x) => !/\.([ths]\.sol)$/.test(x)); - - return [...paths, ...otherPaths]; -}); - export default config; diff --git a/hardhat.helpers.ts b/hardhat.helpers.ts new file mode 100644 index 000000000..47f6533b8 --- /dev/null +++ b/hardhat.helpers.ts @@ -0,0 +1,32 @@ +import { existsSync, readFileSync } from "node:fs"; + +/* Determines the forking configuration for Hardhat */ +export function getHardhatForkingConfig() { + const forkingUrl = process.env.HARDHAT_FORKING_URL || ""; + + if (!forkingUrl) { + // Scratch deploy, need to disable CSM + process.env.INTEGRATION_ON_SCRATCH = "on"; + process.env.INTEGRATION_WITH_CSM = "off"; + return undefined; + } + + return { url: forkingUrl }; +} + +// TODO: this plaintext accounts.json private keys management is a subject +// of rework to a solution with the keys stored encrypted +export function loadAccounts(networkName: string) { + const accountsPath = "./accounts.json"; + + if (!existsSync(accountsPath)) { + return []; + } + + const content = JSON.parse(readFileSync(accountsPath, "utf-8")); + if (!content.eth) { + return []; + } + + return content.eth[networkName] || []; +} diff --git a/tasks/index.ts b/tasks/index.ts index ada28baca..04b17d7c9 100644 --- a/tasks/index.ts +++ b/tasks/index.ts @@ -1,2 +1,3 @@ export * from "./verify-contracts"; export * from "./extract-abis"; +export * from "./solidity-get-source"; diff --git a/tasks/solidity-get-source.ts b/tasks/solidity-get-source.ts new file mode 100644 index 000000000..522a4af59 --- /dev/null +++ b/tasks/solidity-get-source.ts @@ -0,0 +1,18 @@ +import path from "node:path"; + +import { globSync } from "glob"; +import { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } from "hardhat/builtin-tasks/task-names"; +import { subtask } from "hardhat/config"; + +// a workaround for having an additional source directory for compilation +// see, https://github.com/NomicFoundation/hardhat/issues/776#issuecomment-1713584386 + +subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS).setAction(async (_, hre, runSuper) => { + const paths = await runSuper(); + + const otherDirectoryGlob = path.join(hre.config.paths.root, "test", "**", "*.sol"); + // Don't need to compile test, helper and script files that are not part of the contracts for Hardhat. + const otherPaths = globSync(otherDirectoryGlob).filter((x) => !/\.([ths]\.sol)$/.test(x)); + + return [...paths, ...otherPaths]; +}); From e1511f7eea0b26be48c00a2722da0af6fa45fdbe Mon Sep 17 00:00:00 2001 From: failingtwice Date: Wed, 15 Jan 2025 18:34:14 +0500 Subject: [PATCH 14/24] fix: rename roles --- contracts/0.8.25/vaults/Delegation.sol | 287 ++++++++++++------------- 1 file changed, 138 insertions(+), 149 deletions(-) diff --git a/contracts/0.8.25/vaults/Delegation.sol b/contracts/0.8.25/vaults/Delegation.sol index 08429de3c..d244ef270 100644 --- a/contracts/0.8.25/vaults/Delegation.sol +++ b/contracts/0.8.25/vaults/Delegation.sol @@ -13,91 +13,86 @@ import {Dashboard} from "./Dashboard.sol"; * * The delegation hierarchy is as follows: * - DEFAULT_ADMIN_ROLE is the underlying owner of StakingVault; - * - OPERATOR_ROLE is the node operator of StakingVault; and itself is the role admin, - * and the DEFAULT_ADMIN_ROLE cannot assign OPERATOR_ROLE; - * - CLAIM_OPERATOR_DUE_ROLE is the role that can claim operator due; is assigned by OPERATOR_ROLE; + * - NODE_OPERATOR_MANAGER_ROLE is the node operator manager of StakingVault; and itself is the role admin, + * and the DEFAULT_ADMIN_ROLE cannot assign NODE_OPERATOR_MANAGER_ROLE; + * - NODE_OPERATOR_FEE_CLAIMER_ROLE is the role that can claim node operator fee; is assigned by NODE_OPERATOR_MANAGER_ROLE; * - * Additionally, the following roles are assigned by the owner (DEFAULT_ADMIN_ROLE): - * - CURATOR_ROLE is the curator of StakingVault empowered by the owner; - * performs the daily operations of the StakingVault on behalf of the owner; - * - STAKER_ROLE funds and withdraws from the StakingVault; - * - TOKEN_MASTER_ROLE mints and burns shares of stETH backed by the StakingVault; + * Additionally, the following roles are assigned by DEFAULT_ADMIN_ROLE: + * - CURATOR_ROLE is the curator of StakingVault and perfoms some operations on behalf of DEFAULT_ADMIN_ROLE; + * - FUND_WITHDRAW_ROLE funds and withdraws from the StakingVault; + * - MINT_BURN_ROLE mints and burns shares of stETH backed by the StakingVault; * - * Operator and Curator have their respective fees and dues. - * The fee is calculated as a percentage (in basis points) of the StakingVault rewards. - * The due is the amount of ether that is owed to the Curator or Operator based on the fee. + * The curator and node operator have their respective fees. + * The feeBP is the percentage (in basis points) of the StakingVault rewards. + * The unclaimed fee is the amount of ether that is owed to the curator or node operator based on the feeBP. */ contract Delegation is Dashboard { /** - * @notice Maximum fee value; equals to 100%. + * @notice Maximum combined feeBP value; equals to 100%. */ - uint256 private constant MAX_FEE = TOTAL_BASIS_POINTS; + uint256 private constant MAX_FEE_BP = TOTAL_BASIS_POINTS; /** - * @notice Curator: + * @notice Curator role: * - sets curator fee; - * - votes operator fee; + * - claims curator fee; * - votes on vote lifetime; - * - votes on ownership transfer; - * - claims curator due. + * - votes on node operator fee; + * - votes on ownership transfer. */ bytes32 public constant CURATOR_ROLE = keccak256("Vault.Delegation.CuratorRole"); /** - * @notice Staker: - * - funds vault; - * - withdraws from vault. + * @notice Mint/burn role: + * - mints shares of stETH; + * - burns shares of stETH. */ - bytes32 public constant STAKER_ROLE = keccak256("Vault.Delegation.StakerRole"); + bytes32 public constant MINT_BURN_ROLE = keccak256("Vault.Delegation.MintBurnRole"); /** - * @notice Token master: - * - mints shares; - * - burns shares. + * @notice Fund/withdraw role: + * - funds StakingVault; + * - withdraws from StakingVault. */ - bytes32 public constant TOKEN_MASTER_ROLE = keccak256("Vault.Delegation.TokenMasterRole"); + bytes32 public constant FUND_WITHDRAW_ROLE = keccak256("Vault.Delegation.FundWithdrawRole"); /** - * @notice Node operator: + * @notice Node operator manager role: * - votes on vote lifetime; - * - votes on operator fee; + * - votes on node operator fee; * - votes on ownership transfer; - * - is the role admin for CLAIM_OPERATOR_DUE_ROLE. + * - assigns NODE_OPERATOR_FEE_CLAIMER_ROLE. */ - bytes32 public constant OPERATOR_ROLE = keccak256("Vault.Delegation.OperatorRole"); + bytes32 public constant NODE_OPERATOR_MANAGER_ROLE = keccak256("Vault.Delegation.NodeOperatorManagerRole"); /** - * @notice Claim operator due: - * - claims operator due. + * @notice Node operator fee claimer role: + * - claims node operator fee. */ - bytes32 public constant CLAIM_OPERATOR_DUE_ROLE = keccak256("Vault.Delegation.ClaimOperatorDueRole"); + bytes32 public constant NODE_OPERATOR_FEE_CLAIMER_ROLE = keccak256("Vault.Delegation.NodeOperatorFeeClaimerRole"); /** - * @notice Curator fee in basis points; combined with operator fee cannot exceed 100%. - * The term "fee" is used to represent the percentage (in basis points) of curator's share of the rewards. - * The term "due" is used to represent the actual amount of fees in ether. - * The curator due in ether is returned by `curatorDue()`. + * @notice Curator fee in basis points; combined with node operator fee cannot exceed 100%. + * The curator's unclaimed fee in ether is returned by `curatorUnclaimedFee()`. */ - uint256 public curatorFee; + uint256 public curatorFeeBP; /** - * @notice The last report for which curator due was claimed. Updated on each claim. + * @notice The last report for which curator fee was claimed. Updated on each claim. */ - IStakingVault.Report public curatorDueClaimedReport; + IStakingVault.Report public curatorFeeClaimedReport; /** - * @notice Operator fee in basis points; combined with curator fee cannot exceed 100%. - * The term "fee" is used to represent the percentage (in basis points) of operator's share of the rewards. - * The term "due" is used to represent the actual amount of fees in ether. - * The operator due in ether is returned by `operatorDue()`. + * @notice Node operator fee in basis points; combined with curator fee cannot exceed 100%, or 10,000 basis points. + * The node operator's unclaimed fee in ether is returned by `nodeOperatorUnclaimedFee()`. */ - uint256 public operatorFee; + uint256 public nodeOperatorFeeBP; /** - * @notice The last report for which operator due was claimed. Updated on each claim. + * @notice The last report for which node operator fee was claimed. Updated on each claim. */ - IStakingVault.Report public operatorDueClaimedReport; + IStakingVault.Report public nodeOperatorFeeClaimedReport; /** * @notice Tracks committee votes @@ -115,7 +110,8 @@ contract Delegation is Dashboard { uint256 public voteLifetime; /** - * @notice Initializes the contract with the stETH address. + * @notice Constructs the contract. + * @dev Stores token addresses in the bytecode to reduce gas costs. * @param _stETH The address of the stETH token. * @param _weth Address of the weth token contract. * @param _wstETH Address of the wstETH token contract. @@ -126,13 +122,11 @@ contract Delegation is Dashboard { * @notice Initializes the contract: * - sets the address of StakingVault; * - sets up the roles; - * - sets the vote lifetime to 7 days (can be changed later by CURATOR_ROLE and OPERATOR_ROLE). + * - sets the vote lifetime to 7 days (can be changed later by CURATOR_ROLE and NODE_OPERATOR_MANAGER_ROLE). * @param _stakingVault The address of StakingVault. - * @dev The msg.sender here is VaultFactory. It is given the OPERATOR_ROLE - * to be able to set initial operatorFee in VaultFactory, because only OPERATOR_ROLE - * is the admin role for itself. The rest of the roles are also temporarily given to - * VaultFactory to be able to set initial config in VaultFactory. - * All the roles are revoked from VaultFactory at the end of the initialization. + * @dev The msg.sender here is VaultFactory. The VaultFactory is temporarily granted + * DEFAULT_ADMIN_ROLE AND NODE_OPERATOR_MANAGER_ROLE to be able to set initial fees and roles in VaultFactory. + * All the roles are revoked from VaultFactory by the end of the initialization. */ function initialize(address _stakingVault) external override { _initialize(_stakingVault); @@ -140,51 +134,51 @@ contract Delegation is Dashboard { // the next line implies that the msg.sender is an operator // however, the msg.sender is the VaultFactory, and the role will be revoked // at the end of the initialization - _grantRole(OPERATOR_ROLE, msg.sender); - _setRoleAdmin(OPERATOR_ROLE, OPERATOR_ROLE); - _setRoleAdmin(CLAIM_OPERATOR_DUE_ROLE, OPERATOR_ROLE); + _grantRole(NODE_OPERATOR_MANAGER_ROLE, msg.sender); + _setRoleAdmin(NODE_OPERATOR_MANAGER_ROLE, NODE_OPERATOR_MANAGER_ROLE); + _setRoleAdmin(NODE_OPERATOR_FEE_CLAIMER_ROLE, NODE_OPERATOR_MANAGER_ROLE); voteLifetime = 7 days; } /** - * @notice Returns the accumulated curator due in ether, - * calculated as: CD = (SVR * CF) / TBP + * @notice Returns the accumulated unclaimed curator fee in ether, + * calculated as: U = (R * F) / T * where: - * - CD is the curator due; - * - SVR is the StakingVault rewards accrued since the last curator due claim; - * - CF is the curator fee in basis points; - * - TBP is the total basis points (100%). - * @return uint256: the amount of due ether. - */ - function curatorDue() public view returns (uint256) { - return _calculateDue(curatorFee, curatorDueClaimedReport); + * - U is the curator unclaimed fee; + * - R is the StakingVault rewards accrued since the last curator fee claim; + * - F is `curatorFeeBP`; + * - T is the total basis points, 10,000. + * @return uint256: the amount of unclaimed fee in ether. + */ + function curatorUnclaimedFee() public view returns (uint256) { + return _calculateFee(curatorFeeBP, curatorFeeClaimedReport); } /** - * @notice Returns the accumulated operator due in ether, - * calculated as: OD = (SVR * OF) / TBP + * @notice Returns the accumulated unclaimed node operator fee in ether, + * calculated as: U = (R * F) / T * where: - * - OD is the operator due; - * - SVR is the StakingVault rewards accrued since the last operator due claim; - * - OF is the operator fee in basis points; - * - TBP is the total basis points (100%). - * @return uint256: the amount of due ether. - */ - function operatorDue() public view returns (uint256) { - return _calculateDue(operatorFee, operatorDueClaimedReport); + * - U is the node operator unclaimed fee; + * - R is the StakingVault rewards accrued since the last node operator fee claim; + * - F is `nodeOperatorFeeBP`; + * - T is the total basis points, 10,000. + * @return uint256: the amount of unclaimed fee in ether. + */ + function nodeOperatorUnclaimedFee() public view returns (uint256) { + return _calculateFee(nodeOperatorFeeBP, nodeOperatorFeeClaimedReport); } /** * @notice Returns the unreserved amount of ether, * i.e. the amount of ether that is not locked in the StakingVault - * and not reserved for curator due and operator due. + * and not reserved for curator and node operator fees. * This amount does not account for the current balance of the StakingVault and * can return a value greater than the actual balance of the StakingVault. * @return uint256: the amount of unreserved ether. */ function unreserved() public view returns (uint256) { - uint256 reserved = stakingVault.locked() + curatorDue() + operatorDue(); + uint256 reserved = stakingVault.locked() + curatorUnclaimedFee() + nodeOperatorUnclaimedFee(); uint256 valuation = stakingVault.valuation(); return reserved > valuation ? 0 : valuation - reserved; @@ -193,33 +187,33 @@ contract Delegation is Dashboard { /** * @notice Returns the committee that can: * - change the vote lifetime; - * - set the operator fee; + * - set the node operator fee; * - transfer the ownership of the StakingVault. * @return committee is an array of roles that form the voting committee. */ function votingCommittee() public pure returns (bytes32[] memory committee) { committee = new bytes32[](2); committee[0] = CURATOR_ROLE; - committee[1] = OPERATOR_ROLE; + committee[1] = NODE_OPERATOR_MANAGER_ROLE; } /** * @notice Funds the StakingVault with ether. */ - function fund() external payable override onlyRole(STAKER_ROLE) { + function fund() external payable override onlyRole(FUND_WITHDRAW_ROLE) { _fund(); } /** * @notice Withdraws ether from the StakingVault. * Cannot withdraw more than the unreserved amount: which is the amount of ether - * that is not locked in the StakingVault and not reserved for curator due and operator due. + * that is not locked in the StakingVault and not reserved for curator and node operator fees. * Does not include a check for the balance of the StakingVault, this check is present * on the StakingVault itself. * @param _recipient The address to which the ether will be sent. * @param _ether The amount of ether to withdraw. */ - function withdraw(address _recipient, uint256 _ether) external override onlyRole(STAKER_ROLE) { + function withdraw(address _recipient, uint256 _ether) external override onlyRole(FUND_WITHDRAW_ROLE) { if (_recipient == address(0)) revert ZeroArgument("_recipient"); if (_ether == 0) revert ZeroArgument("_ether"); uint256 withdrawable = unreserved(); @@ -238,7 +232,7 @@ contract Delegation is Dashboard { function mint( address _recipient, uint256 _amountOfShares - ) external payable override onlyRole(TOKEN_MASTER_ROLE) fundAndProceed { + ) external payable override onlyRole(MINT_BURN_ROLE) fundAndProceed { _mint(_recipient, _amountOfShares); } @@ -249,7 +243,7 @@ contract Delegation is Dashboard { * NB: Delegation contract must have ERC-20 approved allowance to burn sender's shares. * @param _amountOfShares The amount of shares to burn. */ - function burn(uint256 _amountOfShares) external override onlyRole(TOKEN_MASTER_ROLE) { + function burn(uint256 _amountOfShares) external override onlyRole(MINT_BURN_ROLE) { _burn(_amountOfShares); } @@ -277,56 +271,56 @@ contract Delegation is Dashboard { /** * @notice Sets the curator fee. * The curator fee is the percentage (in basis points) of curator's share of the StakingVault rewards. - * The curator fee combined with the operator fee cannot exceed 100%. - * The curator due must be claimed before the curator fee can be changed to avoid - * @param _newCuratorFee The new curator fee in basis points. + * The curator and node operator fees combined cannot exceed 100%, or 10,000 basis points. + * The function will revert if the curator fee is unclaimed. + * @param _newCuratorFeeBP The new curator fee in basis points. */ - function setCuratorFee(uint256 _newCuratorFee) external onlyRole(CURATOR_ROLE) { - if (_newCuratorFee + operatorFee > MAX_FEE) revert CombinedFeesExceed100Percent(); - if (curatorDue() > 0) revert CuratorDueUnclaimed(); - uint256 oldCuratorFee = curatorFee; - curatorFee = _newCuratorFee; + function setCuratorFeeBP(uint256 _newCuratorFeeBP) external onlyRole(CURATOR_ROLE) { + if (_newCuratorFeeBP + nodeOperatorFeeBP > MAX_FEE_BP) revert CombinedFeesExceed100Percent(); + if (curatorUnclaimedFee() > 0) revert CuratorFeeUnclaimed(); + uint256 oldCuratorFeeBP = curatorFeeBP; + curatorFeeBP = _newCuratorFeeBP; - emit CuratorFeeSet(msg.sender, oldCuratorFee, _newCuratorFee); + emit CuratorFeeBPSet(msg.sender, oldCuratorFeeBP, _newCuratorFeeBP); } /** - * @notice Sets the operator fee. - * The operator fee is the percentage (in basis points) of operator's share of the StakingVault rewards. - * The operator fee combined with the curator fee cannot exceed 100%. - * Note that the function reverts if the operator due is not claimed and all the votes must be recasted to execute it again, - * which is why the deciding voter must make sure that the operator due is claimed before calling this function. - * @param _newOperatorFee The new operator fee in basis points. + * @notice Sets the node operator fee. + * The node operator fee is the percentage (in basis points) of node operator's share of the StakingVault rewards. + * The node operator fee combined with the curator fee cannot exceed 100%. + * Note that the function reverts if the node operator fee is unclaimed and all the votes must be recasted to execute it again, + * which is why the deciding voter must make sure that `nodeOperatorUnclaimedFee()` is 0 before calling this function. + * @param _newNodeOperatorFeeBP The new node operator fee in basis points. */ - function setOperatorFee(uint256 _newOperatorFee) external onlyIfVotedBy(votingCommittee()) { - if (_newOperatorFee + curatorFee > MAX_FEE) revert CombinedFeesExceed100Percent(); - if (operatorDue() > 0) revert OperatorDueUnclaimed(); - uint256 oldOperatorFee = operatorFee; - operatorFee = _newOperatorFee; + function setNodeOperatorFeeBP(uint256 _newNodeOperatorFeeBP) external onlyIfVotedBy(votingCommittee()) { + if (_newNodeOperatorFeeBP + curatorFeeBP > MAX_FEE_BP) revert CombinedFeesExceed100Percent(); + if (nodeOperatorUnclaimedFee() > 0) revert NodeOperatorFeeUnclaimed(); + uint256 oldNodeOperatorFeeBP = nodeOperatorFeeBP; + nodeOperatorFeeBP = _newNodeOperatorFeeBP; - emit OperatorFeeSet(msg.sender, oldOperatorFee, _newOperatorFee); + emit NodeOperatorFeeBPSet(msg.sender, oldNodeOperatorFeeBP, _newNodeOperatorFeeBP); } /** - * @notice Claims the curator due. - * @param _recipient The address to which the curator due will be sent. + * @notice Claims the curator fee. + * @param _recipient The address to which the curator fee will be sent. */ - function claimCuratorDue(address _recipient) external onlyRole(CURATOR_ROLE) { - uint256 due = curatorDue(); - curatorDueClaimedReport = stakingVault.latestReport(); - _claimDue(_recipient, due); + function claimCuratorFee(address _recipient) external onlyRole(CURATOR_ROLE) { + uint256 fee = curatorUnclaimedFee(); + curatorFeeClaimedReport = stakingVault.latestReport(); + _claimFee(_recipient, fee); } /** - * @notice Claims the operator due. - * Note that the authorized role is CLAIM_OPERATOR_DUE_ROLE, not OPERATOR_ROLE, - * although OPERATOR_ROLE is the admin role for CLAIM_OPERATOR_DUE_ROLE. - * @param _recipient The address to which the operator due will be sent. + * @notice Claims the node oper ator fee. + * Note that the authorized role is NODE_OPERATOR_FEE_CLAIMER_ROLE, not OPERATOR_ROLE, + * although OPERATOR_ROLE is the admin role for NODE_OPERATOR_FEE_CLAIMER_ROLE. + * @param _recipient The address to which the node operator fee will be sent. */ - function claimOperatorDue(address _recipient) external onlyRole(CLAIM_OPERATOR_DUE_ROLE) { - uint256 due = operatorDue(); - operatorDueClaimedReport = stakingVault.latestReport(); - _claimDue(_recipient, due); + function claimNodeOperatorFee(address _recipient) external onlyRole(NODE_OPERATOR_FEE_CLAIMER_ROLE) { + uint256 fee = nodeOperatorUnclaimedFee(); + nodeOperatorFeeClaimedReport = stakingVault.latestReport(); + _claimFee(_recipient, fee); } /** @@ -425,13 +419,13 @@ contract Delegation is Dashboard { } /** - * @dev Calculates the curator/operatordue amount based on the fee and the last claimed report. - * @param _fee The fee in basis points. + * @dev Calculates the curator/node operator fee amount based on the fee and the last claimed report. + * @param _feeBP The fee in basis points. * @param _lastClaimedReport The last claimed report. - * @return The accrued due amount. + * @return The accrued fee amount. */ - function _calculateDue( - uint256 _fee, + function _calculateFee( + uint256 _feeBP, IStakingVault.Report memory _lastClaimedReport ) internal view returns (uint256) { IStakingVault.Report memory latestReport = stakingVault.latestReport(); @@ -439,19 +433,19 @@ contract Delegation is Dashboard { int128 rewardsAccrued = int128(latestReport.valuation - _lastClaimedReport.valuation) - (latestReport.inOutDelta - _lastClaimedReport.inOutDelta); - return rewardsAccrued > 0 ? (uint256(uint128(rewardsAccrued)) * _fee) / TOTAL_BASIS_POINTS : 0; + return rewardsAccrued > 0 ? (uint256(uint128(rewardsAccrued)) * _feeBP) / TOTAL_BASIS_POINTS : 0; } /** - * @dev Claims the curator/operator due amount. - * @param _recipient The address to which the due will be sent. - * @param _due The accrued due amount. + * @dev Claims the curator/node operator fee amount. + * @param _recipient The address to which the fee will be sent. + * @param _fee The accrued fee amount. */ - function _claimDue(address _recipient, uint256 _due) internal { + function _claimFee(address _recipient, uint256 _fee) internal { if (_recipient == address(0)) revert ZeroArgument("_recipient"); - if (_due == 0) revert NoDueToClaim(); + if (_fee == 0) revert ZeroArgument("_fee"); - _withdraw(_recipient, _due); + _withdraw(_recipient, _fee); } /** @@ -463,17 +457,17 @@ contract Delegation is Dashboard { /** * @dev Emitted when the curator fee is set. - * @param oldCuratorFee The old curator fee. - * @param newCuratorFee The new curator fee. + * @param oldCuratorFeeBP The old curator fee. + * @param newCuratorFeeBP The new curator fee. */ - event CuratorFeeSet(address indexed sender, uint256 oldCuratorFee, uint256 newCuratorFee); + event CuratorFeeBPSet(address indexed sender, uint256 oldCuratorFeeBP, uint256 newCuratorFeeBP); /** - * @dev Emitted when the operator fee is set. - * @param oldOperatorFee The old operator fee. - * @param newOperatorFee The new operator fee. + * @dev Emitted when the node operator fee is set. + * @param oldNodeOperatorFeeBP The old node operator fee. + * @param newNodeOperatorFeeBP The new node operator fee. */ - event OperatorFeeSet(address indexed sender, uint256 oldOperatorFee, uint256 newOperatorFee); + event NodeOperatorFeeBPSet(address indexed sender, uint256 oldNodeOperatorFeeBP, uint256 newNodeOperatorFeeBP); /** * @dev Emitted when a committee member votes. @@ -490,17 +484,17 @@ contract Delegation is Dashboard { error NotACommitteeMember(); /** - * @dev Error emitted when the curator due is unclaimed. + * @dev Error emitted when the curator fee is unclaimed. */ - error CuratorDueUnclaimed(); + error CuratorFeeUnclaimed(); /** - * @dev Error emitted when the operator due is unclaimed. + * @dev Error emitted when the node operator fee is unclaimed. */ - error OperatorDueUnclaimed(); + error NodeOperatorFeeUnclaimed(); /** - * @dev Error emitted when the combined fees exceed 100%. + * @dev Error emitted when the combined feeBPs exceed 100%. */ error CombinedFeesExceed100Percent(); @@ -508,9 +502,4 @@ contract Delegation is Dashboard { * @dev Error emitted when the requested amount exceeds the unreserved amount. */ error RequestedAmountExceedsUnreserved(); - - /** - * @dev Error emitted when there is no due to claim. - */ - error NoDueToClaim(); } From 07b62494b0c4fdb2e2b4d0ee0341fab5e8199afd Mon Sep 17 00:00:00 2001 From: failingtwice Date: Wed, 15 Jan 2025 18:37:29 +0500 Subject: [PATCH 15/24] feat: admin sets curator fee --- contracts/0.8.25/vaults/Delegation.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/0.8.25/vaults/Delegation.sol b/contracts/0.8.25/vaults/Delegation.sol index d244ef270..d79a62d05 100644 --- a/contracts/0.8.25/vaults/Delegation.sol +++ b/contracts/0.8.25/vaults/Delegation.sol @@ -275,7 +275,7 @@ contract Delegation is Dashboard { * The function will revert if the curator fee is unclaimed. * @param _newCuratorFeeBP The new curator fee in basis points. */ - function setCuratorFeeBP(uint256 _newCuratorFeeBP) external onlyRole(CURATOR_ROLE) { + function setCuratorFeeBP(uint256 _newCuratorFeeBP) external onlyRole(DEFAULT_ADMIN_ROLE) { if (_newCuratorFeeBP + nodeOperatorFeeBP > MAX_FEE_BP) revert CombinedFeesExceed100Percent(); if (curatorUnclaimedFee() > 0) revert CuratorFeeUnclaimed(); uint256 oldCuratorFeeBP = curatorFeeBP; @@ -313,8 +313,8 @@ contract Delegation is Dashboard { /** * @notice Claims the node oper ator fee. - * Note that the authorized role is NODE_OPERATOR_FEE_CLAIMER_ROLE, not OPERATOR_ROLE, - * although OPERATOR_ROLE is the admin role for NODE_OPERATOR_FEE_CLAIMER_ROLE. + * Note that the authorized role is NODE_OPERATOR_FEE_CLAIMER_ROLE, not NODE_OPERATOR_MANAGER_ROLE, + * although NODE_OPERATOR_MANAGER_ROLE is the admin role for NODE_OPERATOR_FEE_CLAIMER_ROLE. * @param _recipient The address to which the node operator fee will be sent. */ function claimNodeOperatorFee(address _recipient) external onlyRole(NODE_OPERATOR_FEE_CLAIMER_ROLE) { From 4f7a96a0c7e12c28eedc79bb5ac4544d37d5551f Mon Sep 17 00:00:00 2001 From: failingtwice Date: Wed, 15 Jan 2025 18:37:51 +0500 Subject: [PATCH 16/24] fix: typo --- contracts/0.8.25/vaults/Delegation.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.8.25/vaults/Delegation.sol b/contracts/0.8.25/vaults/Delegation.sol index d79a62d05..f35b860b8 100644 --- a/contracts/0.8.25/vaults/Delegation.sol +++ b/contracts/0.8.25/vaults/Delegation.sol @@ -312,7 +312,7 @@ contract Delegation is Dashboard { } /** - * @notice Claims the node oper ator fee. + * @notice Claims the node operator fee. * Note that the authorized role is NODE_OPERATOR_FEE_CLAIMER_ROLE, not NODE_OPERATOR_MANAGER_ROLE, * although NODE_OPERATOR_MANAGER_ROLE is the admin role for NODE_OPERATOR_FEE_CLAIMER_ROLE. * @param _recipient The address to which the node operator fee will be sent. From 5ee67ae39803c63e12ec9f99ebe5390a2879f604 Mon Sep 17 00:00:00 2001 From: Aleksei Potapkin Date: Wed, 15 Jan 2025 21:46:28 +0200 Subject: [PATCH 17/24] chore: added a comment about denominator greater than zero --- contracts/0.4.24/Lido.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 4668bff76..2194052c4 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -196,7 +196,7 @@ contract Lido is Versioned, StETHPermit, AragonApp { * @param _eip712StETH eip712 helper contract for StETH */ function initialize(address _lidoLocator, address _eip712StETH) public payable onlyInit { - _bootstrapInitialHolder(); + _bootstrapInitialHolder(); // stone in the elevator LIDO_LOCATOR_POSITION.setStorageAddress(_lidoLocator); emit LidoLocatorSet(_lidoLocator); @@ -958,7 +958,7 @@ contract Lido is Versioned, StETHPermit, AragonApp { /// @dev the denominator (in shares) of the share rate for StETH conversion between shares and ether and vice versa. function _getShareRateDenominator() internal view returns (uint256) { uint256 externalShares = EXTERNAL_SHARES_POSITION.getStorageUint256(); - uint256 internalShares = _getTotalShares() - externalShares; + uint256 internalShares = _getTotalShares() - externalShares; // never 0 because of the stone in the elevator return internalShares; } From 66390803731fb9263ca0ecb77d2af3847e6ac4eb Mon Sep 17 00:00:00 2001 From: failingtwice Date: Thu, 16 Jan 2025 13:54:48 +0500 Subject: [PATCH 18/24] test(StakingVault): fix test after renaming --- test/0.8.25/vaults/staking-vault/staking-vault.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/0.8.25/vaults/staking-vault/staking-vault.test.ts b/test/0.8.25/vaults/staking-vault/staking-vault.test.ts index eb4b27468..b08d97b6c 100644 --- a/test/0.8.25/vaults/staking-vault/staking-vault.test.ts +++ b/test/0.8.25/vaults/staking-vault/staking-vault.test.ts @@ -120,8 +120,7 @@ describe("StakingVault", () => { expect(await stakingVault.DEPOSIT_CONTRACT()).to.equal(depositContractAddress); expect(await stakingVault.getBeacon()).to.equal(vaultFactoryAddress); expect(await stakingVault.owner()).to.equal(await vaultOwner.getAddress()); - expect(await stakingVault.operator()).to.equal(operator); - + expect(await stakingVault.nodeOperator()).to.equal(operator); expect(await stakingVault.locked()).to.equal(0n); expect(await stakingVault.unlocked()).to.equal(0n); expect(await stakingVault.inOutDelta()).to.equal(0n); From 520f9ba042d50285700552857c434aa07a90a74c Mon Sep 17 00:00:00 2001 From: Aleksei Potapkin Date: Thu, 16 Jan 2025 11:59:13 +0200 Subject: [PATCH 19/24] docs: better comments --- contracts/0.8.25/utils/PausableUntilWithRoles.sol | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/contracts/0.8.25/utils/PausableUntilWithRoles.sol b/contracts/0.8.25/utils/PausableUntilWithRoles.sol index 2fbce151a..e8c2d831b 100644 --- a/contracts/0.8.25/utils/PausableUntilWithRoles.sol +++ b/contracts/0.8.25/utils/PausableUntilWithRoles.sol @@ -20,16 +20,18 @@ abstract contract PausableUntilWithRoles is PausableUntil, AccessControlEnumerab /** * @notice Resume the contract + * @dev Reverts if contracts is not paused + * @dev Reverts if sender has no `RESUME_ROLE` */ function resume() external onlyRole(RESUME_ROLE) { _resume(); } /** - * @notice Pause the contract + * @notice Pause the contract for a specified period * @param _duration pause duration in seconds (use `PAUSE_INFINITELY` for unlimited) * @dev Reverts if contract is already paused - * @dev Reverts reason if sender has no `PAUSE_ROLE` + * @dev Reverts if sender has no `PAUSE_ROLE` * @dev Reverts if zero duration is passed */ function pauseFor(uint256 _duration) external onlyRole(PAUSE_ROLE) { @@ -37,7 +39,7 @@ abstract contract PausableUntilWithRoles is PausableUntil, AccessControlEnumerab } /** - * @notice Pause the contract until a specific timestamp + * @notice Pause the contract until a specified timestamp * @param _pauseUntilInclusive the last second to pause until inclusive * @dev Reverts if the timestamp is in the past * @dev Reverts if sender has no `PAUSE_ROLE` From e93484a7ef69b96b9acdf4e0240fe967b7c21910 Mon Sep 17 00:00:00 2001 From: failingtwice Date: Thu, 16 Jan 2025 15:04:36 +0500 Subject: [PATCH 20/24] test(Delegation): fix test afte renaming --- contracts/0.8.25/vaults/VaultFactory.sol | 42 +-- .../vaults/delegation/delegation.test.ts | 265 ++++++++++-------- 2 files changed, 170 insertions(+), 137 deletions(-) diff --git a/contracts/0.8.25/vaults/VaultFactory.sol b/contracts/0.8.25/vaults/VaultFactory.sol index 2edf21e73..a32e841c9 100644 --- a/contracts/0.8.25/vaults/VaultFactory.sol +++ b/contracts/0.8.25/vaults/VaultFactory.sol @@ -12,31 +12,31 @@ pragma solidity 0.8.25; interface IDelegation { struct InitialState { address curator; - address staker; - address tokenMaster; - address operator; - address claimOperatorDueRole; - uint256 curatorFee; - uint256 operatorFee; + address minterBurner; + address funderWithdrawer; + address nodeOperatorManager; + address nodeOperatorFeeClaimer; + uint256 curatorFeeBP; + uint256 nodeOperatorFeeBP; } function DEFAULT_ADMIN_ROLE() external view returns (bytes32); function CURATOR_ROLE() external view returns (bytes32); - function STAKER_ROLE() external view returns (bytes32); + function FUND_WITHDRAW_ROLE() external view returns (bytes32); - function TOKEN_MASTER_ROLE() external view returns (bytes32); + function MINT_BURN_ROLE() external view returns (bytes32); - function OPERATOR_ROLE() external view returns (bytes32); + function NODE_OPERATOR_MANAGER_ROLE() external view returns (bytes32); - function CLAIM_OPERATOR_DUE_ROLE() external view returns (bytes32); + function NODE_OPERATOR_FEE_CLAIMER_ROLE() external view returns (bytes32); function initialize(address _stakingVault) external; - function setCuratorFee(uint256 _newCuratorFee) external; + function setCuratorFeeBP(uint256 _newCuratorFeeBP) external; - function setOperatorFee(uint256 _newOperatorFee) external; + function setNodeOperatorFeeBP(uint256 _newNodeOperatorFee) external; function grantRole(bytes32 role, address account) external; @@ -74,28 +74,28 @@ contract VaultFactory is UpgradeableBeacon { delegation = IDelegation(Clones.clone(delegationImpl)); // initialize StakingVault - vault.initialize(address(delegation), _delegationInitialState.operator, _stakingVaultInitializerExtraParams); + vault.initialize(address(delegation), _delegationInitialState.nodeOperatorManager, _stakingVaultInitializerExtraParams); // initialize Delegation delegation.initialize(address(vault)); // grant roles to owner, manager, operator delegation.grantRole(delegation.DEFAULT_ADMIN_ROLE(), msg.sender); delegation.grantRole(delegation.CURATOR_ROLE(), _delegationInitialState.curator); - delegation.grantRole(delegation.STAKER_ROLE(), _delegationInitialState.staker); - delegation.grantRole(delegation.TOKEN_MASTER_ROLE(), _delegationInitialState.tokenMaster); - delegation.grantRole(delegation.OPERATOR_ROLE(), _delegationInitialState.operator); - delegation.grantRole(delegation.CLAIM_OPERATOR_DUE_ROLE(), _delegationInitialState.claimOperatorDueRole); + delegation.grantRole(delegation.FUND_WITHDRAW_ROLE(), _delegationInitialState.funderWithdrawer); + delegation.grantRole(delegation.MINT_BURN_ROLE(), _delegationInitialState.minterBurner); + delegation.grantRole(delegation.NODE_OPERATOR_MANAGER_ROLE(), _delegationInitialState.nodeOperatorManager); + delegation.grantRole(delegation.NODE_OPERATOR_FEE_CLAIMER_ROLE(), _delegationInitialState.nodeOperatorFeeClaimer); // grant temporary roles to factory delegation.grantRole(delegation.CURATOR_ROLE(), address(this)); - delegation.grantRole(delegation.OPERATOR_ROLE(), address(this)); + delegation.grantRole(delegation.NODE_OPERATOR_MANAGER_ROLE(), address(this)); // set fees - delegation.setCuratorFee(_delegationInitialState.curatorFee); - delegation.setOperatorFee(_delegationInitialState.operatorFee); + delegation.setCuratorFeeBP(_delegationInitialState.curatorFeeBP); + delegation.setNodeOperatorFeeBP(_delegationInitialState.nodeOperatorFeeBP); // revoke temporary roles from factory delegation.revokeRole(delegation.CURATOR_ROLE(), address(this)); - delegation.revokeRole(delegation.OPERATOR_ROLE(), address(this)); + delegation.revokeRole(delegation.NODE_OPERATOR_MANAGER_ROLE(), address(this)); delegation.revokeRole(delegation.DEFAULT_ADMIN_ROLE(), address(this)); emit VaultCreated(address(delegation), address(vault)); diff --git a/test/0.8.25/vaults/delegation/delegation.test.ts b/test/0.8.25/vaults/delegation/delegation.test.ts index 5ad7b08ea..408ecc0c9 100644 --- a/test/0.8.25/vaults/delegation/delegation.test.ts +++ b/test/0.8.25/vaults/delegation/delegation.test.ts @@ -25,10 +25,10 @@ const MAX_FEE = BP_BASE; describe("Delegation.sol", () => { let vaultOwner: HardhatEthersSigner; let curator: HardhatEthersSigner; - let staker: HardhatEthersSigner; - let tokenMaster: HardhatEthersSigner; - let operator: HardhatEthersSigner; - let claimOperatorDueRole: HardhatEthersSigner; + let funderWithdrawer: HardhatEthersSigner; + let minterBurner: HardhatEthersSigner; + let nodeOperatorManager: HardhatEthersSigner; + let nodeOperatorFeeClaimer: HardhatEthersSigner; let stranger: HardhatEthersSigner; let factoryOwner: HardhatEthersSigner; let hubSigner: HardhatEthersSigner; @@ -49,8 +49,17 @@ describe("Delegation.sol", () => { let originalState: string; before(async () => { - [vaultOwner, curator, staker, tokenMaster, operator, claimOperatorDueRole, stranger, factoryOwner, rewarder] = - await ethers.getSigners(); + [ + vaultOwner, + curator, + funderWithdrawer, + minterBurner, + nodeOperatorManager, + nodeOperatorFeeClaimer, + stranger, + factoryOwner, + rewarder, + ] = await ethers.getSigners(); steth = await ethers.deployContract("StETH__MockForDelegation"); weth = await ethers.deployContract("WETH9__MockForVault"); @@ -74,12 +83,18 @@ describe("Delegation.sol", () => { expect(await factory.implementation()).to.equal(vaultImpl); expect(await factory.delegationImpl()).to.equal(delegationImpl); - const vaultCreationTx = await factory - .connect(vaultOwner) - .createVault( - { curator, staker, tokenMaster, operator, claimOperatorDueRole, curatorFee: 0n, operatorFee: 0n }, - "0x", - ); + const vaultCreationTx = await factory.connect(vaultOwner).createVault( + { + curator, + funderWithdrawer, + minterBurner, + nodeOperatorManager, + nodeOperatorFeeClaimer, + curatorFeeBP: 0n, + nodeOperatorFeeBP: 0n, + }, + "0x", + ); const vaultCreationReceipt = await vaultCreationTx.wait(); if (!vaultCreationReceipt) throw new Error("Vault creation receipt not found"); @@ -157,7 +172,7 @@ describe("Delegation.sol", () => { context("initialized state", () => { it("initializes the contract correctly", async () => { expect(await vault.owner()).to.equal(delegation); - expect(await vault.operator()).to.equal(operator); + expect(await vault.nodeOperator()).to.equal(nodeOperatorManager); expect(await delegation.stakingVault()).to.equal(vault); expect(await delegation.vaultHub()).to.equal(hub); @@ -166,21 +181,22 @@ describe("Delegation.sol", () => { expect(await delegation.getRoleMemberCount(await delegation.DEFAULT_ADMIN_ROLE())).to.equal(1); expect(await delegation.hasRole(await delegation.CURATOR_ROLE(), curator)).to.be.true; expect(await delegation.getRoleMemberCount(await delegation.CURATOR_ROLE())).to.equal(1); - expect(await delegation.hasRole(await delegation.STAKER_ROLE(), staker)).to.be.true; - expect(await delegation.getRoleMemberCount(await delegation.STAKER_ROLE())).to.equal(1); - expect(await delegation.hasRole(await delegation.TOKEN_MASTER_ROLE(), tokenMaster)).to.be.true; - expect(await delegation.getRoleMemberCount(await delegation.TOKEN_MASTER_ROLE())).to.equal(1); - expect(await delegation.hasRole(await delegation.OPERATOR_ROLE(), operator)).to.be.true; - expect(await delegation.getRoleMemberCount(await delegation.OPERATOR_ROLE())).to.equal(1); - expect(await delegation.hasRole(await delegation.CLAIM_OPERATOR_DUE_ROLE(), claimOperatorDueRole)).to.be.true; - expect(await delegation.getRoleMemberCount(await delegation.CLAIM_OPERATOR_DUE_ROLE())).to.equal(1); - - expect(await delegation.curatorFee()).to.equal(0n); - expect(await delegation.operatorFee()).to.equal(0n); - expect(await delegation.curatorDue()).to.equal(0n); - expect(await delegation.operatorDue()).to.equal(0n); - expect(await delegation.curatorDueClaimedReport()).to.deep.equal([0n, 0n]); - expect(await delegation.operatorDueClaimedReport()).to.deep.equal([0n, 0n]); + expect(await delegation.hasRole(await delegation.FUND_WITHDRAW_ROLE(), funderWithdrawer)).to.be.true; + expect(await delegation.getRoleMemberCount(await delegation.FUND_WITHDRAW_ROLE())).to.equal(1); + expect(await delegation.hasRole(await delegation.MINT_BURN_ROLE(), minterBurner)).to.be.true; + expect(await delegation.getRoleMemberCount(await delegation.MINT_BURN_ROLE())).to.equal(1); + expect(await delegation.hasRole(await delegation.NODE_OPERATOR_MANAGER_ROLE(), nodeOperatorManager)).to.be.true; + expect(await delegation.getRoleMemberCount(await delegation.NODE_OPERATOR_MANAGER_ROLE())).to.equal(1); + expect(await delegation.hasRole(await delegation.NODE_OPERATOR_FEE_CLAIMER_ROLE(), nodeOperatorFeeClaimer)).to.be + .true; + expect(await delegation.getRoleMemberCount(await delegation.NODE_OPERATOR_FEE_CLAIMER_ROLE())).to.equal(1); + + expect(await delegation.curatorFeeBP()).to.equal(0n); + expect(await delegation.nodeOperatorFeeBP()).to.equal(0n); + expect(await delegation.curatorUnclaimedFee()).to.equal(0n); + expect(await delegation.nodeOperatorUnclaimedFee()).to.equal(0n); + expect(await delegation.curatorFeeClaimedReport()).to.deep.equal([0n, 0n]); + expect(await delegation.nodeOperatorFeeClaimedReport()).to.deep.equal([0n, 0n]); }); }); @@ -188,7 +204,7 @@ describe("Delegation.sol", () => { it("returns the correct roles", async () => { expect(await delegation.votingCommittee()).to.deep.equal([ await delegation.CURATOR_ROLE(), - await delegation.OPERATOR_ROLE(), + await delegation.NODE_OPERATOR_MANAGER_ROLE(), ]); }); }); @@ -212,55 +228,54 @@ describe("Delegation.sol", () => { .withArgs(curator, await delegation.CURATOR_ROLE(), voteTimestamp, msgData); voteTimestamp = await getNextBlockTimestamp(); - await expect(delegation.connect(operator).setVoteLifetime(newVoteLifetime)) + await expect(delegation.connect(nodeOperatorManager).setVoteLifetime(newVoteLifetime)) .to.emit(delegation, "RoleMemberVoted") - .withArgs(operator, await delegation.OPERATOR_ROLE(), voteTimestamp, msgData) + .withArgs(nodeOperatorManager, await delegation.NODE_OPERATOR_MANAGER_ROLE(), voteTimestamp, msgData) .and.to.emit(delegation, "VoteLifetimeSet") - .withArgs(operator, oldVoteLifetime, newVoteLifetime); + .withArgs(nodeOperatorManager, oldVoteLifetime, newVoteLifetime); expect(await delegation.voteLifetime()).to.equal(newVoteLifetime); }); }); - context("claimCuratorDue", () => { + context("claimCuratorFee", () => { it("reverts if the caller is not a member of the curator due claim role", async () => { - await expect(delegation.connect(stranger).claimCuratorDue(stranger)) + await expect(delegation.connect(stranger).claimCuratorFee(stranger)) .to.be.revertedWithCustomError(delegation, "AccessControlUnauthorizedAccount") .withArgs(stranger, await delegation.CURATOR_ROLE()); }); it("reverts if the recipient is the zero address", async () => { - await expect(delegation.connect(curator).claimCuratorDue(ethers.ZeroAddress)) + await expect(delegation.connect(curator).claimCuratorFee(ethers.ZeroAddress)) .to.be.revertedWithCustomError(delegation, "ZeroArgument") .withArgs("_recipient"); }); it("reverts if the due is zero", async () => { - expect(await delegation.curatorDue()).to.equal(0n); - await expect(delegation.connect(curator).claimCuratorDue(stranger)).to.be.revertedWithCustomError( - delegation, - "NoDueToClaim", - ); + expect(await delegation.curatorUnclaimedFee()).to.equal(0n); + await expect(delegation.connect(curator).claimCuratorFee(stranger)) + .to.be.revertedWithCustomError(delegation, "ZeroArgument") + .withArgs("_fee"); }); it("claims the due", async () => { const curatorFee = 10_00n; // 10% - await delegation.connect(curator).setCuratorFee(curatorFee); - expect(await delegation.curatorFee()).to.equal(curatorFee); + await delegation.connect(vaultOwner).setCuratorFeeBP(curatorFee); + expect(await delegation.curatorFeeBP()).to.equal(curatorFee); const rewards = ether("1"); await vault.connect(hubSigner).report(rewards, 0n, 0n); const expectedDue = (rewards * curatorFee) / BP_BASE; - expect(await delegation.curatorDue()).to.equal(expectedDue); - expect(await delegation.curatorDue()).to.be.greaterThan(await ethers.provider.getBalance(vault)); + expect(await delegation.curatorUnclaimedFee()).to.equal(expectedDue); + expect(await delegation.curatorUnclaimedFee()).to.be.greaterThan(await ethers.provider.getBalance(vault)); expect(await ethers.provider.getBalance(vault)).to.equal(0n); await rewarder.sendTransaction({ to: vault, value: rewards }); expect(await ethers.provider.getBalance(vault)).to.equal(rewards); expect(await ethers.provider.getBalance(recipient)).to.equal(0n); - await expect(delegation.connect(curator).claimCuratorDue(recipient)) + await expect(delegation.connect(curator).claimCuratorFee(recipient)) .to.emit(vault, "Withdrawn") .withArgs(delegation, recipient, expectedDue); expect(await ethers.provider.getBalance(recipient)).to.equal(expectedDue); @@ -268,47 +283,46 @@ describe("Delegation.sol", () => { }); }); - context("claimOperatorDue", () => { + context("claimNodeOperatorFee", () => { it("reverts if the caller does not have the operator due claim role", async () => { - await expect(delegation.connect(stranger).claimOperatorDue(stranger)).to.be.revertedWithCustomError( + await expect(delegation.connect(stranger).claimNodeOperatorFee(stranger)).to.be.revertedWithCustomError( delegation, "AccessControlUnauthorizedAccount", ); }); it("reverts if the recipient is the zero address", async () => { - await expect(delegation.connect(claimOperatorDueRole).claimOperatorDue(ethers.ZeroAddress)) + await expect(delegation.connect(nodeOperatorFeeClaimer).claimNodeOperatorFee(ethers.ZeroAddress)) .to.be.revertedWithCustomError(delegation, "ZeroArgument") .withArgs("_recipient"); }); it("reverts if the due is zero", async () => { - expect(await delegation.operatorDue()).to.equal(0n); - await expect(delegation.connect(claimOperatorDueRole).claimOperatorDue(recipient)).to.be.revertedWithCustomError( - delegation, - "NoDueToClaim", - ); + expect(await delegation.nodeOperatorUnclaimedFee()).to.equal(0n); + await expect(delegation.connect(nodeOperatorFeeClaimer).claimNodeOperatorFee(recipient)) + .to.be.revertedWithCustomError(delegation, "ZeroArgument") + .withArgs("_fee"); }); it("claims the due", async () => { const operatorFee = 10_00n; // 10% - await delegation.connect(operator).setOperatorFee(operatorFee); - await delegation.connect(curator).setOperatorFee(operatorFee); - expect(await delegation.operatorFee()).to.equal(operatorFee); + await delegation.connect(nodeOperatorManager).setNodeOperatorFeeBP(operatorFee); + await delegation.connect(curator).setNodeOperatorFeeBP(operatorFee); + expect(await delegation.nodeOperatorFeeBP()).to.equal(operatorFee); const rewards = ether("1"); await vault.connect(hubSigner).report(rewards, 0n, 0n); const expectedDue = (rewards * operatorFee) / BP_BASE; - expect(await delegation.operatorDue()).to.equal(expectedDue); - expect(await delegation.operatorDue()).to.be.greaterThan(await ethers.provider.getBalance(vault)); + expect(await delegation.nodeOperatorUnclaimedFee()).to.equal(expectedDue); + expect(await delegation.nodeOperatorUnclaimedFee()).to.be.greaterThan(await ethers.provider.getBalance(vault)); expect(await ethers.provider.getBalance(vault)).to.equal(0n); await rewarder.sendTransaction({ to: vault, value: rewards }); expect(await ethers.provider.getBalance(vault)).to.equal(rewards); expect(await ethers.provider.getBalance(recipient)).to.equal(0n); - await expect(delegation.connect(claimOperatorDueRole).claimOperatorDue(recipient)) + await expect(delegation.connect(nodeOperatorFeeClaimer).claimNodeOperatorFee(recipient)) .to.emit(vault, "Withdrawn") .withArgs(delegation, recipient, expectedDue); expect(await ethers.provider.getBalance(recipient)).to.equal(expectedDue); @@ -345,7 +359,7 @@ describe("Delegation.sol", () => { expect(await vault.inOutDelta()).to.equal(0n); expect(await vault.valuation()).to.equal(0n); - await expect(delegation.connect(staker).fund({ value: amount })) + await expect(delegation.connect(funderWithdrawer).fund({ value: amount })) .to.emit(vault, "Funded") .withArgs(delegation, amount); @@ -364,14 +378,13 @@ describe("Delegation.sol", () => { }); it("reverts if the recipient is the zero address", async () => { - await expect(delegation.connect(staker).withdraw(ethers.ZeroAddress, ether("1"))).to.be.revertedWithCustomError( - delegation, - "ZeroArgument", - ); + await expect( + delegation.connect(funderWithdrawer).withdraw(ethers.ZeroAddress, ether("1")), + ).to.be.revertedWithCustomError(delegation, "ZeroArgument"); }); it("reverts if the amount is zero", async () => { - await expect(delegation.connect(staker).withdraw(recipient, 0n)).to.be.revertedWithCustomError( + await expect(delegation.connect(funderWithdrawer).withdraw(recipient, 0n)).to.be.revertedWithCustomError( delegation, "ZeroArgument", ); @@ -379,10 +392,9 @@ describe("Delegation.sol", () => { it("reverts if the amount is greater than the unreserved amount", async () => { const unreserved = await delegation.unreserved(); - await expect(delegation.connect(staker).withdraw(recipient, unreserved + 1n)).to.be.revertedWithCustomError( - delegation, - "RequestedAmountExceedsUnreserved", - ); + await expect( + delegation.connect(funderWithdrawer).withdraw(recipient, unreserved + 1n), + ).to.be.revertedWithCustomError(delegation, "RequestedAmountExceedsUnreserved"); }); it("withdraws the amount", async () => { @@ -396,7 +408,7 @@ describe("Delegation.sol", () => { expect(await ethers.provider.getBalance(vault)).to.equal(amount); expect(await ethers.provider.getBalance(recipient)).to.equal(0n); - await expect(delegation.connect(staker).withdraw(recipient, amount)) + await expect(delegation.connect(funderWithdrawer).withdraw(recipient, amount)) .to.emit(vault, "Withdrawn") .withArgs(delegation, recipient, amount); expect(await ethers.provider.getBalance(vault)).to.equal(0n); @@ -414,7 +426,7 @@ describe("Delegation.sol", () => { it("rebalances the vault by transferring ether", async () => { const amount = ether("1"); - await delegation.connect(staker).fund({ value: amount }); + await delegation.connect(funderWithdrawer).fund({ value: amount }); await expect(delegation.connect(curator).rebalanceVault(amount)) .to.emit(hub, "Mock__Rebalanced") @@ -441,7 +453,7 @@ describe("Delegation.sol", () => { it("mints the tokens", async () => { const amount = 100n; - await expect(delegation.connect(tokenMaster).mint(recipient, amount)) + await expect(delegation.connect(minterBurner).mint(recipient, amount)) .to.emit(steth, "Transfer") .withArgs(ethers.ZeroAddress, recipient, amount); }); @@ -457,25 +469,45 @@ describe("Delegation.sol", () => { it("burns the tokens", async () => { const amount = 100n; - await delegation.connect(tokenMaster).mint(tokenMaster, amount); + await delegation.connect(minterBurner).mint(minterBurner, amount); - await expect(delegation.connect(tokenMaster).burn(amount)) + await expect(delegation.connect(minterBurner).burn(amount)) .to.emit(steth, "Transfer") - .withArgs(tokenMaster, hub, amount) + .withArgs(minterBurner, hub, amount) .and.to.emit(steth, "Transfer") .withArgs(hub, ethers.ZeroAddress, amount); }); }); - context("setCuratorFee", () => { + context("setCuratorFeeBP", () => { it("reverts if caller is not curator", async () => { - await expect(delegation.connect(stranger).setCuratorFee(1000n)) + await expect(delegation.connect(stranger).setCuratorFeeBP(1000n)) .to.be.revertedWithCustomError(delegation, "AccessControlUnauthorizedAccount") - .withArgs(stranger, await delegation.CURATOR_ROLE()); + .withArgs(stranger, await delegation.DEFAULT_ADMIN_ROLE()); + }); + + it("reverts if curator fee is not zero", async () => { + // set the curator fee to 5% + const newCuratorFee = 500n; + await delegation.connect(vaultOwner).setCuratorFeeBP(newCuratorFee); + expect(await delegation.curatorFeeBP()).to.equal(newCuratorFee); + + // bring rewards + const totalRewards = ether("1"); + const inOutDelta = 0n; + const locked = 0n; + await vault.connect(hubSigner).report(totalRewards, inOutDelta, locked); + expect(await delegation.curatorUnclaimedFee()).to.equal((totalRewards * newCuratorFee) / BP_BASE); + + // attempt to change the performance fee to 6% + await expect(delegation.connect(vaultOwner).setCuratorFeeBP(600n)).to.be.revertedWithCustomError( + delegation, + "CuratorFeeUnclaimed", + ); }); it("reverts if new fee is greater than max fee", async () => { - await expect(delegation.connect(curator).setCuratorFee(MAX_FEE + 1n)).to.be.revertedWithCustomError( + await expect(delegation.connect(vaultOwner).setCuratorFeeBP(MAX_FEE + 1n)).to.be.revertedWithCustomError( delegation, "CombinedFeesExceed100Percent", ); @@ -483,66 +515,65 @@ describe("Delegation.sol", () => { it("sets the curator fee", async () => { const newCuratorFee = 1000n; - await delegation.connect(curator).setCuratorFee(newCuratorFee); - expect(await delegation.curatorFee()).to.equal(newCuratorFee); + await delegation.connect(vaultOwner).setCuratorFeeBP(newCuratorFee); + expect(await delegation.curatorFeeBP()).to.equal(newCuratorFee); }); }); context("setOperatorFee", () => { it("reverts if new fee is greater than max fee", async () => { const invalidFee = MAX_FEE + 1n; - await delegation.connect(curator).setOperatorFee(invalidFee); + await delegation.connect(curator).setNodeOperatorFeeBP(invalidFee); - await expect(delegation.connect(operator).setOperatorFee(invalidFee)).to.be.revertedWithCustomError( - delegation, - "CombinedFeesExceed100Percent", - ); + await expect( + delegation.connect(nodeOperatorManager).setNodeOperatorFeeBP(invalidFee), + ).to.be.revertedWithCustomError(delegation, "CombinedFeesExceed100Percent"); }); it("reverts if performance due is not zero", async () => { // set the performance fee to 5% const newOperatorFee = 500n; - await delegation.connect(curator).setOperatorFee(newOperatorFee); - await delegation.connect(operator).setOperatorFee(newOperatorFee); - expect(await delegation.operatorFee()).to.equal(newOperatorFee); + await delegation.connect(curator).setNodeOperatorFeeBP(newOperatorFee); + await delegation.connect(nodeOperatorManager).setNodeOperatorFeeBP(newOperatorFee); + expect(await delegation.nodeOperatorFeeBP()).to.equal(newOperatorFee); // bring rewards const totalRewards = ether("1"); const inOutDelta = 0n; const locked = 0n; await vault.connect(hubSigner).report(totalRewards, inOutDelta, locked); - expect(await delegation.operatorDue()).to.equal((totalRewards * newOperatorFee) / BP_BASE); + expect(await delegation.nodeOperatorUnclaimedFee()).to.equal((totalRewards * newOperatorFee) / BP_BASE); // attempt to change the performance fee to 6% - await delegation.connect(curator).setOperatorFee(600n); - await expect(delegation.connect(operator).setOperatorFee(600n)).to.be.revertedWithCustomError( + await delegation.connect(curator).setNodeOperatorFeeBP(600n); + await expect(delegation.connect(nodeOperatorManager).setNodeOperatorFeeBP(600n)).to.be.revertedWithCustomError( delegation, - "OperatorDueUnclaimed", + "NodeOperatorFeeUnclaimed", ); }); it("requires both curator and operator to set the operator fee and emits the RoleMemberVoted event", async () => { - const previousOperatorFee = await delegation.operatorFee(); + const previousOperatorFee = await delegation.nodeOperatorFeeBP(); const newOperatorFee = 1000n; let voteTimestamp = await getNextBlockTimestamp(); - const msgData = delegation.interface.encodeFunctionData("setOperatorFee", [newOperatorFee]); + const msgData = delegation.interface.encodeFunctionData("setNodeOperatorFeeBP", [newOperatorFee]); - await expect(delegation.connect(curator).setOperatorFee(newOperatorFee)) + await expect(delegation.connect(curator).setNodeOperatorFeeBP(newOperatorFee)) .to.emit(delegation, "RoleMemberVoted") .withArgs(curator, await delegation.CURATOR_ROLE(), voteTimestamp, msgData); // fee is unchanged - expect(await delegation.operatorFee()).to.equal(previousOperatorFee); + expect(await delegation.nodeOperatorFeeBP()).to.equal(previousOperatorFee); // check vote expect(await delegation.votings(keccak256(msgData), await delegation.CURATOR_ROLE())).to.equal(voteTimestamp); voteTimestamp = await getNextBlockTimestamp(); - await expect(delegation.connect(operator).setOperatorFee(newOperatorFee)) + await expect(delegation.connect(nodeOperatorManager).setNodeOperatorFeeBP(newOperatorFee)) .to.emit(delegation, "RoleMemberVoted") - .withArgs(operator, await delegation.OPERATOR_ROLE(), voteTimestamp, msgData) - .and.to.emit(delegation, "OperatorFeeSet") - .withArgs(operator, previousOperatorFee, newOperatorFee); + .withArgs(nodeOperatorManager, await delegation.NODE_OPERATOR_MANAGER_ROLE(), voteTimestamp, msgData) + .and.to.emit(delegation, "NodeOperatorFeeBPSet") + .withArgs(nodeOperatorManager, previousOperatorFee, newOperatorFee); - expect(await delegation.operatorFee()).to.equal(newOperatorFee); + expect(await delegation.nodeOperatorFeeBP()).to.equal(newOperatorFee); // resets the votes for (const role of await delegation.votingCommittee()) { @@ -552,23 +583,23 @@ describe("Delegation.sol", () => { it("reverts if the caller is not a member of the operator fee committee", async () => { const newOperatorFee = 1000n; - await expect(delegation.connect(stranger).setOperatorFee(newOperatorFee)).to.be.revertedWithCustomError( + await expect(delegation.connect(stranger).setNodeOperatorFeeBP(newOperatorFee)).to.be.revertedWithCustomError( delegation, "NotACommitteeMember", ); }); it("doesn't execute if an earlier vote has expired", async () => { - const previousOperatorFee = await delegation.operatorFee(); + const previousOperatorFee = await delegation.nodeOperatorFeeBP(); const newOperatorFee = 1000n; - const msgData = delegation.interface.encodeFunctionData("setOperatorFee", [newOperatorFee]); + const msgData = delegation.interface.encodeFunctionData("setNodeOperatorFeeBP", [newOperatorFee]); const callId = keccak256(msgData); let voteTimestamp = await getNextBlockTimestamp(); - await expect(delegation.connect(curator).setOperatorFee(newOperatorFee)) + await expect(delegation.connect(curator).setNodeOperatorFeeBP(newOperatorFee)) .to.emit(delegation, "RoleMemberVoted") .withArgs(curator, await delegation.CURATOR_ROLE(), voteTimestamp, msgData); // fee is unchanged - expect(await delegation.operatorFee()).to.equal(previousOperatorFee); + expect(await delegation.nodeOperatorFeeBP()).to.equal(previousOperatorFee); // check vote expect(await delegation.votings(callId, await delegation.CURATOR_ROLE())).to.equal(voteTimestamp); @@ -576,24 +607,26 @@ describe("Delegation.sol", () => { await advanceChainTime(days(7n) + 1n); const expectedVoteTimestamp = await getNextBlockTimestamp(); expect(expectedVoteTimestamp).to.be.greaterThan(voteTimestamp + days(7n)); - await expect(delegation.connect(operator).setOperatorFee(newOperatorFee)) + await expect(delegation.connect(nodeOperatorManager).setNodeOperatorFeeBP(newOperatorFee)) .to.emit(delegation, "RoleMemberVoted") - .withArgs(operator, await delegation.OPERATOR_ROLE(), expectedVoteTimestamp, msgData); + .withArgs(nodeOperatorManager, await delegation.NODE_OPERATOR_MANAGER_ROLE(), expectedVoteTimestamp, msgData); // fee is still unchanged - expect(await delegation.operatorFee()).to.equal(previousOperatorFee); + expect(await delegation.nodeOperatorFeeBP()).to.equal(previousOperatorFee); // check vote - expect(await delegation.votings(callId, await delegation.OPERATOR_ROLE())).to.equal(expectedVoteTimestamp); + expect(await delegation.votings(callId, await delegation.NODE_OPERATOR_MANAGER_ROLE())).to.equal( + expectedVoteTimestamp, + ); // curator has to vote again voteTimestamp = await getNextBlockTimestamp(); - await expect(delegation.connect(curator).setOperatorFee(newOperatorFee)) + await expect(delegation.connect(curator).setNodeOperatorFeeBP(newOperatorFee)) .to.emit(delegation, "RoleMemberVoted") .withArgs(curator, await delegation.CURATOR_ROLE(), voteTimestamp, msgData) - .and.to.emit(delegation, "OperatorFeeSet") + .and.to.emit(delegation, "NodeOperatorFeeBPSet") .withArgs(curator, previousOperatorFee, newOperatorFee); // fee is now changed - expect(await delegation.operatorFee()).to.equal(newOperatorFee); + expect(await delegation.nodeOperatorFeeBP()).to.equal(newOperatorFee); }); }); @@ -616,9 +649,9 @@ describe("Delegation.sol", () => { expect(await vault.owner()).to.equal(delegation); voteTimestamp = await getNextBlockTimestamp(); - await expect(delegation.connect(operator).transferStVaultOwnership(newOwner)) + await expect(delegation.connect(nodeOperatorManager).transferStVaultOwnership(newOwner)) .to.emit(delegation, "RoleMemberVoted") - .withArgs(operator, await delegation.OPERATOR_ROLE(), voteTimestamp, msgData); + .withArgs(nodeOperatorManager, await delegation.NODE_OPERATOR_MANAGER_ROLE(), voteTimestamp, msgData); // owner changed expect(await vault.owner()).to.equal(newOwner); }); From 4f99e588c1539f200534a7767a9978272f4814af Mon Sep 17 00:00:00 2001 From: failingtwice Date: Thu, 16 Jan 2025 15:08:55 +0500 Subject: [PATCH 21/24] test(Dashboard): fix test after renaming --- test/0.8.25/vaults/dashboard/dashboard.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/0.8.25/vaults/dashboard/dashboard.test.ts b/test/0.8.25/vaults/dashboard/dashboard.test.ts index f678a6c92..5f0b57204 100644 --- a/test/0.8.25/vaults/dashboard/dashboard.test.ts +++ b/test/0.8.25/vaults/dashboard/dashboard.test.ts @@ -25,7 +25,7 @@ import { Snapshot } from "test/suite"; describe("Dashboard", () => { let factoryOwner: HardhatEthersSigner; let vaultOwner: HardhatEthersSigner; - let operator: HardhatEthersSigner; + let nodeOperator: HardhatEthersSigner; let stranger: HardhatEthersSigner; let steth: StETHPermit__HarnessForDashboard; @@ -45,7 +45,7 @@ describe("Dashboard", () => { const BP_BASE = 10_000n; before(async () => { - [factoryOwner, vaultOwner, operator, stranger] = await ethers.getSigners(); + [factoryOwner, vaultOwner, nodeOperator, stranger] = await ethers.getSigners(); steth = await ethers.deployContract("StETHPermit__HarnessForDashboard"); await steth.mock__setTotalShares(ether("1000000")); @@ -67,7 +67,7 @@ describe("Dashboard", () => { expect(await factory.implementation()).to.equal(vaultImpl); expect(await factory.dashboardImpl()).to.equal(dashboardImpl); - const createVaultTx = await factory.connect(vaultOwner).createVault(operator); + const createVaultTx = await factory.connect(vaultOwner).createVault(nodeOperator); const createVaultReceipt = await createVaultTx.wait(); if (!createVaultReceipt) throw new Error("Vault creation receipt not found"); @@ -139,7 +139,7 @@ describe("Dashboard", () => { context("initialized state", () => { it("post-initialization state is correct", async () => { expect(await vault.owner()).to.equal(dashboard); - expect(await vault.operator()).to.equal(operator); + expect(await vault.nodeOperator()).to.equal(nodeOperator); expect(await dashboard.isInitialized()).to.equal(true); expect(await dashboard.stakingVault()).to.equal(vault); expect(await dashboard.vaultHub()).to.equal(hub); From fd1c88be72b877b9249aa41e66a7f9ba7ae2cac1 Mon Sep 17 00:00:00 2001 From: failingtwice Date: Thu, 16 Jan 2025 15:12:58 +0500 Subject: [PATCH 22/24] fix(VaultFactory): fix after renaming --- lib/proxy.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/proxy.ts b/lib/proxy.ts index 582a8312a..c86dacdc7 100644 --- a/lib/proxy.ts +++ b/lib/proxy.ts @@ -54,13 +54,13 @@ export async function createVaultProxy( ): Promise { // Define the parameters for the struct const initializationParams: DelegationInitializationParamsStruct = { - curatorFee: 100n, - operatorFee: 200n, + curatorFeeBP: 100n, + nodeOperatorFeeBP: 200n, curator: await _owner.getAddress(), - staker: await _owner.getAddress(), - tokenMaster: await _owner.getAddress(), - operator: await _operator.getAddress(), - claimOperatorDueRole: await _owner.getAddress(), + funderWithdrawer: await _owner.getAddress(), + minterBurner: await _owner.getAddress(), + nodeOperatorManager: await _operator.getAddress(), + nodeOperatorFeeClaimer: await _owner.getAddress(), }; const tx = await vaultFactory.connect(_owner).createVault(initializationParams, "0x"); From 3588e471317566c41c0edbfedc3142ddd517fbd1 Mon Sep 17 00:00:00 2001 From: failingtwice Date: Thu, 16 Jan 2025 15:18:47 +0500 Subject: [PATCH 23/24] test(VaultHappyPath): fix after renaming --- .../vaults-happy-path.integration.ts | 82 ++++++++++--------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/test/integration/vaults-happy-path.integration.ts b/test/integration/vaults-happy-path.integration.ts index 6725c6086..258b349ff 100644 --- a/test/integration/vaults-happy-path.integration.ts +++ b/test/integration/vaults-happy-path.integration.ts @@ -43,10 +43,10 @@ describe("Scenario: Staking Vaults Happy Path", () => { let ethHolder: HardhatEthersSigner; let owner: HardhatEthersSigner; - let operator: HardhatEthersSigner; + let nodeOperator: HardhatEthersSigner; let curator: HardhatEthersSigner; - let staker: HardhatEthersSigner; - let tokenMaster: HardhatEthersSigner; + let funderWithdrawer: HardhatEthersSigner; + let minterBurner: HardhatEthersSigner; let depositContract: string; @@ -70,7 +70,7 @@ describe("Scenario: Staking Vaults Happy Path", () => { before(async () => { ctx = await getProtocolContext(); - [ethHolder, owner, operator, curator, staker, tokenMaster] = await ethers.getSigners(); + [ethHolder, owner, nodeOperator, curator, funderWithdrawer, minterBurner] = await ethers.getSigners(); const { depositSecurityModule } = ctx.contracts; depositContract = await depositSecurityModule.DEPOSIT_CONTRACT(); @@ -160,13 +160,13 @@ describe("Scenario: Staking Vaults Happy Path", () => { // Owner can create a vault with operator as a node operator const deployTx = await stakingVaultFactory.connect(owner).createVault( { - operatorFee: VAULT_OWNER_FEE, - curatorFee: VAULT_NODE_OPERATOR_FEE, + nodeOperatorFeeBP: VAULT_OWNER_FEE, + curatorFeeBP: VAULT_NODE_OPERATOR_FEE, curator: curator, - operator: operator, - staker: staker, - tokenMaster: tokenMaster, - claimOperatorDueRole: operator, + nodeOperatorManager: nodeOperator, + funderWithdrawer: funderWithdrawer, + minterBurner: minterBurner, + nodeOperatorFeeClaimer: nodeOperator, }, "0x", ); @@ -185,28 +185,28 @@ describe("Scenario: Staking Vaults Happy Path", () => { expect(await delegation.getRoleMemberCount(await delegation.CURATOR_ROLE())).to.be.equal(1n); expect(await delegation.hasRole(await delegation.CURATOR_ROLE(), curator)).to.be.true; - expect(await delegation.getRoleMemberCount(await delegation.OPERATOR_ROLE())).to.be.equal(1n); - expect(await delegation.hasRole(await delegation.OPERATOR_ROLE(), operator)).to.be.true; + expect(await delegation.getRoleMemberCount(await delegation.NODE_OPERATOR_MANAGER_ROLE())).to.be.equal(1n); + expect(await delegation.hasRole(await delegation.NODE_OPERATOR_MANAGER_ROLE(), nodeOperator)).to.be.true; - expect(await delegation.getRoleMemberCount(await delegation.CLAIM_OPERATOR_DUE_ROLE())).to.be.equal(1n); - expect(await delegation.hasRole(await delegation.CLAIM_OPERATOR_DUE_ROLE(), operator)).to.be.true; - expect(await delegation.getRoleAdmin(await delegation.CLAIM_OPERATOR_DUE_ROLE())).to.be.equal( - await delegation.OPERATOR_ROLE(), + expect(await delegation.getRoleMemberCount(await delegation.NODE_OPERATOR_FEE_CLAIMER_ROLE())).to.be.equal(1n); + expect(await delegation.hasRole(await delegation.NODE_OPERATOR_FEE_CLAIMER_ROLE(), nodeOperator)).to.be.true; + expect(await delegation.getRoleAdmin(await delegation.NODE_OPERATOR_FEE_CLAIMER_ROLE())).to.be.equal( + await delegation.NODE_OPERATOR_MANAGER_ROLE(), ); - expect(await delegation.getRoleMemberCount(await delegation.TOKEN_MASTER_ROLE())).to.be.equal(1n); - expect(await delegation.hasRole(await delegation.TOKEN_MASTER_ROLE(), tokenMaster)).to.be.true; + expect(await delegation.getRoleMemberCount(await delegation.MINT_BURN_ROLE())).to.be.equal(1n); + expect(await delegation.hasRole(await delegation.MINT_BURN_ROLE(), minterBurner)).to.be.true; - expect(await delegation.getRoleMemberCount(await delegation.STAKER_ROLE())).to.be.equal(1n); - expect(await delegation.hasRole(await delegation.STAKER_ROLE(), staker)).to.be.true; + expect(await delegation.getRoleMemberCount(await delegation.FUND_WITHDRAW_ROLE())).to.be.equal(1n); + expect(await delegation.hasRole(await delegation.FUND_WITHDRAW_ROLE(), funderWithdrawer)).to.be.true; }); it("Should allow Owner to assign Staker and Token Master roles", async () => { - await delegation.connect(owner).grantRole(await delegation.STAKER_ROLE(), staker); - await delegation.connect(owner).grantRole(await delegation.TOKEN_MASTER_ROLE(), tokenMaster); + await delegation.connect(owner).grantRole(await delegation.FUND_WITHDRAW_ROLE(), funderWithdrawer); + await delegation.connect(owner).grantRole(await delegation.MINT_BURN_ROLE(), minterBurner); - expect(await delegation.hasRole(await delegation.STAKER_ROLE(), staker)).to.be.true; - expect(await delegation.hasRole(await delegation.TOKEN_MASTER_ROLE(), tokenMaster)).to.be.true; + expect(await delegation.hasRole(await delegation.FUND_WITHDRAW_ROLE(), funderWithdrawer)).to.be.true; + expect(await delegation.hasRole(await delegation.MINT_BURN_ROLE(), minterBurner)).to.be.true; }); it("Should allow Lido to recognize vaults and connect them to accounting", async () => { @@ -231,7 +231,7 @@ describe("Scenario: Staking Vaults Happy Path", () => { }); it("Should allow Staker to fund vault via delegation contract", async () => { - const depositTx = await delegation.connect(staker).fund({ value: VAULT_DEPOSIT }); + const depositTx = await delegation.connect(funderWithdrawer).fund({ value: VAULT_DEPOSIT }); await trace("delegation.fund", depositTx); const vaultBalance = await ethers.provider.getBalance(stakingVault); @@ -245,7 +245,9 @@ describe("Scenario: Staking Vaults Happy Path", () => { pubKeysBatch = ethers.randomBytes(Number(keysToAdd * PUBKEY_LENGTH)); signaturesBatch = ethers.randomBytes(Number(keysToAdd * SIGNATURE_LENGTH)); - const topUpTx = await stakingVault.connect(operator).depositToBeaconChain(keysToAdd, pubKeysBatch, signaturesBatch); + const topUpTx = await stakingVault + .connect(nodeOperator) + .depositToBeaconChain(keysToAdd, pubKeysBatch, signaturesBatch); await trace("stakingVault.depositToBeaconChain", topUpTx); @@ -272,12 +274,12 @@ describe("Scenario: Staking Vaults Happy Path", () => { }); // Validate minting with the cap - const mintOverLimitTx = delegation.connect(tokenMaster).mint(tokenMaster, stakingVaultMaxMintingShares + 1n); + const mintOverLimitTx = delegation.connect(minterBurner).mint(minterBurner, stakingVaultMaxMintingShares + 1n); await expect(mintOverLimitTx) .to.be.revertedWithCustomError(accounting, "InsufficientValuationToMint") .withArgs(stakingVault, stakingVault.valuation()); - const mintTx = await delegation.connect(tokenMaster).mint(tokenMaster, stakingVaultMaxMintingShares); + const mintTx = await delegation.connect(minterBurner).mint(minterBurner, stakingVaultMaxMintingShares); const mintTxReceipt = await trace("delegation.mint", mintTx); const mintEvents = ctx.getEvents(mintTxReceipt, "MintedSharesOnVault"); @@ -324,25 +326,25 @@ describe("Scenario: Staking Vaults Happy Path", () => { expect(vaultReportedEvent[0].args?.inOutDelta).to.equal(VAULT_DEPOSIT); // TODO: add assertions or locked values and rewards - expect(await delegation.curatorDue()).to.be.gt(0n); - expect(await delegation.operatorDue()).to.be.gt(0n); + expect(await delegation.curatorUnclaimedFee()).to.be.gt(0n); + expect(await delegation.nodeOperatorUnclaimedFee()).to.be.gt(0n); }); it("Should allow Operator to claim performance fees", async () => { - const performanceFee = await delegation.operatorDue(); + const performanceFee = await delegation.nodeOperatorUnclaimedFee(); log.debug("Staking Vault stats", { "Staking Vault performance fee": ethers.formatEther(performanceFee), }); - const operatorBalanceBefore = await ethers.provider.getBalance(operator); + const operatorBalanceBefore = await ethers.provider.getBalance(nodeOperator); - const claimPerformanceFeesTx = await delegation.connect(operator).claimOperatorDue(operator); + const claimPerformanceFeesTx = await delegation.connect(nodeOperator).claimNodeOperatorFee(nodeOperator); const claimPerformanceFeesTxReceipt = await trace( - "delegation.claimOperatorDue", + "delegation.claimNodeOperatorFee", claimPerformanceFeesTx, ); - const operatorBalanceAfter = await ethers.provider.getBalance(operator); + const operatorBalanceAfter = await ethers.provider.getBalance(nodeOperator); const gasFee = claimPerformanceFeesTxReceipt.gasPrice * claimPerformanceFeesTxReceipt.cumulativeGasUsed; log.debug("Operator's StETH balance", { @@ -375,7 +377,7 @@ describe("Scenario: Staking Vaults Happy Path", () => { }); it("Should allow Manager to claim manager rewards in ETH after rebase with exited validator", async () => { - const feesToClaim = await delegation.curatorDue(); + const feesToClaim = await delegation.curatorUnclaimedFee(); log.debug("Staking Vault stats after operator exit", { "Staking Vault management fee": ethers.formatEther(feesToClaim), @@ -384,8 +386,8 @@ describe("Scenario: Staking Vaults Happy Path", () => { const managerBalanceBefore = await ethers.provider.getBalance(curator); - const claimEthTx = await delegation.connect(curator).claimCuratorDue(curator); - const { gasUsed, gasPrice } = await trace("delegation.claimCuratorDue", claimEthTx); + const claimEthTx = await delegation.connect(curator).claimCuratorFee(curator); + const { gasUsed, gasPrice } = await trace("delegation.claimCuratorFee", claimEthTx); const managerBalanceAfter = await ethers.provider.getBalance(curator); const vaultBalance = await ethers.provider.getBalance(stakingVaultAddress); @@ -406,11 +408,11 @@ describe("Scenario: Staking Vaults Happy Path", () => { // Token master can approve the vault to burn the shares const approveVaultTx = await lido - .connect(tokenMaster) + .connect(minterBurner) .approve(delegation, await lido.getPooledEthByShares(stakingVaultMaxMintingShares)); await trace("lido.approve", approveVaultTx); - const burnTx = await delegation.connect(tokenMaster).burn(stakingVaultMaxMintingShares); + const burnTx = await delegation.connect(minterBurner).burn(stakingVaultMaxMintingShares); await trace("delegation.burn", burnTx); const { elapsedProtocolReward, elapsedVaultReward } = await calculateReportParams(); From 4d3f84d3bb490dd8c8a277d2e772f862e85cf056 Mon Sep 17 00:00:00 2001 From: Logachev Nikita Date: Thu, 16 Jan 2025 20:23:53 +0300 Subject: [PATCH 24/24] feat: add proxy bytecode verification, remove implementation verification --- contracts/0.8.25/vaults/Dashboard.sol | 10 ++-- contracts/0.8.25/vaults/StakingVault.sol | 13 +---- contracts/0.8.25/vaults/VaultHub.sol | 45 +++++---------- .../0.8.25/vaults/interfaces/IBeaconProxy.sol | 10 ---- .../vaults/interfaces/IStakingVault.sol | 1 + scripts/scratch/steps/0145-deploy-vaults.ts | 9 ++- .../StakingVault__HarnessForTestUpgrade.sol | 56 ++++++++++++++++--- .../0.8.25/vaults/dashboard/dashboard.test.ts | 2 +- .../vaults/delegation/delegation.test.ts | 1 - .../staking-vault/staking-vault.test.ts | 12 +--- test/0.8.25/vaults/vaultFactory.test.ts | 38 +++++-------- 11 files changed, 92 insertions(+), 105 deletions(-) delete mode 100644 contracts/0.8.25/vaults/interfaces/IBeaconProxy.sol diff --git a/contracts/0.8.25/vaults/Dashboard.sol b/contracts/0.8.25/vaults/Dashboard.sol index 3bb4c8ddf..d2b2a546a 100644 --- a/contracts/0.8.25/vaults/Dashboard.sol +++ b/contracts/0.8.25/vaults/Dashboard.sol @@ -44,9 +44,6 @@ contract Dashboard is AccessControlEnumerable { /// @notice Total basis points for fee calculations; equals to 100%. uint256 internal constant TOTAL_BASIS_POINTS = 10000; - /// @notice Indicates whether the contract has been initialized - bool public isInitialized; - /// @notice The stETH token contract IStETH public immutable STETH; @@ -56,6 +53,9 @@ contract Dashboard is AccessControlEnumerable { /// @notice The wrapped ether token contract IWeth public immutable WETH; + /// @notice Indicates whether the contract has been initialized + bool public initialized; + /// @notice The underlying `StakingVault` contract IStakingVault public stakingVault; @@ -101,10 +101,10 @@ contract Dashboard is AccessControlEnumerable { */ function _initialize(address _stakingVault) internal { if (_stakingVault == address(0)) revert ZeroArgument("_stakingVault"); - if (isInitialized) revert AlreadyInitialized(); + if (initialized) revert AlreadyInitialized(); if (address(this) == _SELF) revert NonProxyCallsForbidden(); - isInitialized = true; + initialized = true; stakingVault = IStakingVault(_stakingVault); vaultHub = VaultHub(stakingVault.vaultHub()); _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); diff --git a/contracts/0.8.25/vaults/StakingVault.sol b/contracts/0.8.25/vaults/StakingVault.sol index 6c0f55762..2a63e2ffa 100644 --- a/contracts/0.8.25/vaults/StakingVault.sol +++ b/contracts/0.8.25/vaults/StakingVault.sol @@ -9,9 +9,6 @@ import {BeaconChainDepositLogistics} from "./BeaconChainDepositLogistics.sol"; import {VaultHub} from "./VaultHub.sol"; import {IStakingVault} from "./interfaces/IStakingVault.sol"; -import {IBeaconProxy} from "./interfaces/IBeaconProxy.sol"; - -import {ERC1967Utils} from "@openzeppelin/contracts-v5.0.2/proxy/ERC1967/ERC1967Utils.sol"; /** * @title StakingVault @@ -52,7 +49,7 @@ import {ERC1967Utils} from "@openzeppelin/contracts-v5.0.2/proxy/ERC1967/ERC1967 * deposit contract. * */ -contract StakingVault is IStakingVault, IBeaconProxy, BeaconChainDepositLogistics, OwnableUpgradeable { +contract StakingVault is IStakingVault, BeaconChainDepositLogistics, OwnableUpgradeable { /** * @notice ERC-7201 storage namespace for the vault * @dev ERC-7201 namespace is used to prevent upgrade collisions @@ -133,14 +130,6 @@ contract StakingVault is IStakingVault, IBeaconProxy, BeaconChainDepositLogistic return _VERSION; } - /** - * @notice Returns the beacon proxy address that controls this contract's implementation - * @return address The beacon proxy address - */ - function beacon() public view returns (address) { - return ERC1967Utils.getBeacon(); - } - // * * * * * * * * * * * * * * * * * * * * // // * * * STAKING VAULT BUSINESS LOGIC * * * // // * * * * * * * * * * * * * * * * * * * * // diff --git a/contracts/0.8.25/vaults/VaultHub.sol b/contracts/0.8.25/vaults/VaultHub.sol index 425f0b6e4..d6ec85a17 100644 --- a/contracts/0.8.25/vaults/VaultHub.sol +++ b/contracts/0.8.25/vaults/VaultHub.sol @@ -10,7 +10,6 @@ import {OwnableUpgradeable} from "contracts/openzeppelin/5.0.2/upgradeable/acces import {IStakingVault} from "./interfaces/IStakingVault.sol"; import {ILido as IStETH} from "../interfaces/ILido.sol"; -import {IBeaconProxy} from "./interfaces/IBeaconProxy.sol"; import {Math256} from "contracts/common/lib/Math256.sol"; @@ -29,9 +28,7 @@ abstract contract VaultHub is AccessControlEnumerableUpgradeable { /// @dev if vault is not connected to the hub, its index is zero mapping(address => uint256) vaultIndex; /// @notice allowed beacon addresses - mapping(address => bool) vaultBeacons; - /// @notice allowed vault implementation addresses - mapping(address => bool) vaultImpl; + mapping(bytes32 => bool) vaultProxyCodehash; } struct VaultSocket { @@ -91,26 +88,15 @@ abstract contract VaultHub is AccessControlEnumerableUpgradeable { _grantRole(DEFAULT_ADMIN_ROLE, _admin); } - /// @notice added beacon address to allowed list - /// @param beacon beacon address - function addBeacon(address beacon) public onlyRole(VAULT_REGISTRY_ROLE) { - if (beacon == address(0)) revert ZeroArgument("beacon"); + /// @notice added vault proxy codehash to allowed list + /// @param codehash vault proxy codehash + function addVaultProxyCodehash(bytes32 codehash) public onlyRole(VAULT_REGISTRY_ROLE) { + if (codehash == bytes32(0)) revert ZeroArgument("codehash"); VaultHubStorage storage $ = _getVaultHubStorage(); - if ($.vaultBeacons[beacon]) revert AlreadyExists(beacon); - $.vaultBeacons[beacon] = true; - emit VaultBeaconAdded(beacon); - } - - /// @notice added vault implementation address to allowed list - /// @param impl vault implementation address - function addVaultImpl(address impl) public onlyRole(VAULT_REGISTRY_ROLE) { - if (impl == address(0)) revert ZeroArgument("impl"); - - VaultHubStorage storage $ = _getVaultHubStorage(); - if ($.vaultImpl[impl]) revert AlreadyExists(impl); - $.vaultImpl[impl] = true; - emit VaultImplAdded(impl); + if ($.vaultProxyCodehash[codehash]) revert AlreadyExists(codehash); + $.vaultProxyCodehash[codehash] = true; + emit VaultProxyCodehashAdded(codehash); } /// @notice returns the number of vaults connected to the hub @@ -163,11 +149,8 @@ abstract contract VaultHub is AccessControlEnumerableUpgradeable { VaultHubStorage storage $ = _getVaultHubStorage(); if ($.vaultIndex[_vault] != 0) revert AlreadyConnected(_vault, $.vaultIndex[_vault]); - address vaultBeacon = IBeaconProxy(address (_vault)).beacon(); - if (!$.vaultBeacons[vaultBeacon]) revert BeaconNotAllowed(vaultBeacon); - - address impl = IBeacon(vaultBeacon).implementation(); - if (!$.vaultImpl[impl]) revert ImplNotAllowed(impl); + bytes32 vaultProxyCodehash = address(_vault).codehash; + if (!$.vaultProxyCodehash[vaultProxyCodehash]) revert VaultProxyNotAllowed(_vault); VaultSocket memory vr = VaultSocket( _vault, @@ -524,8 +507,7 @@ abstract contract VaultHub is AccessControlEnumerableUpgradeable { event MintedSharesOnVault(address indexed vault, uint256 amountOfShares); event BurnedSharesOnVault(address indexed vault, uint256 amountOfShares); event VaultRebalanced(address indexed vault, uint256 sharesBurned); - event VaultImplAdded(address indexed impl); - event VaultBeaconAdded(address indexed beacon); + event VaultProxyCodehashAdded(bytes32 indexed codehash); error StETHMintFailed(address vault); error AlreadyBalanced(address vault, uint256 mintedShares, uint256 rebalancingThresholdInShares); @@ -543,8 +525,7 @@ abstract contract VaultHub is AccessControlEnumerableUpgradeable { error TreasuryFeeTooHigh(address vault, uint256 treasuryFeeBP, uint256 maxTreasuryFeeBP); error ExternalSharesCapReached(address vault, uint256 capShares, uint256 maxMintableExternalShares); error InsufficientValuationToMint(address vault, uint256 valuation); - error AlreadyExists(address addr); - error ImplNotAllowed(address impl); + error AlreadyExists(bytes32 codehash); error NoMintedSharesShouldBeLeft(address vault, uint256 sharesMinted); - error BeaconNotAllowed(address beacon); + error VaultProxyNotAllowed(address beacon); } diff --git a/contracts/0.8.25/vaults/interfaces/IBeaconProxy.sol b/contracts/0.8.25/vaults/interfaces/IBeaconProxy.sol deleted file mode 100644 index c49bf63c4..000000000 --- a/contracts/0.8.25/vaults/interfaces/IBeaconProxy.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Lido -// SPDX-License-Identifier: GPL-3.0 - -// See contracts/COMPILERS.md -pragma solidity 0.8.25; - -interface IBeaconProxy { - function beacon() external view returns (address); - function version() external pure returns(uint64); -} diff --git a/contracts/0.8.25/vaults/interfaces/IStakingVault.sol b/contracts/0.8.25/vaults/interfaces/IStakingVault.sol index 54d597073..e7d0df602 100644 --- a/contracts/0.8.25/vaults/interfaces/IStakingVault.sol +++ b/contracts/0.8.25/vaults/interfaces/IStakingVault.sol @@ -21,6 +21,7 @@ interface IStakingVault { } function initialize(address _owner, address _operator, bytes calldata _params) external; + function version() external pure returns(uint64); function getInitializedVersion() external view returns (uint64); function vaultHub() external view returns (address); function operator() external view returns (address); diff --git a/scripts/scratch/steps/0145-deploy-vaults.ts b/scripts/scratch/steps/0145-deploy-vaults.ts index f91233f96..ddd879311 100644 --- a/scripts/scratch/steps/0145-deploy-vaults.ts +++ b/scripts/scratch/steps/0145-deploy-vaults.ts @@ -1,3 +1,4 @@ +import { keccak256 } from "ethers"; import { ethers } from "hardhat"; import { Accounting } from "typechain-types"; @@ -36,6 +37,11 @@ export async function main() { const beacon = await deployWithoutProxy(Sk.stakingVaultBeacon, "UpgradeableBeacon", deployer, [impAddress, deployer]); const beaconAddress = await beacon.getAddress(); + // Deploy BeaconProxy to get bytecode and add it to whitelist + const vaultBeaconProxy = await ethers.deployContract("BeaconProxy", [beaconAddress, "0x"]); + const vaultBeaconProxyCode = await ethers.provider.getCode(await vaultBeaconProxy.getAddress()); + const vaultBeaconProxyCodeHash = keccak256(vaultBeaconProxyCode); + // Deploy VaultFactory contract const factory = await deployWithoutProxy(Sk.stakingVaultFactory, "VaultFactory", deployer, [ beaconAddress, @@ -53,8 +59,7 @@ export async function main() { await makeTx(accounting, "grantRole", [vaultMasterRole, deployer], { from: deployer }); await makeTx(accounting, "grantRole", [vaultRegistryRole, deployer], { from: deployer }); - await makeTx(accounting, "addBeacon", [beaconAddress], { from: deployer }); - await makeTx(accounting, "addVaultImpl", [impAddress], { from: deployer }); + await makeTx(accounting, "addVaultProxyCodehash", [vaultBeaconProxyCodeHash], { from: deployer }); await makeTx(accounting, "renounceRole", [vaultMasterRole, deployer], { from: deployer }); await makeTx(accounting, "renounceRole", [vaultRegistryRole, deployer], { from: deployer }); diff --git a/test/0.8.25/vaults/contracts/StakingVault__HarnessForTestUpgrade.sol b/test/0.8.25/vaults/contracts/StakingVault__HarnessForTestUpgrade.sol index a6b22b756..ced641a7b 100644 --- a/test/0.8.25/vaults/contracts/StakingVault__HarnessForTestUpgrade.sol +++ b/test/0.8.25/vaults/contracts/StakingVault__HarnessForTestUpgrade.sol @@ -6,13 +6,11 @@ pragma solidity 0.8.25; import {OwnableUpgradeable} from "contracts/openzeppelin/5.0.2/upgradeable/access/OwnableUpgradeable.sol"; import {SafeCast} from "@openzeppelin/contracts-v5.0.2/utils/math/SafeCast.sol"; import {IERC20} from "@openzeppelin/contracts-v5.0.2/token/ERC20/IERC20.sol"; -import {ERC1967Utils} from "@openzeppelin/contracts-v5.0.2/proxy/ERC1967/ERC1967Utils.sol"; import {VaultHub} from "contracts/0.8.25/vaults/VaultHub.sol"; import {IStakingVault} from "contracts/0.8.25/vaults/interfaces/IStakingVault.sol"; -import {IBeaconProxy} from "contracts/0.8.25/vaults/interfaces/IBeaconProxy.sol"; import {BeaconChainDepositLogistics} from "contracts/0.8.25/vaults/BeaconChainDepositLogistics.sol"; -contract StakingVault__HarnessForTestUpgrade is IBeaconProxy, BeaconChainDepositLogistics, OwnableUpgradeable { +contract StakingVault__HarnessForTestUpgrade is IStakingVault, BeaconChainDepositLogistics, OwnableUpgradeable { /// @custom:storage-location erc7201:StakingVault.Vault struct VaultStorage { IStakingVault.Report report; @@ -22,7 +20,7 @@ contract StakingVault__HarnessForTestUpgrade is IBeaconProxy, BeaconChainDeposit } uint64 private constant _version = 2; - VaultHub public immutable vaultHub; + VaultHub private immutable VAULT_HUB; /// keccak256(abi.encode(uint256(keccak256("StakingVault.Vault")) - 1)) & ~bytes32(uint256(0xff)); bytes32 private constant VAULT_STORAGE_LOCATION = @@ -34,7 +32,10 @@ contract StakingVault__HarnessForTestUpgrade is IBeaconProxy, BeaconChainDeposit ) BeaconChainDepositLogistics(_beaconChainDepositContract) { if (_vaultHub == address(0)) revert ZeroArgument("_vaultHub"); - vaultHub = VaultHub(_vaultHub); + VAULT_HUB = VaultHub(_vaultHub); + + // Prevents reinitialization of the implementation + _disableInitializers(); } /// @notice Initialize the contract storage explicitly. Only new contracts can be initialized here. @@ -68,10 +69,6 @@ contract StakingVault__HarnessForTestUpgrade is IBeaconProxy, BeaconChainDeposit return _version; } - function beacon() public view returns (address) { - return ERC1967Utils.getBeacon(); - } - function latestReport() external view returns (IStakingVault.Report memory) { VaultStorage storage $ = _getVaultStorage(); return IStakingVault.Report({valuation: $.report.valuation, inOutDelta: $.report.inOutDelta}); @@ -83,6 +80,47 @@ contract StakingVault__HarnessForTestUpgrade is IBeaconProxy, BeaconChainDeposit } } + function depositToBeaconChain( + uint256 _numberOfDeposits, + bytes calldata _pubkeys, + bytes calldata _signatures + ) external {} + function fund() external payable {} + function inOutDelta() external view returns (int256) { + return -1; + } + function isBalanced() external view returns (bool) { + return true; + } + function operator() external view returns (address) { + return _getVaultStorage().operator; + } + function rebalance(uint256 _ether) external {} + function report(uint256 _valuation, int256 _inOutDelta, uint256 _locked) external {} + function requestValidatorExit(bytes calldata _pubkeys) external {} + function lock(uint256 _locked) external {} + + function locked() external view returns (uint256) { + return 0; + } + function unlocked() external view returns (uint256) { + return 0; + } + + function valuation() external view returns (uint256) { + return 0; + } + + function vaultHub() external view returns (address) { + return address(VAULT_HUB); + } + + function withdraw(address _recipient, uint256 _ether) external {} + + function withdrawalCredentials() external view returns (bytes32) { + return bytes32((0x01 << 248) + uint160(address(this))); + } + error ZeroArgument(string name); error VaultAlreadyInitialized(); } diff --git a/test/0.8.25/vaults/dashboard/dashboard.test.ts b/test/0.8.25/vaults/dashboard/dashboard.test.ts index 616f9f48d..101883cf9 100644 --- a/test/0.8.25/vaults/dashboard/dashboard.test.ts +++ b/test/0.8.25/vaults/dashboard/dashboard.test.ts @@ -140,7 +140,7 @@ describe("Dashboard.sol", () => { it("post-initialization state is correct", async () => { expect(await vault.owner()).to.equal(dashboard); expect(await vault.operator()).to.equal(operator); - expect(await dashboard.isInitialized()).to.equal(true); + expect(await dashboard.initialized()).to.equal(true); expect(await dashboard.stakingVault()).to.equal(vault); expect(await dashboard.vaultHub()).to.equal(hub); expect(await dashboard.STETH()).to.equal(steth); diff --git a/test/0.8.25/vaults/delegation/delegation.test.ts b/test/0.8.25/vaults/delegation/delegation.test.ts index 374b1246b..7525c0069 100644 --- a/test/0.8.25/vaults/delegation/delegation.test.ts +++ b/test/0.8.25/vaults/delegation/delegation.test.ts @@ -97,7 +97,6 @@ describe("Delegation.sol", () => { const stakingVaultAddress = vaultCreatedEvents[0].args.vault; vault = await ethers.getContractAt("StakingVault", stakingVaultAddress, vaultOwner); - expect(await vault.beacon()).to.equal(beacon); const delegationCreatedEvents = findEvents(vaultCreationReceipt, "DelegationCreated"); expect(delegationCreatedEvents.length).to.equal(1); diff --git a/test/0.8.25/vaults/staking-vault/staking-vault.test.ts b/test/0.8.25/vaults/staking-vault/staking-vault.test.ts index 1d4ad2904..2df2c8e3e 100644 --- a/test/0.8.25/vaults/staking-vault/staking-vault.test.ts +++ b/test/0.8.25/vaults/staking-vault/staking-vault.test.ts @@ -26,7 +26,6 @@ describe("StakingVault.sol", () => { let vaultOwner: HardhatEthersSigner; let operator: HardhatEthersSigner; let stranger: HardhatEthersSigner; - let beaconSigner: HardhatEthersSigner; let elRewardsSender: HardhatEthersSigner; let vaultHubSigner: HardhatEthersSigner; @@ -34,21 +33,18 @@ describe("StakingVault.sol", () => { let stakingVaultImplementation: StakingVault; let depositContract: DepositContract__MockForStakingVault; let vaultHub: VaultHub__MockForStakingVault; - let vaultFactory: VaultFactory__MockForStakingVault; let ethRejector: EthRejector; let vaultOwnerAddress: string; let stakingVaultAddress: string; let vaultHubAddress: string; - let vaultFactoryAddress: string; let depositContractAddress: string; - let beaconAddress: string; let ethRejectorAddress: string; let originalState: string; before(async () => { [vaultOwner, operator, elRewardsSender, stranger] = await ethers.getSigners(); - [stakingVault, vaultHub, vaultFactory, stakingVaultImplementation, depositContract] = + [stakingVault, vaultHub /* vaultFactory */, , stakingVaultImplementation, depositContract] = await deployStakingVaultBehindBeaconProxy(); ethRejector = await ethers.deployContract("EthRejector"); @@ -56,11 +52,8 @@ describe("StakingVault.sol", () => { stakingVaultAddress = await stakingVault.getAddress(); vaultHubAddress = await vaultHub.getAddress(); depositContractAddress = await depositContract.getAddress(); - beaconAddress = await stakingVaultImplementation.beacon(); - vaultFactoryAddress = await vaultFactory.getAddress(); ethRejectorAddress = await ethRejector.getAddress(); - beaconSigner = await impersonate(beaconAddress, ether("10")); vaultHubSigner = await impersonate(vaultHubAddress, ether("10")); }); @@ -101,7 +94,7 @@ describe("StakingVault.sol", () => { it("reverts on initialization", async () => { await expect( - stakingVaultImplementation.connect(beaconSigner).initialize(vaultOwner, operator, "0x"), + stakingVaultImplementation.connect(stranger).initialize(vaultOwner, operator, "0x"), ).to.be.revertedWithCustomError(stakingVaultImplementation, "InvalidInitialization"); }); }); @@ -112,7 +105,6 @@ describe("StakingVault.sol", () => { expect(await stakingVault.getInitializedVersion()).to.equal(1n); expect(await stakingVault.vaultHub()).to.equal(vaultHubAddress); expect(await stakingVault.DEPOSIT_CONTRACT()).to.equal(depositContractAddress); - expect(await stakingVault.beacon()).to.equal(vaultFactoryAddress); expect(await stakingVault.owner()).to.equal(await vaultOwner.getAddress()); expect(await stakingVault.operator()).to.equal(operator); diff --git a/test/0.8.25/vaults/vaultFactory.test.ts b/test/0.8.25/vaults/vaultFactory.test.ts index 98233d67d..765946c65 100644 --- a/test/0.8.25/vaults/vaultFactory.test.ts +++ b/test/0.8.25/vaults/vaultFactory.test.ts @@ -1,11 +1,12 @@ import { expect } from "chai"; -import { ZeroAddress } from "ethers"; +import { keccak256, ZeroAddress } from "ethers"; import { ethers } from "hardhat"; import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import { Accounting, + BeaconProxy, Delegation, DepositContract__MockForBeaconChainDepositor, LidoLocator, @@ -49,6 +50,9 @@ describe("VaultFactory.sol", () => { let locator: LidoLocator; + let vaultBeaconProxy: BeaconProxy; + let vaultBeaconProxyCode: string; + let originalState: string; before(async () => { @@ -78,6 +82,9 @@ describe("VaultFactory.sol", () => { //beacon beacon = await ethers.deployContract("UpgradeableBeacon", [implOld, admin]); + vaultBeaconProxy = await ethers.deployContract("BeaconProxy", [beacon, "0x"]); + vaultBeaconProxyCode = await ethers.provider.getCode(await vaultBeaconProxy.getAddress()); + delegation = await ethers.deployContract("Delegation", [steth, weth, wsteth], { from: deployer }); vaultFactory = await ethers.deployContract("VaultFactory", [beacon, delegation], { from: deployer }); @@ -164,7 +171,6 @@ describe("VaultFactory.sol", () => { .withArgs(await admin.getAddress(), await delegation_.getAddress()); expect(await delegation_.getAddress()).to.eq(await vault.owner()); - expect(await vault.beacon()).to.eq(await beacon.getAddress()); }); it("check `version()`", async () => { @@ -212,7 +218,7 @@ describe("VaultFactory.sol", () => { expect(await delegator1.getAddress()).to.eq(await vault1.owner()); expect(await delegator2.getAddress()).to.eq(await vault2.owner()); - //attempting to add a vault without adding a beacon to the allowed list + //attempting to add a vault without adding a proxy bytecode to the allowed list await expect( accounting .connect(admin) @@ -223,26 +229,12 @@ describe("VaultFactory.sol", () => { config1.thresholdReserveRatioBP, config1.treasuryFeeBP, ), - ).to.revertedWithCustomError(accounting, "BeaconNotAllowed"); + ).to.revertedWithCustomError(accounting, "VaultProxyNotAllowed"); - //add beacon to whitelist - await accounting.connect(admin).addBeacon(beacon); - - //attempting to add a vault without adding a implementation to the allowed list - await expect( - accounting - .connect(admin) - .connectVault( - await vault1.getAddress(), - config1.shareLimit, - config1.minReserveRatioBP, - config1.thresholdReserveRatioBP, - config1.treasuryFeeBP, - ), - ).to.revertedWithCustomError(accounting, "ImplNotAllowed"); + const vaultProxyCodeHash = keccak256(vaultBeaconProxyCode); - //add impl to whitelist - await accounting.connect(admin).addVaultImpl(implOld); + //add proxy code hash to whitelist + await accounting.connect(admin).addVaultProxyCodehash(vaultProxyCodeHash); //connect vault 1 to VaultHub await accounting @@ -273,7 +265,7 @@ describe("VaultFactory.sol", () => { //create new vault with new implementation const { vault: vault3 } = await createVaultProxy(vaultFactory, admin, vaultOwner1, operator); - //we upgrade implementation and do not add it to whitelist + //we upgrade implementation - we do not check implementation, just proxy bytecode await expect( accounting .connect(admin) @@ -284,7 +276,7 @@ describe("VaultFactory.sol", () => { config2.thresholdReserveRatioBP, config2.treasuryFeeBP, ), - ).to.revertedWithCustomError(accounting, "ImplNotAllowed"); + ).to.not.revertedWithCustomError(accounting, "VaultProxyNotAllowed"); const vault1WithNewImpl = await ethers.getContractAt("StakingVault__HarnessForTestUpgrade", vault1, deployer); const vault2WithNewImpl = await ethers.getContractAt("StakingVault__HarnessForTestUpgrade", vault2, deployer);