Solidity

呼叫已部署合約的功能的最佳和有效方法是什麼?

  • September 29, 2022

如果我想呼叫已經部署的合約的函式,例如 ERC20 代幣,哪一種是呼叫它的函式的最佳和有效方式?使用呼叫方法

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

或使用界面

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

或其他方式?

使用介面呼叫合約是最好的選擇,因為它提供了類型安全。

在氣體效率方面,它們幾乎相同。

檢查以下測試契約:

// 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));
   //...
 }

}

注意test1test2功能。

test1函式使用介面呼叫合約函式。test2使用呼叫。

在 Remix 上進行測試時,在呼叫test1時,它消耗了大約 25161 個 gas。呼叫test2它消耗了大約 25234 氣體。

但是當我反編譯執行時字節碼時,我可以看到編譯器如何處理這兩個函式:

執行時字節碼:608060405234801561001057600080fd5b50600436106100415760003560e01c806366e41cb7146100465780636b59084d14610050578063b12f97bb1461005a575b600080fd5b61004e610078565b005b610058610199565b005b610062610227565b60405161006f919061028c565b60405180910390f35b60008060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16607b6040516024016100c49190610356565b6040516020818303038152906040527f24ccab8f000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161014e91906103f5565b6000604051808303816000865af19150503d806000811461018b576040519150601f19603f3d011682016040523d82523d6000602084013e610190565b606091505b50915091505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166324ccab8f60016040518263ffffffff1660e01b81526004016101f39190610451565b600060405180830381600087803b15801561020d57600080fd5b505af1158015610221573d6000803e3d6000fd5b50505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102768261024b565b9050919050565b6102868161026b565b82525050565b60006020820190506102a1600083018461027d565b92915050565b600082825260208201905092915050565b7f6574630000000000000000000000000000000000000000000000000000000000600082015250565b60006102ee6003836102a7565b91506102f9826102b8565b602082019050919050565b6000819050919050565b600060ff82169050919050565b6000819050919050565b600061034061033b61033684610304565b61031b565b61030e565b9050919050565b61035081610325565b82525050565b6000604082019050818103600083015261036f816102e1565b905061037e6020830184610347565b92915050565b600081519050919050565b600081905092915050565b60005b838110156103b857808201518184015260208101905061039d565b60008484015250505050565b60006103cf82610384565b6103d9818561038f565b93506103e981856020860161039a565b80840191505092915050565b600061040182846103c4565b915081905092915050565b6000819050919050565b6000819050919050565b600061043b6104366104318461040c565b61031b565b610416565b9050919050565b61044b81610420565b82525050565b6000604082019050818103600083015261046a816102e1565b90506104796020830184610442565b9291505056fea2646970667358221220457ef6359d19c61a480bb018e910d55d619678bd20f65427ea5dcd3c93e86ebb64736f6c63430008100033

此處反編譯:https ://ethervm.io/decompile

反編譯test1函式:

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]);
   }

反編譯test2函式:

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];
   }

請注意兩者都是如何使用的.call

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

但似乎test1做的工作少了一點:

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

test2

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

因此,正如我們所見,就性能而言,它們幾乎相同,但使用介面似乎更便宜一些。

一般來說,使用介面更好,因為它是類型安全的。如果你拼錯了函式名,它將無法編譯,例如IFACE(myContractAddress).fou("etc", 1). 但是如果你.call直接使用並且拼錯了函式名,比如add.call(abi.encodeWithSignature("fou(string,uint256)", "call foo", 123),它將編譯並執行,如果你呼叫的合約fallback定義了一個函式,那麼它將執行該fallback函式,否則將恢復。

引用自:https://ethereum.stackexchange.com/questions/136581