V1.6.1 - Bugfixes (#432)
* Change label for cancelled tx to 'cancelled' (#393) * Feature 309: Listen for web3connect disconnect event (#324) * add 'disconnect' event listener for web3connect * Update web3connect, set preventDuplicate to true for disconnected message Co-authored-by: Germán Martínez <germartinez@users.noreply.github.com> * Update package.json * Fix signatures order in txData (#429) * Fix signatures order in tx data * extract generateSignaturesFromTxConfirmations to a separate file, rewrite test for signatures Co-authored-by: Mikhail Mikheev <mmvsha73@gmail.com> * check if txNonce is defined in createTransaction, not for truthy * fix txNonce check in createTransaction * check if transaction.nonce is defined, not for thruthy value * fix check for defined nonce Co-authored-by: Germán Martínez <germartinez@users.noreply.github.com>
This commit is contained in:
parent
1ddd17423e
commit
d391d6e7b3
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "safe-react",
|
"name": "safe-react",
|
||||||
"version": "1.6.0",
|
"version": "1.6.1",
|
||||||
"description": "Allowing crypto users manage funds in a safer way",
|
"description": "Allowing crypto users manage funds in a safer way",
|
||||||
"homepage": "https://github.com/gnosis/safe-react#readme",
|
"homepage": "https://github.com/gnosis/safe-react#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
@ -76,7 +76,7 @@
|
||||||
"semver": "^7.1.1",
|
"semver": "^7.1.1",
|
||||||
"squarelink": "^1.1.4",
|
"squarelink": "^1.1.4",
|
||||||
"web3": "1.2.4",
|
"web3": "1.2.4",
|
||||||
"web3connect": "^1.0.0-beta.23"
|
"web3connect": "^1.0.0-beta.25"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "7.7.5",
|
"@babel/cli": "7.7.5",
|
||||||
|
|
|
@ -7,7 +7,7 @@ import Fortmatic from 'fortmatic'
|
||||||
import Portis from '@portis/web3'
|
import Portis from '@portis/web3'
|
||||||
import Squarelink from 'squarelink'
|
import Squarelink from 'squarelink'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
import { fetchProvider } from '~/logic/wallets/store/actions'
|
import { fetchProvider, removeProvider } from '~/logic/wallets/store/actions'
|
||||||
import { getNetwork } from '~/config'
|
import { getNetwork } from '~/config'
|
||||||
import { store } from '~/store'
|
import { store } from '~/store'
|
||||||
|
|
||||||
|
@ -62,6 +62,10 @@ web3Connect.on('connect', (provider: any) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
web3Connect.on('disconnect', () => {
|
||||||
|
store.dispatch(removeProvider())
|
||||||
|
})
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
enqueueSnackbar: Function,
|
enqueueSnackbar: Function,
|
||||||
closeSnackbar: Function,
|
closeSnackbar: Function,
|
||||||
|
|
|
@ -89,6 +89,7 @@ export const NOTIFICATIONS: Notifications = {
|
||||||
variant: SUCCESS,
|
variant: SUCCESS,
|
||||||
persist: false,
|
persist: false,
|
||||||
autoHideDuration: shortDuration,
|
autoHideDuration: shortDuration,
|
||||||
|
preventDuplicate: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
UNLOCK_WALLET_MSG: {
|
UNLOCK_WALLET_MSG: {
|
||||||
|
|
|
@ -1,19 +1,38 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { List } from 'immutable'
|
import { List } from 'immutable'
|
||||||
|
import { type Confirmation } from '~/routes/safe/store/models/confirmation'
|
||||||
|
|
||||||
const generateSignatureFrom = (account: string) => `000000000000000000000000${account.replace(
|
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
|
||||||
'0x',
|
// https://github.com/gnosis/safe-contracts/blob/master/test/gnosisSafeTeamEdition.js#L26
|
||||||
'',
|
export const generateSignaturesFromTxConfirmations = (
|
||||||
)}000000000000000000000000000000000000000000000000000000000000000001`
|
confirmations: List<Confirmation>,
|
||||||
|
preApprovingOwner?: string,
|
||||||
|
) => {
|
||||||
|
// The constant parts need to be sorted so that the recovered signers are sorted ascending
|
||||||
|
// (natural order) by address (not checksummed).
|
||||||
|
const confirmationsMap = confirmations.reduce((map, obj) => {
|
||||||
|
map[obj.owner.address.toLowerCase()] = obj // eslint-disable-line no-param-reassign
|
||||||
|
return map
|
||||||
|
}, {})
|
||||||
|
|
||||||
export const buildSignaturesFrom = (ownersWhoHasSigned: List<string>, sender: string) => {
|
if (preApprovingOwner) {
|
||||||
const signatures = ownersWhoHasSigned.push(sender)
|
confirmationsMap[preApprovingOwner.toLowerCase()] = { owner: preApprovingOwner }
|
||||||
const orderedSignatures = signatures.sort() // JS by default sorts in a non case-senstive way
|
}
|
||||||
|
|
||||||
let sigs = '0x'
|
let sigs = '0x'
|
||||||
orderedSignatures.forEach((owner: string) => {
|
Object.keys(confirmationsMap)
|
||||||
sigs += generateSignatureFrom(owner)
|
.sort()
|
||||||
|
.forEach((addr) => {
|
||||||
|
const conf = confirmationsMap[addr]
|
||||||
|
if (conf.signature) {
|
||||||
|
sigs += conf.signature.slice(2)
|
||||||
|
} else {
|
||||||
|
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
|
||||||
|
sigs += `000000000000000000000000${addr.replace(
|
||||||
|
'0x',
|
||||||
|
'',
|
||||||
|
)}000000000000000000000000000000000000000000000000000000000000000001`
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return sigs
|
return sigs
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json'
|
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json'
|
||||||
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
||||||
import { getWeb3, getAccountFrom } from '~/logic/wallets/getWeb3'
|
import { getWeb3, getAccountFrom } from '~/logic/wallets/getWeb3'
|
||||||
import { generateSignaturesFromTxConfirmations } from '~/routes/safe/store/actions/processTransaction'
|
import { generateSignaturesFromTxConfirmations } from '~/logic/safe/safeTxSigner'
|
||||||
import { calculateGasOf, calculateGasPrice } from '~/logic/wallets/ethTransactions'
|
import { calculateGasOf, calculateGasPrice } from '~/logic/wallets/ethTransactions'
|
||||||
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
|
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
|
||||||
import { CALL } from '.'
|
import { CALL } from '.'
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { createAction } from 'redux-actions'
|
import { createAction } from 'redux-actions'
|
||||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||||
import { NOTIFICATIONS, showSnackbar } from '~/logic/notifications'
|
import { NOTIFICATIONS, enhanceSnackbarForAction } from '~/logic/notifications'
|
||||||
import { getWeb3, resetWeb3 } from '~/logic/wallets/getWeb3'
|
import { getWeb3, resetWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
|
import enqueueSnackbar from '~/logic/notifications/store/actions/enqueueSnackbar'
|
||||||
|
|
||||||
export const REMOVE_PROVIDER = 'REMOVE_PROVIDER'
|
export const REMOVE_PROVIDER = 'REMOVE_PROVIDER'
|
||||||
|
|
||||||
const removeProvider = createAction<string, *, *>(REMOVE_PROVIDER)
|
const removeProvider = createAction<string, *, *>(REMOVE_PROVIDER)
|
||||||
|
|
||||||
export default (enqueueSnackbar: Function, closeSnackbar: Function) => (dispatch: ReduxDispatch<*>) => {
|
export default () => (dispatch: ReduxDispatch<*>) => {
|
||||||
showSnackbar(NOTIFICATIONS.WALLET_DISCONNECTED_MSG, enqueueSnackbar, closeSnackbar)
|
dispatch(enqueueSnackbar(enhanceSnackbarForAction(NOTIFICATIONS.WALLET_DISCONNECTED_MSG)))
|
||||||
|
|
||||||
const web3 = getWeb3()
|
const web3 = getWeb3()
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ const statusToIcon = {
|
||||||
|
|
||||||
const statusToLabel = {
|
const statusToLabel = {
|
||||||
success: 'Success',
|
success: 'Success',
|
||||||
cancelled: 'Failed',
|
cancelled: 'Cancelled',
|
||||||
awaiting_your_confirmation: 'Awaiting your confirmation',
|
awaiting_your_confirmation: 'Awaiting your confirmation',
|
||||||
awaiting_confirmations: 'Awaiting confirmations',
|
awaiting_confirmations: 'Awaiting confirmations',
|
||||||
awaiting_execution: 'Awaiting execution',
|
awaiting_execution: 'Awaiting execution',
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { List, Map } from 'immutable'
|
import { List, Map } from 'immutable'
|
||||||
import { createSelector, createStructuredSelector, type Selector } from 'reselect'
|
import {
|
||||||
|
createSelector,
|
||||||
|
createStructuredSelector,
|
||||||
|
type Selector,
|
||||||
|
} from 'reselect'
|
||||||
import {
|
import {
|
||||||
safeSelector,
|
safeSelector,
|
||||||
safeActiveTokensSelector,
|
safeActiveTokensSelector,
|
||||||
|
@ -11,16 +15,29 @@ import {
|
||||||
type RouterProps,
|
type RouterProps,
|
||||||
type SafeSelectorProps,
|
type SafeSelectorProps,
|
||||||
} from '~/routes/safe/store/selectors'
|
} from '~/routes/safe/store/selectors'
|
||||||
import { providerNameSelector, userAccountSelector, networkSelector } from '~/logic/wallets/store/selectors'
|
import {
|
||||||
|
providerNameSelector,
|
||||||
|
userAccountSelector,
|
||||||
|
networkSelector,
|
||||||
|
} from '~/logic/wallets/store/selectors'
|
||||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||||
import { type GlobalState } from '~/store'
|
import { type GlobalState } from '~/store'
|
||||||
import { isUserOwner } from '~/logic/wallets/ethAddresses'
|
import { isUserOwner } from '~/logic/wallets/ethAddresses'
|
||||||
import { orderedTokenListSelector, tokensSelector } from '~/logic/tokens/store/selectors'
|
import {
|
||||||
|
orderedTokenListSelector,
|
||||||
|
tokensSelector,
|
||||||
|
} from '~/logic/tokens/store/selectors'
|
||||||
import { type Token } from '~/logic/tokens/store/model/token'
|
import { type Token } from '~/logic/tokens/store/model/token'
|
||||||
import { type Transaction, type TransactionStatus } from '~/routes/safe/store/models/transaction'
|
import {
|
||||||
|
type Transaction,
|
||||||
|
type TransactionStatus,
|
||||||
|
} from '~/routes/safe/store/models/transaction'
|
||||||
import { safeParamAddressSelector } from '../store/selectors'
|
import { safeParamAddressSelector } from '../store/selectors'
|
||||||
import { getEthAsToken } from '~/logic/tokens/utils/tokenHelpers'
|
import { getEthAsToken } from '~/logic/tokens/utils/tokenHelpers'
|
||||||
import { currencyValuesListSelector, currentCurrencySelector } from '~/logic/currencyValues/store/selectors'
|
import {
|
||||||
|
currencyValuesListSelector,
|
||||||
|
currentCurrencySelector,
|
||||||
|
} from '~/logic/currencyValues/store/selectors'
|
||||||
import type { BalanceCurrencyType } from '~/logic/currencyValues/store/model/currencyValues'
|
import type { BalanceCurrencyType } from '~/logic/currencyValues/store/model/currencyValues'
|
||||||
import type { IncomingTransaction } from '~/routes/safe/store/models/incomingTransaction'
|
import type { IncomingTransaction } from '~/routes/safe/store/models/incomingTransaction'
|
||||||
|
|
||||||
|
@ -35,10 +52,14 @@ export type SelectorProps = {
|
||||||
safeUrl: string,
|
safeUrl: string,
|
||||||
currencySelected: string,
|
currencySelected: string,
|
||||||
currencyValues: BalanceCurrencyType[],
|
currencyValues: BalanceCurrencyType[],
|
||||||
transactions: List<Transaction | IncomingTransaction>,
|
transactions: List<Transaction | IncomingTransaction>
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTxStatus = (tx: Transaction, userAddress: string, safe: Safe): TransactionStatus => {
|
const getTxStatus = (
|
||||||
|
tx: Transaction,
|
||||||
|
userAddress: string,
|
||||||
|
safe: Safe,
|
||||||
|
): TransactionStatus => {
|
||||||
let txStatus
|
let txStatus
|
||||||
if (tx.executionTxHash) {
|
if (tx.executionTxHash) {
|
||||||
txStatus = 'success'
|
txStatus = 'success'
|
||||||
|
@ -51,37 +72,54 @@ const getTxStatus = (tx: Transaction, userAddress: string, safe: Safe): Transact
|
||||||
} else if (!tx.confirmations.size) {
|
} else if (!tx.confirmations.size) {
|
||||||
txStatus = 'pending'
|
txStatus = 'pending'
|
||||||
} else {
|
} else {
|
||||||
const userConfirmed = tx.confirmations.filter((conf) => conf.owner.address === userAddress).size === 1
|
const userConfirmed = tx.confirmations.filter((conf) => conf.owner.address === userAddress)
|
||||||
|
.size === 1
|
||||||
const userIsSafeOwner = safe.owners.filter((owner) => owner.address === userAddress).size === 1
|
const userIsSafeOwner = safe.owners.filter((owner) => owner.address === userAddress).size === 1
|
||||||
txStatus = !userConfirmed && userIsSafeOwner ? 'awaiting_your_confirmation' : 'awaiting_confirmations'
|
txStatus = !userConfirmed && userIsSafeOwner
|
||||||
|
? 'awaiting_your_confirmation'
|
||||||
|
: 'awaiting_confirmations'
|
||||||
}
|
}
|
||||||
|
|
||||||
return txStatus
|
return txStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
export const grantedSelector: Selector<GlobalState, RouterProps, boolean> = createSelector(
|
export const grantedSelector: Selector<
|
||||||
|
GlobalState,
|
||||||
|
RouterProps,
|
||||||
|
boolean
|
||||||
|
> = createSelector(
|
||||||
userAccountSelector,
|
userAccountSelector,
|
||||||
safeSelector,
|
safeSelector,
|
||||||
(userAccount: string, safe: Safe | typeof undefined): boolean => isUserOwner(safe, userAccount),
|
(userAccount: string, safe: Safe | typeof undefined): boolean => isUserOwner(safe, userAccount),
|
||||||
)
|
)
|
||||||
|
|
||||||
const safeEthAsTokenSelector: Selector<GlobalState, RouterProps, ?Token> = createSelector(
|
const safeEthAsTokenSelector: Selector<
|
||||||
safeSelector,
|
GlobalState,
|
||||||
(safe: Safe) => {
|
RouterProps,
|
||||||
|
?Token
|
||||||
|
> = createSelector(safeSelector, (safe: Safe) => {
|
||||||
if (!safe) {
|
if (!safe) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
return getEthAsToken(safe.ethBalance)
|
return getEthAsToken(safe.ethBalance)
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
const extendedSafeTokensSelector: Selector<GlobalState, RouterProps, List<Token>> = createSelector(
|
const extendedSafeTokensSelector: Selector<
|
||||||
|
GlobalState,
|
||||||
|
RouterProps,
|
||||||
|
List<Token>
|
||||||
|
> = createSelector(
|
||||||
safeActiveTokensSelector,
|
safeActiveTokensSelector,
|
||||||
safeBalancesSelector,
|
safeBalancesSelector,
|
||||||
tokensSelector,
|
tokensSelector,
|
||||||
safeEthAsTokenSelector,
|
safeEthAsTokenSelector,
|
||||||
(safeTokens: List<string>, balances: Map<string, string>, tokensList: Map<string, Token>, ethAsToken: Token) => {
|
(
|
||||||
|
safeTokens: List<string>,
|
||||||
|
balances: Map<string, string>,
|
||||||
|
tokensList: Map<string, Token>,
|
||||||
|
ethAsToken: Token,
|
||||||
|
) => {
|
||||||
const extendedTokens = Map().withMutations((map) => {
|
const extendedTokens = Map().withMutations((map) => {
|
||||||
safeTokens.forEach((tokenAddress: string) => {
|
safeTokens.forEach((tokenAddress: string) => {
|
||||||
const baseToken = tokensList.get(tokenAddress)
|
const baseToken = tokensList.get(tokenAddress)
|
||||||
|
@ -101,7 +139,11 @@ const extendedSafeTokensSelector: Selector<GlobalState, RouterProps, List<Token>
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const extendedTransactionsSelector: Selector<GlobalState, RouterProps, List<Transaction | IncomingTransaction>> = createSelector(
|
const extendedTransactionsSelector: Selector<
|
||||||
|
GlobalState,
|
||||||
|
RouterProps,
|
||||||
|
List<Transaction | IncomingTransaction>
|
||||||
|
> = createSelector(
|
||||||
safeSelector,
|
safeSelector,
|
||||||
userAccountSelector,
|
userAccountSelector,
|
||||||
safeTransactionsSelector,
|
safeTransactionsSelector,
|
||||||
|
@ -114,17 +156,21 @@ const extendedTransactionsSelector: Selector<GlobalState, RouterProps, List<Tran
|
||||||
// it means that the transaction was cancelled (Replaced) and shouldn't get executed
|
// it means that the transaction was cancelled (Replaced) and shouldn't get executed
|
||||||
let replacementTransaction
|
let replacementTransaction
|
||||||
if (!tx.isExecuted) {
|
if (!tx.isExecuted) {
|
||||||
replacementTransaction = transactions.size > 1 && transactions.findLast(
|
replacementTransaction = transactions.size > 1
|
||||||
(transaction) => (
|
&& transactions.findLast(
|
||||||
transaction.isExecuted && transaction.nonce && transaction.nonce >= tx.nonce
|
(transaction) => transaction.isExecuted
|
||||||
),
|
&& transaction.nonce != null
|
||||||
|
&& transaction.nonce >= tx.nonce,
|
||||||
)
|
)
|
||||||
if (replacementTransaction) {
|
if (replacementTransaction) {
|
||||||
extendedTx = tx.set('cancelled', true)
|
extendedTx = tx.set('cancelled', true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return extendedTx.set('status', getTxStatus(extendedTx, userAddress, safe))
|
return extendedTx.set(
|
||||||
|
'status',
|
||||||
|
getTxStatus(extendedTx, userAddress, safe),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
return List([...extendedTransactions, ...incomingTransactions])
|
return List([...extendedTransactions, ...incomingTransactions])
|
||||||
|
|
|
@ -78,7 +78,7 @@ const createTransaction = ({
|
||||||
const from = userAccountSelector(state)
|
const from = userAccountSelector(state)
|
||||||
const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
|
const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
|
||||||
const threshold = await safeInstance.getThreshold()
|
const threshold = await safeInstance.getThreshold()
|
||||||
const nonce = txNonce || await getLastPendingTxNonce(safeAddress)
|
const nonce = typeof txNonce !== 'undefined' ? txNonce : await getLastPendingTxNonce(safeAddress)
|
||||||
const isExecution = threshold.toNumber() === 1 || shouldExecute
|
const isExecution = threshold.toNumber() === 1 || shouldExecute
|
||||||
|
|
||||||
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
|
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||||
import { List } from 'immutable'
|
|
||||||
import { type Confirmation } from '~/routes/safe/store/models/confirmation'
|
|
||||||
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
||||||
import { userAccountSelector } from '~/logic/wallets/store/selectors'
|
import { userAccountSelector } from '~/logic/wallets/store/selectors'
|
||||||
import fetchSafe from '~/routes/safe/store/actions/fetchSafe'
|
import fetchSafe from '~/routes/safe/store/actions/fetchSafe'
|
||||||
|
@ -16,44 +14,10 @@ import {
|
||||||
TX_TYPE_EXECUTION,
|
TX_TYPE_EXECUTION,
|
||||||
TX_TYPE_CONFIRMATION,
|
TX_TYPE_CONFIRMATION,
|
||||||
} from '~/logic/safe/transactions'
|
} from '~/logic/safe/transactions'
|
||||||
|
import { generateSignaturesFromTxConfirmations } from '~/logic/safe/safeTxSigner'
|
||||||
import { type NotificationsQueue, getNotificationsFromTxType, showSnackbar } from '~/logic/notifications'
|
import { type NotificationsQueue, getNotificationsFromTxType, showSnackbar } from '~/logic/notifications'
|
||||||
import { getErrorMessage } from '~/test/utils/ethereumErrors'
|
import { getErrorMessage } from '~/test/utils/ethereumErrors'
|
||||||
|
|
||||||
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
|
|
||||||
// https://github.com/gnosis/safe-contracts/blob/master/test/gnosisSafeTeamEdition.js#L26
|
|
||||||
export const generateSignaturesFromTxConfirmations = (
|
|
||||||
confirmations: List<Confirmation>,
|
|
||||||
preApprovingOwner?: string,
|
|
||||||
) => {
|
|
||||||
// The constant parts need to be sorted so that the recovered signers are sorted ascending
|
|
||||||
// (natural order) by address (not checksummed).
|
|
||||||
const confirmationsMap = confirmations.reduce((map, obj) => {
|
|
||||||
map[obj.owner.address] = obj // eslint-disable-line no-param-reassign
|
|
||||||
return map
|
|
||||||
}, {})
|
|
||||||
|
|
||||||
if (preApprovingOwner) {
|
|
||||||
confirmationsMap[preApprovingOwner] = { owner: preApprovingOwner }
|
|
||||||
}
|
|
||||||
|
|
||||||
let sigs = '0x'
|
|
||||||
Object.keys(confirmationsMap)
|
|
||||||
.sort()
|
|
||||||
.forEach((addr) => {
|
|
||||||
const conf = confirmationsMap[addr]
|
|
||||||
if (conf.signature) {
|
|
||||||
sigs += conf.signature.slice(2)
|
|
||||||
} else {
|
|
||||||
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
|
|
||||||
sigs += `000000000000000000000000${addr.replace(
|
|
||||||
'0x',
|
|
||||||
'',
|
|
||||||
)}000000000000000000000000000000000000000000000000000000000000000001`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return sigs
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProcessTransactionArgs = {
|
type ProcessTransactionArgs = {
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
tx: Transaction,
|
tx: Transaction,
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { List } from 'immutable'
|
import { List } from 'immutable'
|
||||||
import { buildSignaturesFrom } from '~/logic/safe/safeTxSigner'
|
import { generateSignaturesFromTxConfirmations } from '~/logic/safe/safeTxSigner'
|
||||||
|
|
||||||
|
const makeMockConfirmation = (address: string) => ({ owner: { address } })
|
||||||
|
|
||||||
describe('Signatures Blockchain Test', () => {
|
describe('Signatures Blockchain Test', () => {
|
||||||
it('generates signatures in natural order even checksumed', async () => {
|
it('generates signatures in natural order even checksumed', async () => {
|
||||||
|
@ -8,39 +10,104 @@ describe('Signatures Blockchain Test', () => {
|
||||||
const userA = 'baR'
|
const userA = 'baR'
|
||||||
const userB = 'baz'
|
const userB = 'baz'
|
||||||
const userC = 'foZa'
|
const userC = 'foZa'
|
||||||
const sender = 'foZ'
|
const userD = 'foZ'
|
||||||
|
|
||||||
|
const confirmationA = makeMockConfirmation(userA)
|
||||||
|
const confirmationB = makeMockConfirmation(userB)
|
||||||
|
const confirmationC = makeMockConfirmation(userC)
|
||||||
|
const confirmationD = makeMockConfirmation(userD)
|
||||||
|
|
||||||
// WHEN
|
// WHEN
|
||||||
const result = '0x'
|
const result = '0x'
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
+ '000000000000000000000000' +
|
+ "000000000000000000000000" +
|
||||||
'baR'
|
'bar'
|
||||||
+ '000000000000000000000000000000000000000000000000000000000000000001'
|
+ '000000000000000000000000000000000000000000000000000000000000000001'
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
+ '000000000000000000000000' +
|
+ "000000000000000000000000" +
|
||||||
'baz'
|
'baz'
|
||||||
+ '000000000000000000000000000000000000000000000000000000000000000001'
|
+ '000000000000000000000000000000000000000000000000000000000000000001'
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
+ '000000000000000000000000' +
|
+ "000000000000000000000000" +
|
||||||
'foZ'
|
'foz'
|
||||||
+ '000000000000000000000000000000000000000000000000000000000000000001'
|
+ '000000000000000000000000000000000000000000000000000000000000000001'
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
+ '000000000000000000000000' +
|
+ "000000000000000000000000" +
|
||||||
'foZa'
|
'foza'
|
||||||
+ '000000000000000000000000000000000000000000000000000000000000000001'
|
+ '000000000000000000000000000000000000000000000000000000000000000001'
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
expect(buildSignaturesFrom(List([userA, userB, userC]), sender)).toEqual(result)
|
expect(
|
||||||
expect(buildSignaturesFrom(List([userB, userA, userC]), sender)).toEqual(result)
|
generateSignaturesFromTxConfirmations(
|
||||||
expect(buildSignaturesFrom(List([userA, sender, userC]), userB)).toEqual(result)
|
List([confirmationA, confirmationB, confirmationC]),
|
||||||
expect(buildSignaturesFrom(List([sender, userA, userC]), userB)).toEqual(result)
|
userD,
|
||||||
expect(buildSignaturesFrom(List([userB, sender, userC]), userA)).toEqual(result)
|
),
|
||||||
expect(buildSignaturesFrom(List([sender, userB, userC]), userA)).toEqual(result)
|
).toEqual(result)
|
||||||
expect(buildSignaturesFrom(List([userA, userB, sender]), userC)).toEqual(result)
|
expect(
|
||||||
expect(buildSignaturesFrom(List([userB, userA, sender]), userC)).toEqual(result)
|
generateSignaturesFromTxConfirmations(
|
||||||
expect(buildSignaturesFrom(List([userA, sender, userB]), userC)).toEqual(result)
|
List([confirmationB, confirmationA, confirmationC]),
|
||||||
expect(buildSignaturesFrom(List([sender, userA, userB]), userC)).toEqual(result)
|
userD,
|
||||||
expect(buildSignaturesFrom(List([userB, sender, userA]), userC)).toEqual(result)
|
),
|
||||||
expect(buildSignaturesFrom(List([sender, userB, userA]), userC)).toEqual(result)
|
).toEqual(result)
|
||||||
|
expect(
|
||||||
|
generateSignaturesFromTxConfirmations(
|
||||||
|
List([confirmationA, confirmationD, confirmationC]),
|
||||||
|
userB,
|
||||||
|
),
|
||||||
|
).toEqual(result)
|
||||||
|
expect(
|
||||||
|
generateSignaturesFromTxConfirmations(
|
||||||
|
List([confirmationD, confirmationA, confirmationC]),
|
||||||
|
userB,
|
||||||
|
),
|
||||||
|
).toEqual(result)
|
||||||
|
expect(
|
||||||
|
generateSignaturesFromTxConfirmations(
|
||||||
|
List([confirmationB, confirmationD, confirmationC]),
|
||||||
|
userA,
|
||||||
|
),
|
||||||
|
).toEqual(result)
|
||||||
|
expect(
|
||||||
|
generateSignaturesFromTxConfirmations(
|
||||||
|
List([confirmationD, confirmationB, confirmationC]),
|
||||||
|
userA,
|
||||||
|
),
|
||||||
|
).toEqual(result)
|
||||||
|
expect(
|
||||||
|
generateSignaturesFromTxConfirmations(
|
||||||
|
List([confirmationA, confirmationB, confirmationD]),
|
||||||
|
userC,
|
||||||
|
),
|
||||||
|
).toEqual(result)
|
||||||
|
expect(
|
||||||
|
generateSignaturesFromTxConfirmations(
|
||||||
|
List([confirmationB, confirmationA, confirmationD]),
|
||||||
|
userC,
|
||||||
|
),
|
||||||
|
).toEqual(result)
|
||||||
|
expect(
|
||||||
|
generateSignaturesFromTxConfirmations(
|
||||||
|
List([confirmationA, confirmationD, confirmationB]),
|
||||||
|
userC,
|
||||||
|
),
|
||||||
|
).toEqual(result)
|
||||||
|
expect(
|
||||||
|
generateSignaturesFromTxConfirmations(
|
||||||
|
List([confirmationD, confirmationA, confirmationB]),
|
||||||
|
userC,
|
||||||
|
),
|
||||||
|
).toEqual(result)
|
||||||
|
expect(
|
||||||
|
generateSignaturesFromTxConfirmations(
|
||||||
|
List([confirmationB, confirmationD, confirmationA]),
|
||||||
|
userC,
|
||||||
|
),
|
||||||
|
).toEqual(result)
|
||||||
|
expect(
|
||||||
|
generateSignaturesFromTxConfirmations(
|
||||||
|
List([confirmationD, confirmationB, confirmationA]),
|
||||||
|
userC,
|
||||||
|
),
|
||||||
|
).toEqual(result)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue