(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 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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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 = (
|
||||||
|
|
Loading…
Reference in New Issue