Skip to content

Commit

Permalink
it just works
Browse files Browse the repository at this point in the history
  • Loading branch information
amiecorso committed Oct 3, 2023
1 parent 1378d7a commit 3ccc240
Show file tree
Hide file tree
Showing 9 changed files with 358 additions and 35 deletions.
8 changes: 2 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
"@ethersproject/abstract-signer": "5.7.0",
"@ethersproject/properties": "5.7.0",
"@ethersproject/providers": "5.7.2",
"@fireblocks/fireblocks-web3-provider": "^1.2.4",
"@fireblocks/hardhat-fireblocks": "^1.2.2",
"@nomiclabs/hardhat-ethers": "^2.2.3",
"@nomiclabs/hardhat-etherscan": "^3.1.7",
"@nomiclabs/hardhat-waffle": "^2.0.3",
Expand Down Expand Up @@ -62,8 +64,6 @@
"ethereum-waffle": "^3.4.4",
"ethernal": "^2.0.2",
"ethers": "^5.7.2",
"fireblocks-defi-sdk": "1.2.15",
"fireblocks-sdk": "^4.0.0",
"fs-extra": "^10.1.0",
"google-artifactregistry-auth": "^3.1.0",
"handlebars": "^4.7.7",
Expand Down Expand Up @@ -116,9 +116,5 @@
"snapshot:production": "FOUNDRY_PROFILE=production forge snapshot --snap .gas-snapshot-production",
"lint:diff": "npx eslint --cache --cache-location ./.eslintcache -c .eslintrc.js $(git diff --relative --name-only --diff-filter=d $(git merge-base HEAD origin/master) -- \"*.ts\" \"*.js\" \"*.env\" \"*.toml\")",
"artifactregistry-login": "npx google-artifactregistry-auth"
},
"dependencies": {
"@fireblocks/fireblocks-web3-provider": "^1.2.4",
"@fireblocks/hardhat-fireblocks": "^1.2.2"
}
}
145 changes: 145 additions & 0 deletions plugins/fireblocks/fireblocks-signer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/* eslint-disable @typescript-eslint/dot-notation -- frequent FireblocksWeb3Provider private member access */
import type {
Signer,
TypedDataDomain,
TypedDataField,
} from '@ethersproject/abstract-signer';
import type {
TransactionRequest,
TransactionResponse,
} from '@ethersproject/providers';
import { _TypedDataEncoder, toUtf8Bytes } from 'ethers/lib/utils';
import type { Deferrable } from '@ethersproject/properties';
import type { Bytes } from '@ethersproject/bytes';
import type { BigNumber } from 'ethers';
import { ethers } from 'ethers';
import { FireblocksSigner as HardhatFireblocksSigner } from '@fireblocks/hardhat-fireblocks/dist/src/provider';
import type {
FireblocksProviderConfig,
FireblocksWeb3Provider,
} from '@fireblocks/fireblocks-web3-provider';
import type { EIP1193Provider } from 'hardhat/types/provider';

