How Staking Works
Understand how Tally's staking contracts work under the hood
Staker is a flexible, configurable staking contract. It distributes onchain staking rewards to the holders of an ERC20 token, including DAO governance tokens. The rewards are proportional to the amount staked over time. Rewards can be boosted or penalized by the eligibility criteria.
System Architecture
Here's an architecture diagram of the staking smart contracts:

The staking contracts have modules for calculating earning power, hooking up sources of rewards, and extensions. Protocol teams can assemble a staking system from these audited pieces.
Staking is out-of-the-box compatible with existing `ERC20Votes` governance tokens. It supports `ERC20Votes` delegation with the "surrogate factory" pattern. Staking creates a surrogate contract for each delegate. It delegates voting power in each surrogate to the delegate.
When Staker receives rewards, it distributes them over a period of time, e.g. 30 days. Distributing over time gives unstaked tokenholders a chance to stake. A smooth schedule also minimizes discontinuities from flash staking.
Staker is built on UniStaker. Unistaker is based on Synthetix's StakingRewards.
UniStaker and Staker have been audited several times. The audit reports are available here.
Core Components
Any instance of Staker needs these pieces to work.
Staker Contract: The main contract that handles staking, reward distribution, and voting power delegation
Earning Power Calculator: Determines how rewards are distributed to stakers
Delegation Surrogate: Manages governance voting power for staked tokens
Reward Notifier(s): Connect reward sources to the staking system
Extensions
Instances of Staker can add these extensions for extra features.
StakerPermitAndStake: Adds EIP-2612 permit functionality for better UX
StakerOnBehalf: Enables signature-based execution of staking actions
StakerCapDeposits: Enforces a cap on the total stake amount
Liquid Staking Token (LST)
The LST, also called stGOV, is the easiest way to get rewards from staking.
The LST is, of course, liquid. Staking positions can be transferred without unstaking.
The LST auto-compounds rewards. Holders will automatically accrue the rewards without having to call claim()
The LST keeps governance power active. When the LST is in DeFi, cold storage, or a centralized exchange, the LST provides a backup plan for governance power.
The backup is a configurable strategy for keeping voting power active in governance. For example, see the OverwhelmingSupportAutoDelegate, which only votes on proposals that have lots of consensus.
Access the open-source stGOV repo.
In-Depth Technical Walkthrough
Tally's Staking contracts are open source:
Staker Contract
The Staker
contract is the core of the system. It manages:
Staking deposits and withdrawals
Reward distribution over time
Delegation of voting power
Earning power calculation
Staker uses a streaming reward mechanism, where rewards are added as lump sums, then distributed evenly over time. This gives stakers time to respond to changes in reward rates.
// Sample code to create a basic Staker implementation
contract MyStaker is
Staker,
StakerDelegateSurrogateVotes,
StakerPermitAndStake
{
constructor(
IERC20 _rewardsToken,
IERC20Staking _stakeToken,
IEarningPowerCalculator _earningPowerCalculator,
uint256 _maxBumpTip,
address _admin
)
Staker(_rewardsToken, _stakeToken, _earningPowerCalculator, _maxBumpTip, _admin)
StakerPermitAndStake(_stakeToken)
StakerDelegateSurrogateVotes(_stakeToken)
{}
}
Earning Power Calculation
Staker uses a concept called "Earning Power" to distribute rewards. Every depositor gets Earning Power. Their share of the reward is their earning power divided by the total earning power in Staker, over time. The earning power calculator determines which depositors are eligible for rewards and how much they earn
Flat Earning Power
IdentityEarningPowerCalculator: Simple 1:1 mapping where earning power equals staked amount.
Oracle-based Earning Power
BinaryEligibilityOracleEarningPowerCalculator: More advanced calculator where earning power depends on the delegate's activity score. Here’s how it works:
An oracle puts scores onchain
The calculator turns earning power on and off based on whether an address's score exceeds a configurable threshold
Staker uses the earning power over time to distribute rewards.
. Tally provides two implementations:
// Deploy a simple earning power calculator
IdentityEarningPowerCalculator calculator = new IdentityEarningPowerCalculator();
// Or deploy an oracle-based calculator
BinaryEligibilityOracleEarningPowerCalculator oracleCalculator =
new BinaryEligibilityOracleEarningPowerCalculator(
owner,
scoreOracle,
staleOracleWindow,
oraclePauseGuardian,
delegateeScoreEligibilityThreshold,
updateEligibilityDelay
);
Reward Notifiers
Reward Source Options
Tally's system distributes rewards through a stream mechanism:
Rewards periodically enter the staking system as lump sums
Those rewards stream to stakers over time
Stakers earn proportional to their staked amount
This approach gives stakers time to respond to changes in rewards.
Reward Notifiers
Reward notifiers connect different token sources to the staking system. Tally provides three standard notifiers:
ERC20 transfer() Direct token transfers from a treasury or revenue source
ERC20 transferFrom() Approved transfers from a separate contract or wallet
Each notifier handles capturing rewards from your chosen source and adding them to the staking reward pool.
Reward notifiers are responsible for informing the staking contract about new rewards:
TransferRewardNotifier.sol holds rewards directly and distributes them by calling
transfer()
TransferFromRewardNotifier.sol relies on an
approve()
, so that it can calltransferFrom()
on the reward sourceMintRewardNotifier.sol calls
mint()
on a token contract.
// Example: deploy a transfer reward notifier
TransferRewardNotifier transferNotifier = new TransferRewardNotifier(
stakerContract, // The staking contract to notify
initialRewardAmount, // Amount to distribute per period
initialRewardInterval, // Time between distributions
owner // Admin of the notifier
);
// Transfer reward tokens to the notifier
rewardToken.transfer(address(transferNotifier), totalRewards);
// Call notify to distribute rewards
transferNotifier.notify();
Delegation Surrogates
Staker uses the “surrogate pattern” to make staking compatible with governance tokens. That way, tokenholders don’t have to choose between earning rewards and doing governance.
Staker creates a surrogate contract for each delegate (address receiving voting power)
Surrogate deposits and withdrawals are fully controlled by the staking system. Staker does all the accounting.
The surrogate contract holds staked tokens and delegates all its voting power to the chosen delegatee. That way, staking is compatible with the underlying governance token.
Note that these surrogate contracts allow tokenholders to split up their voting power. i.e. partial delegation
// The staking contract creates a surrogate for each delegatee
function _fetchOrDeploySurrogate(address _delegatee) internal returns (DelegationSurrogate _surrogate) {
_surrogate = new DelegationSurrogateVotes(stakeToken, _delegatee);
return _surrogate;
}
Configuring Earning Power
Custom Earning Power Rules
Create custom earning power calculators to incentivize specific behaviors:
Activity-based rewards: Require governance participation to earn full rewards.
Time-weighted staking: Increase rewards for long-term stakers
Protocol usage rewards: Tie rewards to protocol usage
// Example of a custom earning power calculator
contract CustomEarningPowerCalculator is IEarningPowerCalculator {
function getEarningPower(uint256 _amountStaked, address _staker, address _delegatee)
external
view
returns (uint256)
{
// Custom logic to determine earning power
return _calculateCustomEarningPower(_amountStaked, _staker, _delegatee);
}
function getNewEarningPower(
uint256 _amountStaked,
address _staker,
address _delegatee,
uint256 _oldEarningPower
) external view returns (uint256, bool) {
uint256 newPower = _calculateCustomEarningPower(_amountStaked, _staker, _delegatee);
bool qualifiedForBump = _isQualifiedForBump(newPower, _oldEarningPower);
return (newPower, qualifiedForBump);
}
// Your custom calculation logic
function _calculateCustomEarningPower(uint256 _amountStaked, address _staker, address _delegatee)
internal
view
returns (uint256)
{
// Implementation details
}
}
Roles
Admin controls:
The admin of the staking contracts can’t touch staked assets. They do control some system parameters:
Add and remove reward sources, by enabling and disabling reward notifiers
Set the eligibility criteria for rewards, by changing the earning power calculator
Change the emergency pause guardian.
Override eligibility for a particular address.
Set claim fee parameters
Upgrade strategy:
These contracts could be deployed immutable or with the upgradeable proxy pattern
To migrate an immutable Staker:
Deploy a new staking contract
Send rewards there
Have tokenholders migrate to the new one
To upgrade-in-place a proxy contract:
Use initializers instead of constructors for key params at deployment
Check the storage slots carefully to avoid corrupting state
Emergency measures:
The oracle-based calculator has failsafes in case the oracle misbehaves or goes offline:
If the oracle misbehaves by posting incorrect scores, a `PauseGuardian` can pause the system, reverting it to flat earning power.
If the oracle goes offline, the calculator also automatically reverts to using flat earning power.
The oracle can be replaced by Staker's admin.
Resource Usage and Gas Optimization
Deposit: ~100,000-150,000 gas
Claiming rewards: ~60,000-100,000 gas
Withdrawal: ~80,000-120,000 gas
For optimal performance:
Distribute rewards at reasonable intervals, e.g., weekly or monthly
The earning power calculator should wait between updates, e.g. daily
The earning power calculator shouldn’t make lots of small updates, especially networks with high gas costs
Anyone can update earning power as it changes, but someone needs to do it. Staker provides “tips” as incentive for bots to do updates. If MEV bots do not know about the incentive, consider running the staker bots script directly.
These incentives don’t work testnets or for tokens with no market value.
Last updated
Was this helpful?