Merge pull request #223 from gnosis/bug/218-non-18-decimals-tokens-transfer
BUG: Hotfix #218 Non-18 decimals tokens transfer
This commit is contained in:
commit
c3f4732aa5
|
@ -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 {}
|
|
@ -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'
|
||||
|
@ -17,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 { 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'
|
||||
|
@ -66,8 +67,10 @@ const ReviewTx = ({
|
|||
let txAmount = web3.utils.toWei(tx.amount, 'ether')
|
||||
|
||||
if (!isSendingETH) {
|
||||
const StandardToken = await getStandardTokenContract()
|
||||
const tokenInstance = await StandardToken.at(tx.token.address)
|
||||
const HumanFriendlyToken = await getHumanFriendlyToken()
|
||||
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()
|
||||
// txAmount should be 0 if we send tokens
|
||||
|
|
|
@ -46,7 +46,7 @@ type CustomDescProps = {
|
|||
value: string,
|
||||
recipient: string,
|
||||
data: String,
|
||||
classes: Obeject,
|
||||
classes: Object,
|
||||
}
|
||||
|
||||
const TransferDescription = ({ value = '', symbol, recipient }: TransferDescProps) => (
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
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')
|
||||
await act(async () => {
|
||||
fireEvent.change(recipientInput, { target: { value: recipient } })
|
||||
fireEvent.change(amountInput, { target: { value } })
|
||||
await sleep(200)
|
||||
fireEvent.click(reviewBtn)
|
||||
})
|
||||
|
||||
// Submit the tx (Review Tx screen)
|
||||
const submitBtn = await waitForElement(() => SafeDom.getByTestId('submit-tx-btn'))
|
||||
await act(async () => {
|
||||
fireEvent.click(submitBtn)
|
||||
})
|
||||
await sleep(1000)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue