Search code examples
blockchainethereumsolidity

Staking contract based on time: need to avoid loop


we are building a staking smart contract on the ethereum mainnet. All the stakes will need to have a timestamp attached to it, so our logic relies on time. The logic is at the end of the month each stakeholders will have credits, and based on that the contract owner will attribute rewards to each stakeholders based on their creditsAmount. But in order to get the total creditsAmount we need to iterate through the list of stakeHolders, which is super expensive. Here is a very short example of our staking contract:

pragma solidity 0.8.6;
import “@openzeppelin/contracts/utils/structs/EnumerableSet.sol”;

contract Test {
  using EnumerableSet for EnumerableSet.AddressSet;

  struct Stake {
    uint256 lockedToken;
    uint256 creditsEarned; // numberOfDays * lockedToken = 15days * 1000 = 15000
  }
  
  // Rewards = RatioOfCreditsEarnedByStakeholder * MonthlyRewards

  EnumerableSet.AddressSet private stakeholders;
  mapping(address => Stake) private stakeholderToStake;

  function createStake(
   address stakeholder,
   uint256 lockedToken,
   uint256 creditsEarned
  ) public {
   stakeholders.add(stakeholder);
   stakeholderToStake[stakeholder] = Stake({
     lockedToken: lockedToken,
     creditsEarned: creditsEarned
   });
  }
      
function distributeRewards() public {
  uint256 totalCredits = 0;
  for (uint256 i = 0; i < stakeholders.length(); i++) {
    totalCredits += stakeholderToStake[stakeholders.at(i)].creditsEarned;
      }
    }
   }

So as you can imagine the very last loop is extremely costly but we did not find another way to do so for now. Do you have any idea proposition on how to avoid such loop? or other staking contract which relies on time like us?

Thanks


Solution

  • To expand on my comments, this is how I will do it.

    pragma solidity 0.8.6;
    import “@openzeppelin/contracts/utils/structs/EnumerableSet.sol”;
    
    contract Test {
    using EnumerableSet for EnumerableSet.AddressSet;
    
    struct Stake {
          uint256 lockedToken;
          uint256 creditsEarned;
       }
      
      // Rewards = RatioOfCreditsEarnedByStakeholder * MonthlyRewards
    
      EnumerableSet.AddressSet private stakeholders;
      mapping(address => Stake) private stakeholderToStake;
          
      uint256 private totalCredits;
    
      function createStake(
         address stakeholder,
         uint256 lockedToken,
         uint256 creditsEarned
      ) public {
          stakeholders.add(stakeholder);
          stakeholderToStake[stakeholder] = Stake({
          lockedToken: lockedToken,
          creditsEarned: creditsEarned
         });
        totalCredits += creditsEarned;
      }
          
      function distributeRewards() public {
      //do whatever you want with totalCredits here
       }