Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(Fix) Validate whitelist min/max #778

Merged
merged 10 commits into from
Apr 12, 2018
2 changes: 1 addition & 1 deletion src/components/Common/TierBlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ export const TierBlock = ({ fields, ...props }) => {
<div className="section-title">
<p className="title">Whitelist</p>
</div>
<WhitelistInputBlock num={index}/>
<WhitelistInputBlock num={index} decimals={props.decimals} />
</div>
) : null
}
Expand Down
120 changes: 112 additions & 8 deletions src/components/Common/WhitelistInputBlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import Dropzone from 'react-dropzone';
import Papa from 'papaparse'
import '../../assets/stylesheets/application.css';
import { InputField } from './InputField'
import { TEXT_FIELDS, VALIDATION_TYPES } from '../../utils/constants'
import { TEXT_FIELDS, VALIDATION_MESSAGES, VALIDATION_TYPES } from '../../utils/constants'
import { WhitelistItem } from './WhitelistItem'
import { inject, observer } from 'mobx-react'
import { clearingWhitelist, whitelistImported } from '../../utils/alerts'
import processWhitelist from '../../utils/processWhitelist'
import { validateWhitelistMax, validateWhitelistMin } from '../../utils/validations'
const { ADDRESS, MIN, MAX } = TEXT_FIELDS
const {VALID, INVALID} = VALIDATION_TYPES;

Expand All @@ -26,7 +27,17 @@ export class WhitelistInputBlock extends React.Component {
address: {
pristine: true,
valid: INVALID
}
},
min: {
pristine: true,
valid: INVALID,
errorMessage: VALIDATION_MESSAGES.REQUIRED
},
max: {
pristine: true,
valid: INVALID,
errorMessage: VALIDATION_MESSAGES.REQUIRED
},
}
}
}
Expand All @@ -40,11 +51,23 @@ export class WhitelistInputBlock extends React.Component {
validation: {
address: {
pristine: { $set: false }
}
},
min: {
pristine: { $set: false }
},
max: {
pristine: { $set: false }
},
}
}))

if (!addr || !min || !max || this.state.validation.address.valid === INVALID) {
const {
address: { valid: addrValid },
min: { valid: minValid },
max: { valid: maxValid },
} = this.state.validation

if (!addr || !min || !max || addrValid === INVALID || minValid === INVALID || maxValid === INVALID) {
return
}

Expand All @@ -56,7 +79,17 @@ export class WhitelistInputBlock extends React.Component {
address: {
pristine: true,
valid: INVALID
}
},
min: {
pristine: true,
valid: INVALID,
errorMessage: VALIDATION_MESSAGES.REQUIRED
},
max: {
pristine: true,
valid: INVALID,
errorMessage: VALIDATION_MESSAGES.REQUIRED
},
}
})

Expand All @@ -81,12 +114,77 @@ export class WhitelistInputBlock extends React.Component {
this.setState(newState)
}

handleMinChange = ({ min }) => {
const errorMessage = validateWhitelistMin({
min,
max: this.state.max,
decimals: this.props.decimals
})

return new Promise((resolve) => {
this.setState(update(this.state, {
min: { $set: min },
validation: {
min: {
$set: {
pristine: false,
valid: errorMessage ? INVALID : VALID,
errorMessage
}
}
}
}), resolve)
})
}

handleMaxChange = ({ max }) => {
const errorMessage = validateWhitelistMax({
min: this.state.min,
max,
decimals: this.props.decimals
})

return new Promise((resolve) => {
this.setState(update(this.state, {
max: { $set: max },
validation: {
max: {
$set: {
pristine: false,
valid: errorMessage ? INVALID : VALID,
errorMessage
}
}
}
}), resolve)
})
}

handleMinMaxChange = ({ min, max }) => {
if (min !== undefined) {
this.handleMinChange({ min })
.then(() => {
if (!this.state.validation.max.pristine) this.handleMaxChange({ max: this.state.max })
})
}

if (max !== undefined) {
this.handleMaxChange({ max })
.then(() => {
if (!this.state.validation.min.pristine) this.handleMinChange({ min: this.state.min })
})
}
}

