Skip to content

Commit

Permalink
feat: sort stake pools by fixed cost
Browse files Browse the repository at this point in the history
  • Loading branch information
lgobbi-atix committed Jul 8, 2022
1 parent 161ccd8 commit 6e1d6e4
Show file tree
Hide file tree
Showing 8 changed files with 2,545 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -813,27 +813,36 @@ FROM last_pool_update AS pool_update
LEFT JOIN last_pool_retire AS pool_retire
ON pool_update.hash_id = pool_retire.hash_id`;

const sortFieldMapping: Record<string, string> = {
name: "lower((pod.json -> 'name')::TEXT)"
const sortFieldMapping: Record<string, { field: string; secondary?: string[] }> = {
cost: { field: 'fixed_cost', secondary: ['margin'] },
name: { field: "lower((pod.json -> 'name')::TEXT)" }
};

const mapSort = (sort: OrderByOptions | undefined) => {
if (!sort) return [];
const mapping = sortFieldMapping[sort.field];
if (!mapping) return [{ field: sort.field, order: sort.order }];
const secondarySorts = mapping.secondary?.map((field) => ({ field, order: sort.order })) ?? [];
return [{ field: mapping.field, order: sort.order }, ...secondarySorts];
};

export const withSort = (query: string, sort?: StakePoolQueryOptions['sort'], defaultSort?: OrderByOptions[]) => {
if (!sort?.field && defaultSort) {
const defaultMappedSort = defaultSort.map((s) => ({ field: sortFieldMapping[s.field] || s.field, order: s.order }));
const defaultMappedSort = defaultSort.flatMap(mapSort);
return orderBy(query, defaultMappedSort);
}
if (!sort?.field) return query;
const sortType = getStakePoolSortType(sort.field);
const mappedSort = { field: sortFieldMapping[sort.field] || sort.field, order: sort.order };
const mappedSort = mapSort(sort);
switch (sortType) {
case 'data':
return orderBy(query, [mappedSort, { field: 'pool_id', order: 'asc' }]);
return orderBy(query, [...mappedSort, { field: 'pool_id', order: 'asc' }]);
case 'metrics':
return orderBy(query, [mappedSort, { field: 'id', order: 'asc' }]);
return orderBy(query, [...mappedSort, { field: 'id', order: 'asc' }]);
case 'apy':
return orderBy(query, [mappedSort, { field: 'hash_id', order: 'asc' }]);
return orderBy(query, [...mappedSort, { field: 'hash_id', order: 'asc' }]);
default:
return orderBy(query, [mappedSort]);
return orderBy(query, [...mappedSort]);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,62 @@ Object {

exports[`RewardsBuilder getAccountBalance returns AccountBalanceModel 1`] = `
Object {
"balance": "48642468465",
"balance": "97675046319",
}
`;

exports[`RewardsBuilder getRewardsHistory returns RewardEpochModel when there is epochs field 1`] = `Array []`;

exports[`RewardsBuilder getRewardsHistory returns RewardEpochModel when there is no epochs field 1`] = `Array []`;
exports[`RewardsBuilder getRewardsHistory returns RewardEpochModel when there is no epochs field 1`] = `
Array [
Object {
"address": "stake_test1uqfu74w3wh4gfzu8m6e7j987h4lq9r3t7ef5gaw497uu85qsqfy27",
"epoch": "169",
"quantity": "9225614904",
},
Object {
"address": "stake_test1uqfu74w3wh4gfzu8m6e7j987h4lq9r3t7ef5gaw497uu85qsqfy27",
"epoch": "88",
"quantity": "10994270921",
},
Object {
"address": "stake_test1uqfu74w3wh4gfzu8m6e7j987h4lq9r3t7ef5gaw497uu85qsqfy27",
"epoch": "85",
"quantity": "12597745314",
},
Object {
"address": "stake_test1uqfu74w3wh4gfzu8m6e7j987h4lq9r3t7ef5gaw497uu85qsqfy27",
"epoch": "161",
"quantity": "16214946715",
},
]
`;

exports[`RewardsBuilder getRewardsHistory returns RewardEpochModel when there is no epochs field and empty accounts 1`] = `Array []`;

exports[`RewardsBuilder getRewardsHistory returns RewardEpochModel when there is partially epochs field with lowerBound 1`] = `Array []`;
exports[`RewardsBuilder getRewardsHistory returns RewardEpochModel when there is partially epochs field with lowerBound 1`] = `
Array [
Object {
"address": "stake_test1uqfu74w3wh4gfzu8m6e7j987h4lq9r3t7ef5gaw497uu85qsqfy27",
"epoch": "169",
"quantity": "9225614904",
},
Object {
"address": "stake_test1uqfu74w3wh4gfzu8m6e7j987h4lq9r3t7ef5gaw497uu85qsqfy27",
"epoch": "88",
"quantity": "10994270921",
},
Object {
"address": "stake_test1uqfu74w3wh4gfzu8m6e7j987h4lq9r3t7ef5gaw497uu85qsqfy27",
"epoch": "85",
"quantity": "12597745314",
},
Object {
"address": "stake_test1uqfu74w3wh4gfzu8m6e7j987h4lq9r3t7ef5gaw497uu85qsqfy27",
"epoch": "161",
"quantity": "16214946715",
},
]
`;

exports[`RewardsBuilder getRewardsHistory returns RewardEpochModel when there is partially epochs field with upperBound 1`] = `Array []`;
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ describe('StakePoolBuilder', () => {
});
describe('queryPoolData', () => {
describe('sort', () => {
it('by default sort', async () => {
it('by default sort (name asc)', async () => {
const pools = (await builder.queryPoolData([1, 6, 14, 15, 20])).map((qR) => {
const { hashId, updateId, ...poolData } = qR;
return poolData;
Expand All @@ -117,6 +117,26 @@ describe('StakePoolBuilder', () => {
expect(pools).toHaveLength(3);
expect(pools).toMatchSnapshot();
});
it('by real-world cost considering fixed cost and margin when specifying sort by cost desc', async () => {
const pools = (await builder.queryPoolData([14, 15, 20], { sort: { field: 'cost', order: 'desc' } })).map(
(qR) => {
const { hashId: _1, updateId: _2, ...poolData } = qR;
return poolData;
}
);
expect(pools).toHaveLength(3);
expect(pools).toMatchSnapshot();
});
it('by real-world cost considering fixed cost and margin when specifying sort by cost asc', async () => {
const pools = (await builder.queryPoolData([14, 15, 20], { sort: { field: 'cost', order: 'asc' } })).map(
(qR) => {
const { hashId: _1, updateId: _2, ...poolData } = qR;
return poolData;
}
);
expect(pools).toHaveLength(3);
expect(pools).toMatchSnapshot();
});
});
describe('pagination', () => {
it('with limit', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -989,7 +989,7 @@ Array [
]
`;

