Merge pull request #181 from gnosis/171-txhash-refactoring

Feature #181: Get transaction hash after confirmation
This commit is contained in:
Germán Martínez 2019-09-26 18:57:16 +02:00 committed by GitHub
commit 033284d835
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 145 additions and 70 deletions

View File

@ -1,11 +1,12 @@
// @flow // @flow
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json'
import { getWeb3 } from '~/logic/wallets/getWeb3' import { getWeb3 } from '~/logic/wallets/getWeb3'
import { getStandardTokenContract } from '~/logic/tokens/store/actions/fetchTokens' import { getStandardTokenContract } from '~/logic/tokens/store/actions/fetchTokens'
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions' import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
import { isEther } from '~/logic/tokens/utils/tokenHelpers' import { isEther } from '~/logic/tokens/utils/tokenHelpers'
import { type Token } from '~/logic/tokens/store/model/token' import { type Token } from '~/logic/tokens/store/model/token'
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts' import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
import { type Operation, saveTxToHistory } from '~/logic/safe/transactions' import { type Operation } from '~/logic/safe/transactions'
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses' import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
import { getErrorMessage } from '~/test/utils/ethereumErrors' import { getErrorMessage } from '~/test/utils/ethereumErrors'
@ -13,7 +14,7 @@ export const CALL = 0
export const TX_TYPE_EXECUTION = 'execution' export const TX_TYPE_EXECUTION = 'execution'
export const TX_TYPE_CONFIRMATION = 'confirmation' export const TX_TYPE_CONFIRMATION = 'confirmation'
export const approveTransaction = async ( export const getApprovalTransaction = async (
safeInstance: any, safeInstance: any,
to: string, to: string,
valueInWei: number | string, valueInWei: number | string,
@ -22,7 +23,7 @@ export const approveTransaction = async (
nonce: number, nonce: number,
sender: string, sender: string,
) => { ) => {
const contractTxHash = await safeInstance.getTransactionHash( const txHash = await safeInstance.getTransactionHash(
to, to,
valueInWei, valueInWei,
data, data,
@ -37,24 +38,23 @@ export const approveTransaction = async (
from: sender, from: sender,
}, },
) )
const receipt = await safeInstance.approveHash(contractTxHash, { from: sender })
await saveTxToHistory( try {
safeInstance, const web3 = getWeb3()
to, const contract = new web3.eth.Contract(GnosisSafeSol.abi, safeInstance.address)
valueInWei,
data,
operation,
nonce,
receipt.tx, // tx hash,
sender,
TX_TYPE_CONFIRMATION,
)
return receipt return contract.methods.approveHash(txHash)
} catch (error) {
/* eslint-disable */
const executeData = safeInstance.contract.methods.approveHash(txHash).encodeABI()
const errMsg = await getErrorMessage(safeInstance.address, 0, executeData, sender)
console.log(`Error executing the TX: ${errMsg}`)
throw error
}
} }
export const executeTransaction = async ( export const getExecutionTransaction = async (
safeInstance: any, safeInstance: any,
to: string, to: string,
valueInWei: number | string, valueInWei: number | string,
@ -75,33 +75,10 @@ export const executeTransaction = async (
} }
try { try {
const receipt = await safeInstance.execTransaction( const web3 = getWeb3()
to, const contract = new web3.eth.Contract(GnosisSafeSol.abi, safeInstance.address)
valueInWei,
data,
operation,
0,
0,
0,
ZERO_ADDRESS,
ZERO_ADDRESS,
sigs,
{ from: sender },
)
await saveTxToHistory( return contract.methods.execTransaction(to, valueInWei, data, operation, 0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, sigs)
safeInstance,
to,
valueInWei,
data,
operation,
nonce,
receipt.tx, // tx hash,
sender,
TX_TYPE_EXECUTION,
)
return receipt
} catch (error) { } catch (error) {
/* eslint-disable */ /* eslint-disable */
const executeDataUsedSignatures = safeInstance.contract.methods const executeDataUsedSignatures = safeInstance.contract.methods

View File

@ -36,8 +36,6 @@ export const sendReplaceOwner = async (
values: Object, values: Object,
safeAddress: string, safeAddress: string,
ownerAddressToRemove: string, ownerAddressToRemove: string,
ownerNameToRemove: string,
ownersOld: List<Owner>,
openSnackbar: Function, openSnackbar: Function,
createTransaction: Function, createTransaction: Function,
replaceSafeOwner: Function, replaceSafeOwner: Function,
@ -109,8 +107,6 @@ const ReplaceOwner = ({
values, values,
safeAddress, safeAddress,
ownerAddress, ownerAddress,
ownerName,
owners,
openSnackbar, openSnackbar,
createTransaction, createTransaction,
replaceSafeOwner, replaceSafeOwner,

View File

@ -13,7 +13,8 @@ export const TRANSACTIONS_DESC_ADD_OWNER_TEST_ID = 'tx-description-add-owner'
export const TRANSACTIONS_DESC_REMOVE_OWNER_TEST_ID = 'tx-description-remove-owner' export const TRANSACTIONS_DESC_REMOVE_OWNER_TEST_ID = 'tx-description-remove-owner'
export const TRANSACTIONS_DESC_CHANGE_THRESHOLD_TEST_ID = 'tx-description-change-threshold' export const TRANSACTIONS_DESC_CHANGE_THRESHOLD_TEST_ID = 'tx-description-change-threshold'
export const TRANSACTIONS_DESC_SEND_TEST_ID = 'tx-description-send' export const TRANSACTIONS_DESC_SEND_TEST_ID = 'tx-description-send'
export const TRANSACTIONS_DESC_CUSTOM_TEST_ID = 'tx-description-custom' export const TRANSACTIONS_DESC_CUSTOM_VALUE_TEST_ID = 'tx-description-custom-value'
export const TRANSACTIONS_DESC_CUSTOM_DATA_TEST_ID = 'tx-description-custom-data'
export const styles = () => ({ export const styles = () => ({
txDataContainer: { txDataContainer: {
@ -42,6 +43,8 @@ type DescriptionDescProps = {
} }
type CustomDescProps = { type CustomDescProps = {
value: string,
recipient: string,
data: String, data: String,
classes: Obeject, classes: Obeject,
} }
@ -88,9 +91,24 @@ const SettingsDescription = ({ removedOwner, addedOwner, newThreshold }: Descrip
</> </>
) )
const CustomDescription = ({ data, classes }: CustomDescProps) => ( const CustomDescription = ({
data, value = 0, recipient, classes,
}: CustomDescProps) => (
<> <>
<Paragraph className={classes.txData} data-testid={TRANSACTIONS_DESC_CUSTOM_TEST_ID}> <Paragraph noMargin data-testid={TRANSACTIONS_DESC_CUSTOM_VALUE_TEST_ID}>
<Bold>
Send
{' '}
{value}
{' '}
ETH
{' '}
to:
</Bold>
<br />
<EtherscanLink type="address" value={recipient} />
</Paragraph>
<Paragraph className={classes.txData} data-testid={TRANSACTIONS_DESC_CUSTOM_DATA_TEST_ID}>
<Bold>Data (hex encoded):</Bold> <Bold>Data (hex encoded):</Bold>
<br /> <br />
{data} {data}
@ -109,7 +127,7 @@ const TxDescription = ({ tx, classes }: Props) => {
<SettingsDescription removedOwner={removedOwner} newThreshold={newThreshold} addedOwner={addedOwner} /> <SettingsDescription removedOwner={removedOwner} newThreshold={newThreshold} addedOwner={addedOwner} />
)} )}
{customTx && ( {customTx && (
<CustomDescription data={data} classes={classes} /> <CustomDescription data={data} value={value} recipient={recipient} classes={classes} />
)} )}
{!cancellationTx && !modifySettingsTx && !customTx && ( {!cancellationTx && !modifySettingsTx && !customTx && (
<TransferDescription value={value} symbol={tx.symbol} recipient={recipient} /> <TransferDescription value={value} symbol={tx.symbol} recipient={recipient} />

View File

@ -23,6 +23,11 @@ export const getTxData = (tx: Transaction): DecodedTxData => {
if (tx.isTokenTransfer && tx.decodedParams) { if (tx.isTokenTransfer && tx.decodedParams) {
txData.recipient = tx.decodedParams.recipient txData.recipient = tx.decodedParams.recipient
txData.value = fromWei(toBN(tx.decodedParams.value), 'ether') txData.value = fromWei(toBN(tx.decodedParams.value), 'ether')
} else if (tx.customTx) {
txData.recipient = tx.recipient
txData.value = fromWei(toBN(tx.value), 'ether')
txData.data = tx.data
txData.customTx = true
} else if (Number(tx.value) > 0) { } else if (Number(tx.value) > 0) {
txData.recipient = tx.recipient txData.recipient = tx.recipient
txData.value = fromWei(toBN(tx.value), 'ether') txData.value = fromWei(toBN(tx.value), 'ether')
@ -49,9 +54,6 @@ export const getTxData = (tx: Transaction): DecodedTxData => {
} }
} else if (tx.cancellationTx) { } else if (tx.cancellationTx) {
txData.cancellationTx = true txData.cancellationTx = true
} else if (tx.customTx) {
txData.data = tx.data
txData.customTx = true
} }
return txData return txData

View File

@ -6,11 +6,14 @@ import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
import { type GlobalState } from '~/store' import { type GlobalState } from '~/store'
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts' import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
import { import {
approveTransaction, getApprovalTransaction,
executeTransaction, getExecutionTransaction,
CALL, CALL,
type Notifications, type Notifications,
DEFAULT_NOTIFICATIONS, DEFAULT_NOTIFICATIONS,
TX_TYPE_CONFIRMATION,
TX_TYPE_EXECUTION,
saveTxToHistory,
} from '~/logic/safe/transactions' } from '~/logic/safe/transactions'
const createTransaction = ( const createTransaction = (
@ -31,18 +34,54 @@ const createTransaction = (
const isExecution = threshold.toNumber() === 1 || shouldExecute const isExecution = threshold.toNumber() === 1 || shouldExecute
let txHash let txHash
let tx
try { try {
if (isExecution) { if (isExecution) {
openSnackbar(notifications.BEFORE_EXECUTION_OR_CREATION, 'success') tx = await getExecutionTransaction(safeInstance, to, valueInWei, txData, CALL, nonce, from)
txHash = await executeTransaction(safeInstance, to, valueInWei, txData, CALL, nonce, from)
openSnackbar(notifications.AFTER_EXECUTION, 'success')
} else { } else {
openSnackbar(notifications.BEFORE_EXECUTION_OR_CREATION, 'success') tx = await getApprovalTransaction(safeInstance, to, valueInWei, txData, CALL, nonce, from)
txHash = await approveTransaction(safeInstance, to, valueInWei, txData, CALL, nonce, from)
openSnackbar(notifications.CREATED_MORE_CONFIRMATIONS_NEEDED, 'success')
} }
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
openSnackbar(notifications.BEFORE_EXECUTION_OR_CREATION, 'success')
})
.on('error', (error) => {
console.error('Tx error: ', error)
})
.then(async (receipt) => {
await saveTxToHistory(
safeInstance,
to,
valueInWei,
txData,
CALL,
nonce,
receipt.transactionHash,
from,
isExecution ? TX_TYPE_EXECUTION : TX_TYPE_CONFIRMATION,
)
return receipt.transactionHash
})
openSnackbar(
isExecution ? notifications.AFTER_EXECUTION : notifications.CREATED_MORE_CONFIRMATIONS_NEEDED,
'success',
)
} catch (err) { } catch (err) {
openSnackbar(notifications.ERROR, '') openSnackbar(notifications.ERROR, 'error')
console.error(`Error while creating transaction: ${err}`) console.error(`Error while creating transaction: ${err}`)
} }

View File

@ -5,7 +5,14 @@ import { userAccountSelector } from '~/logic/wallets/store/selectors'
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions' import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
import { type GlobalState } from '~/store' import { type GlobalState } from '~/store'
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts' import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
import { approveTransaction, executeTransaction, CALL } from '~/logic/safe/transactions' import {
getApprovalTransaction,
getExecutionTransaction,
CALL,
saveTxToHistory,
TX_TYPE_EXECUTION,
TX_TYPE_CONFIRMATION,
} from '~/logic/safe/transactions'
// 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
// https://github.com/gnosis/safe-contracts/blob/master/test/gnosisSafeTeamEdition.js#L26 // https://github.com/gnosis/safe-contracts/blob/master/test/gnosisSafeTeamEdition.js#L26
@ -45,16 +52,52 @@ const processTransaction = (
const sigs = generateSignaturesFromTxConfirmations(tx, approveAndExecute && userAddress) const sigs = generateSignaturesFromTxConfirmations(tx, approveAndExecute && userAddress)
let txHash let txHash
let transaction
if (shouldExecute) { if (shouldExecute) {
openSnackbar('Transaction has been submitted', 'success') transaction = await getExecutionTransaction(safeInstance, tx.recipient, tx.value, tx.data, CALL, nonce, from, sigs)
txHash = await executeTransaction(safeInstance, tx.recipient, tx.value, tx.data, CALL, nonce, from, sigs)
openSnackbar('Transaction has been confirmed', 'success')
} else { } else {
openSnackbar('Approval transaction has been submitted', 'success') transaction = await getApprovalTransaction(safeInstance, tx.recipient, tx.value, tx.data, CALL, nonce, from)
txHash = await approveTransaction(safeInstance, tx.recipient, tx.value, tx.data, CALL, nonce, from)
openSnackbar('Approval transaction has been confirmed', 'success')
} }
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
openSnackbar(
shouldExecute ? 'Transaction has been submitted' : 'Approval transaction has been submitted',
'success',
)
})
.on('error', (error) => {
console.error('Processing transaction error: ', error)
})
.then(async (receipt) => {
await saveTxToHistory(
safeInstance,
tx.recipient,
tx.value,
tx.data,
CALL,
nonce,
receipt.transactionHash,
from,
shouldExecute ? TX_TYPE_EXECUTION : TX_TYPE_CONFIRMATION,
)
return receipt.transactionHash
})
openSnackbar(shouldExecute ? 'Transaction has been confirmed' : 'Approval transaction has been confirmed', 'success')
dispatch(fetchTransactions(safeAddress)) dispatch(fetchTransactions(safeAddress))
return txHash return txHash