hardhat-deploy
Version:
Hardhat Plugin For Replicable Deployments And Tests
189 lines (173 loc) • 5.74 kB
text/typescript
import {
TransactionReceipt,
TransactionRequest,
TransactionResponse,
} from '@ethersproject/providers';
import { ContractFactory, PayableOverrides, Signer, ethers } from 'ethers';
import { Artifact } from 'hardhat/types';
import * as zk from 'zksync-ethers';
import { Address, Deployment, DeployOptions, ExtendedArtifact } from '../types';
import { getAddress } from '@ethersproject/address';
import { keccak256 as solidityKeccak256 } from '@ethersproject/solidity';
import { hexConcat } from '@ethersproject/bytes';
export class DeploymentFactory {
private factory: ContractFactory;
private artifact: Artifact | ExtendedArtifact;
private isZkSync: boolean;
private getArtifact: (name: string) => Promise<Artifact>;
private overrides: PayableOverrides;
private args: any[];
constructor(
getArtifact: (name: string) => Promise<Artifact>,
artifact: Artifact | ExtendedArtifact,
args: any[],
network: any,
ethersSigner?: Signer | zk.Signer,
overrides: PayableOverrides = {}
) {
this.overrides = overrides;
this.getArtifact = getArtifact;
this.isZkSync = network.zksync;
this.artifact = artifact;
if (this.isZkSync) {
this.factory = new zk.ContractFactory(
artifact.abi,
artifact.bytecode,
ethersSigner as zk.Signer
);
} else {
this.factory = new ContractFactory(
artifact.abi,
artifact.bytecode,
ethersSigner
);
}
const numArguments = this.factory.interface.deploy.inputs.length;
if (args.length !== numArguments) {
throw new Error(
`expected ${numArguments} constructor arguments, got ${args.length}`
);
}
this.args = args;
}
public async extractFactoryDeps(artifact: any): Promise<string[]> {
const visited = new Set<string>();
visited.add(`${artifact.sourceName}:${artifact.contractName}`);
return await this._extractFactoryDepsRecursive(artifact, visited);
}
private async _extractFactoryDepsRecursive(
artifact: any,
visited: Set<string>
): Promise<string[]> {
// Load all the dependency bytecodes.
// We transform it into an array of bytecodes.
const factoryDeps: string[] = [];
for (const dependencyHash in artifact.factoryDeps) {
if (!dependencyHash) continue;
const dependencyContract = artifact.factoryDeps[dependencyHash];
if (!visited.has(dependencyContract)) {
const dependencyArtifact = await this.getArtifact(dependencyContract);
factoryDeps.push(dependencyArtifact.bytecode);
visited.add(dependencyContract);
const transitiveDeps = await this._extractFactoryDepsRecursive(
dependencyArtifact,
visited
);
factoryDeps.push(...transitiveDeps);
}
}
return factoryDeps;
}
public async getDeployTransaction(): Promise<TransactionRequest> {
let overrides = this.overrides;
if (this.isZkSync) {
const factoryDeps = await this.extractFactoryDeps(this.artifact);
const { customData, ..._overrides } = overrides ?? {};
overrides = {
..._overrides,
customData: {
...customData,
factoryDeps,
feeToken: zk.utils.ETH_ADDRESS,
},
};
}
return this.factory.getDeployTransaction(...this.args, overrides);
}
private async calculateEvmCreate2Address(
create2DeployerAddress: Address,
salt: string
): Promise<Address> {
const deploymentTx = await this.getDeployTransaction();
if (typeof deploymentTx.data !== 'string')
throw Error('unsigned tx data as bytes not supported');
return getAddress(
'0x' +
solidityKeccak256(
['bytes'],
[
`0xff${create2DeployerAddress.slice(2)}${salt.slice(
2
)}${solidityKeccak256(['bytes'], [deploymentTx.data]).slice(2)}`,
]
).slice(-40)
);
}
private async calculateZkCreate2Address(
create2DeployerAddress: Address,
salt: string
): Promise<Address> {
const bytecodeHash = zk.utils.hashBytecode(this.artifact.bytecode);
const constructor = this.factory.interface.encodeDeploy(this.args);
return zk.utils.create2Address(
create2DeployerAddress,
bytecodeHash,
salt,
constructor
);
}
public async getCreate2Address(
create2DeployerAddress: Address,
create2Salt: string
): Promise<Address> {
if (this.isZkSync)
return await this.calculateZkCreate2Address(
create2DeployerAddress,
create2Salt
);
return await this.calculateEvmCreate2Address(
create2DeployerAddress,
create2Salt
);
}
public async compareDeploymentTransaction(
transaction: TransactionResponse,
deployment: Deployment
): Promise<boolean> {
const newTransaction = await this.getDeployTransaction();
const newData = newTransaction.data?.toString();
if (this.isZkSync) {
const currentFlattened = hexConcat(deployment.factoryDeps || []);
const newFlattened = hexConcat(newTransaction.customData?.factoryDeps);
return transaction.data !== newData || currentFlattened != newFlattened;
} else {
return transaction.data !== newData;
}
}
getDeployedAddress(
receipt: TransactionReceipt,
options: DeployOptions,
create2Address: string | undefined
): string {
if (options.deterministicDeployment && create2Address) {
return create2Address;
}
if (this.isZkSync) {
const deployedAddresses = zk.utils
.getDeployedContracts(receipt)
.map((info) => info.deployedAddress);
return deployedAddresses[deployedAddresses.length - 1];
}
return receipt.contractAddress;
}
}