mirror of
https://github.com/vacp2p/linea-monorepo.git
synced 2026-01-09 20:27:58 -05:00
* first pass contracts and makefile cleanup * remove unused 4844 test files * Remove Goerli references * lint fixing * lint fixing * remove more Goerli references * correct folder paths for L2 service tests * fix pathing and hardhat version * reinstate deleted file * correct file pathing
1550 lines
58 KiB
TypeScript
1550 lines
58 KiB
TypeScript
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
|
|
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
|
|
import { expect } from "chai";
|
|
import { ethers, upgrades } 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,
|
|
expectEvents,
|
|
expectRevertWithCustomError,
|
|
expectRevertWithReason,
|
|
generateKeccak256,
|
|
generateKeccak256Hash,
|
|
} from "../../common/helpers";
|
|
import { ZeroAddress } from "ethers";
|
|
import { generateRoleAssignments } from "../../../common/helpers";
|
|
import {
|
|
L2_MESSAGE_SERVICE_PAUSE_TYPES_ROLES,
|
|
L2_MESSAGE_SERVICE_ROLES,
|
|
L2_MESSAGE_SERVICE_UNPAUSE_TYPES_ROLES,
|
|
PAUSE_L1_L2_ROLE,
|
|
PAUSE_L2_L1_ROLE,
|
|
UNPAUSE_L1_L2_ROLE,
|
|
UNPAUSE_L2_L1_ROLE,
|
|
} 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 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("Can upgrade existing contract", async () => {
|
|
const contract = await deployUpgradableFromFactory("L2MessageServiceLineaMainnet", [
|
|
securityCouncil.address,
|
|
l1l2MessageSetter.address,
|
|
ONE_DAY_IN_SECONDS,
|
|
INITIAL_WITHDRAW_LIMIT,
|
|
]);
|
|
|
|
const l2MessageServiceFactory = await ethers.getContractFactory("L2MessageService");
|
|
await upgrades.validateUpgrade(contract, l2MessageServiceFactory);
|
|
|
|
const newContract = await upgrades.upgradeProxy(contract, l2MessageServiceFactory);
|
|
|
|
const upgradedContract = await newContract.waitForDeployment();
|
|
await upgrades.validateImplementation(l2MessageServiceFactory);
|
|
|
|
expect(await upgradedContract.lastAnchoredL1MessageNumber()).to.equal(0);
|
|
});
|
|
});
|
|
|
|
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("L2MessageService Upgradeable Tests", () => {
|
|
let newRoleAddresses: { addressWithRole: string; role: string }[];
|
|
|
|
async function deployL2MessageServiceFixture() {
|
|
return deployUpgradableFromFactory(
|
|
"contracts/test-contracts/L2MessageServiceLineaMainnet.sol:L2MessageServiceLineaMainnet",
|
|
[securityCouncil.address, l1l2MessageSetter.address, ONE_DAY_IN_SECONDS, INITIAL_WITHDRAW_LIMIT],
|
|
) as unknown as Promise<TestL2MessageService>;
|
|
}
|
|
|
|
before(async () => {
|
|
const securityCouncilAddress = securityCouncil.address;
|
|
|
|
newRoleAddresses = [
|
|
{ addressWithRole: securityCouncilAddress, role: USED_RATE_LIMIT_RESETTER_ROLE },
|
|
{ addressWithRole: securityCouncilAddress, role: PAUSE_ALL_ROLE },
|
|
{ addressWithRole: securityCouncilAddress, role: PAUSE_L1_L2_ROLE },
|
|
{ addressWithRole: securityCouncilAddress, role: PAUSE_L2_L1_ROLE },
|
|
{ addressWithRole: securityCouncilAddress, role: UNPAUSE_ALL_ROLE },
|
|
{ addressWithRole: securityCouncilAddress, role: UNPAUSE_L1_L2_ROLE },
|
|
{ addressWithRole: securityCouncilAddress, role: UNPAUSE_L2_L1_ROLE },
|
|
];
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
[admin, securityCouncil, l1l2MessageSetter, notAuthorizedAccount, postmanAddress] = await ethers.getSigners();
|
|
l2MessageService = await loadFixture(deployL2MessageServiceFixture);
|
|
});
|
|
|
|
it("Should deploy and upgrade the L2MessageService contract", async () => {
|
|
expect(await l2MessageService.nextMessageNumber()).to.equal(1);
|
|
|
|
// Deploy new implementation
|
|
const newL2MessageServiceFactory = await ethers.getContractFactory(
|
|
"contracts/messageService/l2/L2MessageService.sol:L2MessageService",
|
|
);
|
|
const newL2MessageService = await upgrades.upgradeProxy(l2MessageService, newL2MessageServiceFactory);
|
|
|
|
await newL2MessageService.reinitializePauseTypesAndPermissions(
|
|
newRoleAddresses,
|
|
L2_MESSAGE_SERVICE_PAUSE_TYPES_ROLES,
|
|
L2_MESSAGE_SERVICE_UNPAUSE_TYPES_ROLES,
|
|
);
|
|
|
|
expect(await newL2MessageService.nextMessageNumber()).to.equal(1);
|
|
});
|
|
|
|
it("Should revert with ZeroAddressNotAllowed when addressWithRole is zero address in reinitializePauseTypesAndPermissions", async () => {
|
|
// Deploy new implementation
|
|
const newL2MessageServiceFactory = await ethers.getContractFactory(
|
|
"contracts/messageService/l2/L2MessageService.sol:L2MessageService",
|
|
);
|
|
const newL2MessageService = await upgrades.upgradeProxy(l2MessageService, newL2MessageServiceFactory);
|
|
|
|
const roleAddresses = [...newRoleAddresses, { addressWithRole: ZeroAddress, role: DEFAULT_ADMIN_ROLE }];
|
|
|
|
await expectRevertWithCustomError(
|
|
newL2MessageService,
|
|
newL2MessageService.reinitializePauseTypesAndPermissions(
|
|
roleAddresses,
|
|
L2_MESSAGE_SERVICE_PAUSE_TYPES_ROLES,
|
|
L2_MESSAGE_SERVICE_UNPAUSE_TYPES_ROLES,
|
|
),
|
|
"ZeroAddressNotAllowed",
|
|
);
|
|
});
|
|
|
|
it("Should set all permissions", async () => {
|
|
// Deploy new implementation
|
|
const newL2MessageServiceFactory = await ethers.getContractFactory(
|
|
"contracts/messageService/l2/L2MessageService.sol:L2MessageService",
|
|
);
|
|
const newL2MessageService = await upgrades.upgradeProxy(l2MessageService, newL2MessageServiceFactory);
|
|
|
|
await newL2MessageService.reinitializePauseTypesAndPermissions(
|
|
newRoleAddresses,
|
|
L2_MESSAGE_SERVICE_PAUSE_TYPES_ROLES,
|
|
L2_MESSAGE_SERVICE_UNPAUSE_TYPES_ROLES,
|
|
);
|
|
|
|
for (const { role, addressWithRole } of newRoleAddresses) {
|
|
expect(await newL2MessageService.hasRole(role, addressWithRole)).to.be.true;
|
|
}
|
|
});
|
|
|
|
it("Should set all pause types and unpause types in mappings and emit events", async () => {
|
|
// Deploy new implementation
|
|
const newL2MessageServiceFactory = await ethers.getContractFactory(
|
|
"contracts/messageService/l2/L2MessageService.sol:L2MessageService",
|
|
);
|
|
const newL2MessageService = await upgrades.upgradeProxy(l2MessageService, newL2MessageServiceFactory);
|
|
|
|
const reinitializePromise = newL2MessageService.reinitializePauseTypesAndPermissions(
|
|
newRoleAddresses,
|
|
L2_MESSAGE_SERVICE_PAUSE_TYPES_ROLES,
|
|
L2_MESSAGE_SERVICE_UNPAUSE_TYPES_ROLES,
|
|
);
|
|
|
|
await Promise.all([
|
|
expectEvents(
|
|
newL2MessageService,
|
|
reinitializePromise,
|
|
L2_MESSAGE_SERVICE_PAUSE_TYPES_ROLES.map(({ pauseType, role }) => ({
|
|
name: "PauseTypeRoleSet",
|
|
args: [pauseType, role],
|
|
})),
|
|
),
|
|
expectEvents(
|
|
newL2MessageService,
|
|
reinitializePromise,
|
|
L2_MESSAGE_SERVICE_UNPAUSE_TYPES_ROLES.map(({ pauseType, role }) => ({
|
|
name: "UnPauseTypeRoleSet",
|
|
args: [pauseType, role],
|
|
})),
|
|
),
|
|
]);
|
|
|
|
const pauseTypeRolesMappingSlot = 167;
|
|
const unpauseTypeRolesMappingSlot = 168;
|
|
|
|
for (const { pauseType, role } of L2_MESSAGE_SERVICE_PAUSE_TYPES_ROLES) {
|
|
const slot = generateKeccak256(["uint8", "uint256"], [pauseType, pauseTypeRolesMappingSlot]);
|
|
const roleInMapping = await ethers.provider.getStorage(newL2MessageService.getAddress(), slot);
|
|
expect(roleInMapping).to.equal(role);
|
|
}
|
|
|
|
for (const { pauseType, role } of L2_MESSAGE_SERVICE_UNPAUSE_TYPES_ROLES) {
|
|
const slot = generateKeccak256(["uint8", "uint256"], [pauseType, unpauseTypeRolesMappingSlot]);
|
|
const roleInMapping = await ethers.provider.getStorage(newL2MessageService.getAddress(), slot);
|
|
expect(roleInMapping).to.equal(role);
|
|
}
|
|
});
|
|
});
|
|
});
|