export class FireblocksSigner
extends HardhatFireblocksSigner
implements Signer
{
_isSigner = true;

provider?: ethers.providers.Provider | undefined;

private _jsonRpcSigner: ethers.providers.JsonRpcSigner;

private _ethersWeb3Provider: ethers.providers.Web3Provider;

private _defaultNote: string | undefined;

constructor(
provider: EIP1193Provider,
fireblocksConfig: FireblocksProviderConfig
) {
super(provider, fireblocksConfig);
this._ethersWeb3Provider = new ethers.providers.Web3Provider(
this['_fireblocksWeb3Provider'] as FireblocksWeb3Provider
);
this._defaultNote = fireblocksConfig.note;
this._jsonRpcSigner = this._ethersWeb3Provider.getSigner();
}

connect(provider: ethers.providers.Provider): Signer {
return this._jsonRpcSigner.connect(provider);
}

async getAddress(): Promise<string> {
return this._jsonRpcSigner.getAddress();
}

setNote(memo: string): void {
(this['_fireblocksWeb3Provider'] as FireblocksWeb3Provider)['note'] = memo;
}

restoreDefaultNote(): void {
(this['_fireblocksWeb3Provider'] as FireblocksWeb3Provider)['note'] =
this._defaultNote;
}

getBalance(
blockTag?: ethers.providers.BlockTag | undefined
): Promise<BigNumber> {
return this._jsonRpcSigner.getBalance(blockTag);
}

getTransactionCount(
blockTag?: ethers.providers.BlockTag | undefined
): Promise<number> {
return this._jsonRpcSigner.getTransactionCount(blockTag);
}

estimateGas(transaction: Deferrable<TransactionRequest>): Promise<BigNumber> {
return this._jsonRpcSigner.estimateGas(transaction);
}

call(
transaction: Deferrable<TransactionRequest>,
blockTag?: ethers.providers.BlockTag | undefined
): Promise<string> {
return this._jsonRpcSigner.call(transaction, blockTag);
}

sendTransaction(
transaction: Deferrable<TransactionRequest>
): Promise<TransactionResponse> {
return this.sendTransaction(transaction);
}

getChainId(): Promise<number> {
return this._jsonRpcSigner.getChainId();
}

getGasPrice(): Promise<BigNumber> {
return this._jsonRpcSigner.getGasPrice();
}

getFeeData(): Promise<ethers.providers.FeeData> {
return this._jsonRpcSigner.getFeeData();
}

resolveName(name: string): Promise<string> {
return this._jsonRpcSigner.resolveName(name);
}

checkTransaction(
transaction: Deferrable<TransactionRequest>
): Deferrable<TransactionRequest> {
return this._jsonRpcSigner.checkTransaction(transaction);
}

populateTransaction(
transaction: Deferrable<TransactionRequest>
): Promise<TransactionRequest> {
return this._jsonRpcSigner.populateTransaction(transaction);
}

_checkProvider(operation?: string | undefined): void {
return this._jsonRpcSigner._checkProvider(operation);
}

async signMessage(message: Bytes | string): Promise<string> {
const data = typeof message === 'string' ? toUtf8Bytes(message) : message;
return this._jsonRpcSigner.signMessage(data);
}

signTransaction(

Check warning on line 132 in plugins/fireblocks/fireblocks-signer.ts

View workflow job for this annotation

GitHub Actions / lint

Expected 'this' to be used by class method 'signTransaction'
_transaction: Deferrable<TransactionRequest>
): Promise<string> {
throw new Error('signing transactions is unsupported by JsonRpcSigner');
}

async _signTypedData(
domain: TypedDataDomain,
types: Record<string, TypedDataField[]>,
value: Record<string, any>

Check warning on line 141 in plugins/fireblocks/fireblocks-signer.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
): Promise<string> {
return this._jsonRpcSigner._signTypedData(domain, types, value);
}
}
74 changes: 74 additions & 0 deletions plugins/fireblocks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/* eslint-disable no-param-reassign -- hre and config are intended to be configured via assignment in this file */
import '@nomiclabs/hardhat-ethers';

import { extendConfig, extendEnvironment } from 'hardhat/config';
import { BackwardsCompatibilityProviderAdapter } from 'hardhat/internal/core/providers/backwards-compatibility';
import {
AutomaticGasPriceProvider,
AutomaticGasProvider,
} from 'hardhat/internal/core/providers/gas-providers';
import { HttpProvider } from 'hardhat/internal/core/providers/http';
import type {

Check warning on line 11 in plugins/fireblocks/index.ts

View workflow job for this annotation

GitHub Actions / lint

There should be no empty line within import group
EIP1193Provider,
HardhatConfig,
HardhatUserConfig,
HttpNetworkUserConfig,
} from 'hardhat/types';

import './type-extensions';
import { version as SDK_VERSION } from '@fireblocks/hardhat-fireblocks/package.json';

import { FireblocksSigner } from './fireblocks-signer';

