From a59ca97c4246bb945f5dbd2ed34af4bc38dac38b Mon Sep 17 00:00:00 2001 From: apanizo Date: Wed, 4 Jul 2018 09:36:58 +0200 Subject: [PATCH] WA-232 Moving createTransaction file to src/wallets --- .../safe/component/EditDailyLimit/index.jsx | 2 +- .../safe/component/RemoveOwner/index.jsx | 2 +- src/routes/safe/component/SendToken/index.jsx | 2 +- src/routes/safe/component/Threshold/index.jsx | 2 +- .../Transactions/processTransactions.js | 2 +- .../safe/component/Withdraw/withdraw.js | 2 +- src/routes/safe/test/Safe.owners.test.js | 2 +- src/routes/safe/test/Safe.threshold.test.js | 2 +- src/test/safe.service.transactions.test.js | 2 +- src/wallets/createTransactions.js | 126 ++++++++++++++++++ 10 files changed, 135 insertions(+), 9 deletions(-) create mode 100644 src/wallets/createTransactions.js diff --git a/src/routes/safe/component/EditDailyLimit/index.jsx b/src/routes/safe/component/EditDailyLimit/index.jsx index d5e9f587..fed1ef4e 100644 --- a/src/routes/safe/component/EditDailyLimit/index.jsx +++ b/src/routes/safe/component/EditDailyLimit/index.jsx @@ -2,7 +2,7 @@ import * as React from 'react' import Stepper from '~/components/Stepper' import { connect } from 'react-redux' -import { createTransaction } from '~/routes/safe/component/AddTransaction/createTransactions' +import { createTransaction } from '~/wallets/createTransactions' import { getEditDailyLimitData, getDailyLimitAddress } from '~/routes/safe/component/Withdraw/withdraw' import { type Safe } from '~/routes/safe/store/model/safe' import EditDailyLimitForm, { EDIT_DAILY_LIMIT_PARAM } from './EditDailyLimitForm' diff --git a/src/routes/safe/component/RemoveOwner/index.jsx b/src/routes/safe/component/RemoveOwner/index.jsx index c35d4da7..b6955b59 100644 --- a/src/routes/safe/component/RemoveOwner/index.jsx +++ b/src/routes/safe/component/RemoveOwner/index.jsx @@ -3,7 +3,7 @@ import * as React from 'react' import Stepper from '~/components/Stepper' import { connect } from 'react-redux' import { type Safe } from '~/routes/safe/store/model/safe' -import { getSafeEthereumInstance, createTransaction } from '~/routes/safe/component/AddTransaction/createTransactions' +import { getSafeEthereumInstance, createTransaction } from '~/wallets/createTransactions' import RemoveOwnerForm, { DECREASE_PARAM } from './RemoveOwnerForm' import Review from './Review' import selector, { type SelectorProps } from './selector' diff --git a/src/routes/safe/component/SendToken/index.jsx b/src/routes/safe/component/SendToken/index.jsx index a63e3e94..34a394f1 100644 --- a/src/routes/safe/component/SendToken/index.jsx +++ b/src/routes/safe/component/SendToken/index.jsx @@ -5,7 +5,7 @@ import Stepper from '~/components/Stepper' import { sleep } from '~/utils/timer' import { type Safe } from '~/routes/safe/store/model/safe' import { type Balance } from '~/routes/safe/store/model/balance' -import { createTransaction } from '~/routes/safe/component/AddTransaction/createTransactions' +import { createTransaction } from '~/wallets/createTransactions' import { getStandardTokenContract } from '~/routes/safe/store/actions/fetchBalances' import { EMPTY_DATA } from '~/wallets/ethTransactions' import actions, { type Actions } from './actions' diff --git a/src/routes/safe/component/Threshold/index.jsx b/src/routes/safe/component/Threshold/index.jsx index 39c359ea..d90f831d 100644 --- a/src/routes/safe/component/Threshold/index.jsx +++ b/src/routes/safe/component/Threshold/index.jsx @@ -2,7 +2,7 @@ import * as React from 'react' import Stepper from '~/components/Stepper' import { connect } from 'react-redux' -import { getSafeEthereumInstance, createTransaction } from '~/routes/safe/component/AddTransaction/createTransactions' +import { getSafeEthereumInstance, createTransaction } from '~/wallets/createTransactions' import { type Safe } from '~/routes/safe/store/model/safe' import ThresholdForm, { THRESHOLD_PARAM } from './ThresholdForm' import selector, { type SelectorProps } from './selector' diff --git a/src/routes/safe/component/Transactions/processTransactions.js b/src/routes/safe/component/Transactions/processTransactions.js index 5db6165f..0ef47b58 100644 --- a/src/routes/safe/component/Transactions/processTransactions.js +++ b/src/routes/safe/component/Transactions/processTransactions.js @@ -7,7 +7,7 @@ import { makeTransaction, type Transaction, type TransactionProps } from '~/rout import { getGnosisSafeContract } from '~/wallets/safeContracts' import { getWeb3 } from '~/wallets/getWeb3' import { sameAddress } from '~/wallets/ethAddresses' -import { EXECUTED_CONFIRMATION_HASH } from '~/routes/safe/component/AddTransaction/createTransactions' +import { EXECUTED_CONFIRMATION_HASH } from '~/wallets/createTransactions' import { checkReceiptStatus, calculateGasOf, calculateGasPrice } from '~/wallets/ethTransactions' export const updateTransaction = ( diff --git a/src/routes/safe/component/Withdraw/withdraw.js b/src/routes/safe/component/Withdraw/withdraw.js index 12fa4e72..ecbe9828 100644 --- a/src/routes/safe/component/Withdraw/withdraw.js +++ b/src/routes/safe/component/Withdraw/withdraw.js @@ -5,7 +5,7 @@ import { getGnosisSafeContract, getCreateDailyLimitExtensionContract } from '~/w import { type DailyLimitProps } from '~/routes/safe/store/model/dailyLimit' import { checkReceiptStatus, calculateGasOf, calculateGasPrice, EMPTY_DATA } from '~/wallets/ethTransactions' import { type Safe } from '~/routes/safe/store/model/safe' -import { buildExecutedConfirmationFrom, storeTransaction } from '~/routes/safe/component/AddTransaction/createTransactions' +import { buildExecutedConfirmationFrom, storeTransaction } from '~/wallets/createTransactions' import { type Confirmation } from '~/routes/safe/store/model/confirmation' export const LIMIT_POSITION = 0 diff --git a/src/routes/safe/test/Safe.owners.test.js b/src/routes/safe/test/Safe.owners.test.js index b9047541..de44e8d0 100644 --- a/src/routes/safe/test/Safe.owners.test.js +++ b/src/routes/safe/test/Safe.owners.test.js @@ -9,7 +9,7 @@ import { processTransaction } from '~/routes/safe/component/Transactions/process import { confirmationsTransactionSelector, safeSelector, safeTransactionsSelector } from '~/routes/safe/store/selectors/index' import { getTransactionFromReduxStore } from '~/routes/safe/test/testMultisig' import { buildMathPropsFrom } from '~/test/utils/buildReactRouterProps' -import { createTransaction } from '~/routes/safe/component/AddTransaction/createTransactions' +import { createTransaction } from '~/wallets/createTransactions' import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions' import { type GlobalState } from '~/store/index' import { type Safe } from '~/routes/safe/store/model/safe' diff --git a/src/routes/safe/test/Safe.threshold.test.js b/src/routes/safe/test/Safe.threshold.test.js index 7ce99d2d..8aa01a6d 100644 --- a/src/routes/safe/test/Safe.threshold.test.js +++ b/src/routes/safe/test/Safe.threshold.test.js @@ -9,7 +9,7 @@ import { processTransaction } from '~/routes/safe/component/Transactions/process import { confirmationsTransactionSelector, safeSelector, safeTransactionsSelector } from '~/routes/safe/store/selectors/index' import { getTransactionFromReduxStore } from '~/routes/safe/test/testMultisig' import { buildMathPropsFrom } from '~/test/utils/buildReactRouterProps' -import { createTransaction } from '~/routes/safe/component/AddTransaction/createTransactions' +import { createTransaction } from '~/wallets/createTransactions' import { getGnosisSafeContract } from '~/wallets/safeContracts' import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions' diff --git a/src/test/safe.service.transactions.test.js b/src/test/safe.service.transactions.test.js index ea05bbcb..1c32f61f 100644 --- a/src/test/safe.service.transactions.test.js +++ b/src/test/safe.service.transactions.test.js @@ -1,6 +1,6 @@ // @flow import { List, Map } from 'immutable' -import { storeTransaction, buildConfirmationsFrom, EXECUTED_CONFIRMATION_HASH, buildExecutedConfirmationFrom } from '~/routes/safe/component/AddTransaction/createTransactions' +import { storeTransaction, buildConfirmationsFrom, EXECUTED_CONFIRMATION_HASH, buildExecutedConfirmationFrom } from '~/wallets/createTransactions' import { type Transaction } from '~/routes/safe/store/model/transaction' import { SafeFactory } from '~/routes/safe/store/test/builder/safe.builder' import { type Safe } from '~/routes/safe/store/model/safe' diff --git a/src/wallets/createTransactions.js b/src/wallets/createTransactions.js new file mode 100644 index 00000000..1d5b6790 --- /dev/null +++ b/src/wallets/createTransactions.js @@ -0,0 +1,126 @@ +// @flow +import { List } from 'immutable' +import { type Owner } from '~/routes/safe/store/model/owner' +import { load, TX_KEY } from '~/utils/localStorage' +import { type Confirmation, makeConfirmation } from '~/routes/safe/store/model/confirmation' +import { makeTransaction, type Transaction, type TransactionProps } from '~/routes/safe/store/model/transaction' +import { getGnosisSafeContract } from '~/wallets/safeContracts' +import { getWeb3 } from '~/wallets/getWeb3' +import { type Safe } from '~/routes/safe/store/model/safe' +import { sameAddress } from '~/wallets/ethAddresses' +import { checkReceiptStatus, calculateGasOf, calculateGasPrice, EMPTY_DATA } from '~/wallets/ethTransactions' + +export const TX_NAME_PARAM = 'txName' +export const TX_DESTINATION_PARAM = 'txDestination' +export const TX_VALUE_PARAM = 'txValue' + +export const EXECUTED_CONFIRMATION_HASH = 'EXECUTED' + +// Exported for testing it, should not use it. Use #transactions fnc. +export const buildConfirmationsFrom = + (owners: List, creator: string, confirmationHash: string): List => { + if (!owners) { + throw new Error('This safe has no owners') + } + + if (!owners.find((owner: Owner) => sameAddress(owner.get('address'), creator))) { + throw new Error('The creator of the tx is not an owner') + } + + return owners.map((owner: Owner) => makeConfirmation({ + owner, + status: sameAddress(owner.get('address'), creator), + hash: sameAddress(owner.get('address'), creator) ? confirmationHash : undefined, + })) + } + +export const buildExecutedConfirmationFrom = (owners: List, creator: string): List => + buildConfirmationsFrom(owners, creator, EXECUTED_CONFIRMATION_HASH) + +export const storeTransaction = ( + name: string, + nonce: number, + destination: string, + value: number, + creator: string, + confirmations: List, + tx: string, + safeAddress: string, + safeThreshold: number, + data: string, +) => { + const notMinedWhenOneOwnerSafe = confirmations.count() === 1 && !tx + if (notMinedWhenOneOwnerSafe) { + throw new Error('The tx should be mined before storing it in safes with one owner') + } + + const transaction: Transaction = makeTransaction({ + name, nonce, value, confirmations, destination, threshold: safeThreshold, tx, data, + }) + + const safeTransactions = load(TX_KEY) || {} + const transactions = safeTransactions[safeAddress] + const txsRecord = transactions ? List(transactions) : List([]) + + if (txsRecord.find((txs: TransactionProps) => txs.nonce === nonce)) { + throw new Error(`Transaction with same nonce: ${nonce} already created for safe: ${safeAddress}`) + } + + safeTransactions[safeAddress] = txsRecord.push(transaction) + + localStorage.setItem(TX_KEY, JSON.stringify(safeTransactions)) +} + +const hasOneOwner = (safe: Safe) => { + const owners = safe.get('owners') + if (!owners) { + throw new Error('Received a Safe without owners when creating a tx') + } + + return owners.count() === 1 +} + +export const getSafeEthereumInstance = async (safeAddress: string) => { + const web3 = getWeb3() + const GnosisSafe = await getGnosisSafeContract(web3) + return GnosisSafe.at(safeAddress) +} + +export const createTransaction = async ( + safe: Safe, + txName: string, + txDest: string, + txValue: number, + nonce: number, + user: string, + data: string = EMPTY_DATA, +) => { + const web3 = getWeb3() + const safeAddress = safe.get('address') + const gnosisSafe = await getSafeEthereumInstance(safeAddress) + const valueInWei = web3.toWei(txValue, 'ether') + const CALL = 0 + const gasPrice = await calculateGasPrice() + + const thresholdIsOne = safe.get('threshold') === 1 + if (hasOneOwner(safe) || thresholdIsOne) { + const txConfirmationData = + gnosisSafe.contract.execTransactionIfApproved.getData(txDest, valueInWei, data, CALL, nonce) + const gas = await calculateGasOf(txConfirmationData, user, safeAddress) + const txHash = + await gnosisSafe.execTransactionIfApproved(txDest, valueInWei, data, CALL, nonce, { from: user, gas, gasPrice }) + await checkReceiptStatus(txHash.tx) + const executedConfirmations: List = buildExecutedConfirmationFrom(safe.get('owners'), user) + return storeTransaction(txName, nonce, txDest, txValue, user, executedConfirmations, txHash.tx, safeAddress, safe.get('threshold'), data) + } + + const txData = gnosisSafe.contract.approveTransactionWithParameters.getData(txDest, valueInWei, data, CALL, nonce) + const gas = await calculateGasOf(txData, user, safeAddress) + const txConfirmationHash = await gnosisSafe + .approveTransactionWithParameters(txDest, valueInWei, data, CALL, nonce, { from: user, gas, gasPrice }) + await checkReceiptStatus(txConfirmationHash.tx) + + const confirmations: List = buildConfirmationsFrom(safe.get('owners'), user, txConfirmationHash.tx) + + return storeTransaction(txName, nonce, txDest, txValue, user, confirmations, '', safeAddress, safe.get('threshold'), data) +}