aboutsummaryrefslogtreecommitdiffstats
path: root/packages/utils/src/abi_encoder
diff options
context:
space:
mode:
authorGreg Hysen <greg.hysen@gmail.com>2018-11-26 09:12:21 +0800
committerGreg Hysen <greg.hysen@gmail.com>2018-11-29 08:38:11 +0800
commitd2d89adbddaec435ddb65545a86fc4dc981de521 (patch)
tree0b95f1e18889440e67a534131aee7e6f18c73bd0 /packages/utils/src/abi_encoder
parent58a2dfbc4d191ea21e6a749371e586dcff3b3239 (diff)
downloaddexon-0x-contracts-d2d89adbddaec435ddb65545a86fc4dc981de521.tar
dexon-0x-contracts-d2d89adbddaec435ddb65545a86fc4dc981de521.tar.gz
dexon-0x-contracts-d2d89adbddaec435ddb65545a86fc4dc981de521.tar.bz2
dexon-0x-contracts-d2d89adbddaec435ddb65545a86fc4dc981de521.tar.lz
dexon-0x-contracts-d2d89adbddaec435ddb65545a86fc4dc981de521.tar.xz
dexon-0x-contracts-d2d89adbddaec435ddb65545a86fc4dc981de521.tar.zst
dexon-0x-contracts-d2d89adbddaec435ddb65545a86fc4dc981de521.zip
Abstracted out encoding/decoding of numeric values into its own utility. Could be useful elsewhere.
Diffstat (limited to 'packages/utils/src/abi_encoder')
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types/array.ts6
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types/int.ts6
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types/number.ts71
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts8
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types/string.ts2
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types/tuple.ts2
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types/uint.ts6
-rw-r--r--packages/utils/src/abi_encoder/utils/math.ts103
-rw-r--r--packages/utils/src/abi_encoder/utils/queue.ts2
9 files changed, 125 insertions, 81 deletions
diff --git a/packages/utils/src/abi_encoder/evm_data_types/array.ts b/packages/utils/src/abi_encoder/evm_data_types/array.ts
index dd8184fd0..527cdadfe 100644
--- a/packages/utils/src/abi_encoder/evm_data_types/array.ts
+++ b/packages/utils/src/abi_encoder/evm_data_types/array.ts
@@ -4,17 +4,17 @@ import { DataTypeFactory, MemberDataType } from '../abstract_data_types';
import * as Constants from '../utils/constants';
export class Array extends MemberDataType {
- private static readonly _matcher = RegExp('^(.+)\\[([0-9]*)\\]$');
+ private static readonly _MATCHER = RegExp('^(.+)\\[([0-9]*)\\]$');
private readonly _arraySignature: string;
private readonly _elementType: string;
public static matchType(type: string): boolean {
- return Array._matcher.test(type);
+ return Array._MATCHER.test(type);
}
public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) {
// Sanity check
- const matches = Array._matcher.exec(dataItem.type);
+ const matches = Array._MATCHER.exec(dataItem.type);
if (matches === null || matches.length !== 3) {
throw new Error(`Could not parse array: ${dataItem.type}`);
} else if (matches[1] === undefined) {
diff --git a/packages/utils/src/abi_encoder/evm_data_types/int.ts b/packages/utils/src/abi_encoder/evm_data_types/int.ts
index ec41b9cfc..457c41b28 100644
--- a/packages/utils/src/abi_encoder/evm_data_types/int.ts
+++ b/packages/utils/src/abi_encoder/evm_data_types/int.ts
@@ -7,16 +7,16 @@ import { DataTypeFactory } from '../abstract_data_types';
import { Number } from './number';
export class Int extends Number {
- private static readonly _matcher = RegExp(
+ private static readonly _MATCHER = RegExp(
'^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$',
);
public static matchType(type: string): boolean {
- return Int._matcher.test(type);
+ return Int._MATCHER.test(type);
}
public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) {
- super(dataItem, Int._matcher, dataTypeFactory);
+ super(dataItem, Int._MATCHER, dataTypeFactory);
}
public getMaxValue(): BigNumber {
diff --git a/packages/utils/src/abi_encoder/evm_data_types/number.ts b/packages/utils/src/abi_encoder/evm_data_types/number.ts
index 86acdce07..053a574e3 100644
--- a/packages/utils/src/abi_encoder/evm_data_types/number.ts
+++ b/packages/utils/src/abi_encoder/evm_data_types/number.ts
@@ -1,11 +1,11 @@
import { DataItem } from 'ethereum-types';
-import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';
import { BigNumber } from '../../configured_bignumber';
import { DataTypeFactory, PayloadDataType } from '../abstract_data_types';
import { RawCalldata } from '../calldata';
import * as Constants from '../utils/constants';
+import * as EncoderMath from '../utils/math';
export abstract class Number extends PayloadDataType {
private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true;
@@ -25,73 +25,14 @@ export abstract class Number extends PayloadDataType {
: (this._width = Number._DEFAULT_WIDTH);
}
- public encodeValue(value_: BigNumber | string | number): Buffer {
- const value = new BigNumber(value_, 10);
- if (value.greaterThan(this.getMaxValue())) {
- throw new Error(`Tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`);
- } else if (value.lessThan(this.getMinValue())) {
- throw new Error(`Tried to assign value of ${value}, which exceeds min value of ${this.getMinValue()}`);
- }
-
- let valueBuf: Buffer;
- if (value.greaterThanOrEqualTo(0)) {
- valueBuf = ethUtil.setLengthLeft(
- ethUtil.toBuffer(`0x${value.toString(Constants.HEX_BASE)}`),
- Constants.EVM_WORD_WIDTH_IN_BYTES,
- );
- } else {
- // BigNumber can't write a negative hex value, so we use twos-complement conversion to do it ourselves.
- // Step 1/3: Convert value to positive binary string
- const binBase = 2;
- const valueBin = value.times(-1).toString(binBase);
-
- // Step 2/3: Invert binary value
- let invertedValueBin = '1'.repeat(Constants.EVM_WORD_WIDTH_IN_BITS - valueBin.length);
- _.each(valueBin, (bit: string) => {
- invertedValueBin += bit === '1' ? '0' : '1';
- });
- const invertedValue = new BigNumber(invertedValueBin, binBase);
-
- // Step 3/3: Add 1 to inverted value
- // The result is the two's-complement represent of the input value.
- const negativeValue = invertedValue.plus(1);
-
- // Convert the negated value to a hex string
- valueBuf = ethUtil.setLengthLeft(
- ethUtil.toBuffer(`0x${negativeValue.toString(Constants.HEX_BASE)}`),
- Constants.EVM_WORD_WIDTH_IN_BYTES,
- );
- }
-
- return valueBuf;
+ public encodeValue(value: BigNumber | string | number): Buffer {
+ const encodedValue = EncoderMath.safeEncodeNumericValue(value, this.getMinValue(), this.getMaxValue());
+ return encodedValue;
}
public decodeValue(calldata: RawCalldata): BigNumber {
- const paddedValueBuf = calldata.popWord();
- const paddedValueHex = ethUtil.bufferToHex(paddedValueBuf);
- let value = new BigNumber(paddedValueHex, 16);
- if (this.getMinValue().lessThan(0)) {
- // Check if we're negative
- const valueBin = value.toString(Constants.BIN_BASE);
- if (valueBin.length === Constants.EVM_WORD_WIDTH_IN_BITS && valueBin[0].startsWith('1')) {
- // Negative
- // Step 1/3: Invert binary value
- let invertedValueBin = '';
- _.each(valueBin, (bit: string) => {
- invertedValueBin += bit === '1' ? '0' : '1';
- });
- const invertedValue = new BigNumber(invertedValueBin, Constants.BIN_BASE);
-
- // Step 2/3: Add 1 to inverted value
- // The result is the two's-complement represent of the input value.
- const positiveValue = invertedValue.plus(1);
-
- // Step 3/3: Invert positive value
- const negativeValue = positiveValue.times(-1);
- value = negativeValue;
- }
- }
-
+ const valueBuf = calldata.popWord();
+ const value = EncoderMath.safeDecodeNumericValue(valueBuf, this.getMinValue(), this.getMaxValue());
return value;
}
diff --git a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts
index afa9afdf2..0d01e6105 100644
--- a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts
+++ b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts
@@ -8,18 +8,18 @@ import * as Constants from '../utils/constants';
export class StaticBytes extends PayloadDataType {
private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true;
- private static readonly _matcher = RegExp(
+ private static readonly _MATCHER = RegExp(
'^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$',
);
private static readonly _DEFAULT_WIDTH = 1;
private readonly _width: number;
public static matchType(type: string): boolean {
- return StaticBytes._matcher.test(type);
+ return StaticBytes._MATCHER.test(type);
}
private static _decodeWidthFromType(type: string): number {
- const matches = StaticBytes._matcher.exec(type);
+ const matches = StaticBytes._MATCHER.exec(type);
const width = (matches !== null && matches.length === 3 && matches[2] !== undefined)
? parseInt(matches[2], Constants.DEC_BASE)
: StaticBytes._DEFAULT_WIDTH;
@@ -55,7 +55,7 @@ export class StaticBytes extends PayloadDataType {
this._sanityCheckValue(value);
return value;
}
-
+
private _sanityCheckValue(value: string | Buffer): void {
if (typeof value === 'string') {
if (!value.startsWith('0x')) {
diff --git a/packages/utils/src/abi_encoder/evm_data_types/string.ts b/packages/utils/src/abi_encoder/evm_data_types/string.ts
index 15b93e447..428ea21db 100644
--- a/packages/utils/src/abi_encoder/evm_data_types/string.ts
+++ b/packages/utils/src/abi_encoder/evm_data_types/string.ts
@@ -13,7 +13,7 @@ export class String extends PayloadDataType {
public static matchType(type: string): boolean {
return type === 'string';
}
-
+
public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) {
super(dataItem, dataTypeFactory, String._SIZE_KNOWN_AT_COMPILE_TIME);
if (!String.matchType(dataItem.type)) {
diff --git a/packages/utils/src/abi_encoder/evm_data_types/tuple.ts b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts
index 89dd6604d..63d9dfa9e 100644
--- a/packages/utils/src/abi_encoder/evm_data_types/tuple.ts
+++ b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts
@@ -4,7 +4,7 @@ import { DataTypeFactory, MemberDataType } from '../abstract_data_types';
export class Tuple extends MemberDataType {
private readonly _signature: string;
-
+
public static matchType(type: string): boolean {
return type === 'tuple';
}
diff --git a/packages/utils/src/abi_encoder/evm_data_types/uint.ts b/packages/utils/src/abi_encoder/evm_data_types/uint.ts
index ced3ef08b..c2b6e214a 100644
--- a/packages/utils/src/abi_encoder/evm_data_types/uint.ts
+++ b/packages/utils/src/abi_encoder/evm_data_types/uint.ts
@@ -7,16 +7,16 @@ import { DataTypeFactory } from '../abstract_data_types';
import { Number } from './number';
export class UInt extends Number {
- private static readonly _matcher = RegExp(
+ private static readonly _MATCHER = RegExp(
'^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$',
);
public static matchType(type: string): boolean {
- return UInt._matcher.test(type);
+ return UInt._MATCHER.test(type);
}
public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) {
- super(dataItem, UInt._matcher, dataTypeFactory);
+ super(dataItem, UInt._MATCHER, dataTypeFactory);
}
public getMaxValue(): BigNumber {
diff --git a/packages/utils/src/abi_encoder/utils/math.ts b/packages/utils/src/abi_encoder/utils/math.ts
new file mode 100644
index 000000000..8d21ada0a
--- /dev/null
+++ b/packages/utils/src/abi_encoder/utils/math.ts
@@ -0,0 +1,103 @@
+import BigNumber from 'bignumber.js';
+import * as ethUtil from 'ethereumjs-util';
+import * as _ from 'lodash';
+
+import * as Constants from '../utils/constants';
+
+function sanityCheckBigNumberRange(value_: BigNumber | string | number, minValue: BigNumber, maxValue: BigNumber): void {
+ const value = new BigNumber(value_, 10);
+ if (value.greaterThan(maxValue)) {
+ throw new Error(`Tried to assign value of ${value}, which exceeds max value of ${maxValue}`);
+ } else if (value.lessThan(minValue)) {
+ throw new Error(`Tried to assign value of ${value}, which exceeds min value of ${minValue}`);
+ }
+}
+function bigNumberToPaddedBuffer(value: BigNumber): Buffer {
+ const valueHex = `0x${value.toString(Constants.HEX_BASE)}`;
+ const valueBuf = ethUtil.toBuffer(valueHex);
+ const valueBufPadded = ethUtil.setLengthLeft(valueBuf, Constants.EVM_WORD_WIDTH_IN_BYTES);
+ return valueBufPadded;
+}
+/**
+ * Takes a numeric value and returns its ABI-encoded value
+ * @param value_ The value to encode.
+ * @return ABI Encoded value
+ */
+export function encodeNumericValue(value_: BigNumber | string | number): Buffer {
+ const value = new BigNumber(value_, 10);
+ // Case 1/2: value is non-negative
+ if (value.greaterThanOrEqualTo(0)) {
+ const encodedPositiveValue = bigNumberToPaddedBuffer(value);
+ return encodedPositiveValue;
+ }
+ // Case 2/2: Value is negative
+ // Use two's-complement to encode the value
+ // Step 1/3: Convert negative value to positive binary string
+ const valueBin = value.times(-1).toString(Constants.BIN_BASE);
+ // Step 2/3: Invert binary value
+ let invertedValueBin = '1'.repeat(Constants.EVM_WORD_WIDTH_IN_BITS - valueBin.length);
+ _.each(valueBin, (bit: string) => {
+ invertedValueBin += bit === '1' ? '0' : '1';
+ });
+ const invertedValue = new BigNumber(invertedValueBin, Constants.BIN_BASE);
+ // Step 3/3: Add 1 to inverted value
+ const negativeValue = invertedValue.plus(1);
+ const encodedValue = bigNumberToPaddedBuffer(negativeValue);
+ return encodedValue;
+}
+/**
+ * Takes a numeric value and returns its ABI-encoded value.
+ * Performs an additional sanity check, given the min/max allowed value.
+ * @param value_ The value to encode.
+ * @return ABI Encoded value
+ */
+export function safeEncodeNumericValue(value: BigNumber | string | number, minValue: BigNumber, maxValue: BigNumber): Buffer {
+ sanityCheckBigNumberRange(value, minValue, maxValue);
+ const encodedValue = encodeNumericValue(value);
+ return encodedValue;
+}
+/**
+ * Takes an ABI-encoded numeric value and returns its decoded value as a BigNumber.
+ * @param encodedValue The encoded numeric value.
+ * @param minValue The minimum possible decoded value.
+ * @return ABI Decoded value
+ */
+export function decodeNumericValue(encodedValue: Buffer, minValue: BigNumber): BigNumber {
+ const valueHex = ethUtil.bufferToHex(encodedValue);
+ // Case 1/3: value is definitely non-negative because of numeric boundaries
+ const value = new BigNumber(valueHex, Constants.HEX_BASE);
+ if (!minValue.lessThan(0)) {
+ return value;
+ }
+ // Case 2/3: value is non-negative because there is no leading 1 (encoded as two's-complement)
+ const valueBin = value.toString(Constants.BIN_BASE);
+ const valueIsNegative = valueBin.length === Constants.EVM_WORD_WIDTH_IN_BITS && valueBin[0].startsWith('1');
+ if (!valueIsNegative) {
+ return value;
+ }
+ // Case 3/3: value is negative
+ // Step 1/3: Invert b inary value
+ let invertedValueBin = '';
+ _.each(valueBin, (bit: string) => {
+ invertedValueBin += bit === '1' ? '0' : '1';
+ });
+ const invertedValue = new BigNumber(invertedValueBin, Constants.BIN_BASE);
+ // Step 2/3: Add 1 to inverted value
+ // The result is the two's-complement representation of the input value.
+ const positiveValue = invertedValue.plus(1);
+ // Step 3/3: Invert positive value to get the negative value
+ const negativeValue = positiveValue.times(-1);
+ return negativeValue;
+}
+/**
+ * Takes an ABI-encoded numeric value and returns its decoded value as a BigNumber.
+ * Performs an additional sanity check, given the min/max allowed value.
+ * @param encodedValue The encoded numeric value.
+ * @param minValue The minimum possible decoded value.
+ * @return ABI Decoded value
+ */
+export function safeDecodeNumericValue(encodedValue: Buffer, minValue: BigNumber, maxValue: BigNumber): BigNumber {
+ const value = decodeNumericValue(encodedValue, minValue);
+ sanityCheckBigNumberRange(value, minValue, maxValue);
+ return value;
+}
diff --git a/packages/utils/src/abi_encoder/utils/queue.ts b/packages/utils/src/abi_encoder/utils/queue.ts
index 506a0b56e..53afb7e11 100644
--- a/packages/utils/src/abi_encoder/utils/queue.ts
+++ b/packages/utils/src/abi_encoder/utils/queue.ts
@@ -32,7 +32,7 @@ export class Queue<T> {
public getStore(): T[] {
return this._store;
}
-
+
public peekFront(): T | undefined {
return this._store.length >= 0 ? this._store[0] : undefined;
}