0

I'm working on a code which requires getting the smallest unique number from a number of bets placed by players. I created an isolated code snippet to demonstrate it, it's on gist as well.

My issue that updateResults() costs a lot of gas. I understand it's primarily because it's writing a lot into maps and arrays in storage (through itMaps). It wouldn't need to be so but I couldn't figure out how to do that in memory.

So suggestions on how to reduce gas usage of updateResult() are appreciated. Whether it's a completely different approach or optimising variable scopes only.

Note: I understand that this won't scale for a higher number of bets because it iterates through an array. The current plan to address that is to calculate results in smaller chunks/batches for the future. Also, bets need to be encrypted but I removed that complexity for this question.

Current gas consumption

addBet()

  • First bet: 102,854
  • consecutive bets: 87,854
  • replace bet: 27,164

updateResults()

  • 0 bet: 48,138
  • 1 bet: 101,340
  • 2 bets / no winner: 145,564
  • 3 bets / no winner: 153,732
  • 2 bets / with winner: 150,816
  • 3 bets / with winner: 222,835

using 0.4.11+commit.68ef5810.Emscripten.clang


pragma solidity ^0.4.8;

import "github.com/szerintedmi/solidity-itMapsLib/itMapsLib.sol";

contract GasTest  {
    using itMaps for itMaps.itMapUintUint;
    using itMaps for itMaps.itMapUintAddress;
    using itMaps for itMaps.itMapAddressUint;
    using itMaps for itMaps.itMapUintBool;
    using GasTestLib for GasTestLib.Game;

    GasTestLib.Game game;

    function addBet(uint number) returns (bool overwrittenBet) {
        if (game.winningAddress != address(0)) {
            // it's a first bet so clear winner from previous round
            game.winningAddress = address(0);
            game.smallestNumber = 0;
        }
        return game.im_bets.insert(msg.sender,number); 
    }

    function updateResults() returns (uint numberOfUnrevealedOrInvalidBets) {
        return game._updateResults();
    }

    function getResults() constant returns (address winningAddress, uint smallestNumber) {
        return (game.winningAddress, game.smallestNumber);
    }
}

library GasTestLib {

  /* TODO: hove to structrure this? what data/logic shall we move here? */

  using itMaps for itMaps.itMapAddressUint;
  using itMaps for itMaps.itMapUintAddress;
  using itMaps for itMaps.itMapUintBool;

  struct Game {

      // playeraddress => bet number , bet number is 0 until revealed
      itMaps.itMapAddressUint im_bets;
      // callback queryId => player address : used for retrieving playerAddress at __callback
      uint smallestNumber;
      address winningAddress;
      ResultCalcHelper resultCalcHelper;
  }

  struct ResultCalcHelper {
    /* temporary structure to calculate results - can't be in memory because can't create mappings and dynamic arrays in memory
    could it be done without storage ??  */
    itMaps.itMapUintAddress im_seenOnce; // Key:betnumber -> Value:PlayerAddress
    itMaps.itMapUintBool im_seenMultiple; // Key:betnumber => Value:seen (bool)
                                          // mapping(uint=>bool) m_seenMultiple; would be enough to calc results
                                          // but it needs to be itmap to be able to destroy after round.
  }

  function _updateResults(Game storage self) internal returns (uint numberOfUnrevealedOrInvalidBets) {
    // This function needs to be updated to run in batches of bets to allow 
    //  higher number of bets evaluated without hitting the gas limit
      ResultCalcHelper storage _resultCalcHelper = self.resultCalcHelper;
      uint numberOfBets = self.im_bets.size() ;
      uint numberToCheck;

      // collect unique betnumbers in seenOnce from all bets (im_bets)
      for(uint i = 0; i < numberOfBets  ; i++) {
          numberToCheck = self.im_bets.getValueByIndex(i); // CHECK: does it overwrite value in im_bets?
          if(numberToCheck > 0) { // if this bid has been already revealed and valid...
            if (_resultCalcHelper.im_seenMultiple.contains(numberToCheck) ) {
              continue;
            }
            if (_resultCalcHelper.im_seenOnce.contains(numberToCheck)) {
              _resultCalcHelper.im_seenOnce.remove(numberToCheck);
              _resultCalcHelper.im_seenMultiple.insert(numberToCheck, true);
            } else {
              // first occurence, add to seenOnce
              _resultCalcHelper.im_seenOnce.insert( numberToCheck, self.im_bets.getKeyByIndex(i));
            }
          } else {
              numberOfUnrevealedOrInvalidBets++ ;
          } // numberToCheck
      } // for

      // find smallestNumber in seenOnce
      self.winningAddress = address(0);
      self.smallestNumber = 0;
      uint seenOnceCount = _resultCalcHelper.im_seenOnce.size();
      for( i=0; i < seenOnceCount; i++) {
        numberToCheck = _resultCalcHelper.im_seenOnce.getKeyByIndex(i);
        if (numberToCheck < self.smallestNumber || self.smallestNumber == 0) {
          self.smallestNumber = numberToCheck;
          self.winningAddress = _resultCalcHelper.im_seenOnce.getValueByIndex(i);
        }
      }
      // Clean up
      // CHECK: is it the best way? ie. shall we just set array lengthto zero? (im_seenOnce.clear())
      //    https://ethereum.stackexchange.com/questions/14017/solidity-how-could-i-apply-delete-to-complete-storage-ref-with-one-call
      _resultCalcHelper.im_seenOnce.destroy();
      _resultCalcHelper.im_seenMultiple.destroy();
      self.im_bets.destroy();
      return numberOfUnrevealedOrInvalidBets;
  } // updateResults

}
1

you can replace if conditions in the code using modifiers as follows

function addBet(uint number) returns (bool overwrittenBet) {
    if (game.winningAddress != address(0)) {
        // it's a first bet so clear winner from previous round
        game.winningAddress = address(0);
        game.smallestNumber = 0;
    }
    return game.im_bets.insert(msg.sender,number); 
}

take the following as example

function addBet(uint number) checkCondition(game.winningAddress) returns (bool oerwrittenBet){
      game.winningAddress = address(0);
      game.smallestNumber = 0;
      return game.im_bets.insert(msg.sender,number);
}

modifier checkCondition(winningAddress){
     require(winningAddress == address(0))
     _;
}

for more information look into http://solidity.readthedocs.io/en/develop/common-patterns.html

  • It actually increased the gas consumption for addBet eg. for first bet it went up to 113,003 from 102,854 – szerte Jun 6 '17 at 10:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.