solidity[48]-call函数

发布时间:2020-07-03 11:36:38 作者:jonson_jackson
来源:网络 阅读:733

调用外部合约的代码

在之前我们已经看到过,使用interface、library的方式调用外部合约的代码。
接下来,我们将为大家补充第三种形式:
在下面的代码中,部署cat合约之后,例如地址为 0x345678.. 在部署animal合约时,传递此cat合约地址。从而能够存储合约的引用。调用test方法即可调用到外部合约的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pragma solidity ^0.4.23;

contract cat{

   uint public a=5;
   function eat() public returns(uint){
       a = 256;
       return a;
   }
}

contract animal{
   cat c;

   constructor(address _addr){
       c = cat(_addr);
   }

   function test() public returns(uint){

       return c.eat();
   }
}

call函数

不管是interface、library还是上面看到的形式,要调用外部代码,都是底层调用了call或者是delecall函数。

call函数基本使用方法

call函数的使用方法,首先需要外部合约的地址。如下例中的animalCall合约,在部署合约时,传递了外部合约cat的地址 0x345678.. ,存储在address c当中。
通过合约地址.call(函数标志符)的方式来调用合约。函数标志符是对于函数声明哈希之后的前4个字节的数据。
如下例中,c.call(bytes4(keccak256(“eat()”)))将调用cat合约中的eat方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
contract cat{

   uint public a=5;
   function eat() public returns(uint){
       a = 256;
       return a;
   }
}
contract animalCall{
   address c;
   constructor(address _addr){
       c = _addr;
   }

   function test1() public returns(bool){
       return c.call(bytes4(keccak256("eat()")));
   }
   function test2() public returns(bool){
       return c.call(bytes4(keccak256("eat")));
   }
}

call函数返回值

call函数的返回值为true或者false。只有当能够找到此方法并执行成功后,会返回true,而如果不能够找到此函数或执行失则会返回false。因此调用test1方法会返回true,调用test2方法会返回false,因为找不到函数。

call 函数与回调函数

call函数如果找不到函数,默认会调用回调函数。
回调函数是特殊的函数,其没有函数名。
其形式为:

1
2
3
function(){

}

对于如下的cat合约。书写了回调函数。假设合约地址为c.那么在外部调用c.call(“abc”);会找不到此函数,默认会执行回调函数.因此在外部调用的c.call(“abc”) 会使得cat合约的状态变量变为999。而且call函数会返回true。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
contract cat{

   uint public a=5;

   function eat() public returns(uint){
       a = 256;
       return a;
   }

   function (){
       a=999;
   }

}

call函数与msg.data

回调函数是非常有用的,例如我们可以在外部调用失败的时候,执行某一些操作。
对于如下的cat合约。书写了回调函数。假设合约地址为c.那么在外部调用c.call(“abc”);会找不到此函数,默认会执行回调函数.回调函数中,将msg.data的值赋值给了fail变量。通过getfail函数可查看call函数传递过来的完整数据。fail变量的值为32个字节0x6162630000000000000000000000000000000000000000000000000000000000,前3个字节是参数字母a、b、c的ASCII码。61、62、63.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pragma solidity ^0.4.23;

contract cat{

   bytes fail;
   function (){
       fail = msg.data;
   }

   function getfail() returns(bytes){
       return fail;
   }

}

call函数修改外部合约的状态变量

在下例中,cat合约与animalcall合约中都有状态变量我们首先部署cat合约,得到地址0x3456.., 接下来,将合约地址作为参数部署anumalCall合约。
调用test2方法,其调用了cat合约的eat方法,修改了cat合约中a的值为256. call函数调用外部合约,修改外部合约中的状态变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pragma solidity ^0.4.23;
contract cat{
   uint public a=5;
   function eat() public returns(uint){
       a = 256;
       return a;
   }
}

contract animalCall{
    uint public a=4;
   address c;

   constructor(address _addr){
       c = _addr;
   }

     function test2() public returns(bool){
       return c.call(bytes4(keccak256("eat()")));
     }
}

delegatecall

delegatecall函数的使用方法和call函数一样,通过合约地址.delegatecall(函数标志符)的方式来调用合约。函数标志符是对于函数声明哈希之后的前4个字节的数据。
library库的远程调用正是使用了delegatecall函数。delegatecall与call不同之处在于,delegatecall不会修改外部合约中的状态变量,其好像是将外部函数的代码加载到了本地合约中执行。会修改本地合约状态变量的值。
例如下面的代码,首先部署cat合约,得到地址0x3456.., 接下来,将合约地址作为参数部署anumalCall合约。
调用test2方法,其调用了cat合约的eat方法,但是却是修改了animalcall合约中的状态变量a。因此当查询后发现,cat合约中的a并没有变化,animalCall合约变量a变为了了256。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pragma solidity ^0.4.23;
contract cat{
   uint public a=5;
   function eat() public returns(uint){
       a = 256;
       return a;
   }
}

contract animalCall{
    uint public a=4;
   address c;

   constructor(address _addr){
       c = _addr;
   }

     function test2() public returns(bool){
       return c.delegatecall(bytes4(keccak256("eat()")));
     }
}

call函数转账与回调函数细节

call函数可以进行转账,并且是transfer与send的底层函数。call函数转账的使用方法是
地址.call.value(转账金额)()

要注意的是,执行转账的时候,如果转账的地址为合约,并且转账合约中有回调函数。那么将默认会执行回调函数。
但是以太坊为了避免重入***,对于transfer与send函数进行了限制。当使用transfer与send函数,回调函数中执行的操作最多不能够超过2300gas。这也就意味着不能够执行转账、赋值等操作,而只能够执行事件触发等操作。
例如下面的代码: 首先部署Receiver合约,得到地址0x3456..,再传递Receiver的地址部署Sender合约。当调用sendMoney方法的时候,为合约地址0x3456..转账的操作会触发回调函数,将状态变量balance的数量增加。但是由于修改状态变量的操作超过了最大2300gas的限制,所以下面的操作不会成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pragma solidity ^0.4.23;

contract Sender {
 function sendMoney(address _receiver) payable {
   _receiver.send(msg.value);
 }
}



contract Receiver {
 uint public balance = 0;

 function () payable {
   balance += msg.value;
 }
}

call函数能够让上面的操作成功。call函数能够指定gas的限制,超过2300gas限制的约束。
如下例所示:
首先部署Receiver合约,得到地址0x3456..,再传递Receiver的地址部署Sender合约。当调用sendMoney方法转移100wei的时候,为合约地址0x3456..转账的操作会触发回调函数,将状态变量balance的数量增加。由于call函数指定的最大gas限制为20317,所以触发回调函数可以将balance的金额修改为100.但是要注意,正因为此,call函数是危险的底层函数,不能够避免重入***的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pragma solidity ^0.4.23;

contract Sender {
 function send(address _receiver) payable {
   _receiver.call.value(msg.value).gas(20317)();
 }
}

contract Receiver {
 uint public balance = 0;

 function () payable {
   balance += msg.value;
 }
}

solidity[48]-call函数

推荐阅读:
  1. 为什么区块链宠物得到这么多资本家的青睐?
  2. 宜信区块链|一篇干货文读懂宜信的区块链实践

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

区块链 以太坊 solidity

上一篇:c语言的标识符可以和保留字同名吗

下一篇:解决python图片中文乱码的方法

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》