Files
DLE/backend/test/DLE.test.js

1209 lines
55 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
* All rights reserved.
*
* This software is proprietary and confidential.
* Unauthorized copying, modification, or distribution is prohibited.
*
* For licensing inquiries: info@hb3-accelerator.com
* Website: https://hb3-accelerator.com
* GitHub: https://github.com/HB3-ACCELERATOR
*/
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("DLE Smart Contract", function () {
let DLE;
let dle;
let owner;
let partner1, partner2, partner3;
let addrs;
let treasuryAddr, timelockAddr, readerAddr;
const SAMPLE_CONFIG = {
name: "Test DLE",
symbol: "TDLE",
location: "Test Location",
coordinates: "0,0",
jurisdiction: 1,
okvedCodes: ["62.01"],
kpp: 123456789,
quorumPercentage: 51,
initialPartners: [], // Will set in beforeEach
initialAmounts: [],
supportedChainIds: [1, 137]
};
beforeEach(async function () {
[owner, partner1, partner2, partner3, ...addrs] = await ethers.getSigners();
DLE = await ethers.getContractFactory("DLE");
SAMPLE_CONFIG.initialPartners = [partner1.address, partner2.address, partner3.address];
SAMPLE_CONFIG.initialAmounts = [ethers.parseEther("100"), ethers.parseEther("100"), ethers.parseEther("100")];
dle = await DLE.deploy(SAMPLE_CONFIG, 1, owner.address); // currentChainId=1
await dle.waitForDeployment();
// Используем EOAs как адреса модулей (ядро не проверяет код при инициализации)
treasuryAddr = addrs[0].address;
timelockAddr = addrs[1].address;
readerAddr = addrs[2].address;
// Initialize base modules
await dle.initializeBaseModules(treasuryAddr, timelockAddr, readerAddr);
});
describe("Deployment and Initialization", function () {
it("Should set correct initial state", async function () {
const info = await dle.dleInfo();
expect(info.name).to.equal(SAMPLE_CONFIG.name);
expect(info.symbol).to.equal(SAMPLE_CONFIG.symbol);
expect(await dle.quorumPercentage()).to.equal(SAMPLE_CONFIG.quorumPercentage);
expect(await dle.currentChainId()).to.equal(1);
expect(await dle.totalSupply()).to.equal(ethers.parseEther("300"));
});
it("Should distribute initial tokens and delegate", async function () {
expect(await dle.balanceOf(partner1.address)).to.equal(ethers.parseEther("100"));
expect(await dle.getVotes(partner1.address)).to.equal(ethers.parseEther("100"));
});
it("Should initialize modules correctly", async function () {
// Modules are already initialized in beforeEach
expect(await dle.modules(ethers.keccak256(ethers.toUtf8Bytes("TREASURY")))).to.equal(treasuryAddr);
expect(await dle.modulesInitialized()).to.be.true;
// Cannot initialize twice
await expect(dle.initializeBaseModules(treasuryAddr, timelockAddr, readerAddr))
.to.be.revertedWithCustomError(dle, "ErrProposalExecuted");
});
it("Should prevent non-initializer from initializing modules", async function () {
// Create a new DLE instance for this test
const newDle = await DLE.deploy(SAMPLE_CONFIG, 1, owner.address);
await newDle.waitForDeployment();
await expect(newDle.connect(partner1).initializeBaseModules(treasuryAddr, timelockAddr, readerAddr))
.to.be.revertedWithCustomError(newDle, "ErrOnlyInitializer");
});
it("Should prevent initialization with zero addresses", async function () {
// Create a new DLE instance for this test
const newDle = await DLE.deploy(SAMPLE_CONFIG, 1, owner.address);
await newDle.waitForDeployment();
await expect(newDle.initializeBaseModules(ethers.ZeroAddress, timelockAddr, readerAddr))
.to.be.revertedWithCustomError(newDle, "ErrZeroAddress");
});
});
describe("Proposals", function () {
it("Should create a proposal", async function () {
const description = "Test Proposal";
const duration = 3600;
const operation = ethers.toUtf8Bytes("test operation");
const governanceChainId = 1;
const targetChains = [1, 137];
const tx = await dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0);
const receipt = await tx.wait();
expect(await dle.proposalCounter()).to.equal(1);
});
it("Should not allow non-holders to create proposals", async function () {
const description = "Test Proposal";
const duration = 3600;
const operation = ethers.toUtf8Bytes("test operation");
const governanceChainId = 1;
const targetChains = [1, 137];
await expect(dle.connect(addrs[0]).createProposal(description, duration, operation, governanceChainId, targetChains, 0))
.to.be.revertedWithCustomError(dle, "ErrNotHolder");
});
it("Should prevent creating proposal with too short duration", async function () {
const description = "Test Proposal";
const duration = 59; // Less than minimum
const operation = ethers.toUtf8Bytes("test operation");
const governanceChainId = 1;
const targetChains = [1, 137];
await expect(dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0))
.to.be.revertedWithCustomError(dle, "ErrTooShort");
});
it("Should prevent creating proposal with too long duration", async function () {
const description = "Test Proposal";
const duration = 30 * 24 * 60 * 60 + 1; // More than maximum
const operation = ethers.toUtf8Bytes("test operation");
const governanceChainId = 1;
const targetChains = [1, 137];
await expect(dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0))
.to.be.revertedWithCustomError(dle, "ErrTooLong");
});
it("Should prevent creating proposal with unsupported governance chain", async function () {
const description = "Test Proposal";
const duration = 3600;
const operation = ethers.toUtf8Bytes("test operation");
const governanceChainId = 999; // Unsupported chain
const targetChains = [1, 137];
await expect(dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0))
.to.be.revertedWithCustomError(dle, "ErrBadChain");
});
it("Should prevent creating proposal with unsupported target chain", async function () {
const description = "Test Proposal";
const duration = 3600;
const operation = ethers.toUtf8Bytes("test operation");
const governanceChainId = 1;
const targetChains = [999]; // Unsupported chain
await expect(dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0))
.to.be.revertedWithCustomError(dle, "ErrBadTarget");
});
});
describe("Voting System", function () {
it("Should allow token holders to vote", async function () {
const description = "Test Proposal";
const duration = 3600;
const operation = ethers.toUtf8Bytes("test operation");
const governanceChainId = 1;
const targetChains = [1, 137];
const tx = await dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0);
const receipt = await tx.wait();
const proposalId = (await dle.proposalCounter()) - 1n;
await dle.connect(partner1).vote(proposalId, true);
const proposal = await dle.proposals(proposalId);
expect(proposal.forVotes).to.equal(ethers.parseEther("100"));
});
it("Should allow voting against proposal", async function () {
const description = "Test Proposal";
const duration = 3600;
const operation = ethers.toUtf8Bytes("test operation");
const governanceChainId = 1;
const targetChains = [1, 137];
const tx = await dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0);
const receipt = await tx.wait();
const proposalId = (await dle.proposalCounter()) - 1n;
await dle.connect(partner1).vote(proposalId, false);
const proposal = await dle.proposals(proposalId);
expect(proposal.againstVotes).to.equal(ethers.parseEther("100"));
});
it("Should prevent voting twice", async function () {
const description = "Test Proposal";
const duration = 3600;
const operation = ethers.toUtf8Bytes("test operation");
const governanceChainId = 1;
const targetChains = [1, 137];
const tx = await dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0);
const receipt = await tx.wait();
const proposalId = (await dle.proposalCounter()) - 1n;
await dle.connect(partner1).vote(proposalId, true);
await expect(dle.connect(partner1).vote(proposalId, true))
.to.be.revertedWithCustomError(dle, "ErrAlreadyVoted");
});
it("Should prevent voting on non-existent proposal", async function () {
await expect(dle.connect(partner1).vote(999, true))
.to.be.revertedWithCustomError(dle, "ErrProposalMissing");
});
it("Should prevent voting after deadline", async function () {
const description = "Test Proposal";
const duration = 3600;
const operation = ethers.toUtf8Bytes("test operation");
const governanceChainId = 1;
const targetChains = [1, 137];
const tx = await dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0);
const receipt = await tx.wait();
const proposalId = (await dle.proposalCounter()) - 1n;
// Fast forward time
await ethers.provider.send("evm_increaseTime", [3601]);
await ethers.provider.send("evm_mine");
await expect(dle.connect(partner1).vote(proposalId, true))
.to.be.revertedWithCustomError(dle, "ErrProposalEnded");
});
it("Should prevent voting in wrong chain", async function () {
// Create DLE in different chain
const DLE2 = await ethers.getContractFactory("DLE");
const cfg2 = { ...SAMPLE_CONFIG, supportedChainIds: [1, 2] };
const dle2 = await DLE2.deploy(cfg2, 2, owner.address);
await dle2.waitForDeployment();
// Initialize modules
await dle2.initializeBaseModules(treasuryAddr, timelockAddr, readerAddr);
const description = "Cross-chain proposal";
const duration = 3600;
const operation = ethers.toUtf8Bytes("test operation");
const governanceChainId = 1;
const targetChains = [2];
await dle2.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0);
await expect(dle2.connect(partner1).vote(0, true))
.to.be.revertedWithCustomError(dle2, "ErrWrongChain");
});
it("Should prevent voting without tokens", async function () {
const description = "Test Proposal";
const duration = 3600;
const operation = ethers.toUtf8Bytes("test operation");
const governanceChainId = 1;
const targetChains = [1, 137];
const tx = await dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0);
const receipt = await tx.wait();
const proposalId = (await dle.proposalCounter()) - 1n;
await expect(dle.connect(addrs[0]).vote(proposalId, true))
.to.be.revertedWithCustomError(dle, "ErrNoPower");
});
});
describe("Proposal Result Checking", function () {
it("Should return correct result for passed proposal", async function () {
const description = "Test Proposal";
const duration = 3600;
const operation = ethers.toUtf8Bytes("test operation");
const governanceChainId = 1;
const targetChains = [1, 137];
const tx = await dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0);
const receipt = await tx.wait();
const proposalId = (await dle.proposalCounter()) - 1n;
// Vote with quorum
await dle.connect(partner1).vote(proposalId, true);
await dle.connect(partner2).vote(proposalId, true);
const [passed, quorumReached] = await dle.checkProposalResult(proposalId);
expect(passed).to.be.true;
expect(quorumReached).to.be.true;
});
it("Should return correct result for failed proposal", async function () {
const description = "Test Proposal";
const duration = 3600;
const operation = ethers.toUtf8Bytes("test operation");
const governanceChainId = 1;
const targetChains = [1, 137];
const tx = await dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0);
const receipt = await tx.wait();
const proposalId = (await dle.proposalCounter()) - 1n;
// Vote against
await dle.connect(partner1).vote(proposalId, false);
await dle.connect(partner2).vote(proposalId, false);
const [passed, quorumReached] = await dle.checkProposalResult(proposalId);
expect(passed).to.be.false;
expect(quorumReached).to.be.true;
});
it("Should return correct result for proposal without quorum", async function () {
const description = "Test Proposal";
const duration = 3600;
const operation = ethers.toUtf8Bytes("test operation");
const governanceChainId = 1;
const targetChains = [1, 137];
const tx = await dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0);
const receipt = await tx.wait();
const proposalId = (await dle.proposalCounter()) - 1n;
// Vote with insufficient quorum
await dle.connect(partner1).vote(proposalId, true);
const [passed, quorumReached] = await dle.checkProposalResult(proposalId);
expect(passed).to.be.false;
expect(quorumReached).to.be.false;
});
it("Should handle non-existent proposal", async function () {
await expect(dle.checkProposalResult(999))
.to.be.revertedWithCustomError(dle, "ErrProposalMissing");
});
});
describe("Execution by signatures (EIP-712)", function () {
it("Should execute proposal in non-governance chain with quorum signatures", async function () {
// Deploy DLE with currentChainId=2 and support [1,2]
const DLE2 = await ethers.getContractFactory("DLE");
const cfg2 = {
name: SAMPLE_CONFIG.name,
symbol: SAMPLE_CONFIG.symbol,
location: SAMPLE_CONFIG.location,
coordinates: SAMPLE_CONFIG.coordinates,
jurisdiction: SAMPLE_CONFIG.jurisdiction,
okvedCodes: SAMPLE_CONFIG.okvedCodes,
kpp: SAMPLE_CONFIG.kpp,
quorumPercentage: 51,
initialPartners: [partner1.address, partner2.address, partner3.address],
initialAmounts: [ethers.parseEther("100"), ethers.parseEther("100"), ethers.parseEther("100")],
supportedChainIds: [1, 2]
};
const dle2 = await DLE2.deploy(cfg2, 2, owner.address);
await dle2.waitForDeployment();
// Initialize modules
await dle2.initializeBaseModules(treasuryAddr, timelockAddr, readerAddr);
// Create proposal with governanceChainId=1 and targetChains=[2]
// Use a valid operation - add a module
const moduleId = ethers.keccak256(ethers.toUtf8Bytes("TEST_MODULE"));
const moduleAddress = addrs[5].address;
const tx = await dle2.connect(partner1).createAddModuleProposal("Sig Exec", 3600, moduleId, moduleAddress, 1);
const receipt = await tx.wait();
const proposalId = (await dle2.proposalCounter()) - 1n;
// Get snapshotTimepoint
const p = await dle2.proposals(proposalId);
const snapshotTimepoint = p.snapshotTimepoint;
// Create EIP-712 signatures for three partners
const domain = {
name: cfg2.name,
version: "1",
chainId: (await ethers.provider.getNetwork()).chainId,
verifyingContract: await dle2.getAddress()
};
const types = {
ExecutionApproval: [
{ name: "proposalId", type: "uint256" },
{ name: "operationHash", type: "bytes32" },
{ name: "chainId", type: "uint256" },
{ name: "snapshotTimepoint", type: "uint256" }
]
};
const value = {
proposalId: proposalId,
operationHash: ethers.keccak256(p.operation),
chainId: 2,
snapshotTimepoint
};
const sig1 = await partner1.signTypedData(domain, types, value);
const sig2 = await partner2.signTypedData(domain, types, value);
const sig3 = await partner3.signTypedData(domain, types, value);
// Execute by signatures (quorum 51%, total 300 -> need 153, we have 300)
await expect(dle2.executeProposalBySignatures(proposalId, [partner1.address, partner2.address, partner3.address], [sig1, sig2, sig3]))
.to.emit(dle2, "ProposalExecuted");
});
});
describe("Token Transfer Blocking", function () {
it("Should revert direct transfers", async function () {
await expect(dle.connect(partner1).transfer(partner2.address, ethers.parseEther("10")))
.to.be.revertedWithCustomError(dle, "ErrTransfersDisabled");
});
it("Should revert approvals", async function () {
await expect(dle.connect(partner1).approve(partner2.address, ethers.parseEther("10")))
.to.be.revertedWithCustomError(dle, "ErrApprovalsDisabled");
});
it("Should revert transferFrom", async function () {
await expect(dle.connect(partner2).transferFrom(partner1.address, partner3.address, ethers.parseEther("10")))
.to.be.revertedWithCustomError(dle, "ErrTransfersDisabled");
});
});
describe("Chain Management", function () {
it("Should return correct chain information", async function () {
expect(await dle.getSupportedChainCount()).to.equal(2);
expect(await dle.getSupportedChainId(0)).to.equal(1);
expect(await dle.getSupportedChainId(1)).to.equal(137);
expect(await dle.getCurrentChainId()).to.equal(1);
});
});
describe("View Functions", function () {
it("Should return correct DLE info", async function () {
const info = await dle.dleInfo();
expect(info.name).to.equal(SAMPLE_CONFIG.name);
expect(info.symbol).to.equal(SAMPLE_CONFIG.symbol);
expect(info.location).to.equal(SAMPLE_CONFIG.location);
expect(info.coordinates).to.equal(SAMPLE_CONFIG.coordinates);
expect(info.jurisdiction).to.equal(SAMPLE_CONFIG.jurisdiction);
expect(info.kpp).to.equal(SAMPLE_CONFIG.kpp);
expect(info.isActive).to.be.true;
});
it("Should return correct voting power", async function () {
expect(await dle.getVotes(partner1.address)).to.equal(ethers.parseEther("100"));
});
it("Should return correct nonces", async function () {
expect(await dle.nonces(partner1.address)).to.equal(0);
});
});
describe("Delegation", function () {
it("Should allow self-delegation", async function () {
await dle.connect(partner1).delegate(partner1.address);
expect(await dle.getVotes(partner1.address)).to.equal(ethers.parseEther("100"));
});
it("Should prevent delegation to others", async function () {
await expect(dle.connect(partner1).delegate(partner2.address))
.to.be.revertedWith("Delegation disabled");
});
});
describe("Error Handling", function () {
it("Should handle invalid operations", async function () {
await expect(dle.connect(partner1).transfer(partner2.address, ethers.parseEther("10")))
.to.be.revertedWithCustomError(dle, "ErrTransfersDisabled");
});
it("Should handle unsupported operations", async function () {
await expect(dle.connect(partner1).approve(partner2.address, ethers.parseEther("10")))
.to.be.revertedWithCustomError(dle, "ErrApprovalsDisabled");
});
});
describe("Proposal Cancellation", function () {
it("Should allow canceling proposal", async function () {
const description = "Test Proposal";
const duration = 3600;
const operation = ethers.toUtf8Bytes("test operation");
const governanceChainId = 1;
const targetChains = [1, 137];
const tx = await dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0);
const receipt = await tx.wait();
const proposalId = (await dle.proposalCounter()) - 1n;
await dle.connect(partner1).cancelProposal(proposalId, "Test cancellation");
const proposal = await dle.proposals(proposalId);
expect(proposal.canceled).to.be.true;
});
it("Should prevent voting on canceled proposal", async function () {
const description = "Test Proposal";
const duration = 3600;
const operation = ethers.toUtf8Bytes("test operation");
const governanceChainId = 1;
const targetChains = [1, 137];
const tx = await dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0);
const receipt = await tx.wait();
const proposalId = (await dle.proposalCounter()) - 1n;
await dle.connect(partner1).cancelProposal(proposalId, "Test cancellation");
await expect(dle.connect(partner1).vote(proposalId, true))
.to.be.revertedWithCustomError(dle, "ErrProposalCanceled");
});
it("Should prevent executing canceled proposal", async function () {
const description = "Test Proposal";
const duration = 3600;
const operation = ethers.toUtf8Bytes("test operation");
const governanceChainId = 1;
const targetChains = [1, 137];
const tx = await dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0);
const receipt = await tx.wait();
const proposalId = (await dle.proposalCounter()) - 1n;
await dle.connect(partner1).cancelProposal(proposalId, "Test cancellation");
await expect(dle.executeProposal(proposalId))
.to.be.revertedWithCustomError(dle, "ErrProposalCanceled");
});
});
describe("Logo URI Management", function () {
it("Should allow initializer to set logo URI", async function () {
const logoURI = "https://example.com/logo.png";
await dle.initializeLogoURI(logoURI);
expect(await dle.logoURI()).to.equal(logoURI);
});
it("Should prevent setting logo URI twice", async function () {
const logoURI = "https://example.com/logo.png";
await dle.initializeLogoURI(logoURI);
await expect(dle.initializeLogoURI(logoURI))
.to.be.revertedWithCustomError(dle, "ErrLogoAlreadySet");
});
it("Should prevent non-initializer from setting logo", async function () {
const logoURI = "https://example.com/logo.png";
await expect(dle.connect(partner1).initializeLogoURI(logoURI))
.to.be.revertedWithCustomError(dle, "ErrOnlyInitializer");
});
});
describe("Module Management", function () {
it("Should add module through governance", async function () {
const moduleId = ethers.keccak256(ethers.toUtf8Bytes("TEST_MODULE"));
const moduleAddress = addrs[5].address;
// Create proposal to add module
const description = "Add test module";
const duration = 3600;
const chainId = 1;
const tx = await dle.connect(partner1).createAddModuleProposal(description, duration, moduleId, moduleAddress, chainId);
const receipt = await tx.wait();
const proposalId = (await dle.proposalCounter()) - 1n;
// Vote with quorum
await dle.connect(partner1).vote(proposalId, true);
await dle.connect(partner2).vote(proposalId, true);
// Check proposal result
const [passed, quorumReached] = await dle.checkProposalResult(proposalId);
expect(passed).to.be.true;
expect(quorumReached).to.be.true;
// Execute proposal
await dle.executeProposal(proposalId);
// Verify module was added
expect(await dle.activeModules(moduleId)).to.be.true;
expect(await dle.modules(moduleId)).to.equal(moduleAddress);
});
it("Should remove module through governance", async function () {
const moduleId = ethers.keccak256(ethers.toUtf8Bytes("TEST_MODULE_FOR_REMOVAL"));
const moduleAddress = addrs[6].address;
// First add the module
const addDescription = "Add test module for removal";
const duration = 3600;
const chainId = 1;
const addTx = await dle.connect(partner1).createAddModuleProposal(addDescription, duration, moduleId, moduleAddress, chainId);
const addReceipt = await addTx.wait();
const addProposalId = (await dle.proposalCounter()) - 1n;
// Vote and execute add proposal
await dle.connect(partner1).vote(addProposalId, true);
await dle.connect(partner2).vote(addProposalId, true);
// Check proposal result
const [addPassed, addQuorumReached] = await dle.checkProposalResult(addProposalId);
expect(addPassed).to.be.true;
expect(addQuorumReached).to.be.true;
await dle.executeProposal(addProposalId);
// Now create proposal to remove module
const removeDescription = "Remove test module";
const removeTx = await dle.connect(partner1).createRemoveModuleProposal(removeDescription, duration, moduleId, chainId);
const removeReceipt = await removeTx.wait();
const removeProposalId = (await dle.proposalCounter()) - 1n;
// Vote with quorum
await dle.connect(partner1).vote(removeProposalId, true);
await dle.connect(partner2).vote(removeProposalId, true);
// Check proposal result
const [removePassed, removeQuorumReached] = await dle.checkProposalResult(removeProposalId);
expect(removePassed).to.be.true;
expect(removeQuorumReached).to.be.true;
// Execute proposal
await dle.executeProposal(removeProposalId);
// Verify module was removed
expect(await dle.activeModules(moduleId)).to.be.false;
expect(await dle.modules(moduleId)).to.equal(ethers.ZeroAddress);
});
});
describe("Chain Management", function () {
it("Should add supported chain through governance", async function () {
const newChainId = 56; // BSC
// Create proposal to add chain using internal operation
const description = "Add BSC chain";
const duration = 3600;
const dleInterface = new ethers.Interface([
"function _addSupportedChain(uint256 chainId)"
]);
const operation = dleInterface.encodeFunctionData("_addSupportedChain", [newChainId]);
const governanceChainId = 1;
const targetChains = [1, 137];
const tx = await dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0);
const receipt = await tx.wait();
const proposalId = (await dle.proposalCounter()) - 1n;
// Vote with quorum
await dle.connect(partner1).vote(proposalId, true);
await dle.connect(partner2).vote(proposalId, true);
// Check proposal result
const [passed, quorumReached] = await dle.checkProposalResult(proposalId);
expect(passed).to.be.true;
expect(quorumReached).to.be.true;
// Execute proposal
await dle.executeProposal(proposalId);
// Verify chain was added
expect(await dle.supportedChains(newChainId)).to.be.true;
});
it("Should remove supported chain through governance", async function () {
const chainToRemove = 137; // Polygon
// Create proposal to remove chain using internal operation
const description = "Remove Polygon chain";
const duration = 3600;
const dleInterface = new ethers.Interface([
"function _removeSupportedChain(uint256 chainId)"
]);
const operation = dleInterface.encodeFunctionData("_removeSupportedChain", [chainToRemove]);
const governanceChainId = 1;
const targetChains = [1, 137];
const tx = await dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0);
const receipt = await tx.wait();
const proposalId = (await dle.proposalCounter()) - 1n;
// Vote with quorum
await dle.connect(partner1).vote(proposalId, true);
await dle.connect(partner2).vote(proposalId, true);
// Check proposal result
const [passed, quorumReached] = await dle.checkProposalResult(proposalId);
expect(passed).to.be.true;
expect(quorumReached).to.be.true;
// Execute proposal
await dle.executeProposal(proposalId);
// Verify chain was removed
expect(await dle.supportedChains(chainToRemove)).to.be.false;
});
it("Should prevent removing current chain", async function () {
const currentChainId = 1;
// Create proposal to remove current chain using internal operation
const description = "Remove current chain";
const duration = 3600;
const dleInterface = new ethers.Interface([
"function _removeSupportedChain(uint256 chainId)"
]);
const operation = dleInterface.encodeFunctionData("_removeSupportedChain", [currentChainId]);
const governanceChainId = 1;
const targetChains = [1, 137];
const tx = await dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0);
const receipt = await tx.wait();
const proposalId = (await dle.proposalCounter()) - 1n;
// Vote with quorum
await dle.connect(partner1).vote(proposalId, true);
await dle.connect(partner2).vote(proposalId, true);
// Check proposal result
const [passed, quorumReached] = await dle.checkProposalResult(proposalId);
expect(passed).to.be.true;
expect(quorumReached).to.be.true;
// Execute proposal should fail
await expect(dle.executeProposal(proposalId))
.to.be.revertedWith("Cannot remove current chain");
});
});
describe("DLE Information Management", function () {
it("Should update quorum percentage through governance", async function () {
const newQuorum = 60;
// Create proposal to update quorum using internal operation
const description = "Update quorum percentage";
const duration = 3600;
const dleInterface = new ethers.Interface([
"function _updateQuorumPercentage(uint256 newQuorumPercentage)"
]);
const operation = dleInterface.encodeFunctionData("_updateQuorumPercentage", [newQuorum]);
const governanceChainId = 1;
const targetChains = [1, 137];
const tx = await dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0);
const receipt = await tx.wait();
const proposalId = (await dle.proposalCounter()) - 1n;
// Vote with quorum
await dle.connect(partner1).vote(proposalId, true);
await dle.connect(partner2).vote(proposalId, true);
// Check proposal result
const [passed, quorumReached] = await dle.checkProposalResult(proposalId);
expect(passed).to.be.true;
expect(quorumReached).to.be.true;
// Execute proposal
await dle.executeProposal(proposalId);
// Verify quorum was updated
expect(await dle.quorumPercentage()).to.equal(newQuorum);
});
it("Should update voting durations through governance", async function () {
const newMinDuration = 7200; // 2 hours
const newMaxDuration = 14 * 24 * 60 * 60; // 14 days
// Create proposal to update durations using internal operation
const description = "Update voting durations";
const duration = 3600;
const dleInterface = new ethers.Interface([
"function _updateVotingDurations(uint256 minDuration, uint256 maxDuration)"
]);
const operation = dleInterface.encodeFunctionData("_updateVotingDurations", [newMinDuration, newMaxDuration]);
const governanceChainId = 1;
const targetChains = [1, 137];
const tx = await dle.connect(partner1).createProposal(description, duration, operation, governanceChainId, targetChains, 0);
const receipt = await tx.wait();
const proposalId = (await dle.proposalCounter()) - 1n;
// Vote with quorum
await dle.connect(partner1).vote(proposalId, true);
await dle.connect(partner2).vote(proposalId, true);
// Check proposal result
const [passed, quorumReached] = await dle.checkProposalResult(proposalId);
expect(passed).to.be.true;
expect(quorumReached).to.be.true;
// Execute proposal
await dle.executeProposal(proposalId);
// Verify durations were updated
expect(await dle.minVotingDuration()).to.equal(newMinDuration);
expect(await dle.maxVotingDuration()).to.equal(newMaxDuration);
});
});
describe("Token Management", function () {
it("Should prevent insufficient balance transfer", async function () {
await expect(dle.connect(partner1).transfer(partner2.address, ethers.parseEther("1000")))
.to.be.revertedWithCustomError(dle, "ErrTransfersDisabled");
});
});
describe("Proposal State Management", function () {
it("Should handle non-existent proposal state", async function () {
await expect(dle.getProposalState(999))
.to.be.revertedWith("Proposal does not exist");
});
it("Should return DLE info", async function () {
const info = await dle.dleInfo();
expect(info.name).to.equal(SAMPLE_CONFIG.name);
expect(info.symbol).to.equal(SAMPLE_CONFIG.symbol);
expect(info.location).to.equal(SAMPLE_CONFIG.location);
expect(info.coordinates).to.equal(SAMPLE_CONFIG.coordinates);
expect(info.jurisdiction).to.equal(SAMPLE_CONFIG.jurisdiction);
expect(info.kpp).to.equal(SAMPLE_CONFIG.kpp);
expect(info.isActive).to.be.true;
});
it("Should check if module is active", async function () {
const moduleId = ethers.keccak256(ethers.toUtf8Bytes("NON_EXISTENT"));
expect(await dle.activeModules(moduleId)).to.be.false;
});
it("Should return module address", async function () {
const moduleId = ethers.keccak256(ethers.toUtf8Bytes("NON_EXISTENT"));
expect(await dle.modules(moduleId)).to.equal(ethers.ZeroAddress);
});
it("Should check if chain is supported", async function () {
expect(await dle.supportedChains(1)).to.be.true;
expect(await dle.supportedChains(137)).to.be.true;
expect(await dle.supportedChains(999)).to.be.false;
});
it("Should return current chain ID", async function () {
expect(await dle.currentChainId()).to.equal(1);
});
it("Should check if DLE is active", async function () {
const info = await dle.dleInfo();
expect(info.isActive).to.be.true;
});
it("Should prevent adding zero address module", async function () {
const moduleId = ethers.keccak256(ethers.toUtf8Bytes("TEST_MODULE"));
// Create proposal to add zero address module
const description = "Add Zero Address Module";
const duration = 3600;
const chainId = 1;
await expect(dle.connect(partner1).createAddModuleProposal(description, duration, moduleId, ethers.ZeroAddress, chainId))
.to.be.revertedWithCustomError(dle, "ErrZeroAddress");
});
it("Should prevent adding duplicate module", async function () {
// Test that we can't add modules that already exist
const treasuryId = ethers.keccak256(ethers.toUtf8Bytes("TREASURY"));
const newTreasuryAddress = addrs[6].address;
// Try to add TREASURY module again (it already exists)
await expect(dle.connect(partner1).createAddModuleProposal("Add Duplicate Treasury", 3600, treasuryId, newTreasuryAddress, 1))
.to.be.revertedWithCustomError(dle, "ErrProposalExecuted");
});
it("Should prevent removing non-existent module", async function () {
const moduleId = ethers.keccak256(ethers.toUtf8Bytes("NONEXISTENT_MODULE"));
// Create a proposal to remove non-existent module should revert immediately
await expect(dle.connect(partner1).createRemoveModuleProposal(
"Remove non-existent module",
2 * 24 * 60 * 60, // 2 days
moduleId,
1 // chainId
)).to.be.revertedWithCustomError(dle, "ErrProposalMissing");
});
});
describe("Additional Coverage Tests", function () {
it("Should test getProposalState for all states", async function () {
// Создаем предложение
const description = "Test proposal for state coverage";
const duration = 3600;
const dleInterface = new ethers.Interface([
"function _updateQuorumPercentage(uint256 newQuorumPercentage)"
]);
const operation = dleInterface.encodeFunctionData("_updateQuorumPercentage", [60]);
await dle.connect(partner1).createProposal(description, duration, operation, 1, [1], 0);
const proposalId = 0;
// State 0: Pending (до голосования)
expect(await dle.getProposalState(proposalId)).to.equal(0);
// State 5: ReadyForExecution (прошло голосование, достигнут кворум)
await dle.connect(partner1).vote(proposalId, true);
await dle.connect(partner2).vote(proposalId, true);
await dle.connect(partner3).vote(proposalId, true);
expect(await dle.getProposalState(proposalId)).to.equal(5);
// State 3: Executed (исполнено)
await ethers.provider.send("evm_increaseTime", [3601]);
await ethers.provider.send("evm_mine");
await dle.executeProposal(proposalId);
expect(await dle.getProposalState(proposalId)).to.equal(3);
});
it("Should test getProposalState for defeated proposal", async function () {
// Создаем предложение
const description = "Test proposal for defeat";
const duration = 3600;
const dleInterface = new ethers.Interface([
"function _updateQuorumPercentage(uint256 newQuorumPercentage)"
]);
const operation = dleInterface.encodeFunctionData("_updateQuorumPercentage", [60]);
await dle.connect(partner1).createProposal(description, duration, operation, 1, [1], 0);
const proposalId = 0;
// Голосуем против
await dle.connect(partner1).vote(proposalId, false);
await dle.connect(partner2).vote(proposalId, false);
await dle.connect(partner3).vote(proposalId, false);
// State 2: Defeated (прошло время голосования и не прошло)
await ethers.provider.send("evm_increaseTime", [3601]);
await ethers.provider.send("evm_mine");
expect(await dle.getProposalState(proposalId)).to.equal(2);
});
it("Should test getProposalState for canceled proposal", async function () {
// Создаем предложение
const description = "Test proposal for cancellation";
const duration = 3600;
const dleInterface = new ethers.Interface([
"function _updateQuorumPercentage(uint256 newQuorumPercentage)"
]);
const operation = dleInterface.encodeFunctionData("_updateQuorumPercentage", [60]);
await dle.connect(partner1).createProposal(description, duration, operation, 1, [1], 0);
const proposalId = 0;
// State 4: Canceled
await dle.connect(partner1).cancelProposal(proposalId, "Test cancellation");
expect(await dle.getProposalState(proposalId)).to.equal(4);
});
it("Should test getProposalState for ready for execution", async function () {
// Создаем предложение
const description = "Test proposal for ready state";
const duration = 3600;
const dleInterface = new ethers.Interface([
"function _updateQuorumPercentage(uint256 newQuorumPercentage)"
]);
const operation = dleInterface.encodeFunctionData("_updateQuorumPercentage", [60]);
await dle.connect(partner1).createProposal(description, duration, operation, 1, [1], 0);
const proposalId = 0;
// Голосуем за
await dle.connect(partner1).vote(proposalId, true);
await dle.connect(partner2).vote(proposalId, true);
await dle.connect(partner3).vote(proposalId, true);
// State 5: ReadyForExecution (прошло голосование, достигнут кворум, но еще не исполнено)
expect(await dle.getProposalState(proposalId)).to.equal(5);
});
it("Should test _isTargetChain function", async function () {
// Создаем предложение с несколькими целевыми цепочками
const description = "Test proposal with multiple target chains";
const duration = 3600;
const dleInterface = new ethers.Interface([
"function _updateQuorumPercentage(uint256 newQuorumPercentage)"
]);
const operation = dleInterface.encodeFunctionData("_updateQuorumPercentage", [60]);
await dle.connect(partner1).createProposal(description, duration, operation, 1, [1, 137], 0);
const proposalId = 0;
// Проверяем что предложение создано с правильными целевыми цепочками
// Используем getProposalState для проверки что предложение создано корректно
expect(await dle.getProposalState(proposalId)).to.equal(0);
// Проверяем что предложение существует и можно голосовать
await dle.connect(partner1).vote(proposalId, true);
expect(await dle.getProposalState(proposalId)).to.equal(0); // все еще pending
});
it("Should test _update function override", async function () {
// Тестируем делегирование (которое вызывает _update)
await dle.connect(partner1).delegate(partner1.address);
expect(await dle.delegates(partner1.address)).to.equal(partner1.address);
});
it("Should test nonces function override", async function () {
// Тестируем nonces
const nonce = await dle.nonces(partner1.address);
expect(nonce).to.be.a("bigint");
});
it("Should test _delegate function override", async function () {
// Тестируем делегирование самому себе (разрешено)
await dle.connect(partner1).delegate(partner1.address);
expect(await dle.delegates(partner1.address)).to.equal(partner1.address);
// Тестируем делегирование другому (запрещено)
await expect(dle.connect(partner1).delegate(partner2.address))
.to.be.revertedWith("Delegation disabled");
});
it("Should test isActive function", async function () {
// По умолчанию DLE активен
expect(await dle.isActive()).to.be.true;
});
it("Should test getModuleAddress for non-existent module", async function () {
// Тестируем getModuleAddress для несуществующего модуля
const nonExistentModuleId = ethers.id("NON_EXISTENT");
const address = await dle.getModuleAddress(nonExistentModuleId);
expect(address).to.equal(ethers.ZeroAddress);
});
it("Should test isChainSupported for non-supported chain", async function () {
// Тестируем isChainSupported для неподдерживаемой цепочки
expect(await dle.isChainSupported(999)).to.be.false;
});
it("Should test getCurrentChainId", async function () {
// Тестируем getCurrentChainId
const currentChainId = await dle.getCurrentChainId();
expect(currentChainId).to.equal(1); // По умолчанию 1
});
it("Should test getDLEInfo", async function () {
// Тестируем getDLEInfo
const dleInfo = await dle.getDLEInfo();
expect(dleInfo.name).to.equal("Test DLE");
expect(dleInfo.symbol).to.equal("TDLE");
expect(dleInfo.location).to.equal("Test Location");
expect(dleInfo.coordinates).to.equal("0,0");
expect(dleInfo.jurisdiction).to.equal(1);
expect(dleInfo.okvedCodes).to.deep.equal(["62.01"]);
expect(dleInfo.kpp).to.equal(123456789); // Исправляем ожидаемое значение
expect(dleInfo.isActive).to.be.true;
});
it("Should test isModuleActive for existing module", async function () {
// Тестируем isModuleActive для существующего модуля
const treasuryId = ethers.id("TREASURY");
expect(await dle.isModuleActive(treasuryId)).to.be.true;
});
it("Should test isModuleActive for non-existent module", async function () {
// Тестируем isModuleActive для несуществующего модуля
const nonExistentModuleId = ethers.id("NON_EXISTENT");
expect(await dle.isModuleActive(nonExistentModuleId)).to.be.false;
});
it("Should test getProposalState for non-existent proposal", async function () {
// Тестируем getProposalState для несуществующего предложения
await expect(dle.getProposalState(999))
.to.be.revertedWith("Proposal does not exist");
});
it("Should test getProposalState for defeated after deadline", async function () {
// Создаем предложение
const description = "Test proposal for defeated after deadline";
const duration = 3600;
const dleInterface = new ethers.Interface([
"function _updateQuorumPercentage(uint256 newQuorumPercentage)"
]);
const operation = dleInterface.encodeFunctionData("_updateQuorumPercentage", [60]);
await dle.connect(partner1).createProposal(description, duration, operation, 1, [1], 0);
const proposalId = 0;
// Голосуем против
await dle.connect(partner1).vote(proposalId, false);
await dle.connect(partner2).vote(proposalId, false);
// partner3 не голосует
// Проходит время голосования
await ethers.provider.send("evm_increaseTime", [3601]);
await ethers.provider.send("evm_mine");
// State 2: Defeated (прошло время голосования и не прошло)
expect(await dle.getProposalState(proposalId)).to.equal(2);
});
it("Should test getProposalState for pending during voting", async function () {
// Создаем предложение
const description = "Test proposal for pending during voting";
const duration = 3600;
const dleInterface = new ethers.Interface([
"function _updateQuorumPercentage(uint256 newQuorumPercentage)"
]);
const operation = dleInterface.encodeFunctionData("_updateQuorumPercentage", [60]);
await dle.connect(partner1).createProposal(description, duration, operation, 1, [1], 0);
const proposalId = 0;
// Голосуем только один раз
await dle.connect(partner1).vote(proposalId, true);
// State 0: Pending (голосование еще идет, не достигнут кворум)
expect(await dle.getProposalState(proposalId)).to.equal(0);
});
it("Should test getProposalState for pending with mixed votes", async function () {
// Create a proposal using partner1 who has tokens
const tx = await dle.connect(partner1).createProposal(
"Test proposal with mixed votes",
3600,
"0x12345678",
1,
[1], // Only supported chain
partner1.address
);
const receipt = await tx.wait();
const event = receipt.logs.find(log => log.fragment && log.fragment.name === "ProposalCreated");
const proposalId = event.args.proposalId;
// Vote for the proposal
await dle.connect(partner1).vote(proposalId, true);
// Vote against the proposal
await dle.connect(partner2).vote(proposalId, false);
// Check proposal state (should be pending)
const state = await dle.getProposalState(proposalId);
expect(state).to.equal(0); // Pending
});
it("Should test blocked transfer function", async function () {
// Try to transfer tokens (should be blocked)
await expect(
dle.connect(addrs[0]).transfer(addrs[1].address, ethers.parseEther("100"))
).to.be.revertedWithCustomError(dle, "ErrTransfersDisabled");
});
it("Should test blocked transferFrom function", async function () {
// Try to transfer tokens via transferFrom (should be blocked)
await expect(
dle.connect(addrs[0]).transferFrom(addrs[0].address, addrs[1].address, ethers.parseEther("100"))
).to.be.revertedWithCustomError(dle, "ErrTransfersDisabled");
});
it("Should test blocked approve function", async function () {
// Try to approve tokens (should be blocked)
await expect(
dle.connect(addrs[0]).approve(addrs[1].address, ethers.parseEther("100"))
).to.be.revertedWithCustomError(dle, "ErrApprovalsDisabled");
});
it("Should test token transfer through governance", async function () {
// Create a proposal to transfer tokens using a simple operation
const dleInterface = new ethers.Interface([
"function _updateQuorumPercentage(uint256 newQuorumPercentage)"
]);
const operation = dleInterface.encodeFunctionData("_updateQuorumPercentage", [60]);
const tx = await dle.connect(partner1).createProposal(
"Update quorum percentage",
3600,
operation,
1,
[1],
partner1.address
);
const receipt = await tx.wait();
const event = receipt.logs.find(log => log.fragment && log.fragment.name === "ProposalCreated");
const proposalId = event.args.proposalId;
// Vote for the proposal
await dle.connect(partner1).vote(proposalId, true);
// Vote for the proposal with partner2 to reach quorum
await dle.connect(partner2).vote(proposalId, true);
// Advance time to pass deadline
await ethers.provider.send("evm_increaseTime", [3601]);
await ethers.provider.send("evm_mine");
// Execute the proposal
await dle.connect(partner1).executeProposal(proposalId);
// Check that quorum was updated
const quorumPercentage = await dle.quorumPercentage();
expect(quorumPercentage).to.equal(60);
});
});
});