Skip to content

Commit

Permalink
fix: multichainToken rate for non evm
Browse files Browse the repository at this point in the history
  • Loading branch information
salimtb committed Jan 20, 2025
1 parent 409e083 commit ac5d0e4
Show file tree
Hide file tree
Showing 4 changed files with 928 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,389 @@
import { ControllerMessenger } from '@metamask/base-controller';
import nock from 'nock';
import { useFakeTimers } from 'sinon';

import { MultiChainRatesController } from './MultiChainRatesController';
import {
multiChainAssetsControllerName,
type AllowedActions,
type AllowedEvents,
type MultichainAssetsControllerState,
} from './types';
import { advanceTime } from '../../../../../tests/helpers';

const setupController = ({
config,
tokens = {
allNonEvmTokens: {
account1: [
'bip122:000000000019d6689c085ae165831e93/slip44:0',
'eip155:1/slip44:60',
'solana:So11111111111111111111111111111111111111112',
'solana:EPjFWdd5AufqSSqeM2qZQznF1bpP1Pbjx38cVxBYNRTB',
'solana:JUP9a7mHxhR4WGcK6RBtkG7xMbRBVU7Q8tqsyHG4d3Z',
],
account2: ['solana:test1', 'solana:test2'],
},
},
}: {
config?: Partial<ConstructorParameters<typeof MultiChainRatesController>[0]>;
tokens?: Partial<MultichainAssetsControllerState>;
} = {}) => {
const messenger = new ControllerMessenger<AllowedActions, AllowedEvents>();

messenger.registerActionHandler('AccountsController:getState', () => ({
accounts: {
account1: {
type: 'eip155:eoa',
id: 'account1',
options: {},
metadata: { name: 'Test Account' },
address: '0x123',
methods: [],
},
},
selectedAccount: 'account1',
internalAccounts: { accounts: {}, selectedAccount: 'account1' },
}));

messenger.registerActionHandler(
'MultiChainAssetsController:getState',
jest.fn().mockImplementation(() => tokens),
);

const multiChainRatesControllerMessenger = messenger.getRestricted({
name: 'MultiChainRatesController',
allowedActions: [
'AccountsController:getState',
'MultiChainAssetsController:getState',
],
allowedEvents: [
'AccountsController:selectedAccountChange',
'MultiChainAssetsController:stateChange',
'KeyringController:lock',
'KeyringController:unlock',
],
});

return {
controller: new MultiChainRatesController({
messenger: multiChainRatesControllerMessenger,
...config,
includeUsdRate: config?.includeUsdRate ?? false,
}),
messenger,
};
};

