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.

You definitely should not have unbounded loops like this. Either make the function support paging (pass in a starting value for i and iterate), or better yet make it so users submit a transaction to pull their rewards, and you track who has already withdrawn.

@MicahZoltu 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:

Distributing received assets to an unbounded pool is a hard problem. If the frequency of received assets is low, you can just have each asset receipt be in a separate withdrawal pool and then have the user doing the withdraw iterate over them. Alternatively, if you can somehow bound the number of receivers you could safely loop (e.g., only 10 receivers ever).

Another option is to build a tree of recipients and when you withdraw you would trigger distribution down the tree, but only along your branch and its sibling roots (similar to how a merkle tree proof works). This allows you to have a functionally unlimited number of receivers with bounded loops, but it is incredibly complex to implement.

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:

Don’t use decimal numbers, always use rational numbers. Also, always do multiplication before division. Also, update the denominator as people with draw so you avoid having leftovers.

Example:
There is a pot of 9e18 rewards and there are 42 tokens distributed across 2 users with 10 TOKN and 32 TOKN respectively. First person would get 9e18 * 10 / 42 rewards and this would result in updating the remaining rewards to 9e18 - 9e18*10/42 or 6857142857142857143 and the total number of tokens to 32. The next user would then get 6857142857142857143 * 32 / 32 tokens, which means they would get everything that was left (including the “remainder”). You’ll note that the second person did receive 1 more reward, but this is insignificant and it ensures the contract is always zeroed out in the end.

1 Like