From 8925513b0338307d7a021057dded544f16db6c08 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 5 Mar 2018 18:30:30 -0300 Subject: [PATCH 01/12] Indicate to user how many addresses were imported --- src/components/Common/WhitelistInputBlock.js | 18 ++-- src/utils/alerts.js | 7 ++ src/utils/processWhitelist.js | 30 +++++++ src/utils/processWhitelist.spec.js | 89 ++++++++++++++++++++ 4 files changed, 132 insertions(+), 12 deletions(-) create mode 100644 src/utils/processWhitelist.js create mode 100644 src/utils/processWhitelist.spec.js diff --git a/src/components/Common/WhitelistInputBlock.js b/src/components/Common/WhitelistInputBlock.js index bf9fc06fc..33ade245c 100644 --- a/src/components/Common/WhitelistInputBlock.js +++ b/src/components/Common/WhitelistInputBlock.js @@ -6,9 +6,10 @@ import Papa from 'papaparse' import '../../assets/stylesheets/application.css'; import { InputField } from './InputField' import { TEXT_FIELDS, VALIDATION_TYPES } from '../../utils/constants' -import { validateAddress } from '../../utils/utils' import { WhitelistItem } from './WhitelistItem' import { inject, observer } from 'mobx-react' +import { whitelistImported } from '../../utils/alerts' +import processWhitelist from '../../utils/processWhitelist' const { ADDRESS, MIN, MAX } = TEXT_FIELDS const {VALID, INVALID} = VALIDATION_TYPES; @@ -80,23 +81,16 @@ export class WhitelistInputBlock extends React.Component { this.setState(newState) } - isAddress = (address) => validateAddress(address) - isNumber = (number) => !isNaN(parseFloat(number)) - onDrop = (acceptedFiles, rejectedFiles) => { acceptedFiles.forEach(file => { Papa.parse(file, { skipEmptyLines: true, complete: results => { - results.data.forEach((row) => { - if (row.length !== 3) return - - const [addr, min, max] = row - - if (!this.isAddress(addr) || !this.isNumber(min) || !this.isNumber(max)) return - - this.props.tierStore.addWhitelistItem({ addr, min, max }, this.props.num) + const { called } = processWhitelist(results.data, item => { + this.props.tierStore.addWhitelistItem(item, this.props.num) }) + + whitelistImported(called) } }) }) diff --git a/src/utils/alerts.js b/src/utils/alerts.js index dcad846d9..4efd3452e 100644 --- a/src/utils/alerts.js +++ b/src/utils/alerts.js @@ -213,3 +213,10 @@ export function skippingTransaction() { reverseButtons: true }) } +export function whitelistImported(count) { + return sweetAlert2({ + title: 'Addresses imported', + html: `${count} addresses were added to the whitelist`, + type: 'info' + }) +} diff --git a/src/utils/processWhitelist.js b/src/utils/processWhitelist.js new file mode 100644 index 000000000..cedc29736 --- /dev/null +++ b/src/utils/processWhitelist.js @@ -0,0 +1,30 @@ +import { validateAddress } from './utils' + +const isAddress = (address) => validateAddress(address) +const isNumber = (number) => !isNaN(parseFloat(number)) + +/** + * 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 {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) { + let called = 0 + rows.forEach((row) => { + if (row.length !== 3) return + + const [addr, min, max] = row + + if (!isAddress(addr) || !isNumber(min) || !isNumber(max)) return + + cb({ addr, min, max }) + + called++ + }) + + return { called } +} + diff --git a/src/utils/processWhitelist.spec.js b/src/utils/processWhitelist.spec.js new file mode 100644 index 000000000..f7d05f919 --- /dev/null +++ b/src/utils/processWhitelist.spec.js @@ -0,0 +1,89 @@ +import processWhitelist from './processWhitelist' + +describe('processWhitelist function', () => { + it('should call the callback for each whitelist item', () => { + // Given + const rows = [ + ['0x1111111111111111111111111111111111111111', '1', '10'], + ['0x2222222222222222222222222222222222222222', '1', '10'], + ['0x3333333333333333333333333333333333333333', '1', '10'] + ] + const cb = jest.fn() + + // When + processWhitelist(rows, cb) + + // Then + expect(cb).toHaveBeenCalledTimes(3) + expect(cb.mock.calls[0]).toEqual([{ addr: rows[0][0], min: rows[0][1], max: rows[0][2] }]) + expect(cb.mock.calls[1]).toEqual([{ addr: rows[1][0], min: rows[1][1], max: rows[1][2] }]) + 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', () => { + // Given + const rows = [ + ['1', '10'], + ['0x2222222222222222222222222222222222222222', '10'], + ['0x3333333333333333333333333333333333333333', '1'], + ['0x4444444444444444444444444444444444444444'], + [], + ['0x4444444444444444444444444444444444444444', '1', '10', '100'], + ] + const cb = jest.fn() + + // When + processWhitelist(rows, cb) + + // Then + expect(cb).toHaveBeenCalledTimes(0) + }) + + it('should return the number of times the callback was called', () => { + // Given + const rows = [ + ['0x1111111111111111111111111111111111111111', '1', '10'], + ['0x2222222222222222222222222222222222222222', '1', '10'], + ['0x3333333333333333333333333333333333333333', '1', '10'] + ] + const cb = jest.fn() + + // When + const { called } = processWhitelist(rows, cb) + + // Then + expect(called).toBe(3) + }) + + it('should ignore invalid numbers', () => { + // Given + const rows = [ + ['0x1111111111111111111111111111111111111111', 'foo', '10'], + ['0x2222222222222222222222222222222222222222', '1', 'bar'], + ['0x3333333333333333333333333333333333333333', '', '10'], + ['0x4444444444444444444444444444444444444444', '1', ''] + ] + const cb = jest.fn() + + // When + const { called } = processWhitelist(rows, cb) + + // Then + expect(called).toBe(0) + }) + + it('should ignore invalid addresses', () => { + // Given + const rows = [ + ['0x123456789012345678901234567890123456789', '1', '10'], + ['0x12345678901234567890123456789012345678901', '1', '10'] + ] + const cb = jest.fn() + + // When + const { called } = processWhitelist(rows, cb) + + // Then + expect(called).toBe(0) + }) +}) From bd4fe39cd110838dc14946859a000331ec63d4f6 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 6 Mar 2018 12:26:30 -0300 Subject: [PATCH 02/12] Validate imported whitelist addresses --- src/utils/processWhitelist.js | 5 ++--- src/utils/processWhitelist.spec.js | 6 ++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/utils/processWhitelist.js b/src/utils/processWhitelist.js index cedc29736..6d220e2e0 100644 --- a/src/utils/processWhitelist.js +++ b/src/utils/processWhitelist.js @@ -1,6 +1,5 @@ -import { validateAddress } from './utils' +import Web3 from 'web3' -const isAddress = (address) => validateAddress(address) const isNumber = (number) => !isNaN(parseFloat(number)) /** @@ -18,7 +17,7 @@ export default function (rows, cb) { const [addr, min, max] = row - if (!isAddress(addr) || !isNumber(min) || !isNumber(max)) return + if (!Web3.utils.isAddress(addr) || !isNumber(min) || !isNumber(max)) return cb({ addr, min, max }) diff --git a/src/utils/processWhitelist.spec.js b/src/utils/processWhitelist.spec.js index f7d05f919..b03d9fa42 100644 --- a/src/utils/processWhitelist.spec.js +++ b/src/utils/processWhitelist.spec.js @@ -75,8 +75,10 @@ describe('processWhitelist function', () => { it('should ignore invalid addresses', () => { // Given const rows = [ - ['0x123456789012345678901234567890123456789', '1', '10'], - ['0x12345678901234567890123456789012345678901', '1', '10'] + ['0x123456789012345678901234567890123456789', '1', '10'], // 41 characters + ['0x12345678901234567890123456789012345678901', '1', '10'], // 43 characters + ['0x90F8bf6A479f320ead074411a4B0e7944Ea8c9CG', '1', '10'], // invalid character + ['0x90F8bf6A479f320ead074411a4B0e7944Ea8c9c1', '1', '10'] // invalid checksum ] const cb = jest.fn() From 49e55d09fdce91e9ad1cc8e8114810beca8c9925 Mon Sep 17 00:00:00 2001 From: viktor Date: Tue, 6 Mar 2018 18:14:32 +0100 Subject: [PATCH 03/12] e2e tests --- .gitmodules | 4 ++++ .travis.yml | 10 ++++++++++ submodules/token-wizard-test-automation | 1 + 3 files changed, 15 insertions(+) create mode 160000 submodules/token-wizard-test-automation diff --git a/.gitmodules b/.gitmodules index 61c462c2a..c1923368c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,7 @@ path = submodules/poa-token-market-net-ico url = https://github.com/poanetwork/ico branch = wizard +[submodule "submodules/token-wizard-test-automation"] + path = submodules/token-wizard-test-automation + url = https://github.com/poanetwork/token-wizard-test-automation + branch = master diff --git a/.travis.yml b/.travis.yml index 77bc3da15..d0f83a477 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,16 @@ install: before_script: - npm run installWeb3 + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start + - npm i -g npm@^3 + - sleep 3 + - wget -N http://chromedriver.storage.googleapis.com/2.30/chromedriver_linux64.zip -P ~/ + - unzip ~/chromedriver_linux64.zip -d ~/ + - rm ~/chromedriver_linux64.zip + - sudo mv -f ~/chromedriver /usr/local/share/ + - sudo chmod +x /usr/local/share/chromedriver + - sudo ln -s /usr/local/share/chromedriver /usr/local/bin/chromedriver script: - npm run lint diff --git a/submodules/token-wizard-test-automation b/submodules/token-wizard-test-automation new file mode 160000 index 000000000..483a59572 --- /dev/null +++ b/submodules/token-wizard-test-automation @@ -0,0 +1 @@ +Subproject commit 483a595722c15b24eba6e5fb050a0e37d20eb304 From 2e83ce5515cfa889db0e7b92343daf7e1f09d239 Mon Sep 17 00:00:00 2001 From: viktor Date: Tue, 6 Mar 2018 18:21:39 +0100 Subject: [PATCH 04/12] e2e npm script --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 1ecfb4e44..932537b6f 100755 --- a/package.json +++ b/package.json @@ -120,8 +120,9 @@ "deployRegistry": "node scripts/deployRegistry.js", "startWin": "npm run installWeb3 && npm run generateFlatSoliditySafeMathLibContractWin && npm run generateFlatSolidityCrowdsaleNullFinalizeAgentContractWin && npm run generateFlatSolidityCrowdsaleFinalizeAgentContractWin && npm run generateFlatSolidityCrowdsaleContractWin && npm run generateFlatSolidityCrowdsaleTokenContractWin && npm run generateFlatSolidityCrowdsalePricingStrategyContractWin && npm run compileSafeMathLibExtContractWin && npm run compileCrowdsaleNullFinalizeAgentContractWin && npm run compileCrowdsaleFinalizeAgentContractWin && npm run compileCrowdsaleContractWin && npm run compileCrowdsaleTokenContractWin && npm run compileCrowdsalePricingStrategyContractWin && node scripts/start.js", "build": "git submodule update --init --recursive --remote && cd submodules/solidity-flattener && npm install && cd ../../ && npm install && cd submodules/poa-web3-1.0 && npm install && cd ../../ && npm install --no-save submodules/poa-web3-1.0/packages/web3 && npm run generateContracts && npm run compileContracts && node scripts/build.js && cp ./build/index.html ./build/invest.html && cp ./build/index.html ./build/crowdsale.html && cp ./build/index.html ./build/manage.html", - "test": "bash ./start_testrpc.sh && cd ./submodules/poa-token-market-net-ico/ && npm install && node_modules/.bin/truffle migrate --network testrpc && node_modules/.bin/truffle test --network testrpc", + "test": "npm run test:e2e && bash ./start_testrpc.sh && cd ./submodules/poa-token-market-net-ico/ && npm install && node_modules/.bin/truffle migrate --network testrpc && node_modules/.bin/truffle test --network testrpc", "test:dapp": "jest --env=jsdom", + "test:e2e": "cd submodules/token-wizard-test-automation && npm i && npm run test1", "coveralls": "jest --env=jsdom --coverage && cat coverage/lcov.info | coveralls", "generateContracts": "npm run generateFlatSoliditySafeMathLibContract && npm run generateFlatSolidityCrowdsaleNullFinalizeAgentContract && npm run generateFlatSolidityCrowdsaleFinalizeAgentContract && npm run generateFlatSolidityCrowdsaleContract && npm run generateFlatSolidityCrowdsaleTokenContract && npm run generateFlatSolidityCrowdsalePricingStrategyContract && npm run generateFlatSolidityRegistryContract", "generateFlatSoliditySafeMathLibContract": "node $npm_package_config_combine_solidity_script $npm_package_config_tokenmarketnet_path/$npm_package_config_safe_math_lib_contract_name.sol $npm_package_config_contract_folder SafeMathLibExt", From fc1dcb6a86122ffc673ce26b466f5cc6ffff6b19 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Tue, 6 Mar 2018 14:28:15 -0300 Subject: [PATCH 05/12] Fix disable status check --- src/components/manage/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/manage/index.js b/src/components/manage/index.js index 5369bc579..a271a3dd2 100644 --- a/src/components/manage/index.js +++ b/src/components/manage/index.js @@ -458,7 +458,8 @@ export class Manage extends Component { const { formPristine, canFinalize, shouldDistribute, canDistribute, crowdsaleHasEnded, ownerCurrentUser } = this.state const { generalStore, tierStore, tokenStore, crowdsaleStore } = this.props const { address: crowdsaleAddress, finalized, updatable } = crowdsaleStore.selected - let disabled = !ownerCurrentUser || canDistribute || canFinalize || finalized + + const canEditTier = ownerCurrentUser && !canDistribute && !canFinalize && !finalized const distributeTokensStep = (
@@ -528,7 +529,7 @@ export class Manage extends Component { } const tierStartAndEndTime = (tier, index) => { - disabled = disabled || !tier.updatable || this.tierHasEnded(index) + const disabled = !canEditTier || !tier.updatable || this.tierHasEnded(index) return
{ - disabled = disabled || !tier.updatable || this.tierHasEnded(index) || this.tierHasStarted(index) + const disabled = !canEditTier || !tier.updatable || this.tierHasEnded(index) || this.tierHasStarted(index) return
Date: Tue, 6 Mar 2018 14:30:01 -0300 Subject: [PATCH 06/12] Refactor tierHasStarted method --- src/components/manage/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/manage/index.js b/src/components/manage/index.js index a271a3dd2..78e08250b 100644 --- a/src/components/manage/index.js +++ b/src/components/manage/index.js @@ -446,7 +446,7 @@ export class Manage extends Component { tierHasStarted = (index) => { const initialTierValues = this.props.crowdsaleStore.selected.initialTiersValues[index] - return initialTierValues ? Date.now() > new Date(initialTierValues.startTime).getTime() : true + return initialTierValues && new Date(initialTierValues.startTime).getTime() < Date.now() } tierHasEnded = (index) => { From 3b8464240cbd54552ef5758bdc2dc0260e40f217 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Tue, 6 Mar 2018 17:31:13 -0300 Subject: [PATCH 07/12] Reload 500ms after finalize --- src/components/manage/index.js | 8 ++++++-- src/utils/alerts.js | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/manage/index.js b/src/components/manage/index.js index 5369bc579..f04582e94 100644 --- a/src/components/manage/index.js +++ b/src/components/manage/index.js @@ -293,9 +293,13 @@ export class Manage extends Component { return sendTXToContract(finalizeMethod.send(opts)) }) .then(() => { - successfulFinalizeAlert() crowdsaleStore.setSelectedProperty('finalized', true) - this.setState({ canFinalize: false }) + this.setState({ canFinalize: false }, () => { + successfulFinalizeAlert().then(() => { + this.setState({ loading: true }) + setTimeout(() => window.location.reload(), 500) + }) + }) }) .catch((err) => { console.log(err) diff --git a/src/utils/alerts.js b/src/utils/alerts.js index dcad846d9..c8d4dbfdb 100644 --- a/src/utils/alerts.js +++ b/src/utils/alerts.js @@ -130,7 +130,7 @@ export function warningOnFinalizeCrowdsale() { } export function successfulFinalizeAlert() { - sweetAlert2({ + return sweetAlert2({ title: "Success", html: "Congrats! You've successfully finalized the Crowdsale!", type: "success" From ef903c4ca6b35f0b0b25228026b190e194818618 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Tue, 6 Mar 2018 17:32:51 -0300 Subject: [PATCH 08/12] Update canFinalize method to check for 'isFinalized' status --- src/components/manage/index.js | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/components/manage/index.js b/src/components/manage/index.js index f04582e94..5ff2fcb52 100644 --- a/src/components/manage/index.js +++ b/src/components/manage/index.js @@ -193,15 +193,24 @@ export class Manage extends Component { const lastCrowdsaleAddress = contractStore.crowdsale.addr.slice(-1)[0] return attachToContract(contractStore.crowdsale.abi, lastCrowdsaleAddress) - .then(crowdsaleContract => crowdsaleContract.methods.isCrowdsaleFull().call()) - .then( - (isCrowdsaleFull) => { - const { crowdsaleHasEnded, shouldDistribute, canDistribute } = this.state - const wasDistributed = shouldDistribute && !canDistribute + .then(crowdsaleContract => { + const whenIsFinalized = crowdsaleContract.methods.finalized().call() + const whenIsCrowdsaleFull = crowdsaleContract.methods.isCrowdsaleFull().call() - this.setState({ - canFinalize: (crowdsaleHasEnded || isCrowdsaleFull) && (wasDistributed || !shouldDistribute) - }) + return Promise.all([whenIsFinalized, whenIsCrowdsaleFull]) + }) + .then( + ([isFinalized, isCrowdsaleFull]) => { + if (isFinalized) { + this.setState({ canFinalize: false }) + } else { + const { crowdsaleHasEnded, shouldDistribute, canDistribute } = this.state + const wasDistributed = shouldDistribute && !canDistribute + + this.setState({ + canFinalize: (crowdsaleHasEnded || isCrowdsaleFull) && (wasDistributed || !shouldDistribute) + }) + } }, () => this.setState({ canFinalize: false }) ) From b8f7a85c2c429aec345d6e6e371e14ef425e95ed Mon Sep 17 00:00:00 2001 From: viktor Date: Wed, 7 Mar 2018 00:36:45 +0100 Subject: [PATCH 09/12] npm i -g npm@^3 removed from travis --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d0f83a477..84b2dc5ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,6 @@ before_script: - npm run installWeb3 - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start - - npm i -g npm@^3 - sleep 3 - wget -N http://chromedriver.storage.googleapis.com/2.30/chromedriver_linux64.zip -P ~/ - unzip ~/chromedriver_linux64.zip -d ~/ From 38ed98e02163300839bb34c361275751dd9aca28 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 8 Mar 2018 11:10:16 -0300 Subject: [PATCH 10/12] Set deploymentStep when starting step four --- src/components/stepFour/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/stepFour/index.js b/src/components/stepFour/index.js index 81f739225..94d1739a9 100644 --- a/src/components/stepFour/index.js +++ b/src/components/stepFour/index.js @@ -44,6 +44,7 @@ export class stepFour extends React.Component { modal: false, transactionFailed: false } + this.props.deploymentStore.setDeploymentStep(0) } contractDownloadSuccess = options => { From 42039ceb97e17da7b44f7b312849dc7d12ff61b6 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Fri, 9 Mar 2018 07:49:55 -0300 Subject: [PATCH 11/12] Fix token's name max length --- src/utils/utils.js | 2 +- src/utils/utils.spec.js | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/utils/utils.js b/src/utils/utils.js index 3cb1616ce..547ae6f5f 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -70,7 +70,7 @@ export const getStepClass = (step, activeStep) => step === activeStep ? "step-na export const validateTier = (tier) => typeof tier === 'string' && tier.length > 0 && tier.length < 30 -export const validateName = (name) => typeof name === 'string' && name.length > 0 && name.length < 30 +export const validateName = (name) => typeof name === 'string' && name.length > 0 && name.length <= 30 export const validateSupply = (supply) => isNaN(Number(supply)) === false && Number(supply) > 0 diff --git a/src/utils/utils.spec.js b/src/utils/utils.spec.js index 29e75bde7..de24df6f5 100644 --- a/src/utils/utils.spec.js +++ b/src/utils/utils.spec.js @@ -1,4 +1,4 @@ -import { countDecimalPlaces, validateTicker } from './utils' +import { countDecimalPlaces, validateName, validateTicker } from './utils' describe('countDecimalPlaces', () => { [ @@ -55,3 +55,22 @@ describe('validateTicker', () => { }) }) }) + +describe('validateName', () => { + [ + {value: '', expected: false}, + {value: 'T', expected: true}, + {value: 'MyToken', expected: true}, + {value: '123456789012345678901234567890', expected: true}, + {value: '1234567890123456789012345678901', expected: false}, + {value: 23, expected: false}, + {value: ['my', 'token'], expected: false}, + {value: { a: 1 }, expected: false}, + ].forEach(testCase => { + const action = testCase.expected ? 'pass' : 'fail' + + it(`Should ${action} for '${testCase.value}'`, () => { + expect(validateName(testCase.value)).toBe(testCase.expected) + }) + }) +}) From 23f7b8c085c8dcd9ad27ba3f942bb814988ac829 Mon Sep 17 00:00:00 2001 From: viktor Date: Fri, 9 Mar 2018 23:03:38 +0300 Subject: [PATCH 12/12] e2e tests update --- submodules/token-wizard-test-automation | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/token-wizard-test-automation b/submodules/token-wizard-test-automation index 483a59572..03c628515 160000 --- a/submodules/token-wizard-test-automation +++ b/submodules/token-wizard-test-automation @@ -1 +1 @@ -Subproject commit 483a595722c15b24eba6e5fb050a0e37d20eb304 +Subproject commit 03c6285158aa1fca89c19f7ce9163c39d00fe5ce