diff --git a/contracts/DiamondDao.sol b/contracts/DiamondDao.sol index b60c4d9..15efe37 100644 --- a/contracts/DiamondDao.sol +++ b/contracts/DiamondDao.sol @@ -278,15 +278,14 @@ contract DiamondDao is IDiamondDao, Initializable, ReentrancyGuardUpgradeable, V proposal.description = description; proposal.discussionUrl = discussionUrl; proposal.daoPhaseCount = daoPhaseCount; + proposal.proposalFee = createProposalFee; proposal.proposalType = proposalType; currentPhaseProposals.push(proposalId); statistic.total += 1; unfinalizedProposals += 1; - _transfer(reinsertPot, msg.value); - - emit ProposalCreated(proposer, proposalId, targets, values, calldatas, title, description, discussionUrl); + emit ProposalCreated(proposer, proposalId, targets, values, calldatas, title, description, discussionUrl, createProposalFee); } function cancel(uint256 proposalId, string calldata reason) external exists(proposalId) { @@ -343,8 +342,14 @@ contract DiamondDao is IDiamondDao, Initializable, ReentrancyGuardUpgradeable, V if (accepted) { statistic.accepted += 1; + + // return fee back to the proposer + _transfer(proposal.proposer, proposal.proposalFee); } else { statistic.declined += 1; + + // send fee to the reinsert pot + _transfer(reinsertPot, proposal.proposalFee); } unfinalizedProposals -= 1; diff --git a/contracts/interfaces/IDiamondDao.sol b/contracts/interfaces/IDiamondDao.sol index 4a5b75e..899dad1 100644 --- a/contracts/interfaces/IDiamondDao.sol +++ b/contracts/interfaces/IDiamondDao.sol @@ -12,7 +12,8 @@ interface IDiamondDao { bytes[] calldatas, string title, string description, - string discussionUrl + string discussionUrl, + uint256 proposalFee ); event ProposalCanceled(address indexed proposer, uint256 indexed proposalId, string reason); diff --git a/contracts/library/DaoStructs.sol b/contracts/library/DaoStructs.sol index 442d1fe..a78ae61 100644 --- a/contracts/library/DaoStructs.sol +++ b/contracts/library/DaoStructs.sol @@ -59,6 +59,7 @@ struct Proposal { string description; string discussionUrl; uint256 daoPhaseCount; + uint256 proposalFee; ProposalType proposalType; } diff --git a/test/DiamondDao.spec.ts b/test/DiamondDao.spec.ts index 61b0bfc..aecefcd 100644 --- a/test/DiamondDao.spec.ts +++ b/test/DiamondDao.spec.ts @@ -447,55 +447,16 @@ describe("DiamondDao contract", function () { .withArgs(DaoPhase.Voting); }); - it("should revert propose if fee transfer failed", async function () { - const daoFactory = await ethers.getContractFactory("DiamondDao"); - const mockFactory = await ethers.getContractFactory("MockValidatorSetHbbft"); - - const mockValidatorSet = await mockFactory.deploy(); - await mockValidatorSet.waitForDeployment(); - - const startTime = await time.latest(); - - const daoProxy = await upgrades.deployProxy(daoFactory, [ - await mockValidatorSet.getAddress(), - await mockValidatorSet.getAddress(), - await mockValidatorSet.getAddress(), - ethers.ZeroAddress, - createProposalFee, - startTime + 1 - ], { - initializer: "initialize", - }); - - await daoProxy.waitForDeployment(); - - const dao = daoFactory.attach(await daoProxy.getAddress()) as DiamondDao; - - const targets = [users[3].address]; - const values = [ethers.parseEther("1")]; - const calldatas = [EmptyBytes]; - const description = "test"; - - await expect( - dao.propose( - targets, - values, - calldatas, - "title", - description, - "url", - { value: createProposalFee } - ) - ).to.be.revertedWithCustomError(dao, "TransferFailed") - .withArgs(await dao.getAddress(), await mockValidatorSet.getAddress(), createProposalFee); - }); - it("should revert propose if limit was reached", async function () { const proposer = users[2]; const { dao } = await loadFixture(deployFixture); - for (let i = 0; i < 1000; ++i) { - expect(await createProposal(dao, users[1], `proposal ${i}`)); + const usersSubset = users.slice(10, 20); + + for (let i = 0; i < 100; ++i) { + for (const user of usersSubset) { + expect(await createProposal(dao, user, `proposal ${i} ${user.address}`)); + } } await expect( @@ -531,24 +492,6 @@ describe("DiamondDao contract", function () { ).to.be.revertedWithCustomError(dao, "UnfinalizedProposalsExist"); }); - it("should create proposal and transfer fee to reinsert pot", async function () { - const { dao } = await loadFixture(deployFixture); - - const proposer = users[2]; - - const targets = [users[3].address]; - const values = [ethers.parseEther("1")]; - const calldatas = [EmptyBytes]; - const description = "test"; - - await expect( - dao.connect(proposer).propose(targets, values, calldatas, "title", description, "url", { value: createProposalFee }) - ).to.changeEtherBalances( - [proposer.address, reinsertPot.address], - [-createProposalFee, createProposalFee] - ); - }); - it("should create proposal and emit event", async function () { const { dao } = await loadFixture(deployFixture); @@ -577,7 +520,8 @@ describe("DiamondDao contract", function () { calldatas, "title", description, - "url" + "url", + createProposalFee ); }); @@ -623,6 +567,7 @@ describe("DiamondDao contract", function () { description, "url", 1, // first phase + createProposalFee, 0 // open proposal ]); }); @@ -1191,6 +1136,31 @@ describe("DiamondDao contract", function () { expect(statisticsAfter.accepted).to.equal(statisticBefore.accepted + 1n); }); + it("should finalize accepted proposal and transfer fee to back to proposer", async function () { + const { dao, mockValidatorSet, mockStaking } = await loadFixture(deployFixture); + + const proposer = users[2]; + const voters = users.slice(10, 20); + + const { proposalId } = await createProposal(dao, proposer, "a"); + + await addValidatorsStake(mockValidatorSet, mockStaking, voters); + + await swithPhase(dao); + + await vote(dao, proposalId, voters.slice(0, 10), Vote.Yes); + await vote(dao, proposalId, voters.slice(10), Vote.No); + + await swithPhase(dao); + + await expect( + await dao.finalize(proposalId) + ).to.changeEtherBalances( + [await dao.getAddress(), proposer.address], + [-createProposalFee, createProposalFee] + ); + }); + it("should finalize declined proposal and emit event", async function () { const { dao, mockValidatorSet, mockStaking } = await loadFixture(deployFixture); @@ -1216,6 +1186,31 @@ describe("DiamondDao contract", function () { expect((await dao.getProposal(proposalId)).state).to.equal(ProposalState.Declined); }); + it("should finalize declined proposal and transfer fee to reinsert pot", async function () { + const { dao, mockValidatorSet, mockStaking } = await loadFixture(deployFixture); + + const proposer = users[2]; + const voters = users.slice(10, 20); + + const { proposalId } = await createProposal(dao, proposer, "a"); + + await addValidatorsStake(mockValidatorSet, mockStaking, voters); + + await swithPhase(dao); + + await vote(dao, proposalId, voters.slice(0, 10), Vote.No); + await vote(dao, proposalId, voters.slice(10), Vote.Yes); + + await swithPhase(dao); + + await expect( + await dao.finalize(proposalId) + ).to.changeEtherBalances( + [await dao.getAddress(), reinsertPot.address], + [-createProposalFee, createProposalFee] + ); + }); + it("should finalize declined proposal and update statistics", async function () { const voters = users.slice(10, 20); diff --git a/test/ProposalExecution.spec.ts b/test/ProposalExecution.spec.ts index cb2afa7..230f16f 100644 --- a/test/ProposalExecution.spec.ts +++ b/test/ProposalExecution.spec.ts @@ -208,6 +208,63 @@ describe("DAO proposal execution", function () { expect(await dao.createProposalFee()).to.equal(newFeeValue); }); + + it("should update createProposalFee and refund original fee to proposers", async function () { + const firstProposer = users[2]; + const secondProposer = users[3]; + const voters = users.slice(5, 15); + + const { dao, mockValidatorSet, mockStaking } = await loadFixture(deployFixture); + + const originalFeeValue = createProposalFee; + const newFeeValue = ethers.parseEther('20'); + const calldata = dao.interface.encodeFunctionData("setCreateProposalFee", [newFeeValue]); + + const { proposalId: firstProposalId } = await createProposal( + dao, + firstProposer, + getRandomBigInt().toString(), + [await dao.getAddress()], + [0n], + [calldata] + ); + + const { proposalId: secondProposalId } = await createProposal( + dao, + secondProposer, + getRandomBigInt().toString(), + [await dao.getAddress()], + [0n], + [calldata] + ); + + await addValidatorsStake(mockValidatorSet, mockStaking, voters); + await swithPhase(dao); + await vote(dao, firstProposalId, voters, Vote.Yes); + await vote(dao, secondProposalId, voters, Vote.Yes); + await swithPhase(dao); + + await expect( + await dao.finalize(firstProposalId) + ).to.changeEtherBalances( + [await dao.getAddress(), firstProposer.address], + [-originalFeeValue, originalFeeValue] + ); + + await expect(dao.connect(firstProposer).execute(firstProposalId)) + .to.emit(dao, "SetCreateProposalFee") + .withArgs(newFeeValue); + + expect(await dao.createProposalFee()).to.equal(newFeeValue); + + // even after fee is changed the user should get his original fee back\ + await expect( + await dao.finalize(secondProposalId) + ).to.changeEtherBalances( + [await dao.getAddress(), secondProposer.address], + [-originalFeeValue, originalFeeValue] + ); + }); }); describe("self upgrade", async function () { diff --git a/test/ProposalFinalization.spec.ts b/test/ProposalFinalization.spec.ts index a4ac30d..db361f5 100644 --- a/test/ProposalFinalization.spec.ts +++ b/test/ProposalFinalization.spec.ts @@ -440,7 +440,8 @@ describe("Proposal Acceptance Threshold", function () { calldatas, "title", description, - "url" + "url", + createProposalFee ); await swithPhase(dao); @@ -508,7 +509,8 @@ describe("Proposal Acceptance Threshold", function () { calldatas, "title", description, - "url" + "url", + createProposalFee ); await swithPhase(dao); diff --git a/test/ProposalValueGuards.spec.ts b/test/ProposalValueGuards.spec.ts index 3e38014..b5724aa 100644 --- a/test/ProposalValueGuards.spec.ts +++ b/test/ProposalValueGuards.spec.ts @@ -275,7 +275,8 @@ describe("DAO Ecosystem Paramater Change Value Guards Test", function () { calldatas, "title", description, - "url" + "url", + createProposalFee ); }); @@ -306,7 +307,8 @@ describe("DAO Ecosystem Paramater Change Value Guards Test", function () { calldatas, "title", description, - "url" + "url", + createProposalFee ); expect((await dao.getProposal(proposalId)).proposalType).to.equal(2); @@ -339,7 +341,8 @@ describe("DAO Ecosystem Paramater Change Value Guards Test", function () { calldatas, "title", description, - "url" + "url", + createProposalFee ); expect((await dao.getProposal(proposalId)).proposalType).to.equal(1);