From 31477a78fbeecd62b9abfe0386b2c3a485dba037 Mon Sep 17 00:00:00 2001 From: qianbin Date: Tue, 19 Mar 2019 19:22:58 +0800 Subject: [PATCH 1/2] replace web3-eth-abi with ethers.abiCoder --- package-lock.json | 139 +++++++++++++++++++++------------------------- package.json | 2 +- src/abi.ts | 114 +++++++++++++++++++++++++++++-------- tests/abi.test.ts | 31 ++++++++--- 4 files changed, 178 insertions(+), 108 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5853c1a..7559bfc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -148,29 +148,12 @@ "@types/node": { "version": "10.5.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.5.2.tgz", - "integrity": "sha512-m9zXmifkZsMHZBOyxZWilMwmTlpC8x5Ty360JKTiXvlXZfBWYpsg9ZZvP/Ye+iZUh+Q+MxDLjItVTWIsfwz+8Q==", - "dev": true + "integrity": "sha512-m9zXmifkZsMHZBOyxZWilMwmTlpC8x5Ty360JKTiXvlXZfBWYpsg9ZZvP/Ye+iZUh+Q+MxDLjItVTWIsfwz+8Q==" }, - "@vechain/web3-eth-abi": { - "version": "1.0.0-beta.34-2", - "resolved": "https://registry.npmjs.org/@vechain/web3-eth-abi/-/web3-eth-abi-1.0.0-beta.34-2.tgz", - "integrity": "sha512-38T0v2tyLlawwTwDcFox5ZN1WrO1Ky2Ybaq5tjtk31rnEev1RIresDpD2MptsoNmQRPoNRVAAsOaHAD+qaaA+g==", - "requires": { - "bn.js": "4.11.6", - "ethjs-unit": "0.1.6", - "keccak": "1.4.0", - "number-to-bn": "1.7.0", - "randomhex": "0.1.5", - "underscore": "1.8.3", - "utf8": "2.1.1" - }, - "dependencies": { - "bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=" - } - } + "aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=" }, "ajv": { "version": "5.5.2", @@ -724,19 +707,47 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, - "ethjs-unit": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", - "integrity": "sha1-xmWSHkduh7ziqdWIpv4EBbLEFpk=", + "ethers": { + "version": "4.0.27", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.27.tgz", + "integrity": "sha512-+DXZLP/tyFnXWxqr2fXLT67KlGUfLuvDkHSOtSC9TUVG9OIj6yrG5JPeXRMYo15xkOYwnjgdMKrXp5V94rtjJA==", "requires": { - "bn.js": "4.11.6", - "number-to-bn": "1.7.0" + "@types/node": "^10.3.2", + "aes-js": "3.0.0", + "bn.js": "^4.4.0", + "elliptic": "6.3.3", + "hash.js": "1.1.3", + "js-sha3": "0.5.7", + "scrypt-js": "2.0.4", + "setimmediate": "1.0.4", + "uuid": "2.0.1", + "xmlhttprequest": "1.8.0" }, "dependencies": { - "bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=" + "elliptic": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.3.3.tgz", + "integrity": "sha1-VILZZG1UvLif19mU/J4ulWiHbj8=", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "inherits": "^2.0.1" + } + }, + "hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" + } + }, + "uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w=" } } }, @@ -946,11 +957,6 @@ "loose-envify": "^1.0.0" } }, - "is-hex-prefixed": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", - "integrity": "sha1-fY035q135dEnFIkTxXPggtd39VQ=" - }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -984,6 +990,11 @@ "semver": "^5.5.0" } }, + "js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=" + }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", @@ -1216,22 +1227,6 @@ "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" }, - "number-to-bn": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", - "integrity": "sha1-uzYjWS9+X54AMLGXe9QaDFP+HqA=", - "requires": { - "bn.js": "4.11.6", - "strip-hex-prefix": "1.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=" - } - } - }, "nyc": { "version": "12.0.2", "resolved": "https://registry.npmjs.org/nyc/-/nyc-12.0.2.tgz", @@ -3389,11 +3384,6 @@ "safe-buffer": "^5.1.0" } }, - "randomhex": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/randomhex/-/randomhex-0.1.5.tgz", - "integrity": "sha1-us7vmCMpCRQA8qKRLGzQLxCU9YU=" - }, "request": { "version": "2.87.0", "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", @@ -3475,6 +3465,11 @@ "nan": "^2.0.8" } }, + "scrypt-js": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.4.tgz", + "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==" + }, "secp256k1": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.5.0.tgz", @@ -3496,6 +3491,11 @@ "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", "dev": true }, + "setimmediate": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", + "integrity": "sha1-IOgd5iLUoCWIzgyNqJc8vPHTE48=" + }, "sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", @@ -3566,14 +3566,6 @@ "ansi-regex": "^2.0.0" } }, - "strip-hex-prefix": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", - "integrity": "sha1-DF8VX+8RUTczd96du1iNoFUA428=", - "requires": { - "is-hex-prefixed": "1.0.0" - } - }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", @@ -3683,21 +3675,11 @@ "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", "dev": true }, - "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" - }, "unorm": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.4.1.tgz", "integrity": "sha1-NkIA1fE2RsqLzURJAnEzVhR5IwA=" }, - "utf8": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.1.tgz", - "integrity": "sha1-LgHbAvfY0JRPdxBPFgnrDDBM92g=" - }, "uuid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz", @@ -3720,6 +3702,11 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" + }, "yn": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", diff --git a/package.json b/package.json index 76bc2ed..6627cc2 100644 --- a/package.json +++ b/package.json @@ -70,10 +70,10 @@ "typescript": "^2.9.1" }, "dependencies": { - "@vechain/web3-eth-abi": "^1.0.0-beta.34-2", "bignumber.js": "^7.2.1", "bip39": "^2.5.0", "blakejs": "^1.1.0", + "ethers": "^4.0.27", "fast-json-stable-stringify": "^2.0.0", "hdkey": "^1.0.0", "keccak": "^1.4.0", diff --git a/src/abi.ts b/src/abi.ts index a55056c..fc4d740 100644 --- a/src/abi.ts +++ b/src/abi.ts @@ -1,17 +1,41 @@ -const ethABI = require('@vechain/web3-eth-abi'); - -// avoid address checksumed -(ethABI._types as [any]).forEach(t => { - if (Object.getPrototypeOf(t).constructor.name === 'SolidityTypeAddress') { - t._outputFormatter = (param: any, name: any) => { - const value = param.staticPart() - if (!value) { - throw new Error('Couldn\'t decode ' + name + ' from ABI: 0x' + param.rawValue) +import { AbiCoder, formatSignature } from 'ethers/utils/abi-coder' +import { keccak256 } from './cry' +const coder = (() => { + const c = new AbiCoder((type, value) => { + if ((type.match(/^u?int/) && !Array.isArray(value) && typeof value !== 'object') || + value.constructor.name === 'BigNumber' + ) { + return value.toString() + } + if (type === 'address' && typeof value === 'string') { + // fucking checksum. it's too stupid to checksum address in non-ui part. + return value.toLowerCase() + } + return value + }) + return { + encode(types: string[], values: any[]): string { + try { + return c.encode(types, values) + } catch (err) { + if (err.reason) { + throw new Error(err.reason) + } + throw err + } + }, + decode(types: string[], data: string): any[] { + try { + return c.decode(types, data) + } catch (err) { + if (err.reason) { + throw new Error(err.reason) + } + throw err } - return '0x' + value.slice(value.length - 40, value.length) } } -}) +})() /** encode/decode parameters of contract function call, event log, according to ABI JSON */ export namespace abi { @@ -23,7 +47,7 @@ export namespace abi { * @returns encoded value in hex string */ export function encodeParameter(type: string, value: any) { - return ethABI.encodeParameter(type, value) as string + return coder.encode([type], [value]) } /** @@ -33,7 +57,7 @@ export namespace abi { * @returns decoded value */ export function decodeParameter(type: string, data: string) { - return ethABI.decodeParameter(type, data) as string + return coder.decode([type], data)[0] } /** @@ -43,7 +67,7 @@ export namespace abi { * @returns encoded values in hex string */ export function encodeParameters(types: Function.Parameter[], values: any[]) { - return ethABI.encodeParameters(types.map(p => p.type), values) as string + return coder.encode(types.map(p => p.type), values) } /** @@ -53,7 +77,13 @@ export namespace abi { * @returns decoded object */ export function decodeParameters(types: Function.Parameter[], data: string) { - return ethABI.decodeParameters(types, data) as Decoded + const result = coder.decode(types.map(p => p.type), data) + const decoded: Decoded = {} + types.forEach((t, i) => { + decoded[i] = result[i] + decoded[t.name] = result[i] + }) + return decoded } /** for contract function */ @@ -66,7 +96,7 @@ export namespace abi { * @param definition abi definition of the function */ constructor(public readonly definition: Function.Definition) { - this.signature = ethABI.encodeFunctionSignature(definition) + this.signature = '0x' + keccak256(formatSignature(definition as any)).slice(0, 4).toString('hex') } /** @@ -111,7 +141,7 @@ export namespace abi { /** for contract event */ constructor(public readonly definition: Event.Definition) { - this.signature = ethABI.encodeEventSignature(this.definition) + this.signature = '0x' + keccak256(formatSignature(definition as any)).toString('hex') } /** @@ -131,8 +161,19 @@ export namespace abi { if (value === undefined || value === null) { topics.push(null) } else { - // TODO: special case for dynamic types - topics.push(encodeParameter(input.type, value)) + if (isDynamicType(input.type)) { + if (input.type === 'string') { + topics.push('0x' + keccak256(value).toString('hex')) + } else { + if (typeof value === 'string' && /^0x[0-9a-f]+$/i.test(value) && value.length % 2 === 0) { + topics.push('0x' + keccak256(Buffer.from(value.slice(2), 'hex')).toString('hex')) + } else { + throw new Error(`invalid ${input.type} value`) + } + } + } else { + topics.push(encodeParameter(input.type, value)) + } } } return topics @@ -144,10 +185,31 @@ export namespace abi { * @param topics topics in event */ public decode(data: string, topics: string[]) { - return ethABI.decodeLog( - this.definition.inputs, - data, - this.definition.anonymous ? topics : topics.slice(1)) as Decoded + if (!this.definition.anonymous) { + topics = topics.slice(1) + } + + if (this.definition.inputs.filter(t => t.indexed).length !== topics.length) { + throw new Error('invalid topics count') + } + + const decodedNonIndexed = coder.decode( + this.definition.inputs.filter(t => !t.indexed).map(t => t.type), data) + + const decoded: Decoded = {} + this.definition.inputs.forEach((t, i) => { + if (t.indexed) { + if (isDynamicType(t.type)) { + decoded[i] = decoded[t.name] = topics.shift() + } else { + decoded[i] = decoded[t.name] = decodeParameter(t.type, topics.shift()!) + } + } else { + decoded[i] = decoded[t.name] = decodedNonIndexed.shift() + } + }) + + return decoded } } @@ -166,5 +228,9 @@ export namespace abi { } } - export type Decoded = { __length__: number } & { [field: string]: string } + export type Decoded = { [field: string]: any } & { [index: number]: any } + + function isDynamicType(type: string) { + return type === 'bytes' || type === 'string' || type.endsWith('[]') + } } diff --git a/tests/abi.test.ts b/tests/abi.test.ts index 069080e..1be92fe 100644 --- a/tests/abi.test.ts +++ b/tests/abi.test.ts @@ -1,5 +1,6 @@ import { expect } from 'chai' import { abi } from '../src' +import { keccak256 } from '../src/cry' // tslint:disable:quotemark // tslint:disable:object-literal-key-quotes @@ -89,12 +90,25 @@ describe('abi', () => { "name": "E3", "type": "event" }) + const e4 = new abi.Event({ + "inputs": [ + { + "indexed": true, + "name": "a1", + "type": "string" + } + ], + "name": "E4", + "type": "event" + }) it('codec', () => { expect(abi.encodeParameter('uint256', '2345675643')).equal('0x000000000000000000000000000000000000000000000000000000008bd02b7b') - expect(abi.encodeParameter('bytes32', '0xdf3234')).equal('0xdf32340000000000000000000000000000000000000000000000000000000000') + expect(() => abi.encodeParameter('bytes32', '0xdf3234')).to.throw() + + expect(abi.encodeParameter('bytes32', '0xdf32340000000000000000000000000000000000000000000000000000000000')).equal('0xdf32340000000000000000000000000000000000000000000000000000000000') expect(abi.encodeParameter('bytes', '0xdf3234')).equal('0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003df32340000000000000000000000000000000000000000000000000000000000') expect(abi.encodeParameter('uint256', '2345675643')).equal('0x000000000000000000000000000000000000000000000000000000008bd02b7b') - expect(abi.encodeParameter('bytes32[]', ['0xdf3234', '0xfdfd'])).equal('0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002df32340000000000000000000000000000000000000000000000000000000000fdfd000000000000000000000000000000000000000000000000000000000000') + expect(abi.encodeParameter('bytes32[]', ['0xdf32340000000000000000000000000000000000000000000000000000000000', '0xfdfd000000000000000000000000000000000000000000000000000000000000'])).equal('0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002df32340000000000000000000000000000000000000000000000000000000000fdfd000000000000000000000000000000000000000000000000000000000000') expect(abi.decodeParameter('uint256', '0x0000000000000000000000000000000000000000000000000000000000000010')).equal("16") expect(abi.decodeParameter('string', '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000848656c6c6f212521000000000000000000000000000000000000000000000000')) @@ -110,7 +124,6 @@ describe('abi', () => { expect(f1.decode('0x000000000000000000000000abc000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003666f6f0000000000000000000000000000000000000000000000000000000000')).deep.equal({ 0: '0xabc0000000000000000000000000000000000001', 1: '0x666f6f', - __length__: 2, r1: '0xabc0000000000000000000000000000000000001', r2: '0x666f6f' }) @@ -129,7 +142,6 @@ describe('abi', () => { "1": "foo", "a1": "1", "a2": "foo", - "__length__": 2 }) expect(e2.decode('0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003666f6f0000000000000000000000000000000000000000000000000000000000', @@ -139,7 +151,6 @@ describe('abi', () => { "1": "foo", "a1": "1", "a2": "foo", - "__length__": 2 }) @@ -177,8 +188,14 @@ describe('abi', () => { .deep.equal({ "0": "1", "a1": "1", - "__length__": 1 }) - }) + const preimage = 'hello' + const hash = '0x' + keccak256('hello').toString('hex') + expect(e4.encode({ a1: preimage })).deep.equal([e4.signature, hash]) + expect(e4.decode('0x', [e4.signature, hash])).deep.equal({ + 0: hash, + a1: hash + }) + }) }) From f8c53de5fc9eb32c706d66cb511a1d2641b4e1ca Mon Sep 17 00:00:00 2001 From: qianbin Date: Tue, 19 Mar 2019 23:59:09 +0800 Subject: [PATCH 2/2] ver 0.9.1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7559bfc..218b200 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "thor-devkit", - "version": "0.9.0", + "version": "0.9.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6627cc2..e809a79 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "thor-devkit", - "version": "0.9.0", + "version": "0.9.1", "description": "Typescript library to aid DApp development on VeChain Thor", "main": "dist/index.js", "module": "es6/index.js",