diff --git a/apps/bend/content/developers/deployed-contracts.md b/apps/bend/content/developers/deployed-contracts.md index 3b60c7f6..6a9c43ed 100644 --- a/apps/bend/content/developers/deployed-contracts.md +++ b/apps/bend/content/developers/deployed-contracts.md @@ -19,7 +19,11 @@ head: This is a list of addresses where contracts can be read from or written to. -> A full list of Contract ABIs can be found at https://github.com/berachain/doc-abis +> **Contract ABIs:** +> +> - **Mainnet ABIs:** [berachain/abis/tree/main/mainnet/contracts](https://github.com/berachain/abis/tree/main/mainnet/contracts) +> - **Testnet ABIs:** [berachain/abis/tree/main/bepolia/contracts](https://github.com/berachain/abis/tree/main/bepolia/contracts) +> - **Documentation ABIs:** [berachain/doc-abis](https://github.com/berachain/doc-abis) :::info Deployed contracts have received several audits from various parties. All audit reports are publicly available on [Github](https://github.com/berachain/security-audits). diff --git a/apps/bex/content/developers/index.md b/apps/bex/content/developers/index.md index 14a07e44..823332ed 100644 --- a/apps/bex/content/developers/index.md +++ b/apps/bex/content/developers/index.md @@ -30,7 +30,11 @@ For more information, see the [Balancer disclosure](https://forum.balancer.fi/t/ The following is a list of contract address in order to interact with Berachain BEX. -> A full list of Contract ABIs can be found at https://github.com/berachain/doc-abis +> **Contract ABIs:** +> +> - **Mainnet ABIs:** [berachain/abis/tree/main/mainnet/contracts](https://github.com/berachain/abis/tree/main/mainnet/contracts) +> - **Testnet ABIs:** [berachain/abis/tree/main/bepolia/contracts](https://github.com/berachain/abis/tree/main/bepolia/contracts) +> - **Documentation ABIs:** [berachain/doc-abis](https://github.com/berachain/doc-abis) ## Mainnet Contracts diff --git a/apps/core/.vitepress/sidebar.ts b/apps/core/.vitepress/sidebar.ts index b1dce36a..6d6bb527 100644 --- a/apps/core/.vitepress/sidebar.ts +++ b/apps/core/.vitepress/sidebar.ts @@ -269,6 +269,10 @@ const SIDEBAR = { { text: "WBERAStakerVault", link: "/developers/contracts/wbera-staker-vault" + }, + { + text: "Staking Pools >>", + link: "/nodes/staking-pools/contracts" } ] }, @@ -373,6 +377,50 @@ const SIDEBAR = { } ] }, + { + text: "Staking Pools", + items: [ + { text: "Overview", link: "/nodes/staking-pools/" }, + { text: "Installation Guide", link: "/nodes/staking-pools/installation" }, + { text: "Operator Guide", link: "/nodes/staking-pools/operators" }, + { text: "Delegation Guide", link: "/nodes/staking-pools/delegators" }, + { text: "Smart Contract Reference", link: "/nodes/staking-pools/contracts" }, + { + text: "Contracts", + collapsed: true, + items: [ + { + text: "StakingPoolContractsFactory", + link: "/nodes/staking-pools/contracts/StakingPoolContractsFactory.md" + }, + { + text: "StakingPool", + link: "/nodes/staking-pools/contracts/StakingPool.md" + }, + { + text: "SmartOperator", + link: "/nodes/staking-pools/contracts/SmartOperator.md" + }, + { + text: "IncentiveCollector", + link: "/nodes/staking-pools/contracts/IncentiveCollector.md" + }, + { + text: "StakingRewardsVault", + link: "/nodes/staking-pools/contracts/StakingRewardsVault.md" + }, + { + text: "WithdrawalVault", + link: "/nodes/staking-pools/contracts/WithdrawalVault.md" + }, + { + text: "DelegationHandler", + link: "/nodes/staking-pools/contracts/DelegationHandler.md" + } + ] + } + ] + }, { text: "Help", items: [{ text: "Validator Support FAQ", link: "/nodes/faq" }] diff --git a/apps/core/content/developers/deployed-contracts.md b/apps/core/content/developers/deployed-contracts.md index ae26acbc..69b0f94e 100644 --- a/apps/core/content/developers/deployed-contracts.md +++ b/apps/core/content/developers/deployed-contracts.md @@ -19,7 +19,11 @@ head: This is a list of addresses where contracts can be read from or written to. -> A full list of Contract ABIs can be found at https://github.com/berachain/doc-abis +> **Contract ABIs:** +> +> - **Mainnet ABIs:** [berachain/abis/tree/main/mainnet/contracts](https://github.com/berachain/abis/tree/main/mainnet/contracts) +> - **Testnet ABIs:** [berachain/abis/tree/main/bepolia/contracts](https://github.com/berachain/abis/tree/main/bepolia/contracts) +> - **Documentation ABIs:** [berachain/doc-abis](https://github.com/berachain/doc-abis) :::info Deployed contracts have received several audits from various parties. @@ -31,6 +35,7 @@ All audit reports are publicly available on [Github](https://github.com/berachai + +# Staking Pools Smart Contract Reference + +This reference provides contract addresses and links to detailed documentation for each contract in the staking pools system. For an overview of how the contracts work together, see the [Staking Pools Overview](/nodes/staking-pools/). + +## Contract Addresses + +### Singleton Contracts + +Singleton contracts are deployed once and shared across all staking pools. The **StakingPoolContractsFactory** is the entry point for deploying a staking pool: you call it to create and register your pool's contracts. The other singletons below are shared infrastructure. These are the contracts you interact with directly to deploy and manage your staking pool. + +#### Mainnet + +| Name | Address | ABI | +| ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **{{config.contracts.stakingPools.stakingPoolContractsFactory.name}}** | {{config.contracts.stakingPools.stakingPoolContractsFactory.address.berachainMainnet}}Not yet deployed | ABI JSONN/A | +| **{{config.contracts.stakingPools.withdrawalVault.name}}** | {{config.contracts.stakingPools.withdrawalVault.address.berachainMainnet}}Not yet deployed | ABI JSONN/A | + +#### Bepolia + +| Name | Address | ABI | +| ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **{{config.contracts.stakingPools.stakingPoolContractsFactory.name}}** | {{config.contracts.stakingPools.stakingPoolContractsFactory.address.berachainBepolia}}Not yet deployed | ABI JSONN/A | +| **{{config.contracts.stakingPools.withdrawalVault.name}}** | {{config.contracts.stakingPools.withdrawalVault.address.berachainBepolia}}Not yet deployed | ABI JSONN/A | + +### Deployed Contracts + +When you deploy a staking pool through the StakingPoolContractsFactory, the factory creates proxy contracts for your validator. These proxy contracts are what you and your stakers interact with directly. Each validator receives unique proxy addresses for these contracts when deploying their staking pool. + +The factory returns these proxy addresses when you call `deployStakingPoolContracts`. Store these addresses for your operations and provide the StakingPool address to your stakers for deposits. + +**Proxy Contracts Deployed with Your Pool:** + +- **[StakingPool](contracts/StakingPool)**: Main staking functionality and staker interactions. This is the contract address your stakers use to deposit BERA and receive stBERA shares. +- **[SmartOperator](contracts/SmartOperator)**: Validator operations and Proof of Liquidity integration. Use this contract to manage BGT operations, commission rates, reward allocations, and protocol fees. +- **[IncentiveCollector](contracts/IncentiveCollector)**: Incentive token collection and conversion. Handles the incentive auction mechanism where accumulated tokens can be claimed. +- **[StakingRewardsVault](contracts/StakingRewardsVault)**: Reward collection and automatic reinvestment. Automatically compounds rewards from the consensus layer. +- **[DelegationHandler](contracts/DelegationHandler)**: Delegation handling for capital providers. Only deployed if you're using delegated funds from the Berachain Foundation. diff --git a/apps/core/content/nodes/staking-pools/contracts/DelegationHandler.md b/apps/core/content/nodes/staking-pools/contracts/DelegationHandler.md new file mode 100644 index 00000000..6d2f52e9 --- /dev/null +++ b/apps/core/content/nodes/staking-pools/contracts/DelegationHandler.md @@ -0,0 +1,188 @@ +--- +head: + - - meta + - property: og:title + content: DelegationHandler Contract Reference + - - meta + - name: description + content: Developer reference for the DelegationHandler contract used in staking pools + - - meta + - property: og:description + content: Developer reference for the DelegationHandler contract used in staking pools +--- + + + +# DelegationHandler + +The DelegationHandler contract manages delegated funds for validators, allowing third parties to provide capital for staking pool operations while maintaining clear separation between fund providers and validator operators. + +## State Variables + +### FIRST_DEPOSIT_AMOUNT + +```solidity +uint256 public constant FIRST_DEPOSIT_AMOUNT = 10_000 ether; +``` + +### stakingPool + +```solidity +address public stakingPool; +``` + +### pubkey + +```solidity +bytes public pubkey; +``` + +### delegatedAmount + +```solidity +uint256 public delegatedAmount; +``` + +### delegatedAmountAvailable + +```solidity +uint256 public delegatedAmountAvailable; +``` + +### isYieldRedeemRequest + +```solidity +mapping(uint256 => bool) public isYieldRedeemRequest; +``` + +Tracks which withdrawal requests are for yield redemption (validator admin) versus principal withdrawal (delegator). When a validator admin calls `requestYieldWithdrawal()`, the resulting request ID is marked as a yield redeem request in this mapping. This allows the `completeWithdrawal()` function to determine the correct access control and fund handling based on the request type. + +## Functions + +### createStakingPoolWithDelegatedFunds + +Creates a new staking pool with delegated funds. + +**Required Role**: `VALIDATOR_ADMIN_ROLE` + +```solidity +function createStakingPoolWithDelegatedFunds( + bytes memory _pubkey, + bytes memory withdrawalCredentials, + bytes memory signature +) + external + onlyRole(VALIDATOR_ADMIN_ROLE); +``` + +**Parameters** + +| Name | Type | Description | +| ----------------------- | ------- | -------------------------------------------- | +| `_pubkey` | `bytes` | The validator's public key. | +| `withdrawalCredentials` | `bytes` | The withdrawal credentials of the validator. | +| `signature` | `bytes` | The signature of the validator. | + +### depositDelegatedFunds + +Deposits delegated funds into a staking pool. + +**Required Role**: `VALIDATOR_ADMIN_ROLE` + +```solidity +function depositDelegatedFunds(uint256 amount) external onlyRole(VALIDATOR_ADMIN_ROLE); +``` + +**Parameters** + +| Name | Type | Description | +| -------- | --------- | ------------------------------- | +| `amount` | `uint256` | The amount of funds to deposit. | + +### requestYieldWithdrawal + +Requests a yield withdrawal for delegated funds. + +**Required Role**: `VALIDATOR_ADMIN_ROLE` + +```solidity +function requestYieldWithdrawal() external payable onlyRole(VALIDATOR_ADMIN_ROLE) whenNotPaused; +``` + +### completeWithdrawal + +Completes a withdrawal request. Access control varies by request type: VALIDATOR_ADMIN_ROLE for yield withdrawals, DEFAULT_ADMIN_ROLE for principal withdrawals. + +**Required Role**: `VALIDATOR_ADMIN_ROLE` (for yield withdrawals) or `DEFAULT_ADMIN_ROLE` (for principal withdrawals) + +```solidity +function completeWithdrawal(uint256 requestId) external whenNotPaused auth(requestId); +``` + +**Parameters** + +| Name | Type | Description | +| ----------- | --------- | ------------------------------------------------ | +| `requestId` | `uint256` | The unique identifier of the withdrawal request. | + +## Events + +### DelegatedStakingPoolCreated + +```solidity +event DelegatedStakingPoolCreated(address stakingPool); +``` + +Emitted when a validator admin successfully creates a staking pool using delegated funds via `createStakingPoolWithDelegatedFunds()`. The event includes the address of the newly created staking pool. + +### DelegatedFundsDeposited + +```solidity +event DelegatedFundsDeposited(uint256 amount); +``` + +Emitted when a validator admin deposits additional delegated funds into the staking pool via `depositDelegatedFunds()`. The event includes the amount of funds deposited. + +### YieldRedeemRequested + +```solidity +event YieldRedeemRequested(uint256 requestId, uint256 redeemableShares); +``` + +Emitted when a validator admin requests a yield withdrawal via `requestYieldWithdrawal()`. The event includes the withdrawal request ID and the amount of shares that can be redeemed for yield. + +### WithdrawalCompleted + +```solidity +event WithdrawalCompleted(uint256 requestId, uint256 assetsRequested, address receiver); +``` + +Emitted when any withdrawal request is completed via `completeWithdrawal()`. The event includes the request ID, the amount of assets withdrawn, and the address that received the funds (either the validator admin for yield withdrawals or the delegation handler for principal withdrawals). + +## Errors + +### InvalidAmount + +```solidity +error InvalidAmount(); +``` + +Thrown by `delegate()`, `withdraw()`, `depositDelegatedFunds()`, `requestDelegatedFundsWithdrawal()`, and `requestYieldWithdrawal()` when the amount is zero or exceeds available balance. + +### InvalidPubkey + +```solidity +error InvalidPubkey(); +``` + +Thrown by `createStakingPoolWithDelegatedFunds()` when the provided pubkey doesn't match the validator's pubkey. + +### InvalidDelegatedAmount + +```solidity +error InvalidDelegatedAmount(); +``` + +Thrown by `createStakingPoolWithDelegatedFunds()` when there are insufficient delegated funds available for the initial deposit. diff --git a/apps/core/content/nodes/staking-pools/contracts/DelegationHandlerFactory.md b/apps/core/content/nodes/staking-pools/contracts/DelegationHandlerFactory.md new file mode 100644 index 00000000..b948aca1 --- /dev/null +++ b/apps/core/content/nodes/staking-pools/contracts/DelegationHandlerFactory.md @@ -0,0 +1,70 @@ +--- +head: + - - meta + - property: og:title + content: DelegationHandlerFactory Contract Reference + - - meta + - name: description + content: Developer reference for the DelegationHandlerFactory contract + - - meta + - property: og:description + content: Developer reference for the DelegationHandlerFactory contract +--- + + + +# DelegationHandlerFactory + +> {{config.contracts.stakingPools.delegationHandlerFactory.address.berachainMainnet}}
Bepolia: {{config.contracts.stakingPools.delegationHandlerFactory.address.berachainBepolia}}
 | ABI JSON
