Staking Contract Development
A staking contract is one of the basic DeFi primitives. A user deposits tokens, receives rewards over time, and can withdraw. It sounds simple — and that's exactly why most implementations contain bugs. A correct reward calculation mechanism is non-trivial, and mistakes here cost money.
Reward Distribution: accumulated reward per token algorithm
A naive implementation iterates through all stakers and updates their balances — this is O(n) in gas and kills the contract with a large number of users.
The standard algorithm: accumulated reward per token. A global variable rewardPerTokenStored accumulates rewards per 1 token since deployment. With each deposit/withdraw it's updated. Individual reward is calculated as (rewardPerToken - userCheckpoint) × userBalance.
uint256 public rewardPerTokenStored;
uint256 public lastUpdateTime;
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewards;
function rewardPerToken() public view returns (uint256) {
if (totalSupply == 0) return rewardPerTokenStored;
return rewardPerTokenStored +
(block.timestamp - lastUpdateTime) * rewardRate * 1e18 / totalSupply;
}
function earned(address account) public view returns (uint256) {
return balanceOf[account] *
(rewardPerToken() - userRewardPerTokenPaid[account]) / 1e18
+ rewards[account];
}
This is O(1) — gas doesn't depend on the number of users. This is how Synthetix StakingRewards works, which became the reference implementation.
Core staking mechanics
Lock period: tokens are locked for N days after deposit. Reduces sell pressure. Requires storing depositTime and checking on withdraw.
Unstaking cooldown: after initiating withdrawal — waiting 7-28 days. Popular in PoS protocols (imitates unbonding period).
Early withdrawal penalty: when withdrawing before lock period expires — a penalty (for example, 10% of the amount). The penalty goes to the reward pool or is burned.
Time-based multiplier: the longer staking — the higher reward rate. Implemented through tiered multipliers or linear vesting multiplier.
Security
Reentrancy: when paying rewards via transfer → external call → function of the contract again. CEI pattern (Checks-Effects-Interactions): first update state, then make transfer.
Precision loss: division in Solidity rounds down. With small amounts or large totalSupply, precision can be lost. Use scaling factor 1e18 (or 1e27 for extra precision).
Reward token != staking token: if reward and staking tokens are the same — totalSupply includes reward tokens in the pool, which breaks rewardPerToken calculation. Need to carefully count only "staked" tokens.
Admin keys: who can change rewardRate? If it's an EOA — a single point of failure. Timelock + multisig — the standard for production contracts.
Staking contracts must pass audit before deployment — they hold user funds and are frequent targets for exploits.







