Is it possible to deploy upgradeable contracts to Quorum?

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