+ +The DelegationHandlerFactory contract deploys and manages DelegationHandler instances for validators using an upgradeable beacon proxy pattern. + +## State Variables + +### delegationHandlers + +```solidity +mapping(bytes => address) public delegationHandlers; +``` + +Maps validator pubkeys to their corresponding delegation handler addresses. Validators can use this to check if a delegation handler has been deployed for their pubkey. + +## Functions + +### deployDelegationHandler + +Deploys a delegation handler for a validator. + +```solidity +function deployDelegationHandler(bytes memory pubkey) external returns (address); +``` + +**Parameters** + +| Name | Type | Description | +| -------- | ------- | ---------------------------- | +| `pubkey` | `bytes` | The pubkey of the validator. | + +**Returns** + +| Name | Type | Description | +| -------- | --------- | -------------------------------------------------------- | +| `` | `address` | delegationHandler The address of the delegation handler. | + +## Events + +### DelegationHandlerDeployed + +```solidity +event DelegationHandlerDeployed(bytes indexed pubkey, address indexed delegationHandler); +``` + +## Errors + +### InvalidAddress + +```solidity +error InvalidAddress(); +``` diff --git a/apps/core/content/nodes/staking-pools/contracts/IncentiveCollector.md b/apps/core/content/nodes/staking-pools/contracts/IncentiveCollector.md new file mode 100644 index 00000000..94d1b3c5 --- /dev/null +++ b/apps/core/content/nodes/staking-pools/contracts/IncentiveCollector.md @@ -0,0 +1,246 @@ +--- +head: + - - meta + - property: og:title + content: IncentiveCollector Contract Reference + - - meta + - name: description + content: Developer reference for the IncentiveCollector contract + - - meta + - property: og:description + content: Developer reference for the IncentiveCollector contract +--- + + + +# IncentiveCollector + +The IncentiveCollector contract manages incentive token collection for validators. It allows anyone (operator, arbitrageur, or other addresses) to claim accumulated incentive tokens by paying a required payout amount, with fees going to the smart operator and the remaining amount to the staking rewards vault. + +## State Variables + +### payoutAmount + +```solidity +uint256 public payoutAmount; +``` + +### queuedPayoutAmount + +```solidity +uint256 public queuedPayoutAmount; +``` + +### feePercentage + +```solidity +uint96 public feePercentage; +``` + +## Functions + +### queuePayoutAmountChange + +Queues a payout amount change. The change takes effect on the next `claim()` call. This function can only be called by the SmartOperator contract, which requires the caller to have the `INCENTIVE_COLLECTOR_MANAGER_ROLE` on SmartOperator. + +```solidity +function queuePayoutAmountChange(uint256 newPayoutAmount) external; +``` + +**Parameters** + +| Name | Type | Description | +| ----------------- | --------- | --------------------------------------- | +| `newPayoutAmount` | `uint256` | The new payout amount in native tokens. | + +**Requirements** + +- Must be called by the SmartOperator contract +- New payout amount must not be zero + +**How to Update:** + +To update the payout amount, call `queueIncentiveCollectorPayoutAmountChange()` on your SmartOperator contract (requires `INCENTIVE_COLLECTOR_MANAGER_ROLE`). The change is queued and will take effect automatically on the next `claim()` call. + +### claim + +Claims all accumulated incentive tokens by paying the required payout amount. This function implements a permissionless incentive auction mechanism where **anyone** can pay the `payoutAmount` (initially 100 BERA) to claim **all** accumulated tokens for the specified token addresses. The buyer may be the validator operator, an arbitrageur monitoring the network, or any other address that finds the pool profitable. + +**Important**: This is a winner-takes-all incentive auction. The first buyer to pay the payout amount receives all accumulated tokens. There is no partial claiming or proportional distribution. The incentive auction is completely permissionless—anyone can call this function. + +**What Happens:** + +- The buyer receives all balances of the specified ERC20 tokens held by the contract +- Protocol fee (based on `feePercentage`) is deducted from the payout amount and minted as shares to the validator's `defaultShareRecipient` +- Net payout amount (payoutAmount - fee) is sent to StakingRewardsVault for distribution to shareholders and auto-compounded +- Any queued payout amount or fee percentage changes are applied + +**Checking Current Payout Available:** + +To determine the current value of tokens available for claim, check the balance of each token in the IncentiveCollector contract: + +```solidity +// For each token address you want to check +uint256 balance = IERC20(tokenAddress).balanceOf(address(incentiveCollector)); +``` + +The buyer should verify that the total value of all token balances exceeds the `payoutAmount` before calling `claim()`. Validators should develop a scheme to sample payouts by monitoring token balances in their IncentiveCollector contract to ensure the `payoutAmount` remains appropriate relative to accumulated token values. + +```solidity +function claim(address[] calldata tokens) external payable; +``` + +**Parameters** + +| Name | Type | Description | +| -------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `tokens` | `address[]` | Array of ERC20 token addresses to claim balances from. All balances of these tokens held by the contract will be transferred to the buyer. | + +**Requirements** + +- Must send exactly `payoutAmount` in native tokens (msg.value must equal `payoutAmount`) +- Validator must not be fully exited +- This function does NOT implement slippage protection - the buyer must verify that the value of tokens received exceeds the payout cost + +**Token Sources** + +Tokens accumulate in IncentiveCollector when you forward them from SmartOperator using `claimBoostRewards()`. Tokens may also accumulate from direct transfers if anyone sends tokens directly to the contract. + +**Example Flow:** + +1. Your validator earns commission on incentive tokens (automatically sent to SmartOperator) +2. You call `claimBoostRewards()` to forward tokens to IncentiveCollector +3. Tokens accumulate in IncentiveCollector +4. A buyer (operator, arbitrageur, or any address) checks token balances and determines the value exceeds the `payoutAmount` +5. The buyer calls `claim()` with the payout amount (default 100 BERA) to receive all accumulated tokens +6. The payout amount (minus protocol fee) goes to StakingRewardsVault and is distributed to stakers + +## Events + +### Donated + +```solidity +event Donated(address indexed donor, uint256 amount); +``` + +Emitted when native tokens are sent directly to the contract via the `receive()` function. The tokens are automatically forwarded to the staking rewards vault. + +**Parameters** + +| Name | Type | Description | +| -------- | --------- | ------------------------- | +| `donor` | `address` | The address of the donor. | +| `amount` | `uint256` | The amount donated. | + +### PayoutAmountSet + +Emitted when payout amount is set by admin. + +```solidity +event PayoutAmountSet(uint256 oldValue, uint256 newValue); +``` + +**Parameters** + +| Name | Type | Description | +| ---------- | --------- | ------------------------------------- | +| `oldValue` | `uint256` | The previous value for payout amount. | +| `newValue` | `uint256` | The new value for payout amount. | + +### QueuedPayoutAmount + +Emitted when payout amount is queued by admin. + +```solidity +event QueuedPayoutAmount(uint256 newPayoutAmount, uint256 oldPayoutAmount); +``` + +**Parameters** + +| Name | Type | Description | +| ----------------- | --------- | ------------------------------------- | +| `newPayoutAmount` | `uint256` | The new value for payout amount. | +| `oldPayoutAmount` | `uint256` | The previous value for payout amount. | + +### FeePercentageSet + +Emitted when fee percentage is set by admin. + +```solidity +event FeePercentageSet(uint96 oldValue, uint96 newValue); +``` + +**Parameters** + +| Name | Type | Description | +| ---------- | -------- | -------------------------------------- | +| `oldValue` | `uint96` | The previous value for fee percentage. | +| `newValue` | `uint96` | The new value for fee percentage. | + +### IncentiveTokenClaimed + +Emitted when one token is claimed. + +```solidity +event IncentiveTokenClaimed(address indexed from, address token, uint256 amount); +``` + +**Parameters** + +| Name | Type | Description | +| -------- | --------- | ---------------------------- | +| `from` | `address` | The address of the claimer. | +| `token` | `address` | The address of the token. | +| `amount` | `uint256` | The amount of token claimed. | + +### IncentiveTokensClaimed + +Emitted when all incentive tokens are claimed. + +```solidity +event IncentiveTokensClaimed(address indexed from, uint256 payoutAmount, uint256 fee); +``` + +**Parameters** + +| Name | Type | Description | +| -------------- | --------- | ----------------------------------------------------------------- | +| `from` | `address` | The address of the claimer. | +| `payoutAmount` | `uint256` | The amount paid to claim. | +| `fee` | `uint256` | The fee paid to the validator on the payoutAmount for this claim. | + +## Errors + +### InvalidSender + +```solidity +error InvalidSender(address sender, address expected); +``` + +Thrown when a function is called by an unauthorized address. + +### IncorrectPayoutAmount + +```solidity +error IncorrectPayoutAmount(); +``` + +Thrown by `claim()` when the msg.value does not match the required payout amount. + +### NodeIsFullyExited + +```solidity +error NodeIsFullyExited(); +``` + +Thrown by functions when the validator node has been fully exited. + +### PayoutAmountIsZero + +```solidity +error PayoutAmountIsZero(); +``` + +Thrown by `claim()` when the payout amount is zero. diff --git a/apps/core/content/nodes/staking-pools/contracts/SmartOperator.md b/apps/core/content/nodes/staking-pools/contracts/SmartOperator.md new file mode 100644 index 00000000..cdcf560d --- /dev/null +++ b/apps/core/content/nodes/staking-pools/contracts/SmartOperator.md @@ -0,0 +1,380 @@ +--- +head: + - - meta + - property: og:title + content: SmartOperator Contract Reference + - - meta + - name: description + content: Developer reference for the SmartOperator contract + - - meta + - property: og:description + content: Developer reference for the SmartOperator contract +--- + + + +# SmartOperator + +The SmartOperator contract manages validator operations including BGT boosting, fee management, and reward allocation. It serves as the central controller for validator-related activities within the staking pool ecosystem. + +## State Variables + +### protocolFeePercentage + +```solidity +uint96 public protocolFeePercentage; +``` + +## View Functions + +### rebaseableBgtAmount + +Returns BGT balance of the smart operator minus the protocol fee. This function is used to calculate the rebased value of staking pool's shares. + +```solidity +function rebaseableBgtAmount() external view returns (uint256); +``` + +**Returns** + +| Name | Type | Description | +| -------- | --------- | ----------------------------------------- | +| `` | `uint256` | The amount of BGT available for rebasing. | + +### unboostedBalance + +Get the unboosted BGT balance of the smart operator. + +```solidity +function unboostedBalance() external view returns (uint256); +``` + +**Returns** + +| Name | Type | Description | +| -------- | --------- | ------------------------------------------------ | +| `` | `uint256` | The unboosted BGT balance of the smart operator. | + +### getEarnedBGTFeeState + +Returns the current base rate fee state information. Useful for monitoring and debugging fee calculations. + +```solidity +function getEarnedBGTFeeState() + external + view + returns (uint256 currentBalance, uint256 bgtBalanceAlreadyCharged, uint256 chargeableBalance, uint96); +``` + +**Returns** + +| Name | Type | Description | +| -------------------------- | --------- | --------------------------------------------------------- | +| `currentBalance` | `uint256` | The current BGT balance of the smart operator | +| `bgtBalanceAlreadyCharged` | `uint256` | The amount of BGT that has already been charged fees | +| `chargeableBalance` | `uint256` | The amount of BGT that can still be charged fees | +| `` | `uint96` | protocolFeePercentage The current protocol fee percentage | + +## Functions + +### setRewardAllocator + +Sets the reward allocator for the validator key on the BeraChef contract. + +**Required Role**: `VALIDATOR_ADMIN_ROLE` + +```solidity +function setRewardAllocator(address rewardAllocator) external; +``` + +**Parameters** + +| Name | Type | Description | +| ----------------- | --------- | ------------------------------------ | +| `rewardAllocator` | `address` | The address of the reward allocator. | + +### queueBoost + +This function is used to auto-boost the validator key for the current unboosted amount of BGT. Only enqueues boost if not already queued to avoid spamming the queue. + +```solidity +function queueBoost() external whenNotFullyExited returns (bool); +``` + +**Returns** + +| Name | Type | Description | +| -------- | ------ | ------------------------------------------ | +| `` | `bool` | True if the boost was successfully queued. | + +### activateBoost + +Simple relayer to BGT.activateBoost() function for this operator. + +```solidity +function activateBoost() external; +``` + +### queueDropBoost + +Enqueues a drop boost request for a given amount. + +**Required Role**: `BGT_MANAGER_ROLE` + +```solidity +function queueDropBoost(uint128 amount) external; +``` + +**Parameters** + +| Name | Type | Description | +| -------- | --------- | ----------------------------- | +| `amount` | `uint128` | The amount of BGT to unboost. | + +### dropBoost + +Executes the drop boost for this operator. + +```solidity +function dropBoost() external; +``` + +### redeemBGT + +Redeems a given amount of BGT for BERA. The protocol fee is deducted from the amount. + +**Required Role**: `BGT_MANAGER_ROLE` + +```solidity +function redeemBGT(uint256 amount) external whenNotFullyExited; +``` + +**Parameters** + +| Name | Type | Description | +| -------- | --------- | ---------------------------- | +| `amount` | `uint256` | The amount of BGT to redeem. | + +### fullExitRedeemBGT + +Redeems all BGT tokens owned by this contract for BERA. The protocol fee is deducted from the amount. + +```solidity +function fullExitRedeemBGT(address receiver) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---------- | --------- | -------------------------------- | +| `receiver` | `address` | The address to send the BERA to. | + +### queueRewardsAllocation + +Queues a new rewards allocation for the validator key. + +**Required Role**: `REWARDS_ALLOCATION_MANAGER_ROLE` + +```solidity +function queueRewardsAllocation( + uint64 startBlock, + IBeraChef.Weight[] calldata weights +) + external + ; +``` + +**Parameters** + +| Name | Type | Description | +| ------------ | -------------------- | -------------------------------------------------------- | +| `startBlock` | `uint64` | The block number when the new rewards allocation starts. | +| `weights` | `IBeraChef.Weight[]` | The weights for the new rewards allocation. | + +### queueValCommission + +Queues a new validator commission for the validator key. + +**Required Role**: `COMMISSION_MANAGER_ROLE` + +```solidity +function queueValCommission(uint96 commission) external; +``` + +**Parameters** + +| Name | Type | Description | +| ------------ | -------- | --------------------------------------------- | +| `commission` | `uint96` | The new validator commission in basis points. | + +### setMinEffectiveBalance + +Sets the minimum effective balance for the staking pool. + +**Required Role**: `VALIDATOR_ADMIN_ROLE` + +```solidity +function setMinEffectiveBalance(uint256 newMinEffectiveBalance) external; +``` + +**Parameters** + +| Name | Type | Description | +| ------------------------ | --------- | ---------------------------------------------------------- | +| `newMinEffectiveBalance` | `uint256` | The minimum effective balance to set for the staking pool. | + +### queueIncentiveCollectorPayoutAmountChange + +Queues a payout amount change for the incentive collector. + +**Required Role**: `INCENTIVE_COLLECTOR_MANAGER_ROLE` + +```solidity +function queueIncentiveCollectorPayoutAmountChange(uint256 newPayoutAmount) + external + ; +``` + +**Parameters** + +| Name | Type | Description | +| ----------------- | --------- | -------------------------------------------------- | +| `newPayoutAmount` | `uint256` | The new payout amount for the incentive collector. | + +### setProtocolFeePercentage + +Sets the protocol fee percentage for the smart operator. + +**Required Role**: `PROTOCOL_FEE_MANAGER_ROLE` + +```solidity +function setProtocolFeePercentage(uint96 protocolFeePercentage_) + external + whenNotFullyExited + ; +``` + +**Parameters** + +| Name | Type | Description | +| ------------------------ | -------- | ---------------------------------------------------------- | +| `protocolFeePercentage_` | `uint96` | The protocol fee percentage to set for the smart operator. | + +### accrueEarnedBGTFees + +Accrues the base rate fees. Fees are accrued only on balance not yet charged. + +```solidity +function accrueEarnedBGTFees() external whenNotFullyExited; +``` + +### claimBgtStakerReward + +Claims accumulated HONEY token rewards from BGT staking and forwards them to the IncentiveCollector. HONEY rewards accumulate from protocol fees distributed to BGT stakers. Once forwarded to IncentiveCollector, these tokens become available for anyone to claim via the incentive auction mechanism. + +**Note:** This function does not need to be called manually. It is called internally by `claimBoostRewards()`, which handles both HONEY rewards and incentive tokens. Use `claimBoostRewards()` instead. See [claimBoostRewards](#claimboostrewards) for details. + +```solidity +function claimBgtStakerReward() external returns (uint256); +``` + +**Returns** + +| Name | Type | Description | +| -------- | --------- | --------------------------------------------- | +| `` | `uint256` | The amount of HONEY rewards claimed and sent. | + +### claimBoostRewards + +Claims both HONEY rewards from BGT staking and incentive tokens from the boost program, then forwards all accumulated tokens to the IncentiveCollector. Once tokens are in IncentiveCollector, they become available for anyone to claim via the permissionless auction mechanism by paying the required payout amount. + +This function internally calls `claimBgtStakerReward()` to handle HONEY rewards, so calling this function alone is sufficient to forward both HONEY rewards and incentive tokens. + +```solidity +function claimBoostRewards(IBGTIncentiveDistributor.Claim[] calldata claims, address[] memory tokens) external; +``` + +**Parameters** + +| Name | Type | Description | +| -------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| `claims` | `IBGTIncentiveDistributor.Claim[]` | Array of merkle claims for incentive program rewards. Each claim includes an identifier, account, amount, and merkle proof for verification. | +| `tokens` | `address[]` | Array of token addresses to forward to IncentiveCollector. All balances of these tokens held by SmartOperator will be transferred. | + +**What Happens** + +- Claims any accumulated HONEY rewards from BGT staking +- Claims incentive tokens from the boost program using the provided merkle claims +- Transfers all balances of the specified tokens (including any HONEY claimed) from SmartOperator to IncentiveCollector +- Tokens become available for anyone to claim via IncentiveCollector's incentive auction mechanism + +**Token Sources** + +Tokens accumulate in SmartOperator from: + +- **Commission share**: Your validator's commission share of incentive tokens (sent automatically when RewardVault processes incentives) +- **BGT staking rewards**: HONEY tokens from protocol fee distribution + +**Important**: Tokens must be manually forwarded to IncentiveCollector using this function before they can be claimed. They do not automatically flow to IncentiveCollector. + +## Events + +### ProtocolFeePercentageSet + +```solidity +event ProtocolFeePercentageSet(uint96 protocolFeePercentage); +``` + +Emitted when the protocol fee percentage is updated via `setProtocolFeePercentage()`. + +### BGTRedeemed + +```solidity +event BGTRedeemed(address receiver, uint256 amount); +``` + +Emitted when BGT tokens are redeemed via `redeemBGT()` or `redeemBGTTo()`. + +### RewardAllocatorSet + +```solidity +event RewardAllocatorSet(address rewardAllocator); +``` + +Emitted when the reward allocator is set via `setRewardAllocator()`. + +## Errors + +### InvalidSender + +```solidity +error InvalidSender(address sender, address expected); +``` + +Thrown by `_validateSender()` when the sender is not the expected address. + +### InvalidProtocolFeePercentage + +```solidity +error InvalidProtocolFeePercentage(); +``` + +Thrown by `setProtocolFeePercentage()` when the protocol fee percentage is invalid. + +### StakingPoolIsFullyExited + +```solidity +error StakingPoolIsFullyExited(); +``` + +Thrown by various functions when the staking pool has been fully exited. + +### ZeroAddress + +```solidity +error ZeroAddress(); +``` + +Thrown by functions when a zero address is provided where a valid address is required. diff --git a/apps/core/content/nodes/staking-pools/contracts/StakingPool.md b/apps/core/content/nodes/staking-pools/contracts/StakingPool.md new file mode 100644 index 00000000..f00250fb --- /dev/null +++ b/apps/core/content/nodes/staking-pools/contracts/StakingPool.md @@ -0,0 +1,352 @@ +--- +head: + - - meta + - property: og:title + content: StakingPool Contract Reference + - - meta + - name: description + content: Developer reference for the StakingPool contract + - - meta + - property: og:description + content: Developer reference for the StakingPool contract +--- + + + +# StakingPool + +The StakingPool contract is the main liquid staking contract that allows stakers to deposit BERA and receive StBERA shares. It manages your validator's stake, processes deposits and withdrawals, and handles the transition between inactive and active validator states. + +## State Variables + +### isActive + +```solidity +bool public isActive; +``` + +### isFullyExited + +```solidity +bool public isFullyExited; +``` + +### bufferedAssets + +```solidity +uint256 public bufferedAssets; +``` + +### totalDeposits + +```solidity +uint256 public totalDeposits; +``` + +### activeThresholdReached + +```solidity +bool public activeThresholdReached; +``` + +## View Functions + +### getValidatorPubkey + +```solidity +function getValidatorPubkey() external view returns (bytes memory); +``` + +### minEffectiveBalance + +Returns the minimum effective balance for the staking pool. If not set, returns the default minimum effective balance. + +```solidity +function minEffectiveBalance() public view returns (uint256); +``` + +**Returns** + +| Name | Type | Description | +| -------- | --------- | --------------------------------------------------- | +| `` | `uint256` | The minimum effective balance for the staking pool. | + +## Functions + +### activate + +Activates the staking pool with an initial deposit. Can only be called once by the factory contract while the pool is paused. + +```solidity +function activate(uint256 initialDepositAmount) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---------------------- | --------- | ----------------------------------- | +| `initialDepositAmount` | `uint256` | Initial amount of assets to deposit | + +**Errors** + +- `StakingPoolAlreadyActivated` — Pool already activated or has deposits +- `InvalidSender` — Not called by factory contract + +### submit + +Submit BERA to the staking pool, mints StBERA shares. This function handles the deposit logic including: compute effective deposit amounts and any refunds needed, mint stBERA shares to the depositor, collect rewards from the staking rewards vault if needed, deposit to CL if minimum threshold is met, pause deposits if maximum capacity is reached. + +```solidity +function submit(address receiver) external payable returns (uint256 shares); +``` + +**Parameters** + +| Name | Type | Description | +| ---------- | --------- | ---------------------------------- | +| `receiver` | `address` | The address to mint the shares to. | + +**Returns** + +| Name | Type | Description | +| -------- | --------- | ---------------------------------------------------- | +| `shares` | `uint256` | The amount of stBERA shares minted to the depositor. | + +### processRewards + +Processes rewards from the staking rewards vault. Compound rewards from the staking rewards vault to the staking pool. + +```solidity +function processRewards() external; +``` + +### receiveRewards + +Receives execution layer rewards. Can only be called by the StakingRewardsVault. + +```solidity +function receiveRewards() external payable; +``` + +### notifyWithdrawalRequest + +Processes a withdrawal request by burning user shares. Called by the withdrawal request ERC721 contract. + +```solidity +function notifyWithdrawalRequest( + address user, + uint256 assets +) external returns (uint256 sharesToBurn, bool isFullyExited, bool isShortCircuit); +``` + +**Parameters** + +| Name | Type | Description | +| -------- | --------- | ----------------------------- | +| `user` | `address` | Address requesting withdrawal | +| `assets` | `uint256` | Amount of BERA to withdraw | + +**Returns** + +| Name | Type | Description | +| ---------------- | --------- | ----------------------------------------------------------- | +| `sharesToBurn` | `uint256` | Amount of shares burned | +| `isFullyExited` | `bool` | Whether the pool has fully exited | +| `isShortCircuit` | `bool` | Whether withdrawal was short-circuited (immediate transfer) | + +**Behavior** + +- Short-circuit: If validator not activated and sufficient buffered assets exist, funds transfer immediately +- After activation: Withdrawals require consensus layer exit process with cooldown + +### setMinEffectiveBalance + +Sets the minimum effective balance for the staking pool. Can only be called by the smart operator. + +```solidity +function setMinEffectiveBalance(uint256 minEffectiveBalance) external; +``` + +**Parameters** + +| Name | Type | Description | +| --------------------- | --------- | ------------------------------------ | +| `minEffectiveBalance` | `uint256` | The minimum effective balance to set | + +**Errors** + +- `InvalidSender` — Not called by smart operator +- `InvalidMinEffectiveBalance` — New min effective balance is not valid + +### updateTotalDeposits + +Updates the total deposits of the staking pool. Meant to be called by the accounting oracle to align total deposits with CL data. + +```solidity +function updateTotalDeposits(uint256 newTotalDeposits) external; +``` + +**Parameters** + +| Name | Type | Description | +| ------------------ | --------- | ---------------------- | +| `newTotalDeposits` | `uint256` | The new total deposits | + +### mintFeeShares + +Mints shares to the default shares recipient. Can only be called by the smart operator. Used to mint shares to the validator instead of accruing fees. + +```solidity +function mintFeeShares(uint256 amount) external; +``` + +**Parameters** + +| Name | Type | Description | +| -------- | --------- | ------------------------ | +| `amount` | `uint256` | Amount of assets to mint | + +### pause + +Pauses deposits. Admin function. + +```solidity +function pause() external; +``` + +### unpause + +Unpauses deposits. Admin function. Can only be called if the pool is not fully exited. + +```solidity +function unpause() external; +``` + +**Errors** + +- `StakingPoolFullExited` — Pool has fully exited + +## Events + +### StakingPoolActivated + +```solidity +event StakingPoolActivated(); +``` + +Emitted when the pool is activated via `activate()`. + +### StakingRewardsReceived + +```solidity +event StakingRewardsReceived(uint256 amount); +``` + +Emitted when execution layer rewards are received via `receiveRewards()`. + +### MaxCapacityReached + +```solidity +event MaxCapacityReached(bytes pubkey); +``` + +Emitted when the staking pool reaches maximum capacity during `_processDeposit()`. + +### FullExitTriggered + +```solidity +event FullExitTriggered(); +``` + +Emitted when a full exit is triggered via `triggerFullExit()`. + +### TotalDepositsUpdated + +```solidity +event TotalDepositsUpdated(uint256 newTotalDeposits); +``` + +Emitted when total deposits are updated during `_processDeposit()` or via `updateTotalDeposits()`. + +### ActiveThresholdReached + +```solidity +event ActiveThresholdReached(); +``` + +Emitted when the active threshold is reached during `_processDeposit()`. + +### MinEffectiveBalanceUpdated + +```solidity +event MinEffectiveBalanceUpdated(uint256 newMinEffectiveBalance); +``` + +Emitted when the minimum effective balance is updated via `setMinEffectiveBalance()`. + +### DepositSubmitted + +```solidity +event DepositSubmitted( + address indexed receiver, + uint256 userDepositAmount, + uint256 shares, + uint256 rewardsCollected, + uint256 bufferedAssets, + uint256 totalDeposits +); +``` + +Emitted when a deposit is submitted via `submit()`. Includes details about the deposit amount, shares minted, rewards collected, and updated pool state. + +## Errors + +### StakingPoolAlreadyActivated + +```solidity +error StakingPoolAlreadyActivated(); +``` + +Thrown by `activate()` when the pool is already activated or has deposits. + +### StakingPoolFullExited + +```solidity +error StakingPoolFullExited(); +``` + +Thrown by various functions when the staking pool has been fully exited. + +### InvalidSender + +```solidity +error InvalidSender(address sender, address expected); +``` + +Thrown by `_validateSender()` when the sender is not the expected address. + +### InvalidAmount + +```solidity +error InvalidAmount(); +``` + +Thrown by `_submit()` when the deposit amount is zero. + +### WithdrawalNotAllowed + +```solidity +error WithdrawalNotAllowed(); +``` + +Thrown by withdrawal functions when the withdrawal cooldown period has not elapsed. + +### InvalidMinEffectiveBalance + +```solidity +error InvalidMinEffectiveBalance(); +``` + +Thrown by `setMinEffectiveBalance()` when the new minimum effective balance is not valid (must be between MIN_EFFECTIVE_BALANCE and MAX_EFFECTIVE_BALANCE). diff --git a/apps/core/content/nodes/staking-pools/contracts/StakingPoolContractsFactory.md b/apps/core/content/nodes/staking-pools/contracts/StakingPoolContractsFactory.md new file mode 100644 index 00000000..4f8721dc --- /dev/null +++ b/apps/core/content/nodes/staking-pools/contracts/StakingPoolContractsFactory.md @@ -0,0 +1,368 @@ +--- +head: + - - meta + - property: og:title + content: StakingPoolContractsFactory Contract Reference + - - meta + - name: description + content: Developer reference for the StakingPoolContractsFactory contract + - - meta + - property: og:description + content: Developer reference for the StakingPoolContractsFactory contract +--- + + + +# StakingPoolContractsFactory + +> Mainnet: {{config.contracts.stakingPools.stakingPoolContractsFactory.address.berachainMainnet}}Mainnet: Not yet deployed
Bepolia: {{config.contracts.stakingPools.stakingPoolContractsFactory.address.berachainBepolia}}
 | ABI JSON
