Skip to content

Commit 2428e26

Browse files
feat(smart-contract): Add doc and sample deploy upgrade with multisig
1 parent 2c12617 commit 2428e26

File tree

3 files changed

+107
-1
lines changed

3 files changed

+107
-1
lines changed

infrastructure/smart-contracts/README.md

+30-1
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,33 @@ To deploy on a local node start the node with `pnpm run local` and change the `d
2222
- Change the proxy address to the contract that needs to be upgraded
2323
- Change the contract name to the new contract version (`esXai` -> `esXai2`) in ` const esXai2 = await ethers.getContractFactory("esXai2");`
2424
- Create a new upgrade command in `package.json`
25-
- Run the script with `pnpm run upgrade-esxai`
25+
- Run the script with `pnpm run upgrade-esxai`
26+
27+
28+
## MultiSig deployments
29+
30+
### Upgrade an existing proxy to a new implementation
31+
32+
If the beacon proxy admin owner is a multisig we use `upgrades.upgradeProxy` anymore as it will throw the error `Caller is not owner` fro the beacon proxy contract.
33+
For multisig deployments we need to call `upgrades.prepareUpgrade`, then take the implementation and build the transaction on the multisig contract.
34+
If there is an initialize function to be called with the upgrade, we will call `upgradeAndCall` to the beacon proxy contract, this will require sending the encoded function call. For this we have a helper function `./utils/getUpgradeTransactionData.mjs`. It will create the encoded function call data to be sent with the multisig transaction.
35+
From `upgrades.prepareUpgrade` we will get the new implementation address to upgrade to, we have to verify this implementation.
36+
37+
### Deploy a new contract
38+
39+
Deploy a new proxy will use the existing beacon proxy admin contract which is owned by the multisig, this wokrs the same no matter who the beacon proxy admin is.
40+
41+
### How to upgrade using the multisig account
42+
43+
- Login on the multisig webapp
44+
- Create a new transaction (transaction builder)
45+
- Enter Beacon proxy contract address
46+
- `0xD88c8E0aE21beA6adE41A41130Bb4cd43e6b1723` ArbitrumOne
47+
- `0x2b0AE314A4CE6BE6E47a88b758a0b6cD7C143C5A` ArbitrumSepolia
48+
- Enter Beacon proxy ABI
49+
- Select Contract method to call:
50+
- `upgrade` for upgrades without calling any initializer
51+
- `upgradeAndCall` for upgrades with calling an initializer or migration function
52+
- Enter proxy address of the contract to upgrade
53+
- Enter the new implementation address (from running `upgrades.prepareUpgrade`)
54+
- When calling `upgradeAndCall` enter the **data** generated from encoding the function signature with the call data (`getUpgradeTransactionData.mjs`)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import hardhat from "hardhat";
2+
import { getUpgradeAndCallData } from "./utils/getUpgradeTransactionData.mjs";
3+
const { ethers, upgrades } = hardhat;
4+
5+
const PROXY_TO_UPGRADE_ADDRESS = "0xProxyAddressToUpgrade";
6+
const NEW_IMPLEMENTATION_NAME = "ContractName";
7+
8+
async function main() {
9+
const [deployer] = (await ethers.getSigners());
10+
const deployerAddress = await deployer.getAddress();
11+
console.log("Deployer address", deployerAddress);
12+
13+
console.log(`Deploy implementation ${NEW_IMPLEMENTATION_NAME}...`);
14+
15+
const contractInstance = await ethers.getContractFactory(NEW_IMPLEMENTATION_NAME);
16+
const implementationAddress = await upgrades.prepareUpgrade(PROXY_TO_UPGRADE_ADDRESS, contractInstance);
17+
18+
console.log(`DEPLOYED IMPLEMENTATION ${NEW_IMPLEMENTATION_NAME}`);
19+
console.log(implementationAddress);
20+
21+
const upgradeTXData = getUpgradeAndCallData(
22+
{
23+
upgradeCallFunctionName: "initialize",
24+
upgradeCallFunctionSignature: "function initialize(address _esXaiBurnFoundationRecipient, uint256 _esXaiBurnFoundationBasePoints)",
25+
upgradeCallFunctionParams: ["0xaf88d065e77c8cC2239327C5EDb3A432268e5831", 100n]
26+
}
27+
);
28+
29+
console.log("MultiSig transaction builder arguments:");
30+
console.log(`Proxy: ${PROXY_TO_UPGRADE_ADDRESS}`);
31+
console.log(`Implementation: ${implementationAddress}`);
32+
console.log(`data: ${upgradeTXData}`);
33+
34+
console.log("verifying implementation...");
35+
36+
await run("verify:verify", {
37+
address: implementationAddress,
38+
constructorArguments: [],
39+
});
40+
41+
console.log("verified")
42+
}
43+
44+
// We recommend this pattern to be able to use async/await everywhere
45+
// and properly handle errors.
46+
main().catch((error) => {
47+
console.error(error);
48+
process.exitCode = 1;
49+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import hardhat from "hardhat";
2+
const { ethers } = hardhat;
3+
4+
/**
5+
* Create upgrade call data
6+
*
7+
* @param {string} [opts.upgradeCallFunctionName] - e.g. "initialize"
8+
* @param {string} [opts.upgradeCallFunctionSignature] - e.g. "function initialize(address,uint256)"
9+
* @param {Array} [opts.upgradeCallFunctionParams] - e.g. ["0x1234...", 42]
10+
* @returns {string} The ABI-encoded transaction data
11+
*/
12+
export function getUpgradeAndCallData(
13+
{
14+
upgradeCallFunctionName,
15+
upgradeCallFunctionSignature,
16+
upgradeCallFunctionParams
17+
} = {}
18+
) {
19+
20+
const initIface = new ethers.Interface([upgradeCallFunctionSignature]);
21+
const initData = initIface.encodeFunctionData(
22+
upgradeCallFunctionName,
23+
upgradeCallFunctionParams
24+
);
25+
26+
return initData;
27+
28+
}

0 commit comments

Comments
 (0)