aboutsummaryrefslogtreecommitdiffstats
path: root/packages/utils/src/abi_encoder/utils/math.ts
blob: d84983c5b9057eb737fd274f595629af59d3d0fc (plain) (blame)
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import BigNumber from 'bignumber.js';
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';

import { 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 isValueNegative = valueBin.length === constants.EVM_WORD_WIDTH_IN_BITS && _.startsWith(valueBin[0], '1');
    if (!isValueNegative) {
        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;
}