Skip to content

Commit

Permalink
support more chains
Browse files Browse the repository at this point in the history
  • Loading branch information
Troublor committed Aug 24, 2024
1 parent 71cfe87 commit 7aeab85
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 26 deletions.
4 changes: 2 additions & 2 deletions src/chain.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Chain } from 'viem';
import { mainnet } from 'viem/chains';
import * as chains from 'viem/chains';

export const supportedChains: Chain[] = [mainnet];
export const supportedChains: Chain[] = Object.values(chains);

/**
* Find a chain by its ID.
Expand Down
35 changes: 21 additions & 14 deletions src/clone/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Address, Chain } from 'viem';
import { CloneMetadata } from './meta';
import { instanceToPlain, plainToInstance } from 'class-transformer';
import assert from 'node:assert';
import { FileOverriddenError } from '../error';
import { FileOverriddenError, UnsupportedError } from '../error';

/**
* Clone a contract from a chain into the current project.
Expand All @@ -30,6 +30,25 @@ export async function cloneContract(
quiet?: boolean; // Do not log anything
},
) {
// load clone metadata file
const metaFile = path.join(hre.config.paths.root, CloneMetadata.META_FILE);
let metas: CloneMetadata[] = [];
if (fs.existsSync(metaFile)) {
const metaRaw = JSON.parse(fs.readFileSync(metaFile, 'utf-8'));
assert.ok(
metaRaw instanceof Array,
'Invalid metadata file, expected an array of CloneMetadata',
);
metas = metaRaw.map((meta: unknown) =>
plainToInstance(CloneMetadata, meta),
);
}
if (metas.length >= 1) {
throw new UnsupportedError(
'cloning multiple contracts in the same project is not yet supported',
);
}

// Log the cloning operation
opts.quiet ||
console.info(
Expand All @@ -53,7 +72,7 @@ export async function cloneContract(
}

// check API KEY
!apiKey && console.debug('No API key provided');
!apiKey && !opts.quiet && console.debug('No API key provided');

// fetch source from Etherscan
opts.quiet || console.debug('Fetching source code for', address);
Expand Down Expand Up @@ -118,18 +137,6 @@ export async function cloneContract(
cloneMetadata.clonedFiles = source_meta.sourceTree.allFiles;
// append to the metadata file
opts.quiet || console.debug('Appending to clone metadata file...');
const metaFile = path.join(hre.config.paths.root, CloneMetadata.META_FILE);
let metas: CloneMetadata[] = [];
if (fs.existsSync(metaFile)) {
const metaRaw = JSON.parse(fs.readFileSync(metaFile, 'utf-8'));
assert.ok(
metaRaw instanceof Array,
'Invalid metadata file, expected an array of CloneMetadata',
);
metas = metaRaw.map((meta: unknown) =>
plainToInstance(CloneMetadata, meta),
);
}
metas.push(cloneMetadata);
fs.writeFileSync(
path.join(hre.config.paths.root, CloneMetadata.META_FILE),
Expand Down
17 changes: 17 additions & 0 deletions src/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ export abstract class CloneError extends Error {
this.stack = options?.stack ?? new Error().stack;
this.cause = options?.cause;
}

toString(): string {
return `${this.message}
Cause:
${this.cause}
Stack:
${this.stack}`;
}
}

export class NotVerifiedError extends CloneError {
Expand Down Expand Up @@ -44,3 +52,12 @@ export class FileOverriddenError extends CloneError {
});
}
}

export class UnsupportedError extends CloneError {
constructor(feature: string, cause?: unknown) {
super('UnsupportedError', `Unsupported: ${feature}`, {
cause,
stack: new Error().stack,
});
}
}
35 changes: 25 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,23 @@ import './config-extensions';
import { task } from 'hardhat/config';
import * as types from './types';
import { cloneContract } from './clone';
import { findChain, supportedChains } from './chain';
import { findChain } from './chain';
import { CloneError } from './error';

task('clone', 'Clone on-chain contract into current Hardhat project')
.addOptionalParam(
'chain',
`The chain ID where the contract is deployed. Supported: ${supportedChains
.map((chain) => `${chain.id}(${chain.name})`)
.join(', ')}`,
`The chain ID where the contract is deployed. This option is used to determine Etherscan API endpoint. List of supported chains: https://github.com/wevm/viem/blob/main/src/chains/index.ts`,
1,
types.chain,
)
.addOptionalParam(
'etherscanApiUrl',
'The Etherscan API endpoint URL. Default to the mainnet API of the chain specified as --chain.',
undefined,
types.url,
)
.addParam(
'etherscanApiKey',
'The Etherscan API key (or equivalent) to use to fetch the contract',
)
Expand All @@ -41,10 +46,20 @@ task('clone', 'Clone on-chain contract into current Hardhat project')
let { address, destination, chain, etherscanApiKey, quiet } = args;
if (!(chain instanceof Object)) chain = findChain(chain);

await cloneContract(hre, chain, address, destination, {
apiKey: etherscanApiKey,
quiet,
});

console.log('Successfully cloned contract to', destination);
try {
await cloneContract(hre, chain, address, destination, {
apiKey: etherscanApiKey,
quiet,
});
quiet || console.log('Successfully cloned contract to', destination);
} catch (e) {
const err = e as CloneError;
quiet ||
console.error(
`Failed to clone contract due to error: ${
err.name
}\n${err.toString()}`,
);
process.exit(1);
}
});
12 changes: 12 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,16 @@ export const chain: CLIArgumentType<Chain> = {
validate: () => {},
};

export const url: CLIArgumentType<string> = {
parse: (_argName, strValue) => strValue,
name: 'url',
validate: (_argName: string, value: string): void => {
try {
new URL(value);
} catch (e) {
throw new Error(`Invalid URL: ${e}`);
}
},
};

export const string = types.string;

0 comments on commit 7aeab85

Please sign in to comment.