mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-02-02 00:02:49 +00:00
Send mobymask p2p messages to laconicd (#339)
* Use laconic ETH RPC endpoint for querying * Run peer with message handler to send tx to laconic * Handle revoke messages in mobymask p2p * Set tx gasLimit explicitly for slow eth_estimateGas call * Convert delegationHash to hex string before broadcasting json
This commit is contained in:
parent
787991c432
commit
c44eff36b4
@ -15,3 +15,4 @@ export * from './job-runner';
|
|||||||
export * from './index-block';
|
export * from './index-block';
|
||||||
export * from './fill';
|
export * from './fill';
|
||||||
export * from './peer';
|
export * from './peer';
|
||||||
|
export * from './utils';
|
||||||
|
@ -158,7 +158,7 @@ export class Indexer implements IndexerInterface {
|
|||||||
if (this._serverMode === ETH_CALL_MODE) {
|
if (this._serverMode === ETH_CALL_MODE) {
|
||||||
const contract = new ethers.Contract(token, this._abi, this._ethProvider);
|
const contract = new ethers.Contract(token, this._abi, this._ethProvider);
|
||||||
|
|
||||||
// eth_call doesnt support calling method by blockHash https://eth.wiki/json-rpc/API#the-default-block-parameter
|
// eth_call doesn't support calling method by blockHash https://eth.wiki/json-rpc/API#the-default-block-parameter
|
||||||
const value = await contract.balanceOf(owner, { blockTag: blockHash });
|
const value = await contract.balanceOf(owner, { blockTag: blockHash });
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
|
@ -24,7 +24,8 @@
|
|||||||
"import-state:dev": "DEBUG=vulcanize:* ts-node src/cli/import-state.ts",
|
"import-state:dev": "DEBUG=vulcanize:* ts-node src/cli/import-state.ts",
|
||||||
"inspect-cid": "DEBUG=vulcanize:* ts-node src/cli/inspect-cid.ts",
|
"inspect-cid": "DEBUG=vulcanize:* ts-node src/cli/inspect-cid.ts",
|
||||||
"index-block": "DEBUG=vulcanize:* ts-node src/cli/index-block.ts",
|
"index-block": "DEBUG=vulcanize:* ts-node src/cli/index-block.ts",
|
||||||
"peer": "DEBUG='vulcanize:*, laconic:*' node --enable-source-maps dist/cli/peer.js"
|
"peer": "DEBUG='vulcanize:*, laconic:*' node --enable-source-maps dist/cli/peer.js",
|
||||||
|
"peer-listener": "DEBUG='vulcanize:*, laconic:*' node --enable-source-maps dist/peer-listener.js"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -42,6 +43,7 @@
|
|||||||
"@cerc-io/ipld-eth-client": "^0.2.30",
|
"@cerc-io/ipld-eth-client": "^0.2.30",
|
||||||
"@cerc-io/solidity-mapper": "^0.2.30",
|
"@cerc-io/solidity-mapper": "^0.2.30",
|
||||||
"@cerc-io/util": "^0.2.30",
|
"@cerc-io/util": "^0.2.30",
|
||||||
|
"@cerc-io/peer": "^0.2.30",
|
||||||
"@ethersproject/providers": "^5.4.4",
|
"@ethersproject/providers": "^5.4.4",
|
||||||
"apollo-type-bigint": "^0.1.3",
|
"apollo-type-bigint": "^0.1.3",
|
||||||
"debug": "^4.3.1",
|
"debug": "^4.3.1",
|
||||||
|
@ -11,7 +11,7 @@ import { ethers } from 'ethers';
|
|||||||
import { JsonFragment } from '@ethersproject/abi';
|
import { JsonFragment } from '@ethersproject/abi';
|
||||||
import { JsonRpcProvider } from '@ethersproject/providers';
|
import { JsonRpcProvider } from '@ethersproject/providers';
|
||||||
import { EthClient } from '@cerc-io/ipld-eth-client';
|
import { EthClient } from '@cerc-io/ipld-eth-client';
|
||||||
import { MappingKey, StorageLayout } from '@cerc-io/solidity-mapper';
|
import { MappingKey, StorageLayout, getStorageValue } from '@cerc-io/solidity-mapper';
|
||||||
import {
|
import {
|
||||||
Indexer as BaseIndexer,
|
Indexer as BaseIndexer,
|
||||||
IndexerInterface,
|
IndexerInterface,
|
||||||
@ -270,11 +270,14 @@ export class Indexer implements IndexerInterface {
|
|||||||
defaultValue: any
|
defaultValue: any
|
||||||
): Promise<Entity> {
|
): Promise<Entity> {
|
||||||
const [{ number }, syncStatus] = await Promise.all([
|
const [{ number }, syncStatus] = await Promise.all([
|
||||||
this._ethProvider.send('eth_getHeaderByHash', [blockHash]),
|
// Laconicd doesn't support eth_getHeaderByHash
|
||||||
|
// this._ethProvider.send('eth_getHeaderByHash', [blockHash]),
|
||||||
|
this._ethProvider.getBlock(blockHash),
|
||||||
this.getSyncStatus()
|
this.getSyncStatus()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const blockNumber = ethers.BigNumber.from(number).toNumber();
|
// const blockNumber = ethers.BigNumber.from(number).toNumber();
|
||||||
|
const blockNumber = number;
|
||||||
|
|
||||||
let result: ValueResult = {
|
let result: ValueResult = {
|
||||||
value: defaultValue
|
value: defaultValue
|
||||||
@ -294,7 +297,17 @@ export class Indexer implements IndexerInterface {
|
|||||||
const storageLayout = this._storageLayoutMap.get(KIND_PHISHERREGISTRY);
|
const storageLayout = this._storageLayoutMap.get(KIND_PHISHERREGISTRY);
|
||||||
assert(storageLayout);
|
assert(storageLayout);
|
||||||
|
|
||||||
result = await this._baseIndexer.getStorageValue(
|
// Get storage value using ipld-eth-server
|
||||||
|
// result = await this._baseIndexer.getStorageValue(
|
||||||
|
// storageLayout,
|
||||||
|
// blockHash,
|
||||||
|
// contractAddress,
|
||||||
|
// storageVariableName,
|
||||||
|
// ...Object.values(mappingKeys)
|
||||||
|
// );
|
||||||
|
|
||||||
|
// Get storage value using ETH RPC endpoint
|
||||||
|
result = await this._getStorageValueRPC(
|
||||||
storageLayout,
|
storageLayout,
|
||||||
blockHash,
|
blockHash,
|
||||||
contractAddress,
|
contractAddress,
|
||||||
@ -313,6 +326,30 @@ export class Indexer implements IndexerInterface {
|
|||||||
} as any;
|
} as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _getStorageValueRPC (storageLayout: StorageLayout, blockHash: string, contractAddress: string, variable: string, ...mappingKeys: MappingKey[]): Promise<ValueResult> {
|
||||||
|
const getStorageAt = async (params: { blockHash: string, contract: string, slot: string }) => {
|
||||||
|
const { blockHash, contract, slot } = params;
|
||||||
|
const value = await this._ethProvider.getStorageAt(contract, slot, blockHash);
|
||||||
|
|
||||||
|
return {
|
||||||
|
value,
|
||||||
|
proof: {
|
||||||
|
// Returning null value as proof, since ethers library getStorageAt method doesn't return proof.
|
||||||
|
data: JSON.stringify(null)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return getStorageValue(
|
||||||
|
storageLayout,
|
||||||
|
getStorageAt,
|
||||||
|
blockHash,
|
||||||
|
contractAddress,
|
||||||
|
variable,
|
||||||
|
...mappingKeys
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async getStorageValue (storageLayout: StorageLayout, blockHash: string, contractAddress: string, variable: string, ...mappingKeys: MappingKey[]): Promise<ValueResult> {
|
async getStorageValue (storageLayout: StorageLayout, blockHash: string, contractAddress: string, variable: string, ...mappingKeys: MappingKey[]): Promise<ValueResult> {
|
||||||
return this._baseIndexer.getStorageValue(
|
return this._baseIndexer.getStorageValue(
|
||||||
storageLayout,
|
storageLayout,
|
||||||
@ -608,11 +645,19 @@ export class Indexer implements IndexerInterface {
|
|||||||
return this._baseIndexer.getAncestorAtDepth(blockHash, depth);
|
return this._baseIndexer.getAncestorAtDepth(blockHash, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get latest block using eth client.
|
// Get latest block using eth provider.
|
||||||
async getLatestBlock (): Promise<BlockHeight> {
|
async getLatestBlock (): Promise<BlockHeight> {
|
||||||
const { block } = await this._ethClient.getBlockByHash();
|
// Use ipld-eth-server
|
||||||
|
// const { block } = await this._ethClient.getBlockByHash();
|
||||||
|
|
||||||
return block;
|
// Use ETH RPC endpoint
|
||||||
|
const number = await this._ethProvider.getBlockNumber();
|
||||||
|
const { hash } = await this._ethProvider.getBlock(number);
|
||||||
|
|
||||||
|
return {
|
||||||
|
number,
|
||||||
|
hash
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get full transaction data.
|
// Get full transaction data.
|
||||||
|
@ -3,10 +3,13 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import { ethers } from 'ethers';
|
import { ethers, Signer } from 'ethers';
|
||||||
|
import { TransactionReceipt, TransactionResponse } from '@ethersproject/providers';
|
||||||
|
|
||||||
import { abi as PhisherRegistryABI } from './artifacts/PhisherRegistry.json';
|
import { abi as PhisherRegistryABI } from './artifacts/PhisherRegistry.json';
|
||||||
|
|
||||||
|
const log = debug('laconic:libp2p-utils');
|
||||||
|
|
||||||
const contractInterface = new ethers.utils.Interface(PhisherRegistryABI);
|
const contractInterface = new ethers.utils.Interface(PhisherRegistryABI);
|
||||||
|
|
||||||
const MESSAGE_KINDS = {
|
const MESSAGE_KINDS = {
|
||||||
@ -14,6 +17,71 @@ const MESSAGE_KINDS = {
|
|||||||
REVOKE: 'revoke'
|
REVOKE: 'revoke'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export async function sendMessageToL2 (
|
||||||
|
signer: Signer,
|
||||||
|
{ contractAddress, gasLimit }: {
|
||||||
|
contractAddress: string,
|
||||||
|
gasLimit: number
|
||||||
|
},
|
||||||
|
data: any
|
||||||
|
): Promise<void> {
|
||||||
|
const { kind, message } = data;
|
||||||
|
const contract = new ethers.Contract(contractAddress, PhisherRegistryABI, signer);
|
||||||
|
let receipt: TransactionReceipt | undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (kind) {
|
||||||
|
case MESSAGE_KINDS.INVOKE: {
|
||||||
|
const signedInvocations = message;
|
||||||
|
|
||||||
|
const transaction: TransactionResponse = await contract.invoke(
|
||||||
|
signedInvocations,
|
||||||
|
// Setting gasLimit as eth_estimateGas call takes too long in L2 chain
|
||||||
|
{ gasLimit }
|
||||||
|
);
|
||||||
|
|
||||||
|
receipt = await transaction.wait();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MESSAGE_KINDS.REVOKE: {
|
||||||
|
const { signedDelegation, signedIntendedRevocation } = message;
|
||||||
|
|
||||||
|
const transaction: TransactionResponse = await contract.revokeDelegation(
|
||||||
|
signedDelegation,
|
||||||
|
signedIntendedRevocation,
|
||||||
|
// Setting gasLimit as eth_estimateGas call takes too long in L2 chain
|
||||||
|
{ gasLimit }
|
||||||
|
);
|
||||||
|
|
||||||
|
receipt = await transaction.wait();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
log(`Handler for libp2p message kind ${kind} not implemented`);
|
||||||
|
log(JSON.stringify(message, null, 2));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (receipt) {
|
||||||
|
log(`Transaction receipt for ${kind} message`, {
|
||||||
|
to: receipt.to,
|
||||||
|
blockNumber: receipt.blockNumber,
|
||||||
|
blockHash: receipt.blockHash,
|
||||||
|
transactionHash: receipt.transactionHash,
|
||||||
|
effectiveGasPrice: receipt.effectiveGasPrice.toString(),
|
||||||
|
gasUsed: receipt.gasUsed.toString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function parseLibp2pMessage (log: debug.Debugger, peerId: string, data: any): void {
|
export function parseLibp2pMessage (log: debug.Debugger, peerId: string, data: any): void {
|
||||||
log('Received a message on mobymask P2P network from peer:', peerId);
|
log('Received a message on mobymask P2P network from peer:', peerId);
|
||||||
const { kind, message } = data;
|
const { kind, message } = data;
|
||||||
@ -57,17 +125,5 @@ function _parseRevocation (log: debug.Debugger, msg: any): void {
|
|||||||
log('Signed delegation:');
|
log('Signed delegation:');
|
||||||
log(JSON.stringify(signedDelegation, null, 2));
|
log(JSON.stringify(signedDelegation, null, 2));
|
||||||
log('Signed intention to revoke:');
|
log('Signed intention to revoke:');
|
||||||
const stringifiedSignedIntendedRevocation = JSON.stringify(
|
log(JSON.stringify(signedIntendedRevocation, null, 2));
|
||||||
signedIntendedRevocation,
|
|
||||||
(key, value) => {
|
|
||||||
if (key === 'delegationHash' && value.type === 'Buffer') {
|
|
||||||
// Show hex value for delegationHash instead of Buffer
|
|
||||||
return ethers.utils.hexlify(Buffer.from(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
},
|
|
||||||
2
|
|
||||||
);
|
|
||||||
log(stringifiedSignedIntendedRevocation);
|
|
||||||
}
|
}
|
||||||
|
105
packages/mobymask-v2-watcher/src/peer-listener.ts
Normal file
105
packages/mobymask-v2-watcher/src/peer-listener.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import debug from 'debug';
|
||||||
|
import { hideBin } from 'yargs/helpers';
|
||||||
|
import yargs from 'yargs';
|
||||||
|
import assert from 'assert';
|
||||||
|
|
||||||
|
import { Config, DEFAULT_CONFIG_PATH, getConfig, initClients } from '@cerc-io/util';
|
||||||
|
import {
|
||||||
|
PeerInitConfig,
|
||||||
|
PeerIdObj
|
||||||
|
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/49721#issuecomment-1319854183
|
||||||
|
} from '@cerc-io/peer';
|
||||||
|
|
||||||
|
import { sendMessageToL2 } from './libp2p-utils';
|
||||||
|
import { readPeerId } from '@cerc-io/cli';
|
||||||
|
import { ethers } from 'ethers';
|
||||||
|
|
||||||
|
const log = debug('vulcanize:peer-listener');
|
||||||
|
|
||||||
|
const DEFAULT_GAS_LIMIT = 500000;
|
||||||
|
|
||||||
|
interface Arguments {
|
||||||
|
configFile: string;
|
||||||
|
privateKey: string;
|
||||||
|
contractAddress: string;
|
||||||
|
gasLimit: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const main = async (): Promise<any> => {
|
||||||
|
const argv = _getArgv();
|
||||||
|
const config: Config = await getConfig(argv.configFile);
|
||||||
|
const { ethProvider } = await initClients(config);
|
||||||
|
|
||||||
|
const p2pConfig = config.server.p2p;
|
||||||
|
const peerConfig = p2pConfig.peer;
|
||||||
|
assert(peerConfig, 'Peer config not set');
|
||||||
|
|
||||||
|
const { Peer } = await import('@cerc-io/peer');
|
||||||
|
|
||||||
|
let peerIdObj: PeerIdObj | undefined;
|
||||||
|
if (peerConfig.peerIdFile) {
|
||||||
|
peerIdObj = readPeerId(peerConfig.peerIdFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
const peer = new Peer(peerConfig.relayMultiaddr, true);
|
||||||
|
|
||||||
|
const peerNodeInit: PeerInitConfig = {
|
||||||
|
pingInterval: peerConfig.pingInterval,
|
||||||
|
pingTimeout: peerConfig.pingTimeout,
|
||||||
|
maxRelayConnections: peerConfig.maxRelayConnections,
|
||||||
|
relayRedialInterval: peerConfig.relayRedialInterval,
|
||||||
|
maxConnections: peerConfig.maxConnections,
|
||||||
|
dialTimeout: peerConfig.dialTimeout,
|
||||||
|
enableDebugInfo: peerConfig.enableDebugInfo
|
||||||
|
};
|
||||||
|
|
||||||
|
await peer.init(peerNodeInit, peerIdObj);
|
||||||
|
const wallet = new ethers.Wallet(argv.privateKey, ethProvider);
|
||||||
|
|
||||||
|
peer.subscribeTopic(peerConfig.pubSubTopic, (peerId, data) => {
|
||||||
|
log('Received a message on mobymask P2P network from peer:', peerId);
|
||||||
|
|
||||||
|
// TODO: throttle message handler
|
||||||
|
sendMessageToL2(wallet, argv, data);
|
||||||
|
});
|
||||||
|
|
||||||
|
log(`Peer ID: ${peer.peerId?.toString()}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _getArgv = (): Arguments => {
|
||||||
|
return yargs(hideBin(process.argv)).parserConfiguration({
|
||||||
|
'parse-numbers': false
|
||||||
|
}).options({
|
||||||
|
configFile: {
|
||||||
|
alias: 'config-file',
|
||||||
|
describe: 'configuration file path (toml)',
|
||||||
|
type: 'string',
|
||||||
|
default: DEFAULT_CONFIG_PATH
|
||||||
|
},
|
||||||
|
privateKey: {
|
||||||
|
alias: 'private-key',
|
||||||
|
demandOption: true,
|
||||||
|
describe: 'Private key of the account to use for eth_call',
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
contractAddress: {
|
||||||
|
alias: 'contract',
|
||||||
|
demandOption: true,
|
||||||
|
describe: 'Address of MobyMask contract',
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
gasLimit: {
|
||||||
|
alias: 'gas-limit',
|
||||||
|
describe: 'Gas limit for eth txs',
|
||||||
|
type: 'number',
|
||||||
|
default: DEFAULT_GAS_LIMIT
|
||||||
|
}
|
||||||
|
// https://github.com/yargs/yargs/blob/main/docs/typescript.md?plain=1#L83
|
||||||
|
}).parseSync();
|
||||||
|
};
|
||||||
|
|
||||||
|
main().then(() => {
|
||||||
|
log('Starting peer...');
|
||||||
|
}).catch(err => {
|
||||||
|
log(err);
|
||||||
|
});
|
@ -5,8 +5,8 @@
|
|||||||
/* Basic Options */
|
/* Basic Options */
|
||||||
// "incremental": true, /* Enable incremental compilation */
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
||||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
"module": "CommonJS", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||||
"lib": ["es2019"], /* Specify library files to be included in the compilation. */
|
"lib": [ "ES5", "ES6", "ES2020" ], /* Specify library files to be included in the compilation. */
|
||||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
// "checkJs": true, /* Report errors in .js files. */
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
||||||
@ -44,7 +44,7 @@
|
|||||||
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||||
|
|
||||||
/* Module Resolution Options */
|
/* Module Resolution Options */
|
||||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
"moduleResolution": "Node16", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||||
|
@ -13,7 +13,7 @@ import isArray from 'lodash/isArray';
|
|||||||
|
|
||||||
import { StorageLayout, GetStorageAt } from '../src';
|
import { StorageLayout, GetStorageAt } from '../src';
|
||||||
|
|
||||||
// storageLayout doesnt exist in type CompilerOutput doesnt.
|
// storageLayout doesn't exist in type CompilerOutput doesn't.
|
||||||
// Extending CompilerOutput type to include storageLayout property.
|
// Extending CompilerOutput type to include storageLayout property.
|
||||||
interface StorageCompilerOutput extends CompilerOutput {
|
interface StorageCompilerOutput extends CompilerOutput {
|
||||||
contracts: {
|
contracts: {
|
||||||
@ -82,7 +82,7 @@ export const getStorageAt: GetStorageAt = async ({ blockHash, contract, slot })
|
|||||||
return {
|
return {
|
||||||
value,
|
value,
|
||||||
proof: {
|
proof: {
|
||||||
// Returning null value as proof, since ethers library getStorageAt method doesnt return proof.
|
// Returning null value as proof, since ethers library getStorageAt method doesn't return proof.
|
||||||
// This function is used in tests to mock the getStorageAt method of ipld-eth-client which returns proof along with value.
|
// This function is used in tests to mock the getStorageAt method of ipld-eth-client which returns proof along with value.
|
||||||
data: JSON.stringify(null)
|
data: JSON.stringify(null)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user