@reef-defi/evm-provider
Version:
574 lines • 23.5 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.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
;