extendConfig(
(config: HardhatConfig, userConfig: Readonly<HardhatUserConfig>) => {
const userNetworks = userConfig.networks;
if (userNetworks === undefined) {
return;
}
for (const networkName in userNetworks) {

Check warning on line 29 in plugins/fireblocks/index.ts

View workflow job for this annotation

GitHub Actions / lint

Using 'ForInStatement' is not allowed

Check warning on line 29 in plugins/fireblocks/index.ts

View workflow job for this annotation

GitHub Actions / lint

The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype
const network = userNetworks[networkName]! as HttpNetworkUserConfig;

Check warning on line 30 in plugins/fireblocks/index.ts

View workflow job for this annotation

GitHub Actions / lint

Forbidden non-null assertion
if (network.fireblocks !== undefined) {
if (
networkName === 'hardhat' ||
(network.url || '').includes('localhost') ||

Check warning on line 34 in plugins/fireblocks/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected nullable string value in conditional. Please handle the nullish/empty cases explicitly
(network.url || '').includes('127.0.0.1')
) {
throw new Error('Fireblocks is only supported for public networks.');
}
(config.networks[networkName] as HttpNetworkUserConfig).fireblocks = {
note: 'Created by Nori custom Fireblocks Hardhat Plugin',
logTransactionStatusChanges: true,
...network.fireblocks,
rpcUrl: network.url,
userAgent: `hardhat-fireblocks/${SDK_VERSION}`,
};
}
}
}
);

extendEnvironment((hre) => {
if ((hre.network.config as HttpNetworkUserConfig).fireblocks != undefined) {
const httpNetConfig = hre.network.config as HttpNetworkUserConfig;
const eip1193Provider = new HttpProvider(
httpNetConfig.url!,
hre.network.name,
httpNetConfig.httpHeaders,
httpNetConfig.timeout
);
let wrappedProvider: EIP1193Provider;
wrappedProvider = new FireblocksSigner(
eip1193Provider,
(hre.network.config as HttpNetworkUserConfig).fireblocks!
);
wrappedProvider = new AutomaticGasProvider(
wrappedProvider,
hre.network.config.gasMultiplier
);
wrappedProvider = new AutomaticGasPriceProvider(wrappedProvider);
hre.network.provider = new BackwardsCompatibilityProviderAdapter(
wrappedProvider
);
}
});
27 changes: 27 additions & 0 deletions plugins/fireblocks/type-extensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import 'hardhat/types/config';
import 'hardhat/types/runtime';

import type { FireblocksProviderConfig } from '@fireblocks/fireblocks-web3-provider';

import type { FireblocksSigner } from './fireblocks-signer';

declare module 'hardhat/types/config' {
interface HttpNetworkUserConfig {
fireblocks?: FireblocksProviderConfig;
}
interface HttpNetworkConfig {
fireblocks?: FireblocksProviderConfig;
}
interface HardhatConfig {
fireblocks: FireblocksProviderConfig;
}
}

declare module 'hardhat/types/runtime' {
interface HardhatRuntimeEnvironment {
fireblocks: {
getSigners: () => Promise<FireblocksSigner[]>;
getSigner: (index: number) => Promise<FireblocksSigner | undefined>;
};
}
}
42 changes: 29 additions & 13 deletions plugins/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable no-param-reassign -- hre is intended to be configured via assignment in this file */

import 'tsconfig-paths/register';
import '@nomiclabs/hardhat-waffle';
import '@openzeppelin/hardhat-defender';
Expand All @@ -21,7 +22,8 @@ import { lazyFunction, lazyObject } from 'hardhat/plugins';
import type { FactoryOptions } from '@nomiclabs/hardhat-ethers/types';
import type { HardhatNetworkHDAccountsConfig } from 'hardhat/types';
import { Wallet } from 'ethers';
import type { FireblocksWeb3Provider } from '@fireblocks/fireblocks-web3-provider';
import type { FireblocksSigner } from 'plugins/fireblocks/fireblocks-signer';
import { fireblocks } from 'hardhat';

import { Eip2612Signer } from '@/signers/eip-26126';
import { namedAccountIndices, namedAccounts } from '@/config/accounts';
Expand Down Expand Up @@ -94,7 +96,7 @@ const deployOrUpgradeProxy = async <
// '0x582a885C03A0104Dc3053FAA8486c178e51E48Db'
// );

const [signer] = await hre.getSigners();
const [signer]: Signer[] = await hre.getSigners();

