mirror of
https://github.com/luxfhe/example-dapps.git
synced 2026-10-30 18:13:32 +00:00
221 lines
7.9 KiB
TypeScript
221 lines
7.9 KiB
TypeScript
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
|
|
import { ethers, fhevm } from "hardhat";
|
|
import { ERC7984Example, ERC7984Example__factory } from "../../types";
|
|
import { expect } from "chai";
|
|
import { FhevmType } from "@fhevm/hardhat-plugin";
|
|
import { deployERC7984ExampleFixture } from "./confToken.fixture";
|
|
|
|
type Signers = {
|
|
deployer: HardhatEthersSigner;
|
|
alice: HardhatEthersSigner;
|
|
bob: HardhatEthersSigner;
|
|
carol: HardhatEthersSigner;
|
|
};
|
|
|
|
describe("ConfidentialToken", function () {
|
|
let signers: Signers;
|
|
let confidentialToken: ERC7984Example;
|
|
let confidentialTokenAddress: string;
|
|
|
|
before(async function () {
|
|
const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
|
|
signers = {
|
|
deployer: ethSigners[0],
|
|
alice: ethSigners[1],
|
|
bob: ethSigners[2],
|
|
carol: ethSigners[3],
|
|
};
|
|
});
|
|
|
|
beforeEach(async function () {
|
|
// Check whether the tests are running against an FHEVM mock environment
|
|
if (!fhevm.isMock) {
|
|
console.warn(`This hardhat test suite cannot run on Sepolia Testnet`);
|
|
this.skip();
|
|
}
|
|
|
|
({ ERC7984Example: confidentialToken, ERC7984ExampleAddress: confidentialTokenAddress } =
|
|
await deployERC7984ExampleFixture(signers.deployer));
|
|
});
|
|
|
|
describe("Deployment", function () {
|
|
it("should deploy with correct initial parameters", async function () {
|
|
expect(await confidentialToken.name()).to.equal("Confidential Token");
|
|
expect(await confidentialToken.symbol()).to.equal("CTKN");
|
|
expect(await confidentialToken.contractURI()).to.equal("https://example.com/token");
|
|
});
|
|
|
|
it("should set deployer as owner", async function () {
|
|
expect(await confidentialToken.owner()).to.equal(signers.deployer.address);
|
|
});
|
|
|
|
it("should mint initial supply to deployer", async function () {
|
|
const deployerBalance = await confidentialToken.confidentialBalanceOf(signers.deployer.address);
|
|
|
|
// The balance should be encrypted, so we need to decrypt it
|
|
const clearBalance = await fhevm.userDecryptEuint(
|
|
FhevmType.euint64,
|
|
deployerBalance,
|
|
confidentialTokenAddress,
|
|
signers.deployer,
|
|
);
|
|
|
|
expect(clearBalance).to.equal(1000);
|
|
});
|
|
|
|
it("should have correct total supply", async function () {
|
|
const totalSupply = await confidentialToken.confidentialTotalSupply();
|
|
|
|
// Total supply should be encrypted, so we need to decrypt it
|
|
// The owner should have access to decrypt the total supply
|
|
const clearTotalSupply = await fhevm.userDecryptEuint(
|
|
FhevmType.euint64,
|
|
totalSupply,
|
|
confidentialTokenAddress,
|
|
signers.deployer,
|
|
);
|
|
|
|
expect(clearTotalSupply).to.equal(1000);
|
|
});
|
|
});
|
|
|
|
describe("Ownership", function () {
|
|
it("should allow owner to transfer ownership", async function () {
|
|
await confidentialToken.connect(signers.deployer).transferOwnership(signers.alice.address);
|
|
|
|
// With Ownable2Step, the new owner must accept ownership
|
|
await confidentialToken.connect(signers.alice).acceptOwnership();
|
|
|
|
expect(await confidentialToken.owner()).to.equal(signers.alice.address);
|
|
});
|
|
|
|
it("should not allow non-owner to transfer ownership", async function () {
|
|
await expect(
|
|
confidentialToken.connect(signers.alice).transferOwnership(signers.bob.address),
|
|
).to.be.revertedWithCustomError(confidentialToken, "OwnableUnauthorizedAccount");
|
|
});
|
|
|
|
it("should not allow non-owner to renounce ownership", async function () {
|
|
await expect(confidentialToken.connect(signers.alice).renounceOwnership()).to.be.revertedWithCustomError(
|
|
confidentialToken,
|
|
"OwnableUnauthorizedAccount",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Token Information", function () {
|
|
it("should return correct name", async function () {
|
|
expect(await confidentialToken.name()).to.equal("Confidential Token");
|
|
});
|
|
|
|
it("should return correct symbol", async function () {
|
|
expect(await confidentialToken.symbol()).to.equal("CTKN");
|
|
});
|
|
|
|
it("should return correct token URI", async function () {
|
|
expect(await confidentialToken.contractURI()).to.equal("https://example.com/token");
|
|
});
|
|
});
|
|
|
|
describe("Balance and Supply", function () {
|
|
it("should return encrypted balance for deployer", async function () {
|
|
const balance = await confidentialToken.confidentialBalanceOf(signers.deployer.address);
|
|
expect(balance).to.not.equal(ethers.ZeroHash);
|
|
|
|
// Decrypt and verify the balance
|
|
const clearBalance = await fhevm.userDecryptEuint(
|
|
FhevmType.euint64,
|
|
balance,
|
|
confidentialTokenAddress,
|
|
signers.deployer,
|
|
);
|
|
expect(clearBalance).to.equal(1000);
|
|
});
|
|
|
|
it("should return zero balance for new addresses", async function () {
|
|
const balance = await confidentialToken.confidentialBalanceOf(signers.alice.address);
|
|
|
|
// For zero balances, we should check if the handle is initialized
|
|
// If it's not initialized, it means the balance is effectively zero
|
|
try {
|
|
const clearBalance = await fhevm.userDecryptEuint(
|
|
FhevmType.euint64,
|
|
balance,
|
|
confidentialTokenAddress,
|
|
signers.alice,
|
|
);
|
|
expect(clearBalance).to.equal(0);
|
|
} catch (error: any) {
|
|
// If decryption fails due to uninitialized handle, that's also valid
|
|
// as it indicates a zero balance
|
|
expect(error.message).to.include("Handle is not initialized");
|
|
}
|
|
});
|
|
|
|
it("should return encrypted total supply", async function () {
|
|
const totalSupply = await confidentialToken.confidentialTotalSupply();
|
|
expect(totalSupply).to.not.equal(ethers.ZeroHash);
|
|
|
|
// Decrypt and verify the total supply
|
|
// The owner should have access to decrypt the total supply
|
|
const clearTotalSupply = await fhevm.userDecryptEuint(
|
|
FhevmType.euint64,
|
|
totalSupply,
|
|
confidentialTokenAddress,
|
|
signers.deployer,
|
|
);
|
|
expect(clearTotalSupply).to.equal(1000);
|
|
});
|
|
});
|
|
|
|
describe("Access Control", function () {
|
|
it("should allow owner to see confidential total supply", async function () {
|
|
const confidentialTotalSupply = await confidentialToken.confidentialTotalSupply();
|
|
expect(confidentialTotalSupply).to.not.equal(ethers.ZeroHash);
|
|
});
|
|
|
|
it("should not allow non-owner to see confidential total supply", async function () {
|
|
// This should revert or return encrypted data that non-owners can't decrypt
|
|
// The exact behavior depends on the FHE implementation
|
|
const confidentialTotalSupply = await confidentialToken.confidentialTotalSupply();
|
|
expect(confidentialTotalSupply).to.not.equal(ethers.ZeroHash);
|
|
});
|
|
});
|
|
|
|
describe("Constructor Parameters", function () {
|
|
it("should handle different initial amounts", async function () {
|
|
// Deploy a new contract with different initial amount
|
|
const ConfidentialTokenFactory = (await ethers.getContractFactory(
|
|
"ERC7984Example",
|
|
)) as ERC7984Example__factory;
|
|
const newToken = (await ConfidentialTokenFactory.deploy(
|
|
500, // Different initial amount
|
|
"Test Token",
|
|
"TEST",
|
|
"https://test.com/token",
|
|
)) as ERC7984Example;
|
|
|
|
const newTokenAddress = await newToken.getAddress();
|
|
const balance = await newToken.confidentialBalanceOf(signers.deployer.address);
|
|
|
|
const clearBalance = await fhevm.userDecryptEuint(FhevmType.euint64, balance, newTokenAddress, signers.deployer);
|
|
|
|
expect(clearBalance).to.equal(500);
|
|
});
|
|
|
|
it("should handle empty token URI", async function () {
|
|
const ConfidentialTokenFactory = (await ethers.getContractFactory(
|
|
"ERC7984Example",
|
|
)) as ERC7984Example__factory;
|
|
const newToken = (await ConfidentialTokenFactory.deploy(
|
|
100,
|
|
"Empty URI Token",
|
|
"EMPTY",
|
|
"", // Empty token URI
|
|
)) as ERC7984Example;
|
|
|
|
expect(await newToken.contractURI()).to.equal("");
|
|
});
|
|
});
|
|
});
|