Skip to content

Commit

Permalink
feat: add upgrade diamond script
Browse files Browse the repository at this point in the history
  • Loading branch information
jr-alpaca committed May 10, 2023
1 parent 544c947 commit 55d427b
Show file tree
Hide file tree
Showing 19 changed files with 411 additions and 41 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.vscode
.env

cache/
forge_cache/
out/

artifacts/
Expand Down
3 changes: 2 additions & 1 deletion .mainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"collateralFacet": "0x8cE125c65605f937AE16645a3642d3A335852936",
"diamondCutFacet": "0x2E28CbF8995969d4ea1F03FfB6f25dab8afEE45C",
"diamondLoupeFacet": "0xaDB5Be7b114Ca6721e00778F97e615B85D095952",
"flashloanFacet": "",
"lendFacet": "0x0d8AE68ee3eE728Ed7AE9E99Edc10f2cfc3959b1",
"liquidationFacet": "0xaB530549D03AE12A96292d749c75f7f719EA2f9D",
"nonCollatBorrowFacet": "0xF22133299081534780777D1504A6B38955195b03",
Expand Down Expand Up @@ -195,4 +196,4 @@
]
},
"moneyMarketReader": "0x1d3cb2f91207afDA9E9baB89caDE1a4c3222cf6a"
}
}
155 changes: 155 additions & 0 deletions deploy/core/facets/diamond-cut/execute-diamond.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
import { ethers } from "hardhat";

import * as readlineSync from "readline-sync";
import { ConfigFileHelper } from "../../../file-helper.ts/config-file.helper";
import { getDeployer } from "../../../utils/deployer-helper";
import { facetContractNameToAddress, getSelectors } from "../../../utils/diamond";
import { IMMDiamondCut, MMDiamondCutFacet__factory, MMDiamondLoupeFacet__factory } from "../../../../typechain";

enum FacetCutAction {
Add,
Replace,
Remove,
}

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
/*
░██╗░░░░░░░██╗░█████╗░██████╗░███╗░░██╗██╗███╗░░██╗░██████╗░
░██║░░██╗░░██║██╔══██╗██╔══██╗████╗░██║██║████╗░██║██╔════╝░
░╚██╗████╗██╔╝███████║██████╔╝██╔██╗██║██║██╔██╗██║██║░░██╗░
░░████╔═████║░██╔══██║██╔══██╗██║╚████║██║██║╚████║██║░░╚██╗
░░╚██╔╝░╚██╔╝░██║░░██║██║░░██║██║░╚███║██║██║░╚███║╚██████╔╝
░░░╚═╝░░░╚═╝░░╚═╝░░╚═╝╚═╝░░╚═╝╚═╝░░╚══╝╚═╝╚═╝░░╚══╝░╚═════╝░
Check all variables below before execute the deployment script
*/

const FACET = "ViewFacet";
const INITIALIZER_ADDRESS = ethers.constants.AddressZero;
const OLD_FACET_ADDRESS = "0xA7D618BF3880f146Bbc0F0d18eB6f13F59d3D339";

const deployer = await getDeployer();

const configFileHelper = new ConfigFileHelper();
const config = configFileHelper.getConfig();

const diamondLoupeFacet = MMDiamondLoupeFacet__factory.connect(config.moneyMarket.moneyMarketDiamond, deployer);

const diamondCutFacet = MMDiamondCutFacet__factory.connect(config.moneyMarket.moneyMarketDiamond, deployer);

console.log(`> Diamond cutting ${FACET}`);

// Build the facetCuts array
console.log(`> Build the action selectors array from ${FACET} contract`);
const contractFactory = await ethers.getContractFactory(FACET);
const facetAddress = facetContractNameToAddress(FACET);
const existedFacetCuts = (await diamondLoupeFacet.facets())
.map((each) => each.functionSelectors)
.reduce((result, array) => result.concat(array), []);

