Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

Commit

Permalink
feat: asset-swapper singleton gas price oracle
Browse files Browse the repository at this point in the history
  • Loading branch information
dekz committed Jul 1, 2020
1 parent 676698a commit 2e3a5c6
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 18 deletions.
7 changes: 6 additions & 1 deletion packages/asset-swapper/CHANGELOG.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
]
},
Expand Down
13 changes: 10 additions & 3 deletions packages/asset-swapper/src/swap_quoter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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[]>> = [];
Expand Down
51 changes: 39 additions & 12 deletions packages/asset-swapper/src/utils/protocol_fee_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,38 @@ import * as heartbeats from 'heartbeats';
import { constants } from '../constants';
import { SwapQuoterError } from '../types';

const MAX_ERROR_COUNT = 5;

export class ProtocolFeeUtils {
public gasPriceEstimation: BigNumber;
private static _instance: ProtocolFeeUtils;
private readonly _ethGasStationUrl!: string;
private readonly _gasPriceHeart: any;
private readonly _ethGasStationUrl: string;
private _gasPriceEstimation: BigNumber = constants.ZERO_AMOUNT;
private _errorCount: number = 0;

constructor(
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;
}
}

Expand All @@ -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 {
Expand All @@ -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();
});
}
}
10 changes: 8 additions & 2 deletions packages/asset-swapper/test/utils/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 = (
Expand Down

0 comments on commit 2e3a5c6

Please sign in to comment.