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

update contract workflows to take in external artifacts #236

Merged
merged 5 commits into from
Oct 28, 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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@open-ibc/vibc-core-smart-contracts",
"version": "4.0.5",
"version": "4.0.7",
"main": "dist/index.js",
"bin": {
"verify-vibc-core-smart-contracts": "./dist/scripts/verify-contract-script.js",
Expand Down
44 changes: 32 additions & 12 deletions src/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from "./utils/io";
import assert from "assert";
import {
AccountRegistry,
SingleSigAccountRegistry,
connectProviderAccounts,
Wallet,
} from "./evm/schemas/account";
Expand All @@ -22,7 +22,8 @@ import { Logger } from "./utils/cli";
import { DEFAULT_DEPLOYER } from "./utils/constants";
import { Chain } from "./evm/chain";
import * as vibcContractFactories from "./evm/contracts/index";
import { isParsedMultiSigWallet } from "./evm/schemas/account";
import { isMultisig } from "./evm/schemas/multisig";
import { SendingAccountRegistry } from "./evm/schemas/sendingAccount";

export async function updateNoncesForSender(
nonces: Record<string, number>,
Expand All @@ -38,6 +39,21 @@ export async function updateNoncesForSender(
return nonces;
}

// Converts a factory to a name. If a solc version is specified (which needs to happen for multiple solc versions in a foundry/hardhat project, then it will return the file with the constructed name)
export const getFactoryFileName = (
factoryName: string,
solcVersion: string | undefined
) => {
if (!solcVersion) return `${factoryName}__factory`;

// Filter version string to remove periods e.g. 0.8.15 -> 0815
const versionStr = solcVersion
.split("")
.filter((c) => c !== "." && c !== "v")
.join("");
return `${factoryName}${versionStr}__factory`;
};

/**
* Return deployment libraries, factory, factory constructor,
* and rendered arguments for a contract deployment
Expand All @@ -48,11 +64,12 @@ const getDeployData = (
env: StringToStringMap,
libraries: any[] = [],
init: { args: any[]; signature: string } | undefined,
contractFactories: Record<string, any>
contractFactories: Record<string, any>,
solcVersion: string | undefined
) => {
const contractFactoryFileName = getFactoryFileName(factoryName, solcVersion);
// @ts-ignore
const contractFactoryConstructor =
contractFactories[`${factoryName}__factory`];
const contractFactoryConstructor = contractFactories[contractFactoryFileName];
RnkSngh marked this conversation as resolved.
Show resolved Hide resolved
assert(
contractFactoryConstructor,
`cannot find contract factory constructor for contract: ${factoryName}`
Expand All @@ -67,7 +84,7 @@ const getDeployData = (
const factory = new contractFactoryConstructor(...libs);
if (!factory) {
throw new Error(
`cannot load contract factory for contract: ${factoryName} with factory name: ${factoryName}__factory`
`cannot load contract factory for contract: ${factoryName} from factory file: ${contractFactoryFileName}`
);
}

Expand All @@ -81,12 +98,12 @@ const getDeployData = (

export const deployContract = async (
chain: Chain,
accountRegistry: AccountRegistry,
accountRegistry: SingleSigAccountRegistry|SendingAccountRegistry,
contract: ContractItem,
logger: Logger,
dryRun: boolean = false,
writeContracts: boolean = true, // True if you want to save persisted artifact files.
extraContractFactories: Record<string, any> = {},
extraContractFactories: Record<string, object> = {},
nonces: Record<string, number> = {},
env: StringToStringMap = {}
) => {
Expand All @@ -107,26 +124,28 @@ export const deployContract = async (
env,
contract.libraries,
contract.init,
contractFactories
contractFactories,
contract.solcVersion
);

logger.info(
`[${chain.chainName}-${chain.deploymentEnvironment}]: deploying ${
contract.name
} with args: [${constructorData.args}] with libraries: ${JSON.stringify(
constructorData.libraries
)}`
)} `
);
let deployedAddr = `new.${contract.name}.address`;
const deployer = accountRegistry.mustGet(
contract.deployer ? contract.deployer : DEFAULT_DEPLOYER
);

if (isParsedMultiSigWallet(deployer)) {
if (isMultisig(deployer)) {
throw new Error(
`Contract Deployments not supported for multisig wallets!`
"Contract Deployments not supported for multisig wallets!"
);
}

const updatedNonces = await updateNoncesForSender(
nonces,
deployer.address,
Expand Down Expand Up @@ -164,6 +183,7 @@ export const deployContract = async (
name: contract.name,
args: constructorData.args,
libraries: constructorData.libraries,
solcVersion: contract.solcVersion,
};
writeDeployedContractToFile(chain, contractObject);
}
Expand Down
178 changes: 43 additions & 135 deletions src/evm/schemas/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,133 +3,68 @@ import { ethers } from "ethers";
import fs from "fs";
import path from "path";
import { Registry } from "../../utils/registry";
import { parseZodSchema, renderString } from "../../utils/io";
// ethers wallet with encryption
export type Wallet = ethers.Wallet | ethers.HDNodeWallet;

const privateKey = z
.object({
name: z.string().min(1),
// privateKey should be a hex string prefixed with 0x
privateKey: z.string().min(1),
})
.strict();

const mnemonic = z
.object({
name: z.string().min(1),
// a 12-word mnemonic; or more words per BIP-39 spec
mnemonic: z.string().min(1),
path: z.optional(z.string().min(1)),
index: z.optional(z.number().int().min(0)),
})
.strict();

const singleSigAccount = z.union([privateKey, mnemonic]);

// Uninitialized User input type
const multisigConfig = z
.object({
name: z.string().min(1),
privateKey: z.string().min(1),
safeAddress: z.string().min(1),
chainId: z.number(),
})
.strict();

// Type that loadEvmAccounts will return for multisig types
const multisigAccount = z
.object({
name: z.string().min(1),
privateKey: z.string().min(1),
safeAddress: z.string().min(1),
chainId: z.number(),
wallet: z.union([
z.instanceof(ethers.Wallet),
z.instanceof(ethers.HDNodeWallet),
]),
})

.strict();
import { renderString } from "../../utils/io";
import { initializedMultisigConfig, uninitializedMultisigConfig } from "./multisig";
import {
isMnemonic,
isPrivateKey,
isSingleSigAccount,
SingleSigAccount,
singleSigAccount,
Wallet,
} from "./wallet";

// ethers wallet with encryption
// geth compatible keystore
const keyStore = z.object({
dir: z.string().min(1),
password: z.optional(z.string()),
});

type Privatekey = z.infer<typeof privateKey>;
type Mnemonic = z.infer<typeof mnemonic>;
type SingleSigAccount = z.infer<typeof singleSigAccount>;
type MultiSigConfig = z.infer<typeof multisigConfig>;
type ParsedMultiSigWallet = z.infer<typeof multisigAccount>;
export type SendingAccount = Wallet | ParsedMultiSigWallet;
export type KeyStore = z.infer<typeof keyStore>;

const evmAccounts = z.array(z.union([singleSigAccount, multisigConfig]));
export const evmAccounts = z.array(
z.union([singleSigAccount, initializedMultisigConfig, uninitializedMultisigConfig])
); // Type of account that one can send transactions from
export type EvmAccounts = z.infer<typeof evmAccounts>;
export const EvmAccountsConfig = z.union([evmAccounts, keyStore]);
export type EvmAccountConfig = z.infer<typeof EvmAccountsConfig>;

export const isPrivateKey = (account: any): account is Privatekey => {
return privateKey.safeParse(account).success;
};
export const isMnemonic = (account: any): account is Mnemonic => {
return mnemonic.safeParse(account).success;
};

export const isSingleSigAccount = (
account: any
): account is SingleSigAccount => {
return singleSigAccount.safeParse(account).success;
};

export const isMultiSigConfig = (account: any): account is MultiSigConfig => {
return multisigConfig.safeParse(account).success;
};

export const isKeyStore = (account: any): account is KeyStore => {
export const isKeyStore = (account: unknown): account is KeyStore => {
return keyStore.safeParse(account).success;
};

export const isEvmAccount = (account: any): account is EvmAccounts => {
export const isEvmAccounts = (account: unknown): account is EvmAccounts => {
return evmAccounts.safeParse(account).success;
};
export const isEvmAccountsConfig = (
account: any
account: unknown
): account is EvmAccountConfig => {
return EvmAccountsConfig.safeParse(account).success;
};

// Note: only use this for already declared accounts.
export const isParsedMultiSigWallet = (
account: any
): account is ParsedMultiSigWallet => {
return multisigAccount.safeParse(account).success;
};

export class AccountRegistry extends Registry<SendingAccount> {
static load(config: any[], name: string): AccountRegistry {
return new AccountRegistry(loadEvmAccounts(config), config, name);
export class SingleSigAccountRegistry extends Registry<Wallet> {
static load(config: unknown[], name: string): SingleSigAccountRegistry {
return new SingleSigAccountRegistry(loadEvmAccounts(config), config, name);
}

static loadMultiple(registryItems: { name: string; registry: any }[]) {
const result = new Registry([] as AccountRegistry[], {
const result = new Registry([] as SingleSigAccountRegistry[], {
toObj: (t) => {
return { name: t.name, registry: t.serialize() };
},
});
for (const item of registryItems) {
result.set(item.name, AccountRegistry.load(item.registry, item.name));
result.set(
item.name,
SingleSigAccountRegistry.load(item.registry, item.name)
);
}
return result;
}

RnkSngh marked this conversation as resolved.
Show resolved Hide resolved
constructor(
r: Registry<SendingAccount>,
private config: any[],
name: string
) {
constructor(r: Registry<Wallet>, private config: any[], name: string) {
super([], { nameInParent: name });
for (const [name, wallet] of r.entries()) {
this.set(name, wallet);
Expand All @@ -143,44 +78,39 @@ export class AccountRegistry extends Registry<SendingAccount> {
return {
name: item.name,
privateKey: wallets[index].privateKey,
address: isParsedMultiSigWallet(wallets[index])
? wallets[index].wallet.address
: wallets[index].address,
address: wallets[index].address,
...item,
};
});
}

public getSinglePrivateKeyFromAccount = (accountName: string) => {
const account = this.mustGet(accountName);
if (isParsedMultiSigWallet(account)) {
return account.wallet.privateKey;
if (!isMnemonic(account)) {
return account.privateKey;
}
return account.privateKey;
return account.mnemonic;
};
RnkSngh marked this conversation as resolved.
Show resolved Hide resolved
// Connect all accounts to the provider
public connectProviderAccounts = (rpc: string) => {
const provider = ethers.getDefaultProvider(rpc);
for (const [name, account] of this.entries()) {
this.set(name, account.connect(provider), true);
}
return this;
RnkSngh marked this conversation as resolved.
Show resolved Hide resolved
};
}

// load a Map of { [name: string]: Wallet } from EvmAccountsSchema object
export function loadEvmAccounts(config: any): Registry<SendingAccount> {
export function loadEvmAccounts(config: unknown): Registry<Wallet> {
if (!isEvmAccountsConfig(config)) {
throw new Error(`Error parsing schema: ${config}`);
throw new Error(`Error parsing schema: ${config}: \n ${EvmAccountsConfig.safeParse(config).error}`);
}
const walletMap = new Registry<Wallet>([]);

RnkSngh marked this conversation as resolved.
Show resolved Hide resolved
const walletMap = new Registry<SendingAccount>([]);

if (isEvmAccount(config)) {
if (isEvmAccounts(config)) {
for (const account of config) {
if (isMultiSigConfig(account)) {
const wallet = createWallet({
privateKey: account.privateKey,
name: account.name,
});
const multisigAccount = {
...account,
wallet,
};
walletMap.set(account.name, multisigAccount);
} else if (isSingleSigAccount(account)) {
if (isSingleSigAccount(account)) {
walletMap.set(account.name, createWallet(account));
}
}
Expand All @@ -199,28 +129,6 @@ export function loadEvmAccounts(config: any): Registry<SendingAccount> {
return walletMap;
}

// Connect all accounts to the provider
export const connectProviderAccounts = (
accountRegistry: AccountRegistry,
rpc: string
) => {
const provider = ethers.getDefaultProvider(rpc);
const newAccounts = accountRegistry.subset([]);
for (const [name, account] of accountRegistry.entries()) {
if (isParsedMultiSigWallet(account)) {
const newMultisigWallet = {
...account,
wallet: account.wallet.connect(provider),
};
newAccounts.set(name, newMultisigWallet);
} else {
newAccounts.set(name, account.connect(provider));
}
}

return newAccounts;
};

export function createWallet(opt: SingleSigAccount): Wallet {
if (isPrivateKey(opt)) {
let renderedPrivatekey = opt.privateKey;
Expand Down
1 change: 1 addition & 0 deletions src/evm/schemas/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const ContractItemSchema = z
})
),
abi: z.optional(z.any()),
solcVersion: z.optional(z.string()),
RnkSngh marked this conversation as resolved.
Show resolved Hide resolved
})
.strict();

Expand Down
Loading
Loading