Msg.sender for initMethod (v2.3.1) / methodName (v2.4.0) for upgradeProxy (lib) or update (cli) in a zos project

I’m thinking through the best path towards calling a function with a onlyX modifier as part of an initMethod (v2.3.1) / methodName (v2.4.0) for upgradeProxy (lib) or update (cli) in a zos project.

When calling a function as part of an upgrade in that way, which of these is the msg.sender?

A. The address corresponding to the the private key doing the truffle deployment (I’m 90% sure I’ve concluded it’s not this)
B. The contract serving as the proxy admin (my best guess at this point)
C. The owner of the contract serving as the proxy admin
D. Other

I can/may do some experimentation and check, but I figured this thread would be helpful for others to stumble upon in case the question is a common one - plus I’m curious for the rationale behind the answer.

I guess it might depend whether it’s a “simple project” or not. I haven’t fully grokked the difference yet. Are there any docs on that distinction?

Thanks!

2 Likes

Hi @pcowgill

The documentation shows the upgradeAndCall function as part of the ProxyAdmin:

Testing this on zos 2.4.1 with the cli shows it is B the proxy admin when calling an initialize function as part of an upgrade.

If the initialize function was called not as part of the zos update then it was A, i.e. zos update without an initialize function and then call the initialize function was called separately.

Setup

mkdir sender
cd sender
npm init -y
npm i zos
npx zos init

In another terminal run ganache-cli

$ ganache-cli -d
Ganache CLI v6.4.4 (ganache-core: 2.5.6)

Available Accounts
==================
(0) 0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1 (~100 ETH)
(1) 0xffcf8fdee72ac11b5c542428b35eef5769c409f0 (~100 ETH)
(2) 0x22d491bde2303f2f43325b2108d26f1eaba1e32b (~100 ETH)
...

MyContract.sol

pragma solidity ^0.5.0;

import "zos-lib/contracts/Initializable.sol";

contract MyContract is Initializable {
    address private _sender;

    function initialize() public initializer {
        _sender = msg.sender;
    }

    function getSender() public view returns (address) {
        return _sender;
    }
}

zos create

$ npx zos create
✓ Compiled contracts with solc 0.5.10 (commit.5a6ea5b1)
? Pick a contract to instantiate MyContract
? Pick a network development
✓ Contract MyContract deployed
All contracts have been deployed
? Do you want to call a function on the instance after creating it? Yes
? Select which function * initialize()
✓ Setting everything up to create contract instances
✓ Instance created at 0xCfEB869F69431e42cdB54A4F4f105C19C080A601
0xCfEB869F69431e42cdB54A4F4f105C19C080A601

get sender of initialize = ganache-cli account[0]

$ npx zos call
? Pick a network development
? Pick an instance MyContract at 0xCfEB869F69431e42cdB54A4F4f105C19C080A601
? Select which function getSender()
✓ Method 'getSender()' returned: 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1

upgrade MyContract.sol

Add upgrade function with _upgraded guard.

pragma solidity ^0.5.0;

import "zos-lib/contracts/Initializable.sol";

contract MyContract is Initializable {
    address private _sender;
    bool private _upgraded;

    function initialize() public initializer {
        _sender = msg.sender;
    }

    function upgrade() public {
        require(!_upgraded, "MyContract: already upgraded");
        _upgraded = true;
        _sender = msg.sender;
    }

    function getSender() public view returns (address) {
        return _sender;
    }
}

zos upgrade

call upgrade as part of upgrade

$ npx zos upgrade
? Pick a network development
✓ Compiled contracts with solc 0.5.10 (commit.5a6ea5b1)
- New variable 'bool _upgraded' was added in contract MyContract in contracts/MyContract.sol:1 at the end of the contract.
See https://docs.zeppelinos.org/docs/writing_contracts.html#modifying-your-contracts for more info.
✓ Contract MyContract deployed
All contracts have been deployed
? Which instances would you like to upgrade? Choose by name
? Pick an instance to upgrade MyContract
? Do you want to call a function on the instance after upgrading it? Yes
? Select which function upgrade()
✓ Instance upgraded at 0xCfEB869F69431e42cdB54A4F4f105C19C080A601. Transaction receipt: 0x2918af744d06eedf65421aae9fdddca9d38525672cc678cb96ed8343985ac77f

get sender of upgrade= proxyAdmin contract

$ npx zos call
? Pick a network development
? Pick an instance MyContract at 0xCfEB869F69431e42cdB54A4F4f105C19C080A601
? Select which function getSender()
✓ Method 'getSender()' returned: 0x5b1869D9A4C187F2EAa108f3062412ecf0526b24
0x5b1869D9A4C187F2EAa108f3062412ecf0526b24

zos.dev-XXXXX.json

https://docs.zeppelinos.org/docs/configuration.html#zos-network-json
The field <proxyAdmin> contains the address of the ProxyAdmin contract, used to manage the transparent proxy pattern in the project’s proxies.

...

  "proxyAdmin": {
    "address": "0x5b1869D9A4C187F2EAa108f3062412ecf0526b24"
  }
2 Likes

@abcoathup Thanks so much for looking into this for me!! That’s a very, very helpful response, and that’s a great method for looking into questions like this.

There’s one small thing that I think needs to change in what you posted above: The second to last heading should say, “get sender of initializeUpdate = proxyAdmin contract” as opposed to “get sender of initializeUpdate = proxyAdmin owner” Because it matches the address in zos.dev-XXXXX.json, and here’s some info about what that address represents in the config file:

“The field <proxyAdmin> contains the address of the ProxyAdmin contract, used to manage the transparent proxy pattern in the project’s proxies.” (Source)

2 Likes

@abcoathup One other tiny nit to pick for clarity: Maybe also worth renaming the initializeUpdate method to something else, now that the functions optionally called during updates are called methodName in 2.4.0 rather than initMethod in 2.3.1? Given that the initialize concept replaces the constructor in zos, it’s probably cleaner to never use that word elsewhere during upgrades. What do you think?

2 Likes

For anyone following this, the TL;DR answer thanks to @abcoathup’s research into this is:

When calling a function as part of an upgrade, (B) the contract serving as the proxy admin (not the owner of the proxy admin contract) is the msg.sender for the function.

2 Likes

Good spot. When I looked at your question I expected the answer to be A but then found out it wasn’t, I wrote my response as if it was C proxyAdmin owner, and then realized it was actually B proxyAdmin contract and then found the code of how it works for upgradeAndCall function. The text was a hangover from my incorrect answer. Sorry, my mistake.

I have changed the code to use a function called upgrade. I like using a different word from initialize so it isn’t considered a constructor, but it is a function associated with the upgrade. Need to make it clear that this function should be guarded so that it is only called once, and ideally as part of the upgrade using upgradeAndCall. For further versions it could be called upgrade2, upgrade3

Finally, thanks for this awesome question. :pray: I learnt a lot in finding out the answer.

2 Likes