Skip to content

Commit

Permalink
feat: tool interfaces aka fasad (#10)
Browse files Browse the repository at this point in the history
* fix mistakes

* small config validation and refactor

* fixes and improvements

* add sygma sdk package

* fix lock file

* implement environment config

* implement initialization validation

* implement deployMultichain mock

* test and small fixes

* `yarn.lock` fix?

* with `yarn` `--immutable`?

* basic readme

* Readme with secret sauce

* fix ethereumjs-abi ?

* remove resolutions

* return `resolutions`, updated `yarn.lock`

* add `skipLibCheck` to `tsconfig.json`

* refactor initialization

* small typings improve on `deployMultichain`

* lint fix

* fixes

* fix `yarn.lock`

* improve README.md
  • Loading branch information
BeroBurny authored Jan 8, 2024
1 parent 114c05f commit 6351819
Show file tree
Hide file tree
Showing 14 changed files with 4,698 additions and 212 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: yarn-cache

- run: yarn install # install dependencies
- run: yarn install --immutable # install dependencies
- run: yarn run lint # lint code
- run: yarn run build # compile typescript into javascript
- run: yarn run test # run tests
14 changes: 4 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
1. [Introduction](#introduction)
2. [Prerequisites](#prerequisites)
3. [Getting Started](#getting-started)
4. [Building and Running the Snap](#building-and-running-the-snap)
4. [Building](#building)
5. [Testing](#testing)
6. [Linting and Formatting](#linting-and-formatting)

Expand Down Expand Up @@ -53,23 +53,17 @@ corepack enable
yarn install
```

<a name="building-and-running-the-snap"></a>
## Building and Running the Snap
<a name="building"></a>
## Building

To build and run the project, follow these steps:

1. Build all packages:
Build all packages:

```shell
yarn build
```

2. Run the MetaMask Snap:

```shell
yarn start
```

<a name="testing"></a>
## Testing

Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,8 @@
"chai": "^4.2.0",
"eslint": "8.37.0",
"typescript": "^5.0.3"
},
"resolutions": {
"ethereumjs-abi": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz"
}
}
62 changes: 57 additions & 5 deletions packages/plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,78 @@ With this tool, you're not just deploying contracts; you're unlocking new horizo
## Installation

```bash
npm install --save-dev @nomicfoundation/hardhat-ignition
npm install --save-dev @chainsafe/hardhat-plugin-multichain-deploy
```

Import the plugin in your `hardhat.config.js``:

```js
require("@nomicfoundation/hardhat-ignition");
require("@chainsafe/hardhat-plugin-multichain-deploy");
```

Or if you are using TypeScript, in your `hardhat.config.ts``:

```js
import "@nomicfoundation/hardhat-ignition";
import "@chainsafe/hardhat-plugin-multichain-deploy";
```

## Environment extensions

The package introduces a `multichain` namespace to the Hardhat Runtime Environment (HRE).

New methods introduced:
* `async waitInitialization(): Promise<void>`: Returns a promise. Wait for this promise to resolve to ensure readiness for using Sygma.
* `async deployMultichain(nameOrBytecode: string, arguments: string[], options?: Object): Promise<TxHash>`: Deploys a smart contract.
* `nameOrBytecode`: Name or bytecode of the smart contract.
* `arguments`: Arguments for the smart contract deployment.
* `options`: Additional deployment options (details TBD).

## Configuration

The Hardhat Plugin Multichain Deployment plugin requires specific configurations for successful multi-chain deployment.

This plugin extends introduce new name space called `multichain` with options:
* `environment`: Specifies the Sygma environment for deployment.
* Import `Environment` from `@buildwithsygma/sygma-sdk-core` for constant values.
* Options: `mainnet`, `testnet`, `devnet`, `local`.
* `deploymentNetworks`: List of networks for deployment.
* Ensure network names match those in `networks`.
* Networks must correspond with Sygma routes. Refer to [Sygma documentation](https://docs.buildwithsygma.com/environments) for routes.

Example configuration:

```typescript
import { Environment } from "@buildwithsygma/sygma-sdk-core";

const config: HardhatUserConfig = {
// ... other configurations ...
defaultNetwork: "goerli",
networks: {
sepolia: { ... },
goerli: { ... },
optimisticGoerli: { ... },
},
multichain: {
environment: Environment.TESTNET,
deploymentNetworks: ["sepolia", "optimisticGoerli"],
},
};
```

## Usages

# TODO
### TODO

After familiarizing yourself with the capabilities, let's see how everything works together.

Example scenario: You've created an ERC20 contract and want to deploy it across multiple chains using the configuration mentioned above.
```typescript
// Deploy the contract
const tx = await hre.multichain.deployMultichain('MySuperToken', [name, symbol, decimals], { singer: web3signer });

console.log("Transaction Hash: ", tx);
```

## Contribution

Refer to [root readme](../../README.md) file
For contributing to the project, please refer to the [root readme](../../README.md) file.
7 changes: 7 additions & 0 deletions packages/plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,26 @@
"README.md"
],
"devDependencies": {
"@buildwithsygma/sygma-sdk-core": "^2.4.0",
"@types/chai": "^4.1.7",
"@types/chai-as-promised": "^7",
"@types/eslint": "^8",
"@types/fs-extra": "^5.0.4",
"@types/mocha": "^5.2.6",
"@types/node": "^18",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"eslint": "^8",
"hardhat": "^2.0.0",
"mocha": "^10",
"ts-node": "^10",
"typescript": "^5"
},
"peerDependencies": {
"@buildwithsygma/sygma-sdk-core": ">= 2.4.0 < 3",
"hardhat": "^2.0.0"
},
"dependencies": {
"web3": "^4.3.0"
}
}
5 changes: 0 additions & 5 deletions packages/plugin/src/ExampleHardhatRuntimeEnvironmentField.ts

This file was deleted.

70 changes: 70 additions & 0 deletions packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { Config } from "@buildwithsygma/sygma-sdk-core";
import { HardhatPluginError } from "hardhat/plugins";
import { ContractConstructorArgs, ContractAbi } from "web3";
import {
getConfigEnvironmentVariable,
getDeploymentNetworks,
getNetworkChainId,
} from "./utils";

export class MultichainHardhatRuntimeEnvironmentField {
private isValidated: boolean = false;

public constructor(private readonly hre: HardhatRuntimeEnvironment) {}

private async validateConfig(): Promise<void> {
const originChainId = await getNetworkChainId(
this.hre.network.name,
this.hre
);
const environment = getConfigEnvironmentVariable(this.hre);

const config = new Config();
await config.init(originChainId, environment);
const domainChainIds = config.getDomains().map(({ chainId }) => chainId);

const deploymentNetworks = getDeploymentNetworks(this.hre);
const deploymentNetworksInfo = await Promise.all(
deploymentNetworks.map(async (name) => {
const chainId = await getNetworkChainId(name, this.hre);
return { name, chainId };
})
);

const missedRoutes: typeof deploymentNetworksInfo = [];
deploymentNetworksInfo.forEach(({ chainId, name }) => {
if (!domainChainIds.includes(chainId))
missedRoutes.push({ chainId, name });
});
if (missedRoutes.length)
throw new HardhatPluginError(
"@chainsafe/hardhat-plugin-multichain-deploy",
`Unavailable Networks in Deployment: The following networks from 'deploymentNetworks' are not routed in Sygma for the '${environment}' environment: ${missedRoutes
.map(({ chainId, name }) => `${name}(${chainId})`)
.join(", ")
.replace(/, ([^,]*)$/, " and $1")}\n` +
`Please adjust your 'deploymentNetworks' to align with the supported routes in this environment. For details on supported networks, refer to the Sygma documentation.`
);

this.isValidated = true;
}

public async deployMultichain<Abi extends ContractAbi = any>(
nameOrBytecode: string,
args: ContractConstructorArgs<Abi>,
options?: Object
): Promise<string> {
if (!this.isValidated) await this.validateConfig();

const bytcode = await this.hre.artifacts
.readArtifact(nameOrBytecode)
.then((artifact) => artifact.bytecode)
.catch(() => nameOrBytecode);

// temp to silence eslint
console.log(args, options, bytcode);

return "0x00";
}
}
81 changes: 48 additions & 33 deletions packages/plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,64 @@
import path from "path";
import { extendConfig, extendEnvironment } from "hardhat/config";
import { lazyObject } from "hardhat/plugins";
import { HardhatConfig, HardhatUserConfig } from "hardhat/types";
import { HardhatPluginError, lazyObject } from "hardhat/plugins";
import {
HardhatConfig,
HardhatUserConfig,
MultichainConfig,
} from "hardhat/types";
import { Environment } from "@buildwithsygma/sygma-sdk-core";

import { MultichainHardhatRuntimeEnvironmentField } from "./MultichainHardhatRuntimeEnvironmentField";

import { ExampleHardhatRuntimeEnvironmentField } from "./ExampleHardhatRuntimeEnvironmentField";
// This import is needed to let the TypeScript compiler know that it should include your type
// extensions in your npm package's types file.
import "./type-extensions";

extendConfig(
(config: HardhatConfig, userConfig: Readonly<HardhatUserConfig>) => {
// We apply our default config here. Any other kind of config resolution
// or normalization should be placed here.
//
// `config` is the resolved config, which will be used during runtime and
// you should modify.
// `userConfig` is the config as provided by the user. You should not modify
// it.
//
// If you extended the `HardhatConfig` type, you need to make sure that
// executing this function ensures that the `config` object is in a valid
// state for its type, including its extensions. For example, you may
// need to apply a default value, like in this example.
const userPath = userConfig.paths?.newPath;

let newPath: string;
if (userPath === undefined) {
newPath = path.join(config.paths.root, "newPath");
} else {
if (path.isAbsolute(userPath)) {
newPath = userPath;
} else {
// We resolve relative paths starting from the project's root.
// Please keep this convention to avoid confusion.
newPath = path.normalize(path.join(config.paths.root, userPath));
}
const multichainConfig = Object.assign({}, userConfig.multichain);

if (!multichainConfig.environment) {
console.warn(
"Warning: Missing 'environment' setting. Defaulting to ",
Environment.TESTNET
);
multichainConfig.environment = Environment.TESTNET;
}

config.paths.newPath = newPath;
if (
!multichainConfig.deploymentNetworks ||
!multichainConfig.deploymentNetworks.length
) {
console.warn(
"Warning: Missing Deployment Networks - It appears that you have not provided the Deployment Networks. To avoid potential issues, it is recommended that you supply these values. If they are not provided, you will be required to enter them manually as parameters. Please ensure that the necessary information is included to facilitate a smoother process."
);
multichainConfig.deploymentNetworks = [];
}

/** Validates that all networks in 'deploymentNetworks' are defined in 'config.networks'. */
const missedNetworks: string[] = [];
const configNetworkKeys = Object.keys(config.networks);
multichainConfig.deploymentNetworks.forEach((networkName) => {
if (!configNetworkKeys.includes(networkName))
missedNetworks.push(networkName);
});
if (missedNetworks.length)
throw new HardhatPluginError(
"@chainsafe/hardhat-plugin-multichain-deploy",
`Missing Configuration for Deployment Networks: ${missedNetworks
.join(", ")
.replace(/, ([^,]*)$/, " and $1")}\n` +
`The above networks are listed in your 'deploymentNetworks' but they are not defined in 'config.networks'. ` +
`Please ensure each of these networks is properly configured in the 'config.networks' section of your configuration.`
);

config.multichain = multichainConfig as MultichainConfig;
}
);

extendEnvironment((hre) => {
// We add a field to the Hardhat Runtime Environment here.
// We use lazyObject to avoid initializing things until they are actually
// needed.
hre.example = lazyObject(() => new ExampleHardhatRuntimeEnvironmentField());
hre.multichain = lazyObject(
() => new MultichainHardhatRuntimeEnvironmentField(hre)
);
});
34 changes: 15 additions & 19 deletions packages/plugin/src/type-extensions.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,30 @@
// If your plugin extends types from another plugin, you should import the plugin here.

// To extend one of Hardhat's types, you need to import the module where it has been defined, and redeclare it.
import "hardhat/types/config";
import "hardhat/types/runtime";

import { ExampleHardhatRuntimeEnvironmentField } from "./ExampleHardhatRuntimeEnvironmentField";
import { Environment } from "@buildwithsygma/sygma-sdk-core";
import { MultichainHardhatRuntimeEnvironmentField } from "./MultichainHardhatRuntimeEnvironmentField";

declare module "hardhat/types/config" {
// This is an example of an extension to one of the Hardhat config values.
/**
* Typings for config that can be used for using by hardhat.
*/

export interface MultichainConfig {
environment: Environment;
deploymentNetworks: string[];
}

// We extend the UserConfig type, which represents the config as written
// by the users. Things are normally optional here.
export interface ProjectPathsUserConfig {
newPath?: string;
export interface HardhatUserConfig {
multichain?: Partial<MultichainConfig>;
}

// We also extend the Config type, which represents the configuration
// after it has been resolved. This is the type used during the execution
// of tasks, tests and scripts.
// Normally, you don't want things to be optional here. As you can apply
// default values using the extendConfig function.
export interface ProjectPathsConfig {
newPath: string;
export interface HardhatConfig {
multichain: MultichainConfig;
}
}

declare module "hardhat/types/runtime" {
// This is an example of an extension to the Hardhat Runtime Environment.
// This new field will be available in tasks' actions, scripts, and tests.
export interface HardhatRuntimeEnvironment {
example: ExampleHardhatRuntimeEnvironmentField;
multichain: MultichainHardhatRuntimeEnvironmentField;
}
}
Loading

0 comments on commit 6351819

Please sign in to comment.