@layerzerolabs/hardhat-deploy
Version:
Hardhat Plugin For Replicable Deployments And Tests
190 lines (179 loc) • 6.28 kB
text/typescript
import {
TransactionRequest,
TransactionResponse,
} from '@ethersproject/providers';
import {ContractFactory, PayableOverrides, Signer} from 'ethers';
import {Artifact} from 'hardhat/types';
import * as zk from 'zksync-web3';
import {Address, ExtendedArtifact} from '../types';
import {getAddress} from '@ethersproject/address';
import {keccak256 as solidityKeccak256} from '@ethersproject/solidity';
import {arrayify, hexConcat} from '@ethersproject/bytes';
import {TronContractFactory} from './tron/contract';
import {TronSigner} from './tron/signer';
import {CreateSmartContract} from './tron/types';
export class DeploymentFactory {
private factory: ContractFactory;
private artifact: Artifact | ExtendedArtifact;
private isZkSync: boolean;
private isTron: 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 | TronSigner,
overrides: PayableOverrides = {}
) {
this.overrides = overrides;
this.getArtifact = getArtifact;
this.isZkSync = network.zksync;
this.isTron = network.tron;
this.artifact = artifact;
if (this.isZkSync) {
this.factory = new zk.ContractFactory(
artifact.abi,
artifact.bytecode,
ethersSigner as zk.Signer
);
} else if (this.isTron) {
let contractName = '';
if ('contractName' in artifact) ({contractName} = artifact);
this.factory = new TronContractFactory(
artifact.abi,
artifact.bytecode,
ethersSigner as TronSigner,
contractName
);
} 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;
}
// TODO add ZkSyncArtifact
private async extractFactoryDeps(artifact: any): Promise<string[]> {
// Load all the dependency bytecodes.
// We transform it into an array of bytecodes.
const factoryDeps: string[] = [];
for (const dependencyHash in artifact.factoryDeps) {
const dependencyContract = artifact.factoryDeps[dependencyHash];
const dependencyBytecodeString = (
await this.getArtifact(dependencyContract)
).bytecode;
factoryDeps.push(dependencyBytecodeString);
}
return factoryDeps;
}
public async getDeployTransaction(): Promise<TransactionRequest> {
let overrides = this.overrides;
if (this.isZkSync) {
const factoryDeps = await this.extractFactoryDeps(this.artifact);
const customData = {
customData: {
factoryDeps,
feeToken: zk.utils.ETH_ADDRESS,
},
};
overrides = {
...overrides,
...customData,
};
}
return this.factory.getDeployTransaction(...this.args, overrides);
}
// TVM formula is identical than EVM except for the prefix: keccak256( 0x41 ++ address ++ salt ++ keccak256(init_code))[12:]
// https://developers.tron.network/v4.4.0/docs/vm-vs-evm#tvm-is-basically-compatible-with-evm-with-some-differences-in-details
private async calculateEvmCreate2Address(
create2DeployerAddress: Address,
salt: string,
isTron?: boolean
): Promise<Address> {
const deploymentTx = await this.getDeployTransaction();
if (typeof deploymentTx.data !== 'string') {
throw Error('unsigned tx data as bytes not supported');
}
const prefix = isTron ? '0x41' : '0xff';
return getAddress(
'0x' +
solidityKeccak256(
['bytes'],
[
`${prefix}${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,
this.isTron
);
}
public async compareDeploymentTransaction(
transaction: TransactionResponse
): Promise<boolean> {
const newTransaction = await this.getDeployTransaction();
const newData = newTransaction.data?.toString();
if (this.isZkSync) {
const EIP712_TX_TYPE = 0x71;
const bytes = arrayify(transaction.data);
// zk.utils.parseTransaction cannot parse tx others than eip712
if (bytes[0] != EIP712_TX_TYPE) {
return transaction.data !== newData;
}
const deserialize = zk.utils.parseTransaction(transaction.data) as any;
const desFlattened = hexConcat(deserialize.customData.factoryDeps);
const factoryDeps = await this.extractFactoryDeps(this.artifact);
const newFlattened = hexConcat(factoryDeps);
return deserialize.data !== newData || desFlattened != newFlattened;
} else if (this.isTron) {
const tronDeployTx = newTransaction as CreateSmartContract;
const res = await (
this.factory.signer as TronSigner
).getTronWebTransaction(transaction.hash);
const contract = res.raw_data.contract[0];
const deployed_bytecode = contract.parameter.value.new_contract?.bytecode;
const newBytecode = tronDeployTx.bytecode + tronDeployTx.rawParameter;
return deployed_bytecode !== newBytecode;
} else {
return transaction.data !== newData;
}
}
}