From 87c288542cecb41a7521ddc9e9e62aa43c30bb97 Mon Sep 17 00:00:00 2001 From: irubido Date: Wed, 10 Jan 2024 16:45:43 +0100 Subject: [PATCH 01/10] encode function and deploy flow --- ...ultichainHardhatRuntimeEnvironmentField.ts | 117 ++++- packages/plugin/src/adapterABI.ts | 419 ++++++++++++++++++ 2 files changed, 525 insertions(+), 11 deletions(-) create mode 100644 packages/plugin/src/adapterABI.ts diff --git a/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts b/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts index 11bbc5f..dd9eeb2 100644 --- a/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts +++ b/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts @@ -1,15 +1,23 @@ -import { HardhatRuntimeEnvironment } from "hardhat/types"; +import crypto from "crypto"; +import { Artifact, HardhatRuntimeEnvironment } from "hardhat/types"; import { Config } from "@buildwithsygma/sygma-sdk-core"; import { HardhatPluginError } from "hardhat/plugins"; -import { ContractConstructorArgs, ContractAbi } from "web3"; +import Web3, { + ContractConstructorArgs, + ContractAbi, + MatchPrimitiveType, + Transaction, +} from "web3"; import { getConfigEnvironmentVariable, getDeploymentNetworks, getNetworkChainId, } from "./utils"; +import { AdapterABI } from "./adapterABI"; export class MultichainHardhatRuntimeEnvironmentField { private isValidated: boolean = false; + private domainIds: number[] = []; public constructor(private readonly hre: HardhatRuntimeEnvironment) {} @@ -23,6 +31,8 @@ export class MultichainHardhatRuntimeEnvironmentField { const config = new Config(); await config.init(originChainId, environment); const domainChainIds = config.getDomains().map(({ chainId }) => chainId); + const domainIds = config.getDomains().map(({ id }) => id); + this.domainIds = domainIds; const deploymentNetworks = getDeploymentNetworks(this.hre); const deploymentNetworksInfo = await Promise.all( @@ -50,21 +60,106 @@ export class MultichainHardhatRuntimeEnvironmentField { this.isValidated = true; } + private async encodeFunction( + web3: Web3, + args: ContractConstructorArgs, + computedContractAddress: string, + artifact: Artifact + ): Promise<{ + constructorArgs: string[]; + initDatas: string[]; + gasLimit: bigint; + }> { + const { hexToBytes, bytesToHex } = web3.utils; + + const Contract = new web3.eth.Contract( + artifact.abi, + computedContractAddress + ); + const Method = Contract.deploy({ + data: artifact.bytecode, + arguments: args, + }); + const data = Method.encodeABI(); + const constructorArgs = [ + bytesToHex(hexToBytes(data).slice(hexToBytes(artifact.bytecode).length)), + ]; + const initDatas = [data]; + + const deployGasLimit = await Method.estimateGas(); + const gasLimit = deployGasLimit * BigInt(1.4); + + return { + constructorArgs, + initDatas, + gasLimit, + }; + } + public async deployMultichain( nameOrBytecode: string, args: ContractConstructorArgs, - options?: Object - ): Promise { + options?: { + salt?: MatchPrimitiveType<"bytes32", unknown>; + isUniquePerChain?: boolean; + } + ): Promise { if (!this.isValidated) await this.validateConfig(); - const bytcode = await this.hre.artifacts - .readArtifact(nameOrBytecode) - .then((artifact) => artifact.bytecode) - .catch(() => nameOrBytecode); + const ADAPTER_ADDRESS = "0x85d62ad850b322152bf4ad9147bfbf097da42217"; + + const artifact = this.hre.artifacts.readArtifactSync(nameOrBytecode); + const provider = this.hre.network.provider; + + //web3 + const web3 = new Web3(provider); + const [deployer] = await web3.eth.getAccounts(); + + //optional params + const salt = options?.salt ?? crypto.randomBytes(32).toString("hex"); + const isUniquePerChain = options?.isUniquePerChain ?? false; + + //adapter contract + const adapterContract = new web3.eth.Contract(AdapterABI, ADAPTER_ADDRESS); + const computedContractAddress = await adapterContract.methods + .computeContractAddress(deployer, salt, isUniquePerChain) + .call(); + + //deployment contract + const { constructorArgs, initDatas, gasLimit } = await this.encodeFunction( + web3, + args, + computedContractAddress, + artifact + ); + + const initCode = artifact.bytecode; + const destinationDomainIDs = this.domainIds; - // temp to silence eslint - console.log(args, options, bytcode); + const fees = await adapterContract.methods + .calculateDeployFee( + initCode, + gasLimit, + salt, + isUniquePerChain, + constructorArgs, + initDatas, + destinationDomainIDs + ) + .call(); - return "0x00"; + const tx = await adapterContract.methods + .deploy( + initCode, + gasLimit, + salt, + isUniquePerChain, + constructorArgs, + initDatas, + destinationDomainIDs, + fees + ) + .send(); + return tx; } } diff --git a/packages/plugin/src/adapterABI.ts b/packages/plugin/src/adapterABI.ts new file mode 100644 index 0000000..4ce16e0 --- /dev/null +++ b/packages/plugin/src/adapterABI.ts @@ -0,0 +1,419 @@ +export const AdapterABI = [ + { + inputs: [ + { + internalType: "contract ICreateX", + name: "factory", + type: "address", + }, + { + internalType: "contract IBridge", + name: "bridge", + type: "address", + }, + { + internalType: "bytes32", + name: "resourceID", + type: "bytes32", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "ExcessFee", + type: "error", + }, + { + inputs: [], + name: "InsufficientFee", + type: "error", + }, + { + inputs: [], + name: "InvalidHandler", + type: "error", + }, + { + inputs: [], + name: "InvalidLength", + type: "error", + }, + { + inputs: [], + name: "InvalidOrigin", + type: "error", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: false, + internalType: "bytes32", + name: "fortifiedSalt", + type: "bytes32", + }, + { + indexed: false, + internalType: "uint8", + name: "destinationDomainID", + type: "uint8", + }, + ], + name: "DeployRequested", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "bytes32", + name: "fortifiedSalt", + type: "bytes32", + }, + { + indexed: false, + internalType: "address", + name: "newContract", + type: "address", + }, + ], + name: "Deployed", + type: "event", + }, + { + inputs: [], + name: "BRIDGE", + outputs: [ + { + internalType: "contract IBridge", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "DOMAIN_ID", + outputs: [ + { + internalType: "uint8", + name: "", + type: "uint8", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "FACTORY", + outputs: [ + { + internalType: "contract ICreateX", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "RESOURCE_ID", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes", + name: "initCode", + type: "bytes", + }, + { + internalType: "uint256", + name: "gasLimit", + type: "uint256", + }, + { + internalType: "bytes32", + name: "salt", + type: "bytes32", + }, + { + internalType: "bool", + name: "isUniquePerChain", + type: "bool", + }, + { + internalType: "bytes[]", + name: "constructorArgs", + type: "bytes[]", + }, + { + internalType: "bytes[]", + name: "initDatas", + type: "bytes[]", + }, + { + internalType: "uint8[]", + name: "destinationDomainIDs", + type: "uint8[]", + }, + ], + name: "calculateDeployFee", + outputs: [ + { + internalType: "uint256[]", + name: "fees", + type: "uint256[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "sender", + type: "address", + }, + { + internalType: "bytes32", + name: "salt", + type: "bytes32", + }, + { + internalType: "bool", + name: "isUniquePerChain", + type: "bool", + }, + ], + name: "computeContractAddress", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "sender", + type: "address", + }, + { + internalType: "bytes32", + name: "salt", + type: "bytes32", + }, + { + internalType: "bool", + name: "isUniquePerChain", + type: "bool", + }, + { + internalType: "uint256", + name: "chainId", + type: "uint256", + }, + ], + name: "computeContractAddressForChain", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes", + name: "initCode", + type: "bytes", + }, + { + internalType: "uint256", + name: "gasLimit", + type: "uint256", + }, + { + internalType: "bytes32", + name: "salt", + type: "bytes32", + }, + { + internalType: "bool", + name: "isUniquePerChain", + type: "bool", + }, + { + internalType: "bytes[]", + name: "constructorArgs", + type: "bytes[]", + }, + { + internalType: "bytes[]", + name: "initDatas", + type: "bytes[]", + }, + { + internalType: "uint8[]", + name: "destinationDomainIDs", + type: "uint8[]", + }, + { + internalType: "uint256[]", + name: "fees", + type: "uint256[]", + }, + ], + name: "deploy", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "originDepositor", + type: "address", + }, + { + internalType: "bytes", + name: "initCode", + type: "bytes", + }, + { + internalType: "bytes", + name: "initData", + type: "bytes", + }, + { + internalType: "bytes32", + name: "fortifiedSalt", + type: "bytes32", + }, + ], + name: "execute", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "sender", + type: "address", + }, + { + internalType: "bytes32", + name: "salt", + type: "bytes32", + }, + { + internalType: "bool", + name: "isUniquePerChain", + type: "bool", + }, + ], + name: "fortify", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "gasLimit", + type: "uint256", + }, + { + internalType: "bytes", + name: "initCode", + type: "bytes", + }, + { + internalType: "bytes", + name: "initData", + type: "bytes", + }, + { + internalType: "bytes32", + name: "fortifiedSalt", + type: "bytes32", + }, + ], + name: "prepareDepositData", + outputs: [ + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes", + name: "input", + type: "bytes", + }, + { + internalType: "uint256", + name: "position", + type: "uint256", + }, + ], + name: "slice", + outputs: [ + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + stateMutability: "pure", + type: "function", + }, +] as const; From d5d83637f45638306b01ea09d59f18fa9ab5b47f Mon Sep 17 00:00:00 2001 From: irubido Date: Fri, 12 Jan 2024 16:45:37 +0100 Subject: [PATCH 02/10] draft --- ...ultichainHardhatRuntimeEnvironmentField.ts | 90 ++++++++++--------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts b/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts index dd9eeb2..ea2903c 100644 --- a/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts +++ b/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts @@ -7,6 +7,11 @@ import Web3, { ContractAbi, MatchPrimitiveType, Transaction, + net, + ContractMethodInputParameters, + ContractMethods, + AbiParameter, + Contract, } from "web3"; import { getConfigEnvironmentVariable, @@ -59,46 +64,45 @@ export class MultichainHardhatRuntimeEnvironmentField { this.isValidated = true; } - - private async encodeFunction( + + private async estimateGas( web3: Web3, - args: ContractConstructorArgs, - computedContractAddress: string, artifact: Artifact - ): Promise<{ - constructorArgs: string[]; - initDatas: string[]; - gasLimit: bigint; - }> { - const { hexToBytes, bytesToHex } = web3.utils; - + ): Promise { const Contract = new web3.eth.Contract( artifact.abi, - computedContractAddress ); - const Method = Contract.deploy({ - data: artifact.bytecode, - arguments: args, - }); - const data = Method.encodeABI(); - const constructorArgs = [ - bytesToHex(hexToBytes(data).slice(hexToBytes(artifact.bytecode).length)), - ]; - const initDatas = [data]; - - const deployGasLimit = await Method.estimateGas(); + const deployGasLimit = await Contract.deploy().estimateGas(); const gasLimit = deployGasLimit * BigInt(1.4); + return gasLimit + } + + private validateNetworkArgs() { + + } + + public encodeInitData(initMethodName: string, initMethodArgs: any) { - return { - constructorArgs, - initDatas, - gasLimit, - }; } + public async deployMultichainBytecode( + bytecode: string, + networkArgs: Record, + initData?: string, + }>, + options?: { + salt?: MatchPrimitiveType<"bytes32", unknown>; + isUniquePerChain?: boolean; + } + ) {} + public async deployMultichain( - nameOrBytecode: string, - args: ContractConstructorArgs, + name: string, + networkArgs: Record, + initData?: string, + }>, options?: { salt?: MatchPrimitiveType<"bytes32", unknown>; isUniquePerChain?: boolean; @@ -106,14 +110,15 @@ export class MultichainHardhatRuntimeEnvironmentField { ): Promise { if (!this.isValidated) await this.validateConfig(); + this.validateNetworkArgs(); + const ADAPTER_ADDRESS = "0x85d62ad850b322152bf4ad9147bfbf097da42217"; - const artifact = this.hre.artifacts.readArtifactSync(nameOrBytecode); + const artifact = this.hre.artifacts.readArtifactSync(name); const provider = this.hre.network.provider; //web3 const web3 = new Web3(provider); - const [deployer] = await web3.eth.getAccounts(); //optional params const salt = options?.salt ?? crypto.randomBytes(32).toString("hex"); @@ -121,24 +126,23 @@ export class MultichainHardhatRuntimeEnvironmentField { //adapter contract const adapterContract = new web3.eth.Contract(AdapterABI, ADAPTER_ADDRESS); - const computedContractAddress = await adapterContract.methods - .computeContractAddress(deployer, salt, isUniquePerChain) - .call(); + //deployment contract - const { constructorArgs, initDatas, gasLimit } = await this.encodeFunction( - web3, - args, - computedContractAddress, - artifact + const gasLimit = await this.estimateGas( + web3, artifact ); - const initCode = artifact.bytecode; + //TODO - func that checks initData for given network + const constructorArgs = mapConstructorArgs(); + const initDatas = mapInitData(); + + const deployBytecode = artifact.bytecode; const destinationDomainIDs = this.domainIds; const fees = await adapterContract.methods .calculateDeployFee( - initCode, + deployBytecode, gasLimit, salt, isUniquePerChain, @@ -150,7 +154,7 @@ export class MultichainHardhatRuntimeEnvironmentField { const tx = await adapterContract.methods .deploy( - initCode, + deployBytecode, gasLimit, salt, isUniquePerChain, From ae1c8be00793248f9ac3d2b1d75c280f03a267cc Mon Sep 17 00:00:00 2001 From: irubido Date: Tue, 16 Jan 2024 14:57:52 +0100 Subject: [PATCH 03/10] fixed args and initData --- ...ultichainHardhatRuntimeEnvironmentField.ts | 87 ++++++++----------- packages/plugin/src/utils.ts | 68 ++++++++++++++- 2 files changed, 103 insertions(+), 52 deletions(-) diff --git a/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts b/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts index ea2903c..55b4101 100644 --- a/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts +++ b/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts @@ -7,16 +7,15 @@ import Web3, { ContractAbi, MatchPrimitiveType, Transaction, - net, - ContractMethodInputParameters, - ContractMethods, - AbiParameter, - Contract, + Bytes, + utils, } from "web3"; import { getConfigEnvironmentVariable, getDeploymentNetworks, getNetworkChainId, + mapNetworkArgs, + sumedFees, } from "./utils"; import { AdapterABI } from "./adapterABI"; @@ -26,6 +25,8 @@ export class MultichainHardhatRuntimeEnvironmentField { public constructor(private readonly hre: HardhatRuntimeEnvironment) {} + public ADAPTER_ADDRESS = "0x85d62ad850b322152bf4ad9147bfbf097da42217"; + private async validateConfig(): Promise { const originChainId = await getNetworkChainId( this.hre.network.name, @@ -64,45 +65,35 @@ export class MultichainHardhatRuntimeEnvironmentField { this.isValidated = true; } - - private async estimateGas( - web3: Web3, - artifact: Artifact - ): Promise { - const Contract = new web3.eth.Contract( - artifact.abi, - ); + + private async estimateGas(web3: Web3, artifact: Artifact): Promise { + const Contract = new web3.eth.Contract(artifact.abi); const deployGasLimit = await Contract.deploy().estimateGas(); const gasLimit = deployGasLimit * BigInt(1.4); - return gasLimit - } - - private validateNetworkArgs() { - + return gasLimit; } - public encodeInitData(initMethodName: string, initMethodArgs: any) { - + public static encodeInitData( + artifact: Artifact, + initMethodName: string, + initMethodArgs: string[] + ): Bytes { + //TODO + // const contract = new Contract(artifact.abi); + // const encodedInitMethod = contract.methods[initMethodName](initMethodArgs).encodeABI(); + console.log(artifact, initMethodArgs, initMethodName); + return utils.hexToBytes("0x"); } - public async deployMultichainBytecode( - bytecode: string, - networkArgs: Record, - initData?: string, - }>, - options?: { - salt?: MatchPrimitiveType<"bytes32", unknown>; - isUniquePerChain?: boolean; - } - ) {} - public async deployMultichain( name: string, - networkArgs: Record, - initData?: string, - }>, + networkArgs: Record< + string, + { + args: ContractConstructorArgs; + initData?: string; + } + >, options?: { salt?: MatchPrimitiveType<"bytes32", unknown>; isUniquePerChain?: boolean; @@ -110,11 +101,7 @@ export class MultichainHardhatRuntimeEnvironmentField { ): Promise { if (!this.isValidated) await this.validateConfig(); - this.validateNetworkArgs(); - - const ADAPTER_ADDRESS = "0x85d62ad850b322152bf4ad9147bfbf097da42217"; - - const artifact = this.hre.artifacts.readArtifactSync(name); + const artifact = this.hre.artifacts.readArtifactSync(name); const provider = this.hre.network.provider; //web3 @@ -125,18 +112,18 @@ export class MultichainHardhatRuntimeEnvironmentField { const isUniquePerChain = options?.isUniquePerChain ?? false; //adapter contract - const adapterContract = new web3.eth.Contract(AdapterABI, ADAPTER_ADDRESS); + const adapterContract = new web3.eth.Contract( + AdapterABI, + this.ADAPTER_ADDRESS + ); + const gasLimit = await this.estimateGas(web3, artifact); - //deployment contract - const gasLimit = await this.estimateGas( - web3, artifact + const { constructorArgs, initDatas } = mapNetworkArgs( + artifact, + networkArgs ); - //TODO - func that checks initData for given network - const constructorArgs = mapConstructorArgs(); - const initDatas = mapInitData(); - const deployBytecode = artifact.bytecode; const destinationDomainIDs = this.domainIds; @@ -163,7 +150,7 @@ export class MultichainHardhatRuntimeEnvironmentField { destinationDomainIDs, fees ) - .send(); + .send({ value: sumedFees(fees) }); return tx; } } diff --git a/packages/plugin/src/utils.ts b/packages/plugin/src/utils.ts index e557822..11a1e5a 100644 --- a/packages/plugin/src/utils.ts +++ b/packages/plugin/src/utils.ts @@ -1,7 +1,18 @@ import assert from "assert"; -import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { Artifact, HardhatRuntimeEnvironment } from "hardhat/types"; import { Environment } from "@buildwithsygma/sygma-sdk-core"; -import { FMT_BYTES, FMT_NUMBER, HttpProvider, Web3 } from "web3"; +import { + Bytes, + Contract, + ContractAbi, + ContractConstructorArgs, + FMT_BYTES, + FMT_NUMBER, + HttpProvider, + Numbers, + Web3, + utils, +} from "web3"; export function getConfigEnvironmentVariable( hre: HardhatRuntimeEnvironment @@ -34,3 +45,56 @@ export function getDeploymentNetworks( ): string[] { return hre.config.multichain.deploymentNetworks; } + +export function sumedFees(fees: Numbers[]): string { + const sumOfFees = fees.reduce( + (previous, current) => BigInt(previous) + BigInt(current), + 0 + ); + return sumOfFees.toString(); +} + +export function mapNetworkArgs( + artifact: Artifact, + networkArgs: Record< + string, + { + args: ContractConstructorArgs; + initData?: Bytes; + } + > +): { constructorArgs: string[]; initDatas: Bytes[] } { + const { bytesToHex, hexToBytes } = utils; + const contract = new Contract(artifact.abi); + + const constructorArgs: string[] = []; + const initDatas: Bytes[] = []; + + Object.keys(networkArgs).map((networkName) => { + const encodedDeployMethod = contract + .deploy({ + data: artifact.bytecode, + arguments: networkArgs[networkName].args, + }) + .encodeABI(); + + const argsInBytes = bytesToHex( + hexToBytes(encodedDeployMethod).slice( + hexToBytes(artifact.bytecode).length + ) + ); + + constructorArgs.push(argsInBytes); + + if (networkArgs[networkName].initData) { + initDatas.push(networkArgs[networkName].initData as Bytes); + } else { + initDatas.push(hexToBytes("0x")); + } + }); + + return { + constructorArgs, + initDatas, + }; +} From c1baa2aaf3a9ae4c87eb485351d57ddb0a1c62e4 Mon Sep 17 00:00:00 2001 From: irubido Date: Tue, 16 Jan 2024 17:04:01 +0100 Subject: [PATCH 04/10] comments, web3constructor, optional params --- ...ultichainHardhatRuntimeEnvironmentField.ts | 52 ++++++++++++++----- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts b/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts index 55b4101..0c0d177 100644 --- a/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts +++ b/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts @@ -1,4 +1,3 @@ -import crypto from "crypto"; import { Artifact, HardhatRuntimeEnvironment } from "hardhat/types"; import { Config } from "@buildwithsygma/sygma-sdk-core"; import { HardhatPluginError } from "hardhat/plugins"; @@ -9,6 +8,8 @@ import Web3, { Transaction, Bytes, utils, + PayableCallOptions, + NonPayableCallOptions, } from "web3"; import { getConfigEnvironmentVariable, @@ -22,8 +23,12 @@ import { AdapterABI } from "./adapterABI"; export class MultichainHardhatRuntimeEnvironmentField { private isValidated: boolean = false; private domainIds: number[] = []; + private web3: Web3 | null; - public constructor(private readonly hre: HardhatRuntimeEnvironment) {} + public constructor(private readonly hre: HardhatRuntimeEnvironment) { + const provider = this.hre.network.provider; + this.web3 = new Web3(provider); + } public ADAPTER_ADDRESS = "0x85d62ad850b322152bf4ad9147bfbf097da42217"; @@ -85,8 +90,18 @@ export class MultichainHardhatRuntimeEnvironmentField { return utils.hexToBytes("0x"); } + /** + * @param contractName name of the contract + * @param networkArgs record key is name of the deploymentNetwork, same as in config multichain.deploymentNetwork + * @param args contract contructor args + * @param initData optional encoded initilize method, can be encoded with encodeInitData + * @param salt optional or generated by default from randombytes(32) + * @param isUniquePerChain optional + * @param customGasLimit optional gas limit for transaction, default is calculated in method + * @param customNonPayableTxOptions non payable options for web3 deploy.method.send(), payable summed fees are always calculated by the method + */ public async deployMultichain( - name: string, + contractName: string, networkArgs: Record< string, { @@ -97,28 +112,27 @@ export class MultichainHardhatRuntimeEnvironmentField { options?: { salt?: MatchPrimitiveType<"bytes32", unknown>; isUniquePerChain?: boolean; + customGasLimit?: bigint; + customNonPayableTxOptions?: NonPayableCallOptions; } - ): Promise { + ): Promise { if (!this.isValidated) await this.validateConfig(); + if (!this.web3) return; - const artifact = this.hre.artifacts.readArtifactSync(name); - const provider = this.hre.network.provider; - - //web3 - const web3 = new Web3(provider); + const artifact = this.hre.artifacts.readArtifactSync(contractName); //optional params - const salt = options?.salt ?? crypto.randomBytes(32).toString("hex"); + const salt = options?.salt ?? utils.randomBytes(32); const isUniquePerChain = options?.isUniquePerChain ?? false; + const gasLimit = + options?.customGasLimit ?? (await this.estimateGas(this.web3, artifact)); //adapter contract - const adapterContract = new web3.eth.Contract( + const adapterContract = new this.web3.eth.Contract( AdapterABI, this.ADAPTER_ADDRESS ); - const gasLimit = await this.estimateGas(web3, artifact); - const { constructorArgs, initDatas } = mapNetworkArgs( artifact, networkArgs @@ -139,6 +153,15 @@ export class MultichainHardhatRuntimeEnvironmentField { ) .call(); + let payableTxOptions: PayableCallOptions = { value: sumedFees(fees) }; + + if (options?.customNonPayableTxOptions) { + payableTxOptions = { + ...options.customNonPayableTxOptions, + value: sumedFees(fees), + }; + } + const tx = await adapterContract.methods .deploy( deployBytecode, @@ -150,7 +173,8 @@ export class MultichainHardhatRuntimeEnvironmentField { destinationDomainIDs, fees ) - .send({ value: sumedFees(fees) }); + .send(payableTxOptions); + return tx; } } From b7045e186b895a06df6a40d45f9208ef61bec570 Mon Sep 17 00:00:00 2001 From: irubido Date: Wed, 17 Jan 2024 12:41:27 +0100 Subject: [PATCH 05/10] build --- packages/plugin/test/project.test.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/plugin/test/project.test.ts b/packages/plugin/test/project.test.ts index 86943e0..c0a423e 100644 --- a/packages/plugin/test/project.test.ts +++ b/packages/plugin/test/project.test.ts @@ -42,10 +42,5 @@ describe("Integration tests examples", function () { describe("Hardhat Runtime Environment extension", function () { useEnvironment("hardhat-project"); - it("The deployMultichain field should return 0x00", async function () { - expect( - await this.hre.multichain.deployMultichain('test', []) - ).to.be.equal("0x00"); - }); }); }); From 6fc2580686bb1927fadb1b5265258734274dadac Mon Sep 17 00:00:00 2001 From: irubido Date: Wed, 17 Jan 2024 14:30:31 +0100 Subject: [PATCH 06/10] hardcoded gasLimit, deploymentIDs fix --- ...ultichainHardhatRuntimeEnvironmentField.ts | 40 ++++++++----------- packages/plugin/src/utils.ts | 26 ++++++++++-- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts b/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts index 0c0d177..3df4f72 100644 --- a/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts +++ b/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts @@ -1,5 +1,5 @@ import { Artifact, HardhatRuntimeEnvironment } from "hardhat/types"; -import { Config } from "@buildwithsygma/sygma-sdk-core"; +import { Config, Domain } from "@buildwithsygma/sygma-sdk-core"; import { HardhatPluginError } from "hardhat/plugins"; import Web3, { ContractConstructorArgs, @@ -22,7 +22,7 @@ import { AdapterABI } from "./adapterABI"; export class MultichainHardhatRuntimeEnvironmentField { private isValidated: boolean = false; - private domainIds: number[] = []; + private domains: Domain[] = []; private web3: Web3 | null; public constructor(private readonly hre: HardhatRuntimeEnvironment) { @@ -32,6 +32,9 @@ export class MultichainHardhatRuntimeEnvironmentField { public ADAPTER_ADDRESS = "0x85d62ad850b322152bf4ad9147bfbf097da42217"; + //current Sygma hardcoded gasLimit + private gasLimit = 1000000; + private async validateConfig(): Promise { const originChainId = await getNetworkChainId( this.hre.network.name, @@ -41,9 +44,9 @@ export class MultichainHardhatRuntimeEnvironmentField { const config = new Config(); await config.init(originChainId, environment); - const domainChainIds = config.getDomains().map(({ chainId }) => chainId); - const domainIds = config.getDomains().map(({ id }) => id); - this.domainIds = domainIds; + + this.domains = config.getDomains(); + const domainChainIds = this.domains.map(({ chainId }) => chainId); const deploymentNetworks = getDeploymentNetworks(this.hre); const deploymentNetworksInfo = await Promise.all( @@ -71,13 +74,6 @@ export class MultichainHardhatRuntimeEnvironmentField { this.isValidated = true; } - private async estimateGas(web3: Web3, artifact: Artifact): Promise { - const Contract = new web3.eth.Contract(artifact.abi); - const deployGasLimit = await Contract.deploy().estimateGas(); - const gasLimit = deployGasLimit * BigInt(1.4); - return gasLimit; - } - public static encodeInitData( artifact: Artifact, initMethodName: string, @@ -92,12 +88,11 @@ export class MultichainHardhatRuntimeEnvironmentField { /** * @param contractName name of the contract - * @param networkArgs record key is name of the deploymentNetwork, same as in config multichain.deploymentNetwork + * @param networkArgs record key is name of the networks on which contract is being deployed * @param args contract contructor args * @param initData optional encoded initilize method, can be encoded with encodeInitData * @param salt optional or generated by default from randombytes(32) * @param isUniquePerChain optional - * @param customGasLimit optional gas limit for transaction, default is calculated in method * @param customNonPayableTxOptions non payable options for web3 deploy.method.send(), payable summed fees are always calculated by the method */ public async deployMultichain( @@ -112,7 +107,6 @@ export class MultichainHardhatRuntimeEnvironmentField { options?: { salt?: MatchPrimitiveType<"bytes32", unknown>; isUniquePerChain?: boolean; - customGasLimit?: bigint; customNonPayableTxOptions?: NonPayableCallOptions; } ): Promise { @@ -124,8 +118,6 @@ export class MultichainHardhatRuntimeEnvironmentField { //optional params const salt = options?.salt ?? utils.randomBytes(32); const isUniquePerChain = options?.isUniquePerChain ?? false; - const gasLimit = - options?.customGasLimit ?? (await this.estimateGas(this.web3, artifact)); //adapter contract const adapterContract = new this.web3.eth.Contract( @@ -133,23 +125,23 @@ export class MultichainHardhatRuntimeEnvironmentField { this.ADAPTER_ADDRESS ); - const { constructorArgs, initDatas } = mapNetworkArgs( + const { constructorArgs, initDatas, deployDomainIDs } = mapNetworkArgs( artifact, - networkArgs + networkArgs, + this.domains ); const deployBytecode = artifact.bytecode; - const destinationDomainIDs = this.domainIds; const fees = await adapterContract.methods .calculateDeployFee( deployBytecode, - gasLimit, + this.gasLimit, salt, isUniquePerChain, constructorArgs, initDatas, - destinationDomainIDs + deployDomainIDs ) .call(); @@ -165,12 +157,12 @@ export class MultichainHardhatRuntimeEnvironmentField { const tx = await adapterContract.methods .deploy( deployBytecode, - gasLimit, + this.gasLimit, salt, isUniquePerChain, constructorArgs, initDatas, - destinationDomainIDs, + deployDomainIDs, fees ) .send(payableTxOptions); diff --git a/packages/plugin/src/utils.ts b/packages/plugin/src/utils.ts index 11a1e5a..c1521d8 100644 --- a/packages/plugin/src/utils.ts +++ b/packages/plugin/src/utils.ts @@ -1,6 +1,6 @@ import assert from "assert"; import { Artifact, HardhatRuntimeEnvironment } from "hardhat/types"; -import { Environment } from "@buildwithsygma/sygma-sdk-core"; +import { Domain, Environment } from "@buildwithsygma/sygma-sdk-core"; import { Bytes, Contract, @@ -13,6 +13,7 @@ import { Web3, utils, } from "web3"; +import { HardhatPluginError } from "hardhat/plugins"; export function getConfigEnvironmentVariable( hre: HardhatRuntimeEnvironment @@ -62,15 +63,33 @@ export function mapNetworkArgs( args: ContractConstructorArgs; initData?: Bytes; } - > -): { constructorArgs: string[]; initDatas: Bytes[] } { + >, + domains: Domain[] +): { + deployDomainIDs: bigint[]; + constructorArgs: string[]; + initDatas: Bytes[]; +} { const { bytesToHex, hexToBytes } = utils; const contract = new Contract(artifact.abi); + const deployDomainIDs: bigint[] = []; const constructorArgs: string[] = []; const initDatas: Bytes[] = []; Object.keys(networkArgs).map((networkName) => { + //checks if network args + const matchingDomain = domains.find( + (domain) => domain.name === networkName + ); + if (matchingDomain) deployDomainIDs.push(BigInt(matchingDomain.id)); + else { + throw new HardhatPluginError( + "@chainsafe/hardhat-plugin-multichain-deploy", + `Unavailable Networks in networkArgs: The following network ${networkName} is not supported as destination network.` + ); + } + const encodedDeployMethod = contract .deploy({ data: artifact.bytecode, @@ -94,6 +113,7 @@ export function mapNetworkArgs( }); return { + deployDomainIDs, constructorArgs, initDatas, }; From 3a1065105b833db4f0c1b66d26d0de2fa30c46c4 Mon Sep 17 00:00:00 2001 From: irubido Date: Wed, 17 Jan 2024 15:05:39 +0100 Subject: [PATCH 07/10] abi from contracts package --- packages/plugin/package.json | 1 + ...ultichainHardhatRuntimeEnvironmentField.ts | 4 +- packages/plugin/src/adapterABI.ts | 422 +----------------- 3 files changed, 6 insertions(+), 421 deletions(-) diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 6de5745..741bae2 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -48,6 +48,7 @@ "hardhat": "^2.0.0" }, "dependencies": { + "@chainsafe/hardhat-plugin-multichain-deploy-contracts": "workspace:^", "web3": "^4.3.0" } } diff --git a/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts b/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts index 3df4f72..e6d40ab 100644 --- a/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts +++ b/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts @@ -68,7 +68,7 @@ export class MultichainHardhatRuntimeEnvironmentField { .map(({ chainId, name }) => `${name}(${chainId})`) .join(", ") .replace(/, ([^,]*)$/, " and $1")}\n` + - `Please adjust your 'deploymentNetworks' to align with the supported routes in this environment. For details on supported networks, refer to the Sygma documentation.` + `Please adjust your 'deploymentNetworks' to align with the supported routes in this environment. For details on supported networks, refer to the Sygma documentation.` ); this.isValidated = true; @@ -120,7 +120,7 @@ export class MultichainHardhatRuntimeEnvironmentField { const isUniquePerChain = options?.isUniquePerChain ?? false; //adapter contract - const adapterContract = new this.web3.eth.Contract( + const adapterContract = new this.web3.eth.Contract( AdapterABI, this.ADAPTER_ADDRESS ); diff --git a/packages/plugin/src/adapterABI.ts b/packages/plugin/src/adapterABI.ts index 4ce16e0..30653e4 100644 --- a/packages/plugin/src/adapterABI.ts +++ b/packages/plugin/src/adapterABI.ts @@ -1,419 +1,3 @@ -export const AdapterABI = [ - { - inputs: [ - { - internalType: "contract ICreateX", - name: "factory", - type: "address", - }, - { - internalType: "contract IBridge", - name: "bridge", - type: "address", - }, - { - internalType: "bytes32", - name: "resourceID", - type: "bytes32", - }, - ], - stateMutability: "nonpayable", - type: "constructor", - }, - { - inputs: [], - name: "ExcessFee", - type: "error", - }, - { - inputs: [], - name: "InsufficientFee", - type: "error", - }, - { - inputs: [], - name: "InvalidHandler", - type: "error", - }, - { - inputs: [], - name: "InvalidLength", - type: "error", - }, - { - inputs: [], - name: "InvalidOrigin", - type: "error", - }, - { - anonymous: false, - inputs: [ - { - indexed: false, - internalType: "address", - name: "sender", - type: "address", - }, - { - indexed: false, - internalType: "bytes32", - name: "fortifiedSalt", - type: "bytes32", - }, - { - indexed: false, - internalType: "uint8", - name: "destinationDomainID", - type: "uint8", - }, - ], - name: "DeployRequested", - type: "event", - }, - { - anonymous: false, - inputs: [ - { - indexed: false, - internalType: "bytes32", - name: "fortifiedSalt", - type: "bytes32", - }, - { - indexed: false, - internalType: "address", - name: "newContract", - type: "address", - }, - ], - name: "Deployed", - type: "event", - }, - { - inputs: [], - name: "BRIDGE", - outputs: [ - { - internalType: "contract IBridge", - name: "", - type: "address", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "DOMAIN_ID", - outputs: [ - { - internalType: "uint8", - name: "", - type: "uint8", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "FACTORY", - outputs: [ - { - internalType: "contract ICreateX", - name: "", - type: "address", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "RESOURCE_ID", - outputs: [ - { - internalType: "bytes32", - name: "", - type: "bytes32", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes", - name: "initCode", - type: "bytes", - }, - { - internalType: "uint256", - name: "gasLimit", - type: "uint256", - }, - { - internalType: "bytes32", - name: "salt", - type: "bytes32", - }, - { - internalType: "bool", - name: "isUniquePerChain", - type: "bool", - }, - { - internalType: "bytes[]", - name: "constructorArgs", - type: "bytes[]", - }, - { - internalType: "bytes[]", - name: "initDatas", - type: "bytes[]", - }, - { - internalType: "uint8[]", - name: "destinationDomainIDs", - type: "uint8[]", - }, - ], - name: "calculateDeployFee", - outputs: [ - { - internalType: "uint256[]", - name: "fees", - type: "uint256[]", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "sender", - type: "address", - }, - { - internalType: "bytes32", - name: "salt", - type: "bytes32", - }, - { - internalType: "bool", - name: "isUniquePerChain", - type: "bool", - }, - ], - name: "computeContractAddress", - outputs: [ - { - internalType: "address", - name: "", - type: "address", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "sender", - type: "address", - }, - { - internalType: "bytes32", - name: "salt", - type: "bytes32", - }, - { - internalType: "bool", - name: "isUniquePerChain", - type: "bool", - }, - { - internalType: "uint256", - name: "chainId", - type: "uint256", - }, - ], - name: "computeContractAddressForChain", - outputs: [ - { - internalType: "address", - name: "", - type: "address", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes", - name: "initCode", - type: "bytes", - }, - { - internalType: "uint256", - name: "gasLimit", - type: "uint256", - }, - { - internalType: "bytes32", - name: "salt", - type: "bytes32", - }, - { - internalType: "bool", - name: "isUniquePerChain", - type: "bool", - }, - { - internalType: "bytes[]", - name: "constructorArgs", - type: "bytes[]", - }, - { - internalType: "bytes[]", - name: "initDatas", - type: "bytes[]", - }, - { - internalType: "uint8[]", - name: "destinationDomainIDs", - type: "uint8[]", - }, - { - internalType: "uint256[]", - name: "fees", - type: "uint256[]", - }, - ], - name: "deploy", - outputs: [], - stateMutability: "payable", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "originDepositor", - type: "address", - }, - { - internalType: "bytes", - name: "initCode", - type: "bytes", - }, - { - internalType: "bytes", - name: "initData", - type: "bytes", - }, - { - internalType: "bytes32", - name: "fortifiedSalt", - type: "bytes32", - }, - ], - name: "execute", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "sender", - type: "address", - }, - { - internalType: "bytes32", - name: "salt", - type: "bytes32", - }, - { - internalType: "bool", - name: "isUniquePerChain", - type: "bool", - }, - ], - name: "fortify", - outputs: [ - { - internalType: "bytes32", - name: "", - type: "bytes32", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "uint256", - name: "gasLimit", - type: "uint256", - }, - { - internalType: "bytes", - name: "initCode", - type: "bytes", - }, - { - internalType: "bytes", - name: "initData", - type: "bytes", - }, - { - internalType: "bytes32", - name: "fortifiedSalt", - type: "bytes32", - }, - ], - name: "prepareDepositData", - outputs: [ - { - internalType: "bytes", - name: "", - type: "bytes", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes", - name: "input", - type: "bytes", - }, - { - internalType: "uint256", - name: "position", - type: "uint256", - }, - ], - name: "slice", - outputs: [ - { - internalType: "bytes", - name: "", - type: "bytes", - }, - ], - stateMutability: "pure", - type: "function", - }, -] as const; +import CrosschainDeployAdapter from "@chainsafe/hardhat-plugin-multichain-deploy-contracts/artifacts/contracts/CrosschainDeployAdapter.sol/CrosschainDeployAdapter" + +export const AdapterABI = CrosschainDeployAdapter.abi; From a7799691456dd1f3e009fe8a2560456a34ea8d9c Mon Sep 17 00:00:00 2001 From: irubido Date: Wed, 17 Jan 2024 15:06:52 +0100 Subject: [PATCH 08/10] lint --- packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts | 2 +- packages/plugin/src/adapterABI.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts b/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts index e6d40ab..e27bf58 100644 --- a/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts +++ b/packages/plugin/src/MultichainHardhatRuntimeEnvironmentField.ts @@ -68,7 +68,7 @@ export class MultichainHardhatRuntimeEnvironmentField { .map(({ chainId, name }) => `${name}(${chainId})`) .join(", ") .replace(/, ([^,]*)$/, " and $1")}\n` + - `Please adjust your 'deploymentNetworks' to align with the supported routes in this environment. For details on supported networks, refer to the Sygma documentation.` + `Please adjust your 'deploymentNetworks' to align with the supported routes in this environment. For details on supported networks, refer to the Sygma documentation.` ); this.isValidated = true; diff --git a/packages/plugin/src/adapterABI.ts b/packages/plugin/src/adapterABI.ts index 30653e4..bc7ca71 100644 --- a/packages/plugin/src/adapterABI.ts +++ b/packages/plugin/src/adapterABI.ts @@ -1,3 +1,3 @@ -import CrosschainDeployAdapter from "@chainsafe/hardhat-plugin-multichain-deploy-contracts/artifacts/contracts/CrosschainDeployAdapter.sol/CrosschainDeployAdapter" +import CrosschainDeployAdapter from "@chainsafe/hardhat-plugin-multichain-deploy-contracts/artifacts/contracts/CrosschainDeployAdapter.sol/CrosschainDeployAdapter"; export const AdapterABI = CrosschainDeployAdapter.abi; From 8d38676fa8592a051a158be6bf9aee4f85fb1c8a Mon Sep 17 00:00:00 2001 From: irubido Date: Wed, 17 Jan 2024 15:07:13 +0100 Subject: [PATCH 09/10] yarn --- yarn.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 005409e..ffa355d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -196,7 +196,7 @@ __metadata: languageName: node linkType: hard -"@chainsafe/hardhat-plugin-multichain-deploy-contracts@workspace:packages/contracts": +"@chainsafe/hardhat-plugin-multichain-deploy-contracts@workspace:^, @chainsafe/hardhat-plugin-multichain-deploy-contracts@workspace:packages/contracts": version: 0.0.0-use.local resolution: "@chainsafe/hardhat-plugin-multichain-deploy-contracts@workspace:packages/contracts" dependencies: @@ -221,6 +221,7 @@ __metadata: resolution: "@chainsafe/hardhat-plugin-multichain-deploy@workspace:packages/plugin" dependencies: "@buildwithsygma/sygma-sdk-core": ^2.4.0 + "@chainsafe/hardhat-plugin-multichain-deploy-contracts": "workspace:^" "@types/chai": ^4.1.7 "@types/chai-as-promised": ^7 "@types/eslint": ^8 From e693a5957d42d5e2d56db1616ee1174459e6993d Mon Sep 17 00:00:00 2001 From: irubido Date: Wed, 17 Jan 2024 15:50:35 +0100 Subject: [PATCH 10/10] CI build before lint --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7448b6..260d2e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,6 @@ jobs: key: yarn-cache - run: yarn install --immutable # install dependencies - - run: yarn run lint # lint code - run: yarn run build # compile typescript into javascript + - run: yarn run lint # lint code - run: yarn run test # run tests