Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: v3 codec #4

Merged
merged 13 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
### NOTE: DO NOT USE THIS FILE IF USING TESTNET'S .ENV
ETHERSCAN_API_KEY=ABC123ABC123ABC123ABC123ABC123ABC1

MAINNET_FORK_RPC=
RINKEBY_RPC=https://eth-rinkeby.alchemyapi.io/v2/<YOUR ALCHEMY KEY>
SCROLL_L1_RPC=https://prealpha.scroll.io/l1
SCROLL_L2_RPC=https://prealpha.scroll.io/l2
Expand Down
305 changes: 3 additions & 302 deletions hardhat-test/L1MessageQueue.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,13 @@ import { expect } from "chai";
import { ethers } from "hardhat";

import { L1MessageQueue, L2GasPriceOracle } from "../typechain";
import {
MaxUint256,
ZeroAddress,
concat,
encodeRlp,
getAddress,
hexlify,
keccak256,
randomBytes,
toBeHex,
toBigInt,
} from "ethers";
import { MaxUint256, ZeroAddress, concat, encodeRlp, hexlify, keccak256, randomBytes, toBeHex } from "ethers";

describe("L1MessageQueue", async () => {
let deployer: HardhatEthersSigner;
let scrollChain: HardhatEthersSigner;
let messenger: HardhatEthersSigner;
let gateway: HardhatEthersSigner;
let signer: HardhatEthersSigner;

let oracle: L2GasPriceOracle;
let queue: L1MessageQueue;
Expand All @@ -37,7 +25,7 @@ describe("L1MessageQueue", async () => {
};

beforeEach(async () => {
[deployer, scrollChain, messenger, gateway, signer] = await ethers.getSigners();
[deployer, scrollChain, messenger, gateway] = await ethers.getSigners();

const ProxyAdmin = await ethers.getContractFactory("ProxyAdmin", deployer);
const admin = await ProxyAdmin.deploy();
Expand All @@ -62,51 +50,7 @@ describe("L1MessageQueue", async () => {
await queue.initialize(messenger.address, scrollChain.address, ZeroAddress, oracle.getAddress(), 10000000);
});

context("auth", async () => {
it("should initialize correctly", async () => {
expect(await queue.owner()).to.eq(deployer.address);
expect(await queue.messenger()).to.eq(messenger.address);
expect(await queue.scrollChain()).to.eq(scrollChain.address);
expect(await queue.enforcedTxGateway()).to.eq(gateway.address);
expect(await queue.gasOracle()).to.eq(await oracle.getAddress());
expect(await queue.maxGasLimit()).to.eq(10000000);
});

it("should revert, when initialize again", async () => {
await expect(queue.initialize(ZeroAddress, ZeroAddress, ZeroAddress, ZeroAddress, 0)).to.revertedWith(
"Initializable: contract is already initialized"
);
});

context("#updateGasOracle", async () => {
it("should revert, when non-owner call", async () => {
await expect(queue.connect(signer).updateGasOracle(ZeroAddress)).to.revertedWith(
"Ownable: caller is not the owner"
);
});

it("should succeed", async () => {
expect(await queue.gasOracle()).to.eq(await oracle.getAddress());
await expect(queue.updateGasOracle(deployer.address))
.to.emit(queue, "UpdateGasOracle")
.withArgs(await oracle.getAddress(), deployer.address);
expect(await queue.gasOracle()).to.eq(deployer.address);
});
});

context("#updateMaxGasLimit", async () => {
it("should revert, when non-owner call", async () => {
await expect(queue.connect(signer).updateMaxGasLimit(0)).to.revertedWith("Ownable: caller is not the owner");
});

it("should succeed", async () => {
expect(await queue.maxGasLimit()).to.eq(10000000);
await expect(queue.updateMaxGasLimit(0)).to.emit(queue, "UpdateMaxGasLimit").withArgs(10000000, 0);
expect(await queue.maxGasLimit()).to.eq(0);
});
});
});

// other functions are tested in `src/test/L1MessageQueue.t.sol`
context("#computeTransactionHash", async () => {
it("should succeed", async () => {
const sender = "0xb2a70fab1a45b1b9be443b6567849a1702bc1232";
Expand Down Expand Up @@ -147,247 +91,4 @@ describe("L1MessageQueue", async () => {
}
});
});

context("#appendCrossDomainMessage", async () => {
it("should revert, when non-messenger call", async () => {
await expect(queue.connect(signer).appendCrossDomainMessage(ZeroAddress, 0, "0x")).to.revertedWith(
"Only callable by the L1ScrollMessenger"
);
});

it("should revert, when exceed maxGasLimit", async () => {
await expect(queue.connect(messenger).appendCrossDomainMessage(ZeroAddress, 10000001, "0x")).to.revertedWith(
"Gas limit must not exceed maxGasLimit"
);
});

it("should revert, when below intrinsic gas", async () => {
await expect(queue.connect(messenger).appendCrossDomainMessage(ZeroAddress, 0, "0x")).to.revertedWith(
"Insufficient gas limit, must be above intrinsic gas"
);
});

it("should succeed", async () => {
expect(await queue.nextCrossDomainMessageIndex()).to.eq(0n);
const sender = getAddress(
toBeHex((toBigInt(messenger.address) + toBigInt("0x1111000000000000000000000000000000001111")) % 2n ** 160n)
.slice(2)
.padStart(40, "0")
);
const hash = await queue.computeTransactionHash(sender, 0, 0, signer.address, 100000, "0x01");
await expect(queue.connect(messenger).appendCrossDomainMessage(signer.address, 100000, "0x01"))
.to.emit(queue, "QueueTransaction")
.withArgs(sender, signer.address, 0, 0, 100000, "0x01");
expect(await queue.nextCrossDomainMessageIndex()).to.eq(1n);
expect(await queue.getCrossDomainMessage(0)).to.eq(hash);
});
});

context("#appendEnforcedTransaction", async () => {
it("should revert, when non-gateway call", async () => {
await expect(
queue.connect(signer).appendEnforcedTransaction(signer.address, ZeroAddress, 0, 0, "0x")
).to.revertedWith("Only callable by the EnforcedTxGateway");
});

it("should revert, when sender is not EOA", async () => {
await expect(
queue.connect(gateway).appendEnforcedTransaction(queue.getAddress(), ZeroAddress, 0, 0, "0x")
).to.revertedWith("only EOA");
});

it("should revert, when exceed maxGasLimit", async () => {
await expect(
queue.connect(gateway).appendEnforcedTransaction(signer.address, ZeroAddress, 0, 10000001, "0x")
).to.revertedWith("Gas limit must not exceed maxGasLimit");
});

it("should revert, when below intrinsic gas", async () => {
await expect(
queue.connect(gateway).appendEnforcedTransaction(signer.address, ZeroAddress, 0, 0, "0x")
).to.revertedWith("Insufficient gas limit, must be above intrinsic gas");
});

it("should succeed", async () => {
expect(await queue.nextCrossDomainMessageIndex()).to.eq(0n);
const sender = signer.address;
const hash = await queue.computeTransactionHash(sender, 0, 200, signer.address, 100000, "0x01");
await expect(
queue.connect(gateway).appendEnforcedTransaction(signer.address, signer.address, 200, 100000, "0x01")
)
.to.emit(queue, "QueueTransaction")
.withArgs(sender, signer.address, 200, 0, 100000, "0x01");
expect(await queue.nextCrossDomainMessageIndex()).to.eq(1n);
expect(await queue.getCrossDomainMessage(0)).to.eq(hash);
});
});

context("#popCrossDomainMessage", async () => {
it("should revert, when non-scrollChain call", async () => {
await expect(queue.connect(signer).popCrossDomainMessage(0, 0, 0)).to.revertedWith(
"Only callable by the ScrollChain"
);
});

it("should revert, when pop too many messages", async () => {
await expect(queue.connect(scrollChain).popCrossDomainMessage(0, 257, 0)).to.revertedWith(
"pop too many messages"
);
});

it("should revert, when start index mismatch", async () => {
await expect(queue.connect(scrollChain).popCrossDomainMessage(1, 256, 0)).to.revertedWith("start index mismatch");
});

it("should succeed", async () => {
// append 512 messages
for (let i = 0; i < 256 * 2; i++) {
await queue.connect(messenger).appendCrossDomainMessage(ZeroAddress, 1000000, "0x");
}

// pop 50 messages with no skip
await expect(queue.connect(scrollChain).popCrossDomainMessage(0, 50, 0))
.to.emit(queue, "DequeueTransaction")
.withArgs(0, 50, 0);
for (let i = 0; i < 50; i++) {
expect(await queue.isMessageSkipped(i)).to.eq(false);
expect(await queue.isMessageDropped(i)).to.eq(false);
}
expect(await queue.pendingQueueIndex()).to.eq(50);

// pop 10 messages all skip
await expect(queue.connect(scrollChain).popCrossDomainMessage(50, 10, 1023))
.to.emit(queue, "DequeueTransaction")
.withArgs(50, 10, 1023);
expect(await queue.pendingQueueIndex()).to.eq(60);
for (let i = 50; i < 60; i++) {
expect(await queue.isMessageSkipped(i)).to.eq(true);
expect(await queue.isMessageDropped(i)).to.eq(false);
}

// pop 20 messages, skip first 5
await expect(queue.connect(scrollChain).popCrossDomainMessage(60, 20, 31))
.to.emit(queue, "DequeueTransaction")
.withArgs(60, 20, 31);
expect(await queue.pendingQueueIndex()).to.eq(80);
for (let i = 60; i < 65; i++) {
expect(await queue.isMessageSkipped(i)).to.eq(true);
expect(await queue.isMessageDropped(i)).to.eq(false);
}
for (let i = 65; i < 80; i++) {
expect(await queue.isMessageSkipped(i)).to.eq(false);
expect(await queue.isMessageDropped(i)).to.eq(false);
}

// pop 256 messages with random skip
const bitmap = toBigInt("0x496525059c3f33758d17030403e45afe067b8a0ae1317cda0487fd2932cbea1a");
const tx = await queue.connect(scrollChain).popCrossDomainMessage(80, 256, bitmap);
await expect(tx).to.emit(queue, "DequeueTransaction").withArgs(80, 256, bitmap);
console.log("gas used:", (await tx.wait())!.gasUsed.toString());
for (let i = 80; i < 80 + 256; i++) {
expect(await queue.isMessageSkipped(i)).to.eq(((bitmap >> toBigInt(i - 80)) & 1n) === 1n);
expect(await queue.isMessageDropped(i)).to.eq(false);
}
});

// @note skip this random benchmark tests
for (const count1 of [1, 2, 128, 129, 256]) {
for (const count2 of [1, 2, 128, 129, 256]) {
for (const count3 of [1, 2, 128, 129, 256]) {
it.skip(`should succeed on random tests, pop three times each with ${count1} ${count2} ${count3} msgs`, async () => {
// append count1 + count2 + count3 messages
for (let i = 0; i < count1 + count2 + count3; i++) {
await queue.connect(messenger).appendCrossDomainMessage(ZeroAddress, 1000000, "0x");
}

// first pop `count1` messages
const bitmap1 = toBigInt(randomBytes(32));
let tx = await queue.connect(scrollChain).popCrossDomainMessage(0, count1, bitmap1);
await expect(tx)
.to.emit(queue, "DequeueTransaction")
.withArgs(0, count1, bitmap1 & ((1n << toBigInt(count1)) - 1n));
for (let i = 0; i < count1; i++) {
expect(await queue.isMessageSkipped(i)).to.eq(((bitmap1 >> toBigInt(i)) & 1n) === 1n);
expect(await queue.isMessageDropped(i)).to.eq(false);
}

// then pop `count2` messages
const bitmap2 = toBigInt(randomBytes(32));
tx = await queue.connect(scrollChain).popCrossDomainMessage(count1, count2, bitmap2);
await expect(tx)
.to.emit(queue, "DequeueTransaction")
.withArgs(count1, count2, bitmap2 & ((1n << toBigInt(count2)) - 1n));
for (let i = 0; i < count2; i++) {
expect(await queue.isMessageSkipped(i + count1)).to.eq(((bitmap2 >> toBigInt(i)) & 1n) === 1n);
expect(await queue.isMessageDropped(i + count1)).to.eq(false);
}

// last pop `count3` messages
const bitmap3 = toBigInt(randomBytes(32));
tx = await queue.connect(scrollChain).popCrossDomainMessage(count1 + count2, count3, bitmap3);
await expect(tx)
.to.emit(queue, "DequeueTransaction")
.withArgs(count1 + count2, count3, bitmap3 & ((1n << toBigInt(count3)) - 1n));
for (let i = 0; i < count3; i++) {
expect(await queue.isMessageSkipped(i + count1 + count2)).to.eq(((bitmap3 >> toBigInt(i)) & 1n) === 1n);
expect(await queue.isMessageDropped(i + count1 + count2)).to.eq(false);
}
});
}
}
}
});

context("#dropCrossDomainMessage", async () => {
it("should revert, when non-messenger call", async () => {
await expect(queue.connect(signer).dropCrossDomainMessage(0)).to.revertedWith(
"Only callable by the L1ScrollMessenger"
);
});

it("should revert, when drop non-skipped message", async () => {
// append 10 messages
for (let i = 0; i < 10; i++) {
await queue.connect(messenger).appendCrossDomainMessage(ZeroAddress, 1000000, "0x");
}
// pop 5 messages with no skip
await expect(queue.connect(scrollChain).popCrossDomainMessage(0, 5, 0))
.to.emit(queue, "DequeueTransaction")
.withArgs(0, 5, 0);
for (let i = 0; i < 5; i++) {
expect(await queue.isMessageSkipped(i)).to.eq(false);
expect(await queue.isMessageDropped(i)).to.eq(false);
}
expect(await queue.pendingQueueIndex()).to.eq(5);

for (let i = 0; i < 5; i++) {
await expect(queue.connect(messenger).dropCrossDomainMessage(i)).to.revertedWith("drop non-skipped message");
}

// drop pending message
for (let i = 6; i < 10; i++) {
await expect(queue.connect(messenger).dropCrossDomainMessage(i)).to.revertedWith("cannot drop pending message");
}
});

it("should succeed", async () => {
// append 10 messages
for (let i = 0; i < 10; i++) {
await queue.connect(messenger).appendCrossDomainMessage(ZeroAddress, 1000000, "0x");
}
// pop 10 messages, all skipped
await expect(queue.connect(scrollChain).popCrossDomainMessage(0, 10, 0x3ff))
.to.emit(queue, "DequeueTransaction")
.withArgs(0, 10, 0x3ff);

for (let i = 0; i < 10; i++) {
expect(await queue.isMessageSkipped(i)).to.eq(true);
expect(await queue.isMessageDropped(i)).to.eq(false);
await expect(queue.connect(messenger).dropCrossDomainMessage(i)).to.emit(queue, "DropTransaction").withArgs(i);
await expect(queue.connect(messenger).dropCrossDomainMessage(i)).to.revertedWith("message already dropped");
expect(await queue.isMessageSkipped(i)).to.eq(true);
expect(await queue.isMessageDropped(i)).to.eq(true);
}
});
});
});
Loading
Loading