exports[`StakePoolBuilder queryPoolData sort by default sort 1`] = `
exports[`StakePoolBuilder queryPoolData sort by default sort (name asc) 1`] = `
Array [
Object {
"cost": 340000000n,
Expand Down Expand Up @@ -1163,6 +1163,148 @@ Array [
]
`;

exports[`StakePoolBuilder queryPoolData sort by real-world cost considering fixed cost and margin when specifying sort by cost asc 1`] = `
Array [
Object {
"cost": 340000000n,
"hexId": "5d99282bbb4840380bb98c075498ed1983aee18a4a0925b9b44d93f1",
"id": "pool1tkvjs2amfpqrszae3sr4fx8drxp6acv2fgyjtwd5fkflzguqp96",
"margin": Object {
"denominator": 1000,
"numerator": 27,
},
"metadata": Object {
"description": "Pool a of the banderini devtest staking pools",
"homepage": "http://www.banderini.net",
"name": "banderini-devtest-a",
"ticker": "BANDA",
},
"metadataJson": Object {
"hash": "4d89054c2962215694a7122dfe41bc728d3ec248f80ea9a2e0d493057d7d2338",
"url": "https://git.io/JJ7wm",
},
"pledge": 100000000n,
"rewardAccount": "stake_test1upx9faamuf54pm7alg4lna5l7ll08pz833rj45tgr9m2jyceasqjt",
"vrfKeyHash": "c062fabfeb7a68c61c34532e6f441b999c6a5a30b409d24c93174f047d4d935a",
},
Object {
"cost": 340000000n,
"hexId": "961d329fba1807eef89db767ba405aec0c5426501c6b1df20f5c0995",
"id": "pool1jcwn98a6rqr7a7yakanm5sz6asx9gfjsr343mus0tsye23wmg70",
"margin": Object {
"denominator": 20,
"numerator": 1,
},
"metadata": Object {
"description": "What's past is prologue",
"homepage": "https://clio.one",
"name": "CLIO1",
"ticker": "CLIO1",
},
"metadataJson": Object {
"hash": "47530ba97c12e2ac40462e9c86eeb07ea555877d2a1f9d74b6ff8471839267d8",
"url": "https://clio.one/metadata/clio1_testnet.json",
},
"pledge": 1000000000000n,
"rewardAccount": "stake_test1upzu5aw5swqmhy09e2aaa62nac468mnyjzyfww999trzavccrj7pw",
"vrfKeyHash": "0a164c03ef34f26ffda7242b36db0a57ab7b23e230ea8802e50695f1f664de42",
},
Object {
"cost": 400000000n,
"hexId": "5ee7591bf30eaa4f5dce70b4a676eb02d5be8012d188f04fe3beffb0",
"id": "pool1tmn4jxlnp64y7hwwwz62vahtqt2maqqj6xy0qnlrhmlmq3u8q0e",
"margin": Object {
"denominator": 10000,
"numerator": 1,
},
"metadata": Object {
"description": "Our Amsterdam Node",
"homepage": "https://twitter.com/A92Syed",
"name": "THE AMSTERDAM NODE",
"ticker": "AMS",
},
"metadataJson": Object {
"hash": "cc019105f084aef2a956b2f7f2c0bf4e747bf7696705312c244620089429df6f",
"url": "https://git.io/JJ1dz",
},
"pledge": 500000000n,
"rewardAccount": "stake_test1uz9y7juwzcyanva4x4fzpx7tft6ckntn6ulsjjd2k7a0pxgldmzp5",
"vrfKeyHash": "83a817519ec34d3c637db8f9d46fcf6f7f9e826093d1b9a8158c89da4b47a801",
},
]
`;

exports[`StakePoolBuilder queryPoolData sort by real-world cost considering fixed cost and margin when specifying sort by cost desc 1`] = `
Array [
Object {
"cost": 400000000n,
"hexId": "5ee7591bf30eaa4f5dce70b4a676eb02d5be8012d188f04fe3beffb0",
"id": "pool1tmn4jxlnp64y7hwwwz62vahtqt2maqqj6xy0qnlrhmlmq3u8q0e",
"margin": Object {
"denominator": 10000,
"numerator": 1,
},
"metadata": Object {
"description": "Our Amsterdam Node",
"homepage": "https://twitter.com/A92Syed",
"name": "THE AMSTERDAM NODE",
"ticker": "AMS",
},
"metadataJson": Object {
"hash": "cc019105f084aef2a956b2f7f2c0bf4e747bf7696705312c244620089429df6f",
"url": "https://git.io/JJ1dz",
},
"pledge": 500000000n,
"rewardAccount": "stake_test1uz9y7juwzcyanva4x4fzpx7tft6ckntn6ulsjjd2k7a0pxgldmzp5",
"vrfKeyHash": "83a817519ec34d3c637db8f9d46fcf6f7f9e826093d1b9a8158c89da4b47a801",
},
Object {
"cost": 340000000n,
"hexId": "961d329fba1807eef89db767ba405aec0c5426501c6b1df20f5c0995",
"id": "pool1jcwn98a6rqr7a7yakanm5sz6asx9gfjsr343mus0tsye23wmg70",
"margin": Object {
"denominator": 20,
"numerator": 1,
},
"metadata": Object {
"description": "What's past is prologue",
"homepage": "https://clio.one",
"name": "CLIO1",
"ticker": "CLIO1",
},
"metadataJson": Object {
"hash": "47530ba97c12e2ac40462e9c86eeb07ea555877d2a1f9d74b6ff8471839267d8",
"url": "https://clio.one/metadata/clio1_testnet.json",
},
"pledge": 1000000000000n,
"rewardAccount": "stake_test1upzu5aw5swqmhy09e2aaa62nac468mnyjzyfww999trzavccrj7pw",
"vrfKeyHash": "0a164c03ef34f26ffda7242b36db0a57ab7b23e230ea8802e50695f1f664de42",
},
Object {
"cost": 340000000n,
"hexId": "5d99282bbb4840380bb98c075498ed1983aee18a4a0925b9b44d93f1",
"id": "pool1tkvjs2amfpqrszae3sr4fx8drxp6acv2fgyjtwd5fkflzguqp96",
"margin": Object {
"denominator": 1000,
"numerator": 27,
},
"metadata": Object {
"description": "Pool a of the banderini devtest staking pools",
"homepage": "http://www.banderini.net",
"name": "banderini-devtest-a",
"ticker": "BANDA",
},
"metadataJson": Object {
"hash": "4d89054c2962215694a7122dfe41bc728d3ec248f80ea9a2e0d493057d7d2338",
"url": "https://git.io/JJ7wm",
},
"pledge": 100000000n,
"rewardAccount": "stake_test1upx9faamuf54pm7alg4lna5l7ll08pz833rj45tgr9m2jyceasqjt",
"vrfKeyHash": "c062fabfeb7a68c61c34532e6f441b999c6a5a30b409d24c93174f047d4d935a",
},
]
`;

exports[`StakePoolBuilder queryPoolMetrics pagination with limit 1`] = `
Array [
Object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { withSort } from '../../../src/StakePool/DbSyncStakePoolProvider/queries';

describe('queries', () => {
describe('withSort', () => {
const dummyQuery = 'SELECT * FROM table';
test('sort by field with no mapping', () => {
const query = withSort(dummyQuery, { field: 'saturation', order: 'asc' });
expect(query).toEqual(`${dummyQuery} ORDER BY saturation asc NULLS LAST, id asc NULLS LAST`);
});
test('sort by field with simple mapping', () => {
const query = withSort(dummyQuery, { field: 'name', order: 'asc' });
expect(query).toEqual(
`${dummyQuery} ORDER BY lower((pod.json -> 'name')::TEXT) asc NULLS LAST, pool_id asc NULLS LAST`
);
});
test('sort by field with secondary sorts', () => {
const query = withSort(dummyQuery, { field: 'cost', order: 'asc' });
expect(query).toEqual(
`${dummyQuery} ORDER BY fixed_cost asc NULLS LAST, margin asc NULLS LAST, pool_id asc NULLS LAST`
);
});
test('single default sort', () => {
const query = withSort(dummyQuery, undefined, [{ field: 'some_field', order: 'asc' }]);
expect(query).toEqual(`${dummyQuery} ORDER BY some_field asc NULLS LAST`);
});
test('multiple default sort', () => {
const query = withSort(dummyQuery, undefined, [
{ field: 'some_field', order: 'asc' },
{ field: 'another_field', order: 'desc' }
]);
expect(query).toEqual(`${dummyQuery} ORDER BY some_field asc NULLS LAST, another_field desc NULLS LAST`);
});
test('multiple default sort with secondary mapped sorts', () => {
const query = withSort(dummyQuery, undefined, [
{ field: 'some_field', order: 'asc' },
{ field: 'cost', order: 'desc' }
]);
expect(query).toEqual(
`${dummyQuery} ORDER BY some_field asc NULLS LAST, fixed_cost desc NULLS LAST, margin desc NULLS LAST`
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,37 @@ describe('StakePoolHttpService', () => {
expect(secondPageResultSet).toMatchSnapshot();
});
});

describe('sort by cost and margin', () => {
it('desc order', async () => {
const response = await doStakePoolRequest<[StakePoolQueryOptions], StakePoolSearchResults>(url, [
setSortCondition({}, 'desc', 'cost')
]);
expect(response).toMatchSnapshot();
});
it('asc order', async () => {
const response = await doStakePoolRequest<[StakePoolQueryOptions], StakePoolSearchResults>(url, [
setSortCondition({}, 'asc', 'cost')
]);
expect(response).toMatchSnapshot();
});
it('with applied filters', async () => {
const response = await doStakePoolRequest<[StakePoolQueryOptions], StakePoolSearchResults>(url, [
setSortCondition(setFilterCondition(filterArgs, 'or'), 'asc', 'cost')
]);
expect(response).toMatchSnapshot();
});
it('with applied pagination', async () => {
const firstPageResultSet = await doStakePoolRequest<[StakePoolQueryOptions], StakePoolSearchResults>(url, [
setSortCondition(setPagination({}, 0, 3), 'desc', 'cost')
]);
const secondPageResultSet = await doStakePoolRequest<[StakePoolQueryOptions], StakePoolSearchResults>(url, [
setSortCondition(setPagination({}, 3, 3), 'desc', 'cost')
]);
expect(firstPageResultSet).toMatchSnapshot();
expect(secondPageResultSet).toMatchSnapshot();
});
});
});
});

Expand Down
Loading

0 comments on commit 6e1d6e4

Please sign in to comment.