const previousFacet = (await diamondLoupeFacet.facets()).find((each) => each.facetAddress == OLD_FACET_ADDRESS);
if (!previousFacet) {
console.log("Previous facet not found");
return;
}

const facetCuts: Array<IMMDiamondCut.FacetCutStruct> = [];
const replaceSelectors: Array<string> = [];
const addSelectors: Array<string> = [];
const removeSelectors: Array<string> = [];
const functionSelectors = getSelectors(contractFactory);
// Loop through each selector to find out if it needs to replace or add
for (const selector of functionSelectors) {
if (existedFacetCuts.includes(selector)) {
replaceSelectors.push(selector);
} else {
addSelectors.push(selector);
}
}
// Loop through existed facet cuts to find out selectors to remove
for (const selector of previousFacet.functionSelectors) {
if (!functionSelectors.includes(selector)) {
removeSelectors.push(selector);
}
}

console.log(`> Build the facetCuts array from ${FACET} contract`);
// Put the replaceSelectors and addSelectors into facetCuts
if (replaceSelectors.length > 0) {
facetCuts.push({
facetAddress,
action: FacetCutAction.Replace,
functionSelectors: replaceSelectors,
});
}
if (addSelectors.length > 0) {
facetCuts.push({
facetAddress,
action: FacetCutAction.Add,
functionSelectors: addSelectors,
});
}
if (removeSelectors.length > 0) {
// Get the old facet address based on the selector
facetCuts.push({
facetAddress: ethers.constants.AddressZero,
action: FacetCutAction.Remove,
functionSelectors: removeSelectors,
});
}

console.log(`> Found ${replaceSelectors.length} selectors to replace`);
console.log(`> Methods to replace:`);
console.table(
replaceSelectors.map((each) => {
return {
functionName: contractFactory.interface.getFunction(each).name,
selector: each,
};
})
);
console.log(`> Found ${addSelectors.length} selectors to add`);
console.log(`> Methods to add:`);
console.table(
addSelectors.map((each) => {
return {
functionName: contractFactory.interface.getFunction(each).name,
selector: each,
};
})
);
console.log(`> Found ${removeSelectors.length} selectors to remove`);
console.log(`> Methods to remove:`);
console.table(
removeSelectors.map((each) => {
return {
functionName: "unknown (TODO: integrate with 4bytes dictionary)",
selector: each,
};
})
);

// Ask for confirmation
const confirmExecuteDiamondCut = readlineSync.question("Confirm? (y/n): ");
switch (confirmExecuteDiamondCut.toLowerCase()) {
case "y":
break;
case "n":
console.log("Aborting");
return;
default:
console.log("Invalid input");
return;
}

console.log("> Executing diamond cut");
const tx = await diamondCutFacet.diamondCut(facetCuts, INITIALIZER_ADDRESS, "0x", { gasLimit: 10000000 });
console.log(`> Tx is submitted: ${tx.hash}`);
console.log(`> Waiting for tx to be mined`);
await tx.wait();
console.log(`> Tx is mined`);
};

export default func;
func.tags = ["ExecuteDiamondCut"];
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export class ConfigFileHelper {
this._writeConfigFile(this.config);
}

public getConfig() {
return this.config;
}

