diff --git a/system-contracts/test/Compressor.spec.ts b/system-contracts/test/Compressor.spec.ts index 2d50ff59a..7913412e5 100644 --- a/system-contracts/test/Compressor.spec.ts +++ b/system-contracts/test/Compressor.spec.ts @@ -1,5 +1,4 @@ import { expect } from "chai"; -import type { BytesLike } from "ethers"; import { BigNumber } from "ethers"; import { ethers, network } from "hardhat"; import type { Wallet } from "zksync-web3"; @@ -13,7 +12,7 @@ import { TWO_IN_256, } from "./shared/constants"; import { encodeCalldata, getMock, prepareEnvironment, setResult } from "./shared/mocks"; -import { deployContractOnAddress, getWallets } from "./shared/utils"; +import { compressStateDiffs, deployContractOnAddress, encodeStateDiffs, getWallets } from "./shared/utils"; describe("Compressor tests", function () { let wallet: Wallet; @@ -374,73 +373,3 @@ describe("Compressor tests", function () { }); }); }); - -interface StateDiff { - key: BytesLike; - index: number; - initValue: BigNumber; - finalValue: BigNumber; -} - -function encodeStateDiffs(stateDiffs: StateDiff[]): string { - const rawStateDiffs = []; - for (const stateDiff of stateDiffs) { - rawStateDiffs.push( - ethers.utils.solidityPack( - ["address", "bytes32", "bytes32", "uint64", "uint256", "uint256", "bytes"], - [ - ethers.constants.AddressZero, - ethers.constants.HashZero, - stateDiff.key, - stateDiff.index, - stateDiff.initValue, - stateDiff.finalValue, - "0x" + "00".repeat(116), - ] - ) - ); - } - return ethers.utils.hexlify(ethers.utils.concat(rawStateDiffs)); -} - -function compressStateDiffs(enumerationIndexSize: number, stateDiffs: StateDiff[]): string { - let num_initial = 0; - const initial = []; - const repeated = []; - for (const stateDiff of stateDiffs) { - const addition = stateDiff.finalValue.sub(stateDiff.initValue).add(TWO_IN_256).mod(TWO_IN_256); - const subtraction = stateDiff.initValue.sub(stateDiff.finalValue).add(TWO_IN_256).mod(TWO_IN_256); - let op = 3; - let min = stateDiff.finalValue; - if (addition.lt(min)) { - min = addition; - op = 1; - } - if (subtraction.lt(min)) { - min = subtraction; - op = 2; - } - if (min.gte(BigNumber.from(2).pow(248))) { - min = stateDiff.finalValue; - op = 0; - } - let len = 0; - const minHex = min.eq(0) ? "0x" : min.toHexString(); - if (op > 0) { - len = (minHex.length - 2) / 2; - } - const metadata = (len << 3) + op; - const enumerationIndexType = "uint" + (enumerationIndexSize * 8).toString(); - if (stateDiff.index === 0) { - num_initial += 1; - initial.push(ethers.utils.solidityPack(["bytes32", "uint8", "bytes"], [stateDiff.key, metadata, minHex])); - } else { - repeated.push( - ethers.utils.solidityPack([enumerationIndexType, "uint8", "bytes"], [stateDiff.index, metadata, minHex]) - ); - } - } - return ethers.utils.hexlify( - ethers.utils.concat([ethers.utils.solidityPack(["uint16"], [num_initial]), ...initial, ...repeated]) - ); -} diff --git a/system-contracts/test/L1Messenger.spec.ts b/system-contracts/test/L1Messenger.spec.ts new file mode 100644 index 000000000..609eaff5c --- /dev/null +++ b/system-contracts/test/L1Messenger.spec.ts @@ -0,0 +1,529 @@ +import { ethers, network } from "hardhat"; +import type { L1Messenger } from "../typechain"; +import { L1MessengerFactory } from "../typechain"; +import { prepareEnvironment, setResult } from "./shared/mocks"; +import type { StateDiff } from "./shared/utils"; +import { compressStateDiffs, deployContractOnAddress, encodeStateDiffs, getCode, getWallets } from "./shared/utils"; +import { utils } from "zksync-web3"; +import type { Wallet } from "zksync-web3"; +import { + TEST_KNOWN_CODE_STORAGE_CONTRACT_ADDRESS, + TEST_L1_MESSENGER_SYSTEM_CONTRACT_ADDRESS, + TEST_BOOTLOADER_FORMAL_ADDRESS, + TWO_IN_256, +} from "./shared/constants"; +import { expect } from "chai"; +import { BigNumber } from "ethers"; +import { randomBytes } from "crypto"; + +describe("L1Messenger tests", () => { + let l1Messenger: L1Messenger; + let wallet: Wallet; + let l1MessengerAccount: ethers.Signer; + let knownCodeStorageAccount: ethers.Signer; + let bootloaderAccount: ethers.Signer; + let stateDiffsSetupData: StateDiffSetupData; + let logData: LogData; + let bytecodeData: ContentLengthPair; + let emulator: L1MessengerPubdataEmulator; + + before(async () => { + await prepareEnvironment(); + wallet = getWallets()[0]; + await deployContractOnAddress(TEST_L1_MESSENGER_SYSTEM_CONTRACT_ADDRESS, "L1Messenger"); + l1Messenger = L1MessengerFactory.connect(TEST_L1_MESSENGER_SYSTEM_CONTRACT_ADDRESS, wallet); + l1MessengerAccount = await ethers.getImpersonatedSigner(TEST_L1_MESSENGER_SYSTEM_CONTRACT_ADDRESS); + knownCodeStorageAccount = await ethers.getImpersonatedSigner(TEST_KNOWN_CODE_STORAGE_CONTRACT_ADDRESS); + bootloaderAccount = await ethers.getImpersonatedSigner(TEST_BOOTLOADER_FORMAL_ADDRESS); + // setup + stateDiffsSetupData = await setupStateDiffs(); + logData = setupLogData(l1MessengerAccount, l1Messenger); + bytecodeData = await setupBytecodeData(l1Messenger.address); + await setResult("SystemContext", "txNumberInBlock", [], { + failure: false, + returnData: ethers.utils.defaultAbiCoder.encode(["uint16"], [1]), + }); + emulator = new L1MessengerPubdataEmulator(); + }); + + after(async () => { + // cleaning the state of l1Messenger + await l1Messenger + .connect(bootloaderAccount) + .publishPubdataAndClearState(emulator.buildTotalL2ToL1PubdataAndStateDiffs()); + await network.provider.request({ + method: "hardhat_stopImpersonatingAccount", + params: [TEST_L1_MESSENGER_SYSTEM_CONTRACT_ADDRESS], + }); + await network.provider.request({ + method: "hardhat_stopImpersonatingAccount", + params: [TEST_KNOWN_CODE_STORAGE_CONTRACT_ADDRESS], + }); + await network.provider.request({ + method: "hardhat_stopImpersonatingAccount", + params: [TEST_BOOTLOADER_FORMAL_ADDRESS], + }); + }); + + describe("publishPubdataAndClearState", async () => { + it("publishPubdataAndClearState passes correctly", async () => { + await ( + await l1Messenger.connect(l1MessengerAccount).sendL2ToL1Log(logData.isService, logData.key, logData.value) + ).wait(); + emulator.addLog(logData.logs[0].log); + await (await l1Messenger.connect(l1MessengerAccount).sendToL1(logData.messages[0].message)).wait(); + emulator.addLog(logData.messages[0].log); + emulator.addMessage({ + lengthBytes: logData.messages[0].currentMessageLengthBytes, + content: logData.messages[0].message, + }); + await ( + await l1Messenger + .connect(knownCodeStorageAccount) + .requestBytecodeL1Publication(await ethers.utils.hexlify(utils.hashBytecode(bytecodeData.content)), { + gasLimit: 130000000, + }) + ).wait(); + emulator.addBytecode(bytecodeData); + emulator.setStateDiffsSetupData(stateDiffsSetupData); + await ( + await l1Messenger + .connect(bootloaderAccount) + .publishPubdataAndClearState(emulator.buildTotalL2ToL1PubdataAndStateDiffs(), { gasLimit: 10000000 }) + ).wait(); + }); + + it("should revert Too many L2->L1 logs", async () => { + // set numberOfLogsBytes to 0x900 to trigger the revert (max value is 0x800) + await expect( + l1Messenger + .connect(bootloaderAccount) + .publishPubdataAndClearState(emulator.buildTotalL2ToL1PubdataAndStateDiffs({ numberOfLogs: 0x900 })) + ).to.be.rejectedWith("Too many L2->L1 logs"); + }); + + it("should revert logshashes mismatch", async () => { + await ( + await l1Messenger.connect(l1MessengerAccount).sendL2ToL1Log(logData.isService, logData.key, logData.value) + ).wait(); + await (await l1Messenger.connect(l1MessengerAccount).sendToL1(logData.messages[0].message)).wait(); + // set secondlog hash to random data to trigger the revert + const overrideData = { encodedLogs: [...emulator.encodedLogs] }; + overrideData.encodedLogs[1] = encodeL2ToL1Log({ + l2ShardId: 0, + isService: true, + txNumberInBlock: 1, + sender: l1Messenger.address, + key: ethers.utils.hexZeroPad(ethers.utils.hexStripZeros(l1MessengerAccount.address), 32).toLowerCase(), + value: ethers.utils.hexlify(randomBytes(32)), + }); + await expect( + l1Messenger + .connect(bootloaderAccount) + .publishPubdataAndClearState(emulator.buildTotalL2ToL1PubdataAndStateDiffs(overrideData)) + ).to.be.rejectedWith("reconstructedChainedLogsHash is not equal to chainedLogsHash"); + }); + + it("should revert chainedMessageHash mismatch", async () => { + // Buffer.alloc(32, 6), to trigger the revert + const wrongMessage = { lengthBytes: logData.messages[0].currentMessageLengthBytes, content: Buffer.alloc(32, 6) }; + const overrideData = { messages: [...emulator.messages] }; + overrideData.messages[0] = wrongMessage; + await expect( + l1Messenger + .connect(bootloaderAccount) + .publishPubdataAndClearState(emulator.buildTotalL2ToL1PubdataAndStateDiffs(overrideData)) + ).to.be.rejectedWith("reconstructedChainedMessagesHash is not equal to chainedMessagesHash"); + }); + + it("should revert state diff compression version mismatch", async () => { + await ( + await l1Messenger + .connect(knownCodeStorageAccount) + .requestBytecodeL1Publication(await ethers.utils.hexlify(utils.hashBytecode(bytecodeData.content)), { + gasLimit: 130000000, + }) + ).wait(); + // modify version to trigger the revert + await expect( + l1Messenger.connect(bootloaderAccount).publishPubdataAndClearState( + emulator.buildTotalL2ToL1PubdataAndStateDiffs({ + version: ethers.utils.hexZeroPad(ethers.utils.hexlify(66), 1), + }) + ) + ).to.be.rejectedWith("state diff compression version mismatch"); + }); + + it("should revert extra data", async () => { + // add extra data to trigger the revert + await expect( + l1Messenger + .connect(bootloaderAccount) + .publishPubdataAndClearState( + ethers.utils.concat([emulator.buildTotalL2ToL1PubdataAndStateDiffs(), Buffer.alloc(1, 64)]) + ) + ).to.be.rejectedWith("Extra data in the totalL2ToL1Pubdata array"); + }); + }); + + describe("sendL2ToL1Log", async () => { + it("should revert when not called by the system contract", async () => { + await expect(l1Messenger.sendL2ToL1Log(true, logData.key, logData.value)).to.be.rejectedWith( + "This method require the caller to be system contract" + ); + }); + + it("should emit L2ToL1LogSent event when called by the system contract", async () => { + await expect( + l1Messenger + .connect(l1MessengerAccount) + .sendL2ToL1Log(true, ethers.utils.hexlify(logData.key), ethers.utils.hexlify(logData.value)) + ) + .to.emit(l1Messenger, "L2ToL1LogSent") + .withArgs([ + 0, + true, + 1, + l1MessengerAccount.address, + ethers.utils.hexlify(logData.key), + ethers.utils.hexlify(logData.value), + ]); + emulator.addLog(logData.logs[0].log); + }); + + it("should emit L2ToL1LogSent event when called by the system contract with isService false", async () => { + await expect( + l1Messenger + .connect(l1MessengerAccount) + .sendL2ToL1Log(false, ethers.utils.hexlify(logData.key), ethers.utils.hexlify(logData.value)) + ) + .to.emit(l1Messenger, "L2ToL1LogSent") + .withArgs([ + 0, + false, + 1, + l1MessengerAccount.address, + ethers.utils.hexlify(logData.key), + ethers.utils.hexlify(logData.value), + ]); + emulator.addLog( + encodeL2ToL1Log({ + l2ShardId: 0, + isService: false, + txNumberInBlock: 1, + sender: l1MessengerAccount.address, + key: logData.key, + value: logData.value, + }) + ); + }); + }); + + describe("sendToL1", async () => { + it("should emit L1MessageSent & L2ToL1LogSent events", async () => { + const expectedKey = ethers.utils + .hexZeroPad(ethers.utils.hexStripZeros(l1MessengerAccount.address), 32) + .toLowerCase(); + await expect(l1Messenger.connect(l1MessengerAccount).sendToL1(logData.messages[0].message)) + .to.emit(l1Messenger, "L1MessageSent") + .withArgs( + l1MessengerAccount.address, + ethers.utils.keccak256(logData.messages[0].message), + logData.messages[0].message + ) + .and.to.emit(l1Messenger, "L2ToL1LogSent") + .withArgs([0, true, 1, l1Messenger.address, expectedKey, ethers.utils.keccak256(logData.messages[0].message)]); + emulator.addLog(logData.messages[0].log); + emulator.addMessage({ + lengthBytes: logData.messages[0].currentMessageLengthBytes, + content: logData.messages[0].message, + }); + }); + }); + + describe("requestBytecodeL1Publication", async () => { + it("should revert when not called by known code storage contract", async () => { + const byteCodeHash = ethers.utils.hexlify(randomBytes(32)); + await expect(l1Messenger.requestBytecodeL1Publication(byteCodeHash)).to.be.rejectedWith("Inappropriate caller"); + }); + + it("shoud emit event, called by known code system contract", async () => { + await expect( + l1Messenger + .connect(knownCodeStorageAccount) + .requestBytecodeL1Publication(await ethers.utils.hexlify(utils.hashBytecode(bytecodeData.content)), { + gasLimit: 130000000, + }) + ) + .to.emit(l1Messenger, "BytecodeL1PublicationRequested") + .withArgs(await ethers.utils.hexlify(utils.hashBytecode(bytecodeData.content))); + emulator.addBytecode(bytecodeData); + }); + }); +}); + +// Interface represents the structure of the data that that is used in totalL2ToL1PubdataAndStateDiffs. +interface StateDiffSetupData { + encodedStateDiffs: string; + compressedStateDiffs: string; + enumerationIndexSizeBytes: string; + numberOfStateDiffsBytes: string; + compressedStateDiffsSizeBytes: string; +} + +async function setupStateDiffs(): Promise { + const stateDiffs: StateDiff[] = [ + { + key: "0x1234567890123456789012345678901234567890123456789012345678901230", + index: 0, + initValue: BigNumber.from("0x1234567890123456789012345678901234567890123456789012345678901231"), + finalValue: BigNumber.from("0x1234567890123456789012345678901234567890123456789012345678901230"), + }, + { + key: "0x1234567890123456789012345678901234567890123456789012345678901232", + index: 1, + initValue: TWO_IN_256.sub(1), + finalValue: BigNumber.from(1), + }, + { + key: "0x1234567890123456789012345678901234567890123456789012345678901234", + index: 0, + initValue: TWO_IN_256.div(2), + finalValue: BigNumber.from(1), + }, + { + key: "0x1234567890123456789012345678901234567890123456789012345678901236", + index: 2323, + initValue: BigNumber.from("0x1234567890123456789012345678901234567890123456789012345678901237"), + finalValue: BigNumber.from("0x0239329298382323782378478237842378478237847237237872373272373272"), + }, + { + key: "0x1234567890123456789012345678901234567890123456789012345678901238", + index: 2, + initValue: BigNumber.from(0), + finalValue: BigNumber.from(1), + }, + ]; + const encodedStateDiffs = encodeStateDiffs(stateDiffs); + const compressedStateDiffs = compressStateDiffs(4, stateDiffs); + const enumerationIndexSizeBytes = ethers.utils.hexZeroPad(ethers.utils.hexlify(4), 1); + await setResult( + "Compressor", + "verifyCompressedStateDiffs", + [stateDiffs.length, 4, encodedStateDiffs, compressedStateDiffs], + { + failure: false, + returnData: ethers.utils.defaultAbiCoder.encode(["bytes32"], [ethers.utils.keccak256(encodedStateDiffs)]), + } + ); + const numberOfStateDiffsBytes = ethers.utils.hexZeroPad(ethers.utils.hexlify(stateDiffs.length), 4); + const compressedStateDiffsSizeBytes = ethers.utils.hexZeroPad( + ethers.utils.hexlify(ethers.utils.arrayify(compressedStateDiffs).length), + 3 + ); + return { + encodedStateDiffs, + compressedStateDiffs, + enumerationIndexSizeBytes, + numberOfStateDiffsBytes, + compressedStateDiffsSizeBytes, + }; +} + +// Interface for L2ToL1Log struct. +interface L2ToL1Log { + l2ShardId: number; + isService: boolean; + txNumberInBlock: number; + sender: string; + key: Buffer; + value: Buffer; +} + +// Function to encode L2ToL1Log struct. +function encodeL2ToL1Log(log: L2ToL1Log): string { + return ethers.utils.concat([ + ethers.utils.hexlify([log.l2ShardId]), + ethers.utils.hexlify(log.isService ? 1 : 0), + ethers.utils.hexZeroPad(ethers.utils.hexlify(log.txNumberInBlock), 2), + ethers.utils.hexZeroPad(log.sender, 20), + log.key, + log.value, + ]); +} + +interface LogInfo { + log: string; +} + +interface MessageInfo extends LogInfo { + message: string; + currentMessageLengthBytes: string; +} + +// The LogData interface represents the structure of the data that will be logged. +interface LogData { + isService: boolean; + key: Buffer; + value: Buffer; + messages: MessageInfo[]; + logs: LogInfo[]; +} + +function setupLogData(l1MessengerAccount: ethers.Signer, l1Messenger: L1Messenger): LogData { + const key = Buffer.alloc(32, 1); + const value = Buffer.alloc(32, 2); + const message = Buffer.alloc(32, 3); + const currentMessageLengthBytes = ethers.utils.hexZeroPad(ethers.utils.hexlify(32), 4); + const logs: LogInfo[] = [ + { + log: encodeL2ToL1Log({ + l2ShardId: 0, + isService: true, + txNumberInBlock: 1, + sender: l1MessengerAccount.address, + key, + value, + }), + }, + ]; + + const messages: MessageInfo[] = [ + { + message, + currentMessageLengthBytes, + log: encodeL2ToL1Log({ + l2ShardId: 0, + isService: true, + txNumberInBlock: 1, + sender: l1Messenger.address, + key: ethers.utils.hexZeroPad(ethers.utils.hexStripZeros(l1MessengerAccount.address), 32).toLowerCase(), + value: ethers.utils.keccak256(message), + }), + }, + ]; + + return { + isService: true, + key, + value, + messages, + logs, + }; +} + +// Represents the structure of the bytecode/message data that is part of the pubdata. +interface ContentLengthPair { + content: string; + lengthBytes: string; +} + +async function setupBytecodeData(l1MessengerAddress: string): Promise { + const content = await getCode(l1MessengerAddress); + const lengthBytes = ethers.utils.hexZeroPad(ethers.utils.hexlify(ethers.utils.arrayify(content).length), 4); + return { + content, + lengthBytes, + }; +} + +// Used for emulating the pubdata published by the L1Messenger. +class L1MessengerPubdataEmulator implements EmulatorData { + numberOfLogs: number; + encodedLogs: string[]; + numberOfMessages: number; + messages: ContentLengthPair[]; + numberOfBytecodes: number; + bytecodes: ContentLengthPair[]; + stateDiffsSetupData: StateDiffSetupData; + version: string; + + constructor() { + this.numberOfLogs = 0; + this.encodedLogs = []; + this.numberOfMessages = 0; + this.messages = []; + this.numberOfBytecodes = 0; + this.bytecodes = []; + this.stateDiffsSetupData = { + compressedStateDiffsSizeBytes: "", + enumerationIndexSizeBytes: "", + compressedStateDiffs: "", + numberOfStateDiffsBytes: "", + encodedStateDiffs: "", + }; + this.version = ethers.utils.hexZeroPad(ethers.utils.hexlify(1), 1); + } + + addLog(log: string): void { + this.encodedLogs.push(log); + this.numberOfLogs++; + } + + addMessage(message: ContentLengthPair): void { + this.messages.push(message); + this.numberOfMessages++; + } + + addBytecode(bytecode: ContentLengthPair): void { + this.bytecodes.push(bytecode); + this.numberOfBytecodes++; + } + + setStateDiffsSetupData(data: StateDiffSetupData) { + this.stateDiffsSetupData = data; + } + + buildTotalL2ToL1PubdataAndStateDiffs(overrideData: EmulatorOverrideData = {}): string { + const { + numberOfLogs = this.numberOfLogs, + encodedLogs = this.encodedLogs, + numberOfMessages = this.numberOfMessages, + messages = this.messages, + numberOfBytecodes = this.numberOfBytecodes, + bytecodes = this.bytecodes, + stateDiffsSetupData = this.stateDiffsSetupData, + version = this.version, + } = overrideData; + + const messagePairs = []; + for (let i = 0; i < numberOfMessages; i++) { + messagePairs.push(messages[i].lengthBytes, messages[i].content); + } + + const bytecodePairs = []; + for (let i = 0; i < numberOfBytecodes; i++) { + bytecodePairs.push(bytecodes[i].lengthBytes, bytecodes[i].content); + } + + return ethers.utils.concat([ + ethers.utils.hexZeroPad(ethers.utils.hexlify(numberOfLogs), 4), + ...encodedLogs, + ethers.utils.hexZeroPad(ethers.utils.hexlify(numberOfMessages), 4), + ...messagePairs, + ethers.utils.hexZeroPad(ethers.utils.hexlify(numberOfBytecodes), 4), + ...bytecodePairs, + version, + stateDiffsSetupData.compressedStateDiffsSizeBytes, + stateDiffsSetupData.enumerationIndexSizeBytes, + stateDiffsSetupData.compressedStateDiffs, + stateDiffsSetupData.numberOfStateDiffsBytes, + stateDiffsSetupData.encodedStateDiffs, + ]); + } +} +// Represents the structure of the data that the emulator uses. +interface EmulatorData { + numberOfLogs: number; + encodedLogs: string[]; + numberOfMessages: number; + messages: ContentLengthPair[]; + numberOfBytecodes: number; + bytecodes: ContentLengthPair[]; + stateDiffsSetupData: StateDiffSetupData; + version: string; +} + +// Represents a type that allows for overriding specific properties of the EmulatorData. +// This is useful when you want to change some properties of the emulator data without affecting the others. +type EmulatorOverrideData = Partial; diff --git a/system-contracts/test/shared/constants.ts b/system-contracts/test/shared/constants.ts index a37b86196..906f8fe8e 100644 --- a/system-contracts/test/shared/constants.ts +++ b/system-contracts/test/shared/constants.ts @@ -10,6 +10,7 @@ export const TEST_FORCE_DEPLOYER_ADDRESS = "0x0000000000000000000000000000000000 export const TEST_L1_MESSENGER_SYSTEM_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000009008"; export const TEST_MSG_VALUE_SYSTEM_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000009009"; export const TEST_ETH_TOKEN_SYSTEM_CONTRACT_ADDRESS = "0x000000000000000000000000000000000000900a"; +export const TEST_SYSTEM_CONTEXT_CONTRACT_ADDRESS = "0x000000000000000000000000000000000000900b"; export const TEST_BOOTLOADER_UTILITIES_ADDRESS = "0x000000000000000000000000000000000000900c"; export const TEST_COMPRESSOR_CONTRACT_ADDRESS = "0x000000000000000000000000000000000000900e"; export const TEST_COMPLEX_UPGRADER_CONTRACT_ADDRESS = "0x000000000000000000000000000000000000900f"; diff --git a/system-contracts/test/shared/mocks.ts b/system-contracts/test/shared/mocks.ts index 6fb6aea36..fa252e4c8 100644 --- a/system-contracts/test/shared/mocks.ts +++ b/system-contracts/test/shared/mocks.ts @@ -10,6 +10,8 @@ import { TEST_L1_MESSENGER_SYSTEM_CONTRACT_ADDRESS, TEST_MSG_VALUE_SYSTEM_CONTRACT_ADDRESS, TEST_NONCE_HOLDER_SYSTEM_CONTRACT_ADDRESS, + TEST_SYSTEM_CONTEXT_CONTRACT_ADDRESS, + TEST_COMPRESSOR_CONTRACT_ADDRESS, } from "./constants"; import { deployContractOnAddress, getWallets, loadArtifact } from "./utils"; @@ -21,6 +23,8 @@ type CallResult = { // Currently listed only contracts, that actually need to be mocked in the tests. // But other contracts can be added if needed. const TEST_SYSTEM_CONTRACTS_MOCKS = { + Compressor: TEST_COMPRESSOR_CONTRACT_ADDRESS, + SystemContext: TEST_SYSTEM_CONTEXT_CONTRACT_ADDRESS, NonceHolder: TEST_NONCE_HOLDER_SYSTEM_CONTRACT_ADDRESS, L1Messenger: TEST_L1_MESSENGER_SYSTEM_CONTRACT_ADDRESS, KnownCodesStorage: TEST_KNOWN_CODE_STORAGE_CONTRACT_ADDRESS, diff --git a/system-contracts/test/shared/utils.ts b/system-contracts/test/shared/utils.ts index bde6af669..9f6d96ee0 100644 --- a/system-contracts/test/shared/utils.ts +++ b/system-contracts/test/shared/utils.ts @@ -1,5 +1,6 @@ import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; import type { ZkSyncArtifact } from "@matterlabs/hardhat-zksync-deploy/dist/types"; +import { BigNumber } from "ethers"; import type { BytesLike } from "ethers"; import * as hre from "hardhat"; import { ethers } from "hardhat"; @@ -9,7 +10,11 @@ import { Provider, utils, Wallet } from "zksync-web3"; import { Language } from "../../scripts/constants"; import { readYulBytecode, readZasmBytecode } from "../../scripts/utils"; import { AccountCodeStorageFactory, ContractDeployerFactory } from "../../typechain"; -import { REAL_ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT_ADDRESS, REAL_DEPLOYER_SYSTEM_CONTRACT_ADDRESS } from "./constants"; +import { + REAL_ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT_ADDRESS, + REAL_DEPLOYER_SYSTEM_CONTRACT_ADDRESS, + TWO_IN_256, +} from "./constants"; const RICH_WALLETS = [ { @@ -167,3 +172,73 @@ export async function setConstructingCodeHash(address: string, bytecode: string) bytecodeHash[1] = 1; await accountCodeStorage.storeAccountConstructingCodeHash(address, bytecodeHash); } + +export interface StateDiff { + key: BytesLike; + index: number; + initValue: BigNumber; + finalValue: BigNumber; +} + +export function encodeStateDiffs(stateDiffs: StateDiff[]): string { + const rawStateDiffs = []; + for (const stateDiff of stateDiffs) { + rawStateDiffs.push( + ethers.utils.solidityPack( + ["address", "bytes32", "bytes32", "uint64", "uint256", "uint256", "bytes"], + [ + ethers.constants.AddressZero, + ethers.constants.HashZero, + stateDiff.key, + stateDiff.index, + stateDiff.initValue, + stateDiff.finalValue, + "0x" + "00".repeat(116), + ] + ) + ); + } + return ethers.utils.hexlify(ethers.utils.concat(rawStateDiffs)); +} + +export function compressStateDiffs(enumerationIndexSize: number, stateDiffs: StateDiff[]): string { + let numInitial = 0; + const initial = []; + const repeated = []; + for (const stateDiff of stateDiffs) { + const addition = stateDiff.finalValue.sub(stateDiff.initValue).add(TWO_IN_256).mod(TWO_IN_256); + const subtraction = stateDiff.initValue.sub(stateDiff.finalValue).add(TWO_IN_256).mod(TWO_IN_256); + let op = 3; + let min = stateDiff.finalValue; + if (addition.lt(min)) { + min = addition; + op = 1; + } + if (subtraction.lt(min)) { + min = subtraction; + op = 2; + } + if (min.gte(BigNumber.from(2).pow(248))) { + min = stateDiff.finalValue; + op = 0; + } + let len = 0; + const minHex = min.eq(0) ? "0x" : min.toHexString(); + if (op > 0) { + len = (minHex.length - 2) / 2; + } + const metadata = (len << 3) + op; + if (stateDiff.index === 0) { + numInitial += 1; + initial.push(ethers.utils.solidityPack(["bytes32", "uint8", "bytes"], [stateDiff.key, metadata, minHex])); + } else { + const enumerationIndexType = "uint" + (enumerationIndexSize * 8).toString(); + repeated.push( + ethers.utils.solidityPack([enumerationIndexType, "uint8", "bytes"], [stateDiff.index, metadata, minHex]) + ); + } + } + return ethers.utils.hexlify( + ethers.utils.concat([ethers.utils.solidityPack(["uint16"], [numInitial]), ...initial, ...repeated]) + ); +}