diff --git a/packages/worker/src/app.module.ts b/packages/worker/src/app.module.ts index 4e9371ed5..1b7ad5b43 100644 --- a/packages/worker/src/app.module.ts +++ b/packages/worker/src/app.module.ts @@ -52,7 +52,7 @@ import { MetricsModule } from "./metrics"; import { DbMetricsService } from "./dbMetrics.service"; import { UnitOfWorkModule } from "./unitOfWork"; import { DataFetcherService } from "./dataFetcher/dataFetcher.service"; -import { SystemContractService } from "./systemContracts/systemContract.service"; +import { SystemContractService } from "./contracts/systemContract.service"; @Module({ imports: [ diff --git a/packages/worker/src/app.service.spec.ts b/packages/worker/src/app.service.spec.ts index eb265b499..54d4810e0 100644 --- a/packages/worker/src/app.service.spec.ts +++ b/packages/worker/src/app.service.spec.ts @@ -13,7 +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 "./systemContracts/systemContract.service"; +import { SystemContractService } from "./contracts/systemContract.service"; jest.mock("./utils/runMigrations"); @@ -214,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 ddd67ec49..4cd15bc78 100644 --- a/packages/worker/src/app.service.ts +++ b/packages/worker/src/app.service.ts @@ -10,7 +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 "./systemContracts/systemContract.service"; +import { SystemContractService } from "./contracts/systemContract.service"; @Injectable() export class AppService implements OnModuleInit, OnModuleDestroy { diff --git a/packages/worker/src/contracts/systemContract.service.spec.ts b/packages/worker/src/contracts/systemContract.service.spec.ts new file mode 100644 index 000000000..8e07ebaa9 --- /dev/null +++ b/packages/worker/src/contracts/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/contracts/systemContract.service.ts b/packages/worker/src/contracts/systemContract.service.ts new file mode 100644 index 000000000..78171487e --- /dev/null +++ b/packages/worker/src/contracts/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 attribute 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", + }, + ]; + } +} diff --git a/packages/worker/src/systemContracts/systemContract.service.spec.ts b/packages/worker/src/systemContracts/systemContract.service.spec.ts deleted file mode 100644 index 3e9f88167..000000000 --- a/packages/worker/src/systemContracts/systemContract.service.spec.ts +++ /dev/null @@ -1,116 +0,0 @@ -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; - beforeEach(async () => { - blockchainServiceMock = mock({ - getCode(): Promise { - return Promise.resolve("0x11111111"); - }, - }); - - addressRepositoryMock = mock(); - jest.spyOn(addressRepositoryMock, "find").mockResolvedValue([ - mock
({ - address: "0x000000000000000000000000000000000000800b", - }), - mock
({ - address: "0x0000000000000000000000000000000000008002", - }), - mock
({ - address: "0x000000000000000000000000000000000000800c", - }), - mock
({ - address: "0x0000000000000000000000000000000000000001", - }), - mock
({ - address: "0x0000000000000000000000000000000000000000", - }), - mock
({ - address: "0x0000000000000000000000000000000000000002", - }), - mock
({ - address: "0x0000000000000000000000000000000000008010", - }), - mock
({ - address: "0x000000000000000000000000000000000000800a", - }), - mock
({ - address: "0x0000000000000000000000000000000000008009", - }), - mock
({ - address: "0x0000000000000000000000000000000000008004", - }), - mock
({ - address: "0x0000000000000000000000000000000000008006", - }), - mock
({ - address: "0x0000000000000000000000000000000000008005", - }), - mock
({ - address: "0x0000000000000000000000000000000000008008", - }), - mock
({ - address: "0x0000000000000000000000000000000000008003", - }), - mock
({ - address: "0x000000000000000000000000000000000000800d", - }), - mock
({ - address: "0x000000000000000000000000000000000000800e", - }), - ]); - - 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("dont add any system contracts", async () => { - await systemContractService.addSystemContracts(); - expect(addressRepositoryMock.add).toBeCalledTimes(0); - }); - - it("add all system contracts", async () => { - jest.spyOn(addressRepositoryMock, "find").mockResolvedValue([]); - await systemContractService.addSystemContracts(); - expect(addressRepositoryMock.add).toBeCalledTimes(16); - }); - - it("add some system contracts", async () => { - jest.spyOn(addressRepositoryMock, "find").mockResolvedValue([ - mock
({ - address: "0x000000000000000000000000000000000000800d", - }), - mock
({ - address: "0x0000000000000000000000000000000000008006", - }), - ]); - await systemContractService.addSystemContracts(); - expect(addressRepositoryMock.add).toBeCalledTimes(14); - }); - }); -}); diff --git a/packages/worker/src/systemContracts/systemContract.service.ts b/packages/worker/src/systemContracts/systemContract.service.ts deleted file mode 100644 index 53adfcaaa..000000000 --- a/packages/worker/src/systemContracts/systemContract.service.ts +++ /dev/null @@ -1,53 +0,0 @@ -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 = this.getSystemContracts(); - const existingContracts = await this.addressRepository.find({ - where: { - address: In(systemContracts.map((contract) => contract.address)), - }, - }); - - for (const contract of systemContracts) { - if (!existingContracts.find((existingContract) => existingContract.address === contract.address)) { - const bytecode = await this.blockchainService.getCode(contract.address); - - await this.addressRepository.add({ - address: contract.address, - bytecode, - }); - } - } - } - - getSystemContracts() { - return [ - { address: "0x000000000000000000000000000000000000800b" }, - { address: "0x0000000000000000000000000000000000008002" }, - { address: "0x000000000000000000000000000000000000800c" }, - { address: "0x0000000000000000000000000000000000000001" }, - { address: "0x0000000000000000000000000000000000000000" }, - { address: "0x0000000000000000000000000000000000000002" }, - { address: "0x0000000000000000000000000000000000008010" }, - { address: "0x000000000000000000000000000000000000800a" }, - { address: "0x0000000000000000000000000000000000008009" }, - { address: "0x0000000000000000000000000000000000008004" }, - { address: "0x0000000000000000000000000000000000008006" }, - { address: "0x0000000000000000000000000000000000008005" }, - { address: "0x0000000000000000000000000000000000008008" }, - { address: "0x0000000000000000000000000000000000008003" }, - { address: "0x000000000000000000000000000000000000800d" }, - { address: "0x000000000000000000000000000000000000800e" }, - ]; - } -}