Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: accounting fixes after review #924

Merged
merged 9 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .solhintignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
contracts/openzeppelin/
contracts/0.8.9/utils/access/AccessControl.sol
contracts/0.8.9/utils/access/AccessControlEnumerable.sol

contracts/0.4.24/template/
contracts/0.6.11/deposit_contract.sol
contracts/0.6.12/WstETH.sol
contracts/0.6.12/
contracts/0.8.4/WithdrawalsManagerProxy.sol
contracts/0.8.9/LidoExecutionLayerRewardsVault.sol
4 changes: 2 additions & 2 deletions contracts/0.4.24/Lido.sol
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,11 @@ contract Lido is Versioned, StETHPermit, AragonApp {
event DepositedValidatorsChanged(uint256 depositedValidators);

// Emitted when oracle accounting report processed
// @dev `principalCLBalance` is the balance of the validators on previous report
// @dev `preClBalance` is the balance of the validators on previous report
// plus the amount of ether that was deposited to the deposit contract since then
event ETHDistributed(
uint256 indexed reportTimestamp,
uint256 principalCLBalance, // preClBalance + deposits
uint256 preClBalance, // actually its preClBalance + deposits due to compatibility reasons
uint256 postCLBalance,
uint256 withdrawalsWithdrawn,
uint256 executionLayerRewardsWithdrawn,
Expand Down
61 changes: 32 additions & 29 deletions contracts/0.8.25/Accounting.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2023 Lido <info@lido.fi>
// SPDX-FileCopyrightText: 2025 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0

// See contracts/COMPILERS.md
Expand All @@ -17,9 +17,11 @@ import {ReportValues} from "contracts/common/interfaces/ReportValues.sol";

/// @title Lido Accounting contract
/// @author folkyatina
/// @notice contract is responsible for handling oracle reports
/// @notice contract is responsible for handling accounting oracle reports
/// calculating all the state changes that is required to apply the report
/// and distributing calculated values to relevant parts of the protocol
/// @dev accounting is inherited from VaultHub contract to reduce gas costs and
/// simplify the auth flows, but they are mostly independent
contract Accounting is VaultHub {
struct Contracts {
address accountingOracleAddress;
Expand Down Expand Up @@ -54,11 +56,12 @@ contract Accounting is VaultHub {
uint256 sharesToBurnForWithdrawals;
/// @notice number of stETH shares that will be burned from Burner this report
uint256 totalSharesToBurn;
/// @notice number of stETH shares to mint as a fee to Lido treasury
/// @notice number of stETH shares to mint as a protocol fee
uint256 sharesToMintAsFees;
/// @notice amount of NO fees to transfer to each module
StakingRewardsDistribution rewardDistribution;
/// @notice amount of CL ether that is not rewards earned during this report period
/// the sum of CL balance on the previous report and the amount of fresh deposits since then
uint256 principalClBalance;
/// @notice total number of stETH shares after the report is applied
uint256 postTotalShares;
Expand Down Expand Up @@ -104,11 +107,11 @@ contract Accounting is VaultHub {

/// @notice calculates all the state changes that is required to apply the report
/// @param _report report values
/// @param _withdrawalShareRate maximum share rate used for withdrawal resolution
/// @param _withdrawalShareRate maximum share rate used for withdrawal finalization
/// if _withdrawalShareRate == 0, no withdrawals are
/// simulated
function simulateOracleReport(
ReportValues memory _report,
ReportValues calldata _report,
uint256 _withdrawalShareRate
) public view returns (CalculatedValues memory update) {
Contracts memory contracts = _loadOracleReportContracts();
Expand All @@ -120,7 +123,7 @@ contract Accounting is VaultHub {
/// @notice Updates accounting stats, collects EL rewards and distributes collected rewards
/// if beacon balance increased, performs withdrawal requests finalization
/// @dev periodically called by the AccountingOracle contract
function handleOracleReport(ReportValues memory _report) external {
function handleOracleReport(ReportValues calldata _report) external {
Contracts memory contracts = _loadOracleReportContracts();
if (msg.sender != contracts.accountingOracleAddress) revert NotAuthorized("handleOracleReport", msg.sender);

Expand All @@ -136,7 +139,7 @@ contract Accounting is VaultHub {
/// @dev prepare all the required data to process the report
function _calculateOracleReportContext(
Contracts memory _contracts,
ReportValues memory _report
ReportValues calldata _report
) internal view returns (PreReportState memory pre, CalculatedValues memory update, uint256 withdrawalsShareRate) {
pre = _snapshotPreReportState();

Expand All @@ -161,7 +164,7 @@ contract Accounting is VaultHub {
function _simulateOracleReport(
Contracts memory _contracts,
PreReportState memory _pre,
ReportValues memory _report,
ReportValues calldata _report,
uint256 _withdrawalsShareRate
) internal view returns (CalculatedValues memory update) {
update.rewardDistribution = _getStakingRewardsDistribution(_contracts.stakingRouter);
Expand Down Expand Up @@ -239,7 +242,7 @@ contract Accounting is VaultHub {
/// @dev return amount to lock on withdrawal queue and shares to burn depending on the finalization batch parameters
function _calculateWithdrawals(
Contracts memory _contracts,
ReportValues memory _report,
ReportValues calldata _report,
uint256 _simulatedShareRate
) internal view returns (uint256 etherToLock, uint256 sharesToBurn) {
if (_report.withdrawalFinalizationBatches.length != 0 && !_contracts.withdrawalQueue.isPaused()) {
Expand All @@ -250,36 +253,36 @@ contract Accounting is VaultHub {
}
}

/// @dev calculates shares that are minted to treasury as the protocol fees
/// @dev calculates shares that are minted as the protocol fees
function _calculateFeesAndExternalEther(
ReportValues memory _report,
ReportValues calldata _report,
PreReportState memory _pre,
CalculatedValues memory _calculated
CalculatedValues memory _update
) internal pure returns (uint256 sharesToMintAsFees, uint256 postExternalEther) {
// we are calculating the share rate equal to the post-rebase share rate
// but with fees taken as eth deduction
// and without externalBalance taken into account
uint256 shares = _pre.totalShares - _calculated.totalSharesToBurn - _pre.externalShares;
uint256 eth = _pre.totalPooledEther - _calculated.etherToFinalizeWQ - _pre.externalEther;
uint256 shares = _pre.totalShares - _update.totalSharesToBurn - _pre.externalShares;
uint256 eth = _pre.totalPooledEther - _update.etherToFinalizeWQ - _pre.externalEther;

uint256 unifiedClBalance = _report.clBalance + _calculated.withdrawals;
uint256 unifiedClBalance = _report.clBalance + _update.withdrawals;

// Don't mint/distribute any protocol fee on the non-profitable Lido oracle report
// (when consensus layer balance delta is zero or negative).
// See LIP-12 for details:
// https://research.lido.fi/t/lip-12-on-chain-part-of-the-rewards-distribution-after-the-merge/1625
if (unifiedClBalance > _calculated.principalClBalance) {
uint256 totalRewards = unifiedClBalance - _calculated.principalClBalance + _calculated.elRewards;
uint256 totalFee = _calculated.rewardDistribution.totalFee;
uint256 precision = _calculated.rewardDistribution.precisionPoints;
if (unifiedClBalance > _update.principalClBalance) {
uint256 totalRewards = unifiedClBalance - _update.principalClBalance + _update.elRewards;
uint256 totalFee = _update.rewardDistribution.totalFee;
uint256 precision = _update.rewardDistribution.precisionPoints;
uint256 feeEther = (totalRewards * totalFee) / precision;
eth += totalRewards - feeEther;

// but we won't pay fees in ether, so we need to calculate how many shares we need to mint as fees
sharesToMintAsFees = (feeEther * shares) / eth;
} else {
uint256 clPenalty = _calculated.principalClBalance - unifiedClBalance;
eth = eth - clPenalty + _calculated.elRewards;
uint256 clPenalty = _update.principalClBalance - unifiedClBalance;
eth = eth - clPenalty + _update.elRewards;
}

// externalBalance is rebasing at the same rate as the primary balance does
Expand All @@ -289,10 +292,10 @@ contract Accounting is VaultHub {
/// @dev applies the precalculated changes to the protocol state
function _applyOracleReportContext(
Contracts memory _contracts,
ReportValues memory _report,
ReportValues calldata _report,
PreReportState memory _pre,
CalculatedValues memory _update,
uint256 _simulatedShareRate
uint256 _withdrawalShareRate
) internal {
_checkAccountingOracleReport(_contracts, _report, _pre, _update);

Expand Down Expand Up @@ -328,13 +331,13 @@ contract Accounting is VaultHub {
_update.withdrawals,
_update.elRewards,
lastWithdrawalRequestToFinalize,
_simulatedShareRate,
_withdrawalShareRate,
_update.etherToFinalizeWQ
);

_updateVaults(
_report.vaultValues,
_report.netCashFlows,
_report.inOutDeltas,
_update.vaultsLockedEther,
_update.vaultsTreasuryFeeShares
);
Expand All @@ -343,7 +346,7 @@ contract Accounting is VaultHub {
STETH.mintExternalShares(LIDO_LOCATOR.treasury(), _update.totalVaultsTreasuryFeeShares);
}

_notifyObserver(_contracts.postTokenRebaseReceiver, _report, _pre, _update);
_notifyRebaseObserver(_contracts.postTokenRebaseReceiver, _report, _pre, _update);

LIDO.emitTokenRebase(
_report.timestamp,
Expand All @@ -360,7 +363,7 @@ contract Accounting is VaultHub {
/// reverts if a check fails
function _checkAccountingOracleReport(
Contracts memory _contracts,
ReportValues memory _report,
ReportValues calldata _report,
PreReportState memory _pre,
CalculatedValues memory _update
) internal {
Expand Down Expand Up @@ -389,9 +392,9 @@ contract Accounting is VaultHub {
}

/// @dev Notify observer about the completed token rebase.
function _notifyObserver(
function _notifyRebaseObserver(
IPostTokenRebaseReceiver _postTokenRebaseReceiver,
ReportValues memory _report,
ReportValues calldata _report,
PreReportState memory _pre,
CalculatedValues memory _update
) internal {
Expand Down
7 changes: 3 additions & 4 deletions contracts/0.8.9/oracle/AccountingOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,8 @@ contract AccountingOracle is BaseOracle {
/// @dev The values of the vaults as observed at the reference slot.
/// Sum of all the balances of Lido validators of the vault plus the balance of the vault itself.
uint256[] vaultsValues;
/// @dev The net cash flows of the vaults as observed at the reference slot.
/// Flow of the funds in and out of the vaults (deposit/withdrawal) without the rewards.
int256[] vaultsNetCashFlows;
/// @dev The in-out deltas (deposits - withdrawals) of the vaults as observed at the reference slot.
int256[] vaultsInOutDeltas;
///
/// Extra data — the oracle information that allows asynchronous processing in
/// chunks, after the main data is processed. The oracle doesn't enforce that extra data
Expand Down Expand Up @@ -583,7 +582,7 @@ contract AccountingOracle is BaseOracle {
data.sharesRequestedToBurn,
data.withdrawalFinalizationBatches,
data.vaultsValues,
data.vaultsNetCashFlows
data.vaultsInOutDeltas
)
);

Expand Down
2 changes: 1 addition & 1 deletion contracts/common/interfaces/ILidoLocator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: GPL-3.0

// See contracts/COMPILERS.md
// solhint-disable-next-line
// solhint-disable-next-line lido/fixed-compiler-version
pragma solidity >=0.4.24 <0.9.0;

interface ILidoLocator {
Expand Down
9 changes: 4 additions & 5 deletions contracts/common/interfaces/ReportValues.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// SPDX-License-Identifier: GPL-3.0

// See contracts/COMPILERS.md
// solhint-disable-next-line
pragma solidity >=0.4.24 <0.9.0;
// solhint-disable-next-line lido/fixed-compiler-version
pragma solidity ^0.8.9;

struct ReportValues {
/// @notice timestamp of the block the report is based on. All provided report values is actual on this timestamp
Expand All @@ -27,7 +27,6 @@ struct ReportValues {
/// (sum of all the balances of Lido validators of the vault
/// plus the balance of the vault itself)
uint256[] vaultValues;
/// @notice netCashFlow of each Lido vault
/// (difference between deposits to and withdrawals from the vault)
int256[] netCashFlows;
/// @notice in-out deltas (deposits - withdrawals) of each Lido vault
int256[] inOutDeltas;
}
20 changes: 10 additions & 10 deletions contracts/testnets/sepolia/SepoliaDepositAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
/* See contracts/COMPILERS.md */
pragma solidity 0.8.9;

import "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts-v4.4/access/Ownable.sol";
import "../../0.8.9/utils/Versioned.sol";
import {IERC20} from "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol";
import {Ownable} from "@openzeppelin/contracts-v4.4/access/Ownable.sol";
import {Versioned} from "../../0.8.9/utils/Versioned.sol";

interface IDepositContract {
event DepositEvent(bytes pubkey, bytes withdrawal_credentials, bytes amount, bytes signature, bytes index);
Expand Down Expand Up @@ -43,10 +43,10 @@ contract SepoliaDepositAdapter is IDepositContract, Ownable, Versioned {
error ZeroAddress(string field);

// Sepolia original deposit contract address
ISepoliaDepositContract public immutable originalContract;
ISepoliaDepositContract public immutable ORIGINAL_CONTRACT;

constructor(address _deposit_contract) {
originalContract = ISepoliaDepositContract(_deposit_contract);
ORIGINAL_CONTRACT = ISepoliaDepositContract(_deposit_contract);
}

function initialize(address _owner) external {
Expand All @@ -57,11 +57,11 @@ contract SepoliaDepositAdapter is IDepositContract, Ownable, Versioned {
}

function get_deposit_root() external view override returns (bytes32) {
return originalContract.get_deposit_root();
return ORIGINAL_CONTRACT.get_deposit_root();
}

function get_deposit_count() external view override returns (bytes memory) {
return originalContract.get_deposit_count();
return ORIGINAL_CONTRACT.get_deposit_count();
}

receive() external payable {
Expand All @@ -79,8 +79,8 @@ contract SepoliaDepositAdapter is IDepositContract, Ownable, Versioned {
}

function recoverBepolia() external onlyOwner {
uint256 bepoliaOwnTokens = originalContract.balanceOf(address(this));
bool success = originalContract.transfer(owner(), bepoliaOwnTokens);
uint256 bepoliaOwnTokens = ORIGINAL_CONTRACT.balanceOf(address(this));
bool success = ORIGINAL_CONTRACT.transfer(owner(), bepoliaOwnTokens);
if (!success) {
revert BepoliaRecoverFailed();
}
Expand All @@ -93,7 +93,7 @@ contract SepoliaDepositAdapter is IDepositContract, Ownable, Versioned {
bytes calldata signature,
bytes32 deposit_data_root
) external payable override {
originalContract.deposit{value: msg.value}(pubkey, withdrawal_credentials, signature, deposit_data_root);
ORIGINAL_CONTRACT.deposit{value: msg.value}(pubkey, withdrawal_credentials, signature, deposit_data_root);
// solhint-disable-next-line avoid-low-level-calls
(bool success,) = owner().call{value: msg.value}("");
if (!success) {
Expand Down
4 changes: 2 additions & 2 deletions lib/oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const DEFAULT_REPORT_FIELDS: OracleReport = {
withdrawalFinalizationBatches: [],
isBunkerMode: false,
vaultsValues: [],
vaultsNetCashFlows: [],
vaultsInOutDeltas: [],
extraDataFormat: 0n,
extraDataHash: ethers.ZeroHash,
extraDataItemsCount: 0n,
Expand All @@ -66,7 +66,7 @@ export function getReportDataItems(r: OracleReport) {
r.withdrawalFinalizationBatches,
r.isBunkerMode,
r.vaultsValues,
r.vaultsNetCashFlows,
r.vaultsInOutDeltas,
r.extraDataFormat,
r.extraDataHash,
r.extraDataItemsCount,
Expand Down
Loading
Loading