private _writeConfigFile(config: Config) {
console.log(`>> Writing ${this.filePath}`);
fs.writeFileSync(this.filePath, JSON.stringify(config, null, 2));
Expand Down
14 changes: 14 additions & 0 deletions type-script/interfaces.ts → deploy/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ export interface Config {
}

export interface MoneyMarket {
moneyMarketDiamond: string;
facets: {
adminFacet: string;
borrowFacet: string;
collateralFacet: string;
diamondCutFacet: string;
diamondLoupeFacet: string;
flashloanFacet: string;
lendFacet: string;
liquidationFacet: string;
nonCollatBorrowFacet: string;
ownershipFacet: string;
viewFacet: string;
};
markets: Market[];
}

Expand Down
26 changes: 26 additions & 0 deletions deploy/utils/deployer-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { ethers, network } from "hardhat";
import { JsonRpcProvider } from "@ethersproject/providers";
import { HttpNetworkUserConfig } from "hardhat/types";

export async function getDeployer(): Promise<SignerWithAddress> {
const [defaultDeployer] = await ethers.getSigners();

if (isFork()) {
const provider = ethers.getDefaultProvider((network.config as HttpNetworkUserConfig).url) as JsonRpcProvider;
const signer = provider.getSigner("0xC44f82b07Ab3E691F826951a6E335E1bC1bB0B51");
const mainnetForkDeployer = await SignerWithAddress.create(signer);

return mainnetForkDeployer;
}

return defaultDeployer;
}

export function isFork() {
const networkUrl = (network.config as HttpNetworkUserConfig).url;
if (networkUrl) {
return networkUrl.indexOf("https://rpc.tenderly.co/fork/") !== -1;
}
throw new Error("invalid Network Url");
}
23 changes: 23 additions & 0 deletions deploy/utils/diamond.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ContractFactory } from "ethers";
import _ from "lodash";
import { ConfigFileHelper } from "../file-helper.ts/config-file.helper";

export function getSelectors(contract: ContractFactory): Array<string> {
const signatures = Object.keys(contract.interface.functions);
const selectors = signatures.reduce((acc, val) => {
acc.push(contract.interface.getSighash(val));
return acc;
}, [] as Array<string>);
return selectors;
}

export function facetContractNameToAddress(contractName: string): string {
const config = new ConfigFileHelper().getConfig();
const facetList = config.moneyMarket.facets as any;
contractName = _.camelCase(contractName);
const address = facetList[contractName];
if (!address) {
throw new Error(`${contractName} not found in config`);
}
return address;
}
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ optimizer = true
optimizer-runs = 1
src = 'solidity/contracts'
out = 'out'
cache_path = './forge_cache'
libs = ['node_modules']
test = 'solidity/tests'
fs_permissions = [{ access = "read-write", path = "./" }]
Expand Down
36 changes: 8 additions & 28 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
import fs from "fs";
import { HardhatUserConfig } from "hardhat/config";
import { config as dotEnvConfig } from "dotenv";
import { HardhatUserConfig } from "hardhat/config";
dotEnvConfig();

import "@openzeppelin/hardhat-upgrades";
import "@nomiclabs/hardhat-ethers";
import "@typechain/hardhat";
import "hardhat-deploy";

function getRemappings() {
return fs
.readFileSync("remappings.txt", "utf8")
.split("\n")
.filter(Boolean)
.map((line) => line.trim().split("="));
}

module.exports = {
const config: HardhatUserConfig = {
defaultNetwork: "hardhat",
networks: {
mainnet: {
bsc_mainnet: {
chainId: 56,
url: process.env.BSC_RPC_URL,
accounts: process.env.DEPLOYER_PRIVATE_KEY !== undefined ? [process.env.DEPLOYER_PRIVATE_KEY] : [],
Expand All @@ -36,27 +27,16 @@ module.exports = {
},
paths: {
sources: "./solidity/contracts",
tests: "./tests",
cache: "./cache",
artifacts: "./artifacts",
},
typechain: {
outDir: "./typechain",
target: process.env.TYPECHAIN_TARGET || "ethers-v5",
target: "ethers-v5",
},
// This fully resolves paths for imports in the ./lib directory for Hardhat
preprocess: {
eachLine: (hre: any) => ({
transform: (line: string) => {
if (line.match(/^\s*import /i)) {
getRemappings().forEach(([find, replace]) => {
if (line.match(find)) {
line = line.replace(find, replace);
}
});
}
return line;
},
}),
mocha: {
timeout: 100000,
},
};

export default config;
Loading

0 comments on commit 55d427b

Please sign in to comment.