UNPKG

@rsksmart/rsk-contract-parser

Version:

A tool to parse/interact with contracts and decode events from the Rootstock blockchain.

204 lines (174 loc) 7.5 kB
"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.addSignatureDataToAbi = exports.abiSignatureData = exports.abiMethods = exports.abiEvents = void 0;exports.binarySearchNumber = binarySearchNumber;exports.erc165IdFromMethods = exports.erc165Id = void 0;exports.filterEvents = filterEvents;exports.formatAddressFromSlot = formatAddressFromSlot;exports.getSignatureDataFromAbi = exports.getLatestBridgeMethods = exports.getLatestBridgeAbi = exports.getInputsIndexes = exports.getBridgeAddress = void 0;exports.notZero = notZero;exports.soliditySignature = exports.soliditySelector = exports.solidityName = exports.setAbi = exports.removeAbiSignatureData = exports.processInputType = void 0;exports.toHex = toHex;var _rskUtils = require("@rsksmart/rsk-utils"); var _types = require("./types"); var _rskPrecompiledAbis = require("@rsksmart/rsk-precompiled-abis"); var _addresses = require("@rsksmart/rsk-utils/dist/addresses"); const getLatestBridgeAbi = () => { try { if (!_rskPrecompiledAbis.bridge || !_rskPrecompiledAbis.bridge.abi || !Array.isArray(_rskPrecompiledAbis.bridge.abi)) throw new Error('Invalid Bridge ABI'); return _rskPrecompiledAbis.bridge.abi; } catch (error) { console.error(error); return []; } };exports.getLatestBridgeAbi = getLatestBridgeAbi; const getLatestBridgeMethods = () => { try { const bridgeAbi = getLatestBridgeAbi(); return bridgeAbi.map(solidityName); } catch (error) { console.error(error); return []; } };exports.getLatestBridgeMethods = getLatestBridgeMethods; const getBridgeAddress = () => { try { if (!_rskPrecompiledAbis.bridge || !_rskPrecompiledAbis.bridge.address || !(0, _addresses.isAddress)(_rskPrecompiledAbis.bridge.address)) throw new Error('Invalid Bridge Address'); return _rskPrecompiledAbis.bridge.address; } catch (error) { console.error(error); return null; } };exports.getBridgeAddress = getBridgeAddress; const setAbi = (abi) => addSignatureDataToAbi(abi, true);exports.setAbi = setAbi; const abiEvents = (abi) => abi.filter((v) => v.type === 'event');exports.abiEvents = abiEvents; const abiMethods = (abi) => abi.filter((v) => v.type === 'function');exports.abiMethods = abiMethods; const soliditySignature = (name) => (0, _rskUtils.keccak256)(name);exports.soliditySignature = soliditySignature; const soliditySelector = (signature) => signature.slice(0, 8);exports.soliditySelector = soliditySelector; const processInputType = (input) => { if (input.type !== 'tuple' && !input.type.startsWith('tuple[')) { return input.type; } // Process tuple components const componentsTypes = input.components.map((component) => processInputType(component)); const tupleRepresentation = `(${componentsTypes.join(',')})`; // If it's an array of tuples, append the array brackets if (input.type.startsWith('tuple[')) { const arrayBrackets = input.type.substring(5); // extract the array part: [] for dynamic array, '[number]' for fixed array return `${tupleRepresentation}${arrayBrackets}`; } return tupleRepresentation; }; /** * Returns the solidity name (signature) of the provided function fragment * @param {Object} abi - The function fragment * @returns {string} The solidity name of the function fragment * @example * solidityName({ * name: 'balanceOf', * inputs: [{ type: 'address' }], * outputs: [{ type: 'uint256' }] * // ...other function fragment fields * }) * // returns 'balanceOf(address)' */exports.processInputType = processInputType; const solidityName = (abi) => { let { name, inputs } = abi; inputs = inputs ? inputs.map((i) => processInputType(i)) : []; return name ? `${name}(${inputs.join(',')})` : null; };exports.solidityName = solidityName; const removeAbiSignatureData = (abi) => { abi = Object.assign({}, abi); if (undefined !== abi[_types.ABI_SIGNATURE]) delete abi[_types.ABI_SIGNATURE]; return abi; };exports.removeAbiSignatureData = removeAbiSignatureData; const getInputsIndexes = (abi) => { const { inputs } = abi; return inputs && abi.type === 'event' ? inputs.map((i) => i.indexed) : []; };exports.getInputsIndexes = getInputsIndexes; const abiSignatureData = (abi) => { const method = solidityName(abi); const signature = method ? soliditySignature(method) : null; const index = getInputsIndexes(abi); const indexed = index ? index.filter((i) => i === true).length : 0; let eventSignature = null; if (method && abi.type === 'event') { eventSignature = soliditySignature(`${method}${Buffer.from(index).toString('hex')}`); } return { method, signature, index, indexed, eventSignature }; };exports.abiSignatureData = abiSignatureData; const addSignatureDataToAbi = (abi, skip) => { if (!Array.isArray(abi)) { throw new Error('ABI must be an array'); } abi.forEach((value) => { if (!value[_types.ABI_SIGNATURE] || !skip) { value[_types.ABI_SIGNATURE] = abiSignatureData(value); } }); return abi; };exports.addSignatureDataToAbi = addSignatureDataToAbi; const erc165Id = (selectors) => { const id = selectors.map((s) => Buffer.from(s, 'hex')). reduce((a, bytes) => { for (let i = 0; i < _types.INTERFACE_ID_BYTES; i++) { a[i] = a[i] ^ bytes[i]; } return a; }, Buffer.alloc(_types.INTERFACE_ID_BYTES)); return (0, _rskUtils.add0x)(id.toString('hex')); };exports.erc165Id = erc165Id; const erc165IdFromMethods = (methods) => { return erc165Id(methods.map((m) => soliditySelector(soliditySignature(m)))); };exports.erc165IdFromMethods = erc165IdFromMethods; const getSignatureDataFromAbi = (abi) => { return abi[_types.ABI_SIGNATURE]; };exports.getSignatureDataFromAbi = getSignatureDataFromAbi; function filterEvents(abi) { const type = 'event'; // get events from ABI const events = abi.filter((a) => a.type === type); // remove events from ABI abi = abi.filter((a) => a.type !== type); const keys = [...new Set(events.map((e) => e[_types.ABI_SIGNATURE].eventSignature))]; const filteredEvents = keys.map((k) => events.find((e) => e[_types.ABI_SIGNATURE].eventSignature === k)); abi = abi.concat(filteredEvents); return abi; } function filterArr(a) { if (!Array.isArray(a)) return a; return a.find((x) => filterArr(x)); } async function binarySearchNumber(searchCb, high, low) { try { high = parseInt(high || 0); low = parseInt(low || 0); if (typeof searchCb !== 'function') throw new Error('SeachCb must be a function'); const [l, h] = await Promise.all([low, high].map((b) => searchCb(b))); if (l !== h) { if (high === low + 1) { return high; } else { const mid = Math.floor(high / 2 + low / 2); const res = await Promise.all([ binarySearchNumber(searchCb, high, mid), binarySearchNumber(searchCb, mid, low)]); return filterArr(res); } } } catch (err) { return Promise.reject(err); } } function notZero(value) { if (typeof value === 'string' && /^0x[0-9a-f]*$/i.test(value)) { return BigInt(value) !== BigInt(0); } return false; } function formatAddressFromSlot(slot) { if (typeof slot === 'string') { return `0x${slot.slice(-40)}`; } return slot; } /** * Converts a number to a hex string. * @param {number} number - The number to convert * @returns {string} The hex string */ function toHex(number) { if (typeof number === 'number') { return '0x' + number.toString(16); } return number; }