Merge pull request #149 from poanetwork/add-tokens-validation

(Fix) Add custom tokens validation
This commit is contained in:
Victor Baranov 2018-10-16 18:32:41 +03:00 committed by GitHub
commit 27dd7a4708
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 107 additions and 106 deletions

View File

@ -209,7 +209,7 @@ App.prototype.renderAppBar = function () {
alignItems: 'center',
},
}, [
props.isUnlocked && h(AccountDropdowns, {
h(AccountDropdowns, {
style: {},
enableAccountsSelector: true,
identities: this.props.identities,
@ -219,7 +219,7 @@ App.prototype.renderAppBar = function () {
}, []),
// hamburger
props.isUnlocked && h('div', {
h('div', {
className: state.sandwichClass || 'sandwich-expando',
style: {
width: 16,

View File

@ -42,14 +42,14 @@ class AddTokenScreen extends Component {
this.state = {
warning: null,
customAddress: '',
customSymbol: 'Token',
customDecimals: 18,
customSymbol: '',
customDecimals: '',
searchResults: [],
selectedTokens: {},
tokenSelectorError: null,
customAddressError: null,
customSymbolError: null,
customDecimalsError: null,
customAddressError: true,
customSymbolError: true,
customDecimalsError: true,
autoFilled: false,
displayedTab: SEARCH_TAB,
}
@ -79,7 +79,7 @@ class AddTokenScreen extends Component {
const {
address: customAddress = '',
symbol: customSymbol = '',
decimals: customDecimals = 0,
decimals: customDecimals = '',
} = customToken
const displayedTab = Object.keys(selectedTokens).length > 0 ? SEARCH_TAB : CUSTOM_TOKEN_TAB
@ -148,7 +148,7 @@ class AddTokenScreen extends Component {
renderAddToken () {
const props = this.props
const state = this.state
const { warning, customSymbol, customDecimals } = state
const { warning, customAddress, customSymbol, customDecimals, autoFilled } = state
const { network, goHome, addToken } = props
return h('.flex-column.flex-justify-center.flex-grow.select-none', [
warning ? h('div', {
@ -183,6 +183,7 @@ class AddTokenScreen extends Component {
h('input.large-input#token-address', {
name: 'address',
placeholder: 'Token Contract Address',
value: customAddress,
style: {
width: '100%',
margin: '10px 0',
@ -199,6 +200,7 @@ class AddTokenScreen extends Component {
h('div', { style: {display: 'flex'} }, [
h('input.large-input#token_symbol', {
disabled: !autoFilled,
placeholder: `Like "ETH"`,
value: customSymbol,
style: {
@ -217,6 +219,7 @@ class AddTokenScreen extends Component {
h('div', { style: {display: 'flex'} }, [
h('input.large-input#token_decimals', {
disabled: true,
value: customDecimals,
type: 'number',
min: 0,
@ -243,6 +246,7 @@ class AddTokenScreen extends Component {
},
}, 'Cancel' /* this.context.t('cancel')*/),
h('button', {
disabled: this.hasError() || !this.hasSelected(),
onClick: (event) => {
const valid = this.validateInputs()
if (!valid) return
@ -294,7 +298,7 @@ class AddTokenScreen extends Component {
}, 'Cancel' /* this.context.t('cancel')*/),
h('button.btn-primary', {
onClick: () => this.handleNext(),
disabled: this.hasError() || !this.hasSelected(),
disabled: !this.hasSelected(),
}, 'Next' /* this.context.t('next')*/),
]),
]),
@ -323,9 +327,13 @@ class AddTokenScreen extends Component {
} = nextProps
if (oldNet !== newNet) {
this.tokenInfoGetter = tokenInfoGetter()
this.setState({
selectedTokens: {},
searchResults: [],
customAddress: '',
customSymbol: '',
customDecimals: '',
})
}
}
@ -358,7 +366,7 @@ class AddTokenScreen extends Component {
msg = 'Personal address detected. Input the token contract address.'
}
const isValid = validAddress && validDecimals && !ownAddress
const isValid = validAddress && validDecimals && validSymbol && !ownAddress
if (!isValid) {
this.setState({
@ -400,15 +408,11 @@ class AddTokenScreen extends Component {
}
hasSelected = () => {
const { customAddress = '', selectedTokens = {} } = this.state
return customAddress || Object.keys(selectedTokens).length > 0
const { customAddress = '', customDecimals = '', customSymbol = '', selectedTokens = {} } = this.state
return (customAddress && customDecimals && customSymbol) || Object.keys(selectedTokens).length > 0
}
handleNext = () => {
if (this.hasError()) {
return
}
if (!this.hasSelected()) {
this.setState({ tokenSelectorError: 'Must select at least 1 token.' /* this.context.t('mustSelectOne')*/ })
return
@ -434,12 +438,12 @@ class AddTokenScreen extends Component {
}
attemptToAutoFillTokenParams = async (address) => {
const { symbol = '', decimals = 0 } = await this.tokenInfoGetter(address)
const { symbol = '', decimals = '' } = await this.tokenInfoGetter(address)
const autoFilled = Boolean(symbol && decimals)
this.setState({ autoFilled })
this.handleCustomSymbolChange(symbol || '')
this.handleCustomDecimalsChange(decimals)
this.handleCustomDecimalsChange(decimals || '')
}
handleCustomAddressChange = (value) => {
@ -459,7 +463,7 @@ class AddTokenScreen extends Component {
this.setState({
customAddressError: 'Invalid address' /* this.context.t('invalidAddress')*/,
customSymbol: '',
customDecimals: 0,
customDecimals: null,
customSymbolError: null,
customDecimalsError: null,
})
@ -497,7 +501,7 @@ class AddTokenScreen extends Component {
}
handleCustomDecimalsChange = (value) => {
const customDecimals = value.trim()
const customDecimals = (value && value.trim())
const validDecimals = customDecimals !== null &&
customDecimals !== '' &&
customDecimals >= 0 &&

View File

@ -151,9 +151,7 @@ TokenCell.prototype.send = function (address, event) {
event.preventDefault()
event.stopPropagation()
const url = tokenFactoryFor(address)
if (url) {
navigateTo(url)
}
navigateTo(url)
}
TokenCell.prototype.view = function (address, userAddress, network, event) {

View File

@ -195,10 +195,11 @@ TokenList.prototype.createFreshTokenTracker = function () {
if (!global.ethereumProvider) return
const { userAddress } = this.props
const tokensFromCurrentNetwork = this.props.tokens.filter(token => (parseInt(token.network) === parseInt(this.props.network) || !token.network))
this.tracker = new TokenTracker({
userAddress,
provider: global.ethereumProvider,
tokens: this.props.tokens,
tokens: tokensFromCurrentNetwork,
pollingInterval: 8000,
})

View File

@ -1032,4 +1032,8 @@ div.message-container > div:first-child {
.hidden {
visibility: hidden;
}
input[disabled] {
background: #FFFADE
}

View File

@ -54,7 +54,6 @@ function iconExistsFor (address, networkID) {
function imageElFor (address, networkID) {
const contractMap = networkID === 1 ? contractMapETH : contractMapPOA
console.log(contractMap)
const contract = contractMap[address]
const fileName = contract.logo
const imagesFolder = networkID === 1 ? 'images/contract' : 'images/contractPOA'

View File

@ -72,6 +72,7 @@ describe('Metamask popup page', async function () {
})
it('screen \'Terms of Use\' has not empty agreement', async () => {
await delay(5000)
const terms = await waitUntilShowUp(screens.TOU.agreement, 300)
const text = await terms.getText()
assert.equal(text.length > 400, true, 'agreement is too short')
@ -135,7 +136,7 @@ describe('Metamask popup page', async function () {
await field.click()
const accountName = await waitUntilShowUp(screens.main.fieldAccountName)
assert.notEqual(accountName, false, '\'Account name\' change dialog isn\'t opened')
assert.equal(await accountName.getAttribute('value'), 'Account 1', 'incorrect placeholder')
assert.equal(await accountName.getAttribute('value'), 'Account 1', 'incorrect account name')
})
it('fill out new account\'s name', async () => {
@ -889,6 +890,8 @@ describe('Metamask popup page', async function () {
})
describe('Add Token: Custom', function () {
const symbol = 'TST'
const decimals = '0'
describe('Token Factory', function () {
@ -911,8 +914,8 @@ describe('Metamask popup page', async function () {
await totalSupply.sendKeys('100')
await tokenName.sendKeys('Test')
await tokenDecimal.sendKeys('0')
await tokenSymbol.sendKeys('TST')
await tokenDecimal.sendKeys(decimals)
await tokenSymbol.sendKeys(symbol)
await click(createToken)
await delay(1000)
})
@ -942,7 +945,7 @@ describe('Metamask popup page', async function () {
await delay(700)
})
})
describe('Add token', function () {
describe('Add token to LOCALHOST', function () {
it('navigates to the add token screen', async function () {
await waitUntilShowUp(screens.main.identicon)
@ -958,19 +961,38 @@ describe('Metamask popup page', async function () {
const addTokenScreen = await waitUntilShowUp(screens.addToken.title)
assert.equal(await addTokenScreen.getText(), screens.addToken.titleText)
})
it('adds token parameters', async function () {
const tab = await waitUntilShowUp(screens.addToken.tab.custom, 30)
if (!await waitUntilShowUp(screens.addToken.custom.fields.contractAddress)) await tab.click()
})
it('address input is displayed and has correct placeholder', async function () {
const field = await waitUntilShowUp(screens.addToken.custom.fields.contractAddress)
assert.equal(await field.getAttribute('placeholder'), 'Token Contract Address', 'incorrect placeholder')
})
it('fill out address input', async function () {
const tokenContractAddress = await waitUntilShowUp(screens.addToken.custom.fields.contractAddress)
await tokenContractAddress.sendKeys(tokenAddress)
const button = await waitUntilShowUp(screens.addToken.custom.buttons.add)
await click(button)
})
it('field \'Symbol\' enabled and has correct value', async function () {
const field = await waitUntilShowUp(screens.addToken.custom.fields.tokenSymbol)
assert.equal(await field.isEnabled(), true, 'field disabled')
assert.equal(await field.getAttribute('placeholder'), 'Like "ETH"', 'incorrect placeholder')
assert.equal(await field.getAttribute('value'), symbol, 'incorrect value')
})
it('field \'Decimals\' enabled and has correct value', async function () {
const field = await waitUntilShowUp(screens.addToken.custom.fields.decimals)
assert.equal(await field.isEnabled(), false, 'field disabled')
assert.equal(await field.getAttribute('value'), decimals, 'incorrect value')
})
it('checks the token balance', async function () {
const button = await waitUntilShowUp(screens.addToken.custom.buttons.add)
await click(button)
const tokenBalance = await waitUntilShowUp(screens.main.tokens.balance)
assert.equal(await tokenBalance.getText(), '100 TST')
assert.equal(await tokenBalance.getText(), '100 TST', 'balance is incorrect or not displayed')
})
it('token balance updates if switch account', async function () {
@ -1070,55 +1092,44 @@ describe('Metamask popup page', async function () {
})
})
describe('Add token with the same address to each network ', async function () {
describe('Custom tokens validation ', async function () {
const tokenName = 'DVT'
const tokenDecimals = '13'
it('adds token with the same address to POA network', async function () {
it('can not add inexistent token to POA network', async function () {
await setProvider(NETWORKS.POA)
await addToken(tokenAddress, tokenName, tokenDecimals)
const tokenBalance = await waitUntilShowUp(screens.main.tokens.balance)
assert.notEqual(await tokenBalance.getText(), '')
assert(await isDisabledAddInexistentToken(tokenAddress), true, 'can add inexistent token in POA network')
})
it('adds token with the same address to SOKOL network', async function () {
it('can not add inexistent token to SOKOL network', async function () {
await setProvider(NETWORKS.SOKOL)
await addToken(tokenAddress, tokenName, tokenDecimals)
const tokenBalance = await waitUntilShowUp(screens.main.tokens.balance)
assert.notEqual(await tokenBalance.getText(), '')
assert(await isDisabledAddInexistentToken(tokenAddress), true, 'can add inexistent token in POA network')
})
it('adds token with the same address to ROPSTEN network', async function () {
it('can not add inexistent token to ROPSTEN network', async function () {
await setProvider(NETWORKS.ROPSTEN)
await addToken(tokenAddress, tokenName, tokenDecimals)
const tokenBalance = await waitUntilShowUp(screens.main.tokens.balance)
assert.notEqual(await tokenBalance.getText(), '')
assert(await isDisabledAddInexistentToken(tokenAddress), true, 'can add inexistent token in POA network')
})
it('adds token with the same address to KOVAN network', async function () {
it('can not add inexistent token to KOVAN network', async function () {
await setProvider(NETWORKS.KOVAN)
await addToken(tokenAddress, tokenName, tokenDecimals)
const tokenBalance = await waitUntilShowUp(screens.main.tokens.balance)
assert.notEqual(await tokenBalance.getText(), '')
assert(await isDisabledAddInexistentToken(tokenAddress), true, 'can add inexistent token in POA network')
})
it('adds token with the same address to RINKEBY network', async function () {
it('can not add inexistent token to RINKEBY network', async function () {
await setProvider(NETWORKS.RINKEBY)
await addToken(tokenAddress, tokenName, tokenDecimals)
const tokenBalance = await waitUntilShowUp(screens.main.tokens.balance)
assert.notEqual(await tokenBalance.getText(), '')
assert(await isDisabledAddInexistentToken(tokenAddress), true, 'can add inexistent token in POA network')
})
it('adds token with the same address to MAINNET network', async function () {
it('can not add inexistent token to MAINNET network', async function () {
await setProvider(NETWORKS.MAINNET)
await addToken(tokenAddress, tokenName, tokenDecimals)
const tokenBalance = await waitUntilShowUp(screens.main.tokens.balance)
assert.notEqual(await tokenBalance.getText(), '')
assert(await isDisabledAddInexistentToken(tokenAddress), true, 'can add inexistent token in POA network')
})
it('can not add inexistent token to LOCALHOST network', async function () {
await setProvider(NETWORKS.LOCALHOST)
assert(await isDisabledAddInexistentToken(tokenAddress.slice(0, tokenAddress.length - 2) + '0'), true, 'can add inexistent token in POA network')
})
it('token still should be displayed in LOCALHOST network', async function () {
await setProvider(NETWORKS.LOCALHOST)
await waitUntilDisappear(screens.main.tokens.amount)
assert.notEqual(await waitUntilShowUp(screens.main.tokens.amount), false, 'App is frozen')
const tokens = await driver.findElements(screens.main.tokens.amount)
@ -1347,7 +1358,6 @@ describe('Metamask popup page', async function () {
assert.equal(await assertTokensNotDisplayed(), true, 'tokens are displayed')
})
it('check if token was removed from SOKOL network', async function () {
await setProvider(NETWORKS.SOKOL)
assert.equal(await assertTokensNotDisplayed(), true, 'tokens are displayed')
@ -1633,7 +1643,7 @@ describe('Metamask popup page', async function () {
}
}
async function addToken (tokenAddress, tokenName, tokenDecimals) {
async function isDisabledAddInexistentToken (tokenAddress) {
try {
const button = await waitUntilShowUp(screens.main.tokens.buttonAdd, 300)
await click(button)
@ -1645,17 +1655,25 @@ describe('Metamask popup page', async function () {
}
}
while (await waitUntilShowUp(screens.addToken.custom.fields.contractAddress) === false)
const field = await waitUntilShowUp(screens.addToken.custom.fields.contractAddress)
await clearField(field)
await field.sendKeys(tokenAddress)
const buttonAdd = await waitUntilShowUp(screens.addToken.custom.buttons.add)
await click(buttonAdd)
return true
} catch (err) {
console.log(err)
return false
}
const fieldAddress = await waitUntilShowUp(screens.addToken.custom.fields.contractAddress)
await clearField(fieldAddress)
await fieldAddress.sendKeys(tokenAddress)
const fieldSymbols = await waitUntilShowUp(screens.addToken.custom.fields.tokenSymbol)
if (await fieldSymbols.isEnabled()) return false
const fieldDecimals = await waitUntilShowUp(screens.addToken.custom.fields.tokenSymbol)
if (await fieldDecimals.isEnabled()) return false
const buttonAdd = await waitUntilShowUp(screens.addToken.custom.buttons.add)
if (await buttonAdd.isEnabled()) return false
const buttonCancel = await waitUntilShowUp(screens.addToken.custom.buttons.cancel)
await click(buttonCancel)
return true
}
async function checkBrowserForConsoleErrors () {

View File

@ -58,44 +58,20 @@ async function runAddTokenFlowTest (assert, done) {
const customAddress = (await findAsync($, '#token-address'))[0]
await customAddress.focus()
await timeout(1000)
await nativeInputValueSetter.call(customAddress, 'invalid address')
await customAddress.dispatchEvent(new Event('input', { bubbles: true }))
const buttonAdd = await queryAsync($, '#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div.flex-space-around > div:nth-child(7) > button:nth-child(2)')
assert.ok(buttonAdd[0], 'add button rendered')
await buttonAdd[0].click()
// Verify contract error since contract address is invalid
const errorMessage = await queryAsync($, '.error')
assert.ok(errorMessage[0], 'error rendered')
// Input valid token contract address
await nativeInputValueSetter.call(customAddress, '0x177af043D3A1Aed7cc5f2397C70248Fc6cDC056c')
await customAddress.dispatchEvent(new Event('input', { bubbles: true }))
// Verify button add disabled since contract is invalid
const buttonAdd = await queryAsync($, '#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div.flex-space-around > div:nth-child(7) > button:nth-child(2)')
assert.ok(buttonAdd[0], 'add button rendered')
assert.equal(await buttonAdd[0].getAttribute('disabled'), '', 'add button isn\'t disabled')
// Input token symbol with length more than 10
const customSymbol = (await findAsync($, '#token_symbol'))[0]
assert.ok(customSymbol, 'symbol field rendered')
/*
await customSymbol.focus()
await timeout(1000)
await nativeInputValueSetter.call(customSymbol, 'POAPOAPOA20')
await customSymbol.dispatchEvent(new Event('input', { bubbles: true }))
await buttonAdd[0].click()
// Verify symbol length error since length more than 10
errorMessage = await queryAsync($, '.error')[0]
assert.ok(errorMessage, 'error rendered')
*/
// Input valid token symbol
await nativeInputValueSetter.call(customSymbol, 'POA')
await customSymbol.dispatchEvent(new Event('input', { bubbles: true }))
assert.equal(await customSymbol.getAttribute('disabled'), '', 'symbol field isn\'t disabled')
// Input valid decimals
const customDecimals = (await findAsync($, '#token_decimals'))[0]
assert.ok(customDecimals, 'decimals field rendered')
// Click Add button
await buttonAdd[0].click()
// check if main screen
assert.ok((await queryAsync($, '.identicon-wrapper'))[0], 'returned to account detail wallet view')
assert.equal(await customDecimals.getAttribute('disabled'), '', 'decimals field isn\'t disabled')
}

View File

@ -102,9 +102,10 @@ async function runFirstTimeUsageTest(assert, done) {
assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.')
assert.ok(qrContainer, 'QR Container found')
await timeout(10000)
const networkMenu = (await findAsync(app, '.network-indicator'))[0]
networkMenu.click()
await timeout(5000)
const networkMenu2 = (await findAsync(app, '.network-indicator'))[0]
const children2 = networkMenu2.children
children2.length[3]

View File

@ -27,7 +27,7 @@ describe('Add Token Screen', function () {
it('Default State', function () {
addTokenComponent.instance().validateInputs()
const state = addTokenComponent.state()
assert.equal(state.warning, 'Address is invalid.')
assert.equal(state.warning, 'Address is invalid.Symbol must be between 0 and 10 characters.')
})
it('Address is a Metamask Identity', function () {