Repository: defi-wonderland/smock (ts)

Issue:
Mocks: support large bytes (> 31 bytes) when using setVariable
**Is your feature request related to a problem? Please describe.**
Currently, an error is thrown when using `setVariable` in a variable of type `bytes` and sending an string larger than 31 bytes

**Describe the solution you'd like**
Bytes larger than 31 bytes do have an special handling in the storage, we need to adapt.

**Additional context**
From [Hardhat Docs](https://docs.soliditylang.org/en/v0.6.8/internals/layout_in_storage.html):
> For byte arrays that store data which is 32 or more bytes long, the main slot stores length * 2 + 1 and the data is stored as usual in keccak256(slot). This means that you can distinguish a short array from a long array by checking if the lowest bit is set: short (not set) and long (set).

There is an skipped test that should be unskipped as soon as this is working:
`Mock: Editable storage logic - should be able to set bytes`

Pull request description:
feat: add support for large bytes > 31 bytes
**Description**
Adds support for large bytes array or string > 31 bytes based on the solidity docs 

- Fixes #30 


Patch:
diff --git a/src/utils/storage.ts b/src/utils/storage.ts
index a25b284..489f61e 100644
--- a/src/utils/storage.ts
+++ b/src/utils/storage.ts
@@ -317,10 +317,28 @@ function encodeVariable(
         },
       ];
     } else {
-      // TODO: add support for large strings or byte arrays
-      throw new Error(
-        'large strings (> 31 bytes) not supported. Follow this issue for more info: https://github.com/defi-wonderland/smock/issues/30'
-      );
+      let slots: StorageSlotPair[] = [];
+      // According to the solidity docs (https://docs.soliditylang.org/en/v0.8.4/internals/layout_in_storage.html#bytes-and-string)
+      // For bytes or strings that store data which is 32 or more bytes long, the main slot p stores length * 2 + 1
+      // and the data is stored as usual in keccak256(p)
+      slots = slots.concat({
+        key: slotKey,
+        val: padNumHexSlotValue(bytes.length * 2 + 1, 0),
+      });
+
+      // Each storage slot has 32 bytes so we make sure to slice the large bytes into 32bytes chunks
+      for (let i = 0; i * 32 < bytes.length; i++) {
+        // We calculate the Storage Slot key based on the solidity docs (see above link)
+        let key = BigNumber.from(ethers.utils.keccak256(slotKey))
+          .add(BigNumber.from(i.toString(16)))
+          .toHexString();
+        slots = slots.concat({
+          key: key,
+          val: ethers.utils.hexlify(ethers.utils.concat([bytes.slice(i * 32, i * 32 + 32), ethers.constants.HashZero]).slice(0, 32)),
+        });
+      }
+
+      return slots;
     }
   } else if (variableType.encoding === 'mapping') {
     if (variableType.key === undefined || variableType.value === undefined) {


Test patch:
diff --git a/test/unit/mock/editable-storage-logic.spec.ts b/test/unit/mock/editable-storage-logic.spec.ts
index aa22cd5..b6d16ae 100644
--- a/test/unit/mock/editable-storage-logic.spec.ts
+++ b/test/unit/mock/editable-storage-logic.spec.ts
@@ -41,7 +41,13 @@ describe('Mock: Editable storage logic', () => {
       expect(await mock.getBool()).to.equal(true);
     });
 
-    it.skip('should be able to set bytes', async () => {
+    it('should be able to set a small value < 31 bytes', async () => {
+      const value = BYTES_EXAMPLE.slice(0, 10);
+      await mock.setVariable('_bytes', value);
+      expect(await mock.getBytes()).to.equal(value);
+    });
+
+    it('should be able to set a large value > 31 bytes', async () => {
       await mock.setVariable('_bytes', BYTES_EXAMPLE);
       expect(await mock.getBytes()).to.equal(BYTES_EXAMPLE);
     });