EIP7(2015-11-15)引入了一个新的操作码DELEGATECALL。
提案内容
以太坊改进提案EIP7在主网区块高度1150000(家园版本硬分叉)生效,该提案在0xf4处引入了一个新的操作码DELEGATECALL。DELEGATECALL和CALLCODE(已不被推荐使用)用法类似,可以调用其它合约的代码并来改变自身合约的状态,区别在于DELEGATECALL会将msg.sender的值传递给子调用,因此子调用会有和原始调用相同的msg.sender。
msg.sender在子调用的传递使得合约更容易去调用另外一个合约的代码,因为子调用将在与父调用相同的环境下执行代码(除了会减少gas和增加栈堆深度外)。
DELEGATECALL需要6个操作数如下:
gas:代码在执行时可能使用的gas量;
to:代码执行的目标地址;
in_offset:输入在内存中的偏移量;
in_size:输入的大小,单位是字节;
out_offset:输出在内存中的偏移量;
out_size:输出的大小。
使用DELEGATECALL时,CALLER和VALUE在被调用者环境中的状态与他们在调用者环境中的状态完全相同。
用例(合约层面)
pragma solidity 0.8.10;
// 被调用函数
contract operator{
uint public flag;
bytes public word;
function getValue() public view returns(uint,bytes memory){
return (flag, word);
}
function changeValue(uint num,bytes memory something) public returns(uint,bytes memory,address){
flag = num;
word = something;
return (flag, word, msg.sender);
}
}
// 进行delegatecall的函数
contract Caller1{
uint public flag;
bytes public word;
address public Op;
constructor(address _addr){
Op = _addr;
}
function getValue() public view returns(uint,bytes memory){
return (flag, word);
}
//从delegatecall的返回值中看到msg.sender是自身账户,因此msg.sender传递给了子调用
function changeValue(uint num,bytes memory something) public returns(bool, bytes memory){
return Op.delegatecall(abi.encodeWithSignature("changeValue(uint256,bytes)",num,something));
}
}
// 用delegatecall调用函数改变自身变量时,变量的存储布局必须和被调用合约一致,否则会写入脏数据。
contract Caller2{
address public Op;
bytes public word;
uint public flag;
constructor(address _addr){
Op = _addr;
}
function getValue() public view returns(uint,bytes memory){
return (flag, word);
}
function changeValue(uint num,bytes memory something) public returns(bool, bytes memory){
return Op.delegatecall(abi.encodeWithSignature("changeValue(uint256,bytes)",num,something));
}
}