forked from sphinx-labs/sphinx
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(exec): add basic execution flow (sphinx-labs#44)
Updates the ChugSplash executor service to include the basic execution flow. Many components are missing and need to be added to smart contracts before this will work, but the foundation is there.
- Loading branch information
1 parent
efccd1a
commit 4c73fc1
Showing
9 changed files
with
317 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@chugsplash/executor': minor | ||
--- | ||
|
||
Updates ChugSplash executor to include basic execution flow. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// We use hardhat for compiling Solidity contracts. We could use other tools for doing this | ||
// compilation, but hardhat was a simple solution. We should probably replace this with a simpler | ||
// solution later and put the compilation function in @chugsplash/core. | ||
|
||
import { HardhatUserConfig } from 'hardhat/types' | ||
|
||
const config: HardhatUserConfig = { | ||
solidity: { | ||
version: '0.8.15', | ||
}, | ||
} | ||
|
||
export default config |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
import { BaseServiceV2, validators } from '@eth-optimism/common-ts' | ||
import { ethers } from 'ethers' | ||
import { | ||
ChugSplashRegistryABI, | ||
ChugSplashManagerABI, | ||
} from '@chugsplash/contracts' | ||
|
||
import { | ||
parseStrategyString, | ||
ExecutorSelectionStrategy, | ||
compileRemoteBundle, | ||
} from './utils' | ||
|
||
type Options = { | ||
registry: string | ||
rpc: ethers.providers.StaticJsonRpcProvider | ||
key: string | ||
ess: string | ||
eps: string | ||
} | ||
|
||
type Metrics = {} | ||
|
||
type State = { | ||
registry: ethers.Contract | ||
wallet: ethers.Wallet | ||
ess: string[] | ||
eps: string[] | ||
} | ||
|
||
export class ChugSplashExecutor extends BaseServiceV2<Options, Metrics, State> { | ||
constructor(options?: Partial<Options>) { | ||
super({ | ||
name: 'executor', | ||
// eslint-disable-next-line @typescript-eslint/no-var-requires | ||
version: require('../package.json').version, | ||
loop: true, | ||
loopIntervalMs: 1000, | ||
options, | ||
optionsSpec: { | ||
registry: { | ||
desc: 'address of the ChugSplashRegistry contract', | ||
validator: validators.str, | ||
}, | ||
rpc: { | ||
desc: 'rpc for the chain to run the executor on', | ||
validator: validators.staticJsonRpcProvider, | ||
}, | ||
key: { | ||
desc: 'private key to use for signing transactions', | ||
validator: validators.str, | ||
}, | ||
ess: { | ||
desc: 'comma separated list of ESS contracts to accept', | ||
validator: validators.str, | ||
}, | ||
eps: { | ||
desc: 'comma separated list of EPS contracts to accept', | ||
validator: validators.str, | ||
}, | ||
}, | ||
metricsSpec: {}, | ||
}) | ||
} | ||
|
||
async init() { | ||
this.state.registry = new ethers.Contract( | ||
this.options.registry, | ||
ChugSplashRegistryABI, | ||
this.options.rpc | ||
) | ||
this.state.wallet = new ethers.Wallet(this.options.key, this.options.rpc) | ||
this.state.ess = parseStrategyString(this.options.ess) | ||
this.state.eps = parseStrategyString(this.options.eps) | ||
} | ||
|
||
async main() { | ||
// Put TODOs here | ||
// TODO: recover if we crashed and are in the middle of executing an upgrade | ||
|
||
// Find all active upgrades that have not yet been started | ||
const approvalAnnouncementEvents = await this.state.registry.queryFilter( | ||
this.state.registry.filters.EventAnnounced('ChugSplashBundleApproved') | ||
) | ||
|
||
for (const approvalAnnouncementEvent of approvalAnnouncementEvents) { | ||
const manager = new ethers.Contract( | ||
approvalAnnouncementEvent.args.manager, | ||
ChugSplashManagerABI, | ||
this.state.wallet | ||
) | ||
|
||
// TODO: Add this to the ChugSplashManager contract | ||
const ess = await manager.getExecutorSelectionStrategy() | ||
if (!this.state.ess.includes(ess)) { | ||
// We don't like this strategy, skip the upgrade. | ||
continue | ||
} | ||
|
||
// TODO: Add this to the ChugSplashManager contract | ||
const eps = await manager.getExecutorPaymentStrategy() | ||
if (!this.state.eps.includes(eps)) { | ||
// We don't like this strategy, skip the upgrade. | ||
continue | ||
} | ||
|
||
const receipt = await approvalAnnouncementEvent.getTransactionReceipt() | ||
const approvalEvent = manager.parseLog( | ||
receipt.logs.find((log) => { | ||
return log.logIndex === approvalAnnouncementEvent.logIndex - 1 | ||
}) | ||
) | ||
|
||
const activeBundleId = await manager.activeBundleId() | ||
if (activeBundleId !== approvalEvent.args.bundleId) { | ||
// This is not the active bundle, so we can skip it. | ||
continue | ||
} | ||
|
||
// TODO: Add this to the ChugSplashManager contract | ||
const selectedExecutor = await manager.getSelectedExecutor(activeBundleId) | ||
if (selectedExecutor !== ethers.constants.AddressZero) { | ||
// Someone else has been selected to execute the upgrade, so we can skip it. | ||
continue | ||
} | ||
|
||
const proposalEvents = await manager.queryFilter( | ||
manager.filters.EventProposed(activeBundleId) | ||
) | ||
if (proposalEvents.length !== 1) { | ||
// TODO: throw an error here or skip | ||
} | ||
|
||
const proposalEvent = proposalEvents[0] | ||
const bundle = await compileRemoteBundle(proposalEvent.args.configUri) | ||
if (bundle.root !== proposalEvent.args.bundleRoot) { | ||
// TODO: throw an error here or skip | ||
} | ||
|
||
// Try to become the selected executor. | ||
// TODO: Use an adapter system to make this easier. | ||
if (ess === ExecutorSelectionStrategy.SIMPLE_LOCK) { | ||
try { | ||
const strategy = new ethers.Contract( | ||
ess, | ||
// TODO: Use the right ABI here | ||
ChugSplashManagerABI, | ||
this.state.wallet | ||
) | ||
|
||
const tx = await strategy.claim(activeBundleId) | ||
await tx.wait() | ||
} catch (err) { | ||
// Unable to claim the lock, so skip this upgrade. | ||
continue | ||
} | ||
} else { | ||
throw new Error(`unsupported strategy: ${ess}`) | ||
} | ||
|
||
// TODO: Handle cancellation cleanly | ||
for (const action of bundle.actions) { | ||
// TODO: Handle errors cleanly | ||
const tx = await manager.executeChugSplashBundleAction( | ||
action.action, | ||
action.proof.actionIndex, | ||
action.proof.siblings | ||
) | ||
await tx.wait() | ||
} | ||
|
||
const completedEvents = await manager.queryFilter( | ||
manager.filters.ChugSplashBundleCompleted(activeBundleId) | ||
) | ||
if (completedEvents.length !== 1) { | ||
// TODO: throw an error here | ||
} | ||
|
||
// TODO: Check that we got paid appropriately. | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import hre from 'hardhat' | ||
import { SolcBuild } from 'hardhat/types' | ||
import { | ||
TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD, | ||
TASK_COMPILE_SOLIDITY_RUN_SOLCJS, | ||
TASK_COMPILE_SOLIDITY_RUN_SOLC, | ||
} from 'hardhat/builtin-tasks/task-names' | ||
import { add0x } from '@eth-optimism/core-utils' | ||
import { | ||
CanonicalChugSplashConfig, | ||
ChugSplashActionBundle, | ||
makeActionBundleFromConfig, | ||
} from '@chugsplash/core' | ||
|
||
/** | ||
* Compiles a remote ChugSplashBundle from a uri. | ||
* | ||
* @param uri URI of the ChugSplashBundle to compile. | ||
* @returns Compiled ChugSplashBundle. | ||
*/ | ||
export const compileRemoteBundle = async ( | ||
uri: string | ||
): Promise<ChugSplashActionBundle> => { | ||
let config: CanonicalChugSplashConfig | ||
if (uri.startsWith('ipfs://')) { | ||
config = await ( | ||
await fetch( | ||
`https://cloudflare-ipfs.com/ipfs/${uri.replace('ipfs://', '')}` | ||
) | ||
).json() | ||
} else { | ||
throw new Error('unsupported URI type') | ||
} | ||
|
||
const artifacts = {} | ||
for (const source of config.inputs) { | ||
const solcBuild: SolcBuild = await hre.run( | ||
TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD, | ||
{ | ||
quiet: true, | ||
solcVersion: source.solcVersion, | ||
} | ||
) | ||
|
||
let output: any // TODO: Compiler output | ||
if (solcBuild.isSolcJs) { | ||
output = await hre.run(TASK_COMPILE_SOLIDITY_RUN_SOLCJS, { | ||
input: source.input, | ||
solcJsPath: solcBuild.compilerPath, | ||
}) | ||
} else { | ||
output = await hre.run(TASK_COMPILE_SOLIDITY_RUN_SOLC, { | ||
input: source.input, | ||
solcPath: solcBuild.compilerPath, | ||
}) | ||
} | ||
|
||
for (const fileOutput of Object.values(output.contracts)) { | ||
for (const [contractName, contractOutput] of Object.entries(fileOutput)) { | ||
artifacts[contractName] = { | ||
bytecode: add0x(contractOutput.evm.bytecode.object), | ||
storageLayout: contractOutput.storageLayout, | ||
} | ||
} | ||
} | ||
} | ||
|
||
return makeActionBundleFromConfig(config, artifacts, {}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export enum ExecutorSelectionStrategy { | ||
// TODO: Fill this in once we know the address | ||
SIMPLE_LOCK = '0x0000000000000000000000000000000000000000', | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { ethers } from 'ethers' | ||
|
||
/** | ||
* Parses a comma-separated list of addresses into an array of addresses. | ||
* | ||
* @param strategy Comma-separated list of addresses. | ||
* @returns Array of addresses. | ||
*/ | ||
export const parseStrategyString = (strategy: string): string[] => { | ||
return strategy.split(',').map((address) => { | ||
return ethers.utils.getAddress(address) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './compile' | ||
export * from './constants' | ||
export * from './helpers' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters