aboutsummaryrefslogblamecommitdiffstats
path: root/packages/utils/src/abi_encoder/evm_data_types/number.ts
blob: 86acdce076a328f7be451e4692dff82923825def (plain) (tree)
1
2
3
4
5
6
7
8




                                                       
                                                                          
                                          
                                                



























































































                                                                                                                   
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';

export abstract class Number extends PayloadDataType {
    private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true;
    private static readonly _MAX_WIDTH: number = 256;
    private static readonly _DEFAULT_WIDTH: number = Number._MAX_WIDTH;
    protected _width: number;

    constructor(dataItem: DataItem, matcher: RegExp, dataTypeFactory: DataTypeFactory) {
        super(dataItem, dataTypeFactory, Number._SIZE_KNOWN_AT_COMPILE_TIME);
        const matches = matcher.exec(dataItem.type);
        if (matches === null) {
            throw new Error(`Tried to instantiate Number with bad input: ${dataItem}`);
        }
        this._width =
            matches !== null && matches.length === 2 && matches[1] !== undefined
                ? parseInt(matches[1], Constants.DEC_BASE)
                : (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 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;
            }
        }

        return value;
    }

    public abstract getMaxValue(): BigNumber;
    public abstract getMinValue(): BigNumber;
}