Home Reference Source

lib/data/decode/index.js

import debugModule from "debug";
const debug = debugModule("debugger:data:decode");

import {BigNumber} from "bignumber.js";

import * as memory from "./memory";
import * as storage from "./storage";
import * as utils from "./utils";
import { WORD_SIZE } from "./utils";

export function read(pointer, state) {
  if (pointer.stack != undefined && state.stack && pointer.stack < state.stack.length) {
    return state.stack[pointer.stack];
  } else if (pointer.storage != undefined && state.storage) {
    return storage.readRange(state.storage, pointer.storage);
  } else if (pointer.memory != undefined && state.memory) {
    return memory.readBytes(state.memory, pointer.memory.start, pointer.memory.length);
  } else if (pointer.literal) {
    return pointer.literal;
  }
}


export function decodeValue(definition, pointer, state, ...args) {
  let bytes = read(pointer, state);
  if (!bytes) {
    return undefined;
  }

  switch (utils.typeClass(definition)) {
    case "bool":
      return !utils.toBigNumber(bytes).isZero();

    case "uint":
      return utils.toBigNumber(bytes);

    case "int":
      return utils.toSignedBigNumber(bytes);

    case "address":
      return utils.toHexString(bytes, true);

    case "bytes":
      debug("typeIdentifier %s %o", utils.typeIdentifier(definition), bytes);
      let length = utils.specifiedSize(definition);
      return utils.toHexString(bytes, length);

    case "string":
      debug("typeIdentifier %s %o", utils.typeIdentifier(definition), bytes);
      return String.fromCharCode.apply(null, bytes);

    default:
      debug("Unknown value type: %s", utils.typeIdentifier(definition));
      return null;
  }
}

export function decodeMemoryReference(definition, pointer, state, ...args) {
  let rawValue = utils.toBigNumber(read(pointer, state)).toNumber();

  var bytes;
  switch (utils.typeClass(definition)) {

    case "bytes":
    case "string":
      bytes = read({
        memory: { start: rawValue, length: WORD_SIZE}
      }, state); // bytes contain length

      return decodeValue(definition, {
        memory: { start: rawValue + WORD_SIZE, length: bytes }
      }, state, ...args);

    case "array":
      bytes = utils.toBigNumber(read({
        memory: { start: rawValue, length: WORD_SIZE },
      }, state)).toNumber();  // bytes contain array length

      bytes = read({ memory: {
        start: rawValue + WORD_SIZE, length: bytes * WORD_SIZE
      }}, state); // now bytes contain items

      return memory.chunk(bytes, WORD_SIZE)
        .map(
          (chunk) => decode(utils.baseDefinition(definition), {
            literal: chunk
          }, state, ...args)
        )

    case "struct":
      let [refs] = args;
      let structDefinition = refs[definition.typeName.referencedDeclaration];
      let structVariables = structDefinition.variables || [];

      return Object.assign(
        {}, ...structVariables
          .map(
            ({name, id}, i) => {
              let memberDefinition = refs[id].definition;
              let memberPointer = {
                memory: { start: rawValue + i * WORD_SIZE, length: WORD_SIZE }
              };
              // let memberPointer = memory.read(state.memory, pointer + i * WORD_SIZE);

              // HACK
              memberDefinition = {
                ...memberDefinition,

                typeDescriptions: {
                  ...memberDefinition.typeDescriptions,

                  typeIdentifier:
                    memberDefinition.typeDescriptions.typeIdentifier
                      .replace(/_storage_/g, "_memory_")
                }
              };

              return {
                [name]: decode(
                  memberDefinition, memberPointer, state, ...args
                )
              };
            }
          )
      );


    default:
      debug("Unknown memory reference type: %s", utils.typeIdentifier(definition));
      return null;

  }

}

export function decodeStorageReference(definition, pointer, state, ...args) {
  var data;
  var bytes;
  var length;
  var slot;

  switch (utils.typeClass(definition)) {
    case "array":
      debug("storage array! %o", pointer);
      data = read(pointer, state);
      if (!data) {
        return null;
      }

      length = utils.toBigNumber(data).toNumber();
      debug("length %o", length);

      const baseSize = utils.storageSize(utils.baseDefinition(definition));
      const perWord = Math.floor(WORD_SIZE / baseSize);
      debug("baseSize %o", baseSize);
      debug("perWord %d", perWord);

      const offset = (i) => {
        if (perWord == 1) {
          return i;
        }

        return Math.floor(i * baseSize / WORD_SIZE);
      }

      const index = (i) => {
        if (perWord == 1) {
          return WORD_SIZE - baseSize;
        }

        const position = perWord - i % perWord - 1;
        return position * baseSize;
      }

      debug("pointer: %o", pointer);
      return [...Array(length).keys()]
        .map( (i) => {
          let childFrom = pointer.storage.from.offset != undefined ?
            {
              slot: ["0x" + utils.toBigNumber(
                utils.keccak256(...pointer.storage.from.slot)
              ).plus(pointer.storage.from.offset).toString(16)],
              offset: offset(i),
              index: index(i)
            } : {
              slot: [pointer.storage.from.slot],
              offset: offset(i),
              index: index(i)
            };
          return childFrom;
        })
        .map( (childFrom, idx) => {
          debug("childFrom %d, %o", idx, childFrom);
          return decode(utils.baseDefinition(definition), { storage: {
            from: childFrom,
            length: baseSize
          }}, state, ...args);
        });

    case "bytes":
    case "string":
      data = read(pointer, state);
      if (!data) {
        return null;
      }

      if (data[WORD_SIZE - 1] % 2 == 0) {
        // string lives in word, length is last byte / 2
        length = data[WORD_SIZE - 1] / 2;
        debug("in-word; length %o", length);
        if (length == 0) {
          return "";
        }

        return decodeValue(definition, { storage: {
          from: { slot: pointer.storage.from.slot, index: 0 },
          to: { slot: pointer.storage.from.slot, index: length - 1}
        }}, state, ...args);

      } else {
        length = utils.toBigNumber(data).minus(1).div(2).toNumber();
        debug("new-word, length %o", length);

        return decodeValue(definition, { storage: {
          from: { slot: [pointer.storage.from.slot], index: 0 },
          length
        }}, state, ...args);
      }

    case "struct":
      let [refs] = args;

      return Object.assign(
        {}, ...Object.entries(pointer.storage.children)
          .map( ([id, childPointer]) => ({
            [childPointer.name]: decode(
              refs[id].definition, { storage: childPointer }, state, ...args
            )
          }))
      );

    default:
      debug("Unknown storage reference type: %s", utils.typeIdentifier(definition));
      return undefined;
  }
}



export default function decode(definition, ...args) {
  if (!utils.isReference(definition)) {
    return decodeValue(definition, ...args);
  }

  switch (utils.referenceType(definition)) {
    case "memory":
      return decodeMemoryReference(definition, ...args);
    case "storage":
      return decodeStorageReference(definition, ...args);
    default:
      debug("Unknown reference category: %s", utils.typeIdentifier(definition));
      return undefined;
  }
}