diff --git a/.gitignore b/.gitignore index 12bd2818..1a91b1c5 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ eth_common/.ethereum src/temp **/*.lcov coverage.lcov +test-result diff --git a/Makefile b/Makefile index e350065b..872720e6 100644 --- a/Makefile +++ b/Makefile @@ -46,12 +46,14 @@ watch: ${TSC} --project tsconfig.json --watch lint: - ${TSLINT} + ${TSLINT} --project tsconfig.json test: node --experimental-modules --es-module-specifier-resolution=node node_modules/.bin/nyc node_modules/mocha/bin/_mocha test-fast: node --inspect --experimental-modules node_modules/.bin/_mocha +test-fast-bail: + node --inspect --experimental-modules node_modules/.bin/_mocha --bail test-coveralls: ${NYC} report --reporter=text-lcov | ${COVERALLS} --verbose diff --git a/README.md b/README.md index c6a44952..919a973a 100644 --- a/README.md +++ b/README.md @@ -141,3 +141,4 @@ make test-local You can find full documentation [here](docs/index.md). You may find issues while using this library, as it's still under development. Please report any issues you come accross. + diff --git a/docs/eth-connect.abiinput.components.md b/docs/eth-connect.abiinput.components.md deleted file mode 100644 index 708dd718..00000000 --- a/docs/eth-connect.abiinput.components.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [eth-connect](./eth-connect.md) > [AbiInput](./eth-connect.abiinput.md) > [components](./eth-connect.abiinput.components.md) - -## AbiInput.components property - -Signature: - -```typescript -components?: AbiInput[]; -``` diff --git a/docs/eth-connect.abiinput.internaltype.md b/docs/eth-connect.abiinput.internaltype.md deleted file mode 100644 index d67c5849..00000000 --- a/docs/eth-connect.abiinput.internaltype.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [eth-connect](./eth-connect.md) > [AbiInput](./eth-connect.abiinput.md) > [internalType](./eth-connect.abiinput.internaltype.md) - -## AbiInput.internalType property - -Signature: - -```typescript -internalType?: string; -``` diff --git a/docs/eth-connect.abiinput.md b/docs/eth-connect.abiinput.md index 0032ed22..6b614d7a 100644 --- a/docs/eth-connect.abiinput.md +++ b/docs/eth-connect.abiinput.md @@ -8,16 +8,13 @@ Signature: ```typescript -export interface AbiInput +export interface AbiInput extends AbiOutput ``` +Extends: [AbiOutput](./eth-connect.abioutput.md) ## Properties | Property | Type | Description | | --- | --- | --- | -| [components?](./eth-connect.abiinput.components.md) | [AbiInput](./eth-connect.abiinput.md)\[\] | (Optional) | | [indexed?](./eth-connect.abiinput.indexed.md) | boolean | (Optional) | -| [internalType?](./eth-connect.abiinput.internaltype.md) | string | (Optional) | -| [name](./eth-connect.abiinput.name.md) | string | | -| [type](./eth-connect.abiinput.type.md) | string | | diff --git a/docs/eth-connect.abiinput.name.md b/docs/eth-connect.abiinput.name.md deleted file mode 100644 index 116e5579..00000000 --- a/docs/eth-connect.abiinput.name.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [eth-connect](./eth-connect.md) > [AbiInput](./eth-connect.abiinput.md) > [name](./eth-connect.abiinput.name.md) - -## AbiInput.name property - -Signature: - -```typescript -name: string; -``` diff --git a/docs/eth-connect.abiinput.type.md b/docs/eth-connect.abiinput.type.md deleted file mode 100644 index 64fe435c..00000000 --- a/docs/eth-connect.abiinput.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [eth-connect](./eth-connect.md) > [AbiInput](./eth-connect.abiinput.md) > [type](./eth-connect.abiinput.type.md) - -## AbiInput.type property - -Signature: - -```typescript -type: string; -``` diff --git a/docs/eth-connect.bytestohex.md b/docs/eth-connect.bytestohex.md new file mode 100644 index 00000000..7751a2bd --- /dev/null +++ b/docs/eth-connect.bytestohex.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [eth-connect](./eth-connect.md) > [bytesToHex](./eth-connect.bytestohex.md) + +## bytesToHex() function + + +Signature: + +```typescript +export declare function bytesToHex(bytes: Uint8Array): string; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| bytes | Uint8Array | | + +Returns: + +string + diff --git a/docs/eth-connect.bytestoutf8string.md b/docs/eth-connect.bytestoutf8string.md new file mode 100644 index 00000000..59b340b3 --- /dev/null +++ b/docs/eth-connect.bytestoutf8string.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [eth-connect](./eth-connect.md) > [bytesToUtf8String](./eth-connect.bytestoutf8string.md) + +## bytesToUtf8String() function + +Decodes an Uint8Array or hex string into a UTF-8 string + +Signature: + +```typescript +export declare function bytesToUtf8String(bytesOrHexString: Uint8Array | string): string; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| bytesOrHexString | Uint8Array \| string | | + +Returns: + +string + diff --git a/docs/eth-connect.concatbytes.md b/docs/eth-connect.concatbytes.md new file mode 100644 index 00000000..2d1ba4d8 --- /dev/null +++ b/docs/eth-connect.concatbytes.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [eth-connect](./eth-connect.md) > [concatBytes](./eth-connect.concatbytes.md) + +## concatBytes() function + +Signature: + +```typescript +export declare function concatBytes(...buffers: Uint8Array[]): Uint8Array; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| buffers | Uint8Array\[\] | | + +Returns: + +Uint8Array + diff --git a/docs/eth-connect.fromtwoscomplement.md b/docs/eth-connect.fromtwoscomplement.md new file mode 100644 index 00000000..65944308 --- /dev/null +++ b/docs/eth-connect.fromtwoscomplement.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [eth-connect](./eth-connect.md) > [fromTwosComplement](./eth-connect.fromtwoscomplement.md) + +## fromTwosComplement() function + +If the bit N is 1 + +Signature: + +```typescript +export declare function fromTwosComplement(num: BigNumber, bits?: number): BigNumber; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| num | [BigNumber](./eth-connect.bignumber.md) | | +| bits | number | | + +Returns: + +[BigNumber](./eth-connect.bignumber.md) + diff --git a/docs/eth-connect.fromutf8.md b/docs/eth-connect.fromutf8.md deleted file mode 100644 index d962738f..00000000 --- a/docs/eth-connect.fromutf8.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [eth-connect](./eth-connect.md) > [fromUtf8](./eth-connect.fromutf8.md) - -## fromUtf8() function - -Should be called to get hex representation (prefixed by 0x) of utf8 string - -Signature: - -```typescript -export declare function fromUtf8(_str: string, allowZero?: boolean): string; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| \_str | string | | -| allowZero | boolean | | - -Returns: - -string - diff --git a/docs/eth-connect.getaddress.md b/docs/eth-connect.getaddress.md new file mode 100644 index 00000000..6aac622d --- /dev/null +++ b/docs/eth-connect.getaddress.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [eth-connect](./eth-connect.md) > [getAddress](./eth-connect.getaddress.md) + +## getAddress() function + + +Signature: + +```typescript +export declare function getAddress(address: string): string; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| address | string | | + +Returns: + +string + diff --git a/docs/eth-connect.signedisnegative.md b/docs/eth-connect.signedisnegative.md new file mode 100644 index 00000000..b32b3b99 --- /dev/null +++ b/docs/eth-connect.signedisnegative.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [eth-connect](./eth-connect.md) > [signedIsNegative](./eth-connect.signedisnegative.md) + +## signedIsNegative() function + +Check if input value is negative in twos complement + +Signature: + +```typescript +export declare function signedIsNegative(value: BigNumber, bits: number): boolean; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | [BigNumber](./eth-connect.bignumber.md) | | +| bits | number | | + +Returns: + +boolean + diff --git a/docs/eth-connect.solidityevent.types.md b/docs/eth-connect.solidityevent.types.md index 392b6e2d..11c41de2 100644 --- a/docs/eth-connect.solidityevent.types.md +++ b/docs/eth-connect.solidityevent.types.md @@ -9,7 +9,7 @@ Should be used to get filtered param types Signature: ```typescript -types(indexed: boolean): string[]; +types(indexed: boolean): AbiInput[]; ``` ## Parameters @@ -20,5 +20,5 @@ types(indexed: boolean): string[]; Returns: -string\[\] +[AbiInput](./eth-connect.abiinput.md)\[\] diff --git a/docs/eth-connect.solidityfunction._inputtypes.md b/docs/eth-connect.solidityfunction._inputtypes.md index 30ec3e8a..367e2087 100644 --- a/docs/eth-connect.solidityfunction._inputtypes.md +++ b/docs/eth-connect.solidityfunction._inputtypes.md @@ -7,5 +7,5 @@ Signature: ```typescript -_inputTypes: string[]; +_inputTypes: AbiInput[]; ``` diff --git a/docs/eth-connect.solidityfunction._outputtypes.md b/docs/eth-connect.solidityfunction._outputtypes.md index 755c06bb..c7cfee02 100644 --- a/docs/eth-connect.solidityfunction._outputtypes.md +++ b/docs/eth-connect.solidityfunction._outputtypes.md @@ -7,5 +7,5 @@ Signature: ```typescript -_outputTypes: string[]; +_outputTypes: AbiOutput[]; ``` diff --git a/docs/eth-connect.solidityfunction.md b/docs/eth-connect.solidityfunction.md index 6a386810..b0fbc571 100644 --- a/docs/eth-connect.solidityfunction.md +++ b/docs/eth-connect.solidityfunction.md @@ -23,9 +23,9 @@ export declare class SolidityFunction | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [\_constant](./eth-connect.solidityfunction._constant.md) | | boolean | | -| [\_inputTypes](./eth-connect.solidityfunction._inputtypes.md) | | string\[\] | | +| [\_inputTypes](./eth-connect.solidityfunction._inputtypes.md) | | [AbiInput](./eth-connect.abiinput.md)\[\] | | | [\_name](./eth-connect.solidityfunction._name.md) | | string | | -| [\_outputTypes](./eth-connect.solidityfunction._outputtypes.md) | | string\[\] | | +| [\_outputTypes](./eth-connect.solidityfunction._outputtypes.md) | | [AbiOutput](./eth-connect.abioutput.md)\[\] | | | [\_payable](./eth-connect.solidityfunction._payable.md) | | boolean | | | [json](./eth-connect.solidityfunction.json.md) | | [AbiFunction](./eth-connect.abifunction.md) | | | [needsToBeTransaction](./eth-connect.solidityfunction.needstobetransaction.md) | | boolean | | diff --git a/docs/eth-connect.stringtoutf8bytes.md b/docs/eth-connect.stringtoutf8bytes.md new file mode 100644 index 00000000..0f757855 --- /dev/null +++ b/docs/eth-connect.stringtoutf8bytes.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [eth-connect](./eth-connect.md) > [stringToUtf8Bytes](./eth-connect.stringtoutf8bytes.md) + +## stringToUtf8Bytes() function + +Converts a string into a Uint8Array encoded with UTF-8 + +Signature: + +```typescript +export declare function stringToUtf8Bytes(str: string): Uint8Array; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| str | string | | + +Returns: + +Uint8Array + diff --git a/docs/eth-connect.tohex.md b/docs/eth-connect.tohex.md index 32348558..3f23e9f4 100644 --- a/docs/eth-connect.tohex.md +++ b/docs/eth-connect.tohex.md @@ -11,14 +11,14 @@ And even stringifys objects before. Signature: ```typescript -export declare function toHex(val: BigNumber.Value | boolean): string; +export declare function toHex(val: BigNumber.Value | boolean | Uint8Array): string; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| val | [BigNumber.Value](./eth-connect.bignumber.value.md) \| boolean | | +| val | [BigNumber.Value](./eth-connect.bignumber.value.md) \| boolean \| Uint8Array | | Returns: diff --git a/docs/eth-connect.totwoscomplement.md b/docs/eth-connect.totwoscomplement.md index 97c8541b..6812259f 100644 --- a/docs/eth-connect.totwoscomplement.md +++ b/docs/eth-connect.totwoscomplement.md @@ -9,7 +9,7 @@ Takes and input transforms it into bignumber and if it is negative value, into t Signature: ```typescript -export declare function toTwosComplement(num: BigNumber.Value): BigNumber; +export declare function toTwosComplement(num: BigNumber.Value, bits?: number): BigNumber; ``` ## Parameters @@ -17,6 +17,7 @@ export declare function toTwosComplement(num: BigNumber.Value): BigNumber; | Parameter | Type | Description | | --- | --- | --- | | num | [BigNumber.Value](./eth-connect.bignumber.value.md) | | +| bits | number | | Returns: diff --git a/docs/eth-connect.toutf8.md b/docs/eth-connect.toutf8.md deleted file mode 100644 index 8e2550fc..00000000 --- a/docs/eth-connect.toutf8.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [eth-connect](./eth-connect.md) > [toUtf8](./eth-connect.toutf8.md) - -## toUtf8() function - -Should be called to get utf8 from it's hex representation - -Signature: - -```typescript -export declare function toUtf8(hex: string): string; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| hex | string | | - -Returns: - -string - diff --git a/docs/index.md b/docs/index.md index fd3a05f9..197c1654 100644 --- a/docs/index.md +++ b/docs/index.md @@ -35,13 +35,17 @@ | Function | Description | | --- | --- | +| [bytesToHex(bytes)](./eth-connect.bytestohex.md) | | +| [bytesToUtf8String(bytesOrHexString)](./eth-connect.bytestoutf8string.md) | Decodes an Uint8Array or hex string into a UTF-8 string | +| [concatBytes(buffers)](./eth-connect.concatbytes.md) | | | [extractDisplayName(name)](./eth-connect.extractdisplayname.md) | Should be called to get display name of contract function | | [extractTypeName(name)](./eth-connect.extracttypename.md) | Should be called to get type name of contract function | | [fromAscii(str, num)](./eth-connect.fromascii.md) | Should be called to get hex representation (prefixed by 0x) of ascii string | | [fromDecimal(value)](./eth-connect.fromdecimal.md) | Converts value to it's hex representation | -| [fromUtf8(\_str, allowZero)](./eth-connect.fromutf8.md) | Should be called to get hex representation (prefixed by 0x) of utf8 string | +| [fromTwosComplement(num, bits)](./eth-connect.fromtwoscomplement.md) | If the bit N is 1 | | [fromWei(num, unit)](./eth-connect.fromwei.md) | Takes a number of wei and converts it to any other ether unit.Possible units are: SI Short SI Full Effigy Other - kwei femtoether babbage - mwei picoether lovelace - gwei nanoether shannon nano - -- microether szabo micro - -- milliether finney milli - ether -- -- - kether -- grand - mether - gether - tether | | [fromWei(num, unit)](./eth-connect.fromwei_1.md) | | +| [getAddress(address)](./eth-connect.getaddress.md) | | | [getValueOfUnit(\_unit)](./eth-connect.getvalueofunit.md) | Returns value of unit in Wei | | [hexToBytes(hex)](./eth-connect.hextobytes.md) | | | [isAddress(address)](./eth-connect.isaddress.md) | Checks if the given string is an address | @@ -61,6 +65,8 @@ | [padLeft(str, chars, sign)](./eth-connect.padleft.md) | Should be called to pad string to expected length | | [padRight(str, chars, sign)](./eth-connect.padright.md) | Should be called to pad string to expected length | | [sha3(value, options)](./eth-connect.sha3.md) | | +| [signedIsNegative(value, bits)](./eth-connect.signedisnegative.md) | Check if input value is negative in twos complement | +| [stringToUtf8Bytes(str)](./eth-connect.stringtoutf8bytes.md) | Converts a string into a Uint8Array encoded with UTF-8 | | [toAddress(address)](./eth-connect.toaddress.md) | Transforms given string to valid 20 bytes-length addres with 0x prefix | | [toArray(value)](./eth-connect.toarray.md) | Ensures the result will be an array | | [toAscii(hex)](./eth-connect.toascii.md) | Should be called to get ascii from it's hex representation | @@ -74,8 +80,7 @@ | [toJsonRpcRequest(method, params)](./eth-connect.tojsonrpcrequest.md) | Should be called to valid json create payload object | | [toNullDecimal(value)](./eth-connect.tonulldecimal.md) | Converts value to it's decimal representation in string | | [toString\_2(value)](./eth-connect.tostring_2.md) | Converts value to string | -| [toTwosComplement(num)](./eth-connect.totwoscomplement.md) | Takes and input transforms it into bignumber and if it is negative value, into two's complement | -| [toUtf8(hex)](./eth-connect.toutf8.md) | Should be called to get utf8 from it's hex representation | +| [toTwosComplement(num, bits)](./eth-connect.totwoscomplement.md) | Takes and input transforms it into bignumber and if it is negative value, into two's complement | | [toWei(num, unit)](./eth-connect.towei.md) | Takes a number of a unit and converts it to wei.Possible units are: SI Short SI Full Effigy Other - kwei femtoether babbage - mwei picoether lovelace - gwei nanoether shannon nano - -- microether szabo micro - -- milliether finney milli - ether -- -- - kether -- grand - mether - gether - tether | | [transformToFullName(json)](./eth-connect.transformtofullname.md) | Should be used to create full function/event name from json abi | diff --git a/package-lock.json b/package-lock.json index 5c576c44..b9acaf27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -794,12 +794,6 @@ "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", "dev": true }, - "@types/utf8": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@types/utf8/-/utf8-2.1.6.tgz", - "integrity": "sha512-pRs2gYF5yoKYrgSaira0DJqVg2tFuF+Qjp838xS7K+mJyY2jJzjsrl6y17GbIa4uMRogMbxs+ghNCvKg6XyNrA==", - "dev": true - }, "@types/yargs": { "version": "15.0.13", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", @@ -4421,12 +4415,6 @@ "node-gyp-build": "^4.2.0" } }, - "utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", - "dev": true - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 590419a2..1ecb41c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eth-connect", - "version": "5.0.0", + "version": "6.0.0", "private": true, "description": "Ethereum TypeScript API, middleware to talk to a ethereum node using an async provider", "decentralandLibrary": {}, @@ -41,13 +41,11 @@ ], "all": true }, - "dependencies": {}, "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.1", "@microsoft/api-documenter": "^7.12.7", "@microsoft/api-extractor": "^7.13.2", "@types/mocha": "^8.2.1", - "@types/utf8": "^2.1.6", "bignumber.js": "^9.0.1", "coveralls": "^3.1.0", "dcl-tslint-config-standard": "^3.0.0", @@ -69,7 +67,6 @@ "tslint": "^6.1.3", "tslint-language-service": "^0.9.9", "typescript": "^4.2.3", - "utf8": "^3.0.0", "websocket": "^1.0.33" } } diff --git a/report/eth-connect.api.md b/report/eth-connect.api.md index 8bdad7f0..e1aa0ae6 100644 --- a/report/eth-connect.api.md +++ b/report/eth-connect.api.md @@ -93,17 +93,9 @@ export interface AbiFunction { } // @public (undocumented) -export interface AbiInput { - // (undocumented) - components?: AbiInput[]; +export interface AbiInput extends AbiOutput { // (undocumented) indexed?: boolean; - // (undocumented) - internalType?: string; - // (undocumented) - name: string; - // (undocumented) - type: string; } // @public (undocumented) @@ -599,11 +591,22 @@ export type BlockObject = { uncles: Array; }; +// @public (undocumented) +export function bytesToHex(bytes: Uint8Array): string; + +// @public +export function bytesToUtf8String(bytesOrHexString: Uint8Array | string): string; + // Warning: (ae-missing-release-tag) "Callback" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export type Callback = (err: Error | null, message?: any) => void; +// Warning: (ae-missing-release-tag) "concatBytes" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function concatBytes(...buffers: Uint8Array[]): Uint8Array; + // @public (undocumented) export type ConfirmedTransaction = TransactionObject & { type: TransactionType.confirmed; @@ -868,7 +871,7 @@ export function fromAscii(str: string, num?: number): string; export function fromDecimal(value: BigNumber.Value): string; // @public -export function fromUtf8(_str: string, allowZero?: boolean): string; +export function fromTwosComplement(num: BigNumber, bits?: number): BigNumber; // Warning: (ae-missing-release-tag) "fromWei" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -878,6 +881,9 @@ export function fromWei(num: BigNumber, unit: Unit): BigNumber; // @public (undocumented) export function fromWei(num: string | number, unit: Unit): string; +// @public (undocumented) +export function getAddress(address: string): string; + // @public export function getValueOfUnit(_unit: Unit): BigNumber; @@ -1241,6 +1247,11 @@ export type SHHPost = { ttl: Quantity; }; +// Warning: (ae-missing-release-tag) "signedIsNegative" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export function signedIsNegative(value: BigNumber, bits: number): boolean; + // Warning: (ae-missing-release-tag) "SolidityEvent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -1281,7 +1292,7 @@ export class SolidityEvent { requestManager: RequestManager; signature(): string; typeName(): string; - types(indexed: boolean): string[]; + types(indexed: boolean): AbiInput[]; } // Warning: (ae-missing-release-tag) "SolidityFunction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1298,7 +1309,7 @@ export class SolidityFunction { // (undocumented) extractDefaultBlock(args: any[]): string; // (undocumented) - _inputTypes: string[]; + _inputTypes: AbiInput[]; // (undocumented) json: AbiFunction; // (undocumented) @@ -1306,7 +1317,7 @@ export class SolidityFunction { // (undocumented) needsToBeTransaction: boolean; // (undocumented) - _outputTypes: string[]; + _outputTypes: AbiOutput[]; // (undocumented) _payable: boolean; signature(): string; @@ -1320,6 +1331,9 @@ export class SolidityFunction { // @public (undocumented) export type StateMutabilityType = 'pure' | 'view' | 'nonpayable' | 'payable'; +// @public +export function stringToUtf8Bytes(str: string): Uint8Array; + // @public (undocumented) export type Syncing = { startingBlock: Quantity; @@ -1365,7 +1379,7 @@ export function toData(val: BigNumber.Value): string; export function toDecimal(value: BigNumber.Value): number; // @public -export function toHex(val: BigNumber.Value | boolean): string; +export function toHex(val: BigNumber.Value | boolean | Uint8Array): string; // Warning: (ae-missing-release-tag) "toJsonRpcRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1391,10 +1405,7 @@ function toString_2(value: BigNumber.Value): string; export { toString_2 as toString } // @public -export function toTwosComplement(num: BigNumber.Value): BigNumber; - -// @public -export function toUtf8(hex: string): string; +export function toTwosComplement(num: BigNumber.Value, bits?: number): BigNumber; // @public export function toWei(num: number | string, unit: Unit): string | BigNumber; diff --git a/src/ContractFactory.ts b/src/ContractFactory.ts index cb416c34..261f0600 100644 --- a/src/ContractFactory.ts +++ b/src/ContractFactory.ts @@ -87,9 +87,7 @@ function encodeConstructorParams(abi: AbiItem[], params: any[]) { return json.type === 'constructor' && json.inputs && json.inputs.length === params.length }) .map(function (json) { - return json.inputs!.map(function (input) { - return input.type - }) + return json.inputs || [] }) .map(function (types) { return coder.encodeParams(types, params) diff --git a/src/Filter.ts b/src/Filter.ts index 5148cb65..09707c77 100644 --- a/src/Filter.ts +++ b/src/Filter.ts @@ -21,6 +21,7 @@ import { RequestManager } from './RequestManager' import * as config from './utils/config' import { FilterOptions, LogObject, TxHash, SHHFilterOptions, Data, SHHFilterMessage } from './Schema' import { future, IFuture } from 'fp-future' +import { stringToUtf8Bytes } from './utils/utf8' function safeAsync(fn: () => Promise) { return function () { @@ -42,7 +43,7 @@ function toTopic(value: any): string | null { if (strValue.indexOf('0x') === 0) { return strValue } else { - return utils.fromUtf8(strValue) + return utils.bytesToHex(stringToUtf8Bytes(strValue)) } } diff --git a/src/Schema.ts b/src/Schema.ts index 8c7332b6..4be9abbb 100644 --- a/src/Schema.ts +++ b/src/Schema.ts @@ -524,23 +524,20 @@ export interface AbiConstructor { gas?: number } + /** * @public */ -export interface AbiInput { + export interface AbiOutput { name: string type: string - indexed?: boolean - components?: AbiInput[] + components?: AbiOutput[] internalType?: string } /** * @public */ -export interface AbiOutput { - name: string - type: string - components?: AbiOutput[] - internalType?: string +export interface AbiInput extends AbiOutput { + indexed?: boolean } diff --git a/src/SolidityEvent.ts b/src/SolidityEvent.ts index dbb0a546..ffd935b3 100644 --- a/src/SolidityEvent.ts +++ b/src/SolidityEvent.ts @@ -48,14 +48,10 @@ export class SolidityEvent { * * @param decide - True if returned typed should be indexed */ - types(indexed: boolean): string[] { - return this._params - .filter(function (i) { - return i.indexed === indexed - }) - .map(function (i) { - return i.type - }) + types(indexed: boolean): AbiInput[] { + return this._params.filter(function (i) { + return i.indexed === indexed + }) } /** @@ -114,10 +110,10 @@ export class SolidityEvent { if (utils.isArray(value)) { return value.map(function (v) { - return '0x' + coder.encodeParam(i.type, v) + return '0x' + coder.encodeParams([i], [v]) }) } - return '0x' + coder.encodeParam(i.type, value) + return '0x' + coder.encodeParams([i], [value]) }) result.topics = result.topics.concat(indexedTopics) diff --git a/src/SolidityFunction.ts b/src/SolidityFunction.ts index b4ca729e..44471fc8 100644 --- a/src/SolidityFunction.ts +++ b/src/SolidityFunction.ts @@ -22,14 +22,14 @@ import * as errors from './utils/errors' import { coder } from './solidity/coder' import { RequestManager } from './RequestManager' import { Contract } from './Contract' -import { AbiFunction, Quantity } from './Schema' +import { AbiFunction, AbiInput, AbiOutput, Quantity } from './Schema' /** * This prototype should be used to call/sendTransaction to solidity functions */ export class SolidityFunction { - _inputTypes: string[] - _outputTypes: string[] + _inputTypes: AbiInput[] + _outputTypes: AbiOutput[] _constant: boolean _name: string _payable: boolean @@ -37,12 +37,8 @@ export class SolidityFunction { needsToBeTransaction: boolean constructor(public json: AbiFunction) { - this._inputTypes = (json.inputs || []).map(function (i) { - return i.type - }) - this._outputTypes = (json.outputs || []).map(function (i) { - return i.type - }) + this._inputTypes = json.inputs || [] + this._outputTypes = json.outputs || [] this._constant = !!json.constant this._payable = !!json.payable || json.stateMutability === 'payable' @@ -94,7 +90,7 @@ export class SolidityFunction { } if (args.length > this._inputTypes.length && utils.isObject(args[args.length - 1])) { - options = args[args.length - 1] + options = args.pop() } this.validateArgs(args) diff --git a/src/abi/bytes.ts b/src/abi/bytes.ts new file mode 100644 index 00000000..3755b09b --- /dev/null +++ b/src/abi/bytes.ts @@ -0,0 +1,79 @@ +import { INVALID_ARGUMENT, error } from '../utils/errors' + +/////////////////////////////// +// Exported Types + +export type Arrayish = string | ArrayLike + +/////////////////////////////// + +function addSlice(array: Uint8Array): Uint8Array { + if ('slice' in array && array.slice) { + return array + } + + array.slice = function () { + var args: any = Array.prototype.slice.call(arguments) + return new Uint8Array(Array.prototype.slice.apply(array, args)) + } + + return array +} + +export function isArrayish(value: any): value is Arrayish { + if (!value || parseInt(String(value.length)) != value.length || typeof value === 'string') { + return false + } + + for (var i = 0; i < value.length; i++) { + var v = value[i] + if (v < 0 || v >= 256 || parseInt(String(v)) != v) { + return false + } + } + + return true +} + +export function arrayify(value: Arrayish): Uint8Array { + if (value == null) { + throw error('cannot convert null value to array', INVALID_ARGUMENT, { arg: 'value', value: value }) + } + + if (value instanceof Uint8Array) { + return addSlice(new Uint8Array(value)) + } + + if (typeof value === 'string') { + let match = value.match(/^(0x)?[0-9a-fA-F]*$/) + + if (!match) { + throw error('invalid hexidecimal string', INVALID_ARGUMENT, { arg: 'value', value: value }) + } + + if (match[1] !== '0x') { + throw error('hex string must have 0x prefix', INVALID_ARGUMENT, { + arg: 'value', + value: value + }) + } + + value = value.substring(2) + if (value.length % 2) { + value = '0' + value + } + + var result: any = [] + for (var i = 0; i < value.length; i += 2) { + result.push(parseInt(value.substr(i, 2), 16)) + } + + return addSlice(new Uint8Array(result)) + } + + if (isArrayish(value)) { + return addSlice(new Uint8Array(value)) + } + + throw error('invalid arrayify value', undefined, { arg: 'value', value: value, type: typeof value }) +} diff --git a/src/abi/coder.ts b/src/abi/coder.ts new file mode 100644 index 00000000..33d3bc15 --- /dev/null +++ b/src/abi/coder.ts @@ -0,0 +1,805 @@ +'use strict' + +// See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI + +import { checkArgumentCount, checkNew, INVALID_ARGUMENT, error } from '../utils/errors' +import { BigNumber } from '../utils/BigNumber' +import { arrayify } from './bytes' +import { stringToUtf8Bytes, bytesToUtf8String } from '../utils/utf8' +import { defineReadOnly } from './properties' + +/////////////////////////////// +// Imported Types + +import { Arrayish } from './bytes' +import { + bytesToHex, + fromTwosComplement, + hexToBytes, + isAddress, + padLeft, + toBigNumber, + toTwosComplement, + concatBytes, + toHex, + getAddress +} from '../utils/utils' +import { inputAddressFormatter } from '../utils/formatters' +import { AbiEvent, AbiFunction, AbiInput, AbiOutput } from '../Schema' +import { MaxUint256, NegativeOne, One, Zero } from './constants' +import { parseParamType } from './parser' + +/////////////////////////////// +// Exported Types + +export type CoerceFunc = (type: string, value: any) => any + +/////////////////////////////// + +const paramTypeBytes = new RegExp(/^bytes([0-9]*)$/) +const paramTypeNumber = new RegExp(/^(u?int)([0-9]*)$/) +const paramTypeArray = new RegExp(/^(.*)\[([0-9]*)\]$/) + +export const defaultCoerceFunc: CoerceFunc = function (type: string, value: any): any { + var match = type.match(paramTypeNumber) + if (match && parseInt(match[2]) <= 48) { + return value.toNumber() + } + return value +} + +export function formatParamType(paramType: Readonly): string { + return getParamCoder(defaultCoerceFunc, paramType).type +} + +// @TODO: Allow a second boolean to expose names and modifiers +export function formatSignature(fragment: AbiEvent | AbiFunction): string { + return fragment.name + '(' + (fragment.inputs || []).map((i) => formatParamType(i)).join(',') + ')' +} + +/////////////////////////////////// +// Coders + +type DecodedResult = { consumed: number; value: T } +abstract class Coder { + readonly coerceFunc: CoerceFunc + readonly name: string + readonly type: string + readonly localName: string + readonly dynamic: boolean + constructor(coerceFunc: CoerceFunc, name: string, type: string, localName: string = '', dynamic: boolean) { + this.coerceFunc = coerceFunc + this.name = name + this.type = type + this.localName = localName + this.dynamic = dynamic + } + + abstract encode(value: any): Uint8Array + abstract decode(data: Uint8Array, offset: number): DecodedResult +} + +// Clones the functionality of an existing Coder, but without a localName +class CoderAnonymous extends Coder { + private coder!: Coder + constructor(coder: Coder) { + super(coder.coerceFunc, coder.name, coder.type, undefined, coder.dynamic) + defineReadOnly(this, 'coder', coder) + } + encode(value: any): Uint8Array { + return this.coder.encode(value) + } + decode(data: Uint8Array, offset: number): DecodedResult { + return this.coder.decode(data, offset) + } +} + +class CoderNull extends Coder { + constructor(coerceFunc: CoerceFunc, localName: string) { + super(coerceFunc, 'null', '', localName, false) + } + + encode(_value: any): Uint8Array { + return arrayify([]) + } + + decode(data: Uint8Array, offset: number): DecodedResult { + if (offset > data.length) { + throw new Error('invalid null') + } + return { + consumed: 0, + value: this.coerceFunc('null', undefined) + } + } +} + +function maskn(v: BigNumber, bits: number): BigNumber { + return new BigNumber(v.toString(2).substr(-bits), 2) +} + +class CoderNumber extends Coder { + readonly size: number + readonly signed: boolean + constructor(coerceFunc: CoerceFunc, size: number, signed: boolean, localName: string) { + const name = (signed ? 'int' : 'uint') + size * 8 + super(coerceFunc, name, name, localName, false) + + this.size = size + this.signed = signed + } + + encode(value: BigNumber.Value): Uint8Array { + try { + let v = toBigNumber(value) + + if (this.signed) { + let bounds = maskn(MaxUint256, this.size * 8 - 1) + if (v.gt(bounds)) { + throw new Error('out-of-bounds') + } + bounds = bounds.plus(One).multipliedBy(NegativeOne) + if (v.lt(bounds)) { + throw new Error('out-of-bounds') + } + } else if (v.lt(Zero) || v.gt(maskn(MaxUint256, this.size * 8))) { + throw new Error('out-of-bounds') + } + + v = maskn(toTwosComplement(v, this.size * 8), this.size * 8) + + if (this.signed) { + v = toTwosComplement(fromTwosComplement(v, this.size * 8), 256) + } + + let result = padLeft(toTwosComplement(v).toString(16), 64) + + if (result.indexOf('NaN') != -1) { + throw error('invalid number value, NaN', INVALID_ARGUMENT, { + arg: this.localName, + coderType: this.name, + value: value, + v, + twosComplement: toTwosComplement(v), + twosComplement16: toTwosComplement(v).toString(16), + pad: padLeft(toTwosComplement(v).toString(16), 64), + size: this.size + }) + } + + return hexToBytes(result) + } catch (error) { + throw error('invalid number value', INVALID_ARGUMENT, { + arg: this.localName, + coderType: this.name, + value: value, + message: error.toString() + }) + } + } + + decode(data: Uint8Array, offset: number): DecodedResult { + if (data.length < offset + 32) { + throw error('insufficient data for ' + this.name + ' type', INVALID_ARGUMENT, { + arg: this.localName, + coderType: this.name, + value: toHex(data.slice(offset, offset + 32)) + }) + } + var junkLength = 32 - this.size + var value = new BigNumber(bytesToHex(data.slice(offset + junkLength, offset + 32)), 16) + + if (this.signed) { + value = fromTwosComplement(value, this.size * 8) + } else { + value = maskn(value, this.size * 8) + } + + return { + consumed: 32, + value: this.coerceFunc(this.name, value) + } + } +} +var uint256Coder = new CoderNumber( + function (_type: string, value: any) { + return value + }, + 32, + false, + 'none' +) + +class CoderBoolean extends Coder { + constructor(coerceFunc: CoerceFunc, localName: string) { + super(coerceFunc, 'bool', 'bool', localName, false) + } + + encode(value: boolean): Uint8Array { + return uint256Coder.encode(!!value ? 1 : 0) + } + + decode(data: Uint8Array, offset: number): DecodedResult { + try { + var result = uint256Coder.decode(data, offset) + } catch (error) { + if (error.reason === 'insufficient data for uint256 type') { + throw error('insufficient data for boolean type', INVALID_ARGUMENT, { + arg: this.localName, + coderType: 'boolean', + value: error.value + }) + } + throw error + } + return { + consumed: result.consumed, + value: this.coerceFunc('bool', !result.value.isZero()) + } + } +} + +class CoderFixedBytes extends Coder { + readonly length: number + constructor(coerceFunc: CoerceFunc, length: number, localName: string) { + const name = 'bytes' + length + super(coerceFunc, name, name, localName, false) + this.length = length + } + + encode(value: Arrayish): Uint8Array { + var result = new Uint8Array(32) + + try { + if (typeof value == 'string') { + if (value.length % 2 !== 0) { + throw new Error(`hex string cannot be odd-length`) + } + } + + let data = arrayify(value) + + if (data.length > this.length) { + throw new Error(`incorrect data length`) + } + + result.set(data) + } catch (error) { + throw error('invalid ' + this.name + ' value. Use hex strings or Uint8Array', INVALID_ARGUMENT, { + arg: this.localName, + coderType: this.name, + value: error.value || value, + details: error.message + }) + } + + return result + } + + decode(data: Uint8Array, offset: number): DecodedResult { + if (data.length < offset + 32) { + throw error('insufficient data for ' + this.name + ' type', INVALID_ARGUMENT, { + arg: this.localName, + coderType: this.name, + value: toHex(data.slice(offset, offset + 32)) + }) + } + + return { + consumed: 32, + value: this.coerceFunc(this.name, data.slice(offset, offset + this.length)) + } + } +} + +class CoderFunction extends CoderFixedBytes { + constructor(coerceFunc: CoerceFunc, localName: string) { + super(coerceFunc, 24, localName) + } +} + +class CoderAddress extends Coder { + constructor(coerceFunc: CoerceFunc, localName: string) { + super(coerceFunc, 'address', 'address', localName, false) + } + encode(inputAddress: string): Uint8Array { + let result = new Uint8Array(32) + const address = inputAddress.trim() + if (!isAddress(address)) { + throw error(`invalid address format`, INVALID_ARGUMENT, { + arg: this.localName, + coderType: 'address', + value: inputAddress + }) + } + try { + result.set(hexToBytes(inputAddressFormatter(address)), 12) + } catch (error) { + throw error(`invalid address (${error.message})`, INVALID_ARGUMENT, { + arg: this.localName, + coderType: 'address', + value: inputAddress + }) + } + return result + } + decode(data: Uint8Array, offset: number): DecodedResult { + if (data.length < offset + 32) { + throw error('insufficuent data for address type', INVALID_ARGUMENT, { + arg: this.localName, + coderType: 'address', + value: toHex(data.slice(offset, offset + 32)), + missingBytes: offset + 32 - data.length + }) + } + return { + consumed: 32, + value: this.coerceFunc('address', getAddress(toHex(data.slice(offset + 12, offset + 32)))) + } + } +} + +function _encodeDynamicBytes(value: Uint8Array): Uint8Array { + var dataLength = 32 * Math.ceil(value.length / 32) + var padding = new Uint8Array(dataLength - value.length) + + return concatBytes(uint256Coder.encode(value.length), value, padding) +} + +function _decodeDynamicBytes(data: Uint8Array, offset: number, localName: string): DecodedResult { + if (data.length < offset + 32) { + throw error('insufficient data for dynamicBytes length', INVALID_ARGUMENT, { + arg: localName, + coderType: 'dynamicBytes', + value: toHex(data.slice(offset, offset + 32)) + }) + } + + var length = uint256Coder.decode(data, offset).value + try { + length = length.toNumber() + } catch (error) { + throw error('dynamic bytes count too large', INVALID_ARGUMENT, { + arg: localName, + coderType: 'dynamicBytes', + value: length.toString() + }) + } + + if (data.length < offset + 32 + length) { + throw error('insufficient data for dynamicBytes type', INVALID_ARGUMENT, { + arg: localName, + coderType: 'dynamicBytes', + value: toHex(data.slice(offset, offset + 32 + length)) + }) + } + + return { + consumed: 32 + 32 * Math.ceil(length / 32), + value: data.slice(offset + 32, offset + 32 + length) + } +} + +class CoderDynamicBytes extends Coder { + constructor(coerceFunc: CoerceFunc, localName: string) { + super(coerceFunc, 'bytes', 'bytes', localName, true) + } + encode(value: Arrayish): Uint8Array { + try { + return _encodeDynamicBytes(arrayify(value)) + } catch (error) { + throw error('invalid bytes value', INVALID_ARGUMENT, { + arg: this.localName, + coderType: 'bytes', + value: error.value + }) + } + } + + decode(data: Uint8Array, offset: number): DecodedResult { + var result = _decodeDynamicBytes(data, offset, this.localName) + result.value = this.coerceFunc('bytes', result.value) + return result + } +} + +class CoderString extends Coder { + constructor(coerceFunc: CoerceFunc, localName: string) { + super(coerceFunc, 'string', 'string', localName, true) + } + + encode(value: string): Uint8Array { + if (typeof value !== 'string') { + throw error('invalid string value', INVALID_ARGUMENT, { + arg: this.localName, + coderType: 'string', + value: value + }) + } + return _encodeDynamicBytes(stringToUtf8Bytes(value)) + } + + decode(data: Uint8Array, offset: number): DecodedResult { + var result = _decodeDynamicBytes(data, offset, this.localName) + result.value = this.coerceFunc('string', bytesToUtf8String(result.value)) + return result + } +} + +function alignSize(size: number): number { + return 32 * Math.ceil(size / 32) +} + +function pack(coders: Array, values: Array): Uint8Array { + if (Array.isArray(values)) { + // do nothing + } else if (values && typeof values === 'object') { + var arrayValues: Array = [] + coders.forEach(function (coder) { + arrayValues.push((values)[coder.localName]) + }) + values = arrayValues + } else { + throw error('invalid tuple value', INVALID_ARGUMENT, { + coderType: 'tuple', + value: values + }) + } + + if (coders.length !== values.length) { + throw error('types/value length mismatch', INVALID_ARGUMENT, { + coderType: 'tuple', + value: values + }) + } + + var parts: Array<{ dynamic: boolean; value: any }> = [] + + coders.forEach(function (coder, index) { + parts.push({ dynamic: coder.dynamic, value: coder.encode(values[index]) }) + }) + + var staticSize = 0, + dynamicSize = 0 + parts.forEach(function (part) { + if (part.dynamic) { + staticSize += 32 + dynamicSize += alignSize(part.value.length) + } else { + staticSize += alignSize(part.value.length) + } + }) + + var offset = 0, + dynamicOffset = staticSize + var data = new Uint8Array(staticSize + dynamicSize) + + parts.forEach(function (part) { + if (part.dynamic) { + //uint256Coder.encode(dynamicOffset).copy(data, offset); + data.set(uint256Coder.encode(dynamicOffset), offset) + offset += 32 + + //part.value.copy(data, dynamicOffset); @TODO + data.set(part.value, dynamicOffset) + dynamicOffset += alignSize(part.value.length) + } else { + //part.value.copy(data, offset); @TODO + data.set(part.value, offset) + offset += alignSize(part.value.length) + } + }) + + return data +} + +export class Tuple extends Array { + [key: string]: any +} + +function unpack(coders: Array, data: Uint8Array, offset: number): DecodedResult { + var baseOffset = offset + var consumed = 0 + + var value: any[] = [] + coders.forEach(function (coder) { + if (coder.dynamic) { + var dynamicOffset = uint256Coder.decode(data, offset) + var result = coder.decode(data, baseOffset + dynamicOffset.value.toNumber()) + // The dynamic part is leap-frogged somewhere else; doesn't count towards size + result.consumed = dynamicOffset.consumed + } else { + var result = coder.decode(data, offset) + } + + if (result.value != undefined) { + value.push(result.value) + } + + offset += result.consumed + consumed += result.consumed + }) + + return { + value: value, + consumed: consumed + } +} + +function unpackWithNames(coders: Array, data: Uint8Array, offset: number): DecodedResult { + var baseOffset = offset + var consumed = 0 + var value = new Tuple() + coders.forEach(function (coder) { + if (coder.dynamic) { + var dynamicOffset = uint256Coder.decode(data, offset) + var result = coder.decode(data, baseOffset + dynamicOffset.value.toNumber()) + // The dynamic part is leap-frogged somewhere else; doesn't count towards size + result.consumed = dynamicOffset.consumed + } else { + var result = coder.decode(data, offset) + } + + if (result.value != undefined) { + value.push(result.value) + } + + offset += result.consumed + consumed += result.consumed + }) + + coders.forEach(function (coder: Coder, index: number) { + let name: string = coder.localName + if (!name) { + return + } + + if (name === 'length') { + name = '_length' + } + + if (value[name] != null) { + return + } + + value[name] = value[index] + }) + + return { + value: value, + consumed: consumed + } +} + +class CoderArray extends Coder { + readonly coder: Coder + readonly length: number + constructor(coerceFunc: CoerceFunc, coder: Coder, length: number, localName: string) { + const type = coder.type + '[' + (length >= 0 ? length : '') + ']' + const dynamic = length === -1 || coder.dynamic + super(coerceFunc, 'array', type, localName, dynamic) + + this.coder = coder + this.length = length + } + + encode(value: Array): Uint8Array { + if (!Array.isArray(value)) { + throw error('expected array value', INVALID_ARGUMENT, { + arg: this.localName, + coderType: 'array', + value: value + }) + } + + var count = this.length + + var result = new Uint8Array(0) + if (count === -1) { + count = value.length + result = uint256Coder.encode(count) + } + + checkArgumentCount(count, value.length, 'in coder array' + (this.localName ? ' ' + this.localName : '')) + + var coders: Coder[] = [] + for (var i = 0; i < value.length; i++) { + coders.push(this.coder) + } + + return concatBytes(result, pack(coders, value)) + } + + decode(data: Uint8Array, offset: number) { + // @TODO: + //if (data.length < offset + length * 32) { throw new Error('invalid array'); } + + var consumed = 0 + + var count = this.length + + if (count === -1) { + try { + var decodedLength = uint256Coder.decode(data, offset) + } catch (error) { + throw error('insufficient data for dynamic array length', INVALID_ARGUMENT, { + arg: this.localName, + coderType: 'array', + value: error.value + }) + } + try { + count = decodedLength.value.toNumber() + } catch (error) { + throw error('array count too large', INVALID_ARGUMENT, { + arg: this.localName, + coderType: 'array', + value: decodedLength.value.toString() + }) + } + consumed += decodedLength.consumed + offset += decodedLength.consumed + } + + var coders: Coder[] = [] + for (var i = 0; i < count; i++) { + coders.push(new CoderAnonymous(this.coder)) + } + + var result = unpack(coders, data, offset) + result.consumed += consumed + result.value = this.coerceFunc(this.type, result.value) + return result + } +} + +class CoderTuple extends Coder { + readonly coders: Array + constructor(coerceFunc: CoerceFunc, coders: Array, localName: string) { + var dynamic = false + var types: Array = [] + coders.forEach(function (coder) { + if (coder.dynamic) { + dynamic = true + } + types.push(coder.type) + }) + var type = 'tuple(' + types.join(',') + ')' + + super(coerceFunc, 'tuple', type, localName, dynamic) + this.coders = coders + } + + encode(value: Array): Uint8Array { + return pack(this.coders, value) + } + + decode(data: Uint8Array, offset: number): DecodedResult { + var result = unpackWithNames(this.coders, data, offset) + result.value = this.coerceFunc(this.type, result.value) + return result + } +} + +// @TODO: Is there a way to return "class"? +const paramTypeSimple: { [key: string]: any } = { + address: CoderAddress, + bool: CoderBoolean, + string: CoderString, + bytes: CoderDynamicBytes, + function: CoderFunction +} + +function getTupleParamCoder( + coerceFunc: CoerceFunc, + components: ReadonlyArray>, + localName: string +): CoderTuple { + if (!components) { + components = [] + } + var coders = components.map((component) => getParamCoder(coerceFunc, component)) + + return new CoderTuple(coerceFunc, coders, localName) +} + +function getParamCoder(coerceFunc: CoerceFunc, param: Readonly): Coder { + var coder = paramTypeSimple[param.type] + if (coder) { + return new coder(coerceFunc, param.name) + } + var match = param.type.match(paramTypeNumber) + if (match) { + let size = parseInt(match[2] || '256') + if (size === 0 || size > 256 || size % 8 !== 0) { + throw error('invalid ' + match[1] + ' bit length', INVALID_ARGUMENT, { + arg: 'param', + value: param + }) + } + return new CoderNumber(coerceFunc, size / 8, match[1] === 'int', param.name!) + } + + var match = param.type.match(paramTypeBytes) + if (match) { + let size = parseInt(match[1]) + if (size === 0 || size > 32) { + throw error('invalid bytes length', INVALID_ARGUMENT, { + arg: 'param', + value: param + }) + } + return new CoderFixedBytes(coerceFunc, size, param.name!) + } + + var match = param.type.match(paramTypeArray) + + if (match) { + const newParam = { ...param } + let size = parseInt(match[2] || '-1') + newParam.type = match[1] + return new CoderArray(coerceFunc, getParamCoder(coerceFunc, newParam), size, param.name!) + } + + if (param.type.substring(0, 5) === 'tuple') { + return getTupleParamCoder(coerceFunc, param.components!, param.name!) + } + + if (param.type === '') { + return new CoderNull(coerceFunc, param.name!) + } + + throw error('invalid type', INVALID_ARGUMENT, { + arg: 'type', + value: param.type, + fullType: param + }) +} + +export class AbiCoder { + readonly coerceFunc!: CoerceFunc + constructor(coerceFunc?: CoerceFunc) { + checkNew(this, AbiCoder) + + if (!coerceFunc) { + coerceFunc = defaultCoerceFunc + } + + defineReadOnly(this, 'coerceFunc', coerceFunc) + } + + encode(types: ReadonlyArray>, values: Array): Uint8Array { + if (types.length !== values.length) { + throw error('types/values length mismatch', INVALID_ARGUMENT, { + count: { types: types.length, values: values.length }, + value: { types: types, values: values } + }) + } + + const coders = types + .map(($) => { + if (typeof $ === 'string') { + return parseParamType($) + } else { + return $ + } + }) + .map(($) => getParamCoder(this.coerceFunc, $)) + + return new CoderTuple(this.coerceFunc, coders, '_').encode(values) + } + + decode(types: ReadonlyArray>, data: Uint8Array): any { + const coders = types + .map(($) => { + if (typeof $ === 'string') { + return parseParamType($) + } else { + return $ + } + }) + .map(($) => getParamCoder(this.coerceFunc, $)) + + return new CoderTuple(this.coerceFunc, coders, '_').decode(data, 0).value + } +} diff --git a/src/abi/constants.ts b/src/abi/constants.ts new file mode 100644 index 00000000..e6875377 --- /dev/null +++ b/src/abi/constants.ts @@ -0,0 +1,14 @@ +import { BigNumber } from '../utils/BigNumber' + +// NFKD (decomposed) +//const EtherSymbol = '\uD835\uDF63'; + +// NFKC (composed) +const EtherSymbol = '\u039e' + +const NegativeOne: BigNumber = new BigNumber(-1) +const Zero: BigNumber = new BigNumber(0) +const One: BigNumber = new BigNumber(1) +const MaxUint256: BigNumber = new BigNumber('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff') + +export { EtherSymbol, NegativeOne, Zero, One, MaxUint256 } diff --git a/src/abi/parser.ts b/src/abi/parser.ts new file mode 100644 index 00000000..be814479 --- /dev/null +++ b/src/abi/parser.ts @@ -0,0 +1,362 @@ +/////////////////////////////////// +// Parsing for Solidity Signatures + +import { AbiEvent, AbiFunction, AbiInput } from '../Schema' +import { formatSignature } from './coder' + +export function parseParamType(type: string): AbiInput { + return parseParam(type, true) +} + +export function parseSignatureFunction(fragment: string): AbiFunction { + var abi: AbiFunction = { + constant: false, + inputs: [], + name: '', + outputs: [], + payable: false, + type: 'function' + } + + let comps = fragment.split('@') + if (comps.length !== 1) { + if (comps.length > 2) { + throw new Error('invalid signature') + } + if (!comps[1].match(/^[0-9]+$/)) { + throw new Error('invalid signature gas') + } + abi.gas = parseFloat(comps[1]) + fragment = comps[0] + } + + comps = fragment.split(' returns ') + var left = comps[0].match(regexParen) + if (!left) { + throw new Error('invalid signature') + } + + abi.name = left[1].trim() + if (!abi.name.match(regexIdentifier)) { + throw new Error('invalid identifier: "' + left[1] + '"') + } + + splitNesting(left[2]).forEach(function (param) { + abi.inputs!.push(parseParam(param)) + }) + + left[3].split(' ').forEach(function (modifier) { + switch (modifier) { + case 'constant': + abi.constant = true + break + case 'payable': + abi.payable = true + abi.stateMutability = 'payable' + break + case 'pure': + abi.constant = true + abi.stateMutability = 'pure' + break + case 'view': + abi.constant = true + abi.stateMutability = 'view' + break + case 'external': + case 'public': + case '': + break + } + }) + + // We have outputs + if (comps.length > 1) { + var right = comps[1].match(regexParen) + if (!right || right[1].trim() != '' || right[3].trim() != '') { + throw new Error('unexpected tokens') + } + + splitNesting(right[2]).forEach(function (param) { + abi.outputs!.push(parseParam(param)) + }) + } + + if (abi.name === 'constructor') { + ;(abi as any).type = 'constructor' + + if (abi.outputs!.length) { + throw new Error('constructor may not have outputs') + } + + // delete abi.name + // delete abi.outputs + } + + return abi +} + +const regexParen = new RegExp('^([^)(]*)\\((.*)\\)([^)(]*)$') +const regexIdentifier = new RegExp('^[A-Za-z_][A-Za-z0-9_]*$') + +function verifyType(type: string): string { + // These need to be transformed to their full description + if (type.match(/^uint($|[^1-9])/)) { + type = 'uint256' + type.substring(4) + } else if (type.match(/^int($|[^1-9])/)) { + type = 'int256' + type.substring(3) + } + + return type +} + +type ParseState = { + allowArray?: boolean + allowName?: boolean + allowParams?: boolean + allowType?: boolean + readArray?: boolean +} + +type ParseNode = { + parent?: any + type?: string + name?: string + state?: ParseState + indexed?: boolean + components?: Array +} + +function parseParam(param: string, allowIndexed?: boolean): AbiInput { + function throwError(i: number) { + return new Error('unexpected character "' + param[i] + '" at position ' + i + ' in "' + param + '"') + } + + var parent: ParseNode = { type: '', name: '', state: { allowType: true } } + var node: any = parent + + for (var i = 0; i < param.length; i++) { + var c = param[i] + switch (c) { + case '(': + if (!node.state.allowParams) { + throw throwError(i) + } + node.state.allowType = false + node.type = verifyType(node.type) + node.components = [{ type: '', name: '', parent: node, state: { allowType: true } }] + node = node.components[0] + break + + case ')': + delete node.state + if (allowIndexed && node.name === 'indexed') { + node.indexed = true + node.name = '' + } + node.type = verifyType(node.type) + + var child = node + node = node.parent + if (!node) { + throw throwError(i) + } + delete child.parent + node.state.allowParams = false + node.state.allowName = true + node.state.allowArray = true + break + + case ',': + delete node.state + if (allowIndexed && node.name === 'indexed') { + node.indexed = true + node.name = '' + } + node.type = verifyType(node.type) + + var sibling: ParseNode = { type: '', name: '', parent: node.parent, state: { allowType: true } } + node.parent.components.push(sibling) + delete node.parent + node = sibling + break + + // Hit a space... + case ' ': + // If reading type, the type is done and may read a param or name + if (node.state.allowType) { + if (node.type !== '') { + node.type = verifyType(node.type) + delete node.state.allowType + node.state.allowName = true + node.state.allowParams = true + } + } + + // If reading name, the name is done + if (node.state.allowName) { + if (node.name !== '') { + if (allowIndexed && node.name === 'indexed') { + node.indexed = true + node.name = '' + } else { + node.state.allowName = false + } + } + } + + break + + case '[': + if (!node.state.allowArray) { + throw throwError(i) + } + + node.type += c + + node.state.allowArray = false + node.state.allowName = false + node.state.readArray = true + break + + case ']': + if (!node.state.readArray) { + throw throwError(i) + } + + node.type += c + + node.state.readArray = false + node.state.allowArray = true + node.state.allowName = true + break + + default: + if (node.state.allowType) { + node.type += c + node.state.allowParams = true + node.state.allowArray = true + } else if (node.state.allowName) { + node.name += c + delete node.state.allowArray + } else if (node.state.readArray) { + node.type += c + } else { + throw throwError(i) + } + } + } + + if (node.parent) { + throw new Error('unexpected eof') + } + + delete parent.state + + if (allowIndexed && node.name === 'indexed') { + node.indexed = true + node.name = '' + } + + parent.type = verifyType(parent.type!) + + return parent as AbiInput +} + +// @TODO: Better return type +export function parseSignatureEvent(fragment: string): AbiEvent { + var abi: AbiEvent = { + anonymous: false, + inputs: [], + name: '', + type: 'event' + } + + var match = fragment.match(regexParen) + if (!match) { + throw new Error('invalid event: ' + fragment) + } + + abi.name = match[1].trim() + + splitNesting(match[2]).forEach(function (param) { + param = parseParam(param, true) + param.indexed = !!param.indexed + abi.inputs!.push(param) + }) + + match[3].split(' ').forEach(function (modifier) { + switch (modifier) { + case 'anonymous': + abi.anonymous = true + break + case '': + break + } + }) + + if (abi.name && !abi.name.match(regexIdentifier)) { + throw new Error('invalid identifier: "' + abi.name + '"') + } + + return abi +} + +function splitNesting(value: string): Array { + value = value.trim() + + var result: string[] = [] + var accum = '' + var depth = 0 + for (var offset = 0; offset < value.length; offset++) { + var c = value[offset] + if (c === ',' && depth === 0) { + result.push(accum) + accum = '' + } else { + accum += c + if (c === '(') { + depth++ + } else if (c === ')') { + depth-- + if (depth === -1) { + throw new Error('unbalanced parenthsis') + } + } + } + } + if (accum) { + result.push(accum) + } + + return result +} + +export function parseSignature(fragment: string): AbiEvent | AbiFunction { + if (typeof fragment === 'string') { + // Make sure the "returns" is surrounded by a space and all whitespace is exactly one space + fragment = fragment.replace(/\(/g, ' (').replace(/\)/g, ') ').replace(/\s+/g, ' ') + fragment = fragment.trim() + + if (fragment.substring(0, 6) === 'event ') { + const ret = parseSignatureEvent(fragment.substring(6).trim()) + + // check if it throws + formatSignature(ret) + + return ret + } else { + if (fragment.substring(0, 9) === 'function ') { + fragment = fragment.substring(9) + } + + const ret = parseSignatureFunction(fragment.trim()) + + // check if it throws + formatSignature(ret) + + return ret + } + } + + throw new Error('unknown signature') +} diff --git a/src/abi/properties.ts b/src/abi/properties.ts new file mode 100644 index 00000000..df06decc --- /dev/null +++ b/src/abi/properties.ts @@ -0,0 +1,7 @@ +export function defineReadOnly(object: any, name: string, value: any): void { + Object.defineProperty(object, name, { + enumerable: true, + value: value, + writable: false + }) +} diff --git a/src/index.ts b/src/index.ts index 4bf154ef..30c6708d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,6 +19,7 @@ export * from './providers/HTTPProvider' export * from './providers/WebSocketProvider' export * from './utils/utils' export * from './Schema' +export * from './utils/utf8' export * from './utils/jsonrpc' export { IFuture } from 'fp-future' @@ -36,4 +37,5 @@ export { SolidityFunction } from './SolidityFunction' export { SolidityEvent } from './SolidityEvent' import { RequestManager } from './RequestManager' + export default RequestManager diff --git a/src/solidity/address.ts b/src/solidity/address.ts deleted file mode 100644 index 64d473f1..00000000 --- a/src/solidity/address.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as f from './formatters' -import { SolidityType } from './type' - -export class SolidityTypeAddress extends SolidityType { - constructor() { - super({ - inputFormatter: f.formatInputAddress, - outputFormatter: f.formatOutputAddress - }) - } - - // tslint:disable-next-line:prefer-function-over-method - isType(name: string) { - return !!name.match(/address(\[([0-9]*)\])?/) - } -} diff --git a/src/solidity/bool.ts b/src/solidity/bool.ts deleted file mode 100644 index 7423726b..00000000 --- a/src/solidity/bool.ts +++ /dev/null @@ -1,24 +0,0 @@ -import * as f from './formatters' -import { SolidityType } from './type' - -/** - * SolidityTypeBool is a prootype that represents bool type - * It matches: - * bool - * bool[] - * bool[4] - * bool[][] - * bool[3][] - * bool[][6][], ... - */ -export class SolidityTypeBool extends SolidityType { - constructor() { - super({ - inputFormatter: f.formatInputBool, - outputFormatter: f.formatOutputBool - }) - } - isType(name: string) { - return !!name.match(/^bool(\[([0-9]*)\])*$/) - } -} diff --git a/src/solidity/bytes.ts b/src/solidity/bytes.ts deleted file mode 100644 index e4728ca5..00000000 --- a/src/solidity/bytes.ts +++ /dev/null @@ -1,28 +0,0 @@ -import * as f from './formatters' -import { SolidityType } from './type' - -/** - * SolidityTypeBytes is a prototype that represents the bytes type. - * It matches: - * bytes - * bytes[] - * bytes[4] - * bytes[][] - * bytes[3][] - * bytes[][6][], ... - * bytes32 - * bytes8[4] - * bytes[3][] - */ -export class SolidityTypeBytes extends SolidityType { - constructor() { - super({ - inputFormatter: f.formatInputBytes, - outputFormatter: f.formatOutputBytes - }) - } - // tslint:disable-next-line:prefer-function-over-method - isType(name: string) { - return !!name.match(/^bytes([0-9]{1,})(\[([0-9]*)\])*$/) - } -} diff --git a/src/solidity/coder.ts b/src/solidity/coder.ts index 1ff62d44..069c4d48 100644 --- a/src/solidity/coder.ts +++ b/src/solidity/coder.ts @@ -15,64 +15,17 @@ along with web3.js. If not, see . */ -import * as formatter from './formatters' +import { AbiCoder } from '../abi/coder' +import { AbiOutput } from '../Schema' +import * as utils from '../utils/utils' +import { bytesToHex } from '../utils/utils' -import { SolidityTypeAddress } from './address' -import { SolidityTypeBool } from './bool' -import { SolidityTypeInt } from './int' -import { SolidityTypeUInt } from './uint' -import { SolidityTypeDynamicBytes } from './dynamicbytes' -import { SolidityTypeString } from './string' -import { SolidityTypeReal } from './real' -import { SolidityTypeUReal } from './ureal' -import { SolidityTypeBytes } from './bytes' -import { SolidityType } from './type' - -function isDynamic(solidityType: SolidityType, type: string) { - return solidityType.isDynamicType(type) || solidityType.isDynamicArray(type) -} +const ethersAbiCoder = new AbiCoder() /** * SolidityCoder prototype should be used to encode/decode solidity params of any type */ -export class SolidityCoder { - _types: SolidityType[] - - constructor(types: SolidityType[]) { - this._types = types - } - - /** - * This method should be used to transform type to SolidityType - * - * @param {string} type - * @returns {SolidityType} - * @throws {Error} throws if no matching type is found - */ - _requireType(type: string): SolidityType { - let solidityType = this._types.filter(function (t) { - return t.isType(type) - })[0] - - if (!solidityType) { - throw Error('invalid solidity type!: ' + type) - } - - return solidityType - } - - /** - * Should be used to encode plain param - * - * @method encodeParam - * @param {string} type - * @param {object} plain param - * @return {string} encoded plain param - */ - encodeParam(type: string, param: any): string { - return this.encodeParams([type], [param]) - } - +export namespace coder { /** * Should be used to encode list of params * @@ -81,123 +34,8 @@ export class SolidityCoder { * @param {Array} params * @return {string} encoded list of params */ - encodeParams(types: string[], params: any[]): string { - let solidityTypes = this.getSolidityTypes(types) - - let encodeds = solidityTypes.map(function (solidityType, index) { - return solidityType.encode(params[index], types[index]) - }) - - let dynamicOffset = solidityTypes.reduce(function (acc, solidityType, index) { - let staticPartLength = solidityType.staticPartLength(types[index]) - let roundedStaticPartLength = Math.floor((staticPartLength + 31) / 32) * 32 - - return acc + (isDynamic(solidityTypes[index], types[index]) ? 32 : roundedStaticPartLength) - }, 0) - - let result = this.encodeMultiWithOffset(types, solidityTypes, encodeds, dynamicOffset) - - return result - } - - encodeMultiWithOffset( - types: string[], - solidityTypes: SolidityType[], - encodeds: (string | string[])[], - _dynamicOffset: number - ): string { - let dynamicOffset = _dynamicOffset - let results: string[] = [] - - types.forEach((_, i) => { - if (isDynamic(solidityTypes[i], types[i])) { - results.push(formatter.formatInputInt(dynamicOffset).encode()) - let e = this.encodeWithOffset(types[i], solidityTypes[i], encodeds[i], dynamicOffset) - dynamicOffset += e.length / 2 - } else { - // don't add length to dynamicOffset. it's already counted - results.push(this.encodeWithOffset(types[i], solidityTypes[i], encodeds[i], dynamicOffset)) - } - - // TODO: figure out nested arrays - }) - - types.forEach((_, i) => { - if (isDynamic(solidityTypes[i], types[i])) { - let e = this.encodeWithOffset(types[i], solidityTypes[i], encodeds[i], dynamicOffset) - dynamicOffset += e.length / 2 - results.push(e) - } - }) - return results.join('') - } - - // tslint:disable-next-line:prefer-function-over-method - encodeWithOffset(type: string, solidityType: SolidityType, encoded: string | string[], offset: number): string { - /* jshint maxcomplexity: 17 */ - /* jshint maxdepth: 5 */ - - let encodingMode = { dynamic: 1, static: 2, other: 3 } - - let mode = solidityType.isDynamicArray(type) - ? encodingMode.dynamic - : solidityType.isStaticArray(type) - ? encodingMode.static - : encodingMode.other - - if (mode !== encodingMode.other) { - let nestedName = solidityType.nestedName(type) - let nestedStaticPartLength = solidityType.staticPartLength(nestedName) - let results: string[] = [] - - if (mode === encodingMode.dynamic) { - results.push(encoded[0] as string) - } - - if (solidityType.isDynamicArray(nestedName)) { - let previousLength = mode === encodingMode.dynamic ? 2 : 0 - - for (let i = 0; i < encoded.length; i++) { - // calculate length of previous item - if (mode === encodingMode.dynamic) { - previousLength += +encoded[i - 1][0] || 0 - } else if (mode === encodingMode.static) { - previousLength += +(encoded[i - 1] || [])[0] || 0 - } - results.push(formatter.formatInputInt(offset + i * nestedStaticPartLength + previousLength * 32).encode()) - } - } - - let len = mode === encodingMode.dynamic ? encoded.length - 1 : encoded.length - for (let c = 0; c < len; c++) { - let additionalOffset = results.join('').length / 2 - if (mode === encodingMode.dynamic) { - results.push(this.encodeWithOffset(nestedName, solidityType, encoded[c + 1], offset + additionalOffset)) - } else if (mode === encodingMode.static) { - results.push(this.encodeWithOffset(nestedName, solidityType, encoded[c], offset + additionalOffset)) - } - } - - return results.join('') - } - - if (typeof encoded != 'string') { - throw new Error('Encoded is not string') - } - - return encoded as any - } - - /** - * Should be used to decode bytes to plain param - * - * @method decodeParam - * @param {string} type - * @param {string} bytes - * @return {object} plain param - */ - decodeParam(type: string, bytes: string) { - return this.decodeParams([type], bytes)[0] + export function encodeParams(types: ReadonlyArray>, params: any[]): string { + return bytesToHex(ethersAbiCoder.encode(types, params)) } /** @@ -208,46 +46,17 @@ export class SolidityCoder { * @param {string} bytes * @return {Array} array of plain params */ - decodeParams(types: string[], bytes: string): any[] { - let solidityTypes = this.getSolidityTypes(types) - let offsets = this.getOffsets(types, solidityTypes) - - return solidityTypes.map(function (solidityType, index) { - return solidityType.decode(bytes, offsets[index], types[index]) - }) - } - - // tslint:disable-next-line:prefer-function-over-method - getOffsets(types: string[], solidityTypes: SolidityType[]): number[] { - let lengths = solidityTypes.map(function (solidityType, index) { - return solidityType.staticPartLength(types[index]) - }) - - for (let i = 1; i < lengths.length; i++) { - // sum with length of previous element - lengths[i] += lengths[i - 1] + export function decodeParams(outputs: ReadonlyArray>, bytes: string): any { + if (outputs.length > 0 && (!bytes || bytes === '0x' || bytes === '0X')) { + throw new Error( + "Returned values aren't valid, did it run Out of Gas? " + + 'You might also see this error if you are not using the ' + + 'correct ABI for the contract you are retrieving data from, ' + + 'requesting data from a block number that does not exist, ' + + 'or querying a node which is not fully synced.' + ) } - return lengths.map(function (length, index) { - // remove the current length, so the length is sum of previous elements - let staticPartLength = solidityTypes[index].staticPartLength(types[index]) - return length - staticPartLength - }) - } - - getSolidityTypes(types: string[]): SolidityType[] { - return types.map((type) => this._requireType(type)) + return ethersAbiCoder.decode(outputs, utils.hexToBytes('0x' + bytes.replace(/0x/i, ''))) } } - -export const coder = new SolidityCoder([ - new SolidityTypeAddress(), - new SolidityTypeBool(), - new SolidityTypeInt(), - new SolidityTypeUInt(), - new SolidityTypeDynamicBytes(), - new SolidityTypeBytes(), - new SolidityTypeString(), - new SolidityTypeReal(), - new SolidityTypeUReal() -]) diff --git a/src/solidity/dynamicbytes.ts b/src/solidity/dynamicbytes.ts deleted file mode 100644 index 826aa31b..00000000 --- a/src/solidity/dynamicbytes.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as f from './formatters' -import { SolidityType } from './type' - -export class SolidityTypeDynamicBytes extends SolidityType { - constructor() { - super({ - inputFormatter: f.formatInputDynamicBytes, - outputFormatter: f.formatOutputDynamicBytes - }) - } - - // tslint:disable-next-line:prefer-function-over-method - isType(name: string) { - return !!name.match(/^bytes(\[([0-9]*)\])*$/) - } - - // tslint:disable-next-line:prefer-function-over-method - isDynamicType() { - return true - } -} diff --git a/src/solidity/formatters.ts b/src/solidity/formatters.ts deleted file mode 100644 index 2ba03330..00000000 --- a/src/solidity/formatters.ts +++ /dev/null @@ -1,191 +0,0 @@ -/* - This file is part of web3.js. - - web3.js is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - web3.js 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with web3.js. If not, see . -*/ - -import * as utils from '../utils/utils' -import * as config from '../utils/config' -import { SolidityParam } from './param' -import { BigNumber } from '../utils/BigNumber' -import { inputAddressFormatter } from '../utils/formatters' - -/** - * Formats input value to byte representation of int - * If value is negative, return it's two's complement - * If the value is floating point, round it down - */ -export function formatInputInt(value: BigNumber.Value) { - BigNumber.config(config.ETH_BIGNUMBER_ROUNDING_MODE) - - let result = utils.padLeft(utils.toTwosComplement(value).toString(16), 64) - - const ret = new SolidityParam(result) - - if (ret.value.indexOf('NaN') != -1) { - throw new Error(`The number ${JSON.stringify(value)} can't be parsed.`) - } - - return ret -} - -export function formatInputAddress(value: string) { - if (typeof value != 'string') throw new Error('The input must be a valid address, got: ' + JSON.stringify(value)) - return formatInputInt(inputAddressFormatter(value.trim())) -} - -/** - * Formats input bytes - */ -export function formatInputBytes(value: string) { - let result = utils.toHex(value).substr(2) - let l = Math.floor((result.length + 63) / 64) - result = utils.padRight(result, l * 64) - return new SolidityParam(result) -} - -/** - * Formats input bytes - */ -export function formatInputDynamicBytes(value: string) { - let result = utils.toHex(value).substr(2) - let length = result.length / 2 - let l = Math.floor((result.length + 63) / 64) - result = utils.padRight(result, l * 64) - return new SolidityParam(formatInputInt(length).value + result) -} - -/** - * Formats input value to byte representation of string - */ -export function formatInputString(value: string) { - let result = utils.fromUtf8(value).substr(2) - let length = result.length / 2 - let l = Math.floor((result.length + 63) / 64) - result = utils.padRight(result, l * 64) - return new SolidityParam(formatInputInt(length).value + result) -} - -/** - * Formats input value to byte representation of bool - */ -export function formatInputBool(value: boolean) { - let result = '000000000000000000000000000000000000000000000000000000000000000' + (value ? '1' : '0') - return new SolidityParam(result) -} - -/** - * Formats input value to byte representation of real - * Values are multiplied by 2^m and encoded as integers - */ -export function formatInputReal(value: BigNumber.Value) { - return formatInputInt(new BigNumber(value).times(new BigNumber(2).pow(128))) -} - -/** - * Check if input value is negative - * - * @param value - The value is hex format - */ -export function signedIsNegative(value: string) { - return new BigNumber(value.substr(0, 1), 16).toString(2).substr(0, 1) === '1' -} - -/** - * Formats right-aligned output bytes to int - */ -export function formatOutputInt(param: SolidityParam): BigNumber { - let value = param.staticPart() || '0' - - // check if it's negative number - // it it is, return two's complement - if (signedIsNegative(value)) { - return new BigNumber(value, 16) - .minus(new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)) - .minus(1) - } - return new BigNumber(value, 16) -} - -/** - * Formats right-aligned output bytes to uint - */ -export function formatOutputUInt(param: SolidityParam) { - let value = param.staticPart() - return new BigNumber(value, 16) -} - -/** - * Formats right-aligned output bytes to real - */ -export function formatOutputReal(param: SolidityParam) { - return formatOutputInt(param).dividedBy(new BigNumber(2).pow(128)) -} - -/** - * Formats right-aligned output bytes to ureal - */ -export function formatOutputUReal(param: SolidityParam) { - return formatOutputUInt(param).dividedBy(new BigNumber(2).pow(128)) -} - -/** - * Should be used to format output bool - */ -export function formatOutputBool(param: SolidityParam) { - return param.staticPart() === '0000000000000000000000000000000000000000000000000000000000000001' ? true : false -} - -/** - * Should be used to format output bytes - * - * @param param - The left-aligned hex representation of string - * @param name - The type name - */ -export function formatOutputBytes(param: SolidityParam, name: string) { - let matches = name.match(/^bytes([0-9]*)/) - if (!matches) throw new Error('Type is not bytes') - let size = parseInt(matches[1], 10) - return '0x' + param.staticPart().slice(0, 2 * size) -} - -/** - * Should be used to format output bytes - * - * @param param - The left-aligned hex representation of string - */ -export function formatOutputDynamicBytes(param: SolidityParam) { - let length = new BigNumber(param.dynamicPart().slice(0, 64), 16).toNumber() * 2 - return '0x' + param.dynamicPart().substr(64, length) -} - -/** - * Should be used to format output string - * - * @param param - The left-aligned hex representation of string - */ -export function formatOutputString(param: SolidityParam) { - let length = new BigNumber(param.dynamicPart().slice(0, 64), 16).toNumber() * 2 - return utils.toUtf8(param.dynamicPart().substr(64, length)) -} - -/** - * Should be used to format output address - * - * @param param - The right-aligned input bytes - */ -export function formatOutputAddress(param: SolidityParam) { - let value = param.staticPart() - return '0x' + value.slice(value.length - 40, value.length) -} diff --git a/src/solidity/int.ts b/src/solidity/int.ts deleted file mode 100644 index bd9bd094..00000000 --- a/src/solidity/int.ts +++ /dev/null @@ -1,33 +0,0 @@ -import BigNumber from 'bignumber.js' -import * as f from './formatters' -import { SolidityType } from './type' - -/** - * SolidityTypeInt is a prootype that represents int type - * It matches: - * int - * int[] - * int[4] - * int[][] - * int[3][] - * int[][6][], ... - * int32 - * int64[] - * int8[4] - * int256[][] - * int[3][] - * int64[][6][], ... - */ -export class SolidityTypeInt extends SolidityType { - constructor() { - super({ - inputFormatter: f.formatInputInt, - outputFormatter: f.formatOutputInt - }) - } - - // tslint:disable-next-line:prefer-function-over-method - isType(name: string) { - return !!name.match(/^int([0-9]*)?(\[([0-9]*)\])*$/) - } -} diff --git a/src/solidity/param.ts b/src/solidity/param.ts deleted file mode 100644 index 05de9b3d..00000000 --- a/src/solidity/param.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - This file is part of web3.js. - - web3.js is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - web3.js 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with web3.js. If not, see . -*/ - -import * as utils from '../utils/utils' - -/** - * SolidityParam object prototype. - * Should be used when encoding, decoding solidity bytes - */ -export class SolidityParam { - constructor(public value: string = '', public offset?: number) {} - - /** - * This method should be called to check if param has dynamic size. - * If it has, it returns true, otherwise false - * - * @method isDynamic - * @returns {Boolean} - */ - isDynamic(): boolean { - return this.offset !== undefined - } - - /** - * This method should be called to transform offset to bytes - * - * @method offsetAsBytes - * @returns {string} bytes representation of offset - */ - offsetAsBytes(): string { - return !this.isDynamic() ? '' : utils.padLeft(utils.toTwosComplement(this.offset || '').toString(16), 64) - } - - /** - * This method should be called to get static part of param - * - * @method staticPart - * @returns {string} offset if it is a dynamic param, otherwise value - */ - staticPart(): string { - if (!this.isDynamic()) { - return this.value - } - return this.offsetAsBytes() - } - - /** - * This method should be called to get dynamic part of param - * - * @method dynamicPart - * @returns {string} returns a value if it is a dynamic param, otherwise empty string - */ - dynamicPart(): string { - return this.isDynamic() ? this.value : '' - } - - /** - * This method should be called to encode param - * - * @method encode - * @returns {string} - */ - encode(): string { - return this.staticPart() + this.dynamicPart() - } -} diff --git a/src/solidity/real.ts b/src/solidity/real.ts deleted file mode 100644 index a895d3e5..00000000 --- a/src/solidity/real.ts +++ /dev/null @@ -1,33 +0,0 @@ -import * as f from './formatters' -import { SolidityType } from './type' -import { BigNumber } from '../utils/BigNumber' - -/** - * SolidityTypeReal is a prootype that represents real type - * It matches: - * real - * real[] - * real[4] - * real[][] - * real[3][] - * real[][6][], ... - * real32 - * real64[] - * real8[4] - * real256[][] - * real[3][] - * real64[][6][], ... - */ -export class SolidityTypeReal extends SolidityType { - constructor() { - super({ - inputFormatter: f.formatInputReal, - outputFormatter: f.formatOutputReal - }) - } - - // tslint:disable-next-line:prefer-function-over-method - isType(name: string) { - return !!name.match(/real([0-9]*)?(\[([0-9]*)\])?/) - } -} diff --git a/src/solidity/string.ts b/src/solidity/string.ts deleted file mode 100644 index c4533951..00000000 --- a/src/solidity/string.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as f from './formatters' -import { SolidityType } from './type' - -export class SolidityTypeString extends SolidityType { - constructor() { - super({ - inputFormatter: f.formatInputString, - outputFormatter: f.formatOutputString - }) - } - - // tslint:disable-next-line:prefer-function-over-method - isType(name: string) { - return !!name.match(/^string(\[([0-9]*)\])*$/) - } - - // tslint:disable-next-line:prefer-function-over-method - isDynamicType() { - return true - } -} diff --git a/src/solidity/type.ts b/src/solidity/type.ts deleted file mode 100644 index 87ad12f7..00000000 --- a/src/solidity/type.ts +++ /dev/null @@ -1,242 +0,0 @@ -import * as f from './formatters' -import { SolidityParam } from './param' - -/** - * SolidityType prototype is used to encode/decode solidity params of certain type - */ -export abstract class SolidityType { - _inputFormatter: (value: ValueType, name: string) => SolidityParam - _outputFormatter: (param: SolidityParam, name: string) => ValueType - - constructor(config: { - inputFormatter: (value: ValueType, name: string) => SolidityParam - outputFormatter: (param: SolidityParam, name: string) => ValueType - }) { - this._inputFormatter = config.inputFormatter - this._outputFormatter = config.outputFormatter - } - - /** - * Should be used to determine if this SolidityType do match given name - * - * @method isType - * @param {string} name - * @return {Bool} true if type match this SolidityType, otherwise false - */ - abstract isType(name: string): boolean - - /** - * Should be used to determine what is the length of static part in given type - * - * @method staticPartLength - * @param {string} name - * @return {number} length of static part in bytes - */ - staticPartLength(name: string): number { - // If name isn't an array then treat it like a single element array. - return (this.nestedTypes(name) || ['[1]']) - .map(function (type) { - // the length of the nested array - return parseInt(type.slice(1, -1), 10) || 1 - }) - .reduce(function (previous, current) { - return previous * current - // all basic types are 32 bytes long - }, 32) - } - - /** - * Should be used to determine if type is dynamic array - * eg: - * "type[]" => true - * "type[4]" => false - * - * @method isDynamicArray - * @param {string} name - * @return {bool} true if the type is dynamic array - */ - isDynamicArray(name: string): boolean { - let nestedTypes = this.nestedTypes(name) - return !!nestedTypes && !nestedTypes[nestedTypes.length - 1].match(/[0-9]{1,}/g) - } - - /** - * Should be used to determine if type is static array - * eg: - * "type[]" => false - * "type[4]" => true - * - * @method isStaticArray - * @param {string} name - * @return {Bool} true if the type is static array - */ - isStaticArray(name: string): boolean { - let nestedTypes = this.nestedTypes(name) - return !!nestedTypes && !!nestedTypes[nestedTypes.length - 1].match(/[0-9]{1,}/g) - } - - /** - * Should return length of static array - * eg. - * "int[32]" => 32 - * "int256[14]" => 14 - * "int[2][3]" => 3 - * "int" => 1 - * "int[1]" => 1 - * "int[]" => 1 - * - * @method staticArrayLength - * @param {string} name - * @return {number} static array length - */ - staticArrayLength(name: string): number { - let nestedTypes = this.nestedTypes(name) - if (nestedTypes) { - const match = nestedTypes[nestedTypes.length - 1].match(/[0-9]{1,}/g) - if (!match) throw new Error('untested path') - return parseInt(match[match.length - 1] || '1', 10) - } - return 1 - } - - /** - * Should return nested type - * eg. - * "int[32]" => "int" - * "int256[14]" => "int256" - * "int[2][3]" => "int[2]" - * "int" => "int" - * "int[]" => "int" - * - * @method nestedName - * @param {string} name - * @return {string} nested name - */ - nestedName(name: string): string { - // remove last [] in name - let nestedTypes = this.nestedTypes(name) - if (!nestedTypes) { - return name - } - - return name.substr(0, name.length - nestedTypes[nestedTypes.length - 1].length) - } - - /** - * Should return true if type has dynamic size by default - * such types are "string", "bytes" - * - * @method isDynamicType - * @param {string} name - * @return {Bool} true if is dynamic, otherwise false - */ - // tslint:disable-next-line:prefer-function-over-method - isDynamicType(_?: string): boolean { - return false - } - - /** - * Should return array of nested types - * eg. - * "int[2][3][]" => ["[2]", "[3]", "[]"] - * "int[] => ["[]"] - * "int" => null - * - * @method nestedTypes - * @param {string} name - * @return {Array} array of nested types - */ - // tslint:disable-next-line:prefer-function-over-method - nestedTypes(name: string): string[] | null { - // return list of strings eg. "[]", "[3]", "[]", "[2]" - return name.match(/(\[[0-9]*\])/g) - } - - /** - * Should be used to encode the value - * - * @method encode - * @param {object} value - * @param {string} name - * @return {string} encoded value - */ - encode(value: any, name: string): string | string[] { - if (this.isDynamicArray(name)) { - let length = value.length // in int - let nestedName = this.nestedName(name) - - let result = [] - result.push(f.formatInputInt(length).encode()) - - value.forEach((v: any) => { - result.push(this.encode(v, nestedName)) - }) - - return result - } else if (this.isStaticArray(name)) { - let length = this.staticArrayLength(name) // in int - let nestedName = this.nestedName(name) - - let result: string[] = [] - for (let i = 0; i < length; i++) { - result.push(this.encode(value[i], nestedName) as string) - } - - return result - } - - return this._inputFormatter(value, name).encode() - } - - /** - * Should be used to decode value from bytes - * - * @method decode - * @param {string} bytes - * @param {number} offset in bytes - * @param {string} name type name - * @returns {object} decoded value - */ - decode(bytes: string, offset: number, name: string): any { - if (this.isDynamicArray(name)) { - let arrayOffset = parseInt('0x' + bytes.substr(offset * 2, 64), 16) // in bytes - let length = parseInt('0x' + bytes.substr(arrayOffset * 2, 64), 16) // in int - let arrayStart = arrayOffset + 32 // array starts after length; // in bytes - - let nestedName = this.nestedName(name) - let nestedStaticPartLength = this.staticPartLength(nestedName) // in bytes - let roundedNestedStaticPartLength = Math.floor((nestedStaticPartLength + 31) / 32) * 32 - let result = [] - - for (let i = 0; i < length * roundedNestedStaticPartLength; i += roundedNestedStaticPartLength) { - result.push(this.decode(bytes, arrayStart + i, nestedName)) - } - - return result - } else if (this.isStaticArray(name)) { - let length = this.staticArrayLength(name) // in int - let arrayStart = offset // in bytes - - let nestedName = this.nestedName(name) - let nestedStaticPartLength = this.staticPartLength(nestedName) // in bytes - let roundedNestedStaticPartLength = Math.floor((nestedStaticPartLength + 31) / 32) * 32 - let result = [] - - for (let i = 0; i < length * roundedNestedStaticPartLength; i += roundedNestedStaticPartLength) { - result.push(this.decode(bytes, arrayStart + i, nestedName)) - } - - return result - } else if (this.isDynamicType(name)) { - let dynamicOffset = parseInt('0x' + bytes.substr(offset * 2, 64), 16) // in bytes - let length = parseInt('0x' + bytes.substr(dynamicOffset * 2, 64), 16) // in bytes - let roundedLength = Math.floor((length + 31) / 32) // in int - let param = new SolidityParam(bytes.substr(dynamicOffset * 2, (1 + roundedLength) * 64), 0) - return this._outputFormatter(param, name) - } - - let length = this.staticPartLength(name) - let param = new SolidityParam(bytes.substr(offset * 2, length * 2)) - return this._outputFormatter(param, name) - } -} diff --git a/src/solidity/uint.ts b/src/solidity/uint.ts deleted file mode 100644 index a75c4ef9..00000000 --- a/src/solidity/uint.ts +++ /dev/null @@ -1,33 +0,0 @@ -import BigNumber from 'bignumber.js' -import * as f from './formatters' -import { SolidityType } from './type' - -/** - * SolidityTypeUInt is a prootype that represents uint type - * It matches: - * uint - * uint[] - * uint[4] - * uint[][] - * uint[3][] - * uint[][6][], ... - * uint32 - * uint64[] - * uint8[4] - * uint256[][] - * uint[3][] - * uint64[][6][], ... - */ -export class SolidityTypeUInt extends SolidityType { - constructor() { - super({ - inputFormatter: f.formatInputInt, - outputFormatter: f.formatOutputUInt - }) - } - - // tslint:disable-next-line:prefer-function-over-method - isType(name: string) { - return !!name.match(/^uint([0-9]*)?(\[([0-9]*)\])*$/) - } -} diff --git a/src/solidity/ureal.ts b/src/solidity/ureal.ts deleted file mode 100644 index 8fe83099..00000000 --- a/src/solidity/ureal.ts +++ /dev/null @@ -1,33 +0,0 @@ -import * as f from './formatters' -import { SolidityType } from './type' -import { BigNumber } from '../utils/BigNumber' - -/** - * SolidityTypeUReal is a prootype that represents ureal type - * It matches: - * ureal - * ureal[] - * ureal[4] - * ureal[][] - * ureal[3][] - * ureal[][6][], ... - * ureal32 - * ureal64[] - * ureal8[4] - * ureal256[][] - * ureal[3][] - * ureal64[][6][], ... - */ -export class SolidityTypeUReal extends SolidityType { - constructor() { - super({ - inputFormatter: f.formatInputReal, - outputFormatter: f.formatOutputUReal - }) - } - - // tslint:disable-next-line:prefer-function-over-method - isType(name: string) { - return !!name.match(/^ureal([0-9]*)?(\[([0-9]*)\])*$/) - } -} diff --git a/src/utils/BigNumber.ts b/src/utils/BigNumber.ts index e1259015..d60dbcde 100644 --- a/src/utils/BigNumber.ts +++ b/src/utils/BigNumber.ts @@ -1 +1,8 @@ -export { BigNumber } from 'bignumber.js' +import { BigNumber } from 'bignumber.js' +export { BigNumber } + +const ETH_BIGNUMBER_ROUNDING_MODE = { + ROUNDING_MODE: BigNumber.ROUND_DOWN as BigNumber.RoundingMode +} + +BigNumber.config(ETH_BIGNUMBER_ROUNDING_MODE) diff --git a/src/utils/config.ts b/src/utils/config.ts index 47c51bb3..c554ce9a 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -15,8 +15,6 @@ along with web3.js. If not, see . */ -import { BigNumber } from '../utils/BigNumber' - export const ETH_UNITS = [ 'wei', 'kwei', @@ -47,10 +45,5 @@ export const ETH_UNITS = [ 'Uether' ] -export const ETH_PADDING = 32 -export const ETH_SIGNATURE_LENGTH = 4 -export const ETH_BIGNUMBER_ROUNDING_MODE = { - ROUNDING_MODE: BigNumber.ROUND_DOWN as BigNumber.RoundingMode -} export const ETH_POLLING_TIMEOUT = 1000 / 2 export let defaultBlock: 'latest' = 'latest' diff --git a/src/utils/errors.ts b/src/utils/errors.ts index a2e58ac6..10b514e3 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -15,6 +15,62 @@ along with web3.js. If not, see . */ +// Unknown Error +export const UNKNOWN_ERROR = 'UNKNOWN_ERROR' + +// Not implemented +export const NOT_IMPLEMENTED = 'NOT_IMPLEMENTED' + +// Missing new operator to an object +// - name: The name of the class +export const MISSING_NEW = 'MISSING_NEW' + +// Call exception +// - transaction: the transaction +// - address?: the contract address +// - args?: The arguments passed into the function +// - method?: The Solidity method signature +// - errorSignature?: The EIP848 error signature +// - errorArgs?: The EIP848 error parameters +// - reason: The reason (only for EIP848 "Error(string)") +export const CALL_EXCEPTION = 'CALL_EXCEPTION' + +// Invalid argument (e.g. value is incompatible with type) to a function: +// - arg: The argument name that was invalid +// - value: The value of the argument +export const INVALID_ARGUMENT = 'INVALID_ARGUMENT' + +// Missing argument to a function: +// - count: The number of arguments received +// - expectedCount: The number of arguments expected +export const MISSING_ARGUMENT = 'MISSING_ARGUMENT' + +// Too many arguments +// - count: The number of arguments received +// - expectedCount: The number of arguments expected +export const UNEXPECTED_ARGUMENT = 'UNEXPECTED_ARGUMENT' + +// Numeric Fault +// - operation: the operation being executed +// - fault: the reason this faulted +export const NUMERIC_FAULT = 'NUMERIC_FAULT' + +// Insufficien funds (< value + gasLimit * gasPrice) +// - transaction: the transaction attempted +export const INSUFFICIENT_FUNDS = 'INSUFFICIENT_FUNDS' + +// Nonce has already been used +// - transaction: the transaction attempted +export const NONCE_EXPIRED = 'NONCE_EXPIRED' + +// The replacement fee for the transaction is too low +// - transaction: the transaction attempted +export const REPLACEMENT_UNDERPRICED = 'REPLACEMENT_UNDERPRICED' + +// Unsupported operation +// - operation +export const UNSUPPORTED_OPERATION = 'UNSUPPORTED_OPERATION' + export function InvalidNumberOfSolidityArgs(given: number, expected: number) { return new Error(`Invalid number of arguments to Solidity function. given: ${given}, expected: ${expected}`) } @@ -44,3 +100,64 @@ export function InvalidResponse(result: any) { export function ConnectionTimeout(ms: number) { return new Error('CONNECTION TIMEOUT: timeout of ' + ms + ' ms achived') } + +let _permanentCensorErrors = false +let _censorErrors = false + +export function error(message: string, code: string = UNKNOWN_ERROR, params: any = {}): Error { + if (_censorErrors) { + return new Error('unknown error') + } + + let messageDetails: Array = [] + Object.keys(params).forEach((key) => { + try { + messageDetails.push(key + '=' + JSON.stringify(params[key])) + } catch (error) { + messageDetails.push(key + '=' + JSON.stringify(params[key].toString())) + } + }) + + let reason = message + if (messageDetails.length) { + message += ' (' + messageDetails.join(', ') + ')' + } + + // @TODO: Any?? + let error: any = new Error(message) + error.reason = reason + error.code = code + + Object.keys(params).forEach(function (key) { + error[key] = params[key] + }) + + return error +} + +export function checkNew(self: any, kind: any): void { + if (!(self instanceof kind)) { + throw error('missing new', MISSING_NEW, { name: kind.name }) + } +} + +export function checkArgumentCount(count: number, expectedCount: number, suffix?: string): void { + if (!suffix) { + suffix = '' + } + if (count < expectedCount) { + throw error('missing argument' + suffix, MISSING_ARGUMENT, { count: count, expectedCount: expectedCount }) + } + if (count > expectedCount) { + throw error('too many arguments' + suffix, UNEXPECTED_ARGUMENT, { count: count, expectedCount: expectedCount }) + } +} + +export function setCensorship(censorship: boolean, permanent?: boolean): void { + if (_permanentCensorErrors) { + throw error('error censorship permanent', UNSUPPORTED_OPERATION, { operation: 'setCersorship' }) + } + + _censorErrors = !!censorship + _permanentCensorErrors = !!permanent +} diff --git a/src/utils/formatters.ts b/src/utils/formatters.ts index 699b09ea..98388a44 100644 --- a/src/utils/formatters.ts +++ b/src/utils/formatters.ts @@ -30,6 +30,7 @@ import { TransactionReceipt } from '../Schema' import { BigNumber } from './BigNumber' +import { stringToUtf8Bytes } from './utf8' /** * Should format the output to a big number @@ -255,7 +256,7 @@ export function inputPostFormatter(post: any) { // format the following options post.topics = post.topics.map(function (topic: string) { // convert only if not hex - return topic.indexOf('0x') === 0 ? topic : utils.fromUtf8(topic) + return topic.indexOf('0x') === 0 ? topic : '0x' + utils.bytesToHex(stringToUtf8Bytes(topic)) }) return post diff --git a/src/utils/utf8.ts b/src/utils/utf8.ts new file mode 100644 index 00000000..48728412 --- /dev/null +++ b/src/utils/utf8.ts @@ -0,0 +1,20 @@ +import { hexToBytes } from './utils' + +/** + * Converts a string into a Uint8Array encoded with UTF-8 + * @public + */ +export function stringToUtf8Bytes(str: string): Uint8Array { + return new TextEncoder().encode(str) +} + +/** + * Decodes an Uint8Array or hex string into a UTF-8 string + * @public + */ +export function bytesToUtf8String(bytesOrHexString: Uint8Array | string): string { + if (typeof bytesOrHexString == 'string') { + return bytesToUtf8String(hexToBytes(bytesOrHexString)) + } + return new TextDecoder().decode(bytesOrHexString) +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 2f89369d..ff1bbd08 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -15,10 +15,11 @@ along with web3.js. If not, see . */ -import * as utf8 from 'utf8' import { keccak256 } from 'js-sha3' import { BigNumber } from './BigNumber' -import { AbiItem } from '../Schema' +import { AbiInput, AbiItem } from '../Schema' +import { stringToUtf8Bytes } from './utf8' +import * as errors from './errors' /** * @public @@ -36,13 +37,22 @@ export function hexToBytes(hex: string): Uint8Array { for (let char = 0; char < hex.length; char += 2) { const n = parseInt(hex.substr(char, 2), 16) if (isNaN(n)) throw new Error('Cannot read hex string:' + JSON.stringify(hex)) - result[i] = parseInt(hex.substr(char, 2), 16) + result[i] = n i++ } return result } +/** + * @public + */ +export function bytesToHex(bytes: Uint8Array): string { + const hashArray = Array.from(bytes) // convert buffer to byte array + const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('') // convert bytes to hex string + return hashHex +} + /** * @public */ @@ -56,7 +66,7 @@ export function sha3(value: string | number[] | ArrayBuffer | Uint8Array, option const t = hexToBytes(mutValue) return keccak256(t) } else { - return keccak256(utf8.encode(value)) + return keccak256(stringToUtf8Bytes(value)) } } @@ -111,27 +121,6 @@ export function padRight(str: string, chars: number, sign?: string) { return str + new Array(chars - str.length + 1).join(sign ? sign : '0') } -/** - * @public - * Should be called to get utf8 from it's hex representation - */ -export function toUtf8(hex: string): string { - // Find termination - let str = '' - let i = 0 - let l = hex.length - if (hex.substring(0, 2) === '0x') { - i = 2 - } - for (; i < l; i += 2) { - let code = parseInt(hex.substr(i, 2), 16) - if (code === 0) break - str += String.fromCharCode(code) - } - - return utf8.decode(str) -} - /** * @public * Should be called to get ascii from it's hex representation @@ -152,31 +141,6 @@ export function toAscii(hex: string) { return str } -/** - * @public - * Should be called to get hex representation (prefixed by 0x) of utf8 string - */ -export function fromUtf8(_str: string, allowZero = false) { - let str = utf8.encode(_str) - let hex = '' - - for (let i = 0; i < str.length; i++) { - let code = str.charCodeAt(i) - if (code === 0) { - if (allowZero) { - hex += '00' - } else { - break - } - } else { - let n = code.toString(16) - hex += n.length < 2 ? '0' + n : n - } - } - - return '0x' + hex -} - /** * @public * Should be called to get hex representation (prefixed by 0x) of ascii string @@ -197,20 +161,59 @@ export function fromAscii(str: string, num: number = 0) { * Should be used to create full function/event name from json abi */ export function transformToFullName(json: AbiItem) { - if (json.name && json.name.indexOf('(') !== -1) { + if (isObject(json) && json.name && json.name.indexOf('(') !== -1) { return json.name } - let typeName: string = '' - if (json.inputs) { - typeName = json.inputs - .map(function (i) { - return i.type - }) - .join() + return json.name + '(' + _flattenTypes(false, json.inputs || []).join(',') + ')' +} + +export function concatBytes(...buffers: Uint8Array[]) { + const length = buffers.reduce(($, buf) => $ + buf.length, 0) + var mergedArray = new Uint8Array(length) + let cursor = 0 + for (let buf of buffers) { + mergedArray.set(buf, cursor) + cursor += buf.length } + return mergedArray +} + +/** + * Should be used to flatten json abi inputs/outputs into an array of type-representing-strings + * + * @method _flattenTypes + * @param {bool} includeTuple + * @param {Object} puts + * @return {Array} parameters as strings + */ +function _flattenTypes(includeTuple: boolean, puts: AbiInput[]) { + const types: string[] = [] + + puts.forEach(function (param) { + if (typeof param.components === 'object') { + if (param.type.substring(0, 5) !== 'tuple') { + throw new Error('components found but type is not tuple; report on GitHub') + } + var suffix = '' + var arrayBracket = param.type.indexOf('[') + if (arrayBracket >= 0) { + suffix = param.type.substring(arrayBracket) + } + var result = _flattenTypes(includeTuple, param.components) + if (isArray(result) && includeTuple) { + types.push('tuple(' + result.join(',') + ')' + suffix) + } else if (!includeTuple) { + types.push('(' + result.join(',') + ')' + suffix) + } else { + types.push('(' + result + ')') + } + } else { + types.push(param.type) + } + }) - return json.name + '(' + typeName + ')' + return types } /** @@ -310,19 +313,23 @@ export function fromDecimal(value: BigNumber.Value) { * * And even stringifys objects before. */ -export function toHex(val: BigNumber.Value | boolean) { +export function toHex(val: BigNumber.Value | boolean | Uint8Array) { if (isBoolean(val)) return fromDecimal(+val) if (isBigNumber(val)) return fromDecimal(val) - if (typeof val === 'object') return fromUtf8(JSON.stringify(val)) - // if its a negative number, pass it through fromDecimal if (isString(val)) { const valStr = val as string if (valStr.indexOf('-0x') === 0) return fromDecimal(valStr) else if (valStr.indexOf('0x') === 0) return valStr - else if (!isFinite(valStr as any)) return fromUtf8(valStr, true) + else if (!isFinite(valStr as any)) return bytesToHex(stringToUtf8Bytes(valStr)) + } + + if (val instanceof Uint8Array) return '0x' + bytesToHex(val) + + if (isArray(val) || isObject(val)) { + throw new Error('toHex can only be called with scalar values, not objects or arrays') } return fromDecimal(val) @@ -411,23 +418,74 @@ export function toBigNumber(_num: BigNumber.Value): BigNumber { return new BigNumber(num.replace('0x', '').toLowerCase(), 16) } + if (num instanceof Uint8Array) { + return new BigNumber(bytesToHex(num), 16) + } + return new BigNumber(num, 10) } +function bitMask(bits: number) { + return new BigNumber(new Array(bits).fill('1').join(''), 2) +} + /** * @public * Takes and input transforms it into bignumber and if it is negative value, into two's complement */ -export function toTwosComplement(num: BigNumber.Value): BigNumber { +export function toTwosComplement(num: BigNumber.Value, bits = 256): BigNumber { let bigNumber = toBigNumber(num).integerValue() as BigNumber if (bigNumber.isLessThan(0)) { - return new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16).plus(bigNumber).plus(1) + const mask = bitMask(bits) + return mask.plus(bigNumber).plus(1) } return bigNumber } +/** + * Check if input value is negative in twos complement + */ +export function signedIsNegative(value: BigNumber, bits: number) { + const binary = padLeft(value.toString(2), bits, '0') + return binary[0] == '1' +} + +/** + * @public + */ +export function getAddress(address: string): string { + if (typeof address !== 'string') { + throw errors.error('invalid address', errors.INVALID_ARGUMENT, { arg: 'address', value: address }) + } + + if (address.trim().match(/^(0x)?[0-9a-fA-F]{40}$/)) { + // Missing the 0x prefix + if (address.trim().substring(0, 2) !== '0x') { + address = '0x' + address + } + + return toChecksumAddress(address) + } else { + throw errors.error('invalid address', errors.INVALID_ARGUMENT, { arg: 'address', value: address }) + } +} + +/** + * @public + * If the bit N is 1 + */ +export function fromTwosComplement(num: BigNumber, bits = 256): BigNumber { + // check if it's negative number + // it it is, return two's complement + if (signedIsNegative(num, bits)) { + const mask = bitMask(bits) + return num.minus(mask).minus(1) + } + return num +} + /** * @public * Checks if the given string is strictly an address diff --git a/test/allevents.decode.ts b/test/allevents.decode.ts index 033c52f2..f3c9efc5 100644 --- a/test/allevents.decode.ts +++ b/test/allevents.decode.ts @@ -140,7 +140,8 @@ let tests = [ '0000000000000000000000000000000000000000000000000000000000000004', topics: [ '0x0e99121e9409665e680573fa7da0cb6651dbdcf10c95f585b92b2c9b9702cff9', - '0x0000000000000000000000000000000000000000000000000000000000000010' + '0x0000000000000000000000000000000000000000000000000000000000000010', + '0x0000000000000000000000000000000000000000000000000000000000000002' ] }, expected: { @@ -149,7 +150,7 @@ let tests = [ a: new BigNumber(1), b: new BigNumber(16), c: new BigNumber(4), - d: new BigNumber(0) + d: new BigNumber(2) }, logIndex: 1, removed: false, @@ -158,7 +159,8 @@ let tests = [ '0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000004', topics: [ '0x0e99121e9409665e680573fa7da0cb6651dbdcf10c95f585b92b2c9b9702cff9', - '0x0000000000000000000000000000000000000000000000000000000000000010' + '0x0000000000000000000000000000000000000000000000000000000000000010', + '0x0000000000000000000000000000000000000000000000000000000000000002' ], transactionHash: '0x1234567890', address: address, diff --git a/test/coder.decodeParam.ts b/test/coder.decodeParam.ts index dd0f8172..9c02b907 100644 --- a/test/coder.decodeParam.ts +++ b/test/coder.decodeParam.ts @@ -1,35 +1,38 @@ import * as expect from 'expect' import * as coder from '../src/solidity/coder' +import { Tuple } from '../src/abi/coder' import { BigNumber as bn } from '../dist/eth-connect' +import { hexToBytes } from '../src' +import { parseParamType } from '../src/abi/parser' -describe('lib/solidity/coder', function() { - describe('decodeParam', function() { - let test = function(t) { - it('should turn ' + t.value + ' to ' + t.expected, function() { - expect(coder.coder.decodeParam(t.type, t.value)).toEqual(t.expected) +describe('lib/solidity/coder', function () { + describe('decodeParam', function () { + let test = function (t) { + it('should turn ' + t.value + ' to ' + t.type + '=' + t.expected, function () { + expect(coder.coder.decodeParams([parseParamType(t.type)], t.value)).toEqual([t.expected]) + expect(coder.coder.decodeParams([t.type], t.value)).toEqual([t.expected]) }) } - test({ type: 'address', - expected: '0x407d73d8a49eeb85d32cf465507dd71d507100c1', + expected: '0x407D73d8a49eeb85D32Cf465507dd71d507100c1', value: '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c1' }) test({ type: 'address', - expected: '0xbf79ce2fbd819e5abc2327563d02a200255b7cb3', + expected: '0xBF79cE2fbd819e5aBC2327563D02a200255B7Cb3', value: '000000000000000000000000bf79ce2fbd819e5abc2327563d02a200255b7cb3' }) test({ type: 'address[2]', - expected: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1', '0x407d73d8a49eeb85d32cf465507dd71d507100c3'], + expected: ['0x407D73d8a49eeb85D32Cf465507dd71d507100c1', '0x407D73d8A49eEB85D32Cf465507Dd71d507100c3'], value: '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c1' + '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c3' }) test({ type: 'address[]', - expected: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1', '0x407d73d8a49eeb85d32cf465507dd71d507100c3'], + expected: ['0x407D73d8a49eeb85D32Cf465507dd71d507100c1', '0x407D73d8A49eEB85D32Cf465507Dd71d507100c3'], value: '0000000000000000000000000000000000000000000000000000000000000020' + '0000000000000000000000000000000000000000000000000000000000000002' + @@ -39,10 +42,11 @@ describe('lib/solidity/coder', function() { test({ type: 'address[][2]', expected: [ - ['0x407d73d8a49eeb85d32cf465507dd71d507100c1', '0x407d73d8a49eeb85d32cf465507dd71d507100c2'], - ['0x407d73d8a49eeb85d32cf465507dd71d507100c3', '0x407d73d8a49eeb85d32cf465507dd71d507100c4'] + ['0x407D73d8a49eeb85D32Cf465507dd71d507100c1', '0x407d73d8a49EEB85d32Cf465507dD71D507100c2'], + ['0x407D73d8A49eEB85D32Cf465507Dd71d507100c3', '0x407D73d8a49eeb85D32CF465507dd71d507100C4'] ], value: + '0000000000000000000000000000000000000000000000000000000000000020' + '0000000000000000000000000000000000000000000000000000000000000040' + '00000000000000000000000000000000000000000000000000000000000000a0' + '0000000000000000000000000000000000000000000000000000000000000002' /* 40 */ + @@ -55,29 +59,29 @@ describe('lib/solidity/coder', function() { test({ type: 'address[2][]', expected: [ - ['0x407d73d8a49eeb85d32cf465507dd71d507100c1', '0x407d73d8a49eeb85d32cf465507dd71d507100c2'], - ['0x407d73d8a49eeb85d32cf465507dd71d507100c3', '0x407d73d8a49eeb85d32cf465507dd71d507100c4'] + ['0x507D73d8a49EEb85d32cf465507Dd71D507100c1', '0x507D73D8A49EEb85d32cF465507Dd71D507100C2'], + ['0x507d73D8a49EEB85D32cF465507DD71d507100c3', '0x507D73d8a49Eeb85D32CF465507dd71d507100C4'] ], value: '0000000000000000000000000000000000000000000000000000000000000020' + '0000000000000000000000000000000000000000000000000000000000000002' /* 20 */ + - '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c1' + - '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c2' + - '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c3' + - '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c4' + '000000000000000000000000507d73d8a49eeb85d32cf465507dd71d507100c1' + + '000000000000000000000000507d73d8a49eeb85d32cf465507dd71d507100c2' + + '000000000000000000000000507d73d8a49eeb85d32cf465507dd71d507100c3' + + '000000000000000000000000507d73d8a49eeb85d32cf465507dd71d507100c4' }) test({ type: 'address[][]', expected: [ - ['0x407d73d8a49eeb85d32cf465507dd71d507100c1', '0x407d73d8a49eeb85d32cf465507dd71d507100c2'], - ['0x407d73d8a49eeb85d32cf465507dd71d507100c3', '0x407d73d8a49eeb85d32cf465507dd71d507100c4'] + ['0x407D73d8a49eeb85D32Cf465507dd71d507100c1', '0x407d73d8a49EEB85d32Cf465507dD71D507100c2'], + ['0x407D73d8A49eEB85D32Cf465507Dd71d507100c3', '0x407D73d8a49eeb85D32CF465507dd71d507100C4'] ], value: '0000000000000000000000000000000000000000000000000000000000000020' + '0000000000000000000000000000000000000000000000000000000000000002' /* 20 */ + - '0000000000000000000000000000000000000000000000000000000000000080' + - '00000000000000000000000000000000000000000000000000000000000000e0' + - '0000000000000000000000000000000000000000000000000000000000000002' /* 80 */ + + '0000000000000000000000000000000000000000000000000000000000000040' + + '00000000000000000000000000000000000000000000000000000000000000a0' + + '0000000000000000000000000000000000000000000000000000000000000002' /* 40 */ + '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c1' /* a0 */ + '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c2' + '0000000000000000000000000000000000000000000000000000000000000002' /* e0 */ + @@ -141,19 +145,19 @@ describe('lib/solidity/coder', function() { }) test({ type: 'int8', - expected: new bn(16), + expected: 16, value: '0000000000000000000000000000000000000000000000000000000000000010' }) test({ type: 'int8[2]', - expected: [new bn(16), new bn(2)], + expected: [16, 2], value: '0000000000000000000000000000000000000000000000000000000000000010' + '0000000000000000000000000000000000000000000000000000000000000002' }) test({ type: 'int32', - expected: new bn(16), + expected: 16, value: '0000000000000000000000000000000000000000000000000000000000000010' }) test({ @@ -201,7 +205,10 @@ describe('lib/solidity/coder', function() { }) test({ type: 'int[3][]', - expected: [[new bn(1), new bn(2), new bn(3)], [new bn(4), new bn(5), new bn(6)]], + expected: [ + [new bn(1), new bn(2), new bn(3)], + [new bn(4), new bn(5), new bn(6)] + ], value: '0000000000000000000000000000000000000000000000000000000000000020' + '0000000000000000000000000000000000000000000000000000000000000002' + @@ -245,12 +252,12 @@ describe('lib/solidity/coder', function() { }) test({ type: 'uint8', - expected: new bn(16), + expected: 16, value: '0000000000000000000000000000000000000000000000000000000000000010' }) test({ type: 'uint32', - expected: new bn(16), + expected: 16, value: '0000000000000000000000000000000000000000000000000000000000000010' }) test({ @@ -298,7 +305,10 @@ describe('lib/solidity/coder', function() { }) test({ type: 'uint[3][]', - expected: [[new bn(1), new bn(2), new bn(3)], [new bn(4), new bn(5), new bn(6)]], + expected: [ + [new bn(1), new bn(2), new bn(3)], + [new bn(4), new bn(5), new bn(6)] + ], value: '0000000000000000000000000000000000000000000000000000000000000020' + '0000000000000000000000000000000000000000000000000000000000000002' + @@ -311,7 +321,7 @@ describe('lib/solidity/coder', function() { }) test({ type: 'bytes', - expected: '0x6761766f66796f726b', + expected: hexToBytes('0x6761766f66796f726b'), value: '0000000000000000000000000000000000000000000000000000000000000020' + '0000000000000000000000000000000000000000000000000000000000000009' + @@ -319,7 +329,7 @@ describe('lib/solidity/coder', function() { }) test({ type: 'bytes', - expected: '0x731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b', + expected: hexToBytes('0x731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b'), value: '0000000000000000000000000000000000000000000000000000000000000020' + '0000000000000000000000000000000000000000000000000000000000000020' + @@ -327,10 +337,11 @@ describe('lib/solidity/coder', function() { }) test({ type: 'bytes', - expected: + expected: hexToBytes( '0x131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b' + - '231a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b' + - '331a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b', + '231a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b' + + '331a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b' + ), value: '0000000000000000000000000000000000000000000000000000000000000020' + '0000000000000000000000000000000000000000000000000000000000000060' + @@ -340,9 +351,10 @@ describe('lib/solidity/coder', function() { }) test({ type: 'bytes', - expected: + expected: hexToBytes( '0x731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b' + - '731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b', + '731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b' + ), value: '0000000000000000000000000000000000000000000000000000000000000020' + '0000000000000000000000000000000000000000000000000000000000000040' + @@ -352,10 +364,11 @@ describe('lib/solidity/coder', function() { test({ type: 'bytes[2]', expected: [ - '0x731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134a', - '0x731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b' + hexToBytes('0x731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134a'), + hexToBytes('0x731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b') ], value: + '0000000000000000000000000000000000000000000000000000000000000020' + '0000000000000000000000000000000000000000000000000000000000000040' + '0000000000000000000000000000000000000000000000000000000000000080' + '0000000000000000000000000000000000000000000000000000000000000020' + @@ -366,38 +379,45 @@ describe('lib/solidity/coder', function() { test({ type: 'bytes[][2]', expected: [ - ['0x731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134a'], [ - '0x731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b' + - '731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134c', - '0x731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134d' + hexToBytes('0x00000c8c18f9252830fb3c56471c51335a8262f16a6d70e276417a7c7d897f617fff'), + hexToBytes('0x21f9252830fb3c56471c51335a8262f16a6d70e276417a7c7d897f617fff') + ], + [ + hexToBytes('0x00000c8c18f9252830fb3c56471c51335a8262f16a6d70e276417a7c7d897f617fff'), + hexToBytes('0x21f9252830fb3c56471c51335a8262f16a6d70e276417a7c7d897f617fff') ] ], value: + '0000000000000000000000000000000000000000000000000000000000000020' + '0000000000000000000000000000000000000000000000000000000000000040' + - '0000000000000000000000000000000000000000000000000000000000000080' + - '0000000000000000000000000000000000000000000000000000000000000001' + // 40 - '00000000000000000000000000000000000000000000000000000000000000e0' + - '0000000000000000000000000000000000000000000000000000000000000002' + // 80 - '0000000000000000000000000000000000000000000000000000000000000120' + - '0000000000000000000000000000000000000000000000000000000000000180' + - '0000000000000000000000000000000000000000000000000000000000000020' + // e0 - '731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134a' + - '0000000000000000000000000000000000000000000000000000000000000040' + // 120 - '731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b' + - '731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134c' + - '0000000000000000000000000000000000000000000000000000000000000020' + // 180 - '731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134d' + '0000000000000000000000000000000000000000000000000000000000000140' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '00000000000000000000000000000000000000000000000000000000000000a0' + + '0000000000000000000000000000000000000000000000000000000000000022' + + '00000c8c18f9252830fb3c56471c51335a8262f16a6d70e276417a7c7d897f61' + + '7fff000000000000000000000000000000000000000000000000000000000000' + + '000000000000000000000000000000000000000000000000000000000000001e' + + '21f9252830fb3c56471c51335a8262f16a6d70e276417a7c7d897f617fff0000' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '00000000000000000000000000000000000000000000000000000000000000a0' + + '0000000000000000000000000000000000000000000000000000000000000022' + + '00000c8c18f9252830fb3c56471c51335a8262f16a6d70e276417a7c7d897f61' + + '7fff000000000000000000000000000000000000000000000000000000000000' + + '000000000000000000000000000000000000000000000000000000000000001e' + + '21f9252830fb3c56471c51335a8262f16a6d70e276417a7c7d897f617fff0000' }) test({ type: 'bytes1', - expected: '0xcf', + expected: hexToBytes('0xcf'), value: 'cf00000000000000000000000000000000000000000000000000000000000000' }) test({ type: 'bytes1[4]', - expected: ['0xcf', '0x68', '0x4d', '0xfb'], + expected: [hexToBytes('0xcf'), hexToBytes('0x68'), hexToBytes('0x4d'), hexToBytes('0xfb')], value: 'cf00000000000000000000000000000000000000000000000000000000000000' + '6800000000000000000000000000000000000000000000000000000000000000' + @@ -406,7 +426,7 @@ describe('lib/solidity/coder', function() { }) test({ type: 'bytes32', - expected: '0x6761766f66796f726b0000000000000000000000000000000000000000000000', + expected: hexToBytes('0x6761766f66796f726b0000000000000000000000000000000000000000000000'), value: '6761766f66796f726b0000000000000000000000000000000000000000000000' }) @@ -444,7 +464,7 @@ describe('lib/solidity/coder', function() { }) test({ type: 'bytes', - expected: '0xc3a40000c3a4', + expected: hexToBytes('0xc3a40000c3a4'), value: '0000000000000000000000000000000000000000000000000000000000000020' + '0000000000000000000000000000000000000000000000000000000000000006' + @@ -452,47 +472,12 @@ describe('lib/solidity/coder', function() { }) test({ type: 'bytes32', - expected: '0xc3a40000c3a40000000000000000000000000000000000000000000000000000', + expected: hexToBytes('0xc3a40000c3a40000000000000000000000000000000000000000000000000000'), value: 'c3a40000c3a40000000000000000000000000000000000000000000000000000' }) - test({ - type: 'real', - expected: new bn(1), - value: '0000000000000000000000000000000100000000000000000000000000000000' - }) - test({ - type: 'real', - expected: new bn(2.125), - value: '0000000000000000000000000000000220000000000000000000000000000000' - }) - test({ - type: 'real', - expected: new bn(8.5), - value: '0000000000000000000000000000000880000000000000000000000000000000' - }) - test({ - type: 'real', - expected: new bn(-1), - value: 'ffffffffffffffffffffffffffffffff00000000000000000000000000000000' - }) - test({ - type: 'ureal', - expected: new bn(1), - value: '0000000000000000000000000000000100000000000000000000000000000000' - }) - test({ - type: 'ureal', - expected: new bn(2.125), - value: '0000000000000000000000000000000220000000000000000000000000000000' - }) - test({ - type: 'ureal', - expected: new bn(8.5), - value: '0000000000000000000000000000000880000000000000000000000000000000' - }) test({ type: 'address', - expected: '0x407d73d8a49eeb85d32cf465507dd71d507100c1', + expected: '0x407D73d8a49eeb85D32Cf465507dd71d507100c1', value: '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c1' }) test({ @@ -506,12 +491,13 @@ describe('lib/solidity/coder', function() { }) test({ type: 'bytes', - expected: + expected: hexToBytes( '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + - 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1', + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1' + ), value: '0000000000000000000000000000000000000000000000000000000000000020' + '000000000000000000000000000000000000000000000000000000000000009f' + @@ -521,39 +507,190 @@ describe('lib/solidity/coder', function() { 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff100' }) + + test({ + type: 'tuple(address)', + expected: new Tuple('0xbBF289D846208c16EDc8474705C748aff07732dB'), + value: '000000000000000000000000bbf289d846208c16edc8474705c748aff07732db' + }) + test({ + type: 'tuple(address,address)', + expected: new Tuple('0xbBF289D846208c16EDc8474705C748aff07732dB', '0xbBF289D846208c16EDc8474705C748aff07732dB'), + value: + '000000000000000000000000bbf289d846208c16edc8474705c748aff07732db' + + '000000000000000000000000bbf289d846208c16edc8474705c748aff07732db' + }) + test({ + type: 'tuple(uint256,uint256)', + expected: new Tuple(new bn(5), new bn(5)), + value: + '0000000000000000000000000000000000000000000000000000000000000005' + + '0000000000000000000000000000000000000000000000000000000000000005' + }) + test({ + type: 'tuple(string,string)', + expected: new Tuple('hello', 'world'), + value: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '0000000000000000000000000000000000000000000000000000000000000005' + + '68656c6c6f000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000005' + + '776f726c64000000000000000000000000000000000000000000000000000000' + }) + test({ + type: 'tuple(bytes,bytes)', + expected: new Tuple(hexToBytes('0x01fe517acd15ff'), hexToBytes('0xabcdef12345678')), + value: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '0000000000000000000000000000000000000000000000000000000000000007' + + '01fe517acd15ff00000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000007' + + 'abcdef1234567800000000000000000000000000000000000000000000000000' + }) + test({ + type: 'tuple(bool,bool)', + expected: new Tuple(false, true), + value: + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000001' + }) + test({ + type: 'tuple(uint256,string,bytes)', + expected: new Tuple(new bn(4), 'what what', hexToBytes('0xabcdef12345678')), + value: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000004' + + '0000000000000000000000000000000000000000000000000000000000000060' + + '00000000000000000000000000000000000000000000000000000000000000a0' + + '0000000000000000000000000000000000000000000000000000000000000009' + + '7768617420776861740000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000007' + + 'abcdef1234567800000000000000000000000000000000000000000000000000' + }) + test({ + type: 'tuple(uint128,string,bytes)', + expected: new Tuple(new bn(666), 'encode your kids', hexToBytes('0x656e636f646520796f75722077696665')), + value: + '0000000000000000000000000000000000000000000000000000000000000020' + + '000000000000000000000000000000000000000000000000000000000000029a' + + '0000000000000000000000000000000000000000000000000000000000000060' + + '00000000000000000000000000000000000000000000000000000000000000a0' + + '0000000000000000000000000000000000000000000000000000000000000010' + + '656e636f646520796f7572206b69647300000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000010' + + '656e636f646520796f7572207769666500000000000000000000000000000000' + }) + test({ + type: 'tuple(string,bytes32,uint256,bool)', + expected: new Tuple( + 'foo bar', + hexToBytes('0xaabbccddeeff0000000000000000000000000000000000000000000000000000'), + new bn(321), + true + ), + value: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000080' + + 'aabbccddeeff0000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000141' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000007' + + '666f6f2062617200000000000000000000000000000000000000000000000000' + }) + test({ + type: 'tuple(uint8,uint8,uint8,uint8,string,address,bool)', + expected: new Tuple(1, 2, 3, 4, 'five', '0x0000000000000000000000000000000000000006', true), + value: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000003' + + '0000000000000000000000000000000000000000000000000000000000000004' + + '00000000000000000000000000000000000000000000000000000000000000e0' + + '0000000000000000000000000000000000000000000000000000000000000006' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000004' + + '6669766500000000000000000000000000000000000000000000000000000000' + }) + test({ + type: 'tuple(tuple(address,address),tuple(uint256,uint256))', + expected: [ + ['0x1234567890123456789012345678901234567890', '0x1234567890123456789012345678901234567890'], + [new bn(5), new bn(6)] + ], + value: + '0000000000000000000000001234567890123456789012345678901234567890' + + '0000000000000000000000001234567890123456789012345678901234567890' + + '0000000000000000000000000000000000000000000000000000000000000005' + + '0000000000000000000000000000000000000000000000000000000000000006' + }) + test({ + type: 'tuple(tuple(address,address),tuple(uint256,uint256),tuple(string,string))', + expected: [ + ['0x1234567890123456789012345678901234567890', '0x1234567890123456789012345678901234567890'], + [new bn(5), new bn(6)], + ['a string', 'another string'] + ], + value: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000001234567890123456789012345678901234567890' + + '0000000000000000000000001234567890123456789012345678901234567890' + + '0000000000000000000000000000000000000000000000000000000000000005' + + '0000000000000000000000000000000000000000000000000000000000000006' + + '00000000000000000000000000000000000000000000000000000000000000a0' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '0000000000000000000000000000000000000000000000000000000000000008' + + '6120737472696e67000000000000000000000000000000000000000000000000' + + '000000000000000000000000000000000000000000000000000000000000000e' + + '616e6f7468657220737472696e67000000000000000000000000000000000000' + }) }) }) -describe('lib/solidity/coder', function() { - describe('decodeParams', function() { - let test = function(t) { - it('should turn ' + t.values + ' to ' + t.expected, function() { +describe('lib/solidity/coder', function () { + describe('decodeParams', function () { + let test = function (t) { + it('should turn ' + t.values + ' to ' + t.expected, function () { + expect( + coder.coder.decodeParams( + t.types.map((type) => parseParamType(type)), + t.values + ) + ).toEqual(t.expected) expect(coder.coder.decodeParams(t.types, t.values)).toEqual(t.expected) }) } test({ types: ['address'], - expected: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1'], + expected: ['0x407D73d8a49eeb85D32Cf465507dd71d507100c1'], values: '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c1' }) test({ types: ['address', 'address'], - expected: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1', '0x407d73d8a49eeb85d32cf465507dd71d507100c3'], + expected: ['0x407D73d8a49eeb85D32Cf465507dd71d507100c1', '0x407D73d8A49eEB85D32Cf465507Dd71d507100c3'], values: '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c1' + '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c3' }) test({ types: ['address', 'address'], - expected: ['0x6224fe0bea79701d338cf65ebc0da0caa566c544', '0xBF79cE2fbd819e5aBC2327563D02a200255B7Cb3'], + expected: ['0x6224fe0bEA79701d338Cf65ebc0DA0CaA566C544', '0xBF79cE2fbd819e5aBC2327563D02a200255B7Cb3'], values: '0000000000000000000000006224fe0bea79701d338cf65ebc0da0caa566c544' + '000000000000000000000000BF79cE2fbd819e5aBC2327563D02a200255B7Cb3' }) test({ types: ['bool[2]', 'bool[3]'], - expected: [[true, false], [false, false, true]], + expected: [ + [true, false], + [false, false, true] + ], values: '0000000000000000000000000000000000000000000000000000000000000001' + '0000000000000000000000000000000000000000000000000000000000000000' + @@ -563,7 +700,10 @@ describe('lib/solidity/coder', function() { }) test({ types: ['int[2]', 'int256[3]'], - expected: [[new bn(1), new bn(2)], [new bn(3), new bn(4), new bn(5)]], + expected: [ + [new bn(1), new bn(2)], + [new bn(3), new bn(4), new bn(5)] + ], values: '0000000000000000000000000000000000000000000000000000000000000001' + '0000000000000000000000000000000000000000000000000000000000000002' + @@ -578,7 +718,10 @@ describe('lib/solidity/coder', function() { }) test({ types: ['uint[2]', 'uint256[3]'], - expected: [[new bn(1), new bn(2)], [new bn(3), new bn(4), new bn(5)]], + expected: [ + [new bn(1), new bn(2)], + [new bn(3), new bn(4), new bn(5)] + ], values: '0000000000000000000000000000000000000000000000000000000000000001' + '0000000000000000000000000000000000000000000000000000000000000002' + @@ -593,14 +736,14 @@ describe('lib/solidity/coder', function() { }) test({ types: ['bytes1', 'bytes1'], - expected: ['0xaa', '0xbb'], + expected: [new Uint8Array([0xaa]), new Uint8Array([0xbb])], values: 'aa00000000000000000000000000000000000000000000000000000000000000' + 'bb00000000000000000000000000000000000000000000000000000000000000' }) test({ types: ['bytes1[2]', 'bytes1'], - expected: [['0xaa', '0xbb'], '0xcc'], + expected: [[new Uint8Array([0xaa]), new Uint8Array([0xbb])], new Uint8Array([0xcc])], values: 'aa00000000000000000000000000000000000000000000000000000000000000' + 'bb00000000000000000000000000000000000000000000000000000000000000' + @@ -609,8 +752,8 @@ describe('lib/solidity/coder', function() { test({ types: ['bytes', 'bytes'], expected: [ - '0x731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b', - '0x731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134c' + hexToBytes('0x731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b'), + hexToBytes('0x731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134c') ], values: '0000000000000000000000000000000000000000000000000000000000000040' + @@ -632,14 +775,14 @@ describe('lib/solidity/coder', function() { }) test({ types: ['bytes32', 'int'], - expected: ['0x6761766f66796f726b0000000000000000000000000000000000000000000000', new bn(5)], + expected: [hexToBytes('0x6761766f66796f726b0000000000000000000000000000000000000000000000'), new bn(5)], values: '6761766f66796f726b0000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000000000005' }) test({ types: ['int', 'bytes32'], - expected: [new bn(5), '0x6761766f66796f726b0000000000000000000000000000000000000000000000'], + expected: [new bn(5), hexToBytes('0x6761766f66796f726b0000000000000000000000000000000000000000000000')], values: '0000000000000000000000000000000000000000000000000000000000000005' + '6761766f66796f726b0000000000000000000000000000000000000000000000' @@ -665,11 +808,15 @@ describe('lib/solidity/coder', function() { types: ['int', 'bytes', 'int', 'bytes'], expected: [ new bn(5), - '0x131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b' + - '231a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b', + hexToBytes( + '0x131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b' + + '231a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b' + ), new bn(3), - '0x331a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b' + - '431a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b' + hexToBytes( + '0x331a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b' + + '431a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b' + ) ], values: '0000000000000000000000000000000000000000000000000000000000000005' + @@ -685,7 +832,7 @@ describe('lib/solidity/coder', function() { }) test({ types: ['address[2][1]', 'bool'], - expected: [[['0x407d73d8a49eeb85d32cf465507dd71d507100c1', '0x407d73d8a49eeb85d32cf465507dd71d507100c3']], false], + expected: [[['0x407D73d8a49eeb85D32Cf465507dd71d507100c1', '0x407D73d8A49eEB85D32Cf465507Dd71d507100c3']], false], values: '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c1' + '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c3' + @@ -701,7 +848,7 @@ describe('lib/solidity/coder', function() { }) test({ types: ['bytes1[2][1]', 'bool'], - expected: [[['0xaa', '0xbb']], true], + expected: [[[hexToBytes('0xaa'), hexToBytes('0xbb')]], true], values: 'aa00000000000000000000000000000000000000000000000000000000000000' + 'bb00000000000000000000000000000000000000000000000000000000000000' + @@ -716,28 +863,304 @@ describe('lib/solidity/coder', function() { '0000000000000000000000000000000000000000000000000000000000000001' }) test({ - types: ['real[2][1]', 'bool'], - expected: [[[new bn(1), new bn(2.125)]], true], + types: ['uint[2][1]', 'bool'], + expected: [[[new bn(1), new bn(2)]], true], values: - '0000000000000000000000000000000100000000000000000000000000000000' + - '0000000000000000000000000000000220000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002' + '0000000000000000000000000000000000000000000000000000000000000001' }) test({ - types: ['uint[2][1]', 'bool'], - expected: [[[new bn(1), new bn(2)]], true], + types: ['tuple(address,address)', 'tuple(string,string)', 'bool'], + expected: [ + ['0x1234567890123456789012345678901234567890', '0x1234567890123456789012345678901234567890'], + ['hello', 'world'], + false + ], + values: + '0000000000000000000000001234567890123456789012345678901234567890' + + '0000000000000000000000001234567890123456789012345678901234567890' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '0000000000000000000000000000000000000000000000000000000000000005' + + '68656c6c6f000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000005' + + '776f726c64000000000000000000000000000000000000000000000000000000' + }) + + test({ + types: ['string', 'tuple(uint256,string)', 'bool', 'tuple(bytes32,bytes)'], + expected: [ + 'the string', + new Tuple(new bn(56), 'some string'), + true, + new Tuple( + hexToBytes('0x1234567890123456789012345678901234567890123456789012345678901234'), + hexToBytes('0x129581') + ) + ], + values: + '0000000000000000000000000000000000000000000000000000000000000080' + + '00000000000000000000000000000000000000000000000000000000000000c0' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000140' + + '000000000000000000000000000000000000000000000000000000000000000a' + + '74686520737472696e6700000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000038' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '000000000000000000000000000000000000000000000000000000000000000b' + + '736f6d6520737472696e67000000000000000000000000000000000000000000' + + '1234567890123456789012345678901234567890123456789012345678901234' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000003' + + '1295810000000000000000000000000000000000000000000000000000000000' + }) + test({ + types: [ + 'bool', + 'tuple(bool,tuple(bool,tuple(bool,bool)))', + 'tuple(uint256,tuple(uint256,uint256,tuple(uint256,string)))', + 'string' + ], + expected: [ + true, + [false, [true, [false, true]]], + [new bn(256), [new bn(76), new bn(67), [new bn(1337), 'hello']]], + 'last param' + ], values: '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '00000000000000000000000000000000000000000000000000000000000000e0' + + '0000000000000000000000000000000000000000000000000000000000000200' + + '0000000000000000000000000000000000000000000000000000000000000100' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '000000000000000000000000000000000000000000000000000000000000004c' + + '0000000000000000000000000000000000000000000000000000000000000043' + + '0000000000000000000000000000000000000000000000000000000000000060' + + '0000000000000000000000000000000000000000000000000000000000000539' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000005' + + '68656c6c6f000000000000000000000000000000000000000000000000000000' + + '000000000000000000000000000000000000000000000000000000000000000a' + + '6c61737420706172616d00000000000000000000000000000000000000000000' + }) + + test({ + types: ['string', 'tuple(string,string,tuple(string,string))', 'bytes', 'tuple(bytes,tuple(bytes,string))'], + expected: [ + 'hello world', + new Tuple('what', 'is', new Tuple('even', 'happening')), + hexToBytes('0x696e'), + new Tuple(hexToBytes('0x74686973'), new Tuple(hexToBytes('0x676f64666f7273616b656e'), 'test')) + ], + values: + '0000000000000000000000000000000000000000000000000000000000000080' + + '00000000000000000000000000000000000000000000000000000000000000c0' + + '0000000000000000000000000000000000000000000000000000000000000260' + + '00000000000000000000000000000000000000000000000000000000000002a0' + + '000000000000000000000000000000000000000000000000000000000000000b' + + '68656c6c6f20776f726c64000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000060' + + '00000000000000000000000000000000000000000000000000000000000000a0' + + '00000000000000000000000000000000000000000000000000000000000000e0' + + '0000000000000000000000000000000000000000000000000000000000000004' + + '7768617400000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000000000002' + - '0000000000000000000000000000000000000000000000000000000000000001' + '6973000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '0000000000000000000000000000000000000000000000000000000000000004' + + '6576656e00000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000009' + + '68617070656e696e670000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '696e000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '0000000000000000000000000000000000000000000000000000000000000004' + + '7468697300000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '000000000000000000000000000000000000000000000000000000000000000b' + + '676f64666f7273616b656e000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000004' + + '7465737400000000000000000000000000000000000000000000000000000000' }) test({ - types: ['ureal[2][1]', 'bool'], - expected: [[[new bn(1), new bn(2.125)]], true], + types: [ + 'string', + 'bool', + 'tuple(string,tuple(tuple(bool,string,bool),tuple(string,bytes32,bytes)))', + 'bytes', + 'tuple(bool,tuple(bytes32,address),bytes)' + ], + expected: new Tuple( + 'this', + true, + new Tuple( + 'is', + new Tuple( + new Tuple(true, 'utter', true), + new Tuple( + 'madness', + hexToBytes('0x1234567890123456789012345678901234567890123456789012345678901234'), + hexToBytes('0x6275742049206c6f7665206974') + ) + ) + ), + hexToBytes('0x6265636175736520697420776f726b73'), + new Tuple( + true, + new Tuple( + hexToBytes('0x1234567890123456789012345678901234567890123456789012345678901234'), + '0x1337133713371337133713371337133713371337' + ), + hexToBytes('0x6265636175736520697420776f726b736265636175736520697420776f726b73') + ) + ), values: - '0000000000000000000000000000000100000000000000000000000000000000' + - '0000000000000000000000000000000220000000000000000000000000000000' + - '0000000000000000000000000000000000000000000000000000000000000001' + '00000000000000000000000000000000000000000000000000000000000000a0' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '00000000000000000000000000000000000000000000000000000000000000e0' + + '0000000000000000000000000000000000000000000000000000000000000320' + + '0000000000000000000000000000000000000000000000000000000000000360' + + '0000000000000000000000000000000000000000000000000000000000000004' + + '7468697300000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '6973000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '00000000000000000000000000000000000000000000000000000000000000e0' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000060' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000005' + + '7574746572000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000060' + + '1234567890123456789012345678901234567890123456789012345678901234' + + '00000000000000000000000000000000000000000000000000000000000000a0' + + '0000000000000000000000000000000000000000000000000000000000000007' + + '6d61646e65737300000000000000000000000000000000000000000000000000' + + '000000000000000000000000000000000000000000000000000000000000000d' + + '6275742049206c6f766520697400000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000010' + + '6265636175736520697420776f726b7300000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '1234567890123456789012345678901234567890123456789012345678901234' + + '0000000000000000000000001337133713371337133713371337133713371337' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '0000000000000000000000000000000000000000000000000000000000000020' + + '6265636175736520697420776f726b736265636175736520697420776f726b73' + }) + test({ + types: [ + 'tuple(string,tuple(bool,bool))', + 'address', + 'bytes', + 'tuple(bytes,bytes,string,bool,address)', + 'int256', + 'tuple(int256,uint256,tuple(string,int256,address))' + ], + expected: [ + new Tuple('this is more reasonable', new Tuple(true, false)), + '0x1b3F5FE0Fd513E6cbdEE459F0b0e19095FE91958', + hexToBytes('0x6c6f6c6f6c6f6c6f6c'), + new Tuple( + hexToBytes('0xabcdef12345678'), + hexToBytes('0x87654321fedcba'), + 'bazbar', + false, + '0xd13b6e9058E58B8677233CEc2315e1D9e77C79C4' + ), + new bn(-6), + new Tuple(new bn(-7), new bn(5), new Tuple('foobar', new bn(-8), '0xB1eeF147028E9f480DbC5ccaA3277D417D1b85F0')) + ], + values: + '00000000000000000000000000000000000000000000000000000000000000c0' + + '0000000000000000000000001b3f5fe0fd513e6cbdee459f0b0e19095fe91958' + + '0000000000000000000000000000000000000000000000000000000000000160' + + '00000000000000000000000000000000000000000000000000000000000001a0' + + 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa' + + '0000000000000000000000000000000000000000000000000000000000000300' + + '0000000000000000000000000000000000000000000000000000000000000060' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000017' + + '74686973206973206d6f726520726561736f6e61626c65000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000009' + + '6c6f6c6f6c6f6c6f6c0000000000000000000000000000000000000000000000' + + '00000000000000000000000000000000000000000000000000000000000000a0' + + '00000000000000000000000000000000000000000000000000000000000000e0' + + '0000000000000000000000000000000000000000000000000000000000000120' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '000000000000000000000000d13b6e9058e58b8677233cec2315e1d9e77c79c4' + + '0000000000000000000000000000000000000000000000000000000000000007' + + 'abcdef1234567800000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000007' + + '87654321fedcba00000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000006' + + '62617a6261720000000000000000000000000000000000000000000000000000' + + 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9' + + '0000000000000000000000000000000000000000000000000000000000000005' + + '0000000000000000000000000000000000000000000000000000000000000060' + + '0000000000000000000000000000000000000000000000000000000000000060' + + 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8' + + '000000000000000000000000b1eef147028e9f480dbc5ccaa3277d417d1b85f0' + + '0000000000000000000000000000000000000000000000000000000000000006' + + '666f6f6261720000000000000000000000000000000000000000000000000000' + }) + test({ + types: [ + 'tuple(bytes32,bool,bytes32)', + 'address', + 'tuple(bytes32,bytes32,string)', + 'tuple(tuple(address,bool),tuple(address,bytes32),tuple(int256,uint256))' + ], + expected: [ + new Tuple( + hexToBytes('0xffffffffffffffffffffffffffffffffabdef123849181759adebfadecaefbae'), + true, + hexToBytes('0xffffffffffffffffffffffffffffffffabdef123849181759adebfadecaefbae') + ), + '0x1234567890123456789012345678901234567890', + new Tuple( + hexToBytes('0x0ab3e6dfa1594c15af0000000000000000000000000000000000000000000000'), + hexToBytes('0xffffffffffffffffffffffffffffffffabdef123849181759adebfadecaefbae'), + 'string' + ), + [ + new Tuple('0x1234567890123456789012345678901234567890', true), + new Tuple( + '0x1234567890123456789012345678901234567890', + hexToBytes('0xffffffffffffffffffffffffffffffffabdef123849181759adebfadecaefbae') + ), + new Tuple(new bn(-6124612), new bn(89000)) + ] + ], + values: + 'ffffffffffffffffffffffffffffffffabdef123849181759adebfadecaefbae' + + '0000000000000000000000000000000000000000000000000000000000000001' + + 'ffffffffffffffffffffffffffffffffabdef123849181759adebfadecaefbae' + + '0000000000000000000000001234567890123456789012345678901234567890' + + '0000000000000000000000000000000000000000000000000000000000000160' + + '0000000000000000000000001234567890123456789012345678901234567890' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000001234567890123456789012345678901234567890' + + 'ffffffffffffffffffffffffffffffffabdef123849181759adebfadecaefbae' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa28bbc' + + '0000000000000000000000000000000000000000000000000000000000015ba8' + + '0ab3e6dfa1594c15af0000000000000000000000000000000000000000000000' + + 'ffffffffffffffffffffffffffffffffabdef123849181759adebfadecaefbae' + + '0000000000000000000000000000000000000000000000000000000000000060' + + '0000000000000000000000000000000000000000000000000000000000000006' + + '737472696e670000000000000000000000000000000000000000000000000000' }) }) }) diff --git a/test/coder.encodeParam.ts b/test/coder.encodeParam.ts index d6368742..12864980 100644 --- a/test/coder.encodeParam.ts +++ b/test/coder.encodeParam.ts @@ -1,17 +1,20 @@ import * as expect from 'expect' import * as coder from '../src/solidity/coder' +import { hexToBytes } from '../src' +import { parseParamType } from '../src/abi/parser' describe('lib/solidity/coder', function () { describe('encodeParam', function () { let test = function (t) { - it('should turn ' + t.value + ' to ' + t.expected, function () { - expect(coder.coder.encodeParam(t.type, t.value)).toEqual(t.expected) + it('should turn ' + t.type + ' ' + t.value + ' to ' + t.expected, function () { + expect(coder.coder.encodeParams([parseParamType(t.type)], [t.value])).toEqual(t.expected) + expect(coder.coder.encodeParams([t.type], [t.value])).toEqual(t.expected) }) } test({ type: 'address', - value: '0x407d73d8a49eeb85d32cf465507dd71d507100c1', + value: '0x407D73d8a49eeb85D32Cf465507dd71d507100c1', expected: '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c1' }) test({ @@ -21,21 +24,21 @@ describe('lib/solidity/coder', function () { }) test({ type: 'address[2]', - value: ['0xBF79cE2fbd819e5aBC2327563D02a200255B7Cb3', '0x407d73d8a49eeb85d32cf465507dd71d507100c3'], + value: ['0xBF79cE2fbd819e5aBC2327563D02a200255B7Cb3', '0x407D73d8A49eEB85D32Cf465507Dd71d507100c3'], expected: '000000000000000000000000bf79ce2fbd819e5abc2327563d02a200255b7cb3' + '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c3' }) test({ type: 'address[2]', - value: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1', '0x407d73d8a49eeb85d32cf465507dd71d507100c3'], + value: ['0x407D73d8a49eeb85D32Cf465507dd71d507100c1', '0x407D73d8A49eEB85D32Cf465507Dd71d507100c3'], expected: '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c1' + '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c3' }) test({ type: 'address[]', - value: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1', '0x407d73d8a49eeb85d32cf465507dd71d507100c3'], + value: ['0x407D73d8a49eeb85D32Cf465507dd71d507100c1', '0x407D73d8A49eEB85D32Cf465507Dd71d507100c3'], expected: '0000000000000000000000000000000000000000000000000000000000000020' + '0000000000000000000000000000000000000000000000000000000000000002' + @@ -45,10 +48,11 @@ describe('lib/solidity/coder', function () { test({ type: 'address[][2]', value: [ - ['0x407d73d8a49eeb85d32cf465507dd71d507100c1', '0x407d73d8a49eeb85d32cf465507dd71d507100c2'], - ['0x407d73d8a49eeb85d32cf465507dd71d507100c3', '0x407d73d8a49eeb85d32cf465507dd71d507100c4'] + ['0x407D73d8a49eeb85D32Cf465507dd71d507100c1', '0x407d73d8a49EEB85d32Cf465507dD71D507100c2'], + ['0x407D73d8A49eEB85D32Cf465507Dd71d507100c3', '0x407D73d8a49eeb85D32CF465507dd71d507100C4'] ], expected: + '0000000000000000000000000000000000000000000000000000000000000020' + '0000000000000000000000000000000000000000000000000000000000000040' + '00000000000000000000000000000000000000000000000000000000000000a0' + '0000000000000000000000000000000000000000000000000000000000000002' + @@ -61,8 +65,8 @@ describe('lib/solidity/coder', function () { test({ type: 'address[2][]', value: [ - ['0x407d73d8a49eeb85d32cf465507dd71d507100c1', '0x407d73d8a49eeb85d32cf465507dd71d507100c2'], - ['0x407d73d8a49eeb85d32cf465507dd71d507100c3', '0x407d73d8a49eeb85d32cf465507dd71d507100c4'] + ['0x407D73d8a49eeb85D32Cf465507dd71d507100c1', '0x407d73d8a49EEB85d32Cf465507dD71D507100c2'], + ['0x407D73d8A49eEB85D32Cf465507Dd71d507100c3', '0x407D73d8a49eeb85D32CF465507dd71d507100C4'] ], expected: '0000000000000000000000000000000000000000000000000000000000000020' + @@ -188,15 +192,6 @@ describe('lib/solidity/coder', function () { value: '0xc3a40000c3a4', expected: 'c3a40000c3a40000000000000000000000000000000000000000000000000000' }) - test({ - type: 'bytes64', - value: - '0xc3a40000c3a40000000000000000000000000000000000000000000000000000' + - 'c3a40000c3a40000000000000000000000000000000000000000000000000000', - expected: - 'c3a40000c3a40000000000000000000000000000000000000000000000000000' + - 'c3a40000c3a40000000000000000000000000000000000000000000000000000' - }) test({ type: 'string', value: 'ää', @@ -264,13 +259,6 @@ describe('lib/solidity/coder', function () { 'fb00000000000000000000000000000000000000000000000000000000000000' }) - test({ type: 'real', value: 1, expected: '0000000000000000000000000000000100000000000000000000000000000000' }) - test({ type: 'real', value: 2.125, expected: '0000000000000000000000000000000220000000000000000000000000000000' }) - test({ type: 'real', value: 8.5, expected: '0000000000000000000000000000000880000000000000000000000000000000' }) - test({ type: 'real', value: -1, expected: 'ffffffffffffffffffffffffffffffff00000000000000000000000000000000' }) - test({ type: 'ureal', value: 1, expected: '0000000000000000000000000000000100000000000000000000000000000000' }) - test({ type: 'ureal', value: 2.125, expected: '0000000000000000000000000000000220000000000000000000000000000000' }) - test({ type: 'ureal', value: 8.5, expected: '0000000000000000000000000000000880000000000000000000000000000000' }) test({ type: 'bytes', value: @@ -304,6 +292,192 @@ describe('lib/solidity/coder', function () { '77656c636f6d6520746f20657468657265756d2e2077656c636f6d6520746f20' + '657468657265756d2e2077656c636f6d6520746f20657468657265756d2e0000' }) + + test({ + type: 'tuple(string,string)', + value: ['welcome to ethereum.', 'welcome to ethereum.'], + expected: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '0000000000000000000000000000000000000000000000000000000000000014' + + '77656c636f6d6520746f20657468657265756d2e000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000014' + + '77656c636f6d6520746f20657468657265756d2e000000000000000000000000' + }) + test({ + type: 'tuple(bytes,bytes)', + value: ['0x77656c636f6d6520746f20657468657265756d2e', '0x77656c636f6d6520746f20657468657265756d2e'], + expected: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '0000000000000000000000000000000000000000000000000000000000000014' + + '77656c636f6d6520746f20657468657265756d2e000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000014' + + '77656c636f6d6520746f20657468657265756d2e000000000000000000000000' + }) + test({ + type: 'tuple(bytes,bool,uint256)', + value: ['0x77656c636f6d6520746f20657468657265756d2e', true, 124515], + expected: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000060' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '000000000000000000000000000000000000000000000000000000000001e663' + + '0000000000000000000000000000000000000000000000000000000000000014' + + '77656c636f6d6520746f20657468657265756d2e000000000000000000000000' + }) + test({ + type: 'tuple(string,tuple(bool,int256),address)', + value: ['hello', [true, -151], '0x0175010374017501037401750103740175010374'], + expected: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '0000000000000000000000000000000000000000000000000000000000000001' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff69' + + '0000000000000000000000000175010374017501037401750103740175010374' + + '0000000000000000000000000000000000000000000000000000000000000005' + + '68656c6c6f000000000000000000000000000000000000000000000000000000' + }) + test({ + type: 'tuple(tuple(bool,bool),tuple(address,address),tuple(string,string))', + value: [ + [true, false], + ['0x81017589ab81017589ab81017589ab81017589ab', '0x81017589ab81017589ab81017589ab81017589ab'], + ['string One', 'string Two'] + ], + expected: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '00000000000000000000000081017589ab81017589ab81017589ab81017589ab' + + '00000000000000000000000081017589ab81017589ab81017589ab81017589ab' + + '00000000000000000000000000000000000000000000000000000000000000a0' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '000000000000000000000000000000000000000000000000000000000000000a' + + '737472696e67204f6e6500000000000000000000000000000000000000000000' + + '000000000000000000000000000000000000000000000000000000000000000a' + + '737472696e672054776f00000000000000000000000000000000000000000000' + }) + test({ + type: 'tuple(tuple(tuple(bool,bool),tuple(bytes,bytes),tuple(address,bool)),address)', + value: [ + [ + [false, false], + ['0xab1394581edfa2ef9ca71', '0x15abe391df19aef19a4561'], + ['0xec2270c849236333c86834728e783cd2f789088e', true] + ], + '0x81017589ab81017589ab81017589ab81017589ab' + ], + expected: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '00000000000000000000000081017589ab81017589ab81017589ab81017589ab' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '00000000000000000000000000000000000000000000000000000000000000a0' + + '000000000000000000000000ec2270c849236333c86834728e783cd2f789088e' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '000000000000000000000000000000000000000000000000000000000000000b' + + '0ab1394581edfa2ef9ca71000000000000000000000000000000000000000000' + + '000000000000000000000000000000000000000000000000000000000000000b' + + '15abe391df19aef19a4561000000000000000000000000000000000000000000' + }) + test({ + type: 'tuple(bool,string,bool,tuple(address,address))', + value: [ + true, + 'testing', + false, + ['0x1981710abe1981710abe1981710abe1981710abe', '0x1981710abe1981710abe1981710abe1981710abe'] + ], + expected: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '00000000000000000000000000000000000000000000000000000000000000a0' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000001981710abe1981710abe1981710abe1981710abe' + + '0000000000000000000000001981710abe1981710abe1981710abe1981710abe' + + '0000000000000000000000000000000000000000000000000000000000000007' + + '74657374696e6700000000000000000000000000000000000000000000000000' + }) + test({ + type: 'tuple(address,address,tuple(string,tuple(int256,int256),string))', + value: [ + '0x1981710abe1981710abe1981710abe1981710abe', + '0x1981710abe1981710abe1981710abe1981710abe', + ['structs are great', [-1951, 194018], 'so many possibilities'] + ], + expected: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000001981710abe1981710abe1981710abe1981710abe' + + '0000000000000000000000001981710abe1981710abe1981710abe1981710abe' + + '0000000000000000000000000000000000000000000000000000000000000060' + + '0000000000000000000000000000000000000000000000000000000000000080' + + 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff861' + + '000000000000000000000000000000000000000000000000000000000002f5e2' + + '00000000000000000000000000000000000000000000000000000000000000c0' + + '0000000000000000000000000000000000000000000000000000000000000011' + + '7374727563747320617265206772656174000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000015' + + '736f206d616e7920706f73736962696c69746965730000000000000000000000' + }) + test({ + type: 'tuple(bool,tuple(bytes32,int256,tuple(bytes24,bytes8)),tuple(bool,bool,bool),string)', + value: [ + true, + [ + '0xabdef18710a18a18abdef18710a18a18abdef18710a18a18abdef18710a18a18', + -18291849, + ['0xabdef18710a18a18abdef18710a18a18abdef18710a18a18', '0xabdef18710a18a18'] + ], + [false, true, false], + 'testing testing' + ], + expected: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000001' + + 'abdef18710a18a18abdef18710a18a18abdef18710a18a18abdef18710a18a18' + + 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffffee8e377' + + 'abdef18710a18a18abdef18710a18a18abdef18710a18a180000000000000000' + + 'abdef18710a18a18000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000120' + + '000000000000000000000000000000000000000000000000000000000000000f' + + '74657374696e672074657374696e670000000000000000000000000000000000' + }) + test({ + type: 'tuple(bool,tuple(bytes32,int256,tuple(bytes24,bytes8)),tuple(bool,bool,bool),string)', + value: [ + true, + [ + hexToBytes('0xdeadbeef01'), + -18291849, + ['0xabdef18710a18a18abdef18710a18a18abdef18710a18a18', '0xabdef18710a18a18'] + ], + [false, true, false], + 'testing testing' + ], + expected: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000001' + + 'deadbeef01000000000000000000000000000000000000000000000000000000' + + 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffffee8e377' + + 'abdef18710a18a18abdef18710a18a18abdef18710a18a180000000000000000' + + 'abdef18710a18a18000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000120' + + '000000000000000000000000000000000000000000000000000000000000000f' + + '74657374696e672074657374696e670000000000000000000000000000000000' + }) }) }) @@ -311,13 +485,19 @@ describe('lib/solidity/coder', function () { describe('encodeParams', function () { let test = function (t) { it('should turn ' + t.values + ' to ' + t.expected, function () { + expect( + coder.coder.encodeParams( + t.types.map((type) => parseParamType(type)), + t.values + ) + ).toEqual(t.expected) expect(coder.coder.encodeParams(t.types, t.values)).toEqual(t.expected) }) } test({ types: ['address', 'address'], - values: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1', '0x407d73d8a49eeb85d32cf465507dd71d507100c3'], + values: ['0x407D73d8a49eeb85D32Cf465507dd71d507100c1', '0x407D73d8A49eEB85D32Cf465507Dd71d507100c3'], expected: '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c1' + '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c3' @@ -559,9 +739,313 @@ describe('lib/solidity/coder', function () { '0000000000000000000000000000000000000000000000000000000000000020' + '4d00000000000000000000000000000000000000000000000000000000000012' }) - }) + test({ + types: ['string', 'tuple(string,string)'], + values: ['what', ['what what', 'what what']], + expected: + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '0000000000000000000000000000000000000000000000000000000000000004' + + '7768617400000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '0000000000000000000000000000000000000000000000000000000000000009' + + '7768617420776861740000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000009' + + '7768617420776861740000000000000000000000000000000000000000000000' + }) + test({ + types: ['tuple(bytes32,bool)', 'tuple(bool,address)'], + values: [ + ['0xabdef18710a18a18abdef18710a18a18abdef18710a18a18abdef18710a18a18', true], + [true, '0x77656c636f6d6520746f20657468657265756d2e'] + ], + expected: + 'abdef18710a18a18abdef18710a18a18abdef18710a18a18abdef18710a18a18' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '00000000000000000000000077656c636f6d6520746f20657468657265756d2e' + }) + test({ + types: ['tuple(bytes32,bool)', 'tuple(bool,address)'], + values: [ + ['0xabdef18710a18a18abdef18710a18a18abdef18710a18a18abdef18710a18a18', true], + [true, '0x77656c636f6d6520746f20657468657265756d2e'] + ], + expected: + 'abdef18710a18a18abdef18710a18a18abdef18710a18a18abdef18710a18a18' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '00000000000000000000000077656c636f6d6520746f20657468657265756d2e' + }) + test({ + types: ['tuple(address,uint256)', 'tuple(uint256,bool)', 'tuple(bytes32,bytes32)'], + values: [ + ['0x77656c636f6d6520746f20657468657265756d2e', '148'], + ['5910', true], + [ + '0xabdef18710a18a18abdef18710a18a18abdef18710a18a18abdef18710a18a18', + '0xabdef18710a18a18abdef18710a18a18abdef18710a18a18abdef18710a18a18' + ] + ], + expected: + '00000000000000000000000077656c636f6d6520746f20657468657265756d2e' + + '0000000000000000000000000000000000000000000000000000000000000094' + + '0000000000000000000000000000000000000000000000000000000000001716' + + '0000000000000000000000000000000000000000000000000000000000000001' + + 'abdef18710a18a18abdef18710a18a18abdef18710a18a18abdef18710a18a18' + + 'abdef18710a18a18abdef18710a18a18abdef18710a18a18abdef18710a18a18' + }) + + test({ + types: [ + 'tuple(tuple(address,address),tuple(bool,bool))', + 'tuple(tuple(bool,bool),tuple(bytes,bytes),tuple(uint256,uint256))', + 'address' + ], + values: [ + [ + ['0x77656c636f6d6520746f20657468657265756d2e', '0x77656c636f6d6520746f20657468657265756d2e'], + [true, false] + ], + [ + [false, true], + ['0xab1394581edfa2ef9ca71', '0x15abe391df19aef19a4561'], + ['182', '1937'] + ], + '0x77656c636f6d6520746f20657468657265756d2e' + ], + expected: + '00000000000000000000000077656c636f6d6520746f20657468657265756d2e' + + '00000000000000000000000077656c636f6d6520746f20657468657265756d2e' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '00000000000000000000000000000000000000000000000000000000000000c0' + + '00000000000000000000000077656c636f6d6520746f20657468657265756d2e' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '00000000000000000000000000000000000000000000000000000000000000a0' + + '00000000000000000000000000000000000000000000000000000000000000b6' + + '0000000000000000000000000000000000000000000000000000000000000791' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '000000000000000000000000000000000000000000000000000000000000000b' + + '0ab1394581edfa2ef9ca71000000000000000000000000000000000000000000' + + '000000000000000000000000000000000000000000000000000000000000000b' + + '15abe391df19aef19a4561000000000000000000000000000000000000000000' + }) + test({ + types: [ + 'tuple(tuple(uint256,bool),tuple(uint256,tuple(bytes,bytes)))', + 'bytes', + 'tuple(bool,tuple(bytes,address),bytes)' + ], + values: [ + [ + ['18320', true], + ['691', ['0xab1394581edfa2ef9ca71', '0x15abe391df19aef19a4561']] + ], + '0xab1394581edfa2ef9ca71', + [ + false, + ['0xfe', '0x77656c636f6d6520746f20657468657265756d2e'], + '0xabdef18710a18a18abdef18710a18a18abdef18710a18a18abdef18710a18a18' + ] + ], + expected: + '0000000000000000000000000000000000000000000000000000000000000060' + + '00000000000000000000000000000000000000000000000000000000000001c0' + + '0000000000000000000000000000000000000000000000000000000000000200' + + '0000000000000000000000000000000000000000000000000000000000004790' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000060' + + '00000000000000000000000000000000000000000000000000000000000002b3' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '000000000000000000000000000000000000000000000000000000000000000b' + + '0ab1394581edfa2ef9ca71000000000000000000000000000000000000000000' + + '000000000000000000000000000000000000000000000000000000000000000b' + + '15abe391df19aef19a4561000000000000000000000000000000000000000000' + + '000000000000000000000000000000000000000000000000000000000000000b' + + '0ab1394581edfa2ef9ca71000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000060' + + '00000000000000000000000000000000000000000000000000000000000000e0' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '00000000000000000000000077656c636f6d6520746f20657468657265756d2e' + + '0000000000000000000000000000000000000000000000000000000000000001' + + 'fe00000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000020' + + 'abdef18710a18a18abdef18710a18a18abdef18710a18a18abdef18710a18a18' + }) + + test({ + types: ['address', 'tuple(bool,tuple(address,address))', 'tuple(tuple(address,tuple(int256,int256)),bool)'], + values: [ + '0x77656c636f6d6520746f20657468657265756d2e', + [true, ['0x77656c636f6d6520746f20657468657265756d2e', '0x77656c636f6d6520746f20657468657265756d2e']], + [['0x77656c636f6d6520746f20657468657265756d2e', ['-12451', '-12451018']], false] + ], + expected: + '00000000000000000000000077656c636f6d6520746f20657468657265756d2e' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '00000000000000000000000077656c636f6d6520746f20657468657265756d2e' + + '00000000000000000000000077656c636f6d6520746f20657468657265756d2e' + + '00000000000000000000000077656c636f6d6520746f20657468657265756d2e' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcf5d' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff420336' + + '0000000000000000000000000000000000000000000000000000000000000000' + }) + + test({ + types: [ + 'bytes', + 'tuple(tuple(bool,bool,bool),tuple(bytes,bytes,bytes),tuple(bytes,bool,address),tuple(uint256,int256,address))', + 'tuple(bytes,tuple(tuple(int256,bool),tuple(uint256,bytes)))' + ], + values: [ + '0xabef15', + [ + [true, false, true], + ['0xabef15', '0xcdef151', '0xabfe151'], + ['0x15abe391df19aef19a4561', true, '0x77656c636f6d6520746f20657468657265756d2e'], + ['1840181', '-819184919', '0x77656c636f6d6520746f20657468657265756d2e'] + ], + [ + '0x77656c636f6d6520746f20657468657265756d2e', + [ + ['-18491', false], + ['1918491', '0xabdcf151dae'] + ] + ] + ], + expected: + '0000000000000000000000000000000000000000000000000000000000000060' + + '00000000000000000000000000000000000000000000000000000000000000a0' + + '0000000000000000000000000000000000000000000000000000000000000360' + + '0000000000000000000000000000000000000000000000000000000000000003' + + 'abef150000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000100' + + '0000000000000000000000000000000000000000000000000000000000000220' + + '00000000000000000000000000000000000000000000000000000000001c1435' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffcf2c3ae9' + + '00000000000000000000000077656c636f6d6520746f20657468657265756d2e' + + '0000000000000000000000000000000000000000000000000000000000000060' + + '00000000000000000000000000000000000000000000000000000000000000a0' + + '00000000000000000000000000000000000000000000000000000000000000e0' + + '0000000000000000000000000000000000000000000000000000000000000003' + + 'abef150000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000004' + + '0cdef15100000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000004' + + '0abfe15100000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000060' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '00000000000000000000000077656c636f6d6520746f20657468657265756d2e' + + '000000000000000000000000000000000000000000000000000000000000000b' + + '15abe391df19aef19a4561000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '0000000000000000000000000000000000000000000000000000000000000014' + + '77656c636f6d6520746f20657468657265756d2e000000000000000000000000' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb7c5' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000060' + + '00000000000000000000000000000000000000000000000000000000001d461b' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000006' + + '0abdcf151dae0000000000000000000000000000000000000000000000000000' + }) + + test({ + types: [ + 'tuple(bytes,tuple(address,address))', + 'tuple(address,address,address,bytes,address)', + 'tuple(address,tuple(address,tuple(address,bool)))' + ], + values: [ + ['0xabef15', ['0x77656c636f6d6520746f20657468657265756d2e', '0x77656c636f6d6520746f20657468657265756d2e']], + [ + '0x77656c636f6d6520746f20657468657265756d2e', + '0x77656c636f6d6520746f20657468657265756d2e', + '0x77656c636f6d6520746f20657468657265756d2e', + '0xabef15', + '0x77656c636f6d6520746f20657468657265756d2e' + ], + [ + '0x81017589ab81017589ab81017589ab81017589ab', + ['0x77656c636f6d6520746f20657468657265756d2e', ['0x81017589ab81017589ab81017589ab81017589ab', false]] + ] + ], + expected: + '00000000000000000000000000000000000000000000000000000000000000c0' + + '0000000000000000000000000000000000000000000000000000000000000160' + + '00000000000000000000000081017589ab81017589ab81017589ab81017589ab' + + '00000000000000000000000077656c636f6d6520746f20657468657265756d2e' + + '00000000000000000000000081017589ab81017589ab81017589ab81017589ab' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000060' + + '00000000000000000000000077656c636f6d6520746f20657468657265756d2e' + + '00000000000000000000000077656c636f6d6520746f20657468657265756d2e' + + '0000000000000000000000000000000000000000000000000000000000000003' + + 'abef150000000000000000000000000000000000000000000000000000000000' + + '00000000000000000000000077656c636f6d6520746f20657468657265756d2e' + + '00000000000000000000000077656c636f6d6520746f20657468657265756d2e' + + '00000000000000000000000077656c636f6d6520746f20657468657265756d2e' + + '00000000000000000000000000000000000000000000000000000000000000a0' + + '00000000000000000000000077656c636f6d6520746f20657468657265756d2e' + + '0000000000000000000000000000000000000000000000000000000000000003' + + 'abef150000000000000000000000000000000000000000000000000000000000' + }) + + test({ + types: ['tuple(bytes,bytes)', 'bytes', 'tuple(tuple(bytes),tuple(bytes,bytes),tuple(bytes,bytes,bytes))'], + values: [ + ['0xabef15', '0xa'], + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + [['0xaf'], ['0xaf', '0xbc'], ['0xaf', '0xbc', '0xde']] + ], + expected: + '0000000000000000000000000000000000000000000000000000000000000060' + + '0000000000000000000000000000000000000000000000000000000000000120' + + '0000000000000000000000000000000000000000000000000000000000000160' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '0000000000000000000000000000000000000000000000000000000000000003' + + 'abef150000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0a00000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000020' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + '0000000000000000000000000000000000000000000000000000000000000060' + + '00000000000000000000000000000000000000000000000000000000000000c0' + + '0000000000000000000000000000000000000000000000000000000000000180' + + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000001' + + 'af00000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '0000000000000000000000000000000000000000000000000000000000000001' + + 'af00000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000001' + + 'bc00000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000060' + + '00000000000000000000000000000000000000000000000000000000000000a0' + + '00000000000000000000000000000000000000000000000000000000000000e0' + + '0000000000000000000000000000000000000000000000000000000000000001' + + 'af00000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000001' + + 'bc00000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000001' + + 'de00000000000000000000000000000000000000000000000000000000000000' + }) + }) var tests = [ { @@ -584,10 +1068,23 @@ describe('lib/solidity/coder', function () { } ] - it('encodeParams', function () { tests.forEach(function (test) { + expect( + coder.coder.encodeParams( + test.types.map((type) => parseParamType(type)), + test.params + ) + ).toEqual(test.result) expect(coder.coder.encodeParams(test.types, test.params)).toEqual(test.result) }) }) + + it('sanity hexToBytes', () => { + expect(hexToBytes('0xf')).toEqual(new Uint8Array([0xf])) + expect(hexToBytes('0x0f')).toEqual(new Uint8Array([0xf])) + expect(hexToBytes('0x81')).toEqual(new Uint8Array([0x81])) + expect(hexToBytes('0x0f1')).toEqual(new Uint8Array([0xf, 0x1])) + expect(hexToBytes('0xf1')).toEqual(new Uint8Array([0xf1])) + }) }) diff --git a/test/contract.ts b/test/contract.ts index 0bd8bc87..e676aba9 100644 --- a/test/contract.ts +++ b/test/contract.ts @@ -58,6 +58,41 @@ let desc = [ }, ], }, + { + "inputs": [ + { + "components": [ + { + "internalType": "contract IERC721CollectionV2", + "name": "collection", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "prices", + "type": "uint256[]" + }, + { + "internalType": "address[]", + "name": "beneficiaries", + "type": "address[]" + } + ], + "internalType": "struct CollectionStore.ItemToBuy[]", + "name": "_itemsToBuy", + "type": "tuple[]" + } + ], + "name": "buy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { name: 'Changed', type: 'event', @@ -379,6 +414,33 @@ describe('contract', function () { await didCall }) + it('should sendTransaction with tuples to contract function', async function () { + const provider = new FakeHttpProvider() + const rm = new RequestManager(provider) + let address = '0x1234567890123456789012345678901234567891' + + const didCall = provider.injectHandler('eth_sendTransaction', async (payload) => { + expect(payload.params).toEqual([ + { + data: + '0xa4fdc78a0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000006b80863d347a7bde24ee9317f04baaa9259a5d6a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955', + from: address, + to: address, + }, + ]) + provider.injectResult('0xb') + }) + + let contract: any = await new ContractFactory(rm, desc).at(address) + + await contract.buy( + [ + ['0x6b80863d347a7bde24ee9317f04baaa9259a5d6a', [0], ['10000000000000000000'], ['0x14dC79964da2C08b23698B3D3cc7Ca32193d9955']] + ], { from: address }) + + await didCall + }) + it('should make a call with optional params', async function () { const provider = new FakeHttpProvider() const rm = new RequestManager(provider) diff --git a/test/errors.ts b/test/errors.ts index 53621ab0..0e5c6661 100644 --- a/test/errors.ts +++ b/test/errors.ts @@ -1,12 +1,10 @@ import * as expect from 'expect' -import * as errors from '../src/utils/errors' +import { error } from '../src/utils/errors' -describe('lib/web3/method', function() { - describe('getCall', function() { - for (let key in errors) { - it('should return and error', function() { - expect(errors[key]()).toBeInstanceOf(Error) - }) - } +describe('lib/web3/method', function () { + describe('getCall', function () { + it('should return and error', function () { + expect(error('something')).toBeInstanceOf(Error) + }) }) }) diff --git a/test/formatters.inputAddressFormatter.ts b/test/formatters.inputAddressFormatter.ts deleted file mode 100644 index f23cc1ba..00000000 --- a/test/formatters.inputAddressFormatter.ts +++ /dev/null @@ -1,51 +0,0 @@ -import * as expect from 'expect' -import { coder } from '../src/solidity/coder' -import { formatInputAddress } from '../src/solidity/formatters' -import * as formatters from '../src/utils/formatters' - -let tests = [ - // { input: 'XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS', result: '0x00c5496aee77c1ba1f0854206a26dda82a81d6d8' }, - { input: '0x00c5496aee77c1ba1f0854206a26dda82a81d6d8', result: '0x00c5496aee77c1ba1f0854206a26dda82a81d6d8' }, - { input: '00c5496aee77c1ba1f0854206a26dda82a81d6d8', result: '0x00c5496aee77c1ba1f0854206a26dda82a81d6d8' }, - { input: '0x11f4d0a3c12e86b4b5f39b213f7e19d048276dae', result: '0x11f4d0a3c12e86b4b5f39b213f7e19d048276dae' }, - { input: '0xBF79cE2fbd819e5aBC2327563D02a200255B7Cb3', result: '0xBF79cE2fbd819e5aBC2327563D02a200255B7Cb3' } -] - -let errorTests = [ - '0x0c5496aee77c1ba1f0854206a26dda82a81d6d8', - '0x0c5496aee77c1ba1f0854206a26dda82a81d6d8', - '00c5496aee77c1ba1f0854206a26dda82a81d6d', - '0x0', - 'XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZE', - '0x' -] - -describe('formatters', function () { - describe('inputAddressFormatter', function () { - tests.forEach(function (test, i) { - it('should return the correct value: ' + i, function () { - expect(formatters.inputAddressFormatter(test.input)).toEqual(test.result) - }) - }) - }) -}) - -describe('formatters', function () { - describe('inputAddressFormatter', function () { - errorTests.forEach(function (test, i) { - it('should throw an exception: ' + i, function () { - expect(function () { - formatters.inputAddressFormatter(test) - }).toThrow() - }) - }) - }) - describe('formatInputAddress', () => { - tests.forEach(function (test) { - it('formatInputAddress: ' + test.input, () => { - const t = formatInputAddress(test.input) - expect(coder.decodeParam('address', t.encode())).toEqual(test.result.toLowerCase()) - }) - }) - }) -}) diff --git a/test/geth.packing.ts b/test/geth.packing.ts new file mode 100644 index 00000000..2437ce25 --- /dev/null +++ b/test/geth.packing.ts @@ -0,0 +1,1159 @@ +import { AbiInput, AbiOutput, BigNumber as bn } from '../dist/eth-connect' +import * as expect from 'expect' +import { coder } from '../src/solidity/coder' +import { hexToBytes, isArray, isBigNumber } from '../src' +import { formatParamType, Tuple } from '../src/abi/coder' + +var packUnpackTests: Array<{ def: Partial[]; packed: string; unpacked: any }> = [ + // Booleans + { + def: [{ type: 'bool' }], + packed: '0000000000000000000000000000000000000000000000000000000000000001', + unpacked: [true] + }, + { + def: [{ type: 'bool' }], + packed: '0000000000000000000000000000000000000000000000000000000000000000', + unpacked: [false] + }, + // Integers + { + def: [{ type: 'uint8' }], + unpacked: [2], + packed: '0000000000000000000000000000000000000000000000000000000000000002' + }, + { + def: [{ type: 'uint256' }], + unpacked: [new bn(10)], + packed: '000000000000000000000000000000000000000000000000000000000000000a' + }, + { + def: [{ type: 'uint8[]' }], + unpacked: [[1, 2]], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002' + }, + { + def: [{ type: 'uint16' }], + unpacked: [5], + packed: '0000000000000000000000000000000000000000000000000000000000000005' + }, + { + def: [{ type: 'uint16[]' }], + unpacked: [[1, 2]], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002' + }, + { + def: [{ type: 'uint16' }], + packed: '0000000000000000000000000000000000000000000000000000000000000001', + unpacked: [1] + }, + { + def: [{ type: 'uint32' }], + packed: '0000000000000000000000000000000000000000000000000000000000000001', + unpacked: [1] + }, + { + def: [{ type: 'uint32[]' }], + unpacked: [[1, 2]], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002' + }, + { + def: [{ type: 'uint64' }], + unpacked: [new bn(6)], + packed: '0000000000000000000000000000000000000000000000000000000000000006' + }, + { + def: [{ type: 'uint64[]' }], + unpacked: [[new bn(1), new bn(2)]], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002' + }, + { + def: [{ type: 'uint256' }], + unpacked: [new bn(11)], + packed: '000000000000000000000000000000000000000000000000000000000000000b' + }, + { + def: [{ type: 'uint256[]' }], + unpacked: [[new bn(1), new bn(2)]], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002' + }, + { + def: [{ type: 'int8' }], + unpacked: [2], + packed: '0000000000000000000000000000000000000000000000000000000000000002' + }, + { + def: [{ type: 'int8[]' }], + unpacked: [[1, 2]], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002' + }, + { + def: [{ type: 'int16' }], + unpacked: [2], + packed: '0000000000000000000000000000000000000000000000000000000000000002' + }, + { + def: [{ type: 'int16[]' }], + unpacked: [[1, 2]], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002' + }, + { + def: [{ type: 'int32' }], + unpacked: [2], + packed: '0000000000000000000000000000000000000000000000000000000000000002' + }, + { + def: [{ type: 'int32' }], + packed: '0000000000000000000000000000000000000000000000000000000000000001', + unpacked: [1] + }, + { + def: [{ type: 'int32[]' }], + unpacked: [[1, 2]], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002' + }, + { + def: [{ type: 'int64' }], + unpacked: [new bn(2)], + packed: '0000000000000000000000000000000000000000000000000000000000000002' + }, + { + def: [{ type: 'int64[]' }], + unpacked: [[new bn(1), new bn(2)]], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002' + }, + { + def: [{ type: 'int256' }], + unpacked: [new bn(2)], + packed: '0000000000000000000000000000000000000000000000000000000000000002' + }, + { + def: [{ type: 'int256' }], + packed: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + unpacked: [new bn(-1)] + }, + { + def: [{ type: 'int256[]' }], + unpacked: [[new bn(1), new bn(2)]], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002' + }, + // Address + { + def: [{ type: 'address' }], + packed: '0000000000000000000000000100000000000000000000000000000000000000', + unpacked: ['0x0100000000000000000000000000000000000000'] + }, + { + def: [{ type: 'address[]' }], + unpacked: [['0x0100000000000000000000000000000000000000', '0x0200000000000000000000000000000000000000']], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000100000000000000000000000000000000000000' + + '0000000000000000000000000200000000000000000000000000000000000000' + }, + // Bytes + { + def: [{ type: 'bytes1' }], + unpacked: [hexToBytes('0x01')], + packed: '0100000000000000000000000000000000000000000000000000000000000000' + }, + { + def: [{ type: 'bytes2' }], + unpacked: [hexToBytes('0100')], + packed: '0100000000000000000000000000000000000000000000000000000000000000' + }, + { + def: [{ type: 'bytes3' }], + unpacked: [hexToBytes('010000')], + packed: '0100000000000000000000000000000000000000000000000000000000000000' + }, + { + def: [{ type: 'bytes32' }], + packed: '0100000000000000000000000000000000000000000000000000000000000000', + unpacked: [hexToBytes('0100000000000000000000000000000000000000000000000000000000000000')] + }, + { + def: [{ type: 'bytes' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000020' + + '0100000000000000000000000000000000000000000000000000000000000000', + unpacked: [hexToBytes('0100000000000000000000000000000000000000000000000000000000000000')] + }, + // TODO: + // Functions + { + def: [{ type: 'function' }], + packed: '0100000000000000000000000000000000000000000000000000000000000000', + unpacked: [hexToBytes('0x010000000000000000000000000000000000000000000000')] + }, + + // Slice and Array + { + def: [{ type: 'uint8[]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [[1, 2]] + }, + { + def: [{ type: 'uint8[]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000000', + unpacked: [[]] + }, + { + def: [{ type: 'uint256[]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000000', + unpacked: [[]] + }, + { + def: [{ type: 'uint8[2]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [[1, 2]] + }, + { + def: [{ type: 'int8[2]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [[1, 2]] + }, + { + def: [{ type: 'int16[]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [[1, 2]] + }, + { + def: [{ type: 'int16[2]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [[1, 2]] + }, + { + def: [{ type: 'int32[]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [[1, 2]] + }, + { + def: [{ type: 'int32[2]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [[1, 2]] + }, + { + def: [{ type: 'int64[]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [[new bn(1), new bn(2)]] + }, + { + def: [{ type: 'int64[2]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [[new bn(1), new bn(2)]] + }, + { + def: [{ type: 'int256[]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [[new bn(1), new bn(2)]] + }, + { + def: [{ type: 'int256[3]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000003', + unpacked: [[new bn(1), new bn(2), new bn(3)]] + }, + // multi dimensional, if these pass, all types that don't require length prefix should pass + { + def: [{ type: 'uint8[][]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000000', + unpacked: [[]] + }, + { + def: [{ type: 'uint8[][]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '00000000000000000000000000000000000000000000000000000000000000a0' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [ + [ + [1, 2], + [1, 2] + ] + ] + }, + { + def: [{ type: 'uint8[][]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '00000000000000000000000000000000000000000000000000000000000000a0' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000003' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000003', + unpacked: [ + [ + [1, 2], + [1, 2, 3] + ] + ] + }, + { + def: [{ type: 'uint8[][2]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000060' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000', + unpacked: [[[], []]] + }, + { + def: [{ type: 'uint8[2][2]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [ + [ + [1, 2], + [1, 2] + ] + ] + }, + { + def: [{ type: 'uint8[][2]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000001', + unpacked: [[[1], [1]]] + }, + { + def: [{ type: 'uint8[2][]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000000', + unpacked: [[]] + }, + { + def: [{ type: 'uint8[2][]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [[[1, 2]]] + }, + { + def: [{ type: 'uint8[2][]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [ + [ + [1, 2], + [1, 2] + ] + ] + }, + { + def: [{ type: 'uint16[]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [[1, 2]] + }, + { + def: [{ type: 'uint16[2]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [[1, 2]] + }, + { + def: [{ type: 'uint32[]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [[1, 2]] + }, + { + def: [{ type: 'uint32[2][3][4]' }], + unpacked: [ + [ + [ + [1, 2], + [3, 4], + [5, 6] + ], + [ + [7, 8], + [9, 10], + [11, 12] + ], + [ + [13, 14], + [15, 16], + [17, 18] + ], + [ + [19, 20], + [21, 22], + [23, 24] + ] + ] + ], + packed: + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000003' + + '0000000000000000000000000000000000000000000000000000000000000004' + + '0000000000000000000000000000000000000000000000000000000000000005' + + '0000000000000000000000000000000000000000000000000000000000000006' + + '0000000000000000000000000000000000000000000000000000000000000007' + + '0000000000000000000000000000000000000000000000000000000000000008' + + '0000000000000000000000000000000000000000000000000000000000000009' + + '000000000000000000000000000000000000000000000000000000000000000a' + + '000000000000000000000000000000000000000000000000000000000000000b' + + '000000000000000000000000000000000000000000000000000000000000000c' + + '000000000000000000000000000000000000000000000000000000000000000d' + + '000000000000000000000000000000000000000000000000000000000000000e' + + '000000000000000000000000000000000000000000000000000000000000000f' + + '0000000000000000000000000000000000000000000000000000000000000010' + + '0000000000000000000000000000000000000000000000000000000000000011' + + '0000000000000000000000000000000000000000000000000000000000000012' + + '0000000000000000000000000000000000000000000000000000000000000013' + + '0000000000000000000000000000000000000000000000000000000000000014' + + '0000000000000000000000000000000000000000000000000000000000000015' + + '0000000000000000000000000000000000000000000000000000000000000016' + + '0000000000000000000000000000000000000000000000000000000000000017' + + '0000000000000000000000000000000000000000000000000000000000000018' + }, + + { + def: [{ type: 'bytes32[]' }], + unpacked: [ + [ + hexToBytes('0100000000000000000000000000000000000000000000000000000000000000'), + hexToBytes('0200000000000000000000000000000000000000000000000000000000000000') + ] + ], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0100000000000000000000000000000000000000000000000000000000000000' + + '0200000000000000000000000000000000000000000000000000000000000000' + }, + { + def: [{ type: 'uint32[2]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [[1, 2]] + }, + { + def: [{ type: 'uint128[2]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [[new bn(1), new bn(2)]] + }, + { + def: [{ type: 'uint64[]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [[new bn(1), new bn(2)]] + }, + { + def: [{ type: 'uint64[2]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [[new bn(1), new bn(2)]] + }, + { + def: [{ type: 'uint256[]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [[new bn(1), new bn(2)]] + }, + { + def: [{ type: 'uint256[3]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000003', + unpacked: [[new bn(1), new bn(2), new bn(3)]] + }, + { + def: [{ type: 'string[4]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '00000000000000000000000000000000000000000000000000000000000000c0' + + '0000000000000000000000000000000000000000000000000000000000000100' + + '0000000000000000000000000000000000000000000000000000000000000140' + + '0000000000000000000000000000000000000000000000000000000000000005' + + '48656c6c6f000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000005' + + '576f726c64000000000000000000000000000000000000000000000000000000' + + '000000000000000000000000000000000000000000000000000000000000000b' + + '476f2d657468657265756d000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000008' + + '457468657265756d000000000000000000000000000000000000000000000000', + unpacked: [['Hello', 'World', 'Go-ethereum', 'Ethereum']] + }, + { + def: [{ type: 'string[]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '0000000000000000000000000000000000000000000000000000000000000008' + + '457468657265756d000000000000000000000000000000000000000000000000' + + '000000000000000000000000000000000000000000000000000000000000000b' + + '676f2d657468657265756d000000000000000000000000000000000000000000', + unpacked: [['Ethereum', 'go-ethereum']] + }, + { + def: [{ type: 'bytes[]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '0000000000000000000000000000000000000000000000000000000000000080' + + '0000000000000000000000000000000000000000000000000000000000000003' + + 'f0f0f00000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000003' + + 'f0f0f00000000000000000000000000000000000000000000000000000000000', + unpacked: [[hexToBytes('f0f0f0'), hexToBytes('f0f0f0')]] + }, + { + def: [{ type: 'uint256[2][][]' }], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '00000000000000000000000000000000000000000000000000000000000000e0' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '00000000000000000000000000000000000000000000000000000000000000c8' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '00000000000000000000000000000000000000000000000000000000000003e8' + + '0000000000000000000000000000000000000000000000000000000000000002' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '00000000000000000000000000000000000000000000000000000000000000c8' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '00000000000000000000000000000000000000000000000000000000000003e8', + unpacked: [ + [ + [ + [new bn(1), new bn(200)], + [new bn(1), new bn(1000)] + ], + [ + [new bn(1), new bn(200)], + [new bn(1), new bn(1000)] + ] + ] + ] + }, + // struct outputs + { + def: [ + { + components: [ + { name: 'int1', type: 'int256' }, + { name: 'int2', type: 'int256' } + ], + type: 'tuple' + } + ], + packed: + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [ + { + int1: new bn(1), + int2: new bn(2) + } + ] + }, + { + def: [{ components: [{ name: 'int_one', type: 'int256' }], type: 'tuple' }], + packed: '0000000000000000000000000000000000000000000000000000000000000001', + unpacked: [ + { + int_one: new bn(1) + } + ] + }, + { + def: [{ components: [{ name: 'int__one', type: 'int256' }], type: 'tuple' }], + packed: '0000000000000000000000000000000000000000000000000000000000000001', + unpacked: [ + { + int__one: new bn(1) + } + ] + }, + { + def: [{ components: [{ name: 'int_one_', type: 'int256' }], type: 'tuple' }], + packed: '0000000000000000000000000000000000000000000000000000000000000001', + unpacked: [ + { + int_one_: new bn(1) + } + ] + }, + { + def: [ + { + components: [ + { name: 'int_one', type: 'int256' }, + { name: 'intone', type: 'int256' } + ], + type: 'tuple' + } + ], + packed: + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000002', + unpacked: [ + { + int_one: new bn(1), + intone: new bn(2) + } + ] + }, + { + def: [{ type: 'string' }], + unpacked: ['foobar'], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000006' + + '666f6f6261720000000000000000000000000000000000000000000000000000' + }, + { + def: [{ type: 'string[]' }], + unpacked: [['hello', 'foobar']], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + // len(array) = 2 + '0000000000000000000000000000000000000000000000000000000000000040' + // offset 64 to i = 0 + '0000000000000000000000000000000000000000000000000000000000000080' + // offset 128 to i = 1 + '0000000000000000000000000000000000000000000000000000000000000005' + // len(str[0]) = 5 + '68656c6c6f000000000000000000000000000000000000000000000000000000' + // str[0] + '0000000000000000000000000000000000000000000000000000000000000006' + // len(str[1]) = 6 + '666f6f6261720000000000000000000000000000000000000000000000000000' // str[1] + }, + { + def: [{ type: 'string[2]' }], + unpacked: [['hello', 'foobar']], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000040' + // offset to i = 0 + '0000000000000000000000000000000000000000000000000000000000000080' + // offset to i = 1 + '0000000000000000000000000000000000000000000000000000000000000005' + // len(str[0]) = 5 + '68656c6c6f000000000000000000000000000000000000000000000000000000' + // str[0] + '0000000000000000000000000000000000000000000000000000000000000006' + // len(str[1]) = 6 + '666f6f6261720000000000000000000000000000000000000000000000000000' // str[1] + }, + { + def: [{ type: 'bytes32[][]' }], + unpacked: [ + [ + [ + hexToBytes('0100000000000000000000000000000000000000000000000000000000000000'), + hexToBytes('0200000000000000000000000000000000000000000000000000000000000000') + ], + [ + hexToBytes('0300000000000000000000000000000000000000000000000000000000000000'), + hexToBytes('0400000000000000000000000000000000000000000000000000000000000000'), + hexToBytes('0500000000000000000000000000000000000000000000000000000000000000') + ] + ] + ], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + // len(array) = 2 + '0000000000000000000000000000000000000000000000000000000000000040' + // offset 64 to i = 0 + '00000000000000000000000000000000000000000000000000000000000000a0' + // offset 160 to i = 1 + '0000000000000000000000000000000000000000000000000000000000000002' + // len(array[0]) = 2 + '0100000000000000000000000000000000000000000000000000000000000000' + // array[0][0] + '0200000000000000000000000000000000000000000000000000000000000000' + // array[0][1] + '0000000000000000000000000000000000000000000000000000000000000003' + // len(array[1]) = 3 + '0300000000000000000000000000000000000000000000000000000000000000' + // array[1][0] + '0400000000000000000000000000000000000000000000000000000000000000' + // array[1][1] + '0500000000000000000000000000000000000000000000000000000000000000' // array[1][2] + }, + { + def: [{ type: 'bytes32[][2]' }], + unpacked: [ + [ + [ + hexToBytes('0100000000000000000000000000000000000000000000000000000000000000'), + hexToBytes('0200000000000000000000000000000000000000000000000000000000000000') + ], + [ + hexToBytes('0300000000000000000000000000000000000000000000000000000000000000'), + hexToBytes('0400000000000000000000000000000000000000000000000000000000000000'), + hexToBytes('0500000000000000000000000000000000000000000000000000000000000000') + ] + ] + ], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000040' + // offset 64 to i = 0 + '00000000000000000000000000000000000000000000000000000000000000a0' + // offset 160 to i = 1 + '0000000000000000000000000000000000000000000000000000000000000002' + // len(array[0]) = 2 + '0100000000000000000000000000000000000000000000000000000000000000' + // array[0][0] + '0200000000000000000000000000000000000000000000000000000000000000' + // array[0][1] + '0000000000000000000000000000000000000000000000000000000000000003' + // len(array[1]) = 3 + '0300000000000000000000000000000000000000000000000000000000000000' + // array[1][0] + '0400000000000000000000000000000000000000000000000000000000000000' + // array[1][1] + '0500000000000000000000000000000000000000000000000000000000000000' // array[1][2] + }, + { + def: [{ type: 'bytes32[3][2]' }], + unpacked: [ + [ + [ + hexToBytes('0100000000000000000000000000000000000000000000000000000000000000'), + hexToBytes('0200000000000000000000000000000000000000000000000000000000000000'), + hexToBytes('0300000000000000000000000000000000000000000000000000000000000000') + ], + [ + hexToBytes('0300000000000000000000000000000000000000000000000000000000000000'), + hexToBytes('0400000000000000000000000000000000000000000000000000000000000000'), + hexToBytes('0500000000000000000000000000000000000000000000000000000000000000') + ] + ] + ], + packed: + '0100000000000000000000000000000000000000000000000000000000000000' + // array[0][0] + '0200000000000000000000000000000000000000000000000000000000000000' + // array[0][1] + '0300000000000000000000000000000000000000000000000000000000000000' + // array[0][2] + '0300000000000000000000000000000000000000000000000000000000000000' + // array[1][0] + '0400000000000000000000000000000000000000000000000000000000000000' + // array[1][1] + '0500000000000000000000000000000000000000000000000000000000000000' // array[1][2] + }, + // // tuples + { + // static tuple + def: [ + { + components: [ + { name: 'a', type: 'int64' }, + { name: 'b', type: 'int256' }, + { name: 'c', type: 'int256' }, + { name: 'd', type: 'bool' }, + { name: 'e', type: 'bytes32[3][2]' } + ], + type: 'tuple' + } + ], + unpacked: [ + { + a: new bn(1), + b: new bn(1), + c: new bn(-1), + d: true, + e: [ + [ + hexToBytes('0100000000000000000000000000000000000000000000000000000000000000'), + hexToBytes('0200000000000000000000000000000000000000000000000000000000000000'), + hexToBytes('0300000000000000000000000000000000000000000000000000000000000000') + ], + [ + hexToBytes('0300000000000000000000000000000000000000000000000000000000000000'), + hexToBytes('0400000000000000000000000000000000000000000000000000000000000000'), + hexToBytes('0500000000000000000000000000000000000000000000000000000000000000') + ] + ] + } + ], + packed: + '0000000000000000000000000000000000000000000000000000000000000001' + // struct[a] + '0000000000000000000000000000000000000000000000000000000000000001' + // struct[b] + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + // struct[c] + '0000000000000000000000000000000000000000000000000000000000000001' + // struct[d] + '0100000000000000000000000000000000000000000000000000000000000000' + // struct[e] array[0][0] + '0200000000000000000000000000000000000000000000000000000000000000' + // struct[e] array[0][1] + '0300000000000000000000000000000000000000000000000000000000000000' + // struct[e] array[0][2] + '0300000000000000000000000000000000000000000000000000000000000000' + // struct[e] array[1][0] + '0400000000000000000000000000000000000000000000000000000000000000' + // struct[e] array[1][1] + '0500000000000000000000000000000000000000000000000000000000000000' // struct[e] array[1][2] + }, + { + def: [ + { + components: [ + { name: 'a', type: 'string' }, + { name: 'b', type: 'int64' }, + { name: 'c', type: 'bytes' }, + { name: 'd', type: 'string[]' }, + { name: 'e', type: 'int256[]' }, + { name: 'f', type: 'address[]' } + ], + type: 'tuple' + } + ], + unpacked: [ + { + a: 'foobar', + b: new bn(1), + c: new Uint8Array([1]), + d: ['foo', 'bar'], + e: [new bn(1), new bn(-1)], + f: ['0x407D73d8a49eeb85D32Cf465507dd71d507100c1', '0x407d73d8a49EEB85d32Cf465507dD71D507100c2'] + } + ], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + // struct a + '00000000000000000000000000000000000000000000000000000000000000c0' + // struct[a] offset + '0000000000000000000000000000000000000000000000000000000000000001' + // struct[b] + '0000000000000000000000000000000000000000000000000000000000000100' + // struct[c] offset + '0000000000000000000000000000000000000000000000000000000000000140' + // struct[d] offset + '0000000000000000000000000000000000000000000000000000000000000220' + // struct[e] offset + '0000000000000000000000000000000000000000000000000000000000000280' + // struct[f] offset + '0000000000000000000000000000000000000000000000000000000000000006' + // struct[a] length + '666f6f6261720000000000000000000000000000000000000000000000000000' + // struct[a] "foobar" + '0000000000000000000000000000000000000000000000000000000000000001' + // struct[c] length + '0100000000000000000000000000000000000000000000000000000000000000' + // []byte{1} + '0000000000000000000000000000000000000000000000000000000000000002' + // struct[d] length + '0000000000000000000000000000000000000000000000000000000000000040' + // foo offset + '0000000000000000000000000000000000000000000000000000000000000080' + // bar offset + '0000000000000000000000000000000000000000000000000000000000000003' + // foo length + '666f6f0000000000000000000000000000000000000000000000000000000000' + // foo + '0000000000000000000000000000000000000000000000000000000000000003' + // bar offset + '6261720000000000000000000000000000000000000000000000000000000000' + // bar + '0000000000000000000000000000000000000000000000000000000000000002' + // struct[e] length + '0000000000000000000000000000000000000000000000000000000000000001' + // 1 + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + // -1 + '0000000000000000000000000000000000000000000000000000000000000002' + // struct[f] length + '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c1' + // common.Address{1} + '000000000000000000000000407d73d8a49eeb85d32cf465507dd71d507100c2' // common.Address{2} + }, + { + def: [ + { + components: [ + { + type: 'tuple', + components: [ + { name: 'c', type: 'uint256' }, + { name: 'd', type: 'uint256[]' } + ], + name: 'a' + }, + { name: 'b', type: 'uint256[]' } + ], + type: 'tuple' + } + ], + unpacked: [ + { + a: { + c: new bn(1), + d: [new bn(1), new bn(2)] + }, + b: [new bn(1), new bn(2)] + } + ], + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + // struct a + '0000000000000000000000000000000000000000000000000000000000000040' + // a offset + '00000000000000000000000000000000000000000000000000000000000000e0' + // b offset + '0000000000000000000000000000000000000000000000000000000000000001' + // a.c value + '0000000000000000000000000000000000000000000000000000000000000040' + // a.d offset + '0000000000000000000000000000000000000000000000000000000000000002' + // a.d length + '0000000000000000000000000000000000000000000000000000000000000001' + // a.d[0] value + '0000000000000000000000000000000000000000000000000000000000000002' + // a.d[1] value + '0000000000000000000000000000000000000000000000000000000000000002' + // b length + '0000000000000000000000000000000000000000000000000000000000000001' + // b[0] value + '0000000000000000000000000000000000000000000000000000000000000002' // b[1] value + }, + + { + def: [ + { + components: [ + { name: 'a', type: 'int256' }, + { name: 'b', type: 'int256[]' } + ], + name: 'envelope', + type: 'tuple[]' + } + ], + unpacked: { + envelope: [ + { a: new bn(-1), b: [new bn(1), new bn(3)] }, + { a: new bn(1), b: [new bn(2), new bn(-1)] } + ] + }, + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000002' + // tuple length + '0000000000000000000000000000000000000000000000000000000000000040' + // tuple[0] offset + '00000000000000000000000000000000000000000000000000000000000000e0' + // tuple[1] offset + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + // tuple[0].A + '0000000000000000000000000000000000000000000000000000000000000040' + // tuple[0].B offset + '0000000000000000000000000000000000000000000000000000000000000002' + // tuple[0].B length + '0000000000000000000000000000000000000000000000000000000000000001' + // tuple[0].B[0] value + '0000000000000000000000000000000000000000000000000000000000000003' + // tuple[0].B[1] value + '0000000000000000000000000000000000000000000000000000000000000001' + // tuple[1].A + '0000000000000000000000000000000000000000000000000000000000000040' + // tuple[1].B offset + '0000000000000000000000000000000000000000000000000000000000000002' + // tuple[1].B length + '0000000000000000000000000000000000000000000000000000000000000002' + // tuple[1].B[0] value + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' // tuple[1].B[1] value + }, + { + def: [ + { + components: [ + { name: 'a', type: 'int256' }, + { name: 'b', type: 'int256' } + ], + name: 'a', + type: 'tuple[2]' + } + ], + unpacked: { + a: [ + { + a: new bn(-1), + b: new bn(1) + }, + { + a: new bn(1), + b: new bn(-1) + } + ] + }, + packed: + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + // tuple[0].a + '0000000000000000000000000000000000000000000000000000000000000001' + // tuple[0].b + '0000000000000000000000000000000000000000000000000000000000000001' + // tuple[1].a + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' // tuple[1].b + }, + { + def: [ + { + name: 'envelope', + components: [{ name: 'a', type: 'int256[]' }], + type: 'tuple[2]' + } + ], + unpacked: { envelope: [{ a: [new bn(-1), new bn(1)] }, { a: [new bn(1), new bn(-1)] }] }, + packed: + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000040' + // tuple[0] offset + '00000000000000000000000000000000000000000000000000000000000000c0' + // tuple[1] offset + '0000000000000000000000000000000000000000000000000000000000000020' + // tuple[0].A offset + '0000000000000000000000000000000000000000000000000000000000000002' + // tuple[0].A length + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + // tuple[0].A[0] + '0000000000000000000000000000000000000000000000000000000000000001' + // tuple[0].A[1] + '0000000000000000000000000000000000000000000000000000000000000020' + // tuple[1].A offset + '0000000000000000000000000000000000000000000000000000000000000002' + // tuple[1].A length + '0000000000000000000000000000000000000000000000000000000000000001' + // tuple[1].A[0] + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' // tuple[1].A[1] + } +] + +function coerceTuple(value: any) { + if ( + value instanceof Uint8Array || + typeof value == 'string' || + typeof value == 'boolean' || + typeof value == 'number' || + isBigNumber(value) + ) { + return value + } + if (Array.isArray(value)) { + return value.map(coerceTuple) + } + if (typeof value == 'object' && value && value.__proto__ == ({} as any).__proto__) { + const ret = new Tuple() + Object.entries(value).forEach(([key, v], index) => { + ret[key] = coerceTuple(v) // this vlaue NEEDS to be duplicated in order to make the comparator work + ret[index] = coerceTuple(v) // this vlaue NEEDS to be duplicated in order to make the comparator work + }) + return ret + } + + return value +} + +function coerce(t: any) { + if (isArray(t)) { + return t.map(coerceTuple) + } else { + return coerceTuple(t) + } +} + +function coerceTupleNoName(value: any) { + if ( + value instanceof Uint8Array || + typeof value == 'string' || + typeof value == 'boolean' || + typeof value == 'number' || + isBigNumber(value) + ) { + return value + } + if (Array.isArray(value)) { + return value.map(coerceTupleNoName) + } + if (typeof value == 'object' && value && value.__proto__ == ({} as any).__proto__) { + const ret = new Tuple() + Object.entries(value).forEach(([_, v], index) => { + ret[index] = coerceTupleNoName(v) // this vlaue NEEDS to be duplicated in order to make the comparator work + }) + return ret + } + + return value +} + +function coerceNoName(t: any) { + if (isArray(t)) { + return t.map(coerceTupleNoName) + } else { + return coerceTupleNoName(t) + } +} + +describe('geth/packing', function () { + packUnpackTests.forEach((test) => { + describe(JSON.stringify(test.def), () => { + it('pack: should turn ' + JSON.stringify(test.unpacked) + ' to ' + test.packed, function () { + expect(coder.encodeParams(test.def as AbiInput[], coerce(test.unpacked))).toEqual(test.packed) + }) + it('unpack: should turn ' + test.packed + ' to ' + JSON.stringify(test.unpacked), function () { + expect(coder.decodeParams(test.def as AbiInput[], test.packed)).toEqual(coerce(test.unpacked)) + }) + const fmt = test.def.map(formatParamType) + describe('toStr: ' + fmt, () => { + it('pack: should turn ' + JSON.stringify(test.unpacked) + ' to ' + test.packed, function () { + expect(coder.encodeParams(fmt, coerceNoName(test.unpacked))).toEqual(test.packed) + }) + const unpacked = coerceNoName(test.unpacked) + it('unpack: should turn ' + test.packed + ' to ' + JSON.stringify(unpacked), function () { + expect(coder.decodeParams(fmt, test.packed)).toEqual(unpacked) + }) + it('integration: unpacked (str) > packed (str) > unpacked (obj)', function () { + expect( + coder.decodeParams(test.def as AbiInput[], coder.encodeParams(fmt, coerceNoName(test.unpacked))) + ).toEqual(coerce(test.unpacked)) + }) + }) + }) + }) +}) diff --git a/test/integration.erc20.ts b/test/integration.erc20.ts index 58366111..6bfaa6c9 100644 --- a/test/integration.erc20.ts +++ b/test/integration.erc20.ts @@ -202,7 +202,7 @@ function doTest(requestManager: RequestManager) { it('test allowance, invalid address', async function () { this.timeout(30000) const accounts = await requestManager.eth_accounts() - await expect(ERC20Contract.allowance(accounts[0], '0x1')).rejects.toThrow('Invalid address') + await expect(ERC20Contract.allowance(accounts[0], '0x1')).rejects.toThrow(/invalid address/) }) it('test allowance', async function () { diff --git a/test/integration.events.ts b/test/integration.events.ts index e54b2624..798e609b 100644 --- a/test/integration.events.ts +++ b/test/integration.events.ts @@ -79,11 +79,11 @@ const contract = { name: 'getInstructor', outputs: [ { - name: '', + name: 'name', type: 'string' }, { - name: '', + name: 'age', type: 'uint256' } ], @@ -211,12 +211,18 @@ function doTest(rm: RequestManager) { await rm.waitForCompletion(tx) }) - it('getInstructor()', async () => { + it('getInstructor() - index', async () => { const [name, age] = await TestContract.getInstructor() expect(name).toEqual('agustin') expect(age.toNumber()).toEqual(99) }) + it('getInstructor() - assoc', async () => { + const { name, age } = await TestContract.getInstructor() + expect(name).toEqual('agustin') + expect(age.toNumber()).toEqual(99) + }) + it('did receive a filter message', async () => { await didReceiveAnyMessage }) diff --git a/test/parser.spec.ts b/test/parser.spec.ts new file mode 100644 index 00000000..c2932e98 --- /dev/null +++ b/test/parser.spec.ts @@ -0,0 +1,276 @@ +import * as expect from 'expect' +import { AbiEvent, AbiFunction } from '../src' +import { parseSignature } from '../src/abi/parser' + +const suite: Record = { + 'event BaseURI(string _oldBaseURI, string _newBaseURI)': { + anonymous: false, + inputs: [ + { type: 'string', name: '_oldBaseURI', indexed: false }, + { type: 'string', name: '_newBaseURI', indexed: false } + ], + name: 'BaseURI', + type: 'event' + }, + 'event SetGlobalMinter(address indexed _minter, bool _value)': { + anonymous: false, + inputs: [ + { type: 'address', name: '_minter', indexed: true }, + { type: 'bool', name: '_value', indexed: false } + ], + name: 'SetGlobalMinter', + type: 'event' + }, + 'event SetGlobalManager(address indexed _manager, bool _value)': { + anonymous: false, + inputs: [ + { type: 'address', name: '_manager', indexed: true }, + { type: 'bool', name: '_value', indexed: false } + ], + name: 'SetGlobalManager', + type: 'event' + }, + 'event SetItemMinter(uint256 indexed _itemId, address indexed _minter, bool _value)': { + anonymous: false, + inputs: [ + { type: 'uint256', name: '_itemId', indexed: true }, + { type: 'address', name: '_minter', indexed: true }, + { type: 'bool', name: '_value', indexed: false } + ], + name: 'SetItemMinter', + type: 'event' + }, + 'event SetItemManager(uint256 indexed _itemId, address indexed _manager, bool _value)': { + anonymous: false, + inputs: [ + { type: 'uint256', name: '_itemId', indexed: true }, + { type: 'address', name: '_manager', indexed: true }, + { type: 'bool', name: '_value', indexed: false } + ], + name: 'SetItemManager', + type: 'event' + }, + 'event AddItem(uint256 indexed _itemId, tuple(uint256) _item)': { + anonymous: false, + inputs: [ + { type: 'uint256', name: '_itemId', indexed: true }, + { + type: 'tuple', + name: '_item', + indexed: false, + components: [ + { + name: '', + type: 'uint256' + } + ] + } + ], + name: 'AddItem', + type: 'event' + }, + 'event RescueItem(uint256 indexed _itemId, bytes32 _contentHash, string _metadata)': { + anonymous: false, + inputs: [ + { type: 'uint256', name: '_itemId', indexed: true }, + { type: 'bytes32', name: '_contentHash', indexed: false }, + { type: 'string', name: '_metadata', indexed: false } + ], + name: 'RescueItem', + type: 'event' + }, + 'event Issue(address indexed _beneficiary, uint256 indexed _tokenId, uint256 indexed _itemId, uint256 _issuedId)': { + anonymous: false, + inputs: [ + { type: 'address', name: '_beneficiary', indexed: true }, + { type: 'uint256', name: '_tokenId', indexed: true }, + { type: 'uint256', name: '_itemId', indexed: true }, + { type: 'uint256', name: '_issuedId', indexed: false } + ], + name: 'Issue', + type: 'event' + }, + 'event UpdateItemSalesData(uint256 indexed _itemId, uint256 _price, address _beneficiary)': { + anonymous: false, + inputs: [ + { type: 'uint256', name: '_itemId', indexed: true }, + { type: 'uint256', name: '_price', indexed: false }, + { type: 'address', name: '_beneficiary', indexed: false } + ], + name: 'UpdateItemSalesData', + type: 'event' + }, + 'event UpdateItemMetadata(uint256 indexed _itemId, string _metadata)': { + anonymous: false, + inputs: [ + { type: 'uint256', name: '_itemId', indexed: true }, + { type: 'string', name: '_metadata', indexed: false } + ], + name: 'UpdateItemMetadata', + type: 'event' + }, + 'event CreatorshipTransferred(address indexed _previousCreator, address indexed _newCreator)': { + anonymous: false, + inputs: [ + { type: 'address', name: '_previousCreator', indexed: true }, + { type: 'address', name: '_newCreator', indexed: true } + ], + name: 'CreatorshipTransferred', + type: 'event' + }, + 'event SetApproved(bool _previousValue, bool _newValue)': { + anonymous: false, + inputs: [ + { type: 'bool', name: '_previousValue', indexed: false }, + { type: 'bool', name: '_newValue', indexed: false } + ], + name: 'SetApproved', + type: 'event' + }, + 'event SetEditable(bool _previousValue, bool _newValue)': { + anonymous: false, + inputs: [ + { type: 'bool', name: '_previousValue', indexed: false }, + { type: 'bool', name: '_newValue', indexed: false } + ], + name: 'SetEditable', + type: 'event' + }, + 'event Complete()': { anonymous: false, inputs: [], name: 'Complete', type: 'event' }, + 'function issueTokens(address[] _beneficiaries, uint256[] _itemIds) external virtual': { + constant: false, + inputs: [ + { + name: '_beneficiaries', + type: 'address[]' + }, + { + name: '_itemIds', + type: 'uint256[]' + } + ], + name: 'issueTokens', + outputs: [], + payable: false, + type: 'function' + }, + 'function getRarityName(tuple(uint256) _rarity) public pure returns (string memory)': { + constant: true, + inputs: [ + { + type: 'tuple', + name: '_rarity', + components: [ + { + name: '', + type: 'uint256' + } + ] + } + ], + name: 'getRarityName', + outputs: [{ type: 'string', name: 'memory' }], + payable: false, + type: 'function', + stateMutability: 'pure' + }, + 'function isMintingAllowed() public view returns (bool)': { + constant: true, + inputs: [], + name: 'isMintingAllowed', + outputs: [{ type: 'bool', name: '' }], + payable: false, + type: 'function', + stateMutability: 'view' + }, + 'function tokenURI(uint256 _tokenId) public view virtual override returns (string memory)': { + constant: true, + inputs: [{ type: 'uint256', name: '_tokenId' }], + name: 'tokenURI', + outputs: [{ type: 'string', name: 'memory' }], + payable: false, + type: 'function', + stateMutability: 'view' + }, + 'function batchTransferFrom(address _from, address _to, uint256[] _tokenIds) public': { + constant: false, + inputs: [ + { + name: '_from', + type: 'address' + }, + { + name: '_to', + type: 'address' + }, + { + name: '_tokenIds', + type: 'uint256[]' + } + ], + name: 'batchTransferFrom', + outputs: [], + payable: false, + type: 'function' + }, + // TODO: test this with real ABI and integration tests + 'function decodeTokenId(uint256 _id) public pure returns (uint256 itemId, uint256 issuedId)': { + constant: true, + inputs: [{ type: 'uint256', name: '_id' }], + name: 'decodeTokenId', + outputs: [ + { type: 'uint256', name: 'itemId' }, + { type: 'uint256', name: 'issuedId' } + ], + payable: false, + type: 'function', + stateMutability: 'pure' + }, + // TODO: test this with real ABI and integration tests + 'function bar(tuple(uint256,uint256)) returns (tuple(bool a,bool b))': { + constant: false, + inputs: [ + { + components: [ + { + name: '', + type: 'uint256' + }, + { + name: '', + type: 'uint256' + } + ], + name: '', + type: 'tuple' + } + ], + name: 'bar', + outputs: [ + { + components: [ + { + name: 'a', + type: 'bool' + }, + { + name: 'b', + type: 'bool' + } + ], + name: '', + type: 'tuple' + } + ], + payable: false, + type: 'function' + } +} + +describe('parser', function () { + for (let test in suite) { + it(`converts "${test}" to an object`, () => { + expect(parseSignature(test)).toEqual(suite[test]) + }) + } +}) diff --git a/test/setup.js b/test/setup.js index fd501b03..def66d29 100644 --- a/test/setup.js +++ b/test/setup.js @@ -1,11 +1,23 @@ -const { resolve } = require("path") -process.env.TS_NODE_PROJECT = resolve(__dirname, "tsconfig.json") +const { resolve } = require('path') +process.env.TS_NODE_PROJECT = resolve(__dirname, 'tsconfig.json') process.stdout.write('creating test environment...\n') require('ts-node/register') require('source-map-support/register') +const { Tuple } = require('../src/abi/coder') +const util = require('util') + +Tuple.prototype[util.inspect.custom] = function () { + return ` +Tuple { + array ${JSON.stringify(this.slice())} + struct {} +} +`.trim() +} + process.on('unhandledRejection', (reason, promise) => { promise.catch((e) => { console.error('unhandledRejection:', reason.toString()) diff --git a/test/soldity.formatters.formatInputInt.ts b/test/soldity.formatters.formatInputInt.ts deleted file mode 100644 index da174755..00000000 --- a/test/soldity.formatters.formatInputInt.ts +++ /dev/null @@ -1,20 +0,0 @@ -import * as expect from 'expect' -import * as formatters from '../src/solidity/formatters' -import { SolidityParam } from '../src/solidity/param' - -let tests = [ - { input: 1, result: new SolidityParam('0000000000000000000000000000000000000000000000000000000000000001') }, - { input: 1.1, result: new SolidityParam('0000000000000000000000000000000000000000000000000000000000000001') }, - { input: -1.1, result: new SolidityParam('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff') }, - { input: -1, result: new SolidityParam('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff') } -] - -describe('formatters', function () { - describe('formatInputInt', function () { - tests.forEach(function (test, i) { - it('should return the correct value: ' + i, function () { - expect(formatters.formatInputInt(test.input)).toEqual(test.result) - }) - }) - }) -}) diff --git a/test/utils.fromUtf8.ts b/test/utils.fromUtf8.ts index 241b7e35..2a5a643b 100644 --- a/test/utils.fromUtf8.ts +++ b/test/utils.fromUtf8.ts @@ -1,26 +1,27 @@ import * as expect from 'expect' +import { stringToUtf8Bytes } from '../src/utils/utf8' import * as utils from '../src/utils/utils' let tests = [ - { value: 'myString', expected: '0x6d79537472696e67' }, - { value: 'myString\x00', expected: '0x6d79537472696e67' }, + { value: 'myString', expected: '6d79537472696e67' }, + { value: 'myString\x00', expected: '6d79537472696e6700' }, { value: '我能吞下玻璃而不伤身体。', - expected: '0xe68891e883bde5909ee4b88be78ebbe79283e8808ce4b88de4bca4e8baabe4bd93e38082' + expected: 'e68891e883bde5909ee4b88be78ebbe79283e8808ce4b88de4bca4e8baabe4bd93e38082' }, { value: '나는 유리를 먹을 수 있어요. 그래도 아프지 않아요', expected: - '0xeb8298eb8a9420ec9ca0eba6aceba5bc20eba8b9ec9d8420ec889820ec9e88ec96b4ec9a942e20eab7b8eb9e98eb8f8420ec9584ed9484eca78020ec958aec9584ec9a94' + 'eb8298eb8a9420ec9ca0eba6aceba5bc20eba8b9ec9d8420ec889820ec9e88ec96b4ec9a942e20eab7b8eb9e98eb8f8420ec9584ed9484eca78020ec958aec9584ec9a94' }, - { value: 'expected value\u0000\u0000\u0000', expected: '0x65787065637465642076616c7565' } + { value: 'expected value\u0000\u0000\u0000', expected: '65787065637465642076616c7565000000' } ] describe('lib/utils/utils', function () { describe('fromUtf8', function () { tests.forEach(function (test) { it('should turn ' + test.value + ' to ' + test.expected, function () { - expect(utils.fromUtf8(test.value)).toEqual(test.expected) + expect(utils.bytesToHex(stringToUtf8Bytes(test.value))).toEqual(test.expected) }) }) }) diff --git a/test/utils.toHex.ts b/test/utils.toHex.ts index 287c275c..1e4e4b18 100644 --- a/test/utils.toHex.ts +++ b/test/utils.toHex.ts @@ -26,17 +26,16 @@ let tests = [ value: '-0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd', expected: '-0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd' }, + { value: new Uint8Array([0, 1, 2, 3, 4, 0, 0]), expected: '0x00010203040000' }, { value: 0, expected: '0x0' }, { value: '0', expected: '0x0' }, { value: '0x0', expected: '0x0' }, { value: -0, expected: '0x0' }, { value: '-0', expected: '0x0' }, { value: '-0x0', expected: '0x0' }, - { value: [1, 2, 3, { test: 'data' }], expected: '0x5b312c322c332c7b2274657374223a2264617461227d5d' }, - { value: { test: 'test' }, expected: '0x7b2274657374223a2274657374227d' }, - { value: '{"test": "test"}', expected: '0x7b2274657374223a202274657374227d' }, - { value: 'myString', expected: '0x6d79537472696e67' }, - { value: '내가 제일 잘 나가', expected: '0xeb82b4eab08020eca09cec9dbc20ec9e9820eb8298eab080' }, + { value: '{"test": "test"}', expected: '7b2274657374223a202274657374227d' }, + { value: 'myString', expected: '6d79537472696e67' }, + { value: '내가 제일 잘 나가', expected: 'eb82b4eab08020eca09cec9dbc20ec9e9820eb8298eab080' }, { value: new BigNumber(15), expected: '0xf' }, { value: true, expected: '0x1' }, { value: false, expected: '0x0' }, @@ -44,16 +43,22 @@ let tests = [ value: '\u0003\u0000\u0000\u00005èÆÕL]\u0012|Î¾ž\u001a7«›\u00052\u0011(ЗY\n<\u0010\u0000\u0000\u0000\u0000\u0000\u0000e!ßd/ñõì\f:z¦Î¦±ç·÷Í¢Ëß\u00076*…\bŽ—ñžùC1ÉUÀé2\u001aӆBŒ', expected: - '0x0300000035c3a8c386c3954c5d127cc29dc38ec2bec29e1a37c2abc29b05321128c390c297590a3c100000000000006521c39f642fc3b1c3b5c3ac0c3a7ac2a6c38ec2a6c2b1c3a7c2b7c3b7c38dc2a2c38bc39f07362ac28508c28ec297c3b1c29ec3b94331c38955c380c3a9321ac393c28642c28c' + '0300000035c3a8c386c3954c5d127cc29dc38ec2bec29e1a37c2abc29b05321128c390c297590a3c100000000000006521c39f642fc3b1c3b5c3ac0c3a7ac2a6c38ec2a6c2b1c3a7c2b7c3b7c38dc2a2c38bc39f07362ac28508c28ec297c3b1c29ec3b94331c38955c380c3a9321ac393c28642c28c' } ] -describe('lib/utils/utils', function() { - describe('toHex', function() { - tests.forEach(function(test) { - it('should turn ' + test.value + ' to ' + test.expected, function() { +describe('lib/utils/utils', function () { + describe('toHex', function () { + tests.forEach(function (test) { + it('should turn ' + test.value + ' to ' + test.expected, function () { expect(utils.toHex(test.value as any)).toEqual(test.expected) }) }) + it('should throw in arrays', function () { + expect(() => utils.toHex([1, 2, 3, { test: 'data' }] as any)).toThrow() + }) + it('should throw in objects', function () { + expect(() => utils.toHex({ test: 'data' } as any)).toThrow() + }) }) }) diff --git a/test/utils.toUtf8.ts b/test/utils.toUtf8.ts index 4171b0b8..21a00f88 100644 --- a/test/utils.toUtf8.ts +++ b/test/utils.toUtf8.ts @@ -1,17 +1,20 @@ import * as expect from 'expect' -import * as utils from '../src/utils/utils' +import { hexToBytes } from '../src' +import * as utils from '../src/utils/utf8' let tests = [ { value: '0x6d79537472696e67', expected: 'myString' }, - { value: '0x6d79537472696e6700', expected: 'myString' }, - { value: '0x65787065637465642076616c7565000000000000000000000000000000000000', expected: 'expected value' } -] + { value: '0x6d79537472696e6700', expected: 'myString\x00' }, + { value: hexToBytes('0x6d79537472696e6700'), expected: 'myString\x00' }, + { value: hexToBytes('6d79537472696e6700'), expected: 'myString\x00' }, + { value: '0x65787065637465642076616c75650000', expected: 'expected value\x00\x00' } +] as const describe('lib/utils/utils', function () { describe('toUtf8', function () { tests.forEach(function (test) { it('should turn ' + test.value + ' to ' + test.expected, function () { - expect(utils.toUtf8(test.value)).toEqual(test.expected) + expect(utils.bytesToUtf8String(test.value)).toEqual(test.expected) }) }) })