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

Overall UX improvements #48

Merged
merged 15 commits into from
Oct 28, 2023
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@solarity/hardhat-migrate",
"version": "2.0.0-alpha.5",
"version": "2.0.0-alpha.6",
"description": "Automatic deployment and verification of smart contracts",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
Expand Down
28 changes: 19 additions & 9 deletions src/deployer/Deployer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Signer } from "ethers";
import { isAddress, Signer } from "ethers";

import { HardhatRuntimeEnvironment } from "hardhat/types";

Expand Down Expand Up @@ -40,21 +40,31 @@ export class Deployer {

public async deployed<T, A = T, I = any>(
contract: Instance<A, I> | (T extends Truffle.Contract<I> ? T : never),
contractAddress?: string,
contractIdentifier?: string,
): Promise<I> {
const adapter = this._resolveAdapter(contract);
const contractName = adapter.getContractName(contract, {});
const defaultContractName = adapter.getContractName(contract, {});

if (contractAddress) {
if (!(await isDeployedContractAddress(contractAddress))) {
throw new MigrateError(`Contract with address '${contractAddress}' is not deployed`);
let contractAddress;

if (contractIdentifier === undefined) {
contractAddress = await TransactionProcessor.tryRestoreContractAddressByName(defaultContractName);

return adapter.toInstance(contract, contractAddress, {});
}

if (isAddress(contractIdentifier)) {
if (!(await isDeployedContractAddress(contractIdentifier))) {
throw new MigrateError(`Contract with address '${contractIdentifier}' is not deployed`);
}

TransactionProcessor.saveDeploymentTransactionWithContractName(contractName, contractAddress);
} else {
contractAddress = await TransactionProcessor.tryRestoreContractAddressByName(contractName);
TransactionProcessor.saveDeploymentTransactionWithContractName(defaultContractName, contractIdentifier);

return adapter.toInstance(contract, contractIdentifier, {});
}

contractAddress = await TransactionProcessor.tryRestoreContractAddressByName(contractIdentifier);

return adapter.toInstance(contract, contractAddress, {});
}

Expand Down
35 changes: 14 additions & 21 deletions src/deployer/MinimalContract.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ethers, Interface, Overrides, Signer, TransactionResponse } from "ethers";
import { ethers, Interface, Overrides, Signer } from "ethers";

import { Linker } from "./Linker";

Expand Down Expand Up @@ -107,34 +107,27 @@ export class MinimalContract {

const txResponse = await signer.sendTransaction(tx);

const [contractAddress] = await Promise.all([
this._waitForDeployment(txResponse),
Reporter.reportTransaction(txResponse, tx.contractName),
]);
await Reporter.reportTransaction(txResponse, tx.contractName);

const contractAddress = (await txResponse.wait(0))!.contractAddress;

if (typeof contractAddress !== "string") {
throw new MigrateError("Contract deployment failed. Invalid contract address conversion.");
}

TransactionProcessor.saveDeploymentTransaction(tx, tx.contractName, contractAddress);

VerificationProcessor.saveVerificationFunction({
contractAddress,
contractName: tx.contractName,
constructorArguments: args,
chainId: Number(await getChainId()),
});

return contractAddress;
}

private async _waitForDeployment(tx: TransactionResponse): Promise<string> {
const receipt = await tx.wait(this._config.wait);

if (receipt) {
return receipt.contractAddress!;
try {
VerificationProcessor.saveVerificationFunction({
contractAddress,
contractName: ArtifactProcessor.tryGetContractName(this._bytecode),
constructorArguments: args,
chainId: Number(await getChainId()),
});
} catch {
Reporter.reportVerificationFailedToSave(tx.contractName);
}

throw new MigrateError("Contract deployment failed.");
return contractAddress;
}
}
2 changes: 2 additions & 0 deletions src/deployer/adapters/TruffleAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ export class TruffleAdapter extends Adapter {

const methodString = getMethodString(contractName, methodName);

TruffleReporter.notifyTransactionSending(methodString);

if (this._config.continue) {
return this._recoverTransaction(methodString, onlyToSaveTx, oldMethod, args);
}
Expand Down
5 changes: 3 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { Provider } from "./tools/Provider";
import { Verifier } from "./verifier/Verifier";

export { Deployer } from "./deployer/Deployer";
export { Reporter } from "./tools/reporters/Reporter";
export { DefaultStorage } from "./tools/storage/MigrateStorage";

extendConfig(migrateConfigExtender);
Expand All @@ -33,7 +34,7 @@ const migrate: ActionType<MigrateConfig> = async (taskArgs, env) => {
env.config.migrate = mergeConfigs(taskArgs, env.config.migrate);

Linker.setConfig(env.config.migrate);
Reporter.init(env.config.migrate);
await Reporter.init(env.config.migrate);

// Make sure that contract artifacts are up-to-date.
await env.run(TASK_COMPILE, {
Expand All @@ -57,7 +58,7 @@ const migrate: ActionType<MigrateConfig> = async (taskArgs, env) => {
const migrateVerify: ActionType<MigrateVerifyConfig> = async (taskArgs, env) => {
const config = extendVerifyConfigs(taskArgs);

Reporter.init(env.config.migrate);
await Reporter.init(env.config.migrate);

await new Verifier(env, config).verifyBatch(
VerificationProcessor.restoreSavedVerificationFunctions(config.inputFile),
Expand Down
60 changes: 38 additions & 22 deletions src/tools/reporters/Reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,26 @@ import { ChainRecord, predefinedChains } from "../../types/verifier";
@catchError
export class Reporter {
private static _config: MigrateConfig;
private static _network: Network;
private static _nativeSymbol: string;
private static _explorerUrl: string;

private static totalCost: bigint = 0n;
private static totalTransactions: number = 0;

public static init(config: MigrateConfig) {
public static async init(config: MigrateConfig) {
this._config = config;

this._network = await this._getNetwork();

this._nativeSymbol = await this._getNativeSymbol();
this._explorerUrl = (await this._getExplorerUrl()) + "/tx/";
}

public static async reportMigrationBegin(files: string[]) {
public static reportMigrationBegin(files: string[]) {
this._reportMigrationFiles(files);

await this._reportChainInfo();
this._reportChainInfo();

console.log("\nStarting migration...\n");
}
Expand All @@ -36,12 +44,12 @@ export class Reporter {
console.log(`\n${underline(`Running ${file}...`)}`);
}

public static async summary() {
public static summary() {
const output =
`> ${"Total transactions:".padEnd(20)} ${this.totalTransactions}\n` +
`> ${"Final cost:".padEnd(20)} ${this.castAmount(this.totalCost, await this._getNativeSymbol())}\n`;
`> ${"Final cost:".padEnd(20)} ${this.castAmount(this.totalCost, this._nativeSymbol)}\n`;

console.log(output);
console.log(`\n${output}`);
}

public static async reportTransactionByHash(txHash: string, instanceName: string) {
Expand All @@ -66,6 +74,7 @@ export class Reporter {

const spinner = ora(await formatPendingTimeTask()).start();

// TODO: make 1000 configurable
const spinnerInterval = setInterval(async () => (spinner.text = await formatPendingTimeTask()), 1000);

let receipt: TransactionReceipt;
Expand Down Expand Up @@ -138,6 +147,21 @@ export class Reporter {
console.log(output);
}

public static reportVerificationFailedToSave(contractName: string) {
const output = `\nFailed to save verification arguments for contract: ${contractName}\n`;

console.log(output);
}

public static reportContracts(...contracts: [string, string][]): void {
const table: { Contract: string; Address: string }[] = contracts.map(([contract, address]) => ({
Contract: contract,
Address: address,
}));
console.table(table);
console.log();
}

private static _parseTransactionTitle(tx: TransactionResponse, instanceName: string): string {
if (tx.to === null) {
if (instanceName.split(":").length == 1) {
Expand All @@ -160,8 +184,8 @@ export class Reporter {
}; Seconds: ${((Date.now() - startTime) / 1000).toFixed(0)}`;
}

private static async _getExplorerLink(txHash: string): Promise<string> {
return (await this._getExplorerUrl()) + "/tx/" + txHash;
private static _getExplorerLink(txHash: string): string {
return this._explorerUrl + txHash;
}

private static async _printTransaction(tx: TransactionReceipt) {
Expand All @@ -171,7 +195,7 @@ export class Reporter {
output += `> contractAddress: ${tx.contractAddress}\n`;
}

const nativeSymbol = await this._getNativeSymbol();
const nativeSymbol = this._nativeSymbol;

output += `> blockNumber: ${tx.blockNumber}\n`;

Expand Down Expand Up @@ -214,10 +238,10 @@ export class Reporter {
console.log("");
}

private static async _reportChainInfo() {
console.log(`> ${"Network:".padEnd(20)} ${(await this._getNetwork()).name}`);
private static _reportChainInfo() {
console.log(`> ${"Network:".padEnd(20)} ${this._network.name}`);

console.log(`> ${"Network id:".padEnd(20)} ${await this._getChainId()}`);
console.log(`> ${"Network id:".padEnd(20)} ${this._network.chainId}`);
}

private static async _getNetwork(): Promise<Network> {
Expand All @@ -228,16 +252,8 @@ export class Reporter {
}
}

private static async _getChainId(): Promise<number> {
try {
return Number((await this._getNetwork()).chainId);
} catch {
return 1337;
}
}

private static async _getExplorerUrl(): Promise<string> {
const chainId = await this._getChainId();
const chainId = Number(this._network.chainId);

if (predefinedChains[chainId]) {
const explorers = predefinedChains[chainId].explorers;
Expand All @@ -251,7 +267,7 @@ export class Reporter {
}

private static async _getNativeSymbol(): Promise<string> {
const chainId = await this._getChainId();
const chainId = Number(this._network.chainId);

if (predefinedChains[chainId]) {
return predefinedChains[chainId].nativeCurrency.symbol;
Expand Down
5 changes: 5 additions & 0 deletions src/tools/reporters/TruffleReporter.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-console */
import { Reporter } from "./Reporter";

import { TruffleTransactionResponse } from "../../types/deployer";
Expand All @@ -8,4 +9,8 @@ export class TruffleReporter {

await Reporter.reportTransactionByHash(hash, instanceName);
}

public static notifyTransactionSending(methodString: string) {
console.log(`> ${methodString} is being sent...`);
}
}
8 changes: 7 additions & 1 deletion src/tools/storage/TransactionProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { validateKeyDeploymentFields, validateKeyTxFields } from "../../types/ty
export class TransactionProcessor {
private static _deployedContracts: Map<string, boolean> = new Map();

@catchError
@validateKeyDeploymentFields
public static saveDeploymentTransaction(args: ContractDeployTransaction, contractName: string, address: string) {
this._saveContract(args, address);
Expand All @@ -44,6 +45,7 @@ export class TransactionProcessor {
);
}

@catchError
@validateKeyDeploymentFields
public static async tryRestoreContractAddressByKeyFields(key: ContractDeployTransaction): Promise<string> {
const contractAddress = this._tryGetDataFromStorage(
Expand All @@ -62,6 +64,7 @@ export class TransactionProcessor {
return contractAddress;
}

@catchError
public static async tryRestoreContractAddressByName(contractName: string): Promise<string> {
const contractAddress = this._tryGetDataFromStorage(contractName);

Expand All @@ -72,6 +75,7 @@ export class TransactionProcessor {
return contractAddress;
}

@catchError
@validateKeyTxFields
public static tryRestoreSavedTransaction(key: KeyTransactionFields): ContractTransactionResponse {
if (this._deployedContracts.has(key.to)) {
Expand All @@ -89,6 +93,7 @@ export class TransactionProcessor {
);
}

@catchError
@validateKeyTxFields
private static _saveTransaction(args: KeyTransactionFields, transaction: KeyTransactionFields) {
TransactionStorage.set(
Expand All @@ -104,6 +109,7 @@ export class TransactionProcessor {
);
}

@catchError
@validateKeyDeploymentFields
private static _saveContract(args: ContractDeployTransaction, contractAddress: string) {
this._deployedContracts.set(contractAddress, true);
Expand All @@ -128,7 +134,7 @@ export class TransactionProcessor {
const value = TransactionStorage.get(key);

if (!value) {
throw new MigrateError(`Transaction not found in storage`);
throw new MigrateError(`Requested data not found in storage`);
}

return value;
Expand Down
12 changes: 7 additions & 5 deletions src/verifier/Verifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,21 @@ export class Verifier {

const instance = await this._getEtherscanInstance(this._hre);

if (await instance.isVerified(contractAddress)) {
Reporter.reportAlreadyVerified(contractAddress, contractName);
for (let attempts = 0; attempts < this._config.attempts; attempts++) {
if (await instance.isVerified(contractAddress)) {
Reporter.reportAlreadyVerified(contractAddress, contractName);

return;
}
break;
}

for (let attempts = 0; attempts < this._config.attempts; attempts++) {
try {
await this._tryVerify(instance, contractAddress, contractName, constructorArguments);
break;
} catch (e: any) {
this._handleVerificationError(contractAddress, contractName, e);
}

await new Promise((resolve) => setTimeout(resolve, 1000));
}
}

Expand Down
Loading