+ +The StakingPoolContractsFactory is responsible for deploying and managing staking pool contracts for validators. It creates the core contracts (SmartOperator, StakingPool, StakingRewardsVault, and IncentiveCollector) and handles the initial validator deposit to register with the consensus layer. + +## State Variables + +### accountingOracle + +```solidity +address public accountingOracle; +``` + +### withdrawalVault + +```solidity +address public withdrawalVault; +``` + +## Structs + +### ValidatorData + +Data for the validator. + +```solidity +struct ValidatorData { + bytes pubkey; + bytes withdrawalCredentials; + uint64 initialDepositAmount; + uint64 validatorIndex; +} +``` + +**Properties** + +| Name | Type | Description | +| ----------------------- | -------- | ---------------------------------------------------- | +| `pubkey` | `bytes` | The pubkey of the validator. | +| `withdrawalCredentials` | `bytes` | The withdrawal credentials of the validator. | +| `initialDepositAmount` | `uint64` | The initial deposit amount of the validator in GWei. | +| `validatorIndex` | `uint64` | The index of the validator. | + +### ProofData + +Data for the proofs. + +```solidity +struct ProofData { + bytes32[] pubkeyProof; + bytes32[] withdrawalCredentialsProof; + bytes32[] initialDepositProof; + bytes32 initialDepositProofLeaf; +} +``` + +**Properties** + +| Name | Type | Description | +| ---------------------------- | ----------- | ---------------------------------------- | +| `pubkeyProof` | `bytes32[]` | The proof of the pubkey. | +| `withdrawalCredentialsProof` | `bytes32[]` | The proof of the withdrawal credentials. | +| `initialDepositProof` | `bytes32[]` | The proof of the initial deposit. | +| `initialDepositProofLeaf` | `bytes32` | The leaf of the initial deposit. | + +## View Functions + +### getCoreContracts + +Returns the core contracts for a given pubkey + +```solidity +function getCoreContracts(bytes memory pubkey) external view returns (CoreContracts memory); +``` + +**Parameters** + +| Name | Type | Description | +| -------- | ------- | --------------------------- | +| `pubkey` | `bytes` | The pubkey of the validator | + +### predictStakingPoolContractsAddresses + +Predicts the addresses of the staking pool contracts for a given pubkey + +```solidity +function predictStakingPoolContractsAddresses(bytes memory pubkey) external view returns (CoreContracts memory); +``` + +**Parameters** + +| Name | Type | Description | +| -------- | ------- | --------------------------- | +| `pubkey` | `bytes` | The pubkey of the validator | + +**Returns** + +| Name | Type | Description | +| -------- | --------------- | ------------------------------------------- | +| `` | `CoreContracts` | The addresses of the staking pool contracts | + +### validatePubkeyProof + +Validates the pubkey proof + +```solidity +function validatePubkeyProof( + bytes calldata pubkey, + bytes32[] calldata pubkeyProof, + uint64 validatorIndex, + uint64 timestamp +) + external + view; +``` + +**Parameters** + +| Name | Type | Description | +| ---------------- | ----------- | --------------------------------- | +| `pubkey` | `bytes` | The pubkey of the validator | +| `pubkeyProof` | `bytes32[]` | The proof of the pubkey | +| `validatorIndex` | `uint64` | The index of the validator | +| `timestamp` | `uint64` | The timestamp of the beacon block | + +### validateBalanceProof + +Validates the balance proof + +```solidity +function validateBalanceProof( + uint64 balance, + bytes32 balancesLeaf, + bytes32[] calldata balanceProof, + uint64 validatorIndex, + uint64 timestamp +) + external + view; +``` + +**Parameters** + +| Name | Type | Description | +| ---------------- | ----------- | --------------------------------- | +| `balance` | `uint64` | The balance of the validator | +| `balancesLeaf` | `bytes32` | The leaf of the balances | +| `balanceProof` | `bytes32[]` | The proof of the balance | +| `validatorIndex` | `uint64` | The index of the validator | +| `timestamp` | `uint64` | The timestamp of the beacon block | + +## Functions + +### deployStakingPoolContracts + +Deploys all staking pool contracts for a validator and performs the initial deposit to register the validator with the consensus layer. + +```solidity +function deployStakingPoolContracts( + bytes memory pubkey, + bytes memory withdrawalCredentials, + bytes memory signature, + address validatorAdmin, + address defaultSharesRecipient +) + external + payable + returns (address, address, address, address); +``` + +**Parameters** + +| Name | Type | Description | +| ------------------------ | --------- | -------------------------------------------------------------- | +| `pubkey` | `bytes` | The validator's public key (48 bytes) | +| `withdrawalCredentials` | `bytes` | The withdrawal credentials for the validator | +| `signature` | `bytes` | The validator's signature for the deposit | +| `validatorAdmin` | `address` | The address that will have admin privileges over the validator | +| `defaultSharesRecipient` | `address` | The address that will receive validator fee shares by default | + +**Returns** + +| Name | Type | Description | +| -------- | --------- | ------------------------------------ | +| `` | `address` | SmartOperator contract address | +| `` | `address` | StakingPool contract address | +| `` | `address` | StakingRewardsVault contract address | +| `` | `address` | IncentiveCollector contract address | + +**Requirements** + +- Must send exactly 10,000 BERA with the transaction +- Validator must not already be registered +- Withdrawal credentials must match the withdrawal vault address + +### activateStakingPool + +Activates a staking pool by validating proofs and updating the validator's state. + +```solidity +function activateStakingPool( + ValidatorData calldata validatorData, + ProofData calldata proofData, + uint64 timestamp +) + external; +``` + +**Parameters** + +| Name | Type | Description | +| --------------- | --------------- | -------------------------------------------------------------- | +| `validatorData` | `ValidatorData` | The validator data including pubkey and withdrawal credentials | +| `proofData` | `ProofData` | The proof data for validation | +| `timestamp` | `uint64` | The timestamp of the beacon block | + +## Events + +### StakingPoolContractsDeployed + +Emitted when staking pool contracts are deployed for a validator. + +```solidity +event StakingPoolContractsDeployed( + address smartOperator, address stakingPool, address stakingRewardsVault, address incentiveCollector +); +``` + +**Parameters** + +| Name | Type | Description | +| --------------------- | --------- | -------------------------------------------------------- | +| `smartOperator` | `address` | The address of the deployed SmartOperator contract | +| `stakingPool` | `address` | The address of the deployed StakingPool contract | +| `stakingRewardsVault` | `address` | The address of the deployed StakingRewardsVault contract | +| `incentiveCollector` | `address` | The address of the deployed IncentiveCollector contract | + +### StakingPoolActivated + +Emitted when a staking pool is activated. + +```solidity +event StakingPoolActivated(address stakingPool); +``` + +**Parameters** + +| Name | Type | Description | +| ------------- | --------- | ----------------------------------------- | +| `stakingPool` | `address` | The address of the activated staking pool | + +### SmartOperatorBeaconImplUpgraded + +Emitted when the SmartOperator beacon implementation is upgraded. + +```solidity +event SmartOperatorBeaconImplUpgraded(address newImplementation); +``` + +**Parameters** + +| Name | Type | Description | +| ------------------- | --------- | ------------------------------------- | +| `newImplementation` | `address` | The address of the new implementation | + +### StakingPoolBeaconImplUpgraded + +Emitted when the StakingPool beacon implementation is upgraded. + +```solidity +event StakingPoolBeaconImplUpgraded(address newImplementation); +``` + +**Parameters** + +| Name | Type | Description | +| ------------------- | --------- | ------------------------------------- | +| `newImplementation` | `address` | The address of the new implementation | + +### StakingRewardsVaultBeaconImplUpgraded + +Emitted when the StakingRewardsVault beacon implementation is upgraded. + +```solidity +event StakingRewardsVaultBeaconImplUpgraded(address newImplementation); +``` + +**Parameters** + +| Name | Type | Description | +| ------------------- | --------- | ------------------------------------- | +| `newImplementation` | `address` | The address of the new implementation | + +### IncentiveCollectorBeaconImplUpgraded + +Emitted when the IncentiveCollector beacon implementation is upgraded. + +```solidity +event IncentiveCollectorBeaconImplUpgraded(address newImplementation); +``` + +**Parameters** + +| Name | Type | Description | +| ------------------- | --------- | ------------------------------------- | +| `newImplementation` | `address` | The address of the new implementation | + +## Errors + +### OperatorAlreadySet + +```solidity +error OperatorAlreadySet(); +``` + +### InvalidOperator + +```solidity +error InvalidOperator(); +``` + +### InvalidWithdrawalCredentials + +```solidity +error InvalidWithdrawalCredentials(); +``` + +### InvalidAddress + +```solidity +error InvalidAddress(); +``` + +### InvalidFirstDepositAmount + +```solidity +error InvalidFirstDepositAmount(); +``` + +### InvalidInitialDepositAmount + +```solidity +error InvalidInitialDepositAmount(); +``` + +### InvalidTimestamp + +```solidity +error InvalidTimestamp(); +``` diff --git a/apps/core/content/nodes/staking-pools/contracts/StakingRewardsVault.md b/apps/core/content/nodes/staking-pools/contracts/StakingRewardsVault.md new file mode 100644 index 00000000..b7229f41 --- /dev/null +++ b/apps/core/content/nodes/staking-pools/contracts/StakingRewardsVault.md @@ -0,0 +1,78 @@ +--- +head: + - - meta + - property: og:title + content: StakingRewardsVault Contract Reference + - - meta + - name: description + content: Developer reference for the StakingRewardsVault contract + - - meta + - property: og:description + content: Developer reference for the StakingRewardsVault contract +--- + + + +# StakingRewardsVault + +The StakingRewardsVault contract holds execution layer rewards for validators. It receives BERA rewards from validator activities and allows the staking pool to withdraw these rewards when needed. + +## Functions + +### withdrawRewards + +Withdraws rewards from the vault to the staking pool. Can only be called by the staking pool contract. + +```solidity +function withdrawRewards(uint256 amount) external; +``` + +**Parameters** + +| Name | Type | Description | +| -------- | --------- | --------------------------------- | +| `amount` | `uint256` | The amount of rewards to withdraw | + +**Requirements** + +- Must be called by the staking pool contract + +## Events + +### RewardsReceived + +Emitted when the default payable function is called. + +```solidity +event RewardsReceived(uint256 amount); +``` + +**Parameters** + +| Name | Type | Description | +| -------- | --------- | ----------------------------- | +| `amount` | `uint256` | The amount of token received. | + +### RewardsWithdrawn + +Emitted when rewards are withdrawn from the vault to the staking pool. + +```solidity +event RewardsWithdrawn(uint256 amount); +``` + +**Parameters** + +| Name | Type | Description | +| -------- | --------- | -------------------------------- | +| `amount` | `uint256` | The amount of rewards withdrawn. | + +## Errors + +### InvalidSender + +```solidity +error InvalidSender(); +``` diff --git a/apps/core/content/nodes/staking-pools/contracts/WithdrawalVault.md b/apps/core/content/nodes/staking-pools/contracts/WithdrawalVault.md new file mode 100644 index 00000000..1c1f661e --- /dev/null +++ b/apps/core/content/nodes/staking-pools/contracts/WithdrawalVault.md @@ -0,0 +1,298 @@ +--- +head: + - - meta + - property: og:title + content: WithdrawalVault Contract Reference + - - meta + - name: description + content: Developer reference for the WithdrawalVault contract + - - meta + - property: og:description + content: Developer reference for the WithdrawalVault contract +--- + + + +# WithdrawalVault + +> Mainnet: {{config.contracts.stakingPools.withdrawalVault.address.berachainMainnet}}Mainnet: Not yet deployed
Bepolia: {{config.contracts.stakingPools.withdrawalVault.address.berachainBepolia}}
 | ABI JSON
