概述

tx.origin与msg.sender是solidity中容易令人迷惑的两个变量,尤其是当我们直接调用合约时两者的值是相同的。为了更清晰的说明两者的关系我们需要构造合约间的链式调用,如下:

EOA -> Contract A -> Contract B -> Contract C

这里先说明结论:tx.origin始终保持是EOA,msg.sender是其直接调用者的地址。如:合约B中msg.sender的值为合约A的地址,合约C中msg.sender的值为合约B的地址。

简单来说,前者是原始的交易发起者的外部地址(EOA),后者是方法的直接调用者(可以是EOA也可以是合约地址)。以下我们通过简单的合约示例来观察两者值的变化。

抽丝剥茧

下面通过合约直接调用、合约间调用和合约间链式调用的形式由浅入深逐步揭开tx.origin和msg.sender的神秘面纱。

直接调用

此处我们直接通过外部账户(EOA)来调用合约。

代码

contract AA {constructor() {console.log("Contract AA's address:", address(this));}fallback() external {console.log("In AA fallback msg.sender:[%s] tx.origin:[%s] ",msg.sender,tx.origin);}}

上面代码中在构造函数中输出了合约地址,在fallback函数中输出了msg.sender和tx.origin的值。

执行

操作过程如下:

1. 部署合约;

2. 执行合约的fallback方法;

由上面的执行结果(箭头1)我们可以看出,当直接调用合约时msg.sender和tx.origin的值是相同的。

注:我们构造了一个calldata参数来调用函数方法,该参数前4个字节标示要调用的方法。其计算方法是先对方法签名计算hash值(keccak256),然后截取hash的前四个字节(8个16进制字符),最后补0至32个字节。此处我们调用合约中的fallback方法,因此随机8个16进制数即可。

合约间调用

此处我们通过合约间调用来观察两者的值。

代码

contract AA {constructor() {console.log("Contract AA's address:", address(this));}fallback() external {console.log("In AA fallback msg.sender:[%s] tx.origin:[%s] ",msg.sender,tx.origin);}function remoteCall(address _instance) external {console.log("In BB fallback msg.sender:[%s] tx.origin:[%s] ",msg.sender,tx.origin);(bool sucess, ) = _instance.call(abi.encodeWithSignature("nonExistingFunction()"));require(sucess, "call error");}}contract BB {constructor() {console.log("Contract BB's address:", address(this));}fallback() external {console.log("In BB fallback msg.sender:[%s] tx.origin:[%s] ",msg.sender,tx.origin);}}

以上代码合约AA中的remoteCall方法接受一个合约地址(在执行过程中我们输入合约BB的地址),用于调用其它合约的方法,此处我们调用的是合约中不存在的一个方法,因此目标合约的fallback方法会被触发。

执行

操作过程如下:

1. 部署合约AA;

2. 部署合约BB;

3. 以合约BB的地址为参数来调用合约AA的remoteCall方法;

由上面执行结果我们可能看出,当合约AA调用合约BB中的方法时,合约B中的msg.sender为合约AA的地址。

合约间链式调用

代码

此处我们实现文章最开始描述的链式调用,即:

EOA -> Contract A -> Contract B -> Contract C

contract AA {constructor() {console.log("Contract AA's address:", address(this));}fallback() external {console.log("In AA fallback msg.sender:[%s] tx.origin:[%s] ",msg.sender,tx.origin);}function remoteCall(address _instance, address _instance2) external {console.log("In AA fallback msg.sender:[%s] tx.origin:[%s] ",msg.sender,tx.origin);(bool sucess, ) = _instance.call(abi.encodeWithSignature("remoteCall(address)", _instance2));require(sucess, "call error");}}contract BB {constructor() {console.log("Contract BB's address:", address(this));}fallback() external {console.log("In BB fallback msg.sender:[%s] tx.origin:[%s] ",msg.sender,tx.origin);}function remoteCall(address _instance) external {console.log("In BB fallback msg.sender:[%s] tx.origin:[%s] ",msg.sender,tx.origin);(bool sucess, ) = _instance.call(abi.encodeWithSignature("nonExistingFunction()"));require(sucess, "call error");}}contract CC {constructor() {console.log("Contract CC's address:", address(this));}fallback() external {console.log("In CC fallback msg.sender:[%s] tx.origin:[%s] ",msg.sender,tx.origin);}}

上述代码中合约AA通过remoteCall方法接收两个地址,分别是合约BB、CC的地址,其中调用过程为:合约AA的remoteCall -> 合约BB的remoteCall -> 合约CC的fallback

执行

操作过程如下:

1. 部署合约AA;

2. 部署合约BB;

3. 部署合约CC;

4. 调用合约AA的remoteCall方法,其参数分别为合约BB地址、合约CC地址;

由上图的执行过程,我们可以验证文章开头处的结论:tx.origin始终保持不变,其值是交易发起者的外部地址(EOA),msg.sender是其直接调用者的地址。