(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:
parent
e3adb24825
commit
3d43db039b
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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 = (
|
||||
|
|
Loading…
Reference in New Issue