diff --git a/package.json b/package.json index 5c96072d..aa7417af 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "test-evm-versions": "bash test/test-evm-versions.bash", "build": "rm -rf build && waffle waffle.json", "lint": "solhint 'src/**/*.sol' && eslint 'test/**/*.js'", - "prettier": "prettier --write 'test/**/*.js' && prettier --write 'src/**/*.sol'", + "prettier": "prettier --write 'test/**/*.js' && prettier --write 'src/**/*.sol' && prettier --write 'scripts/**/*.js'", "flatten": "rm -rf build/flattened && waffle flatten waffle.json" }, "repository": { diff --git a/scripts/crosschain-token-execute.js b/scripts/crosschain-token-execute.js new file mode 100644 index 00000000..1ba94006 --- /dev/null +++ b/scripts/crosschain-token-execute.js @@ -0,0 +1,103 @@ +'use strict'; + +require('dotenv').config(); + +const { + Contract, + Wallet, + utils, + providers: { JsonRpcProvider }, +} = require('ethers'); + +const { printLog, printObj } = require('./logging'); + +const { join, resolve } = require('node:path'); + +const { existsSync } = require('node:fs'); + +// these environment variables should be defined in an '.env' file +const contractsPath = resolve(process.env.CONTRACTS_PATH || './build'); +const url = process.env.URL; +const privKey = process.env.PRIVATE_KEY; +const sourceChain = process.env.SOURCE_CHAIN; +const commandIDhex = process.env.COMMAND_ID; +const symbol = process.env.SYMBOL; +const amount = process.env.AMOUNT; +const gatewayAddress = process.env.GATEWAY_ADDRESS; + +printObj({ + 'environment_variables:': { + CONTRACTS_PATH: contractsPath || null, + URL: url || null, + PRIVATE_KEY: privKey || null, + SOURCE_CHAIN: sourceChain || null, + COMMAND_ID: commandIDhex || null, + SYMBOL: symbol || null, + AMOUNT: amount || null, + GATEWAY_ADDRESS: gatewayAddress || null, + }, +}); + +if ( + !( + url && + privKey && + sourceChain && + commandIDhex && + symbol && + amount && + gatewayAddress + ) +) { + console.error( + `One or more of the required environment variable not defined. Make sure to declare these variables in an .env file.`, + ); + process.exit(1); +} + +// the ABIs for the contracts below must be manually downloaded/compiled +const IAxelarGatewayPath = join(contractsPath, 'IAxelarGateway.json'); + +if (!existsSync(IAxelarGatewayPath)) { + console.error( + `Missing IAxelarGateway ABI. Make sure IAxelarGateway.json is present in ${contractsPath}`, + ); + process.exit(1); +} + +const IAxelarGateway = require(IAxelarGatewayPath); + +const provider = new JsonRpcProvider(url); +const wallet = new Wallet(privKey, provider); +const gateway = new Contract(gatewayAddress, IAxelarGateway.abi, wallet); + +const hash = utils.keccak256(utils.arrayify(Buffer.from([]))); +const commandID = utils.arrayify( + commandIDhex.startsWith('0x') ? commandIDhex : '0x' + commandIDhex, +); + +printLog( + `validating contract call with token for chain ${sourceChain} and destination address ${wallet.address}`, +); + +gateway + .validateContractCallAndMint( + commandID, + sourceChain, + wallet.address, + hash, + symbol, + amount, + ) + .then(async (tx) => { + await tx.wait(); + printLog( + `successfully validated contract call with token for chain ${sourceChain} and destination address ${wallet.address} at tx ${tx.hash}`, + ); + printObj({ validated: tx.hash }); + process.exit(0); + }) + .catch((err) => { + console.error(err); + process.exit(1); + }); diff --git a/scripts/crosschain-token-transfer.js b/scripts/crosschain-token-transfer.js new file mode 100644 index 00000000..cb95f78d --- /dev/null +++ b/scripts/crosschain-token-transfer.js @@ -0,0 +1,106 @@ +'use strict'; + +require('dotenv').config(); + +const { + Contract, + Wallet, + providers: { JsonRpcProvider }, +} = require('ethers'); + +const { printLog, printObj } = require('./logging'); + +const { join, resolve } = require('node:path'); + +const { existsSync } = require('node:fs'); + +// these environment variables should be defined in an '.env' file +const contractsPath = resolve(process.env.CONTRACTS_PATH || './build'); +const url = process.env.URL; +const privKey = process.env.PRIVATE_KEY; +const destinationChain = process.env.DESTINATION_CHAIN; +const symbol = process.env.SYMBOL; +const amount = process.env.AMOUNT; +const gatewayAddress = process.env.GATEWAY_ADDRESS; + +printObj({ + 'environment_variables:': { + CONTRACTS_PATH: contractsPath || null, + URL: url || null, + PRIVATE_KEY: privKey || null, + DESTINATION_CHAIN: destinationChain || null, + SYMBOL: symbol || null, + AMOUNT: amount || null, + GATEWAY_ADDRESS: gatewayAddress || null, + }, +}); + +if ( + !(url && privKey && destinationChain && symbol && amount && gatewayAddress) +) { + console.error( + `One or more of the required environment variable not defined. Make sure to declare these variables in an .env file.`, + ); + process.exit(1); +} + +// the ABIs for the contracts below must be manually downloaded/compiled +const IAxelarGatewayPath = join(contractsPath, 'IAxelarGateway.json'); +const IERC20Path = join(contractsPath, 'IERC20.json'); + +if (!(existsSync(IAxelarGatewayPath) && existsSync(IERC20Path))) { + console.error( + `Missing one or more ABIs/bytecodes. Make sure IAxelarGateway.json and IERC20.json are present in ${contractsPath}`, + ); + process.exit(1); +} + +const IAxelarGateway = require(IAxelarGatewayPath); +const IERC20 = require(IERC20Path); + +const provider = new JsonRpcProvider(url); +const wallet = new Wallet(privKey, provider); +const gateway = new Contract(gatewayAddress, IAxelarGateway.abi, wallet); +const payload = Buffer.from([]); +let transactions = {}; + +printLog(`approving amount of ${amount}${symbol}`); + +gateway + .tokenAddresses(symbol) + .then((tokenAddress) => { + const token = new Contract(tokenAddress, IERC20.abi, wallet); + return token.approve(gatewayAddress, amount); + }) + .then(async (tx) => { + await tx.wait(); + printLog( + `successfully approved amount of ${amount}${symbol} at tx ${tx.hash}`, + ); + printLog( + `calling contract with token for chain ${destinationChain} and destination address ${wallet.address}`, + ); + transactions.approve = tx.hash; + }) + .then(() => + gateway.callContractWithToken( + destinationChain, + wallet.address, + payload, + symbol, + amount, + ), + ) + .then(async (tx) => { + await tx.wait(); + printLog( + `successfully called contract with token for chain ${destinationChain} and destination address ${wallet.address} at tx ${tx.hash}`, + ); + transactions.mint = tx.hash; + printObj(transactions); + process.exit(0); + }) + .catch((err) => { + console.error(err); + process.exit(1); + }); diff --git a/scripts/deploy-gateway.js b/scripts/deploy-gateway.js index 14f4503d..5a7fc0b3 100644 --- a/scripts/deploy-gateway.js +++ b/scripts/deploy-gateway.js @@ -9,61 +9,110 @@ const { utils: { defaultAbiCoder, arrayify, computeAddress }, } = require('ethers'); -const { execSync } = require('child_process'); +const { execSync } = require('child_process'); +const { join, resolve } = require('node:path'); + +const { existsSync } = require('node:fs'); + +const { printLog, printObj } = require('./logging'); // these environment variables should be defined in an '.env' file +const contractsPath = resolve(process.env.CONTRACTS_PATH || './build'); const prefix = process.env.PREFIX; const chain = process.env.CHAIN; const url = process.env.URL; const privKey = process.env.PRIVATE_KEY; const adminThreshold = parseInt(process.env.ADMIN_THRESHOLD); -const provider = new JsonRpcProvider(url); -const wallet = new Wallet(privKey, provider); +printObj({ + 'environment_variables:': { + CONTRACTS_PATH: contractsPath || null, + PREFIX: prefix || null, + CHAIN: chain || null, + URL: url || null, + PRIVATE_KEY: privKey || null, + ADMIN_THRESHOLD: adminThreshold || null, + }, +}); + +if (!(prefix && chain && url && privKey && adminThreshold)) { + console.error( + `One or more of the required environment variable not defined. Make sure to declare these variables in an .env file.`, + ); + process.exit(1); +} -const TokenDeployer = require('../build/TokenDeployer.json'); -const AxelarGatewayMultisig = require('../build/AxelarGatewayMultisig.json'); -const AxelarGatewayProxy = require('../build/AxelarGatewayProxy.json'); +// the ABIs for the contracts below must be manually downloaded/compiled +const TokenDeployerPath = join(contractsPath, 'TokenDeployer.json'); +const AxelarGatewayMultisigPath = join( + contractsPath, + 'AxelarGatewayMultisig.json', +); +const AxelarGatewayProxyPath = join(contractsPath, 'AxelarGatewayProxy.json'); + +if ( + !( + existsSync(TokenDeployerPath) && + existsSync(AxelarGatewayMultisigPath) && + existsSync(AxelarGatewayProxyPath) + ) +) { + console.error( + `Missing one or more ABIs/bytecodes. Make sure TokenDeployer.json, AxelarGatewayMultisig.json, and AxelarGatewayProxy.json are present in ${contractsPath}`, + ); + process.exit(1); +} -const printLog = (log) => { console.log(JSON.stringify({ log })) } -const printObj = (obj) => { console.log(JSON.stringify(obj)) } +const TokenDeployer = require(TokenDeployerPath); +const AxelarGatewayMultisig = require(AxelarGatewayMultisigPath); +const AxelarGatewayProxy = require(AxelarGatewayProxyPath); -printLog("retrieving admin addresses") -const adminKeyIDs = JSON.parse(execSync(`${prefix} "axelard q tss external-key-id ${chain} --output json"`)).key_ids; +const provider = new JsonRpcProvider(url); +const wallet = new Wallet(privKey, provider); -const admins = adminKeyIDs.map(adminKeyID => { - const output = execSync(`${prefix} "axelard q tss key ${adminKeyID} --output json"`); +printLog('retrieving admin addresses'); +const adminKeyIDs = JSON.parse( + execSync(`${prefix} "axelard q tss external-key-id ${chain} --output json"`), +).key_ids; +const admins = adminKeyIDs.map((adminKeyID) => { + const output = execSync( + `${prefix} "axelard q tss key ${adminKeyID} --output json"`, + ); const key = JSON.parse(output).ecdsa_key.key; - + return computeAddress(`0x04${key.x}${key.y}`); }); const getAddresses = (role) => { - const keyID = execSync(`${prefix} "axelard q tss key-id ${chain} ${role}"`, { encoding: 'utf-8' }).replaceAll('\n',''); - const output = execSync(`${prefix} "axelard q tss key ${keyID} --output json"`); + const keyID = execSync(`${prefix} "axelard q tss key-id ${chain} ${role}"`, { + encoding: 'utf-8', + }).replaceAll('\n', ''); + const output = execSync( + `${prefix} "axelard q tss key ${keyID} --output json"`, + ); const keys = JSON.parse(output).multisig_key.key; - - const addresses = keys.map(key => { - const x = `${'0'.repeat(64)}${key.x}`.slice(-64); - const y = `${'0'.repeat(64)}${key.y}`.slice(-64); - return computeAddress(`0x04${x}${y}`) - }); + + const addresses = keys.map((key) => { + const x = `${'0'.repeat(64)}${key.x}`.slice(-64); + const y = `${'0'.repeat(64)}${key.y}`.slice(-64); + return computeAddress(`0x04${x}${y}`); + }); return { addresses: addresses, - threshold: JSON.parse(output).multisig_key.threshold - } -} - -printObj({admins: {addresses: admins, threshold: adminThreshold}}); + threshold: JSON.parse(output).multisig_key.threshold, + }; +}; -printLog("retrieving owner addresses") -const { addresses: owners, threshold: ownerThreshold } = getAddresses("master") -printObj({owners: owners, threshold: ownerThreshold }) +printObj({ admins: { addresses: admins, threshold: adminThreshold } }); +printLog('retrieving owner addresses'); +const { addresses: owners, threshold: ownerThreshold } = getAddresses('master'); +printObj({ owners: owners, threshold: ownerThreshold }); -printLog("retrieving operator addresses") -const { addresses: operators, threshold: operatorThreshold } = getAddresses("secondary") -printObj({operators: operators, threshold: operatorThreshold }) +printLog('retrieving operator addresses'); +const { addresses: operators, threshold: operatorThreshold } = + getAddresses('secondary'); +printObj({ operators: operators, threshold: operatorThreshold }); const params = arrayify( defaultAbiCoder.encode( @@ -95,29 +144,29 @@ const axelarGatewayProxyFactory = new ContractFactory( wallet, ); -let contracts = {} +let contracts = {}; -printLog("deploying contracts") +printLog('deploying contracts'); tokenDeployerFactory .deploy() .then((tokenDeployer) => tokenDeployer.deployed()) .then(({ address }) => { printLog(`deployed token deployer at address ${address}`); - contracts.tokenDeployed = address - return axelarGatewayMultisigFactory.deploy(address) + contracts.tokenDeployed = address; + return axelarGatewayMultisigFactory.deploy(address); }) .then((axelarGatewayMultisig) => axelarGatewayMultisig.deployed()) .then(({ address }) => { printLog(`deployed axelar gateway multisig at address ${address}`); - contracts.gatewayMultisig = address - return axelarGatewayProxyFactory.deploy(address, params) + contracts.gatewayMultisig = address; + return axelarGatewayProxyFactory.deploy(address, params); }) .then((axelarGatewayProxy) => axelarGatewayProxy.deployed()) .then(({ address }) => { printLog(`deployed axelar gateway proxy at address ${address}`); - contracts.gatewayProxy = address - printObj(contracts) + contracts.gatewayProxy = address; + printObj(contracts); process.exit(0); }) .catch((err) => { diff --git a/scripts/logging.js b/scripts/logging.js new file mode 100644 index 00000000..84ae6dd3 --- /dev/null +++ b/scripts/logging.js @@ -0,0 +1,11 @@ +'use strict'; + +module.exports = { + printLog(log) { + console.log(JSON.stringify({ log })); + }, + + printObj(obj) { + console.log(JSON.stringify(obj)); + }, +}; diff --git a/src/util/StringAddressUtils.sol b/src/util/StringAddressUtils.sol index 0d8fb45c..c4dc5158 100644 --- a/src/util/StringAddressUtils.sol +++ b/src/util/StringAddressUtils.sol @@ -20,7 +20,6 @@ library StringToAddress { } } - library AddressToString { function toString(address a) internal pure returns (string memory) { bytes memory data = abi.encodePacked(a); diff --git a/src/util/Upgradable.sol b/src/util/Upgradable.sol index 5ccdeebb..0c203edc 100644 --- a/src/util/Upgradable.sol +++ b/src/util/Upgradable.sol @@ -43,7 +43,8 @@ abstract contract Upgradable is IUpgradable { bytes32 newImplementationCodeHash, bytes calldata params ) external override onlyOwner { - if (IUpgradable(newImplementation).contractId() != IUpgradable(this).contractId()) revert InvalidImplementation(); + if (IUpgradable(newImplementation).contractId() != IUpgradable(this).contractId()) + revert InvalidImplementation(); if (newImplementationCodeHash != newImplementation.codehash) revert InvalidCodeHash(); // solhint-disable-next-line avoid-low-level-calls diff --git a/test/Util.js b/test/Util.js index 661f07a5..9bd9fc1b 100644 --- a/test/Util.js +++ b/test/Util.js @@ -23,7 +23,7 @@ describe('UtilTest', () => { address.toLowerCase(), ); }); - + it('should convert string of any format to address', async () => { const address = ownerWallet.address; expect(await utilTest.stringToAddress(address)).to.equal(address);