合约调用
delegateCall
delegateCall
在low-level
层将外部合约的代码整个复制到当前EVM
环境,与origin
交易复用同一个EVM
执行环境delegateCall
执行在本合约的EVM
环境delegateCall
拥有完整的外部调用合约的合约代码delegateCall
按照外部合约代码去更新/读取EVM
环境中的slot
的状态变量
返回状态
delegateCall
返回(bool success, bytes memory data)
boolean
表明当前调用是否成功data
是执行函数返回的数据delegateCall
执行失败的话,不会revert
回滚交易,因此需要执行异常判断
delegateCall
外部合约不存在的函数- 外部合约存在缺省
fallback()
,就执行fallback()
逻辑 - 不存在缺省函数的话,直接返回
false
- 外部合约存在缺省
Solidity Examples
delegateCall
按照外部合约代码去更新/读取EVM
环境中的slot
的状态变量- 示例合约在
EVM
中的逻辑为:slot1 += slot0
- 本合约按照上述逻辑更新自己合约内部的
slot
参数
- 本合约按照上述逻辑更新自己合约内部的
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;
contract Called {
uint256 base = 3;
uint256 public number; //slot 1
function increment() public returns (uint256) {
number += base; // slot1 += slot0
return number;
}
}
contract Caller {
uint256 base = 99; // 按照业务逻辑会读取slot0数据,参与计算
uint256 public myNumber; // 每次调用:slot1 += slot0(myNumber+=99)
// there is a new storage variable here
address public calledAddress = 0xd9145CCE52D386f254917e481eB44e9943F39138;
function execute(address to, uint256 txGas)
external
returns (bool success)
{
bytes memory data = abi.encodeWithSignature("increment()");
return _execute(to, data, txGas);
}
function _execute(
address to,
bytes memory data,
uint256 txGas
) internal returns (bool success) {
assembly {
success := delegatecall(
txGas,
to,
add(data, 0x20),
mload(data),
0,
0
)
}
}
function delegateCallIncrement(
address delegatedCalled //28187 gas cost
) public returns (uint256) {
(bool success, bytes memory resdata) = delegatedCalled.delegatecall(
abi.encodeWithSignature("increment()") //0xd09de08a
);
if (!success) {
assembly {
revert(add(resdata, 32), mload(resdata))
}
} else {
// 解码call|delegateCall的返回值
return abi.decode(resdata, (uint256));
}
}
}
Immutable|Constant in delegateCall
delegateCall
是将外部合约的代码整个拷贝到当前EVM
执行immutable|constant
是编码到合约代码存储,并不占据slot
存储- 也就是
immutable|constant
类型的数据,是从外部合约代码直接读取
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Caller {
uint256 private immutable a = 3;
function getValueDelegate(address called) public returns (uint256) {
(bool success, bytes memory data) = address(called).delegatecall(
abi.encodeWithSignature("getValue()")
);
if (!success) {
_revert(data, "Calles_Failure");
}
return abi.decode(data, (uint256)); // returns 2
}
function _revert(bytes memory returndata, string memory errorMessage)
private
pure
{
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
contract Called {
uint256 private immutable a = 2;
function getValue() public pure returns (uint256) {
return a;
}
}
Call types validation
delegateCall
- 交易执行在当前的
EVM
执行环境 address(this)
= 当前合约的地址__self
是immutable
类型数据,因此该值不从slot
读取,而是从外部合约代码读取:读取到的是外部合约的合约地址- 因此,采用
delegateCall
的话,两者的地址应该不同
- 交易执行在当前的
call
- 交易执行在新的
EVM
执行环境 address(this)
= 被调用的外部合约的地址__self
是immutable
类型数据,因此该值不从slot
读取,而是从外部合约代码读取:读取到的是外部合约的合约地址- 因此,采用
call
的话,两者的地址应该相同
- 交易执行在新的
总之:
delegateCall –> require(address(this) != __self)
call –> require(address(this) == __self)
address private immutable __self = address(this);
/**
* @dev Check that the execution is being performed through a delegatecall call
* 要求使用delegateCall调用该修饰器修饰的函数
* 使用delegateCall调用时:
** 1. address(this) = 当前合约的地址
** 2. __self是immutable类型数据,从外部合约代码读取,读取到的应该是 外部合约的合约地址
* 因此,采用 delegateCall的话,两者的地址应该不同
*/
modifier onlyProxy() {
require(
address(this) != __self,
"Function must be called through delegatecall"
);
require(
_getImplementation() == __self,
"Function must be called through active proxy"
);
_;
}
/**
* @dev Check that the execution is not being performed through a delegate call.
* 要求使用call调用该修饰器修饰的函数
* 使用call调用时, 交易被打包进新的EVM 执行环境
** 1. address(this) = 外部调用的合约地址
** 2. __self是immutable类型数据,从外部合约代码读取,读取到的应该是 外部合约的合约地址
* 因此,采用 call的话,两者的地址应该相同
*/
modifier notDelegated() {
require(
address(this) == __self,
"UUPSUpgradeable: must not be called through delegatecall"
);
_;
}