Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add actions #10

Merged
merged 5 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
[submodule "lido-l2-with-steth"]
path = lido-l2-with-steth
url = https://github.com/lidofinance/lido-l2-with-steth.git
branch = feature/save-tx-block-number
branch = main
commit = 7ccaec1f494adf34d7dfc060996cb9191b87a20c
[submodule "state-mate"]
path = state-mate
url = https://github.com/lidofinance/state-mate.git
Expand Down
25 changes: 20 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## Overview

**Multichain Automaton** is a sophisticated deployment and verification script designed to streamline the **deployment of stETH/wstETH [custom token bridge following reference architecture](https://docs.lido.fi/token-guides/wsteth-bridging-guide#reference-architecture-and-permissions-setup) solutions on OP Stack-compatible networks** for example OP Mainnet, Base, Zircuit, and Mode. It utilizes recommended initialization parameters and automates the verification of deployment artifacts.
**Multichain Automaton** is a sophisticated deployment and verification script designed to streamline the **deployment of stETH/wstETH [custom token bridge following reference architecture](https://docs.lido.fi/token-guides/wsteth-bridging-guide#reference-architecture-and-permissions-setup) solutions on OP Stack-compatible networks** (such as OP Mainnet, Base, Zircuit, and Mode) with planned support for Arbitrum in the future. It utilizes recommended initialization parameters and automates the verification of deployment artifacts.

## Why Use Multichain Automaton?

Expand Down Expand Up @@ -36,8 +36,9 @@ This command installs all necessary dependencies for the automaton and its submo

### Prerequisites

- deployer must have ETH on both networks.
- config setup with EM brakes.
- A Safe multisig must be created.
- The deployer must have sufficient gas on both networks.
- Token rate forwarding must be set up using an automatic solution, e.g., [Chainlink automation](https://docs.chain.link/chainlink-automation).

### Config Setup

Expand All @@ -53,13 +54,27 @@ cp .env.example .env

### Running the Script

Execute the script with your configuration file:
Execute the script with your config file and default parameters:

```bash
yarn start ./path/to/config.yaml
```

> **Note**: During execution, you will be prompted to confirm certain steps in the deployment process. The entire process typically takes around 15 minutes, depending on network conditions and RPC response times.
You can also run the script with specific actions and showing logs in console:

```bash
yarn start ./path/to/config.yaml --actions fork deploy verify --showLogs true
```

Available actions:

- `fork` - deploy, check and test on forked network
- `deploy` - deploy on live network
- `publish-sources` - publish sources on live network
- `check` - check and test on live network
- `all` - run all actions

> **Note**: The entire process typically takes around 15 minutes, depending on network conditions and RPC response times.

### Docker

Expand Down
76 changes: 42 additions & 34 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,66 @@ import * as YAML from "yaml";
import { logToStream, LogType } from "./log-utils";
import { MainConfig } from "./main-config";
import { ProgressBar } from "./progress-bar";
import { Context, getSteps } from "./steps";
import { Context, getSteps, DeployAction } from "./steps";

function parseCmdLineArgs() {
program
.argument("<config-path>", "path to .yaml config file")
.option("--onlyCheck", "only check the real network deployment")
.option("--onlyForkDeploy", "only deploy to the forked network")
.option("--actions [actions...]", "list of actions: fork deploy publish-sources check. E.g. --actions fork deploy publish-sources check. Default: all", ["all"])
.option("--showLogs", "show logs in console")
.option("--startFromStep", "start from step with index")
.parse();

const configPath = program.args[0];
const actionsOption = program.getOptionValue("actions") || Array("all");
const actions = deployActionsFromActionsOption(actionsOption);

return {
configPath,
onlyCheck: program.getOptionValue("onlyCheck"),
onlyForkDeploy: program.getOptionValue("onlyForkDeploy"),
actions: actions,
showLogs: program.getOptionValue("showLogs"),
startFromStep: Number(program.getOptionValue("startFromStep") ?? 0),
};
}

function deployActionsFromActionsOption(actionsOption: string[]): DeployAction[] {
return (actionsOption[0] === "all") ?
Object.values(DeployAction) :
actionsOption.map(action => {
const trimmedAction = action.trim().toLowerCase();
switch (trimmedAction) {
case "fork": return DeployAction.Fork;
case "deploy": return DeployAction.Deploy;
case "publish-sources": return DeployAction.PublishSources;
case "check": return DeployAction.Check;
default:
throw new Error(`Invalid action: ${action}. Valid actions are: fork, deploy, publish-sources, check`);
}
});
}

function loadYamlConfig(stateFile: string): {
mainConfig: MainConfig;
mainConfigDoc: YAML.Document;
} {
const file = path.resolve(stateFile);
const configContent = fs.readFileSync(file, "utf-8");
const reviver = (_: unknown, v: unknown) => {
return typeof v === "bigint" ? String(v) : v;
};

return {
mainConfig: YAML.parse(configContent, reviver, { schema: "core", intAsBigInt: true }),
mainConfigDoc: YAML.parseDocument(configContent, { intAsBigInt: true }),
};
}

async function main() {
const logStream = fs.createWriteStream("./artifacts/main.log");

const { configPath, onlyCheck, onlyForkDeploy, showLogs, startFromStep } = parseCmdLineArgs();
const { configPath, actions, showLogs } = parseCmdLineArgs();
console.log("Running script with");
console.log(` - configPath: ${configPath}`);
console.log(` - onlyCheck: ${!!onlyCheck}`);
console.log(` - onlyForkDeploy: ${!!onlyForkDeploy}`);
console.log(` - actions: ${actions}`);
console.log(` - showLogs: ${!!showLogs}`);
console.log(` - startFromStep: ${startFromStep}`);

const { mainConfig, mainConfigDoc }: { mainConfig: MainConfig; mainConfigDoc: YAML.Document } =
loadYamlConfig(configPath);
Expand All @@ -52,16 +81,11 @@ async function main() {
};

const progress = new ProgressBar(showLogs);
const steps = getSteps(onlyForkDeploy, onlyCheck);

if (startFromStep < 0 || startFromStep >= steps.length) {
console.error(`Step index is out of bounds ${startFromStep}`);
process.exit(1);
}
const steps = getSteps(actions);

progress.start(steps.length);

for (let stepIdx = startFromStep; stepIdx < steps.length; stepIdx++) {
for (let stepIdx = 0; stepIdx < steps.length; stepIdx++) {
const { name, action } = steps[stepIdx];
progress.update(stepIdx, name);
logStream.write(`[${new Date().toISOString()}] ${name}`);
Expand All @@ -84,19 +108,3 @@ main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

function loadYamlConfig(stateFile: string): {
mainConfig: MainConfig;
mainConfigDoc: YAML.Document;
} {
const file = path.resolve(stateFile);
const configContent = fs.readFileSync(file, "utf-8");
const reviver = (_: unknown, v: unknown) => {
return typeof v === "bigint" ? String(v) : v;
};

return {
mainConfig: YAML.parse(configContent, reviver, { schema: "core", intAsBigInt: true }),
mainConfigDoc: YAML.parseDocument(configContent, { intAsBigInt: true }),
};
}
68 changes: 45 additions & 23 deletions src/steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,35 @@ interface Step {
action: (context: Context, logCallback: LogCallback) => Promise<void> | void;
}

enum DeployAction {
Fork = "fork", // Deploy and test on fork
Deploy = "deploy", // Deploy on real network
PublishSources = "publish-sources", // Publish sources on live network
Check = "check" // Check real network deployment
}

function getSteps(actions: DeployAction[]): Step[] {
const steps: Step[] = [];

if (actions.includes(DeployAction.Fork)) {
steps.push(...deployAndTestOnForksSteps);
}

if (actions.includes(DeployAction.Deploy)) {
steps.push(...deployOnRealNetworkSteps);
}

if (actions.includes(DeployAction.PublishSources)) {
steps.push(...publishSourcesSteps);
}

if (actions.includes(DeployAction.Check)) {
steps.push(...testDeployedOnRealNetworkSteps);
}

return steps;
}

const deployAndTestOnForksSteps: Step[] = [
{
name: "Spawn L1 Fork Node",
Expand Down Expand Up @@ -167,7 +196,7 @@ const deployAndTestOnForksSteps: Step[] = [
},
];

const deployAndVerifyOnRealNetworkSteps: Step[] = [
const deployOnRealNetworkSteps: Step[] = [
{
name: "Deploy Governance Executor",
action: async (ctx, logCallback) => {
Expand Down Expand Up @@ -217,32 +246,35 @@ const deployAndVerifyOnRealNetworkSteps: Step[] = [
deployResultFileName: "l2_live_deployment_args.json",
});
},
},
}
];

const publishSourcesSteps: Step[] = [
{
name: "Wait for block explorer for address become contract",
name: "Wait for Block Explorer to Confirm Address as Contract",
action: async (_, logCallback) => {
await checkAddressesContractStatus({
configWihAddresses: "l1_live_deployment_args.json",
endpoint: `https://${env.string("L1_BLOCK_EXPLORER_API_HOST")}/api`,
apiKey: env.string("L1_EXPLORER_TOKEN"),
maxTries: 3,
checkInterval: 1000,
maxTries: 5,
checkInterval: 2000,
logCallback: logCallback
});
await checkAddressesContractStatus({
configWihAddresses: "l2_live_deployment_args.json",
endpoint: `https://${env.string("L2_BLOCK_EXPLORER_API_HOST")}/api`,
apiKey: env.string("L2_EXPLORER_TOKEN"),
maxTries: 3,
checkInterval: 1000,
maxTries: 5,
checkInterval: 2000,
logCallback: logCallback
});
await checkAddressesContractStatus({
configWihAddresses: "l2_live_gov_executor_deployment_args.json",
endpoint: `https://${env.string("L2_BLOCK_EXPLORER_API_HOST")}/api`,
apiKey: env.string("L2_EXPLORER_TOKEN"),
maxTries: 3,
checkInterval: 1000,
maxTries: 5,
checkInterval: 2000,
logCallback: logCallback
});
},
Expand Down Expand Up @@ -273,7 +305,7 @@ const deployAndVerifyOnRealNetworkSteps: Step[] = [
logCallback: logCallback,
});
},
},
}
];

const testDeployedOnRealNetworkSteps: Step[] = [
Expand Down Expand Up @@ -377,16 +409,6 @@ const testDeployedOnRealNetworkSteps: Step[] = [
},
];

function getSteps(onlyForkDeploy: boolean, onlyCheck: boolean) {
if (onlyForkDeploy) {
return deployAndTestOnForksSteps;
}
if (onlyCheck) {
return testDeployedOnRealNetworkSteps;
}
return [...deployAndTestOnForksSteps, ...deployAndVerifyOnRealNetworkSteps, ...testDeployedOnRealNetworkSteps];
}

async function spawnNode(
rpcForkUrl: string,
chainId: number,
Expand All @@ -400,8 +422,7 @@ async function spawnNode(
if (forkBlock !== undefined) {
nodeArgs.push("--fork-block-number", `${forkBlock}`);
}
console.log("nodeArgs=",nodeArgs);


const output = createWriteStream(`./artifacts/${outputFileName}`);
await once(output, "open");

Expand Down Expand Up @@ -436,5 +457,6 @@ async function spawnNode(
export {
Context,
Step,
DeployAction,
getSteps
}
}