UNPKG

@reef-defi/evm-provider

Version:
574 lines 23.5 kB
"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.Provider = void 0; /* eslint-disable @typescript-eslint/no-unused-vars */ const api_1 = require("@reef-defi/api"); const bytes_1 = require("@ethersproject/bytes"); const properties_1 = require("@ethersproject/properties"); const bignumber_1 = require("@ethersproject/bignumber"); const logger_1 = require("@ethersproject/logger"); const scanner_1 = __importDefault(require("@open-web3/scanner")); const api_2 = require("@polkadot/api"); const util_1 = require("@polkadot/util"); const util_crypto_1 = require("@polkadot/util-crypto"); const utils_1 = require("./utils"); const logger = new logger_1.Logger('evm-provider'); class Provider { /** * * @param _apiOptions * @param dataProvider */ constructor(_apiOptions, dataProvider) { const apiOptions = (0, api_1.options)(_apiOptions); this.api = new api_2.ApiPromise(apiOptions); this.resolveApi = this.api.isReady; this._isProvider = true; this.dataProvider = dataProvider; this.scanner = new scanner_1.default({ wsProvider: apiOptions.provider, types: apiOptions.types, typesAlias: apiOptions.typesAlias, typesSpec: apiOptions.typesSpec, typesChain: apiOptions.typesChain, typesBundle: apiOptions.typesBundle }); } // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types static isProvider(value) { return !!(value && value._isProvider); } init() { return __awaiter(this, void 0, void 0, function* () { yield this.api.isReady; this.dataProvider && (yield this.dataProvider.init()); }); } /** * Get the network the provider is connected to. * @returns A promise resolving to the name and chain ID of the connected chain. */ getNetwork() { return __awaiter(this, void 0, void 0, function* () { yield this.resolveApi; return { name: this.api.runtimeVersion.specName.toString(), chainId: 13939 }; }); } /** * Get the block number of the chain's head. * @returns A promise resolving to the block number of the head block. */ getBlockNumber() { return __awaiter(this, void 0, void 0, function* () { yield this.resolveApi; const r = yield this.api.rpc.chain.getHeader(); return r.number.toNumber(); }); } getGasPrice() { return __awaiter(this, void 0, void 0, function* () { // return logger.throwError(`Unsupport getGasPrice`); return bignumber_1.BigNumber.from(0); }); } getFeeData() { return __awaiter(this, void 0, void 0, function* () { return { maxFeePerGas: bignumber_1.BigNumber.from(1), maxPriorityFeePerGas: bignumber_1.BigNumber.from(1), gasPrice: bignumber_1.BigNumber.from(1) }; }); } /** * Get an account's balance by address or name. * @param addressOrName The address or name of the account * @param blockTag The block to get the balance of, defaults to the head * @returns A promise resolving to the account's balance */ getBalance(addressOrName, blockTag) { return __awaiter(this, void 0, void 0, function* () { yield this.resolveApi; let address = yield (0, utils_1.resolveAddress)(this, addressOrName); if (!address) { address = yield this._toAddress(addressOrName); } const blockHash = yield this._resolveBlockHash(blockTag); const accountInfo = blockHash ? yield this.api.query.system.account.at(blockHash, address) : yield this.api.query.system.account(address); return bignumber_1.BigNumber.from(accountInfo.data.free.toBn().toString()); }); } /** * Get the transaction count of an account at a specified block. * @param addressOrName The address or name of the account * @param blockTag The block to get the transaction count of, defaults to the head block * @returns A promise resolving to the account's transaction count */ getTransactionCount(addressOrName, blockTag) { return __awaiter(this, void 0, void 0, function* () { yield this.resolveApi; const resolvedBlockTag = yield blockTag; const address = yield (0, utils_1.resolveEvmAddress)(this, addressOrName); let account; if (resolvedBlockTag === 'pending') { account = yield this.api.query.evm.accounts(address); } else { const blockHash = yield this._resolveBlockHash(blockTag); account = blockHash ? yield this.api.query.evm.accounts.at(blockHash, address) : yield this.api.query.evm.accounts(address); } if (!account.isNone) { return account.unwrap().nonce.toNumber(); } else { return 0; } }); } /** * Get the code hash at a given address * @param addressOrName The address of the code * @param blockTag The block to look up the address, defaults to latest * @returns A promise resolving in the code hash */ getCode(addressOrName, blockTag) { return __awaiter(this, void 0, void 0, function* () { yield this.resolveApi; const { address, blockHash } = yield (0, properties_1.resolveProperties)({ address: (0, utils_1.resolveEvmAddress)(this, addressOrName), blockHash: this._getBlockTag(blockTag) }); const contractInfo = yield this.queryContractInfo(address, blockHash); if (contractInfo.isNone) { return '0x'; } const codeHash = contractInfo.unwrap().codeHash; const api = yield (blockHash ? this.api.at(blockHash) : this.api); const code = yield api.query.evm.codes(codeHash); return code.toHex(); }); } _getBlockTag(blockTag) { return __awaiter(this, void 0, void 0, function* () { blockTag = yield blockTag; if (blockTag === undefined) { blockTag = 'latest'; } switch (blockTag) { case 'pending': { return logger.throwError('pending tag not implemented', logger_1.Logger.errors.UNSUPPORTED_OPERATION); } case 'latest': { const hash = yield this.api.rpc.chain.getBlockHash(); return hash.toHex(); } case 'earliest': { const hash = this.api.genesisHash; return hash.toHex(); } default: { if (!(0, bytes_1.isHexString)(blockTag)) { return logger.throwArgumentError('blocktag should be a hex string', 'blockTag', blockTag); } // block hash if (typeof blockTag === 'string' && (0, bytes_1.isHexString)(blockTag, 32)) { return blockTag; } const blockNumber = bignumber_1.BigNumber.from(blockTag).toNumber(); const hash = yield this.api.rpc.chain.getBlockHash(blockNumber); return hash.toHex(); } } }); } queryAccountInfo(addressOrName, blockTag) { return __awaiter(this, void 0, void 0, function* () { // pending tag const resolvedBlockTag = yield blockTag; if (resolvedBlockTag === 'pending') { const address = yield (0, utils_1.resolveEvmAddress)(this, addressOrName); return this.api.query.evm.accounts(address); } const { address, blockHash } = yield (0, properties_1.resolveProperties)({ address: (0, utils_1.resolveEvmAddress)(this, addressOrName), blockHash: this._getBlockTag(blockTag) }); const apiAt = yield this.api.at(blockHash); const accountInfo = yield apiAt.query.evm.accounts(address); return accountInfo; }); } queryContractInfo(addressOrName, blockTag) { return __awaiter(this, void 0, void 0, function* () { const accountInfo = yield this.queryAccountInfo(addressOrName, blockTag); if (accountInfo.isNone) { return this.api.createType('Option<EvmContractInfo>', null); } return accountInfo.unwrap().contractInfo; }); } /** * Get the storage from a block. * @param addressOrName The address to retrieve the storage from * @param position * @param blockTag The block to retrieve the storage from, defaults to head * @returns The storage data as a hash */ getStorageAt(addressOrName, position, blockTag) { return __awaiter(this, void 0, void 0, function* () { yield this.resolveApi; const address = yield (0, utils_1.resolveEvmAddress)(this, addressOrName); const blockHash = yield this._resolveBlockHash(blockTag); const code = blockHash ? yield this.api.query.evm.accountStorages.at(blockHash, address) : yield this.api.query.evm.accountStorages(address); return code.toHex(); }); } /** * Unimplemented */ sendTransaction(signedTransaction) { return __awaiter(this, void 0, void 0, function* () { return this._fail('sendTransaction'); }); } /** * Submit a transaction to be executed on chain. * @param transaction The transaction to call * @param blockTag * @returns The call result as a hash */ call(transaction, blockTag) { return __awaiter(this, void 0, void 0, function* () { const resolved = yield this._resolveTransaction(transaction); if (blockTag) { const blockHash = yield this._resolveBlockHash(blockTag); // eslint-disable-next-line @typescript-eslint/no-explicit-any const result = yield this.api.rpc.evm.call(resolved, blockHash); return result.toHex(); } else { // eslint-disable-next-line @typescript-eslint/no-explicit-any const result = yield this.api.rpc.evm.call({ to: resolved.to, from: resolved.from, data: resolved.data, storageLimit: '0' }); return result.toHex(); } }); } /** * Estimate gas for a transaction. * @param transaction The transaction to estimate the gas of * @returns The estimated gas used by this transaction */ estimateGas(transaction) { return __awaiter(this, void 0, void 0, function* () { const resources = yield this.estimateResources(transaction); return resources.gas.add(resources.storage); }); } /** * Estimate resources for a transaction. * @param transaction The transaction to estimate the resources of * @returns The estimated resources used by this transaction */ estimateResources(transaction) { return __awaiter(this, void 0, void 0, function* () { const resolved = yield this._resolveTransaction(transaction); const from = yield resolved.from; const value = yield resolved.value; const to = yield resolved.to; const data = yield resolved.data; const storageLimit = yield this._resolveStorageLimit(resolved); if (!from) { return logger.throwError('From cannot be undefined'); } // construct extrinsic to estimate const extrinsic = !to ? this.api.tx.evm.create(data, (0, utils_1.toBN)(value), (0, utils_1.toBN)(yield resolved.gasLimit), (0, utils_1.toBN)(storageLimit)) : this.api.tx.evm.call(to, data, (0, utils_1.toBN)(value), (0, utils_1.toBN)(yield resolved.gasLimit), (0, utils_1.toBN)(storageLimit)); // eslint-disable-next-line @typescript-eslint/no-explicit-any const result = yield this.api.rpc.evm.estimateResources(resolved.from, extrinsic.toHex() // returns transaction bytecode? ); return { gas: bignumber_1.BigNumber.from(result.gas.toString()), storage: bignumber_1.BigNumber.from(result.storage.toString()), weightFee: bignumber_1.BigNumber.from(result.weightFee.toString()) }; }); } /** * Unimplemented, will always fail. */ getBlock(blockHashOrBlockTag) { return __awaiter(this, void 0, void 0, function* () { return this._fail('getBlock'); }); } /** * Unimplemented, will always fail. */ getBlockWithTransactions(blockHashOrBlockTag) { return __awaiter(this, void 0, void 0, function* () { return this._fail('getBlockWithTransactions'); }); } /** * Unimplemented, will always fail. */ getTransaction(transactionHash) { return __awaiter(this, void 0, void 0, function* () { return this._fail('getTransaction'); }); } getTransactionReceipt(txHash) { return __awaiter(this, void 0, void 0, function* () { if (!this.dataProvider) return this._fail('getTransactionReceipt'); return this.dataProvider.getTransactionReceipt(txHash, this._resolveBlockNumber); }); } resolveName(name) { return __awaiter(this, void 0, void 0, function* () { return name; }); } lookupAddress(address) { return __awaiter(this, void 0, void 0, function* () { return address; }); } /** * Unimplemented, will always fail. */ waitForTransaction(transactionHash, confirmations, timeout) { return __awaiter(this, void 0, void 0, function* () { return this._fail('waitForTransaction'); }); } /** * Get an array of filtered logs from the chain's head. * @param filter The filter to apply to the logs * @returns A promise that resolves to an array of filtered logs */ getLogs(filter) { return __awaiter(this, void 0, void 0, function* () { if (!this.dataProvider) return this._fail('getLogs'); return this.dataProvider.getLogs(filter, this._resolveBlockNumber); }); } // eslint-disable-next-line @typescript-eslint/no-explicit-any _fail(operation) { return Promise.resolve().then(() => { logger.throwError(`Unsupport ${operation}`); }); } // eslint-disable-next-line @typescript-eslint/no-explicit-any emit(eventName, ...args) { return logger.throwError('Unsupport Event'); } listenerCount(eventName) { return logger.throwError('Unsupport Event'); } listeners(eventName) { return logger.throwError('Unsupport Event'); } off(eventName, listener) { return logger.throwError('Unsupport Event'); } on(eventName, listener) { return logger.throwError('Unsupport Event'); } once(eventName, listener) { return logger.throwError('Unsupport Event'); } removeAllListeners(eventName) { return logger.throwError('Unsupport Event'); } addListener(eventName, listener) { return this.on(eventName, listener); } removeListener(eventName, listener) { return this.off(eventName, listener); } _resolveTransactionReceipt(transactionHash, blockHash, from) { return __awaiter(this, void 0, void 0, function* () { const detail = yield this.scanner.getBlockDetail({ blockHash: blockHash }); const blockNumber = detail.number; const extrinsic = detail.extrinsics.find(({ hash }) => hash === transactionHash); if (!extrinsic) { return logger.throwError(`Transaction hash not found`); } const transactionIndex = extrinsic.index; const events = detail.events.filter(({ phaseIndex }) => phaseIndex === transactionIndex); const findCreated = events.find((x) => x.section.toUpperCase() === 'EVM' && x.method.toUpperCase() === 'CREATED'); const findExecuted = events.find((x) => x.section.toUpperCase() === 'EVM' && x.method.toUpperCase() === 'EXECUTED'); const result = events.find((x) => x.section.toUpperCase() === 'SYSTEM' && x.method.toUpperCase() === 'EXTRINSICSUCCESS'); if (!result) { return logger.throwError(`Can't find event`); } const status = findCreated || findExecuted ? 1 : 0; const contractAddress = findCreated ? findCreated.args[0] : null; const to = findExecuted ? findExecuted.args[0] : null; const logs = events .filter((e) => { return (e.method.toUpperCase() === 'LOG' && e.section.toUpperCase() === 'EVM'); }) .map((log, index) => { return { transactionHash, blockNumber, blockHash, transactionIndex, removed: false, address: log.args[0].address, data: log.args[0].data, topics: log.args[0].topics, logIndex: index }; }); const gasUsed = bignumber_1.BigNumber.from(result.args[0].weight); return { to, from, contractAddress, transactionIndex, gasUsed, logsBloom: '0x', blockHash, transactionHash, logs, blockNumber, confirmations: 4, cumulativeGasUsed: gasUsed, byzantium: false, status, effectiveGasPrice: bignumber_1.BigNumber.from('1'), type: 0 }; }); } _resolveBlockHash(blockTag) { return __awaiter(this, void 0, void 0, function* () { yield this.resolveApi; if (!blockTag) return undefined; const resolvedBlockHash = yield blockTag; if (resolvedBlockHash === 'pending') { throw new Error('Unsupport Block Pending'); } if (resolvedBlockHash === 'latest') { const hash = yield this.api.query.system.blockHash(); return hash.toString(); } if (resolvedBlockHash === 'earliest') { const hash = this.api.query.system.blockHash(0); return hash.toString(); } if ((0, util_1.isHex)(resolvedBlockHash)) { return resolvedBlockHash; } const hash = yield this.api.query.system.blockHash(resolvedBlockHash); return hash.toString(); }); } _resolveBlockNumber(blockTag) { return __awaiter(this, void 0, void 0, function* () { yield this.resolveApi; if (!blockTag) { return logger.throwError(`Blocktag cannot be undefined`); } const resolvedBlockNumber = yield blockTag; if (resolvedBlockNumber === 'pending') { throw new Error('Unsupport Block Pending'); } if (resolvedBlockNumber === 'latest') { const header = yield this.api.rpc.chain.getHeader(); return header.number.toNumber(); } if (resolvedBlockNumber === 'earliest') { return 0; } if ((0, util_1.isNumber)(resolvedBlockNumber)) { return resolvedBlockNumber; } else { throw new Error('Expect blockHash to be a number or tag'); } }); } _toAddress(addressOrName) { return __awaiter(this, void 0, void 0, function* () { const resolved = yield addressOrName; const address = (0, util_crypto_1.encodeAddress)((0, util_1.u8aFixLength)((0, util_1.u8aConcat)('evm:', (0, util_1.hexToU8a)(resolved)), 256, true)); return address.toString(); }); } _resolveTransaction(tx) { return __awaiter(this, void 0, void 0, function* () { for (const key of ['gasLimit', 'value']) { const typeKey = key; if (tx[typeKey]) { if (bignumber_1.BigNumber.isBigNumber(tx[typeKey])) { tx[typeKey] = tx[typeKey].toHexString(); } else if ((0, util_1.isNumber)(tx[typeKey])) { tx[typeKey] = (0, util_1.numberToHex)(tx[typeKey]); } } } delete tx.nonce; delete tx.gasPrice; delete tx.chainId; return tx; }); } _resolveStorageLimit(tx) { return __awaiter(this, void 0, void 0, function* () { if (tx.customData) { if ('storageLimit' in tx.customData) { const storageLimit = tx.customData.storageLimit; if (bignumber_1.BigNumber.isBigNumber(storageLimit)) { return storageLimit; } else if ((0, util_1.isNumber)(storageLimit)) { return bignumber_1.BigNumber.from(storageLimit); } } } // At least 60 REEF are needed to deploy return bignumber_1.BigNumber.from(60000); }); } } exports.Provider = Provider; //# sourceMappingURL=Provider.js.map