From e1f2ccd138c38bcaf0a5d41e78bfc299590b7dcc Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 22 Mar 2017 10:36:41 +0100 Subject: [PATCH] Extend api.util (#4979) * cleanupValue * abiUnencode & abiSignature * Export new functions --- js/src/api/util/encode.js | 31 +++++++++++++++++++++++++++++++ js/src/api/util/encode.spec.js | 31 +++++++++++++++++++++++++++++-- js/src/api/util/format.js | 32 ++++++++++++++++++++++++++++++++ js/src/api/util/format.spec.js | 24 +++++++++++++++++++++++- js/src/api/util/index.js | 7 +++++-- 5 files changed, 120 insertions(+), 5 deletions(-) diff --git a/js/src/api/util/encode.js b/js/src/api/util/encode.js index 5b5fb5eac..f18fbbe2b 100644 --- a/js/src/api/util/encode.js +++ b/js/src/api/util/encode.js @@ -17,6 +17,10 @@ import Abi from '~/abi'; import Func from '~/abi/spec/function'; +import { abiDecode } from './decode'; +import { cleanupValue } from './format'; +import { sha3 } from './sha3'; + export function encodeMethodCallAbi (methodAbi = {}, values = []) { const func = new Func(methodAbi); const tokens = Abi.encodeTokens(func.inputParamTypes(), values); @@ -36,3 +40,30 @@ export function abiEncode (methodName, inputTypes, data) { return result; } + +export function abiUnencode (abi, data) { + const callsig = data.substr(2, 8); + const op = abi.find((field) => { + return field.type === 'function' && + abiSignature(field.name, field.inputs.map((input) => input.type)).substr(2, 8) === callsig; + }); + + if (!op) { + console.warn(`Unknown function ID: ${callsig}`); + return null; + } + + let argsByIndex = abiDecode(op.inputs.map((field) => field.type), '0x' + data.substr(10)) + .map((value, index) => cleanupValue(value, op.inputs[index].type)); + const argsByName = op.inputs.reduce((result, field, index) => { + result[field.name] = argsByIndex[index]; + + return result; + }, {}); + + return [op.name, argsByName, argsByIndex]; +} + +export function abiSignature (name, inputs) { + return sha3(`${name}(${inputs.join()})`); +} diff --git a/js/src/api/util/encode.spec.js b/js/src/api/util/encode.spec.js index 6fead2ed5..7847f06ec 100644 --- a/js/src/api/util/encode.spec.js +++ b/js/src/api/util/encode.spec.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { abiEncode, encodeMethodCallAbi } from './encode'; +import { abiEncode, abiUnencode, abiSignature, encodeMethodCallAbi } from './encode'; const ABI = { type: 'function', @@ -51,7 +51,11 @@ describe('api/util/encode', () => { }); it('encodes variable values', () => { - expect(abiEncode('hintUrl', ['bytes32', 'string'], ['0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', 'http://foo.bar/'])).to.equal(`0x${VARIABLE}`); + expect( + abiEncode( + 'hintUrl', ['bytes32', 'string'], ['0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', 'http://foo.bar/'] + ) + ).to.equal(`0x${VARIABLE}`); }); it('encodes only the data with null name', () => { @@ -60,4 +64,27 @@ describe('api/util/encode', () => { ).to.equal(`0x${RESULT.substr(8)}`); }); }); + + describe('abiUnencode', () => { + it('decodes data correctly from abi', () => { + expect( + abiUnencode([{ + name: 'test', + type: 'function', + inputs: [ + { type: 'uint', name: 'arga' } + ] + }], '0x1acb6f7700000000000000000000000000000038') + ).to.deep.equal(['test', { arga: 56 }, [56]]); + }); + }); + + // Same example as in abi/util/signature.spec.js + describe('abiSignature', () => { + it('encodes baz(uint32,bool) correctly', () => { + expect( + abiSignature('baz', ['uint32', 'bool']) + ).to.equal('0xcdcd77c0992ec5bbfc459984220f8c45084cc24d9b6efed1fae540db8de801d2'); + }); + }); }); diff --git a/js/src/api/util/format.js b/js/src/api/util/format.js index c1fac8fff..c7594b692 100644 --- a/js/src/api/util/format.js +++ b/js/src/api/util/format.js @@ -20,6 +20,38 @@ export function bytesToHex (bytes) { return '0x' + bytes.map((b) => ('0' + b.toString(16)).slice(-2)).join(''); } +export function cleanupValue (value, type) { + // TODO: make work with arbitrary depth arrays + if (value instanceof Array && type.match(/bytes[0-9]+/)) { + // figure out if it's an ASCII string hiding in there: + let ascii = ''; + + for (let index = 0, ended = false; index < value.length && ascii !== null; ++index) { + const val = value[index]; + + if (val === 0) { + ended = true; + } else { + ascii += String.fromCharCode(val); + } + + if ((ended && val !== 0) || (!ended && (val < 32 || val >= 128))) { + ascii = null; + } + } + + value = ascii === null + ? bytesToHex(value) + : ascii; + } + + if (type.substr(0, 4) === 'uint' && +type.substr(4) <= 48) { + value = +value; + } + + return value; +} + export function hexToBytes (hex) { const raw = toHex(hex).slice(2); const bytes = []; diff --git a/js/src/api/util/format.spec.js b/js/src/api/util/format.spec.js index 8a89c0862..c37205569 100644 --- a/js/src/api/util/format.spec.js +++ b/js/src/api/util/format.spec.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { bytesToHex, hexToBytes, hexToAscii, bytesToAscii, asciiToHex } from './format'; +import { bytesToHex, cleanupValue, hexToBytes, hexToAscii, bytesToAscii, asciiToHex } from './format'; describe('api/util/format', () => { describe('bytesToHex', () => { @@ -27,6 +27,28 @@ describe('api/util/format', () => { }); }); + describe('cleanupValue', () => { + it('returns unknown values as the original', () => { + expect(cleanupValue('original', 'unknown')).to.equal('original'); + }); + + it('returns ascii arrays as ascii', () => { + expect(cleanupValue([97, 115, 99, 105, 105, 0], 'bytes32')).to.equal('ascii'); + }); + + it('returns non-ascii arrays as hex strings', () => { + expect(cleanupValue([97, 200, 0, 0], 'bytes4')).to.equal('0x61c80000'); + }); + + it('returns uint (>48) as the original', () => { + expect(cleanupValue('original', 'uint49')).to.equal('original'); + }); + + it('returns uint (<=48) as the number value', () => { + expect(cleanupValue('12345', 'uint48')).to.equal(12345); + }); + }); + describe('hexToBytes', () => { it('correctly converts an empty string', () => { expect(hexToBytes('')).to.deep.equal([]); diff --git a/js/src/api/util/index.js b/js/src/api/util/index.js index 254b97c74..cc8fc9b93 100644 --- a/js/src/api/util/index.js +++ b/js/src/api/util/index.js @@ -16,8 +16,8 @@ import { isAddress as isAddressValid, toChecksumAddress } from '../../abi/util/address'; import { abiDecode, decodeCallData, decodeMethodInput, methodToAbi } from './decode'; -import { abiEncode, encodeMethodCallAbi } from './encode'; -import { bytesToHex, hexToAscii, asciiToHex } from './format'; +import { abiEncode, abiUnencode, abiSignature, encodeMethodCallAbi } from './encode'; +import { bytesToHex, hexToAscii, asciiToHex, cleanupValue } from './format'; import { fromWei, toWei } from './wei'; import { sha3 } from './sha3'; import { isArray, isFunction, isHex, isInstanceOf, isString } from './types'; @@ -26,6 +26,9 @@ import { createIdentityImg } from './identity'; export default { abiDecode, abiEncode, + abiUnencode, + abiSignature, + cleanupValue, isAddressValid, isArray, isFunction,