I wrote staking smart contract and I have “loop” concerns

Hello guys, I don’t know if you can help me, but I need an opinion/suggestion. I created smart contract for staking, but I have concerns that maybe my function distributeRewards() will ran out of gas (gas cap reach) if there will be a lot of users (maybe more than 100?) There is a loop. You think that my concerns are legit?

Can this be done in any other way? What I want is that I distribute excess of tokens to stakers. For example if stakers are staking 1000 tokens, if in smart contract is 1100 tokens I want to distribute 100 tokens to stakers.

Yes your concerns are warranted. You should not use loops like this.

Look into one of the existing rewards distribution contracts:

@frangio thank you for answer, but I think that those contracts are not what I want to achieve.

There is random amount of tokens that will be sent to my smart contract as rewards (sent by third party I have no control over) and I want to distribute this random amount to all stakers (received amount depends on their staking share). I have no idea how to do this automatically to be transparent and trustworthy.

I was thinking that I gather all valid addresses off-chain and send them to distributeRewards() function, but I am not sure if this is trustworthy since I can send any address to that function, or for example only one and not for all stakers if you know what I mean. :confused:

Does the reward need to be "immediately" distributed to all stakers, or does it get pooled and distributed over time?

@frangio no, it does not need to be "immediately". My idea was that rewards would be updated for example once per day.

So once per day "I" check how much is there excess of tokens:
uint256 totalRewardsIncludingFee = token.balanceOf(address(this)) - totalStakedBalance - totalDistributedRewards;

and I put that excess to users rewards acounts (I take fee for service along the way):
rewards[userPublicAddress] = rewards[userPublicAddress] + userDistributionReward;

Ideal would be that this would happen automatically.

Then I think the links I shared above should be relevant. They are ways to distribute a pool of rewards, weighted by stake.

I don't know, first one is using loop same as me, and the second one is using staking period rewardRate and I don't know how this can help since it is totally different system I want.

Tokens are sent to my smart contract randomly (random time and random amount of tokens). And I want distribute those tokens as rewards to stakers.

I tried this way, but its not working correctly, when first staker withdraw rewards, for others rewards are calculated incorrect:

I am so close, but so far... :sleepy:

It's very different to loop over an array of bounded length that is user-controlled (in the sense that a user decides for themselves the length of the array they want to loop over), than to always loop over the entire array of user accounts like your snippet above.

The first one is okay, because each user decides and they can always submit a smaller array, the second one isn't because it can be attacker-controlled and stall the progress of the contract if it becomes too big, with no recourse.

I went with totally different approach. I calculate rewardPerToken and give users rewards based on that.

rewardPerToken = rewardPerToken + (totalRewards / totalStakedBalance)

Everything works kinda perfect except now I have different problem - this is probably more mathematical question than solidity, but I will ask anyway. For example reward is 9. In smart contract I calculate rewardPerToken = 0 + (9 / 42) = 0.214285714285714285. Then I calculate based on that how much of reward each staker gets based on how much they are staking. For example first user has 10 tokens, so he gets - 10 * 0.214285714285714285 = 2.14285714285714285. Ok, now second user holds 32 tokens, so he gets - 32 * 0.214285714285714285 = 6.85714285714285712. If I add those results together I get - 6.85714285714285712 + 2.14285714285714285 = 8.99999999999999997, but in reality it should be 9? So there is 0,00000000000000003 tokens "lost in space". Any clue how to avoid that? :man_shrugging:

You need to store the numerator and the denominator separately, and do the division as the last step using Math.mulDiv. We need to fix the docs but it should say:

mulDiv(x, y, denominator):
Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0.

1 Like