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

feat: improve token transfers queries #51

Merged
merged 5 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 0 additions & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,3 @@
- [ ] PR title corresponds to the body of PR (we generate changelog entries from PRs).
- [ ] Tests for the changes have been added / updated.
- [ ] Documentation comments have been added / updated.
- [ ] Code has been formatted via `zk fmt` and `zk lint`.
1 change: 1 addition & 0 deletions packages/api/src/token/token.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { BaseEntity } from "../common/entities/base.entity";
import { normalizeAddressTransformer } from "../common/transformers/normalizeAddress.transformer";

export enum TokenType {
ETH = "ETH",
ERC20 = "ERC20",
ERC721 = "ERC721",
}
Expand Down
7 changes: 6 additions & 1 deletion packages/api/src/transfer/addressTransfer.entity.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Entity, Column, Index, ManyToOne, JoinColumn, PrimaryColumn } from "typeorm";
import { BaseEntity } from "../common/entities/base.entity";
import { Transfer } from "./transfer.entity";
import { TokenType } from "../token/token.entity";
import { bigIntNumberTransformer } from "../common/transformers/bigIntNumber.transformer";
import { normalizeAddressTransformer } from "../common/transformers/normalizeAddress.transformer";

@Entity({ name: "addressTransfers" })
@Index(["address", "isFeeOrRefund", "timestamp", "logIndex"])
@Index(["address", "tokenAddress", "fields", "blockNumber", "logIndex"])
@Index(["address", "tokenType", "blockNumber", "logIndex"])
@Index(["address", "tokenAddress", "blockNumber", "logIndex"])
export class AddressTransfer extends BaseEntity {
@PrimaryColumn({ generated: true, type: "bigint" })
public readonly number: number;
Expand All @@ -32,6 +34,9 @@ export class AddressTransfer extends BaseEntity {
@Column({ type: "timestamp" })
public readonly timestamp: Date;

@Column({ type: "enum", enum: TokenType, default: TokenType.ETH })
public readonly tokenType: TokenType;

@Column({ type: "boolean" })
public readonly isFeeOrRefund: boolean;

Expand Down
7 changes: 5 additions & 2 deletions packages/api/src/transfer/transfer.entity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Entity, Column, Index, ManyToOne, JoinColumn, PrimaryColumn } from "typeorm";
import { BaseEntity } from "../common/entities/base.entity";
import { Token } from "../token/token.entity";
import { Token, TokenType } from "../token/token.entity";
import { normalizeAddressTransformer } from "../common/transformers/normalizeAddress.transformer";
import { bigIntNumberTransformer } from "../common/transformers/bigIntNumber.transformer";
import { hexTransformer } from "../common/transformers/hex.transformer";
Expand All @@ -19,7 +19,7 @@ export enum TransferType {
@Index(["blockNumber", "logIndex"])
@Index(["transactionHash", "timestamp", "logIndex"])
@Index(["tokenAddress", "isFeeOrRefund", "timestamp", "logIndex"])
@Index(["tokenAddress", "fields", "blockNumber", "logIndex"])
@Index(["tokenAddress", "blockNumber", "logIndex"])
export class Transfer extends BaseEntity {
@PrimaryColumn({ generated: true, type: "bigint", select: false })
public number: number;
Expand Down Expand Up @@ -64,6 +64,9 @@ export class Transfer extends BaseEntity {
@Column({ type: "enum", enum: TransferType, default: TransferType.Transfer })
public readonly type: TransferType;

@Column({ type: "enum", enum: TokenType, default: TokenType.ETH })
public readonly tokenType: TokenType;

@Column({ type: "boolean", select: false })
public readonly isFeeOrRefund: boolean;

Expand Down
38 changes: 17 additions & 21 deletions packages/api/src/transfer/transfer.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { TokenType } from "../token/token.entity";
import { AddressTransfer } from "./addressTransfer.entity";
import * as utils from "../common/utils";
import { normalizeAddressTransformer } from "../common/transformers/normalizeAddress.transformer";
import { L2_ETH_TOKEN_ADDRESS } from "../common/constants";

jest.mock("../common/utils");

Expand Down Expand Up @@ -228,26 +227,24 @@ describe("TransferService", () => {
});

describe("when token type is ERC20", () => {
it("adds ERC20 filter", async () => {
it("adds tokenAddress filter", async () => {
await service.findTokenTransfers(filterOptions);
expect(queryBuilderMock.where).toBeCalledTimes(1);
expect(queryBuilderMock.where).toHaveBeenCalledWith({
tokenAddress: filterOptions.tokenAddress,
fields: typeorm.IsNull(),
});
});
});

describe("when token type is ERC721", () => {
it("adds ERC721 filter", async () => {
it("adds tokenAddress filter", async () => {
await service.findTokenTransfers({
...filterOptions,
tokenType: TokenType.ERC721,
});
expect(queryBuilderMock.where).toBeCalledTimes(1);
expect(queryBuilderMock.where).toHaveBeenCalledWith({
tokenAddress: "tokenAddress",
fields: typeorm.Not(typeorm.IsNull()),
});
});
});
Expand Down Expand Up @@ -345,13 +342,19 @@ describe("TransferService", () => {
});

describe("when token type is ERC20", () => {
it("adds ERC20 filter", async () => {
it("adds address and ERC20 filter", async () => {
await service.findTokenTransfers(filterOptions);
expect(addressTransfersQueryBuilderMock.where).toBeCalledTimes(1);
expect(addressTransfersQueryBuilderMock.where).toHaveBeenCalledWith({
address: filterOptions.address,
fields: typeorm.IsNull(),
});
expect(addressTransfersQueryBuilderMock.andWhere).toBeCalledTimes(1);
expect(addressTransfersQueryBuilderMock.andWhere).toHaveBeenCalledWith(
`"addressTransfer"."tokenType" = :tokenType`,
{
tokenType: TokenType.ERC20,
}
);
});
});

Expand All @@ -364,8 +367,14 @@ describe("TransferService", () => {
expect(addressTransfersQueryBuilderMock.where).toBeCalledTimes(1);
expect(addressTransfersQueryBuilderMock.where).toHaveBeenCalledWith({
address: filterOptions.address,
fields: typeorm.Not(typeorm.IsNull()),
});
expect(addressTransfersQueryBuilderMock.andWhere).toBeCalledTimes(1);
expect(addressTransfersQueryBuilderMock.andWhere).toHaveBeenCalledWith(
`"addressTransfer"."tokenType" = :tokenType`,
{
tokenType: TokenType.ERC721,
}
);
});
});

Expand All @@ -385,19 +394,6 @@ describe("TransferService", () => {
});
});

describe("when token address is not specified", () => {
it("adds filter to exclude ETH token", async () => {
await service.findTokenTransfers(filterOptions);
expect(addressTransfersQueryBuilderMock.andWhere).toBeCalledTimes(1);
expect(addressTransfersQueryBuilderMock.andWhere).toHaveBeenCalledWith(
`"addressTransfer"."tokenAddress" != :tokenAddress`,
{
tokenAddress: normalizeAddressTransformer.to(L2_ETH_TOKEN_ADDRESS),
}
);
});
});

it("joins transfers and tokens records to the address transfers", async () => {
await service.findTokenTransfers(filterOptions);
expect(addressTransfersQueryBuilderMock.leftJoinAndSelect).toBeCalledTimes(2);
Expand Down
9 changes: 3 additions & 6 deletions packages/api/src/transfer/transfer.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { BadRequestException, Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository, FindOperator, MoreThanOrEqual, LessThanOrEqual, IsNull, Not } from "typeorm";
import { Repository, FindOperator, MoreThanOrEqual, LessThanOrEqual } from "typeorm";
import { Pagination } from "nestjs-typeorm-paginate";
import { paginate } from "../common/utils";
import { L2_ETH_TOKEN_ADDRESS } from "../common/constants";
import { IPaginationOptions, SortingOrder } from "../common/types";
import { Transfer } from "./transfer.entity";
import { TokenType } from "../token/token.entity";
Expand Down Expand Up @@ -105,15 +104,14 @@ export class TransferService {
queryBuilder.addSelect(["transactionReceipt.gasUsed", "transactionReceipt.cumulativeGasUsed"]);
queryBuilder.where({
address,
fields: tokenType === TokenType.ERC721 ? Not(IsNull()) : IsNull(),
});
if (tokenAddress) {
queryBuilder.andWhere(`"addressTransfer"."tokenAddress" = :tokenAddress`, {
tokenAddress: normalizeAddressTransformer.to(tokenAddress),
});
} else {
queryBuilder.andWhere(`"addressTransfer"."tokenAddress" != :tokenAddress`, {
tokenAddress: normalizeAddressTransformer.to(L2_ETH_TOKEN_ADDRESS),
queryBuilder.andWhere(`"addressTransfer"."tokenType" = :tokenType`, {
tokenType,
});
}
if (startBlock !== undefined) {
Expand Down Expand Up @@ -154,7 +152,6 @@ export class TransferService {
queryBuilder.addSelect(["transactionReceipt.gasUsed", "transactionReceipt.cumulativeGasUsed"]);
queryBuilder.where({
tokenAddress,
fields: tokenType === TokenType.ERC721 ? Not(IsNull()) : IsNull(),
});
if (startBlock !== undefined) {
queryBuilder.andWhere({
Expand Down
7 changes: 6 additions & 1 deletion packages/api/test/address.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Transaction } from "../src/transaction/entities/transaction.entity";
import { AddressTransaction } from "../src/transaction/entities/addressTransaction.entity";
import { TransactionReceipt } from "../src/transaction/entities/transactionReceipt.entity";
import { Log } from "../src/log/log.entity";
import { Token } from "../src/token/token.entity";
import { Token, TokenType } from "../src/token/token.entity";
import { BatchDetails } from "../src/batch/batchDetails.entity";
import { Counter } from "../src/counter/counter.entity";
import { Transfer, TransferType } from "../src/transfer/transfer.entity";
Expand Down Expand Up @@ -310,6 +310,7 @@ describe("AddressController (e2e)", () => {
transactionIndex: i,
timestamp: new Date("2022-11-21T18:16:51.000Z"),
type,
tokenType: i % 2 ? TokenType.ERC20 : TokenType.ETH,
tokenAddress:
i % 2 ? "0x97d0a23f34e535e44df8ba84c53a0945cf0eeb67" : "0x000000000000000000000000000000000000800a",
logIndex: i,
Expand All @@ -326,6 +327,7 @@ describe("AddressController (e2e)", () => {
tokenAddress: transferSpec.tokenAddress,
blockNumber: transferSpec.blockNumber,
timestamp: transferSpec.timestamp,
tokenType: transferSpec.tokenType,
isFeeOrRefund: transferSpec.isFeeOrRefund,
logIndex: transferSpec.logIndex,
isInternal: transferSpec.isInternal,
Expand Down Expand Up @@ -1029,6 +1031,7 @@ describe("AddressController (e2e)", () => {
tokenAddress: "0x97d0a23F34E535e44dF8ba84c53A0945cF0eEb67",
transactionHash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e11",
type: "mint",
tokenType: "ERC20",
isInternal: false,
},
{
Expand All @@ -1048,6 +1051,7 @@ describe("AddressController (e2e)", () => {
tokenAddress: "0x000000000000000000000000000000000000800A",
transactionHash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e11",
type: "transfer",
tokenType: "ETH",
isInternal: false,
},
{
Expand All @@ -1067,6 +1071,7 @@ describe("AddressController (e2e)", () => {
tokenAddress: "0x97d0a23F34E535e44dF8ba84c53A0945cF0eEb67",
transactionHash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e11",
type: "deposit",
tokenType: "ERC20",
isInternal: false,
},
])
Expand Down
16 changes: 15 additions & 1 deletion packages/api/test/token.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Repository } from "typeorm";
import { getRepositoryToken } from "@nestjs/typeorm";
import { AppModule } from "../src/app.module";
import { configureApp } from "../src/configureApp";
import { Token } from "../src/token/token.entity";
import { Token, TokenType } from "../src/token/token.entity";
import { BlockDetail } from "../src/block/blockDetail.entity";
import { Transaction } from "../src/transaction/entities/transaction.entity";
import { Transfer, TransferType } from "../src/transfer/transfer.entity";
Expand Down Expand Up @@ -108,6 +108,7 @@ describe("TokenController (e2e)", () => {
tokenAddress: "0xd754ff5e8a6f257e162f72578a4bb0493c068101",
amount: "1000",
type: TransferType.Deposit,
tokenType: TokenType.ERC20,
logIndex: transferIndex++,
transactionIndex: 0,
timestamp: "2022-11-21T18:16:51.000Z",
Expand All @@ -123,6 +124,7 @@ describe("TokenController (e2e)", () => {
tokenAddress: "0xd754ff5e8a6f257e162f72578a4bb0493c068101",
amount: "1000",
type: TransferType.Fee,
tokenType: TokenType.ERC20,
logIndex: transferIndex++,
transactionIndex: 0,
timestamp: "2022-11-21T18:16:51.000Z",
Expand All @@ -138,6 +140,7 @@ describe("TokenController (e2e)", () => {
tokenAddress: "0xd754ff5e8a6f257e162f72578a4bb0493c068101",
amount: "1000",
type: TransferType.Mint,
tokenType: TokenType.ERC20,
logIndex: transferIndex++,
transactionIndex: 0,
timestamp: "2022-11-21T18:16:51.000Z",
Expand All @@ -153,6 +156,7 @@ describe("TokenController (e2e)", () => {
tokenAddress: "0xd754ff5e8a6f257e162f72578a4bb0493c068101",
amount: "1000",
type: TransferType.Transfer,
tokenType: TokenType.ERC20,
logIndex: transferIndex++,
transactionIndex: 0,
timestamp: "2022-11-21T18:16:51.000Z",
Expand All @@ -168,6 +172,7 @@ describe("TokenController (e2e)", () => {
tokenAddress: "0xd754ff5e8a6f257e162f72578a4bb0493c068101",
amount: "1000",
type: TransferType.Withdrawal,
tokenType: TokenType.ERC20,
logIndex: transferIndex++,
transactionIndex: 0,
timestamp: "2022-11-21T18:16:51.000Z",
Expand All @@ -183,6 +188,7 @@ describe("TokenController (e2e)", () => {
tokenAddress: "0xd754ff5e8a6f257e162f72578a4bb0493c068101",
amount: undefined,
type: TransferType.Mint,
tokenType: TokenType.ERC721,
fields: { tokenId: "1" },
logIndex: transferIndex++,
transactionIndex: 0,
Expand All @@ -199,6 +205,7 @@ describe("TokenController (e2e)", () => {
tokenAddress: "0xd754ff5e8a6f257e162f72578a4bb0493c068101",
amount: "1000",
type: TransferType.Refund,
tokenType: TokenType.ERC20,
logIndex: transferIndex++,
transactionIndex: 0,
timestamp: "2022-11-21T18:16:51.000Z",
Expand Down Expand Up @@ -392,6 +399,7 @@ describe("TokenController (e2e)", () => {
tokenAddress: "0xD754FF5E8a6F257E162f72578a4bB0493c068101",
transactionHash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e10",
type: "transfer",
tokenType: "ERC20",
isInternal: false,
},
{
Expand All @@ -411,6 +419,7 @@ describe("TokenController (e2e)", () => {
tokenAddress: "0xD754FF5E8a6F257E162f72578a4bB0493c068101",
transactionHash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e10",
type: "withdrawal",
tokenType: "ERC20",
isInternal: false,
},
{
Expand All @@ -432,6 +441,7 @@ describe("TokenController (e2e)", () => {
tokenAddress: "0xD754FF5E8a6F257E162f72578a4bB0493c068101",
transactionHash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e10",
type: "mint",
tokenType: "ERC721",
isInternal: false,
},
{
Expand All @@ -451,6 +461,7 @@ describe("TokenController (e2e)", () => {
tokenAddress: "0xD754FF5E8a6F257E162f72578a4bB0493c068101",
transactionHash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e10",
type: "deposit",
tokenType: "ERC20",
isInternal: false,
},
{
Expand All @@ -470,6 +481,7 @@ describe("TokenController (e2e)", () => {
tokenAddress: "0xD754FF5E8a6F257E162f72578a4bB0493c068101",
transactionHash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e10",
type: "mint",
tokenType: "ERC20",
isInternal: false,
},
{
Expand All @@ -489,6 +501,7 @@ describe("TokenController (e2e)", () => {
tokenAddress: "0xD754FF5E8a6F257E162f72578a4bB0493c068101",
transactionHash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e10",
type: "transfer",
tokenType: "ERC20",
isInternal: false,
},
{
Expand All @@ -508,6 +521,7 @@ describe("TokenController (e2e)", () => {
tokenAddress: "0xD754FF5E8a6F257E162f72578a4bB0493c068101",
transactionHash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e10",
type: "withdrawal",
tokenType: "ERC20",
isInternal: false,
},
])
Expand Down
Loading
Loading