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

Detach Accounting from VaultHub #957

Merged
merged 12 commits into from
Feb 27, 2025
6 changes: 3 additions & 3 deletions contracts/0.4.24/Lido.sol
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ contract Lido is Versioned, StETHPermit, AragonApp {
* @dev can be called only by accounting
*/
function mintShares(address _recipient, uint256 _amountOfShares) public {
_auth(getLidoLocator().accounting());
require(msg.sender == getLidoLocator().accounting() || msg.sender == getLidoLocator().vaultHub(), "APP_AUTH_FAILED");
_whenNotStopped();

_mintShares(_recipient, _amountOfShares);
Expand Down Expand Up @@ -639,7 +639,7 @@ contract Lido is Versioned, StETHPermit, AragonApp {
*/
function burnExternalShares(uint256 _amountOfShares) external {
require(_amountOfShares != 0, "BURN_ZERO_AMOUNT_OF_SHARES");
_auth(getLidoLocator().accounting());
_auth(getLidoLocator().vaultHub());
_whenNotStopped();

uint256 externalShares = EXTERNAL_SHARES_POSITION.getStorageUint256();
Expand All @@ -663,7 +663,7 @@ contract Lido is Versioned, StETHPermit, AragonApp {
*/
function rebalanceExternalEtherToInternal() external payable {
require(msg.value != 0, "ZERO_VALUE");
_auth(getLidoLocator().accounting());
_auth(getLidoLocator().vaultHub());
_whenNotStopped();

uint256 shares = getSharesByPooledEth(msg.value);
Expand Down
34 changes: 14 additions & 20 deletions contracts/0.8.25/Accounting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,15 @@ import {ReportValues} from "contracts/common/interfaces/ReportValues.sol";
/// @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 {
contract Accounting {
struct Contracts {
address accountingOracleAddress;
IOracleReportSanityChecker oracleReportSanityChecker;
IBurner burner;
IWithdrawalQueue withdrawalQueue;
IPostTokenRebaseReceiver postTokenRebaseReceiver;
IStakingRouter stakingRouter;
VaultHub vaultHub;
}

struct PreReportState {
Expand Down Expand Up @@ -83,6 +82,8 @@ contract Accounting is VaultHub {
uint256 precisionPoints;
}

error NotAuthorized(string operation, address addr);

/// @notice deposit size in wei (for pre-maxEB accounting)
uint256 private constant DEPOSIT_SIZE = 32 ether;

Expand All @@ -93,24 +94,14 @@ contract Accounting is VaultHub {

/// @param _lidoLocator Lido Locator contract
/// @param _lido Lido contract
/// @param _connectedVaultsLimit Maximum number of active vaults that can be connected to the hub
/// @param _relativeShareLimitBP Maximum share limit for a single vault relative to Lido TVL in basis points
constructor(
ILidoLocator _lidoLocator,
ILido _lido,
uint256 _connectedVaultsLimit,
uint256 _relativeShareLimitBP
) VaultHub(_lido, _connectedVaultsLimit, _relativeShareLimitBP) {
ILido _lido
) {
LIDO_LOCATOR = _lidoLocator;
LIDO = _lido;
}

function initialize(address _admin) external initializer {
if (_admin == address(0)) revert ZeroArgument("_admin");

__VaultHub_init(_admin);
}

/// @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 finalization
Expand Down Expand Up @@ -232,7 +223,7 @@ contract Accounting is VaultHub {
// Calculate the amount of ether locked in the vaults to back external balance of stETH
// and the amount of shares to mint as fees to the treasury for each vaults
(update.vaultsLockedEther, update.vaultsTreasuryFeeShares, update.totalVaultsTreasuryFeeShares) =
_calculateVaultsRebase(
_contracts.vaultHub.calculateVaultsRebase(
update.postTotalShares,
update.postTotalPooledEther,
_pre.totalShares,
Expand Down Expand Up @@ -341,15 +332,16 @@ contract Accounting is VaultHub {
_update.etherToFinalizeWQ
);

_updateVaults(
// TODO: Remove this once decide on vaults reporting
_contracts.vaultHub.updateVaults(
_report.vaultValues,
_report.inOutDeltas,
_update.vaultsLockedEther,
_update.vaultsTreasuryFeeShares
);

if (_update.totalVaultsTreasuryFeeShares > 0) {
STETH.mintExternalShares(LIDO_LOCATOR.treasury(), _update.totalVaultsTreasuryFeeShares);
LIDO.mintExternalShares(LIDO_LOCATOR.treasury(), _update.totalVaultsTreasuryFeeShares);
}

_notifyRebaseObserver(_contracts.postTokenRebaseReceiver, _report, _pre, _update);
Expand Down Expand Up @@ -469,7 +461,8 @@ contract Accounting is VaultHub {
address burner,
address withdrawalQueue,
address postTokenRebaseReceiver,
address stakingRouter
address stakingRouter,
address vaultHub
) = LIDO_LOCATOR.oracleReportComponents();

return
Expand All @@ -479,7 +472,8 @@ contract Accounting is VaultHub {
IBurner(burner),
IWithdrawalQueue(withdrawalQueue),
IPostTokenRebaseReceiver(postTokenRebaseReceiver),
IStakingRouter(stakingRouter)
IStakingRouter(stakingRouter),
VaultHub(vaultHub)
);
}

Expand Down
21 changes: 15 additions & 6 deletions contracts/0.8.25/vaults/VaultHub.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
/// 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 PausableUntilWithRoles {
contract VaultHub is PausableUntilWithRoles {
/// @custom:storage-location erc7201:VaultHub
struct VaultHubStorage {
/// @notice vault sockets with vaults connected to the hub
Expand Down Expand Up @@ -74,22 +74,30 @@

/// @notice Lido stETH contract
IStETH public immutable STETH;
address public immutable accounting;

Check warning on line 77 in contracts/0.8.25/vaults/VaultHub.sol

View workflow job for this annotation

GitHub Actions / Solhint

Immutable variables name are set to be in capitalized SNAKE_CASE

/// @param _stETH Lido stETH contract
/// @param _connectedVaultsLimit Maximum number of vaults that can be connected simultaneously
/// @param _relativeShareLimitBP Maximum share limit relative to TVL in basis points
constructor(IStETH _stETH, uint256 _connectedVaultsLimit, uint256 _relativeShareLimitBP) {
constructor(IStETH _stETH, address _accounting, uint256 _connectedVaultsLimit, uint256 _relativeShareLimitBP) {
if (_connectedVaultsLimit == 0) revert ZeroArgument("_connectedVaultsLimit");
if (_relativeShareLimitBP == 0) revert ZeroArgument("_relativeShareLimitBP");
if (_relativeShareLimitBP > TOTAL_BASIS_POINTS) revert RelativeShareLimitBPTooHigh(_relativeShareLimitBP, TOTAL_BASIS_POINTS);

STETH = _stETH;
accounting = _accounting;
CONNECTED_VAULTS_LIMIT = _connectedVaultsLimit;
RELATIVE_SHARE_LIMIT_BP = _relativeShareLimitBP;

_disableInitializers();
}

function initialize(address _admin) external initializer {
if (_admin == address(0)) revert ZeroArgument("_admin");

__VaultHub_init(_admin);
}

/// @param _admin admin address to manage the roles
function __VaultHub_init(address _admin) internal onlyInitializing {
__AccessControlEnumerable_init();
Expand Down Expand Up @@ -394,56 +402,56 @@
emit VaultDisconnected(_vault);
}

function _calculateVaultsRebase(
function calculateVaultsRebase(
uint256 _postTotalShares,
uint256 _postTotalPooledEther,
uint256 _preTotalShares,
uint256 _preTotalPooledEther,
uint256 _sharesToMintAsFees
) internal view returns (uint256[] memory lockedEther, uint256[] memory treasuryFeeShares, uint256 totalTreasuryFeeShares) {
) public view returns (uint256[] memory lockedEther, uint256[] memory treasuryFeeShares, uint256 totalTreasuryFeeShares) {
/// HERE WILL BE ACCOUNTING DRAGON

// \||/
// | $___oo
// /\ /\ / (__,,,,|
// ) /^\) ^\/ _)
// ) /^\/ _)
// ) _ / / _)
// /\ )/\/ || | )_)
//< > |(,,) )__)
// || / \)___)\
// | \____( )___) )___
// \______(_______;;; __;;;

VaultHubStorage storage $ = _getVaultHubStorage();

uint256 length = vaultsCount();

treasuryFeeShares = new uint256[](length);
lockedEther = new uint256[](length);

for (uint256 i = 0; i < length; ++i) {
VaultSocket memory socket = $.sockets[i + 1];
if (!socket.pendingDisconnect) {
treasuryFeeShares[i] = _calculateTreasuryFees(
socket,
_postTotalShares - _sharesToMintAsFees,
_postTotalPooledEther,
_preTotalShares,
_preTotalPooledEther
);

totalTreasuryFeeShares += treasuryFeeShares[i];

uint256 totalMintedShares = socket.sharesMinted + treasuryFeeShares[i];
uint256 mintedStETH = (totalMintedShares * _postTotalPooledEther) / _postTotalShares; //TODO: check rounding
lockedEther[i] = Math256.max(
(mintedStETH * TOTAL_BASIS_POINTS) / (TOTAL_BASIS_POINTS - socket.reserveRatioBP),
CONNECT_DEPOSIT
);
}
}
}

/// @dev impossible to invoke this method under negative rebase
function _calculateTreasuryFees(
Expand Down Expand Up @@ -476,12 +484,13 @@
treasuryFeeShares = (treasuryFee * _preTotalShares) / _preTotalPooledEther;
}

function _updateVaults(
function updateVaults(
uint256[] memory _valuations,
int256[] memory _inOutDeltas,
uint256[] memory _locked,
uint256[] memory _treasureFeeShares
) internal {
) external {
if (msg.sender != accounting) revert NotAuthorized("updateVaults", msg.sender);
VaultHubStorage storage $ = _getVaultHubStorage();

for (uint256 i = 0; i < _valuations.length; i++) {
Expand Down
7 changes: 6 additions & 1 deletion contracts/0.8.9/LidoLocator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@
address oracleDaemonConfig;
address accounting;
address wstETH;
address vaultHub;
}

error ZeroAddress();

address public immutable accountingOracle;

Check warning on line 38 in contracts/0.8.9/LidoLocator.sol

View workflow job for this annotation

GitHub Actions / Solhint

Immutable variables name are set to be in capitalized SNAKE_CASE
address public immutable depositSecurityModule;

Check warning on line 39 in contracts/0.8.9/LidoLocator.sol

View workflow job for this annotation

GitHub Actions / Solhint

Immutable variables name are set to be in capitalized SNAKE_CASE
address public immutable elRewardsVault;

Check warning on line 40 in contracts/0.8.9/LidoLocator.sol

View workflow job for this annotation

GitHub Actions / Solhint

Immutable variables name are set to be in capitalized SNAKE_CASE
address public immutable legacyOracle;

Check warning on line 41 in contracts/0.8.9/LidoLocator.sol

View workflow job for this annotation

GitHub Actions / Solhint

Immutable variables name are set to be in capitalized SNAKE_CASE
address public immutable lido;
address public immutable oracleReportSanityChecker;
address public immutable postTokenRebaseReceiver;
Expand All @@ -50,6 +51,7 @@
address public immutable oracleDaemonConfig;
address public immutable accounting;
address public immutable wstETH;
address public immutable vaultHub;

/**
* @notice declare service locations
Expand All @@ -73,6 +75,7 @@
oracleDaemonConfig = _assertNonZero(_config.oracleDaemonConfig);
accounting = _assertNonZero(_config.accounting);
wstETH = _assertNonZero(_config.wstETH);
vaultHub = _assertNonZero(_config.vaultHub);
}

function coreComponents() external view returns(
Expand All @@ -99,6 +102,7 @@
address,
address,
address,
address,
address
) {
return (
Expand All @@ -107,7 +111,8 @@
burner,
withdrawalQueue,
postTokenRebaseReceiver,
stakingRouter
stakingRouter,
vaultHub
);
}

Expand Down
5 changes: 3 additions & 2 deletions contracts/common/interfaces/ILidoLocator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ interface ILidoLocator {
function oracleDaemonConfig() external view returns(address);
function accounting() external view returns (address);
function wstETH() external view returns (address);

function vaultHub() external view returns (address);
/// @notice Returns core Lido protocol component addresses in a single call
/// @dev This function provides a gas-efficient way to fetch multiple component addresses in a single call
function coreComponents() external view returns(
Expand All @@ -42,6 +42,7 @@ interface ILidoLocator {
address burner,
address withdrawalQueue,
address postTokenRebaseReceiver,
address stakingRouter
address stakingRouter,
address vaultHub
);
}
1 change: 1 addition & 0 deletions lib/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ async function getLocatorConfig(locatorAddress: string) {
"oracleDaemonConfig",
"accounting",
"wstETH",
"vaultHub",
] as (keyof LidoLocator.ConfigStruct)[];

const configPromises = addresses.map((name) => locator[name]());
Expand Down
6 changes: 4 additions & 2 deletions lib/protocol/discover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,11 @@ const getWstEthContract = async (
/**
* Load all required vaults contracts.
*/
const getVaultsContracts = async (config: ProtocolNetworkConfig) => {
const getVaultsContracts = async (config: ProtocolNetworkConfig, locator: LoadedContract<LidoLocator>) => {
return (await batch({
stakingVaultFactory: loadContract("VaultFactory", config.get("stakingVaultFactory")),
stakingVaultBeacon: loadContract("UpgradeableBeacon", config.get("stakingVaultBeacon")),
vaultHub: loadContract("VaultHub", config.get("vaultHub") || (await locator.vaultHub())),
})) as VaultsContracts;
};

Expand All @@ -177,7 +178,7 @@ export async function discover() {
...(await getStakingModules(foundationContracts.stakingRouter, networkConfig)),
...(await getHashConsensusContract(foundationContracts.accountingOracle, networkConfig)),
...(await getWstEthContract(foundationContracts.withdrawalQueue, networkConfig)),
...(await getVaultsContracts(networkConfig)),
...(await getVaultsContracts(networkConfig, locator)),
} as ProtocolContracts;

log.debug("Contracts discovered", {
Expand All @@ -204,6 +205,7 @@ export async function discover() {
// Vaults
"Staking Vault Factory": contracts.stakingVaultFactory.address,
"Staking Vault Beacon": contracts.stakingVaultBeacon.address,
"Vault Hub": contracts.vaultHub.address,
});

const signers = {
Expand Down
4 changes: 4 additions & 0 deletions lib/protocol/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
UpgradeableBeacon,
ValidatorsExitBusOracle,
VaultFactory,
VaultHub,
WithdrawalQueueERC721,
WithdrawalVault,
WstETH,
Expand Down Expand Up @@ -58,6 +59,7 @@ export type ProtocolNetworkItems = {
// vaults
stakingVaultFactory: string;
stakingVaultBeacon: string;
vaultHub: string;
};

export interface ContractTypes {
Expand All @@ -82,6 +84,7 @@ export interface ContractTypes {
WstETH: WstETH;
VaultFactory: VaultFactory;
UpgradeableBeacon: UpgradeableBeacon;
VaultHub: VaultHub;
}

export type ContractName = keyof ContractTypes;
Expand Down Expand Up @@ -133,6 +136,7 @@ export type WstETHContracts = {
export type VaultsContracts = {
stakingVaultFactory: LoadedContract<VaultFactory>;
stakingVaultBeacon: LoadedContract<UpgradeableBeacon>;
vaultHub: LoadedContract<VaultHub>;
};

export type ProtocolContracts = { locator: LoadedContract<LidoLocator> } & CoreContracts &
Expand Down
1 change: 1 addition & 0 deletions lib/state-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export enum Sk {
scratchDeployGasUsed = "scratchDeployGasUsed",
minFirstAllocationStrategy = "minFirstAllocationStrategy",
accounting = "accounting",
vaultHub = "vaultHub",
tokenRebaseNotifier = "tokenRebaseNotifier",
// Vaults
stakingVaultImpl = "stakingVaultImpl",
Expand Down
2 changes: 1 addition & 1 deletion scripts/defaults/testnet-defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
"epochsPerFrame": 12
}
},
"accounting": {
"vaultHub": {
"deployParameters": {
"connectedVaultsLimit": 500,
"relativeShareLimitBP": 1000
Expand Down
12 changes: 9 additions & 3 deletions scripts/scratch/steps/0090-deploy-non-aragon-contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export async function main() {
const treasuryAddress = state[Sk.appAgent].proxy.address;
const chainSpec = state[Sk.chainSpec];
const depositSecurityModuleParams = state[Sk.depositSecurityModule].deployParameters;
const accountingParams = state[Sk.accounting].deployParameters;
const vaultHubParams = state[Sk.vaultHub].deployParameters;
const burnerParams = state[Sk.burner].deployParameters;
const hashConsensusForAccountingParams = state[Sk.hashConsensusForAccountingOracle].deployParameters;
const hashConsensusForExitBusParams = state[Sk.hashConsensusForValidatorsExitBusOracle].deployParameters;
Expand Down Expand Up @@ -142,8 +142,13 @@ export async function main() {
const accounting = await deployBehindOssifiableProxy(Sk.accounting, "Accounting", proxyContractsOwner, deployer, [
locator.address,
lidoAddress,
accountingParams.connectedVaultsLimit,
accountingParams.relativeShareLimitBP,
]);

const vaultHub = await deployBehindOssifiableProxy(Sk.vaultHub, "VaultHub", proxyContractsOwner, deployer, [
lidoAddress,
accounting.address,
vaultHubParams.connectedVaultsLimit,
vaultHubParams.relativeShareLimitBP,
]);

// Deploy AccountingOracle
Expand Down Expand Up @@ -213,6 +218,7 @@ export async function main() {
oracleDaemonConfig.address,
accounting.address,
wstETH.address,
vaultHub.address,
];
await updateProxyImplementation(Sk.lidoLocator, "LidoLocator", locator.address, proxyContractsOwner, [locatorConfig]);
}
Loading
Loading