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: add support for evm-like contracts and transactions #337

Merged
merged 6 commits into from
Dec 19, 2024
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
2 changes: 2 additions & 0 deletions packages/api/src/address/address.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ describe("AddressController", () => {
blockNumber: addressBalances.blockNumber,
balances: addressBalances.balances,
totalTransactions: totalTxCount,
isEvmLike: addressRecord.isEvmLike,
});
});

Expand All @@ -163,6 +164,7 @@ describe("AddressController", () => {
blockNumber: addressRecord.createdInBlockNumber,
balances: defaultBalancesResponse.balances,
totalTransactions: totalTxCount,
isEvmLike: addressRecord.isEvmLike,
});
});
});
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/address/address.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export class AddressController {
creatorTxHash: addressRecord.creatorTxHash,
totalTransactions,
creatorAddress: addressRecord.creatorAddress,
isEvmLike: addressRecord.isEvmLike,
};
}

Expand Down
3 changes: 3 additions & 0 deletions packages/api/src/address/address.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,7 @@ export class Address extends BaseEntity {

@Column({ type: "bytea", nullable: true, transformer: normalizeAddressTransformer })
public readonly creatorAddress?: string;

@Column({ type: "boolean", default: false })
public readonly isEvmLike: boolean;
}
7 changes: 7 additions & 0 deletions packages/api/src/address/dtos/contract.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,11 @@ export class ContractDto extends BaseAddressDto {
example: "0xd754Ff5e8a6f257E162F72578A4bB0493c0681d8",
})
public readonly creatorAddress: string;

@ApiProperty({
type: Boolean,
description: "Is the contract EVM-like",
example: true,
})
public readonly isEvmLike: boolean;
}
4 changes: 3 additions & 1 deletion packages/api/src/api/dtos/account/accountTransaction.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ export class AccountTransactionDto {
type: String,
description: "The to address of this transaction",
example: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C",
examples: ["0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", null],
nullable: true,
})
public readonly to: string;
public readonly to?: string;

