探索以太坊合约委托调用(DelegateCall)
call()
与delegatecall()
的异同。在delegatecall()
的情况下,我们需要关心字段变量的顺序
> * 原文:https://medium.com/coinmonks/delegatecall-calling-another-contract-function-in-solidity-b579f804178c , 作者:[zerofruit](https://zerofruit.medium.com) > * 译文出自:[登链翻译计划](https://github.com/lbc-team/Pioneer) > * 译者:[翻译小组](https://learnblockchain.cn/people/412) > * 校对:[Tiny 熊](https://learnblockchain.cn/people/15) > * 本文永久链接:[learnblockchain.cn/article…](https://learnblockchain.cn/article/1960) 在本文中,我们看看如何调用另一个合约的函数,并更深入讨论`delegatecall`委托调用。 有时,需要在编写以太坊智能合约代码中,与其他合约进行交互。在Solidity中,有几种方法可以实现此目标: ## 如果知道目标合约的ABI,可以直接使用函数签名 假设已经部署了一个简单的合约,称为“Storage”,该合约允许用户保存`val`。 ```javascript pragma solidity ^0.5.8; contract Storage { uint public val; constructor(uint v) public { val = v; } function setValue(uint v) public { val = v; } } ``` 现在我们部署另一个称为“Machine”的合约,它是“Storage”合约的调用方。 “Machine”引用“Storage”合约并更改其`val`。 ```javascript pragma solidity ^0.5.8; import "./Storage.sol"; contract Machine { Storage public s; constructor(Storage addr) public { s = addr; calculateResult = 0; } function saveValue(uint x) public returns (bool) { s.setValue(x); return true; } function getValue() public view returns (uint) { return s.val(); } } ``` 在此案例中,我们知道 `Storage`合约的[ABI](https://learnblockchain.cn/docs/solidity/abi-spec.html)及其地址,以便我们可以使用该地址初始化现有的`Storage`合约,而ABI的作用是告诉我们如何调用`Storage`合约的函数。可以看到`Machine`合约调用了` Storage.setValue()`函数。 编写测试代码检查`Machine.saveValue()`是否实际上调用了` Storage.setValue()`函数并更改了其状态。 ```javascript const StorageFactory = artifacts.require('Storage'); const MachineFactory = artifacts.require('Machine'); contract('Machine', accounts => { const [owner, ...others] = accounts; beforeEach(async () => { Storage = await StorageFactory.new(new BN('0')); Machine = await MachineFactory.new(Storage.address); }); describe('#saveValue()', () => { it('should successfully save value', async () => { await Machine.saveValue(new BN('54')); (await Storage.val()).should.be.bignumber.equal(new BN('54')); }); }); }); ``` 测试通过了! ``` Contract: Machine After initalize #saveValue() should successfully save value (56ms) 1 passing (56ms) ``` ## 如果不知道目标合约的ABI,请使用call或delegatecall 但是,如果调用者(在本例中为“Machine”合约)不知道目标合约的ABI,该怎么办? 其实,我们仍然可以使用`call()`和`delegatecall()`来调用目标合约的函数。 在解释以太坊 Solidity的 `call()`和`delegatecall()`之前,了解EVM如何保存合约变量对于了解`call()`和`delegatecall()`会有所帮助。 ## EVM如何将字段变量保存到存储 在以太坊中,有两种空间可以保存合约的字段变量。一个是“内存”,另一个是“存储”。而且,“ foo”保存到存储意味着“ foo”的值会永久记录到区块链状态中。 那么,单个合约中的如此多的变量又是怎样让彼此不重叠呢? EVM将插槽号分配给字段变量。 ``` contract Sample1 { uint256 first; // slot 0 uint256 second; // slot 1 } ``` ![Image for post](https://img.learnblockchain.cn/pics/20201231162628.png) <center>EVM使用插槽保存字段变量</center> 因为` first`在` Sample1`合约中最先声明,所以分配了0个插槽。每个不同的变量都通过其插槽号来区分。 在EVM中,智能合约存储中具有2<sup>256<sup>个插槽,每个插槽可以保存32字节大小的数据。 ## 如何调用智能合约函数 像Java,Python这样的通用编程代码一样,Solidity函数可以看作是一组命令。当我们说“函数被调用”时,这意味着我们将特定的上下文(如参数)注入到该组命令(函数)中,并且在此上下文中一个接一个地执行命令。 函数、命令组、地址空间可以通过其名称找到。 在以太坊函数中,调用可以用字节码表示,使用 4 + 32 * N个字节表达。这个字节码由两部分组成。 - **函数选择器**:这是函数调用字节码的前4个字节。**函数选择器**是通过对目标函数的名称加上其参数类型(不包括空格)进行哈希(keccak-256哈希函数)取前 4 个字节得到,例如`bytes4(keccak-256(“saveValue(uint)”))`。基于此函数选择器,EVM可以决定应在合约中调用哪个函数。 - **函数参数**:将参数的每个值转换为固定长度为32bytes的十六进制字符串。如果有多个参数,则串联在一起。 如果用户将此4 + 32 * N字节字节代码传递给交易的数据字段。 EVM可以找到应执行的函数,然后将参数注入该函数。 ## 用测试用例解释DelegateCall ## 上下文(context) 当我们谈论智能合约函数的调用方式时,有一个“上下文(context)”一词。实际上,“上下文”一词在软件中是很笼统的概念,其含义根据场合不同有所改变。 当我们谈论程序的执行时,我们可以说“上下文”是指执行时所有环境(如变量或状态)。例如,在执行程序“A”时,执行该程序的用户名是“zeroFruit”,则用户名 “zeroFruit”可以是程序“A”的上下文。 在以太坊智能合约中,有很多上下文,其中一个代表性的事情是`谁执行这个合约`。你可能会在很多Solidity代码中看到` msg.sender`,而` msg.sender`地址的值就是根据执行此合约函数的人,而有所不同。 ## 委托调用(DelegateCall) **委托调用,顾名思义,是调用方合约如何调用目标合约函数的调用机制,但是当目标合约执行其逻辑时,其使用调用方合约的上下文。** ![Image for post](https://img.learnblockchain.cn/...
- 原文:https://medium.com/coinmonks/delegatecall-calling-another-contract-function-in-solidity-b579f804178c , 作者:zerofruit
- 译文出自:登链翻译计划
- 译者:翻译小组
- 校对:Tiny 熊
- 本文永久链接:learnblockchain.cn/article…
在本文中,我们看看如何调用另一个合约的函数,并更深入讨论delegatecall
委托调用。
有时,需要在编写以太坊智能合约代码中,与其他合约进行交互。在Solidity中,有几种方法可以实现此目标:
如果知道目标合约的ABI,可以直接使用函数签名
假设已经部署了一个简单的合约,称为“Storage”,该合约允许用户保存val
。
pragma solidity ^0.5.8;
contract Storage {
uint public val;
constructor(uint v) public {
val = v;
}
function setValue(uint v) public {
val = v;
}
}
现在我们部署另一个称为“Machine”的合约,它是“Storage”合约的调用方。 “Machine”引用“Storage”合约并更改其val
。
pragma solidity ^0.5.8;
import "./Storage.sol";
contract Machine {
Storage public s;
constructor(Storage addr) public {
s = addr;
calculateResult = 0;
}
function saveValue(uint x) public returns (bool) {
s.setValue(x);
return true;
}
function getValue() public view returns (uint) {
return s.val();
}
}
在此案例中,我们知道 Storage
合约的ABI及其地址,以便我们可以使用该地址初始化现有的Storage
合约,而ABI的作用是告诉我们如何调用Storage
合约的函数。可以看到Machine
合约调用了Storage.setValue()
函数。
编写测试代码检查Machine.saveValue()
是否实际上调用了Storage.setValue()
函数并更改了其状态。
const StorageFactory = artifacts.require('Storage');
const MachineFactory = artifacts.require('Machine');
contract('Machine', accounts => {
const [owner, ...others] = accounts;
beforeEach(async () => {
Storage = await StorageFactory.new(new BN('0'));
Machine = await MachineFactory.new(Storage.address);
});
describe('#saveValue()', () => {
it('should successfully save value', async () => {
await Machine.saveValue(new BN('54'));
(await Storage.val()).should.be.bignumber.equal(new BN('54'));
});
});
});
测试通过了!
Contract: Machine
After initalize
#saveValue()
should successfully save value (56ms)
1 passing (56ms)
如果不知道目标合约的ABI,请使用call或delegatecall
但是,如果调用者(在本例中为“Machine”合约)不知道目标合约的ABI,该怎么办?
其实,我们仍然可以使用call()
和delegatecall()
来调用目标合约的函数。
在解释以太坊 Solidity的 call()
和delegatecall()
之前,了解EVM如何保存合约变量对于了解call()
和delegatecall()
会有所帮助。
EVM如何将字段变量保存到存储
在以太坊中,有两种空间可以保存合约的字段变量。一个是“内存”,另一个是“存储”。而且,“ foo”保存到存储意味着“ foo”的值会永久记录到区块链状态中。
那么,单个合约中的如此多的变量又是怎样让彼此不重叠呢? EVM将插槽号分配给字段变量。
contract Sample1 {
uint256 first; // slot 0
uint256 second; // slot 1
}
<center>EVM使用插槽保存字段变量</center>
因为first
在Sample1
合约中最先声明,所以分配了0个插槽。每个不同的变量都通过其插槽号来区分。
在EVM中,智能合约存储中具有2<sup>256<sup>个插槽,每个插槽可以保存32字节大小的数据。
如何调用智能合约函数
像Java,Python这样的通用编程代码一样,Solidity函数可以看作是一组命令。当我们说“函数被调用”时,这意味着我们将特定的上下文(如参数)注入到该组命令(函数)中,并且在此上下文中一个接一个地执行命令。
函数、命令组、地址空间可以通过其名称找到。
在以太坊函数中,调用可以用字节码表示,使用 4 + 32 * N个字节表达。这个字节码由两部分组成。
- 函数选择器:这是函数调用字节码的前4个字节。函数选择器是通过对目标函数的名称加上其参数类型(不包括空格)进行哈希(keccak-256哈希函数)取前 4 个字节得到,例如
bytes4(keccak-256(“saveValue(uint)”))
。基于此函数选择器,EVM可以决定应在合约中调用哪个函数。 - 函数参数:将参数的每个值转换为固定长度为32bytes的十六进制字符串。如果有多个参数,则串联在一起。
如果用户将此4 + 32 * N字节字节代码传递给交易的数据字段。 EVM可以找到应执行的函数,然后将参数注入该函数。
用测试用例解释DelegateCall
上下文(context)
当我们谈论智能合约函数的调用方式时,有一个“上下文(context)”一词。实际上,“上下文”一词在软件中是很笼统的概念,其含义根据场合不同有所改变。
当我们谈论程序的执行时,我们可以说“上下文”是指执行时所有环境(如变量或状态)。例如,在执行程序“A”时,执行该程序的用户名是“zeroFruit”,则用户名 “zeroFruit”可以是程序“A”的上下文。
在以太坊智能合约中,有很多上下文,其中一个代表性的事情是谁执行这个合约
。你可能会在很多Solidity代码中看到msg.sender
,而msg.sender
地址的值就是根据执行此合约函数的人,而有所不同。
委托调用(DelegateCall)
委托调用,顾名思义,是调用方合约如何调用目标合约函数的调用机制,但是当目标合约执行其逻辑时,其使用调用方合约的上下文。
![Image for post](https://img.learnblockchain.cn/...
剩余50%的内容订阅专栏后可查看
- 单篇购买 10学分
- 永久订阅专栏 (90学分)
- 发表于 2021-01-04 09:47
- 阅读 ( 2549 )
- 学分 ( 285 )
- 分类:Solidity
- 专栏:全面掌握Solidity智能合约开发
评论