From 20ad2e7d42de5be99fbf552e1cab9a271bb9ae83 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 5 Jan 2017 16:51:27 +0100 Subject: [PATCH] Util/validation update (#4051) * Fix code validation for null inputs * tests WIP * tests WIP * Update validation tests * Adjust block names, slice (PR comments) --- .../modals/DeployContract/deployContract.js | 4 +- js/src/util/validation.js | 41 ++- js/src/util/validation.spec.js | 303 ++++++++++++++++++ 3 files changed, 333 insertions(+), 15 deletions(-) create mode 100644 js/src/util/validation.spec.js diff --git a/js/src/modals/DeployContract/deployContract.js b/js/src/modals/DeployContract/deployContract.js index 4d9c4f40e..6b09986ad 100644 --- a/js/src/modals/DeployContract/deployContract.js +++ b/js/src/modals/DeployContract/deployContract.js @@ -441,9 +441,7 @@ class DeployContract extends Component { } onCodeChange = (code) => { - const { api } = this.context; - - this.setState(validateCode(code, api), this.estimateGas); + this.setState(validateCode(code), this.estimateGas); } onDeployStart = () => { diff --git a/js/src/util/validation.js b/js/src/util/validation.js index a789300b4..cb65b662a 100644 --- a/js/src/util/validation.js +++ b/js/src/util/validation.js @@ -16,10 +16,12 @@ import BigNumber from 'bignumber.js'; -import util from '~/api/util'; +import apiutil from '~/api/util'; import { NULL_ADDRESS } from './constants'; +// TODO: Convert to FormattedMessages as soon as comfortable with the impact, i.e. errors +// not being concatted into strings in components, all supporting a non-string format export const ERRORS = { invalidAddress: 'address is an invalid network address', invalidAmount: 'the supplied amount should be a valid positive number', @@ -42,9 +44,14 @@ export function validateAbi (abi) { try { abiParsed = JSON.parse(abi); - if (!util.isArray(abiParsed)) { + if (!apiutil.isArray(abiParsed)) { abiError = ERRORS.invalidAbi; - return { abi, abiError, abiParsed }; + + return { + abi, + abiError, + abiParsed + }; } // Validate each elements of the Array @@ -54,8 +61,15 @@ export function validateAbi (abi) { if (invalidIndex !== -1) { const invalid = abiParsed[invalidIndex]; + + // TODO: Needs seperate error when using FormattedMessage (no concats) abiError = `${ERRORS.invalidAbi} (#${invalidIndex}: ${invalid.name || invalid.type})`; - return { abi, abiError, abiParsed }; + + return { + abi, + abiError, + abiParsed + }; } abi = JSON.stringify(abiParsed); @@ -76,7 +90,7 @@ function isValidAbiFunction (object) { } return ((object.type === 'function' && object.name) || object.type === 'constructor') && - (object.inputs && util.isArray(object.inputs)); + (object.inputs && apiutil.isArray(object.inputs)); } function isAbiFallback (object) { @@ -94,7 +108,7 @@ function isValidAbiEvent (object) { return (object.type === 'event') && (object.name) && - (object.inputs && util.isArray(object.inputs)); + (object.inputs && apiutil.isArray(object.inputs)); } export function validateAddress (address) { @@ -102,10 +116,10 @@ export function validateAddress (address) { if (!address) { addressError = ERRORS.invalidAddress; - } else if (!util.isAddressValid(address)) { + } else if (!apiutil.isAddressValid(address)) { addressError = ERRORS.invalidAddress; } else { - address = util.toChecksumAddress(address); + address = apiutil.toChecksumAddress(address); } return { @@ -114,12 +128,12 @@ export function validateAddress (address) { }; } -export function validateCode (code, api) { +export function validateCode (code) { let codeError = null; - if (!code.length) { + if (!code || !code.length) { codeError = ERRORS.invalidCode; - } else if (!api.util.isHex(code)) { + } else if (!apiutil.isHex(code)) { codeError = ERRORS.invalidCode; } @@ -130,7 +144,9 @@ export function validateCode (code, api) { } export function validateName (name) { - const nameError = !name || name.trim().length < 2 ? ERRORS.invalidName : null; + const nameError = !name || name.trim().length < 2 + ? ERRORS.invalidName + : null; return { name, @@ -162,6 +178,7 @@ export function validateUint (value) { try { const bn = new BigNumber(value); + if (bn.lt(0)) { valueError = ERRORS.negativeNumber; } else if (!bn.isInteger()) { diff --git a/js/src/util/validation.spec.js b/js/src/util/validation.spec.js new file mode 100644 index 000000000..e34f3eb3f --- /dev/null +++ b/js/src/util/validation.spec.js @@ -0,0 +1,303 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import BigNumber from 'bignumber.js'; + +import { NULL_ADDRESS } from './constants'; +import { ERRORS, isNullAddress, validateAbi, validateAddress, validateCode, validateName, validatePositiveNumber, validateUint } from './validation'; + +describe('util/validation', () => { + describe('validateAbi', () => { + it('passes on valid ABI', () => { + const abi = '[{"type":"function","name":"test","inputs":[],"outputs":[]}]'; + + expect(validateAbi(abi)).to.deep.equal({ + abi, + abiError: null, + abiParsed: [{ + type: 'function', + name: 'test', + inputs: [], + outputs: [] + }] + }); + }); + + it('passes on valid ABI & trims ABI', () => { + const abi = '[ { "type" : "function" , "name" : "test" , "inputs" : [] , "outputs" : [] } ]'; + + expect(validateAbi(abi)).to.deep.equal({ + abi: '[{"type":"function","name":"test","inputs":[],"outputs":[]}]', + abiError: null, + abiParsed: [{ + type: 'function', + name: 'test', + inputs: [], + outputs: [] + }] + }); + }); + + it('sets error on invalid JSON', () => { + const abi = 'this is not json'; + + expect(validateAbi(abi)).to.deep.equal({ + abi, + abiError: ERRORS.invalidAbi, + abiParsed: null + }); + }); + + it('sets error on non-array JSON', () => { + const abi = '{}'; + + expect(validateAbi(abi)).to.deep.equal({ + abi, + abiError: ERRORS.invalidAbi, + abiParsed: {} + }); + }); + + it('fails with invalid event', () => { + const abi = '[{ "type":"event" }]'; + + expect(validateAbi(abi)).to.deep.equal({ + abi, + abiError: `${ERRORS.invalidAbi} (#0: event)`, + abiParsed: [{ type: 'event' }] + }); + }); + + it('fails with invalid function', () => { + const abi = '[{ "type":"function" }]'; + + expect(validateAbi(abi)).to.deep.equal({ + abi, + abiError: `${ERRORS.invalidAbi} (#0: function)`, + abiParsed: [{ type: 'function' }] + }); + }); + + it('fails with unknown type', () => { + const abi = '[{ "type":"somethingElse" }]'; + + expect(validateAbi(abi)).to.deep.equal({ + abi, + abiError: `${ERRORS.invalidAbi} (#0: somethingElse)`, + abiParsed: [{ type: 'somethingElse' }] + }); + }); + }); + + describe('validateAddress', () => { + it('validates address', () => { + const address = '0x1234567890123456789012345678901234567890'; + + expect(validateAddress(address)).to.deep.equal({ + address, + addressError: null + }); + }); + + it('validates address and converts to checksum', () => { + const address = '0x5A5eFF38DA95b0D58b6C616f2699168B480953C9'; + + expect(validateAddress(address.toLowerCase())).to.deep.equal({ + address, + addressError: null + }); + }); + + it('sets error on null addresses', () => { + expect(validateAddress(null)).to.deep.equal({ + address: null, + addressError: ERRORS.invalidAddress + }); + }); + + it('sets error on invalid addresses', () => { + const address = '0x12344567'; + + expect(validateAddress(address)).to.deep.equal({ + address, + addressError: ERRORS.invalidAddress + }); + }); + }); + + describe('validateCode', () => { + it('validates hex code', () => { + expect(validateCode('0x123abc')).to.deep.equal({ + code: '0x123abc', + codeError: null + }); + }); + + it('validates hex code (non-prefix)', () => { + expect(validateCode('123abc')).to.deep.equal({ + code: '123abc', + codeError: null + }); + }); + + it('sets error on invalid code', () => { + expect(validateCode(null)).to.deep.equal({ + code: null, + codeError: ERRORS.invalidCode + }); + }); + + it('sets error on empty code', () => { + expect(validateCode('')).to.deep.equal({ + code: '', + codeError: ERRORS.invalidCode + }); + }); + + it('sets error on non-hex code', () => { + expect(validateCode('123hfg')).to.deep.equal({ + code: '123hfg', + codeError: ERRORS.invalidCode + }); + }); + }); + + describe('validateName', () => { + it('validates names', () => { + expect(validateName('Joe Bloggs')).to.deep.equal({ + name: 'Joe Bloggs', + nameError: null + }); + }); + + it('sets error on null names', () => { + expect(validateName(null)).to.deep.equal({ + name: null, + nameError: ERRORS.invalidName + }); + }); + + it('sets error on short names', () => { + expect(validateName(' 1 ')).to.deep.equal({ + name: ' 1 ', + nameError: ERRORS.invalidName + }); + }); + }); + + describe('validatePositiveNumber', () => { + it('validates numbers', () => { + expect(validatePositiveNumber(123)).to.deep.equal({ + number: 123, + numberError: null + }); + }); + + it('validates strings', () => { + expect(validatePositiveNumber('123')).to.deep.equal({ + number: '123', + numberError: null + }); + }); + + it('validates bignumbers', () => { + expect(validatePositiveNumber(new BigNumber(123))).to.deep.equal({ + number: new BigNumber(123), + numberError: null + }); + }); + + it('sets error on invalid numbers', () => { + expect(validatePositiveNumber(null)).to.deep.equal({ + number: null, + numberError: ERRORS.invalidAmount + }); + }); + + it('sets error on negative numbers', () => { + expect(validatePositiveNumber(-1)).to.deep.equal({ + number: -1, + numberError: ERRORS.invalidAmount + }); + }); + }); + + describe('validateUint', () => { + it('validates numbers', () => { + expect(validateUint(123)).to.deep.equal({ + value: 123, + valueError: null + }); + }); + + it('validates strings', () => { + expect(validateUint('123')).to.deep.equal({ + value: '123', + valueError: null + }); + }); + + it('validates bignumbers', () => { + expect(validateUint(new BigNumber(123))).to.deep.equal({ + value: new BigNumber(123), + valueError: null + }); + }); + + it('sets error on invalid numbers', () => { + expect(validateUint(null)).to.deep.equal({ + value: null, + valueError: ERRORS.invalidNumber + }); + }); + + it('sets error on negative numbers', () => { + expect(validateUint(-1)).to.deep.equal({ + value: -1, + valueError: ERRORS.negativeNumber + }); + }); + + it('sets error on decimal numbers', () => { + expect(validateUint(3.1415927)).to.deep.equal({ + value: 3.1415927, + valueError: ERRORS.decimalNumber + }); + }); + }); + + describe('isNullAddress', () => { + it('verifies a prefixed null address', () => { + expect(isNullAddress(`0x${NULL_ADDRESS}`)).to.be.true; + }); + + it('verifies a non-prefixed null address', () => { + expect(isNullAddress(NULL_ADDRESS)).to.be.true; + }); + + it('sets false on a null value', () => { + expect(isNullAddress(null)).to.be.false; + }); + + it('sets false on a non-full length 00..00 value', () => { + expect(isNullAddress(NULL_ADDRESS.slice(2))).to.be.false; + }); + + it('sets false on a valid addess, non 00..00 value', () => { + expect(isNullAddress('0x1234567890123456789012345678901234567890')).to.be.false; + }); + }); +});