Skip to content

Commit

Permalink
Merge pull request #778 from poanetwork/validate-whitelist-min-max-#578
Browse files Browse the repository at this point in the history
(Fix) Validate whitelist min/max
  • Loading branch information
Franco Victorio committed Apr 12, 2018
2 parents 4ec5c10 + ec3f829 commit 91941fd
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 24 deletions.
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
48 changes: 42 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,9 +83,45 @@ 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)
})

it('should reject invalid decimals', () => {
// Given
const rows = [
['0x1111111111111111111111111111111111111111', '10', '10.1'],
['0x3333333333333333333333333333333333333333', '10.1234', '10'],
['0x2222222222222222222222222222222222222222', '10.12', '10.123'],
['0x4444444444444444444444444444444444444444', '10.123456', '10.123456']
]
const cb = jest.fn()

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

// Then
expect(called).toBe(2)
})

it('should reject min > max', () => {
// Given
const rows = [
['0x1111111111111111111111111111111111111111', '15', '10.1'],
['0x2222222222222222222222222222222222222222', '10.13', '10.123'],
['0x3333333333333333333333333333333333333333', '100', '99.999999999999999999'],
['0x3333333333333333333333333333333333333333', '11', '11'],
['0x3333333333333333333333333333333333333333', '10.124', '11'],
['0x4444444444444444444444444444444444444444', '10.124', '10.125']
]
const cb = jest.fn()

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

// Then
expect(called).toBe(3)
})
})
Loading

0 comments on commit 91941fd

Please sign in to comment.