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

View File

@ -1,5 +1,5 @@
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 Field from 'src/components/forms/Field'
@ -15,7 +15,7 @@ type Props = {
placeholder: string
}
const InputComponent = ({ type, keyValue, placeholder }: Props): React.ReactElement | null => {
export const InputComponent = ({ type, keyValue, placeholder }: Props): ReactElement | null => {
if (!type) {
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 Row from 'src/components/layout/Row'
import InputComponent from './InputComponent'
import { InputComponent } from './InputComponent'
import { generateFormFieldKey } from '../utils'
import { AbiItemExtended } from 'src/logic/contractInteraction/sources/ABIService'
const RenderInputParams = (): React.ReactElement | null => {
export const RenderInputParams = (): ReactElement | null => {
const {
meta: { valid: validABI },
} = 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 { makeStyles } from '@material-ui/core/styles'
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 {
input: { value: method },
@ -27,7 +27,11 @@ const RenderOutputParams = () => {
}: any = useField('callResults', { subscription: { value: true } })
const multipleResults = !!method && method.outputs.length > 1
return results ? (
if (!results) {
return null
}
return (
<>
<Row align="left" margin="xs">
<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 { getEthAsToken } from 'src/logic/tokens/utils/tokenHelpers'
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 createTransaction from 'src/logic/safe/store/actions/createTransaction'
import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'

View File

@ -14,10 +14,10 @@ import ContractABI from './ContractABI'
import { EthAddressInput } from './EthAddressInput'
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 { Header } from './Header'
import { MethodsDropdown } from './MethodsDropdown'
import { RenderInputParams } from './RenderInputParams'
import { RenderOutputParams } from './RenderOutputParams'
import { createTxObject, formMutators, handleSubmitError, isReadMethod, ensResolver } from './utils'
import { TransactionReviewType } from './Review'
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 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> => {
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 key = generateFormFieldKey(type, signatureHash, index)
if (isArrayParameter(type)) {
return JSON.parse(values[key])
}
return values[key]
return getParsedJSONOrArrayFromString(values[key]) || values[key]
}
export const createTxObject = (