diff --git a/.eslintrc.js b/.eslintrc.js index 9c896455..041efd2c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -25,6 +25,7 @@ module.exports = { '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-unused-vars': ['error', { ignoreRestSiblings: true }], + '@typescript-eslint/ban-ts-ignore': 'off', }, settings: { react: { diff --git a/package.json b/package.json index cf777b44..f7cbc07c 100644 --- a/package.json +++ b/package.json @@ -167,10 +167,11 @@ "electron-is-dev": "^1.1.0", "electron-log": "4.1.2", "electron-updater": "4.3.1", + "eth-sig-util": "^2.5.3", "ethereum-ens": "0.8.0", "express": "^4.17.1", - "final-form-calculate": "^1.3.1", "final-form": "4.19.1", + "final-form-calculate": "^1.3.1", "history": "4.10.1", "immortal-db": "^1.0.2", "immutable": "^4.0.0-rc.9", @@ -182,9 +183,10 @@ "polished": "3.6.3", "qrcode.react": "1.0.0", "query-string": "6.12.1", + "react": "16.13.1", "react-dom": "16.13.1", - "react-final-form-listeners": "^1.0.2", "react-final-form": "6.4.0", + "react-final-form-listeners": "^1.0.2", "react-ga": "^2.7.0", "react-hot-loader": "4.12.21", "react-qr-reader": "^2.2.1", @@ -192,11 +194,10 @@ "react-router-dom": "5.2.0", "react-scripts": "^3.4.1", "react-window": "^1.8.5", - "react": "16.13.1", "recompose": "^0.30.0", + "redux": "4.0.5", "redux-actions": "^2.6.5", "redux-thunk": "^2.3.0", - "redux": "4.0.5", "reselect": "^4.0.0", "semver": "7.3.2", "styled-components": "^5.0.1", @@ -209,24 +210,24 @@ "@testing-library/user-event": "^7.1.2", "@types/jest": "^25.2.1", "@types/node": "^13.11.0", - "@types/react-dom": "^16.9.6", "@types/react": "^16.9.32", + "@types/react-dom": "^16.9.6", "@typescript-eslint/eslint-plugin": "^2.34.0", "@typescript-eslint/parser": "^2.34.0", "autoprefixer": "9.7.6", "cross-env": "^7.0.2", - "dotenv-expand": "^5.1.0", "dotenv": "^8.2.0", + "dotenv-expand": "^5.1.0", + "electron": "7.1.8", "electron-builder": "22.2.0", "electron-notarize": "^0.2.1", - "electron": "7.1.8", + "eslint": "^6.8.0", "eslint-config-prettier": "6.11.0", "eslint-plugin-import": "2.20.2", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-react": "^7.18.3", "eslint-plugin-sort-destructure-keys": "1.3.4", - "eslint": "^6.8.0", "ethereumjs-abi": "0.6.8", "husky": "^4.2.2", "lint-staged": "10.2.2", @@ -234,7 +235,7 @@ "prettier": "2.0.5", "react-app-rewired": "^2.1.6", "truffle": "5.1.23", - "typescript": "~3.7.2" , + "typescript": "~3.7.2", "wait-on": "5.0.0" } } diff --git a/src/routes/safe/store/actions/createTransaction.ts b/src/routes/safe/store/actions/createTransaction.ts index 2b4b8f05..ae09c2d4 100644 --- a/src/routes/safe/store/actions/createTransaction.ts +++ b/src/routes/safe/store/actions/createTransaction.ts @@ -6,9 +6,15 @@ import semverSatisfies from 'semver/functions/satisfies' import { onboardUser } from 'src/components/ConnectButton' import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts' import { getNotificationsFromTxType, showSnackbar } from 'src/logic/notifications' -import { CALL, getApprovalTransaction, getExecutionTransaction, saveTxToHistory } from 'src/logic/safe/transactions' +import { + CALL, + getApprovalTransaction, + getExecutionTransaction, + SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES, + saveTxToHistory, + tryOffchainSigning, +} from 'src/logic/safe/transactions' import { estimateSafeTxGas } from 'src/logic/safe/transactions/gasNew' -import { SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES, tryOffchainSigning } from 'src/logic/safe/transactions/offchainSigner' import { getCurrentSafeVersion } from 'src/logic/safe/utils/safeVersion' import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses' import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions' @@ -18,7 +24,10 @@ import { addOrUpdateCancellationTransactions } from 'src/routes/safe/store/actio import { addOrUpdateTransactions } from 'src/routes/safe/store/actions/transactions/addOrUpdateTransactions' import { removeCancellationTransaction } from 'src/routes/safe/store/actions/transactions/removeCancellationTransaction' import { removeTransaction } from 'src/routes/safe/store/actions/transactions/removeTransaction' -import { mockTransaction } from 'src/routes/safe/store/actions/transactions/utils/transactionHelpers' +import { + generateSafeTxHash, + mockTransaction, +} from 'src/routes/safe/store/actions/transactions/utils/transactionHelpers' import { getLastTx, getNewTxNonce, shouldExecuteTransaction } from 'src/routes/safe/store/actions/utils' import { getErrorMessage } from 'src/test/utils/ethereumErrors' import { makeConfirmation } from '../models/confirmation' @@ -151,6 +160,7 @@ const createTransaction = ({ ...txArgs, confirmations: [], // this is used to determine if a tx is pending or not. See `calculateTransactionStatus` helper value: txArgs.valueInWei, + safeTxHash: generateSafeTxHash(safeAddress, txArgs), } const mockedTx = await mockTransaction(txToMock, safeAddress, state) diff --git a/src/routes/safe/store/actions/transactions/utils/transactionHelpers.ts b/src/routes/safe/store/actions/transactions/utils/transactionHelpers.ts index dbfc365d..d8e5bd8c 100644 --- a/src/routes/safe/store/actions/transactions/utils/transactionHelpers.ts +++ b/src/routes/safe/store/actions/transactions/utils/transactionHelpers.ts @@ -18,6 +18,7 @@ import { TransactionStatusValues, TransactionTypes, TransactionTypeValues, + TxArgs, } from 'src/routes/safe/store/models/types/transaction' import { CANCELLATION_TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/cancellationTransactions' import { SAFE_REDUCER_ID } from 'src/routes/safe/store/reducer/safe' @@ -26,6 +27,7 @@ import { store } from 'src/store' import { safeSelector, safeTransactionsSelector } from 'src/routes/safe/store/selectors' import { addOrUpdateTransactions } from 'src/routes/safe/store/actions/transactions/addOrUpdateTransactions' import { TxServiceModel } from 'src/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions' +import { TypedDataUtils } from 'eth-sig-util' export const isEmptyData = (data?: string | null): boolean => { return !data || data === EMPTY_DATA @@ -332,3 +334,42 @@ export const updateStoredTransactionsStatus = (dispatch, walletRecord): void => ) } } + +export function generateSafeTxHash(safeAddress: string, txArgs: TxArgs): string { + const typedData = { + types: { + EIP712Domain: [{ type: 'address', name: 'verifyingContract' }], + SafeTx: [ + { type: 'address', name: 'to' }, + { type: 'uint256', name: 'value' }, + { type: 'bytes', name: 'data' }, + { type: 'uint8', name: 'operation' }, + { type: 'uint256', name: 'safeTxGas' }, + { type: 'uint256', name: 'baseGas' }, + { type: 'uint256', name: 'gasPrice' }, + { type: 'address', name: 'gasToken' }, + { type: 'address', name: 'refundReceiver' }, + { type: 'uint256', name: 'nonce' }, + ], + }, + domain: { + verifyingContract: safeAddress, + }, + primaryType: 'SafeTx', + message: { + to: txArgs.to, + value: txArgs.valueInWei, + data: txArgs.data, + operation: txArgs.operation, + safeTxGas: txArgs.safeTxGas, + baseGas: txArgs.baseGas, + gasPrice: txArgs.gasPrice, + gasToken: txArgs.gasToken, + refundReceiver: txArgs.refundReceiver, + nonce: txArgs.nonce, + }, + } + + // @ts-ignore + return `0x${TypedDataUtils.sign(typedData).toString('hex')}` +} diff --git a/src/routes/safe/store/models/types/transaction.ts b/src/routes/safe/store/models/types/transaction.ts index 53b47f00..83c7bd9c 100644 --- a/src/routes/safe/store/models/types/transaction.ts +++ b/src/routes/safe/store/models/types/transaction.ts @@ -75,3 +75,19 @@ export type TransactionProps = { } export type Transaction = import('immutable').RecordOf + +export type TxArgs = { + data: any + baseGas: number + gasToken: string + safeInstance: any + nonce: number + valueInWei: any + safeTxGas: number + refundReceiver: string + sender: any + sigs: string + to: any + operation: any + gasPrice: number +} diff --git a/yarn.lock b/yarn.lock index d47fc442..f4ba5da2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3933,7 +3933,7 @@ bn.js@4.11.8: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.0, bn.js@^4.11.1, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.4.0, bn.js@^4.8.0: +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.1, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.4.0, bn.js@^4.8.0: version "4.11.9" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== @@ -6861,6 +6861,18 @@ eth-sig-util@^1.4.2: ethereumjs-abi "git+https://github.com/ethereumjs/ethereumjs-abi.git" ethereumjs-util "^5.1.1" +eth-sig-util@^2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/eth-sig-util/-/eth-sig-util-2.5.3.tgz#6938308b38226e0b3085435474900b03036abcbe" + integrity sha512-KpXbCKmmBUNUTGh9MRKmNkIPietfhzBqqYqysDavLseIiMUGl95k6UcPEkALAZlj41e9E6yioYXc1PC333RKqw== + dependencies: + buffer "^5.2.1" + elliptic "^6.4.0" + ethereumjs-abi "0.6.5" + ethereumjs-util "^5.1.1" + tweetnacl "^1.0.0" + tweetnacl-util "^0.15.0" + eth-tx-summary@^3.1.2: version "3.2.4" resolved "https://registry.yarnpkg.com/eth-tx-summary/-/eth-tx-summary-3.2.4.tgz#e10eb95eb57cdfe549bf29f97f1e4f1db679035c" @@ -6951,6 +6963,14 @@ ethereum-public-key-to-address@0.0.1: meow "^5.0.0" secp256k1 "^3.7.1" +ethereumjs-abi@0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/ethereumjs-abi/-/ethereumjs-abi-0.6.5.tgz#5a637ef16ab43473fa72a29ad90871405b3f5241" + integrity sha1-WmN+8Wq0NHP6cqKa2QhxQFs/UkE= + dependencies: + bn.js "^4.10.0" + ethereumjs-util "^4.3.0" + ethereumjs-abi@0.6.8, "ethereumjs-abi@git+https://github.com/ethereumjs/ethereumjs-abi.git": version "0.6.8" resolved "git+https://github.com/ethereumjs/ethereumjs-abi.git#1cfbb13862f90f0b391d8a699544d5fe4dfb8c7b" @@ -7021,7 +7041,7 @@ ethereumjs-tx@^2.1.1, ethereumjs-tx@^2.1.2: ethereumjs-common "^1.5.0" ethereumjs-util "^6.0.0" -ethereumjs-util@4.5.0: +ethereumjs-util@4.5.0, ethereumjs-util@^4.3.0: version "4.5.0" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-4.5.0.tgz#3e9428b317eebda3d7260d854fddda954b1f1bc6" integrity sha1-PpQosxfuvaPXJg2FT93alUsfG8Y= @@ -16004,11 +16024,21 @@ tunnel@^0.0.6: resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== +tweetnacl-util@^0.15.0: + version "0.15.1" + resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz#b80fcdb5c97bcc508be18c44a4be50f022eea00b" + integrity sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw== + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= +tweetnacl@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" + integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== + type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"