Merge branch 'development' into fix/#950-parameter-validation-contractInteraction

# Conflicts:
#	package.json
#	src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/RenderInputParams/index.tsx
#	src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/index.tsx
#	src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/style.ts
#	src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx
#	src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils/index.ts
#	yarn.lock
This commit is contained in:
fernandomg 2020-06-04 11:06:45 -03:00
commit 4d41e813ed
16 changed files with 1499 additions and 756 deletions

View File

@ -21,6 +21,8 @@ matrix:
- env: - env:
- REACT_APP_NETWORK='rinkeby' - REACT_APP_NETWORK='rinkeby'
- REACT_APP_GNOSIS_APPS_URL=${REACT_APP_GNOSIS_APPS_URL_STAGING} - REACT_APP_GNOSIS_APPS_URL=${REACT_APP_GNOSIS_APPS_URL_STAGING}
cache:
yarn: true
before_install: before_install:
# Needed to deploy pull request and releases # Needed to deploy pull request and releases
- sudo apt-get update - sudo apt-get update

View File

@ -151,22 +151,22 @@
"@gnosis.pm/safe-contracts": "1.1.1-dev.2", "@gnosis.pm/safe-contracts": "1.1.1-dev.2",
"@gnosis.pm/safe-react-components": "^0.1.2", "@gnosis.pm/safe-react-components": "^0.1.2",
"@gnosis.pm/util-contracts": "2.0.6", "@gnosis.pm/util-contracts": "2.0.6",
"@ledgerhq/hw-transport-node-hid": "5.15.0", "@ledgerhq/hw-transport-node-hid": "5.16.0",
"@material-ui/core": "4.9.14", "@material-ui/core": "4.10.1",
"@material-ui/icons": "4.9.1", "@material-ui/icons": "4.9.1",
"@material-ui/lab": "4.0.0-alpha.39", "@material-ui/lab": "4.0.0-alpha.39",
"@openzeppelin/contracts": "3.0.1", "@openzeppelin/contracts": "3.0.1",
"async-sema": "^3.1.0", "async-sema": "^3.1.0",
"axios": "0.19.2", "axios": "0.19.2",
"bignumber.js": "9.0.0", "bignumber.js": "9.0.0",
"bnc-onboard": "1.9.1", "bnc-onboard": "1.9.4",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"concurrently": "^5.2.0", "concurrently": "^5.2.0",
"connected-react-router": "6.8.0", "connected-react-router": "6.8.0",
"currency-flags": "2.1.2", "currency-flags": "2.1.2",
"date-fns": "2.13.0", "date-fns": "2.14.0",
"electron-is-dev": "^1.1.0", "electron-is-dev": "^1.1.0",
"electron-log": "4.1.2", "electron-log": "4.2.1",
"electron-updater": "4.3.1", "electron-updater": "4.3.1",
"express": "^4.17.1", "express": "^4.17.1",
"final-form": "^4.20.0", "final-form": "^4.20.0",
@ -179,7 +179,7 @@
"material-ui-search-bar": "^1.0.0-beta.13", "material-ui-search-bar": "^1.0.0-beta.13",
"notistack": "https://github.com/gnosis/notistack.git#v0.9.4", "notistack": "https://github.com/gnosis/notistack.git#v0.9.4",
"open": "^7.0.3", "open": "^7.0.3",
"polished": "3.6.3", "polished": "3.6.4",
"qrcode.react": "1.0.0", "qrcode.react": "1.0.0",
"query-string": "6.12.1", "query-string": "6.12.1",
"react": "16.13.1", "react": "16.13.1",
@ -208,19 +208,19 @@
"@testing-library/react": "^9.3.2", "@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2", "@testing-library/user-event": "^7.1.2",
"@types/jest": "^25.2.1", "@types/jest": "^25.2.1",
"@types/node": "^13.11.0", "@types/node": "14.0.10",
"@types/react": "^16.9.32", "@types/react": "^16.9.32",
"@types/react-dom": "^16.9.6", "@types/react-dom": "^16.9.6",
"@typescript-eslint/eslint-plugin": "^2.34.0", "@typescript-eslint/eslint-plugin": "3.1.0",
"@typescript-eslint/parser": "^2.34.0", "@typescript-eslint/parser": "3.1.0",
"autoprefixer": "9.7.6", "autoprefixer": "9.8.0",
"cross-env": "^7.0.2", "cross-env": "^7.0.2",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"dotenv-expand": "^5.1.0", "dotenv-expand": "^5.1.0",
"electron": "7.1.8", "electron": "7.1.8",
"electron-builder": "22.2.0", "electron-builder": "22.7.0",
"electron-notarize": "^0.2.1", "electron-notarize": "^0.2.1",
"eslint": "^6.8.0", "eslint": "6.8.0",
"eslint-config-prettier": "6.11.0", "eslint-config-prettier": "6.11.0",
"eslint-plugin-import": "2.20.2", "eslint-plugin-import": "2.20.2",
"eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-jsx-a11y": "^6.2.3",
@ -229,7 +229,7 @@
"eslint-plugin-sort-destructure-keys": "1.3.4", "eslint-plugin-sort-destructure-keys": "1.3.4",
"ethereumjs-abi": "0.6.8", "ethereumjs-abi": "0.6.8",
"husky": "^4.2.2", "husky": "^4.2.2",
"lint-staged": "10.2.2", "lint-staged": "10.2.8",
"node-sass": "^4.14.1", "node-sass": "^4.14.1",
"prettier": "2.0.5", "prettier": "2.0.5",
"react-app-rewired": "^2.1.6", "react-app-rewired": "^2.1.6",

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -1,15 +0,0 @@
import Checkbox from '@material-ui/core/Checkbox'
import React from 'react'
class GnoCheckbox extends React.PureComponent<any> {
render() {
const {
input: { checked, name, onChange, ...restInput },
...rest
} = this.props
return <Checkbox {...rest} checked={!!checked} inputProps={restInput} name={name} onChange={onChange} />
}
}
export default GnoCheckbox

