Oracle Vault Staking Contract Manual
Contents of Contract Manual
3. Contract Lifecycle & Epoch Logic 3.1 Epoch Duration & Alignment with Flare 3.2 Chunk‐Based Activation (Pending → Active) 3.3 Reward Distribution & Finalization 3.4 Retention & Housekeeping
4. User Actions 4.1 Staking FOTON 4.2 Reducing / Unstaking 4.3 Computing & Withdrawing Rewards 4.4 Bulk Reward Claims & “Claim‐All” Operations 4.5 Monthly Airdrop Claims (claimMonthlyAirdropsInRange)
5. Admin & Owner‐Only Actions 5.1 Manually Advancing Epoch 5.2 RewardManager Integration & Best‑Effort Synchronization 5.3 Timelocked Emergency Withdrawals 5.4 Delegation Management & Miscellaneous Functions
7. Data Structures & Storage 7.1 Epoch Tracking 7.2 Stakes 7.3 Rewards 7.4 Timelock 7.5 Miscellaneous
8. Deployment & Initialization
9. Example User Journeys 9.1 Single User Staking Flow 9.2 Multiple Users & Chunk‐Based Activation 9.3 Emergency Timelock Scenario
10. Error Conditions & Reverts
Coming Soon! Tests Ongoing!
DO NOT Interact With This Contract Until Given Notice!
1. Overview
Oracle Vault is an epoch‑based staking contract developed by Flareporium that interacts with Flare’s RewardManager to claim WNAT delegation rewards and distributes them to FOTON stakers. It distributes both WNAT and (optionally) FOTON rewards to stakers, and synchronizes local epoch data with the official RewardManager. Key features include:
Best-Effort Synchronization: Uses a try/catch mechanism to synchronize epochs without reverting the entire transaction on partial failure. Errors are logged via the
LogError
event.Chunk‐Based Activation: Pending stakers are activated in batches (up to 250 addresses per call) to mitigate gas issues.
Dual Reward Tracking: The contract separately tracks WNAT and FOTON rewards using a fixed-point reward multiplier (1e12) to ensure precise fractional calculations.
Timelocked Emergency Withdrawals: Admin functions for emergency withdrawals are subject to a 2‑day timelock, enhancing security.
Pausable Operations: User‑facing functions (staking, unstaking, claiming) can be paused in emergencies.
2. Key Concepts & Terminology
Epoch: A discrete period tracked by the contract, synchronized with Flare’s RewardManager. A new epoch is triggered when the official epoch increases.
Pending Stake: When a user calls
stakeFoton
, their FOTON tokens are deposited into a pending queue. They become “active” after epoch synchronization.Active Stake: FOTON that is actively earning rewards for the current epoch.
RewardManager: The official Flare contract responsible for delegation rewards (WNAT). OracleVault claims rewards from it.
Forfeiture: If a user reduces their active stake mid‑epoch via
reduceStakeFoton
, the reduced amount forfeits its share of that epoch’s rewards.Scaling Factor: A multiplier of 1e12 is used in reward calculations (accumulated reward per share) to maintain precision.
Retention: The contract retains epoch data for 26 epochs (defined by
EPOCH_RETENTION
), after which old data is pruned.Best‑Effort Synchronization: A mechanism that attempts to update epoch data using try/catch blocks.
Custom Errors: The contract uses custom error types (e.g.,
ZeroAmount()
,BelowMinStake()
) for efficient revert messages.
3. Contract Lifecycle & Epoch Logic
3.1 Epoch Duration & Alignment with Flare
OracleVault does not use a fixed timer; instead, it periodically checks
rewardManager.getCurrentRewardEpochId()
.Important: In order for the local epoch to advance, OracleVault must first successfully claim FTSO delegation rewards from the RewardManager. The contract calls the
claim(...)
function to process these rewards, and only upon successful processing of the claimable rewards does it update its local epoch data.When a new official epoch is detected (i.e., when the official epoch exceeds
lastFlareEpoch
and rewards have been successfully claimed), the contract:Finalizes the current local epoch (distributing rewards),
Updates
lastFlareEpoch
andcurrentEpochId
,Sets
lastFinalizedEpochId
to the previous epoch.
3.2 Chunk‐Based Activation (Pending → Active)
New stakers are added to the
pendingStake
mapping andpendingStakers
array viastakeFoton
.During synchronization, the internal function
_activatePendingStakers()
processes up to 250 addresses per call:Pending amounts are transferred to active stake,
Global totals (
totalActiveStake
) and per-user epoch snapshots (userEpochStake
) are updated,Reward debt values for both WNAT and FOTON are computed for the upcoming epoch.
3.3 Reward Distribution & Finalization
General Process:
The contract determines new rewards from the RewardManager.
These rewards are then distributed among stakers.
The process now covers both WNAT and FOTON rewards.
WNAT Rewards:
Collection:
The contract calls the RewardManager’s
claim(...)
function to collect delegation rewards in WNAT.
Distribution:
The collected WNAT rewards are allocated across stakers.
Allocation uses an accumulated reward per share variable to ensure precise fractional reward calculations.
FOTON Rewards:
Finalization Update:
In addition to claiming WNAT rewards, the updated contract now finalizes FOTON rewards at the end of an epoch.
Process Details:
After pending stakers are activated and WNAT rewards are processed:
The contract calls the new function
_addFotonRewardsToEpoch(currentEpochId - 1)
.
Function Responsibilities:
Surplus Calculation:
Calculates the “surplus” of FOTON by subtracting the total staked FOTON (i.e.,
totalFotonStaked
) from the contract’s overall FOTON balance.
Recycled Rewards Addition:
Adds any recycled rewards obtained by the new
_computeRecycledFotonRewards()
function.
Updating Reward Metrics:
The total (direct surplus + recycled rewards) is used to update the epoch’s accumulated FOTON reward per share (
epochAccFotonRewardPerShare
).This ensures stakers receive their proportional share of FOTON rewards.
3.4 Retention & Housekeeping
The contract retains detailed data for the most recent 26 epochs, as defined by the constant
EPOCH_RETENTION
. After 26 epochs, older data is pruned to save storage. Users must claim rewards within this retention window to receive their full allocation.FOTON Rewards Recycling: In the updated contract, FOTON rewards that remain unclaimed beyond the 26-epoch retention period are not discarded. Instead, they are recycled and incorporated into the next epoch’s reward calculation. This recycling is handled automatically during epoch finalization, so stakers benefit from any surplus FOTON available—even if some rewards were not claimed in earlier epochs.
4. User Actions
4.1 Staking FOTON
Process:
Approve the contract to spend your FOTON.
Call
stakeFoton(uint256 amount)
with an amount ≥minStakeAmount
(and nonzero).The tokens are transferred to the contract and recorded in
pendingStake
andpendingStakers
.Total staked tokens (
totalFotonStaked
) are updated.
Reverts:
If
amount < minStakeAmount
→ custom errorBelowMinStake()
.If
amount == 0
→ custom errorZeroAmount()
.
4.2 Reducing / Unstaking
Reducing Stake (reduceStakeFoton):
Call
reduceStakeFoton(uint256 reduceAmt)
to withdraw part of your active stake.The function deducts
reduceAmt
fromactiveStake
, updatestotalActiveStake
, and records forfeiture (inforfeitedStakeThisEpoch
).The withdrawn tokens are transferred back to your wallet.
Full Unstaking (unstakeFoton):
Call
unstakeFoton()
to withdraw your entire active stake. This internally callsreduceStakeFoton
with your full active balance.
Note: Any stake reduced mid‑epoch forfeits its share of rewards for that epoch.
4.3 Computing & Withdrawing Rewards
Reward Computation: Functions such as
computeEpochReward(uint256 epochId)
and internal methods (e.g.,_ensureEpochRewardsComputed
) calculate your pending rewards for a specific epoch based on:Your effective stake (active stake minus any forfeited amount),
The epoch’s accumulated reward per share (for both WNAT and FOTON), and
Your reward debt.
Withdrawing Rewards:
Use
withdrawEpochReward(uint256 epochId)
to transfer your computed rewards to your wallet.Rewards for both WNAT and FOTON (if applicable) are processed separately.
4.4 Bulk Reward Claims & “Claim‐All” Operations
Bulk Claiming:
Call
claimAllEpochRewards(uint256 startEpoch, uint256 endEpoch)
to claim rewards for a range of epochs.Alternatively, use
claimAllForUser()
orclaimAndFinalizeAll()
as convenience functions to claim all unclaimed rewards within the retention window.
Note: Reward data is maintained only for the last 26 epochs.
4.5 Monthly Airdrop Claims (claimMonthlyAirdropsInRange)
Process:
The function
claimMonthlyAirdropsInRange(address _rewardOwner, uint256 startMonth, uint256 endMonth, bool _wrap)
allows you to claim a series of monthly airdrops in one call.The function loops over the specified month range and attempts to claim for each month. Partial failures do not revert the entire transaction.
Outcome: Claimed amounts are added to the contract’s balance (and may be auto-wrapped into WNAT if
_wrap
is false).
5. Admin & Owner‐Only Actions
5.1 Manually Advancing Epoch
Auto‑Advance Function:
Call
autoAdvanceEpochPublic()
(available to anyone) to trigger epoch synchronization.This function uses the best‑effort synchronization (via the
bestEffortSync
modifier) to update epoch data if a new official epoch is available.
Note: The contract does not expose a separate “advanceEpoch” function; epoch updates occur as part of routine calls.
5.2 RewardManager Integration & Best‑Effort Synchronization
The synchronization process between OracleVault and Flare’s RewardManager is executed via the internal function _fullSynchronize()
. This process includes:
Retrieving the current official epoch using
getCurrentRewardEpochId()
and determining the next claimable epoch withgetNextClaimableRewardEpochId()
.Iterating over claimable epochs and calling
claim(...)
to collect WNAT rewards.During the synchronization routine, after processing WNAT rewards, the contract updates the finalized epoch’s FOTON rewards by calling
_addFotonRewardsToEpoch()
. This change ensures that both WNAT and FOTON rewards are distributed as part of the epoch finalization process.Activating pending stakers in manageable batches (up to 250 addresses per call) and updating local epoch data accordingly.
Logging any errors encountered during synchronization via the
LogError
event so that partial failures do not revert the entire transaction.
5.3 Timelocked Emergency Withdrawals
Scheduling:
Use
scheduleEmergencyWithdraw(address token, address to, uint256 amount, bool isEther)
to schedule an emergency withdrawal.A 2‑day timelock (defined by
TIMELOCK_DELAY
) is applied.
Cancelling & Executing:
cancelEmergencyWithdraw()
cancels a pending withdrawal.executeEmergencyWithdraw()
finalizes the withdrawal after the timelock expires.
Reverts:
Custom errors (e.g.,
ZeroAddress()
,ActiveRequestExists()
,NoActiveRequest()
,NotYetAllowed()
,EtherTransferFailed()
) are used to enforce conditions.
5.4 Delegation Management & Miscellaneous Functions
Delegation:
delegateWnat(address provider, uint256 bips)
delegates a portion of the vault’s WNAT voting power to a provider.undelegateAll()
removes all active delegations.Note: The previous batch delegation function (
batchDelegate
) has been removed.
Token Redirection:
setYeildVaultAddress(address _redirectAddress)
sets the address where extraneous ERC20 tokens (other than FOTON or WNAT) should be sent.redirectERC20ToYieldVault(address tokenAddress)
moves any such tokens from the contract to the designated yield vault address.
6. Pausable Feature
The contract inherits from OpenZeppelin’s Pausable.
The owner can call
pause()
andunpause()
to stop or resume user‑facing functions (e.g., staking, unstaking, claiming rewards).Administrative functions (like delegation or scheduling emergency withdrawals) remain available while paused.
7. Data Structures & Storage
7.1 Epoch Tracking
Key Variables:
currentEpochId
: The contract’s local epoch, updated to match Flare’s RewardManager.lastFinalizedEpochId
: The most recent epoch for which rewards have been finalized.lastFlareEpoch
: The last official Flare epoch processed.contractStartEpoch
: The epoch at which the contract was deployed.
Aggregation:
Epoch data is used to calculate reward distribution and is pruned after 26 epochs (defined by
EPOCH_RETENTION
).
7.2 Stakes
User Stakes:
pendingStake[user]
: Tokens awaiting activation.activeStake[user]
: Tokens actively earning rewards.forfeitedStakeThisEpoch[epochId][user]
: Tracks amounts reduced mid‑epoch (and thus forfeited rewards).
Global Totals:
totalFotonStaked
: Overall FOTON staked in the contract.totalActiveStake
: Sum of all active stakes.
7.3 Rewards
WNAT Rewards:
epochAccRewardPerShare[epochId]
: Accumulated reward factor for WNAT.userEpochRewards[epochId][user]
anduserRewardDebt[epochId][user]
: Track user-specific WNAT reward allocation.epochTotalDebt[epochId]
: Total WNAT allocated for an epoch.
FOTON Rewards:
Similar mappings exist for FOTON rewards:
epochAccFotonRewardPerShare
,userEpochFotonRewards
,userFotonRewardDebt
, andepochFotonTotalDebt
.
Reward Multiplier:
A scaling factor of 1e12 is used to preserve precision in reward calculations.
7.4 Timelock
Structure:
The
TimelockRequest
struct stores details for emergency withdrawals (token, recipient, amount, earliest execution time, and whether the asset is Ether).
Enforcement:
A constant
TIMELOCK_DELAY
of 2 days ensures a delay before withdrawal execution.
7.5 Miscellaneous
Additional Variables:
Variables such as
oldFotonRewardBalance
,totalDelegationRewards
, and others track historical and cumulative reward data.
Aggregator Functions:
Functions like
getGlobalDataAcrossEpochs
,getRewardManagerUnclaimedEpochsData
, andgetEverything
provide comprehensive views of global and user-specific data.
8. Deployment & Initialization
Deployment:
The OracleVault is deployed with references to the FOTON, WNAT, and RewardManager contracts.
Hard‑coded addresses for these tokens (and for the airdrop distribution contract) are provided.
Initialization:
If
rewardManager.active()
returns true, the contract initializeslastFlareEpoch
,currentEpochId
, andlastFinalizedEpochId
based on the current RewardManager epoch; otherwise, it starts at epoch 1.
Owner Setup:
The owner can subsequently set parameters such as
minStakeAmount
and configure delegation or token redirection.
9. Example User Journeys
9.1 Single User Staking Flow
Approve OracleVault to spend your FOTON.
Call
stakeFoton(amount)
with an amount greater than or equal tominStakeAmount
.Your tokens are added to the pending queue. When a new official epoch is detected, they are activated.
Your active stake earns rewards (both WNAT and, if applicable, FOTON).
You later compute and withdraw your rewards via
computeEpochReward
andwithdrawEpochReward
.
9.2 Multiple Users & Chunk‐Based Activation
Many users call
stakeFoton
, populating thependingStakers
array.During synchronization, up to 250 stakers are activated per call via
_activatePendingStakers()
.If some stakers remain pending, further synchronization calls are needed before finalizing the epoch.
Once all stakers are activated, the epoch finalizes and rewards are allocated accordingly.
9.3 Emergency Timelock Scenario
The owner calls
scheduleEmergencyWithdraw
to initiate an emergency withdrawal (of Ether or an ERC20 token), triggering a 2‑day timelock.During the delay, stakers can monitor and, if necessary, adjust their positions.
If required, the owner may cancel the withdrawal via
cancelEmergencyWithdraw()
.After 2 days,
executeEmergencyWithdraw()
is called to transfer the funds.
10. Error Conditions & Reverts
Custom Errors: The contract employs custom errors (e.g.,
ZeroMinStake()
,ZeroAmount()
,BelowMinStake()
,NotEnoughActiveStake()
,NoFotonArrived()
) for gas‑efficient reverts.Staking:
Staking with zero amount or below
minStakeAmount
will revert.
Unstaking:
Attempts to unstake more than the active stake or unstake when no active stake exists revert.
Epoch & Reward Functions:
Incorrect epoch ranges or calls for unfinalized epochs will revert (e.g.,
InvalidEpochId()
,InvalidEpochRange()
).
Emergency Withdrawals:
Reverts occur if a withdrawal is scheduled with a zero address, if an active request exists, or if executed before the timelock expires.
Airdrop & Delegation:
Reverts for invalid month ranges or for trying to redirect FOTON/WNAT tokens are enforced.
11. Security & Best Practices
Reentrancy Protection: User‑facing functions that move tokens are protected by OpenZeppelin’s ReentrancyGuard.
Pausability: The owner can pause the contract to prevent new stakes or claims during emergencies.
Best‑Effort Synchronization: Epoch data is updated using try/catch to ensure that partial failures do not revert entire transactions. Errors are logged via the
LogError
event.Timelocked Emergency Withdrawals: A 2‑day delay on critical admin withdrawals provides a window for stakers to react.
Accurate Reward Accounting: The contract excludes staked FOTON from reward calculations to prevent double counting. The use of a 1e12 scaling factor ensures precise fractional math.
Data Retention: With epoch data retained for 26 epochs, users should claim rewards promptly to avoid loss due to pruning.
Conclusion
OracleVault aligns with Flare’s official RewardManager to facilitate a robust, epoch‑based staking experience for FOTON holders. It handles:
Auto‑Epoch Transitions: Synchronizes with the official epoch using best‑effort, try/catch–based logic.
Chunk‑Based Staker Activation: Processes pending stakers in manageable batches to mitigate gas issues.
Dual Reward Distribution: Separately tracks and distributes WNAT and FOTON rewards with precision.
Robust Error Handling: Uses custom errors and logs errors via the
LogError
event.Pausable Operations & Timelocked Emergencies: Provides safety controls that allow the owner to pause operations and schedule emergency withdrawals with a 2‑day delay.
Comprehensive Data Aggregation: Aggregator functions offer detailed insights into global and user‑specific vault data.
Stakers should:
Always stake above the
minStakeAmount
.Monitor epoch transitions to know when pending stakes are activated.
Claim rewards regularly (within the 26‑epoch retention period) to avoid forfeiture.
Be aware that mid‑epoch stake reductions forfeit rewards for that portion.
Last updated