(Feature) Add unit tests (#1230)

* Test for balances store

* Types for addressBook.ts

* Adds addressBookUtils.test.ts

* Remove duplicated type

* Tests for addressBookUtils

* Adds fetchSafeTokens.test.ts

* Adds getMockedSafeInstance and getMockedTxServiceModel in safeHelper.ts

* Adds shouldExecuteTransaction tests

* Fix types for TransactionProps
And getNewTxNonce

* Adds tests for getNewTxNonce and getLastTx

* Moves utils.test.tsx to /actions folder

* Placeholder for transactionHelpers tests

* isInnerTransaction tests

* calculateTransactionStatus tests

* Adds calculateTransactionType tests

* Adds buildTx test
Adds generateSafeTxHash test

* Absolute imports

* Adds types for getRefundParams

* Adds getRefundParams tests

* Add mock example for isInnerTransaction

* Adds isCancelTransaction tests
Adds isModifySettingsTransaction tests
Adds isMultiSendTransaction tests
Adds isUpgradeTransaction tests
Adds isOutgoingTransaction tests
Adds isCustomTransaction tests

* Adds types in mockNonPayableTransactionObject

* Add TODOS

* Fix shortVersionOf function

* Add ethAddresses.test.ts
Adds sameAddress test
Adds shortVersionOf test
Adds isUserAnOwner test

* Adds isUserAnOwnerOfAnySafe
Adds isValidEnsName

* Fix isERC721Contract

* Adds tokenHelpers.test.ts:
- getEthAsToken
- isTokenTransfer
- getERC20DecimalsAndSymbol
- isERC721Contract

* Fix eslint errors

* Remove unused files

* Use selectors in safeBalance tests

* Move file near his implementation

* Replaces resultExpected with expectedResult

* Update comment

* Reword tests

* Adds utility function description

* Merge with dev
Fix types

* Fix types

* Fix build types

* Mock contract

Co-authored-by: Daniel Sanchez <daniel.sanchez@gnosis.pm>
Co-authored-by: Mikhail Mikheev <mmvsha73@gmail.com>
This commit is contained in:
Agustin Pane 2020-09-14 12:59:28 -03:00 committed by GitHub
parent ac92f49c72
commit f1916e92f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1955 additions and 151 deletions

View File

@ -6,12 +6,10 @@ export interface AddressBookEntryProps {
isOwner: boolean
}
export type AddressBookEntryRecord = RecordOf<AddressBookEntryProps>
export const makeAddressBookEntry = Record<AddressBookEntryProps>({
address: '',
name: '',
isOwner: false,
})
export type AddressBookEntry = RecordOf<AddressBookEntryProps>
export type AddressBookEntryRecord = RecordOf<AddressBookEntryProps>

View File

@ -1,27 +1,31 @@
import { List, Map } from 'immutable'
import { handleActions } from 'redux-actions'
import { AddressBookEntry, makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook'
import {
AddressBookEntryRecord,
AddressBookEntryProps,
makeAddressBookEntry,
} from 'src/logic/addressBook/model/addressBook'
import { ADD_ADDRESS_BOOK } from 'src/logic/addressBook/store/actions/addAddressBook'
import { ADD_ENTRY } from 'src/logic/addressBook/store/actions/addAddressBookEntry'
import { ADD_OR_UPDATE_ENTRY } from 'src/logic/addressBook/store/actions/addOrUpdateAddressBookEntry'
import { LOAD_ADDRESS_BOOK } from 'src/logic/addressBook/store/actions/loadAddressBook'
import { REMOVE_ENTRY } from 'src/logic/addressBook/store/actions/removeAddressBookEntry'
import { UPDATE_ENTRY } from 'src/logic/addressBook/store/actions/updateAddressBookEntry'
import { getAddressesListFromAdbk } from 'src/logic/addressBook/utils'
import { getAddressesListFromSafeAddressBook } from 'src/logic/addressBook/utils'
import { sameAddress } from 'src/logic/wallets/ethAddresses'
import { checksumAddress } from 'src/utils/checksumAddress'
export const ADDRESS_BOOK_REDUCER_ID = 'addressBook'
export type AddressBookCollection = List<AddressBookEntry>
export type AddressBookCollection = List<AddressBookEntryRecord>
export type AddressBookState = Map<string, Map<string, AddressBookCollection>>
export const buildAddressBook = (storedAdbk) => {
let addressBookBuilt = Map([])
export const buildAddressBook = (storedAdbk: AddressBookEntryProps[]): Map<string, AddressBookCollection> => {
let addressBookBuilt: Map<string, AddressBookCollection> = Map([])
Object.entries(storedAdbk).forEach((adbkProps: any) => {
const safeAddress = checksumAddress(adbkProps[0])
const adbkRecords = adbkProps[1].map(makeAddressBookEntry)
const adbkRecords: AddressBookEntryRecord[] = adbkProps[1].map(makeAddressBookEntry)
const adbkSafeEntries = List(adbkRecords)
addressBookBuilt = addressBookBuilt.set(safeAddress, adbkSafeEntries)
})
@ -55,7 +59,7 @@ export default handleActions(
const safeAddressBook = state.getIn(['addressBook', safeAddress])
if (safeAddressBook) {
const adbkAddressList = getAddressesListFromAdbk(safeAddressBook)
const adbkAddressList = getAddressesListFromSafeAddressBook(safeAddressBook)
const found = adbkAddressList.includes(entry.address)
if (!found) {
const updatedSafeAdbkList = safeAddressBook.push(entry)

View File

@ -0,0 +1,101 @@
import { Map, List } from 'immutable'
import {
getAddressBookFromStorage,
getAddressesListFromSafeAddressBook,
getNameFromSafeAddressBook,
getOwnersWithNameFromAddressBook,
saveAddressBook,
} from 'src/logic/addressBook/utils/index'
import { buildAddressBook } from 'src/logic/addressBook/store/reducer/addressBook'
import { AddressBookEntryRecord, makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook'
const getMockAddressBookEntry = (
address: string,
name: string = 'test',
isOwner: boolean = false,
): AddressBookEntryRecord =>
makeAddressBookEntry({
address,
name,
isOwner,
})
describe('getAddressesListFromAdbk', () => {
const entry1 = getMockAddressBookEntry('123456', 'test1')
const entry2 = getMockAddressBookEntry('78910', 'test2')
const entry3 = getMockAddressBookEntry('4781321', 'test3')
it('It should returns the list of addresses within the addressBook given a safeAddressBook', () => {
// given
const safeAddressBook = List([entry1, entry2, entry3])
const expectedResult = [entry1.address, entry2.address, entry3.address]
// when
const result = getAddressesListFromSafeAddressBook(safeAddressBook)
// then
expect(result).toStrictEqual(expectedResult)
})
})
describe('getNameFromSafeAddressBook', () => {
const entry1 = getMockAddressBookEntry('123456', 'test1')
const entry2 = getMockAddressBookEntry('78910', 'test2')
const entry3 = getMockAddressBookEntry('4781321', 'test3')
it('It should returns the user name given a safeAddressBook and an user account', () => {
// given
const safeAddressBook = List([entry1, entry2, entry3])
const expectedResult = entry2.name
// when
const result = getNameFromSafeAddressBook(safeAddressBook, entry2.address)
// then
expect(result).toBe(expectedResult)
})
})
describe('getOwnersWithNameFromAddressBook', () => {
const entry1 = getMockAddressBookEntry('123456', 'test1')
const entry2 = getMockAddressBookEntry('78910', 'test2')
const entry3 = getMockAddressBookEntry('4781321', 'test3')
it('It should returns the list of owners with their names given a safeAddressBook and a list of owners', () => {
// given
const safeAddressBook = List([entry1, entry2, entry3])
const ownerList = List([
{ address: entry1.address, name: '' },
{ address: entry2.address, name: '' },
])
const expectedResult = List([
{ address: entry1.address, name: entry1.name },
{ address: entry2.address, name: entry2.name },
])
// when
const result = getOwnersWithNameFromAddressBook(safeAddressBook, ownerList)
// then
expect(result).toStrictEqual(expectedResult)
})
})
describe('saveAddressBook', () => {
const safeAddress1 = '0xdfA693da0D16F5E7E78FdCBeDe8FC6eBEa44f1Cf'
const safeAddress2 = '0x344B941b1aAE2e4Be73987212FC4741687Bf0503'
const entry1 = getMockAddressBookEntry('123456', 'test1')
const entry2 = getMockAddressBookEntry('78910', 'test2')
const entry3 = getMockAddressBookEntry('4781321', 'test3')
it('It should save a given addressBook to the localStorage', async () => {
// given
const addressBook = Map({ [safeAddress1]: List([entry1, entry2]), [safeAddress2]: List([entry3]) })
// when
// @ts-ignore
await saveAddressBook(addressBook)
const storedAdBk = await getAddressBookFromStorage()
let result = buildAddressBook(storedAdBk)
// then
expect(result).toStrictEqual(addressBook)
})
})

View File

@ -1,27 +1,28 @@
import { List } from 'immutable'
import { loadFromStorage, saveToStorage } from 'src/utils/storage'
import { AddressBookEntryProps } from './../model/addressBook'
import { AddressBookEntryRecord, AddressBookEntryProps } from '../model/addressBook'
import { SafeOwner } from 'src/logic/safe/store/models/safe'
import { AddressBookCollection } from '../store/reducer/addressBook'
import { AddressBookMap } from '../store/reducer/types/addressBook'
const ADDRESS_BOOK_STORAGE_KEY = 'ADDRESS_BOOK_STORAGE_KEY'
export const getAddressBookFromStorage = async (): Promise<Array<AddressBookEntryProps> | undefined> => {
const data = await loadFromStorage<Array<AddressBookEntryProps>>(ADDRESS_BOOK_STORAGE_KEY)
return data
return await loadFromStorage<Array<AddressBookEntryProps>>(ADDRESS_BOOK_STORAGE_KEY)
}
export const saveAddressBook = async (addressBook) => {
export const saveAddressBook = async (addressBook: AddressBookMap): Promise<void> => {
try {
await saveToStorage(ADDRESS_BOOK_STORAGE_KEY, addressBook.toJSON())
await saveToStorage(ADDRESS_BOOK_STORAGE_KEY, addressBook.toJS())
} catch (err) {
console.error('Error storing addressBook in localstorage', err)
}
}
export const getAddressesListFromAdbk = (addressBook) => Array.from(addressBook).map((entry: any) => entry.address)
export const getAddressesListFromSafeAddressBook = (addressBook: AddressBookCollection): string[] =>
Array.from(addressBook).map((entry: AddressBookEntryRecord) => entry.address)
export const getNameFromAdbk = (addressBook, userAddress) => {
export const getNameFromSafeAddressBook = (addressBook: AddressBookCollection, userAddress: string): string | null => {
const entry = addressBook.find((addressBookItem) => addressBookItem.address === userAddress)
if (entry) {
return entry.name
@ -30,18 +31,17 @@ export const getNameFromAdbk = (addressBook, userAddress) => {
}
export const getOwnersWithNameFromAddressBook = (
addressBook: AddressBookEntryProps,
addressBook: AddressBookCollection,
ownerList: List<SafeOwner>,
): List<SafeOwner> | [] => {
if (!ownerList) {
return []
}
const ownersListWithAdbkNames = ownerList.map((owner) => {
const ownerName = getNameFromAdbk(addressBook, owner.address)
return ownerList.map((owner) => {
const ownerName = getNameFromSafeAddressBook(addressBook, owner.address)
return {
address: owner.address,
name: ownerName || owner.name,
}
})
return ownersListWithAdbkNames
}

View File

@ -0,0 +1,53 @@
import { aNewStore } from 'src/store'
import fetchTokenCurrenciesBalances from 'src/logic/currencyValues/api/fetchTokenCurrenciesBalances'
import axios from 'axios'
import { getTxServiceHost } from 'src/config'
jest.mock('axios')
describe('fetchTokenCurrenciesBalances', () => {
let store
const safeAddress = '0xdfA693da0D16F5E7E78FdCBeDe8FC6eBEa44f1Cf'
beforeEach(async () => {
store = aNewStore()
})
afterAll(() => {
jest.unmock('axios')
})
it('Given a safe address, calls the API and returns token balances', async () => {
// given
const expectedResult = [
{
balance: '849890000000000000',
balanceUsd: '337.2449',
token: null,
tokenAddress: null,
usdConversion: '396.81',
},
{
balance: '24698677800000000000',
balanceUsd: '29.3432',
token: {
name: 'Dai',
symbol: 'DAI',
decimals: 18,
logoUri: 'https://gnosis-safe-token-logos.s3.amazonaws.com/0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa.png',
},
tokenAddress: '0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa',
usdConversion: '1.188',
},
]
const apiUrl = getTxServiceHost()
// @ts-ignore
axios.get.mockImplementationOnce(() => Promise.resolve(expectedResult))
// when
const result = await fetchTokenCurrenciesBalances(safeAddress)
// then
expect(result).toStrictEqual(expectedResult)
expect(axios.get).toHaveBeenCalled()
expect(axios.get).toBeCalledWith(`${apiUrl}safes/${safeAddress}/balances/usd/`, { params: { limit: 3000 } })
})
})

View File

@ -0,0 +1,875 @@
import { getMockedSafeInstance, getMockedTxServiceModel } from 'src/test/utils/safeHelper'
import { makeTransaction } from 'src/logic/safe/store/models/transaction'
import { TransactionStatus, TransactionTypes } from 'src/logic/safe/store/models/types/transaction'
import makeSafe from 'src/logic/safe/store/models/safe'
import { List, Map, Record } from 'immutable'
import { makeToken, TokenProps } from 'src/logic/tokens/store/model/token'
import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions'
import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses'
import {
buildTx,
calculateTransactionStatus,
calculateTransactionType,
generateSafeTxHash,
getRefundParams,
isCancelTransaction,
isCustomTransaction,
isInnerTransaction,
isModifySettingsTransaction,
isMultiSendTransaction,
isOutgoingTransaction,
isPendingTransaction,
isUpgradeTransaction,
} from 'src/logic/safe/store/actions/transactions/utils/transactionHelpers'
import { getERC20DecimalsAndSymbol } from 'src/logic/tokens/utils/tokenHelpers'
const safeAddress = '0xdfA693da0D16F5E7E78FdCBeDe8FC6eBEa44f1Cf'
const safeAddress2 = '0x344B941b1aAE2e4Be73987212FC4741687Bf0503'
describe('isInnerTransaction', () => {
it('It should return true if the transaction recipient is our given safeAddress and the txValue is 0', () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress, value: '0' })
// when
const result = isInnerTransaction(transaction, safeAddress)
// then
expect(result).toBe(true)
})
it('It should return false if the transaction recipient is our given safeAddress and the txValue is >0', () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress, value: '100' })
// when
const result = isInnerTransaction(transaction, safeAddress)
// then
expect(result).toBe(false)
})
it('It should return false if the transaction recipient is not our given safeAddress', () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress2, value: '0' })
// when
const result = isInnerTransaction(transaction, safeAddress)
// then
expect(result).toBe(false)
})
it('It should return true if the transaction recipient is the given safeAddress and the txValue is 0', () => {
// given
const transaction = makeTransaction({ recipient: safeAddress, value: '0' })
// when
const result = isInnerTransaction(transaction, safeAddress)
// then
expect(result).toBe(true)
})
it('It should return false if the transaction recipient is the given safeAddress and the txValue is >0', () => {
// given
const transaction = makeTransaction({ recipient: safeAddress, value: '100' })
// when
const result = isInnerTransaction(transaction, safeAddress)
// then
expect(result).toBe(false)
})
it('It should return false if the transaction recipient is not the given safeAddress', () => {
// given
const transaction = makeTransaction({ recipient: safeAddress2, value: '100' })
// when
const result = isInnerTransaction(transaction, safeAddress)
// then
expect(result).toBe(false)
})
})
describe('isCancelTransaction', () => {
const safeAddress = '0xdfA693da0D16F5E7E78FdCBeDe8FC6eBEa44f1Cf'
it('It should return false if given a inner transaction with empty data', () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress, value: '0', data: null })
// when
const result = isCancelTransaction(transaction, safeAddress)
// then
expect(result).toBe(true)
})
it('It should return false if given a inner transaction without empty data', () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress, value: '0', data: 'test' })
// when
const result = isCancelTransaction(transaction, safeAddress)
// then
expect(result).toBe(false)
})
})
describe('isPendingTransaction', () => {
it('It should return true if the transaction is on pending status', () => {
// given
const transaction = makeTransaction({ status: TransactionStatus.PENDING })
const cancelTx = makeTransaction({ data: null })
// when
const result = isPendingTransaction(transaction, cancelTx)
// then
expect(result).toBe(true)
})
it('It should return true If the transaction is not pending status but the cancellation transaction is', () => {
// given
const transaction = makeTransaction({ status: TransactionStatus.AWAITING_CONFIRMATIONS })
const cancelTx = makeTransaction({ status: TransactionStatus.PENDING })
// when
const result = isPendingTransaction(transaction, cancelTx)
// then
expect(result).toBe(true)
})
it('It should return true If the transaction and a cancellation transaction are not pending', () => {
// given
const transaction = makeTransaction({ status: TransactionStatus.CANCELLED })
const cancelTx = makeTransaction({ status: TransactionStatus.AWAITING_CONFIRMATIONS })
// when
const result = isPendingTransaction(transaction, cancelTx)
// then
expect(result).toBe(false)
})
})
describe('isModifySettingsTransaction', () => {
const safeAddress = '0xdfA693da0D16F5E7E78FdCBeDe8FC6eBEa44f1Cf'
it('It should return true if given an inner transaction without empty data', () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress, value: '0', data: 'test' })
// when
const result = isModifySettingsTransaction(transaction, safeAddress)
// then
expect(result).toBe(true)
})
it('It should return false if given an inner transaction with empty data', () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress, value: '0', data: null })
// when
const result = isModifySettingsTransaction(transaction, safeAddress)
// then
expect(result).toBe(false)
})
})
describe('isMultiSendTransaction', () => {
it('It should return true if given a transaction without value, the data has multisend data', () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress, value: '0', data: '0x8d80ff0a' })
// when
const result = isMultiSendTransaction(transaction)
// then
expect(result).toBe(true)
})
it('It should return false if given a transaction without data', () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress, value: '0', data: null })
// when
const result = isMultiSendTransaction(transaction)
// then
expect(result).toBe(false)
})
it('It should return true if given a transaction without value, the data has not multisend substring', () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress, value: '0', data: 'thisiswrongdata' })
// when
const result = isMultiSendTransaction(transaction)
// then
expect(result).toBe(false)
})
})
describe('isUpgradeTransaction', () => {
it('If should return true if the transaction data is empty', () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress, value: '0', data: null })
// when
const result = isUpgradeTransaction(transaction)
// then
expect(result).toBe(false)
})
it('It should return false if the transaction data is multisend transaction but does not have upgradeTx function signature encoded in data', () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress, value: '0', data: '0x8d80ff0a' })
// when
const result = isUpgradeTransaction(transaction)
// then
expect(result).toBe(false)
})
it('It should return true if the transaction data is multisend transaction and has upgradeTx enconded in function signature data', () => {
// given
const upgradeTxData = `0x8d80ff0a000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000f200dfa693da0d16f5e7e78fdcbede8fc6ebea44f1cf000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247de7edef000000000000000000000000d5d82b6addc9027b22dca772aa68d5d74cdbdf4400dfa693da0d16f5e7e78fdcbede8fc6ebea44f1cf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f08a032300000000000000000000000034cfac646f301356faa8b21e94227e3583fe3f5f0000000000000000000000000000`
const transaction = getMockedTxServiceModel({ to: safeAddress, value: '0', data: upgradeTxData })
// when
const result = isUpgradeTransaction(transaction)
// then
expect(result).toBe(true)
})
})
describe('isOutgoingTransaction', () => {
it('It should return true if the transaction recipient is not a safe address and data is not empty', () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress2, value: '0', data: 'test' })
// when
const result = isOutgoingTransaction(transaction, safeAddress)
// then
expect(result).toBe(true)
})
it('It should return true if the transaction has an address equal to the safe address', () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress, value: '0', data: 'test' })
// when
const result = isOutgoingTransaction(transaction, safeAddress)
// then
expect(result).toBe(false)
})
it('It should return false if the transaction recipient is not a safe address and data is empty', () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress, value: '0', data: null })
// when
const result = isOutgoingTransaction(transaction, safeAddress)
// then
expect(result).toBe(false)
})
})
jest.mock('src/logic/tokens/utils/tokenHelpers')
describe('isCustomTransaction', () => {
afterAll(() => {
jest.unmock('src/logic/tokens/utils/tokenHelpers')
})
it('It should return true if Is outgoing transaction, is not an erc20 transaction, not an upgrade transaction and not and erc721 transaction', async () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress2, value: '0', data: 'test' })
const txCode = ''
const knownTokens = Map<string, Record<TokenProps> & Readonly<TokenProps>>()
const token = makeToken({
address: '0x00Df91984582e6e96288307E9c2f20b38C8FeCE9',
name: 'OmiseGo',
symbol: 'OMG',
decimals: 18,
logoUri:
'https://github.com/TrustWallet/tokens/blob/master/images/0x6810e776880c02933d47db1b9fc05908e5386b96.png?raw=true',
})
knownTokens.set('0x00Df91984582e6e96288307E9c2f20b38C8FeCE9', token)
const txHelpers = require('src/logic/tokens/utils/tokenHelpers')
txHelpers.isSendERC20Transaction.mockImplementationOnce(() => false)
txHelpers.isSendERC721Transaction.mockImplementationOnce(() => false)
// when
const result = await isCustomTransaction(transaction, txCode, safeAddress, knownTokens)
// then
expect(result).toBe(true)
expect(txHelpers.isSendERC20Transaction).toHaveBeenCalled()
expect(txHelpers.isSendERC721Transaction).toHaveBeenCalled()
})
it('It should return true if is outgoing transaction, is not SendERC20Transaction, is not isUpgradeTransaction and not isSendERC721Transaction', async () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress2, value: '0', data: 'test' })
const txCode = ''
const knownTokens = Map<string, Record<TokenProps> & Readonly<TokenProps>>()
const token = makeToken({
address: '0x00Df91984582e6e96288307E9c2f20b38C8FeCE9',
name: 'OmiseGo',
symbol: 'OMG',
decimals: 18,
logoUri:
'https://github.com/TrustWallet/tokens/blob/master/images/0x6810e776880c02933d47db1b9fc05908e5386b96.png?raw=true',
})
knownTokens.set('0x00Df91984582e6e96288307E9c2f20b38C8FeCE9', token)
const txHelpers = require('src/logic/tokens/utils/tokenHelpers')
txHelpers.isSendERC20Transaction.mockImplementationOnce(() => false)
txHelpers.isSendERC721Transaction.mockImplementationOnce(() => false)
// when
const result = await isCustomTransaction(transaction, txCode, safeAddress, knownTokens)
// then
expect(result).toBe(true)
expect(txHelpers.isSendERC20Transaction).toHaveBeenCalled()
expect(txHelpers.isSendERC721Transaction).toHaveBeenCalled()
})
it('It should return false if is outgoing transaction, not SendERC20Transaction, isUpgradeTransaction and not isSendERC721Transaction', async () => {
// given
const upgradeTxData = `0x8d80ff0a000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000f200dfa693da0d16f5e7e78fdcbede8fc6ebea44f1cf000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247de7edef000000000000000000000000d5d82b6addc9027b22dca772aa68d5d74cdbdf4400dfa693da0d16f5e7e78fdcbede8fc6ebea44f1cf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f08a032300000000000000000000000034cfac646f301356faa8b21e94227e3583fe3f5f0000000000000000000000000000`
const transaction = getMockedTxServiceModel({ to: safeAddress2, value: '0', data: upgradeTxData })
const txCode = ''
const knownTokens = Map<string, Record<TokenProps> & Readonly<TokenProps>>()
const token = makeToken({
address: '0x00Df91984582e6e96288307E9c2f20b38C8FeCE9',
name: 'OmiseGo',
symbol: 'OMG',
decimals: 18,
logoUri:
'https://github.com/TrustWallet/tokens/blob/master/images/0x6810e776880c02933d47db1b9fc05908e5386b96.png?raw=true',
})
knownTokens.set('0x00Df91984582e6e96288307E9c2f20b38C8FeCE9', token)
const txHelpers = require('src/logic/tokens/utils/tokenHelpers')
txHelpers.isSendERC20Transaction.mockImplementationOnce(() => true)
txHelpers.isSendERC721Transaction.mockImplementationOnce(() => false)
// when
const result = await isCustomTransaction(transaction, txCode, safeAddress, knownTokens)
// then
expect(result).toBe(false)
expect(txHelpers.isSendERC20Transaction).toHaveBeenCalled()
})
it('It should return false if is outgoing transaction, is not SendERC20Transaction, not isUpgradeTransaction and isSendERC721Transaction', async () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress2, value: '0', data: 'test' })
const txCode = ''
const knownTokens = Map<string, Record<TokenProps> & Readonly<TokenProps>>()
const token = makeToken({
address: '0x00Df91984582e6e96288307E9c2f20b38C8FeCE9',
name: 'OmiseGo',
symbol: 'OMG',
decimals: 18,
logoUri:
'https://github.com/TrustWallet/tokens/blob/master/images/0x6810e776880c02933d47db1b9fc05908e5386b96.png?raw=true',
})
knownTokens.set('0x00Df91984582e6e96288307E9c2f20b38C8FeCE9', token)
const txHelpers = require('src/logic/tokens/utils/tokenHelpers')
txHelpers.isSendERC20Transaction.mockImplementationOnce(() => false)
txHelpers.isSendERC721Transaction.mockImplementationOnce(() => true)
// when
const result = await isCustomTransaction(transaction, txCode, safeAddress, knownTokens)
// then
expect(result).toBe(false)
expect(txHelpers.isSendERC20Transaction).toHaveBeenCalled()
expect(txHelpers.isSendERC721Transaction).toHaveBeenCalled()
})
})
describe('getRefundParams', () => {
it('It should return null if given a transaction with the gasPrice == 0', async () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress2, value: '0', gasPrice: '0' })
// when
const result = await getRefundParams(transaction, getERC20DecimalsAndSymbol)
// then
expect(result).toBe(null)
})
it('It should return 0.000000000000020000 if given a transaction with the gasPrice = 100, the baseGas = 100, the txGas = 100 and 18 decimals', async () => {
// given
const gasPrice = '100'
const baseGas = 100
const safeTxGas = 100
const decimals = 18
const transaction = getMockedTxServiceModel({ to: safeAddress2, value: '0', gasPrice, baseGas, safeTxGas })
const feeString = (Number(gasPrice) * (Number(baseGas) + Number(safeTxGas))).toString().padStart(decimals, '0')
const whole = feeString.slice(0, feeString.length - decimals) || '0'
const fraction = feeString.slice(feeString.length - decimals)
const expectedResult = {
fee: `${whole}.${fraction}`,
symbol: 'ETH',
}
const getTokenInfoMock = jest.fn().mockImplementation(() => {
return {
symbol: 'ETH',
decimals,
}
})
// when
const result = await getRefundParams(transaction, getTokenInfoMock)
// then
expect(result).toStrictEqual(expectedResult)
expect(getTokenInfoMock).toBeCalled()
})
it('Given a transaction with the gasPrice = 100, the baseGas = 100, the txGas = 100 and 1 decimal, returns 2000.0', async () => {
// given
const gasPrice = '100'
const baseGas = 100
const safeTxGas = 100
const decimals = 1
const transaction = getMockedTxServiceModel({ to: safeAddress2, value: '0', gasPrice, baseGas, safeTxGas })
const expectedResult = {
fee: `2000.0`,
symbol: 'ETH',
}
const getTokenInfoMock = jest.fn().mockImplementation(() => {
return {
symbol: 'ETH',
decimals,
}
})
// when
const result = await getRefundParams(transaction, getTokenInfoMock)
// then
expect(result).toStrictEqual(expectedResult)
expect(getTokenInfoMock).toBeCalled()
})
it('It should return 0.50000 if given a transaction with the gasPrice = 100, the baseGas = 100, the txGas = 400 and 5 decimals', async () => {
// given
const gasPrice = '100'
const baseGas = 100
const safeTxGas = 400
const decimals = 5
const transaction = getMockedTxServiceModel({ to: safeAddress2, value: '0', gasPrice, baseGas, safeTxGas })
const expectedResult = {
fee: `0.50000`,
symbol: 'ETH',
}
const getTokenInfoMock = jest.fn().mockImplementation(() => {
return {
symbol: 'ETH',
decimals,
}
})
// when
const result = await getRefundParams(transaction, getTokenInfoMock)
// then
expect(result).toStrictEqual(expectedResult)
expect(getTokenInfoMock).toBeCalled()
})
})
describe('getDecodedParams', () => {
it('', () => {
// given
// when
// then
})
})
describe('isTransactionCancelled', () => {
it('', () => {
// given
// when
// then
})
})
describe('calculateTransactionStatus', () => {
it('It should return SUCCESS if the tx is executed and successful', () => {
// given
const transaction = makeTransaction({ isExecuted: true, isSuccessful: true })
const safe = makeSafe()
const currentUser = safeAddress
// when
const result = calculateTransactionStatus(transaction, safe, currentUser)
// then
expect(result).toBe(TransactionStatus.SUCCESS)
})
it('It should return CANCELLED if the tx is cancelled and successful', () => {
// given
const transaction = makeTransaction({ cancelled: true })
const safe = makeSafe()
const currentUser = safeAddress
// when
const result = calculateTransactionStatus(transaction, safe, currentUser)
// then
expect(result).toBe(TransactionStatus.CANCELLED)
})
it('It should return AWAITING_EXECUTION if the tx has an amount of confirmations equal to the safe threshold', () => {
// given
const makeUser = Record({
owner: '',
type: '',
hash: '',
signature: '',
})
const transaction = makeTransaction({ cancelled: true, confirmations: List([makeUser(), makeUser(), makeUser()]) })
const safe = makeSafe({ threshold: 3 })
const currentUser = safeAddress
// when
const result = calculateTransactionStatus(transaction, safe, currentUser)
// then
expect(result).toBe(TransactionStatus.CANCELLED)
})
it('It should return SUCCESS if the tx is the creation transaction', () => {
// given
const transaction = makeTransaction({ creationTx: true, confirmations: List() })
const safe = makeSafe({ threshold: 3 })
const currentUser = safeAddress
// when
const result = calculateTransactionStatus(transaction, safe, currentUser)
// then
expect(result).toBe(TransactionStatus.SUCCESS)
})
it('It should return PENDING if the tx is pending', () => {
// given
const transaction = makeTransaction({ confirmations: List(), isPending: true })
const safe = makeSafe({ threshold: 3 })
const currentUser = safeAddress
// when
const result = calculateTransactionStatus(transaction, safe, currentUser)
// then
expect(result).toBe(TransactionStatus.PENDING)
})
it('It should return PENDING if the tx has no confirmations', () => {
// given
const transaction = makeTransaction({ confirmations: List(), isPending: false })
const safe = makeSafe({ threshold: 3 })
const currentUser = safeAddress
// when
const result = calculateTransactionStatus(transaction, safe, currentUser)
// then
expect(result).toBe(TransactionStatus.PENDING)
})
it('It should return AWAITING_CONFIRMATIONS if the tx has confirmations bellow the threshold, the user is owner and signed', () => {
// given
const userAddress = 'address1'
const userAddress2 = 'address2'
const makeUser = Record({
owner: '',
type: '',
hash: '',
signature: '',
})
const transaction = makeTransaction({ confirmations: List([makeUser({ owner: userAddress })]) })
const safe = makeSafe({
threshold: 3,
owners: List([
{ name: '', address: userAddress },
{ name: '', address: userAddress2 },
]),
})
const currentUser = userAddress
// when
const result = calculateTransactionStatus(transaction, safe, currentUser)
// then
expect(result).toBe(TransactionStatus.AWAITING_CONFIRMATIONS)
})
it('It should return AWAITING_YOUR_CONFIRMATION if the tx has confirmations bellow the threshold, the user is owner and not signed', () => {
// given
const userAddress = 'address1'
const userAddress2 = 'address2'
const makeUser = Record({
owner: '',
type: '',
hash: '',
signature: '',
})
const transaction = makeTransaction({ confirmations: List([makeUser({ owner: userAddress })]) })
const safe = makeSafe({
threshold: 3,
owners: List([
{ name: '', address: userAddress },
{ name: '', address: userAddress2 },
]),
})
const currentUser = userAddress2
// when
const result = calculateTransactionStatus(transaction, safe, currentUser)
// then
expect(result).toBe(TransactionStatus.AWAITING_YOUR_CONFIRMATION)
})
it('It should return AWAITING_CONFIRMATIONS if the tx has confirmations bellow the threshold, the user is not owner', () => {
// given
const userAddress = 'address1'
const userAddress2 = 'address2'
const makeUser = Record({
owner: '',
type: '',
hash: '',
signature: '',
})
const transaction = makeTransaction({ confirmations: List([makeUser({ owner: userAddress })]) })
const safe = makeSafe({ threshold: 3, owners: List([{ name: '', address: userAddress }]) })
const currentUser = userAddress2
// when
const result = calculateTransactionStatus(transaction, safe, currentUser)
// then
expect(result).toBe(TransactionStatus.AWAITING_CONFIRMATIONS)
})
it('It should return FAILED if the tx is not successful', () => {
// given
const userAddress = 'address1'
const userAddress2 = 'address2'
const makeUser = Record({
owner: '',
type: '',
hash: '',
signature: '',
})
const transaction = makeTransaction({
confirmations: List([makeUser({ owner: userAddress })]),
isSuccessful: false,
})
const safe = makeSafe({ threshold: 3, owners: List([{ name: '', address: userAddress }]) })
const currentUser = userAddress2
// when
const result = calculateTransactionStatus(transaction, safe, currentUser)
// then
expect(result).toBe(TransactionStatus.FAILED)
})
})
describe('calculateTransactionType', () => {
it('It should return TOKEN If the tx is a token transfer transaction', () => {
// given
const transaction = makeTransaction({ isTokenTransfer: true })
// when
const result = calculateTransactionType(transaction)
// then
expect(result).toBe(TransactionTypes.TOKEN)
})
it('It should return COLLECTIBLE If the tx is a collectible transfer transaction', () => {
// given
const transaction = makeTransaction({ isCollectibleTransfer: true })
// when
const result = calculateTransactionType(transaction)
// then
expect(result).toBe(TransactionTypes.COLLECTIBLE)
})
it('It should return SETTINGS If the tx is a modifySettings transaction', () => {
// given
const transaction = makeTransaction({ modifySettingsTx: true })
// when
const result = calculateTransactionType(transaction)
// then
expect(result).toBe(TransactionTypes.SETTINGS)
})
it('It should return CANCELLATION If the tx is a cancellation transaction', () => {
// given
const transaction = makeTransaction({ isCancellationTx: true })
// when
const result = calculateTransactionType(transaction)
// then
expect(result).toBe(TransactionTypes.CANCELLATION)
})
it('It should return CUSTOM If the tx is a custom transaction', () => {
// given
const transaction = makeTransaction({ customTx: true })
// when
const result = calculateTransactionType(transaction)
// then
expect(result).toBe(TransactionTypes.CUSTOM)
})
it('It should return CUSTOM If the tx is a creation transaction', () => {
// given
const transaction = makeTransaction({ creationTx: true })
// when
const result = calculateTransactionType(transaction)
// then
expect(result).toBe(TransactionTypes.CREATION)
})
it('It should return UPGRADE If the tx is an upgrade transaction', () => {
// given
const transaction = makeTransaction({ upgradeTx: true })
// when
const result = calculateTransactionType(transaction)
// then
expect(result).toBe(TransactionTypes.UPGRADE)
})
})
describe('buildTx', () => {
it('Returns a valid transaction', async () => {
// given
const cancelTx1 = makeTransaction()
const transaction = getMockedTxServiceModel({ to: safeAddress2, value: '0' })
const userAddress = 'address1'
const cancellationTxs = List([cancelTx1])
const token = makeToken({
address: '0x00Df91984582e6e96288307E9c2f20b38C8FeCE9',
name: 'OmiseGo',
symbol: 'OMG',
decimals: 18,
logoUri:
'https://github.com/TrustWallet/tokens/blob/master/images/0x6810e776880c02933d47db1b9fc05908e5386b96.png?raw=true',
})
const knownTokens = Map<string, Record<TokenProps> & Readonly<TokenProps>>()
knownTokens.set('0x00Df91984582e6e96288307E9c2f20b38C8FeCE9', token)
const outgoingTxs = List([cancelTx1])
const safeInstance = makeSafe({ name: 'LOADED SAFE', address: safeAddress })
const expectedTx = makeTransaction({
baseGas: 0,
blockNumber: 0,
cancelled: false,
confirmations: List([]),
creationTx: false,
customTx: false,
data: EMPTY_DATA,
dataDecoded: null,
decimals: 18,
decodedParams: null,
executionDate: '',
executionTxHash: '',
executor: '',
gasPrice: '',
gasToken: ZERO_ADDRESS,
isCancellationTx: false,
isCollectibleTransfer: false,
isExecuted: false,
isSuccessful: false,
isTokenTransfer: false,
modifySettingsTx: false,
multiSendTx: false,
nonce: 0,
operation: 0,
origin: '',
recipient: safeAddress2,
refundParams: null,
refundReceiver: ZERO_ADDRESS,
safeTxGas: 0,
safeTxHash: '',
setupData: '',
status: TransactionStatus.FAILED,
submissionDate: '',
symbol: 'ETH',
upgradeTx: false,
value: '0',
fee: '',
})
// when
const txResult = await buildTx({
cancellationTxs,
currentUser: userAddress,
knownTokens,
outgoingTxs,
safe: safeInstance,
tx: transaction,
txCode: null,
})
// then
expect(txResult).toStrictEqual(expectedTx)
})
})
describe('updateStoredTransactionsStatus', () => {
it('', () => {
// given
// when
// then
})
})
describe('generateSafeTxHash', () => {
it('It should return a safe transaction hash', () => {
// given
const safeAddress = '0xdfA693da0D16F5E7E78FdCBeDe8FC6eBEa44f1Cf'
const userAddress = 'address1'
const userAddress2 = 'address2'
const userAddress3 = 'address3'
const safeInstance = getMockedSafeInstance({})
const txArgs = {
baseGas: 100,
data: '',
gasPrice: '1000',
gasToken: '',
nonce: 0,
operation: 0,
refundReceiver: userAddress,
safeInstance,
safeTxGas: 1000,
sender: userAddress2,
sigs: '',
to: userAddress3,
valueInWei: '5000',
}
// when
const result = generateSafeTxHash(safeAddress, txArgs)
// then
expect(result).toBe('0x21e6ebc992f959dd0a2a6ce6034c414043c598b7f446c274efb3527c30dec254')
})
})

