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

L2EthToken Tests #152

Merged
merged 11 commits into from
Jan 9, 2024
237 changes: 237 additions & 0 deletions system-contracts/test/L2EthToken.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import { expect } from "chai";
import { ethers, network } from "hardhat";
import type { Wallet } from "zksync-web3";
import type { L2EthToken } from "../typechain";
import { L2EthTokenFactory } from "../typechain";
import { deployContractOnAddress, getWallets, loadArtifact, provider } from "./shared/utils";
import type { BigNumber } from "ethers";
import { TEST_BOOTLOADER_FORMAL_ADDRESS, TEST_ETH_TOKEN_SYSTEM_CONTRACT_ADDRESS } from "./shared/constants";
import { prepareEnvironment, setResult } from "./shared/mocks";
import { randomBytes } from "crypto";

describe("L2EthToken tests", () => {
const richWallet = getWallets()[0];
let wallets: Array<Wallet>;
let l2EthToken: L2EthToken;
let bootloaderAccount: ethers.Signer;
let mailboxIface: ethers.utils.Interface;

before(async () => {
await prepareEnvironment();
await deployContractOnAddress(TEST_ETH_TOKEN_SYSTEM_CONTRACT_ADDRESS, "L2EthToken");
l2EthToken = L2EthTokenFactory.connect(TEST_ETH_TOKEN_SYSTEM_CONTRACT_ADDRESS, richWallet);
bootloaderAccount = await ethers.getImpersonatedSigner(TEST_BOOTLOADER_FORMAL_ADDRESS);
AntonD3 marked this conversation as resolved.
Show resolved Hide resolved
mailboxIface = new ethers.utils.Interface((await loadArtifact("IMailbox")).abi);
});

beforeEach(async () => {
wallets = Array.from({ length: 2 }, () => ethers.Wallet.createRandom().connect(provider));
});

after(async function () {
await network.provider.request({
method: "hardhat_stopImpersonatingAccount",
params: [TEST_BOOTLOADER_FORMAL_ADDRESS],
});
});

describe("mint", () => {
it("called by bootlader", async () => {
const initialSupply: BigNumber = await l2EthToken.totalSupply();
const initialBalanceOfWallet: BigNumber = await l2EthToken.balanceOf(wallets[0].address);
const amountToMint: BigNumber = ethers.utils.parseEther("10.0");

await expect(l2EthToken.connect(bootloaderAccount).mint(wallets[0].address, amountToMint))
.to.emit(l2EthToken, "Mint")
.withArgs(wallets[0].address, amountToMint);

const finalSupply: BigNumber = await l2EthToken.totalSupply();
const balanceOfWallet: BigNumber = await l2EthToken.balanceOf(wallets[0].address);
expect(finalSupply).to.equal(initialSupply.add(amountToMint));
expect(balanceOfWallet).to.equal(initialBalanceOfWallet.add(amountToMint));
});

it("not called by bootloader", async () => {
const amountToMint: BigNumber = ethers.utils.parseEther("10.0");
await expect(l2EthToken.connect(wallets[0]).mint(wallets[0].address, amountToMint)).to.be.rejectedWith(
"Callable only by the bootloader"
);
});
});

describe("transfer", () => {
it("transfer successfully", async () => {
await (
await l2EthToken.connect(bootloaderAccount).mint(wallets[0].address, ethers.utils.parseEther("100.0"))
).wait();

const senderBalanceBeforeTransfer: BigNumber = await l2EthToken.balanceOf(wallets[0].address);
const recipientBalanceBeforeTransfer: BigNumber = await l2EthToken.balanceOf(wallets[1].address);

const amountToTransfer = ethers.utils.parseEther("10.0");

await expect(
l2EthToken.connect(bootloaderAccount).transferFromTo(wallets[0].address, wallets[1].address, amountToTransfer)
)
.to.emit(l2EthToken, "Transfer")
.withArgs(wallets[0].address, wallets[1].address, amountToTransfer);

const senderBalanceAfterTransfer: BigNumber = await l2EthToken.balanceOf(wallets[0].address);
const recipientBalanceAfterTransfer: BigNumber = await l2EthToken.balanceOf(wallets[1].address);
expect(senderBalanceAfterTransfer).to.be.eq(senderBalanceBeforeTransfer.sub(amountToTransfer));
expect(recipientBalanceAfterTransfer).to.be.eq(recipientBalanceBeforeTransfer.add(amountToTransfer));
});

it("no tranfser due to insufficient balance", async () => {
await (
await l2EthToken.connect(bootloaderAccount).mint(wallets[0].address, ethers.utils.parseEther("5.0"))
).wait();
const amountToTransfer: BigNumber = ethers.utils.parseEther("6.0");

await expect(
l2EthToken.connect(bootloaderAccount).transferFromTo(wallets[0].address, wallets[1].address, amountToTransfer)
).to.be.rejectedWith("Transfer amount exceeds balance");
});

it("no transfer - require special access", async () => {
const maliciousWallet: Wallet = ethers.Wallet.createRandom().connect(provider);
await (
await l2EthToken.connect(bootloaderAccount).mint(maliciousWallet.address, ethers.utils.parseEther("20.0"))
).wait();

const amountToTransfer: BigNumber = ethers.utils.parseEther("20.0");

await expect(
l2EthToken
.connect(maliciousWallet)
.transferFromTo(maliciousWallet.address, wallets[1].address, amountToTransfer)
).to.be.rejectedWith("Only system contracts with special access can call this method");
});
});

describe("balanceOf", () => {
it("walletFrom address", async () => {
const amountToMint: BigNumber = ethers.utils.parseEther("10.0");

await l2EthToken.connect(bootloaderAccount).mint(wallets[0].address, amountToMint);
const balance = await l2EthToken.balanceOf(wallets[0].address);
expect(balance).to.equal(amountToMint);
});

it("address larger than 20 bytes", async () => {
const amountToMint: BigNumber = ethers.utils.parseEther("123.0");

const res = await l2EthToken.connect(bootloaderAccount).mint(wallets[0].address, amountToMint);
await res.wait();
const largerAddress = ethers.BigNumber.from(
"0x" + randomBytes(12).toString("hex") + wallets[0].address.slice(2)
).toHexString();
const balance = await l2EthToken.balanceOf(largerAddress);

expect(balance).to.equal(amountToMint);
});
});

describe("totalSupply", () => {
it("correct total supply", async () => {
const totalSupplyBefore = await l2EthToken.totalSupply();
const amountToMint: BigNumber = ethers.utils.parseEther("10.0");

await l2EthToken.connect(bootloaderAccount).mint(wallets[0].address, amountToMint);
const totalSupply = await l2EthToken.totalSupply();

expect(totalSupply).to.equal(totalSupplyBefore.add(amountToMint));
});
});

describe("name", () => {
it("correct name", async () => {
const name = await l2EthToken.name();
expect(name).to.equal("Ether");
});
});

describe("symbol", () => {
it("correct symbol", async () => {
const symbol = await l2EthToken.symbol();
expect(symbol).to.equal("ETH");
});
});

describe("decimals", () => {
it("correct decimals", async () => {
const decimals = await l2EthToken.decimals();
expect(decimals).to.equal(18);
});
});

describe("withdraw", () => {
it("event, balance, totalsupply", async () => {
const amountToWithdraw: BigNumber = ethers.utils.parseEther("1.0");
const message: string = ethers.utils.solidityPack(
["bytes4", "address", "uint256"],
[mailboxIface.getSighash("finalizeEthWithdrawal"), wallets[1].address, amountToWithdraw]
);

await setResult("L1Messenger", "sendToL1", [message], {
failure: false,
returnData: ethers.utils.defaultAbiCoder.encode(["bytes32"], [ethers.utils.keccak256(message)]),
});

// To prevent underflow since initial values are 0's and we are substracting from them
const amountToMint: BigNumber = ethers.utils.parseEther("100.0");
await (await l2EthToken.connect(bootloaderAccount).mint(l2EthToken.address, amountToMint)).wait();
AntonD3 marked this conversation as resolved.
Show resolved Hide resolved

const balanceBeforeWithdrawal: BigNumber = await l2EthToken.balanceOf(l2EthToken.address);
const totalSupplyBefore = await l2EthToken.totalSupply();

await expect(l2EthToken.connect(richWallet).withdraw(wallets[1].address, { value: amountToWithdraw }))
.to.emit(l2EthToken, "Withdrawal")
.withArgs(richWallet.address, wallets[1].address, amountToWithdraw);

const balanceAfterWithdrawal: BigNumber = await l2EthToken.balanceOf(l2EthToken.address);
const totalSupplyAfter = await l2EthToken.totalSupply();

expect(balanceAfterWithdrawal).to.equal(balanceBeforeWithdrawal.sub(amountToWithdraw));
expect(totalSupplyAfter).to.equal(totalSupplyBefore.sub(amountToWithdraw));
});

it("event, balance, totalsupply, withdrawWithMessage", async () => {
const amountToWithdraw: BigNumber = ethers.utils.parseEther("1.0");
const additionalData: string = ethers.utils.defaultAbiCoder.encode(["string"], ["additional data"]);
const message: string = ethers.utils.solidityPack(
["bytes4", "address", "uint256", "address", "bytes"],
[
mailboxIface.getSighash("finalizeEthWithdrawal"),
wallets[1].address,
amountToWithdraw,
richWallet.address,
additionalData,
]
);

await setResult("L1Messenger", "sendToL1", [message], {
failure: false,
returnData: ethers.utils.defaultAbiCoder.encode(["bytes32"], [ethers.utils.keccak256(message)]),
});

AntonD3 marked this conversation as resolved.
Show resolved Hide resolved
// Consitency reasons - won't crash if test order reverse
const amountToMint: BigNumber = ethers.utils.parseEther("100.0");
await (await l2EthToken.connect(bootloaderAccount).mint(l2EthToken.address, amountToMint)).wait();

const totalSupplyBefore = await l2EthToken.totalSupply();
const balanceBeforeWithdrawal: BigNumber = await l2EthToken.balanceOf(l2EthToken.address);
await expect(
l2EthToken.connect(richWallet).withdrawWithMessage(wallets[1].address, additionalData, {
value: amountToWithdraw,
})
)
.to.emit(l2EthToken, "WithdrawalWithMessage")
.withArgs(richWallet.address, wallets[1].address, amountToWithdraw, additionalData);
const totalSupplyAfter = await l2EthToken.totalSupply();
const balanceAfterWithdrawal: BigNumber = await l2EthToken.balanceOf(l2EthToken.address);
expect(balanceAfterWithdrawal).to.equal(balanceBeforeWithdrawal.sub(amountToWithdraw));
expect(totalSupplyAfter).to.equal(totalSupplyBefore.sub(amountToWithdraw));
});
});
});
Loading