diff --git a/.eslintrc.js b/.eslintrc.js index 94babbf4..611893d8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -10,12 +10,21 @@ module.exports = { mocha: true, node: true, }, - parser: "@typescript-eslint/parser", - plugins: ["@typescript-eslint", "simple-import-sort", "prettier"], extends: ["plugin:prettier/recommended"], + parser: "@typescript-eslint/parser", parserOptions: { ecmaVersion: 12, }, + plugins: ["@typescript-eslint", "prettier", "simple-import-sort", "sort-keys-fix", "typescript-sort-keys"], + rules: { + "@typescript-eslint/sort-type-union-intersection-members": "error", + camelcase: "off", + "simple-import-sort/exports": "error", + "simple-import-sort/imports": "error", + "sort-keys-fix/sort-keys-fix": "error", + "typescript-sort-keys/interface": "error", + "typescript-sort-keys/string-enum": "error", + }, settings: { "import/parsers": { "@typescript-eslint/parser": [".js", ".jsx", ".ts", ".tsx", ".d.ts"], @@ -29,9 +38,4 @@ module.exports = { }, }, }, - rules: { - camelcase: "off", - "simple-import-sort/imports": "error", - "simple-import-sort/exports": "error", - }, }; diff --git a/package.json b/package.json index 96778948..aacf17d8 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,8 @@ "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-promise": "^6.0.0", "eslint-plugin-simple-import-sort": "7.0.0", + "eslint-plugin-sort-keys-fix": "1.1.2", + "eslint-plugin-typescript-sort-keys": "2.1.0", "ethereum-waffle": "^3.4.4", "ethereumjs-utils": "^5.2.5", "ethers": "5.6.8", diff --git a/packages/addresses/src/addresses.helpers.ts b/packages/addresses/src/addresses.helpers.ts index bb305fce..76e5a023 100644 --- a/packages/addresses/src/addresses.helpers.ts +++ b/packages/addresses/src/addresses.helpers.ts @@ -9,12 +9,18 @@ export type ZetaAddress = | "connector" | "crossChainCounter" | "crossChainNft" + | "dai" | "multiChainSwap" | "multiChainSwapZetaConnector" | "multiChainValue" | "tss" | "tssUpdater" | "uniswapV2Router02" + | "uniswapV3NftManager" + | "uniswapV3Quoter" + | "uniswapV3Router" + | "usdc" + | "weth9" | "zetaToken"; type NetworkAddresses = Record; @@ -22,12 +28,18 @@ const zetaAddresses: Record = { connector: true, crossChainCounter: true, crossChainNft: true, + dai: true, multiChainSwap: true, multiChainSwapZetaConnector: true, multiChainValue: true, tss: true, tssUpdater: true, uniswapV2Router02: true, + uniswapV3NftManager: true, + uniswapV3Quoter: true, + uniswapV3Router: true, + usdc: true, + weth9: true, zetaToken: true, }; @@ -37,7 +49,7 @@ export const isZetaAddress = (a: string | undefined): a is ZetaAddress => Boolea * @description Localnet */ -export type LocalNetworkName = "hardhat" | "eth-localnet" | "bsc-localnet" | "polygon-localnet"; +export type LocalNetworkName = "bsc-localnet" | "eth-localnet" | "hardhat" | "polygon-localnet"; export type ZetaLocalNetworkName = "troy"; type LocalnetAddressGroup = Record; export const isLocalNetworkName = (networkName: string): networkName is LocalNetworkName => @@ -92,20 +104,20 @@ const getMainnetList: () => Record * @description Shared */ -export type NetworkName = LocalNetworkName | TestnetNetworkName | MainnetNetworkName; -export type ZetaNetworkName = ZetaLocalNetworkName | ZetaTestnetNetworkName | ZetaMainnetNetworkName; +export type NetworkName = LocalNetworkName | MainnetNetworkName | TestnetNetworkName; +export type ZetaNetworkName = ZetaLocalNetworkName | ZetaMainnetNetworkName | ZetaTestnetNetworkName; export const getChainId = (networkName: NetworkName) => { const chainIds: Record = { + "bsc-localnet": 97, "bsc-testnet": 97, + "eth-localnet": 5, "eth-mainnet": 1, - "polygon-mumbai": 80001, goerli: 5, - ropsten: 3, hardhat: 1337, - "eth-localnet": 5, - "bsc-localnet": 97, "polygon-localnet": 80001, + "polygon-mumbai": 80001, + ropsten: 3, }; return chainIds[networkName]; @@ -123,15 +135,15 @@ export const getScanVariable = ({ customNetworkName }: { customNetworkName?: str dotenv.config(); const v = { + "bsc-localnet": "", "bsc-testnet": process.env.BSCSCAN_API_KEY || "", + "eth-localnet": "", "eth-mainnet": process.env.ETHERSCAN_API_KEY || "", - "polygon-mumbai": process.env.POLYGONSCAN_API_KEY || "", goerli: process.env.ETHERSCAN_API_KEY || "", - ropsten: process.env.ETHERSCAN_API_KEY || "", hardhat: "", - "eth-localnet": "", - "bsc-localnet": "", "polygon-localnet": "", + "polygon-mumbai": process.env.POLYGONSCAN_API_KEY || "", + ropsten: process.env.ETHERSCAN_API_KEY || "", }; return v[networkName]; @@ -143,15 +155,15 @@ export const getExplorerUrl = ({ customNetworkName }: { customNetworkName?: stri dotenv.config(); const v = { + "bsc-localnet": "", "bsc-testnet": "https://testnet.bscscan.com/", + "eth-localnet": "", "eth-mainnet": "https://etherscan.io/", - "polygon-mumbai": "https://mumbai.polygonscan.com/", goerli: "https://goerli.etherscan.io/", - ropsten: "https://ropsten.etherscan.io/", hardhat: "", - "eth-localnet": "", - "bsc-localnet": "", "polygon-localnet": "", + "polygon-mumbai": "https://mumbai.polygonscan.com/", + ropsten: "https://ropsten.etherscan.io/", }; return v[networkName]; @@ -317,9 +329,9 @@ export const addNewNetwork = (newNetworkName: string, addTo: ZetaNetworkName[]) const orderedFileNetworks = Object.keys(fileNetworks) .sort() .reduce((obj, key) => { - obj[key as MainnetNetworkName | TestnetNetworkName | LocalNetworkName] = fileNetworks[key]; + obj[key as LocalNetworkName | MainnetNetworkName | TestnetNetworkName] = fileNetworks[key]; return obj; - }, {} as MainnetAddressGroup & TestnetAddressGroup & LocalnetAddressGroup); + }, {} as LocalnetAddressGroup & MainnetAddressGroup & TestnetAddressGroup); writeFileSync(addressesFilePath, JSON.stringify(orderedFileNetworks, null, 2)); }); diff --git a/packages/addresses/src/addresses/addresses.athens.json b/packages/addresses/src/addresses/addresses.athens.json index 60a19291..d5e14b85 100644 --- a/packages/addresses/src/addresses/addresses.athens.json +++ b/packages/addresses/src/addresses/addresses.athens.json @@ -3,42 +3,66 @@ "connector": "0x202aed942eb71203741eced2913a29C695a7c7F6", "crossChainCounter": "", "crossChainNft": "0xa9016FB99846314E0f96f657E5271cFD7919a244", + "dai": "", + "multiChainSwap": "0xD4fAa6BAbBb97E0BDF755D396af48CB66eBa736c", "multiChainValue": "0x8f56b3e5b0D4bC33E7E52eC55271e73ece6024c6", "tss": "0x61141bce5352fc9b5ff648468676e356518d86ab", "tssUpdater": "0x7274d1d5dddef36aac53dd45b93487ce01ef0a55", "uniswapV2Router02": "0x9Ac64Cc6e4415144C455BD8E4837Fea55603e5c3", - "zetaToken": "0xad7d0795ffdf21f20c89240D540487B1445c7D03", - "multiChainSwap": "0xD4fAa6BAbBb97E0BDF755D396af48CB66eBa736c" + "uniswapV3NftManager": "", + "uniswapV3Quoter": "", + "uniswapV3Router": "", + "usdc": "", + "weth9": "", + "zetaToken": "0xad7d0795ffdf21f20c89240D540487B1445c7D03" }, "goerli": { "connector": "0x0B16Fc7e2D627d82255ba21e53Cf6c11B5186046", "crossChainCounter": "", "crossChainNft": "0xe08f1d23a68231543a595391D82c39BbaFc22470", + "dai": "", + "multiChainSwap": "0xDB6D960642ea523549Ad108F495c89cfE5E51668", "multiChainValue": "0x0EaA929a2874183e2324Bb2745F812c942b00F60", "tss": "0x61141bce5352fc9b5ff648468676e356518d86ab", "tssUpdater": "0x7274d1d5dddef36aac53dd45b93487ce01ef0a55", "uniswapV2Router02": "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", - "zetaToken": "0x011a76081989aDA18d0a16bf3fee2C6c3BD07B07", - "multiChainSwap": "0xDB6D960642ea523549Ad108F495c89cfE5E51668" + "uniswapV3NftManager": "", + "uniswapV3Quoter": "", + "uniswapV3Router": "", + "usdc": "", + "weth9": "", + "zetaToken": "0x011a76081989aDA18d0a16bf3fee2C6c3BD07B07" }, "polygon-mumbai": { "connector": "0xa5467B39Ad9f51DEb6b30Abfa9828531dCBA99A9", "crossChainCounter": "", "crossChainNft": "", + "dai": "", "multiChainValue": "0xb3021F5603291895b0Df5fC6276cda33a0B90fa6", "tss": "0x61141bce5352fc9b5ff648468676e356518d86ab", "tssUpdater": "0x7274d1d5dddef36aac53dd45b93487ce01ef0a55", "uniswapV2Router02": "", + "uniswapV3NftManager": "", + "uniswapV3Quoter": "", + "uniswapV3Router": "", + "usdc": "", + "weth9": "", "zetaToken": "0x84383be9B3Eda50ABD5899936D4963505d449de2" }, "ropsten": { "connector": "0x91Ea4f79D39DA890B03E965111953d0494936072", "crossChainCounter": "", "crossChainNft": "", + "dai": "", "multiChainValue": "0x4740f4051eA6D896C694303228D86Ba3141065ca", "tss": "0x61141Bce5352fC9b5Ff648468676e356518D86AB", "tssUpdater": "0x7274d1d5dddef36aac53dd45b93487ce01ef0a55", "uniswapV2Router02": "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", + "uniswapV3NftManager": "", + "uniswapV3Quoter": "", + "uniswapV3Router": "", + "usdc": "", + "weth9": "", "zetaToken": "0x3357A3bd90A001282bA2152D6c0a2372c8E0e28E" } -} \ No newline at end of file +} diff --git a/packages/addresses/src/addresses/addresses.mainnet.json b/packages/addresses/src/addresses/addresses.mainnet.json index 685644da..74a87cbd 100644 --- a/packages/addresses/src/addresses/addresses.mainnet.json +++ b/packages/addresses/src/addresses/addresses.mainnet.json @@ -3,10 +3,16 @@ "connector": "", "crossChainCounter": "", "crossChainNft": "", + "dai": "0x6b175474e89094c44da98b954eedeac495271d0f", "multiChainValue": "", "tss": "", "tssUpdater": "", "uniswapV2Router02": "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", + "uniswapV3NftManager": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88", + "uniswapV3Quoter": "0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6", + "uniswapV3Router": "0xE592427A0AEce92De3Edee1F18E0157C05861564", + "usdc": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "weth9": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "zetaToken": "" } -} \ No newline at end of file +} diff --git a/packages/addresses/src/addresses/addresses.troy.json b/packages/addresses/src/addresses/addresses.troy.json index 483347af..626a9f4c 100644 --- a/packages/addresses/src/addresses/addresses.troy.json +++ b/packages/addresses/src/addresses/addresses.troy.json @@ -3,41 +3,65 @@ "connector": "0xB06c856C8eaBd1d8321b687E188204C1018BC4E5", "crossChainCounter": "", "crossChainNft": "", + "dai": "", + "multiChainSwap": "0xddE78e6202518FF4936b5302cC2891ec180E8bFf", "multiChainValue": "", "tss": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "tssUpdater": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "uniswapV2Router02": "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", - "zetaToken": "0xddE78e6202518FF4936b5302cC2891ec180E8bFf", - "multiChainSwap": "0xddE78e6202518FF4936b5302cC2891ec180E8bFf" + "uniswapV3NftManager": "", + "uniswapV3Quoter": "", + "uniswapV3Router": "", + "usdc": "", + "weth9": "", + "zetaToken": "0xddE78e6202518FF4936b5302cC2891ec180E8bFf" }, "eth-localnet": { "connector": "0xD7db1F9D0279876a9dcDb92e06057E0818b9B34b", "crossChainCounter": "", "crossChainNft": "", + "dai": "", "multiChainValue": "", "tss": "0x8C001e2204000D8ED10cAcf0F68106b68e036A26", "tssUpdater": "0xfBec4048e09749515110c89D881EcDcA4460D377", "uniswapV2Router02": "", + "uniswapV3NftManager": "", + "uniswapV3Quoter": "", + "uniswapV3Router": "", + "usdc": "", + "weth9": "", "zetaToken": "0xaEF27C0E302005dB3b442edF762EB070Dc2DB9b5" }, "bsc-localnet": { "connector": "0x960bd6A9d8424455953a68c17Be7f56e0cf83A9E", "crossChainCounter": "", "crossChainNft": "", + "dai": "", "multiChainValue": "", "tss": "0x8C001e2204000D8ED10cAcf0F68106b68e036A26", "tssUpdater": "0x12Dc2227AA4b98b00Ff8148fDA9Ef750929B0F68", "uniswapV2Router02": "", + "uniswapV3NftManager": "", + "uniswapV3Quoter": "", + "uniswapV3Router": "", + "usdc": "", + "weth9": "", "zetaToken": "0x0CF6e5aA211A4b7da5d04e93dC40Ee18202a5f84" }, "polygon-localnet": { "connector": "0xa16BD8468d10d28E6c16F03798EEb710fc84F616", "crossChainCounter": "", "crossChainNft": "", + "dai": "", "multiChainValue": "", "tss": "0x8C001e2204000D8ED10cAcf0F68106b68e036A26", "tssUpdater": "0x78C18E214174A08Fc32e174fb203Aba05B016789", "uniswapV2Router02": "", + "uniswapV3NftManager": "", + "uniswapV3Quoter": "", + "uniswapV3Router": "", + "usdc": "", + "weth9": "", "zetaToken": "0xD443E93f689a7E1d517Fef799b1d2Fd2FA536d3A" } -} \ No newline at end of file +} diff --git a/packages/addresses/src/networks.ts b/packages/addresses/src/networks.ts index 2d231153..1810f8ae 100644 --- a/packages/addresses/src/networks.ts +++ b/packages/addresses/src/networks.ts @@ -1,55 +1,55 @@ import type { NetworksUserConfig } from "hardhat/types"; export const getHardhatConfigNetworks = (PRIVATE_KEYS: string[]): NetworksUserConfig => ({ - hardhat: { - chainId: 1337, - forking: { - url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`, - blockNumber: 14672712, - }, + "bsc-localnet": { + gas: 5000000, + gasPrice: 80000000000, + url: "http://localhost:8120", + }, + "bsc-testnet": { + accounts: PRIVATE_KEYS, + gas: 5000000, + gasPrice: 80000000000, + url: `https://data-seed-prebsc-2-s3.binance.org:8545`, + }, + "eth-localnet": { + gas: 2100000, + gasPrice: 80000000000, + url: "http://localhost:8100", }, "eth-mainnet": { - url: "https://api.mycryptoapi.com/eth", accounts: PRIVATE_KEYS, + url: "https://api.mycryptoapi.com/eth", }, goerli: { - url: "https://rpc.goerli.mudit.blog", accounts: PRIVATE_KEYS, gas: 2100000, gasPrice: 8000000000, + url: "https://rpc.goerli.mudit.blog", }, - "bsc-testnet": { - url: `https://data-seed-prebsc-2-s3.binance.org:8545`, - accounts: PRIVATE_KEYS, + hardhat: { + chainId: 1337, + forking: { + blockNumber: 14672712, + url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`, + }, + }, + "polygon-localnet": { gas: 5000000, gasPrice: 80000000000, + url: "http://localhost:8140", }, "polygon-mumbai": { - url: "https://polygon-mumbai.chainstacklabs.com", accounts: PRIVATE_KEYS, gas: 5000000, gasPrice: 80000000000, + url: "https://polygon-mumbai.chainstacklabs.com", }, ropsten: { - url: "https://ropsten.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161", accounts: PRIVATE_KEYS, gas: 9000000, gasPrice: 80000000000, - }, - "eth-localnet": { - url: "http://localhost:8100", - gas: 2100000, - gasPrice: 80000000000, - }, - "bsc-localnet": { - url: "http://localhost:8120", - gas: 5000000, - gasPrice: 80000000000, - }, - "polygon-localnet": { - url: "http://localhost:8140", - gas: 5000000, - gasPrice: 80000000000, + url: "https://ropsten.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161", }, }); diff --git a/packages/example-contracts/.eslintrc.js b/packages/example-contracts/.eslintrc.js index 4f9ad61e..77b8f69a 100644 --- a/packages/example-contracts/.eslintrc.js +++ b/packages/example-contracts/.eslintrc.js @@ -3,13 +3,16 @@ const path = require("path"); const OFF = 0; module.exports = { - extends: ["../../.eslintrc.js"], env: { browser: false, es2021: true, mocha: true, node: true, }, + extends: ["../../.eslintrc.js"], + rules: { + "no-console": OFF, + }, settings: { "import/resolver": { typescript: { @@ -17,7 +20,4 @@ module.exports = { }, }, }, - rules: { - "no-console": OFF, - }, }; diff --git a/packages/example-contracts/contracts/multi-chain-swap/MultiChainSwap.base.sol b/packages/example-contracts/contracts/multi-chain-swap/MultiChainSwap.base.sol index da56062f..bbd6a9b3 100644 --- a/packages/example-contracts/contracts/multi-chain-swap/MultiChainSwap.base.sol +++ b/packages/example-contracts/contracts/multi-chain-swap/MultiChainSwap.base.sol @@ -52,13 +52,13 @@ contract MultiChainSwapBase is ZetaInteractor, ZetaReceiver, MultiChainSwapError ); constructor( - address _zetaConnector, - address _zetaTokenInput, - address _uniswapV2Router - ) ZetaInteractor(_zetaConnector) { - zetaToken = _zetaTokenInput; - uniswapV2RouterAddress = _uniswapV2Router; - uniswapV2Router = IUniswapV2Router02(_uniswapV2Router); + address zetaConnector_, + address zetaToken_, + address uniswapV2Router_ + ) ZetaInteractor(zetaConnector_) { + zetaToken = zetaToken_; + uniswapV2RouterAddress = uniswapV2Router_; + uniswapV2Router = IUniswapV2Router02(uniswapV2Router_); wETH = uniswapV2Router.WETH(); } @@ -74,7 +74,7 @@ contract MultiChainSwapBase is ZetaInteractor, ZetaReceiver, MultiChainSwapError uint256 destinationChainId, uint256 crossChaindestinationGasLimit ) external payable { - if (!isValidChainId(destinationChainId)) revert InvalidDestinationChainId(); + if (!_isValidChainId(destinationChainId)) revert InvalidDestinationChainId(); if (msg.value == 0) revert ValueShouldBeGreaterThanZero(); if ( @@ -140,8 +140,7 @@ contract MultiChainSwapBase is ZetaInteractor, ZetaReceiver, MultiChainSwapError uint256 destinationChainId, uint256 crossChaindestinationGasLimit ) external { - if (keccak256(interactorsByChainId[destinationChainId]) == keccak256(new bytes(0))) - revert InvalidDestinationChainId(); + if (!_isValidChainId(destinationChainId)) revert InvalidDestinationChainId(); if (sourceInputToken == address(0)) revert MissingSourceInputTokenAddress(); if ( diff --git a/packages/example-contracts/contracts/multi-chain-value/MultiChainValue.sol b/packages/example-contracts/contracts/multi-chain-value/MultiChainValue.sol index 2abf32ee..e7ee6c7d 100644 --- a/packages/example-contracts/contracts/multi-chain-value/MultiChainValue.sol +++ b/packages/example-contracts/contracts/multi-chain-value/MultiChainValue.sol @@ -12,9 +12,9 @@ contract MultiChainValue is Ownable { mapping(uint256 => bool) public availableChainIds; - constructor(address zetaConnector_, address zetaTokenInput_) { + constructor(address zetaConnector_, address zetaToken_) { zetaConnector = zetaConnector_; - zetaToken = zetaTokenInput_; + zetaToken = zetaToken_; connector = ZetaConnector(zetaConnector_); } diff --git a/packages/example-contracts/hardhat.config.ts b/packages/example-contracts/hardhat.config.ts index 8008bae8..c45fbd42 100644 --- a/packages/example-contracts/hardhat.config.ts +++ b/packages/example-contracts/hardhat.config.ts @@ -14,6 +14,16 @@ dotenv.config(); const PRIVATE_KEYS = process.env.PRIVATE_KEY !== undefined ? [`0x${process.env.PRIVATE_KEY}`] : []; const config: HardhatUserConfig = { + etherscan: { + ...getHardhatConfigScanners(), + }, + gasReporter: { + currency: "USD", + enabled: process.env.REPORT_GAS !== undefined, + }, + networks: { + ...getHardhatConfigNetworks(PRIVATE_KEYS), + }, solidity: { compilers: [{ version: "0.6.6" /** For uniswap v2 */ }, { version: "0.8.7" }], settings: { @@ -27,16 +37,6 @@ const config: HardhatUserConfig = { }, }, }, - etherscan: { - ...getHardhatConfigScanners(), - }, - networks: { - ...getHardhatConfigNetworks(PRIVATE_KEYS), - }, - gasReporter: { - enabled: process.env.REPORT_GAS !== undefined, - currency: "USD", - }, }; export default config; diff --git a/packages/example-contracts/lib/cross-chain-counter/CrossChainCounter.helpers.ts b/packages/example-contracts/lib/cross-chain-counter/CrossChainCounter.helpers.ts index 87b66381..804867ed 100644 --- a/packages/example-contracts/lib/cross-chain-counter/CrossChainCounter.helpers.ts +++ b/packages/example-contracts/lib/cross-chain-counter/CrossChainCounter.helpers.ts @@ -44,7 +44,7 @@ export const getCrossChainCounter = async (existingContractAddress?: string) => } console.log("Deploying CrossChainCounter"); - const crossChainCounterContract = (await Factory.deploy(_networkVariables.CONNECTOR_ADDRESS)) as CrossChainCounter; + const crossChainCounterContract = (await Factory.deploy(_networkVariables.connectorAddress)) as CrossChainCounter; await crossChainCounterContract.deployed(); diff --git a/packages/example-contracts/lib/shared/network.constants.ts b/packages/example-contracts/lib/shared/network.constants.ts index 1e94ddd5..f452ff62 100644 --- a/packages/example-contracts/lib/shared/network.constants.ts +++ b/packages/example-contracts/lib/shared/network.constants.ts @@ -3,64 +3,64 @@ import { NetworkName } from "@zetachain/addresses"; type ChainId = 0 | 5 | 97 | 1337; export type NetworkVariables = { - CONNECTOR_ADDRESS: string; chainId: ChainId; + connectorAddress: string; crossChainId: ChainId; crossChainName: NetworkName | ""; }; export const networkVariables: Record = { + "bsc-localnet": { + chainId: 0, + connectorAddress: "", + crossChainId: 0, + crossChainName: "", + }, "bsc-testnet": { - CONNECTOR_ADDRESS: "0xE626402550fB921E4a47c11568F89dF3496fbEF0", chainId: 97, + connectorAddress: "0xE626402550fB921E4a47c11568F89dF3496fbEF0", crossChainId: 5, crossChainName: "goerli", }, - goerli: { - CONNECTOR_ADDRESS: "0x68Bc806414e743D88436AEB771Be387A55B4df70", - chainId: 5, - crossChainId: 97, - crossChainName: "bsc-testnet", - }, - "polygon-mumbai": { - CONNECTOR_ADDRESS: "", + "eth-localnet": { chainId: 0, + connectorAddress: "", crossChainId: 0, crossChainName: "", }, "eth-mainnet": { - CONNECTOR_ADDRESS: "", chainId: 0, + connectorAddress: "", crossChainId: 0, crossChainName: "", }, - hardhat: { - CONNECTOR_ADDRESS: "", - chainId: 0, - crossChainId: 0, - crossChainName: "", + goerli: { + chainId: 5, + connectorAddress: "0x68Bc806414e743D88436AEB771Be387A55B4df70", + crossChainId: 97, + crossChainName: "bsc-testnet", }, - ropsten: { - CONNECTOR_ADDRESS: "", + hardhat: { chainId: 0, + connectorAddress: "", crossChainId: 0, crossChainName: "", }, - "eth-localnet": { - CONNECTOR_ADDRESS: "", + "polygon-localnet": { chainId: 0, + connectorAddress: "", crossChainId: 0, crossChainName: "", }, - "bsc-localnet": { - CONNECTOR_ADDRESS: "", + "polygon-mumbai": { chainId: 0, + connectorAddress: "", crossChainId: 0, crossChainName: "", }, - "polygon-localnet": { - CONNECTOR_ADDRESS: "", + ropsten: { chainId: 0, + connectorAddress: "", crossChainId: 0, crossChainName: "", }, diff --git a/packages/example-contracts/scripts/add-liquidity-uniswap-v2.ts b/packages/example-contracts/scripts/add-liquidity-uniswap-v2.ts index 0a7c6802..136fabc8 100644 --- a/packages/example-contracts/scripts/add-liquidity-uniswap-v2.ts +++ b/packages/example-contracts/scripts/add-liquidity-uniswap-v2.ts @@ -56,7 +56,7 @@ const addZetaEthLiquidity = async ( 0, deployer.address, (await getNow()) + 360, - { value: ETHToAdd, gasLimit: 10_000_000 } + { gasLimit: 10_000_000, value: ETHToAdd } ); await tx2.wait(); }; diff --git a/packages/example-contracts/scripts/multi-chain-value/verify-contract.ts b/packages/example-contracts/scripts/multi-chain-value/verify-contract.ts index 0accdf56..4d3b0da1 100644 --- a/packages/example-contracts/scripts/multi-chain-value/verify-contract.ts +++ b/packages/example-contracts/scripts/multi-chain-value/verify-contract.ts @@ -4,8 +4,8 @@ import hardhat from "hardhat"; async function main() { await hardhat.run("verify:verify", { address: getAddress("multiChainValue"), - contract: "contracts/multi-chain-value/MultiChainValue.sol:MultiChainValue", constructorArguments: [getAddress("connector"), getAddress("zetaToken")], + contract: "contracts/multi-chain-value/MultiChainValue.sol:MultiChainValue", }); } diff --git a/packages/example-contracts/test/CrossChainCounter.spec.ts b/packages/example-contracts/test/CrossChainCounter.spec.ts index 33bcaf36..4f787842 100644 --- a/packages/example-contracts/test/CrossChainCounter.spec.ts +++ b/packages/example-contracts/test/CrossChainCounter.spec.ts @@ -72,11 +72,11 @@ describe("CrossChainCounter tests", () => { it("Should revert if the caller is not the Connector contract", async () => { await expect( crossChainCounterContractA.onZetaMessage({ - zetaTxSenderAddress: ethers.utils.solidityPack(["address"], [crossChainCounterContractA.address]), - sourceChainId: 1, destinationAddress: crossChainCounterContractB.address, - zetaValueAndGas: 0, message: encoder.encode(["address"], [deployerAddress]), + sourceChainId: 1, + zetaTxSenderAddress: ethers.utils.solidityPack(["address"], [crossChainCounterContractA.address]), + zetaValueAndGas: 0, }) ).to.be.revertedWith("This function can only be called by the Connector contract"); }); diff --git a/packages/example-contracts/test/CrossChainWarriors.spec.ts b/packages/example-contracts/test/CrossChainWarriors.spec.ts index 9c29c3eb..e00d60c6 100644 --- a/packages/example-contracts/test/CrossChainWarriors.spec.ts +++ b/packages/example-contracts/test/CrossChainWarriors.spec.ts @@ -145,11 +145,11 @@ describe("CrossChainWarriors tests", () => { it("Should revert if the caller is not the Connector contract", async () => { await expect( crossChainWarriorsContractChainA.onZetaMessage({ - zetaTxSenderAddress: ethers.utils.solidityPack(["address"], [crossChainWarriorsContractChainA.address]), - sourceChainId: 1, destinationAddress: crossChainWarriorsContractChainB.address, - zetaValueAndGas: 0, message: encoder.encode(["address"], [deployerAddress]), + sourceChainId: 1, + zetaTxSenderAddress: ethers.utils.solidityPack(["address"], [crossChainWarriorsContractChainA.address]), + zetaValueAndGas: 0, }) ).to.be.revertedWith("This function can only be called by the Connector contract"); }); diff --git a/packages/example-contracts/test/MultiChainSwap.spec.ts b/packages/example-contracts/test/MultiChainSwap.spec.ts index ab1204cb..777fabd4 100644 --- a/packages/example-contracts/test/MultiChainSwap.spec.ts +++ b/packages/example-contracts/test/MultiChainSwap.spec.ts @@ -402,11 +402,11 @@ describe("MultiChainSwap tests", () => { it("Should revert if the caller is not ZetaConnector", async () => { await expect( multiChainSwapContractA.onZetaMessage({ - zetaTxSenderAddress: ethers.utils.solidityPack(["address"], [multiChainSwapContractA.address]), - sourceChainId: chainBId, destinationAddress: multiChainSwapContractB.address, - zetaValueAndGas: 0, message: encoder.encode(["address"], [multiChainSwapContractA.address]), + sourceChainId: chainBId, + zetaTxSenderAddress: ethers.utils.solidityPack(["address"], [multiChainSwapContractA.address]), + zetaValueAndGas: 0, }) ).to.be.revertedWith(getCustomErrorMessage("InvalidCaller", [deployer.address])); }); @@ -428,12 +428,12 @@ describe("MultiChainSwap tests", () => { it("Should revert if the caller is not ZetaConnector", async () => { await expect( multiChainSwapContractA.onZetaRevert({ - zetaTxSenderAddress: deployer.address, - sourceChainId: chainAId, destinationAddress: ethers.utils.solidityPack(["address"], [multiChainSwapContractB.address]), destinationChainId: chainBId, - zetaValueAndGas: 0, message: encoder.encode(["address"], [multiChainSwapContractA.address]), + sourceChainId: chainAId, + zetaTxSenderAddress: deployer.address, + zetaValueAndGas: 0, }) ).to.be.revertedWith(getCustomErrorMessage("InvalidCaller", [deployer.address])); }); diff --git a/packages/example-contracts/test/test.helpers.ts b/packages/example-contracts/test/test.helpers.ts index ae376c3f..19824bb5 100644 --- a/packages/example-contracts/test/test.helpers.ts +++ b/packages/example-contracts/test/test.helpers.ts @@ -36,7 +36,7 @@ export const parseZetaLog = (logs: ContractReceipt["logs"]) => { return eventNames; }; -type CustomErrorParamType = string | BigNumber | number; +type CustomErrorParamType = BigNumber | number | string; export const getCustomErrorMessage = (errorMethod: string, params?: [CustomErrorParamType]) => { const stringParams = params ? params diff --git a/packages/protocol-contracts/.eslintrc.js b/packages/protocol-contracts/.eslintrc.js index 4f9ad61e..77b8f69a 100644 --- a/packages/protocol-contracts/.eslintrc.js +++ b/packages/protocol-contracts/.eslintrc.js @@ -3,13 +3,16 @@ const path = require("path"); const OFF = 0; module.exports = { - extends: ["../../.eslintrc.js"], env: { browser: false, es2021: true, mocha: true, node: true, }, + extends: ["../../.eslintrc.js"], + rules: { + "no-console": OFF, + }, settings: { "import/resolver": { typescript: { @@ -17,7 +20,4 @@ module.exports = { }, }, }, - rules: { - "no-console": OFF, - }, }; diff --git a/packages/protocol-contracts/contracts/ZetaInteractor.sol b/packages/protocol-contracts/contracts/ZetaInteractor.sol index 69d7742c..ff6a7daf 100644 --- a/packages/protocol-contracts/contracts/ZetaInteractor.sol +++ b/packages/protocol-contracts/contracts/ZetaInteractor.sol @@ -45,7 +45,7 @@ abstract contract ZetaInteractor is Ownable, ZetaInteractorErrors { /** * @dev Useful for contracts that inherit from this one */ - function isValidChainId(uint256 chainId) internal view returns (bool) { + function _isValidChainId(uint256 chainId) internal view returns (bool) { return (keccak256(interactorsByChainId[chainId]) != keccak256(new bytes(0))); } diff --git a/packages/protocol-contracts/contracts/ZetaTokenConsumerUniV2.strategy.sol b/packages/protocol-contracts/contracts/ZetaTokenConsumerUniV2.strategy.sol new file mode 100644 index 00000000..44ff7a58 --- /dev/null +++ b/packages/protocol-contracts/contracts/ZetaTokenConsumerUniV2.strategy.sol @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.7; + +import "@openzeppelin/contracts/interfaces/IERC20.sol"; +import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; + +import "./interfaces/ZetaInterfaces.sol"; + +interface ZetaTokenConsumerUniV2Errors { + error InvalidAddress(); + + error InputCantBeZero(); + + error ErrorGettingZeta(); + + error ErrorExchangingZeta(); +} + +/** + * @dev Uniswap V2 strategy for ZetaTokenConsumer + */ +contract ZetaTokenConsumerUniV2 is ZetaTokenConsumer, ZetaTokenConsumerUniV2Errors { + uint256 internal constant MAX_DEADLINE = 100; + + address public uniswapV2RouterAddress; + address internal immutable wETH; + address public zetaToken; + + IUniswapV2Router02 internal uniswapV2Router; + + constructor(address zetaToken_, address uniswapV2Router_) { + if (zetaToken_ == address(0) || uniswapV2Router_ == address(0)) revert InvalidAddress(); + + zetaToken = zetaToken_; + uniswapV2RouterAddress = uniswapV2Router_; + uniswapV2Router = IUniswapV2Router02(uniswapV2Router_); + wETH = uniswapV2Router.WETH(); + } + + function getZetaFromEth(address destinationAddress, uint256 minAmountOut) external payable override { + if (destinationAddress == address(0)) revert InvalidAddress(); + if (msg.value == 0) revert InputCantBeZero(); + + address[] memory path = new address[](2); + path[0] = wETH; + path[1] = zetaToken; + + uint256[] memory amounts = uniswapV2Router.swapExactETHForTokens{value: msg.value}( + minAmountOut, + path, + destinationAddress, + block.timestamp + MAX_DEADLINE + ); + + uint256 amountOut = amounts[path.length - 1]; + + emit EthExchangedForZeta(msg.value, amountOut); + } + + function getZetaFromToken( + address destinationAddress, + uint256 minAmountOut, + address inputToken, + uint256 inputTokenAmount + ) external override { + if (destinationAddress == address(0) || inputToken == address(0)) revert InvalidAddress(); + if (inputTokenAmount == 0) revert InputCantBeZero(); + + bool success = IERC20(inputToken).transferFrom(msg.sender, address(this), inputTokenAmount); + if (!success) revert ErrorGettingZeta(); + success = IERC20(inputToken).approve(uniswapV2RouterAddress, inputTokenAmount); + if (!success) revert ErrorGettingZeta(); + + address[] memory path; + if (inputToken == wETH) { + path = new address[](2); + path[0] = wETH; + path[1] = zetaToken; + } else { + path = new address[](3); + path[0] = inputToken; + path[1] = wETH; + path[2] = zetaToken; + } + + uint256[] memory amounts = uniswapV2Router.swapExactTokensForTokens( + inputTokenAmount, + minAmountOut, + path, + destinationAddress, + block.timestamp + MAX_DEADLINE + ); + uint256 amountOut = amounts[path.length - 1]; + + emit TokenExchangedForZeta(inputToken, inputTokenAmount, amountOut); + } + + function getEthFromZeta( + address destinationAddress, + uint256 minAmountOut, + uint256 zetaTokenAmount + ) external override { + if (destinationAddress == address(0)) revert InvalidAddress(); + if (zetaTokenAmount == 0) revert InputCantBeZero(); + + bool success = IERC20(zetaToken).transferFrom(msg.sender, address(this), zetaTokenAmount); + if (!success) revert ErrorExchangingZeta(); + success = IERC20(zetaToken).approve(uniswapV2RouterAddress, zetaTokenAmount); + if (!success) revert ErrorExchangingZeta(); + + address[] memory path = new address[](2); + path[0] = zetaToken; + path[1] = wETH; + + uint256[] memory amounts = uniswapV2Router.swapExactTokensForETH( + zetaTokenAmount, + minAmountOut, + path, + destinationAddress, + block.timestamp + MAX_DEADLINE + ); + + uint256 amountOut = amounts[path.length - 1]; + + emit ZetaExchangedForEth(zetaTokenAmount, amountOut); + } + + function getTokenFromZeta( + address destinationAddress, + uint256 minAmountOut, + address outputToken, + uint256 zetaTokenAmount + ) external override { + if (destinationAddress == address(0) || outputToken == address(0)) revert InvalidAddress(); + if (zetaTokenAmount == 0) revert InputCantBeZero(); + + bool success = IERC20(zetaToken).transferFrom(msg.sender, address(this), zetaTokenAmount); + if (!success) revert ErrorExchangingZeta(); + success = IERC20(zetaToken).approve(uniswapV2RouterAddress, zetaTokenAmount); + if (!success) revert ErrorExchangingZeta(); + + address[] memory path; + if (outputToken == wETH) { + path = new address[](2); + path[0] = zetaToken; + path[1] = wETH; + } else { + path = new address[](3); + path[0] = zetaToken; + path[1] = wETH; + path[2] = outputToken; + } + + uint256[] memory amounts = uniswapV2Router.swapExactTokensForTokens( + zetaTokenAmount, + minAmountOut, + path, + destinationAddress, + block.timestamp + MAX_DEADLINE + ); + + uint256 amountOut = amounts[path.length - 1]; + + emit ZetaExchangedForToken(outputToken, zetaTokenAmount, amountOut); + } +} diff --git a/packages/protocol-contracts/contracts/ZetaTokenConsumerUniV3.strategy.sol b/packages/protocol-contracts/contracts/ZetaTokenConsumerUniV3.strategy.sol new file mode 100644 index 00000000..9ae1013f --- /dev/null +++ b/packages/protocol-contracts/contracts/ZetaTokenConsumerUniV3.strategy.sol @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.7; + +import "@openzeppelin/contracts/interfaces/IERC20.sol"; +import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; +import "@uniswap/v3-periphery/contracts/interfaces/IQuoter.sol"; + +import "./interfaces/ZetaInterfaces.sol"; + +interface ZetaTokenConsumerUniV3Errors { + error InvalidAddress(); + + error InputCantBeZero(); + + error ErrorGettingToken(); + + error ErrorSendingETH(); +} + +interface WETH9 { + function withdraw(uint256 wad) external; +} + +/** + * @dev Uniswap V3 strategy for ZetaTokenConsumer + */ +contract ZetaTokenConsumerUniV3 is ZetaTokenConsumer, ZetaTokenConsumerUniV3Errors { + uint256 internal constant MAX_DEADLINE = 100; + + uint24 public immutable zetaPoolFee; + uint24 public immutable tokenPoolFee; + + address internal immutable WETH9Address; + address public immutable zetaToken; + + ISwapRouter public immutable uniswapV3Router; + IQuoter public immutable quoter; + + constructor( + address zetaToken_, + address uniswapV3Router_, + address quoter_, + address WETH9Address_, + uint24 zetaPoolFee_, + uint24 tokenPoolFee_ + ) { + if ( + zetaToken_ == address(0) || + uniswapV3Router_ == address(0) || + quoter_ == address(0) || + WETH9Address_ == address(0) + ) revert InvalidAddress(); + + zetaToken = zetaToken_; + uniswapV3Router = ISwapRouter(uniswapV3Router_); + quoter = IQuoter(quoter_); + WETH9Address = WETH9Address_; + zetaPoolFee = zetaPoolFee_; + tokenPoolFee = tokenPoolFee_; + } + + receive() external payable {} + + function getZetaFromEth(address destinationAddress, uint256 minAmountOut) external payable override { + if (destinationAddress == address(0)) revert InvalidAddress(); + if (msg.value == 0) revert InputCantBeZero(); + + ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({ + deadline: block.timestamp + MAX_DEADLINE, + tokenIn: WETH9Address, + tokenOut: zetaToken, + fee: zetaPoolFee, + recipient: destinationAddress, + amountIn: msg.value, + amountOutMinimum: minAmountOut, + sqrtPriceLimitX96: 0 + }); + + uint256 amountOut = uniswapV3Router.exactInputSingle{value: msg.value}(params); + + emit EthExchangedForZeta(msg.value, amountOut); + } + + function getZetaFromToken( + address destinationAddress, + uint256 minAmountOut, + address inputToken, + uint256 inputTokenAmount + ) external override { + if (destinationAddress == address(0) || inputToken == address(0)) revert InvalidAddress(); + if (inputTokenAmount == 0) revert InputCantBeZero(); + + bool success = IERC20(inputToken).transferFrom(msg.sender, address(this), inputTokenAmount); + if (!success) revert ErrorGettingToken(); + success = IERC20(inputToken).approve(address(uniswapV3Router), inputTokenAmount); + if (!success) revert ErrorGettingToken(); + + ISwapRouter.ExactInputParams memory params = ISwapRouter.ExactInputParams({ + deadline: block.timestamp + MAX_DEADLINE, + path: abi.encodePacked(inputToken, tokenPoolFee, WETH9Address, zetaPoolFee, zetaToken), + recipient: destinationAddress, + amountIn: inputTokenAmount, + amountOutMinimum: minAmountOut + }); + + uint256 amountOut = uniswapV3Router.exactInput(params); + + emit TokenExchangedForZeta(inputToken, inputTokenAmount, amountOut); + } + + function getEthFromZeta( + address destinationAddress, + uint256 minAmountOut, + uint256 zetaTokenAmount + ) external override { + if (destinationAddress == address(0)) revert InvalidAddress(); + if (zetaTokenAmount == 0) revert InputCantBeZero(); + + bool success = IERC20(zetaToken).transferFrom(msg.sender, address(this), zetaTokenAmount); + if (!success) revert ErrorGettingToken(); + success = IERC20(zetaToken).approve(address(uniswapV3Router), zetaTokenAmount); + if (!success) revert ErrorGettingToken(); + + ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({ + deadline: block.timestamp + MAX_DEADLINE, + tokenIn: zetaToken, + tokenOut: WETH9Address, + fee: zetaPoolFee, + recipient: address(this), + amountIn: zetaTokenAmount, + amountOutMinimum: minAmountOut, + sqrtPriceLimitX96: 0 + }); + + uint256 amountOut = uniswapV3Router.exactInputSingle(params); + + WETH9(WETH9Address).withdraw(amountOut); + + (bool sent, ) = destinationAddress.call{value: amountOut}(""); + if (!sent) revert ErrorSendingETH(); + + emit ZetaExchangedForEth(zetaTokenAmount, amountOut); + } + + function getTokenFromZeta( + address destinationAddress, + uint256 minAmountOut, + address outputToken, + uint256 zetaTokenAmount + ) external override { + if (destinationAddress == address(0) || outputToken == address(0)) revert InvalidAddress(); + if (zetaTokenAmount == 0) revert InputCantBeZero(); + + bool success = IERC20(zetaToken).transferFrom(msg.sender, address(this), zetaTokenAmount); + if (!success) revert ErrorGettingToken(); + success = IERC20(zetaToken).approve(address(uniswapV3Router), zetaTokenAmount); + if (!success) revert ErrorGettingToken(); + + ISwapRouter.ExactInputParams memory params = ISwapRouter.ExactInputParams({ + deadline: block.timestamp + MAX_DEADLINE, + path: abi.encodePacked(zetaToken, zetaPoolFee, WETH9Address, tokenPoolFee, outputToken), + recipient: destinationAddress, + amountIn: zetaTokenAmount, + amountOutMinimum: minAmountOut + }); + + uint256 amountOut = uniswapV3Router.exactInput(params); + + emit ZetaExchangedForToken(outputToken, zetaTokenAmount, amountOut); + } +} diff --git a/packages/protocol-contracts/contracts/interfaces/ZetaInterfaces.sol b/packages/protocol-contracts/contracts/interfaces/ZetaInterfaces.sol index 05250aae..2f103fcf 100644 --- a/packages/protocol-contracts/contracts/interfaces/ZetaInterfaces.sol +++ b/packages/protocol-contracts/contracts/interfaces/ZetaInterfaces.sol @@ -63,3 +63,40 @@ interface ZetaReceiver { */ function onZetaRevert(ZetaInterfaces.ZetaRevert calldata zetaRevert) external; } + +/** + * @dev ZetaTokenConsumer makes it easier to handle the following situations: + * - Getting Zeta using native coin (to pay for destination gas while using `connector.send`) + * - Getting Zeta using a token (to pay for destination gas while using `connector.send`) + * - Getting native coin using Zeta (to return unused destination gas when `onZetaRevert` is executed) + * - Getting a token using Zeta (to return unused destination gas when `onZetaRevert` is executed) + * @dev The interface can be implemented using different strategies, like UniswapV2, UniswapV3, etc + */ +interface ZetaTokenConsumer { + event EthExchangedForZeta(uint256 amountIn, uint256 amountOut); + event TokenExchangedForZeta(address token, uint256 amountIn, uint256 amountOut); + event ZetaExchangedForEth(uint256 amountIn, uint256 amountOut); + event ZetaExchangedForToken(address token, uint256 amountIn, uint256 amountOut); + + function getZetaFromEth(address destinationAddress, uint256 minAmountOut) external payable; + + function getZetaFromToken( + address destinationAddress, + uint256 minAmountOut, + address inputToken, + uint256 inputTokenAmount + ) external; + + function getEthFromZeta( + address destinationAddress, + uint256 minAmountOut, + uint256 zetaTokenAmount + ) external; + + function getTokenFromZeta( + address destinationAddress, + uint256 minAmountOut, + address outputToken, + uint256 zetaTokenAmount + ) external; +} diff --git a/packages/protocol-contracts/contracts/testing/TestContracts.sol b/packages/protocol-contracts/contracts/testing/TestContracts.sol new file mode 100644 index 00000000..53520a08 --- /dev/null +++ b/packages/protocol-contracts/contracts/testing/TestContracts.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.6.6; + +/** + * @dev Contracts that need to be compiled for testing purposes + */ + +import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol"; +import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; +import "@uniswap/v2-periphery/contracts/UniswapV2Router02.sol"; diff --git a/packages/protocol-contracts/contracts/testing/TestUniswapV3Contracts.sol b/packages/protocol-contracts/contracts/testing/TestUniswapV3Contracts.sol new file mode 100644 index 00000000..b6564cdc --- /dev/null +++ b/packages/protocol-contracts/contracts/testing/TestUniswapV3Contracts.sol @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; +pragma abicoder v2; + +/// @title Non-fungible token for positions +/// @notice Wraps Uniswap V3 positions in a non-fungible token interface which allows for them to be transferred +/// and authorized. +interface INonfungiblePositionManager { + /// @notice Emitted when liquidity is increased for a position NFT + /// @dev Also emitted when a token is minted + /// @param tokenId The ID of the token for which liquidity was increased + /// @param liquidity The amount by which liquidity for the NFT position was increased + /// @param amount0 The amount of token0 that was paid for the increase in liquidity + /// @param amount1 The amount of token1 that was paid for the increase in liquidity + event IncreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1); + /// @notice Emitted when liquidity is decreased for a position NFT + /// @param tokenId The ID of the token for which liquidity was decreased + /// @param liquidity The amount by which liquidity for the NFT position was decreased + /// @param amount0 The amount of token0 that was accounted for the decrease in liquidity + /// @param amount1 The amount of token1 that was accounted for the decrease in liquidity + event DecreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1); + /// @notice Emitted when tokens are collected for a position NFT + /// @dev The amounts reported may not be exactly equivalent to the amounts transferred, due to rounding behavior + /// @param tokenId The ID of the token for which underlying tokens were collected + /// @param recipient The address of the account that received the collected tokens + /// @param amount0 The amount of token0 owed to the position that was collected + /// @param amount1 The amount of token1 owed to the position that was collected + event Collect(uint256 indexed tokenId, address recipient, uint256 amount0, uint256 amount1); + + /// @notice Returns the position information associated with a given token ID. + /// @dev Throws if the token ID is not valid. + /// @param tokenId The ID of the token that represents the position + /// @return nonce The nonce for permits + /// @return operator The address that is approved for spending + /// @return token0 The address of the token0 for a specific pool + /// @return token1 The address of the token1 for a specific pool + /// @return fee The fee associated with the pool + /// @return tickLower The lower end of the tick range for the position + /// @return tickUpper The higher end of the tick range for the position + /// @return liquidity The liquidity of the position + /// @return feeGrowthInside0LastX128 The fee growth of token0 as of the last action on the individual position + /// @return feeGrowthInside1LastX128 The fee growth of token1 as of the last action on the individual position + /// @return tokensOwed0 The uncollected amount of token0 owed to the position as of the last computation + /// @return tokensOwed1 The uncollected amount of token1 owed to the position as of the last computation + function positions(uint256 tokenId) + external + view + returns ( + uint96 nonce, + address operator, + address token0, + address token1, + uint24 fee, + int24 tickLower, + int24 tickUpper, + uint128 liquidity, + uint256 feeGrowthInside0LastX128, + uint256 feeGrowthInside1LastX128, + uint128 tokensOwed0, + uint128 tokensOwed1 + ); + + struct MintParams { + address token0; + address token1; + uint24 fee; + int24 tickLower; + int24 tickUpper; + uint256 amount0Desired; + uint256 amount1Desired; + uint256 amount0Min; + uint256 amount1Min; + address recipient; + uint256 deadline; + } + + /// @notice Creates a new position wrapped in a NFT + /// @dev Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized + /// a method does not exist, i.e. the pool is assumed to be initialized. + /// @param params The params necessary to mint a position, encoded as `MintParams` in calldata + /// @return tokenId The ID of the token that represents the minted position + /// @return liquidity The amount of liquidity for this position + /// @return amount0 The amount of token0 + /// @return amount1 The amount of token1 + function mint(MintParams calldata params) + external + payable + returns ( + uint256 tokenId, + uint128 liquidity, + uint256 amount0, + uint256 amount1 + ); + + struct IncreaseLiquidityParams { + uint256 tokenId; + uint256 amount0Desired; + uint256 amount1Desired; + uint256 amount0Min; + uint256 amount1Min; + uint256 deadline; + } + + /// @notice Increases the amount of liquidity in a position, with tokens paid by the `msg.sender` + /// @param params tokenId The ID of the token for which liquidity is being increased, + /// amount0Desired The desired amount of token0 to be spent, + /// amount1Desired The desired amount of token1 to be spent, + /// amount0Min The minimum amount of token0 to spend, which serves as a slippage check, + /// amount1Min The minimum amount of token1 to spend, which serves as a slippage check, + /// deadline The time by which the transaction must be included to effect the change + /// @return liquidity The new liquidity amount as a result of the increase + /// @return amount0 The amount of token0 to acheive resulting liquidity + /// @return amount1 The amount of token1 to acheive resulting liquidity + function increaseLiquidity(IncreaseLiquidityParams calldata params) + external + payable + returns ( + uint128 liquidity, + uint256 amount0, + uint256 amount1 + ); + + struct DecreaseLiquidityParams { + uint256 tokenId; + uint128 liquidity; + uint256 amount0Min; + uint256 amount1Min; + uint256 deadline; + } + + /// @notice Decreases the amount of liquidity in a position and accounts it to the position + /// @param params tokenId The ID of the token for which liquidity is being decreased, + /// amount The amount by which liquidity will be decreased, + /// amount0Min The minimum amount of token0 that should be accounted for the burned liquidity, + /// amount1Min The minimum amount of token1 that should be accounted for the burned liquidity, + /// deadline The time by which the transaction must be included to effect the change + /// @return amount0 The amount of token0 accounted to the position's tokens owed + /// @return amount1 The amount of token1 accounted to the position's tokens owed + function decreaseLiquidity(DecreaseLiquidityParams calldata params) + external + payable + returns (uint256 amount0, uint256 amount1); + + struct CollectParams { + uint256 tokenId; + address recipient; + uint128 amount0Max; + uint128 amount1Max; + } + + /// @notice Collects up to a maximum amount of fees owed to a specific position to the recipient + /// @param params tokenId The ID of the NFT for which tokens are being collected, + /// recipient The account that should receive the tokens, + /// amount0Max The maximum amount of token0 to collect, + /// amount1Max The maximum amount of token1 to collect + /// @return amount0 The amount of fees collected in token0 + /// @return amount1 The amount of fees collected in token1 + function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1); + + /// @notice Burns a token ID, which deletes it from the NFT contract. The token must have 0 liquidity and all tokens + /// must be collected first. + /// @param tokenId The ID of the token that is being burned + function burn(uint256 tokenId) external payable; +} + +// import "@uniswap/v3-periphery/contracts/interfaces/IPoolInitializer.sol"; +interface IPoolInitializer { + /// @notice Creates a new pool if it does not exist, then initializes if not initialized + /// @dev This method can be bundled with others via IMulticall for the first action (e.g. mint) performed against a pool + /// @param token0 The contract address of token0 of the pool + /// @param token1 The contract address of token1 of the pool + /// @param fee The fee amount of the v3 pool for the specified token pair + /// @param sqrtPriceX96 The initial square root price of the pool as a Q64.96 value + /// @return pool Returns the pool address based on the pair of tokens and fee, will return the newly created pool address if necessary + function createAndInitializePoolIfNecessary( + address token0, + address token1, + uint24 fee, + uint160 sqrtPriceX96 + ) external payable returns (address pool); +} diff --git a/packages/protocol-contracts/hardhat.config.ts b/packages/protocol-contracts/hardhat.config.ts index e508073d..5d95e45e 100644 --- a/packages/protocol-contracts/hardhat.config.ts +++ b/packages/protocol-contracts/hardhat.config.ts @@ -13,13 +13,19 @@ const PRIVATE_KEYS = process.env.PRIVATE_KEY !== undefined ? [`0x${process.env.PRIVATE_KEY}`, `0x${process.env.TSS_PRIVATE_KEY}`] : []; const config: HardhatUserConfig = { - solidity: "0.8.7", etherscan: { ...getHardhatConfigScanners(), }, networks: { ...getHardhatConfigNetworks(PRIVATE_KEYS), }, + solidity: { + compilers: [ + { version: "0.6.6" /** For uniswap v2 */ }, + { version: "0.7.6" /** For uniswap v3 */ }, + { version: "0.8.7" }, + ], + }, }; export default config; diff --git a/packages/protocol-contracts/lib/contracts.helpers.ts b/packages/protocol-contracts/lib/contracts.helpers.ts index 968dd049..2fe64fbd 100644 --- a/packages/protocol-contracts/lib/contracts.helpers.ts +++ b/packages/protocol-contracts/lib/contracts.helpers.ts @@ -2,7 +2,6 @@ import { BaseContract, ContractFactory } from "ethers"; import { ethers } from "hardhat"; import { - ZetaConnector__factory as ZetaConnectorFactory, ZetaConnectorBase, ZetaConnectorBase__factory as ZetaConnectorBaseFactory, ZetaConnectorEth, @@ -11,10 +10,16 @@ import { ZetaConnectorNonEth__factory as ZetaConnectorNonEthFactory, ZetaEth, ZetaEth__factory as ZetaEthFactory, + ZetaInteractorMock, + ZetaInteractorMock__factory as ZetaInteractorMockFactory, ZetaNonEth, ZetaNonEth__factory as ZetaNonEthFactory, ZetaReceiverMock, ZetaReceiverMock__factory as ZetaReceiverMockFactory, + ZetaTokenConsumerUniV2, + ZetaTokenConsumerUniV2__factory as ZetaTokenConsumerUniV2Factory, + ZetaTokenConsumerUniV3, + ZetaTokenConsumerUniV3__factory as ZetaTokenConsumerUniV3Factory, } from "../typechain-types"; export const isEthNetworkName = (networkName: string) => @@ -96,6 +101,24 @@ export const getZetaConnectorNonEth = async (params: GetContractParams + getContract({ + contractName: "ZetaInteractorMock", + deployParams: [zetaToken], + }); + +export const getZetaTokenConsumerUniV2Strategy = async (params: GetContractParams) => + getContract({ + contractName: "ZetaTokenConsumerUniV2", + ...params, + }); + +export const getZetaTokenConsumerUniV3Strategy = async (params: GetContractParams) => + getContract({ + contractName: "ZetaTokenConsumerUniV3", + ...params, + }); + export type GetContractParams = | { deployParams: Parameters; diff --git a/packages/protocol-contracts/lib/zeta-interactor/ZetaInteractor.helpers.ts b/packages/protocol-contracts/lib/zeta-interactor/ZetaInteractor.helpers.ts index fe327384..d93bf01d 100644 --- a/packages/protocol-contracts/lib/zeta-interactor/ZetaInteractor.helpers.ts +++ b/packages/protocol-contracts/lib/zeta-interactor/ZetaInteractor.helpers.ts @@ -1,7 +1,7 @@ import { ZetaInteractorMock, ZetaInteractorMock__factory } from "../../typechain-types"; import { getContract } from "../contracts.helpers"; -export const getZetaConnectorMock = async (zetaToken: string) => +export const getZetaInteractorMock = async (zetaToken: string) => getContract({ contractName: "ZetaInteractorMock", deployParams: [zetaToken], diff --git a/packages/protocol-contracts/package.json b/packages/protocol-contracts/package.json index 267fb05c..c370bf69 100644 --- a/packages/protocol-contracts/package.json +++ b/packages/protocol-contracts/package.json @@ -23,6 +23,8 @@ }, "dependencies": { "@openzeppelin/contracts": "^4.5.0", + "@uniswap/v2-periphery": "1.1.0-beta.0", + "@uniswap/v3-periphery": "1.1.0", "ethers": "5.6.8" } } diff --git a/packages/protocol-contracts/scripts/test-zeta-send.ts b/packages/protocol-contracts/scripts/test-zeta-send.ts index 8395ae8e..74fc2305 100644 --- a/packages/protocol-contracts/scripts/test-zeta-send.ts +++ b/packages/protocol-contracts/scripts/test-zeta-send.ts @@ -14,12 +14,12 @@ async function main() { console.log("Sending"); await ( await contract.send({ - destinationChainId: getChainId("bsc-testnet"), destinationAddress: encoder.encode(["address"], ["0x09b80BEcBe709Dd354b1363727514309d1Ac3C7b"]), + destinationChainId: getChainId("bsc-testnet"), destinationGasLimit: 1_000_000, message: encoder.encode(["address"], ["0x09b80BEcBe709Dd354b1363727514309d1Ac3C7b"]), - zetaValueAndGas: 0, zetaParams: [], + zetaValueAndGas: 0, }) ).wait(); console.log("Sent"); diff --git a/packages/protocol-contracts/scripts/verify-contracts.ts b/packages/protocol-contracts/scripts/verify-contracts.ts index 6b71a866..f3939404 100644 --- a/packages/protocol-contracts/scripts/verify-contracts.ts +++ b/packages/protocol-contracts/scripts/verify-contracts.ts @@ -15,16 +15,16 @@ async function main() { await hardhat .run("verify:verify", { address: getAddress("zetaToken"), - contract: "contracts/evm/Zeta.eth.sol:ZetaEth", constructorArguments: [ZETA_INITIAL_SUPPLY], + contract: "contracts/evm/Zeta.eth.sol:ZetaEth", }) .catch(handleCatch); await hardhat .run("verify:verify", { address: getAddress("connector"), - contract: "contracts/evm/ZetaConnector.eth.sol:ZetaConnectorEth", constructorArguments: [getAddress("zetaToken"), getAddress("tss"), getAddress("tssUpdater")], + contract: "contracts/evm/ZetaConnector.eth.sol:ZetaConnectorEth", }) .catch(handleCatch); } else { @@ -38,8 +38,8 @@ async function main() { await hardhat .run("verify:verify", { address: getAddress("connector"), - contract: "contracts/evm/ZetaConnector.non-eth.sol:ZetaConnectorNonEth", constructorArguments: [getAddress("zetaToken"), getAddress("tss"), getAddress("tssUpdater")], + contract: "contracts/evm/ZetaConnector.non-eth.sol:ZetaConnectorNonEth", }) .catch(handleCatch); } diff --git a/packages/protocol-contracts/test/Zeta.non-eth.spec.ts b/packages/protocol-contracts/test/Zeta.non-eth.spec.ts index 18e0a6e2..facb429e 100644 --- a/packages/protocol-contracts/test/Zeta.non-eth.spec.ts +++ b/packages/protocol-contracts/test/Zeta.non-eth.spec.ts @@ -137,8 +137,8 @@ describe("ZetaNonEth tests", () => { destinationChainId: 1, destinationGasLimit: 2500000, message: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), - zetaValueAndGas: 1000, zetaParams: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), + zetaValueAndGas: 1000, }); const e2 = await zetaTokenNonEthContract.queryFilter(zetaBurntFilter); diff --git a/packages/protocol-contracts/test/ZetaConnector.spec.ts b/packages/protocol-contracts/test/ZetaConnector.spec.ts index 2a061324..b79ecf0b 100644 --- a/packages/protocol-contracts/test/ZetaConnector.spec.ts +++ b/packages/protocol-contracts/test/ZetaConnector.spec.ts @@ -27,6 +27,7 @@ describe("ZetaConnector tests", () => { let zetaConnectorEthContract: ZetaConnectorEth; let zetaReceiverMockContract: ZetaReceiverMock; let zetaConnectorNonEthContract: ZetaConnectorNonEth; + let tssUpdater: SignerWithAddress; let tssSigner: SignerWithAddress; let randomSigner: SignerWithAddress; @@ -197,8 +198,8 @@ describe("ZetaConnector tests", () => { destinationChainId: 1, destinationGasLimit: 2500000, message: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), - zetaValueAndGas: 1000, zetaParams: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), + zetaValueAndGas: 1000, }) ).to.revertedWith("Pausable: paused"); }); @@ -214,8 +215,8 @@ describe("ZetaConnector tests", () => { destinationChainId: 1, destinationGasLimit: 2500000, message: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), - zetaValueAndGas: 1000, zetaParams: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), + zetaValueAndGas: 1000, }) ).to.revertedWith("ERC20: transfer amount exceeds balance"); }); @@ -227,8 +228,8 @@ describe("ZetaConnector tests", () => { destinationChainId: 1, destinationGasLimit: 2500000, message: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), - zetaValueAndGas: 1000, zetaParams: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), + zetaValueAndGas: 1000, }) ).to.revertedWith("ERC20: insufficient allowance"); }); @@ -248,8 +249,8 @@ describe("ZetaConnector tests", () => { destinationChainId: 1, destinationGasLimit: 2500000, message: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), - zetaValueAndGas: 1000, zetaParams: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), + zetaValueAndGas: 1000, }) ).wait(); @@ -270,8 +271,8 @@ describe("ZetaConnector tests", () => { destinationChainId: 1, destinationGasLimit: 2500000, message: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), - zetaValueAndGas: 0, zetaParams: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), + zetaValueAndGas: 0, }); const e2 = await zetaConnectorEthContract.queryFilter(zetaSentFilter); @@ -288,8 +289,8 @@ describe("ZetaConnector tests", () => { destinationChainId: 1, destinationGasLimit: 2500000, message: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), - zetaValueAndGas: 0, zetaParams: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), + zetaValueAndGas: 0, }); const e2 = await zetaConnectorEthContract.queryFilter(zetaSentFilter); @@ -498,8 +499,8 @@ describe("ZetaConnector tests", () => { destinationChainId: 1, destinationGasLimit: 2500000, message: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), - zetaValueAndGas: 1000, zetaParams: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), + zetaValueAndGas: 1000, }) ).to.revertedWith("Pausable: paused"); }); @@ -515,8 +516,8 @@ describe("ZetaConnector tests", () => { destinationChainId: 1, destinationGasLimit: 2500000, message: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), - zetaValueAndGas: 1000, zetaParams: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), + zetaValueAndGas: 1000, }) ).to.revertedWith("ERC20: insufficient allowance"); }); @@ -528,8 +529,8 @@ describe("ZetaConnector tests", () => { destinationChainId: 1, destinationGasLimit: 2500000, message: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), - zetaValueAndGas: 1000, zetaParams: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), + zetaValueAndGas: 1000, }) ).to.revertedWith("ERC20: insufficient allowance"); }); @@ -546,8 +547,8 @@ describe("ZetaConnector tests", () => { destinationChainId: 1, destinationGasLimit: 2500000, message: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), - zetaValueAndGas: parseEther("1"), zetaParams: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), + zetaValueAndGas: parseEther("1"), }) ).wait(); @@ -565,8 +566,8 @@ describe("ZetaConnector tests", () => { destinationChainId: 1, destinationGasLimit: 2500000, message: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), - zetaValueAndGas: 0, zetaParams: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), + zetaValueAndGas: 0, }); const e2 = await zetaConnectorNonEthContract.queryFilter(zetaSentFilter); @@ -583,8 +584,8 @@ describe("ZetaConnector tests", () => { destinationChainId: 1, destinationGasLimit: 2500000, message: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), - zetaValueAndGas: 0, zetaParams: new ethers.utils.AbiCoder().encode(["string"], ["hello"]), + zetaValueAndGas: 0, }); const e2 = await zetaConnectorNonEthContract.queryFilter(zetaSentFilter); diff --git a/packages/protocol-contracts/test/ZetaInteractor.spec.ts b/packages/protocol-contracts/test/ZetaInteractor.spec.ts index 67c9de81..4fe8fcc1 100644 --- a/packages/protocol-contracts/test/ZetaInteractor.spec.ts +++ b/packages/protocol-contracts/test/ZetaInteractor.spec.ts @@ -2,7 +2,7 @@ import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import chai, { expect } from "chai"; import { ethers } from "hardhat"; -import { getZetaConnectorMock } from "../lib/zeta-interactor/ZetaInteractor.helpers"; +import { getZetaInteractorMock } from "../lib/contracts.helpers"; import { ZetaInteractorMock } from "../typechain-types"; import { getCustomErrorMessage } from "./test.helpers"; @@ -24,7 +24,7 @@ describe("ZetaInteractor tests", () => { accounts = await ethers.getSigners(); [deployer, crossChainContractB, zetaConnector] = accounts; - zetaInteractorMock = await getZetaConnectorMock(zetaConnector.address); + zetaInteractorMock = await getZetaInteractorMock(zetaConnector.address); const encodedCrossChainAddressB = ethers.utils.solidityPack(["address"], [crossChainContractB.address]); await (await zetaInteractorMock.setInteractorByChainId(chainBId, encodedCrossChainAddressB)).wait(); @@ -34,11 +34,11 @@ describe("ZetaInteractor tests", () => { it("Should revert if the caller is not ZetaConnector", async () => { await expect( zetaInteractorMock.onZetaMessage({ - zetaTxSenderAddress: ethers.utils.solidityPack(["address"], [zetaInteractorMock.address]), - sourceChainId: chainBId, destinationAddress: crossChainContractB.address, - zetaValueAndGas: 0, message: encoder.encode(["address"], [zetaInteractorMock.address]), + sourceChainId: chainBId, + zetaTxSenderAddress: ethers.utils.solidityPack(["address"], [zetaInteractorMock.address]), + zetaValueAndGas: 0, }) ).to.be.revertedWith(getCustomErrorMessage("InvalidCaller", [deployer.address])); }); @@ -46,11 +46,11 @@ describe("ZetaInteractor tests", () => { it("Should revert if the zetaTxSenderAddress it not in interactorsByChainId", async () => { await expect( zetaInteractorMock.connect(zetaConnector).onZetaMessage({ - zetaTxSenderAddress: ethers.utils.solidityPack(["address"], [zetaInteractorMock.address]), - sourceChainId: chainBId, destinationAddress: crossChainContractB.address, - zetaValueAndGas: 0, message: encoder.encode(["address"], [crossChainContractB.address]), + sourceChainId: chainBId, + zetaTxSenderAddress: ethers.utils.solidityPack(["address"], [zetaInteractorMock.address]), + zetaValueAndGas: 0, }) ).to.be.revertedWith(getCustomErrorMessage("InvalidZetaMessageCall")); }); @@ -60,12 +60,12 @@ describe("ZetaInteractor tests", () => { it("Should revert if the caller is not ZetaConnector", async () => { await expect( zetaInteractorMock.onZetaRevert({ - zetaTxSenderAddress: deployer.address, - sourceChainId: chainAId, destinationAddress: ethers.utils.solidityPack(["address"], [crossChainContractB.address]), destinationChainId: chainBId, - zetaValueAndGas: 0, message: encoder.encode(["address"], [zetaInteractorMock.address]), + sourceChainId: chainAId, + zetaTxSenderAddress: deployer.address, + zetaValueAndGas: 0, }) ).to.be.revertedWith(getCustomErrorMessage("InvalidCaller", [deployer.address])); }); diff --git a/packages/protocol-contracts/test/ZetaTokenConsumer.spec.ts b/packages/protocol-contracts/test/ZetaTokenConsumer.spec.ts new file mode 100644 index 00000000..f7539487 --- /dev/null +++ b/packages/protocol-contracts/test/ZetaTokenConsumer.spec.ts @@ -0,0 +1,286 @@ +import { MaxUint256 } from "@ethersproject/constants"; +import { parseEther, parseUnits } from "@ethersproject/units"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { getAddress } from "@zetachain/addresses"; +import chai, { expect } from "chai"; +import { BigNumber } from "ethers"; +import { ethers } from "hardhat"; + +import { + deployZetaNonEth, + getZetaTokenConsumerUniV2Strategy, + getZetaTokenConsumerUniV3Strategy, +} from "../lib/contracts.helpers"; +import { + IERC20, + IERC20__factory, + INonfungiblePositionManager, + INonfungiblePositionManager__factory, + IPoolInitializer__factory, + UniswapV2Router02__factory, + ZetaTokenConsumer, + ZetaTokenConsumerUniV2, + ZetaTokenConsumerUniV3, +} from "../typechain-types"; +import { parseZetaConsumerLog } from "./test.helpers"; + +chai.should(); + +describe("ZetaTokenConsumer tests", () => { + let uniswapV2RouterAddr: string; + let uniswapV3RouterAddr: string; + let USDCAddr: string; + + let zetaTokenConsumerUniV2: ZetaTokenConsumerUniV2; + let zetaTokenConsumerUniV3: ZetaTokenConsumerUniV3; + let zetaTokenNonEthAddress: string; + let zetaTokenNonEth: IERC20; + + let accounts: SignerWithAddress[]; + let tssUpdater: SignerWithAddress; + let tssSigner: SignerWithAddress; + let randomSigner: SignerWithAddress; + + const getNow = async () => { + const block = await ethers.provider.getBlock("latest"); + return block.timestamp; + }; + + const swapToken = async (signer: SignerWithAddress, tokenAddress: string, expectedAmount: BigNumber) => { + const uniswapRouter = UniswapV2Router02__factory.connect(uniswapV2RouterAddr, signer); + + const WETH = await uniswapRouter.WETH(); + const path = [WETH, tokenAddress]; + const tx = await uniswapRouter + .connect(signer) + .swapETHForExactTokens(expectedAmount, path, signer.address, (await getNow()) + 360, { value: parseEther("10") }); + + await tx.wait(); + }; + + /** + * @todo (andy): WIP, not in use yet + */ + const createPoolV3 = async (signer: SignerWithAddress, tokenAddress: string) => { + const DAI = getAddress("dai", { + customNetworkName: "eth-mainnet", + customZetaNetwork: "mainnet", + }); + + const UNI_NFT_MANAGER_V3 = getAddress("uniswapV3NftManager", { + customNetworkName: "eth-mainnet", + customZetaNetwork: "mainnet", + }); + + const USDC = getAddress("usdc", { + customNetworkName: "eth-mainnet", + customZetaNetwork: "mainnet", + }); + + await swapToken(signer, DAI, parseUnits("10000", 18)); + + const token = IERC20__factory.connect(USDC, signer); + const tx1 = await token.approve(UNI_NFT_MANAGER_V3, MaxUint256); + await tx1.wait(); + + const token2 = IERC20__factory.connect(DAI, signer); + const tx2 = await token2.approve(UNI_NFT_MANAGER_V3, MaxUint256); + await tx2.wait(); + + const uniswapRouter = INonfungiblePositionManager__factory.connect(UNI_NFT_MANAGER_V3, signer); + + const uniswapNFTManager = IPoolInitializer__factory.connect(UNI_NFT_MANAGER_V3, signer); + const tx3 = await uniswapNFTManager.createAndInitializePoolIfNecessary( + USDC, + DAI, + 3000, + "80000000000000000000000000000" + ); + await tx3.wait(); + + const params: INonfungiblePositionManager.MintParamsStruct = { + amount0Desired: parseEther("10"), + amount0Min: 0, + amount1Desired: parseEther("10"), + amount1Min: 0, + deadline: (await getNow()) + 360, + fee: 3000, + recipient: signer.address, + tickLower: 193, + tickUpper: 194, + token0: USDC, + token1: DAI, + }; + + const tx4 = await uniswapRouter.mint(params); + await tx4.wait(); + }; + + beforeEach(async () => { + accounts = await ethers.getSigners(); + [tssUpdater, tssSigner, randomSigner] = accounts; + + zetaTokenNonEth = await deployZetaNonEth({ + args: [tssSigner.address, tssUpdater.address], + }); + + uniswapV2RouterAddr = getAddress("uniswapV2Router02", { + customNetworkName: "eth-mainnet", + customZetaNetwork: "mainnet", + }); + + const DAI = getAddress("dai", { + customNetworkName: "eth-mainnet", + customZetaNetwork: "mainnet", + }); + + const UNI_QUOTER_V3 = getAddress("uniswapV3Quoter", { + customNetworkName: "eth-mainnet", + customZetaNetwork: "mainnet", + }); + + const UNI_ROUTER_V3 = getAddress("uniswapV3Router", { + customNetworkName: "eth-mainnet", + customZetaNetwork: "mainnet", + }); + + const WETH9 = getAddress("weth9", { + customNetworkName: "eth-mainnet", + customZetaNetwork: "mainnet", + }); + + USDCAddr = getAddress("usdc", { + customNetworkName: "eth-mainnet", + customZetaNetwork: "mainnet", + }); + + // For testing purposes we use an existing uni v3 pool + await swapToken(tssUpdater, DAI, parseEther("10000")); + await swapToken(randomSigner, DAI, parseEther("10000")); + await swapToken(randomSigner, DAI, parseEther("10000")); + await swapToken(randomSigner, DAI, parseEther("10000")); + await swapToken(randomSigner, DAI, parseEther("10000")); + zetaTokenNonEthAddress = DAI; + zetaTokenNonEth = IERC20__factory.connect(zetaTokenNonEthAddress, tssSigner); + + zetaTokenConsumerUniV2 = await getZetaTokenConsumerUniV2Strategy({ + deployParams: [zetaTokenNonEthAddress, uniswapV2RouterAddr], + }); + + uniswapV3RouterAddr = UNI_ROUTER_V3; + zetaTokenConsumerUniV3 = await getZetaTokenConsumerUniV3Strategy({ + deployParams: [zetaTokenNonEthAddress, uniswapV3RouterAddr, UNI_QUOTER_V3, WETH9, 3000, 3000], + }); + }); + + describe("getZetaFromEth", () => { + const shouldGetZetaFromETH = async (zetaTokenConsumer: ZetaTokenConsumer) => { + const initialZetaBalance = await zetaTokenNonEth.balanceOf(randomSigner.address); + const tx = await zetaTokenConsumer.getZetaFromEth(randomSigner.address, 1, { value: parseEther("1") }); + + const result = await tx.wait(); + const eventNames = parseZetaConsumerLog(result.logs); + expect(eventNames.filter((e) => e === "EthExchangedForZeta")).to.have.lengthOf(1); + + const finalZetaBalance = await zetaTokenNonEth.balanceOf(randomSigner.address); + expect(finalZetaBalance).to.be.gt(initialZetaBalance); + }; + + it("Should get zeta from eth using UniV2", async () => { + const zetaTokenConsumer = zetaTokenConsumerUniV2.connect(randomSigner); + await shouldGetZetaFromETH(zetaTokenConsumer); + }); + + it("Should get zeta from eth using UniV3", async () => { + const zetaTokenConsumer = zetaTokenConsumerUniV3.connect(randomSigner); + await shouldGetZetaFromETH(zetaTokenConsumer); + }); + }); + + describe("getZetaFromToken", () => { + const shouldGetZetaFromToken = async (zetaTokenConsumer: ZetaTokenConsumer) => { + const USDCContract = IERC20__factory.connect(USDCAddr, randomSigner); + await swapToken(randomSigner, USDCAddr, parseUnits("10000", 6)); + + const initialZetaBalance = await zetaTokenNonEth.balanceOf(randomSigner.address); + const tx1 = await USDCContract.approve(zetaTokenConsumer.address, MaxUint256); + await tx1.wait(); + + const tx2 = await zetaTokenConsumer.getZetaFromToken(randomSigner.address, 1, USDCAddr, parseUnits("100", 6)); + const result = await tx2.wait(); + + const eventNames = parseZetaConsumerLog(result.logs); + expect(eventNames.filter((e) => e === "TokenExchangedForZeta")).to.have.lengthOf(1); + + const finalZetaBalance = await zetaTokenNonEth.balanceOf(randomSigner.address); + expect(finalZetaBalance).to.be.gt(initialZetaBalance); + }; + + it("Should get zeta from token using UniV2", async () => { + const zetaTokenConsumer = zetaTokenConsumerUniV2.connect(randomSigner); + await shouldGetZetaFromToken(zetaTokenConsumer); + }); + + it("Should get zeta from token using UniV3", async () => { + const zetaTokenConsumer = zetaTokenConsumerUniV3.connect(randomSigner); + await shouldGetZetaFromToken(zetaTokenConsumer); + }); + }); + + describe("getEthFromZeta", () => { + const shouldGetETHFromZeta = async (zetaTokenConsumer: ZetaTokenConsumer) => { + const initialEthBalance = await ethers.provider.getBalance(randomSigner.address); + const tx1 = await zetaTokenNonEth.connect(randomSigner).approve(zetaTokenConsumer.address, MaxUint256); + await tx1.wait(); + + const tx2 = await zetaTokenConsumer.getEthFromZeta(randomSigner.address, 1, parseUnits("5000", 18)); + const result = await tx2.wait(); + + const eventNames = parseZetaConsumerLog(result.logs); + expect(eventNames.filter((e) => e === "ZetaExchangedForEth")).to.have.lengthOf(1); + + const finalEthBalance = await ethers.provider.getBalance(randomSigner.address); + expect(finalEthBalance).to.be.gt(initialEthBalance); + }; + + it("Should get eth from zeta using UniV2", async () => { + const zetaTokenConsumer = zetaTokenConsumerUniV2.connect(randomSigner); + await shouldGetETHFromZeta(zetaTokenConsumer); + }); + + it("Should get eth from zeta using UniV3", async () => { + const zetaTokenConsumer = zetaTokenConsumerUniV3.connect(randomSigner); + + await shouldGetETHFromZeta(zetaTokenConsumer); + }); + }); + + describe("getTokenFromZeta", () => { + const shouldGetTokenFromZeta = async (zetaTokenConsumer: ZetaTokenConsumer) => { + const USDCContract = IERC20__factory.connect(USDCAddr, randomSigner); + + const initialTokenBalance = await USDCContract.balanceOf(randomSigner.address); + const tx1 = await zetaTokenNonEth.connect(randomSigner).approve(zetaTokenConsumer.address, MaxUint256); + await tx1.wait(); + + const tx2 = await zetaTokenConsumer.getTokenFromZeta(randomSigner.address, 1, USDCAddr, parseUnits("5000", 18)); + const result = await tx2.wait(); + + const eventNames = parseZetaConsumerLog(result.logs); + expect(eventNames.filter((e) => e === "ZetaExchangedForToken")).to.have.lengthOf(1); + + const finalTokenBalance = await USDCContract.balanceOf(randomSigner.address); + expect(finalTokenBalance).to.be.gt(initialTokenBalance); + }; + + it("Should get token from zeta using UniV2", async () => { + const zetaTokenConsumer = zetaTokenConsumerUniV2.connect(randomSigner); + await shouldGetTokenFromZeta(zetaTokenConsumer); + }); + + it("Should get token from zeta using UniV3", async () => { + const zetaTokenConsumer = zetaTokenConsumerUniV3.connect(randomSigner); + await shouldGetTokenFromZeta(zetaTokenConsumer); + }); + }); +}); diff --git a/packages/protocol-contracts/test/test.helpers.ts b/packages/protocol-contracts/test/test.helpers.ts index 44cd25cf..95e59d97 100644 --- a/packages/protocol-contracts/test/test.helpers.ts +++ b/packages/protocol-contracts/test/test.helpers.ts @@ -1,6 +1,8 @@ import { BigNumber, ContractReceipt } from "ethers"; -type CustomErrorParamType = string | BigNumber | number; +import { ZetaTokenConsumer__factory } from "../typechain-types"; + +type CustomErrorParamType = BigNumber | number | string; export const getCustomErrorMessage = (errorMethod: string, params?: [CustomErrorParamType]) => { const stringParams = params ? params @@ -15,3 +17,19 @@ export const getCustomErrorMessage = (errorMethod: string, params?: [CustomError : ""; return `VM Exception while processing transaction: reverted with custom error '${errorMethod}(${stringParams})'`; }; + +export const parseZetaConsumerLog = (logs: ContractReceipt["logs"]) => { + const iface = ZetaTokenConsumer__factory.createInterface(); + + const eventNames = logs.map((log) => { + try { + const parsedLog = iface.parseLog(log); + + return parsedLog.name; + } catch (e: any) { + return "NO_ZETA_LOG"; + } + }); + + return eventNames; +}; diff --git a/scripts/slither.ts b/scripts/slither.ts index 5f5287bd..6f925593 100644 --- a/scripts/slither.ts +++ b/scripts/slither.ts @@ -1,4 +1,3 @@ -import fs from "fs"; import inquirer from "inquirer"; import { execSync } from "node:child_process"; import path from "node:path"; @@ -24,10 +23,10 @@ async function getPackageName() { } else { packageName = await inquirer.prompt([ { - type: "list", + choices: packageNames, message: "Which set of contracts would you like to test?", name: "contracts", - choices: packageNames, + type: "list", }, ]); @@ -40,9 +39,9 @@ async function getFilterPaths() { const { confirm: includeLibraries } = await inquirer.prompt([ { - type: "confirm", message: "Do you want to include OpenZeppelin & Uniswap libraries in this scan?", name: "confirm", + type: "confirm", }, ]); diff --git a/yarn.lock b/yarn.lock index 5d01736e..427fe50a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1234,6 +1234,13 @@ __metadata: languageName: node linkType: hard +"@openzeppelin/contracts@npm:3.4.1-solc-0.7-2": + version: 3.4.1-solc-0.7-2 + resolution: "@openzeppelin/contracts@npm:3.4.1-solc-0.7-2" + checksum: 3608a4065f65946117caa543ef72477ce637bd5cc4f4853303b5f5b6c26516f8b50898ea3a8486e2877689cae81453ce22c72c8624c77c363c63f019b4086ffa + languageName: node + linkType: hard + "@openzeppelin/contracts@npm:^4.5.0": version: 4.5.0 resolution: "@openzeppelin/contracts@npm:4.5.0" @@ -1849,6 +1856,17 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/experimental-utils@npm:^5.0.0": + version: 5.30.0 + resolution: "@typescript-eslint/experimental-utils@npm:5.30.0" + dependencies: + "@typescript-eslint/utils": 5.30.0 + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 66a140b04b57e50a4abd6912616b6af0a9a71f989bd2a77574f4117bf7ba923ae7541780f7d2627cf9bc815eea253181cd09ea9f599b2b8e0895c97258c1e1af + languageName: node + linkType: hard + "@typescript-eslint/parser@npm:^5.20.0": version: 5.20.0 resolution: "@typescript-eslint/parser@npm:5.20.0" @@ -1876,6 +1894,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/scope-manager@npm:5.30.0": + version: 5.30.0 + resolution: "@typescript-eslint/scope-manager@npm:5.30.0" + dependencies: + "@typescript-eslint/types": 5.30.0 + "@typescript-eslint/visitor-keys": 5.30.0 + checksum: 51246d0f6c497ad98fcfb02a9da92e855cd5916cf6ea117b1f8a511e4f62d1eae28f2c7278dfe29cc823c36f3b1fe87ff56681654b68faac5dfd1b897c3c58da + languageName: node + linkType: hard + "@typescript-eslint/type-utils@npm:5.20.0": version: 5.20.0 resolution: "@typescript-eslint/type-utils@npm:5.20.0" @@ -1899,6 +1927,13 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/types@npm:5.30.0": + version: 5.30.0 + resolution: "@typescript-eslint/types@npm:5.30.0" + checksum: f83a506880d78419283a86e8aeb6c744b1d1a7fc3a366625125805daf0f9a7640a778537113b8865a4cdd985dcde53066820ea044a750126bc8b478eb93d4d12 + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:5.20.0": version: 5.20.0 resolution: "@typescript-eslint/typescript-estree@npm:5.20.0" @@ -1917,6 +1952,24 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/typescript-estree@npm:5.30.0": + version: 5.30.0 + resolution: "@typescript-eslint/typescript-estree@npm:5.30.0" + dependencies: + "@typescript-eslint/types": 5.30.0 + "@typescript-eslint/visitor-keys": 5.30.0 + debug: ^4.3.4 + globby: ^11.1.0 + is-glob: ^4.0.3 + semver: ^7.3.7 + tsutils: ^3.21.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: cf8caea435c4346fae9c81635864efa17ea106e3dea5cd226c096145958ff6e7918e40cdb2af06526ca43d44717eb088869f1c1db51a52aa9f72b6bf65e11db2 + languageName: node + linkType: hard + "@typescript-eslint/utils@npm:5.20.0": version: 5.20.0 resolution: "@typescript-eslint/utils@npm:5.20.0" @@ -1933,6 +1986,22 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/utils@npm:5.30.0": + version: 5.30.0 + resolution: "@typescript-eslint/utils@npm:5.30.0" + dependencies: + "@types/json-schema": ^7.0.9 + "@typescript-eslint/scope-manager": 5.30.0 + "@typescript-eslint/types": 5.30.0 + "@typescript-eslint/typescript-estree": 5.30.0 + eslint-scope: ^5.1.1 + eslint-utils: ^3.0.0 + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 176eda46292398c0fe069d96f51d0083a9b978d4c6e2ca92f10e9ebad83910d67754bcb2c1667cf4c40e06c112558ff1ad973d6f82719cfe4de7c72f89a3df29 + languageName: node + linkType: hard + "@typescript-eslint/visitor-keys@npm:5.20.0": version: 5.20.0 resolution: "@typescript-eslint/visitor-keys@npm:5.20.0" @@ -1943,6 +2012,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/visitor-keys@npm:5.30.0": + version: 5.30.0 + resolution: "@typescript-eslint/visitor-keys@npm:5.30.0" + dependencies: + "@typescript-eslint/types": 5.30.0 + eslint-visitor-keys: ^3.3.0 + checksum: bf2219cbb910d284e2f224aaa701932287b25fe99312f9abf6406a8f52a3a0b852c96276cd740ae3071b2561b253a6cfa30b0b8ab1d199e08f9550402f8fbf1f + languageName: node + linkType: hard + "@ungap/promise-all-settled@npm:1.1.2": version: 1.1.2 resolution: "@ungap/promise-all-settled@npm:1.1.2" @@ -1957,6 +2036,13 @@ __metadata: languageName: node linkType: hard +"@uniswap/lib@npm:^4.0.1-alpha": + version: 4.0.1-alpha + resolution: "@uniswap/lib@npm:4.0.1-alpha" + checksum: d7bbacccef40966af16c7e215ab085f575686d316b2802c9e1cfd03f7ad351970e547535670a28b2279c3cfcc4fb02888614c46f94efe2987af2309f3ec86127 + languageName: node + linkType: hard + "@uniswap/v2-core@npm:1.0.0": version: 1.0.0 resolution: "@uniswap/v2-core@npm:1.0.0" @@ -1964,6 +2050,13 @@ __metadata: languageName: node linkType: hard +"@uniswap/v2-core@npm:1.0.1": + version: 1.0.1 + resolution: "@uniswap/v2-core@npm:1.0.1" + checksum: eaa118fe45eac2e80b7468547ce2cde12bd3c8157555d2e40e0462a788c9506c6295247b511382da85e44a89ad92aff7bb3433b23bfbd2eeea23942ecd46e979 + languageName: node + linkType: hard + "@uniswap/v2-periphery@npm:1.1.0-beta.0": version: 1.1.0-beta.0 resolution: "@uniswap/v2-periphery@npm:1.1.0-beta.0" @@ -1974,6 +2067,27 @@ __metadata: languageName: node linkType: hard +"@uniswap/v3-core@npm:1.0.0": + version: 1.0.0 + resolution: "@uniswap/v3-core@npm:1.0.0" + checksum: 0e7654cfbf9885f1ca94446c3b220aeddb039af7892bc4ae94669512570ceefdf1ce6c2b0703396927d5a203d7c6c5ccaf8bd61426e52372aafb6ada64141055 + languageName: node + linkType: hard + +"@uniswap/v3-periphery@npm:1.1.0": + version: 1.1.0 + resolution: "@uniswap/v3-periphery@npm:1.1.0" + dependencies: + "@openzeppelin/contracts": 3.4.1-solc-0.7-2 + "@uniswap/lib": ^4.0.1-alpha + "@uniswap/v2-core": 1.0.1 + "@uniswap/v3-core": 1.0.0 + base64-sol: 1.0.1 + hardhat-watcher: ^2.1.1 + checksum: 8716e168ea04f56400a9affe579b06422778eb6c096e8d4117f6ff43aec232d224be9799f3ad3adb421894f330b17548956cf25fb5a8f95ae291e5b311ec427f + languageName: node + linkType: hard + "@yarnpkg/lockfile@npm:^1.1.0": version: 1.1.0 resolution: "@yarnpkg/lockfile@npm:1.1.0" @@ -2008,6 +2122,8 @@ __metadata: resolution: "@zetachain/protocol-contracts@workspace:packages/protocol-contracts" dependencies: "@openzeppelin/contracts": ^4.5.0 + "@uniswap/v2-periphery": 1.1.0-beta.0 + "@uniswap/v3-periphery": 1.1.0 ethers: 5.6.8 tsconfig-paths: ^3.14.1 languageName: unknown @@ -2108,7 +2224,7 @@ __metadata: languageName: node linkType: hard -"acorn-jsx@npm:^5.3.1": +"acorn-jsx@npm:^5.2.0, acorn-jsx@npm:^5.3.1": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" peerDependencies: @@ -2124,6 +2240,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^7.1.1": + version: 7.4.1 + resolution: "acorn@npm:7.4.1" + bin: + acorn: bin/acorn + checksum: 1860f23c2107c910c6177b7b7be71be350db9e1080d814493fae143ae37605189504152d1ba8743ba3178d0b37269ce1ffc42b101547fdc1827078f82671e407 + languageName: node + linkType: hard + "acorn@npm:^8.4.1": version: 8.7.1 resolution: "acorn@npm:8.7.1" @@ -3259,6 +3384,13 @@ __metadata: languageName: node linkType: hard +"base64-sol@npm:1.0.1": + version: 1.0.1 + resolution: "base64-sol@npm:1.0.1" + checksum: be0f9e8cf3c744256913223fbae8187773f530cc096e98a77f49ef0bd6cedeb294d15a784e439419f7cb99f07bf85b08999169feafafa1a9e29c3affc0bc6d0a + languageName: node + linkType: hard + "base@npm:^0.11.1": version: 0.11.2 resolution: "base@npm:0.11.2" @@ -3884,7 +4016,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:3.5.3, chokidar@npm:^3.4.0": +"chokidar@npm:3.5.3, chokidar@npm:^3.4.0, chokidar@npm:^3.5.3": version: 3.5.3 resolution: "chokidar@npm:3.5.3" dependencies: @@ -5255,6 +5387,33 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-sort-keys-fix@npm:1.1.2": + version: 1.1.2 + resolution: "eslint-plugin-sort-keys-fix@npm:1.1.2" + dependencies: + espree: ^6.1.2 + esutils: ^2.0.2 + natural-compare: ^1.4.0 + requireindex: ~1.2.0 + checksum: d089e4ec4f8cea4ec239132f85c807ce71e500370e3ef778d684697da5011b413e625d6f5b9bf3b5b1cf7c4eafffc39d386bffca271b7c3e7983409fb0d7d2c6 + languageName: node + linkType: hard + +"eslint-plugin-typescript-sort-keys@npm:2.1.0": + version: 2.1.0 + resolution: "eslint-plugin-typescript-sort-keys@npm:2.1.0" + dependencies: + "@typescript-eslint/experimental-utils": ^5.0.0 + json-schema: ^0.4.0 + natural-compare-lite: ^1.4.0 + peerDependencies: + "@typescript-eslint/parser": ^1 || ^2 || ^3 || ^4 || ^5 + eslint: ^5 || ^6 || ^7 || ^8 + typescript: ^3 || ^4 + checksum: 98ea442519ca9cdd36aa4d7d4e4f3d96d16a9287e32ea69209865ff3cd10ce33d9c4acb86ddc2cc0406a072aea319841496e66b512f79c58aaf3924d85f73683 + languageName: node + linkType: hard + "eslint-scope@npm:^5.1.1": version: 5.1.1 resolution: "eslint-scope@npm:5.1.1" @@ -5361,6 +5520,17 @@ __metadata: languageName: node linkType: hard +"espree@npm:^6.1.2": + version: 6.2.1 + resolution: "espree@npm:6.2.1" + dependencies: + acorn: ^7.1.1 + acorn-jsx: ^5.2.0 + eslint-visitor-keys: ^1.1.0 + checksum: 99c508950b5b9f53d008d781d2abb7a4ef3496ea699306fb6eb737c7e513aa594644314364c50ec27abb220124c6851fff64a6b62c358479534369904849360b + languageName: node + linkType: hard + "espree@npm:^9.3.1": version: 9.3.1 resolution: "espree@npm:9.3.1" @@ -7037,7 +7207,7 @@ __metadata: languageName: node linkType: hard -"globby@npm:^11.0.4": +"globby@npm:^11.0.4, globby@npm:^11.1.0": version: 11.1.0 resolution: "globby@npm:11.1.0" dependencies: @@ -7154,6 +7324,17 @@ __metadata: languageName: node linkType: hard +"hardhat-watcher@npm:^2.1.1": + version: 2.3.0 + resolution: "hardhat-watcher@npm:2.3.0" + dependencies: + chokidar: ^3.5.3 + peerDependencies: + hardhat: ^2.0.0 + checksum: 9c99c8f3f476990b4b8f452d5257d2060bc0bbe21e24cd4472c0f2eb63fd71c28ca054353ccdf27947198303be768b12185ffd902d470d7a8e750945a4249eb1 + languageName: node + linkType: hard + "hardhat@npm:2.9.7": version: 2.9.7 resolution: "hardhat@npm:2.9.7" @@ -8345,7 +8526,7 @@ __metadata: languageName: node linkType: hard -"json-schema@npm:0.4.0": +"json-schema@npm:0.4.0, json-schema@npm:^0.4.0": version: 0.4.0 resolution: "json-schema@npm:0.4.0" checksum: 66389434c3469e698da0df2e7ac5a3281bcff75e797a5c127db7c5b56270e01ae13d9afa3c03344f76e32e81678337a8c912bdbb75101c62e487dc3778461d72 @@ -9004,6 +9185,15 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^6.0.0": + version: 6.0.0 + resolution: "lru-cache@npm:6.0.0" + dependencies: + yallist: ^4.0.0 + checksum: f97f499f898f23e4585742138a22f22526254fdba6d75d41a1c2526b3b6cc5747ef59c5612ba7375f42aca4f8461950e925ba08c991ead0651b4918b7c978297 + languageName: node + linkType: hard + "lru-cache@npm:^7.4.0, lru-cache@npm:^7.7.1": version: 7.8.1 resolution: "lru-cache@npm:7.8.1" @@ -9719,6 +9909,13 @@ __metadata: languageName: node linkType: hard +"natural-compare-lite@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare-lite@npm:1.4.0" + checksum: 5222ac3986a2b78dd6069ac62cbb52a7bf8ffc90d972ab76dfe7b01892485d229530ed20d0c62e79a6b363a663b273db3bde195a1358ce9e5f779d4453887225 + languageName: node + linkType: hard + "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -11221,6 +11418,13 @@ __metadata: languageName: node linkType: hard +"requireindex@npm:~1.2.0": + version: 1.2.0 + resolution: "requireindex@npm:1.2.0" + checksum: 50d8b10a1ff1fdf6aea7a1870bc7bd238b0fb1917d8d7ca17fd03afc38a65dcd7a8a4eddd031f89128b5f0065833d5c92c4fef67f2c04e8624057fe626c9cf94 + languageName: node + linkType: hard + "resolve-from@npm:^3.0.0": version: 3.0.0 resolution: "resolve-from@npm:3.0.0" @@ -11589,6 +11793,17 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.3.7": + version: 7.3.7 + resolution: "semver@npm:7.3.7" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: 2fa3e877568cd6ce769c75c211beaed1f9fce80b28338cadd9d0b6c40f2e2862bafd62c19a6cff42f3d54292b7c623277bcab8816a2b5521cf15210d43e75232 + languageName: node + linkType: hard + "semver@npm:~5.4.1": version: 5.4.1 resolution: "semver@npm:5.4.1" @@ -14545,6 +14760,8 @@ __metadata: eslint-plugin-prettier: ^4.0.0 eslint-plugin-promise: ^6.0.0 eslint-plugin-simple-import-sort: 7.0.0 + eslint-plugin-sort-keys-fix: 1.1.2 + eslint-plugin-typescript-sort-keys: 2.1.0 ethereum-waffle: ^3.4.4 ethereumjs-utils: ^5.2.5 ethers: 5.6.8