-
Notifications
You must be signed in to change notification settings - Fork 61
Convert Hardhat tasks to Commander.js commands (using the Call example) #270
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| import path from "path"; | ||
| import fs from "fs"; | ||
| import { ethers } from "ethers"; | ||
| import { Command } from "commander"; | ||
| import ZRC20ABI from "@zetachain/protocol-contracts/abi/ZRC20.sol/ZRC20.json"; | ||
|
|
||
| export const getAbi = (name: string) => { | ||
| const abiPath = path.resolve( | ||
| __dirname, | ||
| path.join("..", "artifacts", "contracts", `${name}.sol`, `${name}.json`) | ||
| ); | ||
| return JSON.parse(fs.readFileSync(abiPath, "utf8")); | ||
| }; | ||
|
|
||
| export const createRevertOptions = (options: { | ||
| abortAddress: string; | ||
| callOnRevert: boolean; | ||
| onRevertGasLimit: string; | ||
| revertAddress: string; | ||
| revertMessage: string; | ||
| }) => { | ||
| return { | ||
| abortAddress: options.abortAddress, | ||
| callOnRevert: options.callOnRevert, | ||
| onRevertGasLimit: BigInt(options.onRevertGasLimit), | ||
| revertAddress: options.revertAddress, | ||
| revertMessage: ethers.hexlify(ethers.toUtf8Bytes(options.revertMessage)), | ||
| }; | ||
| }; | ||
|
|
||
| export const approveZRC20 = async ( | ||
| zrc20Address: string, | ||
| contract: string, | ||
| amount: string, | ||
| signer: ethers.Wallet, | ||
| gasLimit?: number | ||
| ) => { | ||
| const zrc20 = new ethers.Contract(zrc20Address, ZRC20ABI.abi, signer); | ||
| const [gasZRC20, gasFee] = gasLimit | ||
| ? await zrc20.withdrawGasFeeWithGasLimit(gasLimit) | ||
| : await zrc20.withdrawGasFee(); | ||
|
|
||
| const zrc20TransferTx = await zrc20.approve(contract, gasFee); | ||
| await zrc20TransferTx.wait(); | ||
|
|
||
| const decimals = await zrc20.decimals(); | ||
| if (gasZRC20 === zrc20.target) { | ||
| const targetTokenApprove = await zrc20.approve( | ||
| contract, | ||
| gasFee + ethers.parseUnits(amount, decimals) | ||
| ); | ||
| await targetTokenApprove.wait(); | ||
| } else { | ||
| const targetTokenApprove = await zrc20.approve( | ||
| contract, | ||
| ethers.parseUnits(amount, decimals) | ||
| ); | ||
| await targetTokenApprove.wait(); | ||
| const gasZRC20Contract = new ethers.Contract( | ||
| gasZRC20, | ||
| ZRC20ABI.abi, | ||
| signer | ||
| ); | ||
| const gasFeeApprove = await gasZRC20Contract.approve(contract, gasFee); | ||
| await gasFeeApprove.wait(); | ||
| } | ||
| return { decimals }; | ||
| }; | ||
|
|
||
| export const createCommand = (name: string) => { | ||
| return new Command(name) | ||
| .requiredOption( | ||
| "-c, --contract <address>", | ||
| "The address of the deployed contract" | ||
| ) | ||
| .requiredOption( | ||
| "-r, --receiver <address>", | ||
| "The address of the receiver contract" | ||
| ) | ||
| .option("--call-on-revert", "Whether to call on revert", false) | ||
| .option( | ||
| "--revert-address <address>", | ||
| "Revert address", | ||
| "0x0000000000000000000000000000000000000000" | ||
| ) | ||
| .option( | ||
| "--abort-address <address>", | ||
| "Abort address", | ||
| "0x0000000000000000000000000000000000000000" | ||
| ) | ||
| .option("--revert-message <string>", "Revert message", "0x") | ||
| .option( | ||
| "--on-revert-gas-limit <number>", | ||
| "Gas limit for revert tx", | ||
| "500000" | ||
| ) | ||
| .option("-n, --name <contract>", "Contract name") | ||
| .option( | ||
| "--rpc <url>", | ||
| "RPC endpoint", | ||
| "https://zetachain-athens-evm.blockpi.network/v1/rpc/public" | ||
| ) | ||
| .option("--gas-limit <number>", "Gas limit for the transaction", "1000000") | ||
| .option("--private-key <key>", "Private key to sign the transaction"); | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import { ethers } from "ethers"; | ||
|
|
||
| import { createCommand, createRevertOptions, getAbi } from "../common"; | ||
|
|
||
| const main = async (options: any) => { | ||
| const provider = new ethers.JsonRpcProvider(options.rpc); | ||
| const signer = new ethers.Wallet(options.privateKey, provider); | ||
|
|
||
| const message = ethers.AbiCoder.defaultAbiCoder().encode( | ||
| options.types, | ||
| options.values | ||
| ); | ||
|
|
||
| const { abi } = getAbi(options.name); | ||
| const contract = new ethers.Contract(options.contract, abi, signer); | ||
|
|
||
| const tx = await contract.call( | ||
| options.receiver, | ||
| message, | ||
| createRevertOptions(options), | ||
| { gasLimit: options.gasLimit } | ||
| ); | ||
| await tx.wait(); | ||
|
|
||
| console.log(`✅ Transaction sent: ${tx.hash}`); | ||
| }; | ||
|
|
||
| export const connectedCallCommand = createCommand("connected-call") | ||
| .requiredOption("-t, --types <types...>", "Parameter types as JSON string") | ||
| .requiredOption("-v, --values <values...>", "The values of the parameters") | ||
| .action(main); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import { ethers } from "ethers"; | ||
|
|
||
| import { createCommand, createRevertOptions, getAbi } from "../common"; | ||
|
|
||
| const main = async (options: any) => { | ||
| const provider = new ethers.JsonRpcProvider(options.rpc); | ||
| const signer = new ethers.Wallet(options.privateKey, provider); | ||
|
|
||
| const { abi } = getAbi(options.name); | ||
| const contract = new ethers.Contract(options.contract, abi, signer); | ||
|
|
||
| const tx = await contract.deposit( | ||
| options.receiver, | ||
| createRevertOptions(options), | ||
| { value: ethers.parseEther(options.amount), gasLimit: options.gasLimit } | ||
| ); | ||
| await tx.wait(); | ||
|
|
||
| console.log(`✅ Transaction sent: ${tx.hash}`); | ||
| }; | ||
|
|
||
| export const connectedDepositCommand = createCommand("connected-deposit") | ||
| .requiredOption("-a, --amount <number>", "Amount to deposit") | ||
| .action(main); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { ethers } from "ethers"; | ||
|
|
||
| import { createCommand, createRevertOptions, getAbi } from "../common"; | ||
|
|
||
| const main = async (options: any) => { | ||
| const provider = new ethers.JsonRpcProvider(options.rpc); | ||
| const signer = new ethers.Wallet(options.privateKey, provider); | ||
|
|
||
| const message = ethers.AbiCoder.defaultAbiCoder().encode( | ||
| options.types, | ||
| options.values | ||
| ); | ||
|
|
||
| const { abi } = getAbi(options.name); | ||
| const contract = new ethers.Contract(options.contract, abi, signer); | ||
|
|
||
| const tx = await contract.depositAndCall( | ||
| options.receiver, | ||
| message, | ||
| createRevertOptions(options), | ||
| { value: ethers.parseEther(options.amount), gasLimit: options.gasLimit } | ||
| ); | ||
| await tx.wait(); | ||
|
|
||
| console.log(`✅ Transaction sent: ${tx.hash}`); | ||
| }; | ||
|
|
||
| export const connectedDepositAndCallCommand = createCommand( | ||
| "connected-deposit-and-call" | ||
| ) | ||
| .requiredOption("-t, --types <types...>", "Parameter types as JSON string") | ||
| .requiredOption("-v, --values <values...>", "The values of the parameters") | ||
| .requiredOption("-a, --amount <number>", "Amount to deposit") | ||
| .action(main); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| #!/usr/bin/env npx tsx | ||
| import { Command } from "commander"; | ||
|
|
||
| import { connectedCallCommand } from "./connected/call"; | ||
| import { connectedDepositCommand } from "./connected/deposit"; | ||
| import { connectedDepositAndCallCommand } from "./connected/depositAndCall"; | ||
| import { universalCallCommand } from "./universal/call"; | ||
| import { universalWithdrawCommand } from "./universal/withdraw"; | ||
| import { universalWithdrawAndCallCommand } from "./universal/withdrawAndCall"; | ||
|
|
||
| const program = new Command() | ||
| .helpCommand(false) | ||
| .addCommand(connectedCallCommand) | ||
| .addCommand(connectedDepositCommand) | ||
| .addCommand(connectedDepositAndCallCommand) | ||
| .addCommand(universalCallCommand) | ||
| .addCommand(universalWithdrawCommand) | ||
| .addCommand(universalWithdrawAndCallCommand); | ||
|
|
||
| if (require.main === module) program.parse(); | ||
|
|
||
| export default program; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import { ethers } from "ethers"; | ||
|
|
||
| import { createCommand, createRevertOptions, getAbi } from "../common"; | ||
| import ZRC20ABI from "@zetachain/protocol-contracts/abi/ZRC20.sol/ZRC20.json"; | ||
|
|
||
| const main = async (options: any) => { | ||
| const provider = new ethers.JsonRpcProvider(options.rpc); | ||
| const signer = new ethers.Wallet(options.privateKey, provider); | ||
|
|
||
| const message = ethers.AbiCoder.defaultAbiCoder().encode( | ||
| options.types, | ||
| options.values | ||
| ); | ||
|
|
||
| const functionSignature = ethers.id(options.function).slice(0, 10); | ||
|
|
||
| const payload = ethers.hexlify(ethers.concat([functionSignature, message])); | ||
|
|
||
| const { abi } = getAbi(options.name); | ||
| const contract = new ethers.Contract(options.contract, abi, signer); | ||
|
|
||
| const zrc20 = new ethers.Contract(options.zrc20, ZRC20ABI.abi, signer); | ||
| const [, gasFee] = await zrc20.withdrawGasFeeWithGasLimit( | ||
| options.callOptionsGasLimit | ||
| ); | ||
| const zrc20TransferTx = await zrc20.approve(options.contract, gasFee); | ||
|
|
||
| await zrc20TransferTx.wait(); | ||
|
|
||
| const tx = await contract.call( | ||
| options.receiver, | ||
| options.zrc20, | ||
| payload, | ||
| { | ||
| gasLimit: options.callOptionsGasLimit, | ||
| isArbitraryCall: options.callOptionsIsArbitraryCall, | ||
| }, | ||
| createRevertOptions(options), | ||
| { gasLimit: options.gasLimit } | ||
| ); | ||
| await tx.wait(); | ||
|
|
||
| console.log(`✅ Transaction sent: ${tx.hash}`); | ||
| }; | ||
|
|
||
| export const universalCallCommand = createCommand("universal-call") | ||
| .requiredOption("-t, --types <types...>", "Parameter types as JSON string") | ||
| .requiredOption("-v, --values <values...>", "The values of the parameters") | ||
| .option("--call-options-is-arbitrary-call", "Call any function", false) | ||
| .option("--call-options-gas-limit", "The gas limit for the call", "500000") | ||
| .option( | ||
| "--function <function>", | ||
| `Function to call (example: "hello(string)")` | ||
| ) | ||
| .option("--zrc20 <address>", "The address of ZRC-20 to pay fees") | ||
| .action(main); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import { ethers } from "ethers"; | ||
|
|
||
| import { | ||
| approveZRC20, | ||
| createCommand, | ||
| createRevertOptions, | ||
| getAbi, | ||
| } from "../common"; | ||
|
|
||
| const main = async (options: any) => { | ||
| const provider = new ethers.JsonRpcProvider(options.rpc); | ||
| const signer = new ethers.Wallet(options.privateKey, provider); | ||
|
|
||
| const { abi } = getAbi(options.name); | ||
| const contract = new ethers.Contract(options.contract, abi, signer); | ||
|
|
||
| const { decimals } = await approveZRC20( | ||
| options.zrc20, | ||
| options.contract, | ||
| options.amount, | ||
| signer | ||
| ); | ||
|
|
||
| const tx = await contract.withdraw( | ||
| options.receiver, | ||
| ethers.parseUnits(options.amount, decimals), | ||
| options.zrc20, | ||
| createRevertOptions(options), | ||
| { gasLimit: options.gasLimit } | ||
| ); | ||
| await tx.wait(); | ||
|
|
||
| console.log(`✅ Transaction sent: ${tx.hash}`); | ||
| }; | ||
|
|
||
| export const universalWithdrawCommand = createCommand("universal-withdraw") | ||
| .requiredOption("--amount <number>", "Amount to withdraw") | ||
| .option("--zrc20 <address>", "The address of ZRC-20 to pay fees") | ||
| .action(main); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| import { ethers } from "ethers"; | ||
|
|
||
| import { | ||
| approveZRC20, | ||
| createCommand, | ||
| createRevertOptions, | ||
| getAbi, | ||
| } from "../common"; | ||
|
|
||
| const main = async (options: any) => { | ||
| const provider = new ethers.JsonRpcProvider(options.rpc); | ||
| const signer = new ethers.Wallet(options.privateKey, provider); | ||
|
|
||
| const message = ethers.AbiCoder.defaultAbiCoder().encode( | ||
| options.types, | ||
| options.values | ||
| ); | ||
|
|
||
| const functionSignature = ethers.id(options.function).slice(0, 10); | ||
|
|
||
| const payload = ethers.hexlify(ethers.concat([functionSignature, message])); | ||
|
|
||
| const { abi } = getAbi(options.name); | ||
| const contract = new ethers.Contract(options.contract, abi, signer); | ||
|
|
||
| const { decimals } = await approveZRC20( | ||
| options.zrc20, | ||
| options.contract, | ||
| options.amount, | ||
| signer, | ||
| options.callOptionsGasLimit | ||
| ); | ||
|
|
||
| const tx = await contract.withdrawAndCall( | ||
| options.receiver, | ||
| ethers.parseUnits(options.amount, decimals), | ||
| options.zrc20, | ||
| payload, | ||
| { | ||
| gasLimit: options.callOptionsGasLimit, | ||
| isArbitraryCall: options.callOptionsIsArbitraryCall, | ||
| }, | ||
| createRevertOptions(options), | ||
| { gasLimit: options.gasLimit } | ||
| ); | ||
| await tx.wait(); | ||
|
|
||
| console.log(`✅ Transaction sent: ${tx.hash}`); | ||
| }; | ||
|
|
||
| export const universalWithdrawAndCallCommand = createCommand( | ||
| "universal-withdraw-and-call" | ||
| ) | ||
| .requiredOption("-t, --types <types...>", "Parameter types as JSON string") | ||
| .requiredOption("-v, --values <values...>", "The values of the parameters") | ||
| .option("--call-options-is-arbitrary-call", "Call any function", false) | ||
| .option("--call-options-gas-limit", "The gas limit for the call", "500000") | ||
| .option( | ||
| "--function <function>", | ||
| `Function to call (example: "hello(string)")` | ||
| ) | ||
| .requiredOption("--amount <number>", "Amount to withdraw") | ||
| .option("--zrc20 <address>", "The address of ZRC-20 to pay fees") | ||
| .action(main); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's ensure we don't use
any, these commands would benefit from zod argument validation, this function is an excellent example of why this is necessary because it usestypesandvalues, and since we're not using any validation or refinements, they could differ in length. We could use a commander preAction hook if we think zod is overkill for this small project.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Examples is a different sort of code base where we can't have extra code laying around. I agree with you about additional validation and I think we can accomplish this by exporting required validation rules/functions from the toolkit and just importing them here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose we can add zod directly here.