From 3b853f4f02f3072ffa6a8a3e475db907f71ee4d9 Mon Sep 17 00:00:00 2001 From: David Ansermino Date: Mon, 15 Jun 2020 11:09:35 -0400 Subject: [PATCH] Add cb-sol-cli (#4) Copies cb-sol-cli in, updates the install process to clone chainbridge-solidity and build the ABI files. --- .gitignore | 4 +- .travis.yml | 13 ++- README.md | 2 +- cb-sol-cli/Makefile | 16 ++++ cb-sol-cli/README.md | 39 ++++++++ cb-sol-cli/cmd/admin.js | 115 ++++++++++++++++++++++ cb-sol-cli/cmd/bridge.js | 101 ++++++++++++++++++++ cb-sol-cli/cmd/centrifuge.js | 23 +++++ cb-sol-cli/cmd/deploy.js | 180 +++++++++++++++++++++++++++++++++++ cb-sol-cli/cmd/erc20.js | 104 ++++++++++++++++++++ cb-sol-cli/cmd/erc721.js | 103 ++++++++++++++++++++ cb-sol-cli/cmd/index.js | 15 +++ cb-sol-cli/cmd/utils.js | 45 +++++++++ cb-sol-cli/constants.js | 55 +++++++++++ cb-sol-cli/docs/admin.md | 75 +++++++++++++++ cb-sol-cli/docs/bridge.md | 60 ++++++++++++ cb-sol-cli/docs/deploy.md | 22 +++++ cb-sol-cli/docs/erc20.md | 48 ++++++++++ cb-sol-cli/docs/erc721.md | 52 ++++++++++ cb-sol-cli/index.js | 48 ++++++++++ cb-sol-cli/package-lock.json | 117 +++++++++++++++++++++++ cb-sol-cli/package.json | 26 +++++ ci/ci_cli.sh | 43 +++++++++ 23 files changed, 1303 insertions(+), 3 deletions(-) create mode 100644 cb-sol-cli/Makefile create mode 100644 cb-sol-cli/README.md create mode 100644 cb-sol-cli/cmd/admin.js create mode 100644 cb-sol-cli/cmd/bridge.js create mode 100644 cb-sol-cli/cmd/centrifuge.js create mode 100644 cb-sol-cli/cmd/deploy.js create mode 100644 cb-sol-cli/cmd/erc20.js create mode 100644 cb-sol-cli/cmd/erc721.js create mode 100644 cb-sol-cli/cmd/index.js create mode 100644 cb-sol-cli/cmd/utils.js create mode 100644 cb-sol-cli/constants.js create mode 100644 cb-sol-cli/docs/admin.md create mode 100644 cb-sol-cli/docs/bridge.md create mode 100644 cb-sol-cli/docs/deploy.md create mode 100644 cb-sol-cli/docs/erc20.md create mode 100644 cb-sol-cli/docs/erc721.md create mode 100755 cb-sol-cli/index.js create mode 100644 cb-sol-cli/package-lock.json create mode 100644 cb-sol-cli/package.json create mode 100755 ci/ci_cli.sh diff --git a/.gitignore b/.gitignore index 62c8935..d8b1cc5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -.idea/ \ No newline at end of file +.idea/ +node_modules/ +chainbridge-solidity/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 39ac9f8..c462034 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ # Copyright 2020 ChainSafe Systems # SPDX-License-Identifier: LGPL-3.0-only +cache: npm + branches: only: - master @@ -17,4 +19,13 @@ jobs: script: - cd cfgBuilder - make lint - - go test ./... \ No newline at end of file + - go test ./... + - name: "cb-sol-cli Tests" + language: node_js + node_js: 12 + env: CI=true + script: + - pushd cb-sol-cli + - make install + - cd chainbridge-solidity && SILENT=true make start-ganache + - popd && ./ci/ci_cli.sh \ No newline at end of file diff --git a/README.md b/README.md index 6ab4ae8..967c45c 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,4 @@ Please see the [README](/cfgBuilder/README.md). ## cb-sol-cli -[WIP] \ No newline at end of file +Please see the [README](/cb-sol-cli/README.md). \ No newline at end of file diff --git a/cb-sol-cli/Makefile b/cb-sol-cli/Makefile new file mode 100644 index 0000000..822d1d2 --- /dev/null +++ b/cb-sol-cli/Makefile @@ -0,0 +1,16 @@ +SOL_URL=https://github.com/ChainSafe/chainbridge-solidity + + +fetch-contracts: + @echo " > \033[32mFetching chainbridge-solidity contracts... \033[0m " + git clone ${SOL_URL} && cd chainbridge-solidity && git checkout ${GIT_COMMIT} + +compile: + cd chainbridge-solidity && npm install && npx truffle compile + +install: fetch-contracts compile + @echo " > \033[32mInstalling cb-sol-cli... \033[0m " + npm link . + +clean: + rm -rf chainbridge-solidity/ \ No newline at end of file diff --git a/cb-sol-cli/README.md b/cb-sol-cli/README.md new file mode 100644 index 0000000..8a2d3e1 --- /dev/null +++ b/cb-sol-cli/README.md @@ -0,0 +1,39 @@ +# cb-sol-cli Documentation + +This CLI supports on-chain interactions with components of ChainBridge. + +## Installation + +Installation requires the ABI files from the contracts which will be fetched and built from the chainbridge-solidity repo. +``` +$ make install +``` + +## Usage + +The root command (`cb-sol-cli`) has some options: +``` +--url URL to connect to +--gasLimit Gas limit for transactions +--gasPrice Gas limit for transactions +``` +\ +The keypair used for interactions can be configured with: +``` +--privateKey Private key to use +``` +or +``` +--jsonWallet Encrypted JSON wallet +--jsonWalletPassword Password for encrypted JSON wallet +``` + +There are multiple subcommands provided: + +- [`deploy`](docs/deploy.md): Deploys contracts via RPC +- [`bridge`](docs/bridge.md): Interactions with the bridge contract such as registering resource IDs and handler addresses +- [`admin`](docs/admin.md): Interactions with the bridge contract for administering relayer set, relayer threshold, fees and more. +- [`erc20`](docs/erc20.md): Interactions with ERC20 contracts and handlers +- [`erc721`](docs/erc721.md): Interactions with ERC721 contracts and handler + + diff --git a/cb-sol-cli/cmd/admin.js b/cb-sol-cli/cmd/admin.js new file mode 100644 index 0000000..54699fa --- /dev/null +++ b/cb-sol-cli/cmd/admin.js @@ -0,0 +1,115 @@ +const ethers = require('ethers'); +const constants = require('../constants'); + +const {Command} = require('commander'); +const {setupParentArgs, waitForTx, log} = require("./utils") + +const isRelayerCmd = new Command("is-relayer") + .description("Check if address is relayer") + .option('--relayer ', 'Address to check', constants.relayerAddresses[0]) + .option('--bridge
', 'Bridge contract address', constants.BRIDGE_ADDRESS) + .action(async function (args) { + await setupParentArgs(args, args.parent.parent) + const bridgeInstance = new ethers.Contract(args.bridge, constants.ContractABIs.Bridge.abi, args.wallet); + + let res = await bridgeInstance.isRelayer(args.relayer) + console.log(`[${args._name}] Address ${args.relayer} ${res ? "is" : "is not"} a relayer.`) + }) + +const addRelayerCmd = new Command("add-relayer") + .description("Add a relayer") + .option('--relayer
', 'Address of relayer', constants.relayerAddresses[0]) + .option('--bridge
', 'Bridge contract address', constants.BRIDGE_ADDRESS) + .action(async function (args) { + await setupParentArgs(args, args.parent.parent) + const bridgeInstance = new ethers.Contract(args.bridge, constants.ContractABIs.Bridge.abi, args.wallet); + log(args, `Adding ${args.relayer} as a relayer.`) + let tx = await bridgeInstance.adminAddRelayer(args.relayer) + await waitForTx(args.provider, tx.hash) + }) + +const removeRelayerCmd = new Command("remove-relayer") + .description("Remove a relayer") + .option('--relayer
', 'Address of relayer', constants.relayerAddresses[0]) + .option('--bridge
', 'Bridge contract address', constants.BRIDGE_ADDRESS) + .action(async function (args) { + await setupParentArgs(args, args.parent.parent) + const bridgeInstance = new ethers.Contract(args.bridge, constants.ContractABIs.Bridge.abi, args.wallet); + log(args, `Removing relayer ${args.relayer}.`) + let tx = await bridgeInstance.adminRemoveRelayer(args.relayer) + await waitForTx(args.provider, tx.hash) + }) + +const setThresholdCmd = new Command("set-threshold") + .description("Set relayer threshold") + .option('--bridge
', 'Bridge contract address', constants.BRIDGE_ADDRESS) + .option('--threshold ', 'New relayer threshold', 3) + .action(async function (args) { + await setupParentArgs(args, args.parent.parent) + const bridgeInstance = new ethers.Contract(args.bridge, constants.ContractABIs.Bridge.abi, args.wallet); + log(args, `Setting relayer threshold to ${args.threshold}`) + let tx = await bridgeInstance.adminChangeRelayerThreshold(args.threshold) + await waitForTx(args.provider, tx.hash) + }) + +const pauseTransfersCmd = new Command("pause") + .description("Pause deposits and proposal on the bridge") + .option('--bridge
', 'Bridge contract address', constants.BRIDGE_ADDRESS) + .action(async function (args) { + await setupParentArgs(args, args.parent.parent) + const bridgeInstance = new ethers.Contract(args.bridge, constants.ContractABIs.Bridge.abi, args.wallet); + log(args, `Pausing deposits and proposals`) + let tx = await bridgeInstance.adminPauseTransfers() + await waitForTx(args.provider, tx.hash) + }) + +const unpauseTransfersCmd = new Command("unpause") + .description("Unpause deposits and proposals on the bridge") + .option('--bridge
', 'Bridge contract address', constants.BRIDGE_ADDRESS) + .action(async function (args) { + await setupParentArgs(args, args.parent.parent) + const bridgeInstance = new ethers.Contract(args.bridge, constants.ContractABIs.Bridge.abi, args.wallet); + log(args, `Unpausing deposits and proposals`) + let tx = await bridgeInstance.adminUnpauseTransfers() + await waitForTx(args.provider, tx.hash) + }) + +const changeFeeCmd = new Command("set-fee") + .description("Set a new fee for deposits") + .option('--bridge
', 'Bridge contract address', constants.BRIDGE_ADDRESS) + .option('--fee ', 'New fee (in wei)', 0) + .action(async function (args) { + await setupParentArgs(args, args.parent.parent) + const bridgeInstance = new ethers.Contract(args.bridge, constants.ContractABIs.Bridge.abi, args.wallet); + log(args, `Setting fee to ${args.fee} wei`) + let tx = await bridgeInstance.adminChangeFee(args.fee) + await waitForTx(args.provider, tx.hash) + }) + +const withdrawCmd = new Command("withdraw") + .description("Withdraw funds collected from fees") + .option('--bridge
', 'Bridge contract address', constants.BRIDGE_ADDRESS) + .option('--handler
', 'Handler contract address', constants.ERC20_HANDLER_ADDRESS) + .option('--tokenContract
', 'ERC20 or ERC721 token contract address', constants.ERC20_ADDRESS) + .option('--recipient
', 'Address to withdraw to', constants.relayerAddresses[0]) + .option('--amountOrId ', 'Token ID or amount to withdraw', 1) + .action(async function (args) { + await setupParentArgs(args, args.parent.parent) + const bridgeInstance = new ethers.Contract(args.bridge, constants.ContractABIs.Bridge.abi, args.wallet); + log(args, `Withdrawing tokens (${args.amountOrId}) in contract ${args.tokenContract} to recipient ${args.recipient}`) + let tx = await bridgeInstance.adminWithdraw(args.handler, args.tokenContract, args.recipient, args.amountOrId) + await waitForTx(args.provider, tx.hash) + }) + +const adminCmd = new Command("admin") + +adminCmd.addCommand(isRelayerCmd) +adminCmd.addCommand(addRelayerCmd) +adminCmd.addCommand(removeRelayerCmd) +adminCmd.addCommand(setThresholdCmd) +adminCmd.addCommand(pauseTransfersCmd) +adminCmd.addCommand(unpauseTransfersCmd) +adminCmd.addCommand(changeFeeCmd) +adminCmd.addCommand(withdrawCmd) + +module.exports = adminCmd \ No newline at end of file diff --git a/cb-sol-cli/cmd/bridge.js b/cb-sol-cli/cmd/bridge.js new file mode 100644 index 0000000..1465139 --- /dev/null +++ b/cb-sol-cli/cmd/bridge.js @@ -0,0 +1,101 @@ +const ethers = require('ethers'); +const constants = require('../constants'); + +const {Command} = require('commander'); +const {setupParentArgs, getFunctionBytes, waitForTx, log} = require("./utils") + +const EMPTY_SIG = "0x00000000" + +const registerResourceCmd = new Command("register-resource") + .description("Register a resource ID with a contract address for a handler") + .option('--bridge
', 'Bridge contract address', constants.BRIDGE_ADDRESS) + .option('--handler
', 'Handler address', constants.ERC20_HANDLER_ADDRESS) + .option('--targetContract
', `Contract address to be registered`, constants.ERC20_ADDRESS) + .option('--resourceId
', `Resource ID to be registered`, constants.ERC20_RESOURCEID) + .action(async function (args) { + await setupParentArgs(args, args.parent.parent) + + const bridgeInstance = new ethers.Contract(args.bridge, constants.ContractABIs.Bridge.abi, args.wallet); + log(args,`Registering contract ${args.targetContract} with resource ID ${args.resourceId} on handler ${args.handler}`); + const tx = await bridgeInstance.adminSetResource(args.handler, args.resourceId, args.targetContract, { gasPrice: args.gasPrice, gasLimit: args.gasLimit}); + await waitForTx(args.provider, tx.hash) + }) + +const registerGenericResourceCmd = new Command("register-generic-resource") + .description("Register a resource ID with a generic handler") + .option('--bridge
', 'Bridge contract address', constants.BRIDGE_ADDRESS) + .option('--handler
', 'Handler contract address', constants.GENERIC_HANDLER_ADDRESS) + .option('--targetContract
', `Contract address to be registered`, constants.CENTRIFUGE_ASSET_STORE_ADDRESS) + .option('--resourceId
', `ResourceID to be registered`, constants.GENERIC_RESOURCEID) + .option('--deposit ', "Deposit function signature", EMPTY_SIG) + .option('--execute ', "Execute proposal function signature", EMPTY_SIG) + .option('--hash', "Treat signature inputs as function prototype strings, hash and take the first 4 bytes", false) + .action(async function(args) { + await setupParentArgs(args, args.parent.parent) + + const bridgeInstance = new ethers.Contract(args.bridge, constants.ContractABIs.Bridge.abi, args.wallet); + + if (args.hash) { + args.deposit = getFunctionBytes(args.deposit) + args.execute = getFunctionBytes(args.execute) + } + + log(args,`Registering generic resource ID ${args.resourceId} with contract ${args.targetContract} on handler ${args.handler}`) + const tx = await bridgeInstance.adminSetGenericResource(args.handler, args.resourceId, args.targetContract, args.deposit, args.execute, { gasPrice: args.gasPrice, gasLimit: args.gasLimit}) + await waitForTx(args.provider, tx.hash) + }) + +const setBurnCmd = new Command("set-burn") + .description("Set a token contract as burnable in a handler") + .option('--bridge
', 'Bridge contract address', constants.BRIDGE_ADDRESS) + .option('--handler
', 'ERC20 handler contract address', constants.ERC20_HANDLER_ADDRESS) + .option('--tokenContract
', `Token contract to be registered`, constants.ERC20_ADDRESS) + .action(async function (args) { + await setupParentArgs(args, args.parent.parent) + const bridgeInstance = new ethers.Contract(args.bridge, constants.ContractABIs.Bridge.abi, args.wallet); + + log(args,`Setting contract ${args.tokenContract} as burnable on handler ${args.handler}`); + const tx = await bridgeInstance.adminSetBurnable(args.handler, args.tokenContract, { gasPrice: args.gasPrice, gasLimit: args.gasLimit}); + await waitForTx(args.provider, tx.hash) + }) + +const queryProposalCmd = new Command("query-proposal") + .description("Query a proposal on-chain") + .option('--bridge
', 'Bridge contract address', constants.BRIDGE_ADDRESS) + .option('--depositNonce
', 'Nonce of proposal', 0) + .option('--chainId ', 'Source chain ID of proposal', constants.DEFAULT_SOURCE_ID) + .action(async function (args) { + await setupParentArgs(args, args.parent.parent) + + // Instances + const bridgeInstance = new ethers.Contract(args.bridge, constants.ContractABIs.Bridge.abi, args.wallet); + + const prop = await bridgeInstance.getProposal(args.chainId, args.depositNonce) + log(args, `Source: ${args.chainId} Nonce: ${args.depositNonce}`) + log(args, `Votes: ${prop._yesVotes} Status: ${prop._status}`) + }) + + +const cancelProposalCmd = new Command("cancel-proposal") + .description("Cancel a proposal that has passed the expiry threshold") + .option('--bridge
', 'Bridge contract address', constants.BRIDGE_ADDRESS) + .option('--chainId ', 'Chain ID of proposal to cancel', 0) + .option('--depositNonce ', 'Deposit nonce of proposal to cancel', 0) + .action(async function (args) { + await setupParentArgs(args, args.parent.parent) + const bridgeInstance = new ethers.Contract(args.bridge, constants.ContractABIs.Bridge.abi, args.wallet); + + log(args, `Setting proposal with chain ID ${args.chainId} and deposit nonce ${args.depositNonce} status to 'Cancelled`); + const tx = await bridgeInstance.adminCancelProposal(args.chainId, args.depositNonce); + await waitForTx(args.provider, tx.hash) + }) + +const bridgeCmd = new Command("bridge") + +bridgeCmd.addCommand(registerResourceCmd) +bridgeCmd.addCommand(registerGenericResourceCmd) +bridgeCmd.addCommand(setBurnCmd) +bridgeCmd.addCommand(queryProposalCmd) +bridgeCmd.addCommand(cancelProposalCmd) + +module.exports = bridgeCmd diff --git a/cb-sol-cli/cmd/centrifuge.js b/cb-sol-cli/cmd/centrifuge.js new file mode 100644 index 0000000..e35634d --- /dev/null +++ b/cb-sol-cli/cmd/centrifuge.js @@ -0,0 +1,23 @@ +const ethers = require('ethers'); +const {Command} = require('commander'); + +const {setupParentArgs, log} = require("./utils") + +const constants = require('../constants'); + +const getHashCmd = new Command('getHash') + .description('Returns if a the given hash exists') + .requiredOption('--hash ', 'A hash to lookup', ethers.utils.hexZeroPad("0x", 32)) + .option('--address ', 'Centrifuge asset store contract address', constants.CENTRIFUGE_ASSET_STORE_ADDRESS) + .action(async function (args) { + await setupParentArgs(args, args.parent.parent); + const assetStore = new ethers.Contract(args.address, constants.ContractABIs.CentrifugeAssetStore.abi, args.wallet); + const res = await assetStore._assetsStored(ethers.utils.hexZeroPad(args.hash, 32)); + log(args, `The hash ${args.hash} was ${res ? "found!" : "NOT found!"}`); + }) + +const centCmd = new Command("cent") + +centCmd.addCommand(getHashCmd) + +module.exports = centCmd diff --git a/cb-sol-cli/cmd/deploy.js b/cb-sol-cli/cmd/deploy.js new file mode 100644 index 0000000..c08a0f1 --- /dev/null +++ b/cb-sol-cli/cmd/deploy.js @@ -0,0 +1,180 @@ +const ethers = require('ethers'); +const {Command} = require('commander'); +const constants = require('../constants'); +const {setupParentArgs, splitCommaList} = require("./utils") + +const deployCmd = new Command("deploy") + .description("Deploys contracts via RPC") + .option('--chainId ', 'Chain ID for the instance', constants.DEFAULT_SOURCE_ID) + .option('--relayers ', 'List of initial relayers', splitCommaList, constants.relayerAddresses) + .option('--relayerThreshold ', 'Number of votes required for a proposal to pass', 2) + .option('--fee ', 'Fee to be taken when making a deposit (decimals allowed)', 0) + .option('--expiry ', 'Numer of blocks after which a proposal is considered cancelled', 100) + .option('--all', 'Deploy all contracts') + .option('--bridge', 'Deploy bridge contract') + .option('--erc20Handler', 'Deploy erc20Handler contract') + .option('--erc721Handler', 'Deploy erc721Handler contract') + .option('--genericHandler', 'Deploy genericHandler contract') + .option('--erc20', 'Deploy erc20 contract') + .option('--erc721', 'Deploy erc721 contract') + .option('--centAsset', 'Deploy centrifuge asset contract') + .action(async (args) => { + await setupParentArgs(args, args.parent) + let startBal = await args.provider.getBalance(args.wallet.address) + console.log("Deploying contracts...") + if(args.all) { + await deployBridgeContract(args); + await deployERC20Handler(args); + await deployERC721Handler(args) + await deployGenericHandler(args) + await deployERC20(args) + await deployERC721(args) + await deployCentrifugeAssetStore(args); + } else { + let deployed = false + if (args.bridge) { + await deployBridgeContract(args); + deployed = true + } + if (args.erc20Handler) { + await deployERC20Handler(args); + deployed = true + } + if (args.erc721Handler) { + await deployERC721Handler(args) + deployed = true + } + if (args.genericHandler) { + await deployGenericHandler(args) + deployed = true + } + if (args.erc20) { + await deployERC20(args) + deployed = true + } + if (args.erc721) { + await deployERC721(args) + deployed = true + } + if (args.centAsset) { + await deployCentrifugeAssetStore(args); + deployed = true + } + + if (!deployed) { + throw new Error("must specify --all or specific contracts to deploy") + } + } + + args.cost = startBal.sub((await args.provider.getBalance(args.wallet.address))) + displayLog(args) + }) + + +const displayLog = (args) => { + console.log(` +================================================================ +Url: ${args.url} +Deployer: ${args.wallet.address} +Gas Limit: ${ethers.utils.bigNumberify(args.gasLimit)} +Gas Price: ${ethers.utils.bigNumberify(args.gasPrice)} +Deploy Cost: ${ethers.utils.formatEther(args.cost)} + +Options +======= +Chain Id: ${args.chainId} +Threshold: ${args.relayerThreshold} +Relayers: ${args.relayers} +Bridge Fee: ${args.fee} +Expiry: ${args.expiry} + +Contract Addresses +================================================================ +Bridge: ${args.bridgeContract ? args.bridgeContract : "Not Deployed"} +---------------------------------------------------------------- +Erc20 Handler: ${args.erc20HandlerContract ? args.erc20HandlerContract : "Not Deployed"} +---------------------------------------------------------------- +Erc721 Handler: ${args.erc721HandlerContract? args.erc721HandlerContract : "Not Deployed"} +---------------------------------------------------------------- +Generic Handler: ${args.genericHandlerContract ? args.genericHandlerContract : "Not Deployed"} +---------------------------------------------------------------- +Erc20: ${args.erc20Contract ? args.erc20Contract : "Not Deployed"} +---------------------------------------------------------------- +Erc721: ${args.erc721Contract ? args.erc721Contract : "Not Deployed"} +---------------------------------------------------------------- +Centrifuge Asset: ${args.centrifugeAssetStoreContract ? args.centrifugeAssetStoreContract : "Not Deployed"} +================================================================ + `) +} + + +async function deployBridgeContract(args) { + // Create an instance of a Contract Factory + let factory = new ethers.ContractFactory(constants.ContractABIs.Bridge.abi, constants.ContractABIs.Bridge.bytecode, args.wallet); + + // Deploy + let contract = await factory.deploy( + args.chainId, + args.relayers, + args.relayerThreshold, + ethers.utils.parseEther(args.fee.toString()), + args.expiry, + { gasPrice: args.gasPrice, gasLimit: args.gasLimit} + + ); + await contract.deployed(); + args.bridgeContract = contract.address + console.log("✓ Bridge contract deployed") +} + +async function deployERC20(args) { + const factory = new ethers.ContractFactory(constants.ContractABIs.Erc20Mintable.abi, constants.ContractABIs.Erc20Mintable.bytecode, args.wallet); + const contract = await factory.deploy("", "", { gasPrice: args.gasPrice, gasLimit: args.gasLimit}); + await contract.deployed(); + args.erc20Contract = contract.address + console.log("✓ ERC20 contract deployed") +} + +async function deployERC20Handler(args) { + const factory = new ethers.ContractFactory(constants.ContractABIs.Erc20Handler.abi, constants.ContractABIs.Erc20Handler.bytecode, args.wallet); + + + const contract = await factory.deploy(args.bridgeContract, [], [], [], { gasPrice: args.gasPrice, gasLimit: args.gasLimit}); + await contract.deployed(); + args.erc20HandlerContract = contract.address + console.log("✓ ERC20Handler contract deployed") +} + +async function deployERC721(args) { + const factory = new ethers.ContractFactory(constants.ContractABIs.Erc721Mintable.abi, constants.ContractABIs.Erc721Mintable.bytecode, args.wallet); + const contract = await factory.deploy("", "", "", { gasPrice: args.gasPrice, gasLimit: args.gasLimit}); + await contract.deployed(); + args.erc721Contract = contract.address + console.log("✓ ERC721 contract deployed") +} + +async function deployERC721Handler(args) { + const factory = new ethers.ContractFactory(constants.ContractABIs.Erc721Handler.abi, constants.ContractABIs.Erc721Handler.bytecode, args.wallet); + const contract = await factory.deploy(args.bridgeContract,[],[],[], { gasPrice: args.gasPrice, gasLimit: args.gasLimit}); + await contract.deployed(); + args.erc721HandlerContract = contract.address + console.log("✓ ERC721Handler contract deployed") +} + +async function deployGenericHandler(args) { + const factory = new ethers.ContractFactory(constants.ContractABIs.GenericHandler.abi, constants.ContractABIs.GenericHandler.bytecode, args.wallet) + const contract = await factory.deploy(args.bridgeContract, [], [], [], [], { gasPrice: args.gasPrice, gasLimit: args.gasLimit}) + await contract.deployed(); + args.genericHandlerContract = contract.address + console.log("✓ GenericHandler contract deployed") +} + +async function deployCentrifugeAssetStore(args) { + const factory = new ethers.ContractFactory(constants.ContractABIs.CentrifugeAssetStore.abi, constants.ContractABIs.CentrifugeAssetStore.bytecode, args.wallet); + const contract = await factory.deploy({ gasPrice: args.gasPrice, gasLimit: args.gasLimit}); + await contract.deployed(); + args.centrifugeAssetStoreContract = contract.address + console.log("✓ CentrifugeAssetStore contract deployed") +} + +module.exports = deployCmd \ No newline at end of file diff --git a/cb-sol-cli/cmd/erc20.js b/cb-sol-cli/cmd/erc20.js new file mode 100644 index 0000000..c241aae --- /dev/null +++ b/cb-sol-cli/cmd/erc20.js @@ -0,0 +1,104 @@ +const ethers = require('ethers'); +const constants = require('../constants'); + +const {Command} = require('commander'); +const {setupParentArgs, waitForTx, log} = require("./utils") + +const mintCmd = new Command("mint") + .description("Mints erc20 tokens") + .option('--amount ', 'Amount to mint', 100) + .option('--erc20Address
', 'ERC20 contract address', constants.ERC20_ADDRESS) + .action(async function (args) { + await setupParentArgs(args, args.parent.parent) + const erc20Instance = new ethers.Contract(args.erc20Address, constants.ContractABIs.Erc20Mintable.abi, args.wallet); + log(args, `Minting ${args.amount} tokens to ${args.wallet.address} on contract ${args.erc20Address}`); + const tx = await erc20Instance.mint(args.wallet.address, args.amount); + await waitForTx(args.provider, tx.hash) + }) + +const addMinterCmd = new Command("add-minter") + .description("Add a new minter to the contract") + .option('--erc20Address
', 'ERC20 contract address', constants.ERC20_ADDRESS) + .option('--minter
', 'Minter address', constants.relayerAddresses[1]) + .action(async function(args) { + await setupParentArgs(args, args.parent.parent) + const erc20Instance = new ethers.Contract(args.erc20Address, constants.ContractABIs.Erc20Mintable.abi, args.wallet); + let MINTER_ROLE = await erc20Instance.MINTER_ROLE(); + log(args, `Adding ${args.minter} as a minter on contract ${args.erc20Address}`); + const tx = await erc20Instance.grantRole(MINTER_ROLE, args.minter); + await waitForTx(args.provider, tx.hash) + }) + +const approveCmd = new Command("approve") + .description("Approve tokens for transfer") + .option('--amount ', "Amount to transfer", 1) + .option('--recipient
', 'Destination recipient address', constants.ERC20_HANDLER_ADDRESS) + .option('--erc20Address
', 'ERC20 contract address', constants.ERC20_ADDRESS) + .action(async function (args) { + await setupParentArgs(args, args.parent.parent) + + const erc20Instance = new ethers.Contract(args.erc20Address, constants.ContractABIs.Erc20Mintable.abi, args.wallet); + log(args, `Approving ${args.recipient} to spend ${args.amount} tokens from ${args.wallet.address}!`); + const tx = await erc20Instance.approve(args.recipient, args.amount, { gasPrice: args.gasPrice, gasLimit: args.gasLimit}); + await waitForTx(args.provider, tx.hash) + }) + +const depositCmd = new Command("deposit") + .description("Initiates a bridge transfer") + .option('--amount ', "Amount to transfer", 1) + .option('--dest ', "Destination chain ID", 1) + .option('--recipient
', 'Destination recipient address', constants.relayerAddresses[4]) + .option('--resourceId ', 'ResourceID for transfer', constants.ERC20_RESOURCEID) + .option('--bridge
', 'Bridge contract address', constants.BRIDGE_ADDRESS) + .action(async function (args) { + await setupParentArgs(args, args.parent.parent) + + // Instances + const bridgeInstance = new ethers.Contract(args.bridge, constants.ContractABIs.Bridge.abi, args.wallet); + + const data = '0x' + + args.resourceId.substr(2) + // OriginHandlerAddress (32 bytes) + ethers.utils.hexZeroPad(ethers.utils.hexlify(Number(args.amount)), 32).substr(2) + // Deposit Amount (32 bytes) + ethers.utils.hexZeroPad(ethers.utils.hexlify((args.recipient.length - 2)/2), 32).substr(2) + // len(recipientAddress) (32 bytes) + args.recipient.substr(2); // recipientAddress (?? bytes) + + log(args, `Constructed deposit:`) + log(args, ` Resource Id: ${args.resourceId}`) + log(args, ` Amount: ${args.amount}`) + log(args, ` len(recipient): ${(args.recipient.length - 2)/ 2}`) + log(args, ` Recipient: ${args.recipient}`) + log(args, ` Raw: ${data}`) + log(args, `Creating deposit to initiate transfer!`); + + // Make the deposit + let tx = await bridgeInstance.deposit( + args.dest, // destination chain id + args.resourceId, + data, + { gasPrice: args.gasPrice, gasLimit: args.gasLimit} + ); + + await waitForTx(args.provider, tx.hash) + }) + +const balanceCmd = new Command("balance") + .description("Get the balance for an account") + .option('--address
', 'Address to query', constants.deployerAddress) + .option('--erc20Address
', 'ERC20 contract address', constants.ERC20_ADDRESS) + .action(async function(args) { + await setupParentArgs(args, args.parent.parent) + + const erc20Instance = new ethers.Contract(args.erc20Address, constants.ContractABIs.Erc20Mintable.abi, args.wallet); + const balance = await erc20Instance.balanceOf(args.address) + log(args, `Account ${args.address} has a balance of ${balance}` ) + }) + +const erc20Cmd = new Command("erc20") + +erc20Cmd.addCommand(mintCmd) +erc20Cmd.addCommand(addMinterCmd) +erc20Cmd.addCommand(approveCmd) +erc20Cmd.addCommand(depositCmd) +erc20Cmd.addCommand(balanceCmd) + +module.exports = erc20Cmd \ No newline at end of file diff --git a/cb-sol-cli/cmd/erc721.js b/cb-sol-cli/cmd/erc721.js new file mode 100644 index 0000000..765a9f7 --- /dev/null +++ b/cb-sol-cli/cmd/erc721.js @@ -0,0 +1,103 @@ +const ethers = require('ethers'); +const constants = require('../constants'); + +const {Command} = require('commander'); +const {setupParentArgs, waitForTx, log} = require("./utils") + +const mintCmd = new Command("mint") + .description("Mint tokens") + .option('--erc721Address
', 'ERC721 contract address', constants.ERC721_ADDRESS) + .option('--id ', "Token id", "0x1") + .option('--metadata ', "Metadata (tokenURI) for token", "") + .action(async function (args) { + await setupParentArgs(args, args.parent.parent) + const erc721Instance = new ethers.Contract(args.erc721Address, constants.ContractABIs.Erc721Mintable.abi, args.wallet); + + log(args, `Minting token with id ${args.id} to ${args.wallet.address} on contract ${args.erc721Address}!`); + const tx = await erc721Instance.mint(args.wallet.address, ethers.utils.hexlify(args.id), args.metadata); + await waitForTx(args.provider, tx.hash) + }) + +const ownerCmd = new Command("owner") + .description("Query ownerOf") + .option('--erc721Address
', 'ERC721 contract address', constants.ERC721_ADDRESS) + .option('--id ', "Token id", "0x1") + .action(async function (args) { + await setupParentArgs(args, args.parent.parent) + const erc721Instance = new ethers.Contract(args.erc721Address, constants.ContractABIs.Erc721Mintable.abi, args.wallet); + const owner = await erc721Instance.ownerOf(ethers.utils.hexlify(args.id)) + log(args, `Owner of token ${args.id} is ${owner}`) + }) + +const addMinterCmd = new Command("add-minter") + .description("Add a new minter to the contract") + .option('--erc721Address
', 'ERC721 contract address', constants.ERC721_ADDRESS) + .option('--minter
', 'Minter address', constants.relayerAddresses[1]) + .action(async function(args) { + await setupParentArgs(args, args.parent.parent) + const erc721Instance = new ethers.Contract(args.erc721Address, constants.ContractABIs.Erc721Mintable.abi, args.wallet); + const MINTER_ROLE = await erc721Instance.MINTER_ROLE() + log(args, `Adding ${args.minter} as a minter of ${args.erc721Address}`) + const tx = await erc721Instance.grantRole(MINTER_ROLE, args.minter); + await waitForTx(args.provider, tx.hash) + }) + +const approveCmd = new Command("approve") + .description("Approve tokens for transfer") + .option('--id ', "Token ID to transfer", "0x1") + .option('--recipient
', 'Destination recipient address', constants.ERC721_HANDLER_ADDRESS) + .option('--erc721Address
', 'ERC721 contract address', constants.ERC721_ADDRESS) + .action(async function (args) { + await setupParentArgs(args, args.parent.parent) + const erc721Instance = new ethers.Contract(args.erc721Address, constants.ContractABIs.Erc721Mintable.abi, args.wallet); + + log(args, `Approving ${args.recipient} to spend token ${args.id} from ${args.wallet.address} on contract ${args.erc721Address}!`); + const tx = await erc721Instance.approve(args.recipient, ethers.utils.hexlify(args.id), { gasPrice: args.gasPrice, gasLimit: args.gasLimit}); + await waitForTx(args.provider, tx.hash) + }) + +const depositCmd = new Command("deposit") + .description("Initiates a bridge transfer") + .option('--id ', "ERC721 token id", "0x1") + .option('--dest ', "destination chain", "1") + .option(`--recipient
`, 'Destination recipient address', constants.relayerAddresses[4]) + .option('--resourceId ', 'Resource ID for transfer', constants.ERC721_RESOURCEID) + .option('--bridge
', 'Bridge contract address', constants.BRIDGE_ADDRESS) + .action(async function (args) { + await setupParentArgs(args, args.parent.parent) + + // Instances + const bridgeInstance = new ethers.Contract(args.bridge, constants.ContractABIs.Bridge.abi, args.wallet); + + const data = '0x' + + args.resourceId.substr(2) + // resourceID (32 bytes) for now + ethers.utils.hexZeroPad(ethers.utils.hexlify(args.id), 32).substr(2) + // Deposit Amount (32 bytes) + ethers.utils.hexZeroPad(ethers.utils.hexlify((args.recipient.length - 2)/2), 32).substr(2) + // len(recipientAddress) (32 bytes) + ethers.utils.hexlify(args.recipient).substr(2) // recipientAddress (?? bytes) + + log(args, `Constructed deposit:`) + log(args, ` Resource Id: ${args.resourceId}`) + log(args, ` Token Id: ${args.id}`) + log(args, ` len(recipient): ${(args.recipient.length - 2)/2}`) + log(args, ` Recipient: ${args.recipient}`) + log(args, ` Raw: ${data}`) + log(args, "Creating deposit to initiate transfer!") + + // Perform deposit + const tx = await bridgeInstance.deposit( + args.dest, // destination chain id + args.resourceId, + data, + { gasPrice: args.gasPrice, gasLimit: args.gasLimit}); + await waitForTx(args.provider, tx.hash) + }) + +const erc721Cmd = new Command("erc721") + +erc721Cmd.addCommand(mintCmd) +erc721Cmd.addCommand(ownerCmd) +erc721Cmd.addCommand(addMinterCmd) +erc721Cmd.addCommand(approveCmd) +erc721Cmd.addCommand(depositCmd) + +module.exports = erc721Cmd diff --git a/cb-sol-cli/cmd/index.js b/cb-sol-cli/cmd/index.js new file mode 100644 index 0000000..43ef02a --- /dev/null +++ b/cb-sol-cli/cmd/index.js @@ -0,0 +1,15 @@ +const deploy = require("./deploy"); +const bridge = require("./bridge") +const admin = require("./admin") +const erc20 = require("./erc20"); +const erc721 = require("./erc721"); +const centrifuge = require("./centrifuge"); + +module.exports = { + deploy, + bridge, + admin, + erc20, + erc721, + centrifuge, +} diff --git a/cb-sol-cli/cmd/utils.js b/cb-sol-cli/cmd/utils.js new file mode 100644 index 0000000..5cf82ef --- /dev/null +++ b/cb-sol-cli/cmd/utils.js @@ -0,0 +1,45 @@ +const ethers = require('ethers'); +const fs = require('fs'); + +const setupParentArgs = async (args, parent) => { + args.url= parent.url + args.provider = new ethers.providers.JsonRpcProvider(args.url); + args.gasLimit = ethers.utils.hexlify(Number(parent.gasLimit)) + args.gasPrice = ethers.utils.hexlify(Number(parent.gasPrice)) + if (!parent.jsonWallet) { + args.wallet = new ethers.Wallet(parent.privateKey, args.provider); + } else { + const raw = fs.readFileSync(parent.jsonWallet); + const keyfile = JSON.parse(raw); + args.wallet = await ethers.Wallet.fromEncryptedJson(keyfile, parent.jsonWalletPassword) + } +} + +const splitCommaList = (str) => { + return str.split(",") +} + +const getFunctionBytes = (sig) => { + return ethers.utils.keccak256(ethers.utils.hexlify(ethers.utils.toUtf8Bytes(sig))).substr(0, 10) +} + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +const waitForTx = async (provider, hash) => { + console.log(`Waiting for tx: ${hash}...`) + while (!await provider.getTransactionReceipt(hash)) { + sleep(5000) + } +} + +const log = (args, msg) => console.log(`[${args.parent._name}/${args._name}] ${msg}`) + +module.exports = { + setupParentArgs, + splitCommaList, + getFunctionBytes, + waitForTx, + log +} \ No newline at end of file diff --git a/cb-sol-cli/constants.js b/cb-sol-cli/constants.js new file mode 100644 index 0000000..deaafcf --- /dev/null +++ b/cb-sol-cli/constants.js @@ -0,0 +1,55 @@ +/** + * Copyright 2020 ChainSafe Systems + * SPDX-License-Identifier: LGPL-3.0-only + */ +const ethers = require('ethers'); + +const CONTRACT_PATH = "./chainbridge-solidity/build/contracts" +const ContractABIs = { + Bridge: require(CONTRACT_PATH + "/Bridge.json"), + Erc20Handler: require(CONTRACT_PATH + "/ERC20Handler.json"), + Erc20Mintable: require(CONTRACT_PATH + "/ERC20PresetMinterPauser.json"), + Erc721Handler: require(CONTRACT_PATH + "/ERC721Handler.json"), + Erc721Mintable: require(CONTRACT_PATH + "/ERC721MinterBurnerPauser.json"), + GenericHandler: require(CONTRACT_PATH + "/GenericHandler.json"), + CentrifugeAssetStore: require(CONTRACT_PATH + "/CentrifugeAsset.json") +} + +module.exports.ContractABIs = ContractABIs +// This is just Alice's key. +module.exports.deployerAddress = "0xff93B45308FD417dF303D6515aB04D9e89a750Ca"; +module.exports.deployerPrivKey = "0x000000000000000000000000000000000000000000000000000000616c696365"; + +module.exports.relayerAddresses = [ + "0xff93B45308FD417dF303D6515aB04D9e89a750Ca", // Alice Public Address + "0x8e0a907331554AF72563Bd8D43051C2E64Be5d35", // Bob Public Address + "0x24962717f8fA5BA3b931bACaF9ac03924EB475a0", // Charlie Public Address + "0x148FfB2074A9e59eD58142822b3eB3fcBffb0cd7", // Dave Public Address + "0x4CEEf6139f00F9F4535Ad19640Ff7A0137708485", // Eve Public Address +] + +module.exports.relayerPrivKeys = [ + "0x000000000000000000000000000000000000000000000000000000616c696365", // Alice Private Key + "0x0000000000000000000000000000000000000000000000000000000000626f62", // Bob Private Key + "0x00000000000000000000000000000000000000000000000000636861726c6965", // Charlie Private Key + "0x0000000000000000000000000000000000000000000000000000000064617665", // Dave Private Key + "0x0000000000000000000000000000000000000000000000000000000000657665", // Eve Private Key +] + + +// These are deterministic +module.exports.BRIDGE_ADDRESS = "0x62877dDCd49aD22f5eDfc6ac108e9a4b5D2bD88B"; +module.exports.ERC20_HANDLER_ADDRESS = "0x3167776db165D8eA0f51790CA2bbf44Db5105ADF"; +module.exports.ERC721_HANDLER_ADDRESS = "0x3f709398808af36ADBA86ACC617FeB7F5B7B193E"; +module.exports.GENERIC_HANDLER_ADDRESS = "0x2B6Ab4b880A45a07d83Cf4d664Df4Ab85705Bc07"; +module.exports.ERC20_ADDRESS = "0x21605f71845f372A9ed84253d2D024B7B10999f4"; +module.exports.ERC721_ADDRESS = "0xd7E33e1bbf65dC001A0Eb1552613106CD7e40C31"; +module.exports.CENTRIFUGE_ASSET_STORE_ADDRESS = "0xc279648CE5cAa25B9bA753dAb0Dfef44A069BaF4"; + +module.exports.DEFAULT_SOURCE_ID = 0; +module.exports.DEFAULT_DEST_ID = 1; + +module.exports.ERC20_RESOURCEID = ethers.utils.hexZeroPad((this.ERC20_ADDRESS + ethers.utils.hexlify(this.DEFAULT_SOURCE_ID).substr(2)), 32); +module.exports.ERC721_RESOURCEID = ethers.utils.hexZeroPad((this.ERC721_ADDRESS + ethers.utils.hexlify(this.DEFAULT_SOURCE_ID).substr(2)), 32); +module.exports.GENERIC_RESOURCEID = ethers.utils.hexZeroPad((this.CENTRIFUGE_ASSET_STORE_ADDRESS + ethers.utils.hexlify(this.DEFAULT_SOURCE_ID).substr(2)), 32); + diff --git a/cb-sol-cli/docs/admin.md b/cb-sol-cli/docs/admin.md new file mode 100644 index 0000000..e302e54 --- /dev/null +++ b/cb-sol-cli/docs/admin.md @@ -0,0 +1,75 @@ +# Admin Command + +- [`is-relayer`](#is-relayer) +- [`add-relayer`](#add-relayer) +- [`remove-relayer`](#remove-relayer) +- [`set-threshold`](#set-threshold) +- [`pause`](#pause) +- [`unpause`](#unpause) +- [`set-fee`](#set-fee) +- [`withdraw`](#withdraw) + +## `is-relayer` +Check if an address is registered as a relayer. + +``` +--relayer Address to check +--bridge
Bridge contract address +``` + +## `add-relayer` +Adds a new relayer. + +``` +--relayer
Address of relayer +--bridge
Bridge contract address +``` + +## `remove-relayer` +Removes a relayer. + +``` +--relayer
Address of relayer +--bridge
Bridge contract address +``` + +## `set-threshold` +Sets a new relayer vote threshold. + +``` +--bridge
Bridge contract address +--threshold New relayer threshold +``` + +## `pause` +Pauses deposits and proposals. + +``` +--bridge
Bridge contract address +``` + +## `unpause` +Unpause deposits and proposals. + +``` +--bridge
Bridge contract address +``` + +## `set-fee` +Set a new fee. + +``` +--bridge
Bridge contract address +--fee New fee (in wei) +``` + +## `withdraw` +Withdraw tokens from a handler contract. + +``` +--bridge
Bridge contract address +--handler
Handler contract address +--tokenContract
ERC20 or ERC721 token contract address +--recipient
Address to withdraw to +--amountOrId Token ID or amount to withdraw +``` \ No newline at end of file diff --git a/cb-sol-cli/docs/bridge.md b/cb-sol-cli/docs/bridge.md new file mode 100644 index 0000000..592f526 --- /dev/null +++ b/cb-sol-cli/docs/bridge.md @@ -0,0 +1,60 @@ +# Bridge Command + +- [`register-resource`](#register-resource) +- [`register-generic-resource`](#register-generic-resource) +- [`set-burn`](#set-burn) +- [`query-proposal`](#query-proposal) +- [`cancel-proposal`](#cancel-proposal) + + +## `register-resource` +Register a resource ID with a contract address for a handler. + +``` + --bridge
Bridge contract address + --handler
Handler address + --targetContract
Contract address to be registered + --resourceId
Resource ID to be registered +``` + +## `register-generic-resource` +Register a resource ID with a contract address for a generic handler. + +>Note: The `--hash` flag can be used to avoid computing the function selector ahead of time. + +``` + --bridge
Bridge contract address + --handler
Handler contract address + --targetContract
Contract address to be registered + --resourceId
ResourceID to be registered + --deposit Deposit function signature + --execute Execute proposal function signature + --hash Treat signature inputs as function prototype strings, hash and take the first 4 bytes +``` + +## `set-burn` +Set a token contract as mintable/burnable in a handler. + +``` + --bridge
Bridge contract address + --handler
ERC20 handler contract address + --tokenContract
Token contract to be registered +``` + +## `query-proposal` +Query a proposal on-chain. + +``` + --bridge
Bridge contract address + --depositNonce
Nonce of proposal + --chainId Source chain ID of proposal +``` + +## `cancel-proposal` +Cancels an expired proposal. + +``` + --bridge
Bridge contract address + --chainId Chain ID of proposal to cancel + --depositNonce Deposit nonce of proposal to cancel +``` diff --git a/cb-sol-cli/docs/deploy.md b/cb-sol-cli/docs/deploy.md new file mode 100644 index 0000000..16001b2 --- /dev/null +++ b/cb-sol-cli/docs/deploy.md @@ -0,0 +1,22 @@ +# Deploy Command + +This command can be used to deploy all or some of the contracts required for bridging. + +Selection of contracts can be made by either specifying `--all` or a subset of these: +``` + --bridge Deploy bridge contract + --erc20Handler Deploy erc20Handler contract + --erc721Handler Deploy erc721Handler contract + --genericHandler Deploy genericHandler contract + --erc20 Deploy erc20 contract + --erc721 Deploy erc721 contract + --centAsset Deploy centrifuge asset contract +``` + +If you are deploying the Bridge contract, you may want to specify these options as well: +``` + --chainId Chain ID for the instance + --relayers List of initial relayers + --relayerThreshold Number of votes required for a proposal to pass + --fee Fee to be taken when making a deposit (in Ether) +``` \ No newline at end of file diff --git a/cb-sol-cli/docs/erc20.md b/cb-sol-cli/docs/erc20.md new file mode 100644 index 0000000..350446e --- /dev/null +++ b/cb-sol-cli/docs/erc20.md @@ -0,0 +1,48 @@ +# ERC20 Command + +- [`mint`](#mint) +- [`add-minter`](#add-minter) +- [`approve`](#approve) +- [`deposit`](#deposit) +- [`balance`](#balance) + +## `mint` +Mint tokens on an ERC20 mintable contract. + +``` + --amount Amount to mint + --erc20Address
ERC20 contract address +``` +## `add-minter` +Add a minter to an ERC20 mintable contact + +``` + --erc20Address
ERC20 contract address + --minter
Minter address +``` +## `approve` +Approve tokens in an ERC20 contract for transfer. + +``` + --erc20Address
ERC20 contract address + --minter
Minter address +``` + +## `deposit` +Initiate a transfer of ERC20 tokens. + +``` + --amount Amount to transfer + --dest Destination chain ID + --recipient
Destination recipient address + --resourceId ResourceID for transfer + --bridge
Bridge contract address +``` + +## `balance` +Query balance for an account in an ERC20 contract. + +``` + --address
Address to query + --erc20Address
ERC20 contract address +``` diff --git a/cb-sol-cli/docs/erc721.md b/cb-sol-cli/docs/erc721.md new file mode 100644 index 0000000..4affb9e --- /dev/null +++ b/cb-sol-cli/docs/erc721.md @@ -0,0 +1,52 @@ +# ERC721 Command + +- [`mint`](#mint) +- [`owner`](#owner) +- [`add-minter`](#add-minter) +- [`approve`](#approve) +- [`deposit`](#deposit) + +## `mint` +Mint tokens on an ERC721 mintable contract. + +``` + --erc721Address
ERC721 contract address + --id Token id + --metadata Metadata (tokenURI) for token +``` + +## `owner` +Query ownerOf for a token + +``` + --erc721Address
ERC721 contract address + --id Token ID +``` + +## `add-minter` +Add a minter to an ERC721 mintable contact + +``` + --erc721Address
ERC721 contract address + --minter
Minter address +``` + +## `approve` +Approve token in an ERC721 contract for transfer. + +``` + --id Token ID to transfer + --recipient
Destination recipient address + --erc721Address
ERC721 contract address +``` + +## `deposit` +Initiate a transfer of ERC721 tokens. + +``` + --id ERC721 token id + --dest destination chain + --recipient
Destination recipient address + --resourceId Resource ID for transfer + --bridge
Bridge contract address +``` diff --git a/cb-sol-cli/index.js b/cb-sol-cli/index.js new file mode 100755 index 0000000..54b6202 --- /dev/null +++ b/cb-sol-cli/index.js @@ -0,0 +1,48 @@ +#!/usr/bin/env node + +const {Command} = require('commander'); +const program = new Command(); + +// Comands +const { + deploy, + bridge, + admin, + erc20, + erc721, + centrifuge, +} = require('./cmd/index'); +const constants = require('./constants'); + + +program.option('--url ', 'URL to connect to', "http://localhost:8545"); +program.option('--privateKey ', 'Private key to use', constants.deployerPrivKey); +program.option('--jsonWallet ', '(Optional) Encrypted JSON wallet'); +program.option('--jsonWalletPassword ', '(Optional) Password for encrypted JSON wallet'); +program.option('--gasLimit ', "Gas limit for transactions", "8000000") +program.option('--gasPrice ', "Gas limit for transactions", "20000000") + +program.addCommand(deploy) +program.addCommand(bridge) +program.addCommand(admin) +program.addCommand(erc20) +program.addCommand(erc721) +program.addCommand(centrifuge) + +program.allowUnknownOption(false); + +const run = async () => { + try { + await program.parseAsync(process.argv); + } catch (e) { + console.log({ e }); + process.exit(1) + } +} + + +if (process.argv && process.argv.length <= 2) { + program.help(); +} else { + run() +} \ No newline at end of file diff --git a/cb-sol-cli/package-lock.json b/cb-sol-cli/package-lock.json new file mode 100644 index 0000000..26605d1 --- /dev/null +++ b/cb-sol-cli/package-lock.json @@ -0,0 +1,117 @@ +{ + "name": "chainbridge-solidity-cli", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=" + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "commander": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.0.0.tgz", + "integrity": "sha512-JrDGPAKjMGSP1G0DUoaceEJ3DZgAfr/q6X7FVk4+U5KxUSKviYGM2k6zWkfyyBHy5rAtzgYJFa1ro2O9PtoxwQ==" + }, + "elliptic": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", + "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "ethers": { + "version": "4.0.46", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.46.tgz", + "integrity": "sha512-/dPMzzpInhtiip4hKFvsDiJKeRk64IhyA+Po7CtNXneQFSOCYXg8eBFt+jXbxUQyApgWnWOtYxWdfn9+CvvxDA==", + "requires": { + "aes-js": "3.0.0", + "bn.js": "^4.4.0", + "elliptic": "6.5.2", + "hash.js": "1.1.3", + "js-sha3": "0.5.7", + "scrypt-js": "2.0.4", + "setimmediate": "1.0.4", + "uuid": "2.0.1", + "xmlhttprequest": "1.8.0" + } + }, + "hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=" + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, + "scrypt-js": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.4.tgz", + "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==" + }, + "setimmediate": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", + "integrity": "sha1-IOgd5iLUoCWIzgyNqJc8vPHTE48=" + }, + "uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w=" + }, + "xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" + } + } +} diff --git a/cb-sol-cli/package.json b/cb-sol-cli/package.json new file mode 100644 index 0000000..d9d5803 --- /dev/null +++ b/cb-sol-cli/package.json @@ -0,0 +1,26 @@ +{ + "name": "chainbridge-solidity-cli", + "version": "0.0.1", + "description": "Basic ethereum interactions for ChainBridge", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ChainSafe/chainbridge-solidity.git" + }, + "author": "ChainSafe Systems", + "license": "LGPL-3.0", + "bugs": { + "url": "https://github.com/ChainSafe/chainbridge-deploy/issues" + }, + "homepage": "https://github.com/ChainSafe/chainbridge-deploy#readme", + "bin": { + "cb-sol-cli": "./index.js" + }, + "dependencies": { + "commander": "^5.0.0", + "ethers": "^4.0.46" + } +} diff --git a/ci/ci_cli.sh b/ci/ci_cli.sh new file mode 100755 index 0000000..788c4f4 --- /dev/null +++ b/ci/ci_cli.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Copyright 2020 ChainSafe Systems +# SPDX-License-Identifier: LGPL-3.0-only + +ERC721_HANDLER="0x3f709398808af36ADBA86ACC617FeB7F5B7B193E" +ERC721_RESOURCE_ID="0x0000000000000000000000d7E33e1bbf65dC001A0Eb1552613106CD7e40C3100" +ERC721_CONTRACT="0xd7E33e1bbf65dC001A0Eb1552613106CD7e40C31" + +GAS_LIMIT=6721975 +GAS_PRICE=20000000000 + +NEW_RELAYER="0x8cED5ad0d8dA4Ec211C17355Ed3DBFEC4Cf0E5b9" + +set -eux + +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE deploy --all + +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE erc20 mint +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE erc20 add-minter +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE bridge register-resource +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE bridge set-burn +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE erc20 approve +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE erc20 deposit +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE erc20 balance + +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE erc721 mint --id 0x1 +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE erc721 add-minter +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE bridge register-resource --handler $ERC721_HANDLER --resourceId $ERC721_RESOURCE_ID --targetContract $ERC721_CONTRACT +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE erc721 approve --id 0x1 +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE bridge set-burn --handler $ERC721_HANDLER --tokenContract $ERC721_CONTRACT +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE erc721 deposit --id 0x1 + +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE bridge register-generic-resource --execute "store(bytes32)" --hash +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE cent getHash + +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE admin is-relayer +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE admin add-relayer --relayer $NEW_RELAYER +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE admin remove-relayer --relayer $NEW_RELAYER +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE admin set-threshold --threshold 3 +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE admin pause +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE admin unpause +cb-sol-cli --gasLimit $GAS_LIMIT --gasPrice $GAS_PRICE admin set-fee --fee 1 +