bguiz
bguiz

Reputation: 28587

How to do "mainnet forking" of the RSK network in hardhat?

Here's the hardhat.config.js, as per the reference: hardhat mainnet forking

module.exports = {
  solidity: '0.8.9',
  networks: {
    hardhat: {
      // Ref: https://hardhat.org/hardhat-network/guides/mainnet-forking.html
      forking: {
        url: 'https://public-node.rsk.co/',
        blockNumber: 4e6,
      },
    },
  },
};

When running npx hardhat test, the following error/ stacktrace is output:

  Main
    1) "before all" hook in "Main"


  0 passing (4s)
  1 failing

  1) Main
       "before all" hook in "Main":
     InvalidResponseError: Invalid JSON-RPC response's result.

Errors: Invalid value null supplied to : RpcBlockWithTransactions | null/transactions: RpcTransaction Array/1: RpcTransaction/v: QUANTITY, Invalid value null supplied to : RpcBlockWithTransactions | null/transactions: RpcTransaction Array/1: RpcTransaction/r: QUANTITY, Invalid value null supplied to : RpcBlockWithTransactions | null/transactions: RpcTransaction Array/1: RpcTransaction/s: QUANTITY
      at decodeJsonRpcResponse (node_modules/hardhat/src/internal/core/jsonrpc/types/output/decodeJsonRpcResponse.ts:15:11)
      at JsonRpcClient._perform (node_modules/hardhat/src/internal/hardhat-network/jsonrpc/client.ts:277:48)
      at processTicksAndRejections (internal/process/task_queues.js:95:5)
      at ForkBlockchain._getBlockByNumber (node_modules/hardhat/src/internal/hardhat-network/provider/fork/ForkBlockchain.ts:236:22)
      at ForkBlockchain.getBlock (node_modules/hardhat/src/internal/hardhat-network/provider/fork/ForkBlockchain.ts:66:13)
      at ForkBlockchain.getLatestBlock (node_modules/hardhat/src/internal/hardhat-network/provider/BlockchainBase.ts:61:19)
      at Function.create (node_modules/hardhat/src/internal/hardhat-network/provider/node.ts:196:31)
      at HardhatNetworkProvider._init (node_modules/hardhat/src/internal/hardhat-network/provider/provider.ts:251:28)
      at HardhatNetworkProvider._send (node_modules/hardhat/src/internal/hardhat-network/provider/provider.ts:192:5)
      at HardhatNetworkProvider.request (node_modules/hardhat/src/internal/hardhat-network/provider/provider.ts:118:18)

What can I do to resolve this?

Upvotes: 2

Views: 554

Answers (1)

Rainer Koirikivi
Rainer Koirikivi

Reputation: 751

This is possible, albeit hacky, by patching Hardhat:

Example patch file for hardhat 2.13.0:

diff --git a/node_modules/hardhat/internal/hardhat-network/jsonrpc/client.js b/node_modules/hardhat/internal/hardhat-network/jsonrpc/client.js
index 8f78e83..71474ad 100644
--- a/node_modules/hardhat/internal/hardhat-network/jsonrpc/client.js
+++ b/node_modules/hardhat/internal/hardhat-network/jsonrpc/client.js
@@ -141,6 +145,60 @@ class JsonRpcClient {
             reward: (0, io_ts_1.optional)(t.array(t.array(base_types_1.rpcQuantity))),
         }), (res) => res.oldestBlock + BigInt(res.baseFeePerGas.length));
     }
