trufflesuite/truffle

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

Summary

Maintainability
A
0 mins
Test Coverage
/**
 * @protected
 *
 * @packageDocumentation
 */

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

import BN from "bn.js";
import read from "@truffle/codec/read";
import * as Conversion from "@truffle/codec/conversion";
import * as Format from "@truffle/codec/format";
import * as Basic from "@truffle/codec/basic";
import * as Bytes from "@truffle/codec/bytes";
import type * as Pointer from "@truffle/codec/pointer";
import type { DecoderRequest, DecoderOptions } from "@truffle/codec/types";
import * as Evm from "@truffle/codec/evm";
import { isSkippedInMemoryStructs } from "@truffle/codec/memory/allocate";
import { handleDecodingError } from "@truffle/codec/errors";

export function* decodeMemory(
  dataType: Format.Types.Type,
  pointer: Pointer.MemoryPointer,
  info: Evm.EvmInfo,
  options: DecoderOptions = {}
): Generator<DecoderRequest, Format.Values.Result, Uint8Array | null> {
  if (Format.Types.isReferenceType(dataType)) {
    if (isSkippedInMemoryStructs(dataType)) {
      //special case; these types are always empty in memory
      return decodeMemorySkippedType(dataType);
    } else {
      return yield* decodeMemoryReferenceByAddress(
        dataType,
        pointer,
        info,
        options
      );
    }
  } else {
    return yield* Basic.Decode.decodeBasic(dataType, pointer, info, options);
  }
}

function decodeMemorySkippedType(
  dataType: Format.Types.Type
): Format.Values.Result {
  switch (dataType.typeClass) {
    case "mapping":
      return {
        type: dataType,
        kind: "value" as const,
        value: [],
        interpretations: {}
      };
    case "array":
      return {
        type: dataType,
        kind: "value" as const,
        value: [],
        interpretations: {}
      };
    //other cases should not arise!
  }
}

export function* decodeMemoryReferenceByAddress(
  dataType: Format.Types.ReferenceType,
  pointer: Pointer.DataPointer,
  info: Evm.EvmInfo,
  options: DecoderOptions = {}
): Generator<DecoderRequest, Format.Values.Result, Uint8Array | null> {
  const { state } = info;
  const memoryVisited = options.memoryVisited || [];
  debug("pointer %o", pointer);
  let rawValue: Uint8Array;
  try {
    rawValue = yield* read(pointer, state);
  } catch (error) {
    return handleDecodingError(dataType, error);
  }

  let startPositionAsBN = Conversion.toBN(rawValue);
  let startPosition: number;
  try {
    startPosition = startPositionAsBN.toNumber();
  } catch {
    return <Format.Errors.ErrorResult>{
      //again with the TS failures...
      type: dataType,
      kind: "error" as const,
      error: {
        kind: "OverlargePointersNotImplementedError" as const,
        pointerAsBN: startPositionAsBN
      }
    };
  }
  //startPosition may get modified later, so let's save the current
  //value for circularity detection purposes
  const objectPosition = startPosition;
  let rawLength: Uint8Array;
  let lengthAsBN: BN;
  let length: number;
  let seenPreviously: number;

  switch (dataType.typeClass) {
    case "bytes":
    case "string":
      //initial word contains length
      try {
        rawLength = yield* read(
          {
            location: "memory" as const,
            start: startPosition,
            length: Evm.Utils.WORD_SIZE
          },
          state
        );
      } catch (error) {
        return handleDecodingError(dataType, error);
      }
      lengthAsBN = Conversion.toBN(rawLength);
      try {
        length = lengthAsBN.toNumber();
      } catch {
        return <
          | Format.Errors.BytesDynamicErrorResult
          | Format.Errors.StringErrorResult
        >{
          //again with the TS failures...
          type: dataType,
          kind: "error" as const,
          error: {
            kind: "OverlongArraysAndStringsNotImplementedError" as const,
            lengthAsBN
          }
        };
      }

      let childPointer: Pointer.MemoryPointer = {
        location: "memory" as const,
        start: startPosition + Evm.Utils.WORD_SIZE,
        length
      };

      return yield* Bytes.Decode.decodeBytes(dataType, childPointer, info);

    case "array": {
      //first: circularity check!
      seenPreviously = memoryVisited.indexOf(objectPosition);
      if (seenPreviously !== -1) {
        return {
          type: dataType,
          kind: "value" as const,
          reference: seenPreviously + 1,
          value: [], //will be fixed later by the tie function
          interpretations: {}
        };
      }
      //otherwise, decode as normal
      if (dataType.kind === "dynamic") {
        //initial word contains array length
        try {
          rawLength = yield* read(
            {
              location: "memory" as const,
              start: startPosition,
              length: Evm.Utils.WORD_SIZE
            },
            state
          );
        } catch (error) {
          return handleDecodingError(dataType, error);
        }
        lengthAsBN = Conversion.toBN(rawLength);
        startPosition += Evm.Utils.WORD_SIZE; //increment startPosition
        //to next word, as first word was used for length
      } else {
        lengthAsBN = dataType.length;
      }
      try {
        length = lengthAsBN.toNumber();
      } catch {
        return {
          type: dataType,
          kind: "error" as const,
          error: {
            kind: "OverlongArraysAndStringsNotImplementedError" as const,
            lengthAsBN
          }
        };
      }

      let memoryNowVisited = [objectPosition, ...memoryVisited];

      let baseType = dataType.baseType;
      let decodedChildren: Format.Values.Result[] = [];
      for (let index = 0; index < length; index++) {
        decodedChildren.push(
          yield* decodeMemory(
            baseType,
            {
              location: "memory" as const,
              start: startPosition + index * Evm.Utils.WORD_SIZE,
              length: Evm.Utils.WORD_SIZE
            },
            info,
            { memoryVisited: memoryNowVisited }
          )
        );
      }

      return {
        type: dataType,
        kind: "value" as const,
        value: decodedChildren,
        interpretations: {}
      };
    }

    case "struct": {
      //first: circularity check!
      seenPreviously = memoryVisited.indexOf(objectPosition);
      if (seenPreviously !== -1) {
        return {
          type: dataType,
          kind: "value" as const,
          reference: seenPreviously + 1,
          value: [], //will be fixed later by the tie function
          interpretations: {}
        };
      }
      //otherwise, decode as normal
      const {
        allocations: { memory: allocations }
      } = info;

      const typeId = dataType.id;
      const structAllocation = allocations[typeId];
      if (!structAllocation) {
        return {
          type: dataType,
          kind: "error" as const,
          error: {
            kind: "UserDefinedTypeNotFoundError" as const,
            type: dataType
          }
        };
      }

      debug("structAllocation %O", structAllocation);

      let memoryNowVisited = [objectPosition, ...memoryVisited];
      let decodedMembers: Format.Values.NameValuePair[] = [];
      for (let index = 0; index < structAllocation.members.length; index++) {
        const memberAllocation = structAllocation.members[index];
        const memberPointer = memberAllocation.pointer;
        const childPointer: Pointer.MemoryPointer = {
          location: "memory" as const,
          start: startPosition + memberPointer.start,
          length: memberPointer.length //always equals WORD_SIZE or 0
        };

        let memberName = memberAllocation.name;
        let memberType = Format.Types.specifyLocation(
          memberAllocation.type,
          "memory"
        );

        decodedMembers.push({
          name: memberName,
          value: yield* decodeMemory(memberType, childPointer, info, {
            memoryVisited: memoryNowVisited
          })
        });
      }
      return {
        type: dataType,
        kind: "value" as const,
        value: decodedMembers,
        interpretations: {}
      };
    }
  }
}