@celo/explorer
Version:
Celo's block explorer consumer
314 lines • 14.5 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BlockExplorer = exports.newBlockExplorer = void 0;
const connect_1 = require("@celo/connect");
const contractkit_1 = require("@celo/contractkit");
const proxy_1 = require("@celo/contractkit/lib/proxy");
const fixidity_1 = require("@celo/utils/lib/fixidity");
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const debug_1 = __importDefault(require("debug"));
const base_1 = require("./base");
const sourcify_1 = require("./sourcify");
const debug = (0, debug_1.default)('kit:explorer:block');
function newBlockExplorer(kit) {
return __awaiter(this, void 0, void 0, function* () {
return new BlockExplorer(kit, yield (0, base_1.obtainKitContractDetails)(kit));
});
}
exports.newBlockExplorer = newBlockExplorer;
const getContractMappingFromDetails = (cd) => ({
details: cd,
fnMapping: (0, base_1.mapFromPairs)(cd.jsonInterface.concat(proxy_1.PROXY_ABI)
.filter((ad) => ad.type === 'function')
.map((ad) => [ad.signature, ad])),
});
const isCoreContract = (contract) => contract in contractkit_1.CeloContract;
class BlockExplorer {
constructor(kit, contractDetails) {
this.kit = kit;
this.contractDetails = contractDetails;
this.proxyImplementationOverride = new Map();
this.getContractMethodAbiFromMapping = (contractMapping, selector) => {
if (contractMapping === undefined) {
return null;
}
const methodAbi = contractMapping.fnMapping.get(selector);
if (methodAbi === undefined) {
return null;
}
return {
contract: contractMapping.details.address,
contractName: contractMapping.details.name,
abi: methodAbi,
};
};
/**
* @deprecated use getContractMappingWithSelector instead
* Returns the contract name and ABI of the method by looking up
* the contract address either in all possible contract mappings.
* @param address
* @param selector
* @param onlyCoreContracts
* @returns The contract name and ABI of the method or null if not found
*/
this.getContractMethodAbi = (address, selector, onlyCoreContracts = false) => __awaiter(this, void 0, void 0, function* () {
if (onlyCoreContracts) {
return this.getContractMethodAbiFromCore(address, selector);
}
const contractMapping = yield this.getContractMappingWithSelector(address, selector);
if (contractMapping === undefined) {
return null;
}
return this.getContractMethodAbiFromMapping(contractMapping, selector);
});
/**
* Returns the contract name and ABI of the method by looking up
* the contract address but only in core contracts
* @param address
* @param selector
* @returns The contract name and ABI of the method or null if not found
*/
this.getContractMethodAbiFromCore = (address, selector) => __awaiter(this, void 0, void 0, function* () {
const contractMapping = yield this.getContractMappingWithSelector(address, selector, [
this.getContractMappingFromCore,
]);
if (contractMapping === undefined) {
return null;
}
return this.getContractMethodAbiFromMapping(contractMapping, selector);
});
/**
* @deprecated use getContractMappingWithSelector instead
* Returns the contract name and ABI of the method by looking up
* the contract address in Sourcify.
* @param address
* @param selector
* @returns The contract name and ABI of the method or null if not found
*/
this.getContractMethodAbiFromSourcify = (address, selector) => __awaiter(this, void 0, void 0, function* () {
const contractMapping = yield this.getContractMappingWithSelector(address, selector, [
this.getContractMappingFromSourcify,
this.getContractMappingFromSourcifyAsProxy,
]);
if (contractMapping === undefined) {
return null;
}
return this.getContractMethodAbiFromMapping(contractMapping, selector);
});
/**
* @deprecated use getContractMappingWithSelector instead
* Returns the contract name and ABI of the method by looking up
* the selector in a list of known functions.
* @param address
* @param selector
* @param onlyCoreContracts
* @returns The contract name and ABI of the method or null if not found
*/
this.getContractMethodAbiFallback = (address, selector) => {
// TODO(bogdan): This could be replaced with a call to 4byte.directory
// or a local database of common functions.
const knownFunctions = {
'0x095ea7b3': 'approve(address to, uint256 value)',
'0x4d49e87d': 'addLiquidity(uint256[] amounts, uint256 minLPToMint, uint256 deadline)',
};
const signature = knownFunctions[selector];
if (signature) {
return {
abi: (0, connect_1.signatureToAbiDefinition)(signature),
contract: `Unknown(${address})`,
};
}
return null;
};
/**
* Returns the ContractMapping for the contract at that address, or undefined
* by looking up the contract address in the core mappings.
* @param address
* @returns The ContractMapping for the contract at that address, or undefined
*/
this.getContractMappingFromCore = (address) => __awaiter(this, void 0, void 0, function* () {
return this.addressMapping.get(address);
});
/**
* Returns the ContractMapping for the contract at that address, or undefined
* by looking up the contract address in Sourcify.
* @param address
* @returns The ContractMapping for the contract at that address, or undefined
*/
this.getContractMappingFromSourcify = (address) => __awaiter(this, void 0, void 0, function* () {
const cached = this.addressMapping.get(address);
if (cached) {
return cached;
}
const metadata = yield (0, sourcify_1.fetchMetadata)(this.kit.connection, this.kit.web3.utils.toChecksumAddress(address));
const mapping = metadata === null || metadata === void 0 ? void 0 : metadata.toContractMapping();
if (mapping) {
this.addressMapping.set(address, mapping);
}
return mapping;
});
/**
* Returns the ContractMapping for the contract at that address, or undefined
* by looking up the contract address in Sourcify but using heuristis to treat
* it as a proxy.
*
* This function is also included by the proxyImplementationOverrides map,
* which can be used to override the implementation address for a given proxy.
* This is exceptionally useful for parsing governence proposals that either
* initialize a proxy or upgrade it, and then calls methods on the new implementation.
* @param address
* @returns The ContractMapping for the contract at that address, or undefined
*/
this.getContractMappingFromSourcifyAsProxy = (address) => __awaiter(this, void 0, void 0, function* () {
let implAddress = yield (0, sourcify_1.tryGetProxyImplementation)(this.kit.connection, address);
if (this.proxyImplementationOverride.has(address)) {
implAddress = this.proxyImplementationOverride.get(address);
}
if (implAddress) {
const contractMapping = yield this.getContractMappingFromSourcify(implAddress);
if (contractMapping) {
return Object.assign(Object.assign({}, contractMapping), { details: Object.assign(Object.assign({}, contractMapping.details), { address }) });
}
}
});
this.addressMapping = (0, base_1.mapFromPairs)(contractDetails
.filter((cd) => /Proxy$/.exec(cd.name) == null)
.map((cd) => [cd.address, getContractMappingFromDetails(cd)]));
}
updateContractDetailsMapping(name, address) {
return __awaiter(this, void 0, void 0, function* () {
if (isCoreContract(name)) {
const contractDetails = yield (0, base_1.getContractDetailsFromContract)(this.kit, name, address);
this.addressMapping.set(contractDetails.address, getContractMappingFromDetails(contractDetails));
}
});
}
setProxyOverride(proxyAddress, implementationAddress) {
return __awaiter(this, void 0, void 0, function* () {
debug('Setting proxy override for %s to %s', proxyAddress, implementationAddress);
this.proxyImplementationOverride.set(proxyAddress, implementationAddress);
});
}
fetchBlockByHash(blockHash) {
return __awaiter(this, void 0, void 0, function* () {
return this.kit.connection.getBlock(blockHash);
});
}
fetchBlock(blockNumber) {
return __awaiter(this, void 0, void 0, function* () {
return this.kit.connection.getBlock(blockNumber);
});
}
fetchBlockRange(from, to) {
return __awaiter(this, void 0, void 0, function* () {
const results = [];
for (let i = from; i < to; i++) {
results.push(yield this.fetchBlock(i));
}
return results;
});
}
parseBlock(block) {
return __awaiter(this, void 0, void 0, function* () {
const parsedTx = [];
for (const tx of block.transactions) {
if (typeof tx !== 'string') {
const maybeKnownCall = yield this.tryParseTx(tx);
if (maybeKnownCall != null) {
parsedTx.push(maybeKnownCall);
}
}
}
return {
block,
parsedTx,
};
});
}
tryParseTx(tx) {
return __awaiter(this, void 0, void 0, function* () {
const callDetails = yield this.tryParseTxInput(tx.to, tx.input);
if (!callDetails) {
return null;
}
return {
tx,
callDetails,
};
});
}
tryParseTxInput(address, input) {
return __awaiter(this, void 0, void 0, function* () {
const selector = input.slice(0, 10);
const contractMapping = yield this.getContractMappingWithSelector(address, selector);
if (contractMapping) {
const methodAbi = contractMapping.fnMapping.get(selector);
return this.buildCallDetails(contractMapping.details, methodAbi, input);
}
return null;
});
}
buildCallDetails(contract, abi, input) {
const encodedParameters = input.slice(10);
const { args, params } = (0, connect_1.parseDecodedParams)(this.kit.connection.getAbiCoder().decodeParameters(abi.inputs, encodedParameters));
// transform numbers to big numbers in params
abi.inputs.forEach((abiInput, idx) => {
if (abiInput.type === 'uint256') {
debug('transforming number param');
params[abiInput.name] = new bignumber_js_1.default(args[idx]);
}
});
// transform fixidity values to fractions in params
Object.keys(params)
.filter((key) => key.includes('fraction')) // TODO: come up with better enumeration
.forEach((fractionKey) => {
debug('transforming fixed number param');
params[fractionKey] = (0, fixidity_1.fromFixed)(params[fractionKey]);
});
return {
contract: contract.name,
contractAddress: contract.address,
isCoreContract: contract.isCore,
function: abi.name,
paramMap: params,
argList: args,
};
}
/**
* Uses all of the strategies available to find a contract mapping that contains
* the given method selector.
* @param address
* @param selector
* @param strategies
* @returns The ContractMapping for the contract which has the function selector, or undefined
*/
getContractMappingWithSelector(address, selector, strategies = [
this.getContractMappingFromCore,
this.getContractMappingFromSourcify,
this.getContractMappingFromSourcifyAsProxy,
]) {
return __awaiter(this, void 0, void 0, function* () {
const mappings = yield Promise.all(strategies.map((strategy) => __awaiter(this, void 0, void 0, function* () {
const contractMapping = yield strategy(address);
if (contractMapping && contractMapping.fnMapping.get(selector)) {
return contractMapping;
}
})));
return mappings.find((mapping) => mapping !== undefined);
});
}
}
exports.BlockExplorer = BlockExplorer;
//# sourceMappingURL=block-explorer.js.map