mirror of
https://github.com/status-im/safe-react.git
synced 2025-01-12 19:14:08 +00:00
(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:
parent
ac92f49c72
commit
f1916e92f1
@ -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>
|
||||
|
@ -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)
|
||||
|
101
src/logic/addressBook/utils/__tests__/addressBookUtils.test.ts
Normal file
101
src/logic/addressBook/utils/__tests__/addressBookUtils.test.ts
Normal 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)
|
||||
})
|
||||
})
|
@ -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
|
||||
}
|
||||
|
53
src/logic/currencyValues/__tests__/fetchSafeTokens.test.ts
Normal file
53
src/logic/currencyValues/__tests__/fetchSafeTokens.test.ts
Normal 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 } })
|
||||
})
|
||||
})
|
@ -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')
|
||||
})
|
||||
})
|
@ -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 (
|
||||
|
59
src/logic/safe/store/tests/safe.balances.test.ts
Normal file
59
src/logic/safe/store/tests/safe.balances.test.ts
Normal 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)
|
||||
})
|
||||
})
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
174
src/logic/tokens/utils/__tests__/tokenHelpers.test.ts
Normal file
174
src/logic/tokens/utils/__tests__/tokenHelpers.test.ts
Normal 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()
|
||||
})
|
||||
})
|
@ -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')
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
281
src/logic/wallets/tests/ethAddresses.test.ts
Normal file
281
src/logic/wallets/tests/ethAddresses.test.ts
Normal 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)
|
||||
})
|
||||
})
|
@ -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 = {
|
||||
|
@ -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: '',
|
||||
})
|
||||
|
@ -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: '',
|
||||
})
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
@ -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 (
|
||||
|
@ -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()
|
||||
})
|
||||
})
|
@ -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`))
|
||||
})
|
||||
})
|
162
src/test/utils/safeHelper.ts
Normal file
162
src/test/utils/safeHelper.ts
Normal 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
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user