View File

@ -81,9 +81,9 @@ export const createSafe = (values, userAccount) => {
const Open = ({ addSafe, network, provider, userAccount }) => { const Open = ({ addSafe, network, provider, userAccount }) => {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [showProgress, setShowProgress] = useState() const [showProgress, setShowProgress] = useState(false)
const [creationTxPromise, setCreationTxPromise] = useState() const [creationTxPromise, setCreationTxPromise] = useState()
const [safeCreationPendingInfo, setSafeCreationPendingInfo] = useState() const [safeCreationPendingInfo, setSafeCreationPendingInfo] = useState<any>()
const [safePropsFromUrl, setSafePropsFromUrl] = useState() const [safePropsFromUrl, setSafePropsFromUrl] = useState()
useEffect(() => { useEffect(() => {
@ -182,7 +182,7 @@ const Open = ({ addSafe, network, provider, userAccount }) => {
<Page> <Page>
{showProgress ? ( {showProgress ? (
<Opening <Opening
creationTxHash={safeCreationPendingInfo ? safeCreationPendingInfo.txHash : undefined} creationTxHash={safeCreationPendingInfo?.txHash}
onCancel={onCancel} onCancel={onCancel}
onRetry={onRetry} onRetry={onRetry}
onSuccess={onSafeCreated as any} onSuccess={onSafeCreated as any}

View File

@ -103,7 +103,7 @@ const BackButton = styled(Button)`
const SafeDeployment = ({ creationTxHash, onCancel, onRetry, onSuccess, provider, submittedPromise }: any) => { const SafeDeployment = ({ creationTxHash, onCancel, onRetry, onSuccess, provider, submittedPromise }: any) => {
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [stepIndex, setStepIndex] = useState() const [stepIndex, setStepIndex] = useState(0)
const [safeCreationTxHash, setSafeCreationTxHash] = useState() const [safeCreationTxHash, setSafeCreationTxHash] = useState()
const [createdSafeAddress, setCreatedSafeAddress] = useState() const [createdSafeAddress, setCreatedSafeAddress] = useState()

View File

@ -74,7 +74,7 @@ const MethodsDropdown = ({ onChange }: MethodsDropdownProps) => {
<> <>
<button className={classes.button} onClick={handleClick} type="button"> <button className={classes.button} onClick={handleClick} type="button">
<span className={classNames(classes.buttonInner, anchorEl && classes.openMenuButton)}> <span className={classNames(classes.buttonInner, anchorEl && classes.openMenuButton)}>
{selectedMethod.name} {(selectedMethod as Record<string, string>).name}
</span> </span>
</button> </button>
<Menu <Menu
@ -125,7 +125,7 @@ const MethodsDropdown = ({ onChange }: MethodsDropdownProps) => {
> >
<ListItemText primary={name} /> <ListItemText primary={name} />
<ListItemIcon className={classes.iconRight}> <ListItemIcon className={classes.iconRight}>
{signatureHash === selectedMethod.signatureHash ? ( {signatureHash === (selectedMethod as Record<string, string>).signatureHash ? (
<img alt="checked" src={CheckIcon} /> <img alt="checked" src={CheckIcon} />
) : ( ) : (
<span /> <span />

View File

@ -0,0 +1,64 @@
import React from 'react'
import Col from 'src/components/layout/Col'
import Field from 'src/components/forms/Field'
import TextField from 'src/components/forms/TextField'
import { composeValidators, mustBeEthereumAddress, required } from 'src/components/forms/validator'
import { Checkbox } from '@gnosis.pm/safe-react-components'
type Props = {
type: string
keyValue: string
placeholder: string
}
const InputComponent = ({ type, keyValue, placeholder }: Props) => {
if (!type) {
return null
}
switch (type) {
case 'bool': {
const inputProps = {
'data-testid': keyValue,
}
return (
<Col>
<Field component={Checkbox} name={keyValue} label={placeholder} type="checkbox" inputProps={inputProps} />
</Col>
)
}
case 'address': {
return (
<Col>
<Field
component={TextField}
name={keyValue}
placeholder={placeholder}
testId={keyValue}
text={placeholder}
type="text"
validate={composeValidators(required, mustBeEthereumAddress)}
/>
</Col>
)
}
default: {
return (
<Col>
<Field
component={TextField}
name={keyValue}
placeholder={placeholder}
testId={keyValue}
text={placeholder}
type="text"
validate={required}
/>
</Col>
)
}
}
}
export default InputComponent

View File

@ -1,12 +1,10 @@
import React from 'react' import React from 'react'
import { useField } from 'react-final-form' import { useField } from 'react-final-form'
import Field from 'src/components/forms/Field'
import TextField from 'src/components/forms/TextField'
import { composeValidators, mustBeEthereumAddress, required } from 'src/components/forms/validator'
import Col from 'src/components/layout/Col'
import Row from 'src/components/layout/Row' import Row from 'src/components/layout/Row'
import InputComponent from './InputComponent'
const RenderInputParams = () => { const RenderInputParams = () => {
const { const {
meta: { valid: validABI }, meta: { valid: validABI },
@ -21,21 +19,10 @@ const RenderInputParams = () => {
: method.inputs.map(({ name, type }, index) => { : method.inputs.map(({ name, type }, index) => {
const placeholder = name ? `${name} (${type})` : type const placeholder = name ? `${name} (${type})` : type
const key = `methodInput-${method.name}_${index}_${type}` const key = `methodInput-${method.name}_${index}_${type}`
const validate = type === 'address' ? composeValidators(required, mustBeEthereumAddress) : required
return ( return (
<Row key={key} margin="sm"> <Row key={key} margin="sm">
<Col> <InputComponent type={type} keyValue={key} placeholder={placeholder} />
<Field
component={TextField}
name={key}
placeholder={placeholder}
testId={key}
text={placeholder}
type="text"
validate={validate}
/>
</Col>
</Row> </Row>
) )
}) })

View File

@ -1,5 +1,5 @@
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import { withSnackbar } from 'notistack' import { useSnackbar } from 'notistack'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
@ -11,6 +11,7 @@ import Hairline from 'src/components/layout/Hairline'
import Img from 'src/components/layout/Img' import Img from 'src/components/layout/Img'
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'
import { AbiItemExtended } from 'src/logic/contractInteraction/sources/ABIService'
import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions' import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions'
import { estimateTxGasCosts } from 'src/logic/safe/transactions/gasNew' import { estimateTxGasCosts } from 'src/logic/safe/transactions/gasNew'
import { formatAmount } from 'src/logic/tokens/utils/formatAmount' import { formatAmount } from 'src/logic/tokens/utils/formatAmount'
@ -21,10 +22,26 @@ import Header from 'src/routes/safe/components/Balances/SendModal/screens/Contra
import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils' import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils'
import createTransaction from 'src/routes/safe/store/actions/createTransaction' import createTransaction from 'src/routes/safe/store/actions/createTransaction'
import { safeSelector } from 'src/routes/safe/store/selectors' import { safeSelector } from 'src/routes/safe/store/selectors'
import { getValueFromTxInputs } from '../utils'
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const ContractInteractionReview = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }: any) => { export type TransactionReviewType = {
abi?: string
contractAddress?: string
data?: string
value?: string
selectedMethod?: AbiItemExtended
}
type Props = {
onClose: () => void
onPrev: () => void
tx: TransactionReviewType
}
const ContractInteractionReview = ({ onClose, onPrev, tx }: Props) => {
const { enqueueSnackbar, closeSnackbar } = useSnackbar()
const classes = useStyles() const classes = useStyles()
const dispatch = useDispatch() const dispatch = useDispatch()
const { address: safeAddress } = useSelector(safeSelector) const { address: safeAddress } = useSelector(safeSelector)
@ -117,6 +134,7 @@ const ContractInteractionReview = ({ closeSnackbar, enqueueSnackbar, onClose, on
</Row> </Row>
{tx.selectedMethod.inputs.map(({ name, type }, index) => { {tx.selectedMethod.inputs.map(({ name, type }, index) => {
const key = `methodInput-${tx.selectedMethod.name}_${index}_${type}` const key = `methodInput-${tx.selectedMethod.name}_${index}_${type}`
const value: string = getValueFromTxInputs(key, type, tx)
return ( return (
<React.Fragment key={key}> <React.Fragment key={key}>
@ -127,7 +145,7 @@ const ContractInteractionReview = ({ closeSnackbar, enqueueSnackbar, onClose, on
</Row> </Row>
<Row align="center" margin="md"> <Row align="center" margin="md">
<Paragraph className={classes.value} noMargin size="md" style={{ margin: 0 }}> <Paragraph className={classes.value} noMargin size="md" style={{ margin: 0 }}>
{tx[key]} {value}
</Paragraph> </Paragraph>
</Row> </Row>
</React.Fragment> </React.Fragment>
@ -172,4 +190,4 @@ const ContractInteractionReview = ({ closeSnackbar, enqueueSnackbar, onClose, on
) )
} }
export default withSnackbar(ContractInteractionReview as any) export default ContractInteractionReview

View File

@ -7,6 +7,7 @@ import { getNetwork } from 'src/config'
import { getConfiguredSource } from 'src/logic/contractInteraction/sources' import { getConfiguredSource } from 'src/logic/contractInteraction/sources'
import { AbiItemExtended } from 'src/logic/contractInteraction/sources/ABIService' import { AbiItemExtended } from 'src/logic/contractInteraction/sources/ABIService'
import { getWeb3 } from 'src/logic/wallets/getWeb3' import { getWeb3 } from 'src/logic/wallets/getWeb3'
import { TransactionReviewType } from '../Review'
export const NO_CONTRACT = 'no contract' export const NO_CONTRACT = 'no contract'
@ -72,3 +73,11 @@ export const createTxObject = (method: AbiItem, contractAddress: string, values)
} }
export const isReadMethod = (method: AbiItemExtended): boolean => method && method.action === 'read' export const isReadMethod = (method: AbiItemExtended): boolean => method && method.action === 'read'
export const getValueFromTxInputs = (key: string, type: string, tx: TransactionReviewType): string => {
let value = tx[key]
if (type === 'bool') {
value = tx[key] ? String(tx[key]) : 'false'
}
return value
}

View File

@ -6,7 +6,6 @@ import { useSelector } from 'react-redux'
import { styles } from './style' import { styles } from './style'
import { getSymbolAndDecimalsFromContract } from './utils' import { getSymbolAndDecimalsFromContract } from './utils'
import Checkbox from 'src/components/forms/Checkbox'
import Field from 'src/components/forms/Field' import Field from 'src/components/forms/Field'
import GnoForm from 'src/components/forms/GnoForm' import GnoForm from 'src/components/forms/GnoForm'
import TextField from 'src/components/forms/TextField' import TextField from 'src/components/forms/TextField'
@ -25,6 +24,7 @@ import {
doesntExistInAssetsList, doesntExistInAssetsList,
} from 'src/routes/safe/components/Balances/Tokens/screens/AddCustomAsset/validators' } from 'src/routes/safe/components/Balances/Tokens/screens/AddCustomAsset/validators'
import TokenPlaceholder from 'src/routes/safe/components/Balances/assets/token_placeholder.svg' import TokenPlaceholder from 'src/routes/safe/components/Balances/assets/token_placeholder.svg'
import { Checkbox } from '@gnosis.pm/safe-react-components'
export const ADD_CUSTOM_ASSET_ADDRESS_INPUT_TEST_ID = 'add-custom-asset-address-input' export const ADD_CUSTOM_ASSET_ADDRESS_INPUT_TEST_ID = 'add-custom-asset-address-input'
export const ADD_CUSTOM_ASSET_SYMBOLS_INPUT_TEST_ID = 'add-custom-asset-symbols-input' export const ADD_CUSTOM_ASSET_SYMBOLS_INPUT_TEST_ID = 'add-custom-asset-symbols-input'
@ -140,11 +140,14 @@ const AddCustomAsset = (props) => {
text="Token decimals*" text="Token decimals*"
type="text" type="text"
/> />
<Block justify="left"> <Block justify="center">
<Field className={classes.checkbox} component={Checkbox} name="showForAllSafes" type="checkbox" /> <Field
<Paragraph className={classes.checkboxLabel} size="md" weight="bolder"> className={classes.checkbox}
Activate assets for all Safes component={Checkbox}
</Paragraph> name="showForAllSafes"
type="checkbox"
label="Activate assets for all Safes"
/>
</Block> </Block>
</Col> </Col>
<Col align="center" layout="column" xs={6}> <Col align="center" layout="column" xs={6}>

View File

@ -6,7 +6,6 @@ import { styles } from './style'
import { getSymbolAndDecimalsFromContract } from './utils' import { getSymbolAndDecimalsFromContract } from './utils'
import { addressIsTokenContract, doesntExistInTokenList } from './validators' import { addressIsTokenContract, doesntExistInTokenList } from './validators'
import Checkbox from 'src/components/forms/Checkbox'
import Field from 'src/components/forms/Field' import Field from 'src/components/forms/Field'
import GnoForm from 'src/components/forms/GnoForm' import GnoForm from 'src/components/forms/GnoForm'
import TextField from 'src/components/forms/TextField' import TextField from 'src/components/forms/TextField'
@ -21,6 +20,7 @@ import Row from 'src/components/layout/Row'
import TokenPlaceholder from 'src/routes/safe/components/Balances/assets/token_placeholder.svg' import TokenPlaceholder from 'src/routes/safe/components/Balances/assets/token_placeholder.svg'
import { checksumAddress } from 'src/utils/checksumAddress' import { checksumAddress } from 'src/utils/checksumAddress'
import { Checkbox } from '@gnosis.pm/safe-react-components'
export const ADD_CUSTOM_TOKEN_ADDRESS_INPUT_TEST_ID = 'add-custom-token-address-input' export const ADD_CUSTOM_TOKEN_ADDRESS_INPUT_TEST_ID = 'add-custom-token-address-input'
export const ADD_CUSTOM_TOKEN_SYMBOLS_INPUT_TEST_ID = 'add-custom-token-symbols-input' export const ADD_CUSTOM_TOKEN_SYMBOLS_INPUT_TEST_ID = 'add-custom-token-symbols-input'
@ -161,11 +161,14 @@ const AddCustomToken = (props) => {
text="Token decimals*" text="Token decimals*"
type="text" type="text"
/> />
<Block justify="left"> <Block justify="center">
<Field className={classes.checkbox} component={Checkbox} name="showForAllSafes" type="checkbox" /> <Field
<Paragraph className={classes.checkboxLabel} size="md" weight="bolder"> className={classes.checkbox}
Activate token for all Safes component={Checkbox}
</Paragraph> name="showForAllSafes"
type="checkbox"
label="Activate token for all Safes"
/>
</Block> </Block>
</Col> </Col>
<Col align="center" layout="column" xs={6}> <Col align="center" layout="column" xs={6}>

2052
yarn.lock

File diff suppressed because it is too large Load Diff