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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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 ETH on both networks.
kovalgek marked this conversation as resolved.
Show resolved Hide resolved
- Token rate forwarding must be set up either by adding a pusher to the Lido core protocol (voting required) or setting up an automatic solution, e.g., [Chainlink automation](https://docs.chain.link/chainlink-automation).
kovalgek marked this conversation as resolved.
Show resolved Hide resolved

### 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 and test on forked network
kovalgek marked this conversation as resolved.
Show resolved Hide resolved
- `deploy` - deploy on live network
- `verify` - verify on live network
- `check` - check on live network
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the difference between verify and check maybe not so clear for external viewers

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is check == test or what?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'verify' step is about contracts source code verification. Since it is somewhat unstable (requiring a wait for the block explorer to recognize the address as a contract and multiple attempts), I have placed it in a separate action.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's name it publish-sources?

- `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 verify check", ["all"])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From this line the format of the list isn't clear. Let's provide an example, smth like:

"list of actions: 'fork' 'deploy' 'verify' 'check'". E.g. "[deploy,verify,check]". Default ["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 "verify": return DeployAction.Verify;
case "check": return DeployAction.Check;
default:
throw new Error(`Invalid action: ${action}. Valid actions are: fork, deploy, verify, 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
Verify = "verify", // Run verification steps
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.Verify)) {
steps.push(...verifyOnRealNetworkSteps);
}

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 verifyOnRealNetworkSteps: 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
}
}