solidity tx.origin和msg.sender那些事儿

概述

        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方法;

solidity tx.origin和msg.sender那些事儿_第1张图片

由上面的执行结果(箭头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方法;

solidity tx.origin和msg.sender那些事儿_第2张图片

由上面执行结果我们可能看出,当合约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地址;

solidity tx.origin和msg.sender那些事儿_第3张图片

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

你可能感兴趣的:(区块链,区块链,智能合约,开发语言,javascript,去中心化)