onDrop = (acceptedFiles, rejectedFiles) => {
acceptedFiles.forEach(file => {
Papa.parse(file, {
skipEmptyLines: true,
complete: results => {
const { called } = processWhitelist(results.data, item => {
const { called } = processWhitelist({
rows: results.data,
decimals: this.props.decimals
}, item => {
this.props.tierStore.addWhitelistItem(item, this.props.num)
})

Expand Down Expand Up @@ -149,16 +247,22 @@ export class WhitelistInputBlock extends React.Component {
type='number'
title={MIN}
value={this.state.min}
onChange={e => this.setState({ min: e.target.value })}
onChange={e => this.handleMinMaxChange({ min: e.target.value })}
description={`Minimum amount tokens to buy. Not a minimal size of a transaction. If minCap is 1 and user bought 1 token in a previous transaction and buying 0.1 token it will allow him to buy.`}
pristine={this.state.validation.min.pristine}
valid={this.state.validation.min.valid}
errorMessage={this.state.validation.min.errorMessage}
/>
<InputField
side='white-list-input-property white-list-input-property-right'
type='number'
title={MAX}
value={this.state.max}
onChange={e => this.setState({ max: e.target.value })}
onChange={e => this.handleMinMaxChange({ max: e.target.value })}
description={`Maximum is the hard limit.`}
pristine={this.state.validation.max.pristine}
valid={this.state.validation.max.valid}
errorMessage={this.state.validation.max.errorMessage}
/>
</div>
<div className="plus-button-container">
Expand Down
2 changes: 2 additions & 0 deletions src/components/manage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -361,10 +361,12 @@ export class Manage extends Component {
}

whitelistInputBlock = index => {
const { tokenStore } = this.props
return (
<WhitelistInputBlock
key={index.toString()}
num={index}
decimals={tokenStore.decimals}
/>
)
}
Expand Down
1 change: 1 addition & 0 deletions src/components/stepThree/StepThreeForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export const StepThreeForm = ({ handleSubmit, values, invalid, pristine, mutator
<TierBlock
fields={fields}
minCap={values.minCap}
decimals={props.decimals}
tierStore={props.tierStore}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1567,10 +1567,13 @@ exports[`CrowdsaleBlock Should render the component for the second Tier with whi
</InputField>
<InputField
description="Minimum amount tokens to buy. Not a minimal size of a transaction. If minCap is 1 and user bought 1 token in a previous transaction and buying 0.1 token it will allow him to buy."
errorMessage="This field is required"
onChange={[Function]}
pristine={true}
side="white-list-input-property white-list-input-property-middle"
title="Min"
type="number"
valid="INVALID"
value=""
>
<div
Expand Down Expand Up @@ -1607,10 +1610,13 @@ exports[`CrowdsaleBlock Should render the component for the second Tier with whi
</InputField>
<InputField
description="Maximum is the hard limit."
errorMessage="This field is required"
onChange={[Function]}
pristine={true}
side="white-list-input-property white-list-input-property-right"
title="Max"
type="number"
valid="INVALID"
value=""
>
<div
Expand Down
2 changes: 1 addition & 1 deletion src/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export const VALIDATION_MESSAGES = {
REQUIRED: 'This field is required',
DECIMAL_PLACES: 'Decimals should not exceed the amount of decimals specified',
LESS_OR_EQUAL: 'Should be less or equal than the specified value',
GREATER_OR_EQUAL: 'Should be greater than the specified value',
GREATER_OR_EQUAL: 'Should be greater or equal than the specified value',
INTEGER: 'Should be integer',
DATE_IN_FUTURE: 'Should be set in the future',
DATE_IS_PREVIOUS: 'Should be previous than specified time',
Expand Down
19 changes: 11 additions & 8 deletions src/utils/processWhitelist.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
import Web3 from 'web3'

const isNumber = (number) => !isNaN(parseFloat(number))
import { isAddress, validateWhitelistMin, validateWhitelistMax } from './validations'

/**
* Execute a callback with each valid whitelist item in the given list
*
* @param {Array} rows Array of whitelist items. Each element in the array has the structure `[address, min, max]`, for
* example: `['0x1234567890123456789012345678901234567890', '1', '10']`
* @param {Object} whitelistInformation
* @param {Array} whitelistInformation.rows Array of whitelist items. Each element in the array has the structure
* `[address, min, max]`, for example: `['0x1234567890123456789012345678901234567890', '1', '10']`
* @param {Number} whitelistInformation.decimals Amount of decimals allowed for the min and max values
* @param {Function} cb The function to be called with each valid item
* @returns {Object} Object with a `called` property, indicating the number of times the callback was called
*/
export default function (rows, cb) {
export default function ({ rows, decimals }, cb) {
let called = 0
rows.forEach((row) => {
if (row.length !== 3) return

const [addr, min, max] = row

if (!Web3.utils.isAddress(addr) || !isNumber(min) || !isNumber(max)) return
if (
isAddress()(addr) ||
validateWhitelistMin({ min, max, decimals }) ||
validateWhitelistMax({ min, max, decimals })
) return

cb({ addr, min, max })

called++
})

Expand Down
12 changes: 6 additions & 6 deletions src/utils/processWhitelist.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('processWhitelist function', () => {
const cb = jest.fn()

// When
processWhitelist(rows, cb)
processWhitelist({ rows, decimals: 3 }, cb)

// Then
expect(cb).toHaveBeenCalledTimes(3)
Expand All @@ -20,7 +20,7 @@ describe('processWhitelist function', () => {
expect(cb.mock.calls[2]).toEqual([{ addr: rows[2][0], min: rows[2][1], max: rows[2][2] }])
})

it('should ignore items that don\t have 3 elements', () => {
it('should ignore items that don\'t have 3 elements', () => {
// Given
const rows = [
['1', '10'],
Expand All @@ -33,7 +33,7 @@ describe('processWhitelist function', () => {
const cb = jest.fn()

// When
processWhitelist(rows, cb)
processWhitelist({ rows, decimals: 3 }, cb)

// Then
expect(cb).toHaveBeenCalledTimes(0)
Expand All @@ -49,7 +49,7 @@ describe('processWhitelist function', () => {
const cb = jest.fn()

// When
const { called } = processWhitelist(rows, cb)
const { called } = processWhitelist({ rows, decimals: 3 }, cb)

// Then
expect(called).toBe(3)
Expand All @@ -66,7 +66,7 @@ describe('processWhitelist function', () => {
const cb = jest.fn()

// When
const { called } = processWhitelist(rows, cb)
const { called } = processWhitelist({ rows, decimals: 3 }, cb)

// Then
expect(called).toBe(0)
Expand All @@ -83,7 +83,7 @@ describe('processWhitelist function', () => {
const cb = jest.fn()

// When
const { called } = processWhitelist(rows, cb)
const { called } = processWhitelist({ rows, decimals: 3 }, cb)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add some test cases for ignoring invalid decimals, and invalid min/max combinations?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


// Then
expect(called).toBe(0)
Expand Down
22 changes: 22 additions & 0 deletions src/utils/validations.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,28 @@ export const validateTicker = (value) => {
return isValid ? undefined : VALIDATION_MESSAGES.TICKER
}

export const validateWhitelistMin = ({ min, max, decimals }) => {
const listOfErrors = composeValidators(
isRequired(),
isNonNegative(),
isDecimalPlacesNotGreaterThan(`Decimals should not exceed ${decimals} places`)(decimals),
isLessOrEqualThan('Should be less or equal than max')(max)
)(min)

return listOfErrors ? listOfErrors.shift() : ''
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this return undefined when there are no errors?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, fixed

}

export const validateWhitelistMax = ({ min, max, decimals }) => {
const listOfErrors = composeValidators(
isRequired(),
isNonNegative(),
isDecimalPlacesNotGreaterThan(`Decimals should not exceed ${decimals} places`)(decimals),
isGreaterOrEqualThan('Should be greater or equal than min')(min)
)(max)

return listOfErrors ? listOfErrors.shift() : ''
}

export const isPositive = (errorMsg = VALIDATION_MESSAGES.POSITIVE) => (value) => {
const isValid = value > 0
return isValid ? undefined : errorMsg
Expand Down
Loading