Repository: defi-wonderland/smock
Pull request #23 (2021-08-06 17:25:42)

feat: refactor storage editing logic
Refactoring the storage editing logic so that we can more easily support packed storage slots.

0xGorilla: We should write some issues where we say that currently we don't support 'arrays', 'long bytes', etc
smartcontracts: > We should write some issues where we say that currently we don't support 'arrays', 'long bytes', etc

Agreed. Maybe we can also add links to the issues to the errors so that people know we're working on it.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e83a098..fea2098 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,29 @@
 
 All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
 
+## [2.0.0-rc.1](https://github.com/defi-wonderland/smock/compare/v2.0.0-rc.0...v2.0.0-rc.1) (2021-08-09)
+
+
+### Features
+
+* setVariable for ints supported ([37259d6](https://github.com/defi-wonderland/smock/commit/37259d6b3d6c88fc1477221b9095e3b80a0d315b))
+
+## [2.0.0-rc.0](https://github.com/defi-wonderland/smock/compare/v1.0.1...v2.0.0-rc.0) (2021-08-09)
+
+
+### ⚠ BREAKING CHANGES
+
+* making this in order to make a rc of 2.0.0
+
+### Features
+
+* refactor storage editing logic ([f95f236](https://github.com/defi-wonderland/smock/commit/f95f236349af73f376ba779a6751469f452141fc))
+
+
+### Bug Fixes
+
+* small change ([b499049](https://github.com/defi-wonderland/smock/commit/b4990496b9de49e991360f74125c6ee21a00b482))
+
 ## [1.0.0](https://github.com/defi-wonderland/smock/compare/v1.0.0-rc.0...v1.0.0) (2021-07-25)
 
 ## [1.0.0-rc.0](https://github.com/defi-wonderland/smock/compare/v0.0.3...v1.0.0-rc.0) (2021-07-25)
diff --git a/package.json b/package.json
index b5900cf..80ad6f1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@defi-wonderland/smock",
-  "version": "1.0.1",
+  "version": "2.0.0-rc.1",
   "description": "Mocking framework for Solidity contracts",
   "keywords": [
     "ethereum",
@@ -54,7 +54,8 @@
     "diff": "^5.0.0",
     "lodash.isequal": "^4.5.0",
     "lodash.isequalwith": "^4.4.0",
-    "rxjs": "^7.2.0"
+    "rxjs": "^7.2.0",
+    "semver": "^7.3.5"
   },
   "devDependencies": {
     "@commitlint/cli": "12.1.4",
@@ -71,6 +72,7 @@
     "@types/lodash.isequalwith": "^4.4.6",
     "@types/mocha": "8.2.2",
     "@types/node": "15.12.2",
+    "@types/semver": "^7.3.8",
     "chai": "4.3.4",
     "cross-env": "7.0.3",
     "ethereum-waffle": "3.4.0",
diff --git a/src/logic/editable-storage-logic.ts b/src/logic/editable-storage-logic.ts
index e44ba0b..3353f2d 100644
--- a/src/logic/editable-storage-logic.ts
+++ b/src/logic/editable-storage-logic.ts
@@ -1,5 +1,6 @@
 import { SmockVMManager } from '../types';
-import { convertToStorageSlots, fromHexString, toFancyAddress } from '../utils';
+import { fromHexString, toFancyAddress } from '../utils';
+import { computeStorageSlots } from '../utils/storage';
 
 export class EditableStorageLogic {
   private storageLayout: any;
@@ -15,14 +16,10 @@ export class EditableStorageLogic {
   async setVariable(variableName: string, value: any) {
     if (value === undefined || value === null) return;
 
-    const slots = convertToStorageSlots(this.storageLayout, variableName, value);
+    const slots = computeStorageSlots(this.storageLayout, { [variableName]: value });
 
     for (const slot of slots) {
-      await this.vmManager.putContractStorage(
-        toFancyAddress(this.contractAddress),
-        fromHexString(slot.hash.toLowerCase()),
-        fromHexString(slot.value)
-      );
+      await this.vmManager.putContractStorage(toFancyAddress(this.contractAddress), fromHexString(slot.key), fromHexString(slot.val));
     }
   }
 }
diff --git a/src/utils/hex-utils.ts b/src/utils/hex-utils.ts
index 0c31046..e36e593 100644
--- a/src/utils/hex-utils.ts
+++ b/src/utils/hex-utils.ts
@@ -56,3 +56,48 @@ export const remove0x = (str: string): string => {
   }
   return str.startsWith('0x') ? str.slice(2) : str;
 };
+
+/**
+ * Turn big numbers into hex (positive and negative)
+ * Source: https://coolaj86.com/articles/convert-decimal-to-hex-with-js-bigints/
+ * @param bn big number to parse
+ * @returns hex representation of the big number
+ */
+export function bigNumberToHex(bn: BigNumber) {
+  let bi = BigInt(bn.toBigInt());
+
+  var pos = true;
+  if (bi < 0) {
+    pos = false;
+    bi = bitnot(bi);
+  }
+
+  var hex = bi.toString(16);
+  if (hex.length % 2) {
+    hex = '0' + hex;
+  }
+
+  if (pos && 0x80 & parseInt(hex.slice(0, 2), 16)) {
+    hex = '00' + hex;
+  }
+
+  return hex;
+}
+
+function bitnot(bi: BigInt) {
+  var bin = (-bi).toString(2);
+  var prefix = '';
+  while (bin.length % 8) {
+    bin = '0' + bin;
+  }
+  if ('1' === bin[0] && -1 !== bin.slice(1).indexOf('1')) {
+    prefix = '11111111';
+  }
+  bin = bin
+    .split('')
+    .map(function (i) {
+      return '0' === i ? '1' : '0';
+    })
+    .join('');
+  return BigInt('0b' + prefix + bin) + BigInt(1);
+}
diff --git a/src/utils/storage.ts b/src/utils/storage.ts
index 941d36a..bb03607 100644
--- a/src/utils/storage.ts
+++ b/src/utils/storage.ts
@@ -1,113 +1,375 @@
-import hre, { ethers } from 'hardhat';
-import { Artifacts } from 'hardhat/internal/artifacts';
-import { remove0x, toHexString32 } from './hex-utils';
-import { flatten } from './misc';
+import { BigNumber, ethers } from 'ethers';
+import { artifacts } from 'hardhat';
+import semver from 'semver';
+import { bigNumberToHex, fromHexString, remove0x } from './hex-utils';
 
-interface InputSlot {
+// Represents the JSON objects outputted by the Solidity compiler that describe the structure of
+// state within the contract. See
+// https://docs.soliditylang.org/en/v0.8.3/internals/layout_in_storage.html for more information.
+interface SolidityStorageObj {
+  astId: number;
+  contract: string;
   label: string;
-  slot: number;
+  offset: number;
+  slot: string;
+  type: string;
 }
 
-interface StorageSlot {
+// Represents the JSON objects outputted by the Solidity compiler that describe the types used for
+// the various pieces of state in the contract. See
+// https://docs.soliditylang.org/en/v0.8.3/internals/layout_in_storage.html for more information.
+interface SolidityStorageType {
+  encoding: string;
   label: string;
-  hash: string;
-  value: string;
+  numberOfBytes: string;
+  key?: string;
+  value?: string;
+  base?: string;
+  members?: SolidityStorageObj[];
+}
+
+// Container object returned by the Solidity compiler. See
+// https://docs.soliditylang.org/en/v0.8.3/internals/layout_in_storage.html for more information.
+export interface SolidityStorageLayout {
+  storage: SolidityStorageObj[];
+  types: {
+    [name: string]: SolidityStorageType;
+  };
+}
+
+interface StorageSlotPair {
+  key: string;
+  val: string;
 }
 
 /**
- * Reads the storage layout of a contract.
+ * Retrieves the storageLayout portion of the compiler artifact for a given contract by name. This
+ * function is hardhat specific.
  *
- * @param name Name of the contract to get a storage layout for.
- * @return Storage layout for the given contract name.
+ * @param hre HardhatRuntimeEnvironment, required for the readArtifactSync function.
+ * @param name Name of the contract to retrieve the storage layout for.
+ * @return Storage layout object from the compiler output.
  */
-export const getStorageLayout = async (name: string): Promise<any> => {
-  const artifacts = new Artifacts(hre.config.paths.artifacts);
-  const { sourceName, contractName } = artifacts.readArtifactSync(name);
-  const buildInfo = await hre.artifacts.getBuildInfo(`${sourceName}:${contractName}`);
-  if (!buildInfo) {
-    throw new Error(`Failed to find contract ${sourceName}:${contractName}`);
-  }
+export async function getStorageLayout(name: string): Promise<SolidityStorageLayout> {
+  const { sourceName, contractName } = await artifacts.readArtifactSync(name);
+  const buildInfo = await artifacts.getBuildInfo(`${sourceName}:${contractName}`);
+  if (!buildInfo) throw new Error(`Build info not found for contract ${sourceName}:${contractName}`);
 
   const output = buildInfo.output.contracts[sourceName][contractName];
+
+  if (!semver.satisfies(buildInfo.solcVersion, '>=0.4.x <0.9.x')) {
+    throw new Error(`Storage layout for Solidity version ${buildInfo.solcVersion} not yet supported. Sorry!`);
+  }
+
   if (!('storageLayout' in output)) {
     throw new Error(
-      `Storage layout for ${name} not found. Did you forget to set the storage layout compiler option in your hardhat config? Read more: https://github.com/ethereum-optimism/smock#note-on-using-smoddit`
+      `Storage layout for ${name} not found. Did you forget to set the storage layout compiler option in your hardhat config? Read more: https://smock.readthedocs.io/en/latest/getting-started.html#enabling-mocks`
     );
   }
 
   return (output as any).storageLayout;
-};
+}
 
 /**
- * Converts storage into a list of storage slots.
+ * Computes the key/value storage slot pairs that would be used if a given set of variable values
+ * were applied to a given contract.
  *
- * @param storageLayout Contract storage layout.
- * @param obj Storage object to convert.
- * @returns List of storage slots.
+ * @param storageLayout Solidity storage layout to use as a template for determining storage slots.
+ * @param variables Variable values to apply against the given storage layout.
+ * @returns An array of key/value storage slot pairs that would result in the desired state.
  */
-export const convertToStorageSlots = (storageLayout: any, variableName: string, value: any): StorageSlot[] => {
-  const slots: StorageSlot[] = [];
+export function computeStorageSlots(storageLayout: SolidityStorageLayout, variables: any = {}): Array<StorageSlotPair> {
+  let slots: StorageSlotPair[] = [];
+  for (const [variableName, variableValue] of Object.entries(variables)) {
+    // Find the entry in the storage layout that corresponds to this variable name.
+    const storageObj = storageLayout.storage.find((entry) => {
+      return entry.label === variableName;
+    });
 
-  const variableDef = storageLayout.storage.find((vDef: any) => vDef.label === variableName);
-  if (!variableDef) throw new Error(`Could not find a matching variable definition for ${variableName}`);
+    // Complain very loudly if attempting to set a variable that doesn't exist within this layout.
+    if (!storageObj) {
+      throw new Error(`Variable name not found in storage layout: ${variableName}`);
+    }
 
-  const baseSlot = parseInt(variableDef.slot, 10);
-  const baseDepth = (variableDef.type.match(/t_mapping/g) || []).length;
+    // Encode this variable as series of storage slot key/value pairs and save it.
+    slots = slots.concat(encodeVariable(variableValue, storageObj, storageLayout.types));
+  }
 
-  Object.entries(flatten({ [variableName]: value })).forEach(([key, value]) => {
-    const path = key.split('.');
+  // Dealing with packed storage slots now. We know that a storage slot is packed when two storage
+  // slots produced by the above encoding have the same key. In this case, we want to merge the two
+  // values into a single bytes32 value. We'll throw an error if the two values overlap (have some
+  // byte where both values are non-zero).
+  slots = slots.reduce((prevSlots: StorageSlotPair[], slot) => {
+    // Find some previous slot where we have the same key.
+    const prevSlot = prevSlots.find((otherSlot) => {
+      return otherSlot.key === slot.key;
+    });
 
-    const slotLabel = path.length > 1 + baseDepth ? path[path.length - 1] : 'default';
+    if (prevSlot === undefined) {
+      // Slot doesn't share a key with any other slot so we can just push it and continue.
+      prevSlots.push(slot);
+    } else {
+      // Slot shares a key with some previous slot.
+      // First, we remove the previous slot from the list of slots since we'll be modifying it.
+      prevSlots = prevSlots.filter((otherSlot) => {
+        return otherSlot.key !== prevSlot.key;
+      });
 
-    const inputSlot = getInputSlots(storageLayout, variableDef.type).find((iSlot) => iSlot.label === slotLabel);
-    if (!inputSlot) throw new Error(`Could not find a matching slot definition for ${slotLabel}`);
+      // Now we'll generate a merged value by taking the non-zero bytes from both values. There's
+      // probably a more efficient way to do this, but this is relatively easy and straightforward.
+      let mergedVal = '0x';
+      const valA = remove0x(slot.val);
+      const valB = remove0x(prevSlot.val);
+      for (let i = 0; i < 64; i += 2) {
+        const byteA = valA.slice(i, i + 2);
+        const byteB = valB.slice(i, i + 2);
 
-    let slotHash = toHexString32(baseSlot);
-    for (let i = 0; i < baseDepth; i++) {
-      slotHash = ethers.utils.keccak256(toHexString32(path[i + 1]) + remove0x(slotHash));
-    }
+        if (byteA === '00' && byteB === '00') {
+          mergedVal += '00';
+        } else if (byteA === '00' && byteB !== '00') {
+          mergedVal += byteB;
+        } else if (byteA !== '00' && byteB === '00') {
+          mergedVal += byteA;
+        } else {
+          // Should never happen, means our encoding is broken. Values should *never* overlap.
+          throw new Error('detected badly encoded packed value, should not happen');
+        }
+      }
 
-    slotHash = toHexString32(ethers.BigNumber.from(slotHash).add(inputSlot.slot));
+      prevSlots.push({
+        key: slot.key,
+        val: mergedVal,
+      });
+    }
 
-    slots.push({
-      label: key,
-      hash: slotHash,
-      value: toHexString32(value),
-    });
-  });
+    return prevSlots;
+  }, []);
 
   return slots;
-};
+}
 
 /**
- * Gets the slot positions for a provided variable type.
+ * Takes number slot value (in hex), left-pads it with zeros or f (when negative),
+ * and displaces it by a given offset.
  *
- * @param storageLayout Contract's storage layout.
- * @param inputTypeName Variable type name.
- * @returns Slot positions.
+ * @param val Number to pad.
+ * @param offset Number of bytes to offset from the right.
+ * @return Padded hex string.
  */
-const getInputSlots = (storageLayout: any, inputTypeName: string): InputSlot[] => {
-  const inputType = storageLayout.types[inputTypeName];
-
-  if (inputType.encoding === 'mapping') {
-    return getInputSlots(storageLayout, inputType.value);
-  } else if (inputType.encoding === 'inplace') {
-    if (inputType.members) {
-      return inputType.members.map((member: any) => {
-        return {
-          label: member.label,
-          slot: member.slot,
-        };
-      });
-    } else {
+function padNumHexSlotValue(val: any, offset: number): string {
+  const bn = BigNumber.from(val);
+
+  return (
+    '0x' +
+    bigNumberToHex(bn)
+      .padStart(64 - offset * 2, bn.isNegative() ? 'f' : '0') // Pad the start with 64 - offset bytes
+      .padEnd(64, '0') // Pad the end (up to 64 bytes) with zero bytes.
+      .toLowerCase() // Making this lower case makes assertions more consistent later.
+  );
+}
+
+/**
+ * Takes bytes slot value (in hex), left-pads it with zeros, and displaces it by a given offset.
+ *
+ * @param val Hex string value to pad.
+ * @param offset Number of bytes to offset from the right.
+ * @return Padded hex string.
+ */
+function padBytesHexSlotValue(val: string, offset: number): string {
+  return (
+    '0x' +
+    remove0x(val)
+      .padStart(64 - offset * 2, '0') // Pad the start with 64 - offset zero bytes.
+      .padEnd(64, '0') // Pad the end (up to 64 bytes) with zero bytes.
+      .toLowerCase() // Making this lower case makes assertions more consistent later.
+  );
+}
+
+/**
+ * Encodes a single variable as a series of key/value storage slot pairs using some storage layout
+ * as instructions for how to perform this encoding. Works recursively with struct types.
+ * ref: https://docs.soliditylang.org/en/v0.8.4/internals/layout_in_storage.html#layout-of-state-variables-in-storage
+ *
+ * @param variable Variable to encode as key/value slot pairs.
+ * @param storageObj Solidity compiler JSON output describing the layout for this
+ * @param storageTypes Full list of storage types allowed for this encoding.
+ * @param nestedSlotOffset Only used for structs. Since slots for struct members are 0-indexed, we
+ * need to be keeping track of the slot offset of the parent struct to figure out the final slot.
+ * See https://docs.soliditylang.org/en/v0.8.6/internals/layout_in_storage.html#mappings-and-dynamic-arrays for additional info.
+ * @param baseSlotKey Only used for maps. Keeps track of the base slot that other elements of the
+ * mapping need to work off of.
+ * @returns Variable encoded as a series of key/value slot pairs.
+ */
+function encodeVariable(
+  variable: any,
+  storageObj: SolidityStorageObj,
+  storageTypes: {
+    [name: string]: SolidityStorageType;
+  },
+  nestedSlotOffset = 0,
+  baseSlotKey?: string
+): StorageSlotPair[] {
+  let slotKey: string =
+    '0x' +
+    remove0x(
+      BigNumber.from(baseSlotKey || nestedSlotOffset)
+        .add(BigNumber.from(parseInt(storageObj.slot, 10)))
+        .toHexString()
+    ).padStart(64, '0');
+
+  const variableType = storageTypes[storageObj.type];
+  if (variableType.encoding === 'inplace') {
+    if (variableType.label === 'address' || variableType.label.startsWith('contract')) {
+      if (!ethers.utils.isAddress(variable)) {
+        throw new Error(`invalid address type: ${variable}`);
+      }
+
+      return [
+        {
+          key: slotKey,
+          val: padNumHexSlotValue(variable, storageObj.offset),
+        },
+      ];
+    } else if (variableType.label === 'bool') {
+      // Do some light parsing here to make sure "true" and "false" are recognized.
+      if (typeof variable === 'string') {
+        if (variable === 'false') {
+          variable = false;
+        }
+        if (variable === 'true') {
+          variable = true;
+        }
+      }
+
+      if (typeof variable !== 'boolean') {
+        throw new Error(`invalid bool type: ${variable}`);
+      }
+
+      return [
+        {
+          key: slotKey,
+          val: padNumHexSlotValue(variable ? '1' : '0', storageObj.offset),
+        },
+      ];
+    } else if (variableType.label.startsWith('bytes')) {
+      if (!ethers.utils.isHexString(variable, parseInt(variableType.numberOfBytes, 10))) {
+        throw new Error(`invalid bytesN type`);
+      }
+
+      return [
+        {
+          key: slotKey,
+          val: padBytesHexSlotValue(remove0x(variable).padEnd(parseInt(variableType.numberOfBytes, 10) * 2, '0'), storageObj.offset),
+        },
+      ];
+    } else if (variableType.label.startsWith('uint') || variableType.label.startsWith('int')) {
+      if (remove0x(BigNumber.from(variable).toHexString()).length / 2 > parseInt(variableType.numberOfBytes, 10)) {
+        throw new Error(`provided ${variableType.label} is too big: ${variable}`);
+      }
+
       return [
         {
-          label: 'default',
-          slot: 0,
+          key: slotKey,
+          val: padNumHexSlotValue(variable, storageObj.offset),
         },
       ];
+    } else if (variableType.label.startsWith('struct')) {
+      // Structs are encoded recursively, as defined by their `members` field.
+      let slots: StorageSlotPair[] = [];
+      for (const [varName, varVal] of Object.entries(variable)) {
+        slots = slots.concat(
+          encodeVariable(
+            varVal,
+            (variableType.members as SolidityStorageObj[]).find((member) => {
+              return member.label === varName;
+            }) as SolidityStorageObj,
+            storageTypes,
+            nestedSlotOffset + parseInt(storageObj.slot as any, 10),
+            baseSlotKey
+          )
+        );
+      }
+      return slots;
     }
-  } else {
-    throw new Error(`Encoding type not supported: ${inputType.encoding}`);
+  } else if (variableType.encoding === 'bytes') {
+    if (storageObj.offset !== 0) {
+      // string/bytes types are *not* packed by Solidity.
+      throw new Error(`got offset for string/bytes type, should never happen`);
+    }
+
+    // `string` types are converted to utf8 bytes, `bytes` are left as-is (assuming 0x prefixed).
+    const bytes = storageObj.type === 'string' ? ethers.utils.toUtf8Bytes(variable) : fromHexString(variable);
+
+    // ref: https://docs.soliditylang.org/en/v0.8.4/internals/layout_in_storage.html#bytes-and-string
+    if (bytes.length < 32) {
+      // NOTE: Solidity docs (see above) specifies that strings or bytes with a length of 31 bytes
+      // should be placed into a storage slot where the last byte of the storage slot is the length
+      // of the variable in bytes * 2.
+      return [
+        {
+          key: slotKey,
+          val: ethers.utils.hexlify(
+            ethers.utils.concat([
+              ethers.utils.concat([bytes, ethers.constants.HashZero]).slice(0, 31),
+              ethers.BigNumber.from(bytes.length * 2).toHexString(),
+            ])
+          ),
+        },
+      ];
+    } else {
+      // TODO: add support for large strings or byte arrays
+      throw new Error('large strings (>31 bytes) not supported');
+    }
+  } else if (variableType.encoding === 'mapping') {
+    if (variableType.key === undefined || variableType.value === undefined) {
+      // Should never happen in practice but required to maintain proper typing.
+      throw new Error(`variable is a mapping but has no key field or has no value field: ${variableType}`);
+    }
+
+    let slots: StorageSlotPair[] = [];
+    for (const [varName, varVal] of Object.entries(variable)) {
+      // Mapping keys are encoded depending on the key type.
+      let key: string;
+      if (variableType.key.startsWith('t_uint')) {
+        key = BigNumber.from(varName).toHexString();
+      } else if (variableType.key.startsWith('t_bytes')) {
+        key = '0x' + remove0x(varName).padEnd(64, '0');
+      } else {
+        // Seems to work for everything else.
+        key = varName;
+      }
+
+      // Figure out the base slot key that the mapped values need to work off of.
+      // If baseSlotKey is defined here, then we're inside of a nested mapping and we should work
+      // off of that previous baseSlotKey. Otherwise the base slot will be the slot of this map.
+      const prevBaseSlotKey = baseSlotKey || padNumHexSlotValue(storageObj.slot, 0);
+      const nextBaseSlotKey = ethers.utils.keccak256(padNumHexSlotValue(key, 0) + remove0x(prevBaseSlotKey));
+
+      // Encode the value. We need to use a dummy storageObj here because the function expects it.
+      // Of course, we're not mapping to a specific variable. We map to a variable /type/. So we
+      // need a dummy thing to represent a variable of that type.
+      slots = slots.concat(
+        encodeVariable(
+          varVal,
+          {
+            label: varName,
+            offset: 0,
+            slot: '0',
+            type: variableType.value,
+            astId: 0,
+            contract: '',
+          },
+          storageTypes,
+          nestedSlotOffset + parseInt(storageObj.slot, 10),
+          nextBaseSlotKey
+        )
+      );
+    }
+    return slots;
+  } else if (variableType.encoding === 'dynamic_array') {
+    // TODO: add support for array types
+    throw new Error('array types not yet supported');
   }
-};
+
+  throw new Error(`unknown unsupported type ${variableType.encoding} ${variableType.label}`);
+}
diff --git a/yarn.lock b/yarn.lock
index a3d16e4..b17da20 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1066,6 +1066,11 @@
   dependencies:
     "@types/node" "*"
 
+"@types/semver@^7.3.8":
+  version "7.3.8"
+  resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.8.tgz#508a27995498d7586dcecd77c25e289bfaf90c59"
+  integrity sha512-D/2EJvAlCEtYFEYmmlGwbGXuK886HzyCc3nZX/tkFTQdEU8jZDAgiv08P162yB17y4ZXZoq7yFAnW4GDBb9Now==
+
 "@types/sinon-chai@^3.2.3":
   version "3.2.5"
   resolved "https://registry.yarnpkg.com/@types/sinon-chai/-/sinon-chai-3.2.5.tgz#df21ae57b10757da0b26f512145c065f2ad45c48"


Test patch:
diff --git a/test/contracts/mock/StorageGetter.sol b/test/contracts/mock/StorageGetter.sol
index 7e9524c..4e0b148 100644
--- a/test/contracts/mock/StorageGetter.sol
+++ b/test/contracts/mock/StorageGetter.sol
@@ -14,7 +14,11 @@ struct PackedStruct {
 contract StorageGetter {
   address internal _address;
   uint256 internal _constructorUint256;
+  int56 internal _int56;
+  int256 internal _int256;
   uint256 internal _uint256;
+  bytes internal _bytes;
+  bytes32 internal _bytes32;
   bool internal _bool;
   SimpleStruct internal _simpleStruct;
   PackedStruct internal _packedStruct;
@@ -23,6 +27,7 @@ contract StorageGetter {
   mapping(bytes5 => bool) _bytes5ToBoolMap;
   mapping(address => bool) _addressToBoolMap;
   mapping(address => address) _addressToAddressMap;
+  uint256[] internal _uint256Array;
 
   // Testing storage slot packing.
   bool internal _packedA;
@@ -36,10 +41,26 @@ contract StorageGetter {
     return _constructorUint256;
   }
 
+  function getInt56() public view returns (int56 _out) {
+    return _int56;
+  }
+
+  function getInt256() public view returns (int256 _out) {
+    return _int256;
+  }
+
   function getUint256() public view returns (uint256 _out) {
     return _uint256;
   }
 
+  function getBytes() public view returns (bytes memory _out) {
+    return _bytes;
+  }
+
+  function getBytes32() public view returns (bytes32 _out) {
+    return _bytes32;
+  }
+
   function setPackedA(bool _val) external {
     _packedA = _val;
   }
@@ -64,6 +85,10 @@ contract StorageGetter {
     return _simpleStruct;
   }
 
+  function getPackedStruct() public view returns (PackedStruct memory _out) {
+    return _packedStruct;
+  }
+
   function getUint256MapValue(uint256 _key) public view returns (uint256 _out) {
     return _uint256Map[_key];
   }
@@ -84,6 +109,10 @@ contract StorageGetter {
     return _addressToAddressMap[_key];
   }
 
+  function getUint256Array() public view returns (uint256[] memory _out) {
+    return _uint256Array;
+  }
+
   function getPackedAddress() public view returns (address) {
     return _packedB;
   }
diff --git a/test/unit/mock/editable-storage-logic.spec.ts b/test/unit/mock/editable-storage-logic.spec.ts
index 36ce28e..613dd3d 100644
--- a/test/unit/mock/editable-storage-logic.spec.ts
+++ b/test/unit/mock/editable-storage-logic.spec.ts
@@ -1,6 +1,6 @@
 import { MockContract, MockContractFactory, smock } from '@src';
 import { convertStructToPojo } from '@src/utils';
-import { ADDRESS_EXAMPLE } from '@test-utils';
+import { ADDRESS_EXAMPLE, BYTES32_EXAMPLE, BYTES_EXAMPLE } from '@test-utils';
 import { StorageGetter, StorageGetter__factory } from '@typechained';
 import { expect } from 'chai';
 import { BigNumber, utils } from 'ethers';
@@ -23,31 +23,52 @@ describe('Mock: Editable storage logic', () => {
     expect(await mock.getUint256()).to.equal(value);
   });
 
+  it('should be able to set a int56', async () => {
+    const value = -1;
+    await mock.setVariable('_int56', value);
+    expect(await mock.getInt56()).to.equal(value);
+  });
+
+  it('should be able to set a int256', async () => {
+    const value = utils.parseUnits('-1');
+    await mock.setVariable('_int256', value);
+    expect(await mock.getInt256()).to.equal(value);
+  });
+
   it('should be able to set a boolean', async () => {
     await mock.setVariable('_bool', true);
     expect(await mock.getBool()).to.equal(true);
   });
 
+  it.skip('should be able to set bytes', async () => {
+    await mock.setVariable('_bytes', BYTES_EXAMPLE);
+    expect(await mock.getBytes()).to.equal(BYTES_EXAMPLE);
+  });
+
+  it('should be able to set bytes32', async () => {
+    await mock.setVariable('_bytes32', BYTES32_EXAMPLE);
+    expect(await mock.getBytes32()).to.equal(BYTES32_EXAMPLE);
+  });
+
   it('should be able to set an address', async () => {
     await mock.setVariable('_address', ADDRESS_EXAMPLE);
-
     expect(await mock.getAddress()).to.equal(ADDRESS_EXAMPLE);
   });
 
-  it.skip('should be able to set an address in a packed storage slot', async () => {
+  it('should be able to set an address in a packed storage slot', async () => {
     await mock.setVariable('_packedB', ADDRESS_EXAMPLE);
 
     expect(await mock.getPackedAddress()).to.equal(ADDRESS_EXAMPLE);
   });
 
-  it.skip('should be able to set an address in a packed struct', async () => {
+  it('should be able to set an address in a packed struct', async () => {
     const struct = {
       packedA: true,
       packedB: ADDRESS_EXAMPLE,
     };
     await mock.setVariable('_packedStruct', struct);
 
-    expect(convertStructToPojo(await mock.getPackedAddress())).to.equal(struct);
+    expect(convertStructToPojo(await mock.getPackedStruct())).to.deep.equal(struct);
   });
 
   it('should be able to set a simple struct', async () => {
@@ -117,4 +138,9 @@ describe('Mock: Editable storage logic', () => {
 
     expect(await mock.getAddressToAddressMapValue(mapKey)).to.equal(mapValue);
   });
+
+  it.skip('should be able to set a uint256[] variable', async () => {
+    await mock.setVariable('_uint256Array', [1, 2]);
+    expect(await mock.getUint256Array()).to.equal([1, 2]);
+  });
 });