Skip to content

Commit

Permalink
Refactor services to align with eventual RpcService
Browse files Browse the repository at this point in the history
In a future commit, we are adding an RpcService class to
`network-controller` to handle automatic failovers. This class is
different from the existing service classes in a couple of ways:

- RpcService will make use of a new `createServicePolicy` function which
  encapsulates retry and circuit breaker logic. This will relieve the
  consuming module for RpcService from needing to test this logic.
- RpcService will have `onBreak` and `onDegraded` methods instead of
  taking `onBreak` and `onDegraded` callbacks as constructor options.
  This is reflected not only in the class but also in the abstract RPC
  service interface.

To these ends, this commit makes the following changes to
`CodefiTokenPricesServiceV2` in `assets-controllers` and
`ClientConfigApiService` in `remote-feature-flag-controller`:

- Refactor service class to use `createServicePolicy`
- Update the abstract service class interface to extend `ServicePolicy`
  from `controller-utils`, and prepend `I` to the type to indicate that
  it's not a superclass, but an interface
- Deprecate the `onBreak` and `onDegraded` constructor options, and add
  methods instead
  • Loading branch information
mcmire committed Jan 23, 2025
1 parent a811bf6 commit dba35e9
Show file tree
Hide file tree
Showing 19 changed files with 562 additions and 1,186 deletions.
4 changes: 2 additions & 2 deletions eslint-warning-thresholds.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"@typescript-eslint/no-unsafe-enum-comparison": 34,
"@typescript-eslint/no-unused-vars": 41,
"@typescript-eslint/prefer-promise-reject-errors": 33,
"@typescript-eslint/prefer-readonly": 147,
"@typescript-eslint/prefer-readonly": 144,
"import-x/namespace": 189,
"import-x/no-named-as-default": 1,
"import-x/no-named-as-default-member": 8,
Expand All @@ -19,7 +19,7 @@
"n/no-unsupported-features/node-builtins": 4,
"n/prefer-global/text-encoder": 4,
"n/prefer-global/text-decoder": 4,
"prettier/prettier": 116,
"prettier/prettier": 115,
"promise/always-return": 3,
"promise/catch-or-return": 2,
"promise/param-names": 8,
Expand Down
9 changes: 9 additions & 0 deletions packages/assets-controllers/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add `onBreak` and `onDegraded` methods to `CodefiTokenPricesServiceV2`
- These serve the same purpose as the `onBreak` and `onDegraded` constructor options, but align more closely with the Cockatiel policy API.

### Changed

- Deprecate `ClientConfigApiService` constructor options `onBreak` and `onDegraded` in favor of methods

## [46.0.1]

