@sentio/truffle-source-fetcher
Version:
Fetches verified source code from services such as Etherscan
277 lines • 11.3 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 };
};
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
const debug_1 = __importDefault(require("debug"));
const debug = (0, debug_1.default)("source-fetcher:sourcify");
const common_1 = require("./common");
const networks_1 = require("./networks");
const async_retry_1 = __importDefault(require("async-retry"));
// must polyfill AbortController to use axios >=0.20.0, <=0.27.2 on node <= v14.x
require("./polyfill");
const axios_1 = __importDefault(require("axios"));
//this looks awkward but the TS docs actually suggest this :P
const SourcifyFetcher = (_a = class SourcifyFetcher {
static get fetcherName() {
return "sourcify";
}
get fetcherName() {
return SourcifyFetcher.fetcherName;
}
static forNetworkId(id, _options) {
return __awaiter(this, void 0, void 0, function* () {
//in the future, we may add protocol and node options,
//but these don't exist yet
return new SourcifyFetcher(id);
});
}
constructor(networkId) {
//but may be in the future
this.domain = "repo.sourcify.dev";
this.networkId = networkId;
this.networkName = networks_1.networkNamesById[networkId];
if (this.networkName === undefined ||
!SourcifyFetcher.supportedNetworks.has(this.networkName)) {
throw new common_1.InvalidNetworkError(networkId, "sourcify");
}
}
static getSupportedNetworks() {
return Object.fromEntries(Object.entries(networks_1.networksByName).filter(([name, _]) => SourcifyFetcher.supportedNetworks.has(name)));
}
fetchSourcesForAddress(address) {
return __awaiter(this, void 0, void 0, function* () {
let result = yield this.fetchSourcesForAddressAndMatchType(address, "full");
if (!result) {
//if we got nothing when trying a full match, try for a partial match
result = yield this.fetchSourcesForAddressAndMatchType(address, "partial");
}
//if partial match also fails, just return null
return result;
});
}
fetchSourcesForAddressAndMatchType(address, matchType) {
return __awaiter(this, void 0, void 0, function* () {
const metadata = yield this.getMetadata(address, matchType);
debug("metadata: %O", metadata);
if (!metadata) {
debug("no metadata");
return null;
}
let sources;
sources = Object.assign({}, ...(yield Promise.all(Object.entries(metadata.sources).map(([sourcePath, { content: source }]) => __awaiter(this, void 0, void 0, function* () {
return ({
[sourcePath]: source !== undefined
? source //sourcify doesn't support this yet but they're planning it
: yield this.getSource(address, sourcePath, matchType)
});
})))));
const constructorArguments = yield this.getConstructorArgs(address, matchType);
debug("compilationTarget: %O", metadata.settings.compilationTarget);
return {
contractName: Object.values(metadata.settings.compilationTarget)[0],
sources,
options: {
language: metadata.language,
version: metadata.compiler.version,
//we also pass the flag to remove compilationTarget, as its
//presence can cause compile errors
settings: (0, common_1.removeLibraries)(metadata.settings, true),
specializations: {
constructorArguments,
libraries: metadata.settings.libraries
}
}
};
});
}
getMetadata(address, matchType) {
return __awaiter(this, void 0, void 0, function* () {
try {
return yield this.requestWithRetries({
url: `https://${this.domain}/contracts/${matchType}_match/${this.networkId}/${address}/metadata.json`,
method: "get",
responseType: "json",
maxRedirects: 50
});
}
catch (error) {
//is this a 404 error? if so just return null
debug("error: %O", error);
if (error.response && error.response.status === 404) {
return null;
}
//otherwise, we've got a problem; rethrow the error
throw error;
}
});
}
getSource(address, sourcePath, matchType) {
return __awaiter(this, void 0, void 0, function* () {
//note: sourcify replaces special characters in paths with underscores
//(special characters here being anything other than ASCII alphanumerics,
//hyphens, periods, and forward slashes)
const transformedSourcePath = sourcePath.replace(/[^\w.\/-]/gu, "_");
return yield this.requestWithRetries({
url: `https://${this.domain}/contracts/${matchType}_match/${this.networkId}/${address}/sources/${transformedSourcePath}`,
responseType: "text",
method: "get",
maxRedirects: 50
});
});
}
getConstructorArgs(address, matchType) {
return __awaiter(this, void 0, void 0, function* () {
try {
const constructorArgs = yield this.requestWithRetries({
url: `https://${this.domain}/contracts/${matchType}_match/${this.networkId}/${address}/constructor-args.txt`,
method: "get",
responseType: "text",
maxRedirects: 50
});
return constructorArgs.slice(2); //remove initial "0x"
}
catch (error) {
//is this a 404 error? if so just return undefined
debug("error: %O", error);
if (error.response && error.response.status === 404) {
return undefined;
}
//otherwise, we've got a problem; rethrow the error
throw error;
}
});
}
requestWithRetries(requestObject //sorry, trying to import the type properly ran into problems
) {
return __awaiter(this, void 0, void 0, function* () {
return yield (0, async_retry_1.default)((bail) => __awaiter(this, void 0, void 0, function* () {
try {
//note: we use axios.request rather than just axios so we can stub it in tests!
return (yield axios_1.default.request(requestObject)).data;
}
catch (error) {
//check: is this a 404 error? if so give up
if (error.response && error.response.status === 404) {
bail(error); //don't retry
}
else {
throw error; //retry
}
}
}), { retries: 3 } //leaving minTimeout as default 1000
);
});
}
},
_a.supportedNetworks = new Set([
"mainnet",
"ropsten",
"kovan",
"rinkeby",
"goerli",
"kovan",
"sepolia",
"optimistic",
"kovan-optimistic",
"goerli-optimistic",
"goerli-bedrock-optimistic",
"arbitrum",
"rinkeby-arbitrum",
"goerli-arbitrum",
"polygon",
"mumbai-polygon",
"gnosis",
"chiado-gnosis",
"optimism-gnosis",
"core-poa",
"sokol-poa",
"binance",
"testnet-binance",
"celo",
"alfajores-celo",
"baklava-celo",
"avalanche",
"fuji-avalanche",
"wagmi-avalanche",
"dfk-avalanche",
"testnet-dfk-avalanche",
"dexalot-avalanche",
"testnet-dexalot-avalanche",
"telos",
"testnet-telos",
"ubiq",
"oneledger",
"frankenstein-oneledger",
"syscoin",
"tanenbaum-syscoin",
"boba",
"rinkeby-boba",
"velas",
"meter",
"testnet-meter",
"aurora",
"testnet-aurora",
"fuse",
"moonbeam",
"moonriver",
"moonbase-alpha",
"palm",
"testnet-palm",
"crab-darwinia",
"pangolin-darwinia",
"evmos",
"testnet-evmos",
"multivac",
"candle",
"gather",
"devnet-gather",
"testnet-gather",
"energyweb",
"volta-energyweb",
"godwoken",
"testnet-godwoken",
//sourcify does *not* support xinfin mainnet...?
"apothem-xinfin",
"canto",
"testnet-canto",
"astar",
"shiden-astar",
"cypress-klaytn",
"baobab-klaytn",
//sourcify does *not* support zetachain mainnet?
"athens-zetachain",
"emerald-oasis",
"testnet-emerald-oasis",
"sapphire-oasis",
"testnet-sapphire-oasis",
"flare",
"songbird-flare",
//sourcify does *not* support stratos mainnet?
"testnet-stratos",
//sourcify does *not* support base mainnet?
"goerli-base",
"bear",
"wanchain",
"testnet-wanchain",
"root",
"porcini-root",
"hedera",
"symplexia",
"dogechain"
//I'm excluding crystaleum as it has network ID different from chain ID
//excluding kekchain for the same reason
]),
_a);
exports.default = SourcifyFetcher;
//# sourceMappingURL=sourcify.js.map