From 9259d79f615853124471090c12d41ab40282d5f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Fri, 27 Sep 2019 11:20:35 +0200 Subject: [PATCH] Refactor create/confirm transactions --- src/logic/safe/transactions/send.js | 156 ++---------------- .../safe/store/actions/createTransaction.js | 103 ++++++++---- .../safe/store/actions/processTransaction.js | 116 +++++++++---- 3 files changed, 173 insertions(+), 202 deletions(-) diff --git a/src/logic/safe/transactions/send.js b/src/logic/safe/transactions/send.js index c702d2b1..ba1044a3 100644 --- a/src/logic/safe/transactions/send.js +++ b/src/logic/safe/transactions/send.js @@ -1,24 +1,14 @@ // @flow import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json' import { getWeb3 } from '~/logic/wallets/getWeb3' -import { getStandardTokenContract } from '~/logic/tokens/store/actions/fetchTokens' -import { EMPTY_DATA } from '~/logic/wallets/ethTransactions' -import { isEther } from '~/logic/tokens/utils/tokenHelpers' -import { type Token } from '~/logic/tokens/store/model/token' -import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts' -import { type Operation, saveTxToHistory } from '~/logic/safe/transactions' -import { type NotificationsQueue } from '~/logic/notifications' +import { type Operation } from '~/logic/safe/transactions' import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses' -import { getErrorMessage } from '~/test/utils/ethereumErrors' export const CALL = 0 export const TX_TYPE_EXECUTION = 'execution' export const TX_TYPE_CONFIRMATION = 'confirmation' -export const approveTransaction = async ( - notiQueue: NotificationsQueue, - enqueueSnackbar: Function, - closeSnackbar: Function, +export const getApprovalTransaction = async ( safeInstance: any, to: string, valueInWei: number | string, @@ -43,62 +33,19 @@ export const approveTransaction = async ( }, ) - const beforeExecutionKey = enqueueSnackbar(notiQueue.beforeExecution.message, notiQueue.beforeExecution.options) - let pendingExecutionKey try { const web3 = getWeb3() const contract = new web3.eth.Contract(GnosisSafeSol.abi, safeInstance.address) - const transactionHash = await contract.methods - .approveHash(txHash) - .send({ - from: sender, - }) - .once('transactionHash', () => { - closeSnackbar(beforeExecutionKey) - pendingExecutionKey = enqueueSnackbar( - notiQueue.pendingExecution.single.message, - notiQueue.pendingExecution.single.options, - ) - }) - .on('error', (error) => { - console.error('Tx error:', error) - }) - .then(async (receipt) => { - closeSnackbar(pendingExecutionKey) - await saveTxToHistory( - safeInstance, - to, - valueInWei, - data, - operation, - nonce, - receipt.transactionHash, - sender, - TX_TYPE_CONFIRMATION, - ) - enqueueSnackbar(notiQueue.afterExecution.message, notiQueue.afterExecution.options) - return receipt.transactionHash - }) + return contract.methods.approveHash(txHash) + } catch (err) { + console.error(`Error while approving transaction: ${err}`) - return transactionHash - } catch (error) { - closeSnackbar(beforeExecutionKey) - closeSnackbar(pendingExecutionKey) - enqueueSnackbar(notiQueue.afterExecutionError.message, notiQueue.afterExecutionError.options) - - const executeData = safeInstance.contract.methods.approveHash(txHash).encodeABI() - const errMsg = await getErrorMessage(safeInstance.address, 0, executeData, sender) - console.error(`Error executing the TX: ${errMsg}`) - - throw error + throw err } } -export const executeTransaction = async ( - notiQueue: NotificationsQueue, - enqueueSnackbar: Function, - closeSnackbar: Function, +export const getExecutionTransaction = async ( safeInstance: any, to: string, valueInWei: number | string, @@ -106,95 +53,16 @@ export const executeTransaction = async ( operation: Operation, nonce: string | number, sender: string, - signatures?: string, + sigs: string, ) => { - let sigs = signatures - - // https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures - if (!sigs) { - sigs = `0x000000000000000000000000${sender.replace( - '0x', - '', - )}000000000000000000000000000000000000000000000000000000000000000001` - } - - const beforeExecutionKey = enqueueSnackbar(notiQueue.beforeExecution.message, notiQueue.beforeExecution.options) - let pendingExecutionKey try { const web3 = getWeb3() const contract = new web3.eth.Contract(GnosisSafeSol.abi, safeInstance.address) - const transactionHash = await contract.methods - .execTransaction(to, valueInWei, data, operation, 0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, sigs) - .send({ - from: sender, - }) - .once('transactionHash', () => { - closeSnackbar(beforeExecutionKey) - pendingExecutionKey = enqueueSnackbar( - notiQueue.pendingExecution.single.message, - notiQueue.pendingExecution.single.options, - ) - }) - .on('error', (error) => { - console.error('Tx error:', error) - }) - .then(async (receipt) => { - closeSnackbar(pendingExecutionKey) - await saveTxToHistory( - safeInstance, - to, - valueInWei, - data, - operation, - nonce, - receipt.transactionHash, - sender, - TX_TYPE_EXECUTION, - ) - enqueueSnackbar(notiQueue.afterExecution.message, notiQueue.afterExecution.options) - return receipt.transactionHash - }) + return contract.methods.execTransaction(to, valueInWei, data, operation, 0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, sigs) + } catch (err) { + console.error(`Error while creating transaction: ${err}`) - return transactionHash - } catch (error) { - closeSnackbar(beforeExecutionKey) - closeSnackbar(pendingExecutionKey) - enqueueSnackbar(notiQueue.afterExecutionError.message, notiQueue.afterExecutionError.options) - - const executeDataUsedSignatures = safeInstance.contract.methods - .execTransaction(to, valueInWei, data, operation, 0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, sigs) - .encodeABI() - const errMsg = await getErrorMessage(safeInstance.address, 0, executeDataUsedSignatures, sender) - console.error(`Error executing the TX: ${errMsg}`) - - throw error + throw err } } - -export const createTransaction = async (safeAddress: string, to: string, valueInEth: string, token: Token) => { - const safeInstance = await getGnosisSafeInstanceAt(safeAddress) - const web3 = getWeb3() - const from = web3.currentProvider.selectedAddress - const threshold = await safeInstance.getThreshold() - const nonce = (await safeInstance.nonce()).toString() - const valueInWei = web3.utils.toWei(valueInEth, 'ether') - const isExecution = threshold.toNumber() === 1 - - let txData = EMPTY_DATA - if (!isEther(token.symbol)) { - const StandardToken = await getStandardTokenContract() - const sendToken = await StandardToken.at(token.address) - - txData = sendToken.contract.transfer(to, valueInWei).encodeABI() - } - - let txHash - if (isExecution) { - txHash = await executeTransaction(safeInstance, to, valueInWei, txData, CALL, nonce, from) - } else { - // txHash = await approveTransaction(safeAddress, to, valueInWei, txData, CALL, nonce) - } - - return txHash -} diff --git a/src/routes/safe/store/actions/createTransaction.js b/src/routes/safe/store/actions/createTransaction.js index 244ee24d..bc06da1e 100644 --- a/src/routes/safe/store/actions/createTransaction.js +++ b/src/routes/safe/store/actions/createTransaction.js @@ -6,9 +6,17 @@ import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions' import { type GlobalState } from '~/store' import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts' import { - type NotifiedTransaction, approveTransaction, executeTransaction, CALL, + getApprovalTransaction, + getExecutionTransaction, + CALL, + type NotifiedTransaction, + TX_TYPE_CONFIRMATION, + TX_TYPE_EXECUTION, + saveTxToHistory, } from '~/logic/safe/transactions' -import { getNofiticationsFromTxType } from '~/logic/notifications' +import { getNofiticationsFromTxType, type NotificationsQueue } from '~/logic/notifications' +import { getErrorMessage } from '~/test/utils/ethereumErrors' +import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses' const createTransaction = ( safeAddress: string, @@ -28,39 +36,78 @@ const createTransaction = ( const nonce = (await safeInstance.nonce()).toString() const isExecution = threshold.toNumber() === 1 || shouldExecute - const notificationsQueue = getNofiticationsFromTxType(notifiedTransaction) + // https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures + const sigs = `0x000000000000000000000000${from.replace( + '0x', + '', + )}000000000000000000000000000000000000000000000000000000000000000001` + + const notificationsQueue: NotificationsQueue = getNofiticationsFromTxType(notifiedTransaction) + const beforeExecutionKey = enqueueSnackbar( + notificationsQueue.beforeExecution.message, + notificationsQueue.beforeExecution.options, + ) + let pendingExecutionKey let txHash + let tx try { if (isExecution) { - txHash = await executeTransaction( - notificationsQueue, - enqueueSnackbar, - closeSnackbar, - safeInstance, - to, - valueInWei, - txData, - CALL, - nonce, - from, - ) + tx = await getExecutionTransaction(safeInstance, to, valueInWei, txData, CALL, nonce, from, sigs) } else { - txHash = await approveTransaction( - notificationsQueue, - enqueueSnackbar, - closeSnackbar, - safeInstance, - to, - valueInWei, - txData, - CALL, - nonce, - from, - ) + tx = await getApprovalTransaction(safeInstance, to, valueInWei, txData, CALL, nonce, from) } + + const sendParams = { from } + // if not set owner management tests will fail on ganache + if (process.env.NODE_ENV === 'test') { + sendParams.gas = '7000000' + } + + await tx + .send(sendParams) + .once('transactionHash', (hash) => { + txHash = hash + closeSnackbar(beforeExecutionKey) + pendingExecutionKey = enqueueSnackbar( + (shouldExecute) + ? notificationsQueue.pendingExecution.single.message + : notificationsQueue.pendingExecution.multiple.message, + notificationsQueue.pendingExecution.single.options, + ) + }) + .on('error', (error) => { + console.error('Tx error: ', error) + }) + .then(async (receipt) => { + closeSnackbar(pendingExecutionKey) + await saveTxToHistory( + safeInstance, + to, + valueInWei, + txData, + CALL, + nonce, + receipt.transactionHash, + from, + isExecution ? TX_TYPE_EXECUTION : TX_TYPE_CONFIRMATION, + ) + if (isExecution) { + enqueueSnackbar(notificationsQueue.afterExecution.message, notificationsQueue.afterExecution.options) + } + + return receipt.transactionHash + }) } catch (err) { - console.error(`Error while creating transaction: ${err}`) + closeSnackbar(beforeExecutionKey) + closeSnackbar(pendingExecutionKey) + enqueueSnackbar(notificationsQueue.afterExecutionError.message, notificationsQueue.afterExecutionError.options) + + const executeDataUsedSignatures = safeInstance.contract.methods + .execTransaction(to, valueInWei, txData, CALL, 0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, sigs) + .encodeABI() + const errMsg = await getErrorMessage(safeInstance.address, 0, executeDataUsedSignatures, from) + console.error(`Error executing the TX: ${errMsg}`) } dispatch(fetchTransactions(safeAddress)) diff --git a/src/routes/safe/store/actions/processTransaction.js b/src/routes/safe/store/actions/processTransaction.js index 4cded12b..7164f54a 100644 --- a/src/routes/safe/store/actions/processTransaction.js +++ b/src/routes/safe/store/actions/processTransaction.js @@ -5,8 +5,17 @@ import { userAccountSelector } from '~/logic/wallets/store/selectors' import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions' import { type GlobalState } from '~/store' import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts' -import { approveTransaction, executeTransaction, CALL } from '~/logic/safe/transactions' +import { + type NotificationsQueue, + getApprovalTransaction, + getExecutionTransaction, + CALL, + saveTxToHistory, + TX_TYPE_EXECUTION, + TX_TYPE_CONFIRMATION, +} from '~/logic/safe/transactions' import { getNofiticationsFromTxType } from '~/logic/notifications' +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 @@ -45,38 +54,85 @@ const processTransaction = ( const nonce = (await safeInstance.nonce()).toString() const threshold = (await safeInstance.getThreshold()).toNumber() const shouldExecute = threshold === tx.confirmations.size || approveAndExecute - const sigs = generateSignaturesFromTxConfirmations(tx, approveAndExecute && userAddress) - const notificationsQueue = getNofiticationsFromTxType(notifiedTransaction) + let sigs = generateSignaturesFromTxConfirmations(tx, approveAndExecute && userAddress) + // https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures + if (!sigs) { + sigs = `0x000000000000000000000000${from.replace( + '0x', + '', + )}000000000000000000000000000000000000000000000000000000000000000001` + } + + const notificationsQueue: NotificationsQueue = getNofiticationsFromTxType(notifiedTransaction) + const beforeExecutionKey = enqueueSnackbar( + notificationsQueue.beforeExecution.message, + notificationsQueue.beforeExecution.options, + ) + let pendingExecutionKey let txHash - if (shouldExecute) { - txHash = await executeTransaction( - notificationsQueue, - enqueueSnackbar, - closeSnackbar, - safeInstance, - tx.recipient, - tx.value, - tx.data, - CALL, - nonce, - from, - sigs, - ) - } else { - txHash = await approveTransaction( - notificationsQueue, - enqueueSnackbar, - closeSnackbar, - safeInstance, - tx.recipient, - tx.value, - tx.data, - CALL, - nonce, - from, - ) + let transaction + try { + if (shouldExecute) { + transaction = await getExecutionTransaction( + safeInstance, + tx.recipient, + tx.value, + tx.data, + CALL, + nonce, + from, + sigs, + ) + } else { + transaction = await getApprovalTransaction(safeInstance, tx.recipient, tx.value, tx.data, CALL, nonce, from) + } + + const sendParams = { from } + // if not set owner management tests will fail on ganache + if (process.env.NODE_ENV === 'test') { + sendParams.gas = '7000000' + } + + await transaction + .send(sendParams) + .once('transactionHash', (hash) => { + txHash = hash + closeSnackbar(beforeExecutionKey) + pendingExecutionKey = enqueueSnackbar( + notificationsQueue.pendingExecution.single.message, + notificationsQueue.pendingExecution.single.options, + ) + }) + .on('error', (error) => { + console.error('Processing transaction error: ', error) + }) + .then(async (receipt) => { + closeSnackbar(pendingExecutionKey) + await saveTxToHistory( + safeInstance, + tx.recipient, + tx.value, + tx.data, + CALL, + nonce, + receipt.transactionHash, + from, + shouldExecute ? TX_TYPE_EXECUTION : TX_TYPE_CONFIRMATION, + ) + enqueueSnackbar(notificationsQueue.afterExecution.message, notificationsQueue.afterExecution.options) + + return receipt.transactionHash + }) + } catch (err) { + closeSnackbar(beforeExecutionKey) + closeSnackbar(pendingExecutionKey) + enqueueSnackbar(notificationsQueue.afterExecutionError.message, notificationsQueue.afterExecutionError.options) + + const executeData = safeInstance.contract.methods.approveHash(txHash).encodeABI() + const errMsg = await getErrorMessage(safeInstance.address, 0, executeData, from) + console.error(`Error executing the TX: ${errMsg}`) } dispatch(fetchTransactions(safeAddress))