1

I'm working on keeping track of data via a struct stored in a mapping.

mapping(uint256 => Session);

Here is the Session struct:

  struct Session {
      // UID
      uint256 id;
      // Timestamp the sale starts
      uint256 start;
      // Timestamp the sale stops
      uint256 stop;
      // Last rate sync timestamp
      uint256 sync;
      // Current Rate
      uint256 rate;
      // Rate normalization: rate / 10**{decimal}
      uint8 decimal;
      // Current TOKEN|BNB Rate
      uint256 issue;
      // WEI raised
      uint256 raised;
      // Maximum sale quantity
      uint256 max;
      // Total tokens sold
      uint256 sold;
      // Total minutes before bnb rate update
      uint256 threshold;
      // Contract address of chainlink aggregator
      address chainlink;
      // Token Sale Owner
      address owner;
      // Token Contract
      IBEP20 token;
  }

What is the most efficient ways to modify a session?

Method 1:

/**
   * @dev low level token purchase ***DO NOT OVERRIDE***
   * @param _beneficiary Address performing the token purchase
   */
  function buyTokens(uint256 _id, address _beneficiary) public payable open(_id) {

    uint256 weiAmount = msg.value;
    _preValidatePurchase(_beneficiary, weiAmount);

    // calculate token amount to be created
    uint256 tokens = _getTokenAmount(weiAmount);

    // update state
    sessions[_id].raised = sessions[_id].raised.add(weiAmount);

    _processPurchase(_beneficiary, tokens);
    emit TokenPurchase(
      msg.sender,
      _beneficiary,
      weiAmount,
      tokens
    );

    _forwardFunds();
    _postValidatePurchase(_beneficiary, weiAmount);
  }

Method 2:

  /**
   * @dev low level token purchase ***DO NOT OVERRIDE***
   * @param _beneficiary Address performing the token purchase
   */
  function buyTokens(uint256 _id, address _beneficiary) public payable open(_id) {

    uint256 weiAmount = msg.value;
    _preValidatePurchase(_beneficiary, weiAmount);

    // calculate token amount to be created
    uint256 tokens = _getTokenAmount(weiAmount);

    // update state
    Session storage sesh = sessions[_id];
    sesh.raised = sesh.raised.add(weiAmount);

    _processPurchase(_beneficiary, tokens);
    emit TokenPurchase(
      msg.sender,
      _beneficiary,
      weiAmount,
      tokens
    );

    _forwardFunds();
    _postValidatePurchase(_beneficiary, weiAmount);
  }

For some reason in my head Method 2 is better, when I access a mapping does it have to search each time?

And the storage keyword, does that actually modify the object in storage within the mapping or am I just bonkers?

2
  • could you please share the Session structure ? I'd like to test this
    – Stormsson
    Aug 31, 2021 at 6:49
  • @Stormsson, thank you for the answer. I've added the Session struct to the question and accepted your edit. If you want to view the current contract in-work even tho it is not operational you can find it here: github.com/BlackSwanFoundry/smart-contracts/blob/main/bsc/… Aug 31, 2021 at 17:12

1 Answer 1

0

It is a bit hard to know in advance which method is optimal. In your scenario, gasEstimate states that the second method is less expensive.


To test your implementation I stripped everything from the contract except for the assign of the value (also I have a fake structure):

CONTRACT

// contracts/Box.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;



contract Optim {
    struct Session {
        uint256 raised;
    }

    mapping(uint256 => Session) sessions; 
    
    
    constructor() {        
        sessions[1] = Session(1);
    }

    function direct(uint256 _id) public payable {            
        // update state
        sessions[_id].raised = sessions[_id].raised + 999;        
    }   

    function tmpvar(uint256 _id) public payable {            
        // update state
        Session storage sesh = sessions[_id];
        sesh.raised = sesh.raised + 999;        
    }   
}

creating a simple test with truffle allows us to have some information:

gasEstimate.js

const Optim = artifacts.require("Optim");

contract("Optim", () => {
    it("should have different gas", async() => {
        const O1 = await Optim.new();

        const method1 = await O1.direct.estimateGas(11);
        const method2 = await O1.tmpvar.estimateGas(11);

        console.log("method1 cost: " + method1, "method2 cost: " + method2);
        assert(method1 < method2, "method 1 < method2");
    });
});

the console output is

method1 cost: 42762 method2 cost: 42675

therefore the second method is less expensive

1
  • Can you try switching the function names? The contract function dispatch has a small cost that depends on the function slot. My guess is that compiling with optimization the cost is the same for both (without considering the function dispatch cost).
    – Ismael
    Sep 2, 2021 at 4:23

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.