In the Synthetix Staking Contract or even the following simple staking contract from Smart Contract Programmer there are a few parameters that govern the rewards dispersed, namely rewardRate and rewardPerToken.

Full Code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

contract StakingRewards {
    IERC20 public rewardsToken;
    IERC20 public stakingToken;

    uint public rewardRate = 100;
    uint public lastUpdateTime;
    uint public rewardPerTokenStored;

    mapping(address => uint) public userRewardPerTokenPaid;
    mapping(address => uint) public rewards;

    uint private _totalSupply;
    mapping(address => uint) private _balances;

    constructor(address _stakingToken, address _rewardsToken) {
        stakingToken = IERC20(_stakingToken);
        rewardsToken = IERC20(_rewardsToken);

    function rewardPerToken() public view returns (uint) {
        if (_totalSupply == 0) {
            return rewardPerTokenStored;
            rewardPerTokenStored +
            (((block.timestamp - lastUpdateTime) * rewardRate * 1e18) / _totalSupply);

    function earned(address account) public view returns (uint) {
            ((_balances[account] *
                (rewardPerToken() - userRewardPerTokenPaid[account])) / 1e18) +

    modifier updateReward(address account) {
        rewardPerTokenStored = rewardPerToken();
        lastUpdateTime = block.timestamp;

        rewards[account] = earned(account);
        userRewardPerTokenPaid[account] = rewardPerTokenStored;

    function stake(uint _amount) external updateReward(msg.sender) {
        _totalSupply += _amount;
        _balances[msg.sender] += _amount;
        stakingToken.transferFrom(msg.sender, address(this), _amount);

    function withdraw(uint _amount) external updateReward(msg.sender) {
        _totalSupply -= _amount;
        _balances[msg.sender] -= _amount;
        stakingToken.transfer(msg.sender, _amount);

    function getReward() external updateReward(msg.sender) {
        uint reward = rewards[msg.sender];
        rewards[msg.sender] = 0;
        rewardsToken.transfer(msg.sender, reward);

interface IERC20 {
    function totalSupply() external view returns (uint);

    function balanceOf(address account) external view returns (uint);

    function transfer(address recipient, uint amount) external returns (bool);

    function allowance(address owner, address spender) external view returns (uint);

    function approve(address spender, uint amount) external returns (bool);

    function transferFrom(
        address sender,
        address recipient,
        uint amount
    ) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint value);
    event Approval(address indexed owner, address indexed spender, uint value);

I'm having a hard time understanding all the functions.

The ones that make sense:

  • earned: function clearly just says how much a user has earned over.

The ones I'm having a hard time with:

  • rewardRate: Is this like reward per second per token?
  • rewardPerToken: This is clearly the reward to be given per token staked, but why is the total supply being divided in the function?

Any and all help appreciated.

1 Answer 1


rewardRate is reward per second, this rate will be multiplied by token user staked / total staked

rewardPerToken - math trick

R = reward rate

l(t) = amount user has staked at time t

L(t) = total amount staked at time t

Total amount of reward earned is the sum of R*l(t) / L(t)

enter image description here

l(t) is constant unless user stakes or unstakes So the equation above can be rewritten as

enter image description here

It can also be written as

enter image description here

rewardPerTokenStored is the left summation multiplied by reward rate

userRewardPerToken[user] is the right summation multiplied by reward rate






  • 1
    Great answer! I'd like to emphasize that the Youtube videos are of great help. I watched them multiple times when trying to understand the math. Commented Mar 17, 2022 at 6:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.