Skip to content

Commit

Permalink
feat: pause and resume deployment cuts
Browse files Browse the repository at this point in the history
  • Loading branch information
hiddentao committed Oct 24, 2023
1 parent 1441076 commit b8518f4
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 92 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ Now you can run the tests in the original terminal:
> pnpm test
```

**Troubleshooting tests**

Note: If you get a `Error: [object Object]` error when running tests it's due to a Typescript syntax error in the test code somewhere.

**Publishing**

To publish a new release:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"prettier-plugin-sort-imports-desc": "^1.0.0",
"standard-version": "^9.5.0",
"ts-node": "^10.9.1",
"typescript": "^5.1.3"
"typescript": "^5.2.2"
},
"engines": {
"node": ">=20.0.0"
Expand Down
36 changes: 18 additions & 18 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

165 changes: 97 additions & 68 deletions src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ import { Signer, ZeroAddress, ethers } from 'ethers'
import { OnChainContract, clearDeploymentRecorder, deployContract, execContractMethod, getContractAt, getDeploymentRecorderData, saveDeploymentInfo, setupTarget, setupWallet } from '../shared/chain.js'
import { Context, getContext } from '../shared/context.js'
import { FacetCut, FacetCutAction, getFinalizedFacetCuts, resolveClean, resolveUpgrade } from '../shared/diamond.js'
import { $ } from '../shared/fs.js'
import { $, loadJson, saveJson } from '../shared/fs.js'
import { error, info, trace, warn } from '../shared/log.js'
import { createCommand, loadExistingDeploymentAndLog, loadFacetArtifactsAndLog, logSuccess } from './common.js'

export const command = () =>
createCommand('deploy', 'Deploy the diamond to a target.')
.argument('target', 'target to deploy')
createCommand('deploy', 'Deploy/upgrade a diamond.')
.argument('target', 'target to deploy/upgrade')
.option('-d, --dry', 'do a dry run without actually deploying anything')
.option('-n, --new', 'do a fresh deployment with a new contract address, overwriting any existing one')
.option('-r, --reset', 'remove all non-core facet selectors from an existing deployment and start afresh')
.option('--pause-cut-to-file <file>', 'pause before the diamondCut() method is called and the write the cut info to a file')
.option('--resume-cut-from-file <file>', 'resume a diamondCut() method call using the cut info in the given file')
.action(async (targetArg, args) => {
const ctx = await getContext(args)

Expand Down Expand Up @@ -107,77 +109,104 @@ export const command = () =>
await callDiamondCut(proxyInterface!, [cleanCut])
}
}

info('Resolving what changes need to be applied ...')
const changes = await resolveUpgrade({
userFacets: userFacetArtifacts,
coreFacets,
diamondProxy: proxyInterface!,
signer
})
const numAdds = changes.namedCuts.filter(c => c.action === FacetCutAction.Add).length
const numReplacements = changes.namedCuts.filter(c => c.action === FacetCutAction.Replace).length
const numRemovals = changes.namedCuts.filter(c => c.action === FacetCutAction.Remove).length
info(` ${changes.facetsToDeploy.length} facets need to be deployed.`)
info(` ${changes.namedCuts.length} facet cuts need to be applied (Add = ${numAdds}, Replace = ${numReplacements}, Remove = ${numRemovals}).`)

if (changes.namedCuts.length === 0) {
info('No changes need to be applied.')
} else {
const facetContracts: Record<string, OnChainContract> = {}

if (changes.facetsToDeploy.length) {
if (args.dry) {
warn(`Dry run requested. Skipping facet deployment...`)
} else {
info('Deploying facets...')
for (const name of changes.facetsToDeploy) {
info(` Deploying ${name} ...`)
const contract = await deployContract(ctx, name, signer)
facetContracts[name] = contract
info(` Deployed ${name} at: ${await contract.address}`)
}
}

let skipPostDeployHook = false

// resuming cut from file?
if (!isNewDeployment && args.resumeCutFromFile) {
info(`Resuming diamondCut() with info from file: ${args.resumeCutFromFile} ...`)
const resumed = loadJson(args.resumeCutFromFile) as {
cuts: FacetCut[],
initContractAddress: string,
initData: string
}
if (args.dry) {
warn(`Dry run requested. Skipping the cut...`)
} else {
info('No new facets need to be deployed.')
await callDiamondCut(proxyInterface!, resumed.cuts, resumed.initContractAddress, resumed.initData)
}

let initContractAddress: string = ethers.ZeroAddress
let initData: string = '0x'

if (isNewDeployment && ctx.config.diamond.init) {
const { contract: initContract, function: initFunction } = ctx.config.diamond.init

if (args.dry) {
warn(`Dry run requested. Skipping initialization...`)
} else {
info(`Deploying initialization contract: ${initContract} ...`)
const init = await deployContract(ctx, initContract, signer)
initContractAddress = init.address
info(` Initialization contract deployed at: ${initContractAddress}`)

const initSelector = init.contract.interface.getFunction(initFunction)
if (!initSelector) {
error(`Initialization contract ${initContract} does not have an ${initFunction}() function.`)
}
// otherwise, resolve changes and deploy
else {
info('Resolving what changes need to be applied ...')
const changes = await resolveUpgrade({
userFacets: userFacetArtifacts,
coreFacets,
diamondProxy: proxyInterface!,
signer
})
const numAdds = changes.namedCuts.filter(c => c.action === FacetCutAction.Add).length
const numReplacements = changes.namedCuts.filter(c => c.action === FacetCutAction.Replace).length
const numRemovals = changes.namedCuts.filter(c => c.action === FacetCutAction.Remove).length
info(` ${changes.facetsToDeploy.length} facets need to be deployed.`)
info(` ${changes.namedCuts.length} facet cuts need to be applied (Add = ${numAdds}, Replace = ${numReplacements}, Remove = ${numRemovals}).`)

if (changes.namedCuts.length === 0) {
info('No changes need to be applied.')
} else {
const facetContracts: Record<string, OnChainContract> = {}

if (changes.facetsToDeploy.length) {
if (args.dry) {
warn(`Dry run requested. Skipping facet deployment...`)
} else {
info('Deploying facets...')
for (const name of changes.facetsToDeploy) {
info(` Deploying ${name} ...`)
const contract = await deployContract(ctx, name, signer)
facetContracts[name] = contract
info(` Deployed ${name} at: ${await contract.address}`)
}
}
} else {
info('No new facets need to be deployed.')
}

// encode init args with function signature to get the init data
info(`Encoding initialization call data...`)
try {
trace(` Encoding initialization call data: [${target.config.initArgs.join(", ")}]`)
initData = init.contract.interface.encodeFunctionData(initSelector!, target.config.initArgs)
trace(` Encoded initialization call data: ${initData}`)
} catch (err: any) {
error(`Error encoding initialization call data: ${err.message}\n\nCheck your initArgs in the target config.`)
let initContractAddress: string = ethers.ZeroAddress
let initData: string = '0x'

if (isNewDeployment && ctx.config.diamond.init) {
const { contract: initContract, function: initFunction } = ctx.config.diamond.init

if (args.dry) {
warn(`Dry run requested. Skipping initialization...`)
} else {
info(`Deploying initialization contract: ${initContract} ...`)
const init = await deployContract(ctx, initContract, signer)
initContractAddress = init.address
info(` Initialization contract deployed at: ${initContractAddress}`)

const initSelector = init.contract.interface.getFunction(initFunction)
if (!initSelector) {
error(`Initialization contract ${initContract} does not have an ${initFunction}() function.`)
}

// encode init args with function signature to get the init data
info(`Encoding initialization call data...`)
try {
trace(` Encoding initialization call data: [${target.config.initArgs.join(", ")}]`)
initData = init.contract.interface.encodeFunctionData(initSelector!, target.config.initArgs)
trace(` Encoded initialization call data: ${initData}`)
} catch (err: any) {
error(`Error encoding initialization call data: ${err.message}\n\nCheck your initArgs in the target config.`)
}
}
}
}

const cuts = getFinalizedFacetCuts(changes.namedCuts, facetContracts)
if (args.dry) {
warn(`Dry run requested. Skipping diamondCut() call...`)
} else {
await callDiamondCut(proxyInterface!, cuts, initContractAddress, initData)
const cuts = getFinalizedFacetCuts(changes.namedCuts, facetContracts)
if (args.dry) {
warn(`Dry run requested. Skipping diamondCut() call...`)
} else if (args.pauseCutToFile) {
info(`Pausing before diamondCut(), writing cut info to ${args.pauseCutToFile} ...`)
saveJson(args.pauseCutToFile, {
cuts,
initContractAddress,
initData
})
skipPostDeployHook = true
} else {
await callDiamondCut(proxyInterface!, cuts, initContractAddress, initData)
}
}
}

Expand All @@ -188,7 +217,7 @@ export const command = () =>
}

// run post-deploy hook
if (ctx.config.hooks.postDeploy) {
if (ctx.config.hooks.postDeploy && !skipPostDeployHook) {
if (args.dry) {
warn(`Dry run requested. Skipping post-deploy hook...`)
} else {
Expand Down
1 change: 0 additions & 1 deletion src/shared/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,6 @@ export const getUserFacetsAndFunctions = (ctx: Context): FacetDefinition[] => {

if (parserMeta.userDefinedTypes.length) {
info(`Custom structs found in facet method signatures: ${parserMeta.userDefinedTypes.join(', ')}`)
info(`Please ensure your gemforge config is setup properly to handle this, see https://gemforge.xyz/advanced/custom-structs/.`)
}

// sort alphabetically
Expand Down
1 change: 0 additions & 1 deletion test/common-build-steps.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'mocha'
import path, { join } from "node:path"
import { GemforgeConfig, assertFileMatchesTemplate, cli, expect, loadFile, loadJsonFile, removeFile, updateConfigFile, writeFile } from './utils.js'
import { Fragment } from 'ethers'

export const addBuildTestSteps = ({
framework,
Expand Down
Loading

0 comments on commit b8518f4

Please sign in to comment.