trufflesuite/truffle

View on GitHub
packages/codec/lib/stack/decode/index.ts

Summary

Maintainability
B
5 hrs
Test Coverage
/**
 * @protected
 *
 * @packageDocumentation
 */

import debugModule from "debug";
const debug = debugModule("codec:stack:decode");

import * as AbiData from "@truffle/codec/abi-data";
import * as Conversion from "@truffle/codec/conversion";
import * as Format from "@truffle/codec/format";
import read from "@truffle/codec/read";
import * as Basic from "@truffle/codec/basic";
import * as Memory from "@truffle/codec/memory";
import * as Storage from "@truffle/codec/storage";
import type * as Pointer from "@truffle/codec/pointer";
import type { DecoderRequest } from "@truffle/codec/types";
import * as Evm from "@truffle/codec/evm";
import { handleDecodingError } from "@truffle/codec/errors";

export function* decodeStack(
  dataType: Format.Types.Type,
  pointer: Pointer.StackPointer,
  info: Evm.EvmInfo
): Generator<DecoderRequest, Format.Values.Result, Uint8Array | null> {
  let rawValue: Uint8Array;
  try {
    rawValue = yield* read(pointer, info.state);
  } catch (error) {
    return handleDecodingError(dataType, error);
  }
  const literalPointer: Pointer.StackLiteralPointer = {
    location: "stackliteral" as const,
    literal: rawValue
  };
  return yield* decodeLiteral(dataType, literalPointer, info);
}

export function* decodeLiteral(
  dataType: Format.Types.Type,
  pointer: Pointer.StackLiteralPointer,
  info: Evm.EvmInfo
): Generator<DecoderRequest, Format.Values.Result, Uint8Array | null> {
  debug("type %O", dataType);
  debug("pointer %o", pointer);

  if (Format.Types.isReferenceType(dataType)) {
    switch (dataType.location) {
      case "memory":
        //first: do we have a memory pointer? if so we can just dispatch to
        //decodeMemoryReference
        return yield* Memory.Decode.decodeMemoryReferenceByAddress(
          dataType,
          pointer,
          info
        );

      case "storage":
        //next: do we have a storage pointer (which may be a mapping)? if so, we can
        //we dispatch to decodeStorageByAddress
        return yield* Storage.Decode.decodeStorageReferenceByAddress(
          dataType,
          pointer,
          info
        );

      case "calldata":
        //next: do we have a calldata pointer?

        //if it's a lookup type, it'll need special handling
        if (
          dataType.typeClass === "bytes" ||
          dataType.typeClass === "string" ||
          (dataType.typeClass === "array" && dataType.kind === "dynamic")
        ) {
          const lengthAsBN = Conversion.toBN(
            pointer.literal.slice(Evm.Utils.WORD_SIZE)
          );
          const locationOnly = pointer.literal.slice(0, Evm.Utils.WORD_SIZE);
          return yield* AbiData.Decode.decodeAbiReferenceByAddress(
            dataType,
            { location: "stackliteral" as const, literal: locationOnly },
            info,
            {
              abiPointerBase: 0, //let's be explicit
              lengthOverride: lengthAsBN
            }
          );
        } else {
          //multivalue case -- this case is straightforward
          return yield* AbiData.Decode.decodeAbiReferenceByAddress(
            dataType,
            pointer,
            info,
            {
              abiPointerBase: 0 //let's be explicit
            }
          );
        }
    }
  }

  //next: do we have an external function?  these work differently on the stack
  //than elsewhere, so we can't just pass it on to decodeBasic.
  if (dataType.typeClass === "function" && dataType.visibility === "external") {
    let address = pointer.literal.slice(0, Evm.Utils.WORD_SIZE);
    let selectorWord = pointer.literal.slice(-Evm.Utils.WORD_SIZE);
    if (
      !Basic.Decode.checkPaddingLeft(address, Evm.Utils.ADDRESS_SIZE) ||
      !Basic.Decode.checkPaddingLeft(selectorWord, Evm.Utils.SELECTOR_SIZE)
    ) {
      return {
        type: dataType,
        kind: "error" as const,
        error: {
          kind: "FunctionExternalStackPaddingError" as const,
          rawAddress: Conversion.toHexString(address),
          rawSelector: Conversion.toHexString(selectorWord)
        }
      };
    }
    let selector = selectorWord.slice(-Evm.Utils.SELECTOR_SIZE);
    return {
      type: dataType,
      kind: "value" as const,
      value: yield* Basic.Decode.decodeExternalFunction(
        address,
        selector,
        info
      ),
      interpretations: {}
    };
  }

  //finally, if none of the above hold, we can just dispatch to decodeBasic.
  //however, note that because we're on the stack, we use the permissive padding
  //option so that errors won't result due to values with bad padding
  //(of numeric or bytesN type, anyway)
  return yield* Basic.Decode.decodeBasic(dataType, pointer, info, {
    paddingMode: "permissive"
  });
}