基于 bytes4 函数选择器动态调用 solidity 函数
Call solidity function dynamically, based on its bytes4 function selector
在智能合约中,假设我有一个函数想要调用
另一个动态功能,基于一些内部逻辑。
这里它获取函数选择器作为 bytes4
变量。
之后可以使用分支逻辑来调用
目标函数之一。
参见:(A)
但是,是否可以避免这种情况并直接调用函数选择器?
参见:(B)
function myDynamicFunc(uint256 someParam) public {
bytes4 selector = /* ... some internal logic ... */
if (selector == this.myFuncA.selector) {
myFuncA(someParam);
} else if (selector == this.myFuncB.selector) {
myFuncB(someParam);
}
// (A) instead of something like this ^ branching logic (which works)
selector.invoke(someParam);
// (B) can something like this ^ instead by calling the selector directly instead (does not work)
}
详情
myDynamicFunc
是 public
而 myFuncA
+myFuncB
也是 public
.
- 所有 3 个功能都在同一个智能合约中实现。
注释
我在评论中写了 扩展 @kj-crypto
的建议。
如果有 另一种方法 来完成上述 而无需 使用 address(this).call(...)
,我洗耳恭听!
关于选项B:
- 使用
call
将 return 一个字节对象,然后您应该将其转换为适当的类型,在本例中为整数。 (额外的气体使用量)
- 要使用
call
,您需要打包选择器和参数(额外的 gas 使用量)
只要你在同一个合约中使用一个函数,就没有必要使用它的 abi 规范,因为你已经知道函数在哪里,它是如何定义的,你可以毫不费力地调用它。
正在扩展
以上:
Do you mean sth like address(this).call(abi.encodePacked(selector, <func-args>))
?
... 并创建了这个实现:
function myDynamicFunc(uint256 someParam)
public
// pure // --> (1)
returns (bytes memory result) // --> (2)
{
bytes4 selector =
/* ... some internal logic ... */
this.myFuncA.selector;
(bool success, bytes memory resultBytes) =
address(this).call(abi.encodePacked(selector, someParam));
require(success, "failed to call selector"); // --> 3
result = resultBytes;
}
总而言之,答案是:“是的,这是可能的,但不,这不是一个好主意。”
原因:
(1) - 如果您需要函数是 pure
,不幸的是,它不能,因为 address(this).call(...)
可能会修改状态。
(2) - return 类型将默认为 bytes memory
,因为这是 address(this).call(...)
的 return 类型。您可以转换它,但这会增加代码的复杂性,这与最初的动机背道而驰。
(3) - 要正确 处理address(this).call(...)
,需要对元组中的bool
returned 做一些事情。例如使用 require()
。这也违背了最初的动机,因为它只是将分支逻辑从一种形式转移到另一种形式(if ... else
到 require()
),并且在那个时候更昂贵。
(4) - 总的来说,原始函数的 gas 成本似乎低于建议的形式,因此更有优势。请注意,这尚未通过实验验证,如果有人想试一试,这里是 (full solidity file).
在智能合约中,假设我有一个函数想要调用
另一个动态功能,基于一些内部逻辑。
这里它获取函数选择器作为 bytes4
变量。
之后可以使用分支逻辑来调用 目标函数之一。 参见:(A)
但是,是否可以避免这种情况并直接调用函数选择器? 参见:(B)
function myDynamicFunc(uint256 someParam) public {
bytes4 selector = /* ... some internal logic ... */
if (selector == this.myFuncA.selector) {
myFuncA(someParam);
} else if (selector == this.myFuncB.selector) {
myFuncB(someParam);
}
// (A) instead of something like this ^ branching logic (which works)
selector.invoke(someParam);
// (B) can something like this ^ instead by calling the selector directly instead (does not work)
}
详情
myDynamicFunc
是public
而myFuncA
+myFuncB
也是public
.- 所有 3 个功能都在同一个智能合约中实现。
注释
我在评论中写了 @kj-crypto
的建议。
如果有 另一种方法 来完成上述 而无需 使用 address(this).call(...)
,我洗耳恭听!
关于选项B:
- 使用
call
将 return 一个字节对象,然后您应该将其转换为适当的类型,在本例中为整数。 (额外的气体使用量) - 要使用
call
,您需要打包选择器和参数(额外的 gas 使用量)
只要你在同一个合约中使用一个函数,就没有必要使用它的 abi 规范,因为你已经知道函数在哪里,它是如何定义的,你可以毫不费力地调用它。
正在扩展
Do you mean sth like
address(this).call(abi.encodePacked(selector, <func-args>))
?
... 并创建了这个实现:
function myDynamicFunc(uint256 someParam)
public
// pure // --> (1)
returns (bytes memory result) // --> (2)
{
bytes4 selector =
/* ... some internal logic ... */
this.myFuncA.selector;
(bool success, bytes memory resultBytes) =
address(this).call(abi.encodePacked(selector, someParam));
require(success, "failed to call selector"); // --> 3
result = resultBytes;
}
总而言之,答案是:“是的,这是可能的,但不,这不是一个好主意。”
原因:
(1) - 如果您需要函数是 pure
,不幸的是,它不能,因为 address(this).call(...)
可能会修改状态。
(2) - return 类型将默认为 bytes memory
,因为这是 address(this).call(...)
的 return 类型。您可以转换它,但这会增加代码的复杂性,这与最初的动机背道而驰。
(3) - 要正确 处理address(this).call(...)
,需要对元组中的bool
returned 做一些事情。例如使用 require()
。这也违背了最初的动机,因为它只是将分支逻辑从一种形式转移到另一种形式(if ... else
到 require()
),并且在那个时候更昂贵。
(4) - 总的来说,原始函数的 gas 成本似乎低于建议的形式,因此更有优势。请注意,这尚未通过实验验证,如果有人想试一试,这里是 (full solidity file).