From 71dd7539f4735595e7d12437736260db680040e6 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Fri, 29 May 2020 21:41:58 -0300 Subject: [PATCH 01/24] upgrade final form --- package.json | 4 ++-- yarn.lock | 38 ++++++++++++++++++++++++++------------ 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index de9932fc..a439e3fd 100644 --- a/package.json +++ b/package.json @@ -168,7 +168,7 @@ "electron-log": "4.1.2", "electron-updater": "4.3.1", "express": "^4.17.1", - "final-form": "4.19.1", + "final-form": "^4.20.0", "final-form-calculate": "^1.3.1", "history": "4.10.1", "immortal-db": "^1.0.2", @@ -183,7 +183,7 @@ "query-string": "6.12.1", "react": "16.13.1", "react-dom": "16.13.1", - "react-final-form": "6.4.0", + "react-final-form": "^6.5.0", "react-final-form-listeners": "^1.0.2", "react-ga": "^2.7.0", "react-hot-loader": "4.12.21", diff --git a/yarn.lock b/yarn.lock index d437aa16..7ab85c41 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1017,6 +1017,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.10.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.1.tgz#b6eb75cac279588d3100baecd1b9894ea2840822" + integrity sha512-nQbbCbQc9u/rpg1XCxoMYQTbSMVZjCDxErQ1ClCn9Pvcmv1lGads19ep0a2VsEiIJeHqjZley6EQGEC3Yo1xMA== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.4.0", "@babel/template@^7.8.3", "@babel/template@^7.8.6": version "7.8.6" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" @@ -1751,6 +1758,11 @@ dependencies: any-observable "^0.3.0" +"@scarf/scarf@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.0.5.tgz#accee0bce88a9047672f7c8faf3cada59c996b81" + integrity sha512-9WKaGVpQH905Aqkk+BczFEeLQxS07rl04afFRPUG9IcSlOwmo5EVVuuNu0d4M9LMYucObvK0LoAe+5HfMW2QhQ== + "@sheerun/mutationobserver-shim@^0.3.2": version "0.3.3" resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz#5405ee8e444ed212db44e79351f0c70a582aae25" @@ -7552,12 +7564,13 @@ final-form-calculate@^1.3.1: resolved "https://registry.yarnpkg.com/final-form-calculate/-/final-form-calculate-1.3.1.tgz#463089114245afa97fea94712bfbfca11da8413e" integrity sha512-vZCvQ08w9FIoHLkZMcJSIXQr5TAVLxHfLD0thmm50zcNyJESruqhgvurSjWYPLoJGnIgbIb94Rumdg5ZXX5WiQ== -final-form@4.19.1: - version "4.19.1" - resolved "https://registry.yarnpkg.com/final-form/-/final-form-4.19.1.tgz#1aa1a3bf67f7399b54ed6185d56f9a8d74cfda5a" - integrity sha512-C4RldRCUs8YZod91ydtrsT+TOeG3fwU4ip9oBDXhvbWdQ6iXl4cIrTAQkqpWijbnI3XFVA0akV7YTjSFJMJ2uw== +final-form@^4.20.0: + version "4.20.0" + resolved "https://registry.yarnpkg.com/final-form/-/final-form-4.20.0.tgz#454ba46f783a4c4404ad875cf36f470395ad5efa" + integrity sha512-kdPGNlR/23M2p7ccVwE/vCBQH9TH1NAhhMVkETHbaQXkTWIJdEii3ZdHrOgYvFY7O87myEhcqzx3zjMERtoNJg== dependencies: - "@babel/runtime" "^7.8.3" + "@babel/runtime" "^7.10.0" + "@scarf/scarf" "^1.0.5" finalhandler@~1.1.2: version "1.1.2" @@ -13413,13 +13426,14 @@ react-final-form-listeners@^1.0.2: dependencies: "@babel/runtime" "^7.1.5" -react-final-form@6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/react-final-form/-/react-final-form-6.4.0.tgz#7f9064c10a8bee6a02cd3e9b8aff06fa07f9c908" - integrity sha512-M7J7f0pnoj0o8sBq3iG6jsWJEh08pNUyl2D4wBC9SJvCNkGdol2UdyjMiEFYD3rz9LIFzQqFSG0kbRBCadqzhA== +react-final-form@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/react-final-form/-/react-final-form-6.5.0.tgz#b0440acf534fd57991c048764ab20af13124aed6" + integrity sha512-H97PLCtfMIN32NHqm85E738Pj+NOF1p0eQEG+h5DbdaofwtqDRp7taHu45+PlXOqg9ANbM6MyXkYxWpIiE6qbQ== dependencies: - "@babel/runtime" "^7.9.2" - ts-essentials "^6.0.3" + "@babel/runtime" "^7.10.0" + "@scarf/scarf" "^1.0.5" + ts-essentials "^6.0.5" react-ga@^2.7.0: version "2.7.0" @@ -15948,7 +15962,7 @@ truncate-utf8-bytes@^1.0.0: dependencies: utf8-byte-length "^1.0.1" -ts-essentials@^6.0.3: +ts-essentials@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-6.0.5.tgz#dd5b98f73bd56dc94d15dfbc0fbf01da3163eb42" integrity sha512-RSAKlpu+E0DCGY8FsbG92EveRLw2Y+UgK3ksX01w1VaHeG01dKkYo/KtAV4q0qPT6nPbLfyerb2YPVSediP+8g== From 4de33080690e7b593e15da50446598527f6b8a99 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Fri, 29 May 2020 21:43:37 -0300 Subject: [PATCH 02/24] extend TextField to support submit errors --- src/components/forms/TextField/index.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/forms/TextField/index.tsx b/src/components/forms/TextField/index.tsx index 70ac771a..c1998d63 100644 --- a/src/components/forms/TextField/index.tsx +++ b/src/components/forms/TextField/index.tsx @@ -33,13 +33,15 @@ class TextField extends React.PureComponent { } = this.props const helperText = value ? text : undefined const showError = (meta.touched || !meta.pristine) && !meta.valid + const hasError = !!meta.error || (!meta.modifiedSinceLastSubmit && !!meta.submitError) + const errorMessage = meta.error || meta.submitError const isInactiveAndPristineOrUntouched = !meta.active && (meta.pristine || !meta.touched) const isInvalidAndUntouched = typeof meta.error === 'undefined' ? true : !meta.touched const disableUnderline = isInactiveAndPristineOrUntouched && isInvalidAndUntouched const inputRoot = helperText ? classes.root : '' - const statusClasses = meta.valid ? 'isValid' : meta.error && (meta.dirty || meta.touched) ? 'isInvalid' : '' + const statusClasses = meta.valid ? 'isValid' : hasError && showError ? 'isInvalid' : '' const inputProps = { ...restInput, autoComplete: 'off', @@ -53,8 +55,8 @@ class TextField extends React.PureComponent { return ( Date: Fri, 29 May 2020 21:43:56 -0300 Subject: [PATCH 03/24] create form error component --- .../FormErrorMessage/index.tsx | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/FormErrorMessage/index.tsx diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/FormErrorMessage/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/FormErrorMessage/index.tsx new file mode 100644 index 00000000..9f259c69 --- /dev/null +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/FormErrorMessage/index.tsx @@ -0,0 +1,27 @@ +import { makeStyles } from '@material-ui/core/styles' +import React from 'react' +import { useFormState } from 'react-final-form' + +import Row from 'src/components/layout/Row' +import Paragraph from 'src/components/layout/Paragraph' +import { styles } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style' + +const useStyles = makeStyles(styles as any) + +const FormErrorMessage = () => { + const classes = useStyles() + const { modifiedSinceLastSubmit, submitError } = useFormState({ + subscription: { modifiedSinceLastSubmit: true, submitError: true }, + }) + + const hasNewSubmitError = !!submitError && !modifiedSinceLastSubmit + return hasNewSubmitError ? ( + + + {submitError} + + + ) : null +} + +export default FormErrorMessage From df0562911e8fb61ee3a5b3b90789647c1d99e47d Mon Sep 17 00:00:00 2001 From: fernandomg Date: Fri, 29 May 2020 21:44:40 -0300 Subject: [PATCH 04/24] hide EthValue if the method set does not require it --- .../SendModal/screens/ContractInteraction/EthValue/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx index f6c0381d..c0bf8fb1 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx @@ -25,7 +25,7 @@ const EthValue = ({ onSetMax }) => { } = useField('selectedMethod', { value: true }) const disabled = !ABIService.isPayable(method) - return ( + return disabled ? null : ( <> From 45bbbc99677d81cec1b1af4389e5d00993cd2571 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Fri, 29 May 2020 21:45:22 -0300 Subject: [PATCH 05/24] properly handle submit errors --- .../ContractInteraction/Buttons/index.tsx | 19 ++++++-- .../screens/ContractInteraction/index.tsx | 47 ++++++++++++------- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx index 9f0f2b99..3b587995 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx @@ -17,8 +17,15 @@ const Buttons = ({ onCallSubmit, onClose }) => { const { input: { value: contractAddress }, } = useField('contractAddress', { valid: true } as any) - const { submitting, valid, validating, values } = useFormState({ - subscription: { submitting: true, valid: true, values: true, validating: true }, + const { modifiedSinceLastSubmit, submitError, submitting, valid, validating, values } = useFormState({ + subscription: { + modifiedSinceLastSubmit: true, + submitError: true, + submitting: true, + valid: true, + values: true, + validating: true, + }, }) const handleCallSubmit = async () => { @@ -48,7 +55,13 @@ const Buttons = ({ onCallSubmit, onClose }) => { className={classes.submitButton} color="primary" data-testid="review-tx-btn" - disabled={submitting || validating || !valid || !method || (method as any).action === 'read'} + disabled={ + submitting || + validating || + ((!valid || !!submitError) && !modifiedSinceLastSubmit) || + !method || + (method as any).action === 'read' + } minWidth={140} type="submit" variant="contained" diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx index 7cceae50..de34ffc0 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx @@ -1,26 +1,23 @@ import { makeStyles } from '@material-ui/core/styles' +import { FORM_ERROR } from 'final-form' import React from 'react' import { styles } from './style' - import GnoForm from 'src/components/forms/GnoForm' import Block from 'src/components/layout/Block' import Hairline from 'src/components/layout/Hairline' import SafeInfo from 'src/routes/safe/components/Balances/SendModal/SafeInfo' -import Buttons from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons' -import ContractABI from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ContractABI' -import EthAddressInput from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthAddressInput' -import EthValue from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue' -import FormDivisor from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/FormDivisor' -import Header from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Header' -import MethodsDropdown from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown' -import RenderInputParams from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderInputParams' -import RenderOutputParams from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderOutputParams' -import { - abiExtractor, - createTxObject, - formMutators, -} from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils' +import Buttons from './Buttons' +import ContractABI from './ContractABI' +import EthAddressInput from './EthAddressInput' +import EthValue from './EthValue' +import FormDivisor from './FormDivisor' +import FormErrorMessage from './FormErrorMessage' +import Header from './Header' +import MethodsDropdown from './MethodsDropdown' +import RenderInputParams from './RenderInputParams' +import RenderOutputParams from './RenderOutputParams' +import { abiExtractor, createTxObject, formMutators } from './utils' const useStyles = makeStyles(styles as any) @@ -35,8 +32,21 @@ const ContractInteraction = ({ contractAddress, initialValues, onClose, onNext } const handleSubmit = async ({ contractAddress, selectedMethod, value, ...values }) => { if (value || (contractAddress && selectedMethod)) { - const data = await createTxObject(selectedMethod, contractAddress, values).encodeABI() - onNext({ contractAddress, data, selectedMethod, value, ...values }) + try { + const txObject = createTxObject(selectedMethod, contractAddress, values) + const data = txObject.encodeABI() + await txObject.estimateGas() + onNext({ contractAddress, data, selectedMethod, value, ...values }) + } catch (e) { + for (const key in values) { + if (values.hasOwnProperty(key) && values[key] === e.value) { + return { [key]: e.reason } + } + } + + // .estimateGas() failed + return { [FORM_ERROR]: e.message } + } } } @@ -62,11 +72,12 @@ const ContractInteraction = ({ contractAddress, initialValues, onClose, onNext } onScannedValue={mutators.setContractAddress} text="Contract Address*" /> - + + From 420b9cb58e33564be57dc40583b84ef0a3453892 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Mon, 1 Jun 2020 14:51:45 -0300 Subject: [PATCH 06/24] properly update method selected --- .../screens/ContractInteraction/MethodsDropdown/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx index ab4c4177..102a0cd7 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx @@ -44,7 +44,7 @@ const MethodsDropdown = ({ onChange }) => { } }, [abi]) - React.useMemo(() => { + React.useEffect(() => { setMethodsListFiltered(methodsList.filter(({ name }) => name.toLowerCase().includes(searchParams.toLowerCase()))) }, [methodsList, searchParams]) From d5cf8c94abc63fc81382ce0191de2223889cf459 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Mon, 1 Jun 2020 14:54:42 -0300 Subject: [PATCH 07/24] change `signature` key name to `methodSignature` There is something down there that mutates the `tx` object in the Review screen and was overwriting the `signature` key with the method's hash --- src/logic/contractInteraction/sources/ABIService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/logic/contractInteraction/sources/ABIService.ts b/src/logic/contractInteraction/sources/ABIService.ts index 50d9f2fb..09ca6553 100644 --- a/src/logic/contractInteraction/sources/ABIService.ts +++ b/src/logic/contractInteraction/sources/ABIService.ts @@ -18,9 +18,9 @@ class ABIService { } static getMethodSignatureAndSignatureHash(method) { - const signature = ABIService.getMethodSignature(method) - const signatureHash = ABIService.getSignatureHash(signature) - return { signature, signatureHash } + const methodSignature = ABIService.getMethodSignature(method) + const signatureHash = ABIService.getSignatureHash(methodSignature) + return { methodSignature, signatureHash } } static getMethodSignature({ inputs, name }) { From 1442f33d5a9106abbf096f04bae1f498853123df Mon Sep 17 00:00:00 2001 From: fernandomg Date: Mon, 1 Jun 2020 23:35:35 -0300 Subject: [PATCH 08/24] fix error message format, prevent text overflow --- .../screens/ContractInteraction/FormErrorMessage/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/FormErrorMessage/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/FormErrorMessage/index.tsx index 9f259c69..6231efa9 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/FormErrorMessage/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/FormErrorMessage/index.tsx @@ -17,7 +17,7 @@ const FormErrorMessage = () => { const hasNewSubmitError = !!submitError && !modifiedSinceLastSubmit return hasNewSubmitError ? ( - + {submitError} From a28c598af1c204f71591198ef857b63088a94c86 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Mon, 1 Jun 2020 23:36:39 -0300 Subject: [PATCH 09/24] add title to call-result values --- .../RenderOutputParams/index.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderOutputParams/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderOutputParams/index.tsx index bbb9c575..2f728580 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderOutputParams/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderOutputParams/index.tsx @@ -3,6 +3,7 @@ import { useField } from 'react-final-form' import TextField from 'src/components/forms/TextField' import Col from 'src/components/layout/Col' +import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' const RenderOutputParams = () => { @@ -14,8 +15,14 @@ const RenderOutputParams = () => { }: any = useField('callResults', { value: true }) const multipleResults = !!method && method.outputs.length > 1 - return results - ? method.outputs.map(({ name, type }, index) => { + return results ? ( + <> + + + Call result: + + + {method.outputs.map(({ name, type }, index) => { const placeholder = name ? `${name} (${type})` : type const key = `methodCallResult-${method.name}_${index}_${type}` const value = multipleResults ? results[index] : results @@ -33,8 +40,9 @@ const RenderOutputParams = () => { ) - }) - : null + })} + + ) : null } export default RenderOutputParams From ed995df5b77a4498711b7274c01f2b2914a40f79 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Mon, 1 Jun 2020 23:38:27 -0300 Subject: [PATCH 10/24] simplify call/review buttons - unify handleSubmit - use `.call` to identify a failing tx --- .../ContractInteraction/Buttons/index.tsx | 57 +++++-------------- .../screens/ContractInteraction/index.tsx | 30 ++++++---- .../ContractInteraction/utils/index.ts | 14 +++++ 3 files changed, 46 insertions(+), 55 deletions(-) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx index 3b587995..3350981c 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx @@ -5,70 +5,41 @@ import { useField, useFormState } from 'react-final-form' import Button from 'src/components/layout/Button' import Row from 'src/components/layout/Row' import { styles } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style' -import { createTxObject } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils' +import { isReadMethod } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils' const useStyles = makeStyles(styles as any) -const Buttons = ({ onCallSubmit, onClose }) => { +const Buttons = ({ onClose }) => { const classes = useStyles() const { input: { value: method }, } = useField('selectedMethod', { value: true }) - const { - input: { value: contractAddress }, - } = useField('contractAddress', { valid: true } as any) - const { modifiedSinceLastSubmit, submitError, submitting, valid, validating, values } = useFormState({ + const { modifiedSinceLastSubmit, submitError, submitting, valid, validating } = useFormState({ subscription: { modifiedSinceLastSubmit: true, submitError: true, submitting: true, valid: true, - values: true, validating: true, }, }) - const handleCallSubmit = async () => { - const results = await createTxObject(method, contractAddress, values).call() - onCallSubmit(results) - } - return ( - {method && (method as any).action === 'read' ? ( - - ) : ( - - )} + ) } diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx index de34ffc0..2c945a0d 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx @@ -1,12 +1,13 @@ import { makeStyles } from '@material-ui/core/styles' -import { FORM_ERROR } from 'final-form' import React from 'react' +import { useSelector } from 'react-redux' import { styles } from './style' import GnoForm from 'src/components/forms/GnoForm' import Block from 'src/components/layout/Block' import Hairline from 'src/components/layout/Hairline' import SafeInfo from 'src/routes/safe/components/Balances/SendModal/SafeInfo' +import { safeSelector } from 'src/routes/safe/store/selectors' import Buttons from './Buttons' import ContractABI from './ContractABI' import EthAddressInput from './EthAddressInput' @@ -17,12 +18,14 @@ import Header from './Header' import MethodsDropdown from './MethodsDropdown' import RenderInputParams from './RenderInputParams' import RenderOutputParams from './RenderOutputParams' -import { abiExtractor, createTxObject, formMutators } from './utils' +import { abiExtractor, createTxObject, formMutators, handleSubmitError, isReadMethod } from './utils' const useStyles = makeStyles(styles as any) const ContractInteraction = ({ contractAddress, initialValues, onClose, onNext }) => { const classes = useStyles() + const { address: safeAddress = '' } = useSelector(safeSelector) + let setCallResults React.useMemo(() => { if (contractAddress) { @@ -35,17 +38,18 @@ const ContractInteraction = ({ contractAddress, initialValues, onClose, onNext } try { const txObject = createTxObject(selectedMethod, contractAddress, values) const data = txObject.encodeABI() - await txObject.estimateGas() - onNext({ contractAddress, data, selectedMethod, value, ...values }) - } catch (e) { - for (const key in values) { - if (values.hasOwnProperty(key) && values[key] === e.value) { - return { [key]: e.reason } - } + const result = await txObject.call({ from: safeAddress }) + + if (isReadMethod(selectedMethod)) { + setCallResults(result) + + // this was a read method, so we won't go to the 'review' screen + return } - // .estimateGas() failed - return { [FORM_ERROR]: e.message } + onNext({ contractAddress, data, selectedMethod, value, ...values }) + } catch (error) { + return handleSubmitError(error, values) } } } @@ -62,6 +66,8 @@ const ContractInteraction = ({ contractAddress, initialValues, onClose, onNext } subscription={{ submitting: true, pristine: true }} > {(submitting, validating, rest, mutators) => { + setCallResults = mutators.setCallResults + return ( <> @@ -80,7 +86,7 @@ const ContractInteraction = ({ contractAddress, initialValues, onClose, onNext } - + ) }} diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils/index.ts b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils/index.ts index 4fa20b0d..2c961591 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils/index.ts +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils/index.ts @@ -1,3 +1,4 @@ +import { FORM_ERROR } from 'final-form' import createDecorator from 'final-form-calculate' import { mustBeEthereumAddress, mustBeEthereumContractAddress } from 'src/components/forms/validator' @@ -48,6 +49,17 @@ export const formMutators = { }, } +export const handleSubmitError = (error, values) => { + for (const key in values) { + if (values.hasOwnProperty(key) && values[key] === error.value) { + return { [key]: error.reason } + } + } + + // .call() failed and we're logging a generic error + return { [FORM_ERROR]: error.message } +} + export const createTxObject = (method, contractAddress, values) => { const web3 = getWeb3() const contract: any = new web3.eth.Contract([method], contractAddress) @@ -56,3 +68,5 @@ export const createTxObject = (method, contractAddress, values) => { return contract.methods[name](...args) } + +export const isReadMethod = (method: any) => method && method.action === 'read' From adfbcdfd196b41e4312cd4bf089a3b914586e26a Mon Sep 17 00:00:00 2001 From: fernandomg Date: Mon, 1 Jun 2020 23:42:01 -0300 Subject: [PATCH 11/24] update yarn.lock --- yarn.lock | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/yarn.lock b/yarn.lock index 8991db61..26515e8a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1098,6 +1098,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.10.0": + version "7.10.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.2.tgz#d103f21f2602497d38348a32e008637d506db839" + integrity sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.10.1", "@babel/template@^7.4.0", "@babel/template@^7.8.6": version "7.10.1" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811" @@ -1832,6 +1839,11 @@ dependencies: any-observable "^0.3.0" +"@scarf/scarf@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.0.5.tgz#accee0bce88a9047672f7c8faf3cada59c996b81" + integrity sha512-9WKaGVpQH905Aqkk+BczFEeLQxS07rl04afFRPUG9IcSlOwmo5EVVuuNu0d4M9LMYucObvK0LoAe+5HfMW2QhQ== + "@sheerun/mutationobserver-shim@^0.3.2": version "0.3.3" resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz#5405ee8e444ed212db44e79351f0c70a582aae25" @@ -7653,12 +7665,13 @@ final-form-calculate@^1.3.1: resolved "https://registry.yarnpkg.com/final-form-calculate/-/final-form-calculate-1.3.1.tgz#463089114245afa97fea94712bfbfca11da8413e" integrity sha512-vZCvQ08w9FIoHLkZMcJSIXQr5TAVLxHfLD0thmm50zcNyJESruqhgvurSjWYPLoJGnIgbIb94Rumdg5ZXX5WiQ== -final-form@4.19.1: - version "4.19.1" - resolved "https://registry.yarnpkg.com/final-form/-/final-form-4.19.1.tgz#1aa1a3bf67f7399b54ed6185d56f9a8d74cfda5a" - integrity sha512-C4RldRCUs8YZod91ydtrsT+TOeG3fwU4ip9oBDXhvbWdQ6iXl4cIrTAQkqpWijbnI3XFVA0akV7YTjSFJMJ2uw== +final-form@^4.20.0: + version "4.20.0" + resolved "https://registry.yarnpkg.com/final-form/-/final-form-4.20.0.tgz#454ba46f783a4c4404ad875cf36f470395ad5efa" + integrity sha512-kdPGNlR/23M2p7ccVwE/vCBQH9TH1NAhhMVkETHbaQXkTWIJdEii3ZdHrOgYvFY7O87myEhcqzx3zjMERtoNJg== dependencies: - "@babel/runtime" "^7.8.3" + "@babel/runtime" "^7.10.0" + "@scarf/scarf" "^1.0.5" finalhandler@~1.1.2: version "1.1.2" @@ -13514,13 +13527,14 @@ react-final-form-listeners@^1.0.2: dependencies: "@babel/runtime" "^7.1.5" -react-final-form@6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/react-final-form/-/react-final-form-6.4.0.tgz#7f9064c10a8bee6a02cd3e9b8aff06fa07f9c908" - integrity sha512-M7J7f0pnoj0o8sBq3iG6jsWJEh08pNUyl2D4wBC9SJvCNkGdol2UdyjMiEFYD3rz9LIFzQqFSG0kbRBCadqzhA== +react-final-form@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/react-final-form/-/react-final-form-6.5.0.tgz#b0440acf534fd57991c048764ab20af13124aed6" + integrity sha512-H97PLCtfMIN32NHqm85E738Pj+NOF1p0eQEG+h5DbdaofwtqDRp7taHu45+PlXOqg9ANbM6MyXkYxWpIiE6qbQ== dependencies: - "@babel/runtime" "^7.9.2" - ts-essentials "^6.0.3" + "@babel/runtime" "^7.10.0" + "@scarf/scarf" "^1.0.5" + ts-essentials "^6.0.5" react-ga@^2.7.0: version "2.7.0" @@ -16062,7 +16076,7 @@ truncate-utf8-bytes@^1.0.0: dependencies: utf8-byte-length "^1.0.1" -ts-essentials@^6.0.3: +ts-essentials@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-6.0.5.tgz#dd5b98f73bd56dc94d15dfbc0fbf01da3163eb42" integrity sha512-RSAKlpu+E0DCGY8FsbG92EveRLw2Y+UgK3ksX01w1VaHeG01dKkYo/KtAV4q0qPT6nPbLfyerb2YPVSediP+8g== @@ -17021,9 +17035,9 @@ web3-provider-engine@^15.0.4: xhr "^2.2.0" xtend "^4.0.1" -"web3-provider-engine@git+https://github.com/trufflesuite/provider-engine.git#web3-one": +"web3-provider-engine@https://github.com/trufflesuite/provider-engine#web3-one": version "14.0.6" - resolved "git+https://github.com/trufflesuite/provider-engine.git#3538c60bc4836b73ccae1ac3f64c8fed8ef19c1a" + resolved "https://github.com/trufflesuite/provider-engine#3538c60bc4836b73ccae1ac3f64c8fed8ef19c1a" dependencies: async "^2.5.0" backoff "^2.5.0" From b07f0e90d9f3cd791b2db72419931e3400475bdd Mon Sep 17 00:00:00 2001 From: fernandomg Date: Tue, 2 Jun 2020 09:04:29 -0300 Subject: [PATCH 12/24] use method `.call` only for the read methods - review screen then will attempt to evaluate the validity of the write method --- .../Balances/SendModal/screens/ContractInteraction/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx index 2c945a0d..46bdcda7 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx @@ -38,9 +38,9 @@ const ContractInteraction = ({ contractAddress, initialValues, onClose, onNext } try { const txObject = createTxObject(selectedMethod, contractAddress, values) const data = txObject.encodeABI() - const result = await txObject.call({ from: safeAddress }) if (isReadMethod(selectedMethod)) { + const result = await txObject.call({ from: safeAddress }) setCallResults(result) // this was a read method, so we won't go to the 'review' screen From f049f8598d5b6f3c5d9d9d3d97aad54f9ca3986d Mon Sep 17 00:00:00 2001 From: fernandomg Date: Tue, 2 Jun 2020 10:17:02 -0300 Subject: [PATCH 13/24] fix styles typing --- .../ContractInteraction/Buttons/index.tsx | 2 +- .../EthAddressInput/index.tsx | 2 +- .../ContractInteraction/EthValue/index.tsx | 3 +- .../FormErrorMessage/index.tsx | 2 +- .../ContractInteraction/Header/index.tsx | 4 +- .../ContractInteraction/Review/index.tsx | 7 +-- .../ContractInteraction/Review/style.ts | 57 ------------------- .../screens/ContractInteraction/index.tsx | 2 +- .../screens/ContractInteraction/style.ts | 28 +++++++-- 9 files changed, 34 insertions(+), 73 deletions(-) delete mode 100644 src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/style.ts diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx index 3350981c..65b10f30 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx @@ -7,7 +7,7 @@ import Row from 'src/components/layout/Row' import { styles } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style' import { isReadMethod } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils' -const useStyles = makeStyles(styles as any) +const useStyles = makeStyles(styles) const Buttons = ({ onClose }) => { const classes = useStyles() diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthAddressInput/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthAddressInput/index.tsx index 3918b087..cf5c652c 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthAddressInput/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthAddressInput/index.tsx @@ -14,7 +14,7 @@ import Col from 'src/components/layout/Col' import Row from 'src/components/layout/Row' import { styles } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style' -const useStyles = makeStyles(styles as any) +const useStyles = makeStyles(styles) const EthAddressInput = ({ isContract = true, isRequired = true, name, onScannedValue, text }) => { const classes = useStyles() diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx index c0bf8fb1..d084f797 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx @@ -15,7 +15,7 @@ import ABIService from 'src/logic/contractInteraction/sources/ABIService' import { styles } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style' import { safeSelector } from 'src/routes/safe/store/selectors' -const useStyles = makeStyles(styles as any) +const useStyles = makeStyles(styles) const EthValue = ({ onSetMax }) => { const classes = useStyles() @@ -42,7 +42,6 @@ const EthValue = ({ onSetMax }) => { { const classes = useStyles() diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Header/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Header/index.tsx index 32ca42cb..e46798f1 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Header/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Header/index.tsx @@ -7,14 +7,14 @@ import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' import { styles } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style' -const useStyles = makeStyles(styles as any) +const useStyles = makeStyles(styles) const Header = ({ onClose, subTitle, title }) => { const classes = useStyles() return ( - + {title} {subTitle} diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/index.tsx index a9b867b4..b01d5bc6 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/index.tsx @@ -3,8 +3,6 @@ import { withSnackbar } from 'notistack' import React, { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' -import { styles } from './style' - import AddressInfo from 'src/components/AddressInfo' import Block from 'src/components/layout/Block' import Button from 'src/components/layout/Button' @@ -18,12 +16,13 @@ import { estimateTxGasCosts } from 'src/logic/safe/transactions/gasNew' import { formatAmount } from 'src/logic/tokens/utils/formatAmount' import { getEthAsToken } from 'src/logic/tokens/utils/tokenHelpers' import { getWeb3 } from 'src/logic/wallets/getWeb3' +import { styles } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style' import Header from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Header' import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils' import createTransaction from 'src/routes/safe/store/actions/createTransaction' import { safeSelector } from 'src/routes/safe/store/selectors' -const useStyles = makeStyles(styles as any) +const useStyles = makeStyles(styles) const ContractInteractionReview = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }: any) => { const classes = useStyles() @@ -79,7 +78,7 @@ const ContractInteractionReview = ({ closeSnackbar, enqueueSnackbar, onClose, on <>
- + Contract Address diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/style.ts b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/style.ts deleted file mode 100644 index c740f8fd..00000000 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/style.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { border, lg, md, secondaryText, sm } from 'src/theme/variables' - -export const styles = () => ({ - heading: { - padding: `${md} ${lg}`, - justifyContent: 'flex-start', - boxSizing: 'border-box', - maxHeight: '75px', - }, - annotation: { - letterSpacing: '-1px', - color: secondaryText, - marginRight: 'auto', - marginLeft: '20px', - }, - headingText: { - fontSize: lg, - }, - closeIcon: { - height: '35px', - width: '35px', - }, - container: { - padding: `${md} ${lg}`, - }, - value: { - marginLeft: sm, - }, - outerData: { - borderRadius: '5px', - border: `1px solid ${border}`, - padding: '11px', - minHeight: '21px', - }, - data: { - wordBreak: 'break-all', - overflow: 'auto', - fontSize: '14px', - fontFamily: 'Averta', - maxHeight: '100px', - letterSpacing: 'normal', - fontStretch: 'normal', - lineHeight: '1.43', - }, - buttonRow: { - height: '84px', - justifyContent: 'center', - '& > button': { - fontFamily: 'Averta', - fontSize: md, - }, - }, - submitButton: { - boxShadow: '1px 2px 10px 0 rgba(212, 212, 211, 0.59)', - marginLeft: '15px', - }, -}) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx index 46bdcda7..34fa54bf 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx @@ -20,7 +20,7 @@ import RenderInputParams from './RenderInputParams' import RenderOutputParams from './RenderOutputParams' import { abiExtractor, createTxObject, formMutators, handleSubmitError, isReadMethod } from './utils' -const useStyles = makeStyles(styles as any) +const useStyles = makeStyles(styles) const ContractInteraction = ({ contractAddress, initialValues, onClose, onNext }) => { const classes = useStyles() diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style.ts b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style.ts index 4f51343f..14fdac14 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style.ts +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style.ts @@ -1,6 +1,7 @@ -import { lg, md } from 'src/theme/variables' +import { lg, md, secondaryText, sm, border } from 'src/theme/variables' +import { createStyles } from '@material-ui/core' -export const styles = () => ({ +export const styles = createStyles({ heading: { padding: `${md} ${lg}`, justifyContent: 'flex-start', @@ -9,11 +10,11 @@ export const styles = () => ({ }, annotation: { letterSpacing: '-1px', - color: '#a2a8ba', + color: secondaryText, marginRight: 'auto', marginLeft: '20px', }, - manage: { + headingText: { fontSize: lg, }, closeIcon: { @@ -26,6 +27,25 @@ export const styles = () => ({ formContainer: { padding: `${md} ${lg}`, }, + value: { + marginLeft: sm, + }, + outerData: { + borderRadius: '5px', + border: `1px solid ${border}`, + padding: '11px', + minHeight: '21px', + }, + data: { + wordBreak: 'break-all', + overflow: 'auto', + fontSize: '14px', + fontFamily: 'Averta', + maxHeight: '100px', + letterSpacing: 'normal', + fontStretch: 'normal', + lineHeight: '1.43', + }, buttonRow: { height: '84px', justifyContent: 'center', From 2c0bcfefe64624ac85b69b72586adfc75c5967e6 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Tue, 2 Jun 2020 10:22:49 -0300 Subject: [PATCH 14/24] convert ABIService from class to module and add typings --- .../contractInteraction/sources/ABIService.ts | 41 ------------------- .../sources/ABIService/index.ts | 38 +++++++++++++++++ .../sources/ABIService/types.d.ts | 25 +++++++++++ .../sources/EtherscanService.ts | 4 +- .../ContractInteraction/ContractABI/index.tsx | 4 +- .../ContractInteraction/EthValue/index.tsx | 4 +- .../MethodsDropdown/index.tsx | 4 +- 7 files changed, 70 insertions(+), 50 deletions(-) delete mode 100644 src/logic/contractInteraction/sources/ABIService.ts create mode 100644 src/logic/contractInteraction/sources/ABIService/index.ts create mode 100644 src/logic/contractInteraction/sources/ABIService/types.d.ts diff --git a/src/logic/contractInteraction/sources/ABIService.ts b/src/logic/contractInteraction/sources/ABIService.ts deleted file mode 100644 index 09ca6553..00000000 --- a/src/logic/contractInteraction/sources/ABIService.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { getWeb3 } from 'src/logic/wallets/getWeb3' - -class ABIService { - static extractUsefulMethods(abi) { - return abi - .filter(({ constant, name, type }) => type === 'function' && !!name && typeof constant === 'boolean') - .map((method) => ({ - action: method.constant ? 'read' : 'write', - ...ABIService.getMethodSignatureAndSignatureHash(method), - ...method, - })) - .sort(({ name: a }, { name: b }) => (a.toLowerCase() > b.toLowerCase() ? 1 : -1)) - } - - static getMethodHash(method) { - const signature = ABIService.getMethodSignature(method) - return ABIService.getSignatureHash(signature) - } - - static getMethodSignatureAndSignatureHash(method) { - const methodSignature = ABIService.getMethodSignature(method) - const signatureHash = ABIService.getSignatureHash(methodSignature) - return { methodSignature, signatureHash } - } - - static getMethodSignature({ inputs, name }) { - const params = inputs.map((x) => x.type).join(',') - return `${name}(${params})` - } - - static getSignatureHash(signature) { - const web3 = getWeb3() - return web3.utils.keccak256(signature).toString() - } - - static isPayable(method) { - return method.payable - } -} - -export default ABIService diff --git a/src/logic/contractInteraction/sources/ABIService/index.ts b/src/logic/contractInteraction/sources/ABIService/index.ts new file mode 100644 index 00000000..8625bdb5 --- /dev/null +++ b/src/logic/contractInteraction/sources/ABIService/index.ts @@ -0,0 +1,38 @@ +import { getWeb3 } from 'src/logic/wallets/getWeb3' +import { ABI, ExtendedABI } from './types' + +export const getMethodSignature = ({ inputs, name }) => { + const params = inputs.map((x) => x.type).join(',') + return `${name}(${params})` +} + +export const getSignatureHash = (signature) => { + const web3 = getWeb3() + return web3.utils.keccak256(signature).toString() +} + +export const getMethodHash = (method) => { + const signature = getMethodSignature(method) + return getSignatureHash(signature) +} + +export const getMethodSignatureAndSignatureHash = (method) => { + const methodSignature = getMethodSignature(method) + const signatureHash = getSignatureHash(methodSignature) + return { methodSignature, signatureHash } +} + +export const extractUsefulMethods = (abi: ABI): ExtendedABI => { + return abi + .filter(({ constant, name, type }) => type === 'function' && !!name && typeof constant === 'boolean') + .map((method) => ({ + action: method.constant ? 'read' : 'write', + ...getMethodSignatureAndSignatureHash(method), + ...method, + })) + .sort(({ name: a }, { name: b }) => (a.toLowerCase() > b.toLowerCase() ? 1 : -1)) +} + +export const isPayable = (method) => { + return method.payable +} diff --git a/src/logic/contractInteraction/sources/ABIService/types.d.ts b/src/logic/contractInteraction/sources/ABIService/types.d.ts new file mode 100644 index 00000000..5cd04f5d --- /dev/null +++ b/src/logic/contractInteraction/sources/ABIService/types.d.ts @@ -0,0 +1,25 @@ +export interface InterfaceParams { + internalType: string + name: string + type: string +} + +export interface ContractInterface { + constant: boolean + inputs: InterfaceParams[] + name: string + outputs: InterfaceParams[] + payable: boolean + stateMutability: string + type: string +} + +export interface ExtendedContractInterface extends ContractInterface { + action: string + methodSignature: string + signatureHash: string +} + +export type ABI = ContractInterface[] + +export type ExtendedABI = ExtendedContractInterface[] diff --git a/src/logic/contractInteraction/sources/EtherscanService.ts b/src/logic/contractInteraction/sources/EtherscanService.ts index 81ebc298..e03c6d46 100644 --- a/src/logic/contractInteraction/sources/EtherscanService.ts +++ b/src/logic/contractInteraction/sources/EtherscanService.ts @@ -1,11 +1,10 @@ import { RateLimit } from 'async-sema' import memoize from 'lodash.memoize' -import ABIService from 'src/logic/contractInteraction/sources/ABIService' import { ETHEREUM_NETWORK } from 'src/logic/wallets/getWeb3' import { ETHERSCAN_API_KEY } from 'src/utils/constants' -class EtherscanService extends ABIService { +class EtherscanService { _rateLimit = async () => {} _endpointsUrls = { @@ -38,7 +37,6 @@ class EtherscanService extends ABIService { ) constructor(options) { - super() this._rateLimit = RateLimit(options.rps) } diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ContractABI/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ContractABI/index.tsx index 0acba016..37aabf00 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ContractABI/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ContractABI/index.tsx @@ -3,13 +3,13 @@ import React from 'react' import TextareaField from 'src/components/forms/TextareaField' import Col from 'src/components/layout/Col' import Row from 'src/components/layout/Row' -import EtherscanService from 'src/logic/contractInteraction/sources/EtherscanService' +import { extractUsefulMethods } from 'src/logic/contractInteraction/sources/ABIService' export const NO_DATA = 'no data' const mustBeValidABI = (abi) => { try { - const parsedABI = EtherscanService.extractUsefulMethods(JSON.parse(abi)) + const parsedABI = extractUsefulMethods(JSON.parse(abi)) if (parsedABI.length === 0) { return NO_DATA diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx index d084f797..b9cafb46 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx @@ -11,7 +11,7 @@ import ButtonLink from 'src/components/layout/ButtonLink' import Col from 'src/components/layout/Col' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' -import ABIService from 'src/logic/contractInteraction/sources/ABIService' +import { isPayable } from 'src/logic/contractInteraction/sources/ABIService' import { styles } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style' import { safeSelector } from 'src/routes/safe/store/selectors' @@ -23,7 +23,7 @@ const EthValue = ({ onSetMax }) => { const { input: { value: method }, } = useField('selectedMethod', { value: true }) - const disabled = !ABIService.isPayable(method) + const disabled = !isPayable(method) return disabled ? null : ( <> diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx index 102a0cd7..b9988c81 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx @@ -11,11 +11,11 @@ import { useField, useFormState } from 'react-final-form' import Col from 'src/components/layout/Col' import Row from 'src/components/layout/Row' -import EtherscanService from 'src/logic/contractInteraction/sources/EtherscanService' import { NO_CONTRACT } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils' import CheckIcon from 'src/routes/safe/components/CurrencyDropdown/img/check.svg' import { useDropdownStyles } from 'src/routes/safe/components/CurrencyDropdown/style' import { DropdownListTheme } from 'src/theme/mui' +import { extractUsefulMethods } from 'src/logic/contractInteraction/sources/ABIService' const MENU_WIDTH = '452px' @@ -37,7 +37,7 @@ const MethodsDropdown = ({ onChange }) => { React.useEffect(() => { if (abi) { try { - setMethodsList(EtherscanService.extractUsefulMethods(JSON.parse(abi))) + setMethodsList(extractUsefulMethods(JSON.parse(abi))) } catch (e) { setMethodsList([]) } From 7dfa7af41d2248986e00ae11ac81181e3f9fa958 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Tue, 2 Jun 2020 10:45:55 -0300 Subject: [PATCH 15/24] add general types to ContractInteraction components --- .../ContractInteraction/Buttons/index.tsx | 6 +++++- .../ContractInteraction/ContractABI/index.tsx | 4 ++-- .../EthAddressInput/index.tsx | 10 +++++++++- .../ContractInteraction/EthValue/index.tsx | 5 ++++- .../screens/ContractInteraction/Header/index.tsx | 8 +++++++- .../MethodsDropdown/index.tsx | 6 +++++- .../screens/ContractInteraction/index.tsx | 16 +++++++++++++++- 7 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx index 65b10f30..82db6855 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx @@ -9,7 +9,11 @@ import { isReadMethod } from 'src/routes/safe/components/Balances/SendModal/scre const useStyles = makeStyles(styles) -const Buttons = ({ onClose }) => { +export interface ButtonProps { + onClose: () => void +} + +const Buttons = ({ onClose }: ButtonProps) => { const classes = useStyles() const { input: { value: method }, diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ContractABI/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ContractABI/index.tsx index 37aabf00..fe47ee3f 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ContractABI/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ContractABI/index.tsx @@ -7,7 +7,7 @@ import { extractUsefulMethods } from 'src/logic/contractInteraction/sources/ABIS export const NO_DATA = 'no data' -const mustBeValidABI = (abi) => { +const mustBeValidABI = (abi: string): undefined | string => { try { const parsedABI = extractUsefulMethods(JSON.parse(abi)) @@ -15,7 +15,7 @@ const mustBeValidABI = (abi) => { return NO_DATA } } catch (e) { - return [] + return NO_DATA } } diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthAddressInput/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthAddressInput/index.tsx index cf5c652c..91ddf4df 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthAddressInput/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthAddressInput/index.tsx @@ -16,7 +16,15 @@ import { styles } from 'src/routes/safe/components/Balances/SendModal/screens/Co const useStyles = makeStyles(styles) -const EthAddressInput = ({ isContract = true, isRequired = true, name, onScannedValue, text }) => { +export interface EthAddressProps { + isContract?: boolean + isRequired?: boolean + name: string + onScannedValue: (scannedValue: string) => void + text: string +} + +const EthAddressInput = ({ isContract = true, isRequired = true, name, onScannedValue, text }: EthAddressProps) => { const classes = useStyles() const validatorsList = [isRequired && required, mustBeEthereumAddress, isContract && mustBeEthereumContractAddress] const validate = composeValidators(...validatorsList.filter((_) => _)) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx index b9cafb46..0a020269 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx @@ -17,7 +17,10 @@ import { safeSelector } from 'src/routes/safe/store/selectors' const useStyles = makeStyles(styles) -const EthValue = ({ onSetMax }) => { +interface EthValueProps { + onSetMax: (ethBalance: string) => void +} +const EthValue = ({ onSetMax }: EthValueProps) => { const classes = useStyles() const { ethBalance } = useSelector(safeSelector) const { diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Header/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Header/index.tsx index e46798f1..b8a8dc5c 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Header/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Header/index.tsx @@ -9,7 +9,13 @@ import { styles } from 'src/routes/safe/components/Balances/SendModal/screens/Co const useStyles = makeStyles(styles) -const Header = ({ onClose, subTitle, title }) => { +interface HeaderProps { + onClose: () => void + subTitle: string + title: string +} + +const Header = ({ onClose, subTitle, title }: HeaderProps) => { const classes = useStyles() return ( diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx index b9988c81..30da6a7f 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx @@ -19,7 +19,11 @@ import { extractUsefulMethods } from 'src/logic/contractInteraction/sources/ABIS const MENU_WIDTH = '452px' -const MethodsDropdown = ({ onChange }) => { +interface MethodsDropdownProps { + onChange: ({}) => void +} + +const MethodsDropdown = ({ onChange }: MethodsDropdownProps) => { const classes = useDropdownStyles({ buttonWidth: MENU_WIDTH }) const { input: { value: abi }, diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx index 34fa54bf..b99c3a90 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx @@ -22,7 +22,21 @@ import { abiExtractor, createTxObject, formMutators, handleSubmitError, isReadMe const useStyles = makeStyles(styles) -const ContractInteraction = ({ contractAddress, initialValues, onClose, onNext }) => { +export interface CreatedTx { + contractAddress: string + data: string + selectedMethod: {} + value: string | number +} + +export interface ContractInteractionProps { + contractAddress: string + initialValues: { contractAddress?: string } + onClose: () => void + onNext: ({}: CreatedTx) => void +} + +const ContractInteraction = ({ contractAddress, initialValues, onClose, onNext }: ContractInteractionProps) => { const classes = useStyles() const { address: safeAddress = '' } = useSelector(safeSelector) let setCallResults From 95a753cbe593235abcdce7f39af44512e02ef597 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Tue, 2 Jun 2020 10:50:32 -0300 Subject: [PATCH 16/24] fix 'non-empty-pattern' ts warning --- src/logic/contractInteraction/sources/ABIService/types.d.ts | 6 +++--- .../screens/ContractInteraction/MethodsDropdown/index.tsx | 5 +++-- .../SendModal/screens/ContractInteraction/index.tsx | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/logic/contractInteraction/sources/ABIService/types.d.ts b/src/logic/contractInteraction/sources/ABIService/types.d.ts index 5cd04f5d..b5b10065 100644 --- a/src/logic/contractInteraction/sources/ABIService/types.d.ts +++ b/src/logic/contractInteraction/sources/ABIService/types.d.ts @@ -4,7 +4,7 @@ export interface InterfaceParams { type: string } -export interface ContractInterface { +export interface MethodInterface { constant: boolean inputs: InterfaceParams[] name: string @@ -14,12 +14,12 @@ export interface ContractInterface { type: string } -export interface ExtendedContractInterface extends ContractInterface { +export interface ExtendedContractInterface extends MethodInterface { action: string methodSignature: string signatureHash: string } -export type ABI = ContractInterface[] +export type ABI = MethodInterface[] export type ExtendedABI = ExtendedContractInterface[] diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx index 30da6a7f..be3a42ad 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx @@ -16,11 +16,12 @@ import CheckIcon from 'src/routes/safe/components/CurrencyDropdown/img/check.svg import { useDropdownStyles } from 'src/routes/safe/components/CurrencyDropdown/style' import { DropdownListTheme } from 'src/theme/mui' import { extractUsefulMethods } from 'src/logic/contractInteraction/sources/ABIService' +import { MethodInterface } from 'src/logic/contractInteraction/sources/ABIService/types' const MENU_WIDTH = '452px' interface MethodsDropdownProps { - onChange: ({}) => void + onChange: (method: MethodInterface) => void } const MethodsDropdown = ({ onChange }: MethodsDropdownProps) => { @@ -60,7 +61,7 @@ const MethodsDropdown = ({ onChange }: MethodsDropdownProps) => { setAnchorEl(null) } - const onMethodSelectedChanged = (chosenMethod) => { + const onMethodSelectedChanged = (chosenMethod: MethodInterface) => { setSelectedMethod(chosenMethod) onChange(chosenMethod) handleClose() diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx index b99c3a90..d767fd3a 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx @@ -33,7 +33,7 @@ export interface ContractInteractionProps { contractAddress: string initialValues: { contractAddress?: string } onClose: () => void - onNext: ({}: CreatedTx) => void + onNext: (tx: CreatedTx) => void } const ContractInteraction = ({ contractAddress, initialValues, onClose, onNext }: ContractInteractionProps) => { From cd77fba5b746f8c3c37f637a710dc4119e5344a1 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Tue, 2 Jun 2020 13:32:11 -0300 Subject: [PATCH 17/24] fix useField abi config object param --- .../screens/ContractInteraction/RenderInputParams/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderInputParams/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderInputParams/index.tsx index 274c812f..1aa97926 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderInputParams/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderInputParams/index.tsx @@ -10,7 +10,7 @@ import Row from 'src/components/layout/Row' const RenderInputParams = () => { const { meta: { valid: validABI }, - } = useField('abi', { valid: true } as any) + } = useField('abi', { value: true }) const { input: { value: method }, }: any = useField('selectedMethod', { value: true }) From e3a9945675e6241a4b48a88154d1ba2a77c56f84 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Wed, 3 Jun 2020 11:43:58 -0300 Subject: [PATCH 18/24] add types and fix `useField` usage --- package.json | 3 +- .../sources/ABIService/index.ts | 38 ++++++++++++------- .../sources/ABIService/types.d.ts | 25 ------------ .../ContractInteraction/Buttons/index.tsx | 2 +- .../ContractInteraction/EthValue/index.tsx | 2 +- .../MethodsDropdown/index.tsx | 8 ++-- .../RenderInputParams/index.tsx | 4 +- .../RenderOutputParams/index.tsx | 4 +- .../ContractInteraction/utils/index.ts | 6 ++- yarn.lock | 2 +- 10 files changed, 41 insertions(+), 53 deletions(-) delete mode 100644 src/logic/contractInteraction/sources/ABIService/types.d.ts diff --git a/package.json b/package.json index 0c79d2b9..d10f3bae 100644 --- a/package.json +++ b/package.json @@ -235,6 +235,7 @@ "react-app-rewired": "^2.1.6", "truffle": "5.1.23", "typescript": "~3.7.2", - "wait-on": "5.0.0" + "wait-on": "5.0.0", + "web3-utils": "^1.2.8" } } diff --git a/src/logic/contractInteraction/sources/ABIService/index.ts b/src/logic/contractInteraction/sources/ABIService/index.ts index 8625bdb5..78afd210 100644 --- a/src/logic/contractInteraction/sources/ABIService/index.ts +++ b/src/logic/contractInteraction/sources/ABIService/index.ts @@ -1,38 +1,48 @@ -import { getWeb3 } from 'src/logic/wallets/getWeb3' -import { ABI, ExtendedABI } from './types' +import { AbiItem } from 'web3-utils' -export const getMethodSignature = ({ inputs, name }) => { +import { web3ReadOnly as web3 } from 'src/logic/wallets/getWeb3' + +export interface AbiItemExtended extends AbiItem { + action: string + methodSignature: string + signatureHash: string +} + +export const getMethodSignature = ({ inputs, name }: AbiItem) => { const params = inputs.map((x) => x.type).join(',') return `${name}(${params})` } -export const getSignatureHash = (signature) => { - const web3 = getWeb3() +export const getSignatureHash = (signature: string): string => { return web3.utils.keccak256(signature).toString() } -export const getMethodHash = (method) => { +export const getMethodHash = (method: AbiItem): string => { const signature = getMethodSignature(method) return getSignatureHash(signature) } -export const getMethodSignatureAndSignatureHash = (method) => { +export const getMethodSignatureAndSignatureHash = ( + method: AbiItem, +): { methodSignature: string; signatureHash: string } => { const methodSignature = getMethodSignature(method) const signatureHash = getSignatureHash(methodSignature) return { methodSignature, signatureHash } } -export const extractUsefulMethods = (abi: ABI): ExtendedABI => { +export const extractUsefulMethods = (abi: AbiItem[]): AbiItemExtended[] => { return abi .filter(({ constant, name, type }) => type === 'function' && !!name && typeof constant === 'boolean') - .map((method) => ({ - action: method.constant ? 'read' : 'write', - ...getMethodSignatureAndSignatureHash(method), - ...method, - })) + .map( + (method): AbiItemExtended => ({ + action: method.constant ? 'read' : 'write', + ...getMethodSignatureAndSignatureHash(method), + ...method, + }), + ) .sort(({ name: a }, { name: b }) => (a.toLowerCase() > b.toLowerCase() ? 1 : -1)) } -export const isPayable = (method) => { +export const isPayable = (method: AbiItem | AbiItemExtended): boolean => { return method.payable } diff --git a/src/logic/contractInteraction/sources/ABIService/types.d.ts b/src/logic/contractInteraction/sources/ABIService/types.d.ts deleted file mode 100644 index b5b10065..00000000 --- a/src/logic/contractInteraction/sources/ABIService/types.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -export interface InterfaceParams { - internalType: string - name: string - type: string -} - -export interface MethodInterface { - constant: boolean - inputs: InterfaceParams[] - name: string - outputs: InterfaceParams[] - payable: boolean - stateMutability: string - type: string -} - -export interface ExtendedContractInterface extends MethodInterface { - action: string - methodSignature: string - signatureHash: string -} - -export type ABI = MethodInterface[] - -export type ExtendedABI = ExtendedContractInterface[] diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx index 82db6855..9022b85d 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Buttons/index.tsx @@ -17,7 +17,7 @@ const Buttons = ({ onClose }: ButtonProps) => { const classes = useStyles() const { input: { value: method }, - } = useField('selectedMethod', { value: true }) + } = useField('selectedMethod', { subscription: { value: true } }) const { modifiedSinceLastSubmit, submitError, submitting, valid, validating } = useFormState({ subscription: { modifiedSinceLastSubmit: true, diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx index 0a020269..551dab91 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx @@ -25,7 +25,7 @@ const EthValue = ({ onSetMax }: EthValueProps) => { const { ethBalance } = useSelector(safeSelector) const { input: { value: method }, - } = useField('selectedMethod', { value: true }) + } = useField('selectedMethod', { subscription: { value: true } }) const disabled = !isPayable(method) return disabled ? null : ( diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx index be3a42ad..8d19d0ee 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/MethodsDropdown/index.tsx @@ -8,6 +8,7 @@ import SearchIcon from '@material-ui/icons/Search' import classNames from 'classnames' import React from 'react' import { useField, useFormState } from 'react-final-form' +import { AbiItem } from 'web3-utils' import Col from 'src/components/layout/Col' import Row from 'src/components/layout/Row' @@ -16,12 +17,11 @@ import CheckIcon from 'src/routes/safe/components/CurrencyDropdown/img/check.svg import { useDropdownStyles } from 'src/routes/safe/components/CurrencyDropdown/style' import { DropdownListTheme } from 'src/theme/mui' import { extractUsefulMethods } from 'src/logic/contractInteraction/sources/ABIService' -import { MethodInterface } from 'src/logic/contractInteraction/sources/ABIService/types' const MENU_WIDTH = '452px' interface MethodsDropdownProps { - onChange: (method: MethodInterface) => void + onChange: (method: AbiItem) => void } const MethodsDropdown = ({ onChange }: MethodsDropdownProps) => { @@ -29,7 +29,7 @@ const MethodsDropdown = ({ onChange }: MethodsDropdownProps) => { const { input: { value: abi }, meta: { valid }, - } = useField('abi', { value: true, valid: true } as any) + } = useField('abi', { subscription: { value: true, valid: true } }) const { initialValues: { selectedMethod: selectedMethodByDefault }, } = useFormState({ subscription: { initialValues: true } }) @@ -61,7 +61,7 @@ const MethodsDropdown = ({ onChange }: MethodsDropdownProps) => { setAnchorEl(null) } - const onMethodSelectedChanged = (chosenMethod: MethodInterface) => { + const onMethodSelectedChanged = (chosenMethod: AbiItem) => { setSelectedMethod(chosenMethod) onChange(chosenMethod) handleClose() diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderInputParams/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderInputParams/index.tsx index 1aa97926..f28d4202 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderInputParams/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderInputParams/index.tsx @@ -10,10 +10,10 @@ import Row from 'src/components/layout/Row' const RenderInputParams = () => { const { meta: { valid: validABI }, - } = useField('abi', { value: true }) + } = useField('abi', { subscription: { valid: true, value: true } }) const { input: { value: method }, - }: any = useField('selectedMethod', { value: true }) + }: any = useField('selectedMethod', { subscription: { value: true } }) const renderInputs = validABI && !!method && method.inputs.length return !renderInputs diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderOutputParams/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderOutputParams/index.tsx index 2f728580..1e637d37 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderOutputParams/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderOutputParams/index.tsx @@ -9,10 +9,10 @@ import Row from 'src/components/layout/Row' const RenderOutputParams = () => { const { input: { value: method }, - }: any = useField('selectedMethod', { value: true }) + }: any = useField('selectedMethod', { subscription: { value: true } }) const { input: { value: results }, - }: any = useField('callResults', { value: true }) + }: any = useField('callResults', { subscription: { value: true } }) const multipleResults = !!method && method.outputs.length > 1 return results ? ( diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils/index.ts b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils/index.ts index 2c961591..c63667f7 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils/index.ts +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils/index.ts @@ -1,9 +1,11 @@ import { FORM_ERROR } from 'final-form' import createDecorator from 'final-form-calculate' +import { AbiItem } from 'web3-utils' import { mustBeEthereumAddress, mustBeEthereumContractAddress } from 'src/components/forms/validator' import { getNetwork } from 'src/config' import { getConfiguredSource } from 'src/logic/contractInteraction/sources' +import { AbiItemExtended } from 'src/logic/contractInteraction/sources/ABIService' import { getWeb3 } from 'src/logic/wallets/getWeb3' export const NO_CONTRACT = 'no contract' @@ -60,7 +62,7 @@ export const handleSubmitError = (error, values) => { return { [FORM_ERROR]: error.message } } -export const createTxObject = (method, contractAddress, values) => { +export const createTxObject = (method: AbiItem, contractAddress: string, values) => { const web3 = getWeb3() const contract: any = new web3.eth.Contract([method], contractAddress) const { inputs, name } = method @@ -69,4 +71,4 @@ export const createTxObject = (method, contractAddress, values) => { return contract.methods[name](...args) } -export const isReadMethod = (method: any) => method && method.action === 'read' +export const isReadMethod = (method: AbiItemExtended): boolean => method && method.action === 'read' diff --git a/yarn.lock b/yarn.lock index 26515e8a..22e88e7b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17146,7 +17146,7 @@ web3-utils@1.2.1: underscore "1.9.1" utf8 "3.0.0" -web3-utils@1.2.8, web3-utils@^1.2.7: +web3-utils@1.2.8, web3-utils@^1.2.7, web3-utils@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.2.8.tgz#5321d91715cd4c0869005705a33c4c042a532b18" integrity sha512-9SIVGFLajwlmo5joC4DGxuy2OeDkRCXVWT8JWcDQ+BayNVHyAWGvn0oGkQ0ys14Un0KK6bjjKoD0xYs4k+FaVw== From 0cf786e8d1ba2aa03b40a44b71ab02d66fb5c424 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Wed, 3 Jun 2020 14:04:58 -0300 Subject: [PATCH 19/24] fix `data` string not being updated after modifying a reviewed tx --- .../Balances/SendModal/screens/ContractInteraction/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx index d767fd3a..833d251d 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx @@ -61,7 +61,7 @@ const ContractInteraction = ({ contractAddress, initialValues, onClose, onNext } return } - onNext({ contractAddress, data, selectedMethod, value, ...values }) + onNext({ ...values, contractAddress, data, selectedMethod, value }) } catch (error) { return handleSubmitError(error, values) } From 3ac845106b357a476b89282c59ce42f0ce827a67 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Wed, 3 Jun 2020 16:29:27 -0300 Subject: [PATCH 22/24] move `isValidEnsName` to utils --- src/components/forms/AddressInput/index.tsx | 3 +-- src/logic/wallets/ethAddresses.ts | 2 ++ .../Balances/SendModal/screens/AddressBookInput/index.tsx | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/forms/AddressInput/index.tsx b/src/components/forms/AddressInput/index.tsx index cc54b754..9ffcc47a 100644 --- a/src/components/forms/AddressInput/index.tsx +++ b/src/components/forms/AddressInput/index.tsx @@ -5,8 +5,7 @@ import { OnChange } from 'react-final-form-listeners' import TextField from 'src/components/forms/TextField' import { composeValidators, mustBeEthereumAddress, required } from 'src/components/forms/validator' import { getAddressFromENS } from 'src/logic/wallets/getWeb3' - -const isValidEnsName = (name) => /^([\w-]+\.)+(eth|test|xyz|luxe)$/.test(name) +import { isValidEnsName } from 'src/logic/wallets/ethAddresses' // an idea for second field was taken from here // https://github.com/final-form/react-final-form-listeners/blob/master/src/OnBlur.js diff --git a/src/logic/wallets/ethAddresses.ts b/src/logic/wallets/ethAddresses.ts index e0b0a3cb..b6ad3a75 100644 --- a/src/logic/wallets/ethAddresses.ts +++ b/src/logic/wallets/ethAddresses.ts @@ -43,3 +43,5 @@ export const isUserOwner = (safe, userAccount) => { } export const isUserOwnerOnAnySafe = (safes, userAccount) => safes.some((safe) => isUserOwner(safe, userAccount)) + +export const isValidEnsName = (name) => /^([\w-]+\.)+(eth|test|xyz|luxe)$/.test(name) diff --git a/src/routes/safe/components/Balances/SendModal/screens/AddressBookInput/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/AddressBookInput/index.tsx index 89fcc25e..e3a50f57 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/AddressBookInput/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/AddressBookInput/index.tsx @@ -12,6 +12,7 @@ import Identicon from 'src/components/Identicon' import { mustBeEthereumAddress, mustBeEthereumContractAddress } from 'src/components/forms/validator' import { getAddressBookListSelector } from 'src/logic/addressBook/store/selectors' import { getAddressFromENS } from 'src/logic/wallets/getWeb3' +import { isValidEnsName } from 'src/logic/wallets/ethAddresses' const textFieldLabelStyle = makeStyles(() => ({ root: { @@ -38,8 +39,6 @@ const filterAddressBookWithContractAddresses = async (addressBook) => { return addressBook.filter((adbkEntry, index) => abFlags[index]) } -const isValidEnsName = (name) => /^([\w-]+\.)+(eth|test|xyz|luxe)$/.test(name) - const AddressBookInput = ({ classes, fieldMutator, From 78682a90af86968dff4bd44f140cc54b98ce8eae Mon Sep 17 00:00:00 2001 From: fernandomg Date: Wed, 3 Jun 2020 16:30:09 -0300 Subject: [PATCH 23/24] add decorator to extract eth address from ENS for contract address --- .../screens/ContractInteraction/index.tsx | 4 ++-- .../ContractInteraction/utils/index.ts | 23 ++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx index 833d251d..08fc5267 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx @@ -18,7 +18,7 @@ import Header from './Header' import MethodsDropdown from './MethodsDropdown' import RenderInputParams from './RenderInputParams' import RenderOutputParams from './RenderOutputParams' -import { abiExtractor, createTxObject, formMutators, handleSubmitError, isReadMethod } from './utils' +import { abiExtractor, createTxObject, formMutators, handleSubmitError, isReadMethod, ensResolver } from './utils' const useStyles = makeStyles(styles) @@ -73,7 +73,7 @@ const ContractInteraction = ({ contractAddress, initialValues, onClose, onNext }
{ + try { + const resolvedAddress = isValidEnsName(contractAddress) && (await getAddressFromENS(contractAddress)) + + if (resolvedAddress) { + return resolvedAddress + } + } catch (e) { + console.error(e.message) + return contractAddress + } + + return contractAddress + }, + }, +}) + export const formMutators = { setMax: (args, state, utils) => { utils.changeValue(state, 'value', () => args[0]) From d4884360544880593084dd3ba0cecdca1c76bbbb Mon Sep 17 00:00:00 2001 From: fernandomg Date: Wed, 3 Jun 2020 16:30:51 -0300 Subject: [PATCH 24/24] avoid populating abi field with invalid data if it's not required --- .../SendModal/screens/ContractInteraction/utils/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils/index.ts b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils/index.ts index 19397012..64f67457 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils/index.ts +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils/index.ts @@ -21,7 +21,7 @@ export const abiExtractor = createDecorator({ mustBeEthereumAddress(contractAddress) || (await mustBeEthereumContractAddress(contractAddress)) ) { - return NO_CONTRACT + return } const network = getNetwork() const source = getConfiguredSource()