(Fix) Contract interaction tuples (#1787)

* Adds isTupleParameter validation to the extractMethodArgs

* Improves tuples parsing

* Show examples for txs inputs

* Remove MethodsInputExamples

Co-authored-by: Mati Dastugue <matias.dastugue@altoros.com>
This commit is contained in:
Agustin Pane 2021-01-27 19:44:02 -03:00 committed by GitHub
parent e3adb24825
commit 3d43db039b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 43 additions and 41 deletions

View File

@ -1,7 +1,7 @@
import IconButton from '@material-ui/core/IconButton' import IconButton from '@material-ui/core/IconButton'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close' import Close from '@material-ui/icons/Close'
import React from 'react' import React, { ReactElement } from 'react'
import Paragraph from 'src/components/layout/Paragraph' import Paragraph from 'src/components/layout/Paragraph'
import Row from 'src/components/layout/Row' import Row from 'src/components/layout/Row'
@ -15,7 +15,7 @@ interface HeaderProps {
title: string title: string
} }
const Header = ({ onClose, subTitle, title }: HeaderProps) => { export const Header = ({ onClose, subTitle, title }: HeaderProps): ReactElement => {
const classes = useStyles() const classes = useStyles()
return ( return (
@ -30,5 +30,3 @@ const Header = ({ onClose, subTitle, title }: HeaderProps) => {
</Row> </Row>
) )
} }
export default Header

View File

@ -6,7 +6,7 @@ import MenuItem from '@material-ui/core/MenuItem'
import { MuiThemeProvider } from '@material-ui/core/styles' import { MuiThemeProvider } from '@material-ui/core/styles'
import SearchIcon from '@material-ui/icons/Search' import SearchIcon from '@material-ui/icons/Search'
import classNames from 'classnames' import classNames from 'classnames'
import React from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import { useField, useFormState } from 'react-final-form' import { useField, useFormState } from 'react-final-form'
import { AbiItem } from 'web3-utils' import { AbiItem } from 'web3-utils'
@ -24,7 +24,7 @@ interface MethodsDropdownProps {
onChange: (method: AbiItem) => void onChange: (method: AbiItem) => void
} }
const MethodsDropdown = ({ onChange }: MethodsDropdownProps): React.ReactElement | null => { export const MethodsDropdown = ({ onChange }: MethodsDropdownProps): ReactElement | null => {
const classes = useDropdownStyles({ buttonWidth: MENU_WIDTH }) const classes = useDropdownStyles({ buttonWidth: MENU_WIDTH })
const { const {
input: { value: abi }, input: { value: abi },
@ -33,13 +33,14 @@ const MethodsDropdown = ({ onChange }: MethodsDropdownProps): React.ReactElement
const { const {
initialValues: { selectedMethod: selectedMethodByDefault }, initialValues: { selectedMethod: selectedMethodByDefault },
} = useFormState({ subscription: { initialValues: true } }) } = useFormState({ subscription: { initialValues: true } })
const [selectedMethod, setSelectedMethod] = React.useState(selectedMethodByDefault ? selectedMethodByDefault : {}) const [selectedMethod, setSelectedMethod] = useState(selectedMethodByDefault ? selectedMethodByDefault : {})
const [methodsList, setMethodsList] = React.useState<AbiItemExtended[]>([]) const [methodsList, setMethodsList] = useState<AbiItemExtended[]>([])
const [methodsListFiltered, setMethodsListFiltered] = React.useState<AbiItemExtended[]>([]) const [methodsListFiltered, setMethodsListFiltered] = useState<AbiItemExtended[]>([])
const [anchorEl, setAnchorEl] = React.useState(null)
const [searchParams, setSearchParams] = React.useState('')
React.useEffect(() => { const [anchorEl, setAnchorEl] = useState(null)
const [searchParams, setSearchParams] = useState('')
useEffect(() => {
if (abi) { if (abi) {
try { try {
setMethodsList(extractUsefulMethods(JSON.parse(abi))) setMethodsList(extractUsefulMethods(JSON.parse(abi)))
@ -49,7 +50,7 @@ const MethodsDropdown = ({ onChange }: MethodsDropdownProps): React.ReactElement
} }
}, [abi]) }, [abi])
React.useEffect(() => { useEffect(() => {
setMethodsListFiltered(methodsList.filter(({ name }) => name?.toLowerCase().includes(searchParams.toLowerCase()))) setMethodsListFiltered(methodsList.filter(({ name }) => name?.toLowerCase().includes(searchParams.toLowerCase())))
}, [methodsList, searchParams]) }, [methodsList, searchParams])
@ -67,7 +68,11 @@ const MethodsDropdown = ({ onChange }: MethodsDropdownProps): React.ReactElement
handleClose() handleClose()
} }
return !valid || !abi || abi === NO_CONTRACT ? null : ( if (!valid || !abi || abi === NO_CONTRACT) {
return null
}
return (
<Row margin="sm"> <Row margin="sm">
<Col> <Col>
<MuiThemeProvider theme={DropdownListTheme}> <MuiThemeProvider theme={DropdownListTheme}>
@ -145,5 +150,3 @@ const MethodsDropdown = ({ onChange }: MethodsDropdownProps): React.ReactElement
</Row> </Row>
) )
} }
export default MethodsDropdown

View File

@ -1,5 +1,5 @@
import { Checkbox } from '@gnosis.pm/safe-react-components' import { Checkbox } from '@gnosis.pm/safe-react-components'
import React from 'react' import React, { ReactElement } from 'react'
import Col from 'src/components/layout/Col' import Col from 'src/components/layout/Col'
import Field from 'src/components/forms/Field' import Field from 'src/components/forms/Field'
@ -15,7 +15,7 @@ type Props = {
placeholder: string placeholder: string
} }
const InputComponent = ({ type, keyValue, placeholder }: Props): React.ReactElement | null => { export const InputComponent = ({ type, keyValue, placeholder }: Props): ReactElement | null => {
if (!type) { if (!type) {
return null return null
} }
@ -67,5 +67,3 @@ const InputComponent = ({ type, keyValue, placeholder }: Props): React.ReactElem
} }
} }
} }
export default InputComponent

