Is it possible to deploy upgradeable contracts to Quorum?

I’m wondering if it possible to deploy upgradeable contracts to Quorum?

Asking because when deploying to Rinkeby, it is using some precompiled contracts. See this thread: Add instructions on how to verify to Upgrades Plugins step by step guides

  • Migrations and Box are 0.7.0
  • ProxyAdmin and AdminUpgradeabilityProxy are 0.6.8 (precompiled)

I think it shouldn’t matter, deploying precompiled bytecode should work just fine?

Don’t trust. Verify. How do I verify that the upgrade works?

When working on Ethereum (not Quorum) I decided to test on Rinkeby because:

  • better tooling - can use Etherscan
  • environment closer to production

When using Quorum I’m experimenting with:

They are pretty good but nowhere as advanced as Etherscan, they do not support upgradeable contracts.

I think that in order to verify that the upgrade process works, I’ll need to write some tests in Truffle, luckily we have tutorial for that: OpenZeppelin Upgrades: Step by Step Tutorial for Truffle

  Contract: Box (proxy)
    ✓ retrieve returns a value previously initialized (38ms)

  Contract: Box
    ✓ retrieve returns a value previously stored (87ms)

  Contract: BoxV2 (proxy)
    ✓ retrieve returns a value previously incremented (90ms)

If you have any advice, experience, feedback, comments, insight - much much much appreciated.

There is no specific question in this thread, I just need to experiment with stuff and see what happens :muscle:

[EDIT @abcoathup] Moved from: Deploying to Quorum with OpenZeppelin CLI

1 Like

Hi @marsxr,

I would suggest trying it. (I don’t have a Quorum environment setup currently)

My assumption is that public contracts should be ok deploying with OpenZeppelin Upgrades Plugins but it may not work for deploying private contracts.

Generally issues arise when an EVM version isn’t supported on a private/permissioned chain.

You could run your higher level tests interacting via the proxy on Quorum (as well as locally) using the examples from the Step by Step tutorial.


Tagging @Alex-dev in case they have any suggestions from their experience.


Out of interest what are you building?

This is pretty good: https://github.com/ConsenSys/quorum-wizard

npx quorum-wizard

Simple network. Raft consensus. Latest versions. Cakeshop

./start.sh

Getting the minimal upgradeable smart contract from here: Truffle Upgrades on Windows: Error: Artifacts are from different compiler runs

In the truffle-config.js

  networks: {
    quorum: {
      host: "127.0.0.1",
      port: 22000,
      network_id: "*",
      gasPrice: 0,
      type: "quorum"
    },

truffle migrate --network quorum


Compiling your contracts...
===========================
✔ Fetching solc version list from solc-bin. Attempt #1
> Everything is up to date, there is nothing to compile.



Starting migrations...
======================
> Network name:    'quorum'
> Network id:      10
> Block gas limit: 3756261602 (0xdfe400e2)


1_initial_migration.js
======================

   Deploying 'Migrations'
   ----------------------
   > transaction hash:    0x5659afe65755c016d955f5d1d3d15b65225ff3b99c9095a7bb03a9c0c0cee4b2

Error:  *** Deployment Failed ***

"Migrations" -- Transaction was not mined within 750 seconds, please make sure your transaction was properly sent. Be aware that it might still be mined!.

Can deploy using Cakeshop: http://localhost:8999/ as well as Remix

Cannot deploy using Truffle :mushroom:


I’m very lucky to be employed by a blockchain-related company that allows me to experiment with stuff, learn more, dig deeper… Entertain my intellectual curiosity and solve some problems along the way.

I’m thinking whether upgradeable on Quorum is required? If the gas is 0 and control all the nodes then can simply deploy new contract and update config file?

Slightly related: https://github.com/ethereum-oasis/baseline/issues/222

Questions: Is upgradability a desired business feature?


EDIT: attempt to unlock the account

ssh into Docker or CLI as on the screenshot below:

geth attach /qdata/dd/geth.ipc

personal.unlockAccount(eth.accounts[0])

Password: NONE (just press enter)

Result: same. It’s no the issue with the locked account. If it was the case, the error would say that explicitly, not 750 seconds timeout.

1 Like

Tagging @Alex-dev in case they have any suggestions from their experience.

Hey! I was able to deploy an upgradeable contract to Quorum. I used Truffle along with the upgradeable OpenZeppelin plugin.

  1. Write a simple contract with a String attribute (+ getter & setter)
  2. Deploy contract & set String attribute to some value (using address of AdminUpgradeabilityProxy)
  3. Make an edit to the contract by adding another method/attribute
  4. Deploy updated contract
  5. If you’re now able to use the same address (address of AdminUpgradeabilityProxy) to fetch the value you set in step 2 & you’re able to use the updated contract’s method/attribute, then you can confirm that the upgrade worked
2 Likes

@Alex-dev instructions how to ensure upgradeability - GOT IT, makes sense.


Fresh AWS. Fresh installation. Everything from scratch. Check the video below:

2m38s

Screenshot

See the 2m38s video, maybe something will come to your mind? Help much appreciated :muscle:

1 Like

Hi @marsxr,

I use WSL2 and am having issues getting up a docker environment :frowning_face: so haven’t been able to try this myself.

@Alex-dev any advice on how @marsxr can resolve?

I was using Cakeshop. Cakeshop runs some stuff in the background (embedded node). Killed Cakeshop.

Now able to progress further.

Now running into this issue:

It was already discussed here:

I will try some of the workarounds that are already described and update this topic / submit an issue to relevant repos on GitHub.

1 Like

It works :zap::zap::zap:

Based on: OpenZeppelin Upgrades: Step by Step Tutorial for Truffle

contracts/Box.sol

pragma solidity ^0.7.0;
contract Box {
    uint256 private value;
 
    // Emitted when the stored value changes
    event ValueChanged(uint256 newValue);
 
    // Stores a new value in the contract
    function store(uint256 newValue) public {
        value = newValue;
        emit ValueChanged(newValue);
    }
 
    // Reads the last stored value
    function retrieve() public view returns (uint256) {
        return value;
    }
}

contracts/BoxV2.sol

pragma solidity ^0.7.0;
 
contract BoxV2 {
    uint256 private value;
    string private text;

    // Emitted when the stored value changes
    event ValueChanged(uint256 newValue);
 
    // Stores a new value in the contract
    function store(uint256 newValue) public {
        value = newValue;
        emit ValueChanged(newValue);
    }
 
    // Reads the last stored value
    function retrieve() public view returns (uint256) {
        return value;
    }

    function storeString(string memory newText) public {
        text = newText;
    }

    function retrieveString() public view returns(string memory) {
        return text;
    }
}

migrations/3_upgrade_proxy.js

const Box = artifacts.require('Box');
const BoxV2 = artifacts.require('BoxV2');
 
const { upgradeProxy } = require('@openzeppelin/truffle-upgrades');
 
module.exports = async function (deployer) {
  const box = await Box.deployed();
  await upgradeProxy(box.address, BoxV2, { deployer });
};

index.html


<!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.storeNumber = function() {
          console.log("storeNumber");
          contract.store($scope.number);
        }

        $scope.retrieve = async function() {
          console.log("retrieve");
          $scope.retrieved = (await contract.retrieve()).toNumber();
          $scope.$apply();
        }

      });

    </script>
  </head>
  <body ng-app="app" ng-controller="ctrl">


    <input ng-model="number" placeholder="number"></input><button ng-click="storeNumber()">store</button> <br>

    <button ng-click="retrieve()">retrieve</button>

    <h1>{{ retrieved }}</h1>


  </body>

