I was asked where state is stored in upgradeable contracts.
The OpenZeppelin SDK Upgrades Pattern documentation explains:
https://docs.openzeppelin.com/sdk/2.5/pattern
A very important thing to note is that the code makes use of the EVM’sdelegatecall
opcode which executes the callee’s code in the context of the caller’s state. That is, the logic contract controls the proxy’s state and the logic contract’s state is meaningless. Thus, the proxy doesn’t only forward transactions to and from the logic contract, but also represents the pair’s state. The state is in the proxy and the logic is in the particular implementation that the proxy points to.
To see the state being stored in the Proxy contract for myself I deployed a simple upgradeable contract and read the contract storage of the proxy contract.
Setup
Assumes openzeppelin/cli
and ganache-cli
already installed.
Create an OpenZeppelin SDK project
mkdir storage
cd storage
npm init -y
oz init
Run ganache-cli -d
to start a local blockchain (if you haven't got one running already).
Counter.sol
Create a simple contract in the OpenZeppelin SDK project (in the contracts directory)
pragma solidity ^0.5.0;
contract Counter {
uint256 public value;
function increase() public {
value++;
}
}
Create
Create the contract using OpenZeppelin SDK.
$ oz create
✓ Compiled contracts with solc 0.5.12 (commit.7709ece9)
? Pick a contract to instantiate Counter
? Pick a network development
✓ Added contract Counter
✓ Contract Counter deployed
All contracts have been deployed
? Do you want to call a function on the instance after creating it? No
✓ Setting everything up to create contract instances
✓ Instance created at 0xCfEB869F69431e42cdB54A4F4f105C19C080A601
0xCfEB869F69431e42cdB54A4F4f105C19C080A601
index.js
The following script uses OpenZeppelin Network.js.
Install using npm i @openzeppelin/network
Paste the following into a file called index.js
The contents at a storage location are obtained using getStorageAt
The Proxy contract address was obtained when we created it above.
value
for the Counter
is at index 0 of the Proxy contract
The logic contract address is at index 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
which is bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
of the Proxy contract (as per the OpenZeppelin SDK Upgrades Pattern documentation)
const start = async function () {
const networkjs = require('@openzeppelin/network');
const web3Context = await networkjs.fromConnection('http://127.0.0.1:8545');
console.log('Counter value: ' + await web3Context.lib.eth.getStorageAt("0xCfEB869F69431e42cdB54A4F4f105C19C080A601", 0));
console.log('Logic contract address: ' + await web3Context.lib.eth.getStorageAt("0xCfEB869F69431e42cdB54A4F4f105C19C080A601", "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"));
return;
}
start();
Check storage
You may need to stop the script (e.g. Ctrl+C).
$ node index.js
Counter value: 0x0
Logic contract address: 0xe78a0f7e598cc8b0bb87894b0f60dd2a88d6a8ab
Increment counter
$ oz send-tx
? Pick a network development
? Pick an instance Counter at 0xCfEB869F69431e42cdB54A4F4f105C19C080A601
? Select which function increase()
✓ Transaction successful. Transaction hash: 0x1993a8b6774ce05f2f2da0c5fc1174de46a3630e642fac81cf71bec28864e451
Check storage (again)
Notice that the Counter value has increased.
$ node index.js
Counter value: 0x01
Logic contract address: 0xe78a0f7e598cc8b0bb87894b0f60dd2a88d6a8ab