diff --git a/ethereum/contracts/DAO.sol b/ethereum/contracts/DAO.sol index 5cadde1..c4ced44 100644 --- a/ethereum/contracts/DAO.sol +++ b/ethereum/contracts/DAO.sol @@ -6,6 +6,11 @@ import "./IAcceptAvailability.sol"; import "hardhat/console.sol"; +struct Post { + address sender; + address author; +} + struct Stake { bool inFavor; uint256 amount; @@ -13,9 +18,9 @@ struct Stake { } struct ValidationPool { + Post post; mapping(uint => Stake) stakes; uint stakeCount; - address author; uint256 fee; uint256 initialStakedFor; uint256 initialStakedAgainst; @@ -39,6 +44,8 @@ contract DAO is ERC20("Reputation", "REP") { mapping(address => bool) public isMember; mapping(uint => ValidationPool) public validationPools; uint public validationPoolCount; + mapping(uint => Post) public posts; + uint public postCount; // ufixed8x1 constant mintingRatio = 1; // ufixed8x1 constant quorum = 0; @@ -50,18 +57,24 @@ contract DAO is ERC20("Reputation", "REP") { event ValidationPoolInitiated(uint poolIndex); event ValidationPoolResolved(uint poolIndex, bool votePasses); + function addPost(address author) public returns (uint postIndex) { + postIndex = postCount++; + Post storage post = posts[postIndex]; + post.author = author; + post.sender = msg.sender; + } + /// Accept fee to initiate a validation pool - /// TODO: Rather than accept author as a parameter, accept a reference to a forum post /// TODO: Handle multiple authors /// TODO: Constrain duration to allowable range function initiateValidationPool( - address author, + uint postIndex, uint duration ) public payable returns (uint poolIndex) { require(msg.value > 0, "Fee is required to initiate validation pool"); poolIndex = validationPoolCount++; ValidationPool storage pool = validationPools[poolIndex]; - pool.author = author; + pool.post = posts[postIndex]; pool.fee = msg.value; pool.duration = duration; pool.endTime = block.timestamp + duration; @@ -71,10 +84,10 @@ contract DAO is ERC20("Reputation", "REP") { // Implementing this with adjustable parameters will require more advanced fixed point math. // TODO: Make minting ratio an adjustable parameter // TODO: Make stakeForAuthor an adjustable parameter - _mint(author, msg.value); + _mint(pool.post.author, msg.value); // TODO: We need a way to exclude this pending reputation from the total supply when computing fee distribution - _stake(pool, author, msg.value / 2, true); - _stake(pool, author, msg.value / 2, false); + _stake(pool, pool.post.author, msg.value / 2, true); + _stake(pool, pool.post.author, msg.value / 2, false); emit ValidationPoolInitiated(poolIndex); } @@ -124,9 +137,9 @@ contract DAO is ERC20("Reputation", "REP") { // This is especially important so that the DAO's first pool can pass, // when no reputation has yet been minted. votePasses = stakedFor >= stakedAgainst; - if (votePasses && !isMember[pool.author]) { - members[memberCount++] = pool.author; - isMember[pool.author] = true; + if (votePasses && !isMember[pool.post.author]) { + members[memberCount++] = pool.post.author; + isMember[pool.post.author] = true; } pool.resolved = true; emit ValidationPoolResolved(poolIndex, votePasses); @@ -146,7 +159,7 @@ contract DAO is ERC20("Reputation", "REP") { // Due to rounding, there may be some reward left over. Include this as a reward to the author. uint256 remainder = amountFromLosers - totalRewards; if (remainder > 0) { - _transfer(address(this), pool.author, remainder); + _transfer(address(this), pool.post.author, remainder); } // Distribute fee proportionatly among all reputation holders for (uint i = 0; i < memberCount; i++) { diff --git a/ethereum/contracts/Work1.sol b/ethereum/contracts/Work1.sol index 7c4f628..afbed8d 100644 --- a/ethereum/contracts/Work1.sol +++ b/ethereum/contracts/Work1.sol @@ -149,9 +149,11 @@ contract Work1 is IAcceptAvailability { AvailabilityStake storage stake = stakes[request.stakeIndex]; request.status = WorkStatus.ApprovalSubmitted; request.approval = approval; + // Make work evidence post + uint postIndex = dao.addPost(stake.worker); // Initiate validation pool request.poolIndex = dao.initiateValidationPool{value: request.fee}( - stake.worker, + postIndex, POOL_DURATION ); } diff --git a/ethereum/test/DAO.js b/ethereum/test/DAO.js index 3888cb2..dcd8319 100644 --- a/ethereum/test/DAO.js +++ b/ethereum/test/DAO.js @@ -6,16 +6,10 @@ const { expect } = require('chai'); const { ethers } = require('hardhat'); describe('DAO', () => { - // We define a fixture to reuse the same setup in every test. - // We use loadFixture to run this setup once, snapshot that state, - // and reset Hardhat Network to that snapshot in every test. async function deploy() { - // Contracts are deployed using the first signer/account by default const [account1, account2] = await ethers.getSigners(); - const DAO = await ethers.getContractFactory('DAO'); const dao = await DAO.deploy(); - return { dao, account1, account2 }; } @@ -25,6 +19,24 @@ describe('DAO', () => { expect(await dao.totalSupply()).to.equal(0); }); + describe('Post', () => { + it('should be able to add a post', async () => { + const { dao, account1 } = await loadFixture(deploy); + await dao.addPost(account1); + const post = await dao.posts(0); + expect(post.author).to.equal(account1); + expect(post.sender).to.equal(account1); + }); + + it('should be able to add a post on behalf of another account', async () => { + const { dao, account1, account2 } = await loadFixture(deploy); + await dao.addPost(account2); + const post = await dao.posts(0); + expect(post.author).to.equal(account2); + expect(post.sender).to.equal(account1); + }); + }); + describe('Validation Pool', () => { let dao; let account1; @@ -35,7 +47,8 @@ describe('DAO', () => { const setup = await loadFixture(deploy); dao = setup.dao; account1 = setup.account1; - const init = () => dao.initiateValidationPool(account1, POOL_DURATION, { value: POOL_FEE }); + await dao.addPost(account1); + const init = () => dao.initiateValidationPool(0, POOL_DURATION, { value: POOL_FEE }); await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(0); expect(await dao.validationPoolCount()).to.equal(1); expect(await dao.memberCount()).to.equal(0); @@ -46,12 +59,12 @@ describe('DAO', () => { describe('Initiate', () => { it('should not be able to initiate a validation pool without a fee', async () => { const setup = await loadFixture(deploy); - const init = () => setup.dao.initiateValidationPool(setup.account1, POOL_DURATION); + const init = () => setup.dao.initiateValidationPool(0, POOL_DURATION); await expect(init()).to.be.revertedWith('Fee is required to initiate validation pool'); }); it('should be able to initiate a second validation pool', async () => { - const init = () => dao.initiateValidationPool(account1, POOL_DURATION, { value: POOL_FEE }); + const init = () => dao.initiateValidationPool(0, POOL_DURATION, { value: POOL_FEE }); await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(1); expect(await dao.validationPoolCount()).to.equal(2); }); @@ -82,7 +95,7 @@ describe('DAO', () => { }); it('should be able to evaluate outcome of second validation pool', async () => { - const init = () => dao.initiateValidationPool(account1, POOL_DURATION, { value: POOL_FEE }); + const init = () => dao.initiateValidationPool(0, POOL_DURATION, { value: POOL_FEE }); await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(1); expect(await dao.validationPoolCount()).to.equal(2); time.increase(POOL_DURATION + 1); @@ -99,7 +112,7 @@ describe('DAO', () => { await dao.evaluateOutcome(0); expect(await dao.balanceOf(account1)).to.equal(100); expect(await dao.balanceOf(dao.target)).to.equal(0); - await dao.initiateValidationPool(account1, POOL_DURATION, { value: POOL_FEE }); + await dao.initiateValidationPool(0, POOL_DURATION, { value: POOL_FEE }); expect(await dao.balanceOf(dao.target)).to.equal(100); }); diff --git a/ethereum/test/Work1.js b/ethereum/test/Work1.js index 7df8dfc..0b9aedf 100644 --- a/ethereum/test/Work1.js +++ b/ethereum/test/Work1.js @@ -17,7 +17,8 @@ describe('Work1', () => { const Work1 = await ethers.getContractFactory('Work1'); const work1 = await Work1.deploy(dao.target, WORK1_PRICE); - await dao.initiateValidationPool(account1, 0, { value: 100 }); + await dao.addPost(account1); + await dao.initiateValidationPool(0, 0, { value: 100 }); await dao.evaluateOutcome(0); return { @@ -177,6 +178,12 @@ describe('Work1', () => { await expect(work1.submitWorkApproval(0, true)).to.emit(dao, 'ValidationPoolInitiated').withArgs(1); }); + it('should be able to submit work disapproval', async () => { + await work1.connect(account2).requestWork({ value: WORK1_PRICE }); + await work1.submitWorkEvidence(0); + await expect(work1.submitWorkApproval(0, false)).to.emit(dao, 'ValidationPoolInitiated').withArgs(1); + }); + it('should not be able to submit work approval/disapproval twice', async () => { await work1.connect(account2).requestWork({ value: WORK1_PRICE }); await work1.submitWorkEvidence(0);