ваше сообщение коммита
This commit is contained in:
482
backend/test/DLE.test.js
Normal file
482
backend/test/DLE.test.js
Normal file
@@ -0,0 +1,482 @@
|
||||
/**
|
||||
* 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;
|
||||
let partner2;
|
||||
let partner3;
|
||||
let addrs;
|
||||
|
||||
beforeEach(async function () {
|
||||
// Получаем аккаунты
|
||||
[owner, partner1, partner2, partner3, ...addrs] = await ethers.getSigners();
|
||||
|
||||
// Деплоим контракт
|
||||
const DLEFactory = await ethers.getContractFactory("DLE");
|
||||
|
||||
const config = {
|
||||
name: "Digital Legal Entity",
|
||||
symbol: "DLE",
|
||||
location: "Moscow, Russia",
|
||||
coordinates: "55.7558,37.6176",
|
||||
jurisdiction: 1, // Россия
|
||||
oktmo: 45000000000,
|
||||
okvedCodes: ["62.01", "62.02", "62.03"],
|
||||
kpp: 770101001,
|
||||
quorumPercentage: 60, // 60%
|
||||
initialPartners: [partner1.address, partner2.address, partner3.address],
|
||||
initialAmounts: [ethers.parseEther("1000"), ethers.parseEther("1000"), ethers.parseEther("1000")],
|
||||
supportedChainIds: [1, 137, 56, 42161] // Ethereum, Polygon, BSC, Arbitrum
|
||||
};
|
||||
|
||||
dle = await DLEFactory.deploy(config, 1); // ChainId = 1 (Ethereum)
|
||||
await dle.waitForDeployment();
|
||||
});
|
||||
|
||||
describe("Деплой и инициализация", function () {
|
||||
it("Должен правильно инициализировать DLE", async function () {
|
||||
const dleInfo = await dle.getDLEInfo();
|
||||
|
||||
expect(dleInfo.name).to.equal("Digital Legal Entity");
|
||||
expect(dleInfo.symbol).to.equal("DLE");
|
||||
expect(dleInfo.location).to.equal("Moscow, Russia");
|
||||
expect(dleInfo.jurisdiction).to.equal(1);
|
||||
expect(dleInfo.isActive).to.be.true;
|
||||
});
|
||||
|
||||
it("Должен распределить начальные токены", async function () {
|
||||
expect(await dle.balanceOf(partner1.address)).to.equal(ethers.parseEther("1000"));
|
||||
expect(await dle.balanceOf(partner2.address)).to.equal(ethers.parseEther("1000"));
|
||||
expect(await dle.balanceOf(partner3.address)).to.equal(ethers.parseEther("1000"));
|
||||
});
|
||||
|
||||
it("Должен установить кворум", async function () {
|
||||
expect(await dle.quorumPercentage()).to.equal(60);
|
||||
});
|
||||
|
||||
it("Должен настроить поддерживаемые цепочки", async function () {
|
||||
expect(await dle.isChainSupported(1)).to.be.true; // Ethereum
|
||||
expect(await dle.isChainSupported(137)).to.be.true; // Polygon
|
||||
expect(await dle.isChainSupported(56)).to.be.true; // BSC
|
||||
expect(await dle.isChainSupported(42161)).to.be.true; // Arbitrum
|
||||
expect(await dle.isChainSupported(999)).to.be.false; // Неподдерживаемая цепочка
|
||||
});
|
||||
});
|
||||
|
||||
describe("Система голосования", function () {
|
||||
it("Должен создать предложение", async function () {
|
||||
const description = "Передать 100 токенов от Partner1 к Partner2";
|
||||
const duration = 7 * 24 * 60 * 60; // 7 дней
|
||||
const operation = ethers.AbiCoder.defaultAbiCoder().encode(
|
||||
["bytes4", "bytes"],
|
||||
[
|
||||
"0xa9059cbb", // transfer(address,uint256) selector
|
||||
ethers.AbiCoder.defaultAbiCoder().encode(
|
||||
["address", "uint256"],
|
||||
[partner2.address, ethers.parseEther("100")]
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
const tx = await dle.connect(partner1).createProposal(
|
||||
description,
|
||||
duration,
|
||||
operation,
|
||||
1 // governanceChainId = Ethereum
|
||||
);
|
||||
|
||||
const receipt = await tx.wait();
|
||||
const event = receipt.logs.find(log =>
|
||||
log.fragment && log.fragment.name === "ProposalCreated"
|
||||
);
|
||||
|
||||
expect(event).to.not.be.undefined;
|
||||
expect(await dle.proposalCounter()).to.equal(1);
|
||||
});
|
||||
|
||||
it("Должен голосовать за предложение", async function () {
|
||||
// Создаем предложение
|
||||
const description = "Тестовое предложение";
|
||||
const duration = 7 * 24 * 60 * 60;
|
||||
const operation = "0x";
|
||||
|
||||
await dle.connect(partner1).createProposal(
|
||||
description,
|
||||
duration,
|
||||
operation,
|
||||
1
|
||||
);
|
||||
|
||||
// Голосуем за предложение
|
||||
await dle.connect(partner1).vote(0, true);
|
||||
await dle.connect(partner2).vote(0, true);
|
||||
|
||||
const proposal = await dle.proposals(0);
|
||||
expect(proposal.forVotes).to.equal(ethers.parseEther("2000")); // 1000 + 1000
|
||||
expect(proposal.againstVotes).to.equal(0);
|
||||
});
|
||||
|
||||
it("Должен проверить результат голосования", async function () {
|
||||
// Создаем предложение
|
||||
const description = "Тестовое предложение";
|
||||
const duration = 7 * 24 * 60 * 60;
|
||||
const operation = "0x";
|
||||
|
||||
await dle.connect(partner1).createProposal(
|
||||
description,
|
||||
duration,
|
||||
operation,
|
||||
1
|
||||
);
|
||||
|
||||
// Голосуем за предложение (60% от 3000 = 1800)
|
||||
await dle.connect(partner1).vote(0, true); // 1000
|
||||
await dle.connect(partner2).vote(0, true); // 1000
|
||||
await dle.connect(partner3).vote(0, true); // 1000
|
||||
|
||||
const [passed, quorumReached] = await dle.checkProposalResult(0);
|
||||
expect(passed).to.be.true;
|
||||
expect(quorumReached).to.be.true;
|
||||
});
|
||||
|
||||
it("Не должен позволить голосовать дважды", async function () {
|
||||
// Создаем предложение
|
||||
const description = "Тестовое предложение";
|
||||
const duration = 7 * 24 * 60 * 60;
|
||||
const operation = "0x";
|
||||
|
||||
await dle.connect(partner1).createProposal(
|
||||
description,
|
||||
duration,
|
||||
operation,
|
||||
1
|
||||
);
|
||||
|
||||
// Первое голосование
|
||||
await dle.connect(partner1).vote(0, true);
|
||||
|
||||
// Второе голосование должно упасть
|
||||
await expect(
|
||||
dle.connect(partner1).vote(0, true)
|
||||
).to.be.revertedWith("Already voted");
|
||||
});
|
||||
|
||||
it("Не должен позволить голосовать без токенов", async function () {
|
||||
// Создаем предложение
|
||||
const description = "Тестовое предложение";
|
||||
const duration = 7 * 24 * 60 * 60;
|
||||
const operation = "0x";
|
||||
|
||||
await dle.connect(partner1).createProposal(
|
||||
description,
|
||||
duration,
|
||||
operation,
|
||||
1
|
||||
);
|
||||
|
||||
// Голосование без токенов должно упасть
|
||||
await expect(
|
||||
dle.connect(addrs[0]).vote(0, true)
|
||||
).to.be.revertedWith("No tokens to vote");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Мультиподпись", function () {
|
||||
it("Должен создать мультиподпись операцию", async function () {
|
||||
const operationHash = ethers.keccak256(ethers.toUtf8Bytes("test operation"));
|
||||
const duration = 7 * 24 * 60 * 60;
|
||||
|
||||
const tx = await dle.connect(partner1).createMultiSigOperation(
|
||||
operationHash,
|
||||
duration
|
||||
);
|
||||
|
||||
const receipt = await tx.wait();
|
||||
const event = receipt.logs.find(log =>
|
||||
log.fragment && log.fragment.name === "MultiSigOperationCreated"
|
||||
);
|
||||
|
||||
expect(event).to.not.be.undefined;
|
||||
expect(await dle.multiSigCounter()).to.equal(1);
|
||||
});
|
||||
|
||||
it("Должен подписать мультиподпись операцию", async function () {
|
||||
const operationHash = ethers.keccak256(ethers.toUtf8Bytes("test operation"));
|
||||
const duration = 7 * 24 * 60 * 60;
|
||||
|
||||
await dle.connect(partner1).createMultiSigOperation(
|
||||
operationHash,
|
||||
duration
|
||||
);
|
||||
|
||||
// Подписываем операцию
|
||||
await dle.connect(partner1).signMultiSigOperation(0, true);
|
||||
await dle.connect(partner2).signMultiSigOperation(0, true);
|
||||
|
||||
const operation = await dle.multiSigOperations(0);
|
||||
expect(operation.forSignatures).to.equal(ethers.parseEther("2000")); // 1000 + 1000
|
||||
expect(operation.againstSignatures).to.equal(0);
|
||||
});
|
||||
|
||||
it("Должен проверить результат мультиподписи", async function () {
|
||||
const operationHash = ethers.keccak256(ethers.toUtf8Bytes("test operation"));
|
||||
const duration = 7 * 24 * 60 * 60;
|
||||
|
||||
await dle.connect(partner1).createMultiSigOperation(
|
||||
operationHash,
|
||||
duration
|
||||
);
|
||||
|
||||
// Подписываем операцию (60% от 3000 = 1800)
|
||||
await dle.connect(partner1).signMultiSigOperation(0, true); // 1000
|
||||
await dle.connect(partner2).signMultiSigOperation(0, true); // 1000
|
||||
await dle.connect(partner3).signMultiSigOperation(0, true); // 1000
|
||||
|
||||
const [passed, quorumReached] = await dle.checkMultiSigResult(0);
|
||||
expect(passed).to.be.true;
|
||||
expect(quorumReached).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe("Мульти-чейн синхронизация", function () {
|
||||
it("Должен синхронизировать голоса из другой цепочки", async function () {
|
||||
// Создаем предложение
|
||||
const description = "Тестовое предложение";
|
||||
const duration = 7 * 24 * 60 * 60;
|
||||
const operation = "0x";
|
||||
|
||||
await dle.connect(partner1).createProposal(
|
||||
description,
|
||||
duration,
|
||||
operation,
|
||||
1
|
||||
);
|
||||
|
||||
// Синхронизируем голоса из другой цепочки
|
||||
await dle.connect(partner1).syncVoteFromChain(
|
||||
0, // proposalId
|
||||
137, // fromChainId (Polygon)
|
||||
ethers.parseEther("500"), // forVotes
|
||||
ethers.parseEther("200"), // againstVotes
|
||||
"0x" // proof
|
||||
);
|
||||
|
||||
const proposal = await dle.proposals(0);
|
||||
expect(proposal.forVotes).to.equal(ethers.parseEther("500"));
|
||||
expect(proposal.againstVotes).to.equal(ethers.parseEther("200"));
|
||||
});
|
||||
|
||||
it("Должен синхронизировать мультиподпись из другой цепочки", async function () {
|
||||
const operationHash = ethers.keccak256(ethers.toUtf8Bytes("test operation"));
|
||||
const duration = 7 * 24 * 60 * 60;
|
||||
|
||||
await dle.connect(partner1).createMultiSigOperation(
|
||||
operationHash,
|
||||
duration
|
||||
);
|
||||
|
||||
// Синхронизируем мультиподпись из другой цепочки
|
||||
await dle.connect(partner1).syncMultiSigFromChain(
|
||||
0, // operationId
|
||||
137, // fromChainId (Polygon)
|
||||
ethers.parseEther("800"), // forSignatures
|
||||
ethers.parseEther("300"), // againstSignatures
|
||||
"0x" // proof
|
||||
);
|
||||
|
||||
const operation = await dle.multiSigOperations(0);
|
||||
expect(operation.forSignatures).to.equal(ethers.parseEther("800"));
|
||||
expect(operation.againstSignatures).to.equal(ethers.parseEther("300"));
|
||||
});
|
||||
|
||||
it("Должен проверить готовность синхронизации", async function () {
|
||||
// Создаем предложение
|
||||
const description = "Тестовое предложение";
|
||||
const duration = 7 * 24 * 60 * 60;
|
||||
const operation = "0x";
|
||||
|
||||
await dle.connect(partner1).createProposal(
|
||||
description,
|
||||
duration,
|
||||
operation,
|
||||
1
|
||||
);
|
||||
|
||||
// Проверяем готовность синхронизации
|
||||
const isReady = await dle.checkSyncReadiness(0);
|
||||
expect(isReady).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe("Управление модулями", function () {
|
||||
it("Должен добавить модуль", async function () {
|
||||
const moduleId = ethers.keccak256(ethers.toUtf8Bytes("TreasuryModule"));
|
||||
const moduleAddress = addrs[0].address;
|
||||
|
||||
await dle.connect(partner1).addModule(moduleId, moduleAddress);
|
||||
|
||||
expect(await dle.isModuleActive(moduleId)).to.be.true;
|
||||
expect(await dle.getModuleAddress(moduleId)).to.equal(moduleAddress);
|
||||
});
|
||||
|
||||
it("Должен удалить модуль", async function () {
|
||||
const moduleId = ethers.keccak256(ethers.toUtf8Bytes("TreasuryModule"));
|
||||
const moduleAddress = addrs[0].address;
|
||||
|
||||
await dle.connect(partner1).addModule(moduleId, moduleAddress);
|
||||
await dle.connect(partner1).removeModule(moduleId);
|
||||
|
||||
expect(await dle.isModuleActive(moduleId)).to.be.false;
|
||||
});
|
||||
|
||||
it("Не должен позволить добавить модуль без токенов", async function () {
|
||||
const moduleId = ethers.keccak256(ethers.toUtf8Bytes("TreasuryModule"));
|
||||
const moduleAddress = addrs[0].address;
|
||||
|
||||
await expect(
|
||||
dle.connect(addrs[0]).addModule(moduleId, moduleAddress)
|
||||
).to.be.revertedWith("Must hold tokens to add module");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Проверка подключений", function () {
|
||||
it("Должен проверить подключение к цепочке", async function () {
|
||||
expect(await dle.checkChainConnection(1)).to.be.true; // Ethereum
|
||||
expect(await dle.checkChainConnection(137)).to.be.true; // Polygon
|
||||
expect(await dle.checkChainConnection(56)).to.be.true; // BSC
|
||||
expect(await dle.checkChainConnection(42161)).to.be.true; // Arbitrum
|
||||
expect(await dle.checkChainConnection(999)).to.be.false; // Неподдерживаемая цепочка
|
||||
});
|
||||
|
||||
it("Должен получить количество поддерживаемых цепочек", async function () {
|
||||
expect(await dle.getSupportedChainCount()).to.equal(4);
|
||||
});
|
||||
|
||||
it("Должен получить ID поддерживаемой цепочки", async function () {
|
||||
expect(await dle.getSupportedChainId(0)).to.equal(1); // Ethereum
|
||||
expect(await dle.getSupportedChainId(1)).to.equal(137); // Polygon
|
||||
expect(await dle.getSupportedChainId(2)).to.equal(56); // BSC
|
||||
expect(await dle.getSupportedChainId(3)).to.equal(42161); // Arbitrum
|
||||
});
|
||||
});
|
||||
|
||||
describe("Исполнение операций", function () {
|
||||
it("Должен исполнить предложение с передачей токенов", async function () {
|
||||
// Создаем предложение для передачи токенов
|
||||
const description = "Передать 100 токенов от Partner1 к Partner2";
|
||||
const duration = 7 * 24 * 60 * 60;
|
||||
const operation = ethers.AbiCoder.defaultAbiCoder().encode(
|
||||
["bytes4", "bytes"],
|
||||
[
|
||||
"0xa9059cbb", // transfer(address,uint256) selector
|
||||
ethers.AbiCoder.defaultAbiCoder().encode(
|
||||
["address", "uint256"],
|
||||
[partner2.address, ethers.parseEther("100")]
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
await dle.connect(partner1).createProposal(
|
||||
description,
|
||||
duration,
|
||||
operation,
|
||||
1
|
||||
);
|
||||
|
||||
// Голосуем за предложение
|
||||
await dle.connect(partner1).vote(0, true);
|
||||
await dle.connect(partner2).vote(0, true);
|
||||
await dle.connect(partner3).vote(0, true);
|
||||
|
||||
// Ждем окончания голосования
|
||||
await ethers.provider.send("evm_increaseTime", [7 * 24 * 60 * 60]);
|
||||
await ethers.provider.send("evm_mine");
|
||||
|
||||
// Исполняем предложение
|
||||
await dle.connect(partner1).executeProposal(0);
|
||||
|
||||
// Проверяем, что токены переданы
|
||||
expect(await dle.balanceOf(partner1.address)).to.equal(ethers.parseEther("900")); // 1000 - 100
|
||||
expect(await dle.balanceOf(partner2.address)).to.equal(ethers.parseEther("1100")); // 1000 + 100
|
||||
});
|
||||
});
|
||||
|
||||
describe("Безопасность", function () {
|
||||
it("Не должен позволить создать предложение без токенов", async function () {
|
||||
const description = "Тестовое предложение";
|
||||
const duration = 7 * 24 * 60 * 60;
|
||||
const operation = "0x";
|
||||
|
||||
await expect(
|
||||
dle.connect(addrs[0]).createProposal(
|
||||
description,
|
||||
duration,
|
||||
operation,
|
||||
1
|
||||
)
|
||||
).to.be.revertedWith("Must hold tokens to create proposal");
|
||||
});
|
||||
|
||||
it("Не должен позволить голосовать после окончания срока", async function () {
|
||||
// Создаем предложение с коротким сроком
|
||||
const description = "Тестовое предложение";
|
||||
const duration = 1; // 1 секунда
|
||||
const operation = "0x";
|
||||
|
||||
await dle.connect(partner1).createProposal(
|
||||
description,
|
||||
duration,
|
||||
operation,
|
||||
1
|
||||
);
|
||||
|
||||
// Ждем окончания срока
|
||||
await ethers.provider.send("evm_increaseTime", [2]);
|
||||
await ethers.provider.send("evm_mine");
|
||||
|
||||
// Голосование должно упасть
|
||||
await expect(
|
||||
dle.connect(partner1).vote(0, true)
|
||||
).to.be.revertedWith("Voting ended");
|
||||
});
|
||||
|
||||
it("Не должен позволить исполнить предложение до окончания срока", async function () {
|
||||
// Создаем предложение
|
||||
const description = "Тестовое предложение";
|
||||
const duration = 7 * 24 * 60 * 60;
|
||||
const operation = "0x";
|
||||
|
||||
await dle.connect(partner1).createProposal(
|
||||
description,
|
||||
duration,
|
||||
operation,
|
||||
1
|
||||
);
|
||||
|
||||
// Голосуем за предложение
|
||||
await dle.connect(partner1).vote(0, true);
|
||||
await dle.connect(partner2).vote(0, true);
|
||||
await dle.connect(partner3).vote(0, true);
|
||||
|
||||
// Исполнение должно упасть
|
||||
await expect(
|
||||
dle.connect(partner1).executeProposal(0)
|
||||
).to.be.revertedWith("Voting not ended");
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user