View File

@ -1,13 +1,13 @@
import React from 'react' import React, { ReactElement } from 'react'
import { useField } from 'react-final-form' import { useField } from 'react-final-form'
import Row from 'src/components/layout/Row' import Row from 'src/components/layout/Row'
import InputComponent from './InputComponent' import { InputComponent } from './InputComponent'
import { generateFormFieldKey } from '../utils' import { generateFormFieldKey } from '../utils'
import { AbiItemExtended } from 'src/logic/contractInteraction/sources/ABIService' import { AbiItemExtended } from 'src/logic/contractInteraction/sources/ABIService'
const RenderInputParams = (): React.ReactElement | null => { export const RenderInputParams = (): ReactElement | null => {
const { const {
meta: { valid: validABI }, meta: { valid: validABI },
} = useField('abi', { subscription: { valid: true, value: true } }) } = useField('abi', { subscription: { valid: true, value: true } })
@ -31,5 +31,3 @@ const RenderInputParams = (): React.ReactElement | null => {
</> </>
) )
} }
export default RenderInputParams

View File

@ -1,4 +1,4 @@
import React from 'react' import React, { ReactElement } from 'react'
import { useField } from 'react-final-form' import { useField } from 'react-final-form'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import TextField from 'src/components/forms/TextField' import TextField from 'src/components/forms/TextField'
@ -17,7 +17,7 @@ const useStyles = makeStyles({
}, },
}) })
const RenderOutputParams = () => { export const RenderOutputParams = (): ReactElement | null => {
const classes = useStyles() const classes = useStyles()
const { const {
input: { value: method }, input: { value: method },
@ -27,7 +27,11 @@ const RenderOutputParams = () => {
}: any = useField('callResults', { subscription: { value: true } }) }: any = useField('callResults', { subscription: { value: true } })
const multipleResults = !!method && method.outputs.length > 1 const multipleResults = !!method && method.outputs.length > 1
return results ? ( if (!results) {
return null
}
return (
<> <>
<Row align="left" margin="xs"> <Row align="left" margin="xs">
<Paragraph color="primary" size="lg" style={{ letterSpacing: '-0.5px' }}> <Paragraph color="primary" size="lg" style={{ letterSpacing: '-0.5px' }}>
@ -57,7 +61,5 @@ const RenderOutputParams = () => {
) )
})} })}
</> </>
) : null )
} }
export default RenderOutputParams

View File

@ -16,7 +16,7 @@ import { AbiItemExtended } from 'src/logic/contractInteraction/sources/ABIServic
import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions' import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions'
import { getEthAsToken } from 'src/logic/tokens/utils/tokenHelpers' import { getEthAsToken } from 'src/logic/tokens/utils/tokenHelpers'
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 Header from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Header' import { Header } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Header'
import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils' import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils'
import createTransaction from 'src/logic/safe/store/actions/createTransaction' import createTransaction from 'src/logic/safe/store/actions/createTransaction'
import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'

View File

@ -14,10 +14,10 @@ import ContractABI from './ContractABI'
import { EthAddressInput } from './EthAddressInput' import { EthAddressInput } from './EthAddressInput'
import FormDivisor from './FormDivisor' import FormDivisor from './FormDivisor'
import FormErrorMessage from './FormErrorMessage' import FormErrorMessage from './FormErrorMessage'
import Header from './Header' 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 { createTxObject, formMutators, handleSubmitError, isReadMethod, ensResolver } from './utils' import { createTxObject, formMutators, handleSubmitError, isReadMethod, ensResolver } from './utils'
import { TransactionReviewType } from './Review' import { TransactionReviewType } from './Review'
import { NativeCoinValue } from './NativeCoinValue' import { NativeCoinValue } from './NativeCoinValue'

View File

@ -63,6 +63,13 @@ export const isInt = (type: string): boolean => type.indexOf('int') === 0
export const isByte = (type: string): boolean => type.indexOf('byte') === 0 export const isByte = (type: string): boolean => type.indexOf('byte') === 0
export const isArrayParameter = (parameter: string): boolean => /(\[\d*])+$/.test(parameter) export const isArrayParameter = (parameter: string): boolean => /(\[\d*])+$/.test(parameter)
export const getParsedJSONOrArrayFromString = (parameter: string): (string | number)[] | null => {
try {
return JSON.parse(parameter)
} catch (err) {
return null
}
}
export const handleSubmitError = (error: SubmissionErrors, values: Record<string, string>): Record<string, string> => { export const handleSubmitError = (error: SubmissionErrors, values: Record<string, string>): Record<string, string> => {
for (const key in values) { for (const key in values) {
@ -83,11 +90,7 @@ export const generateFormFieldKey = (type: string, signatureHash: string, index:
const extractMethodArgs = (signatureHash: string, values: Record<string, string>) => ({ type }, index) => { const extractMethodArgs = (signatureHash: string, values: Record<string, string>) => ({ type }, index) => {
const key = generateFormFieldKey(type, signatureHash, index) const key = generateFormFieldKey(type, signatureHash, index)
if (isArrayParameter(type)) { return getParsedJSONOrArrayFromString(values[key]) || values[key]
return JSON.parse(values[key])
}
return values[key]
} }
export const createTxObject = ( export const createTxObject = (