@ApiProperty({
type: String,
Expand Down
13 changes: 6 additions & 7 deletions packages/api/src/api/transaction/transaction.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { mock } from "jest-mock-extended";
import { Logger } from "@nestjs/common";
import { TransactionService } from "../../transaction/transaction.service";
import { TransactionReceiptService } from "../../transaction/transactionReceipt.service";
import { TransactionStatus } from "../../transaction/entities/transaction.entity";
import { TransactionDetails } from "../../transaction/entities/transactionDetails.entity";
import { TransactionStatus, Transaction } from "../../transaction/entities/transaction.entity";
import { TransactionReceipt } from "../../transaction/entities/transactionReceipt.entity";
import { ResponseStatus, ResponseMessage } from "../dtos/common/responseBase.dto";
import { TransactionController } from "./transaction.controller";
Expand Down Expand Up @@ -57,7 +56,7 @@ describe("TransactionController", () => {
it("returns isError as 0 when transaction is successful", async () => {
jest
.spyOn(transactionServiceMock, "findOne")
.mockResolvedValue({ status: TransactionStatus.Included } as TransactionDetails);
.mockResolvedValue({ status: TransactionStatus.Included } as Transaction);

const response = await controller.getTransactionStatus(transactionHash);
expect(response).toEqual({
Expand All @@ -73,7 +72,7 @@ describe("TransactionController", () => {
it("returns isError as 1 when transaction is failed", async () => {
jest
.spyOn(transactionServiceMock, "findOne")
.mockResolvedValue({ status: TransactionStatus.Failed } as TransactionDetails);
.mockResolvedValue({ status: TransactionStatus.Failed } as Transaction);

const response = await controller.getTransactionStatus(transactionHash);
expect(response).toEqual({
Expand All @@ -91,7 +90,7 @@ describe("TransactionController", () => {
status: TransactionStatus.Failed,
error: "Error",
revertReason: "Reverted",
} as TransactionDetails);
} as Transaction);

const response = await controller.getTransactionStatus(transactionHash);
expect(response).toEqual({
Expand All @@ -107,7 +106,7 @@ describe("TransactionController", () => {
it("returns transaction revert reason in errDescription when transaction is failed and transaction revert reason is present", async () => {
jest
.spyOn(transactionServiceMock, "findOne")
.mockResolvedValue({ status: TransactionStatus.Failed, revertReason: "Reverted" } as TransactionDetails);
.mockResolvedValue({ status: TransactionStatus.Failed, revertReason: "Reverted" } as Transaction);

const response = await controller.getTransactionStatus(transactionHash);
expect(response).toEqual({
Expand All @@ -123,7 +122,7 @@ describe("TransactionController", () => {
it("returns empty errDescription when transaction is failed and transaction error and revert reason are not present", async () => {
jest
.spyOn(transactionServiceMock, "findOne")
.mockResolvedValue({ status: TransactionStatus.Failed } as TransactionDetails);
.mockResolvedValue({ status: TransactionStatus.Failed } as Transaction);

const response = await controller.getTransactionStatus(transactionHash);
expect(response).toEqual({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const normalizeAddressTransformer: ValueTransformer = {
if (!hex) {
return null;
}

return getAddress(hexTransformer.from(hex));
},
};
20 changes: 19 additions & 1 deletion packages/api/src/transaction/dtos/transaction.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ export class TransactionDto {
type: String,
description: "The address this transaction is to",
example: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C",
examples: ["0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", null],
nullable: true,
})
public readonly to: string;
public readonly to?: string;
Romsters marked this conversation as resolved.
Show resolved Hide resolved

@ApiProperty({
type: String,
Expand Down Expand Up @@ -204,4 +206,20 @@ export class TransactionDto {
nullable: true,
})
public readonly revertReason?: string;

@ApiProperty({
type: String,
description: "Gas used by the transaction",
example: "50000000",
})
public readonly gasUsed: string;

@ApiProperty({
type: String,
description: "Address of the deployed contract",
example: "50000000",
examples: ["0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", null],
nullable: true,
})
public readonly contractAddress?: string;
}
11 changes: 0 additions & 11 deletions packages/api/src/transaction/dtos/transactionDetails.dto.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export class AddressTransaction extends BaseEntity {
@Column({ type: "bytea", transformer: hexTransformer })
public readonly transactionHash: string;

@Column({ type: "bytea", transformer: normalizeAddressTransformer })
public readonly address: string;
@Column({ type: "bytea", transformer: normalizeAddressTransformer, nullable: true })
Romsters marked this conversation as resolved.
Show resolved Hide resolved
public readonly address?: string;

@Index()
@Column({ type: "bigint", transformer: bigIntNumberTransformer })
Expand Down
16 changes: 13 additions & 3 deletions packages/api/src/transaction/entities/transaction.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export class Transaction extends BaseEntity {
@Column({ generated: true, type: "bigint" })
public number: number;

@Column({ type: "bytea", transformer: normalizeAddressTransformer })
public readonly to: string;
@Column({ type: "bytea", transformer: normalizeAddressTransformer, nullable: true })
public readonly to?: string;
Romsters marked this conversation as resolved.
Show resolved Hide resolved

@Index()
@Column({ type: "bytea", transformer: normalizeAddressTransformer })
Expand Down Expand Up @@ -141,16 +141,26 @@ export class Transaction extends BaseEntity {
return !!this.batch;
}

public get gasUsed(): string {
return this.transactionReceipt ? this.transactionReceipt.gasUsed : null;
}

public get contractAddress(): string {
return this.transactionReceipt ? this.transactionReceipt.contractAddress : null;
}

toJSON(): any {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { number, receiptStatus, batch, ...restFields } = this;
const { number, receiptStatus, batch, transactionReceipt, ...restFields } = this;
return {
...restFields,
status: this.status,
commitTxHash: this.commitTxHash,
executeTxHash: this.executeTxHash,
proveTxHash: this.proveTxHash,
isL1BatchSealed: this.isL1BatchSealed,
gasUsed: this.gasUsed,
contractAddress: this.contractAddress,
};
}
}
18 changes: 0 additions & 18 deletions packages/api/src/transaction/entities/transactionDetails.entity.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export class TransactionReceipt extends BaseEntity {
@Column({ type: "bytea", transformer: normalizeAddressTransformer })
public readonly from: string;

@Column({ type: "bytea", transformer: hexTransformer, nullable: true })
public readonly to?: string;

@Index()
@Column({ type: "bytea", nullable: true, transformer: normalizeAddressTransformer })
public readonly contractAddress?: string;
Expand Down
3 changes: 1 addition & 2 deletions packages/api/src/transaction/transaction.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { buildDateFilter } from "../common/utils";
import { FilterTransactionsOptionsDto } from "./dtos/filterTransactionsOptions.dto";
import { TransferDto } from "../transfer/transfer.dto";
import { TransactionDto } from "./dtos/transaction.dto";
import { TransactionDetailsDto } from "./dtos/transactionDetails.dto";
import { TransferService } from "../transfer/transfer.service";
import { LogDto } from "../log/log.dto";
import { LogService } from "../log/log.service";
Expand Down Expand Up @@ -74,7 +73,7 @@ export class TransactionController {
@ApiNotFoundResponse({ description: "Transaction with the specified hash does not exist" })
public async getTransaction(
@Param("transactionHash", new ParseTransactionHashPipe()) transactionHash: string
): Promise<TransactionDetailsDto> {
): Promise<TransactionDto> {
const transactionDetail = await this.transactionService.findOne(transactionHash);
if (!transactionDetail) {
throw new NotFoundException();
Expand Down
3 changes: 1 addition & 2 deletions packages/api/src/transaction/transaction.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { TransactionController } from "./transaction.controller";
import { TransactionService } from "./transaction.service";
import { TransactionReceiptService } from "./transactionReceipt.service";
import { Transaction } from "./entities/transaction.entity";
import { TransactionDetails } from "./entities/transactionDetails.entity";
import { AddressTransaction } from "./entities/addressTransaction.entity";
import { TransactionReceipt } from "./entities/transactionReceipt.entity";
import { Batch } from "../batch/batch.entity";
Expand All @@ -14,7 +13,7 @@ import { LogModule } from "../log/log.module";

@Module({
imports: [
TypeOrmModule.forFeature([Transaction, TransactionDetails, AddressTransaction, TransactionReceipt, Batch]),
TypeOrmModule.forFeature([Transaction, AddressTransaction, TransactionReceipt, Batch]),
TransferModule,
LogModule,
CounterModule,
Expand Down
45 changes: 35 additions & 10 deletions packages/api/src/transaction/transaction.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { SortingOrder } from "../common/types";
import { CounterService } from "../counter/counter.service";
import { TransactionService, FilterTransactionsOptions } from "./transaction.service";
import { Transaction } from "./entities/transaction.entity";
import { TransactionDetails } from "./entities/transactionDetails.entity";
import { AddressTransaction } from "./entities/addressTransaction.entity";
import { Batch } from "../batch/batch.entity";

Expand All @@ -18,7 +17,6 @@ describe("TransactionService", () => {
let transaction;
let service: TransactionService;
let repositoryMock: typeorm.Repository<Transaction>;
let repositoryDetailMock: typeorm.Repository<TransactionDetails>;
let addressTransactionRepositoryMock: typeorm.Repository<AddressTransaction>;
let batchRepositoryMock: typeorm.Repository<Batch>;
let counterServiceMock: CounterService;
Expand All @@ -27,7 +25,6 @@ describe("TransactionService", () => {
beforeEach(async () => {
counterServiceMock = mock<CounterService>();
repositoryMock = mock<typeorm.Repository<Transaction>>();
repositoryDetailMock = mock<typeorm.Repository<TransactionDetails>>();
addressTransactionRepositoryMock = mock<typeorm.Repository<AddressTransaction>>();
batchRepositoryMock = mock<typeorm.Repository<Batch>>();
transaction = {
Expand All @@ -41,10 +38,6 @@ describe("TransactionService", () => {
provide: getRepositoryToken(Transaction),
useValue: repositoryMock,
},
{
provide: getRepositoryToken(TransactionDetails),
useValue: repositoryDetailMock,
},
{
provide: getRepositoryToken(AddressTransaction),
useValue: addressTransactionRepositoryMock,
Expand Down Expand Up @@ -73,13 +66,13 @@ describe("TransactionService", () => {

beforeEach(() => {
queryBuilderMock = mock<typeorm.SelectQueryBuilder<Transaction>>();
(repositoryDetailMock.createQueryBuilder as jest.Mock).mockReturnValue(queryBuilderMock);
(repositoryMock.createQueryBuilder as jest.Mock).mockReturnValue(queryBuilderMock);
(queryBuilderMock.getOne as jest.Mock).mockResolvedValue(null);
});

it("creates query builder with proper params", async () => {
await service.findOne(hash);
expect(repositoryDetailMock.createQueryBuilder).toHaveBeenCalledWith("transaction");
expect(repositoryMock.createQueryBuilder).toHaveBeenCalledWith("transaction");
});

it("filters transactions by the specified hash", async () => {
Expand All @@ -99,7 +92,10 @@ describe("TransactionService", () => {

it("selects only needed transactionReceipt fields", async () => {
await service.findOne(hash);
expect(queryBuilderMock.addSelect).toHaveBeenCalledWith(["transactionReceipt.gasUsed"]);
expect(queryBuilderMock.addSelect).toHaveBeenCalledWith([
"transactionReceipt.gasUsed",
"transactionReceipt.contractAddress",
]);
});

it("returns paginated result", async () => {
Expand Down Expand Up @@ -172,6 +168,19 @@ describe("TransactionService", () => {
expect(queryBuilderMock.where).toHaveBeenCalledWith(filterTransactionsOptions);
});

it("joins transactionReceipt record to get receipt specific fields", async () => {
await service.findAll(filterTransactionsOptions, pagingOptions);
expect(queryBuilderMock.leftJoin).toHaveBeenCalledWith("transaction.transactionReceipt", "transactionReceipt");
});

it("selects only needed transactionReceipt fields", async () => {
await service.findAll(filterTransactionsOptions, pagingOptions);
expect(queryBuilderMock.addSelect).toHaveBeenCalledWith([
"transactionReceipt.gasUsed",
"transactionReceipt.contractAddress",
]);
});

it("joins batch record to get batch specific fields", async () => {
await service.findAll(filterTransactionsOptions, pagingOptions);
expect(queryBuilderMock.leftJoin).toHaveBeenCalledWith("transaction.batch", "batch");
Expand Down Expand Up @@ -243,6 +252,22 @@ describe("TransactionService", () => {
);
});

it("joins transactionReceipt record to get receipt specific fields", async () => {
await service.findAll(filterTransactionsOptions, pagingOptions);
expect(addressTransactionsQueryBuilderMock.leftJoin).toHaveBeenCalledWith(
"transaction.transactionReceipt",
"transactionReceipt"
);
});

it("selects only needed transactionReceipt fields", async () => {
await service.findAll(filterTransactionsOptions, pagingOptions);
expect(addressTransactionsQueryBuilderMock.addSelect).toHaveBeenCalledWith([
"transactionReceipt.gasUsed",
"transactionReceipt.contractAddress",
]);
});

it("joins batch records", async () => {
await service.findAll(filterTransactionsOptions, pagingOptions);
expect(addressTransactionsQueryBuilderMock.leftJoinAndSelect).toBeCalledTimes(1);
Expand Down
Loading
Loading