【区块链安全 | 第三十九篇】合约审计之delegatecall(一)_delegatecall和call的区别
文章目录
外部调用函数
在 Solidity 中,常见的两种底层外部函数调用方式是:call 和 delegatecall。
说明:callcode 已在 Solidity 0.5.0 版本后弃用,不再推荐使用,因此本文不再介绍。
call
call 是一种底层调用方法,适用于向其他合约发送消息或调用函数,同时也支持附带以太币(Ether)。它语法灵活,常用于动态调用。
(bool success, bytes memory data) = target.call{value: msg.value}(abi.encodeWithSignature(\"func(uint256)\", 123));
特点:
1.使用的是目标合约的代码和存储环境;
2.当前合约的 msg.sender 和 msg.value 不会传递给被调用合约;
3.返回值为 (bool success, bytes memory data),失败时不会自动回退(revert),需手动处理;
4.常用于与未知接口或外部插件合约交互(如代理合约);
5.可发送 Ether,适用于多种场景。
delegatecall
delegatecall 也是底层调用方式,但与 call 不同,其执行上下文是当前合约的环境。
(bool success, bytes memory data) = target.delegatecall(abi.encodeWithSignature(\"func(uint256)\", 123));
特点:
1.调用目标合约的代码,但使用的是当前合约的存储(storage)、msg.sender 和 msg.value;
2.目标合约不应定义自己的状态变量,否则可能引发存储冲突;
3.常用于代理合约(Proxy Pattern)与可升级合约场景;
4.存在安全风险,需谨慎使用被调用合约,防止覆盖关键变量。
call 与 delegatecall 的区别示例
我们通过一个对比实验,直观理解两者在执行环境中的差异。
编写并部署如下的 A 合约:
// SPDX-License-Identifier: MITpragma solidity ^0.8.0;contract A { address public a; function test() public returns (address b) { b = address(this); // 获取当前合约地址 a = b; // 保存到状态变量 a 中 }}
合约部署完成后,记录合约 A 地址为 0xcD6a42782d230D7c13A74ddec5dD140e55499Df9。
我们将 A 合约地址填入 B 合约中,并部署如下代码:
// SPDX-License-Identifier: MITpragma solidity ^0.8.0;contract B { address public a; // A 合约地址 address Aaddress = address(0xcD6a42782d230D