describe('MultiChainRatesController', () => {
let clock: sinon.SinonFakeTimers;

const mockedDate = 1705760550000;

beforeEach(() => {
clock = useFakeTimers();
jest.spyOn(Date, 'now').mockReturnValue(mockedDate);
});

afterEach(() => {
clock.restore();
jest.restoreAllMocks();
});
describe('constructor', () => {
it('should set default state', () => {
const { controller } = setupController();
expect(controller.state).toStrictEqual({ conversionRates: {} });
});

it('should poll and update rates in the right interval', async () => {
const pollSpy = jest.spyOn(
MultiChainRatesController.prototype,
'_executePoll',
);

const interval = 10;
const { controller } = setupController({ config: { interval } });

controller.startPolling({ accountId: '123' });

await advanceTime({ clock, duration: 1 });
expect(pollSpy).toHaveBeenCalled();
expect(pollSpy).not.toHaveBeenCalledTimes(2);

await advanceTime({ clock, duration: interval * 1.5 });
expect(pollSpy).toHaveBeenCalledTimes(2);
});
});

it('should poll and update rates on poll', async () => {
nock('http://localhost:3000')
.get(
'/v3/spot-prices?assetIds=bip122:000000000019d6689c085ae165831e93/slip44:0,eip155:1/slip44:60,solana:So11111111111111111111111111111111111111112,solana:EPjFWdd5AufqSSqeM2qZQznF1bpP1Pbjx38cVxBYNRTB,solana:JUP9a7mHxhR4WGcK6RBtkG7xMbRBVU7Q8tqsyHG4d3ZfalsevsCurrency=usd',
)
.reply(200, {
'bip122:000000000019d6689c085ae165831e93/slip44:0': {
usd: 45000.0,
},
'eip155:1/slip44:60': {
usd: 3200.0,
},
'solana:So11111111111111111111111111111111111111112': {
usd: 25.0,
},
'solana:EPjFWdd5AufqSSqeM2qZQznF1bpP1Pbjx38cVxBYNRTB': {
usd: 1.0,
},
'solana:JUP9a7mHxhR4WGcK6RBtkG7xMbRBVU7Q8tqsyHG4d3Z': {
usd: 0.01,
},
})
.persist();
const { controller } = setupController();
// initial state
expect(controller.state.conversionRates).toStrictEqual({});

await controller._executePoll({ accountId: 'account1' });
expect(controller.state.conversionRates).toStrictEqual({
'bip122:000000000019d6689c085ae165831e93/slip44:0': {
rate: '45000',
conversionTime: 1705760550000,
expirationTime: 1705846950000,
},
'eip155:1/slip44:60': {
rate: '3200',
conversionTime: 1705760550000,
expirationTime: 1705846950000,
},
'solana:So11111111111111111111111111111111111111112': {
rate: '25',
conversionTime: 1705760550000,
expirationTime: 1705846950000,
},
'solana:EPjFWdd5AufqSSqeM2qZQznF1bpP1Pbjx38cVxBYNRTB': {
rate: '1',
conversionTime: 1705760550000,
expirationTime: 1705846950000,
},
'solana:JUP9a7mHxhR4WGcK6RBtkG7xMbRBVU7Q8tqsyHG4d3Z': {
rate: '0.01',
conversionTime: 1705760550000,
expirationTime: 1705846950000,
},
});
});

it('should update rates when token is added and MultiChainAssetsController:stateChange event is triggered', async () => {
nock('http://localhost:3000')
.get(
'/v3/spot-prices?assetIds=bip122:000000000019d6689c085ae165831e93/slip44:0,eip155:1/slip44:60,solana:So11111111111111111111111111111111111111112,solana:EPjFWdd5AufqSSqeM2qZQznF1bpP1Pbjx38cVxBYNRTB,solana:JUP9a7mHxhR4WGcK6RBtkG7xMbRBVU7Q8tqsyHG4d3ZfalsevsCurrency=usd',
)
.reply(200, {
'bip122:000000000019d6689c085ae165831e93/slip44:0': {
usd: 45000.0,
},
'eip155:1/slip44:60': {
usd: 3200.0,
},
'solana:So11111111111111111111111111111111111111112': {
usd: 25.0,
},
'solana:EPjFWdd5AufqSSqeM2qZQznF1bpP1Pbjx38cVxBYNRTB': {
usd: 1.0,
},
'solana:JUP9a7mHxhR4WGcK6RBtkG7xMbRBVU7Q8tqsyHG4d3Z': {
usd: 0.01,
},
})
.get('/v3/spot-prices?assetIds=solana:testfalsevsCurrency=usd')
.reply(200, {
'solana:test': {
usd: 1.0,
conversionTime: 1705760550000,
expirationTime: 1705846950000,
},
})
.persist();

const interval = 10;
const { controller, messenger } = setupController({ config: { interval } });

controller.startPolling({ accountId: 'account1' });

const expectedRates = {
'bip122:000000000019d6689c085ae165831e93/slip44:0': {
rate: '45000',
conversionTime: 1705760550000,
expirationTime: 1705846950000,
},
'eip155:1/slip44:60': {
rate: '3200',
conversionTime: 1705760550000,
expirationTime: 1705846950000,
},
'solana:So11111111111111111111111111111111111111112': {
rate: '25',
conversionTime: 1705760550000,
expirationTime: 1705846950000,
},
'solana:EPjFWdd5AufqSSqeM2qZQznF1bpP1Pbjx38cVxBYNRTB': {
rate: '1',
conversionTime: 1705760550000,
expirationTime: 1705846950000,
},
'solana:JUP9a7mHxhR4WGcK6RBtkG7xMbRBVU7Q8tqsyHG4d3Z': {
rate: '0.01',
conversionTime: 1705760550000,
expirationTime: 1705846950000,
},
};

await advanceTime({ clock, duration: interval * 1.5 });

expect(controller.state.conversionRates).toStrictEqual(expectedRates);

const newTokenList = {
allNonEvmTokens: {
account1: ['solana:test'],
},
};
await advanceTime({ clock, duration: interval * 1.5 });

messenger.publish(
`${multiChainAssetsControllerName}:stateChange`,
{
...newTokenList,
metadata: {},
allNonEvmIgnoredTokens: {},
},
[],
);

await advanceTime({ clock, duration: interval * 1.5 });

expect(controller.state.conversionRates).toStrictEqual({
'solana:test': {
rate: '1',
conversionTime: 1705760550000,
expirationTime: 1705846950000,
},
});
});

it('should stop polling and rates on interval if unlocked keyring is locked', async () => {
const { controller, messenger } = setupController();

controller.setIntervalLength(10);
await controller.start();

messenger.publish('KeyringController:lock');

await advanceTime({ clock, duration: 10 });

expect(controller.state.conversionRates).toStrictEqual({});
expect(controller.isActive).toBe(false);

messenger.publish('KeyringController:unlock');

await advanceTime({ clock, duration: 10 });
expect(controller.isActive).toBe(true);
});

it('should not update rates if the selected account has been changed', async () => {
nock('http://localhost:3000')
.get(
'/v3/spot-prices?assetIds=bip122:000000000019d6689c085ae165831e93/slip44:0,eip155:1/slip44:60,solana:So11111111111111111111111111111111111111112,solana:EPjFWdd5AufqSSqeM2qZQznF1bpP1Pbjx38cVxBYNRTB,solana:JUP9a7mHxhR4WGcK6RBtkG7xMbRBVU7Q8tqsyHG4d3ZfalsevsCurrency=usd',
)
.reply(200, {
'bip122:000000000019d6689c085ae165831e93/slip44:0': {
usd: 45000.0,
},
'eip155:1/slip44:60': {
usd: 3200.0,
},
'solana:So11111111111111111111111111111111111111112': {
usd: 25.0,
},
'solana:EPjFWdd5AufqSSqeM2qZQznF1bpP1Pbjx38cVxBYNRTB': {
usd: 1.0,
},
'solana:JUP9a7mHxhR4WGcK6RBtkG7xMbRBVU7Q8tqsyHG4d3Z': {
usd: 0.01,
},
})
.get(
'/v3/spot-prices?assetIds=solana:test1,solana:test2falsevsCurrency=usd',
)
.reply(200, {
'solana:test1': {
usd: 1.0,
},
'solana:test2': {
usd: 2.0,
},
})
.persist();

const interval = 10;
const { controller, messenger } = setupController({ config: { interval } });
await controller._executePoll({ accountId: 'account1' });

await advanceTime({ clock, duration: interval * 1.5 });

expect(controller.state.conversionRates).toStrictEqual({
'bip122:000000000019d6689c085ae165831e93/slip44:0': {
rate: '45000',
conversionTime: 1705760550000,
expirationTime: 1705846950000,
},
'eip155:1/slip44:60': {
rate: '3200',
conversionTime: 1705760550000,
expirationTime: 1705846950000,
},
'solana:So11111111111111111111111111111111111111112': {
rate: '25',
conversionTime: 1705760550000,
expirationTime: 1705846950000,
},
'solana:EPjFWdd5AufqSSqeM2qZQznF1bpP1Pbjx38cVxBYNRTB': {
rate: '1',
conversionTime: 1705760550000,
expirationTime: 1705846950000,
},
'solana:JUP9a7mHxhR4WGcK6RBtkG7xMbRBVU7Q8tqsyHG4d3Z': {
rate: '0.01',
conversionTime: 1705760550000,
expirationTime: 1705846950000,
},
});

messenger.publish('AccountsController:selectedAccountChange', {
id: 'account2',
type: 'eip155:eoa',
options: {},
address: '0x123',
methods: [],
metadata: {
name: 'Test Account',
importTime: 1,
keyring: { type: 'eip155:eoa' },
},
scopes: [],
});

await advanceTime({ clock, duration: interval * 1.5 });

expect(controller.state.conversionRates).toStrictEqual({
'solana:test1': {
rate: '1',
conversionTime: 1705760550000,
expirationTime: 1705846950000,
},
'solana:test2': {
rate: '2',
conversionTime: 1705760550000,
expirationTime: 1705846950000,
},
});
});
});
Loading

0 comments on commit ac5d0e4

Please sign in to comment.