simplify call/review buttons

- unify handleSubmit
- use `.call` to identify a failing tx
This commit is contained in:
fernandomg 2020-06-01 23:38:27 -03:00
parent a28c598af1
commit ed995df5b7
3 changed files with 46 additions and 55 deletions

View File

@ -5,70 +5,41 @@ import { useField, useFormState } from 'react-final-form'
import Button from 'src/components/layout/Button' import Button from 'src/components/layout/Button'
import Row from 'src/components/layout/Row' import Row from 'src/components/layout/Row'
import { styles } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style' 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 useStyles = makeStyles(styles as any)
const Buttons = ({ onCallSubmit, onClose }) => { const Buttons = ({ onClose }) => {
const classes = useStyles() const classes = useStyles()
const { const {
input: { value: method }, input: { value: method },
} = useField('selectedMethod', { value: true }) } = useField('selectedMethod', { value: true })
const { const { modifiedSinceLastSubmit, submitError, submitting, valid, validating } = useFormState({
input: { value: contractAddress },
} = useField('contractAddress', { valid: true } as any)
const { modifiedSinceLastSubmit, submitError, submitting, valid, validating, values } = useFormState({
subscription: { subscription: {
modifiedSinceLastSubmit: true, modifiedSinceLastSubmit: true,
submitError: true, submitError: true,
submitting: true, submitting: true,
valid: true, valid: true,
values: true,
validating: true, validating: true,
}, },
}) })
const handleCallSubmit = async () => {
const results = await createTxObject(method, contractAddress, values).call()
onCallSubmit(results)
}
return ( return (
<Row align="center" className={classes.buttonRow}> <Row align="center" className={classes.buttonRow}>
<Button minWidth={140} onClick={onClose}> <Button minWidth={140} onClick={onClose}>
Cancel Cancel
</Button> </Button>
{method && (method as any).action === 'read' ? ( <Button
<Button className={classes.submitButton}
className={classes.submitButton} color="primary"
color="primary" data-testid={`${isReadMethod(method) ? 'call' : 'review'}-tx-btn`}
data-testid="review-tx-btn" disabled={submitting || validating || ((!valid || !!submitError) && !modifiedSinceLastSubmit) || !method}
disabled={validating || !valid} minWidth={140}
minWidth={140} type="submit"
onClick={handleCallSubmit} variant="contained"
variant="contained" >
> {isReadMethod(method) ? 'Call' : 'Review'}
Call </Button>
</Button>
) : (
<Button
className={classes.submitButton}
color="primary"
data-testid="review-tx-btn"
disabled={
submitting ||
validating ||
((!valid || !!submitError) && !modifiedSinceLastSubmit) ||
!method ||
(method as any).action === 'read'
}
minWidth={140}
type="submit"
variant="contained"
>
Review
</Button>
)}
</Row> </Row>
) )
} }

View File

@ -1,12 +1,13 @@
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import { FORM_ERROR } from 'final-form'
import React from 'react' import React from 'react'
import { useSelector } from 'react-redux'
import { styles } from './style' import { styles } from './style'
import GnoForm from 'src/components/forms/GnoForm' import GnoForm from 'src/components/forms/GnoForm'
import Block from 'src/components/layout/Block' import Block from 'src/components/layout/Block'
import Hairline from 'src/components/layout/Hairline' import Hairline from 'src/components/layout/Hairline'
import SafeInfo from 'src/routes/safe/components/Balances/SendModal/SafeInfo' import SafeInfo from 'src/routes/safe/components/Balances/SendModal/SafeInfo'
import { safeSelector } from 'src/routes/safe/store/selectors'
import Buttons from './Buttons' import Buttons from './Buttons'
import ContractABI from './ContractABI' import ContractABI from './ContractABI'
import EthAddressInput from './EthAddressInput' import EthAddressInput from './EthAddressInput'
@ -17,12 +18,14 @@ import Header from './Header'
import MethodsDropdown from './MethodsDropdown' import MethodsDropdown from './MethodsDropdown'
import RenderInputParams from './RenderInputParams' import RenderInputParams from './RenderInputParams'
import RenderOutputParams from './RenderOutputParams' 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 useStyles = makeStyles(styles as any)
const ContractInteraction = ({ contractAddress, initialValues, onClose, onNext }) => { const ContractInteraction = ({ contractAddress, initialValues, onClose, onNext }) => {
const classes = useStyles() const classes = useStyles()
const { address: safeAddress = '' } = useSelector(safeSelector)
let setCallResults
React.useMemo(() => { React.useMemo(() => {
if (contractAddress) { if (contractAddress) {
@ -35,17 +38,18 @@ const ContractInteraction = ({ contractAddress, initialValues, onClose, onNext }
try { try {
const txObject = createTxObject(selectedMethod, contractAddress, values) const txObject = createTxObject(selectedMethod, contractAddress, values)
const data = txObject.encodeABI() const data = txObject.encodeABI()
await txObject.estimateGas() const result = await txObject.call({ from: safeAddress })
onNext({ contractAddress, data, selectedMethod, value, ...values })
} catch (e) { if (isReadMethod(selectedMethod)) {
for (const key in values) { setCallResults(result)
if (values.hasOwnProperty(key) && values[key] === e.value) {
return { [key]: e.reason } // this was a read method, so we won't go to the 'review' screen
} return
} }
// .estimateGas() failed onNext({ contractAddress, data, selectedMethod, value, ...values })
return { [FORM_ERROR]: e.message } } catch (error) {
return handleSubmitError(error, values)
} }
} }
} }
@ -62,6 +66,8 @@ const ContractInteraction = ({ contractAddress, initialValues, onClose, onNext }
subscription={{ submitting: true, pristine: true }} subscription={{ submitting: true, pristine: true }}
> >
{(submitting, validating, rest, mutators) => { {(submitting, validating, rest, mutators) => {
setCallResults = mutators.setCallResults
return ( return (
<> <>
<Block className={classes.formContainer}> <Block className={classes.formContainer}>
@ -80,7 +86,7 @@ const ContractInteraction = ({ contractAddress, initialValues, onClose, onNext }
<FormErrorMessage /> <FormErrorMessage />
</Block> </Block>
<Hairline /> <Hairline />
<Buttons onCallSubmit={mutators.setCallResults} onClose={onClose} /> <Buttons onClose={onClose} />
</> </>
) )
}} }}

View File

@ -1,3 +1,4 @@
import { FORM_ERROR } from 'final-form'
import createDecorator from 'final-form-calculate' import createDecorator from 'final-form-calculate'
import { mustBeEthereumAddress, mustBeEthereumContractAddress } from 'src/components/forms/validator' 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) => { export const createTxObject = (method, contractAddress, values) => {
const web3 = getWeb3() const web3 = getWeb3()
const contract: any = new web3.eth.Contract([method], contractAddress) const contract: any = new web3.eth.Contract([method], contractAddress)
@ -56,3 +68,5 @@ export const createTxObject = (method, contractAddress, values) => {
return contract.methods[name](...args) return contract.methods[name](...args)
} }
export const isReadMethod = (method: any) => method && method.action === 'read'