@xyrusworx/web3-cli
Version:
A command line tool which offers low-level EVM interaction for Web3 developers
157 lines • 7.18 kB
JavaScript
;
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