View File

@ -87,12 +87,12 @@ export const isCustomTransaction = async (
safeAddress: string,
knownTokens: Map<string, Token>,
): Promise<boolean> => {
return (
isOutgoingTransaction(tx, safeAddress) &&
!(await isSendERC20Transaction(tx, txCode, knownTokens)) &&
!isUpgradeTransaction(tx) &&
!isSendERC721Transaction(tx, txCode, knownTokens)
)
const isOutgoing = isOutgoingTransaction(tx, safeAddress)
const isErc20 = await isSendERC20Transaction(tx, txCode, knownTokens)
const isUpgrade = isUpgradeTransaction(tx)
const isErc721 = isSendERC721Transaction(tx, txCode, knownTokens)
return isOutgoing && !isErc20 && !isUpgrade && !isErc721
}
export const getRefundParams = async (

View File

@ -0,0 +1,59 @@
import { Set, Map } from 'immutable'
import { aNewStore } from 'src/store'
import updateActiveTokens from 'src/logic/safe/store/actions/updateActiveTokens'
import '@testing-library/jest-dom/extend-expect'
import updateSafe from 'src/logic/safe/store/actions/updateSafe'
import { makeToken } from 'src/logic/tokens/store/model/token'
import { safesMapSelector } from 'src/logic/safe/store/selectors'
describe('Feature > Balances', () => {
let store
const safeAddress = '0xdfA693da0D16F5E7E78FdCBeDe8FC6eBEa44f1Cf'
beforeEach(async () => {
store = aNewStore()
})
it('It should return an updated balance when updates active tokens', async () => {
// given
const tokensAmount = '100'
const token = makeToken({
address: '0x00Df91984582e6e96288307E9c2f20b38C8FeCE9',
name: 'OmiseGo',
symbol: 'OMG',
decimals: 18,
logoUri:
'https://github.com/TrustWallet/tokens/blob/master/images/0x6810e776880c02933d47db1b9fc05908e5386b96.png?raw=true',
})
const balances = Map({
[token.address]: tokensAmount,
})
const expectedResult = '100'
// when
store.dispatch(updateActiveTokens(safeAddress, Set([token.address])))
store.dispatch(updateSafe({ address: safeAddress, balances }))
const safe = safesMapSelector(store.getState()).get(safeAddress)
const balanceResult = safe.get('balances').get(token.address)
const activeTokens = safe.get('activeTokens')
const tokenIsActive = activeTokens.has(token.address)
// then
expect(balanceResult).toBe(expectedResult)
expect(tokenIsActive).toBe(true)
})
it('The store should have an updated ether balance after updating the value', async () => {
// given
const etherAmount = '1'
const expectedResult = '1'
// when
store.dispatch(updateSafe({ address: safeAddress, ethBalance: etherAmount }))
const safe = safesMapSelector(store.getState()).get(safeAddress)
const balanceResult = safe.get('ethBalance')
// then
expect(balanceResult).toBe(expectedResult)
})
})

View File

@ -1,8 +1,7 @@
import { formatAmount, formatAmountInUsFormat } from 'src/logic/tokens/utils/formatAmount'
describe('formatAmount', () => {
it('Given 0 returns 0', () => {
it('Given 0 returns 0', () => {
// given
const input = '0'
const expectedResult = '0'
@ -13,7 +12,7 @@ describe('formatAmount', () => {
// then
expect(result).toBe(expectedResult)
})
it('Given 1 returns 1', () => {
it('Given 1 returns 1', () => {
// given
const input = '1'
const expectedResult = '1'
@ -24,7 +23,7 @@ describe('formatAmount', () => {
// then
expect(result).toBe(expectedResult)
})
it('Given a string in format XXXXX.XXX returns a number of format XX,XXX.XXX', () => {
it('Given a string in format XXXXX.XXX returns a number of format XX,XXX.XXX', () => {
// given
const input = '19797.899'
const expectedResult = '19,797.899'
@ -35,7 +34,7 @@ describe('formatAmount', () => {
// then
expect(result).toBe(expectedResult)
})
it('Given number > 0.001 && < 1000 returns the same number as string', () => {
it('Given number > 0.001 && < 1000 returns the same number as string', () => {
// given
const input = 999
const expectedResult = '999'
@ -45,7 +44,7 @@ describe('formatAmount', () => {
// then
expect(result).toBe(expectedResult)
})
it('Given a number between 1000 and 10000 returns a number of format XX,XXX', () => {
it('Given a number between 1000 and 10000 returns a number of format XX,XXX', () => {
// given
const input = 9999
const expectedResult = '9,999'
@ -55,7 +54,7 @@ describe('formatAmount', () => {
// then
expect(result).toBe(expectedResult)
})
it('Given a number between 10000 and 100000 returns a number of format XX,XXX', () => {
it('Given a number between 10000 and 100000 returns a number of format XX,XXX', () => {
// given
const input = 99999
const expectedResult = '99,999'
@ -65,7 +64,7 @@ describe('formatAmount', () => {
// then
expect(result).toBe(expectedResult)
})
it('Given a number between 100000 and 1000000 returns a number of format XXX,XXX', () => {
it('Given a number between 100000 and 1000000 returns a number of format XXX,XXX', () => {
// given
const input = 999999
const expectedResult = '999,999'
@ -75,7 +74,7 @@ describe('formatAmount', () => {
// then
expect(result).toBe(expectedResult)
})
it('Given a number between 10000000 and 100000000 returns a number of format X,XXX,XXX', () => {
it('Given a number between 10000000 and 100000000 returns a number of format X,XXX,XXX', () => {
// given
const input = 9999999
const expectedResult = '9,999,999'
@ -85,7 +84,7 @@ describe('formatAmount', () => {
// then
expect(result).toBe(expectedResult)
})
it('Given number < 0.001 returns < 0.001', () => {
it('Given number < 0.001 returns < 0.001', () => {
// given
const input = 0.000001
const expectedResult = '< 0.001'
@ -95,7 +94,7 @@ describe('formatAmount', () => {
// then
expect(result).toBe(expectedResult)
})
it('Given number > 10 ** 15 returns > 1000T', () => {
it('Given number > 10 ** 15 returns > 1000T', () => {
// given
const input = 10 ** 15 * 2
const expectedResult = '> 1000T'
@ -109,7 +108,7 @@ describe('formatAmount', () => {
})
describe('FormatsAmountsInUsFormat', () => {
it('Given 0 returns 0.00', () => {
it('Given 0 returns 0.00', () => {
// given
const input = 0
const expectedResult = '0.00'
@ -120,7 +119,7 @@ describe('FormatsAmountsInUsFormat', () => {
// then
expect(result).toBe(expectedResult)
})
it('Given 1 returns 1.00', () => {
it('Given 1 returns 1.00', () => {
// given
const input = 1
const expectedResult = '1.00'
@ -131,9 +130,9 @@ describe('FormatsAmountsInUsFormat', () => {
// then
expect(result).toBe(expectedResult)
})
it('Given a number in format XXXXX.XX returns a number of format XXX,XXX.XX', () => {
it('Given a number in format XXXXX.XX returns a number of format XXX,XXX.XX', () => {
// given
const input = 311137.30
const input = 311137.3
const expectedResult = '311,137.30'
// when
@ -142,7 +141,7 @@ describe('FormatsAmountsInUsFormat', () => {
// then
expect(result).toBe(expectedResult)
})
it('Given a number in format XXXXX.XXX returns a number of format XX,XXX.XXX', () => {
it('Given a number in format XXXXX.XXX returns a number of format XX,XXX.XXX', () => {
// given
const input = 19797.899
const expectedResult = '19,797.899'
@ -153,7 +152,7 @@ describe('FormatsAmountsInUsFormat', () => {
// then
expect(result).toBe(expectedResult)
})
it('Given a number in format XXXXXXXX.XXX returns a number of format XX,XXX,XXX.XXX', () => {
it('Given a number in format XXXXXXXX.XXX returns a number of format XX,XXX,XXX.XXX', () => {
// given
const input = 19797899.479
const expectedResult = '19,797,899.479'
@ -164,7 +163,7 @@ describe('FormatsAmountsInUsFormat', () => {
// then
expect(result).toBe(expectedResult)
})
it('Given a number in format XXXXXXXXXXX.XXX returns a number of format XX,XXX,XXX,XXX.XXX', () => {
it('Given a number in format XXXXXXXXXXX.XXX returns a number of format XX,XXX,XXX,XXX.XXX', () => {
// given
const input = 19797899479.999
const expectedResult = '19,797,899,479.999'
@ -176,4 +175,3 @@ describe('FormatsAmountsInUsFormat', () => {
expect(result).toBe(expectedResult)
})
})

View File

@ -0,0 +1,174 @@
import { makeToken } from 'src/logic/tokens/store/model/token'
import { getERC20DecimalsAndSymbol, isERC721Contract, isTokenTransfer } from 'src/logic/tokens/utils/tokenHelpers'
import { getMockedTxServiceModel } from 'src/test/utils/safeHelper'
describe('isTokenTransfer', () => {
const safeAddress = '0xdfA693da0D16F5E7E78FdCBeDe8FC6eBEa44f1Cf'
it('It should return false if the transaction has no value but but "transfer" function signature is encoded in the data', () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress, value: '0', data: '0xa9059cbb' })
const expectedResult = true
// when
const result = isTokenTransfer(transaction)
// then
expect(result).toEqual(expectedResult)
})
it('It should return false if the transaction has no value but and no "transfer" function signature encoded in data', () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress, value: '0', data: '0xa9055cbb' })
const expectedResult = false
// when
const result = isTokenTransfer(transaction)
// then
expect(result).toEqual(expectedResult)
})
it('It should return false if the transaction has empty data', () => {
// given
const transaction = getMockedTxServiceModel({ to: safeAddress, value: '0', data: null })
const expectedResult = false
// when
const result = isTokenTransfer(transaction)
// then
expect(result).toEqual(expectedResult)
})
})
jest.mock('src/logic/tokens/store/actions/fetchTokens')
jest.mock('src/logic/contracts/generateBatchRequests')
jest.mock('console')
describe('getERC20DecimalsAndSymbol', () => {
afterAll(() => {
jest.unmock('src/logic/tokens/store/actions/fetchTokens')
jest.unmock('src/logic/contracts/generateBatchRequests')
jest.unmock('console')
})
it('It should return DAI information from the store if given a DAI address', async () => {
// given
const tokenAddress = '0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa'
const decimals = Number(18)
const symbol = 'DAI'
const token = makeToken({
address: tokenAddress,
name: 'Dai',
symbol,
decimals,
logoUri: 'https://gnosis-safe-token-logos.s3.amazonaws.com/0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa.png',
balance: 0,
})
const expectedResult = {
decimals,
symbol,
}
const fetchTokens = require('src/logic/tokens/store/actions/fetchTokens')
const spy = fetchTokens.getTokenInfos.mockImplementationOnce(() => token)
// when
const result = await getERC20DecimalsAndSymbol(tokenAddress)
// then
expect(result).toEqual(expectedResult)
expect(spy).toHaveBeenCalled()
})
it('It should return default value decimals: 18, symbol: UNKNOWN if given a token address and if there is an error fetching the data', async () => {
// given
const tokenAddress = '0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa'
const decimals = Number(18)
const symbol = 'UNKNOWN'
const expectedResult = {
decimals,
symbol,
}
const fetchTokens = require('src/logic/tokens/store/actions/fetchTokens')
const spy = fetchTokens.getTokenInfos.mockImplementationOnce(() => {
throw new Error()
})
console.error = jest.fn()
const spyConsole = jest.spyOn(console, 'error').mockImplementation()
// when
const result = await getERC20DecimalsAndSymbol(tokenAddress)
// then
expect(result).toEqual(expectedResult)
expect(spy).toHaveBeenCalled()
expect(spyConsole).toHaveBeenCalled()
})
it("It should fetch token information from the blockchain if given a token address and if the token doesn't exist in redux store", async () => {
// given
const tokenAddress = '0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa'
const decimals = Number(18)
const symbol = 'DAI'
const expectedResult = {
decimals,
symbol,
}
const fetchTokens = require('src/logic/tokens/store/actions/fetchTokens')
const generateBatchRequests = require('src/logic/contracts/generateBatchRequests')
const spyTokenInfos = fetchTokens.getTokenInfos.mockImplementationOnce(() => null)
const spyGenerateBatchRequest = generateBatchRequests.default.mockImplementationOnce(() => [decimals, symbol])
// when
const result = await getERC20DecimalsAndSymbol(tokenAddress)
// then
expect(result).toEqual(expectedResult)
expect(spyTokenInfos).toHaveBeenCalled()
expect(spyGenerateBatchRequest).toHaveBeenCalled()
})
})
describe('isERC721Contract', () => {
afterAll(() => {
jest.unmock('src/logic/tokens/store/actions/fetchTokens')
})
beforeEach(() => {
jest.mock('src/logic/tokens/store/actions/fetchTokens')
})
it('It should return false if given non-erc721 contract address', async () => {
// given
const contractAddress = '0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa' // DAI Address
const expectedResult = false
const ERC721Contract = {
at: () => {
throw new Error('Contract is not ERC721')
},
}
const fetchTokens = require('src/logic/tokens/store/actions/fetchTokens')
const standardContractSpy = fetchTokens.getStandardTokenContract.mockImplementation(() => ERC721Contract)
// when
const result = await isERC721Contract(contractAddress)
// then
expect(result).toEqual(expectedResult)
expect(standardContractSpy).toHaveBeenCalled
})
it('It should return true if given a Erc721 contract address', async () => {
// given
const contractAddress = '0x014d5883274ab3a9708b0f1e4263df6e90160a30' // dummy ft Address
const ERC721Contract = {
at: (address) => address === contractAddress,
}
const expectedResult = true
const fetchTokens = require('src/logic/tokens/store/actions/fetchTokens')
const standardContractSpy = fetchTokens.getStandardTokenContract.mockImplementation(() => ERC721Contract)
// when
const result = await isERC721Contract(contractAddress)
// then
expect(result).toEqual(expectedResult)
expect(standardContractSpy).toHaveBeenCalled()
})
})

View File

@ -118,8 +118,8 @@ export const isERC721Contract = async (contractAddress: string): Promise<boolean
let isERC721 = false
try {
isERC721 = true
await ERC721Token.at(contractAddress)
isERC721 = true
} catch (error) {
console.warn('Asset not found')
}

View File

@ -20,7 +20,7 @@ export const shortVersionOf = (value: string, cut: number): string => {
}
const final = value.length - cut
if (value.length < final) {
if (value.length <= cut) {
return value
}

View File

@ -0,0 +1,281 @@
import {
isUserAnOwner,
isUserAnOwnerOfAnySafe,
isValidEnsName,
sameAddress,
shortVersionOf,
} from 'src/logic/wallets/ethAddresses'
import makeSafe from 'src/logic/safe/store/models/safe'
import { makeOwner } from 'src/logic/safe/store/models/owner'
import { List } from 'immutable'
describe('Utility function: sameAddress', () => {
it('It should return false if no address given', () => {
// given
const safeAddress = null
const safeAddress2 = '0x344B941b1aAE2e4Be73987212FC4741687Bf0503'
// when
const result = sameAddress(safeAddress, safeAddress2)
// then
expect(result).toBe(false)
})
it('It should return false if not second address given', () => {
// given
const safeAddress = '0xdfA693da0D16F5E7E78FdCBeDe8FC6eBEa44f1Cf'
const safeAddress2 = null
// when
const result = sameAddress(safeAddress, safeAddress2)
// then
expect(result).toBe(false)
})
it('It should return true if two equal addresses given', () => {
// given
const safeAddress = '0xdfA693da0D16F5E7E78FdCBeDe8FC6eBEa44f1Cf'
// when
const result = sameAddress(safeAddress, safeAddress)
// then
expect(result).toBe(true)
})
it('If should return false if two different addresses given', () => {
// given
const safeAddress = '0xdfA693da0D16F5E7E78FdCBeDe8FC6eBEa44f1Cf'
const safeAddress2 = '0x344B941b1aAE2e4Be73987212FC4741687Bf0503'
// when
const result = sameAddress(safeAddress, safeAddress2)
// then
expect(result).toBe(false)
})
})
describe('Utility function: shortVersionOf', () => {
it('It should return Unknown if no address given', () => {
// given
const safeAddress = null
const cut = 5
const expectedResult = 'Unknown'
// when
const result = shortVersionOf(safeAddress, cut)
// then
expect(result).toBe(expectedResult)
})
it('It should return 0x344...f0503 if given 0x344B941b1aAE2e4Be73987212FC4741687Bf0503 and a cut = 5', () => {
// given
const safeAddress = '0x344B941b1aAE2e4Be73987212FC4741687Bf0503'
const cut = 5
const expectedResult = `0x344...f0503`
// when
const result = shortVersionOf(safeAddress, cut)
// then
expect(result).toBe(expectedResult)
})
it('If should return the same address if a cut value bigger than the address length given', () => {
// given
const safeAddress = '0x344B941b1aAE2e4Be73987212FC4741687Bf0503'
const cut = safeAddress.length
const expectedResult = safeAddress
// when
const result = shortVersionOf(safeAddress, cut)
// then
expect(result).toBe(expectedResult)
})
})
describe('Utility function: isUserAnOwner', () => {
it("Should return false if there's no Safe", () => {
// given
const userAddress = 'address1'
const safeInstance = null
const expectedResult = false
// when
const result = isUserAnOwner(safeInstance, userAddress)
// then
expect(result).toBe(expectedResult)
})
it("Should return false if there's no `userAccount`", () => {
// given
const userAddress = null
const owners = List([makeOwner({ address: userAddress })])
const safeInstance = makeSafe({ owners })
const expectedResult = false
// when
const result = isUserAnOwner(safeInstance, userAddress)
// then
expect(result).toBe(expectedResult)
})
it('Should return false if there are no owners for the Safe', () => {
// given
const userAddress = 'address1'
const owners = null
const safeInstance = makeSafe({ owners })
const expectedResult = false
// when
const result = isUserAnOwner(safeInstance, userAddress)
// then
expect(result).toBe(expectedResult)
})
it("Should return true if `userAccount` is not in the list of Safe's owners", () => {
// given
const userAddress = 'address1'
const owners = List([makeOwner({ address: userAddress })])
const safeInstance = makeSafe({ owners })
const expectedResult = true
// when
const result = isUserAnOwner(safeInstance, userAddress)
// then
expect(result).toBe(expectedResult)
})
it("Should return false if `userAccount` is not in the list of Safe's owners", () => {
// given
const userAddress = 'address1'
const userAddress2 = 'address2'
const owners = List([makeOwner({ address: userAddress })])
const safeInstance = makeSafe({ owners })
const expectedResult = false
// when
const result = isUserAnOwner(safeInstance, userAddress2)
// then
expect(result).toBe(expectedResult)
})
})
describe('Utility function: isUserAnOwnerOfAnySafe', () => {
it('Should return true if given a list of safes, one of them has an owner equal to the userAccount', () => {
// given
const userAddress = 'address1'
const userAddress2 = 'address2'
const owners1 = List([makeOwner({ address: userAddress })])
const owners2 = List([makeOwner({ address: userAddress2 })])
const safeInstance = makeSafe({ owners: owners1 })
const safeInstance2 = makeSafe({ owners: owners2 })
const safesList = List([safeInstance, safeInstance2])
const expectedResult = true
// when
const result = isUserAnOwnerOfAnySafe(safesList, userAddress)
// then
expect(result).toBe(expectedResult)
})
it('It should return false if given a list of safes, none of them has an owner equal to the userAccount', () => {
// given
const userAddress = 'address1'
const userAddress2 = 'address2'
const userAddress3 = 'address3'
const owners1 = List([makeOwner({ address: userAddress3 })])
const owners2 = List([makeOwner({ address: userAddress2 })])
const safeInstance = makeSafe({ owners: owners1 })
const safeInstance2 = makeSafe({ owners: owners2 })
const safesList = List([safeInstance, safeInstance2])
const expectedResult = false
// when
const result = isUserAnOwnerOfAnySafe(safesList, userAddress)
// then
expect(result).toBe(expectedResult)
})
})
describe('Utility function: isValidEnsName', () => {
it('If should return false if given no ens name', () => {
// given
const ensName = null
const expectedResult = false
// when
const result = isValidEnsName(ensName)
// then
expect(result).toBe(expectedResult)
})
it('It should return false for an ens without extension in format [value].[eth|test|xyz|luxe]', () => {
// given
const ensName = 'test'
const expectedResult = false
// when
const result = isValidEnsName(ensName)
// then
expect(result).toBe(expectedResult)
})
it('It should return false for an ens without the format [value].[eth|test|xyz|luxe]', () => {
// given
const ensName = 'test.et12312'
const expectedResult = false
// when
const result = isValidEnsName(ensName)
// then
expect(result).toBe(expectedResult)
})
it('It should return true for an ens in format [value].eth', () => {
// given
const ensName = 'test.eth'
const expectedResult = true
// when
const result = isValidEnsName(ensName)
// then
expect(result).toBe(expectedResult)
})
it('It should return true for ens in format [value].test', () => {
// given
const ensName = 'test.test'
const expectedResult = true
// when
const result = isValidEnsName(ensName)
// then
expect(result).toBe(expectedResult)
})
it('It should return true for an ens in the format [value].xyz', () => {
// given
const ensName = 'test.xyz'
const expectedResult = true
// when
const result = isValidEnsName(ensName)
// then
expect(result).toBe(expectedResult)
})
it('It should return true for an ens in format [value].luxe', () => {
// given
const ensName = 'test.luxe'
const expectedResult = true
// when
const result = isValidEnsName(ensName)
// then
expect(result).toBe(expectedResult)
})
})

View File

@ -20,7 +20,7 @@ import Hairline from 'src/components/layout/Hairline'
import Paragraph from 'src/components/layout/Paragraph'
import Row from 'src/components/layout/Row'
import { getAddressBook } from 'src/logic/addressBook/store/selectors'
import { getAddressesListFromAdbk } from 'src/logic/addressBook/utils'
import { getAddressesListFromSafeAddressBook } from 'src/logic/addressBook/utils'
export const CREATE_ENTRY_INPUT_NAME_ID = 'create-entry-input-name'
export const CREATE_ENTRY_INPUT_ADDRESS_ID = 'create-entry-input-address'
@ -43,7 +43,7 @@ const CreateEditEntryModalComponent = ({
}
const addressBook = useSelector(getAddressBook)
const addressBookAddressesList = getAddressesListFromAdbk(addressBook)
const addressBookAddressesList = getAddressesListFromSafeAddressBook(addressBook)
const entryDoesntExist = uniqueAddress(addressBookAddressesList)
const formMutators = {

View File

@ -40,7 +40,7 @@ const EthAddressInput = ({
const {
input: { value },
} = useField('contractAddress', { subscription: { value: true } })
const [selectedEntry, setSelectedEntry] = useState<{ address?: string; name?: string } | null>({
const [selectedEntry, setSelectedEntry] = useState<{ address?: string; name?: string | null } | null>({
address: value,
name: '',
})

View File

@ -54,7 +54,7 @@ const SendCustomTx: React.FC<Props> = ({ initialValues, onClose, onNext, contrac
const classes = useStyles()
const { ethBalance } = useSelector(safeSelector)
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
const [selectedEntry, setSelectedEntry] = useState<{ address?: string; name?: string } | null>({
const [selectedEntry, setSelectedEntry] = useState<{ address?: string; name?: string | null } | null>({
address: contractAddress || initialValues.contractAddress,
name: '',
})

View File

@ -21,7 +21,7 @@ import Hairline from 'src/components/layout/Hairline'
import Paragraph from 'src/components/layout/Paragraph'
import Row from 'src/components/layout/Row'
import { getAddressBook } from 'src/logic/addressBook/store/selectors'
import { getNameFromAdbk } from 'src/logic/addressBook/utils'
import { getNameFromSafeAddressBook } from 'src/logic/addressBook/utils'
import { nftTokensSelector, safeActiveSelectorMap } from 'src/logic/collectibles/store/selectors'
import SafeInfo from 'src/routes/safe/components/Balances/SendModal/SafeInfo'
import AddressBookInput from 'src/routes/safe/components/Balances/SendModal/screens/AddressBookInput'
@ -48,7 +48,7 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
const nftAssets = useSelector(safeActiveSelectorMap)
const nftTokens = useSelector(nftTokensSelector)
const addressBook = useSelector(getAddressBook)
const [selectedEntry, setSelectedEntry] = useState({
const [selectedEntry, setSelectedEntry] = useState<{ address: string; name: string | null }>({
address: recipientAddress || initialValues.recipientAddress,
name: '',
})
@ -97,7 +97,7 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
if (scannedAddress.startsWith('ethereum:')) {
scannedAddress = scannedAddress.replace('ethereum:', '')
}
const scannedName = addressBook ? getNameFromAdbk(addressBook, scannedAddress) : ''
const scannedName = addressBook ? getNameFromSafeAddressBook(addressBook, scannedAddress) : ''
mutators.setRecipient(scannedAddress)
setSelectedEntry({
name: scannedName,

View File

@ -26,7 +26,7 @@ import Hairline from 'src/components/layout/Hairline'
import Paragraph from 'src/components/layout/Paragraph'
import Row from 'src/components/layout/Row'
import { getAddressBook } from 'src/logic/addressBook/store/selectors'
import { getNameFromAdbk } from 'src/logic/addressBook/utils'
import { getNameFromSafeAddressBook } from 'src/logic/addressBook/utils'
import SafeInfo from 'src/routes/safe/components/Balances/SendModal/SafeInfo'
import AddressBookInput from 'src/routes/safe/components/Balances/SendModal/screens/AddressBookInput'
@ -48,7 +48,25 @@ const formMutators = {
const useStyles = makeStyles(styles as any)
const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedToken = '' }): React.ReactElement => {
type SendFundsProps = {
initialValues: {
amount?: string
recipientAddress?: string
token?: string
}
onClose: () => void
onNext: (txInfo: unknown) => void
recipientAddress: string
selectedToken: string
}
const SendFunds = ({
initialValues,
onClose,
onNext,
recipientAddress,
selectedToken = '',
}: SendFundsProps): React.ReactElement => {
const classes = useStyles()
const tokens = useSelector(extendedSafeTokensSelector)
const addressBook = useSelector(getAddressBook)
@ -100,10 +118,10 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT
if (scannedAddress.startsWith('ethereum:')) {
scannedAddress = scannedAddress.replace('ethereum:', '')
}
const scannedName = addressBook ? getNameFromAdbk(addressBook, scannedAddress) : ''
const scannedName = addressBook ? getNameFromSafeAddressBook(addressBook, scannedAddress) : ''
mutators.setRecipient(scannedAddress)
setSelectedEntry({
name: scannedName,
name: scannedName || '',
address: scannedAddress,
})
closeQrModal()

View File

@ -30,8 +30,8 @@ import Paragraph from 'src/components/layout/Paragraph/index'
import Row from 'src/components/layout/Row'
import { getOwnersWithNameFromAddressBook } from 'src/logic/addressBook/utils'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
import { AddressBookEntryProps } from 'src/logic/addressBook/model/addressBook'
import { SafeOwner } from 'src/logic/safe/store/models/safe'
import { AddressBookCollection } from 'src/logic/addressBook/store/reducer/addressBook'
export const RENAME_OWNER_BTN_TEST_ID = 'rename-owner-btn'
export const REMOVE_OWNER_BTN_TEST_ID = 'remove-owner-btn'
@ -84,7 +84,7 @@ const ManageOwners = ({ addressBook, granted, owners }: Props): React.ReactEleme
const columns = generateColumns()
const autoColumns = columns.filter((c) => !c.custom)
const ownersAdbk = getOwnersWithNameFromAddressBook(addressBook as AddressBookEntryProps, owners)
const ownersAdbk = getOwnersWithNameFromAddressBook(addressBook as AddressBookCollection, owners)
const ownerData = getOwnerData(ownersAdbk)
return (

View File

@ -0,0 +1,170 @@
import { getLastTx, getNewTxNonce, shouldExecuteTransaction } from 'src/logic/safe/store/actions/utils'
import { getMockedSafeInstance, getMockedTxServiceModel } from 'src/test/utils/safeHelper'
import axios from 'axios'
import { buildTxServiceUrl } from 'src/logic/safe/transactions'
describe('shouldExecuteTransaction', () => {
it('It should return false if given a safe with a threshold > 1', async () => {
// given
const nonce = '0'
const threshold = '2'
const safeInstance = getMockedSafeInstance({ threshold })
const lastTx = getMockedTxServiceModel({})
// when
const result = await shouldExecuteTransaction(safeInstance, nonce, lastTx)
// then
expect(result).toBe(false)
})
it('It should return true if given a safe with a threshold === 1 and the previous transaction is already executed', async () => {
// given
const nonce = '0'
const threshold = '1'
const safeInstance = getMockedSafeInstance({ threshold })
const lastTx = getMockedTxServiceModel({})
// when
const result = await shouldExecuteTransaction(safeInstance, nonce, lastTx)
// then
expect(result).toBe(true)
})
it('It should return true if given a safe with a threshold === 1 and the previous transaction is already executed', async () => {
// given
const nonce = '10'
const threshold = '1'
const safeInstance = getMockedSafeInstance({ threshold })
const lastTx = getMockedTxServiceModel({ isExecuted: true })
// when
const result = await shouldExecuteTransaction(safeInstance, nonce, lastTx)
// then
expect(result).toBe(true)
})
it('It should return false if given a safe with a threshold === 1 and the previous transaction is not yet executed', async () => {
// given
const nonce = '10'
const threshold = '1'
const safeInstance = getMockedSafeInstance({ threshold })
const lastTx = getMockedTxServiceModel({ isExecuted: false })
// when
const result = await shouldExecuteTransaction(safeInstance, nonce, lastTx)
// then
expect(result).toBe(false)
})
})
describe('getNewTxNonce', () => {
it('It should return 2 if given the last transaction with nonce 1', async () => {
// given
const safeInstance = getMockedSafeInstance({})
const lastTx = getMockedTxServiceModel({ nonce: 1 })
const expectedResult = '2'
// when
const result = await getNewTxNonce(undefined, lastTx, safeInstance)
// then
expect(result).toBe(expectedResult)
})
it('It should return 0 if given a safe with nonce 0 and no transactions should use safe contract instance for retrieving nonce', async () => {
// given
const safeNonce = '0'
const safeInstance = getMockedSafeInstance({ nonce: safeNonce })
const expectedResult = '0'
const mockFnCall = jest.fn().mockImplementation(() => safeNonce)
const mockFnNonce = jest.fn().mockImplementation(() => ({ call: mockFnCall }))
safeInstance.methods.nonce = mockFnNonce
// when
const result = await getNewTxNonce(undefined, null, safeInstance)
// then
expect(result).toBe(expectedResult)
expect(mockFnNonce).toHaveBeenCalled()
expect(mockFnCall).toHaveBeenCalled()
mockFnNonce.mockRestore()
mockFnCall.mockRestore()
})
it('Given a Safe and the last transaction, should return nonce of the last transaction + 1', async () => {
// given
const safeInstance = getMockedSafeInstance({})
const expectedResult = '11'
const lastTx = getMockedTxServiceModel({ nonce: 10 })
// when
const result = await getNewTxNonce(undefined, lastTx, safeInstance)
// then
expect(result).toBe(expectedResult)
})
it('Given a pre-calculated nonce number should return it', async () => {
// given
const safeInstance = getMockedSafeInstance({})
const expectedResult = '114'
const nextNonce = '114'
// when
const result = await getNewTxNonce(nextNonce, null, safeInstance)
// then
expect(result).toBe(expectedResult)
})
})
jest.mock('axios')
jest.mock('console')
describe('getLastTx', () => {
afterAll(() => {
jest.unmock('axios')
jest.unmock('console')
})
const safeAddress = '0xdfA693da0D16F5E7E78FdCBeDe8FC6eBEa44f1Cf'
it('It should return the last transaction for a given a safe address', async () => {
// given
const lastTx = getMockedTxServiceModel({ nonce: 1 })
const url = buildTxServiceUrl(safeAddress)
// when
// @ts-ignore
axios.get.mockImplementationOnce(() => {
return {
data: {
results: [lastTx],
},
}
})
const result = await getLastTx(safeAddress)
// then
expect(result).toStrictEqual(lastTx)
expect(axios.get).toHaveBeenCalled()
expect(axios.get).toBeCalledWith(url, { params: { limit: 1 } })
})
it('If should return null If catches an error getting last transaction', async () => {
// given
const lastTx = null
const url = buildTxServiceUrl(safeAddress)
// when
// @ts-ignore
axios.get.mockImplementationOnce(() => {
throw new Error()
})
console.error = jest.fn()
const result = await getLastTx(safeAddress)
const spyConsole = jest.spyOn(console, 'error').mockImplementation()
// then
expect(result).toStrictEqual(lastTx)
expect(axios.get).toHaveBeenCalled()
expect(axios.get).toBeCalledWith(url, { params: { limit: 1 } })
expect(spyConsole).toHaveBeenCalled()
})
})

View File

@ -1,72 +0,0 @@
//
import { waitForElement } from '@testing-library/react'
import { Set, Map } from 'immutable'
import { aNewStore } from 'src/store'
import { sleep } from 'src/utils/timer'
import { aMinedSafe } from 'src/test/builder/safe.redux.builder'
import { sendTokenTo, sendEtherTo } from 'src/test/utils/tokenMovements'
import { renderSafeView } from 'src/test/builder/safe.dom.utils'
import { dispatchAddTokenToList } from 'src/test/utils/transactions/moveTokens.helper'
// import { calculateBalanceOf } from 'src/routes/safe/store/actions/fetchTokenBalances'
import updateActiveTokens from 'src/logic/safe/store/actions/updateActiveTokens'
import '@testing-library/jest-dom/extend-expect'
import updateSafe from 'src/logic/safe/store/actions/updateSafe'
import { BALANCE_ROW_TEST_ID } from 'src/routes/safe/components/Balances'
import { getBalanceInEtherOf } from 'src/logic/wallets/getWeb3'
describe('DOM > Feature > Balances', () => {
let store
let safeAddress
beforeEach(async () => {
store = aNewStore()
safeAddress = await aMinedSafe(store)
})
it('Updates token balances automatically', async () => {
const tokensAmount = '100'
const tokenAddress = await sendTokenTo(safeAddress, tokensAmount)
await dispatchAddTokenToList(store, tokenAddress)
const SafeDom = await renderSafeView(store, safeAddress)
// Activate token
const safeTokenBalance = undefined
// const safeTokenBalance = await calculateBalanceOf(tokenAddress, safeAddress, 18)
// expect(safeTokenBalance).toBe(tokensAmount)
const balances = Map({
[tokenAddress]: safeTokenBalance,
})
store.dispatch(updateActiveTokens(safeAddress, Set([tokenAddress])))
store.dispatch(updateSafe({ address: safeAddress, balances }))
await sleep(1000)
const balanceRows = SafeDom.getAllByTestId(BALANCE_ROW_TEST_ID)
expect(balanceRows.length).toBe(2)
await waitForElement(() => SafeDom.getByText(`${tokensAmount} OMG`))
await sendTokenTo(safeAddress, tokensAmount)
await waitForElement(() => SafeDom.getByText(`${parseInt(tokensAmount, 10) * 2} OMG`))
})
it('Updates ether balance automatically', async () => {
const etherAmount = '1'
await sendEtherTo(safeAddress, etherAmount)
const SafeDom = await renderSafeView(store, safeAddress)
const safeEthBalance = await getBalanceInEtherOf(safeAddress)
expect(safeEthBalance).toBe(etherAmount)
const balanceRows = SafeDom.getAllByTestId(BALANCE_ROW_TEST_ID)
expect(balanceRows.length).toBe(1)
await waitForElement(() => SafeDom.getByText(`${etherAmount} ETH`))
await sendEtherTo(safeAddress, etherAmount)
await waitForElement(() => SafeDom.getByText(`${parseInt(etherAmount, 10) * 2} ETH`))
})
})

View File

@ -0,0 +1,162 @@
import { NonPayableTransactionObject } from 'src/types/contracts/types'
import { PromiEvent } from 'web3-core'
import { GnosisSafe } from 'src/types/contracts/GnosisSafe'
import { ContractOptions, ContractSendMethod, DeployOptions, EventData, PastEventOptions } from 'web3-eth-contract'
import {
ConfirmationServiceModel,
TxServiceModel,
} from 'src/logic/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions'
import { DataDecoded } from 'src/routes/safe/store/models/types/transactions'
import { List, Map } from 'immutable'
import { PendingActionValues } from 'src/logic/safe/store/models/types/transaction'
const mockNonPayableTransactionObject = (callResult?: string): NonPayableTransactionObject<string | void | boolean | string[]> => {
return {
arguments: [],
call: (tx?) => new Promise((resolve) => resolve(callResult || '')),
encodeABI: (tx?) => '',
estimateGas: (tx?) => new Promise((resolve) => resolve(1000)),
send: () => { return {} as PromiEvent<any>}
}
}
type SafeMethodsProps = {
threshold?: string
nonce?: string
isOwnerUserAddress?: string,
name?: string,
version?: string
}
export const getMockedSafeInstance = (safeProps: SafeMethodsProps): GnosisSafe => {
const { threshold = '1', nonce = '0', isOwnerUserAddress, name = 'safeName', version = '1.0.0' } = safeProps
return {
defaultAccount: undefined,
defaultBlock: undefined,
defaultChain: undefined,
defaultCommon: undefined,
defaultHardfork: undefined,
handleRevert: false,
options: undefined,
transactionBlockTimeout: 0,
transactionConfirmationBlocks: 0,
transactionPollingTimeout: 0,
clone(): GnosisSafe {
return undefined;
},
constructor(jsonInterface: any[], address?: string, options?: ContractOptions): GnosisSafe {
return undefined;
},
deploy(options: DeployOptions): ContractSendMethod {
return undefined;
},
getPastEvents(event: string, options?: PastEventOptions | ((error: Error, event: EventData) => void), callback?: (error: Error, event: EventData) => void): Promise<EventData[]> {
return undefined;
},
once(event: "AddedOwner" | "ExecutionFromModuleSuccess" | "EnabledModule" | "ChangedMasterCopy" | "ExecutionFromModuleFailure" | "RemovedOwner" | "ApproveHash" | "DisabledModule" | "SignMsg" | "ExecutionSuccess" | "ChangedThreshold" | "ExecutionFailure", cb: any): void {
},
events: { } as any,
methods: {
NAME: (): NonPayableTransactionObject<string> => mockNonPayableTransactionObject(name) as NonPayableTransactionObject<string>,
VERSION: (): NonPayableTransactionObject<string> => mockNonPayableTransactionObject(version) as NonPayableTransactionObject<string>,
addOwnerWithThreshold: (): NonPayableTransactionObject<void> => mockNonPayableTransactionObject() as NonPayableTransactionObject<void>,
approvedHashes: (): NonPayableTransactionObject<string> => mockNonPayableTransactionObject() as NonPayableTransactionObject<string>,
changeMasterCopy: (): NonPayableTransactionObject<void> => mockNonPayableTransactionObject() as NonPayableTransactionObject<void>,
changeThreshold: (): NonPayableTransactionObject<void> => mockNonPayableTransactionObject() as NonPayableTransactionObject<void>,
disableModule: (): NonPayableTransactionObject<void> => mockNonPayableTransactionObject() as NonPayableTransactionObject<void>,
domainSeparator: (): NonPayableTransactionObject<string> => mockNonPayableTransactionObject() as NonPayableTransactionObject<string>,
enableModule: (): NonPayableTransactionObject<void> => mockNonPayableTransactionObject() as NonPayableTransactionObject<void>,
execTransactionFromModule: (): NonPayableTransactionObject<boolean> => mockNonPayableTransactionObject() as NonPayableTransactionObject<boolean>,
execTransactionFromModuleReturnData: (): NonPayableTransactionObject<any> => mockNonPayableTransactionObject() as NonPayableTransactionObject<any>,
getModules: (): NonPayableTransactionObject<string[]> => mockNonPayableTransactionObject() as NonPayableTransactionObject<string[]>,
getThreshold: (): NonPayableTransactionObject<string> => mockNonPayableTransactionObject(threshold) as NonPayableTransactionObject<string>,
isOwner: (): NonPayableTransactionObject<boolean> => mockNonPayableTransactionObject(isOwnerUserAddress) as NonPayableTransactionObject<boolean>,
nonce: (): NonPayableTransactionObject<string> => mockNonPayableTransactionObject(nonce) as NonPayableTransactionObject<string>,
removeOwner: (): NonPayableTransactionObject<void> => mockNonPayableTransactionObject() as NonPayableTransactionObject<void>,
setFallbackHandler: (): NonPayableTransactionObject<void> => mockNonPayableTransactionObject() as NonPayableTransactionObject<void>,
signedMessages: (): NonPayableTransactionObject<string> => mockNonPayableTransactionObject() as NonPayableTransactionObject<string>,
swapOwner: (): NonPayableTransactionObject<void> => mockNonPayableTransactionObject() as NonPayableTransactionObject<void>,
setup: (): NonPayableTransactionObject<void> => mockNonPayableTransactionObject() as NonPayableTransactionObject<void>,
execTransaction: (): NonPayableTransactionObject<boolean> => mockNonPayableTransactionObject() as NonPayableTransactionObject<boolean>,
requiredTxGas: (): NonPayableTransactionObject<string> => mockNonPayableTransactionObject() as NonPayableTransactionObject<string>,
approveHash: (): NonPayableTransactionObject<void> => mockNonPayableTransactionObject() as NonPayableTransactionObject<void>,
signMessage: (): NonPayableTransactionObject<void> => mockNonPayableTransactionObject() as NonPayableTransactionObject<void>,
isValidSignature: (): NonPayableTransactionObject<string> => mockNonPayableTransactionObject() as NonPayableTransactionObject<string>,
getMessageHash: (): NonPayableTransactionObject<string> => mockNonPayableTransactionObject() as NonPayableTransactionObject<string>,
encodeTransactionData: (): NonPayableTransactionObject<string> => mockNonPayableTransactionObject() as NonPayableTransactionObject<string>,
getTransactionHash: (): NonPayableTransactionObject<string> => mockNonPayableTransactionObject() as NonPayableTransactionObject<string>,
} as any
}
}
type TransactionProps = {
baseGas?: number
blockNumber?: number | null
confirmations?: ConfirmationServiceModel[]
confirmationsRequired?: number
creationTx?: boolean | null
data?: string | null
dataDecoded?: DataDecoded
ethGasPrice?: string
executionDate?: string | null
executor?: string
fee?: string
gasPrice?: string
gasToken?: string
gasUsed?: number
isExecuted?: boolean
isSuccessful?: boolean
modified?: string
nonce?: number | null
operation?: number
origin?: string | null
ownersWithPendingActions?: Map<PendingActionValues, List<any>>,
recipient?: string,
refundParams?: string,
refundReceiver?: string
safe?: string
safeTxGas?: number
safeTxHash?: string
signatures?: string
submissionDate?: string | null
to?: string
transactionHash?: string | null
value?: string
}
export const getMockedTxServiceModel = (txProps: TransactionProps): TxServiceModel => {
return {
baseGas: 0,
confirmations: [],
confirmationsRequired: 0,
creationTx: false,
data: null,
ethGasPrice: '',
executionDate: '',
executor: '',
fee: '',
gasPrice: '',
gasToken: '',
gasUsed: 0,
isExecuted: false,
isSuccessful: false,
modified: '',
nonce: 0,
operation: 0,
origin: '',
ownersWithPendingActions: Map(),
recipient: '',
refundParams: '',
refundReceiver: '',
safe: '',
safeTxGas: 0,
safeTxHash: '',
signatures: '',
submissionDate: '',
to: '',
transactionHash: '',
value: '',
...txProps
}
}

View File

@ -1,17 +0,0 @@
export const enhancedFetch = async (url, errMsg) => {
const header = new Headers({
'Access-Control-Allow-Origin': '*',
})
const sentData: any = {
mode: 'cors',
header,
}
const response = await fetch(url, sentData)
if (!response.ok) {
return Promise.reject(new Error(errMsg))
}
return Promise.resolve(response.json())
}