Skip to content

Commit

Permalink
feat: add missing system contracts to the DB
Browse files Browse the repository at this point in the history
  • Loading branch information
vasyl-ivanchuk committed Jan 21, 2025
1 parent 63c757c commit 865efe2
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 172 deletions.
2 changes: 1 addition & 1 deletion packages/worker/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down
9 changes: 8 additions & 1 deletion packages/worker/src/app.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -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", () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/worker/src/app.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
107 changes: 107 additions & 0 deletions packages/worker/src/contracts/systemContract.service.spec.ts
Original file line number Diff line number Diff line change
@@ -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<BlockchainService>({
getCode: jest.fn().mockImplementation((address: string) => Promise.resolve(`${address}-code`)),
});

addressRepositoryMock = mock<AddressRepository>({
find: jest.fn(),
});

const app: TestingModule = await Test.createTestingModule({
providers: [
SystemContractService,
{
provide: BlockchainService,
useValue: blockchainServiceMock,
},
{
provide: AddressRepository,
useValue: addressRepositoryMock,
},
],
}).compile();

app.useLogger(mock<Logger>());
systemContractService = app.get<SystemContractService>(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>({ 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>({ 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`,
});
}
}
});
});
});
143 changes: 143 additions & 0 deletions packages/worker/src/contracts/systemContract.service.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
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",
},
];
}
}
Loading

0 comments on commit 865efe2

Please sign in to comment.