How to calculate user's staking reward every block with solidity?

Viewed 2284

I'm building a staking pool as a way to learn smart contract using solidity. The staking reward will be calculated based on how much is the user's balance and for how long he/she has been staking. The problem is during the course of staking the balance can change. So how can I achieve a fair reward distribution? Can one calculate reward distribution every block?

1 Answers

You need to save the stakes in an array instead of one aggregated variable.

This is an example code that could be further optimized, and some parts (such as payout) are not implemented at all. But it clearly shows the reward calculation based on an array of stakes taking into account the block count of each stake.

pragma solidity ^0.8.1;

contract StakingContract {
    struct Stake {
        address user;
        uint256 amount;
        uint64 sinceBlock;
        uint64 untilBlock;
    }

    Stake[] stakes;
    uint256 constant public percentPerBlock = 1; // TODO use more granular units

    event LogPayout(address user, uint256 stakedAmount, uint256 rewardAmount);

    function stake(uint256 _amount) external returns (uint256 stakeId) {
        stakes.push(Stake(msg.sender, _amount, uint64(block.timestamp), 0));
        return stakes.length - 1;
    }

    function unstake (uint256 _id) external {
        require(stakes[_id].user == msg.sender, 'Not your stake');
        require(stakes[_id].untilBlock == 0, 'Already unstaked');

        stakes[_id].untilBlock = uint64(block.timestamp);

        uint256 stakedForBlocks = (block.timestamp - stakes[_id].sinceBlock);
        uint256 rewardAmount = stakes[_id].amount * stakedForBlocks * percentPerBlock / 100;

        emit LogPayout(stakes[_id].user, stakes[_id].amount, rewardAmount);
        // TODO actual payout
    }
    
    function viewUserTotalUnclaimedRewards(address _user) external view returns (uint256) {
        uint256 totalUnclaimedRewards;

        for(uint256 i = 0; i < stakes.length; i++) {
            if (stakes[i].user == _user && stakes[i].untilBlock == 0) {
                uint256 stakedForBlocks = (block.timestamp - stakes[i].sinceBlock);
                uint256 rewardAmount = stakedForBlocks * percentPerBlock / 100;
                totalUnclaimedRewards += rewardAmount;
            }
        }

        return totalUnclaimedRewards;
    }
}

Notes:

  • stake() function pushes new Stake struct to the stakes array
  • unstake() function sets the untilBlock of the one particular array item, marking it as finished
  • viewUserTotalUnclaimedRewards() view function iterates all unfinished (not set untilBlock) stakes of this user and adds the staked amounts and rewards alltogether
Related