-
Notifications
You must be signed in to change notification settings - Fork 113
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add system contracts on the first run (#372)
# What ❔ This PR addresses an issue where system contracts were not displayed as contracts in the UI until they were updated. The implemented solution ensures that system contracts are loaded and saved in the database immediately after work begins. This guarantees their proper display in the UI, providing a consistent and accurate reflection of the system's state. ## Why ❔ System contracts are not displayed as contracts in the UI until they have been updated. ## Checklist <!-- Check your PR fulfills the following items. --> <!-- For draft PRs check the boxes as you complete them. --> This PR fixes: #358 - [+] 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. Co-authored-by: Vasyl Ivanchuk <[email protected]>
- Loading branch information
1 parent
1bd495d
commit 5576811
Showing
6 changed files
with
273 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
107 changes: 107 additions & 0 deletions
107
packages/worker/src/contract/systemContract.service.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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`, | ||
}); | ||
} | ||
} | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 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", | ||
}, | ||
]; | ||
} | ||
} |