import { expect } from "chai";
import { ethers } from "hardhat";
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
import { CAPTokenOFT, MockLayerZeroEndpoint } from "../../typechain-types";
/**
* CAPTokenOFT Tests
*
* Tests for the OFT contract deployed on destination chains
* Focus areas:
* - Deployment and token metadata
* - Burning functionality
* - Access control
* - OFT standard compliance
*/
describe("CAPTokenOFT", function () {
let oft: CAPTokenOFT;
let owner: SignerWithAddress;
let user1: SignerWithAddress;
let mockEndpoint: MockLayerZeroEndpoint;
beforeEach(async function () {
[owner, user1] = await ethers.getSigners();
// Deploy Mock LayerZero Endpoint
const MockEndpoint = await ethers.getContractFactory("MockLayerZeroEndpoint");
mockEndpoint = await MockEndpoint.deploy();
await mockEndpoint.deployed();
// Deploy OFT
const OFT = await ethers.getContractFactory("CAPTokenOFT");
oft = await OFT.deploy(mockEndpoint.address, owner.address);
await oft.deployed();
});
describe("Deployment", function () {
it("Should deploy with correct name", async function () {
expect(await oft.name()).to.equal("Cyberia");
});
it("Should deploy with correct symbol", async function () {
expect(await oft.symbol()).to.equal("CAP");
});
it("Should deploy with correct decimals", async function () {
expect(await oft.decimals()).to.equal(18);
});
it("Should set correct owner", async function () {
expect(await oft.owner()).to.equal(owner.address);
});
it("Should have correct shared decimals", async function () {
const sharedDecimals = await oft.sharedDecimals();
expect(sharedDecimals).to.equal(6);
});
it("Should start with zero total supply", async function () {
expect(await oft.totalSupply()).to.equal(0);
});
it("Should have correct decimal conversion rate", async function () {
const conversionRate = await oft.decimalConversionRate();
// 18 - 6 = 12, so 10^12
expect(conversionRate).to.equal(10n ** 12n);
});
});
describe("Burning", function () {
it("Should NOT have burn function (removed for security)", async function () {
// The burn() and burnFrom() functions were removed from CAPTokenOFT for security
// Only the LayerZero endpoint can trigger burns via the OFT._debit() function
// when users bridge tokens back to Ethereum
// Verify that calling burn() would revert (not available in the contract ABI)
expect(typeof oft.burn).to.equal("undefined");
});
it("Should restrict burning to only LayerZero-initiated burns", async function () {
// Users cannot directly call burn() - only the LZ endpoint can initiate burns
// by calling the OFT's receiveFromEVM function
const balance = await oft.balanceOf(user1.address);
expect(balance).to.equal(0); // Should start with 0
});
});
describe("Access Control", function () {
it("Should only allow owner to transfer ownership", async function () {
await expect(oft.connect(user1).transferOwnership(user1.address)).to.be.reverted;
});
it("Should allow owner to transfer ownership", async function () {
await oft.connect(owner).transferOwnership(user1.address);
expect(await oft.owner()).to.equal(user1.address);
});
});
describe("Token Standards", function () {
it("Should support ERC20 interface", async function () {
// Test basic ERC20 functions exist
expect(await oft.totalSupply()).to.equal(0);
expect(await oft.balanceOf(owner.address)).to.equal(0);
expect(await oft.allowance(owner.address, user1.address)).to.equal(0);
});
it("Should handle approvals", async function () {
const amount = ethers.utils.parseEther("1000");
await oft.connect(owner).approve(user1.address, amount);
expect(await oft.allowance(owner.address, user1.address)).to.equal(amount);
});
it("Should handle max approval", async function () {
const maxUint = ethers.constants.MaxUint256;
await oft.connect(owner).approve(user1.address, maxUint);
expect(await oft.allowance(owner.address, user1.address)).to.equal(maxUint);
});
});
describe("OFT Standard Functions", function () {
it("Should return correct token address (self)", async function () {
const token = await oft.token();
expect(token).to.equal(oft.address);
});
it("Should indicate approval is NOT required", async function () {
const required = await oft.approvalRequired();
expect(required).to.be.false; // OFT doesn't need approval of itself
});
it("Should have matching shared decimals with adapter", async function () {
// Shared decimals must match OFTAdapter (6)
const sharedDecimals = await oft.sharedDecimals();
expect(sharedDecimals).to.equal(6);
});
it("Should use 18 decimals for local token", async function () {
const decimals = await oft.decimals();
expect(decimals).to.equal(18);
});
});
describe("Metadata", function () {
it("Should maintain consistent metadata with source token", async function () {
expect(await oft.name()).to.equal("Cyberia");
expect(await oft.symbol()).to.equal("CAP");
expect(await oft.decimals()).to.equal(18);
});
it("Should have correct OFT version", async function () {
const [_interfaceId, version] = await oft.oftVersion();
expect(version).to.equal(1); // OFT v1
});
});
describe("Decimals Conversion", function () {
it("Should have correct decimal conversion rate", async function () {
const rate = await oft.decimalConversionRate();
// Local decimals (18) - shared decimals (6) = 12
// So conversion rate is 10^12
expect(rate).to.equal(10n ** 12n);
});
it("Should match adapter's shared decimals", async function () {
// This ensures compatibility with OFTAdapter on Ethereum
const sharedDecimals = await oft.sharedDecimals();
expect(sharedDecimals).to.equal(6);
});
});
describe("Edge Cases", function () {
it("Should handle zero amount approvals", async function () {
await oft.connect(owner).approve(user1.address, 0);
const allowance = await oft.allowance(owner.address, user1.address);
expect(allowance).to.equal(0);
});
it("Should revert on transfer with insufficient balance", async function () {
const amount = ethers.utils.parseEther("100");
await expect(oft.connect(user1).transfer(owner.address, amount)).to.be.reverted;
});
it("Should revert on transferFrom with insufficient allowance", async function () {
const amount = ethers.utils.parseEther("100");
await expect(oft.connect(user1).transferFrom(owner.address, user1.address, amount)).to.be.reverted;
});
});
});