Skip to content
This repository was archived by the owner on Aug 12, 2023. It is now read-only.

Commit 3e17026

Browse files
authored
Refactor jobs for deriving value & prices (#234)
* Create job for converting fees * Introduce new job for determining fill values * Shortcut fee conversion for fills without fees * Refactor job for deriving fill prices * Fix tests
1 parent b4cca01 commit 3e17026

21 files changed

+260
-324
lines changed

config/default.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,20 @@ module.exports = {
1919
apiKey: process.env.ETHPLORER_API_KEY,
2020
},
2121
jobs: {
22+
convertFees: {
23+
apiDelayMs: 100,
24+
batchSize: 100,
25+
},
2226
createFills: {
2327
batchSize: 500,
2428
processOldestFirst: true,
2529
},
26-
updateFillPrices: {
30+
deriveFillPrices: {
2731
batchSize: 250,
28-
processOldestFirst: true,
32+
},
33+
determineFillValues: {
34+
apiDelayMs: 100,
35+
batchSize: 100,
2936
},
3037
updateFillRates: {
3138
batchSize: 100,

src/fills/get-base-token.js

+11-24
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,20 @@
11
const _ = require('lodash');
22

33
const { BASE_TOKENS } = require('../constants');
4+
const { getToken } = require('../tokens/token-cache');
45

5-
const getBaseToken = (fill, tokens) => {
6-
const { makerToken, takerToken } = fill;
6+
const getBaseToken = fill => {
7+
const address = _(BASE_TOKENS)
8+
.keys()
9+
.find(baseToken => {
10+
return fill.makerToken === baseToken || fill.takerToken === baseToken;
11+
});
712

8-
return (
9-
_(BASE_TOKENS)
10-
.keys()
11-
.map(baseToken => {
12-
if (
13-
_.has(tokens, makerToken) &&
14-
tokens[makerToken].address === baseToken
15-
) {
16-
return tokens[makerToken];
17-
}
13+
if (address === undefined) {
14+
return null;
15+
}
1816

19-
if (
20-
_.has(tokens, takerToken) &&
21-
tokens[takerToken].address === baseToken
22-
) {
23-
return tokens[takerToken];
24-
}
25-
26-
return null;
27-
})
28-
.compact()
29-
.head() || null
30-
);
17+
return getToken(address);
3118
};
3219

3320
module.exports = getBaseToken;

src/fills/get-base-token.test.js

+5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const getBaseToken = require('./get-base-token');
2+
const tokenCache = require('../tokens/token-cache');
23

34
const tokens = {
45
'0x123': { decimals: 18, symbol: 'ZRX' },
@@ -15,6 +16,10 @@ const tokens = {
1516
'0x7866': { decimals: 18, symbol: 'OMG' },
1617
};
1718

19+
beforeAll(() => {
20+
tokenCache.initialise(tokens);
21+
});
22+
1823
it('should return WETH in WETH/OMG pair', () => {
1924
const fill = {
2025
makerToken: '0x7866',

src/jobs/convert-fees.js

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
const bluebird = require('bluebird');
2+
const signale = require('signale');
3+
4+
const { ZRX_TOKEN_ADDRESS } = require('../constants');
5+
const { getToken } = require('../tokens/token-cache');
6+
const Fill = require('../model/fill');
7+
const formatTokenAmount = require('../tokens/format-token-amount');
8+
const getConversionRate = require('../rates/get-conversion-rate');
9+
10+
const logger = signale.scope('convert fees');
11+
12+
const convertAmount = (amount, conversionRate) => {
13+
if (amount === 0) {
14+
return 0;
15+
}
16+
17+
const zrxToken = getToken(ZRX_TOKEN_ADDRESS);
18+
const formattedAmount = formatTokenAmount(amount, zrxToken);
19+
20+
return formattedAmount * conversionRate;
21+
};
22+
23+
const convertFees = async ({ apiDelayMs, batchSize }) => {
24+
// Fetch a batch of fills for processing where the maker or taker
25+
// fee have not yet been converted.
26+
const fills = await Fill.find({
27+
$or: [
28+
{ 'conversions.USD.makerFee': null },
29+
{ 'conversions.USD.takerFee': null },
30+
],
31+
})
32+
.limit(batchSize)
33+
.lean();
34+
35+
logger.info(`found ${fills.length} fills without converted fees`);
36+
37+
if (fills.length === 0) {
38+
return;
39+
}
40+
41+
await bluebird.mapSeries(fills, async fill => {
42+
if (fill.makerFee + fill.takerFee === 0) {
43+
await Fill.updateOne(
44+
{ _id: fill._id },
45+
{
46+
$set: {
47+
'conversions.USD.makerFee': 0,
48+
'conversions.USD.takerFee': 0,
49+
},
50+
},
51+
);
52+
53+
logger.success(`Skipped fee conversion for fill ${fill._id}`);
54+
} else {
55+
logger.time(`Fetch ZRX conversion rate for ${fill.date}`);
56+
const conversionRate = await getConversionRate('ZRX', 'USD', fill.date);
57+
logger.timeEnd(`Fetch ZRX conversion rate for ${fill.date}`);
58+
59+
if (conversionRate === undefined) {
60+
logger.warn(`Unable to fetch ZRX conversion rate for ${fill.date}`);
61+
return;
62+
}
63+
64+
const makerFee = convertAmount(fill.makerFee, conversionRate);
65+
const takerFee = convertAmount(fill.takerFee, conversionRate);
66+
67+
await Fill.updateOne(
68+
{ _id: fill._id },
69+
{
70+
$set: {
71+
'conversions.USD.makerFee': makerFee,
72+
'conversions.USD.takerFee': takerFee,
73+
'rates.ZRX.USD': conversionRate,
74+
},
75+
},
76+
);
77+
78+
logger.success(`Converted fees for fill ${fill._id}`);
79+
80+
// limit the number of API requests made per second to avoid errors
81+
await bluebird.delay(apiDelayMs);
82+
}
83+
});
84+
};
85+
86+
module.exports = convertFees;

src/jobs/create-fills/create-fill.js

+8
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,17 @@ const createFill = async event => {
4646
takerAddress: taker,
4747
});
4848

49+
const conversions =
50+
paidMakerFee + paidTakerFee === 0
51+
? {
52+
USD: { makerFee: 0, takerFee: 0 },
53+
}
54+
: undefined;
55+
4956
const fill = {
5057
blockHash,
5158
blockNumber,
59+
conversions,
5260
date,
5361
feeRecipient,
5462
logIndex,

src/jobs/update-fill-prices/index.js src/jobs/derive-fill-prices/index.js

+17-14
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,23 @@ const getPrices = require('./get-prices-for-fill');
88
const getRelayerPrices = require('./get-relayer-prices');
99
const Relayer = require('../../model/relayer');
1010
const tokenCache = require('../../tokens/token-cache');
11+
const withTransaction = require('../../util/with-transaction');
1112

12-
const logger = signale.scope('update fill prices');
13+
const logger = signale.scope('derive fill prices');
1314

14-
const updateFillPrices = async ({ batchSize, processOldestFirst }) => {
15+
const deriveFillPrices = async ({ batchSize }) => {
1516
const fills = await Fill.find(
1617
{
17-
'rates.saved': true,
18+
// Determine whether prices can be derived
19+
'conversions.USD.amount': { $ne: null },
1820
'tokenSaved.maker': true,
1921
'tokenSaved.taker': true,
22+
23+
// Determine whether prices have already been derived
2024
'prices.saved': { $in: [null, false] },
2125
},
2226
'_id conversions date makerAmount makerToken relayerId takerAmount takerToken',
23-
)
24-
.sort({ date: processOldestFirst ? 1 : -1 })
25-
.limit(batchSize);
27+
).limit(batchSize);
2628

2729
if (fills.length === 0) {
2830
logger.info('no fills were found without prices');
@@ -40,7 +42,7 @@ const updateFillPrices = async ({ batchSize, processOldestFirst }) => {
4042
)(fills);
4143

4244
if (fillPrices.length === 0) {
43-
logger.warn(`unable to generate prices for ${fillPrices.length} fills`);
45+
logger.warn(`unable to derive prices for ${fillPrices.length} fills`);
4446
return;
4547
}
4648

@@ -79,14 +81,15 @@ const updateFillPrices = async ({ batchSize, processOldestFirst }) => {
7981
},
8082
}));
8183

82-
// TODO: Investigate using the new MongoDB transactions feature to ensure consistency
83-
await Fill.bulkWrite(fillOperations);
84+
await withTransaction(async session => {
85+
await Fill.bulkWrite(fillOperations, { session });
8486

85-
if (relayerOperations.length > 0) {
86-
await Relayer.bulkWrite(relayerOperations);
87-
}
87+
if (relayerOperations.length > 0) {
88+
await Relayer.bulkWrite(relayerOperations, { session });
89+
}
90+
});
8891

89-
logger.success(`updated prices for ${fillPrices.length} fills`);
92+
logger.success(`derived prices of ${fillPrices.length} fills`);
9093
};
9194

92-
module.exports = updateFillPrices;
95+
module.exports = deriveFillPrices;

src/jobs/determine-fill-values.js

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
const _ = require('lodash');
2+
const bluebird = require('bluebird');
3+
const signale = require('signale');
4+
5+
const { BASE_TOKENS } = require('../constants');
6+
const Fill = require('../model/fill');
7+
const formatTokenAmount = require('../tokens/format-token-amount');
8+
const getBaseToken = require('../fills/get-base-token');
9+
const getConversionRate = require('../rates/get-conversion-rate');
10+
const normalizeSymbol = require('../tokens/normalize-symbol');
11+
12+
const logger = signale.scope('determine fill values');
13+
14+
const getTokenValue = (fill, baseToken) => {
15+
if (fill.makerToken === baseToken.address) {
16+
return formatTokenAmount(fill.makerAmount, baseToken);
17+
}
18+
19+
return formatTokenAmount(fill.takerAmount, baseToken);
20+
};
21+
22+
const determineFillValues = async ({ apiDelayMs, batchSize }) => {
23+
const baseTokens = _.keys(BASE_TOKENS);
24+
const fills = await Fill.find({
25+
'conversions.USD.amount': null,
26+
$or: [
27+
{ makerToken: { $in: baseTokens } },
28+
{ takerToken: { $in: baseTokens } },
29+
],
30+
})
31+
.limit(batchSize)
32+
.lean();
33+
34+
logger.info(`found ${fills.length} measurable fills without a value`);
35+
36+
if (fills.length === 0) {
37+
return;
38+
}
39+
40+
await bluebird.mapSeries(fills, async fill => {
41+
const baseToken = getBaseToken(fill);
42+
43+
if (baseToken === null || baseToken === undefined) {
44+
logger.warn(`unable to determine base token of fill ${fill._id}`);
45+
46+
return;
47+
}
48+
49+
const normalisedSymbol = normalizeSymbol(baseToken.symbol);
50+
51+
logger.time(
52+
`fetch conversion rate for ${normalisedSymbol} on ${fill.date}`,
53+
);
54+
const conversionRate = await getConversionRate(
55+
normalisedSymbol,
56+
'USD',
57+
fill.date,
58+
);
59+
logger.timeEnd(
60+
`fetch conversion rate for ${normalisedSymbol} on ${fill.date}`,
61+
);
62+
63+
if (conversionRate === undefined) {
64+
logger.warn(
65+
`unable to determine conversion rate of ${normalisedSymbol} on ${fill.date}`,
66+
);
67+
68+
return;
69+
}
70+
71+
const tokenValue = getTokenValue(fill, baseToken);
72+
const usdValue = tokenValue * conversionRate;
73+
74+
await Fill.updateOne(
75+
{ _id: fill._id },
76+
{
77+
$set: {
78+
'conversions.USD.amount': usdValue,
79+
[`rates.data.${normalisedSymbol}.USD`]: conversionRate,
80+
},
81+
},
82+
);
83+
84+
logger.success(`determined fill value for ${fill._id}`);
85+
86+
// limit the number of API requests made per second to avoid errors
87+
await bluebird.delay(apiDelayMs);
88+
});
89+
};
90+
91+
module.exports = determineFillValues;

src/jobs/index.js

+6-4
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ const backfillRelayerRelationships = require('./backfill-relayer-relationships')
44
const cacheRelayerMetrics = require('./cache-relayer-metrics');
55
const cacheTokenMetrics = require('./cache-token-metrics');
66
const cacheTokenStats = require('./cache-token-stats');
7+
const convertFees = require('./convert-fees');
78
const createFills = require('./create-fills');
9+
const deriveFillPrices = require('./derive-fill-prices');
10+
const determineFillValues = require('./determine-fill-values');
811
const getMissingTokenImages = require('./get-missing-token-images');
912
const getNewArticles = require('./get-new-articles');
1013
const resolveTokens = require('./resolve-tokens');
11-
const updateFillPrices = require('./update-fill-prices');
12-
const updateFillRates = require('./update-fill-rates');
1314
const updateFillStatuses = require('./update-fill-statuses');
1415
const updateRelayerStats = require('./update-relayer-stats');
1516
const updateTokenPrices = require('./update-token-prices');
@@ -19,12 +20,13 @@ const jobFns = {
1920
cacheRelayerMetrics,
2021
cacheTokenMetrics,
2122
cacheTokenStats,
23+
convertFees,
2224
createFills,
25+
deriveFillPrices,
26+
determineFillValues,
2327
getMissingTokenImages,
2428
getNewArticles,
2529
resolveTokens,
26-
updateFillPrices,
27-
updateFillRates,
2830
updateFillStatuses,
2931
updateRelayerStats,
3032
updateTokenPrices,

0 commit comments

Comments
 (0)