diff --git a/scripts/deployKeylessly-Create3Factory.js b/scripts/deployKeylessly-Create3Factory.js index 962e5de..e579a12 100644 --- a/scripts/deployKeylessly-Create3Factory.js +++ b/scripts/deployKeylessly-Create3Factory.js @@ -3,9 +3,8 @@ const factoryToDeploy = "axelarnetwork" // const factoryToDeploy = "ZeframLou" const isDeployEnabled = true // toggle in case you do deployment and verification separately. -// const isDeployEnabled = false + const isVerifyEnabled = true -// const isVerifyEnabled = false async function main() { const [wallet] = await ethers.getSigners() @@ -13,73 +12,9 @@ async function main() { console.log(`Using network: ${hre.network.name} (${hre.network.config.chainId}), account: ${wallet.address} having ${ethers.formatUnits(balanceOfWallet, "ether")} of native currency, RPC url: ${hre.network.config.url}`) const create3FactoryArtifact = getCreate3FactoryArtifact(factoryToDeploy) - - const gasCost = await ethers.provider.estimateGas({ data: create3FactoryArtifact.bytecode }) - console.log(`Expected gas cost: ${gasCost}`) - // const gasFeeEstimate = BigInt(txData.gasPrice) * gasCost - // console.log(`gasFeeEstimate: ${ethers.formatUnits(gasFeeEstimate, "ether")} of native currency`) - const gasLimit = getGasLimit(factoryToDeploy) - const gasLimitPercentageAboveCost = Number(gasLimit * 100n / gasCost) - 100 - console.log(`gasLimit: ${gasLimit} (${gasLimitPercentageAboveCost}% above expected cost)`) - if (gasLimitPercentageAboveCost < 10) { - console.log(`gasLimit may be too low to accommodate possibly increasing future opcode cost. Once you choose a gasLimit, you'll need to use the same value for deployments on other blockchains any time in the future in order for your contract to have the same address.`) - return - } - - - // Keep this data consistent otherwise the deployment address will become different - const txData = { - type: 0, - data: create3FactoryArtifact.bytecode, - nonce: 0, - gasLimit, - gasPrice: 100000000000n, // = 100 Gwei - value: 0, - chainId: 0, - } - // Keep this data consistent otherwise the deployment address will become different - const splitSig = { // manually created - r: "0x3333333333333333333333333333333333333333333333333333333333333333", - s: "0x3333333333333333333333333333333333333333333333333333333333333333", - v: 27 - } - - const { deriveAddressOfSignerFromSig } = require("./utils") - const derivedAddressOfSigner = await deriveAddressOfSignerFromSig(txData, splitSig) - console.log(`Derived address of transaction signer: ${derivedAddressOfSigner}`) - - txData.signature = splitSig - const txSignedSerialized = ethers.Transaction.from(txData).serialized - // console.log(`Signed raw transaction to be pushed to ${hre.network.name}: ${txSignedSerialized}`) - - // const tx = ethers.Transaction.from(txSignedSerialized) // checking the contents of signed transaction - // console.log(`Signed transaction: ${JSON.stringify(tx, null, 2)}`) - - const addressExpected = ethers.getCreateAddress({ from: derivedAddressOfSigner, nonce: txData.nonce }) - console.log(`Expected address of deployed ${factoryToDeploy} factory contract: ${addressExpected}`) - - if (await ethers.provider.getCode(addressExpected) !== "0x") { - console.log(`The factory contract already exists at ${addressExpected}. So you can now simply use it.`) - return - } - - const txSignedSerializedHash = ethers.keccak256(txSignedSerialized) - console.log(`Expected transaction ID: ${txSignedSerializedHash}`) - - - // FUND SIGNER - There needs to be some funds at derivedAddressOfSigner to pay gas fee for the deployment. - const isTransactionSignerFunded = await fundTransactionSigner(txData.gasPrice, txData.gasLimit, derivedAddressOfSigner, wallet, isDeployEnabled) - if (!isTransactionSignerFunded) isDeployEnabled = false - - - // THE DEPLOYMENT TRANSACTION - if (isDeployEnabled) { - console.log(`Deploying ${factoryToDeploy} factory contract by broadcasting signed raw transaction to ${hre.network.name}...`) - const transactionId = await ethers.provider.send("eth_sendRawTransaction", [txSignedSerialized]) - console.log(`${factoryToDeploy} factory contract was successfully deployed to ${addressExpected} in transaction ${transactionId}`) - } + const address = await deployKeylessly(create3FactoryArtifact.contractName, create3FactoryArtifact.bytecode, gasLimit, wallet) // VERIFY ON BLOCKCHAIN EXPLORER @@ -90,8 +25,8 @@ async function main() { await setTimeout(20000) } const { verifyContract } = require("./utils") - await verifyContract(addressExpected, []) - } else console.log(`Verification on local network isn't possible`) + await verifyContract(address, []) + } else console.log(`Verification on local network skipped`) } @@ -131,38 +66,6 @@ const getGasLimit = (factoryToDeploy) => { } -const fundTransactionSigner = async (gasPrice, gasLimit, derivedAddressOfSigner, wallet, isDeployEnabled) => { - const balanceOfSignerMinRequired = gasPrice * gasLimit - console.log(`Minimum balance of signer required based on the gasPrice and gasLimit: ${gasPrice} x ${gasLimit} wei = ${ethers.formatUnits(balanceOfSignerMinRequired, "ether")} of native currency`) - let balanceOfSigner = await ethers.provider.getBalance(derivedAddressOfSigner) - console.log(`balanceOfSigner: ${ethers.formatUnits(balanceOfSigner, "ether")}`) - - const shortfall = balanceOfSignerMinRequired - balanceOfSigner - if (balanceOfSigner < balanceOfSignerMinRequired) { - const balanceOfWallet = await ethers.provider.getBalance(wallet.address) - if (balanceOfWallet > balanceOfSignerMinRequired) { - console.log(`There are insufficient funds at ${derivedAddressOfSigner} on ${network.name} to broadcast the transaction.`) - - if (isDeployEnabled) { - const readlineSync = require("readline-sync") - const anwser = readlineSync.question(`Do you want to try to transfer ${ethers.formatUnits(shortfall, "ether")} of native currency from your wallet ${wallet.address} to there now (y/n)? `) - if (["y", "Y"].includes(anwser)) { - console.log(`Transferring ${ethers.formatUnits(shortfall, "ether")} of native currency from ${wallet.address} to ${derivedAddressOfSigner} on ${network.name}...`) - let txRec = await wallet.sendTransaction({ to: derivedAddressOfSigner, value: shortfall }) - await txRec.wait(1) - balanceOfSigner = await ethers.provider.getBalance(derivedAddressOfSigner) - console.log(`${derivedAddressOfSigner} now has ${ethers.formatUnits(balanceOfSigner, "ether")} of native currency`) - return true - } - } - } - console.log(`You'll need to transfer at least ${ethers.formatUnits(shortfall, "ether")} of native currency to there.`) - return false - } - return true -} - - main().catch(error => { console.error(error) process.exitCode = 1 diff --git a/scripts/deployKeylessly-TESTERC20.js b/scripts/deployKeylessly-TESTERC20.js index 42fca90..ef566d4 100644 --- a/scripts/deployKeylessly-TESTERC20.js +++ b/scripts/deployKeylessly-TESTERC20.js @@ -1,6 +1,11 @@ let isDeployEnabled = true // set to false initially to get gas cost or if you've already deployed and need to do verification on explorer. + const isVerifyEnabled = true +// First run with gasLimit = 0n to see expected gas cost in output for your contract. Then set gasLimit to a rounded-up value for future-proofing. Try to make it > 25% * gasCost. DON'T CHANGE IT AFTER DEPLOYING YOUR FIRST CONTRACT TO LIVE BLOCKCHAIN. +// const gasLimit = 0n +const gasLimit = 2500000n + async function main() { const [wallet] = await ethers.getSigners() const balanceOfWallet = await ethers.provider.getBalance(wallet.address) @@ -18,148 +23,26 @@ async function main() { "0xabcdef", // test byte constructor argument. bytes have to be 0x-prefixed ] - const artifactOfContractToDeploy = getArtifactOfContract(contractName) + const { getArtifactOfContract, deployKeylessly } = require("./keyless-deploy-functions") + const artifactOfContractToDeploy = getArtifactOfContract(contractName) const cfToken = await ethers.getContractFactory(artifactOfContractToDeploy.abi, artifactOfContractToDeploy.bytecode) - const bytecodeWithArgs = (await cfToken.getDeployTransaction(...constructorArgs)).data - // console.log(`bytecodeWithArgs: ${bytecodeWithArgs}`) - - const gasPrice = 100000000000n // = 100 Gwei. Made high for future-proofing. DON'T CHANGE IT AFTER DEPLOYING YOUR FIRST CONTRACT TO LIVE BLOCKCHAIN. - const gasCost = await ethers.provider.estimateGas({ data: bytecodeWithArgs }) - console.log(`Expected gas cost: ${gasCost}`) - // const gasFeeEstimate = BigInt(gasPrice) * gasCost - // console.log(`gasFeeEstimate: ${ethers.formatUnits(gasFeeEstimate, "ether")} of native currency`) - - // ENTER A ROUNDED-UP GAS LIMIT VALUE FOR FUTURE-PROOFING. Try to make it > 25% * gasCost. DON'T CHANGE IT AFTER DEPLOYING YOUR FIRST CONTRACT TO LIVE BLOCKCHAIN. - const gasLimit = 2500000n - const gasLimitPercentageAboveCost = Number(gasLimit * 100n / gasCost) - 100 - console.log(`gasLimit: ${gasLimit} (${gasLimitPercentageAboveCost}% above expected cost)`) - if (gasLimitPercentageAboveCost < 10) { - console.log(`gasLimit may be too low to accommodate for possibly increasing future opcode cost. Once you choose a gasLimit, you'll need to use the same value for deployments on other blockchains any time in the future in order for your contract to have the same address.`) - return - } - - // Keep this data consistent otherwise the deployment address will become different - const txData = { - type: 0, - data: bytecodeWithArgs, - nonce: 0, - gasLimit, - gasPrice, - value: 0, - chainId: 0, - } - - // Keep this data consistent otherwise the deployment address will become different - const splitSig = { // manually created - r: "0x3333333333333333333333333333333333333333333333333333333333333333", - s: "0x3333333333333333333333333333333333333333333333333333333333333333", - v: 27 - } - - const { deriveAddressOfSignerFromSig } = require("./utils") - const derivedAddressOfSigner = await deriveAddressOfSignerFromSig(txData, splitSig) - console.log(`Derived address of transaction signer: ${derivedAddressOfSigner}`) - - txData.signature = splitSig - const txSignedSerialized = ethers.Transaction.from(txData).serialized - // console.log(`Signed raw transaction to be pushed to ${hre.network.name}: ${txSignedSerialized}`) - - // const tx = ethers.Transaction.from(txSignedSerialized) // checking the contents of signed transaction - // console.log(`Signed transaction: ${JSON.stringify(tx, null, 2)}`) - - const addressExpected = ethers.getCreateAddress({ from: derivedAddressOfSigner, nonce: txData.nonce }) - console.log(`Expected address of deployed ${contractName} contract: ${addressExpected}`) - - if (await ethers.provider.getCode(addressExpected) !== "0x") { - console.log(`The contract already exists at ${addressExpected}.`) - return - } - const txSignedSerializedHash = ethers.keccak256(txSignedSerialized) - console.log(`Expected transaction ID: ${txSignedSerializedHash}`) - - - // FUND SIGNER - There needs to be some funds at derivedAddressOfSigner to pay gas fee for the deployment. - const isTransactionSignerFunded = await fundTransactionSigner(txData.gasPrice, txData.gasLimit, derivedAddressOfSigner, wallet, isDeployEnabled) - if (!isTransactionSignerFunded) isDeployEnabled = false - - - // THE DEPLOYMENT TRANSACTION - if (isDeployEnabled) { - console.log(`Deploying ${contractName} contract by broadcasting signed raw transaction to ${network.name}...`) - const transactionId = await ethers.provider.send("eth_sendRawTransaction", [txSignedSerialized]) - console.log(`${contractName} contract was successfully deployed to ${addressExpected} in transaction ${transactionId}`) - } + const address = await deployKeylessly(contractName, bytecodeWithArgs, gasLimitForImpl, wallet) // VERIFY ON BLOCKCHAIN EXPLORER - if (isVerifyEnabled && !["hardhat", "localhost"].includes(network.name)) { - if (isDeployEnabled) { - console.log(`Waiting to ensure that it will be ready for verification on etherscan...`) - const { setTimeout } = require("timers/promises") - await setTimeout(20000) - } - const { verifyContract } = require("./utils") - await verifyContract(addressOfCreate3Factory, []) - } else console.log(`Verification on local network isn't possible`) -} - - -const getArtifactOfContract = contractName => { - const { existsSync, cpSync } = require("fs") - - const savedArtifactFilePath = `artifacts-saved/contracts/${contractName}.sol/${contractName}.json` - const savedArtifactExists = existsSync(savedArtifactFilePath) - - let useSavedArtifact = true - if (savedArtifactExists) { - const readlineSync = require("readline-sync") - useSavedArtifact = !["n", "N"].includes(readlineSync.question(`${contractName} artifact found in artifacts-saved. Reuse it (y/n)? `)) - if (!useSavedArtifact) useSavedArtifact = !["y", "Y"].includes(readlineSync.question(`The saved ${contractName} artifact will be overwritten, causing your contract to be possibly deployed to a different address. Are you sure (y/n)? `)) - if (useSavedArtifact) console.log(`Using ${contractName} artifact that was found in artifacts-saved.`) - } - - if (!savedArtifactExists || !useSavedArtifact) { - console.log(`Storing new ${contractName} artifact into artifacts-saved.`) - cpSync(savedArtifactFilePath.replace("artifacts-saved", "artifacts"), savedArtifactFilePath, { recursive: true }) - } - - const { rootRequire } = require("./utils") - return rootRequire(savedArtifactFilePath) -} - - -const fundTransactionSigner = async (gasPrice, gasLimit, derivedAddressOfSigner, wallet, isDeployEnabled) => { - const balanceOfSignerMinRequired = gasPrice * gasLimit - console.log(`Minimum balance of signer required based on the gasPrice and gasLimit: ${gasPrice} x ${gasLimit} wei = ${ethers.formatUnits(balanceOfSignerMinRequired, "ether")} of native currency`) - let balanceOfSigner = await ethers.provider.getBalance(derivedAddressOfSigner) - console.log(`balanceOfSigner: ${ethers.formatUnits(balanceOfSigner, "ether")}`) - - const shortfall = balanceOfSignerMinRequired - balanceOfSigner - if (balanceOfSigner < balanceOfSignerMinRequired) { - const balanceOfWallet = await ethers.provider.getBalance(wallet.address) - if (balanceOfWallet > balanceOfSignerMinRequired) { - console.log(`There are insufficient funds at ${derivedAddressOfSigner} on ${network.name} to broadcast the transaction.`) - + if (isVerifyEnabled) + if (!["hardhat", "localhost"].includes(network.name)) { if (isDeployEnabled) { - const readlineSync = require("readline-sync") - const anwser = readlineSync.question(`Do you want to try to transfer ${ethers.formatUnits(shortfall, "ether")} of native currency from your wallet ${wallet.address} to there now (y/n)? `) - if (["y", "Y"].includes(anwser)) { - console.log(`Transferring ${ethers.formatUnits(shortfall, "ether")} of native currency from ${wallet.address} to ${derivedAddressOfSigner} on ${network.name}...`) - let txRec = await wallet.sendTransaction({ to: derivedAddressOfSigner, value: shortfall }) - await txRec.wait(1) - balanceOfSigner = await ethers.provider.getBalance(derivedAddressOfSigner) - console.log(`${derivedAddressOfSigner} now has ${ethers.formatUnits(balanceOfSigner, "ether")} of native currency`) - return true - } + console.log(`Waiting to ensure that it will be ready for verification on etherscan...`) + const { setTimeout } = require("timers/promises") + await setTimeout(20000) } - } - console.log(`You'll need to transfer at least ${ethers.formatUnits(shortfall, "ether")} of native currency to there.`) - return false - } - return true + const { verifyContract } = require("./utils") + await verifyContract(address, constructorArgs) + } else console.log(`Verification on local network skipped`) } diff --git a/scripts/deployKeylessly-TESTERC20UG.js b/scripts/deployKeylessly-TESTERC20UG.js new file mode 100644 index 0000000..2d35260 --- /dev/null +++ b/scripts/deployKeylessly-TESTERC20UG.js @@ -0,0 +1,65 @@ +// Both implementation and proxy deployed keylessly + +let isDeployEnabled = true // set to false initially to get gas cost or if you've already deployed and need to do verification on explorer. + +const isVerifyEnabled = true + +// First run with gasLimit = 0n to see expected gas cost in output for your contract. Then set gasLimit to a rounded-up value for future-proofing. Try to make it > 25% * gasCost. DON'T CHANGE IT AFTER DEPLOYING YOUR FIRST CONTRACT TO LIVE BLOCKCHAIN. +// const gasLimitForImpl = 0n +const gasLimitForImpl = 4000000n +const gasLimitForProxy = 500000n + + +async function main() { + const { printNativeCurrencyBalance, verifyContract } = require("./utils") + + const [wallet] = await ethers.getSigners() + console.log(`Using network: ${network.name} (${network.config.chainId}), account: ${wallet.address} having ${await printNativeCurrencyBalance(wallet.address)} of native currency, RPC url: ${network.config.url}`) + + // WRITE YOUR CONTRACT NAME AND CONSTRUCTOR ARGUMENTS HERE + const contractName = "TESTERC20UGV1" + const initializerArgs = [ // constructor not used in UUPS contracts. Instead, proxy will call initializer + wallet.address, + { x: 10, y: 5 }, + ] + + const { getArtifactOfContract, deployKeylessly } = require("./keyless-deploy-functions") + + const artifactOfContractToDeploy = getArtifactOfContract(contractName) + const cfToken = await ethers.getContractFactory(artifactOfContractToDeploy.abi, artifactOfContractToDeploy.bytecode) + const bytecodeWithArgs = (await cfToken.getDeployTransaction()).data // no contructor args + + const implAddress = await deployKeylessly(contractName, bytecodeWithArgs, gasLimitForImpl, wallet) + if (implAddress === undefined) return + + const proxyContractName = "ERC1967Proxy" + const cfProxy = await ethers.getContractFactory(proxyContractName) + const fragment = cfToken.interface.getFunction("initialize") + const initializerData = cfToken.interface.encodeFunctionData(fragment, initializerArgs) + const proxyConstructorArgs = [implAddress, initializerData] + + const proxyBytecodeWithArgs = (await cfProxy.getDeployTransaction(...proxyConstructorArgs)).data + + const proxyAddress = await deployKeylessly(proxyContractName, proxyBytecodeWithArgs, gasLimitForProxy, wallet) + + if (isDeployEnabled) proxy = await upgrades.forceImport(proxyAddress, cfToken) + + + // VERIFY ON BLOCKCHAIN EXPLORER + if (isVerifyEnabled) + if (!["hardhat", "localhost"].includes(network.name)) { + if (isDeployEnabled) { + console.log(`Waiting to ensure that it will be ready for verification on etherscan...`) + const { setTimeout } = require("timers/promises") + await setTimeout(20000) + } + const { verifyContract } = require("./utils") + await verifyContract(proxyAddress) // also verifies implementation + } else console.log(`Verification on local network skipped`) +} + + +main().catch(error => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployViaCREATE3-TESTERC20UG.js b/scripts/deployViaCREATE3-TESTERC20UG.js index f30f324..8aac9b5 100644 --- a/scripts/deployViaCREATE3-TESTERC20UG.js +++ b/scripts/deployViaCREATE3-TESTERC20UG.js @@ -1,3 +1,5 @@ +// implementation deployed normally, proxy deployed via CREATE3 + // CHOOSE WHICH FACTORY YOU WANT TO USE: "axelarnetwork" or "ZeframLou" const factoryToUse = "axelarnetwork" // const factoryToUse = "ZeframLou" @@ -23,7 +25,7 @@ async function main() { console.log(`Using network: ${network.name} (${network.config.chainId}), account: ${wallet.address} having ${await printNativeCurrencyBalance(wallet.address)} of native currency, RPC url: ${network.config.url}`) const tokenContractName = "TESTERC20UGV1" - const constructorArgsOfToken = [ + const initializerArgs = [ // constructor not used in UUPS contracts. Instead, proxy will call initializer wallet.address, { x: 10, y: 5 }, ] @@ -34,7 +36,7 @@ async function main() { let proxy, proxyAddress, implAddress, initializerData if (useDeployProxy) { if (isDeployEnabled) { - proxy = await upgrades.deployProxy(cfToken, constructorArgsOfToken, { kind: "uups", timeout: 0 }) + proxy = await upgrades.deployProxy(cfToken, initializerArgs, { kind: "uups", timeout: 0 }) await proxy.waitForDeployment() proxyAddress = proxy.target @@ -52,20 +54,21 @@ async function main() { console.log(`implAddress ${implAddress === expectedAddressOfImpl ? "matches" : "doesn't match"} expectedAddressOfImpl`) } const proxyContractName = "ERC1967Proxy" - const cfProxy = await ethers.getContractFactory(proxyContractName) // used hardhat-dependency-compiler to get the artifacts locally. The one in @openzeppelin/upgrades-core is old. + const cfProxy = await ethers.getContractFactory(proxyContractName) const fragment = cfToken.interface.getFunction("initialize") - initializerData = cfToken.interface.encodeFunctionData(fragment, constructorArgsOfToken) + initializerData = cfToken.interface.encodeFunctionData(fragment, initializerArgs) + const proxyConstructorArgs = [implAddress, initializerData] if (useCREATE3) { const { getArtifactOfFactory, getDeployedAddress, CREATE3Deploy } = rootRequire("scripts/CREATE3-deploy-functions.js") if (isDeployEnabled) { - proxy = await CREATE3Deploy(factoryToUse, addressOfFactory, cfProxy, proxyContractName, [implAddress, initializerData], salt, wallet) + proxy = await CREATE3Deploy(factoryToUse, addressOfFactory, cfProxy, proxyContractName, proxyConstructorArgs, salt, wallet) proxyAddress = proxy.target } else { const artifactOfFactory = getArtifactOfFactory(factoryToUse) const instanceOfFactory = await ethers.getContractAt(artifactOfFactory.abi, addressOfFactory) - proxyAddress = await getDeployedAddress(factoryToUse, instanceOfFactory, wallet.address, salt) + proxyAddress = await getDeployedAddress(factoryToUse, instanceOfFactory, wallet.address, salt) } } else { // not using CREATE3 proxy = await cfProxy.deploy(implAddress, initializerData) @@ -82,15 +85,16 @@ async function main() { // VERIFY ON BLOCKCHAIN EXPLORER - if (isVerifyEnabled && !["hardhat", "localhost"].includes(network.name)) { - if (isDeployEnabled) { - console.log(`Waiting to ensure that it will be ready for verification on etherscan...`) - const { setTimeout } = require("timers/promises") - await setTimeout(20000) - } + if (isVerifyEnabled) + if (!["hardhat", "localhost"].includes(network.name)) { + if (isDeployEnabled) { + console.log(`Waiting to ensure that it will be ready for verification on etherscan...`) + const { setTimeout } = require("timers/promises") + await setTimeout(20000) + } - await verifyContract(proxyAddress) // also verifies implementation - } + await verifyContract(proxyAddress) // also verifies implementation + } } diff --git a/scripts/keyless-deploy-functions.js b/scripts/keyless-deploy-functions.js new file mode 100644 index 0000000..1f05c1c --- /dev/null +++ b/scripts/keyless-deploy-functions.js @@ -0,0 +1,134 @@ +const fundTransactionSigner = async (gasPrice, gasLimit, derivedAddressOfSigner, wallet, isDeployEnabled) => { + const balanceOfSignerMinRequired = gasPrice * gasLimit + console.log(`Minimum balance of signer required based on the gasPrice and gasLimit: ${gasPrice} x ${gasLimit} wei = ${ethers.formatUnits(balanceOfSignerMinRequired, "ether")} of native currency`) + let balanceOfSigner = await ethers.provider.getBalance(derivedAddressOfSigner) + console.log(`balanceOfSigner: ${ethers.formatUnits(balanceOfSigner, "ether")}`) + + const shortfall = balanceOfSignerMinRequired - balanceOfSigner + if (balanceOfSigner < balanceOfSignerMinRequired) { + const balanceOfWallet = await ethers.provider.getBalance(wallet.address) + if (balanceOfWallet > balanceOfSignerMinRequired) { + console.log(`There are insufficient funds at ${derivedAddressOfSigner} on ${network.name} to broadcast the transaction.`) + + if (isDeployEnabled) { + const readlineSync = require("readline-sync") + const anwser = readlineSync.question(`Do you want to try to transfer ${ethers.formatUnits(shortfall, "ether")} of native currency from your wallet ${wallet.address} to there now (y/n)? `) + if (["y", "Y"].includes(anwser)) { + console.log(`Transferring ${ethers.formatUnits(shortfall, "ether")} of native currency from ${wallet.address} to ${derivedAddressOfSigner} on ${network.name}...`) + let txRec = await wallet.sendTransaction({ to: derivedAddressOfSigner, value: shortfall }) + await txRec.wait(1) + balanceOfSigner = await ethers.provider.getBalance(derivedAddressOfSigner) + console.log(`${derivedAddressOfSigner} now has ${ethers.formatUnits(balanceOfSigner, "ether")} of native currency`) + return true + } + } + } + console.log(`You'll need to transfer at least ${ethers.formatUnits(shortfall, "ether")} of native currency to there.`) + return false + } + return true +} + + +const getArtifactOfContract = contractName => { + const { existsSync, cpSync } = require("fs") + + const savedArtifactFilePath = `artifacts-saved/contracts/${contractName}.sol/${contractName}.json` + const savedArtifactExists = existsSync(savedArtifactFilePath) + + let useSavedArtifact = true + if (savedArtifactExists) { + const readlineSync = require("readline-sync") + useSavedArtifact = !["n", "N"].includes(readlineSync.question(`${contractName} artifact found in artifacts-saved. Reuse it (y/n)? `)) + if (!useSavedArtifact) useSavedArtifact = !["y", "Y"].includes(readlineSync.question(`The saved ${contractName} artifact will be overwritten, causing your contract to be possibly deployed to a different address. Are you sure (y/n)? `)) + if (useSavedArtifact) console.log(`Using ${contractName} artifact that was found in artifacts-saved.`) + } + + if (!savedArtifactExists || !useSavedArtifact) { + console.log(`Storing new ${contractName} artifact into artifacts-saved.`) + cpSync(savedArtifactFilePath.replace("artifacts-saved", "artifacts"), savedArtifactFilePath, { recursive: true }) + } + + const { rootRequire } = require("./utils") + return rootRequire(savedArtifactFilePath) +} + + +const deployKeylessly = async (contractName, bytecodeWithArgs, gasLimit, wallet) => { + console.log(`Deploying ${contractName} keylessly...`) + + const gasPrice = 100000000000n // = 100 Gwei. Made high for future-proofing. DON'T CHANGE IT AFTER DEPLOYING YOUR FIRST CONTRACT TO LIVE BLOCKCHAIN. + const gasCost = await ethers.provider.estimateGas({ data: bytecodeWithArgs }) + console.log(`Expected gas cost: ${gasCost}`) + // const gasFeeEstimate = BigInt(gasPrice) * gasCost + // console.log(`gasFeeEstimate: ${ethers.formatUnits(gasFeeEstimate, "ether")} of native currency`) + + const gasLimitPercentageAboveCost = Number(gasLimit * 100n / gasCost) - 100 + console.log(`gasLimit: ${gasLimit} (${gasLimitPercentageAboveCost}% above expected cost)`) + if (gasLimitPercentageAboveCost < 10) { + console.log(`gasLimit may be too low to accommodate for possibly increasing future opcode cost. Once you choose a gasLimit, you'll need to use the same value for deployments on other blockchains any time in the future in order for your contract to have the same address.`) + return + } + + // Keep this data consistent otherwise the deployment address will become different + const txData = { + type: 0, + data: bytecodeWithArgs, + nonce: 0, + gasLimit, + gasPrice, + value: 0, + chainId: 0, + } + + // Keep this data consistent otherwise the deployment address will become different + const splitSig = { // manually created + r: "0x3333333333333333333333333333333333333333333333333333333333333333", + s: "0x3333333333333333333333333333333333333333333333333333333333333333", + v: 27 + } + + const { deriveAddressOfSignerFromSig } = require("./utils") + const derivedAddressOfSigner = await deriveAddressOfSignerFromSig(txData, splitSig) + console.log(`Derived address of transaction signer: ${derivedAddressOfSigner}`) + + txData.signature = splitSig + const txSignedSerialized = ethers.Transaction.from(txData).serialized + // console.log(`Signed raw transaction to be pushed to ${hre.network.name}: ${txSignedSerialized}`) + + // const tx = ethers.Transaction.from(txSignedSerialized) // checking the contents of signed transaction + // console.log(`Signed transaction: ${JSON.stringify(tx, null, 2)}`) + + const addressExpected = ethers.getCreateAddress({ from: derivedAddressOfSigner, nonce: txData.nonce }) + console.log(`Expected address of deployed ${contractName} contract: ${addressExpected}`) + + if (await ethers.provider.getCode(addressExpected) !== "0x") { + console.log(`The contract already exists at ${addressExpected}.`) + return + } + + const txSignedSerializedHash = ethers.keccak256(txSignedSerialized) + console.log(`Expected transaction ID: ${txSignedSerializedHash}`) + + + // FUND SIGNER - There needs to be some funds at derivedAddressOfSigner to pay gas fee for the deployment. + const isTransactionSignerFunded = await fundTransactionSigner(txData.gasPrice, txData.gasLimit, derivedAddressOfSigner, wallet, isDeployEnabled) + if (!isTransactionSignerFunded) isDeployEnabled = false + + + // THE DEPLOYMENT TRANSACTION + if (isDeployEnabled) { + console.log(`Deploying ${contractName} contract by broadcasting signed raw transaction to ${network.name}...`) + // const txHash = await ethers.provider.send("eth_sendRawTransaction", [txSignedSerialized]) + const txReceipt = await ethers.provider.broadcastTransaction(txSignedSerialized) + + if (await ethers.provider.getCode(addressExpected) !== "0x") console.log(`${contractName} contract was successfully deployed to ${addressExpected} in transaction ${txReceipt.hash}`) + } + return addressExpected +} + + +module.exports = { + getArtifactOfContract, + deployKeylessly, +}