hre.trace(
`deployOrUpgrade: ${contractName} from address ${await signer.getAddress()}`
Expand All @@ -119,15 +121,19 @@ const deployOrUpgradeProxy = async <
// )
) {
hre.trace('Deploying proxy and instance', contractName);
const fireblocksSigner = signer as unknown as FireblocksWeb3Provider;
// eslint-disable-next-line
fireblocksSigner['note'] = `Deploy proxy and instance for ${contractName}`;
const fireblocksSigner = signer as FireblocksSigner;
if (typeof fireblocksSigner.setNote === 'function') {
fireblocksSigner.setNote(`Deploy proxy and instance for ${contractName}`);
}
contract = await hre.upgrades.deployProxy<TContract>(
contractFactory,
args,
options
);
await contract.deployed();
if (typeof fireblocksSigner.restoreDefaultNote === 'function') {
fireblocksSigner.restoreDefaultNote();
}
hre.log(
'Deployed proxy and instance',
contractName,
Expand All @@ -145,9 +151,12 @@ const deployOrUpgradeProxy = async <
const existingImplementationAddress =
await hre.upgrades.erc1967.getImplementationAddress(maybeProxyAddress);
hre.trace('Existing implementation at:', existingImplementationAddress);
const fireblocksSigner = signer as unknown as FireblocksWeb3Provider;
// eslint-disable-next-line
fireblocksSigner['note'] = `Upgrade contract instance for ${contractName}`;
const fireblocksSigner = signer as FireblocksSigner;
if (typeof fireblocksSigner.setNote === 'function') {
fireblocksSigner.setNote(
`Upgrade contract instance for ${contractName}`
);
}
const deployment = await hre.deployments.get(contractName);
const artifact = await hre.deployments.getArtifact(contractName);
if (deployment.bytecode === artifact.bytecode) {
Expand Down Expand Up @@ -177,6 +186,9 @@ const deployOrUpgradeProxy = async <
await contract.deployed();
hre.trace('...successful deployment transaction', contractName);
}
if (typeof fireblocksSigner.restoreDefaultNote === 'function') {
fireblocksSigner.restoreDefaultNote();
}
} catch (error) {
hre.log(`Failed to upgrade ${contractName} with error:`, error);
throw new Error(`Failed to upgrade ${contractName} with error: ${error}`);
Expand All @@ -197,21 +209,25 @@ const deployNonUpgradeable = async <
args: unknown[];
options?: FactoryOptions;
}): Promise<InstanceOfContract<TContract>> => {
const [signer]: Signer[] = await hre.getSigners();
const [signer] = await hre.getSigners();
hre.log(
`deployNonUpgradeable: ${contractName} from address ${await signer.getAddress()}`
);
const contractFactory = await hre.ethers.getContractFactory<TFactory>(
contractName,
{ ...options, signer }
);
const fireblocksSigner = signer as unknown as FireblocksWeb3Provider;
// eslint-disable-next-line
fireblocksSigner['note'] = `Deploy ${contractName}`;
const fireblocksSigner = signer as FireblocksSigner;
if (typeof fireblocksSigner.setNote === 'function') {
fireblocksSigner.setNote(`Deploy ${contractName}`);
}
const contract = (await contractFactory.deploy(
...args
)) as InstanceOfContract<TContract>;
hre.log('Deployed non upgradeable contract', contractName, contract.address);
if (typeof fireblocksSigner.restoreDefaultNote === 'function') {
fireblocksSigner.restoreDefaultNote();
}
return contract;
};

Expand All @@ -228,7 +244,7 @@ extendEnvironment((hre) => {
if (hre.network.config.live) {
if (hre.config.fireblocks === undefined) {
throw new Error(
'Fireblocks config is required for live networks. Please set FIREBLOCKS_API_KEY and FIREBLOCKS_SECRET_KEY_PATH in your environment.'
'Fireblocks config is required for live networks. Please set FIREBLOCKS_API_KEY and FIREBLOCKS_SECRET_KEY_PATH and FIREBLOCKS_VAULT_ID in your environment.'
);
}
if (Boolean(hre.config.fireblocks.apiKey)) {
Expand Down
Loading

0 comments on commit 3ccc240

Please sign in to comment.