Michael.W基于Foundry精读Openzeppelin第8期——Context.sol
- 0. 版本
- 0.1 Context.sol
- 1. 目标合约
- 2. 代码精读
- 2.1 _msgSender()
- 2.2 _msgSender()
0. 版本
[openzeppelin]:v4.8.3,[forge-std]:v1.5.6
0.1 Context.sol
Github: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.3/contracts/utils/Context.sol
Context库是合约开发中最常见的库,同时也是最让人迷惑的库。合约代码里面只将msg.sender和msg.data封装成了函数,感觉多此一举。实际上,本库不提供任何context环境切换的细节功能,而是需要目标合约中重写这两个函数起到了可编辑msg.sender和msg.data的目的。
最经典的应用就是ERC2771。
1. 目标合约
封装Context library成为一个可调用合约并且编写一个最小单元的可编辑msg context的demo:
Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/src/utils/MockContext.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "openzeppelin-contracts/contracts/utils/Context.sol";
contract MockContext is Context {
// worker发来的交易需要经过改变context处理
address _worker;
constructor(){
_worker = msg.sender;
}
event MsgContext(address msgSender, bytes msgData, uint input);
function _msgSender() internal view override returns (address){
if (msg.sender == _worker) {
address sender;
assembly{
// 取calldata的后20个字节作为改变context后的msg.sender
sender := shr(96, calldataload(sub(calldatasize(), 20)))
}
return sender;
} else {
// 如果不是worker,直接返回父类的_msgSender()
return super._msgSender();
}
}
function _msgData() internal view override returns (bytes calldata){
if (msg.sender == _worker) {
// 取calldata的后20个字节之前的全部字节作为改变context后的msg.data
return msg.data[: msg.data.length - 20];
} else {
// 如果不是worker,直接返回父类的_msgData()
return super._msgData();
}
}
function targetFunction(uint number) external {
// emit the msg context in the function
emit MsgContext(_msgSender(), _msgData(), number);
}
}
全部foundry测试合约:
Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/test/utils/Context.t.sol
2. 代码精读
2.1 _msgSender()
直接返回msg.sender。
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
2.2 _msgSender()
直接返回msg.data。
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
foundry代码验证
contract ContextTest is Test {
MockContext mc;
event MsgContext(address msgSender, bytes msgData, uint number);
// 以worker的身份去call MockContext的targetFunction方法
function workerCall(address user, bytes memory inputBytes) external {
// build metadata into calldata
bytes memory callData = abi.encodePacked(inputBytes, user);
// 以worker身份去call MockContext的targetFunction方法
(bool ok,) = address(mc).call(callData);
require(ok, "failed");
}
function test_MsgSenderAndMsgData() external {
mc = new MockContext();
bytes memory callData = abi.encodeCall(mc.targetFunction, (2048));
// worker operation
vm.expectEmit(address(mc));
emit MsgContext(
address(1024),
callData,
2048
);
this.workerCall(address(1024), callData);
// common user operation
vm.expectEmit(address(mc));
emit MsgContext(
address(1024),
callData,
2048
);
vm.prank(address(1024));
mc.targetFunction(2048);
}
}
可见,用户address(1024)自己发交易和worker组装calldata去调用MockContext合约的targetFunction方法,在其方法内部环境context是一样的,即:_msgSender()
、_msgData()
和传参number
都是一样的。
这样就实现了利用Context库对上下文的环境的编辑功能。
ps:
本人热爱图灵,热爱中本聪,热爱V神。
以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。
同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下!
如果需要转发,麻烦注明作者。十分感谢!
公众号名称:后现代泼痞浪漫主义奠基人