UNPKG

@vechain/vebetterdao-contracts

Version:

Open-source repository that houses the smart contracts powering the decentralized VeBetterDAO on the VeChain Thor blockchain.

200 lines (199 loc) 11.3 kB
import { ethers } from "hardhat"; import { getImplementationAddress } from "@openzeppelin/upgrades-core"; import { AddressUtils } from "@repo/utils"; export const deployProxy = async (contractName, args, libraries = {}, version, logOutput = false) => { // Deploy the implementation contract const Contract = await ethers.getContractFactory(contractName, { libraries: libraries, }); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); logOutput && console.log(`${contractName} impl.: ${await implementation.getAddress()}`); // Deploy the proxy contract, link it to the implementation and call the initializer const proxyFactory = await ethers.getContractFactory("B3TRProxy"); const proxy = await proxyFactory.deploy(await implementation.getAddress(), getInitializerData(Contract.interface, args, version)); await proxy.waitForDeployment(); logOutput && console.log(`${contractName} proxy: ${await proxy.getAddress()}`); const newImplementationAddress = await getImplementationAddress(ethers.provider, await proxy.getAddress()); if (!AddressUtils.compareAddresses(newImplementationAddress, await implementation.getAddress())) { throw new Error(`The implementation address is not the one expected: ${newImplementationAddress} !== ${await implementation.getAddress()}`); } // Return an instance of the contract using the proxy address return Contract.attach(await proxy.getAddress()); }; export const deployAndInitializeLatest = async (contractName, initializerCalls, libraries = {}, logOutput = false) => { // Deploy implementation + proxy const proxyAddress = await deployProxyOnly(contractName, libraries, logOutput); // Attach contract instance const Contract = await ethers.getContractFactory(contractName, { libraries }); const contractAtProxy = Contract.attach(proxyAddress); const signer = (await ethers.getSigners())[0]; for (const { name, args } of initializerCalls) { const fn = Contract.interface.getFunction(name); if (!fn) throw new Error(`Function ${name} not found in contract ABI`); const data = Contract.interface.encodeFunctionData(fn, args); const tx = await signer.sendTransaction({ to: proxyAddress, data }); await tx.wait(); } return contractAtProxy; }; export const deployProxyOnly = async (contractName, libraries = {}, logOutput = false) => { // Deploy the implementation contract const Contract = await ethers.getContractFactory(contractName, { libraries: libraries, }); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); logOutput && console.log(`${contractName} impl.: ${await implementation.getAddress()}`); // Deploy the proxy contract without initialization const proxyFactory = await ethers.getContractFactory("B3TRProxy"); const proxy = await proxyFactory.deploy(await implementation.getAddress(), "0x"); await proxy.waitForDeployment(); logOutput && console.log(`${contractName} proxy: ${await proxy.getAddress()}`); const newImplementationAddress = await getImplementationAddress(ethers.provider, await proxy.getAddress()); if (!AddressUtils.compareAddresses(newImplementationAddress, await implementation.getAddress())) { throw new Error(`The implementation address is not the one expected: ${newImplementationAddress} !== ${await implementation.getAddress()}`); } // Return the proxy address return await proxy.getAddress(); }; // It is used to deploy the proxy contract without initializating export const deployProxyWithoutInitialization = async (contractName, libraries = {}, logOutput = false) => { // Deploy the implementation contract const Contract = await ethers.getContractFactory(contractName, { libraries: libraries, }); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); logOutput && console.log(`${contractName} impl.: ${await implementation.getAddress()}`); // Deploy the proxy contract without initialization const proxyFactory = await ethers.getContractFactory("B3TRProxy"); const proxy = await proxyFactory.deploy(await implementation.getAddress(), "0x"); await proxy.waitForDeployment(); logOutput && console.log(`${contractName} proxy: ${await proxy.getAddress()}`); const newImplementationAddress = await getImplementationAddress(ethers.provider, await proxy.getAddress()); if (!AddressUtils.compareAddresses(newImplementationAddress, await implementation.getAddress())) { throw new Error(`The implementation address is not the one expected: ${newImplementationAddress} !== ${await implementation.getAddress()}`); } // Return the proxy address return await proxy.getAddress(); }; export const initializeProxy = async (proxyAddress, contractName, args, libraries = {}, version) => { // Get the ContractFactory const Contract = await ethers.getContractFactory(contractName, { libraries: libraries, }); // Prepare the initializer data using getInitializerData const initializerData = getInitializerData(Contract.interface, args, version); // Interact with the proxy contract to call the initializer using the prepared initializer data const signer = (await ethers.getSigners())[0]; const tx = await signer.sendTransaction({ to: proxyAddress, data: initializerData, gasLimit: 10000000, // Keep! Else, StargateNFT contract init tx will fail }); await tx.wait(); // Return an instance of the contract using the proxy address return Contract.attach(proxyAddress); }; export const upgradeProxy = async (previousVersionContractName, newVersionContractName, proxyAddress, args = [], options) => { // Deploy the implementation contract const Contract = await ethers.getContractFactory(newVersionContractName, { libraries: options.libraries, }); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); const currentImplementationContract = await ethers.getContractAt(previousVersionContractName, proxyAddress); if (options.logOutput) { console.log(`${newVersionContractName} impl.: ${await implementation.getAddress()}`); } const tx = await currentImplementationContract.upgradeToAndCall(await implementation.getAddress(), args.length > 0 ? getInitializerData(Contract.interface, args, options.version) : "0x"); await tx.wait(); const newImplementationAddress = await getImplementationAddress(ethers.provider, proxyAddress); if (!AddressUtils.compareAddresses(newImplementationAddress, await implementation.getAddress())) { throw new Error(`The implementation address is not the one expected: ${newImplementationAddress} !== ${await implementation.getAddress()}`); } return Contract.attach(proxyAddress); }; export const deployAndUpgrade = async (contractNames, args, options) => { if (contractNames.length === 0) throw new Error("No contracts to deploy"); if (contractNames.length !== args.length) throw new Error(`Contract ${contractNames} and arguments must have the same length`); if (options.libraries && contractNames.length !== options.libraries.length) throw new Error("Contract names and libraries must have the same length"); if (options.versions && contractNames.length !== options.versions.length) throw new Error("Contract names and versions must have the same length"); // 1. Deploy proxy and first implementation const contractName = contractNames[0]; const contractArgs = args[0]; let proxy = await deployProxy(contractName, contractArgs, options.libraries?.[0], options.versions?.[0], options.logOutput); // 2. Upgrade the proxy to the next versions for (let i = 1; i < contractNames.length; i++) { const previousVersionContractName = contractNames[i - 1]; const newVersionContractName = contractNames[i]; const contractArgs = args[i]; proxy = await upgradeProxy(previousVersionContractName, newVersionContractName, await proxy.getAddress(), contractArgs, { version: options.versions?.[i], libraries: options.libraries?.[i], logOutput: options.logOutput }); } return proxy; }; export function getInitializerData(contractInterface, args, version) { const initializer = version ? `initializeV${version}` : "initialize"; const fragment = contractInterface.getFunction(initializer); if (!fragment) { throw new Error(`Contract initializer not found`); } return contractInterface.encodeFunctionData(fragment, args); } export const deployUpgradeableWithoutInitialization = async (contractName, libraries = {}, logOutput = false) => { // Deploy the implementation contract const Contract = await ethers.getContractFactory(contractName, { libraries: libraries, }); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); logOutput && console.log(`${contractName} impl.: ${await implementation.getAddress()}`); // Deploy the proxy contract without initialization const proxyFactory = await ethers.getContractFactory("B3TRProxy"); const proxy = await proxyFactory.deploy(await implementation.getAddress(), "0x"); await proxy.waitForDeployment(); logOutput && console.log(`${contractName} proxy: ${await proxy.getAddress()}`); const newImplementationAddress = await getImplementationAddress(ethers.provider, await proxy.getAddress()); if (!AddressUtils.compareAddresses(newImplementationAddress, await implementation.getAddress())) { throw new Error(`The implementation address is not the one expected: ${newImplementationAddress} !== ${await implementation.getAddress()}`); } // Return the proxy address return await proxy.getAddress(); }; export const initializeProxyAllVersions = async (contractName, proxyAddress, initializerCalls, logOutput = false) => { // Get contract instance const Contract = await ethers.getContractAt(contractName, proxyAddress); // Get the signer const signer = (await ethers.getSigners())[0]; // Call all initializers let upgraderCheck = false; for (const { version, args } of initializerCalls) { logOutput && console.log(`Initializing ${contractName} V${version ?? "1"}...`); if (version !== undefined && upgraderCheck === false) { await revertIfSignerIsNotUpgrader(Contract, await signer.getAddress()); upgraderCheck = true; } const data = getInitializerData(Contract.interface, args, version); const tx = await signer.sendTransaction({ to: proxyAddress, data, gasLimit: 10000000, }); await tx.wait(); } // Return the contract instance return Contract; }; async function revertIfSignerIsNotUpgrader(contract, signerAddress) { const upgraderRole = ethers.keccak256(ethers.toUtf8Bytes("UPGRADER_ROLE")); const hasUpgraderRole = await contract.hasRole(upgraderRole, signerAddress); if (!hasUpgraderRole) { throw new Error(`Signer ${signerAddress} is missing UPGRADER_ROLE. Cancelling upgrade.`); } }