# Solidity合约间调用的4种方式

By [tujiao.eth](https://paragraph.com/@tujiao) · 2022-05-26

---

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

在中大型的项目中，我们不可能在一个[智能合约](https://so.csdn.net/so/search?q=%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6&spm=1001.2101.3001.7020)中实现所有的功能，而且这样也不利于分工合作。一般情况下，我们会把代码按功能划分到不同的库或者合约中，然后提供接口互相调用。

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

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

*   CALL
    
*   CALLCODE
    
*   DELEGATECALL
    
*   STATICCALL
    

**1 CALL 和 CALLCODE**
=====================

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

具体来说，CALL修改的是

的storage，而CALLCODE修改的是

的storage。

![https://img-blog.csdnimg.cn/4d16d6e3cb6440d385c4eacb6cfc4fca.png](https://storage.googleapis.com/papyrus_images/0bf39fcf7878839b78226b3cba3d997f318d1114bb842525d446c738354909c5.png)

https://img-blog.csdnimg.cn/4d16d6e3cb6440d385c4eacb6cfc4fca.png

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

    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++;
        }
    
    }
    123456789101112131415161718192021222324
    

我们先调用一下inc\_call()，然后查询合约A和B中x的值有什么变化：

![https://img-blog.csdnimg.cn/e476714da93f4a2d99ee70f1cd10c87b.png](https://storage.googleapis.com/papyrus_images/b7de015c8b423b4845bf8fda28cfdd2859ea228fbbf515ce02a1adf78d97a91f.png)

https://img-blog.csdnimg.cn/e476714da93f4a2d99ee70f1cd10c87b.png

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

我们再调用一下inc\_callcode()试试：

![https://img-blog.csdnimg.cn/c598de0dc7564f68af1af12b29333503.png](https://storage.googleapis.com/papyrus_images/fc55bee820a53eb0dabf1cb884c93b6a9df4466d56a95e0e5c7fbbc81cb708bc.png)

https://img-blog.csdnimg.cn/c598de0dc7564f68af1af12b29333503.png

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

**2 CALLCODE 和 DELEGATECALL**
=============================

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

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

具体来说，DELEGATECALL会一直使用原始调用者的地址，而CALLCODE不会。

![https://img-blog.csdnimg.cn/33cd1fd4eb614f7b93a262c3a9c5fad1.png](https://storage.googleapis.com/papyrus_images/0a74bc60c8b797afe9163452d2500c045cde08b0c7d7e62ee9d3eb7d51a750ea.png)

https://img-blog.csdnimg.cn/33cd1fd4eb614f7b93a262c3a9c5fad1.png

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

    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);
        }
    
    }
    1234567891011121314151617181920212223242526
    

我们首先调用一下inc\_callcode()，观察一下log输出：

![https://img-blog.csdnimg.cn/20181107163125761.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R1cmtleUNvY2s=,size_16,color_FFFFFF,t_70](https://storage.googleapis.com/papyrus_images/d88831ca5bc74916588dcc2bcf3cd9d5bae852ff756428c8bc2b02e13fbd3695.jpg)

https://img-blog.csdnimg.cn/20181107163125761.png?x-oss-process=image/watermark,type\_ZmFuZ3poZW5naGVpdGk,shadow\_10,text\_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R1cmtleUNvY2s=,size\_16,color\_FFFFFF,t\_70

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

我们再调用一下inc\_delegatecall()，观察一下log输出：

![https://img-blog.csdnimg.cn/20181107163155304.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R1cmtleUNvY2s=,size_16,color_FFFFFF,t_70](https://storage.googleapis.com/papyrus_images/ae4ed3ae8088c33baaf7f9b082562af1ad8ec1308dae84f2d17a0396e51bb0c9.jpg)

https://img-blog.csdnimg.cn/20181107163155304.png?x-oss-process=image/watermark,type\_ZmFuZ3poZW5naGVpdGk,shadow\_10,text\_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R1cmtleUNvY2s=,size\_16,color\_FFFFFF,t\_70

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

**3 STATICCALL**
================

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

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

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

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

![https://img-blog.csdnimg.cn/20181107163210212.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R1cmtleUNvY2s=,size_16,color_FFFFFF,t_70](https://storage.googleapis.com/papyrus_images/98946273e81bdbada9ba6bb704b2cd972f60e9140ff6cc3f3e9c882aea7a3c08.jpg)

https://img-blog.csdnimg.cn/20181107163210212.png?x-oss-process=image/watermark,type\_ZmFuZ3poZW5naGVpdGk,shadow\_10,text\_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R1cmtleUNvY2s=,size\_16,color\_FFFFFF,t\_70

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

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

**4 总结**
========

总结一下：

1.  `call`使用的是被调用者的上下文
    
2.  `callcode`和`delegatecall`使用调用者的上下文
    
3.  `call`可以涉及账户间的操作，另外两个可以理解为了放在以太坊上的类库，仅仅是调用他们的函数方法和storage。
    
4.  `callcode`和`delegatecall` 的区别在于后者将`calleradress`和`value`始终指向原始调用的eoa外部账户，后者可能的最大用处就是可以在调用`delegatecall`的时候再调用`call`来对原始账户进行转账操作。

---

*Originally published on [tujiao.eth](https://paragraph.com/@tujiao/solidity-4)*
