@celo/explorer
Version:
Celo's block explorer consumer
210 lines • 8.69 kB
JavaScript
;
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.tryGetProxyImplementation = exports.fetchMetadata = exports.Metadata = void 0;
const cross_fetch_1 = __importDefault(require("cross-fetch"));
const base_1 = require("./base");
const PROXY_IMPLEMENTATION_GETTERS = [
'_getImplementation',
'getImplementation',
'_implementation',
'implementation',
];
// Position of the implementation in the UUPS proxy smart contract storage
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/8b4b7b8d041c62a84e2c23d7f6e1f0d9e0fc1f20/contracts/proxy/ERC1967/ERC1967Utils.sol#L35
const PROXY_IMPLEMENTATION_POSITION_UUPS = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc';
const PROXY_ABI = PROXY_IMPLEMENTATION_GETTERS.map((funcName) => ({
constant: true,
inputs: [],
name: funcName,
outputs: [
{
internalType: 'address',
name: 'implementation',
type: 'address',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
}));
/**
* Wrapper class for a metadata.json response from sourcify.
* Because the response's true structure is unknown this wrapper implements
* light runtime verification.
*/
class Metadata {
constructor(connection, address, response) {
this.abi = null;
this.contractName = null;
this.fnMapping = new Map();
this.abiCoder = connection.getAbiCoder();
this.response = response;
// XXX: For some reason this isn't exported as it should be
// @ts-ignore
this.jsonInterfaceMethodToString = connection.web3.utils._jsonInterfaceMethodToString;
this.address = address;
}
set response(value) {
if (typeof value === 'object' &&
typeof value.output === 'object' &&
'abi' in value.output &&
Array.isArray(value.output.abi) &&
value.output.abi.length > 0) {
this.abi = value.output.abi;
this.fnMapping = (0, base_1.mapFromPairs)((this.abi || [])
.filter((item) => item.type === 'function')
.map((item) => {
const signature = this.abiCoder.encodeFunctionSignature(item);
return Object.assign(Object.assign({}, item), { signature });
})
.map((item) => [item.signature, item]));
}
if (typeof value === 'object' &&
typeof value.settings === 'object' &&
typeof value.settings.compilationTarget === 'object' &&
Object.values(value.settings.compilationTarget).length > 0) {
// XXX: Not sure when there are multiple compilationTargets and what should
// happen then but defaulting to this for now.
const contracts = Object.values(value.settings.compilationTarget);
this.contractName = contracts[0];
}
}
/**
* Turn the ABI into a mapping of function selectors to ABI items.
*/
toContractMapping() {
return {
details: {
name: this.contractName || 'Unknown',
address: this.address,
jsonInterface: this.abi || [],
isCore: false,
},
fnMapping: this.fnMapping,
};
}
/**
* Find the AbiItem for a given function selector
* @param selector the 4-byte selector of the function call
* @returns an AbiItem if found or null
*/
abiForSelector(selector) {
var _a;
return (((_a = this.abi) === null || _a === void 0 ? void 0 : _a.find((item) => {
return item.type === 'function' && this.abiCoder.encodeFunctionSignature(item) === selector;
})) || null);
}
/**
* Find the AbiItem for methods that match the provided method name.
* The function can return more than one AbiItem if the query string
* provided doesn't contain arguments as there can be multiple
* definitions with different arguments.
* @param method name of the method to lookup
* @returns and array of AbiItems matching the query
*/
abiForMethod(query) {
var _a, _b;
if (query.indexOf('(') >= 0) {
// Method is a full call signature with arguments
return (((_a = this.abi) === null || _a === void 0 ? void 0 : _a.filter((item) => {
return item.type === 'function' && this.jsonInterfaceMethodToString(item) === query;
})) || []);
}
else {
// Method is only method name
return (((_b = this.abi) === null || _b === void 0 ? void 0 : _b.filter((item) => {
return item.type === 'function' && item.name === query;
})) || []);
}
}
}
exports.Metadata = Metadata;
/**
* Fetch the sourcify response and instantiate a Metadata wrapper class around it.
* Try a full_match but fallback to partial_match when not strict.
* @param connection @celo/connect instance
* @param contract the address of the contract to query
* @param strict only allow full matches https://docs.sourcify.dev/docs/full-vs-partial-match/
* @returns Metadata or null
*/
function fetchMetadata(connection, contract, strict = false) {
return __awaiter(this, void 0, void 0, function* () {
const fullMatchMetadata = yield querySourcify(connection, 'full_match', contract);
if (fullMatchMetadata !== null) {
return fullMatchMetadata;
}
else if (strict) {
return null;
}
else {
return querySourcify(connection, 'partial_match', contract);
}
});
}
exports.fetchMetadata = fetchMetadata;
/**
* Fetch the sourcify response and instantiate a Metadata wrapper class around it.
* @param connection @celo/connect instance
* @param matchType what type of match to query for https://docs.sourcify.dev/docs/full-vs-partial-match/
* @param contract the address of the contract to query
* @returns Metadata or null
*/
function querySourcify(connection, matchType, contract) {
return __awaiter(this, void 0, void 0, function* () {
const chainID = yield connection.chainId();
const resp = yield (0, cross_fetch_1.default)(`https://repo.sourcify.dev/contracts/${matchType}/${chainID}/${contract}/metadata.json`);
if (resp.ok) {
return new Metadata(connection, contract, yield resp.json());
}
return null;
});
}
/**
* Use heuristics to determine if the contract can be a proxy
* and extract the implementation.
* Available scenarios:
* - _getImplementation() exists
* - getImplementation() exists
* - _implementation() exists
* - implementation() exists
* @param connection @celo/connect instance
* @param contract the address of the contract to query
* @returns the implementation address or null
*/
function tryGetProxyImplementation(connection, contract) {
return __awaiter(this, void 0, void 0, function* () {
const proxyContract = new connection.web3.eth.Contract(PROXY_ABI, contract);
for (const fn of PROXY_IMPLEMENTATION_GETTERS) {
try {
return yield new Promise((resolve, reject) => {
proxyContract.methods[fn]().call().then(resolve).catch(reject);
});
}
catch (_a) {
continue;
}
}
try {
const hexValue = yield connection.web3.eth.getStorageAt(contract, PROXY_IMPLEMENTATION_POSITION_UUPS);
const address = connection.web3.utils.toChecksumAddress('0x' + hexValue.slice(-40));
return address;
}
catch (_b) {
return undefined;
}
});
}
exports.tryGetProxyImplementation = tryGetProxyImplementation;
//# sourceMappingURL=sourcify.js.map