cyberia-token/test/security/Security.test.ts

import { expect } from "chai";
import { ethers, upgrades } from "hardhat";
import { CAPToken } from "../typechain-types";
import { Signer } from "ethers";

describe("Security Tests", function () {
  let cap: CAPToken;
  let owner: Signer;
  let treasury: Signer;
  let attacker: Signer;
  let user1: Signer;
  let user2: Signer;
  let pool: Signer;

  beforeEach(async function () {
    [owner, treasury, attacker, user1, user2, pool] = await ethers.getSigners();

    const CAP = await ethers.getContractFactory("CAPToken");
    cap = (await upgrades.deployProxy(CAP, [owner.address, treasury.address], {
      kind: "uups",
      initializer: "initialize",
    })) as unknown as CAPToken;

    // Distribute some tokens for testing
    await cap.connect(owner).transfer(user1.address, ethers.utils.parseEther("10000"));
    await cap.connect(owner).transfer(attacker.address, ethers.utils.parseEther("1000"));
  });

  describe("Access Control", function () {
    it("Should prevent attackers from calling admin functions", async function () {
      // Tax changes require governance
      await expect(cap.connect(attacker).proposeTaxChange(0, 0, 0)).to.be.revertedWith("ONLY_GOVERNANCE");

      // Fee recipient changes require governance
      await expect(cap.connect(attacker).setFeeRecipient(attacker.address)).to.be.revertedWith("ONLY_GOVERNANCE");

      // Pool management requires governance
      await expect(cap.connect(attacker).addPool(pool.address)).to.be.revertedWith("ONLY_GOVERNANCE");

      await expect(cap.connect(attacker).removePool(pool.address)).to.be.revertedWith("ONLY_GOVERNANCE");
    });

    it("Should prevent unauthorized upgrades", async function () {
      const CAPv2 = await ethers.getContractFactory("CAPToken");
      const newImplementation = await CAPv2.deploy();

      // Upgrades require UPGRADER_ROLE
      await expect(cap.connect(attacker).upgradeToAndCall(newImplementation.address, "0x")).to.be.revertedWith(
        "ONLY_GOVERNANCE"
      );
    });

    it("Should prevent governance transfer by non-governance", async function () {
      // Governance transfer is protected by onlyGovernance modifier
      await expect(cap.connect(attacker).setGovernance(attacker.address)).to.be.revertedWith("ONLY_GOVERNANCE");
    });
  });

  describe("Tax Manipulation Resistance", function () {
    it("Should enforce maximum tax limits even with max values", async function () {
      await expect(cap.connect(owner).proposeTaxChange(10000, 100, 100)).to.be.revertedWith("TRANSFER_TAX_TOO_HIGH");

      await expect(cap.connect(owner).proposeTaxChange(100, 10000, 100)).to.be.revertedWith("SELL_TAX_TOO_HIGH");

      await expect(cap.connect(owner).proposeTaxChange(100, 100, 10000)).to.be.revertedWith("BUY_TAX_TOO_HIGH");

      await expect(cap.connect(owner).proposeTaxChange(400, 400, 200)).to.not.be.reverted;

      await expect(cap.connect(owner).proposeTaxChange(400, 400, 300)).to.be.revertedWith("TOTAL_TAX_TOO_HIGH");
    });
  });

  describe("Supply Manipulation Protection", function () {
    it("Should maintain consistent total supply through all operations", async function () {
      const initialSupply = await cap.totalSupply();

      await cap.connect(user1).transfer(user2.address, ethers.utils.parseEther("100"));
      await cap.connect(owner).addPool(pool.address);
      await cap.connect(user1).transfer(pool.address, ethers.utils.parseEther("50"));
      await cap.connect(user1).burn(ethers.utils.parseEther("10"));

      const currentSupply = await cap.totalSupply();
      expect(currentSupply).to.equal(initialSupply.sub(ethers.utils.parseEther("10")));
    });

    it("Should restrict minting to authorized roles only", async function () {
      const mintAmount = ethers.utils.parseEther("1000000");

      // Attacker without governance cannot propose mint
      await expect(cap.connect(attacker).proposeMint(attacker.address, mintAmount)).to.be.revertedWith(
        "ONLY_GOVERNANCE"
      );

      const initialSupply = await cap.totalSupply();

      // Owner can propose and execute mint after timelock
      await cap.connect(owner).proposeMint(user1.address, mintAmount);

      // Fast forward 7 days
      await ethers.provider.send("evm_increaseTime", [7 * 24 * 60 * 60]);
      await ethers.provider.send("evm_mine", []);

      await expect(cap.connect(owner).executeMint()).to.not.be.reverted;

      expect(await cap.totalSupply()).to.equal(initialSupply.add(mintAmount));
    });
  });

  describe("Governance Attack Resistance", function () {
    it("Should handle governance transfer correctly", async function () {
      const newGovernance = user2.address;

      // Transfer governance to user2
      await cap.connect(owner).setGovernance(newGovernance);
      expect(await cap.governance()).to.equal(newGovernance);

      // Now old owner loses admin access
      await expect(cap.connect(owner).proposeTaxChange(200, 200, 0)).to.be.revertedWith("ONLY_GOVERNANCE");

      // New governance has admin access and can propose tax changes
      await expect(cap.connect(user2).proposeTaxChange(200, 200, 0)).to.not.be.reverted;
    });
  });

  describe("Edge Cases and Error Conditions", function () {
    it("Should handle zero amount transfers", async function () {
      await expect(cap.connect(user1).transfer(user2.address, 0)).to.not.be.reverted;
    });

    it("Should handle transfers with insufficient balance", async function () {
      const userBalance = await cap.balanceOf(user1.address);
      const excessiveAmount = userBalance.add(1);

      await expect(cap.connect(user1).transfer(user2.address, excessiveAmount)).to.be.revertedWith(
        "ERC20InsufficientBalance"
      );
    });

    it("Should handle burning more than balance", async function () {
      const userBalance = await cap.balanceOf(user1.address);
      const excessiveAmount = userBalance.add(1);

      await expect(cap.connect(user1).burn(excessiveAmount)).to.be.revertedWith("ERC20InsufficientBalance");
    });
  });

  describe("Gas Optimization and DoS Resistance", function () {
    it("Should handle maximum tax calculations without overflow", async function () {
      await cap.connect(owner).proposeTaxChange(400, 400, 200);

      // Fast forward 24 hours
      await ethers.provider.send("evm_increaseTime", [24 * 60 * 60]);
      await ethers.provider.send("evm_mine", []);

      await cap.connect(owner).applyTaxChange();

      const userBalance = await cap.balanceOf(user1.address);
      await cap.connect(user1).transfer(user2.address, userBalance);

      expect(await cap.balanceOf(user1.address)).to.equal(0);
    });
  });
});

Neighbours