From 848bc3bfeac424b828fee2f873f95e05b7caa663 Mon Sep 17 00:00:00 2001 From: Manuel Gellfart Date: Fri, 11 Feb 2022 13:59:50 +0100 Subject: [PATCH] Fixes #357 and #358 (#359) * Fixes #357 and #358 * Ignores tokens without address in token lists * Allows to use amount or value column header Other changes * test for column header backward compatibility * fixes bug for native asset drain transfers * renames value => amount for default column headers * generated transfers (drain) use amount instead of value Co-authored-by: schmanu --- public/sample.csv | 2 +- src/__tests__/parser.test.ts | 60 ++++++++++++------- src/__tests__/transfers.test.ts | 6 +- src/components/CSVForm.tsx | 4 +- src/components/CSVUpload.tsx | 1 - src/components/FAQModal.tsx | 2 +- src/components/GenerateTransfersMenu.tsx | 4 +- .../assets/CollectiblesTransferTable.tsx | 2 +- src/contexts/MessageContextProvider.tsx | 1 - src/hooks/token.ts | 4 +- src/parser/csvParser.ts | 3 +- src/parser/transformation.ts | 8 +-- src/parser/validation.ts | 10 ++-- src/transfers/transfers.ts | 6 +- 14 files changed, 67 insertions(+), 46 deletions(-) diff --git a/public/sample.csv b/public/sample.csv index b1599a2e..2a266ff9 100644 --- a/public/sample.csv +++ b/public/sample.csv @@ -1,4 +1,4 @@ -token_type,token_address,receiver,value,id +token_type,token_address,receiver,amount,id erc20,0x6810e776880c02933d47db1b9fc05908e5386b96,0x1000000000000000000000000000000000000000,0.0001 erc20,0x6b175474e89094c44da98b954eedeac495271d0f,0x2000000000000000000000000000000000000000,0.0001 native,,0x3000000000000000000000000000000000000000,0.0001 diff --git a/src/__tests__/parser.test.ts b/src/__tests__/parser.test.ts index e1a4f2fc..338e2d42 100644 --- a/src/__tests__/parser.test.ts +++ b/src/__tests__/parser.test.ts @@ -23,7 +23,7 @@ chai.use(chaiAsPromised); * @param rows array of row-arrays */ const csvStringFromRows = (...rows: string[][]): string => { - const headerRow = "token_type,token_address,receiver,value,id"; + const headerRow = "token_type,token_address,receiver,amount,id"; return [headerRow, ...rows.map((row) => row.join(","))].join("\n"); }; @@ -197,7 +197,7 @@ describe("Parsing CSVs ", () => { ] = warnings; expect(payment).to.be.empty; - expect(warningNegativeAmount.message).to.equal("Only positive amounts possible: -1"); + expect(warningNegativeAmount.message).to.equal("Only positive amounts/values possible: -1"); expect(warningNegativeAmount.lineNo).to.equal(1); expect(warningTokenNotFound.message.toLowerCase()).to.equal( @@ -270,22 +270,22 @@ describe("Parsing CSVs ", () => { payment as CollectibleTransfer[]; expect(transferErc721AndAddress.receiver).to.equal(validReceiverAddress); expect(transferErc721AndAddress.tokenAddress).to.equal(testData.addresses.dummyErc721Address); - expect(transferErc721AndAddress.value).to.be.undefined; + expect(transferErc721AndAddress.amount).to.be.undefined; expect(transferErc721AndAddress.tokenId.isEqualTo(new BigNumber(1))).to.be.true; expect(transferErc721AndAddress.receiverEnsName).to.be.null; expect(transferErc721AndENS.receiver).to.equal(testData.addresses.receiver2); expect(transferErc721AndENS.tokenAddress).to.equal(testData.addresses.dummyErc721Address); expect(transferErc721AndENS.tokenId.isEqualTo(new BigNumber(69))).to.be.true; - expect(transferErc721AndENS.value).to.be.undefined; + expect(transferErc721AndENS.amount).to.be.undefined; expect(transferErc721AndENS.receiverEnsName).to.equal("receiver2.eth"); expect(transferErc1155AndAddress.receiver).to.equal(validReceiverAddress); expect(transferErc1155AndAddress.tokenAddress.toLowerCase()).to.equal( testData.addresses.dummyErc1155Address.toLowerCase(), ); - expect(transferErc1155AndAddress.value).not.to.be.undefined; - expect(transferErc1155AndAddress.value?.isEqualTo(new BigNumber(69))).to.be.true; + expect(transferErc1155AndAddress.amount).not.to.be.undefined; + expect(transferErc1155AndAddress.amount?.isEqualTo(new BigNumber(69))).to.be.true; expect(transferErc1155AndAddress.tokenId.isEqualTo(new BigNumber(420))).to.be.true; expect(transferErc1155AndAddress.receiverEnsName).to.be.null; @@ -293,8 +293,8 @@ describe("Parsing CSVs ", () => { expect(transferErc1155AndENS.tokenAddress.toLowerCase()).to.equal( testData.addresses.dummyErc1155Address.toLowerCase(), ); - expect(transferErc1155AndENS.value).not.to.be.undefined; - expect(transferErc1155AndENS.value?.isEqualTo(new BigNumber(9))).to.be.true; + expect(transferErc1155AndENS.amount).not.to.be.undefined; + expect(transferErc1155AndENS.amount?.isEqualTo(new BigNumber(9))).to.be.true; expect(transferErc1155AndENS.tokenId.isEqualTo(new BigNumber(99))).to.be.true; expect(transferErc1155AndENS.receiverEnsName).to.equal("receiver3.eth"); }); @@ -424,19 +424,39 @@ describe("Parsing CSVs ", () => { expect(warningErc721WithInvalidReceiver.message).to.equal("Invalid Receiver Address: 0xwhoopsie"); }); - it("fallback to erc20 without token_type", async () => { - const missingTokenType = ["", listedToken.address, validReceiverAddress, "15"]; + describe("Support backward compatibility", () => { + it("fallback to erc20 without token_type", async () => { + const missingTokenType = ["", listedToken.address, validReceiverAddress, "15"]; - const [payment, warnings] = await CSVParser.parseCSV( - csvStringFromRows(missingTokenType), - mockTokenInfoProvider, - mockCollectibleTokenInfoProvider, - mockEnsResolver, - ); - expect(warnings).to.be.empty; - expect(payment).to.have.length(1); - const [erc20Transfer] = payment as AssetTransfer[]; + const [payment, warnings] = await CSVParser.parseCSV( + csvStringFromRows(missingTokenType), + mockTokenInfoProvider, + mockCollectibleTokenInfoProvider, + mockEnsResolver, + ); + expect(warnings).to.be.empty; + expect(payment).to.have.length(1); + const [erc20Transfer] = payment as AssetTransfer[]; + + expect(erc20Transfer.token_type).to.equal("erc20"); + }); + + it("allow value instead of amount column", async () => { + const nativeTransfer = ["native", listedToken.address, validReceiverAddress, "15"]; + const headerRow = "token_type,token_address,receiver,value,id"; + const csvString = [headerRow, nativeTransfer.join(",")].join("\n"); + + const [payment, warnings] = await CSVParser.parseCSV( + csvString, + mockTokenInfoProvider, + mockCollectibleTokenInfoProvider, + mockEnsResolver, + ); + expect(warnings).to.be.empty; + expect(payment).to.have.length(1); + const [nativeTransferData] = payment as AssetTransfer[]; - expect(erc20Transfer.token_type).to.equal("erc20"); + expect(nativeTransferData.amount.isEqualTo(new BigNumber(15))).to.be.true; + }); }); }); diff --git a/src/__tests__/transfers.test.ts b/src/__tests__/transfers.test.ts index ac58b2c3..1966168b 100644 --- a/src/__tests__/transfers.test.ts +++ b/src/__tests__/transfers.test.ts @@ -138,7 +138,7 @@ describe("Build Transfers:", () => { }); describe("Mixed", () => { - it("works with arbitrary amount strings on listed, unlisted and native transfers", () => { + it("works with arbitrary value strings on listed, unlisted and native transfers", () => { const mixedAmount = new BigNumber("123456.000000789"); const mixedPayments: AssetTransfer[] = [ // Listed ERC20 @@ -209,7 +209,7 @@ describe("Build Transfers:", () => { const payment: AssetTransfer = { token_type: "erc20", receiver, - amount: amount, + amount, tokenAddress: crappyToken.address, decimals: crappyToken.decimals, symbol: "BTC", @@ -243,7 +243,7 @@ describe("Build Transfers:", () => { receiverEnsName: null, tokenAddress: testData.addresses.dummyErc1155Address, tokenName: "Test MultiToken", - value: new BigNumber("69"), + amount: new BigNumber("69"), tokenId: new BigNumber("420"), hasMetaData: false, }, diff --git a/src/components/CSVForm.tsx b/src/components/CSVForm.tsx index e68b9fe9..0200e916 100644 --- a/src/components/CSVForm.tsx +++ b/src/components/CSVForm.tsx @@ -29,7 +29,7 @@ export interface CSVFormProps { export const CSVForm = (props: CSVFormProps): JSX.Element => { const { updateTransferTable, setParsing } = props; - const [csvText, setCsvText] = useState("token_type,token_address,receiver,value,id"); + const [csvText, setCsvText] = useState("token_type,token_address,receiver,amount,id"); const { setCodeWarnings, setMessages } = useContext(MessageContext); @@ -133,7 +133,7 @@ export const CSVForm = (props: CSVFormProps): JSX.Element => { a CSV file in a single transaction. - Upload, edit or paste your asset transfer CSV
(token_type,token_address,receiver,value,id) + Upload, edit or paste your asset transfer CSV
(token_type,token_address,receiver,amount,id)
diff --git a/src/components/CSVUpload.tsx b/src/components/CSVUpload.tsx index 36fdc808..9af60502 100644 --- a/src/components/CSVUpload.tsx +++ b/src/components/CSVUpload.tsx @@ -12,7 +12,6 @@ export const CSVUpload = (props: CSVUploadProps): JSX.Element => { const onDrop = useCallback( (acceptedFiles: File[]) => { acceptedFiles.forEach((file) => { - console.log("Received Filename", file.name); const reader = new FileReader(); reader.onload = function (evt) { if (!evt.target) { diff --git a/src/components/FAQModal.tsx b/src/components/FAQModal.tsx index bf700bc9..1f46734c 100644 --- a/src/components/FAQModal.tsx +++ b/src/components/FAQModal.tsx @@ -60,7 +60,7 @@ export const FAQModal: () => JSX.Element = () => {
  • - value + amount : the amount of token to be transferred. This can be left blank for erc721 transfers.
  • diff --git a/src/components/GenerateTransfersMenu.tsx b/src/components/GenerateTransfersMenu.tsx index 735f96c7..53d4f7be 100644 --- a/src/components/GenerateTransfersMenu.tsx +++ b/src/components/GenerateTransfersMenu.tsx @@ -32,13 +32,13 @@ export const GenerateTransfersMenu = (props: GenerateTransfersMenuProps): JSX.El const error = drainAddress ? invalidNetworkError || invalidAddressError : ""; const generateDrainTransfers = () => { - let drainCSV = "token_type,token_address,receiver,value,id,"; + let drainCSV = "token_type,token_address,receiver,amount,id,"; if (drainAddress) { assetBalance?.forEach((asset) => { if (asset.token === null && asset.tokenAddress === null) { const decimalBalance = fromWei(new BigNumber(asset.balance), 18); // The API returns zero balances for the native token. - if (!decimalBalance.isZero) { + if (!decimalBalance.isZero()) { drainCSV += `\nnative,,${drainAddress},${decimalBalance},`; } } else { diff --git a/src/components/assets/CollectiblesTransferTable.tsx b/src/components/assets/CollectiblesTransferTable.tsx index c05a9199..78e20fad 100644 --- a/src/components/assets/CollectiblesTransferTable.tsx +++ b/src/components/assets/CollectiblesTransferTable.tsx @@ -80,7 +80,7 @@ export const Row = memo((props: RowProps) => {
    - {row.value?.toFixed()} + {row.amount?.toFixed()}
    {row.tokenId.toFixed()} diff --git a/src/contexts/MessageContextProvider.tsx b/src/contexts/MessageContextProvider.tsx index 6c6e5d59..b552eea9 100644 --- a/src/contexts/MessageContextProvider.tsx +++ b/src/contexts/MessageContextProvider.tsx @@ -45,7 +45,6 @@ export const MessageContextProvider = (props: MessageContextProviderProps) => { const [showMessages, setShowMessages] = useState(false); const removeMessage = (messageToRemove: Message | CodeWarning) => { - console.log("Removing Message"); setMessages(messages.filter((message) => message.message !== messageToRemove.message)); }; diff --git a/src/hooks/token.ts b/src/hooks/token.ts index 2fa68db7..973af8f1 100644 --- a/src/hooks/token.ts +++ b/src/hooks/token.ts @@ -14,7 +14,9 @@ export type TokenMap = Map; function tokenMap(tokenList: TokenInfo[]): TokenMap { const res: TokenMap = new Map(); for (const token of tokenList) { - res.set(utils.getAddress(token.address), token); + if (token.address) { + res.set(utils.getAddress(token.address), token); + } } return res; } diff --git a/src/parser/csvParser.ts b/src/parser/csvParser.ts index 9c70224e..30ded0b2 100644 --- a/src/parser/csvParser.ts +++ b/src/parser/csvParser.ts @@ -36,7 +36,7 @@ export interface CollectibleTransfer { tokenAddress: string; tokenName?: string; tokenId: BigNumber; - value?: BigNumber; + amount?: BigNumber; receiverEnsName: string | null; hasMetaData: boolean; } @@ -50,6 +50,7 @@ export type CSVRow = { token_address: string; receiver: string; value?: string; + amount?: string; id?: string; }; diff --git a/src/parser/transformation.ts b/src/parser/transformation.ts index 0313229b..8f1f74c2 100644 --- a/src/parser/transformation.ts +++ b/src/parser/transformation.ts @@ -20,7 +20,7 @@ interface PreCollectibleTransfer { tokenId: BigNumber; tokenAddress: string; tokenType: "nft"; - value?: BigNumber; + amount?: BigNumber; } export const transform = ( @@ -83,7 +83,7 @@ export const transformAsset = ( const prePayment: PrePayment = { // avoids errors from getAddress. Invalid addresses are later caught in validateRow tokenAddress: transformERC20TokenAddress(row.token_address), - amount: new BigNumber(row.value ?? ""), + amount: new BigNumber(row.amount ?? row.value ?? ""), receiver: normalizeAddress(trimMatchingNetwork(row.receiver, selectedChainShortname)), tokenType: row.token_type, }; @@ -161,7 +161,7 @@ export const transformCollectible = ( tokenId: new BigNumber(row.id ?? ""), receiver: normalizeAddress(row.receiver), tokenType: row.token_type, - value: new BigNumber(row.value ?? ""), + amount: new BigNumber(row.amount ?? ""), }; toCollectibleTransfer(prePayment, erc721InfoProvider, ensResolver) @@ -206,7 +206,7 @@ const toCollectibleTransfer = async ( tokenId: preCollectible.tokenId, tokenAddress: preCollectible.tokenAddress, receiverEnsName, - value: preCollectible.value, + amount: preCollectible.amount, token_type: "erc1155", hasMetaData: tokenInfo.hasMetaInfo, }; diff --git a/src/parser/validation.ts b/src/parser/validation.ts index b7b008e2..aa5ed469 100644 --- a/src/parser/validation.ts +++ b/src/parser/validation.ts @@ -54,7 +54,7 @@ const areAddressesValid = (row: Transfer): string[] => { }; const isAmountPositive = (row: AssetTransfer): string[] => - row.amount.isGreaterThan(0) ? [] : ["Only positive amounts possible: " + row.amount.toFixed()]; + row.amount.isGreaterThan(0) ? [] : ["Only positive amounts/values possible: " + row.amount.toFixed()]; const isAssetTokenValid = (row: AssetTransfer): string[] => row.decimals === -1 && row.symbol === "TOKEN_NOT_FOUND" ? [`No token contract was found at ${row.tokenAddress}`] : []; @@ -69,11 +69,11 @@ const isTokenIdInteger = (row: CollectibleTransfer): string[] => row.tokenId.isInteger() ? [] : [`Token IDs must be integer numbers: ${row.tokenId.toFixed()}`]; const isTokenValueInteger = (row: CollectibleTransfer): string[] => - !row.value || row.value.isNaN() || row.value.isInteger() + !row.amount || row.amount.isNaN() || row.amount.isInteger() ? [] - : [`Value of ERC1155 must be an integer: ${row.value.toFixed()}`]; + : [`Value of ERC1155 must be an integer: ${row.amount.toFixed()}`]; const isTokenValueValid = (row: CollectibleTransfer): string[] => - row.token_type === "erc721" || (typeof row.value !== "undefined" && row.value.isGreaterThan(0)) + row.token_type === "erc721" || (typeof row.amount !== "undefined" && row.amount.isGreaterThan(0)) ? [] - : [`ERC1155 Tokens need a defined value > 0: ${row.value?.toFixed()}`]; + : [`ERC1155 Tokens need a defined value > 0: ${row.amount?.toFixed()}`]; diff --git a/src/transfers/transfers.ts b/src/transfers/transfers.ts index 002179e8..1451a966 100644 --- a/src/transfers/transfers.ts +++ b/src/transfers/transfers.ts @@ -20,11 +20,11 @@ export function buildAssetTransfers(transferData: AssetTransfer[]): BaseTransact } else { // ERC20 transfer const decimals = transfer.decimals; - const amountData = toWei(transfer.amount, decimals); + const valueData = toWei(transfer.amount, decimals); return { to: transfer.tokenAddress, value: "0", - data: erc20Interface.encodeFunctionData("transfer", [transfer.receiver, amountData.toFixed()]), + data: erc20Interface.encodeFunctionData("transfer", [transfer.receiver, valueData.toFixed()]), }; } }); @@ -51,7 +51,7 @@ export function buildCollectibleTransfers(transferData: CollectibleTransfer[]): transfer.from, transfer.receiver, transfer.tokenId.toFixed(), - transfer.value?.toFixed() ?? "0", + transfer.amount?.toFixed() ?? "0", ethers.utils.hexlify("0x00"), ]), };