How to call methods of the upgraded contract using proxy address?

I’m following this great tutorial: OpenZeppelin Upgrades: Step by Step Tutorial for Truffle

Here are my deployed contracts, so far so good.

  • Migrations: https://rinkeby.etherscan.io/address/0x8d11f5d53f8efc86be553d5298a4115b6b3c224a#code

  • Box: https://rinkeby.etherscan.io/address/0xc5097c49f4f59c1df18b4320bda9c7a74031a3d8#code

  • ProxyAdmin: https://rinkeby.etherscan.io/address/0x899fc2cb368daa3fe1ff852ba367a9e2f9bb1afa#code

  • AdminUpgradeabilityProxy: https://rinkeby.etherscan.io/address/0x35937192e3185bd137a899f85d5f2543bc881bc1#code

  • BoxV2: https://rinkeby.etherscan.io/address/0x637f5818b98366ca3f8595a2e09297ad4840e7b5#code

I’m able to send a transaction from my front-end to BoxV2, it works, it increments the counter.

Link to the live code: https://plnkr.co/edit/EpzPkHKp0gzPi0Ui?open=lib%2Fscript.js

<!doctype html>
<html>
  <head>
    <script src='http://ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js'></script>
    <script src="https://cdn.jsdelivr.net/npm/ethers@5.0.17/dist/ethers.umd.js" type="text/javascript"></script>
    <script>
      var app = angular.module("app", []);

      app.controller("ctrl", function($scope) {

        $scope.increment = function() {
          console.log("increment")
          contract.increment();
        }

      });

    </script>
  </head>
  <body ng-app="app" ng-controller="ctrl">
    <button ng-click="increment()">Call increment()</button>
  </body>

<script>
    ABI = [{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"increment","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"retrieve","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"store","outputs":[],"stateMutability":"nonpayable","type":"function"}];
    // address = "0x35937192e3185bd137a899F85d5F2543bc881bC1"; // AdminUpgradeabilityProxy
    address = "0x637f5818b98366ca3f8595a2e09297ad4840e7b5"; // BoxV2

    async function init() {
      await window.ethereum.enable();
      const provider = new ethers.providers.Web3Provider(window.ethereum)
      let network = await provider.getNetwork(); console.log(network);      
      signer = provider.getSigner();
      contract = new ethers.Contract(address, ABI, signer);
    }

    init();

  </script>

</html>

BUT

The whole point of proxy upgradeable pattern:

  1. I keep the proxy address stable
  2. I upgrade the implementation

When I call increment() on AdminUpgradeabilityProxy and then retrieve on BoxV2 it does not get updated…

See this transaction: https://rinkeby.etherscan.io/tx/0xbc15bed9b3b5d75d37a6202a5f376ea1cce43cce22ea40e14db16d342cf348ce

The counter in BoxV2 wasn’t incremented that way… What is the proxy address I should use?

1 Like

Hi @marsxr,

When interacting with the upgradeable contract you should use the ABI of the implementation contract (Box or BoxV2) with the address of the proxy contract.

It looks like you were interacting the with implementation contract BoxV2 using the implementation address.

Etherscan is great to see this process, where you can interact with the upgrabeable contract using the proxy address:

UNDERSTAND. GOT IT.

BUT…

// Stored content is: a content written on Etherscan on BoxV2 from EOA
// Stored content is: From localhost via AdminUpgradeabilityProxy

Here is the live code, open console to see debug messages: https://plnkr.co/edit/3DIvFn3SMNBkBZSd?open=lib%2Fscript.js&preview

I think it has something to do with how the storage works. Can you confirm this is an expected behaviour or am I doing something wrong?

In case I’ll have to return to dig deeper, links for handy reference:


Bonus question:OPSEC

The main admin is Gnosis Safe multisig.

Deployer is EOA (externally owned account).

Compromise of the deployer private key is non-catasthrophic.

Maybe I can add transferOwnership() function to the Migrations.sol?

Most of the time the ownership will belong to multisig. When the update is due, the control can be moved to EOA temporarily, so that it can be used with Truffle?

Secrets of Etherscan

Kaboom!

image

1 Like

Hi @marsxr,

I am not sure what your question is. Can you give a bit more detail? When we interact with the proxy, we are writing to the storage of the proxy. If we directly interact with an implementation contract (not via the proxy) then we are writing to the storage of the implementation contract. For upgradeable contracts we should always interact via the proxy address. For more details see the Learn guides: https://docs.openzeppelin.com/learn/upgrading-smart-contracts#how-upgrades-work


Etherscan supports proxies and you can set a contract as a proxy as long as the proxy and the implementation contract have been verified. I just set this with your proxy contract.

This allows you to read and write to the upgradeable contract.


You can deploy your proxy, ProxyAdmin and implementation contract using an EOA (deployProxy) and then transfer ownership to a Gnosis Safe. (admin.transferProxyAdminOwnership)

For future upgrades you can deploy the new implementation contract using an EOA with prepareUpgrade and then do the upgrade with Gnosis Safe App.

Thank you for confirming!


Using EOA for the prepareUpgrade makes sense.

I was thinking about transferOwnership() to be included in the Migrations.sol so the ownership can be transferred to the Gnosis Safe.

When the update is due, transfer the ownership to EOA to perform operations and then back to the Gnosis Safe.

Naturally, the actual upgrade will happen through Gnosis Safe as described here: https://blog.openzeppelin.com/upgrades-app-for-gnosis-safe/

1 Like

Hi @marsxr,

I assume you mean in the migrations script (rather than in the Migrations.sol smart contract). So your migrations script has a deployProxy and then a transferOwnership to transfer to a Gnosis Safe. That would work.

By default truffle ships with the following Migrations.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.8.0;

contract Migrations {
  address public owner = msg.sender;
  uint public last_completed_migration;

  modifier restricted() {
    require(
      msg.sender == owner,
      "This function is restricted to the contract's owner"
    );
    _;
  }

  function setCompleted(uint completed) public restricted {
    last_completed_migration = completed;
  }
}

It means that the EOA that is the owner will remain the owner forever.

I’m thinking about plugging Ownable.sol and transferOwnership to the Gnosis Safe.

I think it is a justifiable move, I can reasonable explain the motivation, the Gnosis Safe being the “crown jewel”, holding the rights to mint, deploy, upgrade :sunglasses:

1 Like

Hi @marsxr,

The Migrations contract just keeps track on chain of the last completed migration script using truffle migrate.

I don’t think you need to be too worried about this as you can just change the numbering of the migration scripts if you needed. The Migrations contract doesn’t do anything else that I am aware of. So I wouldn’t bother trying to change the owner of this.

1 Like