271 lines
11 KiB
TypeScript
271 lines
11 KiB
TypeScript
import { ChainId } from '@0x/contract-addresses';
|
|
import { BigNumber } from '@0x/utils';
|
|
import Redis from 'ioredis';
|
|
|
|
import { ONE_MINUTE_MS } from '../../src/core/constants';
|
|
import { ERC20Owner } from '../../src/core/types';
|
|
import { CacheClient } from '../../src/utils/cache_client';
|
|
import { REDIS_PORT } from '../constants';
|
|
import { setupDependenciesAsync, TeardownDependenciesFunctionHandle } from '../test_utils/deployment';
|
|
|
|
jest.setTimeout(ONE_MINUTE_MS * 2);
|
|
let teardownDependencies: TeardownDependenciesFunctionHandle;
|
|
|
|
describe('CacheClient', () => {
|
|
let redis: Redis;
|
|
let cacheClient: CacheClient;
|
|
|
|
const chainId = ChainId.Ganache;
|
|
|
|
const makerA = '0x1111111111111111111111111111111111111111';
|
|
const makerB = '0x2222222222222222222222222222222222222222';
|
|
const makerC = '0x3333333333333333333333333333333333333333';
|
|
|
|
const tokenA = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
|
|
const tokenB = '0x6B175474E89094C44Da98b954EedeAC495271d0F';
|
|
const tokenC = '0xdAC17F958D2ee523a2206206994597C13D831ec7';
|
|
|
|
// compareFn is used to determine the order of ERC20Owner elements
|
|
const compareFn = (a: ERC20Owner, b: ERC20Owner) => a.owner.localeCompare(b.owner);
|
|
|
|
beforeAll(async () => {
|
|
teardownDependencies = await setupDependenciesAsync(['redis']);
|
|
redis = new Redis(REDIS_PORT);
|
|
cacheClient = new CacheClient(redis);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await cacheClient.closeAsync();
|
|
if (!teardownDependencies()) {
|
|
throw new Error('Failed to tear down dependencies');
|
|
}
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await redis.flushdb();
|
|
});
|
|
|
|
describe('addERC20OwnerAsync', () => {
|
|
it('adds pending address to observed addresses without error', async () => {
|
|
expect(cacheClient.addERC20OwnerAsync(chainId, { owner: makerA, token: tokenA })).resolves.toEqual(void 0);
|
|
});
|
|
});
|
|
|
|
describe('getERC20OwnersAsync', () => {
|
|
const addresses = [
|
|
{ owner: makerA, token: tokenA },
|
|
{ owner: makerB, token: tokenB },
|
|
{ owner: makerC, token: tokenC },
|
|
];
|
|
|
|
it('fetches maker token addresses in correct format', async () => {
|
|
addresses.forEach(async (address) => {
|
|
await cacheClient.addERC20OwnerAsync(chainId, address);
|
|
});
|
|
const cachedAddresses = await cacheClient.getERC20OwnersAsync(chainId);
|
|
expect(cachedAddresses.sort(compareFn)).toEqual(addresses.sort(compareFn));
|
|
});
|
|
|
|
it('fetches empty arrays if no addresses are found in the set', async () => {
|
|
expect(await cacheClient.getERC20OwnersAsync(chainId)).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('setERC20OwnerBalancesAsync', () => {
|
|
const addresses = [
|
|
{ owner: makerA, token: tokenA },
|
|
{ owner: makerB, token: tokenB },
|
|
{ owner: makerC, token: tokenC },
|
|
];
|
|
const balances = [new BigNumber(1), new BigNumber(2), new BigNumber(3)];
|
|
|
|
it('sets balances in the cache without error', async () => {
|
|
expect(cacheClient.setERC20OwnerBalancesAsync(chainId, addresses, balances)).resolves.toEqual(void 0);
|
|
});
|
|
|
|
it('should fail when addresses do not match balances', async () => {
|
|
expect(cacheClient.setERC20OwnerBalancesAsync(chainId, addresses, balances.slice(0, -1))).rejects.toThrow(
|
|
'balances',
|
|
);
|
|
});
|
|
|
|
it('should not fail when addresses are empty', async () => {
|
|
expect(cacheClient.setERC20OwnerBalancesAsync(chainId, [], [])).resolves.toEqual(void 0);
|
|
});
|
|
});
|
|
|
|
describe('getERC20OwnerBalancesAsync', () => {
|
|
const addresses = [
|
|
{ owner: makerA, token: tokenA },
|
|
{ owner: makerB, token: tokenB },
|
|
{ owner: makerC, token: tokenC },
|
|
];
|
|
const balances = [new BigNumber(1), new BigNumber(2), new BigNumber(3)];
|
|
|
|
beforeEach(async () => {
|
|
await cacheClient.setERC20OwnerBalancesAsync(chainId, addresses, balances);
|
|
});
|
|
|
|
it('fetches correct balances from the cache', async () => {
|
|
expect(await cacheClient.getERC20OwnerBalancesAsync(chainId, addresses)).toEqual([
|
|
new BigNumber(1),
|
|
new BigNumber(2),
|
|
new BigNumber(3),
|
|
]);
|
|
});
|
|
|
|
it('returns null balances if addresses are not in the cache', async () => {
|
|
const badAddresses = [
|
|
{ owner: makerA, token: tokenA },
|
|
{ owner: makerB, token: tokenB },
|
|
{ owner: '0x0000000000000000000000000000000000000000', token: tokenC },
|
|
];
|
|
expect(await cacheClient.getERC20OwnerBalancesAsync(chainId, badAddresses)).toEqual([
|
|
new BigNumber(1),
|
|
new BigNumber(2),
|
|
null,
|
|
]);
|
|
});
|
|
|
|
it('returns null balances if addresses from a different chain are supplied', async () => {
|
|
expect(await cacheClient.getERC20OwnerBalancesAsync(ChainId.PolygonMumbai, addresses)).toEqual([
|
|
null,
|
|
null,
|
|
null,
|
|
]);
|
|
});
|
|
|
|
it('returns an empty array if addresses are empty', async () => {
|
|
expect(await cacheClient.getERC20OwnerBalancesAsync(chainId, [])).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('evictZeroBalancesAsync', () => {
|
|
const addresses = [
|
|
{ owner: makerA, token: tokenA },
|
|
{ owner: makerB, token: tokenB },
|
|
{ owner: makerC, token: tokenC },
|
|
];
|
|
|
|
it('evicts zeroed entries from the cache', async () => {
|
|
addresses.forEach(async (address) => {
|
|
await cacheClient.addERC20OwnerAsync(chainId, address);
|
|
});
|
|
let cachedAddresses = await cacheClient.getERC20OwnersAsync(chainId);
|
|
expect(cachedAddresses.sort(compareFn)).toEqual(addresses.sort(compareFn));
|
|
|
|
const balances = [new BigNumber(1), new BigNumber(2), new BigNumber(0)];
|
|
await cacheClient.setERC20OwnerBalancesAsync(chainId, addresses, balances);
|
|
|
|
const numEvicted = await cacheClient.evictZeroBalancesAsync(chainId);
|
|
expect(numEvicted).toEqual(1);
|
|
cachedAddresses = await cacheClient.getERC20OwnersAsync(chainId);
|
|
expect(cachedAddresses.sort(compareFn)).toEqual(addresses.slice(0, 2).sort(compareFn));
|
|
});
|
|
|
|
it('does not evict any entries if there are no stale balances', async () => {
|
|
addresses.forEach(async (address) => {
|
|
await cacheClient.addERC20OwnerAsync(chainId, address);
|
|
});
|
|
let cachedAddresses = await cacheClient.getERC20OwnersAsync(chainId);
|
|
expect(cachedAddresses.sort(compareFn)).toEqual(addresses.sort(compareFn));
|
|
|
|
const balances = [new BigNumber(1), new BigNumber(2), new BigNumber(3)];
|
|
await cacheClient.setERC20OwnerBalancesAsync(chainId, addresses, balances);
|
|
|
|
const numEvicted = await cacheClient.evictZeroBalancesAsync(chainId);
|
|
expect(numEvicted).toEqual(0);
|
|
cachedAddresses = await cacheClient.getERC20OwnersAsync(chainId);
|
|
expect(cachedAddresses.sort(compareFn)).toEqual(addresses.sort(compareFn));
|
|
});
|
|
|
|
it('does not error if the address set is empty', async () => {
|
|
const owners = await cacheClient.getERC20OwnersAsync(chainId);
|
|
expect(owners.length).toEqual(0);
|
|
const numEvicted = await cacheClient.evictZeroBalancesAsync(chainId);
|
|
expect(numEvicted).toEqual(0);
|
|
});
|
|
});
|
|
|
|
describe('coolDownMakerForPair', () => {
|
|
const makerId1 = 'makerId1';
|
|
const takerToken = 'takerToken';
|
|
const makerToken = 'makerToken';
|
|
|
|
it('should add new makers to the cooling down set for a pair', async () => {
|
|
const isUpdated = await cacheClient.addMakerToCooldownAsync(
|
|
makerId1,
|
|
Date.now(),
|
|
chainId,
|
|
takerToken,
|
|
makerToken,
|
|
);
|
|
expect(isUpdated).toEqual(true);
|
|
});
|
|
|
|
it('should update endTime to a time later than existing endTime', async () => {
|
|
const now = Date.now();
|
|
const oneMinuteLater = now + ONE_MINUTE_MS;
|
|
await cacheClient.addMakerToCooldownAsync(makerId1, now, chainId, takerToken, makerToken);
|
|
const isUpdated = await cacheClient.addMakerToCooldownAsync(
|
|
makerId1,
|
|
oneMinuteLater,
|
|
chainId,
|
|
makerToken,
|
|
takerToken,
|
|
);
|
|
expect(isUpdated).toEqual(true);
|
|
});
|
|
|
|
it('should not update endTime to a time earlier than existing endTime', async () => {
|
|
const now = Date.now();
|
|
const oneMinuteEarlier = now - ONE_MINUTE_MS;
|
|
await cacheClient.addMakerToCooldownAsync(makerId1, now, chainId, takerToken, makerToken);
|
|
const isUpdated = await cacheClient.addMakerToCooldownAsync(
|
|
makerId1,
|
|
oneMinuteEarlier,
|
|
chainId,
|
|
takerToken,
|
|
makerToken,
|
|
);
|
|
expect(isUpdated).toEqual(false);
|
|
});
|
|
});
|
|
|
|
describe('getCoolingDownMakersForPair', () => {
|
|
const makerId1 = 'makerId1';
|
|
const makerId2 = 'makerId2';
|
|
const takerToken = 'takerToken';
|
|
const otherTakerToken = 'otherTakerToken';
|
|
const makerToken = 'makerToken';
|
|
|
|
it('should get all makers that are cooling down', async () => {
|
|
const now = Date.now();
|
|
const oneMinuteLater = now + ONE_MINUTE_MS;
|
|
await cacheClient.addMakerToCooldownAsync(makerId1, oneMinuteLater, chainId, takerToken, makerToken);
|
|
await cacheClient.addMakerToCooldownAsync(makerId2, oneMinuteLater, chainId, takerToken, makerToken);
|
|
const result = await cacheClient.getMakersInCooldownForPairAsync(chainId, takerToken, makerToken, now);
|
|
expect(result).toEqual([makerId1, makerId2]);
|
|
});
|
|
|
|
it('should not include makers whose cooling down periods already ended', async () => {
|
|
const now = Date.now();
|
|
const oneMinuteEarlier = now - ONE_MINUTE_MS;
|
|
const oneMinuteLater = now + ONE_MINUTE_MS;
|
|
await cacheClient.addMakerToCooldownAsync(makerId1, oneMinuteEarlier, chainId, takerToken, makerToken);
|
|
await cacheClient.addMakerToCooldownAsync(makerId2, oneMinuteLater, chainId, takerToken, makerToken);
|
|
const result = await cacheClient.getMakersInCooldownForPairAsync(chainId, takerToken, makerToken, now);
|
|
expect(result).toEqual([makerId2]);
|
|
});
|
|
|
|
it('should only include makers that are cooling down for this pair', async () => {
|
|
const now = Date.now();
|
|
const oneMinuteLater = now + ONE_MINUTE_MS;
|
|
await cacheClient.addMakerToCooldownAsync(makerId1, oneMinuteLater, chainId, takerToken, makerToken);
|
|
await cacheClient.addMakerToCooldownAsync(makerId2, oneMinuteLater, chainId, otherTakerToken, makerToken);
|
|
const result = await cacheClient.getMakersInCooldownForPairAsync(chainId, takerToken, makerToken, now);
|
|
expect(result).toEqual([makerId1]);
|
|
});
|
|
});
|
|
});
|