1

I'm working on a staking contract, where the APY will be generated dynamically from a balance of the "rewards contract".

#1 Deposits 50 #2 Deposits 50 Rewards pool - 200 tokens

After a year 100 tokens will be visible on the UI as rewards for both users, because their share is 50%-50% in the pool. (If they didn't claim). If #3 deposits 100 tokens, then the APY will change, and the UI will show 50-50 tokens for #1-#2. The first 2 users lost 50-50 tokens.

Everytime a user deposits/claims the APY changes, so the task is to save the unclaimed rewards when the balance of the "rewards contract" & staking contract changes.

I thought it will be a good idea to use a loop to save the unclaimed rewards to a variable:

function saveRewards() internal {
        for (uint i = 0; i < farmerIndexes.length; i++) {
            farm.farmers[farmerIndexes[i]].rewards = farm.farmers[farmerIndexes[i]].rewards.add(calculateRewards(farmerIndexes[i]));
        }

        lastUpdate = block.timestamp;
    }

I'm afraid after 100+ users the gas will be very high, and at some point the transaction will fail. How can I achieve this?

3
  • I didn't quite understand your use case, but you should never use unbounded loops. If you have enough farmers, the loop always reverts, making the function useless. Commented Mar 24, 2022 at 13:07
  • @LauriPeltonen sorry I missed your response. It's a farming contract, with rewards in every minute. The share of the user depends on his stake. When someone deposits I have to save the accumulated rewards for every single user or do you have a better idea to do that when using variable APY? The issue is the USERS share changes all the time because of withdrawals, deposits. If it changes I need to save their rewards with the share before. That's why I used the loop to save it for every user. Commented Mar 28, 2022 at 11:43
  • Sorry, can't give any specific answer, depends on case by case. I think the best basic (efficient) staking algorithm is from Synthetix, have a look at that. It's complicated to understand, but has no loops. Commented Mar 28, 2022 at 11:55

1 Answer 1

0

When a user deposit, you mint them share tokens that represent their share of the pool.

function deposit(uint _amount) public{
    
    myToken.transferFrom(msg.sender, address(this), _amount);

    uint shares;
    if(totalShares == 0){
        shares = amountDeposited;
    } else {
        shares = _amount * totalShares / poolSize; // this is the magic
    }
    totalShares += shares;

    _mint(msg.sender, shares);
}

function poolSize() public returns(uint){
    return IERC20(myToken).balanceOf(address(this));

Then write your withdraw function

function withdraw(uint _shares) public {
    _burn(msg.sender, _shares);
    uint amount;
    amount = poolSize * _shares / totalShares; // This is the magic
    
    poolSize -= amount;
    totalShares -= _shares;  

    myToken.transfer(msg.sender, amount);
}

This way you do not have to update each users rewards. When a user deposits he gets share tokens that represent his deposited amount. If the pool is a yield pool that does not shrink from anything else but users withdrawing, then a users share tokens will always represent AT LEAST his deposited amount + any yield that might occur. However, he will NOT be eligible to a share of anything deposited by other users, only share of yield.

You should try to run some arbitrary scenarios through the "magic" calculations, some pen and paper and a calculator, it will help you get a hang of it quicker.

NOTE: Code not tested and written on the fly, so make sure to test and tune it before implementing. See it more as something to get some ideas from. Stay safe xx.

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.