UNPKG

@xyrusworx/web3-cli

Version:

A command line tool which offers low-level EVM interaction for Web3 developers

157 lines 7.18 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ContractStorage = exports.compareStates = exports.decodeRawMappingStorageData = exports.decodeRawStorageData = void 0; const tslib_1 = require("tslib"); const web3_1 = tslib_1.__importDefault(require("web3")); const ethers_1 = require("ethers"); const utils_1 = require("ethers/lib/utils"); const chalk_1 = tslib_1.__importDefault(require("chalk")); const defaultAbi = new web3_1.default().eth.abi; function _typeId(t) { if (t.label == "address payable") return "address"; else return t.label; } function _decode(type, bytes, offset, numberOfBytes) { const end = 32 - (offset || 0); const start = end - (numberOfBytes || 32); const slicedSlotData = "0x" + web3_1.default.utils.bytesToHex(web3_1.default.utils.hexToBytes(bytes).slice(start, end)).replace(/^0[xX]/, '').padStart(64, '0'); return defaultAbi.decodeParameter(type, slicedSlotData); } async function decodeRawStorageData(variable, fetchSlotCallback) { const slotData = await fetchSlotCallback(variable.slot); if (variable.type.encoding == "inplace") { return _decode(_typeId(variable.type), slotData, +variable.offset, +variable.type.numberOfBytes); } else if (variable.type.encoding == "dynamic_array") { const arrayType = variable.type; const arraySize = +_decode("uint256", slotData, variable.offset, +variable.type.numberOfBytes); const startingPosition = ethers_1.BigNumber.from((0, utils_1.keccak256)(ethers_1.ethers.utils.zeroPad([+variable.slot], 32))); const array = []; for (let i = 0; i < arraySize; i++) { const elementSize = +arrayType.base.numberOfBytes; const elementsPerSlot = Math.floor(32 / elementSize); const slot = startingPosition.add(Math.floor(i / elementsPerSlot)).toHexString(); const itemVariable = { slot, type: arrayType.base, offset: (i % elementsPerSlot) * elementSize }; array.push(await decodeRawStorageData(itemVariable, fetchSlotCallback)); } return array; } else if (variable.type.encoding == "mapping") { return new Mapping(); // mappings can be decoded with decodeRawMappingStorageData } else throw "Unsupported variable type: " + variable.type?.encoding; } exports.decodeRawStorageData = decodeRawStorageData; async function decodeRawMappingStorageData(variable, key, fetchSlotCallback) { const mappingBasis = ethers_1.ethers.utils.zeroPad([+variable.slot], 32); const mappingKey = ethers_1.ethers.utils.zeroPad(key, 32); const storagePosition = ethers_1.BigNumber.from((0, utils_1.keccak256)([...mappingKey, ...mappingBasis])); const itemVariable = { slot: storagePosition.toHexString(), type: variable.type.value, offset: 0 }; return await decodeRawStorageData(itemVariable, fetchSlotCallback); } exports.decodeRawMappingStorageData = decodeRawMappingStorageData; async function compareStates(output, beforeState, afterState) { for (const key in beforeState) { const before = beforeState[key]; const after = afterState[key]; if (!after) { output.log(" ", chalk_1.default.red("[deleted]"), key); continue; } if (Array.isArray(before.value)) { const childBefore = {}; const childAfter = {}; const beforeVariable = { type: before.variable.type.base, offset: before.variable.offset, slot: before.variable.slot, }; const afterVariable = { type: after.variable.type.base, offset: after.variable.offset, slot: after.variable.slot, }; for (let i = 0; i < before.value.length; i++) childBefore[i] = { variable: { ...beforeVariable, name: before.variable.name + "[" + i + "]" }, value: before.value[i] }; for (let i = 0; i < after.value.length; i++) childAfter[i] = { variable: { ...afterVariable, name: after.variable.name + "[" + i + "]" }, value: after.value[i] }; await compareStates(output, childBefore, childAfter); } else if (!before.value._isMapping) { if (before.value != after.value) { output.log(" ", chalk_1.default.yellow("[changed]"), key + ":", before.value, "==>", after.value); } } } for (const key in afterState) { const before = beforeState[key]; const after = afterState[key]; if (!!before) { continue; } if (Array.isArray(after.value)) { for (let i = 0; i < after.value.length; i++) { output.log(" ", chalk_1.default.yellow("[added]"), after.variable.name + "[" + i + "]:", after.value); } } else if (!after.value._isMapping) { if (before.value != after.value) { output.log(" ", chalk_1.default.yellow("[added]"), key + ":", after.value); } } } } exports.compareStates = compareStates; class ContractStorage { constructor(provider, address) { this.provider = provider; this.address = address; this.slotMap = new Map(); this.output = console; if (!ethers_1.ethers.utils.isAddress(address)) { throw "Invalid address: " + address; } } getStorageCacheAt(slot) { const slotBN = ethers_1.BigNumber.from(slot); const slotCacheKey = slotBN.toString(); if (this.slotMap.has(slotCacheKey)) { const cacheResult = this.slotMap.get(slotCacheKey); this.output.debug("Slot", slotBN.toHexString(), "has been read from in-memory cache:", cacheResult); return cacheResult; } return undefined; } setProvider(provider) { this.provider = provider; } setStorageCacheAt(slot, value) { const slotBN = ethers_1.BigNumber.from(slot); const slotCacheKey = slotBN.toString(); this.slotMap.set(slotCacheKey, value); } async getStorageAt(slot) { const slotBN = ethers_1.BigNumber.from(slot); const slotCacheKey = slotBN.toString(); if (this.slotMap.has(slotCacheKey)) { const cacheResult = this.slotMap.get(slotCacheKey); this.output.debug("Slot", slotBN.toHexString(), "has been read from in-memory cache:", cacheResult); return cacheResult; } const result = await this.provider.getStorageAt(this.address, slotBN.toHexString()); this.slotMap.set(slotCacheKey, result); this.output.debug("Slot", slotBN.toHexString(), "has been read from provider:", result); return result; } } exports.ContractStorage = ContractStorage; class Mapping { constructor() { // noinspection JSUnusedLocalSymbols this._isMapping = true; } } //# sourceMappingURL=cstorage.js.map