diff --git a/ethereum/contracts/core/Forum.sol b/ethereum/contracts/core/Forum.sol index 32e249b..dc528d7 100644 --- a/ethereum/contracts/core/Forum.sol +++ b/ethereum/contracts/core/Forum.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.24; import "./Reputation.sol"; +import "hardhat/console.sol"; struct Citation { int weightPercent; @@ -36,16 +37,22 @@ contract Forum is Reputation { for (uint i = 0; i < citations.length; i++) { post.citations.push(citations[i]); } - int totalCitationWeightAbs; + int totalCitationWeightPos; + int totalCitationWeightNeg; for (uint i = 0; i < post.citations.length; i++) { int weight = post.citations[i].weightPercent; require(weight >= -100, "Each citation weight must be >= -100"); require(weight <= 100, "Each citation weight must be <= 100"); - totalCitationWeightAbs += weight > 0 ? weight : -weight; + if (weight > 0) totalCitationWeightPos += weight; + else totalCitationWeightNeg += weight; } require( - totalCitationWeightAbs <= 100, - "Sum of absolute value of citations must be <= 100" + totalCitationWeightPos <= 100, + "Sum of positive citations must be <= 100" + ); + require( + totalCitationWeightNeg >= -100, + "Sum of positive citations must be >= -100" ); emit PostAdded(postIndex); } @@ -56,13 +63,18 @@ contract Forum is Reputation { function _propagateValue(uint postIndex, int amount) internal { Post storage post = posts[postIndex]; + int totalOutboundAmount; for (uint i = 0; i < post.citations.length; i++) { int share = (amount * post.citations[i].weightPercent) / 100; totalOutboundAmount += share; _propagateValue(post.citations[i].targetPostIndex, share); } - uint remaining = uint(amount - totalOutboundAmount); - _update(address(this), post.author, remaining); + int remaining = amount - totalOutboundAmount; + if (remaining > 0) { + _update(address(this), post.author, uint(remaining)); + } else { + _update(post.author, address(this), uint(-remaining)); + } } } diff --git a/ethereum/test/Forum.js b/ethereum/test/Forum.js index c19ee9c..f9d46c8 100644 --- a/ethereum/test/Forum.js +++ b/ethereum/test/Forum.js @@ -7,14 +7,17 @@ const { ethers } = require('hardhat'); describe('Forum', () => { async function deploy() { - const [account1, account2] = await ethers.getSigners(); + const [account1, account2, account3] = await ethers.getSigners(); const DAO = await ethers.getContractFactory('DAO'); const dao = await DAO.deploy(); - return { dao, account1, account2 }; + return { + dao, account1, account2, account3, + }; } let dao; let account1; let account2; + let account3; const POOL_DURATION = 3600; // 1 hour const POOL_FEE = 100; const emptyCallbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []); @@ -38,7 +41,9 @@ describe('Forum', () => { describe('Post', () => { beforeEach(async () => { - ({ dao, account1, account2 } = await loadFixture(deploy)); + ({ + dao, account1, account2, account3, + } = await loadFixture(deploy)); }); it('should be able to add a post', async () => { @@ -59,15 +64,50 @@ describe('Forum', () => { expect(post.contentId).to.equal(contentId); }); - it('should propagate reputation to citations', async () => { + it('should be able to donate reputation via citations', async () => { await dao.addPost(account1, 'content-id', []); await dao.addPost(account2, 'second-content-id', [{ weightPercent: 50, targetPostIndex: 0 }]); await initiateValidationPool({ postIndex: 1 }); const pool = await dao.validationPools(0); expect(pool.postIndex).to.equal(1); await dao.evaluateOutcome(0); - expect(await dao.balanceOf(account2)).to.equal(50); expect(await dao.balanceOf(account1)).to.equal(50); + expect(await dao.balanceOf(account2)).to.equal(50); + }); + + it('should be able to leach reputation via citations', async () => { + await dao.addPost(account1, 'content-id', []); + await initiateValidationPool({ postIndex: 0 }); + await dao.evaluateOutcome(0); + expect(await dao.balanceOf(account1)).to.equal(100); + await dao.addPost(account2, 'second-content-id', [{ weightPercent: -50, targetPostIndex: 0 }]); + await initiateValidationPool({ postIndex: 1 }); + const pool = await dao.validationPools(1); + expect(pool.postIndex).to.equal(1); + await time.increase(POOL_DURATION + 1); + await dao.evaluateOutcome(1); + expect(await dao.balanceOf(account1)).to.equal(50); + expect(await dao.balanceOf(account2)).to.equal(150); + }); + + it('should be able to redistribute power via citations', async () => { + await dao.addPost(account1, 'content-id', []); + await initiateValidationPool({ postIndex: 0 }); + await dao.evaluateOutcome(0); + expect(await dao.balanceOf(account1)).to.equal(100); + await dao.addPost(account2, 'second-content-id', []); + expect(await dao.balanceOf(account2)).to.equal(0); + await dao.addPost(account3, 'third-content-id', [ + { weightPercent: -100, targetPostIndex: 0 }, + { weightPercent: 100, targetPostIndex: 1 }, + ]); + await initiateValidationPool({ postIndex: 2 }); + const pool = await dao.validationPools(1); + expect(pool.postIndex).to.equal(2); + await time.increase(POOL_DURATION + 1); + await dao.evaluateOutcome(1); + expect(await dao.balanceOf(account1)).to.equal(0); + expect(await dao.balanceOf(account2)).to.equal(100); }); }); });