1

I test some function as follows,,,

function fooMain() public pure returns(uint8) {
    uint8 tempData;
    uint8 i;
    for(i=0 ; i<10 ; i++){
        tempData = subFooMain(tempData);
    }

    return tempData;

}

function subFooMain(uint8 _in) public pure returns(uint8){
    uint8 i;
    uint8 result;
    result = 0;
    for(i=0 ; i<200 ; i++){
        result = result + _in;
    }
    return result;
}

And my test code(truffle) is like this

gasEstimated = await instant.fooMain.estimateGas();
console.log("estimated gas(fooMain) : " + gasEstimated);
let inData = 1;
gasEstimated = await instant.subFooMain.estimateGas(inData);
console.log("estimated gas(subFooMain) : " + gasEstimated); 

And this is the result

enter image description here

My question is the estimated gas to run fooMain is smaller than I expected? Because the subFooMain consume 37114 gas, and it runs 10 times in fooMain, so I think it should be much more than 175345.

2 Answers 2

5

When you are sending a transaction, there is a fixed amount of gas required (21000 gas currently), plus the TxDataNonZeroGas constant (68 gas) per every byte of data attached to the transaction.

So the gas usage of subFooMain is much larger when calling it alone.

5
  • So the constant amount of gas needed for each function call (even if it is pure???, this might be another question..) The gas is (dramatically) smaller than sum of sub function gas consumption, because B = (b+constant gas), so n * (b+constant gas) = n * b + n * (constant gas)?? The n * b is should be similar to A? Am I right? Then how can I calculate the constant gas? It should be 21000 + 68 * (# of bytes of data attached) ??
    – Kronos
    May 9, 2018 at 12:40
  • In you case, fooMain: 154345 (175345 - 21000), subFooMain: 16046 (37114 - 21000 - 68). So it is similar when you ignore the constant gas
    – Kingmoz
    May 9, 2018 at 13:30
  • So the constant amount of gas needed for each function call (even if it is pure???, this might be another question..) - This is the gas required for a transaction. If you are calling a view or pure function alone, no gas is required as the function will only be ran in the local node. But if you are invoking the function in another function which require a transaction, gas is required no matter it is a pure function or not. The constant gas should be ignored when calculating the gas usage of the constant function.
    – Kingmoz
    May 9, 2018 at 13:47
  • So as long as all the functions are implemented as view and pure, then gas doesn't required? But I don't know why web3.js estimateGas function returns the gas for my pure function.
    – Kronos
    May 10, 2018 at 1:26
  • // EstimateGas returns an estimate of the amount of gas needed to execute the given transaction against the current pending block. See here. A view or pure function can still be called using a transaction which will require gas, and the only different is web3 will call these function without making a real transaction See eth_call
    – Kingmoz
    May 10, 2018 at 1:51
1

Solidity is a high-level programming language, which means we understand it, but machines don't.

The "machine" that runs your code is called the Ethereum Virtual Machine (EVM).

When we compile our solidity code, you transform it in bytecode, which only the EVM understands.

Let's use this simple contract as an example:

pragma solidity ^0.4.23;

contract HelloWorld {
    function sayIt() pure public returns(string) {
        return "Hello world!";
    }
}

If you compile this code on Remix and click on Details on the Compile tab, you will see something like this (trimmed for convenience):

BYTECODE
{
    "linkReferences": {},
    "object": "608060405234801561001057600080fd5b5061013f806100206...",
    "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO...",
    "sourceMap": "26:111:0:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;26:..."
}

As you can see, bytecode includes opcodes (think assembly) that the EVM understands.

Now, every upcode you include in your code will cost gas for execution. You can see the cost per opcode in the Yellow Paper, Appendix G (page 25).

So, when you call a function in a Ethereum smart contract, you have 3 costs:

  1. The transaction cost (see @Kingmoz comment for values);
  2. The execution cost, that will depend on the opcodes executed (type and number);
  3. Storage costs (very expensive), that I'll not talk about it on this answer.

While we can't do much about the transaction cost, we can (and shall) do a lot concerning the execution cost.

For instance, if your code is simpler, it will compile in less opcodes, making it cheaper to execute.

Let's use your code as an example (changed type from uint8 to uint, or it always return 32):

function subFooMain(uint _in) public pure returns(uint){
    uint i;
    uint result;
    result = 0;
    for(i=0 ; i<200 ; i++){
        result = result + _in;
    }
    return result;
}

correct me if I'm wrong, but I think it can be changed to:

function subFooMain(uint _in) public pure returns(uint){
    return _in * 200;
}

The second version uses 58 less opcodes than the first version, making it a lot cheaper to run:

  • long version => execution cost = 14316 gas
  • short version => execution cost = 271 gas

In summary, to reduce gas to run a function:

  1. Minimize the number of opcodes your code originates;
  2. Minimize storage usage.
4
  • Thank you for your reply. But the calculation, result = result + _in is just sample calculation (I know I can minimize the calculation as you wrote.) My question is that there are two methods(functions) funcA and funcB, and the funcB is called inside funcA several times(say n times). let estiamtes gas for funcA and funcB is A and B, respectively. then why A is way too smaler than n * B ??
    – Kronos
    May 9, 2018 at 12:30
  • You're right, I'm responding to the title question "how to reduce gas to run a function?" and not to the question you put in the text. Let me see if I can understand better how estimateGas works and give an answer to your other question.
    – bordalix
    May 9, 2018 at 13:29
  • @Kronos, do you realize your code crashes Remix? The max number of loops it can manage is 4 (in the for loop of fooMain()). estimateGas() is known to behave badly when the function it's evaluation doesn't end correctly. Can you change your code to max 3 loops (i<3) and estimateGas on both functions again?
    – bordalix
    May 9, 2018 at 14:41
  • No problem, I tested my code with Truffle (running testrpc), not Remix. and I set a very large block gas limit. (I know it is not practical, but for experiment to know gas consumption)
    – Kronos
    May 10, 2018 at 1:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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