solidity合约间调用的4种方式


孔乙己懂“回”字的四种写法,你会智能合约的四种调用方式吗?

在中大型的项目中,我们不可能在一个智能合约中实现所有的功能,而且这样也不利于分工合作。一般情况下,我们会把代码按功能划分到不同的库或者合约中,然后提供接口互相调用。

在Solidity中,如果只是为了代码复用,我们会把公共代码抽出来,部署到一个library中,后面就可以像调用C库、Java库一样使用了。但是library中不允许定义任何storage类型的变量,这就意味着library不能修改合约的状态。如果需要修改合约状态,我们需要部署一个新的合约,这就涉及到合约调用合约的情况。

合约调用合约有下面4种方式:

  • CALL
  • CALLCODE
  • DELEGATECALL
  • STATICCALL

1 CALL 和 CALLCODE

CALL和CALLCODE的区别在于:代码执行的上下文环境不同。

具体来说,CALL修改的是被调用者的storage,而CALLCODE修改的是调用者的storage。
在这里插入图片描述

我们写个合约验证一下我们的理解:

pragma solidity ^0.4.25;

contract A {
    int public x;
    

    function inc_call(address _contractAddress) public {
        _contractAddress.call(bytes4(keccak256("inc()")));
    }
    function inc_callcode(address _contractAddress) public {
        _contractAddress.callcode(bytes4(keccak256("inc()")));
    }

}

contract B {
    int public x;
    

    function inc() public {
        x++;
    }

}

我们先调用一下inc_call(),然后查询合约A和B中x的值有什么变化:
在这里插入图片描述

可以发现,合约B中的x被修改了,而合约A中的x还等于0。

我们再调用一下inc_callcode()试试:
在这里插入图片描述

可以发现,这次修改的是合约A中x,合约B中的x保持不变。

2 CALLCODE 和 DELEGATECALL

实际上,可以认为DELEGATECALL是CALLCODE的一个bugfix版本,官方已经不建议使用CALLCODE了。

CALLCODE和DELEGATECALL的区别在于:msg.sender不同。

具体来说,DELEGATECALL会一直使用原始调用者的地址,而CALLCODE不会。
在这里插入图片描述

我们还是写一段代码来验证我们的理解:

pragma solidity ^0.4.25;

contract A {
    int public x;
    

    function inc_callcode(address _contractAddress) public {
        _contractAddress.callcode(bytes4(keccak256("inc()")));
    }
    function inc_delegatecall(address _contractAddress) public {
        _contractAddress.delegatecall(bytes4(keccak256("inc()")));
    }

}

contract B {
    int public x;
    

    event senderAddr(address);
    function inc() public {
        x++;
        emit senderAddr(msg.sender);
    }

}

我们首先调用一下inc_callcode(),观察一下log输出:

在这里插入图片描述

可以发现,msg.sender指向合约A的地址,而非交易发起者的地址。

我们再调用一下inc_delegatecall(),观察一下log输出:

在这里插入图片描述

可以发现,msg.sender指向的是交易的发起者。

3 STATICCALL

STATICCALL放在这里似乎有滥竽充数之嫌,因为目前Solidity中并没有一个low level API可以直接调用它,仅仅是计划将来在编译器层面把调用view和pure类型的函数编译成STATICCALL指令。

view类型的函数表明其不能修改状态变量,而pure类型的函数则更加严格,连读取状态变量都不允许。

目前是在编译阶段来检查这一点的,如果不符合规定则会出现编译错误。如果将来换成STATICCALL指令,就可以完全在运行时阶段来保证这一点了,你可能会看到一个执行失败的交易。

话不多说,我们就先看看STATICCALL的实现代码吧:

可以看到,解释器增加了一个readOnly属性,STATICCALL会把该属性置为true,如果出现状态变量的写操作,则会返回一个errWriteProtection错误。

就聊到这里,相信大家已经掌握了合约的四种调用方式了吧~

4 总结

总结一下:

  1. call使用的是被调用者的上下文
  2. callcodedelegatecall使用调用者的上下文
  3. call可以涉及账户间的操作,另外两个可以理解为了放在以太坊上的类库,仅仅是调用他们的函数方法和storage。
  4. callcodedelegatecall 的区别在于后者将calleradressvalue始终指向原始调用的eoa外部账户,后者可能的最大用处就是可以在调用delegatecall的时候再调用call来对原始账户进行转账操作。
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Solidity 中,合约调用可以通过合约地址进行实现。合约调用的语法如下: ``` contract ContractA { function foo(uint256 x) public returns (uint256) { // do something } } contract ContractB { ContractA contractA = ContractA(0x1234567890123456789012345678901234567890); function bar() public { uint256 result = contractA.foo(123); // do something with result } } ``` 在上面的示例中,合约 B 调用合约 A 中的函数 `foo`,并将值 `123` 作为参数传递给了该函数。需要注意的是,调用 `foo` 函数会消耗 Gas,因此需要确保调用合约有足够的 Gas 储备。 在合约调用时,还需要注意传递参数的类型和顺序。Solidity 支持多数据类型,包括整数、布尔值、字符串、地址等等。在传递参数时,需要确保参数类型和数量与被调用函数的参数类型和数量相匹配,否则会导致编译错误。 另外,如果被调用的函数是视图函数(即不修改状态的函数),则可以使用 `call` 方法进行调用。例如: ``` contract ContractA { function foo(uint256 x) public view returns (uint256) { // do something } } contract ContractB { ContractA contractA = ContractA(0x1234567890123456789012345678901234567890); function bar() public { (bool success, uint256 result) = contractA.call(abi.encodeWithSignature("foo(uint256)", 123)); require(success, "failed to call ContractA.foo"); // do something with result } } ``` 在上面的示例中,`ContractB` 调用了 `ContractA` 中的函数 `foo`,并使用 `call` 方法进行调用。需要注意的是,使用 `call` 方法时需要手动编码参数,因此需要使用 `abi.encodeWithSignature` 函数将参数编码为字节数组。另外,如果调用失败,需要使用 `require` 断言确保调用成功。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值