<script>

    // Box.sol
    ABI = [
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "newValue",
				"type": "uint256"
			}
		],
		"name": "ValueChanged",
		"type": "event"
	},
	{
		"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"
	}
]

    // UPGRADEABILITY PROXY
    contractAddress = "0x11cc6a8CFC0F68FbEE4b5fa27Fb43E8c3bcc19F9";

    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(contractAddress, ABI, signer);
    }

    init();

  </script>

</html>

index-NEW.html

<!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.storeNumber = function() {
          console.log("storeNumber");
          contract.store($scope.number);
        }

        $scope.retrieveNumber = async function() {
          console.log("retrieveNumber");
          $scope.retrieved = (await contract.retrieve()).toNumber();
          $scope.$apply();
        }

        $scope.storeText = function() {
          console.log("storeText");
          contract.storeString($scope.text);
        }

        $scope.retrieveText = async function() {
          console.log("retrieveText");
          $scope.retrieved = await contract.retrieveString();
          $scope.$apply();
        }

      });

    </script>
  </head>
  <body ng-app="app" ng-controller="ctrl">


    <input ng-model="number" placeholder="number"></input><button ng-click="storeNumber()">store</button> <br>
    <button ng-click="retrieveNumber()">retrieve</button>
    <h1>{{ retrieved }}</h1>

    <input ng-model="text" placeholder="text"></input><button ng-click="storeText()">store</button> <br>
    <button ng-click="retrieveText()">retrieve text</button>
    <h1>{{ retrievedText }}</h1>


  </body>

<script>

    // Box.sol
    ABI = [
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "newValue",
				"type": "uint256"
			}
		],
		"name": "ValueChanged",
		"type": "event"
	},
	{
		"inputs": [],
		"name": "retrieve",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "retrieveString",
		"outputs": [
			{
				"internalType": "string",
				"name": "",
				"type": "string"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "newValue",
				"type": "uint256"
			}
		],
		"name": "store",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "string",
				"name": "newText",
				"type": "string"
			}
		],
		"name": "storeString",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	}
];

    // UPGRADEABILITY PROXY
    contractAddress = "0x11cc6a8CFC0F68FbEE4b5fa27Fb43E8c3bcc19F9";

    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(contractAddress, ABI, signer);
    }

    init();

  </script>

</html>

And then launch the browser. This is the simplest way: python3 -m http.server OR python -m SimpleHTTPServer

Have MetaMask configured to use Quorum.

Quorum needs to be started with --rpccorsdomain "*" parameter

Actually, the --allow-insecure-unlock has to be inluded too.

Then you can geth attach new-node-1/geth.ipc and personal.unlockAccount(eth.accounts[0]) and succeed with the deployment.

Safer deployment?

Currently my AWS box has ports wide open. I’m worried something bad can happen as I unlock the account.

Still figuring out if there is a better, safer way to deploy that uses password: https://ethereum.stackexchange.com/questions/25413/truffle-mingration-error-authentication-needed-password-or-unlock

I think I can get the keystore file, import to Metamask, export as private key, then use that - no unlock required™

At the same time: using Gnosis Safe and official upgrades app seems much better, but that’s only on the public networks, but actually you can deploy your own Gnosis Safe and serve app from IPFS…

:turtle::turtle::turtle: on top of the :turtle::turtle::turtle:

Respect to all the buidlers who persevere through the difficulties

1 Like

Hi @marsxr,

Congratulations on deploying upgradeable contracts to Quorum. :rocket: