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
|
import { DataItem } from 'ethereum-types';
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';
import { BigNumber } from '../../configured_bignumber';
import { RawCalldata } from '../calldata';
import * as Constants from '../constants';
import { DataTypeFactory, PayloadDataType } from '../data_type';
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;
}
|