It works 

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…
on top of the