+    _patchRawResult(method, rawResult) {
+        /**
+         * Patch the raw results returned by _send or _sendBatch to fix RSK/Hardhat compatibility issues.
+         */
+        if (
+            method.startsWith('eth_getBlock') &&
+            rawResult &&
+            rawResult.transactions && rawResult.transactions.length
+        ) {
+            /*
+                * Calling a forked node (hardhat node --fork MY_RSK_NODE_RPC_URL) fails with:
+                * eth_call
+                * Invalid JSON-RPC response's result.
+                * Errors: Invalid value null supplied to : RpcBlockWithTransactions | null/transactions:
+                * RpcTransaction Array/0: RpcTransaction/v: QUANTITY, Invalid value null supplied to :
+                * RpcBlockWithTransactions | null/transactions: RpcTransaction Array/0: RpcTransaction/r:
+                * QUANTITY, Invalid value null supplied to : RpcBlockWithTransactions | null/transactions:
+                * RpcTransaction Array/0: RpcTransaction/s: QUANTITY
+                *
+                * This patch is based on:
+                * https://gist.github.com/0x0scion/0422f9135bc37642ba36d55b59e8b424
+                *
+                * More reading:
+                * https://github.com/NomicFoundation/hardhat/issues/2395
+                * https://github.com/NomicFoundation/hardhat/pull/2313/files
+                * https://github.com/NomicFoundation/hardhat/issues/2106
+                */
+            rawResult.transactions.forEach((t) => {
+                // Accesslist is sometimes missing, for other networks and maybe for RSK too
+                if (!t.accessList) t.accessList = [];
+                // Some RSK txs have null vrs
+                //       from: '0x0000000000000000000000000000000000000000',
+                //       to: '0x0000000000000000000000000000000001000008',
+                //       gas: '0x0',
+                //       gasPrice: '0x0',
+                //       value: '0x0',
+                //       input: '0x',
+                //       v: null,
+                //       r: null,
+                //       s: null
+                if (!t.v) t.v = '0x0';
+                if (!t.r) t.r = '0x0';
+                if (!t.s) t.s = '0x0';
+            });
+        } else if (method === 'eth_getStorageAt') {
+            /*
+                * This fixes the error in eth_getStorageAt that says 0x0 is an invalid value for DATA
+                */
+            if (rawResult === '0x0') {
+                rawResult = '0x0000000000000000000000000000000000000000000000000000000000000000';
+            }
+        }
+        return rawResult;
+    }
     async getLatestBlockNumber() {
         return this._perform("eth_blockNumber", [], base_types_1.rpcQuantity, (blockNumber) => blockNumber);
     }
@@ -157,7 +215,8 @@ class JsonRpcClient {
                 return diskCachedResult;
             }
         }
-        const rawResult = await this._send(method, params);
+        let rawResult = await this._send(method, params);
+        rawResult = this._patchRawResult(method, rawResult);
         const decodedResult = (0, decodeJsonRpcResponse_1.decodeJsonRpcResponse)(rawResult, tType);
         const blockNumber = getMaxAffectedBlockNumber(decodedResult);
         if (this._canBeCached(blockNumber)) {
@@ -186,7 +245,13 @@ class JsonRpcClient {
             }
         }
         const rawResults = await this._sendBatch(batch);
-        const decodedResults = rawResults.map((result, i) => (0, decodeJsonRpcResponse_1.decodeJsonRpcResponse)(result, batch[i].tType));
+        const decodedResults = rawResults.map((result, i) => {
+            result = this._patchRawResult(batch[i].method, result);
+            return (0, decodeJsonRpcResponse_1.decodeJsonRpcResponse)(
+                result,
+                batch[i].tType
+            );
+        });
         const blockNumber = getMaxAffectedBlockNumber(decodedResults);
         if (this._canBeCached(blockNumber)) {
             this._storeInCache(cacheKey, decodedResults);

The patch overrides certain non-standard values returned by the RSKJ JSON RPC. More specifically, it overrides null values for vrs with 0x0 for REMASC transactios in eth_getBlock, and 0x0 return value for eth_getStorageAt with 0x0000000000000000000000000000000000000000000000000000000000000000. It's very hacky, but has worked well in practice.

Source: https://github.com/DistributedCollective/Sovryn-smart-contracts/blob/f5da0e2/patches/hardhat%2B2.13.0.dev.patch (Apache licensed)

More information: https://github.com/DistributedCollective/Sovryn-smart-contracts/blob/e8e0be0/HARDHAT_FORKING.md

Upvotes: 1

Related Questions