feat: asset-swapper singleton gas price oracle (#2619)

This commit is contained in:
Jacob Evans
2020-07-01 18:43:47 +10:00
committed by GitHub
parent 676698a59b
commit 85ca926fd0
4 changed files with 65 additions and 20 deletions

View File

@@ -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
}
]
},

View File

@@ -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[]>> = [];

View File

@@ -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();
});
}
}

View File

@@ -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 = (