Using msg.sender

Inside a smart contract, Solidity provides a special, globally accessible msg.sender variable, which stores the address of the current caller. If the caller is an EOA, msg.sender will be the caller account's address. If the caller is another contract, msg.sender will be that contract's address.

Let's write a test to explore how it works:

    function test_msg_sender() public {
        // Not sure what this will return yet...
        // Let's try the zero address and see.
        assertEq(ttt.msgSender(), address(0));
    }

Run it to find out:

$ forge test -m msg_sender

Running 1 test for src/test/TicTacToken.t.sol:TicTacTokenTest
[FAIL] test_msg_sender() (gas: 16368)
Logs:
  Error: a == b not satisfied [address]
    Expected: 0x0000000000000000000000000000000000000000
      Actual: 0xb4c79dab8f259c7aee6e5b2aa729821864227e84

Test result: FAILED. 0 passed; 1 failed; finished in 2.35ms

Failed tests:
[FAIL] test_msg_sender() (gas: 16368)

In this context, msg.sender is the address 0xb4c79dab8f259c7aee6e5b2aa729821864227e84, which corresponds to the address of the TicTacTokenTest contract. Since our test harness contract is calling the function, it's the msg.sender.

We can access the address of the current contract by calling address(this). Let's update our test:

    function test_msg_sender() public {
        assertEq(ttt.msgSender(), address(this));
    }

Success!

Running 1 test for src/test/TicTacToken.t.sol:TicTacTokenTest
[PASS] test_msg_sender() (gas: 5502)
Test result: ok. 1 passed; 0 failed; finished in 1.64ms

Now let's add another layer to explore how msg.sender changes based on the current caller. At the top of our test file, let's create another contract, Caller. We'll give it access to our game contract and add a function call that calls our game's msgSender() function and returns the address:

contract Caller {

    TicTacToken internal ttt;

    constructor(TicTacToken _ttt) {
        ttt = _ttt;
    }
    function call() public returns (address) {
        return ttt.msgSender();
    }
}

Now let's create two instances of the Caller contract in our test. Any guesses on the value of msg.sender here?

    function test_msg_sender() public {
        Caller caller1 = new Caller(ttt);
        Caller caller2 = new Caller(ttt);

        assertEq(ttt.msgSender(), address(this));

        assertEq(caller1.call(), address(0));
        assertEq(caller2.call(), address(0));
    }

Let's run the test to find out:

$ forge test -m msg_sender
Running 1 test for src/test/TicTacToken.t.sol:TicTacTokenTest
[FAIL] test_msg_sender() (gas: 256093)
Logs:
  Error: a == b not satisfied [address]
    Expected: 0x0000000000000000000000000000000000000000
      Actual: 0x185a4dc360ce69bdccee33b3784b0282f7961aea
  Error: a == b not satisfied [address]
    Expected: 0x0000000000000000000000000000000000000000
      Actual: 0xefc56627233b02ea95bae7e19f648d7dcd5bb132

Test result: FAILED. 0 passed; 1 failed; finished in 826.17ยตs

Failed tests:
[FAIL] test_msg_sender() (gas: 256093)

Encountered a total of 1 failing tests, 0 tests succeeded

Notice that the values are different for caller1 and caller2. Since the caller is now another, intermediate contract, msg.sender is the address of caller1 and caller2 respectively. We can access them in the tests like so:

    function test_msg_sender() public {
        Caller caller1 = new Caller(ttt);
        Caller caller2 = new Caller(ttt);

        assertEq(ttt.msgSender(), address(this));

        assertEq(caller1.call(), address(caller1));
        assertEq(caller2.call(), address(caller2));
    }

Let's run the tests and make sure:

$ forge test -m msg_sender
Running 1 test for src/test/TicTacToken.t.sol:TicTacTokenTest
[PASS] test_msg_sender() (gas: 239187)
Test result: ok. 1 passed; 0 failed; finished in 829.67ยตs

In the context of our tests, msg.sender has always been another contract (remember that our ds-test test harness is itself a Solidity contract), but if an EOA were to call our msgSender function, it would return that EOA's address.