Skip to content

Commit

Permalink
feat: v3 codec (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
zimpha authored Jul 31, 2024
1 parent ca7f076 commit 7bb751f
Show file tree
Hide file tree
Showing 50 changed files with 5,092 additions and 1,127 deletions.
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

0 comments on commit 7bb751f

Please sign in to comment.