From b667f438ee0a3cc0a33342d6c82597c59019a986 Mon Sep 17 00:00:00 2001 From: Mikhail Mikheev Date: Fri, 4 Oct 2019 17:09:29 +0400 Subject: [PATCH 1/4] Fix sending TX for tokens with decimals other than 24 --- .../components/Balances/SendModal/screens/ReviewTx/index.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.jsx b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.jsx index 2dd89813..b9d3b666 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.jsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.jsx @@ -1,5 +1,6 @@ // @flow import React from 'react' +import { BigNumber } from 'bignumber.js' import OpenInNew from '@material-ui/icons/OpenInNew' import { withStyles } from '@material-ui/core/styles' import Close from '@material-ui/icons/Close' @@ -68,6 +69,8 @@ const ReviewTx = ({ if (!isSendingETH) { const StandardToken = await getStandardTokenContract() const tokenInstance = await StandardToken.at(tx.token.address) + const decimals = await tokenInstance.decimals() + txAmount = new BigNumber(tx.amount).div(10 ** decimals).toString() txData = tokenInstance.contract.methods.transfer(tx.recipientAddress, txAmount).encodeABI() // txAmount should be 0 if we send tokens From 19ce5abd48daed2b6f4b2d1d28a5e48b34c82526 Mon Sep 17 00:00:00 2001 From: Mikhail Mikheev Date: Fri, 4 Oct 2019 17:35:24 +0400 Subject: [PATCH 2/4] Fix a fix for sending TX for tokens with decimals other than 24 --- .../Balances/SendModal/screens/ReviewTx/index.jsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.jsx b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.jsx index b9d3b666..f72e1905 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.jsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.jsx @@ -18,7 +18,7 @@ import { copyToClipboard } from '~/utils/clipboard' import Hairline from '~/components/layout/Hairline' import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo' import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils' -import { getStandardTokenContract } from '~/logic/tokens/store/actions/fetchTokens' +import { getStandardTokenContract, getHumanFriendlyToken } from '~/logic/tokens/store/actions/fetchTokens' import { EMPTY_DATA } from '~/logic/wallets/ethTransactions' import { getWeb3 } from '~/logic/wallets/getWeb3' import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions' @@ -68,9 +68,11 @@ const ReviewTx = ({ if (!isSendingETH) { const StandardToken = await getStandardTokenContract() + const HumanFriendlyToken = await getHumanFriendlyToken() const tokenInstance = await StandardToken.at(tx.token.address) - const decimals = await tokenInstance.decimals() - txAmount = new BigNumber(tx.amount).div(10 ** decimals).toString() + const hfTokenInstance = await HumanFriendlyToken.at(tx.token.address) + const decimals = await hfTokenInstance.decimals() + txAmount = new BigNumber(tx.amount).times(10 ** decimals.toNumber()).toString() txData = tokenInstance.contract.methods.transfer(tx.recipientAddress, txAmount).encodeABI() // txAmount should be 0 if we send tokens From 17d42a29b55da7377677197124a441880f03ef43 Mon Sep 17 00:00:00 2001 From: mmv Date: Mon, 14 Oct 2019 17:13:15 +0400 Subject: [PATCH 3/4] Add test case for sending token with 6 decimals, wrap moveFunds helper fireEvent in act() --- contracts/DevDependencies.sol | 1 + .../ExpandedTx/TxDescription/index.jsx | 2 +- src/test/contracts/Token6Decimals.sol | 17 ++++++ src/test/safe.dom.funds.threshold=1.test.js | 59 +++++++++++++++++-- src/test/utils/tokenMovements.js | 13 ++++ .../utils/transactions/moveFunds.helper.js | 20 ++++--- 6 files changed, 99 insertions(+), 13 deletions(-) create mode 100644 src/test/contracts/Token6Decimals.sol diff --git a/contracts/DevDependencies.sol b/contracts/DevDependencies.sol index e94e2c7c..a0738ef3 100644 --- a/contracts/DevDependencies.sol +++ b/contracts/DevDependencies.sol @@ -2,5 +2,6 @@ pragma solidity ^0.5.2; import "../src/test/contracts/TokenOMG.sol"; import "../src/test/contracts/TokenRDN.sol"; +import "../src/test/contracts/Token6Decimals.sol"; contract DevDependenciesGetter {} \ No newline at end of file diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/index.jsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/index.jsx index 70fbfe14..2e94c9af 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/index.jsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/index.jsx @@ -46,7 +46,7 @@ type CustomDescProps = { value: string, recipient: string, data: String, - classes: Obeject, + classes: Object, } const TransferDescription = ({ value = '', symbol, recipient }: TransferDescProps) => ( diff --git a/src/test/contracts/Token6Decimals.sol b/src/test/contracts/Token6Decimals.sol new file mode 100644 index 00000000..e0a8417a --- /dev/null +++ b/src/test/contracts/Token6Decimals.sol @@ -0,0 +1,17 @@ +pragma solidity ^0.5.2; + +import "@gnosis.pm/util-contracts/contracts/GnosisStandardToken.sol"; + +contract Token6Decimals is GnosisStandardToken { + string public constant symbol = "6DEC"; + string public constant name = "6 Decimals"; + uint8 public constant decimals = 6; + + constructor( + uint amount + ) + public + { + balances[msg.sender] = amount; + } +} \ No newline at end of file diff --git a/src/test/safe.dom.funds.threshold=1.test.js b/src/test/safe.dom.funds.threshold=1.test.js index e4c0f90f..3e708648 100644 --- a/src/test/safe.dom.funds.threshold=1.test.js +++ b/src/test/safe.dom.funds.threshold=1.test.js @@ -1,17 +1,19 @@ // @flow import { fireEvent } from '@testing-library/react' -import { Map, Set } from 'immutable' +import { Map, Set, List } from 'immutable' import { aNewStore } from '~/store' import { aMinedSafe } from '~/test/builder/safe.redux.builder' -import { sendTokenTo, sendEtherTo } from '~/test/utils/tokenMovements' +import { sendTokenTo, sendEtherTo, get6DecimalsTokenContract } from '~/test/utils/tokenMovements' import { renderSafeView } from '~/test/builder/safe.dom.utils' import { getWeb3, getBalanceInEtherOf } from '~/logic/wallets/getWeb3' import { dispatchAddTokenToList } from '~/test/utils/transactions/moveTokens.helper' import { sleep } from '~/utils/timer' +import saveTokens from '~/logic/tokens/store/actions/saveTokens' import { calculateBalanceOf } from '~/routes/safe/store/actions/fetchTokenBalances' import updateActiveTokens from '~/routes/safe/store/actions/updateActiveTokens' import '@testing-library/jest-dom/extend-expect' import updateSafe from '~/routes/safe/store/actions/updateSafe' +import { makeToken } from '~/logic/tokens/store/model/token' import { checkRegisteredTxSend, fillAndSubmitSendFundsForm } from './utils/transactions' import { BALANCE_ROW_TEST_ID } from '~/routes/safe/components/Balances' @@ -21,7 +23,6 @@ describe('DOM > Feature > Sending Funds', () => { let accounts beforeEach(async () => { store = aNewStore() - // using 4th account because other accounts were used in other tests and paid gas safeAddress = await aMinedSafe(store) accounts = await getWeb3().eth.getAccounts() }) @@ -62,7 +63,7 @@ describe('DOM > Feature > Sending Funds', () => { await checkRegisteredTxSend(SafeDom, ethAmount, 'ETH', accounts[9]) }) - it('Sends Tokens with threshold = 1', async () => { + it('Sends Tokens with 18 decimals with threshold = 1', async () => { // GIVEN const tokensAmount = '100' const tokenReceiver = accounts[1] @@ -102,4 +103,54 @@ describe('DOM > Feature > Sending Funds', () => { // Check that the transaction was registered await checkRegisteredTxSend(SafeDom, tokensAmount, 'OMG', tokenReceiver) }) + + it('Sends Tokens with decimals other than 18 with threshold = 1', async () => { + // GIVEN + const tokensAmount = '1000000' + const tokenReceiver = accounts[1] + const web3 = await getWeb3() + const SixDecimalsToken = await get6DecimalsTokenContract(web3, accounts[0]) + const tokenList = List([ + makeToken({ + address: SixDecimalsToken.address, + name: '6 Decimals', + symbol: '6DEC', + decimals: 6, + logoUri: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png', + }), + ]) + await store.dispatch(saveTokens(tokenList)) + + await SixDecimalsToken.contract.methods.transfer(safeAddress, tokensAmount).send({ from: accounts[0] }) + + // WHEN + const SafeDom = await renderSafeView(store, safeAddress) + await sleep(1300) + + // Activate token + const safeTokenBalance = await calculateBalanceOf(SixDecimalsToken.address, safeAddress, 6) + expect(safeTokenBalance).toBe('1') + + const balances = Map({ + [SixDecimalsToken.address]: safeTokenBalance, + }) + + store.dispatch(updateActiveTokens(safeAddress, Set([SixDecimalsToken.address]))) + store.dispatch(updateSafe({ address: safeAddress, balances })) + await sleep(1000) + + // Open send funds modal + const balanceRows = SafeDom.getAllByTestId(BALANCE_ROW_TEST_ID) + expect(balanceRows.length).toBe(2) + const sendButtons = SafeDom.getAllByTestId('balance-send-btn') + expect(sendButtons.length).toBe(2) + + await fillAndSubmitSendFundsForm(SafeDom, sendButtons[1], '1', tokenReceiver) + + // THEN + const safeFunds = await calculateBalanceOf(SixDecimalsToken.address, safeAddress, 6) + expect(Number(safeFunds)).toBe(0) + const receiverFunds = await calculateBalanceOf(SixDecimalsToken.address, tokenReceiver, 6) + expect(receiverFunds).toBe('1') + }) }) diff --git a/src/test/utils/tokenMovements.js b/src/test/utils/tokenMovements.js index b02095ea..3a9ce93e 100644 --- a/src/test/utils/tokenMovements.js +++ b/src/test/utils/tokenMovements.js @@ -5,6 +5,7 @@ import { ensureOnce } from '~/utils/singleton' import { toNative } from '~/logic/wallets/tokens' import TokenOMG from '../../../build/contracts/TokenOMG' import TokenRDN from '../../../build/contracts/TokenRDN' +import Token6Decimals from '../../../build/contracts/Token6Decimals.json' export const sendEtherTo = async (address: string, eth: string, fromAccountIndex: number = 0) => { const web3 = getWeb3() @@ -41,8 +42,20 @@ const createTokenRDNContract = async (web3: any, creator: string) => { return token.new(amount, { from: creator }) } +const create6DecimalsTokenContract = async (web3: any, creator: string) => { + const token = contract(Token6Decimals) + const { toBN } = web3.utils + const amount = toBN(50000) + .mul(toBN(10).pow(toBN(6))) + .toString() + token.setProvider(web3.currentProvider) + + return token.new(amount, { from: creator }) +} + export const getFirstTokenContract = ensureOnce(createTokenOMGContract) export const getSecondTokenContract = ensureOnce(createTokenRDNContract) +export const get6DecimalsTokenContract = ensureOnce(create6DecimalsTokenContract) export const sendTokenTo = async (safe: string, value: string, tokenContract?: any) => { const web3 = getWeb3() diff --git a/src/test/utils/transactions/moveFunds.helper.js b/src/test/utils/transactions/moveFunds.helper.js index d33d6402..b742a50e 100644 --- a/src/test/utils/transactions/moveFunds.helper.js +++ b/src/test/utils/transactions/moveFunds.helper.js @@ -1,6 +1,6 @@ // @flow import * as React from 'react' -import { fireEvent, waitForElement } from '@testing-library/react' +import { fireEvent, waitForElement, act } from '@testing-library/react' import { sleep } from '~/utils/timer' export const fillAndSubmitSendFundsForm = async ( @@ -9,8 +9,9 @@ export const fillAndSubmitSendFundsForm = async ( value: string, recipient: string, ) => { - // load add multisig form component - fireEvent.click(sendButton) + await act(async () => { + fireEvent.click(sendButton) + }) // give time to re-render it await sleep(400) @@ -18,13 +19,16 @@ export const fillAndSubmitSendFundsForm = async ( const recipientInput = SafeDom.getByPlaceholderText('Recipient*') const amountInput = SafeDom.getByPlaceholderText('Amount*') const reviewBtn = SafeDom.getByTestId('review-tx-btn') - fireEvent.change(recipientInput, { target: { value: recipient } }) - fireEvent.change(amountInput, { target: { value } }) - await sleep(200) - fireEvent.click(reviewBtn) + await act(async () => { + fireEvent.change(recipientInput, { target: { value: recipient } }) + fireEvent.change(amountInput, { target: { value } }) + fireEvent.click(reviewBtn) + }) // Submit the tx (Review Tx screen) const submitBtn = await waitForElement(() => SafeDom.getByTestId('submit-tx-btn')) - fireEvent.click(submitBtn) + await act(async () => { + fireEvent.click(submitBtn) + }) await sleep(1000) } From 2b6f33ccda1bffded1f07012752fed0c1a872900 Mon Sep 17 00:00:00 2001 From: mmv Date: Mon, 14 Oct 2019 17:19:31 +0400 Subject: [PATCH 4/4] Replace StandardToken with HumanFriendlyToken when encoding token transfer data --- .../Balances/SendModal/screens/ReviewTx/index.jsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.jsx b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.jsx index f72e1905..93b58c18 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.jsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.jsx @@ -18,7 +18,7 @@ import { copyToClipboard } from '~/utils/clipboard' import Hairline from '~/components/layout/Hairline' import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo' import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils' -import { getStandardTokenContract, getHumanFriendlyToken } from '~/logic/tokens/store/actions/fetchTokens' +import { getHumanFriendlyToken } from '~/logic/tokens/store/actions/fetchTokens' import { EMPTY_DATA } from '~/logic/wallets/ethTransactions' import { getWeb3 } from '~/logic/wallets/getWeb3' import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions' @@ -67,11 +67,9 @@ const ReviewTx = ({ let txAmount = web3.utils.toWei(tx.amount, 'ether') if (!isSendingETH) { - const StandardToken = await getStandardTokenContract() const HumanFriendlyToken = await getHumanFriendlyToken() - const tokenInstance = await StandardToken.at(tx.token.address) - const hfTokenInstance = await HumanFriendlyToken.at(tx.token.address) - const decimals = await hfTokenInstance.decimals() + const tokenInstance = await HumanFriendlyToken.at(tx.token.address) + const decimals = await tokenInstance.decimals() txAmount = new BigNumber(tx.amount).times(10 ** decimals.toNumber()).toString() txData = tokenInstance.contract.methods.transfer(tx.recipientAddress, txAmount).encodeABI()