呼叫已部署合約的功能的最佳和有效方法是什麼?
如果我想呼叫已經部署的合約的函式,例如 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)); //... } }
注意
test1
和test2
功能。
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
函式,否則將恢復。