Files
linea-monorepo/contracts/test/hardhat/messaging/l2/L2MessageService.ts
The Dark Jester e66abc64fd Feat/1076 refactor and allow overriding (#1079)
* allow tokenbridge overrides

* add L1MessageService overrides

* refactor L2 MessageService

* refactor L2 MessageService V1

* use correct modifier

* refactor LineaRollup for overriding

* allow other overrides

* reinstate general in pause on tokenbridge

* add missing NatSpec

* sample overrides

* add generic bridge and document placeholder

* documentation and folder placement

* documentation cleanup

* use imported references

* use variable pragma for inherited contracts

* reset pragmas for some

* use base abstract contracts with version overrides

* use TokenBridgeBase as abstract

* Update contracts/src/bridging/token/TokenBridgeBase.sol

Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com>
Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com>

* Update contracts/src/bridging/token/TokenBridgeBase.sol

Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com>
Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com>

* Update contracts/src/bridging/token/TokenBridgeBase.sol

Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com>
Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com>

* Update contracts/src/bridging/token/interfaces/ITokenBridge.sol

Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com>
Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com>

* Update contracts/src/messaging/l2/L2MessageServiceBase.sol

Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com>
Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com>

* Update contracts/src/messaging/l2/v1/interfaces/IL2MessageServiceV1.sol

Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com>
Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com>

* Update contracts/src/rollup/interfaces/ILineaRollup.sol

Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com>
Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com>

* Update contracts/src/rollup/LineaRollupBase.sol

Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com>
Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com>

* Update contracts/src/_testing/unit/bridging/InheritingTokenBridge.sol

Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com>
Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com>

* Update contracts/src/verifiers/PlonkVerifierDev.sol

Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com>
Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com>

* Update contracts/src/verifiers/PlonkVerifierForDataAggregation.sol

Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com>
Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com>

* Update contracts/src/verifiers/PlonkVerifierForMultiTypeDataAggregation.sol

Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com>
Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com>

* Update contracts/src/verifiers/PlonkVerifierMainnetFull.sol

Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com>
Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com>

* Update contracts/src/verifiers/PlonkVerifierSepoliaFull.sol

Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com>
Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com>

* linting

* allow submitDataAsCalldata overriding

* address missing test coverage

* adjust gap name for storage clarity

---------

Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com>
Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com>
2025-06-17 09:26:24 -07:00

1419 lines
52 KiB
TypeScript

import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { expect } from "chai";
import { ethers } from "hardhat";
import { TestL2MessageService, TestReceivingContract } from "../../../../typechain-types";
import {
ADDRESS_ZERO,
BLOCK_COINBASE,
DEFAULT_ADMIN_ROLE,
EMPTY_CALLDATA,
GENERAL_PAUSE_TYPE,
INBOX_STATUS_CLAIMED,
INBOX_STATUS_RECEIVED,
INITIALIZED_ALREADY_MESSAGE,
INITIAL_WITHDRAW_LIMIT,
L1_L2_MESSAGE_SETTER_ROLE,
L1_L2_PAUSE_TYPE,
L2_L1_PAUSE_TYPE,
LOW_NO_REFUND_MESSAGE_FEE,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
MINIMUM_FEE,
MINIMUM_FEE_SETTER_ROLE,
ONE_DAY_IN_SECONDS,
PAUSE_ALL_ROLE,
UNPAUSE_ALL_ROLE,
RATE_LIMIT_SETTER_ROLE,
USED_RATE_LIMIT_RESETTER_ROLE,
} from "../../common/constants";
import { deployUpgradableFromFactory } from "../../common/deployment";
import {
buildAccessErrorMessage,
calculateRollingHash,
calculateRollingHashFromCollection,
encodeSendMessage,
expectEvent,
expectRevertWithCustomError,
expectRevertWithReason,
generateKeccak256Hash,
} from "../../common/helpers";
import { generateRoleAssignments } from "../../../../common/helpers";
import {
L2_MESSAGE_SERVICE_PAUSE_TYPES_ROLES,
L2_MESSAGE_SERVICE_ROLES,
L2_MESSAGE_SERVICE_UNPAUSE_TYPES_ROLES,
} from "../../../../common/constants";
describe("L2MessageService", () => {
let l2MessageService: TestL2MessageService;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let admin: SignerWithAddress;
let securityCouncil: SignerWithAddress;
let l1l2MessageSetter: SignerWithAddress;
let notAuthorizedAccount: SignerWithAddress;
let postmanAddress: SignerWithAddress;
let roleAddresses: { role: string; addressWithRole: string }[];
async function deployL2MessageServiceFixture() {
return deployUpgradableFromFactory("TestL2MessageService", [
ONE_DAY_IN_SECONDS,
INITIAL_WITHDRAW_LIMIT,
securityCouncil.address,
roleAddresses,
L2_MESSAGE_SERVICE_PAUSE_TYPES_ROLES,
L2_MESSAGE_SERVICE_UNPAUSE_TYPES_ROLES,
]) as unknown as Promise<TestL2MessageService>;
}
before(async () => {
[admin, securityCouncil, l1l2MessageSetter, notAuthorizedAccount, postmanAddress] = await ethers.getSigners();
roleAddresses = generateRoleAssignments(L2_MESSAGE_SERVICE_ROLES, securityCouncil.address, [
{ role: L1_L2_MESSAGE_SETTER_ROLE, addresses: [l1l2MessageSetter.address] },
]);
});
beforeEach(async () => {
l2MessageService = await loadFixture(deployL2MessageServiceFixture);
});
describe("Initialization checks", () => {
it("Should set minimumFeeInWei to 0.0001 ETH", async () => {
expect(await l2MessageService.minimumFeeInWei()).to.be.equal(ethers.parseEther("0.0001"));
});
it("Security council should have DEFAULT_ADMIN_ROLE", async () => {
expect(await l2MessageService.hasRole(DEFAULT_ADMIN_ROLE, securityCouncil.address)).to.be.true;
});
it("Security council should have MINIMUM_FEE_SETTER_ROLE", async () => {
expect(await l2MessageService.hasRole(MINIMUM_FEE_SETTER_ROLE, securityCouncil.address)).to.be.true;
});
it("Security council should have RATE_LIMIT_SETTER_ROLE role", async () => {
expect(await l2MessageService.hasRole(RATE_LIMIT_SETTER_ROLE, securityCouncil.address)).to.be.true;
});
it("Security council should have USED_RATE_LIMIT_RESETTER_ROLE role", async () => {
expect(await l2MessageService.hasRole(USED_RATE_LIMIT_RESETTER_ROLE, securityCouncil.address)).to.be.true;
});
it("Security council should have PAUSE_ALL_ROLE", async () => {
expect(await l2MessageService.hasRole(PAUSE_ALL_ROLE, securityCouncil.address)).to.be.true;
});
it("Security council should have UNPAUSE_ALL_ROLE", async () => {
expect(await l2MessageService.hasRole(UNPAUSE_ALL_ROLE, securityCouncil.address)).to.be.true;
});
it("L1->L2 message setter should have L1_L2_MESSAGE_SETTER_ROLE role", async () => {
expect(await l2MessageService.hasRole(L1_L2_MESSAGE_SETTER_ROLE, l1l2MessageSetter.address)).to.be.true;
});
it("Should initialize nextMessageNumber", async () => {
expect(await l2MessageService.nextMessageNumber()).to.be.equal(1);
});
it("Should set rate limit and period", async () => {
expect(await l2MessageService.periodInSeconds()).to.be.equal(ONE_DAY_IN_SECONDS);
expect(await l2MessageService.limitInWei()).to.be.equal(INITIAL_WITHDRAW_LIMIT);
});
it("Should have the correct contract version", async () => {
expect(await l2MessageService.CONTRACT_VERSION()).to.equal("1.0");
});
it("Should fail to deploy if default admin is address zero", async () => {
const deployCall = deployUpgradableFromFactory("TestL2MessageService", [
ONE_DAY_IN_SECONDS,
MESSAGE_VALUE_1ETH + MESSAGE_VALUE_1ETH,
ADDRESS_ZERO,
roleAddresses,
L2_MESSAGE_SERVICE_PAUSE_TYPES_ROLES,
L2_MESSAGE_SERVICE_UNPAUSE_TYPES_ROLES,
]);
await expectRevertWithCustomError(l2MessageService, deployCall, "ZeroAddressNotAllowed");
});
it("Should fail to deploy missing limit amount", async () => {
const deployCall = deployUpgradableFromFactory("TestL2MessageService", [
ONE_DAY_IN_SECONDS,
0,
securityCouncil.address,
roleAddresses,
L2_MESSAGE_SERVICE_PAUSE_TYPES_ROLES,
L2_MESSAGE_SERVICE_UNPAUSE_TYPES_ROLES,
]);
await expectRevertWithCustomError(l2MessageService, deployCall, "LimitIsZero");
});
it("Should fail to deploy missing period", async () => {
const deployCall = deployUpgradableFromFactory("TestL2MessageService", [
0,
MESSAGE_VALUE_1ETH + MESSAGE_VALUE_1ETH,
securityCouncil.address,
roleAddresses,
L2_MESSAGE_SERVICE_PAUSE_TYPES_ROLES,
L2_MESSAGE_SERVICE_UNPAUSE_TYPES_ROLES,
]);
await expectRevertWithCustomError(l2MessageService, deployCall, "PeriodIsZero");
});
it("Should fail on second initialisation", async () => {
const deployCall = l2MessageService.initialize(
ONE_DAY_IN_SECONDS,
INITIAL_WITHDRAW_LIMIT,
securityCouncil.address,
roleAddresses,
L2_MESSAGE_SERVICE_PAUSE_TYPES_ROLES,
L2_MESSAGE_SERVICE_UNPAUSE_TYPES_ROLES,
);
await expectRevertWithReason(deployCall, INITIALIZED_ALREADY_MESSAGE);
});
it.skip("Can upgrade existing contract", async () => {
// Deploy V1 from artifact
// Deploy V-next when we have it
});
});
describe("Send message", () => {
describe("When the contract is paused", () => {
it("Should fail to send if the contract is paused", async () => {
await l2MessageService.connect(securityCouncil).pauseByType(GENERAL_PAUSE_TYPE);
const sendMessageCall = l2MessageService
.connect(securityCouncil)
.sendMessage(notAuthorizedAccount.address, MESSAGE_FEE, EMPTY_CALLDATA, { value: INITIAL_WITHDRAW_LIMIT });
await expectRevertWithCustomError(l2MessageService, sendMessageCall, "IsPaused", [GENERAL_PAUSE_TYPE]);
});
});
describe("When the L2->L1 messaging service is paused", () => {
it("Should fail to send if the L2->L1 messaging service is paused", async () => {
await l2MessageService.connect(securityCouncil).pauseByType(L2_L1_PAUSE_TYPE);
const sendMessageCall = l2MessageService
.connect(securityCouncil)
.sendMessage(notAuthorizedAccount.address, MESSAGE_FEE, EMPTY_CALLDATA, { value: INITIAL_WITHDRAW_LIMIT });
await expectRevertWithCustomError(l2MessageService, sendMessageCall, "IsPaused", [L2_L1_PAUSE_TYPE]);
});
});
describe("When the contract is not paused", () => {
it("Should fail when the fee is higher than the amount sent", async () => {
const sendMessageCall = l2MessageService
.connect(admin)
.sendMessage(notAuthorizedAccount.address, MESSAGE_FEE, EMPTY_CALLDATA, {
value: MESSAGE_FEE - ethers.parseEther("0.01") + ethers.parseEther("0.0001"),
});
await expectRevertWithCustomError(l2MessageService, sendMessageCall, "ValueSentTooLow");
});
it("Should fail when the coinbase fee transfer fails", async () => {
await l2MessageService.connect(securityCouncil).setMinimumFee(MINIMUM_FEE);
await ethers.provider.send("hardhat_setCoinbase", [await l2MessageService.getAddress()]);
const sendMessageCall = l2MessageService
.connect(admin)
.sendMessage(notAuthorizedAccount.address, MESSAGE_FEE + MINIMUM_FEE, EMPTY_CALLDATA, {
value: MINIMUM_FEE + MINIMUM_FEE,
});
await expectRevertWithCustomError(l2MessageService, sendMessageCall, "FeePaymentFailed", [
await l2MessageService.getAddress(),
]);
await ethers.provider.send("hardhat_setCoinbase", [BLOCK_COINBASE]);
});
it("Should fail when the minimumFee is higher than the amount sent", async () => {
await l2MessageService.connect(securityCouncil).setMinimumFee(MINIMUM_FEE);
const sendMessageCall = l2MessageService
.connect(admin)
.sendMessage(notAuthorizedAccount.address, MESSAGE_FEE, EMPTY_CALLDATA, {
value: MESSAGE_FEE + ethers.parseEther("0.0001"),
});
await expectRevertWithCustomError(l2MessageService, sendMessageCall, "FeeTooLow");
});
it("Should fail when the to address is address 0", async () => {
const sendMessageCall = l2MessageService
.connect(admin)
.canSendMessage(ADDRESS_ZERO, MESSAGE_FEE, EMPTY_CALLDATA, {
value: MESSAGE_FEE,
});
await expectRevertWithCustomError(l2MessageService, sendMessageCall, "ZeroAddressNotAllowed");
});
it("Should increase the balance of the coinbase with the minimumFee", async () => {
await l2MessageService.connect(securityCouncil).setMinimumFee(MINIMUM_FEE);
const initialCoinbaseBalance = await ethers.provider.getBalance(BLOCK_COINBASE);
await l2MessageService
.connect(admin)
.sendMessage(notAuthorizedAccount.address, MESSAGE_FEE + MINIMUM_FEE, EMPTY_CALLDATA, {
value: MINIMUM_FEE + MESSAGE_FEE + ethers.parseEther("0.0001"),
});
expect(await ethers.provider.getBalance(BLOCK_COINBASE)).to.be.gt(initialCoinbaseBalance + MINIMUM_FEE);
});
it("Should succeed if 'MinimumFeeChanged' event is emitted", async () => {
const initialMinimumFee = ethers.parseEther("0.0001");
await expectEvent(
l2MessageService,
l2MessageService.connect(securityCouncil).setMinimumFee(MINIMUM_FEE),
"MinimumFeeChanged",
[initialMinimumFee, MINIMUM_FEE, securityCouncil.address],
);
// Testing non-zero transition
await expectEvent(
l2MessageService,
l2MessageService.connect(securityCouncil).setMinimumFee(MINIMUM_FEE + 1n),
"MinimumFeeChanged",
[MINIMUM_FEE, MINIMUM_FEE + 1n, securityCouncil.address],
);
});
it("Should succeed if 'MessageSent' event is emitted", async () => {
await l2MessageService.connect(securityCouncil).setMinimumFee(MINIMUM_FEE);
const expectedBytes = await encodeSendMessage(
securityCouncil.address,
notAuthorizedAccount.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH - MESSAGE_FEE - MINIMUM_FEE,
1n,
EMPTY_CALLDATA,
);
const messageHash = ethers.keccak256(expectedBytes);
const sendMessageCall = l2MessageService
.connect(securityCouncil)
.sendMessage(notAuthorizedAccount.address, MESSAGE_FEE + MINIMUM_FEE, EMPTY_CALLDATA, {
value: MESSAGE_VALUE_1ETH,
});
const eventArgs = [
securityCouncil.address,
notAuthorizedAccount.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH - MESSAGE_FEE - MINIMUM_FEE,
1,
EMPTY_CALLDATA,
messageHash,
];
await expectEvent(l2MessageService, sendMessageCall, "MessageSent", eventArgs);
});
it("Should send an ether only message with fees emitting the MessageSent event", async () => {
const expectedBytes = await encodeSendMessage(
admin.address,
notAuthorizedAccount.address,
MESSAGE_FEE - ethers.parseEther("0.0001"),
MESSAGE_VALUE_1ETH,
1n,
EMPTY_CALLDATA,
);
const messageHash = ethers.keccak256(expectedBytes);
const eventArgs = [
admin.address,
notAuthorizedAccount.address,
MESSAGE_FEE - ethers.parseEther("0.0001"),
MESSAGE_VALUE_1ETH,
1,
EMPTY_CALLDATA,
messageHash,
];
const sendMessageCall = l2MessageService
.connect(admin)
.sendMessage(notAuthorizedAccount.address, MESSAGE_FEE, EMPTY_CALLDATA, {
value: MESSAGE_FEE + MESSAGE_VALUE_1ETH,
});
await expectEvent(l2MessageService, sendMessageCall, "MessageSent", eventArgs);
});
it("Should send max limit ether only message with no fee emitting the MessageSent event", async () => {
const expectedBytes = await encodeSendMessage(
securityCouncil.address,
notAuthorizedAccount.address,
0n,
INITIAL_WITHDRAW_LIMIT - ethers.parseEther("0.0001"),
1n,
EMPTY_CALLDATA,
);
const messageHash = ethers.keccak256(expectedBytes);
const sendMessageCall = l2MessageService
.connect(securityCouncil)
.sendMessage(notAuthorizedAccount.address, ethers.parseEther("0.0001"), EMPTY_CALLDATA, {
value: INITIAL_WITHDRAW_LIMIT,
});
const eventArgs = [
securityCouncil.address,
notAuthorizedAccount.address,
0,
INITIAL_WITHDRAW_LIMIT - ethers.parseEther("0.0001"),
1,
EMPTY_CALLDATA,
messageHash,
];
await expectEvent(l2MessageService, sendMessageCall, "MessageSent", eventArgs);
});
it("Should revert with send over max limit amount only", async () => {
const sendMessageCall = l2MessageService
.connect(admin)
.sendMessage(notAuthorizedAccount.address, ethers.parseEther("0.0001"), EMPTY_CALLDATA, {
value: INITIAL_WITHDRAW_LIMIT + ethers.parseEther("0.0002"),
});
await expectRevertWithCustomError(l2MessageService, sendMessageCall, "RateLimitExceeded");
});
it("Should revert with send over max limit amount and fees", async () => {
const sendMessageCall = l2MessageService
.connect(admin)
.sendMessage(notAuthorizedAccount.address, ethers.parseEther("0.0001"), EMPTY_CALLDATA, {
value: INITIAL_WITHDRAW_LIMIT + ethers.parseEther("0.0002"),
});
await expectRevertWithCustomError(l2MessageService, sendMessageCall, "RateLimitExceeded");
});
it("Should fail when the rate limit would be exceeded - multi transactions", async () => {
await l2MessageService
.connect(admin)
.sendMessage(notAuthorizedAccount.address, ethers.parseEther("0.0001"), EMPTY_CALLDATA, {
value: MESSAGE_FEE + MESSAGE_VALUE_1ETH + ethers.parseEther("0.0001"),
});
const breachingAmount = INITIAL_WITHDRAW_LIMIT - MESSAGE_FEE - MESSAGE_VALUE_1ETH + ethers.parseEther("0.0002");
const sendMessageCall = l2MessageService
.connect(admin)
.sendMessage(notAuthorizedAccount.address, ethers.parseEther("0.0001"), EMPTY_CALLDATA, {
value: breachingAmount,
});
await expectRevertWithCustomError(l2MessageService, sendMessageCall, "RateLimitExceeded");
});
it("Should not accrue rate limit while sending transaction with coinbaseFee only", async () => {
const initialCoinbaseBalance = await ethers.provider.getBalance(BLOCK_COINBASE);
await l2MessageService.connect(securityCouncil).setMinimumFee(MINIMUM_FEE);
const initialRateLimitUsed = await l2MessageService.currentPeriodAmountInWei();
const expectedBytes = await encodeSendMessage(
admin.address,
notAuthorizedAccount.address,
0n,
0n,
1n,
EMPTY_CALLDATA,
);
const messageHash = ethers.keccak256(expectedBytes);
const sendMessageCall = l2MessageService
.connect(admin)
.sendMessage(notAuthorizedAccount.address, MINIMUM_FEE, EMPTY_CALLDATA, { value: MINIMUM_FEE });
await expectEvent(l2MessageService, sendMessageCall, "MessageSent", [
admin.address,
notAuthorizedAccount.address,
0n,
0n,
1,
EMPTY_CALLDATA,
messageHash,
]);
const postCoinbaseBalance = await ethers.provider.getBalance(BLOCK_COINBASE);
await expect(postCoinbaseBalance).to.be.gt(initialCoinbaseBalance);
const postRateLimitUsed = await l2MessageService.currentPeriodAmountInWei();
await expect(postRateLimitUsed).to.be.equal(initialRateLimitUsed);
});
it("Should accrue rate limit while sending transaction with 0 value and real fee, postmanFee = fee - coinbaseFee", async () => {
const initialCoinbaseBalance = await ethers.provider.getBalance(BLOCK_COINBASE);
const initialRateLimitUsed = await l2MessageService.currentPeriodAmountInWei();
await l2MessageService.connect(securityCouncil).setMinimumFee(MINIMUM_FEE);
const expectedBytes = await encodeSendMessage(
admin.address,
notAuthorizedAccount.address,
MESSAGE_VALUE_1ETH - MINIMUM_FEE,
0n,
1n,
EMPTY_CALLDATA,
);
const messageHash = ethers.keccak256(expectedBytes);
const sendMessageCall = l2MessageService
.connect(admin)
.sendMessage(notAuthorizedAccount.address, MESSAGE_VALUE_1ETH, EMPTY_CALLDATA, { value: MESSAGE_VALUE_1ETH });
await expectEvent(l2MessageService, sendMessageCall, "MessageSent", [
admin.address,
notAuthorizedAccount.address,
MESSAGE_VALUE_1ETH - MINIMUM_FEE,
0,
1,
EMPTY_CALLDATA,
messageHash,
]);
const postCoinbaseBalance = await ethers.provider.getBalance(BLOCK_COINBASE);
const postRateLimitUsed = await l2MessageService.currentPeriodAmountInWei();
await expect(postCoinbaseBalance).to.be.gt(initialCoinbaseBalance);
expect(await postRateLimitUsed).to.be.gt(initialRateLimitUsed);
});
it("Should accrue rate limit while sending transaction with value with real fee, postmanFee = fee - coinbaseFee", async () => {
const initialCoinbaseBalance = await ethers.provider.getBalance(BLOCK_COINBASE);
const initialRateLimitUsed = await l2MessageService.currentPeriodAmountInWei();
await l2MessageService.connect(securityCouncil).setMinimumFee(MINIMUM_FEE);
const expectedBytes = await encodeSendMessage(
admin.address,
notAuthorizedAccount.address,
MINIMUM_FEE + MESSAGE_FEE - MINIMUM_FEE,
MESSAGE_VALUE_1ETH - (MINIMUM_FEE + MESSAGE_FEE),
1n,
EMPTY_CALLDATA,
);
const messageHash = ethers.keccak256(expectedBytes);
const sendMessageCall = l2MessageService
.connect(admin)
.sendMessage(notAuthorizedAccount.address, MINIMUM_FEE + MESSAGE_FEE, EMPTY_CALLDATA, {
value: MESSAGE_VALUE_1ETH,
});
const eventArgs = [
admin.address,
notAuthorizedAccount.address,
MINIMUM_FEE + MESSAGE_FEE - MINIMUM_FEE,
MESSAGE_VALUE_1ETH - (MINIMUM_FEE + MESSAGE_FEE),
1,
EMPTY_CALLDATA,
messageHash,
];
await expectEvent(l2MessageService, sendMessageCall, "MessageSent", eventArgs);
const postCoinbaseBalance = await ethers.provider.getBalance(BLOCK_COINBASE);
const postRateLimitUsed = await l2MessageService.currentPeriodAmountInWei();
expect(postCoinbaseBalance).to.be.gt(initialCoinbaseBalance);
expect(postRateLimitUsed).to.be.gt(initialRateLimitUsed);
});
it("Should accrue rate limit while sending transaction with value with coinbaseFee, postmanFee = 0", async () => {
const initialCoinbaseBalance = await ethers.provider.getBalance(BLOCK_COINBASE);
const initialRateLimitUsed = await l2MessageService.currentPeriodAmountInWei();
await l2MessageService.connect(securityCouncil).setMinimumFee(MINIMUM_FEE);
const expectedBytes = await encodeSendMessage(
admin.address,
notAuthorizedAccount.address,
0n,
MESSAGE_VALUE_1ETH - MINIMUM_FEE,
1n,
EMPTY_CALLDATA,
);
const messageHash = ethers.keccak256(expectedBytes);
const sendMessageCall = l2MessageService
.connect(admin)
.sendMessage(notAuthorizedAccount.address, MINIMUM_FEE, EMPTY_CALLDATA, { value: MESSAGE_VALUE_1ETH });
const eventArgs = [
admin.address,
notAuthorizedAccount.address,
0n,
MESSAGE_VALUE_1ETH - MINIMUM_FEE,
1,
EMPTY_CALLDATA,
messageHash,
];
await expectEvent(l2MessageService, sendMessageCall, "MessageSent", eventArgs);
const postCoinbaseBalance = await ethers.provider.getBalance(BLOCK_COINBASE);
const postRateLimitUsed = await l2MessageService.currentPeriodAmountInWei();
expect(postCoinbaseBalance).to.be.gt(initialCoinbaseBalance);
expect(postRateLimitUsed).to.be.gt(initialRateLimitUsed);
});
});
});
describe("Claim message", () => {
describe("When the contract is paused", async () => {
it("Should revert if the contract is paused", async () => {
await l2MessageService.connect(securityCouncil).pauseByType(GENERAL_PAUSE_TYPE);
const claimMessageCall = l2MessageService
.connect(securityCouncil)
.claimMessage(
securityCouncil.address,
notAuthorizedAccount.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
notAuthorizedAccount.address,
EMPTY_CALLDATA,
1,
);
await expectRevertWithCustomError(l2MessageService, claimMessageCall, "IsPaused", [GENERAL_PAUSE_TYPE]);
});
});
describe("When L1->L2 messaging service is paused", async () => {
it("Should revert if the L1->L2 messaging service is paused", async () => {
await l2MessageService.connect(securityCouncil).pauseByType(L1_L2_PAUSE_TYPE);
const claimMessageCall = l2MessageService
.connect(securityCouncil)
.claimMessage(
securityCouncil.address,
notAuthorizedAccount.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
notAuthorizedAccount.address,
EMPTY_CALLDATA,
1,
);
await expectRevertWithCustomError(l2MessageService, claimMessageCall, "IsPaused", [L1_L2_PAUSE_TYPE]);
});
});
describe("When the contract is not paused", () => {
it("Should succeed if 'MessageClaimed' event is emitted", async () => {
const expectedBytes = await encodeSendMessage(
admin.address,
notAuthorizedAccount.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
1n,
EMPTY_CALLDATA,
);
await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
const expectedBytesArray = [ethers.keccak256(expectedBytes)];
const expectedRollingHash = calculateRollingHash(ethers.ZeroHash, ethers.keccak256(expectedBytes));
await l2MessageService.setLastAnchoredL1MessageNumber(1);
await l2MessageService
.connect(l1l2MessageSetter)
.anchorL1L2MessageHashes(expectedBytesArray, 2, 2, expectedRollingHash);
const sendMessageCall = l2MessageService.claimMessage(
admin.address,
notAuthorizedAccount.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
postmanAddress.address,
EMPTY_CALLDATA,
1,
);
await expectEvent(l2MessageService, sendMessageCall, "MessageClaimed", [ethers.keccak256(expectedBytes)]);
});
it("Should fail when the message hash does not exist", async () => {
const expectedBytes = await encodeSendMessage(
await l2MessageService.getAddress(),
notAuthorizedAccount.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
1n,
EMPTY_CALLDATA,
);
const messageHash = ethers.keccak256(expectedBytes);
const claimMessageCall = l2MessageService.claimMessage(
await l2MessageService.getAddress(),
notAuthorizedAccount.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
ADDRESS_ZERO,
EMPTY_CALLDATA,
1,
);
await expectRevertWithCustomError(
l2MessageService,
claimMessageCall,
"MessageDoesNotExistOrHasAlreadyBeenClaimed",
[messageHash],
);
});
it("Should execute the claim message and send fees to recipient, left over fee to destination", async () => {
const expectedBytes = await encodeSendMessage(
admin.address,
notAuthorizedAccount.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
1n,
EMPTY_CALLDATA,
);
const destinationBalance = await ethers.provider.getBalance(notAuthorizedAccount.address);
const postmanBalance = await ethers.provider.getBalance(postmanAddress.address);
await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
const expectedBytesArray = [ethers.keccak256(expectedBytes)];
const expectedRollingHash = calculateRollingHash(ethers.ZeroHash, ethers.keccak256(expectedBytes));
await l2MessageService.setLastAnchoredL1MessageNumber(1);
await l2MessageService
.connect(l1l2MessageSetter)
.anchorL1L2MessageHashes(expectedBytesArray, 2, 2, expectedRollingHash);
await l2MessageService.claimMessage(
admin.address,
notAuthorizedAccount.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
postmanAddress.address,
EMPTY_CALLDATA,
1,
);
// greater due to the gas refund
expect(await ethers.provider.getBalance(notAuthorizedAccount.address)).to.be.greaterThan(
destinationBalance + MESSAGE_VALUE_1ETH,
);
expect(await ethers.provider.getBalance(postmanAddress.address)).to.be.greaterThan(postmanBalance);
});
it("Should execute the claim message and send fees to recipient contract and no leftovers", async () => {
const factory = await ethers.getContractFactory("TestReceivingContract");
const testContract = (await factory.deploy()) as TestReceivingContract;
const expectedBytes = await encodeSendMessage(
admin.address,
await testContract.getAddress(),
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
1n,
EMPTY_CALLDATA,
);
const postmanBalance = await ethers.provider.getBalance(postmanAddress.address);
await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
const expectedBytesArray = [ethers.keccak256(expectedBytes)];
const expectedRollingHash = calculateRollingHash(ethers.ZeroHash, ethers.keccak256(expectedBytes));
await l2MessageService.setLastAnchoredL1MessageNumber(1);
await l2MessageService
.connect(l1l2MessageSetter)
.anchorL1L2MessageHashes(expectedBytesArray, 2, 2, expectedRollingHash);
await l2MessageService.claimMessage(
admin.address,
await testContract.getAddress(),
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
postmanAddress.address,
EMPTY_CALLDATA,
1,
);
// greater due to the gas refund
expect(await ethers.provider.getBalance(await testContract.getAddress())).to.be.equal(MESSAGE_VALUE_1ETH);
expect(await ethers.provider.getBalance(postmanAddress.address)).to.be.equal(postmanBalance + MESSAGE_FEE);
});
it("Should execute the claim message and send the fees to set recipient, and NOT refund fee to EOA", async () => {
const expectedBytes = await encodeSendMessage(
admin.address,
notAuthorizedAccount.address,
LOW_NO_REFUND_MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
1n,
EMPTY_CALLDATA,
);
const destinationBalance = await ethers.provider.getBalance(notAuthorizedAccount.address);
const postmanBalance = await ethers.provider.getBalance(postmanAddress.address);
await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
const expectedBytesArray = [ethers.keccak256(expectedBytes)];
const expectedRollingHash = calculateRollingHash(ethers.ZeroHash, ethers.keccak256(expectedBytes));
await l2MessageService.setLastAnchoredL1MessageNumber(1);
await l2MessageService
.connect(l1l2MessageSetter)
.anchorL1L2MessageHashes(expectedBytesArray, 2, 2, expectedRollingHash);
await l2MessageService.claimMessage(
admin.address,
notAuthorizedAccount.address,
LOW_NO_REFUND_MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
postmanAddress.address,
EMPTY_CALLDATA,
1,
{ gasPrice: 1000000000 },
);
// greater due to the gas refund
expect(await ethers.provider.getBalance(notAuthorizedAccount.address)).to.be.equal(
destinationBalance + MESSAGE_VALUE_1ETH,
);
expect(await ethers.provider.getBalance(postmanAddress.address)).to.be.equal(
postmanBalance + LOW_NO_REFUND_MESSAGE_FEE,
);
});
it("Should execute the claim message and send fees to EOA with calldata and no refund sent", async () => {
const expectedBytes = await encodeSendMessage(
admin.address,
notAuthorizedAccount.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
1n,
"0x123456789a",
);
const destinationBalance = await ethers.provider.getBalance(notAuthorizedAccount.address);
const postmanBalance = await ethers.provider.getBalance(postmanAddress.address);
await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
const expectedBytesArray = [ethers.keccak256(expectedBytes)];
const expectedRollingHash = calculateRollingHash(ethers.ZeroHash, ethers.keccak256(expectedBytes));
await l2MessageService.setLastAnchoredL1MessageNumber(1);
await l2MessageService
.connect(l1l2MessageSetter)
.anchorL1L2MessageHashes(expectedBytesArray, 2, 2, expectedRollingHash);
await l2MessageService.claimMessage(
admin.address,
notAuthorizedAccount.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
postmanAddress.address,
"0x123456789a",
1,
);
// greater due to the gas refund
expect(await ethers.provider.getBalance(notAuthorizedAccount.address)).to.be.equal(
destinationBalance + MESSAGE_VALUE_1ETH,
);
expect(await ethers.provider.getBalance(postmanAddress.address)).to.be.equal(postmanBalance + MESSAGE_FEE);
});
it("Should execute the claim message and no fees to EOA with calldata and no refund sent", async () => {
const expectedBytes = await encodeSendMessage(
admin.address,
notAuthorizedAccount.address,
0n,
MESSAGE_VALUE_1ETH,
1n,
"0x123456789a",
);
const destinationBalance = await ethers.provider.getBalance(notAuthorizedAccount.address);
const postmanBalance = await ethers.provider.getBalance(postmanAddress.address);
await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
const expectedBytesArray = [ethers.keccak256(expectedBytes)];
const expectedRollingHash = calculateRollingHash(ethers.ZeroHash, ethers.keccak256(expectedBytes));
await l2MessageService.setLastAnchoredL1MessageNumber(1);
await l2MessageService
.connect(l1l2MessageSetter)
.anchorL1L2MessageHashes(expectedBytesArray, 2, 2, expectedRollingHash);
await l2MessageService.claimMessage(
admin.address,
notAuthorizedAccount.address,
0n,
MESSAGE_VALUE_1ETH,
postmanAddress.address,
"0x123456789a",
1,
);
// greater due to the gas refund
expect(await ethers.provider.getBalance(notAuthorizedAccount.address)).to.be.equal(
destinationBalance + MESSAGE_VALUE_1ETH,
);
expect(await ethers.provider.getBalance(postmanAddress.address)).to.be.equal(postmanBalance);
});
it("Should execute the claim message and no fees to EOA with no calldata and no refund sent", async () => {
const expectedBytes = await encodeSendMessage(
admin.address,
notAuthorizedAccount.address,
0n,
MESSAGE_VALUE_1ETH,
1n,
EMPTY_CALLDATA,
);
const destinationBalance = await ethers.provider.getBalance(notAuthorizedAccount.address);
const postmanBalance = await ethers.provider.getBalance(postmanAddress.address);
await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
const expectedBytesArray = [ethers.keccak256(expectedBytes)];
const expectedRollingHash = calculateRollingHash(ethers.ZeroHash, ethers.keccak256(expectedBytes));
await l2MessageService.setLastAnchoredL1MessageNumber(1);
await l2MessageService
.connect(l1l2MessageSetter)
.anchorL1L2MessageHashes(expectedBytesArray, 2, 2, expectedRollingHash);
await l2MessageService.claimMessage(
admin.address,
notAuthorizedAccount.address,
0n,
MESSAGE_VALUE_1ETH,
postmanAddress.address,
EMPTY_CALLDATA,
1,
);
// greater due to the gas refund
expect(await ethers.provider.getBalance(notAuthorizedAccount.address)).to.be.equal(
destinationBalance + MESSAGE_VALUE_1ETH,
);
expect(await ethers.provider.getBalance(postmanAddress.address)).to.be.equal(postmanBalance);
});
// todo - add tests for refund checks when gas is lower
it("Should fail to send if the contract is paused", async () => {
await l2MessageService.connect(securityCouncil).pauseByType(GENERAL_PAUSE_TYPE);
const sendMessageCall = l2MessageService
.connect(admin)
.canSendMessage(notAuthorizedAccount.address, 0, EMPTY_CALLDATA, { value: INITIAL_WITHDRAW_LIMIT });
await expectRevertWithCustomError(l2MessageService, sendMessageCall, "IsPaused", [GENERAL_PAUSE_TYPE]);
const usedAmount = await l2MessageService.currentPeriodAmountInWei();
expect(usedAmount).to.be.equal(0);
});
it("Should fail when the message hash has been claimed", async () => {
const expectedBytes = await encodeSendMessage(
await l2MessageService.getAddress(),
notAuthorizedAccount.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
1n,
EMPTY_CALLDATA,
);
await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
const expectedBytesArray = [ethers.keccak256(expectedBytes)];
const expectedRollingHash = calculateRollingHash(ethers.ZeroHash, ethers.keccak256(expectedBytes));
await l2MessageService.setLastAnchoredL1MessageNumber(1);
await l2MessageService
.connect(l1l2MessageSetter)
.anchorL1L2MessageHashes(expectedBytesArray, 2, 2, expectedRollingHash);
await l2MessageService.claimMessage(
await l2MessageService.getAddress(),
notAuthorizedAccount.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
postmanAddress.address,
EMPTY_CALLDATA,
1,
);
await expect(
l2MessageService.claimMessage(
await l2MessageService.getAddress(),
notAuthorizedAccount.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
postmanAddress.address,
EMPTY_CALLDATA,
1,
),
).to.be.revertedWithCustomError(l2MessageService, "MessageDoesNotExistOrHasAlreadyBeenClaimed");
});
it("Should execute the claim message and send the fees to msg.sender", async () => {
const expectedBytes = await encodeSendMessage(
await l2MessageService.getAddress(),
notAuthorizedAccount.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
1n,
EMPTY_CALLDATA,
);
const expectedSecondBytes = await encodeSendMessage(
await l2MessageService.getAddress(),
notAuthorizedAccount.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
2n,
EMPTY_CALLDATA,
);
const destinationBalance = await ethers.provider.getBalance(notAuthorizedAccount.address);
await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
const expectedBytesArray = [ethers.keccak256(expectedBytes), ethers.keccak256(expectedSecondBytes)];
const expectedRollingHash = calculateRollingHashFromCollection(ethers.ZeroHash, expectedBytesArray);
await l2MessageService.setLastAnchoredL1MessageNumber(1);
await l2MessageService
.connect(l1l2MessageSetter)
.anchorL1L2MessageHashes(expectedBytesArray, 2, 3, expectedRollingHash);
await l2MessageService
.connect(admin)
.claimMessage(
await l2MessageService.getAddress(),
notAuthorizedAccount.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
ADDRESS_ZERO,
EMPTY_CALLDATA,
1,
);
const adminBalance = await ethers.provider.getBalance(admin.address);
await l2MessageService
.connect(admin)
.claimMessage(
await l2MessageService.getAddress(),
notAuthorizedAccount.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
ADDRESS_ZERO,
EMPTY_CALLDATA,
2,
);
expect(await ethers.provider.getBalance(notAuthorizedAccount.address)).to.be.greaterThan(
destinationBalance + MESSAGE_VALUE_1ETH + MESSAGE_VALUE_1ETH,
);
expect(await ethers.provider.getBalance(admin.address)).to.be.lessThan(adminBalance + MESSAGE_FEE);
expect(await l2MessageService.inboxL1L2MessageStatus(ethers.keccak256(expectedBytes))).to.be.equal(
INBOX_STATUS_CLAIMED,
);
});
// todo also add lower than 5000 gas check for the balances to be equal
it("Should execute the claim message when there are no fees", async () => {
const expectedBytes = await encodeSendMessage(
await l2MessageService.getAddress(),
notAuthorizedAccount.address,
0n,
MESSAGE_VALUE_1ETH,
1n,
EMPTY_CALLDATA,
);
const destinationBalance = await ethers.provider.getBalance(notAuthorizedAccount.address);
await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
const expectedBytesArray = [ethers.keccak256(expectedBytes)];
const expectedRollingHash = calculateRollingHash(ethers.ZeroHash, ethers.keccak256(expectedBytes));
await l2MessageService.setLastAnchoredL1MessageNumber(1);
await l2MessageService
.connect(l1l2MessageSetter)
.anchorL1L2MessageHashes(expectedBytesArray, 2, 2, expectedRollingHash);
const adminBalance = await ethers.provider.getBalance(admin.address);
await l2MessageService
.connect(admin)
.claimMessage(
await l2MessageService.getAddress(),
notAuthorizedAccount.address,
0,
MESSAGE_VALUE_1ETH,
ADDRESS_ZERO,
EMPTY_CALLDATA,
1,
);
expect(await ethers.provider.getBalance(notAuthorizedAccount.address)).to.be.equal(
destinationBalance + MESSAGE_VALUE_1ETH,
);
expect(await ethers.provider.getBalance(admin.address)).to.be.lessThan(adminBalance);
expect(await l2MessageService.inboxL1L2MessageStatus(ethers.keccak256(expectedBytes))).to.be.equal(
INBOX_STATUS_CLAIMED,
);
});
it("Should provide the correct origin sender", async () => {
const sendCalldata = generateKeccak256Hash("setSender()").substring(0, 10);
const expectedBytes = await encodeSendMessage(
await l2MessageService.getAddress(),
await l2MessageService.getAddress(),
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
1n,
sendCalldata,
);
await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
const expectedBytesArray = [ethers.keccak256(expectedBytes)];
const expectedRollingHash = calculateRollingHash(ethers.ZeroHash, ethers.keccak256(expectedBytes));
await l2MessageService.setLastAnchoredL1MessageNumber(1);
await l2MessageService
.connect(l1l2MessageSetter)
.anchorL1L2MessageHashes(expectedBytesArray, 2, 2, expectedRollingHash);
const storedSenderBeforeSending = await l2MessageService.originalSender();
expect(storedSenderBeforeSending).to.be.equal(ADDRESS_ZERO);
await expect(
l2MessageService
.connect(admin)
.claimMessage(
await l2MessageService.getAddress(),
await l2MessageService.getAddress(),
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
ADDRESS_ZERO,
sendCalldata,
1,
),
).to.not.be.reverted;
const newSender = await l2MessageService.originalSender();
expect(newSender).to.be.equal(await l2MessageService.getAddress());
});
it("Should fail on reentry when sending to recipient", async () => {
const callSignature = generateKeccak256Hash("doReentry()").substring(0, 10);
const expectedBytes = await encodeSendMessage(
await l2MessageService.getAddress(),
await l2MessageService.getAddress(),
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
1n,
callSignature,
);
await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
const expectedBytesArray = [ethers.keccak256(expectedBytes)];
const expectedRollingHash = calculateRollingHash(ethers.ZeroHash, ethers.keccak256(expectedBytes));
await l2MessageService.setLastAnchoredL1MessageNumber(1);
await l2MessageService
.connect(l1l2MessageSetter)
.anchorL1L2MessageHashes(expectedBytesArray, 2, 2, expectedRollingHash);
const claimMessageCall = l2MessageService
.connect(admin)
.claimMessage(
await l2MessageService.getAddress(),
await l2MessageService.getAddress(),
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
ADDRESS_ZERO,
callSignature,
1,
);
await expectRevertWithReason(claimMessageCall, "ReentrancyGuard: reentrant call");
});
it("Should fail when the destination errors through receive", async () => {
const expectedBytes = await encodeSendMessage(
await l2MessageService.getAddress(),
await l2MessageService.getAddress(),
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
1n,
EMPTY_CALLDATA,
);
await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
const expectedBytesArray = [ethers.keccak256(expectedBytes)];
const expectedRollingHash = calculateRollingHash(ethers.ZeroHash, ethers.keccak256(expectedBytes));
await l2MessageService.setLastAnchoredL1MessageNumber(1);
await l2MessageService
.connect(l1l2MessageSetter)
.anchorL1L2MessageHashes(expectedBytesArray, 2, 2, expectedRollingHash);
await expect(
l2MessageService
.connect(admin)
.claimMessage(
await l2MessageService.getAddress(),
await l2MessageService.getAddress(),
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
ADDRESS_ZERO,
EMPTY_CALLDATA,
1,
),
).to.be.reverted;
expect(await l2MessageService.inboxL1L2MessageStatus(ethers.keccak256(expectedBytes))).to.be.equal(
INBOX_STATUS_RECEIVED,
);
});
it("Should fail when the destination errors through fallback", async () => {
const expectedBytes = await encodeSendMessage(
await l2MessageService.getAddress(),
await l2MessageService.getAddress(),
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
1n,
"0x1234",
);
await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
const expectedBytesArray = [ethers.keccak256(expectedBytes)];
const expectedRollingHash = calculateRollingHash(ethers.ZeroHash, ethers.keccak256(expectedBytes));
await l2MessageService.setLastAnchoredL1MessageNumber(1);
await l2MessageService
.connect(l1l2MessageSetter)
.anchorL1L2MessageHashes(expectedBytesArray, 2, 2, expectedRollingHash);
await expect(
l2MessageService
.connect(admin)
.claimMessage(
await l2MessageService.getAddress(),
await l2MessageService.getAddress(),
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
ADDRESS_ZERO,
"0x1234",
1,
),
).to.be.reverted;
expect(await l2MessageService.inboxL1L2MessageStatus(ethers.keccak256(expectedBytes))).to.be.equal(
INBOX_STATUS_RECEIVED,
);
});
it("Should fail when the destination errors on empty receive (makeItReceive function)", async () => {
const expectedBytes = await encodeSendMessage(
await l2MessageService.getAddress(),
await l2MessageService.getAddress(),
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
1n,
"0xfc13b6f3",
);
await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
const expectedBytesArray = [ethers.keccak256(expectedBytes)];
const expectedRollingHash = calculateRollingHash(ethers.ZeroHash, ethers.keccak256(expectedBytes));
await l2MessageService.setLastAnchoredL1MessageNumber(1);
await l2MessageService
.connect(l1l2MessageSetter)
.anchorL1L2MessageHashes(expectedBytesArray, 2, 2, expectedRollingHash);
const claimMessageCall = l2MessageService
.connect(admin)
.claimMessage(
await l2MessageService.getAddress(),
await l2MessageService.getAddress(),
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
ADDRESS_ZERO,
"0xfc13b6f3",
1,
);
await expectRevertWithCustomError(l2MessageService, claimMessageCall, "MessageSendingFailed", [
await l2MessageService.getAddress(),
]);
expect(await l2MessageService.inboxL1L2MessageStatus(ethers.keccak256(expectedBytes))).to.be.equal(
INBOX_STATUS_RECEIVED,
);
});
it("Should fail when the fee recipient fails errors", async () => {
const expectedBytes = await encodeSendMessage(
await l2MessageService.getAddress(),
admin.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
1n,
EMPTY_CALLDATA,
);
await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
const expectedBytesArray = [ethers.keccak256(expectedBytes)];
const expectedRollingHash = calculateRollingHash(ethers.ZeroHash, ethers.keccak256(expectedBytes));
await l2MessageService.setLastAnchoredL1MessageNumber(1);
await l2MessageService
.connect(l1l2MessageSetter)
.anchorL1L2MessageHashes(expectedBytesArray, 2, 2, expectedRollingHash);
const claimMessageCall = l2MessageService
.connect(admin)
.claimMessage(
await l2MessageService.getAddress(),
admin.address,
MESSAGE_FEE,
MESSAGE_VALUE_1ETH,
await l2MessageService.getAddress(),
EMPTY_CALLDATA,
1,
);
await expectRevertWithCustomError(l2MessageService, claimMessageCall, "FeePaymentFailed", [
await l2MessageService.getAddress(),
]);
expect(await l2MessageService.inboxL1L2MessageStatus(ethers.keccak256(expectedBytes))).to.be.equal(
INBOX_STATUS_RECEIVED,
);
});
});
});
describe("Set minimum fee", () => {
it("Should fail when caller is not allowed", async () => {
await expect(l2MessageService.connect(notAuthorizedAccount).setMinimumFee(MINIMUM_FEE)).to.be.revertedWith(
"AccessControl: account " +
notAuthorizedAccount.address.toLowerCase() +
" is missing role " +
MINIMUM_FEE_SETTER_ROLE,
);
});
it("Should set the minimum fee", async () => {
await l2MessageService.connect(securityCouncil).setMinimumFee(MINIMUM_FEE);
expect(await l2MessageService.minimumFeeInWei()).to.be.equal(MINIMUM_FEE);
});
});
describe("Pausing contracts", () => {
it("Should fail pausing as non-pauser", async () => {
expect(await l2MessageService.isPaused(GENERAL_PAUSE_TYPE)).to.be.false;
await expectRevertWithReason(
l2MessageService.connect(admin).pauseByType(GENERAL_PAUSE_TYPE),
buildAccessErrorMessage(admin, PAUSE_ALL_ROLE),
);
expect(await l2MessageService.isPaused(GENERAL_PAUSE_TYPE)).to.be.false;
});
it("Should pause as pause manager", async () => {
expect(await l2MessageService.isPaused(GENERAL_PAUSE_TYPE)).to.be.false;
await l2MessageService.connect(securityCouncil).pauseByType(GENERAL_PAUSE_TYPE);
expect(await l2MessageService.isPaused(GENERAL_PAUSE_TYPE)).to.be.true;
});
});
describe("Resetting limits", () => {
it("Should reset limits as limitSetter", async () => {
let usedAmount = await l2MessageService.currentPeriodAmountInWei();
expect(usedAmount).to.be.equal(0);
await l2MessageService
.connect(admin)
.sendMessage(notAuthorizedAccount.address, ethers.parseEther("0.0001"), EMPTY_CALLDATA, {
value: INITIAL_WITHDRAW_LIMIT + ethers.parseEther("0.0001"),
});
usedAmount = await l2MessageService.currentPeriodAmountInWei();
expect(usedAmount).to.be.equal(INITIAL_WITHDRAW_LIMIT);
await l2MessageService.connect(securityCouncil).resetAmountUsedInPeriod();
usedAmount = await l2MessageService.currentPeriodAmountInWei();
expect(usedAmount).to.be.equal(0);
});
it("Should fail reset limits as non-pauser", async () => {
let usedAmount = await l2MessageService.currentPeriodAmountInWei();
expect(usedAmount).to.be.equal(0);
await l2MessageService
.connect(admin)
.sendMessage(notAuthorizedAccount.address, ethers.parseEther("0.0001"), EMPTY_CALLDATA, {
value: INITIAL_WITHDRAW_LIMIT + ethers.parseEther("0.0001"),
});
usedAmount = await l2MessageService.currentPeriodAmountInWei();
expect(usedAmount).to.be.equal(INITIAL_WITHDRAW_LIMIT);
await expectRevertWithReason(
l2MessageService.connect(admin).resetAmountUsedInPeriod(),
buildAccessErrorMessage(admin, USED_RATE_LIMIT_RESETTER_ROLE),
);
usedAmount = await l2MessageService.currentPeriodAmountInWei();
expect(usedAmount).to.be.equal(INITIAL_WITHDRAW_LIMIT);
});
});
describe.skip("L2MessageService Upgradeable Tests", () => {
it.skip("Should deploy and upgrade the L2MessageService contract", async () => {
// Deploy V1 from artifact
// Deploy V-next when we have it
});
});
});