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

Accounting fixes after review #924

Draft
wants to merge 6 commits into
base: feat/vaults
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 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
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 <[email protected]>
// SPDX-FileCopyrightText: 2025 Lido <[email protected]>
// 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
7 changes: 3 additions & 4 deletions contracts/common/interfaces/ReportValues.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

// See contracts/COMPILERS.md
// solhint-disable-next-line
pragma solidity >=0.4.24 <0.9.0;
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;
}
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
32 changes: 16 additions & 16 deletions lib/protocol/helpers/accounting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export type OracleReportParams = {
reportElVault?: boolean;
reportWithdrawalsVault?: boolean;
vaultValues?: bigint[];
netCashFlows?: bigint[];
inOutDeltas?: bigint[];
silent?: boolean;
};

Expand Down Expand Up @@ -85,7 +85,7 @@ export const report = async (
reportElVault = true,
reportWithdrawalsVault = true,
vaultValues = [],
netCashFlows = [],
inOutDeltas = [],
}: OracleReportParams = {},
): Promise<OracleReportResults> => {
const { hashConsensus, lido, elRewardsVault, withdrawalVault, burner, accountingOracle } = ctx.contracts;
Expand Down Expand Up @@ -145,7 +145,7 @@ export const report = async (
withdrawalVaultBalance,
elRewardsVaultBalance,
vaultValues,
netCashFlows,
inOutDeltas,
});

if (!simulatedReport) {
Expand Down Expand Up @@ -189,7 +189,7 @@ export const report = async (
withdrawalFinalizationBatches,
isBunkerMode,
vaultsValues: vaultValues,
vaultsNetCashFlows: netCashFlows,
vaultsInOutDeltas: inOutDeltas,
extraDataFormat,
extraDataHash,
extraDataItemsCount,
Expand Down Expand Up @@ -278,7 +278,7 @@ type SimulateReportParams = {
withdrawalVaultBalance: bigint;
elRewardsVaultBalance: bigint;
vaultValues: bigint[];
netCashFlows: bigint[];
inOutDeltas: bigint[];
};

type SimulateReportResult = {
Expand All @@ -300,7 +300,7 @@ const simulateReport = async (
withdrawalVaultBalance,
elRewardsVaultBalance,
vaultValues,
netCashFlows,
inOutDeltas,
}: SimulateReportParams,
): Promise<SimulateReportResult> => {
const { hashConsensus, accounting } = ctx.contracts;
Expand Down Expand Up @@ -328,7 +328,7 @@ const simulateReport = async (
sharesRequestedToBurn: 0n,
withdrawalFinalizationBatches: [],
vaultValues,
netCashFlows,
inOutDeltas,
},
0n,
);
Expand All @@ -355,7 +355,7 @@ type HandleOracleReportParams = {
withdrawalVaultBalance: bigint;
elRewardsVaultBalance: bigint;
vaultValues?: bigint[];
netCashFlows?: bigint[];
inOutDeltas?: bigint[];
};

export const handleOracleReport = async (
Expand All @@ -367,7 +367,7 @@ export const handleOracleReport = async (
withdrawalVaultBalance,
elRewardsVaultBalance,
vaultValues = [],
netCashFlows = [],
inOutDeltas = [],
}: HandleOracleReportParams,
): Promise<void> => {
const { hashConsensus, accountingOracle, accounting } = ctx.contracts;
Expand Down Expand Up @@ -399,7 +399,7 @@ export const handleOracleReport = async (
sharesRequestedToBurn,
withdrawalFinalizationBatches: [],
vaultValues,
netCashFlows,
inOutDeltas,
});

await trace("accounting.handleOracleReport", handleReportTx);
Expand Down Expand Up @@ -504,7 +504,7 @@ export type OracleReportSubmitParams = {
withdrawalFinalizationBatches?: bigint[];
isBunkerMode?: boolean;
vaultsValues: bigint[];
vaultsNetCashFlows: bigint[];
vaultsInOutDeltas: bigint[];
extraDataFormat?: bigint;
extraDataHash?: string;
extraDataItemsCount?: bigint;
Expand Down Expand Up @@ -534,7 +534,7 @@ const submitReport = async (
withdrawalFinalizationBatches = [],
isBunkerMode = false,
vaultsValues = [],
vaultsNetCashFlows = [],
vaultsInOutDeltas = [],
extraDataFormat = 0n,
extraDataHash = ZERO_BYTES32,
extraDataItemsCount = 0n,
Expand All @@ -555,7 +555,7 @@ const submitReport = async (
"Withdrawal finalization batches": withdrawalFinalizationBatches,
"Is bunker mode": isBunkerMode,
"Vaults values": vaultsValues,
"Vaults net cash flows": vaultsNetCashFlows,
"Vaults in-out deltas": vaultsInOutDeltas,
"Extra data format": extraDataFormat,
"Extra data hash": extraDataHash,
"Extra data items count": extraDataItemsCount,
Expand All @@ -578,7 +578,7 @@ const submitReport = async (
withdrawalFinalizationBatches,
isBunkerMode,
vaultsValues,
vaultsNetCashFlows,
vaultsInOutDeltas,
extraDataFormat,
extraDataHash,
extraDataItemsCount,
Expand Down Expand Up @@ -710,7 +710,7 @@ const getReportDataItems = (data: AccountingOracle.ReportDataStruct) => [
data.withdrawalFinalizationBatches,
data.isBunkerMode,
data.vaultsValues,
data.vaultsNetCashFlows,
data.vaultsInOutDeltas,
data.extraDataFormat,
data.extraDataHash,
data.extraDataItemsCount,
Expand All @@ -733,7 +733,7 @@ const calcReportDataHash = (items: ReturnType<typeof getReportDataItems>) => {
"uint256[]", // withdrawalFinalizationBatches
"bool", // isBunkerMode
"uint256[]", // vaultsValues
"int256[]", // vaultsNetCashFlow
"int256[]", // vaultsInOutDeltas
"uint256", // extraDataFormat
"bytes32", // extraDataHash
"uint256", // extraDataItemsCount
Expand Down
Loading
Loading