Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OTE-762] Affiliates comlink affiliate info #2269

Merged
merged 14 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 46 additions & 10 deletions indexer/packages/postgres/__tests__/helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export const blockedAddress: string = 'dydx1f9k5qldwmqrnwy8hcgp4fw6heuvszt35egvt
export const vaultAddress: string = 'dydx1c0m5x87llaunl5sgv3q5vd7j5uha26d2q2r2q0';

// ============== Subaccounts ==============
export const defaultWalletAddress: string = 'defaultWalletAddress';

export const defaultSubaccount: SubaccountCreateObject = {
address: defaultAddress,
Expand All @@ -97,6 +98,14 @@ export const defaultSubaccount3: SubaccountCreateObject = {
updatedAtHeight: createdHeight,
};

// defaultWalletAddress belongs to defaultWallet2 and is different from defaultAddress
export const defaultSubaccountDefaultWalletAddress: SubaccountCreateObject = {
address: defaultWalletAddress,
subaccountNumber: 0,
updatedAt: createdDateTime.toISO(),
updatedAtHeight: createdHeight,
};

export const defaultSubaccountWithAlternateAddress: SubaccountCreateObject = {
address: defaultAddress2,
subaccountNumber: 0,
Expand Down Expand Up @@ -125,8 +134,6 @@ export const isolatedSubaccount2: SubaccountCreateObject = {
updatedAtHeight: createdHeight,
};

export const defaultWalletAddress: string = 'defaultWalletAddress';

export const defaultSubaccountId: string = SubaccountTable.uuid(
defaultAddress,
defaultSubaccount.subaccountNumber,
Expand All @@ -139,6 +146,10 @@ export const defaultSubaccountId3: string = SubaccountTable.uuid(
defaultAddress,
defaultSubaccount3.subaccountNumber,
);
export const defaultSubaccountIdDefaultWalletAddress: string = SubaccountTable.uuid(
defaultWalletAddress,
defaultSubaccountDefaultWalletAddress.subaccountNumber,
);
export const defaultSubaccountIdWithAlternateAddress: string = SubaccountTable.uuid(
defaultAddress2,
defaultSubaccountWithAlternateAddress.subaccountNumber,
Expand Down Expand Up @@ -906,6 +917,17 @@ export const duplicatedSubaccountUsername: SubaccountUsernamesCreateObject = {
subaccountId: defaultSubaccountId3,
};

// defaultWalletAddress belongs to defaultWallet2 and is different from defaultAddress
export const subaccountUsernameWithDefaultWalletAddress: SubaccountUsernamesCreateObject = {
username: 'EvilRaisin11',
subaccountId: defaultSubaccountIdDefaultWalletAddress,
};

export const subaccountUsernameWithAlternativeAddress: SubaccountUsernamesCreateObject = {
username: 'HonestRaisin32',
subaccountId: defaultSubaccountIdWithAlternateAddress,
};

// ============== Leaderboard pnl Data ==============

export const defaultLeaderboardPnlOneDay: LeaderboardPnlCreateObject = {
Expand Down Expand Up @@ -963,24 +985,38 @@ export const defaultKV2: PersistentCacheCreateObject = {

export const defaultAffiliateInfo: AffiliateInfoCreateObject = {
address: defaultAddress,
affiliateEarnings: '10.00',
affiliateEarnings: '10',
referredMakerTrades: 10,
referredTakerTrades: 20,
totalReferredFees: '10.00',
totalReferredFees: '10',
totalReferredUsers: 5,
referredNetProtocolEarnings: '20.00',
referredNetProtocolEarnings: '20',
firstReferralBlockHeight: '1',
referredTotalVolume: '1000',
};

export const defaultAffiliateInfo1: AffiliateInfoCreateObject = {
address: defaultAddress2,
affiliateEarnings: '11.00',
export const defaultAffiliateInfo2: AffiliateInfoCreateObject = {
address: defaultWalletAddress,
affiliateEarnings: '11',
referredMakerTrades: 11,
referredTakerTrades: 21,
totalReferredFees: '11.00',
totalReferredFees: '11',
totalReferredUsers: 5,
referredNetProtocolEarnings: '21.00',
referredNetProtocolEarnings: '21',
firstReferralBlockHeight: '11',
referredTotalVolume: '1000',
};

export const defaultAffiliateInfo3: AffiliateInfoCreateObject = {
address: defaultAddress2,
affiliateEarnings: '12',
referredMakerTrades: 12,
referredTakerTrades: 22,
totalReferredFees: '12',
totalReferredUsers: 10,
referredNetProtocolEarnings: '22',
firstReferralBlockHeight: '12',
referredTotalVolume: '1111111',
};

// ============== Tokens =============
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AffiliateInfoFromDatabase } from '../../src/types';
import { clearData, migrate, teardown } from '../../src/helpers/db-helpers';
import { defaultAffiliateInfo, defaultAffiliateInfo1 } from '../helpers/constants';
import { defaultAffiliateInfo, defaultAffiliateInfo2 } from '../helpers/constants';
import * as AffiliateInfoTable from '../../src/stores/affiliate-info-table';

describe('Affiliate info store', () => {
Expand Down Expand Up @@ -32,15 +32,15 @@ describe('Affiliate info store', () => {
);
expect(info).toEqual(expect.objectContaining(defaultAffiliateInfo));

await AffiliateInfoTable.upsert(defaultAffiliateInfo1);
info = await AffiliateInfoTable.findById(defaultAffiliateInfo1.address);
expect(info).toEqual(expect.objectContaining(defaultAffiliateInfo1));
await AffiliateInfoTable.upsert(defaultAffiliateInfo2);
info = await AffiliateInfoTable.findById(defaultAffiliateInfo2.address);
expect(info).toEqual(expect.objectContaining(defaultAffiliateInfo2));
});

it('Successfully finds all affiliate infos', async () => {
await Promise.all([
AffiliateInfoTable.create(defaultAffiliateInfo),
AffiliateInfoTable.create(defaultAffiliateInfo1),
AffiliateInfoTable.create(defaultAffiliateInfo2),
]);

const infos: AffiliateInfoFromDatabase[] = await AffiliateInfoTable.findAll(
Expand All @@ -52,7 +52,7 @@ describe('Affiliate info store', () => {
expect(infos.length).toEqual(2);
expect(infos).toEqual(expect.arrayContaining([
expect.objectContaining(defaultAffiliateInfo),
expect.objectContaining(defaultAffiliateInfo1),
expect.objectContaining(defaultAffiliateInfo2),
]));
});

Expand All @@ -65,4 +65,100 @@ describe('Affiliate info store', () => {

expect(info).toEqual(expect.objectContaining(defaultAffiliateInfo));
});

describe('paginatedFindWithAddressFilter', () => {
beforeEach(async () => {
await migrate();
for (let i = 0; i < 10; i++) {
await AffiliateInfoTable.create({
...defaultAffiliateInfo,
address: `address_${i}`,
affiliateEarnings: i.toString(),
});
}
});

it('Successfully filters by address', async () => {
// eslint-disable-next-line max-len
const infos: AffiliateInfoFromDatabase[] | undefined = await AffiliateInfoTable.paginatedFindWithAddressFilter(
['address_0'],
0,
10,
false,
);
expect(infos).toBeDefined();
expect(infos!.length).toEqual(1);
expect(infos![0]).toEqual(expect.objectContaining({
...defaultAffiliateInfo,
address: 'address_0',
affiliateEarnings: '0',
}));
});

it('Successfully sorts by affiliate earning', async () => {
// eslint-disable-next-line max-len
const infos: AffiliateInfoFromDatabase[] | undefined = await AffiliateInfoTable.paginatedFindWithAddressFilter(
[],
0,
10,
true,
);
expect(infos).toBeDefined();
expect(infos!.length).toEqual(10);
expect(infos![0]).toEqual(expect.objectContaining({
...defaultAffiliateInfo,
address: 'address_9',
affiliateEarnings: '9',
}));
expect(infos![9]).toEqual(expect.objectContaining({
...defaultAffiliateInfo,
address: 'address_0',
affiliateEarnings: '0',
}));
});

it('Successfully uses offset and limit', async () => {
// eslint-disable-next-line max-len
const infos: AffiliateInfoFromDatabase[] | undefined = await AffiliateInfoTable.paginatedFindWithAddressFilter(
[],
5,
2,
false,
);
expect(infos).toBeDefined();
expect(infos!.length).toEqual(2);
expect(infos![0]).toEqual(expect.objectContaining({
...defaultAffiliateInfo,
address: 'address_5',
affiliateEarnings: '5',
}));
expect(infos![1]).toEqual(expect.objectContaining({
...defaultAffiliateInfo,
address: 'address_6',
affiliateEarnings: '6',
}));
});

it('Successfully filters, sorts, offsets, and limits', async () => {
// eslint-disable-next-line max-len
const infos: AffiliateInfoFromDatabase[] | undefined = await AffiliateInfoTable.paginatedFindWithAddressFilter(
[],
3,
2,
true,
);
expect(infos).toBeDefined();
expect(infos!.length).toEqual(2);
expect(infos![0]).toEqual(expect.objectContaining({
...defaultAffiliateInfo,
address: 'address_6',
affiliateEarnings: '6',
}));
expect(infos![1]).toEqual(expect.objectContaining({
...defaultAffiliateInfo,
address: 'address_5',
affiliateEarnings: '5',
}));
});
});
});
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { SubaccountFromDatabase, SubaccountUsernamesFromDatabase, SubaccountsWithoutUsernamesResult } from '../../src/types';
import * as SubaccountUsernamesTable from '../../src/stores/subaccount-usernames-table';
import * as WalletTable from '../../src/stores/wallet-table';
import * as SubaccountsTable from '../../src/stores/subaccount-table';
import { clearData, migrate, teardown } from '../../src/helpers/db-helpers';
import {
defaultSubaccountUsername,
defaultSubaccountUsername2,
defaultSubaccountWithAlternateAddress,
defaultWallet,
defaultWallet2,
duplicatedSubaccountUsername,
subaccountUsernameWithAlternativeAddress,
} from '../helpers/constants';
import { seedData } from '../helpers/mock-generators';

Expand Down Expand Up @@ -80,4 +85,24 @@ describe('SubaccountUsernames store', () => {
SubaccountUsernamesTable.getSubaccountsWithoutUsernames();
expect(subaccountIds.length).toEqual(subaccountLength - 1);
});

it('Get username using address', async () => {
await Promise.all([
// Add two usernames for defaultWallet
SubaccountUsernamesTable.create(defaultSubaccountUsername),
SubaccountUsernamesTable.create(defaultSubaccountUsername2),
// Add one username for alternativeWallet
WalletTable.create(defaultWallet2),
SubaccountsTable.create(defaultSubaccountWithAlternateAddress),
SubaccountUsernamesTable.create(subaccountUsernameWithAlternativeAddress),
]);

// Should only get username for defaultWallet's subaccount 0
const usernames = await SubaccountUsernamesTable.findByAddress([defaultWallet.address]);
expect(usernames.length).toEqual(1);
expect(usernames[0]).toEqual(expect.objectContaining({
address: defaultWallet.address,
username: defaultSubaccountUsername.username,
}));
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as Knex from 'knex';

export async function up(knex: Knex): Promise<void> {
return knex.schema.alterTable('affiliate_info', (table) => {
// null indicates variable precision whereas not specifying will result in 8,2 precision,scale
table.decimal('affiliateEarnings', null).alter();
table.decimal('totalReferredFees', null).alter();
table.decimal('referredNetProtocolEarnings', null).alter();
Comment on lines +6 to +8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does variable precision mean? Thinking about this more, we probably want scale of 6 for all fees, thats the smallest unit of USDC

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For variable precision it will not append any 0s after the decimal. Eg. 2 stays 2 and 2.123 stays 2.123. Variable precision allows for up to the maximum postgres limit.

If you set the precision to 6. It will pad 0s so 2 -> 2.000000.

We can do it either way, the main change will be that our unit tests use the string conversion so we have to change those values. I prefer to stick with variable, as that is convention other columns in our tables use.


table.decimal('referredTotalVolume', null).notNullable();
});
}

export async function down(knex: Knex): Promise<void> {
return knex.schema.alterTable('affiliate_info', (table) => {
table.decimal('affiliateEarnings').alter();
table.decimal('totalReferredFees').alter();
table.decimal('referredNetProtocolEarnings').alter();

table.dropColumn('referredTotalVolume');
});
}
2 changes: 2 additions & 0 deletions indexer/packages/postgres/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export { default as SubaccountUsernamesModel } from './models/subaccount-usernam
export { default as LeaderboardPnlModel } from './models/leaderboard-pnl-model';
export { default as PersistentCacheModel } from './models/persistent-cache-model';
export { default as AffiliateReferredUsersModel } from './models/affiliate-referred-users-model';
export { default as AffiliateInfoModel } from './models/affiliate-info-model';

export * as AssetTable from './stores/asset-table';
export * as AssetPositionTable from './stores/asset-position-table';
Expand Down Expand Up @@ -48,6 +49,7 @@ export * as SubaccountUsernamesTable from './stores/subaccount-usernames-table';
export * as PersistentCacheTable from './stores/persistent-cache-table';
export * as AffiliateReferredUsersTable from './stores/affiliate-referred-users-table';
export * as FirebaseNotificationTokenTable from './stores/firebase-notification-token-table';
export * as AffiliateInfoTable from './stores/affiliate-info-table';

export * as perpetualMarketRefresher from './loops/perpetual-market-refresher';
export * as assetRefresher from './loops/asset-refresher';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default class AffiliateInfoModel extends BaseModel {
'totalReferredUsers',
'referredNetProtocolEarnings',
'firstReferralBlockHeight',
'referredTotalVolume',
],
properties: {
address: { type: 'string' },
Expand All @@ -33,6 +34,7 @@ export default class AffiliateInfoModel extends BaseModel {
totalReferredUsers: { type: 'int' },
referredNetProtocolEarnings: { type: 'string', pattern: NonNegativeNumericPattern },
firstReferralBlockHeight: { type: 'string', pattern: NonNegativeNumericPattern },
referredTotalVolume: { type: 'string', pattern: NonNegativeNumericPattern },
},
};
}
Expand All @@ -53,6 +55,7 @@ export default class AffiliateInfoModel extends BaseModel {
totalReferredUsers: 'int',
referredNetProtocolEarnings: 'string',
firstReferralBlockHeight: 'string',
referredTotalVolume: 'string',
};
}

Expand All @@ -73,4 +76,6 @@ export default class AffiliateInfoModel extends BaseModel {
referredNetProtocolEarnings!: string;

firstReferralBlockHeight!: string;

referredTotalVolume!: string;
}
30 changes: 30 additions & 0 deletions indexer/packages/postgres/src/stores/affiliate-info-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export async function upsert(
// should only ever be one AffiliateInfo
return AffiliateInfos[0];
}

export async function findById(
address: string,
options: Options = DEFAULT_POSTGRES_OPTIONS,
Expand All @@ -92,3 +93,32 @@ export async function findById(
.findById(address)
.returning('*');
}

export async function paginatedFindWithAddressFilter(
jerryfan01234 marked this conversation as resolved.
Show resolved Hide resolved
addressFilter: string[],
offset: number,
limit: number,
sortByAffiliateEarning: boolean,
options: Options = DEFAULT_POSTGRES_OPTIONS,
): Promise<AffiliateInfoFromDatabase[] | undefined> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like it returns empty array, when will this ever return undefined?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it cant be undefined, i will remove | undefined in definition and call sites.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix this for other affiliate endpoints in upcoming PR

let baseQuery: QueryBuilder<AffiliateInfoModel> = setupBaseQuery<AffiliateInfoModel>(
AffiliateInfoModel,
options,
);

// Apply address filter if provided
if (addressFilter.length > 0) {
baseQuery = baseQuery.whereIn(AffiliateInfoColumns.address, addressFilter);
}

// Sorting by affiliate earnings or default sorting by address
if (sortByAffiliateEarning) {
baseQuery = baseQuery.orderBy(AffiliateInfoColumns.affiliateEarnings, Ordering.DESC);
}

// Apply pagination using offset and limit
baseQuery = baseQuery.offset(offset).limit(limit);

// Returning all fields
return baseQuery.returning('*');
}
Loading
Loading