### Changed
Expand Down
1 change: 0 additions & 1 deletion packages/assets-controllers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@
"async-mutex": "^0.5.0",
"bitcoin-address-validation": "^2.2.3",
"bn.js": "^5.2.1",
"cockatiel": "^3.1.2",
"immer": "^9.0.6",
"lodash": "^4.17.21",
"multiformats": "^13.1.0",
Expand Down
22 changes: 9 additions & 13 deletions packages/assets-controllers/src/TokenRatesController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
} from '../../network-controller/tests/helpers';
import { TOKEN_PRICES_BATCH_SIZE } from './assetsUtil';
import type {
AbstractTokenPricesService,
IAbstractTokenPricesService,
TokenPrice,
TokenPricesByTokenAddress,
} from './token-prices-service/abstract-token-prices-service';
Expand Down Expand Up @@ -2093,11 +2093,9 @@ describe('TokenRatesController', () => {
price: 0.002,
},
}),
validateCurrencySupported: jest.fn().mockReturnValue(
false,
// Cast used because this method has an assertion in the return
// value that I don't know how to type properly with Jest's mock.
) as unknown as AbstractTokenPricesService['validateCurrencySupported'],
validateCurrencySupported(_currency: unknown): _currency is string {
return false;
},
});
nock('https://min-api.cryptocompare.com')
.get('/data/price')
Expand Down Expand Up @@ -2288,11 +2286,9 @@ describe('TokenRatesController', () => {
value: 0.002,
},
}),
validateChainIdSupported: jest.fn().mockReturnValue(
false,
// Cast used because this method has an assertion in the return
// value that I don't know how to type properly with Jest's mock.
) as unknown as AbstractTokenPricesService['validateChainIdSupported'],
validateChainIdSupported(_chainId: unknown): _chainId is Hex {
return false;
},
});
await withController(
{
Expand Down Expand Up @@ -2758,8 +2754,8 @@ async function callUpdateExchangeRatesMethod({
* @returns The built mock token prices service.
*/
function buildMockTokenPricesService(
overrides: Partial<AbstractTokenPricesService> = {},
): AbstractTokenPricesService {
overrides: Partial<IAbstractTokenPricesService> = {},
): IAbstractTokenPricesService {
return {
async fetchTokenPrices() {
return {};
Expand Down
8 changes: 4 additions & 4 deletions packages/assets-controllers/src/TokenRatesController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { isEqual } from 'lodash';

import { reduceInBatchesSerially, TOKEN_PRICES_BATCH_SIZE } from './assetsUtil';
import { fetchExchangeRate as fetchNativeCurrencyExchangeRate } from './crypto-compare-service';
import type { AbstractTokenPricesService } from './token-prices-service/abstract-token-prices-service';
import type { IAbstractTokenPricesService } from './token-prices-service/abstract-token-prices-service';
import { getNativeTokenAddress } from './token-prices-service/codefi-v2';
import type {
TokensControllerGetStateAction,
Expand Down Expand Up @@ -238,7 +238,7 @@ export class TokenRatesController extends StaticIntervalPollingController<TokenR

#pollState = PollState.Inactive;

#tokenPricesService: AbstractTokenPricesService;
readonly #tokenPricesService: IAbstractTokenPricesService;

#inProcessExchangeRateUpdates: Record<`${Hex}:${string}`, Promise<void>> = {};

Expand Down Expand Up @@ -275,7 +275,7 @@ export class TokenRatesController extends StaticIntervalPollingController<TokenR
}: {
interval?: number;
disabled?: boolean;
tokenPricesService: AbstractTokenPricesService;
tokenPricesService: IAbstractTokenPricesService;
messenger: TokenRatesControllerMessenger;
state?: Partial<TokenRatesControllerState>;
}) {
Expand Down Expand Up @@ -687,7 +687,7 @@ export class TokenRatesController extends StaticIntervalPollingController<TokenR
let contractNativeInformations;
const tokenPricesByTokenAddress = await reduceInBatchesSerially<
Hex,
Awaited<ReturnType<AbstractTokenPricesService['fetchTokenPrices']>>
Awaited<ReturnType<IAbstractTokenPricesService['fetchTokenPrices']>>
>({
values: [...tokenAddresses].sort(),
batchSize: TOKEN_PRICES_BATCH_SIZE,
Expand Down
4 changes: 2 additions & 2 deletions packages/assets-controllers/src/assetsUtil.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { add0x, type Hex } from '@metamask/utils';
import * as assetsUtil from './assetsUtil';
import { TOKEN_PRICES_BATCH_SIZE } from './assetsUtil';
import type { Nft, NftMetadata } from './NftController';
import type { AbstractTokenPricesService } from './token-prices-service';
import type { IAbstractTokenPricesService } from './token-prices-service';

const DEFAULT_IPFS_URL_FORMAT = 'ipfs://';
const ALTERNATIVE_IPFS_URL_FORMAT = 'ipfs://ipfs/';
Expand Down Expand Up @@ -772,7 +772,7 @@ function buildAddress(number: number) {
*
* @returns The mocked functions of token prices service.
*/
function createMockPriceService(): AbstractTokenPricesService {
function createMockPriceService(): IAbstractTokenPricesService {
return {
validateChainIdSupported(_chainId: unknown): _chainId is Hex {
return true;
Expand Down
6 changes: 3 additions & 3 deletions packages/assets-controllers/src/assetsUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { remove0x } from '@metamask/utils';
import BN from 'bn.js';

import type { Nft, NftMetadata } from './NftController';
import type { AbstractTokenPricesService } from './token-prices-service';
import type { IAbstractTokenPricesService } from './token-prices-service';
import { type ContractExchangeRates } from './TokenRatesController';

/**
Expand Down Expand Up @@ -387,7 +387,7 @@ export async function fetchTokenContractExchangeRates({
tokenAddresses,
chainId,
}: {
tokenPricesService: AbstractTokenPricesService;
tokenPricesService: IAbstractTokenPricesService;
nativeCurrency: string;
tokenAddresses: Hex[];
chainId: Hex;
Expand All @@ -403,7 +403,7 @@ export async function fetchTokenContractExchangeRates({

const tokenPricesByTokenAddress = await reduceInBatchesSerially<
Hex,
Awaited<ReturnType<AbstractTokenPricesService['fetchTokenPrices']>>
Awaited<ReturnType<IAbstractTokenPricesService['fetchTokenPrices']>>
>({
values: [...tokenAddresses].sort(),
batchSize: TOKEN_PRICES_BATCH_SIZE,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ServicePolicy } from '@metamask/controller-utils';
import type { Hex } from '@metamask/utils';

/**
Expand Down Expand Up @@ -49,11 +50,11 @@ export type TokenPricesByTokenAddress<
* @template Currency - A type union of valid arguments for the `currency`
* argument to `fetchTokenPrices`.
*/
export type AbstractTokenPricesService<
export type IAbstractTokenPricesService<
ChainId extends Hex = Hex,
TokenAddress extends Hex = Hex,
Currency extends string = string,
> = {
> = Partial<Pick<ServicePolicy, 'onBreak' | 'onDegraded'>> & {
/**
* Retrieves prices in the given currency for the tokens identified by the
* given addresses which are expected to live on the given chain.
Expand Down
Loading

0 comments on commit dba35e9

Please sign in to comment.