diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index c2e0d69f8..4164ce2d1 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -586,7 +586,7 @@ contract Lido is Versioned, StETHPermit, AragonApp { * @dev can be called only by accounting */ function mintShares(address _recipient, uint256 _amountOfShares) public { - require(msg.sender == getLidoLocator().accounting() || msg.sender == getLidoLocator().vaultHub(), "APP_AUTH_FAILED"); + _auth(getLidoLocator().accounting()); _whenNotStopped(); _mintShares(_recipient, _amountOfShares); @@ -614,12 +614,14 @@ contract Lido is Versioned, StETHPermit, AragonApp { * @notice Mint shares backed by external ether sources * @param _recipient Address to receive the minted shares * @param _amountOfShares Amount of shares to mint - * @dev Can be called only by accounting (authentication in mintShares method). + * @dev Can be called only by VaultHub * NB: Reverts if the the external balance limit is exceeded. */ function mintExternalShares(address _recipient, uint256 _amountOfShares) external { require(_recipient != address(0), "MINT_RECEIVER_ZERO_ADDRESS"); require(_amountOfShares != 0, "MINT_ZERO_AMOUNT_OF_SHARES"); + _auth(getLidoLocator().vaultHub()); + _whenNotStopped(); uint256 newExternalShares = EXTERNAL_SHARES_POSITION.getStorageUint256().add(_amountOfShares); uint256 maxMintableExternalShares = _getMaxMintableExternalShares(); @@ -628,7 +630,10 @@ contract Lido is Versioned, StETHPermit, AragonApp { EXTERNAL_SHARES_POSITION.setStorageUint256(newExternalShares); - mintShares(_recipient, _amountOfShares); + _mintShares(_recipient, _amountOfShares); + // emit event after minting shares because we are always having the net new ether under the hood + // for vaults we have new locked ether and for fees we have a part of rewards + _emitTransferAfterMintingShares(_recipient, _amountOfShares); emit ExternalSharesMinted(_recipient, _amountOfShares, getPooledEthByShares(_amountOfShares)); } diff --git a/contracts/0.8.25/Accounting.sol b/contracts/0.8.25/Accounting.sol index 0da4dec23..592112a5e 100644 --- a/contracts/0.8.25/Accounting.sol +++ b/contracts/0.8.25/Accounting.sol @@ -341,7 +341,7 @@ contract Accounting { ); if (_update.totalVaultsTreasuryFeeShares > 0) { - LIDO.mintExternalShares(LIDO_LOCATOR.treasury(), _update.totalVaultsTreasuryFeeShares); + _contracts.vaultHub.mintVaultsTreasuryFeeShares(LIDO_LOCATOR.treasury(), _update.totalVaultsTreasuryFeeShares); } _notifyRebaseObserver(_contracts.postTokenRebaseReceiver, _report, _pre, _update); diff --git a/contracts/0.8.25/vaults/VaultHub.sol b/contracts/0.8.25/vaults/VaultHub.sol index e9034b81e..604a89c1f 100644 --- a/contracts/0.8.25/vaults/VaultHub.sol +++ b/contracts/0.8.25/vaults/VaultHub.sol @@ -74,7 +74,7 @@ contract VaultHub is PausableUntilWithRoles { /// @notice Lido stETH contract IStETH public immutable STETH; - address public immutable accounting; + address public immutable ACCOUNTING; /// @param _stETH Lido stETH contract /// @param _connectedVaultsLimit Maximum number of vaults that can be connected simultaneously @@ -85,7 +85,7 @@ contract VaultHub is PausableUntilWithRoles { if (_relativeShareLimitBP > TOTAL_BASIS_POINTS) revert RelativeShareLimitBPTooHigh(_relativeShareLimitBP, TOTAL_BASIS_POINTS); STETH = _stETH; - accounting = _accounting; + ACCOUNTING = _accounting; CONNECTED_VAULTS_LIMIT = _connectedVaultsLimit; RELATIVE_SHARE_LIMIT_BP = _relativeShareLimitBP; @@ -363,27 +363,27 @@ contract VaultHub is PausableUntilWithRoles { /// @notice Forces validator exit from the beacon chain when vault is unhealthy /// @param _vault The address of the vault to exit validators from /// @param _pubkeys The public keys of the validators to exit - /// @param _refundRecepient The address that will receive the refund for transaction costs + /// @param _refundRecipient The address that will receive the refund for transaction costs /// @dev When the vault becomes unhealthy, anyone can force its validators to exit the beacon chain /// This returns the vault's deposited ETH back to vault's balance and allows to rebalance the vault function forceValidatorExit( address _vault, bytes calldata _pubkeys, - address _refundRecepient + address _refundRecipient ) external payable { if (msg.value == 0) revert ZeroArgument("msg.value"); if (_vault == address(0)) revert ZeroArgument("_vault"); if (_pubkeys.length == 0) revert ZeroArgument("_pubkeys"); - if (_refundRecepient == address(0)) revert ZeroArgument("_refundRecepient"); + if (_refundRecipient == address(0)) revert ZeroArgument("_refundRecipient"); if (_pubkeys.length % PUBLIC_KEY_LENGTH != 0) revert InvalidPubkeysLength(); _requireUnhealthy(_vault); uint256 numValidators = _pubkeys.length / PUBLIC_KEY_LENGTH; uint64[] memory amounts = new uint64[](numValidators); - IStakingVault(_vault).triggerValidatorWithdrawal{value: msg.value}(_pubkeys, amounts, _refundRecepient); + IStakingVault(_vault).triggerValidatorWithdrawal{value: msg.value}(_pubkeys, amounts, _refundRecipient); - emit ForceValidatorExitTriggered(_vault, _pubkeys, _refundRecepient); + emit ForceValidatorExitTriggered(_vault, _pubkeys, _refundRecipient); } function _disconnect(address _vault) internal { @@ -490,7 +490,7 @@ contract VaultHub is PausableUntilWithRoles { uint256[] memory _locked, uint256[] memory _treasureFeeShares ) external { - if (msg.sender != accounting) revert NotAuthorized("updateVaults", msg.sender); + if (msg.sender != ACCOUNTING) revert NotAuthorized("updateVaults", msg.sender); VaultHubStorage storage $ = _getVaultHubStorage(); for (uint256 i = 0; i < _valuations.length; i++) { @@ -522,6 +522,11 @@ contract VaultHub is PausableUntilWithRoles { } } + function mintVaultsTreasuryFeeShares(address _recipient, uint256 _amountOfShares) external { + if (msg.sender != ACCOUNTING) revert NotAuthorized("mintVaultsTreasuryFeeShares", msg.sender); + STETH.mintExternalShares(_recipient, _amountOfShares); + } + function _vaultAuth(address _vault, string memory _operation) internal view { if (msg.sender != OwnableUpgradeable(_vault).owner()) revert NotAuthorized(_operation, msg.sender); } @@ -558,7 +563,7 @@ contract VaultHub is PausableUntilWithRoles { event BurnedSharesOnVault(address indexed vault, uint256 amountOfShares); event VaultRebalanced(address indexed vault, uint256 sharesBurned); event VaultProxyCodehashAdded(bytes32 indexed codehash); - event ForceValidatorExitTriggered(address indexed vault, bytes pubkeys, address refundRecepient); + event ForceValidatorExitTriggered(address indexed vault, bytes pubkeys, address refundRecipient); error StETHMintFailed(address vault); error AlreadyHealthy(address vault); diff --git a/contracts/0.8.9/LidoLocator.sol b/contracts/0.8.9/LidoLocator.sol index 8bf1bfa64..2ce2f6113 100644 --- a/contracts/0.8.9/LidoLocator.sol +++ b/contracts/0.8.9/LidoLocator.sol @@ -35,6 +35,7 @@ contract LidoLocator is ILidoLocator { error ZeroAddress(); + //solhint-disable immutable-vars-naming address public immutable accountingOracle; address public immutable depositSecurityModule; address public immutable elRewardsVault; @@ -52,6 +53,7 @@ contract LidoLocator is ILidoLocator { address public immutable accounting; address public immutable wstETH; address public immutable vaultHub; + //solhint-enable immutable-vars-naming /** * @notice declare service locations diff --git a/test/0.8.25/vaults/vaulthub/vaulthub.forceExit.test.ts b/test/0.8.25/vaults/vaulthub/vaulthub.forceExit.test.ts index 226b3c01f..a7771e14e 100644 --- a/test/0.8.25/vaults/vaulthub/vaulthub.forceExit.test.ts +++ b/test/0.8.25/vaults/vaulthub/vaulthub.forceExit.test.ts @@ -130,7 +130,7 @@ describe("VaultHub.sol:forceExit", () => { it("reverts if zero refund recipient", async () => { await expect(vaultHub.forceValidatorExit(vaultAddress, SAMPLE_PUBKEY, ZeroAddress, { value: 1n })) .to.be.revertedWithCustomError(vaultHub, "ZeroArgument") - .withArgs("_refundRecepient"); + .withArgs("_refundRecipient"); }); it("reverts if pubkeys are not valid", async () => {