From 917cf5a54a158d0ebd5ec69314036be07429e1f7 Mon Sep 17 00:00:00 2001 From: Manuel Gellfart Date: Mon, 17 May 2021 19:21:06 +0200 Subject: [PATCH] Checks for sufficient balances after uploading a csv (#54) * warn about insufficient balance for the transfer * new util methods to ** create summary map covering all transfers ** check for sufficient balances of all tokens in a summary map This commit is the first to use the web3 connection using the SafeProvider. We do not need an Infuria key anymore because we connect with the SafeProvider. Todo: Tests Small changes: * Header placement and gap between multiple warnings * add testcases * testcase for creating transfer summary * testcase for invalid csvs for 100% coverage on parser.ts * Fix linter error * fixe review issues * comparing balances in wei to avoid precision loss * some format issues / tiny refactorings * handle errors while fetching balance for erc20 * Errors are logged and balances are returned as invalid (-1) Important change: * typings for our web3 calls using TypeChain project * types are generated in postinstall yarn script Co-authored-by: schmanu --- .gitignore | 3 + package.json | 11 ++- src/App.tsx | 38 ++++++---- src/__tests__/parser.test.ts | 14 ++++ src/__tests__/utils.test.ts | 133 ++++++++++++++++++++++++++++++++++- src/components/Header.tsx | 15 +++- src/erc20.ts | 9 +-- src/utils.ts | 96 +++++++++++++++++++++++++ yarn.lock | 97 ++++++++++++++++++++++++- 9 files changed, 391 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 21cb71a..e5fde71 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ yarn-error.log* #vscode environment /.vscode + +# auto-generated code +/src/contracts diff --git a/package.json b/package.json index 83d25ed..14d54fb 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,9 @@ "eject": "react-scripts eject", "fmt": "prettier --check '**/*.ts'", "fmt:write": "prettier --write '**/*.ts'", - "prepare": "husky install" + "prepare": "husky install", + "generate-types": "typechain --target=ethers-v5 --out-dir src/contracts './node_modules/@openzeppelin/contracts/build/contracts/IERC20.json'", + "postinstall": "yarn generate-types" }, "dependencies": { "@fast-csv/parse": "^4.3.6", @@ -40,12 +42,16 @@ }, "devDependencies": { "@testing-library/jest-dom": "^5.11.4", + "@typechain/ethers-v5": "^7.0.0", + "@types/chai": "^4.2.18", + "@types/chai-as-promised": "^7.1.4", "@types/node": "^14.14.45", "@types/react": "^17.0.5", "@types/react-dom": "^17.0.5", "@types/styled-components": "^5.1.2", "babel-eslint": "^10.1.0", "chai": "^4.3.4", + "chai-as-promised": "^7.1.1", "eslint-config-prettier": "^8.3.0", "eslint-config-react-app": "^6.0.0", "eslint-plugin-flowtype": "^5.7.2", @@ -56,7 +62,8 @@ "eslint-plugin-react-hooks": "^4.2.0", "husky": "^6.0.0", "prettier": "^2.3.0", - "pretty-quick": "^3.1.0" + "pretty-quick": "^3.1.0", + "typechain": "^5.0.0" }, "browserslist": { "production": [ diff --git a/src/App.tsx b/src/App.tsx index ac4ec35..e2e9faf 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,8 @@ +import { SafeAppProvider } from "@gnosis.pm/safe-apps-provider"; import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; -// TODO - Will need for web3Provider -// import { useMemo } from "react"; -// import { SafeAppProvider } from "@gnosis.pm/safe-apps-provider"; -// import { ethers } from "ethers"; import { Loader, Text } from "@gnosis.pm/safe-react-components"; -import React, { useCallback, useState, useContext } from "react"; +import { ethers } from "ethers"; +import React, { useCallback, useState, useContext, useMemo } from "react"; import styled from "styled-components"; import { CSVForm } from "./components/CSVForm"; @@ -13,29 +11,43 @@ import { MessageContext } from "./contexts/MessageContextProvider"; import { useTokenList } from "./hooks/tokenList"; import { parseCSV, Payment } from "./parser"; import { buildTransfers } from "./transfers"; +import { checkAllBalances, transfersToSummary } from "./utils"; const App: React.FC = () => { - const { sdk } = useSafeAppsSDK(); + const { safe, sdk } = useSafeAppsSDK(); + const { tokenList, isLoading } = useTokenList(); const [submitting, setSubmitting] = useState(false); const [transferContent, setTransferContent] = useState([]); const [csvText, setCsvText] = useState( "token_address,receiver,amount,decimals" ); - const { addMessage, setCodeWarnings } = useContext(MessageContext); + const { addMessage, setCodeWarnings, setMessages } = + useContext(MessageContext); - // const web3Provider = useMemo( - // () => new ethers.providers.Web3Provider(new SafeAppProvider(safe, sdk)), - // [sdk, safe] - // ); + const web3Provider = useMemo( + () => new ethers.providers.Web3Provider(new SafeAppProvider(safe, sdk)), + [sdk, safe] + ); const onChangeTextHandler = useCallback( - async (csvText: string) => { + (csvText: string) => { setCsvText(csvText); // Parse CSV const parsePromise = parseCSV(csvText, tokenList); parsePromise .then(([transfers, warnings]) => { + console.log("CSV parsed!"); + const summary = transfersToSummary(transfers); + checkAllBalances(summary, web3Provider, safe, tokenList).then( + (insufficientBalances) => + setMessages( + insufficientBalances.map((insufficientBalanceInfo) => ({ + message: `Insufficient Balance: ${insufficientBalanceInfo.transferAmount} of ${insufficientBalanceInfo.token}`, + severity: "warning", + })) + ) + ); setTransferContent(transfers); setCodeWarnings(warnings); }) @@ -43,7 +55,7 @@ const App: React.FC = () => { addMessage({ severity: "error", message: reason.message }) ); }, - [addMessage, setCodeWarnings, tokenList] + [addMessage, safe, setCodeWarnings, setMessages, tokenList, web3Provider] ); const submitTx = useCallback(async () => { diff --git a/src/__tests__/parser.test.ts b/src/__tests__/parser.test.ts index 9c06b81..b5e928f 100644 --- a/src/__tests__/parser.test.ts +++ b/src/__tests__/parser.test.ts @@ -1,7 +1,9 @@ /* eslint-disable @typescript-eslint/no-unused-expressions */ import { TokenInfo } from "@uniswap/token-lists"; import { BigNumber } from "bignumber.js"; +import * as chai from "chai"; import { expect } from "chai"; +import chaiAsPromised from "chai-as-promised"; import { TokenMap, fetchTokenList } from "../hooks/tokenList"; import { parseCSV } from "../parser"; @@ -14,6 +16,9 @@ let listedToken: TokenInfo; const validReceiverAddress = testData.addresses.receiver1; const unlistedTokenAddress = testData.unlistedToken.address; +// this lets us handle expectations on Promises. +chai.use(chaiAsPromised); + /** * concatenates csv row arrays into one string. * @param rows array of row-arrays @@ -29,6 +34,15 @@ describe("Parsing CSVs ", () => { listedTokens = Array.from(tokenList.keys()); listedToken = tokenList.get(listedTokens[0]); }); + + it("should throw errors for invalid CSVs", async () => { + // thins csv contains more values than headers in row1 + const invalidCSV = "head1,header2\nvalue1,value2,value3"; + expect(parseCSV(invalidCSV, tokenList)).to.be.rejectedWith( + "column header mismatch expected: 2 columns got: 3" + ); + }); + it("should transform simple, valid CSVs correctly", async () => { const rowWithoutDecimal = [listedToken.address, validReceiverAddress, "1"]; const rowWithDecimal = [ diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts index acde7a0..5edaa75 100644 --- a/src/__tests__/utils.test.ts +++ b/src/__tests__/utils.test.ts @@ -1,7 +1,9 @@ import { BigNumber } from "bignumber.js"; import { expect } from "chai"; -import { fromWei, toWei, TEN, ONE, ZERO } from "../utils"; +import { Payment } from "../parser"; +import { testData } from "../test/util"; +import { fromWei, toWei, TEN, ONE, ZERO, transfersToSummary } from "../utils"; // TODO - this is super ugly at the moment and is probably missing some stuff. describe("toWei()", () => { @@ -40,3 +42,132 @@ describe("fromWei()", () => { expect(fromWei(oneETH, 20).toFixed()).to.be.equal("0.01"); }); }); + +describe("transerToSummary()", () => { + it("works for integer native currency", () => { + const transfers: Payment[] = [ + { + tokenAddress: null, + amount: new BigNumber(1), + receiver: testData.addresses.receiver1, + }, + { + tokenAddress: null, + amount: new BigNumber(2), + receiver: testData.addresses.receiver2, + }, + { + tokenAddress: null, + amount: new BigNumber(3), + receiver: testData.addresses.receiver3, + }, + ]; + const summary = transfersToSummary(transfers); + expect(summary.get(null).amount.toFixed()).to.equal("6"); + }); + + it("works for decimals in native currency", () => { + const transfers: Payment[] = [ + { + tokenAddress: null, + amount: new BigNumber(0.1), + receiver: testData.addresses.receiver1, + }, + { + tokenAddress: null, + amount: new BigNumber(0.01), + receiver: testData.addresses.receiver2, + }, + { + tokenAddress: null, + amount: new BigNumber(0.001), + receiver: testData.addresses.receiver3, + }, + ]; + const summary = transfersToSummary(transfers); + expect(summary.get(null).amount.toFixed()).to.equal("0.111"); + }); + + it("works for decimals in erc20", () => { + const transfers: Payment[] = [ + { + tokenAddress: testData.unlistedToken.address, + amount: new BigNumber(0.1), + receiver: testData.addresses.receiver1, + }, + { + tokenAddress: testData.unlistedToken.address, + amount: new BigNumber(0.01), + receiver: testData.addresses.receiver2, + }, + { + tokenAddress: testData.unlistedToken.address, + amount: new BigNumber(0.001), + receiver: testData.addresses.receiver3, + }, + ]; + const summary = transfersToSummary(transfers); + expect( + summary.get(testData.unlistedToken.address).amount.toFixed() + ).to.equal("0.111"); + }); + + it("works for integer in erc20", () => { + const transfers: Payment[] = [ + { + tokenAddress: testData.unlistedToken.address, + amount: new BigNumber(1), + receiver: testData.addresses.receiver1, + }, + { + tokenAddress: testData.unlistedToken.address, + amount: new BigNumber(2), + receiver: testData.addresses.receiver2, + }, + { + tokenAddress: testData.unlistedToken.address, + amount: new BigNumber(3), + receiver: testData.addresses.receiver3, + }, + ]; + const summary = transfersToSummary(transfers); + expect( + summary.get(testData.unlistedToken.address).amount.toFixed() + ).to.equal("6"); + }); + + it("works for mixed payments", () => { + const transfers: Payment[] = [ + { + tokenAddress: testData.unlistedToken.address, + amount: new BigNumber(1.1), + receiver: testData.addresses.receiver1, + }, + { + tokenAddress: testData.unlistedToken.address, + amount: new BigNumber(2), + receiver: testData.addresses.receiver2, + }, + { + tokenAddress: testData.unlistedToken.address, + amount: new BigNumber(3.3), + receiver: testData.addresses.receiver3, + }, + { + tokenAddress: null, + amount: new BigNumber(3), + receiver: testData.addresses.receiver1, + }, + { + tokenAddress: null, + amount: new BigNumber(0.33), + receiver: testData.addresses.receiver1, + }, + ]; + const summary = transfersToSummary(transfers); + expect( + summary.get(testData.unlistedToken.address).amount.toFixed() + ).to.equal("6.4"); + expect(summary.get(null).amount.toFixed()).to.equal("3.33"); + }); +}); diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 7f698bd..67cea45 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -14,6 +14,14 @@ const HeaderContainer = styled.div` width: 100%; `; +const AlertWrapper = styled.div` + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; + width: 100%; +`; + export const Header = (): JSX.Element => { const messageContext = useContext(MessageContext); const messages = messageContext.messages; @@ -22,12 +30,13 @@ export const Header = (): JSX.Element => { CSV Airdrop {messages?.length > 0 && ( 0} onClose={() => messageContext.setMessages([])} autoHideDuration={6000} + style={{ gap: "4px" }} > -
+ {messages.map((message: Message, index: number) => ( { {message.message} ))} -
+
)} diff --git a/src/erc20.ts b/src/erc20.ts index 73b4d6e..f4a54de 100644 --- a/src/erc20.ts +++ b/src/erc20.ts @@ -1,11 +1,12 @@ -import IERC20 from "@openzeppelin/contracts/build/contracts/IERC20.json"; import { ethers } from "ethers"; -export const erc20Interface = new ethers.utils.Interface(IERC20.abi); +import { IERC20, IERC20__factory } from "./contracts"; + +export const erc20Interface = IERC20__factory.createInterface(); export function erc20Instance( address: string, provider: ethers.providers.Provider -): ethers.Contract { - return new ethers.Contract(address, erc20Interface, provider); +): IERC20 { + return IERC20__factory.connect(address, provider); } diff --git a/src/utils.ts b/src/utils.ts index 286c604..2899cc8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,10 @@ +import { SafeInfo } from "@gnosis.pm/safe-apps-sdk"; import { BigNumber } from "bignumber.js"; +import { ethers, utils } from "ethers"; + +import { erc20Instance } from "./erc20"; +import { TokenMap } from "./hooks/tokenList"; +import { Payment } from "./parser"; export const ZERO = new BigNumber(0); export const ONE = new BigNumber(1); @@ -26,3 +32,93 @@ export function toWei( export function fromWei(amount: BigNumber, decimals: number): BigNumber { return amount.dividedBy(TEN.pow(decimals)); } + +export type SummaryEntry = { + tokenAddress: string; + amount: BigNumber; + decimals: number; +}; + +export const transfersToSummary = (transfers: Payment[]) => { + return transfers.reduce((previousValue, currentValue): Map< + string, + SummaryEntry + > => { + let tokenSummary = previousValue.get(currentValue.tokenAddress); + if (typeof tokenSummary === "undefined") { + tokenSummary = { + tokenAddress: currentValue.tokenAddress, + amount: new BigNumber(0), + decimals: currentValue.decimals, + }; + previousValue.set(currentValue.tokenAddress, tokenSummary); + } + tokenSummary.amount = tokenSummary.amount.plus(currentValue.amount); + + return previousValue; + }, new Map()); +}; + +export type InsufficientBalanceInfo = { + token: string; + transferAmount: string; +}; + +export const checkAllBalances = async ( + summary: Map, + web3Provider: ethers.providers.Web3Provider, + safe: SafeInfo, + tokenList: TokenMap +): Promise => { + const insufficientTokens: InsufficientBalanceInfo[] = []; + for (const { tokenAddress, amount, decimals } of summary.values()) { + if (tokenAddress === null) { + // Check ETH Balance + const tokenBalance = await web3Provider.getBalance( + safe.safeAddress, + "latest" + ); + if (!isSufficientBalance(tokenBalance, amount, 18)) { + insufficientTokens.push({ + token: "ETH", + transferAmount: amount.toFixed(), + }); + } + } else { + const erc20Contract = erc20Instance( + utils.getAddress(tokenAddress), + web3Provider + ); + const tokenBalance = await erc20Contract + .balanceOf(safe.safeAddress) + .catch((reason) => { + console.error(reason); + return ethers.BigNumber.from(-1); + }); + const tokenInfo = tokenList.get(tokenAddress); + if ( + !isSufficientBalance( + tokenBalance, + amount, + tokenInfo?.decimals || decimals + ) + ) { + insufficientTokens.push({ + token: tokenInfo?.symbol || tokenAddress, + transferAmount: amount.toFixed(), + }); + } + } + } + return insufficientTokens; +}; + +const isSufficientBalance = ( + tokenBalance: ethers.BigNumber, + transferAmount: BigNumber, + decimals: number +) => { + const tokenBalanceNumber = new BigNumber(tokenBalance.toString()); + const transferAmountInWei = toWei(transferAmount, decimals); + return tokenBalanceNumber.gte(transferAmountInWei); +}; diff --git a/yarn.lock b/yarn.lock index 4fb4954..6b48bcb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2157,6 +2157,11 @@ lodash "^4.17.15" redent "^3.0.0" +"@typechain/ethers-v5@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-7.0.0.tgz#cadb5262b3827d1616c21f4ba86a36a71269bd7e" + integrity sha512-ykNaqYcQ1yC928x8bogL9LECUg0osfqqHCKBhP7qbGlNfvC/bvTiIfnjQUgXUYWEJRx5r0Y78vcKMo8F3sJTBA== + "@types/anymatch@*": version "1.3.1" resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" @@ -2195,6 +2200,18 @@ dependencies: "@babel/types" "^7.3.0" +"@types/chai-as-promised@^7.1.4": + version "7.1.4" + resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.4.tgz#caf64e76fb056b8c8ced4b761ed499272b737601" + integrity sha512-1y3L1cHePcIm5vXkh1DSGf/zQq5n5xDKG1fpCvf18+uOkpce0Z1ozNFPkyWsVswK7ntN1sZBw3oU6gmN+pDUcA== + dependencies: + "@types/chai" "*" + +"@types/chai@*", "@types/chai@^4.2.18": + version "4.2.18" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.18.tgz#0c8e298dbff8205e2266606c1ea5fbdba29b46e4" + integrity sha512-rS27+EkB/RE1Iz3u0XtVL5q36MGDWbgYe7zWiodyKNUnthxY0rukK5V36eiUCtCisB7NN8zKYH6DO2M37qxFEQ== + "@types/eslint@^7.2.6": version "7.2.10" resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.10.tgz#4b7a9368d46c0f8cd5408c23288a59aa2394d917" @@ -2310,7 +2327,7 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/prettier@^2.0.0": +"@types/prettier@^2.0.0", "@types/prettier@^2.1.1": version "2.2.3" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.3.tgz#ef65165aea2924c9359205bf748865b8881753c0" integrity sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA== @@ -2917,6 +2934,20 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= +array-back@^1.0.3, array-back@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-1.0.4.tgz#644ba7f095f7ffcf7c43b5f0dc39d3c1f03c063b" + integrity sha1-ZEun8JX3/898Q7Xw3DnTwfA8Bjs= + dependencies: + typical "^2.6.0" + +array-back@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-2.0.0.tgz#6877471d51ecc9c9bfa6136fb6c7d5fe69748022" + integrity sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw== + dependencies: + typical "^2.6.1" + array-differ@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" @@ -3762,6 +3793,13 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= +chai-as-promised@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz#08645d825deb8696ee61725dbf590c012eb00ca0" + integrity sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA== + dependencies: + check-error "^1.0.2" + chai@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.4.tgz#b55e655b31e1eac7099be4c08c21964fce2e6c49" @@ -4010,6 +4048,15 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +command-line-args@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-4.0.7.tgz#f8d1916ecb90e9e121eda6428e41300bfb64cc46" + integrity sha512-aUdPvQRAyBvQd2n7jXcsMDz68ckBJELXNzBybCHOibUWEg0mWTnaYCSRU8h9R+aNRSvDihJtssSRCiDRpLaezA== + dependencies: + array-back "^2.0.0" + find-replace "^1.0.3" + typical "^2.6.1" + commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -5724,6 +5771,14 @@ find-cache-dir@^3.3.1: make-dir "^3.0.2" pkg-dir "^4.1.0" +find-replace@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-1.0.3.tgz#b88e7364d2d9c959559f388c66670d6130441fa0" + integrity sha1-uI5zZNLZyVlVnziMZmcNYTBEH6A= + dependencies: + array-back "^1.0.4" + test-value "^2.1.0" + find-up@4.1.0, find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -7431,6 +7486,11 @@ js-sha3@0.5.7: resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" integrity sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc= +js-sha3@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -9700,7 +9760,7 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@^2.3.0: +prettier@^2.1.2, prettier@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.0.tgz#b6a5bf1284026ae640f17f7ff5658a7567fc0d18" integrity sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w== @@ -11553,6 +11613,14 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +test-value@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/test-value/-/test-value-2.1.0.tgz#11da6ff670f3471a73b625ca4f3fdcf7bb748291" + integrity sha1-Edpv9nDzRxpztiXKTz/c97t0gpE= + dependencies: + array-back "^1.0.3" + typical "^2.6.0" + text-table@0.2.0, text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -11674,6 +11742,11 @@ tryer@^1.0.1: resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA== +ts-essentials@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-7.0.1.tgz#d205508cae0cdadfb73c89503140cf2228389e2d" + integrity sha512-8lwh3QJtIc1UWhkQtr9XuksXu3O0YQdEE5g79guDfhCaU1FWTDIEDZ1ZSx4HTHUmlJZ8L812j3BZQ4a0aOUkSA== + ts-pnp@1.2.0, ts-pnp@^1.1.6: version "1.2.0" resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" @@ -11785,6 +11858,21 @@ type@^2.0.0: resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== +typechain@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/typechain/-/typechain-5.0.0.tgz#730e5fb4709964eed3db03be332b38ba6eaa1d9f" + integrity sha512-Ko2/8co0FUmPUkaXPcb8PC3ncWa5P72nvkiNMgcomd4OAInltJlITF0kcW2cZmI2sFkvmaHV5TZmCnOHgo+i5Q== + dependencies: + "@types/prettier" "^2.1.1" + command-line-args "^4.0.7" + debug "^4.1.1" + fs-extra "^7.0.0" + glob "^7.1.6" + js-sha3 "^0.8.0" + lodash "^4.17.15" + prettier "^2.1.2" + ts-essentials "^7.0.1" + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -11802,6 +11890,11 @@ typescript@~4.2.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== +typical@^2.6.0, typical@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/typical/-/typical-2.6.1.tgz#5c080e5d661cbbe38259d2e70a3c7253e873881d" + integrity sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0= + unbox-primitive@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"