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 config simple validations and synthetic metadata #3

Merged
merged 5 commits into from
Nov 5, 2024
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: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@ npx hardhat vars set ALCHEMY_API_KEY
After running Hyperlane CLI `hyperlane warp init` command copy the resulting `warp-route-deployment.yaml` file into the
`configs` folder.

If you are going to deploy some `synthetic` routes you need to add `decimals`, `name`, `symbol` and `totalSupply`
attributes in the configuration file since Hyperlane CLI doesn't do it.

```bash
npm run warpDeploy -- --network NETWORK_NAME --routersalt SOME_SALT_FOR_ROUTER_IMPL --proxyadminsalt SOME_SALT_FOR_PROXY_ADMIN --proxysalt SOME_SALT_FOR_ROUTER_PROXY
```
Expand Down
4 changes: 0 additions & 4 deletions configs/warp-route-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,4 @@ sepolia:
isNft: false
mailbox: "0xfFAEF09B3cd11D9b20d1a19bECca54EEC2884766"
owner: "0xF0e4DD3b900Ef7aa96CDf0874469671B0C48D089"
decimals: 18
name: "Warp Route USDC"
symbol: "WRUSDC"
totalSupply: 0
type: synthetic
2 changes: 2 additions & 0 deletions contracts/Includes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/trans
import { HypERC20 } from "@hyperlane-xyz/core/contracts/token/HypERC20.sol";
import { HypERC20Collateral } from "@hyperlane-xyz/core/contracts/token/HypERC20Collateral.sol";
import { HypNative } from "@hyperlane-xyz/core/contracts/token/HypNative.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
87 changes: 85 additions & 2 deletions tasks/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,98 @@ export const chainNames = {
11155111: "sepolia",
};

