1

if I want to call a function of an already deployed contract for example an ERC20 token which one is the best and efficient way to call it's functions? using call method

(bool success, bytes memory data) = add.call(
            abi.encodeWithSignature("foo(string,uint256)", "call foo", 123)

or using interface

IFACE contract = IFACE(add)
contract.foo("call foo", 123)

or another way?

1 Answer 1

1

Calling the contract with the interface is the best option because it provides type safety.

In terms of gas efficiency, they both are almost the same.

Check the following test contract:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

interface IFACE {
  function foo(string memory s, uint256 n) external;
}

contract MyContract is IFACE {
  function foo(string memory s, uint256 n) external {
    // do something with s and n
  }
}

contract Contract {

  address public myContractAddress;

  constructor(address _myContractAddress) {
    myContractAddress = _myContractAddress;
  }

  function test1() public {
    IFACE(myContractAddress).foo("etc", 1);
  }

  function test2() public {
    (bool success, bytes memory data) = myContractAddress.call(
            abi.encodeWithSignature("foo(string,uint256)", "etc", 123));
    //...
  }

}

Notice the test1 and test2 functions.

test1 function uses the interface to call the contract function. test2 uses call.

While testing on Remix, while calling test1, it consumed about 25161 gas. Calling test2 it consumed about 25234 gas.

But when I decompile the runtime byte code, I can see how both functions are treated by the compiler:

Runtime bytecode: 608060405234801561001057600080fd5b50600436106100415760003560e01c806366e41cb7146100465780636b59084d14610050578063b12f97bb1461005a575b600080fd5b61004e610078565b005b610058610199565b005b610062610227565b60405161006f919061028c565b60405180910390f35b60008060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16607b6040516024016100c49190610356565b6040516020818303038152906040527f24ccab8f000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161014e91906103f5565b6000604051808303816000865af19150503d806000811461018b576040519150601f19603f3d011682016040523d82523d6000602084013e610190565b606091505b50915091505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166324ccab8f60016040518263ffffffff1660e01b81526004016101f39190610451565b600060405180830381600087803b15801561020d57600080fd5b505af1158015610221573d6000803e3d6000fd5b50505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102768261024b565b9050919050565b6102868161026b565b82525050565b60006020820190506102a1600083018461027d565b92915050565b600082825260208201905092915050565b7f6574630000000000000000000000000000000000000000000000000000000000600082015250565b60006102ee6003836102a7565b91506102f9826102b8565b602082019050919050565b6000819050919050565b600060ff82169050919050565b6000819050919050565b600061034061033b61033684610304565b61031b565b61030e565b9050919050565b61035081610325565b82525050565b6000604082019050818103600083015261036f816102e1565b905061037e6020830184610347565b92915050565b600081519050919050565b600081905092915050565b60005b838110156103b857808201518184015260208101905061039d565b60008484015250505050565b60006103cf82610384565b6103d9818561038f565b93506103e981856020860161039a565b80840191505092915050565b600061040182846103c4565b915081905092915050565b6000819050919050565b6000819050919050565b600061043b6104366104318461040c565b61031b565b610416565b9050919050565b61044b81610420565b82525050565b6000604082019050818103600083015261046a816102e1565b90506104796020830184610442565b9291505056fea2646970667358221220457ef6359d19c61a480bb018e910d55d619678bd20f65427ea5dcd3c93e86ebb64736f6c63430008100033

Decompiled here: https://ethervm.io/decompile

Decompiled test1 function:

function test1() {
        var var0 = storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff;
        var var1 = 0x24ccab8f;
        var temp0 = memory[0x40:0x60];
        memory[temp0:temp0 + 0x20] = (var1 & 0xffffffff) << 0xe0;
        var var2 = 0x01f3;
        var var3 = 0x01;
        var var4 = temp0 + 0x04;
        var2 = func_0451(var3, var4);
        var3 = 0x00;
        var4 = memory[0x40:0x60];
        var var5 = var2 - var4;
        var var6 = var4;
        var var7 = 0x00;
        var var8 = var0;
        var var9 = !address(var8).code.length;
    
        if (var9) { revert(memory[0x00:0x00]); }
    
        var temp1;
        temp1, memory[var4:var4 + var3] = address(var8).call.gas(msg.gas).value(var7)(memory[var6:var6 + var5]);
        var3 = !temp1;
    
        if (!var3) { return; }
    
        var temp2 = returndata.length;
        memory[0x00:0x00 + temp2] = returndata[0x00:0x00 + temp2];
        revert(memory[0x00:0x00 + returndata.length]);
    }

Decompiled test2 function:

function test2() {
        var var0 = 0x00;
        var var1 = var0;
        var var2 = storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff;
        var var3 = 0x00c4;
        var var4 = 0x7b;
        var var5 = memory[0x40:0x60] + 0x24;
        var3 = func_0356(var4, var5);
        var temp0 = memory[0x40:0x60];
        var temp1 = var3;
        memory[temp0:temp0 + 0x20] = temp1 - temp0 - 0x20;
        memory[0x40:0x60] = temp1;
        var temp2 = temp0 + 0x20;
        memory[temp2:temp2 + 0x20] = (memory[temp2:temp2 + 0x20] & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff) | (~0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff & 0x24ccab8f00000000000000000000000000000000000000000000000000000000);
        var3 = 0x014e;
        var5 = memory[0x40:0x60];
        var4 = temp0;
        var3 = func_03F5(var4, var5);
        var temp3 = memory[0x40:0x60];
        var temp4;
        temp4, memory[temp3:temp3 + 0x00] = address(var2).call.gas(msg.gas)(memory[temp3:temp3 + var3 - temp3]);
        var3 = returndata.length;
        var4 = var3;
    
        if (var4 == 0x00) { return; }
    
        var temp5 = memory[0x40:0x60];
        var3 = temp5;
        memory[0x40:0x60] = var3 + (returndata.length + 0x3f & ~0x1f);
        memory[var3:var3 + 0x20] = returndata.length;
        var temp6 = returndata.length;
        memory[var3 + 0x20:var3 + 0x20 + temp6] = returndata[0x00:0x00 + temp6];
    }

Notice how both are using .call anyways:

address(var).call.gas(msg.gas).value(...)...

But it seems that test1 is doing a little less work:

address(var8).call.gas(msg.gas).value(var7)(memory[var6:var6 + var5]);

vs test2:

address(var2).call.gas(msg.gas)(memory[temp3:temp3 + var3 - temp3]);

So, as we can see, in terms of performance they are almost the same, but it seems that using the interface is a little cheaper.

In general, using the interface is better because it is type safe. If you misspell the function name, it will not compile, like IFACE(myContractAddress).fou("etc", 1). But if you use .call directly and misspell the function name, like add.call(abi.encodeWithSignature("fou(string,uint256)", "call foo", 123), it will compile and run, and if the contract you are calling has a fallback function defined, then it will execute the fallback function, will revert otherwise.

0

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.