feat: asset-swapper singleton gas price oracle (#2619)
This commit is contained in:
@@ -15,7 +15,12 @@
|
||||
"pr": 2615
|
||||
},
|
||||
{
|
||||
"note": "Specify EthGasStation url as an optional parameter"
|
||||
"note": "Specify EthGasStation url as an optional parameter",
|
||||
"pr": 2617
|
||||
},
|
||||
{
|
||||
"note": "Singleton Gas Price Oracle",
|
||||
"pr": 2619
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@@ -174,7 +174,7 @@ export class SwapQuoter {
|
||||
: (r => r !== undefined && r.skipBuyRequests === true)(constants.DEFAULT_SWAP_QUOTER_OPTS.rfqt);
|
||||
this._contractAddresses = options.contractAddresses || getContractAddressesForChainOrThrow(chainId);
|
||||
this._devUtilsContract = new DevUtilsContract(this._contractAddresses.devUtils, provider);
|
||||
this._protocolFeeUtils = new ProtocolFeeUtils(
|
||||
this._protocolFeeUtils = ProtocolFeeUtils.getInstance(
|
||||
constants.PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS,
|
||||
options.ethGasStationUrl,
|
||||
);
|
||||
@@ -267,7 +267,7 @@ export class SwapQuoter {
|
||||
gasPrice = options.gasPrice;
|
||||
assert.isBigNumber('gasPrice', gasPrice);
|
||||
} else {
|
||||
gasPrice = await this._protocolFeeUtils.getGasPriceEstimationOrThrowAsync();
|
||||
gasPrice = await this.getGasPriceEstimationOrThrowAsync();
|
||||
}
|
||||
|
||||
const apiOrders = await this.orderbook.getBatchOrdersAsync(makerAssetDatas, [takerAssetData]);
|
||||
@@ -483,6 +483,13 @@ export class SwapQuoter {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the recommended gas price for a fast transaction
|
||||
*/
|
||||
public async getGasPriceEstimationOrThrowAsync(): Promise<BigNumber> {
|
||||
return this._protocolFeeUtils.getGasPriceEstimationOrThrowAsync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys any subscriptions or connections.
|
||||
*/
|
||||
@@ -537,7 +544,7 @@ export class SwapQuoter {
|
||||
gasPrice = opts.gasPrice;
|
||||
assert.isBigNumber('gasPrice', gasPrice);
|
||||
} else {
|
||||
gasPrice = await this._protocolFeeUtils.getGasPriceEstimationOrThrowAsync();
|
||||
gasPrice = await this.getGasPriceEstimationOrThrowAsync();
|
||||
}
|
||||
// get batches of orders from different sources, awaiting sources in parallel
|
||||
const orderBatchPromises: Array<Promise<SignedOrder[]>> = [];
|
||||
|
@@ -4,30 +4,38 @@ import * as heartbeats from 'heartbeats';
|
||||
import { constants } from '../constants';
|
||||
import { SwapQuoterError } from '../types';
|
||||
|
||||
export class ProtocolFeeUtils {
|
||||
public gasPriceEstimation: BigNumber;
|
||||
private readonly _gasPriceHeart: any;
|
||||
private readonly _ethGasStationUrl: string;
|
||||
const MAX_ERROR_COUNT = 5;
|
||||
|
||||
constructor(
|
||||
export class ProtocolFeeUtils {
|
||||
private static _instance: ProtocolFeeUtils;
|
||||
private readonly _ethGasStationUrl!: string;
|
||||
private readonly _gasPriceHeart: any;
|
||||
private _gasPriceEstimation: BigNumber = constants.ZERO_AMOUNT;
|
||||
private _errorCount: number = 0;
|
||||
|
||||
public static getInstance(
|
||||
gasPricePollingIntervalInMs: number,
|
||||
ethGasStationUrl: string = constants.ETH_GAS_STATION_API_URL,
|
||||
initialGasPrice: BigNumber = constants.ZERO_AMOUNT,
|
||||
) {
|
||||
this._gasPriceHeart = heartbeats.createHeart(gasPricePollingIntervalInMs);
|
||||
this.gasPriceEstimation = initialGasPrice;
|
||||
this._ethGasStationUrl = ethGasStationUrl;
|
||||
this._initializeHeartBeat();
|
||||
): ProtocolFeeUtils {
|
||||
if (!ProtocolFeeUtils._instance) {
|
||||
ProtocolFeeUtils._instance = new ProtocolFeeUtils(
|
||||
gasPricePollingIntervalInMs,
|
||||
ethGasStationUrl,
|
||||
initialGasPrice,
|
||||
);
|
||||
}
|
||||
return ProtocolFeeUtils._instance;
|
||||
}
|
||||
|
||||
public async getGasPriceEstimationOrThrowAsync(shouldHardRefresh?: boolean): Promise<BigNumber> {
|
||||
if (this.gasPriceEstimation.eq(constants.ZERO_AMOUNT)) {
|
||||
if (this._gasPriceEstimation.eq(constants.ZERO_AMOUNT)) {
|
||||
return this._getGasPriceFromGasStationOrThrowAsync();
|
||||
}
|
||||
if (shouldHardRefresh) {
|
||||
return this._getGasPriceFromGasStationOrThrowAsync();
|
||||
} else {
|
||||
return this.gasPriceEstimation;
|
||||
return this._gasPriceEstimation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +46,17 @@ export class ProtocolFeeUtils {
|
||||
this._gasPriceHeart.kill();
|
||||
}
|
||||
|
||||
private constructor(
|
||||
gasPricePollingIntervalInMs: number,
|
||||
ethGasStationUrl: string = constants.ETH_GAS_STATION_API_URL,
|
||||
initialGasPrice: BigNumber = constants.ZERO_AMOUNT,
|
||||
) {
|
||||
this._gasPriceHeart = heartbeats.createHeart(gasPricePollingIntervalInMs);
|
||||
this._gasPriceEstimation = initialGasPrice;
|
||||
this._ethGasStationUrl = ethGasStationUrl;
|
||||
this._initializeHeartBeat();
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: prefer-function-over-method
|
||||
private async _getGasPriceFromGasStationOrThrowAsync(): Promise<BigNumber> {
|
||||
try {
|
||||
@@ -50,15 +69,23 @@ export class ProtocolFeeUtils {
|
||||
// tslint:disable-next-line:custom-no-magic-numbers
|
||||
const unit = new BigNumber(BASE_TEN).pow(9);
|
||||
const gasPriceWei = unit.times(gasPriceGwei);
|
||||
// Reset the error count to 0 once we have a successful response
|
||||
this._errorCount = 0;
|
||||
return gasPriceWei;
|
||||
} catch (e) {
|
||||
throw new Error(SwapQuoterError.NoGasPriceProvidedOrEstimated);
|
||||
this._errorCount++;
|
||||
// If we've reached our max error count then throw
|
||||
if (this._errorCount > MAX_ERROR_COUNT || this._gasPriceEstimation.isZero()) {
|
||||
this._errorCount = 0;
|
||||
throw new Error(SwapQuoterError.NoGasPriceProvidedOrEstimated);
|
||||
}
|
||||
return this._gasPriceEstimation;
|
||||
}
|
||||
}
|
||||
|
||||
private _initializeHeartBeat(): void {
|
||||
this._gasPriceHeart.createEvent(1, async () => {
|
||||
this.gasPriceEstimation = await this._getGasPriceFromGasStationOrThrowAsync();
|
||||
this._gasPriceEstimation = await this._getGasPriceFromGasStationOrThrowAsync();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -54,7 +54,13 @@ const partiallyMockedSwapQuoter = (provider: Web3ProviderEngine, orderbook: Orde
|
||||
return mockedSwapQuoter;
|
||||
};
|
||||
|
||||
class ProtocolFeeUtilsClass extends ProtocolFeeUtils {
|
||||
class ProtocolFeeUtilsClass {
|
||||
public static getInstance(..._args: any[]): any {
|
||||
return {
|
||||
getGasPriceEstimationOrThrowAsync: async () =>
|
||||
Promise.resolve(new BigNumber(devConstants.DEFAULT_GAS_PRICE)),
|
||||
};
|
||||
}
|
||||
// tslint:disable-next-line:prefer-function-over-method
|
||||
public async getGasPriceEstimationOrThrowAsync(_shouldHardRefresh?: boolean): Promise<BigNumber> {
|
||||
return new BigNumber(devConstants.DEFAULT_GAS_PRICE);
|
||||
@@ -64,7 +70,7 @@ class ProtocolFeeUtilsClass extends ProtocolFeeUtils {
|
||||
export const protocolFeeUtilsMock = (): TypeMoq.IMock<ProtocolFeeUtils> => {
|
||||
const mockProtocolFeeUtils = TypeMoq.Mock.ofType(ProtocolFeeUtilsClass, TypeMoq.MockBehavior.Loose);
|
||||
mockProtocolFeeUtils.callBase = true;
|
||||
return mockProtocolFeeUtils;
|
||||
return mockProtocolFeeUtils as any;
|
||||
};
|
||||
|
||||
const mockGetSignedOrdersWithFillableAmountsAsyncAsync = (
|
||||
|
Reference in New Issue
Block a user