export const getWarpDeployConfig = async (): Promise<WarpRouteDeployConfig> => {
export const getWarpDeployConfig = async (hre: HardhatRuntimeEnvironment): Promise<WarpRouteDeployConfig> => {
let configYAML = await fsp.readFile(path.resolve(__dirname, "../../configs/warp-route-deployment.yaml"), {
encoding: "utf8",
});

return yamlParse(configYAML) as WarpRouteDeployConfig;
const config = yamlParse(configYAML) as WarpRouteDeployConfig;

return validateConfig(hre, config);
};

// Ideally this should be using Hyperlane SDK to validate the config, but it's not possible at the moment given that
// the SDK is a ESM and Hardhat is quite brittle with ESM, so this would be a very simple/rough implementation
// According to HH docs https://hardhat.org/hardhat-runner/docs/advanced/using-esm:
// You can write your scripts and tests as both CommonJS and ES modules. However, your Hardhat config, and any file
// imported by it, must be CommonJS modules.
const validateConfig = async (
hre: HardhatRuntimeEnvironment,
config: WarpRouteDeployConfig,
): Promise<WarpRouteDeployConfig> => {
const entries = Object.entries(config);
const isValid =
entries.some(([_, config]) => config.type === "collateral") ||
entries.every(([_, config]) => hasTokenMetadata(config));

if (!isValid) {
throw new Error("Config must include Native or Collateral OR all synthetics must define token metadata");
}

return completeConfigMetadata(hre, config);
};

const completeConfigMetadata = async (
hre: HardhatRuntimeEnvironment,
config: WarpRouteDeployConfig,
): Promise<WarpRouteDeployConfig> => {
const entries = Object.entries(config);
const collateral = entries.find(([_, config]) => config.type === "collateral");
const synthetics = entries.filter(([_, config]) => config.type === "synthetic");

const updatedConfig = structuredClone(config);

if (collateral) {
const [network, route] = collateral;

if (!hre.network.config.chainId) throw new Error("Chain ID not found in network config");
const localChainId: string = hre.network.config.chainId.toString();
if (chainNames[parseInt(localChainId) as keyof typeof chainNames] !== network)
throw new Error("Collateral should be defined for the local chain");

let tokenAddress: string = "";
if ("token" in route) {
tokenAddress = route.token;
}

if (!tokenAddress) throw new Error("Token address is required for collateral");

let name: string = "";
let symbol: string = "";
let decimals: BigInt = 0n;

if (route.isNft) {
const token = await hre.ethers.getContractAt("IERC721Metadata", tokenAddress);
name = await token.name();
symbol = await token.symbol();
} else {
const token = await hre.ethers.getContractAt("IERC20Metadata", tokenAddress);
name = await token.name();
symbol = await token.symbol();
decimals = await token.decimals();
}

for (const [net, route] of synthetics) {
if (!hasTokenMetadata(route)) {
updatedConfig[net].name = name;
updatedConfig[net].symbol = symbol;
updatedConfig[net].decimals = Number(decimals);
updatedConfig[net].totalSupply = 0;
}
}
}

return updatedConfig;
};

const hasTokenMetadata = (config: any): boolean => {
return config.name && config.symbol && config.decimals;
};

//workaround for for using the @hyperlane-xyz/registry which is an ESM but Hardhat is quite brittle with ESM
// According to HH docs https://hardhat.org/hardhat-runner/docs/advanced/using-esm:
// You can write your scripts and tests as both CommonJS and ES modules. However, your Hardhat config, and any file
// imported by it, must be CommonJS modules.
export const getHyperlaneRegistry = async (chainName: string): Promise<any> => {
const registry = await import(`@hyperlane-xyz/registry/chains/${chainName}/addresses.json`);

Expand Down
48 changes: 22 additions & 26 deletions tasks/warpDeploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ task("warpDeploy", "Deploy multiple warp routes from a single chain")
.addParam("proxyadminsalt", "Salt for deploying the proxy admin")
.addParam("proxysalt", "Salt for deploying the router proxy")
.setAction(async function (taskArguments: TaskArguments, hre) {
const config: WarpRouteDeployConfig = await getWarpDeployConfig();
const config: WarpRouteDeployConfig = await getWarpDeployConfig(hre);
const accounts = await hre.ethers.getSigners();
const deployer = accounts[0];
const deployerAddress = deployer.address;
Expand All @@ -40,13 +40,13 @@ task("warpDeploy", "Deploy multiple warp routes from a single chain")
console.log("Deployer's Multicall address:", deployerMulticallAddress);

const remoteChainsNames = Object.keys(config).filter(
(e) => e != chainNames[parseInt(localChainId) as keyof typeof chainNames],
(e) => e !== chainNames[parseInt(localChainId) as keyof typeof chainNames],
);

const remoteICAAddresses = await Promise.all(
Object.keys(config)
.map((key: string) => chainIds[key as keyof typeof chainIds])
.filter((e) => e != parseInt(localChainId))
.filter((e) => e !== parseInt(localChainId))
.map((id: number) => {
return localRouterContract["getRemoteInterchainAccount(uint32,address)"](
id,
Expand Down Expand Up @@ -119,41 +119,37 @@ task("warpDeploy", "Deploy multiple warp routes from a single chain")
};
}, {});

console.log(dataByChain);

const domains = Object.keys(config).map((key: string) => chainIds[key as keyof typeof chainIds]);
const routerAddressesB32 = [
ethers.zeroPadValue(localWarpProxyAddress, 32),
...remoteWarpProxyAddresses.map((addr: string) => ethers.zeroPadValue(addr, 32)),
];

let calls: CallLib.CallStruct[] = [];
calls.push(
...(await createWarpRouterCall(
hre,
createXContract,
localWarpRouteAddress,
localWarpProxyAdminAddress,
localWarpProxyAddress,
config,
chainNames[parseInt(localChainId) as keyof typeof chainNames],
localRouterSalt,
localProxyAdminSalt,
localProxySalt,
deployerMulticallAddress,
deployerMulticallAddress,
domains,
routerAddressesB32,
)),
const calls: CallLib.CallStruct[] = await createWarpRouterCall(
hre,
createXContract,
localWarpRouteAddress,
localWarpProxyAdminAddress,
localWarpProxyAddress,
config,
chainNames[parseInt(localChainId) as keyof typeof chainNames],
localRouterSalt,
localProxyAdminSalt,
localProxySalt,
deployerMulticallAddress,
deployerMulticallAddress,
domains,
routerAddressesB32,
);

const hyperlaneRegistry = await getHyperlaneRegistry(
chainNames[hre.network.config.chainId as keyof typeof chainNames],
);

let remoteICACalls: CallLib.CallStruct[] = await Promise.all(
Object.keys(dataByChain).map(async (name: string) => {
let data = dataByChain[name];
// Object.keys(dataByChain).map(async (name: string) => {
Object.entries(dataByChain).map(async ([name, data]:[string, any]) => {
// let data = dataByChain[name];
let createCalls: CallLib.CallStruct[] = await createWarpRouterCall(
hre,
createXContract,
Expand Down Expand Up @@ -208,7 +204,7 @@ task("warpDeploy", "Deploy multiple warp routes from a single chain")
let tx;
const multicallCode = await hre.ethers.provider.getCode(deployerMulticallAddress);

if (multicallCode != "0x") {
if (multicallCode !== "0x") {
console.log("Multicall contract already deployed, using multicall");
const userMulticallContract = await hre.ethers.getContractAt(
"TransferrableOwnableMulticall",
Expand Down