lib/data/decode/utils.js
- import debugModule from "debug";
- const debug = debugModule("debugger:data:decode:utils");
-
- import { BigNumber } from "bignumber.js";
- import Web3 from "web3";
-
- export const WORD_SIZE = 0x20;
- export const MAX_WORD = new BigNumber(2).pow(256).minus(1);
-
- /**
- * recursively converts big numbers into something nicer to look at
- */
- export function cleanBigNumbers(value) {
- if (BigNumber.isBigNumber(value)) {
- return value.toNumber();
-
- } else if (value && value.map != undefined) {
- return value.map( (inner) => cleanBigNumbers(inner) );
-
- } else if (value && typeof value == "object") {
- return Object.assign(
- {}, ...Object.entries(value)
- .map( ([key, inner]) => ({ [key]: cleanBigNumbers(inner) }) )
- );
-
- } else {
- return value;
- }
- }
-
- export function typeIdentifier(definition) {
- return definition.typeDescriptions.typeIdentifier;
- }
-
- /**
- * returns basic type class for a variable definition node
- * e.g.:
- * `t_uint256` becomes `uint`
- * `t_struct$_Thing_$20_memory_ptr` becomes `struct`
- */
- export function typeClass(definition) {
- return typeIdentifier(definition).match(/t_([^$_0-9]+)/)[1];
- }
-
- /**
- * Allocate storage for given variable declarations
- *
- * Postcondition: starts a new slot and occupies whole slots
- */
- export function allocateDeclarations(
- declarations,
- refs,
- slot = 0,
- index = WORD_SIZE - 1,
- path = []
- ) {
- if (index < WORD_SIZE - 1) { // starts a new slot
- slot++;
- index = WORD_SIZE - 1;
- }
-
- let parentFrom = { slot, index: 0 };
- var parentTo = { slot, index: WORD_SIZE - 1 };
- let mapping = {};
-
- for (let declaration of declarations) {
- let { from, to, next, children } =
- allocateDeclaration(declaration, refs, slot, index);
-
- mapping[declaration.id] = { from, to, name: declaration.name };
- if (children !== undefined) {
- mapping[declaration.id].children = children;
- }
-
- slot = next.slot;
- index = next.index;
-
- parentTo = { slot: to.slot, index: WORD_SIZE - 1 };
- }
-
- if (index < WORD_SIZE - 1) {
- slot++;
- index = WORD_SIZE - 1;
- }
-
- return {
- from: parentFrom,
- to: parentTo,
- next: { slot, index },
- children: mapping
- };
- }
-
- function allocateValue(slot, index, bytes) {
- let from = index - bytes + 1 >= 0 ?
- { slot, index: index - bytes + 1 } :
- { slot: slot + 1, index: WORD_SIZE - bytes };
-
- let to = { slot: from.slot, index: from.index + bytes - 1 };
-
- let next = from.index == 0 ?
- { slot: from.slot + 1, index: WORD_SIZE - 1 } :
- { slot: from.slot, index: from.index - 1 };
-
- return { from, to, next };
- }
-
- function allocateDeclaration(declaration, refs, slot, index) {
- let definition = refs[declaration.id].definition;
- var byteSize = storageSize(definition); // yum
-
- if (typeClass(definition) != "struct") {
- return allocateValue(slot, index, byteSize);
- }
-
- let struct = refs[definition.typeName.referencedDeclaration];
- debug("struct: %O", struct);
-
- let result = allocateDeclarations(struct.variables || [], refs, slot, index);
- debug("struct result %o", result);
- return result;
- }
-
- /**
- * e.g. uint48 -> 6
- * @return size in bytes for explicit type size, or `null` if not stated
- */
- export function specifiedSize(definition) {
- let specified = typeIdentifier(definition).match(/t_[a-z]+([0-9]+)/);
-
- if (!specified) {
- return null;
- }
-
- let num = specified[1];
-
- switch (typeClass(definition)) {
- case "int":
- case "uint":
- return num / 8;
-
- case "bytes":
- return num;
-
- default:
- debug("Unknown type for size specification: %s", typeIdentifier(definition));
- }
- }
-
- export function storageSize(definition) {
- switch (typeClass(definition)) {
- case "bool":
- return 1;
-
- case "address":
- return 20;
-
- case "int":
- case "uint":
- // is this a HACK? ("256" / 8)
- return typeIdentifier(definition).match(/t_[a-z]+([0-9]+)/)[1] / 8;
-
- case "string":
- case "bytes":
- case "array":
- return WORD_SIZE;
- }
- }
-
- export function isReference(definition) {
- return typeIdentifier(definition).match(/_(memory|storage)(_ptr)?$/) != null;
- }
-
- export function referenceType(definition) {
- return typeIdentifier(definition).match(/_([^_]+)(_ptr)?$/)[1];
- }
-
- export function baseDefinition(definition) {
- let baseIdentifier = typeIdentifier(definition)
- // first dollar sign last dollar sign
- // `---------. ,---'
- .match(/^[^$]+\$_(.+)_\$[^$]+$/)[1]
- // `----' greedy match
-
- // HACK - internal types for memory or storage also seem to be pointers
- if (baseIdentifier.match(/_(memory|storage)$/) != null) {
- baseIdentifier = `${baseIdentifier}_ptr`;
- }
-
- // another HACK - we get away with it becausewe're only using that one property
- return {
- typeDescriptions: {
- typeIdentifier: baseIdentifier
- }
- };
- }
-
-
- export function toBigNumber(bytes) {
- if (bytes == undefined) {
- return undefined;
- } else if (typeof bytes == "string") {
- return new BigNumber(bytes, 16);
- } else if (typeof bytes == "number" || BigNumber.isBigNumber(bytes)) {
- return new BigNumber(bytes);
- } else if (bytes.reduce) {
- return bytes.reduce(
- (num, byte) => num.times(0x100).plus(byte),
- new BigNumber(0)
- );
- }
- }
-
- export function toSignedBigNumber(bytes) {
- if (bytes[0] < 0b10000000) { // first bit is 0
- return toBigNumber(bytes);
- } else {
- return toBigNumber(bytes.map( (b) => 0xff - b )).plus(1).negated();
- }
- }
-
- /**
- * @param bytes - Uint8Array
- * @param length - desired byte length (pad with zeroes)
- * @param trim - omit leading zeroes
- */
- export function toHexString(bytes, length = 0, trim = false) {
- if (typeof length == "boolean") {
- trim = length;
- length = 0;
- }
-
- if (BigNumber.isBigNumber(bytes)) {
- bytes = toBytes(bytes);
- }
-
- const pad = (s) => `${"00".slice(0, 2 - s.length)}${s}`
-
- // 0 1 2 3 4
- // 0 1 2 3 4 5 6 7
- // bytes.length: 5 - 0x( e5 c2 aa 09 11 )
- // length (preferred): 8 - 0x( 00 00 00 e5 c2 aa 09 11 )
- // `--.---'
- // offset 3
- if (bytes.length < length) {
- let prior = bytes;
- bytes = new Uint8Array(length);
-
- bytes.set(prior, length - prior.length);
- }
-
- debug("bytes: %o", bytes);
-
- let string = bytes.reduce(
- (str, byte) => `${str}${pad(byte.toString(16))}`, ""
- );
-
- if (trim) {
- string = string.replace(/^(00)+/, "");
- }
-
- if (string.length == 0) {
- string = "00";
- }
-
- return `0x${string}`;
- }
-
- export function toBytes(number, length = 0) {
- if (number < 0) {
- return [];
- }
-
- let hex = number.toString(16);
- if (hex.length % 2 == 1) {
- hex = `0${hex}`;
- }
-
- let bytes = new Uint8Array(
- hex.match(/.{2}/g)
- .map( (byte) => parseInt(byte, 16) )
- );
-
- if (bytes.length < length) {
- let prior = bytes;
- bytes = new Uint8Array(length);
- bytes.set(prior, length - prior.length);
- }
-
- return bytes;
- }
-
- export function keccak256(...args) {
- let web3 = new Web3();
-
- args = args.map( (arg) => {
- if (typeof arg == "number" || BigNumber.isBigNumber(arg)) {
- return toHexString(toBytes(arg, WORD_SIZE)).slice(2)
- } else if (typeof arg == "string") {
- return web3.toHex(arg).slice(2);
- } else {
- return "";
- }
- });
-
- let sha = web3.sha3(args.join(''), { encoding: 'hex' });
- debug("sha %o", sha);
- return toBigNumber(sha);
- }