+ +The WithdrawalVault contract manages withdrawal requests for staking pools. It implements ERC721Enumerable to create non-transferable NFTs representing withdrawal requests and handles the finalization process after a cooldown period. The contract also manages full exits from the consensus layer. + +**NFT Details:** + +- **Name**: "Berachain Staking Pool Withdrawal Request" +- **Symbol**: "BSPWR" +- **Token ID**: The `requestId` returned from `requestWithdrawal()` or `requestRedeem()` is the NFT token ID +- **Non-Transferable**: NFTs cannot be transferred to other addresses +- **Enumerable**: Supports standard ERC721Enumerable functions for querying owned NFTs + +## State Variables + +### withdrawalRequests + +```solidity +mapping(uint256 => WithdrawalRequest) public withdrawalRequests; +``` + +### allocatedWithdrawalsAmount + +```solidity +mapping(bytes => uint256) public allocatedWithdrawalsAmount; +``` + +## Structs + +### WithdrawalRequest + +Represents a withdrawal request. + +```solidity +struct WithdrawalRequest { + bytes pubkey; + uint256 assetsRequested; + uint256 sharesBurnt; + address user; + uint256 requestBlock; +} +``` + +**Properties** + +| Name | Type | Description | +| ----------------- | --------- | ------------------------------------------------------- | +| `pubkey` | `bytes` | The validator's public key | +| `assetsRequested` | `uint256` | The amount of BERA required for the withdrawal. | +| `sharesBurnt` | `uint256` | The amount of shares burned for the withdrawal. | +| `user` | `address` | The address of the staker who requested the withdrawal. | +| `requestBlock` | `uint256` | The block number when the withdrawal request was made. | + +## View Functions + +### getWithdrawalRequest + +Gets a withdrawal request by ID. The request ID is the same as the NFT token ID. + +```solidity +function getWithdrawalRequest(uint256 requestId) external view returns (WithdrawalRequest memory); +``` + +**Parameters** + +| Name | Type | Description | +| ----------- | --------- | ------------------------------------------------------------------------------- | +| `requestId` | `uint256` | The ID of the withdrawal request (this is also the ERC721 token ID for the NFT) | + +**Returns** + +| Name | Type | Description | +| -------- | ------------------- | ----------------------------- | +| `` | `WithdrawalRequest` | The withdrawal request struct | + +**Note**: Since WithdrawalVault implements ERC721Enumerable, you can also use standard ERC721 functions: + +- `ownerOf(uint256 tokenId)` - Returns the owner of a withdrawal request NFT (the `requestId` is the `tokenId`) +- `balanceOf(address owner)` - Returns the number of withdrawal request NFTs owned by an address +- `tokenOfOwnerByIndex(address owner, uint256 index)` - Returns a token ID owned by an address at a given index +- `totalSupply()` - Returns the total number of withdrawal request NFTs minted + +## Functions + +### requestRedeem + +Requests a redemption of shares from the staking pool. Creates an NFT representing the redemption that will be necessary to finalize the request. + +```solidity +function requestRedeem( + bytes memory pubkey, + uint256 shares, + uint256 maxFeeToPay +) + external + payable + nonReentrant + whenNotPaused; +``` + +**Parameters** + +| Name | Type | Description | +| ------------- | --------- | --------------------------------------------------------------------------- | +| `pubkey` | `bytes` | The validator's public key. | +| `shares` | `uint256` | The amount of shares to redeem. | +| `maxFeeToPay` | `uint256` | The maximum fee the staker is willing to pay for the redemption (EIP-7002). | + +**Requirements** + +- Shares must be greater than 0 +- The number of shares will be rounded down so that the corresponding asset value is a multiple of GWei + +### requestWithdrawal + +Requests a withdrawal of assets from the staking pool. Creates an NFT representing the withdrawal that will be necessary to finalize the request. + +```solidity +function requestWithdrawal( + bytes memory pubkey, + uint64 assetsInGWei, + uint256 maxFeeToPay +) + external + payable + nonReentrant + whenNotPaused; +``` + +**Parameters** + +| Name | Type | Description | +| -------------- | --------- | --------------------------------------------------------------------------- | +| `pubkey` | `bytes` | The validator's public key. | +| `assetsInGWei` | `uint64` | The amount of BERA to withdraw in GWei. | +| `maxFeeToPay` | `uint256` | The maximum fee the staker is willing to pay for the withdrawal (EIP-7002). | + +**Requirements** + +- Assets must be greater than 0 + +### finalizeWithdrawalRequest + +Finalizes a withdrawal request. + +```solidity +function finalizeWithdrawalRequest(uint256 requestId) external nonReentrant whenNotPaused; +``` + +**Parameters** + +| Name | Type | Description | +| ----------- | --------- | --------------------------------------------- | +| `requestId` | `uint256` | The ID of the withdrawal request to finalize. | + +**Requirements** + +- Request must not already be finalized +- Delay period must have passed +- Withdrawal vault must have enough funds + +### finalizeWithdrawalRequests + +Finalizes multiple withdrawal requests. + +```solidity +function finalizeWithdrawalRequests(uint256[] calldata requestIds) external nonReentrant whenNotPaused; +``` + +**Parameters** + +| Name | Type | Description | +| ------------ | ----------- | -------------------------------------------- | +| `requestIds` | `uint256[]` | Array of withdrawal request IDs to finalize. | + +**Requirements** + +- All requests must not already be finalized +- Delay period must have passed for all requests +- Withdrawal vault must have enough funds for all requests + +### notifyFullExitFromCL + +Notifies the withdrawal vault that a full exit was triggered from the CL. + +```solidity +function notifyFullExitFromCL(bytes memory pubkey) external; +``` + +**Parameters** + +| Name | Type | Description | +| -------- | ------- | --------------------------- | +| `pubkey` | `bytes` | The validator's public key. | + +## Events + +### WithdrawalRequested + +```solidity +event WithdrawalRequested( + address indexed user, uint256 amountOfAsset, uint256 amountOfShares, uint256 requestId, bool isFullExitWithdraw +); +``` + +Emitted when a staker creates a withdrawal request that will be processed through the consensus layer after a cooldown period. The request creates an NFT that must be used to finalize the withdrawal after the delay. + +**Parameters** + +| Name | Type | Description | +| -------------------- | --------- | ------------------------------------------------------- | +| `user` | `address` | The address of the staker who requested the withdrawal. | +| `amountOfAsset` | `uint256` | The amount of asset requested for withdrawal. | +| `amountOfShares` | `uint256` | The amount of shares burned for the withdrawal. | +| `requestId` | `uint256` | The ID of the withdrawal request. | +| `isFullExitWithdraw` | `bool` | Indicates if this is a full exit withdrawal. | + +### WithdrawalRequestFinalized + +Emitted when a withdrawal request is finalized. + +```solidity +event WithdrawalRequestFinalized(uint256 requestId); +``` + +**Parameters** + +| Name | Type | Description | +| ----------- | --------- | ---------------------------------------------------- | +| `requestId` | `uint256` | The ID of the withdrawal request that was finalized. | + +## Errors + +### InvalidSender + +```solidity +error InvalidSender(); +``` + +### OverpaidFee + +```solidity +error OverpaidFee(); +``` + +### InsufficientFee + +```solidity +error InsufficientFee(); +``` + +### InvalidAmount + +```solidity +error InvalidAmount(); +``` + +### InvalidRequest + +```solidity +error InvalidRequest(); +``` + +### RequestNotReady + +```solidity +error RequestNotReady(); +``` + +### NotEnoughFunds + +```solidity +error NotEnoughFunds(); +``` + +### NonTransferable + +```solidity +error NonTransferable(); +``` diff --git a/apps/core/content/nodes/staking-pools/delegators.md b/apps/core/content/nodes/staking-pools/delegators.md new file mode 100644 index 00000000..79f9a763 --- /dev/null +++ b/apps/core/content/nodes/staking-pools/delegators.md @@ -0,0 +1,87 @@ +# Delegation Guide (Validator Operators) + +This guide shows validator operators how to use delegated capital to stand up and run a staking pool. It builds on the Installation guide and reuses the same helper scripts. It does not cover delegator actions; coordinate with your capital provider as needed. + +See Installation first: `/nodes/staking-pools/installation`. At minimum, set up env.sh in `install-helpers/`. + +**Important:** All helper scripts write `cast` commands to files; review and run them yourself. Transactions are never sent automatically. Signing defaults to Ledger; set `PRIVATE_KEY` in `env.sh` if you prefer a local key. + +## Assumptions + +- You completed the validator Installation steps (env configured, node synced). +- The Berachain Foundation has prepared a DelegationHandler and delegated funds for your validator pubkey. + +## 1) Check readiness + +Confirm the chain, validator pubkey, and whether a delegated pool/handler is detected: + +```bash +./status.sh +``` + +If a handler exists, the script displays delegated amounts and whether a pool already exists for your pubkey. + +## 2) Create the pool with delegated funds (first 10,000 BERA) + +If the pool is not yet created, use the delegated creation script. It consumes the first 10,000 BERA from the handler to register the validator and writes a ready-to-run command file. This step is the delegated equivalent of the self‑funded flow's `register.sh` (it performs the 10,000 BERA deposit and deploys the pool contracts). + +```bash +./delegated-create-pool.sh +``` + +This will create a script with deployment commands. Review it. Then run it to submit the transaction, then wait for confirmation. + +## 3) Wait for validator registration + +After deploying the contracts, wait for your validator to be registered on the beacon chain. You can check registration status with: + +```bash +./status.sh +``` + +The validator must appear on the beacon chain before you can activate the pool. + +## 4) Activate the pool + +Activation mirrors the Installation flow. Once the validator is recognised as registered on the beacon chain, generate and execute the activation command with fresh proofs and a valid timestamp window. The `activate.sh` script emits `activation-command.sh`: + +```bash +./activate.sh --sr 0xSHARES_RECIPIENT --op 0xOPERATOR +``` + +Execute the generated `activation-command.sh` within ~10 minutes. + +## 5) Deposit remaining delegated funds + +After activation, deposit the remaining delegated funds to reach your target balance (for example, 250,000 BERA on Bepolia). The script writes a command file for you to execute. + +```bash +./delegated-deposit.sh --amount 240000 +``` + +## 6) Verify status + +```bash +./status.sh +``` + +You should see contract addresses, the operator match on the beacon deposit contract, and the pool marked ACTIVE. The script also reports delegated amounts when a handler is present. + +## 7) Withdraw yield (operator) + +Operators can withdraw earned yield independently of principal. The helper script writes two commands (request, then complete after cooldown): + +```bash +./delegated-withdraw-yield.sh --pubkey 0xYOUR_VALIDATOR_PUBKEY +``` + +Follow the prompts to execute the generated request and completion scripts after the cooldown. + +Principal withdrawals are controlled by the delegator and are out of scope here. However, the idea is once you have attracted additional depsitors, the foundation can redeem its stake without causing your validator to exit. + +**Note:** If you are self-funded, use `stake.sh` from the Installation guide instead of the delegated deposit flow. + +## Where to next + +- [Contract reference](/nodes/staking-pools/contracts.md) explains what you've deployed +- [Operator Guide](/nodes/staking-pools/operators.md) explains what a Stake Pool operator can do. diff --git a/apps/core/content/nodes/staking-pools/index.md b/apps/core/content/nodes/staking-pools/index.md new file mode 100644 index 00000000..6a40cf23 --- /dev/null +++ b/apps/core/content/nodes/staking-pools/index.md @@ -0,0 +1,80 @@ +--- +head: + - - meta + - property: og:title + content: Staking Pools Overview + - - meta + - name: description + content: Overview of Berachain Staking Pools system + - - meta + - property: og:description + content: Overview of Berachain Staking Pools system +--- + + + +# Staking Pools Overview + +Staking Pools enable validators to offer liquid staking services to their communities. As a validator, you can build and monetize your own community of stakers, earning commission on rewards. Your stakers deposit BERA through smart contracts and receive liquid shares (stBERA) that automatically grow in value as rewards accumulate. + +## What Are Staking Pools? + +Staking Pools are validator-operated services that allow your community members to stake BERA through smart contracts, receiving liquid shares (stBERA) that represent their stake and automatically grow in value as rewards accumulate. Stakers can stake any amount without running their own validator. + +As a validator, staking pools provide you with a way to build and monetize your own community of stakers, earning commission on rewards. Your stakers benefit from lower barriers to entry, automatic reward reinvestment, and flexible withdrawals. + +## How It Works + +When you create a staking pool, the system deploys several interconnected contracts. Your pool includes a StakingPool contract that manages deposits and shares, a SmartOperator that handles validator operations and Proof of Liquidity integration, and a StakingRewardsVault that collects and reinvests rewards. Shared infrastructure includes a WithdrawalVault that processes withdrawal requests for all pools and an Accounting Oracle that provides consensus layer data updates. + +The system automates staking, reward distribution, and withdrawal management, requiring minimal manual intervention from you while providing your stakers with a seamless staking experience. + +### Contract Architecture + +The staking pools system uses a factory pattern where the **StakingPoolContractsFactory** serves as the deployment mechanism, creating and managing the complete suite of contracts needed for each validator's staking pool. + +The core functionality revolves around the **StakingPool** contract, which handles staker deposits, share management, and the fundamental operations of your staking pool. Stakers interact with this contract to stake their BERA tokens and receive stBERA shares in return. The StakingPool contract maintains the accounting for staker positions and manages the conversion between BERA and stBERA shares. + +Validator operations are coordinated through the **SmartOperator** contract, which manages the integration with Berachain's Proof of Liquidity system. This contract handles BGT boosting, reward allocation, commission management, and other validator-specific operations that require coordination with the broader Berachain ecosystem. + +The **StakingPool** contract inherits from the **StBERA** base contract, which provides the core token functionality for staked BERA shares, including share minting, burning, and conversion between assets and shares. + +### Security Model + +The staking pools system implements a security model designed to protect staker funds while maintaining operational flexibility for validators and administrators. + +Access control is managed through a role-based system that provides granular permissions for different types of operations. The `DEFAULT_ADMIN_ROLE` provides governance control over contract upgrades and emergency actions, ensuring that the system can be maintained and updated as needed. Validators receive the `VALIDATOR_ADMIN_ROLE`, which grants them operational control over their specific staking pool while preventing interference with other validators' operations. + +Specialized roles handle specific aspects of the system. The `REWARDS_ALLOCATION_MANAGER_ROLE` manages reward allocation to specific applications, while the `COMMISSION_MANAGER_ROLE` handles validator commission management. The `PROTOCOL_FEE_MANAGER_ROLE` controls protocol fee settings, and the `INCENTIVE_COLLECTOR_MANAGER_ROLE` manages incentive collector operations, ensuring that these critical functions are properly isolated and controlled. + +Emergency controls provide additional protection. Contracts can be paused in emergency situations. The system includes automatic full exit triggers that activate if the minimum balance threshold is breached, protecting stakers from potential losses. + +## Staker Experience + +When stakers interact with your staking pool, they deposit BERA and immediately receive liquid stBERA shares. Rewards automatically compound as you earn rewards, increasing the value of shares over time without manual claiming or reinvestment. Stakers can withdraw at any time; withdrawals process through the consensus layer in approximately 3 days (129,600 blocks at ~2s block time). Stakers can stake any amount without validator minimums, with full transparency through on-chain verification. + +## Validator Operations + +You deploy a staking pool through the factory contract, which creates all necessary contracts and registers your validator with the consensus layer. You configure commission rates (up to 20% of staker rewards) and can direct Proof of Liquidity incentives to specific applications. Commission is collected automatically on staker rewards, allowing you to focus on community building rather than manual reward management. + +## Integration with Proof of Liquidity + +Staking pools integrate with Berachain's Proof of Liquidity system. Pool BGT rewards automatically boost your PoL performance, and smart contracts automatically claim and distribute PoL incentives. You can direct rewards to specific applications or ecosystem initiatives. + +## Provided Tools + +Berachain provides tools to help you operate your staking pool. A React-based **example frontend template** provides a starting point for building your staking interface. The template demonstrates how stakers can connect wallets, deposit BERA, view positions, and request withdrawals. See the [Building Your Front-End](/nodes/staking-pools/operators#building-your-front-end) section in the operator guide for requirements and template usage. Bash scripts automate deployment and management operations, generating ready-to-review `cast` commands for safe execution. An interactive Python CLI tool is available for managing SmartOperator contracts. These tools are available in the [Berachain guides repository](https://github.com/berachain/guides/tree/main/apps/staking-pools), with detailed documentation in the [install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md). + +## Getting Started + +Validators can set up staking pools using the [Installation Guide](/nodes/staking-pools/installation) and manage operations with the [Operator Guide](/nodes/staking-pools/operators). + +## Smart Contract Reference + +For detailed information about the smart contracts and their functions, see the [Smart Contract Reference](/nodes/staking-pools/contracts). + +## Support and Resources + +If you don't have contact with Berachain Validator Relations, ask on our [Discord's](https://discord.gg/berachain) #node-support channel. diff --git a/apps/core/content/nodes/staking-pools/installation.md b/apps/core/content/nodes/staking-pools/installation.md new file mode 100644 index 00000000..8562e0b8 --- /dev/null +++ b/apps/core/content/nodes/staking-pools/installation.md @@ -0,0 +1,147 @@ +--- +head: + - - meta + - property: og:title + content: Staking Pools Installation Guide + - - meta + - name: description + content: Install and activate staking pools using the helper scripts (validator-focused) + - - meta + - property: og:description + content: Install and activate staking pools using the helper scripts (validator-focused) +--- + + + +# Staking Pools Installation Guide + +This guide walks validator operators through installing and activating a staking pool using the helper scripts. + +## What you’ll use + +The helper scripts are available through github: + +``` +$ git clone https://github.com/berachain/guides/ +$ cd guides/apps/staking-pools +$ ls -F +frontend/ install-helpers/ +``` + +There are many useful scripts under `install-helpers/` which wrap multi-step operations into safe, review-first commands: + +- `register.sh`: Deploys (registers) staking pool contracts and generates the deployment transaction +- `activate.sh`: Activates a deployed pool using validator proofs from the beacon chain +- `status.sh`: Verifies deployment, registration, and activation status +- `stake.sh`: Generates a staking transaction to add BERA to your pool + +**Important:** All scripts write ready-to-run `cast` commands to files for you to review and execute. They do not send transactions without your confirmation. By default, scripts use Ledger for signing; set `PRIVATE_KEY` in `env.sh` if you prefer a local key. + +## Requirements + +- beacond (validator client) running and synced with your validator keys **backed up somewhere safe**. +- Foundry `cast` (`https://getfoundry.sh/`) +- `jq`, `bc`, `curl`, +- Ledger hardware wallet (default) or a private key set via `PRIVATE_KEY` +- Funds: at least 10,000 BERA for the initial deposit; additional stake as desired + +## 1) Configure environment + +In `install-helpers/`: + +```bash +cp env.sh.template env.sh +# Edit env.sh and set at minimum: +# BEACOND_HOME="/path/to/your/beacond/home" +# NODE_API_ADDRESS="127.0.0.1:3500" # If not auto-detected from app.toml +# Optionally set: +# PRIVATE_KEY="0x..." # Defaults to Ledger if unset +``` + +Ensure the beacon node API is enabled in your `app.toml` (`[beacon-kit.node-api]`) or provide `NODE_API_ADDRESS` in `env.sh`. The scripts will auto-detect when possible and verify connectivity before proceeding. + +## 2) Register (deploy contracts) + +Run `register.sh` with your addresses to deploy the staking pool contracts: + +```bash +./register.sh --sr 0xSHARES_RECIPIENT --op 0xOPERATOR +``` + +1. The chain (mainnet or Bepolia) is auto-detected from your beacond configuration. +2. The initial stake is 10,000 BERA (fixed by the consensus layer). The script writes `deployment-command.sh` with this deposit. Review it. Simulate it if you want. Then run it. This should dump a successful transaction receipt. +3. The script also predicts and shows your staking pool contract addresses ahead of time. + +## 3) Wait for validator registration + +After deploying the contracts, wait for your validator to be registered on the beacon chain. You can check registration status with: + +```bash +./status.sh +``` + +The validator must appear on the beacon chain before you can activate the pool. + +## 4) Activate the pool + +Once your validator is registered, run `activate.sh` to activate the pool: + +```bash +./activate.sh --sr 0xSHARES_RECIPIENT --op 0xOPERATOR +``` + +1. The script validates that the pool contract exists (error if not deployed). +2. The script verifies that the validator is registered on the beacon chain (error if not registered). +3. The script writes `activation-command.sh` with fresh proofs and a timestamp. Execute it within ~10 minutes. + +## 5) Verify installation + +Use `status.sh` to check deployment, registration, and activation: + +```bash +./status.sh +``` + +You should see the SmartOperator, StakingPool, StakingRewardsVault, and IncentiveCollector addresses, confirmation that the beacon deposit operator matches your SmartOperator, the pool's active status, and various telemetry. + +## 6) Set minimum effective balance + +**Critical Step:** The `minEffectiveBalance` parameter determines when your staking pool activates its validator on the consensus layer. If you don't set this value correctly, your pool may accumulate deposits without ever activating. + +Set the value using your SmartOperator contract: + +```bash +cast send $SMART_OPERATOR_ADDRESS \ + "setMinEffectiveBalance(uint256)" $CALCULATED_MIN_STAKE \ + --ledger # or --private-key $PRIVATE_KEY +``` + +Replace `$SMART_OPERATOR_ADDRESS` with your SmartOperator address and `$CALCULATED_MIN_STAKE` with the calculated minimum stake amount in wei (multiply BERA amount by 10^18). + +For details on why this matters, how to determine the correct value, and how the consensus layer minimum works, see the [Setting Minimum Effective Balance](/nodes/staking-pools/operators#setting-minimum-effective-balance) section in the operator guide. + +## 7) (Optional) Stake additional BERA + +Add stake to your pool and send stBERA to a receiver address: + +```bash +# With BEACOND_HOME configured (pool auto-detected): +./stake.sh --amount 100 --receiver 0xRECEIVER + +# Or specify an explicit pool address: +./stake.sh --amount 100 --receiver 0xRECEIVER --staking-pool 0xPOOL +``` + +The script writes `stake-command.sh`. Review and execute the command to submit your stake. + +## Troubleshooting (quick) + +- Node API not reachable: enable `[beacon-kit.node-api]` and confirm the address/port; or set `NODE_API_ADDRESS` in `env.sh`. The script will examine your files and tell you how to activate the API if it isn't enabled yet. +- Missing tools: install Foundry (`cast`), `jq`, `bc`, and `curl` and ensure they are on your PATH. + +## What’s next + +- [Contract reference](/nodes/staking-pools/contracts.md) explains what you've deployed +- [Delegation guide](/nodes/staking-pools/delegators) explains how to receive a Foundation delegation, which is very similar to this flow. diff --git a/apps/core/content/nodes/staking-pools/operators.md b/apps/core/content/nodes/staking-pools/operators.md new file mode 100644 index 00000000..e05b5946 --- /dev/null +++ b/apps/core/content/nodes/staking-pools/operators.md @@ -0,0 +1,593 @@ +--- +head: + - - meta + - property: og:title + content: Staking Pools Operator Guide + - - meta + - name: description + content: How to set up and manage staking pools as a validator + - - meta + - property: og:description + content: How to set up and manage staking pools as a validator +--- + + + +# Staking Pools Operator Guide + +This guide helps validators set up and manage staking pools to offer liquid staking services to their communities. + +## Quick Reference + +### Key Parameters + +| Parameter | Range | Purpose | +| ------------------------- | ----------------------------------------------- | -------------------------------------------- | +| Validator Commission | 0-20% | Commission on incentive token distribution | +| Protocol Fee | 0-20% | Fee on BGT balance growth | +| Minimum Effective Balance | ≥ {{ config.mainnet.minEffectiveBalance }} BERA | Activation threshold and full exit safeguard | +| Withdrawal Delay | 129,600 blocks (≈3 days at ~2s block time) | Time before withdrawals can be finalized | + +### Key Roles + +| Role | Controls | Function | +| ---------------------------------- | ----------------- | -------------------------------------- | +| `VALIDATOR_ADMIN_ROLE` | All other roles | Grant/revoke operational roles | +| `REWARDS_ALLOCATION_MANAGER_ROLE` | Reward allocation | Direct PoL incentives to applications | +| `COMMISSION_MANAGER_ROLE` | Commission rate | Adjust validator commission (0-20%) | +| `PROTOCOL_FEE_MANAGER_ROLE` | Protocol fee | Adjust protocol fee percentage (0-20%) | +| `INCENTIVE_COLLECTOR_MANAGER_ROLE` | Payout amount | Adjust incentive collector payout | +| `BGT_MANAGER_ROLE` | BGT operations | Queue drop boost, redeem BGT | + +### Essential Functions + +| Function | Contract | Purpose | +| ---------------------------- | ------------- | ------------------------------------- | +| `setMinEffectiveBalance()` | SmartOperator | Set activation threshold | +| `queueValCommission()` | SmartOperator | Queue commission rate change | +| `queueRewardsAllocation()` | SmartOperator | Queue reward allocation | +| `claimBoostRewards()` | SmartOperator | Forward rewards to IncentiveCollector | +| `setProtocolFeePercentage()` | SmartOperator | Set protocol fee rate | + +## Prerequisites + +Before setting up a staking pool, ensure you have a fully operational Berachain validator node. You'll need at least {{ config.mainnet.votingPowerIncrement }} $BERA to register the pool, though activation requires at least {{ config.mainnet.minEffectiveBalance }} $BERA. + +You'll need technical knowledge of smart contract interactions to deploy and manage contracts. Understanding the underlying mechanics helps troubleshoot issues and optimize performance. You should also have a community of stakers interested in staking with your validator. + +:::warning +Staking pools follow the standard Berachain validator lifecycle. After deployment, your validator will progress through the Deposited → Eligible states, but activation to the Active state depends on the ValidatorSetCap and your validator's priority relative to other validators. +::: + +## Validator Lifecycle + +Your staking pool integrates with Berachain's validator lifecycle. For details on validator states (Deposited, Eligible, Active, Exited, Withdrawn) and transitions, see the [Validator Lifecycle documentation](/nodes/validator-lifecycle). + +The key consideration for staking pools is ensuring sufficient stake for activation. See [Setting Minimum Effective Balance](#setting-minimum-effective-balance) for details on how to configure this. + +## Key Terms and Concepts + +**Active Threshold**: The point at which your pool has sufficient stake (`totalDeposits >= minEffectiveBalance`) to activate the validator. When `activeThresholdReached()` returns `true`, your validator enters a cooldown period before activation. This is separate from the consensus layer's activation queue. + +**Minimum Effective Balance (`minEffectiveBalance`)**: The minimum stake amount required for validator activation and a safeguard that triggers full exit if deposits fall below it. This must match or exceed the current consensus layer minimum, which increases when the validator set is full. + +**Short-Circuit Withdrawal**: A withdrawal path where funds are immediately transferred from the pool's buffer to WithdrawalVault, but stakers still must wait the full withdrawal delay (129,600 blocks ≈ 3 days) before finalization. This occurs when the pool hasn't reached the active threshold and has sufficient buffered funds. + +**Withdrawal Delay**: The time period (129,600 blocks ≈ 3 days at ~2s block time) that must pass after a withdrawal request before it can be finalized. This delay applies regardless of whether the withdrawal uses the short-circuit or standard path. + +**Cooldown Period**: After `activeThresholdReached()` becomes `true`, there is a cooldown period before the validator activates. During this time, withdrawals are still allowed, but the validator is not yet active on the consensus layer. + +**Relationship between `minEffectiveBalance` and `activeThresholdReached`**: + +- `minEffectiveBalance` is the threshold you set (must match consensus layer requirements) +- `activeThresholdReached()` returns `true` when `totalDeposits >= minEffectiveBalance()` +- Once `activeThresholdReached()` is `true`, the validator enters cooldown and will activate after the delay +- If `totalDeposits` later falls below `minEffectiveBalance()`, the pool triggers full exit + +## Configuration + +After deploying your staking pool, configure these essential parameters before accepting staker deposits. + +### Commission Rates + +Staking pools provide validators with a revenue stream through commission on incentive token distribution. You can set commission rates within the range defined by the BeraChef contract (0-20%), allowing you to balance profitability with competitive positioning. + +Commission applies to the distribution of incentive tokens from Proof of Liquidity rewards. Setting commission greater than 0% guarantees stakers a yield, even if the validator maintains minimal boost and burns BGT to increase voting power. Incentive tokens are automatically sent to the SmartOperator when `distributeFor` runs for your validator. + +For detailed instructions on managing validator commission rates, including how to queue and activate changes, see the [Manage Validator Incentives Commission Rate](/nodes/guides/manage-incentives-commission) guide. + +```solidity +// Queue validator commission rate change (in basis points, max 2000 = 20%) +function queueValCommission(uint96 commission) external; +``` + +See: [SmartOperator.queueValCommission](/nodes/staking-pools/contracts/SmartOperator.md#queuevalcommission) + +### Reward Allocations + +The reward allocation system lets you direct Proof of Liquidity incentives to specific applications or use cases, supporting ecosystem initiatives or community projects to differentiate your pool. + +For detailed instructions on managing reward allocations, including how to queue and activate changes, see the [Managing Validator Reward Allocations](/nodes/guides/reward-allocation) guide. + +```solidity +// Queue reward allocation for specific applications +function queueRewardsAllocation( + uint64 startBlock, + IBeraChef.Weight[] calldata weights +) external; +``` + +See: [SmartOperator.queueRewardsAllocation](/nodes/staking-pools/contracts/SmartOperator.md#queuerewardsallocation) + +### Reward Allocator + +You can set a reward allocator address for your validator on the BeraChef contract. This allows you to delegate reward allocation management to a specific address or smart contract. + +```solidity +// Set reward allocator for validator +function setRewardAllocator(address rewardAllocator) external; +``` + +See: [SmartOperator.setRewardAllocator](/nodes/staking-pools/contracts/SmartOperator.md#setrewardallocator) + +### Role Management + +The SmartOperator contract uses role-based access control to delegate operational responsibilities. When you deploy your staking pool, you receive the `VALIDATOR_ADMIN_ROLE`, which allows you to grant and revoke operational roles to other addresses. + +| Role | Controls | Key Functions | +| ---------------------------------- | ----------------------- | --------------------------------------------- | +| `REWARDS_ALLOCATION_MANAGER_ROLE` | Reward allocation | `queueRewardsAllocation()` | +| `COMMISSION_MANAGER_ROLE` | Commission rate (0-20%) | `queueValCommission()` | +| `INCENTIVE_COLLECTOR_MANAGER_ROLE` | Payout amounts | `queueIncentiveCollectorPayoutAmountChange()` | +| `PROTOCOL_FEE_MANAGER_ROLE` | Protocol fee (0-20%) | `setProtocolFeePercentage()` | +| `BGT_MANAGER_ROLE` | BGT operations | `queueDropBoost()`, `redeemBGT()` | + +**Role Assignment Strategy:** Assign roles based on your operational needs. For simplicity, grant all roles to your main validator wallet. Alternatively, grant specific roles to different team members, automated systems, or smart contracts for distributed management. This keeps validator keys secure and separate from day-to-day management. + +### Setting Minimum Effective Balance + +The `minEffectiveBalance` parameter is critical for validator activation and maintaining active status. This value determines when your staking pool becomes eligible to activate its validator on the consensus layer and serves as a safeguard that triggers full exit if your pool's deposits fall below it. + +**How the Consensus Layer Minimum Works:** + +The consensus layer enforces a base minimum of {{ config.mainnet.minEffectiveBalance }} BERA for validator activation. However, when the validator set is full (all {{ config.mainnet.validatorActiveSetSize }} validator slots are occupied), the actual minimum stake required increases in increments of {{ config.mainnet.stakeMinimumIncrement }} BERA. This dynamic adjustment ensures that validators must compete for entry into the active set when capacity is reached. + +You must set `minEffectiveBalance` to match the current minimum stake required on the consensus layer if it exceeds {{ config.mainnet.minEffectiveBalance }} BERA. To determine the current requirement: + +1. Check the current number of validators on [Berachain Hub](https://hub.berachain.com/boost/) +2. If the validator set is full ({{ config.mainnet.validatorActiveSetSize }} validators), identify the lowest stake amount among active validators +3. The minimum required stake will be that lowest amount, rounded up to the nearest {{ config.mainnet.stakeMinimumIncrement }} BERA increment +4. You may, of course, choose to go higher + +Once your validator reaches the activation threshold (when `activeThresholdReached()` becomes `true`), a cooldown period begins. However, if withdrawals later cause `totalDeposits` to fall below `minEffectiveBalance()`, the pool automatically triggers a full exit (see [Full Exit Management](#full-exit-management)). Setting `minEffectiveBalance` correctly from the start prevents your pool from exiting prematurely while ensuring activation occurs when sufficient stake is available. + +```solidity +// Set minimum effective balance (can only be called by SmartOperator) +function setMinEffectiveBalance(uint256 newMinEffectiveBalance) external; +``` + +See: [SmartOperator.setMinEffectiveBalance](/nodes/staking-pools/contracts/SmartOperator.md#setmineffectivebalance) + +## Routine Operations + +### Monitor Pool Status + +Check key metrics regularly: whether the pool is active, total assets under management, and buffered assets not yet staked. + +```solidity +// Check if pool is active +function isActive() external view returns (bool); + +// Get total assets under management +function totalAssets() external view returns (uint256); + +// Get buffered assets (not yet staked) +function bufferedAssets() external view returns (uint256); + +// Check if pool has fully exited +function isFullyExited() external view returns (bool); + +// Get minimum effective balance threshold +function minEffectiveBalance() external view returns (uint256); + +// Check if active threshold is reached +function activeThresholdReached() external view returns (bool); +``` + +See: [StakingPool.isActive](/nodes/staking-pools/contracts/StakingPool.md#isactive), [StakingPool.bufferedAssets](/nodes/staking-pools/contracts/StakingPool.md#bufferedassets), [StakingPool.isFullyExited](/nodes/staking-pools/contracts/StakingPool.md#isfullyexited), [StakingPool.minEffectiveBalance](/nodes/staking-pools/contracts/StakingPool.md#mineffectivebalance), and [StakingPool.activeThresholdReached](/nodes/staking-pools/contracts/StakingPool.md#activethresholdreached) + +### BGT Operations and Protocol Fees + +The SmartOperator contract manages BGT (Berachain Governance Token) operations through role-based access controls. As a validator operator, you or your designated BGT manager will need to actively manage BGT operations to optimize your Proof of Liquidity performance. + +**BGT Management Operations:** + +BGT operations require the `BGT_MANAGER_ROLE` (which you as VALIDATOR_ADMIN can grant): + +- **Queue Drop Boost**: Enqueue a request to unboost a specific amount of BGT using `queueDropBoost()` +- **Redeem BGT**: Convert BGT to BERA and send it to the staking rewards vault using `redeemBGT()` + +Public BGT operations (anyone can call): + +- **Queue Boost**: Any address can call `queueBoost()` to queue unboosted BGT for boosting to your validator +- **Activate Boost**: Any address can call `activateBoost()` to activate a queued boost +- **Drop Boost**: Any address can call `dropBoost()` to execute a queued drop boost + +**Protocol Fee on BGT Balance:** + +The `protocolFeePercentage` (up to 20% maximum) is charged on the SmartOperator's chargeable BGT balance. This fee is separate from your validator commission. + +- Controlled by addresses with `PROTOCOL_FEE_MANAGER_ROLE` (managed by VALIDATOR_ADMIN) +- Charged on new BGT earned since last fee calculation +- Fees are minted as stBERA shares to your validator (the default share recipient) +- Call `accrueEarnedBGTFees()` to trigger fee calculation and minting + +**Incentive Token Flow:** + +The incentive token system involves multiple steps and two types of rewards: + +**Type 1: Validator Commission on Incentive Tokens** + +1. **Distribution**: When `distributeFor` runs for your validator, RewardVault processes incentives and calculates your commission share (based on your validator commission rate, 0-20%) +2. **Receipt**: Your commission share of incentive tokens is automatically sent to SmartOperator +3. **Accumulation**: Tokens accumulate in SmartOperator until you manually forward them by calling `claimBoostRewards()`. This function claims any accumulated rewards and transfers all tokens from SmartOperator to IncentiveCollector, where they become available for stakers to claim via the incentive auction mechanism. + +**Type 2: BGT Staking Rewards (HONEY)** + +1. **Source**: Protocol fees collected from Berachain dApps (such as BEX trading fees) are auctioned for HONEY via the FeeCollector contract. Anyone can claim accumulated protocol fees by paying a fixed amount of HONEY (the payout amount). +2. **Distribution**: When protocol fees are claimed, the HONEY payment is sent to BGTStaker, which distributes it proportionally to all BGT stakers based on their staked BGT balance. +3. **Accumulation**: HONEY rewards accumulate in BGTStaker for your SmartOperator address (since SmartOperator holds BGT) +4. **Claiming**: Handled automatically by `claimBoostRewards()` + +**Forwarding to IncentiveCollector:** + +Tokens accumulate in SmartOperator and must be manually forwarded to IncentiveCollector before stakers can claim them. Use `claimBoostRewards()` to forward both HONEY rewards from BGT staking and incentive tokens from the boost program to IncentiveCollector in a single operation. + +**Incentive Auction Mechanism (Permissionless Claiming):** + +1. **Accumulation**: Tokens accumulate in IncentiveCollector from SmartOperator forwarding operations +2. **Permissionless Incentive Auction**: Anyone can call `claim()` with the payout amount (default 100 BERA) to claim **all** accumulated tokens. The buyer may be you (the operator), an arbitrageur monitoring the network, or any other address that finds the pool profitable +3. **Distribution**: The net payout amount (payoutAmount - protocol fee) is sent to StakingRewardsVault for distribution to shareholders and auto-compounded +4. **Protocol Fee**: A fee (based on `protocolFeePercentage`) is deducted from the payout amount and minted as shares to your validator's `defaultShareRecipient` + +**Key Points:** + +Tokens do not automatically flow to IncentiveCollector, so manual forwarding is required before they can be claimed. The incentive auction mechanism is winner-takes-all, meaning the first buyer to pay the payout amount receives all accumulated tokens. There is no partial claiming available. See [Incentive Collector Payout Requirements](#incentive-collector-payout-requirements) for details on managing payout amounts and checking available balances. + +**Validator Commission vs Protocol Fee:** + +These are two separate fee mechanisms that work independently: + +| Fee Type | Range | Where Set | What It Applies To | Controlled By | +| ------------------------ | ----- | -------------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------ | +| **Validator Commission** | 0-20% | Configured via SmartOperator, enforced by BeraChef | Incentive token distribution from PoL rewards | `COMMISSION_MANAGER_ROLE` via `queueValCommission()` | +| **Protocol Fee** | 0-20% | SmartOperator | BGT balance growth | `PROTOCOL_FEE_MANAGER_ROLE` via `setProtocolFeePercentage()` | + +Both fee structures can be configured independently based on your operational needs. + +### Reward Management + +The pool automatically handles reward collection and distribution: + +```solidity +// Receive rewards from staking rewards vault +function receiveRewards() external payable; + +// Mint fee shares for validator +function mintFeeShares(uint256 amount) external; +``` + +Rewards are collected automatically from the consensus layer as they accrue. Validator fees are minted as shares to the default recipient. All rewards are automatically reinvested through auto-compounding, maximizing returns without manual intervention. + +### Incentive Collector Payout Requirements + +The IncentiveCollector contract requires buyers to pay a specific amount (default: 100 BERA) when claiming incentive tokens. This mechanism ensures buyers contribute to the pool's rewards while retrieving accumulated incentives. + +**How It Works:** + +1. Tokens accumulate in IncentiveCollector from SmartOperator forwarding operations +2. Anyone can call `claim()` with the payout amount to claim **all** accumulated tokens +3. Protocol fees are deducted from the payout and minted as shares to your validator +4. The net payout amount is sent to StakingRewardsVault for distribution and auto-compounding + +**Managing Payout Amounts:** + +```solidity +// Queue a new payout amount (requires INCENTIVE_COLLECTOR_MANAGER_ROLE) +function queueIncentiveCollectorPayoutAmountChange(uint256 newPayoutAmount) external; +``` + +**Key Considerations:** Balance your emissions versus `payoutAmount` to avoid selling valuable incentives for too little. Higher voting power produces more blocks and emissions. If `payoutAmount` is too low relative to emissions, you may sell valuable tokens below market value. Higher amounts may discourage buyers but contribute more to pool rewards. + +**Helper Scripts:** + +Additional helper scripts are available in the [install-helpers directory](https://github.com/berachain/guides/tree/main/apps/staking-pools/install-helpers) for operations like requesting withdrawals (`unstake.sh`), managing SmartOperator contracts interactively, and other utilities. See the [install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md) for complete documentation of all available scripts, including detailed usage instructions for the Smart Operator Manager Python tool. + +## Advanced Topics + +### Full Exit Management + +When your validator fully exits, the withdrawal system handles the transition: + +```solidity +// Notify of full exit from consensus layer +function notifyFullExitFromCL(bytes memory pubkey) external; +``` + +The full exit process operates automatically once triggered. When your validator fully exits from the consensus layer, BGT tokens are automatically redeemed, queued boost operations are cancelled, and pending withdrawals continue processing normally. Once complete, the pool is marked as fully exited, preventing new deposits while allowing existing withdrawals to conclude. + +The system automatically triggers a full exit in two scenarios: + +- When a withdrawal would cause `totalDeposits` to fall below `minEffectiveBalance()` +- When the withdrawal amount exceeds total deposits (which can occur when share values have appreciated significantly from accumulated rewards) + +### Withdrawal System + +The centralized WithdrawalVault contract handles all withdrawal operations for all staking pools. The withdrawal system provides two processing paths: + +| Path | When It Occurs | Processing Time | +| ----------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | +| **Short-Circuit** | Pool hasn't reached active threshold and has sufficient buffered funds | Funds immediately transferred to WithdrawalVault, but still requires full delay before finalization | +| **Standard** | Pool has reached active threshold or lacks sufficient buffered funds | Requires consensus layer processing (256 epochs ≈ 27 hours) | + +**Important:** Regardless of which path is taken, stakers must wait the full withdrawal delay (129,600 blocks ≈ 3 days at ~2s block time) before finalization. + +**Withdrawal Functions:** + +```solidity +// Request withdrawal of specific BERA amount +function requestWithdrawal( + bytes memory pubkey, + uint64 assetsInGWei, + uint256 maxFeeToPay +) external payable returns (uint256); + +// Request redemption of specific share amount +function requestRedeem( + bytes memory pubkey, + uint256 shares, + uint256 maxFeeToPay +) external payable returns (uint256); + +// Finalize a single withdrawal request +function finalizeWithdrawalRequest(uint256 requestId) external; + +// Finalize multiple withdrawal requests +function finalizeWithdrawalRequests(uint256[] calldata requestIds) external; +``` + +**Withdrawal Request NFTs:** Each withdrawal request is represented by an ERC721Enumerable NFT ("Berachain Staking Pool Withdrawal Request", symbol "BSPWR"). The request ID returned from withdrawal functions is the same as the NFT token ID. These NFTs are non-transferable and serve as proof of the withdrawal request throughout its lifecycle. + +### Building Your Front-End + +Your front-end should provide a seamless staking experience, abstracting technical details while clearly showing each staker's position and withdrawal status. The sections below describe the requirements your front-end must meet. Berachain provides a React-based example template in the [guides repository](https://github.com/berachain/guides/tree/main/apps/staking-pools/frontend) that you can use as a starting point, but you should customize it for your needs. + +#### Withdrawal Request Management + +When stakers request a withdrawal, they receive an ERC721 NFT (token name "Berachain Staking Pool Withdrawal Request", symbol "BSPWR") representing their withdrawal request. The withdrawal request ID returned from `requestWithdrawal()` or `requestRedeem()` is the same as the NFT token ID. Your front-end should: + +**Display Withdrawal Status:** + +- Show all pending withdrawal requests for the connected wallet +- Display the withdrawal amount and request timestamp +- Calculate and display when each withdrawal will be ready to finalize +- Use `getWithdrawalRequest(requestId)` to retrieve withdrawal details including the `requestBlock` field +- Calculate readiness by checking if `block.number >= (request.requestBlock + 129600)` (129,600 blocks ≈ 3 days at ~2s block time) +- Show a countdown timer or status indicator for pending withdrawals + +**Handle Withdrawal Finalization:** + +- Automatically detect when withdrawals are ready to finalize +- Provide a clear "Complete Withdrawal" or "Finalize" button when ready +- Support batch finalization using `finalizeWithdrawalRequests([requestId1, requestId2, ...])` for stakers with multiple pending withdrawals +- Show confirmation dialogs before finalizing +- Display transaction status and completion + +**NFT Integration:** + +- Use standard ERC721Enumerable functions to query staker's withdrawal NFTs: + - `balanceOf(stakerAddress)` to get the count of pending withdrawals + - `tokenOfOwnerByIndex(stakerAddress, index)` to enumerate withdrawal request IDs + - `ownerOf(requestId)` to verify ownership +- Display withdrawal NFTs in the staker's wallet if they use NFT-aware wallets +- Note that these NFTs are non-transferable (they revert on transfer attempts) + +Your front-end should handle both withdrawal processing paths transparently. See [Withdrawal System](#withdrawal-system) for details on short-circuit vs standard paths. Regardless of which path is taken, stakers must wait the full withdrawal delay (129,600 blocks ≈ 3 days at ~2s block time) before finalization. + +#### Staker Balance and Position Display + +Your front-end should clearly display: + +- Current BERA balance (including accrued rewards) +- Share balance (stBERA tokens) +- Current share price/value +- Total rewards earned since deposit +- Pool performance metrics (total assets, validator uptime, etc.) + +Use `previewRedeem(shares)` and `previewWithdraw(assets)` to show stakers what they'll receive before they initiate withdrawals. + +**Calculating Total Rewards Earned:** + +Since rewards are automatically compounded into share price, calculate total rewards by comparing the current value of shares to original deposits. Track each deposit amount (which you'll need for transaction history anyway), then: `totalRewardsEarned = previewRedeem(currentShares) - sumOfAllDeposits`. + +#### Error Handling and Staker Communication + +Your front-end should handle and clearly communicate: + +| Error | Condition | Front-End Action | +| ----------------------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | +| `RequestNotReady` | Staker tries to finalize before delay completes (129,600 blocks ≈ 3 days at ~2s block time) | Check `request.requestBlock + 129600 <= block.number` before allowing finalization | +| `NotEnoughFunds` | WithdrawalVault doesn't have enough funds for finalization | Check vault balance before attempting finalization | +| `MaxCapacityReached` | Pool has reached maximum capacity | Check if deposits are paused before allowing new deposits | +| `StakingPoolFullExited` | Pool has triggered full exit | Deposits disabled, withdrawals continue normally | + +**Note**: Withdrawals are not disabled during the cooldown period after activation. Stakers can request withdrawals at any time, but they cannot finalize them until the delay period completes (129,600 blocks ≈ 3 days after the request was made). + +#### Using the Frontend Template + +Berachain provides a React-based example template to help you get started building your staking pool front-end. This template is a **starting point and example**—not a production-ready solution. You should customize it to match your branding, add additional features, and ensure it meets your security and UX requirements. + +The template is available in the [Berachain guides repository](https://github.com/berachain/guides/tree/main/apps/staking-pools/frontend). For detailed setup instructions, see the [frontend README](https://github.com/berachain/guides/blob/main/apps/staking-pools/frontend/README.md). + +**Quick Start:** + +1. Clone the repository and navigate to the frontend directory: + + ```bash + git clone https://github.com/berachain/guides.git + cd guides/apps/staking-pools/frontend + ``` + +2. Install dependencies: + + ```bash + npm install + ``` + +3. Configure the frontend by editing `config.json` or using the helper script (see below). + +**Configuration:** + +The frontend requires a `config.json` file that specifies: + +- **Network settings**: `rpcUrl`, `chainId`, `explorerUrl` +- **Contract addresses**: `withdrawalVault` (required), `stakingPoolFactory` (optional) +- **Pool configuration**: One or more pools with `name`, `validatorPubkey`, `stakingPool`, `enabled` + +**Generate Configuration Automatically:** + +Use `generate-frontend-config.sh` from the `install-helpers` directory to automatically generate `config.json` from your environment and factory contract lookups: + +```bash +cd ../install-helpers +./generate-frontend-config.sh +``` + +This script reads your `env.sh` configuration and queries the factory contract to generate a complete `config.json` file for your frontend. + +**Development and Deployment:** + +- Start development server: `npm run dev` +- Build for production: `npm run build` +- Preview production build: `npm run preview` + +For external access, run: `npm run dev -- --host 0.0.0.0 --port 3000` + +**Tech Stack:** + +The template uses React 18, Vite, Viem, and MetaMask for wallet connections. You can modify or replace any of these components based on your needs. + +## Troubleshooting + +### Pool Activation Issues + +**Problem: Pool won't activate after deployment** + +**Debug Steps:** + +1. **Check Pool Status**: Call `isActive()` on your StakingPool contract +2. **Verify Proofs**: Ensure all proofs are recent (within 10 minutes) and from correct validator index +3. **Check Withdrawal Credentials**: Verify they match the expected withdrawal system address +4. **Validate Initial Deposit**: Confirm exactly 10,000 BERA was sent with deployment + +**Debugging Commands:** + +```solidity +// Check if pool is active +bool active = stakingPool.isActive(); + +// Get validator pubkey +bytes memory pubkey = stakingPool.getValidatorPubkey(); + +// Check minimum effective balance +uint256 minBalance = stakingPool.minEffectiveBalance(); +``` + +### BGT and Rewards Issues + +**Problem: BGT operations not working or rewards not accruing** + +**Debug Steps:** + +1. **Check BGT Balance**: Call `unboostedBalance()` on SmartOperator +2. **Verify Fee State**: Use `getEarnedBGTFeeState()` to check fee calculations +3. **Monitor Boost Status**: Check if boosts are queued or active +4. **Activate your boosts**: Nobody else will +5. **Verify Protocol Fees**: Ensure protocol fee percentage is set correctly + +**Debugging Commands:** + +```solidity +// Check BGT fee state +(uint256 currentBalance, uint256 alreadyCharged, uint256 chargeable, uint96 feePercent) = + smartOperator.getEarnedBGTFeeState(); + +// Check unboosted BGT balance +uint256 unboosted = smartOperator.unboostedBalance(); + +// Check rebaseable BGT amount +uint256 rebaseable = smartOperator.rebaseableBgtAmount(); +``` + +### Withdrawal Issues + +**Problem: Stakers reporting withdrawal problems** + +**Debug Steps:** + +1. **Check Withdrawal Requests**: Use `getWithdrawalRequest(requestId)` to examine specific requests +2. **Verify Processing Time**: Confirm the finalization window has passed for standard withdrawals (256 epochs × 192 blocks/epoch ≈ 27 hours, or 129,600 blocks ≈ 3 days at ~2s block time total delay) +3. **Check Pool Buffer**: Verify sufficient funds available for short-circuit withdrawals +4. **Monitor Withdrawal Events**: Watch for `WithdrawalRequested` and `WithdrawalRequestFinalized` events + +**Debugging Commands:** + +```solidity +// Get withdrawal request details +WithdrawalRequest memory request = withdrawalVault.getWithdrawalRequest(requestId); +``` + +For detailed technical information about the smart contracts, see the [Smart Contract Reference](/nodes/staking-pools/contracts). + +## Going to Market + +Before launching your staking pool to stakers, ensure you have completed the following checklist items to provide a smooth and reliable staking experience. + +### Initial Configuration + +Configure essential parameters before accepting staker deposits: + +- **Set Minimum Effective Balance**: Call `setMinEffectiveBalance()` on your SmartOperator contract to match the current consensus layer requirements. This ensures your pool activates correctly and doesn't exit prematurely. See the [Setting Minimum Effective Balance](/nodes/staking-pools/operators#setting-minimum-effective-balance) section for details on determining the correct value. + +- **Set Protocol Fee Percentage**: Call `setProtocolFeePercentage()` on your SmartOperator contract to configure the protocol fee charged on BGT balance growth. This fee is minted as shares to your validator and can be set up to 20%. + +### Reward Claiming Automation + +Set up automated bots to forward accumulated rewards to IncentiveCollector: + +- **Reward Claiming Bot**: Deploy a bot that calls `claimBoostRewards()` on your SmartOperator contract at least once daily. This function forwards both HONEY rewards from BGT staking and incentive tokens from the boost program to IncentiveCollector, where they become available for stakers to claim via the incentive auction mechanism. Regular forwarding ensures stakers have access to accumulated rewards. + +### Front-End Interface + +Develop or deploy a staker-facing interface that supports: + +- **Deposits**: Allow stakers to deposit BERA and receive stBERA shares +- **Position Display**: Show stakers their current BERA balance (including accrued rewards), share balance, share price, and total rewards earned +- **Withdrawal Requests**: Enable stakers to request withdrawals by amount or by shares +- **Withdrawal Finalization**: Display pending withdrawal requests, calculate when they're ready to finalize (after 129,600 blocks ≈ 3 days at ~2s block time), and allow stakers to complete withdrawals + +For detailed front-end requirements, see the [Building Your Front-End](/nodes/staking-pools/operators#building-your-front-end) section. + +### Cutting Board Updates + +If you're participating in the cutting board system, deploy bots that update your cutting board information weekly to keep your validator information current for stakers. + +### Foundation Delegation + +If you have received a delegation from the Berachain Foundation, you need to manually call the yield harvester function to process delegation-related operations. This is not an automated bot operation and should be handled by the validator admin when needed. + +## Delegation System + +Some validators are issued a delegation of funds from the Berachain Foundation. For detailed information about working with delegated funds, see the [Delegators Guide](/nodes/staking-pools/delegators) and the [DelegationHandler contract reference](/nodes/staking-pools/contracts/DelegationHandler). diff --git a/apps/core/content/nodes/validator-lifecycle.md b/apps/core/content/nodes/validator-lifecycle.md index f0f125d2..5640cabb 100644 --- a/apps/core/content/nodes/validator-lifecycle.md +++ b/apps/core/content/nodes/validator-lifecycle.md @@ -8,6 +8,8 @@ A validator in Berachain is a participant responsible for proposing and attesting to new blocks, helping secure the network and maintain consensus. Validators stake a required amount of the network's native token ($BERA) as collateral, which serves both as an economic incentive to behave honestly and as a mechanism for penalizing malicious behavior. +Validators can operate independently with direct staking, or they can operate **staking pools** that allow community members to stake BERA through smart contracts and receive liquid shares (stBERA). Staking pools follow the same validator lifecycle described in this document. For information about operating staking pools, see the [Staking Pools documentation](/nodes/staking-pools). + Validators have several key responsibilities: - Proposing new blocks when selected diff --git a/packages/config/constants.json b/packages/config/constants.json index c2db8ff8..5a06e2d2 100644 --- a/packages/config/constants.json +++ b/packages/config/constants.json @@ -406,6 +406,35 @@ "docsUrl": "/developers/contracts/reward-vault" } }, + "stakingPools": { + "stakingPoolContractsFactory": { + "name": "StakingPoolContractsFactory", + "address": { + "berachainMainnet": "0xb79b43dBA821Cb67751276Ce050fF4111445fB99", + "berachainBepolia": "0x176c081E95C82CA68DEa20CA419C7506Aa063C24" + }, + "abi": "", + "docsUrl": "/nodes/staking-pools/contracts/StakingPoolContractsFactory" + }, + "withdrawalVault": { + "name": "WithdrawalVault", + "address": { + "berachainMainnet": "0xE858802Ed532C6DAD2D196AB5B1F2C15F9cb52b4", + "berachainBepolia": "0xAFAc2f11Cb39F0521b22494F6101002ce653D2f4" + }, + "abi": "", + "docsUrl": "/nodes/staking-pools/contracts/WithdrawalVault" + }, + "delegationHandlerFactory": { + "name": "DelegationHandlerFactory", + "address": { + "berachainMainnet": "0xAd17932a5B1aaeEa73D277a6AE670623F176E0D0", + "berachainBepolia": "0x8b472791aC2f9e9Bd85f8919401b8Ce3bdFd464c" + }, + "abi": "", + "docsUrl": "/nodes/staking-pools/contracts/DelegationHandlerFactory" + } + }, "tokens": { "bgt": { "name": "BGT Token", diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..71aed71f --- /dev/null +++ b/tests/README.md @@ -0,0 +1,63 @@ +# Documentation Tests + +Tests for verifying rendered documentation output. + +## Setup + +Install test dependencies: + +```bash +cd docs +pip install -r tests/requirements.txt +``` + +Or use the workspace virtual environment: + +```bash +source ~/src/.venv/bin/activate +pip install -r tests/requirements.txt +``` + +## Usage + +### Build Mode (Recommended for CI) + +Build the docs first, then run tests against static HTML: + +```bash +pnpm build +pytest tests/test_abi_references.py +``` + +The build output is located in: + +- `apps/core/.vitepress/dist/` - Core docs HTML +- `apps/bex/.vitepress/dist/` - BEX docs HTML +- `apps/bend/.vitepress/dist/` - Bend docs HTML + +Tests will automatically find the built HTML files in these directories. + +### Dev Server Mode (For Local Testing) + +Start the dev server in one terminal: + +```bash +pnpm dev +# Server runs on http://localhost:55173 (core) +# Other apps may run on different ports +``` + +In another terminal, run tests: + +```bash +pytest tests/test_abi_references.py --dev-server http://localhost:55173 +``` + +Note: VitePress uses clean URLs (no `.html` extension), so the test handles both formats. + +## Test Coverage + +- Verifies ABI references are present on all deployed contracts pages +- Checks that mainnet and testnet ABI links are present and correct +- Validates that links are accessible +- Ensures mainnet and testnet ABIs are clearly distinguished diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 00000000..f7811784 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,4 @@ +pytest>=7.0.0 +requests>=2.28.0 +beautifulsoup4>=4.11.0 +lxml>=4.9.0 diff --git a/tests/test_abi_references.py b/tests/test_abi_references.py new file mode 100644 index 00000000..9e18a112 --- /dev/null +++ b/tests/test_abi_references.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +""" +Test script to verify ABI references in rendered documentation. + +This script can work in two modes: +1. Build mode: Builds the docs and checks the static HTML output +2. Dev server mode: Starts dev server and fetches pages (requires manual server start) + +Usage: + # Build mode (recommended for CI) + pnpm build + pytest tests/test_abi_references.py + + # Dev server mode (for local testing) + pnpm dev & # Start in background + pytest tests/test_abi_references.py --dev-server http://localhost:55173 +""" + +import pytest +import requests +from pathlib import Path +from bs4 import BeautifulSoup +from urllib.parse import urljoin, urlparse + + +class TestABIReferences: + """Test that ABI references are present and correct in rendered docs.""" + + @pytest.fixture + def base_url(self, request): + """Get base URL from pytest option or default to build output.""" + dev_url = request.config.getoption("--dev-server", default=None) + if dev_url: + return dev_url.rstrip("/") + + # Default to build output - check if at least one dist directory exists + docs_root = Path(__file__).parent.parent + build_dirs = [ + docs_root / "apps" / "core" / ".vitepress" / "dist", + docs_root / "apps" / "bex" / ".vitepress" / "dist", + docs_root / "apps" / "bend" / ".vitepress" / "dist" + ] + if not any(d.exists() for d in build_dirs): + pytest.skip("Build output not found. Run 'pnpm build' first or use --dev-server") + return None # Will use file paths instead + + @pytest.fixture + def pages_to_test(self): + """List of pages that should contain ABI references.""" + docs_root = Path(__file__).parent.parent + return [ + { + "path": "/developers/deployed-contracts", + "file": docs_root / "apps/core/.vitepress/dist/developers/deployed-contracts.html", + "name": "Core deployed contracts" + }, + { + "path": "/developers/index", + "file": docs_root / "apps/bex/.vitepress/dist/developers/index.html", + "name": "BEX deployed contracts" + }, + { + "path": "/developers/deployed-contracts", + "file": docs_root / "apps/bend/.vitepress/dist/developers/deployed-contracts.html", + "name": "Bend deployed contracts" + } + ] + + def get_html(self, base_url, page_info): + """Get HTML content from either dev server or build output.""" + if base_url: + # Dev server mode - VitePress uses clean URLs (no .html extension) + url = urljoin(base_url, page_info["path"]) + response = requests.get(url, timeout=5) + response.raise_for_status() + return response.text + else: + # Build output mode - files are .html in dist folder + file_path = page_info["file"] + if not file_path.exists(): + pytest.skip(f"Build output not found: {file_path}. Run 'pnpm build' first.") + return file_path.read_text(encoding="utf-8") + + def test_abi_references_present(self, base_url, pages_to_test): + """Test that ABI references are present on all deployed contracts pages.""" + for page_info in pages_to_test: + html = self.get_html(base_url, page_info) + soup = BeautifulSoup(html, "html.parser") + + # Find the blockquote containing ABI references + blockquotes = soup.find_all("blockquote") + abi_blockquote = None + + for bq in blockquotes: + text = bq.get_text() + if "Contract ABIs" in text and "Mainnet ABIs" in text: + abi_blockquote = bq + break + + assert abi_blockquote is not None, \ + f"ABI references blockquote not found on {page_info['name']}" + + def test_mainnet_abi_link(self, base_url, pages_to_test): + """Test that mainnet ABI link is present and correct.""" + expected_url = "https://github.com/berachain/abis/tree/main/mainnet/contracts" + + for page_info in pages_to_test: + html = self.get_html(base_url, page_info) + soup = BeautifulSoup(html, "html.parser") + + # Find link to mainnet ABIs + links = soup.find_all("a", href=True) + mainnet_link = None + + for link in links: + if expected_url in link.get("href", ""): + mainnet_link = link + break + + assert mainnet_link is not None, \ + f"Mainnet ABI link not found on {page_info['name']}" + + # Verify link text is descriptive + link_text = mainnet_link.get_text(strip=True) + assert "abis" in link_text.lower() or "mainnet" in link_text.lower(), \ + f"Mainnet ABI link text should be descriptive on {page_info['name']}" + + def test_testnet_abi_link(self, base_url, pages_to_test): + """Test that testnet/documentation ABI link is present and correct.""" + expected_url = "https://github.com/berachain/doc-abis" + + for page_info in pages_to_test: + html = self.get_html(base_url, page_info) + soup = BeautifulSoup(html, "html.parser") + + # Find link to testnet/doc-abis + links = soup.find_all("a", href=True) + testnet_link = None + + for link in links: + if expected_url in link.get("href", ""): + testnet_link = link + break + + assert testnet_link is not None, \ + f"Testnet/Documentation ABI link not found on {page_info['name']}" + + def test_abi_links_accessible(self): + """Test that ABI repository links are accessible.""" + urls = [ + "https://github.com/berachain/abis/tree/main/mainnet/contracts", + "https://github.com/berachain/doc-abis" + ] + + for url in urls: + response = requests.head(url, timeout=10, allow_redirects=True) + assert response.status_code == 200, \ + f"ABI repository link not accessible: {url} (status: {response.status_code})" + + def test_abi_references_distinction(self, base_url, pages_to_test): + """Test that mainnet and testnet ABIs are clearly distinguished.""" + for page_info in pages_to_test: + html = self.get_html(base_url, page_info) + soup = BeautifulSoup(html, "html.parser") + + # Get all text content + text = soup.get_text() + + # Should contain both labels + assert "Mainnet ABIs" in text, \ + f"'Mainnet ABIs' label not found on {page_info['name']}" + assert "Testnet" in text or "Documentation" in text, \ + f"Testnet/Documentation label not found on {page_info['name']}" + + +def pytest_addoption(parser): + """Add command-line option for dev server URL.""" + parser.addoption( + "--dev-server", + action="store", + default=None, + help="Base URL of dev server (e.g., http://localhost:55173). If not provided, uses build output." + )