diff --git a/packages/app/src/components/contract/InfoTable.vue b/packages/app/src/components/contract/InfoTable.vue
index 27405d72f..a9cab847f 100644
--- a/packages/app/src/components/contract/InfoTable.vue
+++ b/packages/app/src/components/contract/InfoTable.vue
@@ -9,7 +9,7 @@
-
+
{{ t("contract.table.creator") }}
diff --git a/packages/worker/src/app.module.ts b/packages/worker/src/app.module.ts
index bec09a377..da46beea4 100644
--- a/packages/worker/src/app.module.ts
+++ b/packages/worker/src/app.module.ts
@@ -52,6 +52,7 @@ import { MetricsModule } from "./metrics";
import { DbMetricsService } from "./dbMetrics.service";
import { UnitOfWorkModule } from "./unitOfWork";
import { DataFetcherService } from "./dataFetcher/dataFetcher.service";
+import { SystemContractService } from "./contract/systemContract.service";
@Module({
imports: [
@@ -130,6 +131,7 @@ import { DataFetcherService } from "./dataFetcher/dataFetcher.service";
Logger,
RetryDelayProvider,
DbMetricsService,
+ SystemContractService,
],
})
export class AppModule {}
diff --git a/packages/worker/src/app.service.spec.ts b/packages/worker/src/app.service.spec.ts
index 3d4afb836..60d5a8b29 100644
--- a/packages/worker/src/app.service.spec.ts
+++ b/packages/worker/src/app.service.spec.ts
@@ -13,6 +13,7 @@ import { BlocksRevertService } from "./blocksRevert";
import { TokenOffChainDataSaverService } from "./token/tokenOffChainData/tokenOffChainDataSaver.service";
import runMigrations from "./utils/runMigrations";
import { BLOCKS_REVERT_DETECTED_EVENT } from "./constants";
+import { SystemContractService } from "./contract/systemContract.service";
jest.mock("./utils/runMigrations");
@@ -39,6 +40,7 @@ describe("AppService", () => {
let tokenOffChainDataSaverService: TokenOffChainDataSaverService;
let dataSourceMock: DataSource;
let configServiceMock: ConfigService;
+ let systemContractService: SystemContractService;
beforeEach(async () => {
balancesCleanerService = mock({
@@ -68,6 +70,9 @@ describe("AppService", () => {
configServiceMock = mock({
get: jest.fn().mockReturnValue(false),
});
+ systemContractService = mock({
+ addSystemContracts: jest.fn().mockResolvedValue(null),
+ });
const module = await Test.createTestingModule({
imports: [EventEmitterModule.forRoot()],
@@ -106,6 +111,10 @@ describe("AppService", () => {
provide: ConfigService,
useValue: configServiceMock,
},
+ {
+ provide: SystemContractService,
+ useValue: systemContractService,
+ },
],
}).compile();
@@ -205,6 +214,13 @@ describe("AppService", () => {
appService.onModuleDestroy();
expect(tokenOffChainDataSaverService.stop).toBeCalledTimes(1);
});
+
+ it("adds system contracts", async () => {
+ appService.onModuleInit();
+ await migrationsRunFinished;
+ expect(systemContractService.addSystemContracts).toBeCalledTimes(1);
+ appService.onModuleDestroy();
+ });
});
describe("onModuleDestroy", () => {
diff --git a/packages/worker/src/app.service.ts b/packages/worker/src/app.service.ts
index d6ec852c7..71d82079e 100644
--- a/packages/worker/src/app.service.ts
+++ b/packages/worker/src/app.service.ts
@@ -10,6 +10,7 @@ import { CounterService } from "./counter";
import { BalancesCleanerService } from "./balance";
import { TokenOffChainDataSaverService } from "./token/tokenOffChainData/tokenOffChainDataSaver.service";
import runMigrations from "./utils/runMigrations";
+import { SystemContractService } from "./contract/systemContract.service";
@Injectable()
export class AppService implements OnModuleInit, OnModuleDestroy {
@@ -23,13 +24,15 @@ export class AppService implements OnModuleInit, OnModuleDestroy {
private readonly balancesCleanerService: BalancesCleanerService,
private readonly tokenOffChainDataSaverService: TokenOffChainDataSaverService,
private readonly dataSource: DataSource,
- private readonly configService: ConfigService
+ private readonly configService: ConfigService,
+ private readonly systemContractService: SystemContractService
) {
this.logger = new Logger(AppService.name);
}
public onModuleInit() {
runMigrations(this.dataSource, this.logger).then(() => {
+ this.systemContractService.addSystemContracts();
this.startWorkers();
});
}
diff --git a/packages/worker/src/contract/systemContract.service.spec.ts b/packages/worker/src/contract/systemContract.service.spec.ts
new file mode 100644
index 000000000..8e07ebaa9
--- /dev/null
+++ b/packages/worker/src/contract/systemContract.service.spec.ts
@@ -0,0 +1,107 @@
+import { mock } from "jest-mock-extended";
+import { Test, TestingModule } from "@nestjs/testing";
+import { Logger } from "@nestjs/common";
+import { BlockchainService } from "../blockchain/blockchain.service";
+import { AddressRepository } from "../repositories/address.repository";
+import { SystemContractService } from "./systemContract.service";
+import { Address } from "../entities";
+
+describe("SystemContractService", () => {
+ let systemContractService: SystemContractService;
+ let blockchainServiceMock: BlockchainService;
+ let addressRepositoryMock: AddressRepository;
+ const systemContracts = SystemContractService.getSystemContracts();
+
+ beforeEach(async () => {
+ blockchainServiceMock = mock({
+ getCode: jest.fn().mockImplementation((address: string) => Promise.resolve(`${address}-code`)),
+ });
+
+ addressRepositoryMock = mock({
+ find: jest.fn(),
+ });
+
+ const app: TestingModule = await Test.createTestingModule({
+ providers: [
+ SystemContractService,
+ {
+ provide: BlockchainService,
+ useValue: blockchainServiceMock,
+ },
+ {
+ provide: AddressRepository,
+ useValue: addressRepositoryMock,
+ },
+ ],
+ }).compile();
+
+ app.useLogger(mock());
+ systemContractService = app.get(SystemContractService);
+ });
+
+ describe("addSystemContracts", () => {
+ it("doesn't add any system contracts if they already exist in DB", async () => {
+ (addressRepositoryMock.find as jest.Mock).mockResolvedValue(
+ SystemContractService.getSystemContracts().map((contract) => mock({ address: contract.address }))
+ );
+ await systemContractService.addSystemContracts();
+ expect(addressRepositoryMock.add).toBeCalledTimes(0);
+ });
+
+ it("adds all system contracts if none of them exist in the DB", async () => {
+ (addressRepositoryMock.find as jest.Mock).mockResolvedValue([]);
+ await systemContractService.addSystemContracts();
+ expect(addressRepositoryMock.add).toBeCalledTimes(systemContracts.length);
+ for (const systemContract of systemContracts) {
+ expect(addressRepositoryMock.add).toBeCalledWith({
+ address: systemContract.address,
+ bytecode: `${systemContract.address}-code`,
+ });
+ }
+ });
+
+ it("adds only missing system contracts", async () => {
+ const existingContractAddresses = [
+ "0x000000000000000000000000000000000000800d",
+ "0x0000000000000000000000000000000000008006",
+ ];
+ (addressRepositoryMock.find as jest.Mock).mockResolvedValue(
+ existingContractAddresses.map((existingContractAddress) => mock({ address: existingContractAddress }))
+ );
+ await systemContractService.addSystemContracts();
+ expect(addressRepositoryMock.add).toBeCalledTimes(systemContracts.length - existingContractAddresses.length);
+ for (const systemContract of systemContracts) {
+ if (!existingContractAddresses.includes(systemContract.address)) {
+ expect(addressRepositoryMock.add).toBeCalledWith({
+ address: systemContract.address,
+ bytecode: `${systemContract.address}-code`,
+ });
+ }
+ }
+ });
+
+ it("adds contracts only if they are deployed to the network", async () => {
+ const notDeployedSystemContracts = [
+ "0x000000000000000000000000000000000000800d",
+ "0x0000000000000000000000000000000000008006",
+ ];
+ (addressRepositoryMock.find as jest.Mock).mockResolvedValue([]);
+ (blockchainServiceMock.getCode as jest.Mock).mockImplementation(async (address: string) => {
+ if (notDeployedSystemContracts.includes(address)) {
+ return "0x";
+ }
+ return `${address}-code`;
+ });
+ await systemContractService.addSystemContracts();
+ expect(addressRepositoryMock.add).toBeCalledTimes(systemContracts.length - notDeployedSystemContracts.length);
+ for (const systemContract of systemContracts) {
+ if (!notDeployedSystemContracts.includes(systemContract.address)) {
+ expect(addressRepositoryMock.add).toBeCalledWith({
+ address: systemContract.address,
+ bytecode: `${systemContract.address}-code`,
+ });
+ }
+ }
+ });
+ });
+});
diff --git a/packages/worker/src/contract/systemContract.service.ts b/packages/worker/src/contract/systemContract.service.ts
new file mode 100644
index 000000000..09a8b9304
--- /dev/null
+++ b/packages/worker/src/contract/systemContract.service.ts
@@ -0,0 +1,143 @@
+import { Injectable } from "@nestjs/common";
+import { BlockchainService } from "../blockchain/blockchain.service";
+import { AddressRepository } from "../repositories";
+import { In } from "typeorm";
+
+@Injectable()
+export class SystemContractService {
+ constructor(
+ private readonly addressRepository: AddressRepository,
+ private readonly blockchainService: BlockchainService
+ ) {}
+
+ public async addSystemContracts(): Promise {
+ const systemContracts = SystemContractService.getSystemContracts();
+ const existingContracts = await this.addressRepository.find({
+ where: {
+ address: In(systemContracts.map((contract) => contract.address)),
+ },
+ select: {
+ address: true,
+ },
+ });
+
+ for (const contract of systemContracts) {
+ if (!existingContracts.find((existingContract) => existingContract.address === contract.address)) {
+ const bytecode = await this.blockchainService.getCode(contract.address);
+ // some contract might not exist on the environment yet
+ if (bytecode !== "0x") {
+ await this.addressRepository.add({
+ address: contract.address,
+ bytecode,
+ });
+ }
+ }
+ }
+ }
+
+ public static getSystemContracts() {
+ // name field is never used, it's just for better readability & understanding
+ return [
+ {
+ address: "0x0000000000000000000000000000000000000000",
+ name: "EmptyContract",
+ },
+ {
+ address: "0x0000000000000000000000000000000000000001",
+ name: "Ecrecover",
+ },
+ {
+ address: "0x0000000000000000000000000000000000000002",
+ name: "SHA256",
+ },
+ {
+ address: "0x0000000000000000000000000000000000000006",
+ name: "EcAdd",
+ },
+ {
+ address: "0x0000000000000000000000000000000000000007",
+ name: "EcMul",
+ },
+ {
+ address: "0x0000000000000000000000000000000000000008",
+ name: "EcPairing",
+ },
+ {
+ address: "0x0000000000000000000000000000000000008001",
+ name: "EmptyContract",
+ },
+ {
+ address: "0x0000000000000000000000000000000000008002",
+ name: "AccountCodeStorage",
+ },
+ {
+ address: "0x0000000000000000000000000000000000008003",
+ name: "NonceHolder",
+ },
+ {
+ address: "0x0000000000000000000000000000000000008004",
+ name: "KnownCodesStorage",
+ },
+ {
+ address: "0x0000000000000000000000000000000000008005",
+ name: "ImmutableSimulator",
+ },
+ {
+ address: "0x0000000000000000000000000000000000008006",
+ name: "ContractDeployer",
+ },
+ {
+ address: "0x0000000000000000000000000000000000008008",
+ name: "L1Messenger",
+ },
+ {
+ address: "0x0000000000000000000000000000000000008009",
+ name: "MsgValueSimulator",
+ },
+ {
+ address: "0x000000000000000000000000000000000000800a",
+ name: "L2BaseToken",
+ },
+ {
+ address: "0x000000000000000000000000000000000000800b",
+ name: "SystemContext",
+ },
+ {
+ address: "0x000000000000000000000000000000000000800c",
+ name: "BootloaderUtilities",
+ },
+ {
+ address: "0x000000000000000000000000000000000000800d",
+ name: "EventWriter",
+ },
+ {
+ address: "0x000000000000000000000000000000000000800e",
+ name: "Compressor",
+ },
+ {
+ address: "0x000000000000000000000000000000000000800f",
+ name: "ComplexUpgrader",
+ },
+ {
+ address: "0x0000000000000000000000000000000000008010",
+ name: "Keccak256",
+ },
+ {
+ address: "0x0000000000000000000000000000000000008012",
+ name: "CodeOracle",
+ },
+ {
+ address: "0x0000000000000000000000000000000000000100",
+ name: "P256Verify",
+ },
+ {
+ address: "0x0000000000000000000000000000000000008011",
+ name: "PubdataChunkPublisher",
+ },
+ {
+ address: "0x0000000000000000000000000000000000010000",
+ name: "Create2Factory",
+ },
+ ];
+ }
+}