diff --git a/ethereum/contracts/Onboarding.sol b/ethereum/contracts/Onboarding.sol index 812828a..393c9f8 100644 --- a/ethereum/contracts/Onboarding.sol +++ b/ethereum/contracts/Onboarding.sol @@ -27,7 +27,9 @@ contract Onboarding is WorkContract, IOnValidate { request.status = WorkStatus.ApprovalSubmitted; request.approval = approval; // Make work evidence post - dao.addPost(stake.worker, request.evidenceContentId, request.citations); + Author[] memory authors = new Author[](1); + authors[0] = Author(100, stake.worker); + dao.addPost(authors, request.evidenceContentId, request.citations); emit WorkApprovalSubmitted(requestIndex, approval); // Initiate validation pool uint poolIndex = dao.initiateValidationPool{ @@ -70,8 +72,11 @@ contract Onboarding is WorkContract, IOnValidate { payable(request.customer).transfer(request.fee / 10); return 1; } + // Make onboarding post Citation[] memory emptyCitations; - dao.addPost(request.customer, request.requestContentId, emptyCitations); + Author[] memory authors = new Author[](1); + authors[0] = Author(100, request.customer); + dao.addPost(authors, request.requestContentId, emptyCitations); dao.initiateValidationPool{value: request.fee / 10}( request.requestContentId, POOL_DURATION, diff --git a/ethereum/contracts/Proposals.sol b/ethereum/contracts/Proposals.sol index c2ab1ff..a8d6e6d 100644 --- a/ethereum/contracts/Proposals.sol +++ b/ethereum/contracts/Proposals.sol @@ -73,7 +73,9 @@ contract Proposals is DAOContract, IOnValidate { // or support post lookup by contentId // TODO: Take citations as a parameter Citation[] memory emptyCitations; - dao.addPost(author, contentId, emptyCitations); + Author[] memory authors = new Author[](1); + authors[0] = Author(100, author); + dao.addPost(authors, contentId, emptyCitations); proposalIndex = proposalCount++; Proposal storage proposal = proposals[proposalIndex]; proposal.sender = msg.sender; diff --git a/ethereum/contracts/WorkContract.sol b/ethereum/contracts/WorkContract.sol index c6602f6..cc42595 100644 --- a/ethereum/contracts/WorkContract.sol +++ b/ethereum/contracts/WorkContract.sol @@ -179,7 +179,9 @@ abstract contract WorkContract is request.status = WorkStatus.ApprovalSubmitted; request.approval = approval; // Make work evidence post - dao.addPost(stake.worker, request.evidenceContentId, request.citations); + Author[] memory authors = new Author[](1); + authors[0] = Author(100, stake.worker); + dao.addPost(authors, request.evidenceContentId, request.citations); emit WorkApprovalSubmitted(requestIndex, approval); // Initiate validation pool uint poolIndex = dao.initiateValidationPool{value: request.fee}( diff --git a/ethereum/contracts/core/Forum.sol b/ethereum/contracts/core/Forum.sol index 6f63976..869ad30 100644 --- a/ethereum/contracts/core/Forum.sol +++ b/ethereum/contracts/core/Forum.sol @@ -9,10 +9,15 @@ struct Citation { string targetPostId; } +struct Author { + uint weightPercent; + address authorAddress; +} + struct Post { string id; address sender; - address author; + Author[] authors; Citation[] citations; uint reputation; // TODO: timestamp @@ -31,20 +36,29 @@ contract Forum is Reputation { uint depthLimit = 3; function addPost( - address author, + Author[] calldata authors, string calldata contentId, Citation[] calldata citations ) external { + require(authors.length > 0, "Post must include at least one author"); postCount++; postIds.push(contentId); Post storage post = posts[contentId]; require( - post.author == address(0), + post.authors.length == 0, "A post with this contentId already exists" ); - post.author = author; post.sender = msg.sender; post.id = contentId; + uint authorTotalWeightPercent; + for (uint i = 0; i < authors.length; i++) { + authorTotalWeightPercent += authors[i].weightPercent; + post.authors.push(authors[i]); + } + require( + authorTotalWeightPercent == 100, + "Author weights must sum to 100%" + ); for (uint i = 0; i < citations.length; i++) { post.citations.push(citations[i]); } @@ -68,6 +82,13 @@ contract Forum is Reputation { emit PostAdded(contentId); } + function getPostAuthors( + string calldata postId + ) external view returns (Author[] memory) { + Post storage post = posts[postId]; + return post.authors; + } + function _onValidatePost(string memory postId, uint amount) internal { _propagateReputation(postId, int(amount), false, 0); } @@ -111,6 +132,39 @@ contract Forum is Reputation { _edgeBalances[postId][citation.targetPostId] += outboundAmount; } + function _distributeAmongAuthors( + Post memory post, + int amount + ) internal returns (int refund) { + int allocated; + for (uint i = 0; i < post.authors.length; i++) { + Author memory author = post.authors[i]; + int share; + if (i < post.authors.length - 1) { + share = (amount * int(author.weightPercent)) / 100; + allocated += share; + } else { + // For the last author, allocate the remainder. + share = amount - allocated; + } + if (share > 0) { + _update(address(this), author.authorAddress, uint(share)); + } else if (balanceOf(author.authorAddress) < uint(-share)) { + // Author has already lost some REP gained from this post. + // That means other DAO members have earned it for policing. + // We need to refund the difference here to ensure accurate bookkeeping + refund += share + int(balanceOf(author.authorAddress)); + _update( + author.authorAddress, + address(this), + balanceOf(author.authorAddress) + ); + } else { + _update(author.authorAddress, address(this), uint(-share)); + } + } + } + function _propagateReputation( string memory postId, int amount, @@ -146,37 +200,21 @@ contract Forum is Reputation { } } if (amount > 0) { - _update(address(this), post.author, uint(amount)); + _distributeAmongAuthors(post, amount); post.reputation += uint(amount); } else { - // Prevent reputation from being reduced below zero if (int(post.reputation) + amount >= 0) { - if (balanceOf(post.author) >= uint(-amount)) { - _update(post.author, address(this), uint(-amount)); - } else { - // Author has already lost some REP gained from this post. - // That means other DAO members have earned it for policing. - // We need to refund the difference here to ensure accurate bookkeeping - refundToInbound = amount + int(balanceOf(post.author)); - _update(post.author, address(this), balanceOf(post.author)); - } + // Reduce the reputation of each author proportionately; + // If any author has insufficient reputation, refund the difference. + refundToInbound = _distributeAmongAuthors(post, amount); post.reputation -= uint(-amount); } else { + // If we applied the full amount, the post's reputation would decrease below zero. refundToInbound = int(post.reputation) + amount; - if (balanceOf(post.author) >= post.reputation) { - _update(post.author, address(this), post.reputation); - } else { - // If author has already lost reputation that was gained from this post, - // that means other DAO members gained it through policing. - // We have to increase the magnitude of the amount we're "refunding", which is expressed as a negative number. - // This has the effect of preserving the sum of all members' REP. - // However, we still set the post reputation all the way to zero. - // So we end up decreasing the sum of all posts' REP. - refundToInbound -= int( - post.reputation - balanceOf(post.author) - ); - _update(post.author, address(this), balanceOf(post.author)); - } + refundToInbound += _distributeAmongAuthors( + post, + -int(post.reputation) + ); post.reputation = 0; } } diff --git a/ethereum/contracts/core/ValidationPools.sol b/ethereum/contracts/core/ValidationPools.sol index 13fa5ed..c6299a4 100644 --- a/ethereum/contracts/core/ValidationPools.sol +++ b/ethereum/contracts/core/ValidationPools.sol @@ -117,7 +117,7 @@ contract ValidationPools is Reputation, Forum { require(winRatio[0] <= winRatio[1], "Win ratio is greater than one"); require(bindingPercent <= 100, "Binding percent must be <= 100"); Post storage post = posts[postId]; - require(post.author != address(0), "Target post not found"); + require(post.authors.length != 0, "Target post not found"); poolIndex = validationPoolCount++; ValidationPool storage pool = validationPools[poolIndex]; pool.sender = msg.sender; @@ -197,9 +197,14 @@ contract ValidationPools is Reputation, Forum { votePasses = stakedFor * pool.params.winRatio[1] >= (stakedFor + stakedAgainst) * pool.params.winRatio[0]; - if (votePasses && !isMember[post.author]) { - members[memberCount++] = post.author; - isMember[post.author] = true; + if (votePasses) { + for (uint i = 0; i < post.authors.length; i++) { + address authorAddress = post.authors[i].authorAddress; + if (!isMember[authorAddress]) { + members[memberCount++] = authorAddress; + isMember[authorAddress] = true; + } + } } pool.resolved = true; pool.outcome = votePasses; diff --git a/ethereum/test/Forum.js b/ethereum/test/Forum.js index f74f4c4..67e01d7 100644 --- a/ethereum/test/Forum.js +++ b/ethereum/test/Forum.js @@ -39,6 +39,11 @@ describe('Forum', () => { { value: fee ?? POOL_FEE }, ); + const addPost = (author, contentId, citations) => dao.addPost([{ + weightPercent: 100, + authorAddress: author, + }], contentId, citations); + describe('Post', () => { beforeEach(async () => { ({ @@ -48,25 +53,69 @@ describe('Forum', () => { it('should be able to add a post', async () => { const contentId = 'some-id'; - await expect(dao.addPost(account1, contentId, [])).to.emit(dao, 'PostAdded').withArgs('some-id'); + await expect(addPost(account1, contentId, [])).to.emit(dao, 'PostAdded').withArgs('some-id'); const post = await dao.posts(contentId); - expect(post.author).to.equal(account1); expect(post.sender).to.equal(account1); expect(post.id).to.equal(contentId); + const postAuthors = await dao.getPostAuthors(contentId); + expect(postAuthors).to.have.length(1); + expect(postAuthors[0].weightPercent).to.equal(100); + expect(postAuthors[0].authorAddress).to.equal(account1); }); it('should be able to add a post on behalf of another account', async () => { const contentId = 'some-id'; - await dao.addPost(account2, contentId, []); + await addPost(account2, contentId, []); const post = await dao.posts(contentId); - expect(post.author).to.equal(account2); expect(post.sender).to.equal(account1); expect(post.id).to.equal(contentId); + const postAuthors = await dao.getPostAuthors(contentId); + expect(postAuthors).to.have.length(1); + expect(postAuthors[0].weightPercent).to.equal(100); + expect(postAuthors[0].authorAddress).to.equal(account2); + }); + + it('should be able to add a post with multiple authors', async () => { + const contentId = 'some-id'; + await expect(dao.addPost([ + { weightPercent: 50, authorAddress: account1 }, + { weightPercent: 50, authorAddress: account2 }, + ], contentId, [])).to.emit(dao, 'PostAdded').withArgs('some-id'); + const post = await dao.posts(contentId); + expect(post.sender).to.equal(account1); + expect(post.id).to.equal(contentId); + const postAuthors = await dao.getPostAuthors(contentId); + expect(postAuthors).to.have.length(2); + expect(postAuthors[0].weightPercent).to.equal(50); + expect(postAuthors[0].authorAddress).to.equal(account1); + expect(postAuthors[1].weightPercent).to.equal(50); + expect(postAuthors[1].authorAddress).to.equal(account2); + await initiateValidationPool({ postId: 'some-id' }); + await time.increase(POOL_DURATION + 1); + await dao.evaluateOutcome(0); + expect(await dao.balanceOf(account1)).to.equal(50); + expect(await dao.balanceOf(account2)).to.equal(50); + }); + + it('should not be able to add a post with total author weight < 100%', async () => { + const contentId = 'some-id'; + await expect(dao.addPost([ + { weightPercent: 50, authorAddress: account1 }, + { weightPercent: 40, authorAddress: account2 }, + ], contentId, [])).to.be.rejectedWith('Author weights must sum to 100%'); + }); + + it('should not be able to add a post with total author weight > 100%', async () => { + const contentId = 'some-id'; + await expect(dao.addPost([ + { weightPercent: 50, authorAddress: account1 }, + { weightPercent: 60, authorAddress: account2 }, + ], contentId, [])).to.be.rejectedWith('Author weights must sum to 100%'); }); 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, targetPostId: 'content-id' }]); + await addPost(account1, 'content-id', []); + await addPost(account2, 'second-content-id', [{ weightPercent: 50, targetPostId: 'content-id' }]); await initiateValidationPool({ postId: 'second-content-id' }); const pool = await dao.validationPools(0); expect(pool.postId).to.equal('second-content-id'); @@ -76,13 +125,13 @@ describe('Forum', () => { }); it('should be able to leach reputation via citations', async () => { - await dao.addPost(account1, 'content-id', []); + await addPost(account1, 'content-id', []); expect((await dao.posts('content-id')).reputation).to.equal(0); await initiateValidationPool({ postId: 'content-id' }); await dao.evaluateOutcome(0); expect(await dao.balanceOf(account1)).to.equal(100); expect((await dao.posts('content-id')).reputation).to.equal(100); - await dao.addPost(account2, 'second-content-id', [{ weightPercent: -50, targetPostId: 'content-id' }]); + await addPost(account2, 'second-content-id', [{ weightPercent: -50, targetPostId: 'content-id' }]); expect((await dao.posts('second-content-id')).reputation).to.equal(0); await initiateValidationPool({ postId: 'second-content-id' }); const pool = await dao.validationPools(1); @@ -96,13 +145,13 @@ describe('Forum', () => { }); it('should be able to redistribute power via citations', async () => { - await dao.addPost(account1, 'content-id', []); + await addPost(account1, 'content-id', []); await initiateValidationPool({ postId: 'content-id' }); await dao.evaluateOutcome(0); expect(await dao.balanceOf(account1)).to.equal(100); - await dao.addPost(account2, 'second-content-id', []); + await addPost(account2, 'second-content-id', []); expect(await dao.balanceOf(account2)).to.equal(0); - await dao.addPost(account3, 'third-content-id', [ + await addPost(account3, 'third-content-id', [ { weightPercent: -100, targetPostId: 'content-id' }, { weightPercent: 100, targetPostId: 'second-content-id' }, ]); @@ -117,17 +166,17 @@ describe('Forum', () => { }); it('should be able to reverse a negative citation with a negative citation', async () => { - await dao.addPost(account1, 'content-id', []); + await addPost(account1, 'content-id', []); await initiateValidationPool({ postId: 'content-id' }); await dao.evaluateOutcome(0); expect(await dao.balanceOf(account1)).to.equal(100); - await dao.addPost(account2, 'second-content-id', [{ weightPercent: -100, targetPostId: 'content-id' }]); + await addPost(account2, 'second-content-id', [{ weightPercent: -100, targetPostId: 'content-id' }]); await initiateValidationPool({ postId: 'second-content-id' }); 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(200); - await dao.addPost(account3, 'third-content-id', [{ weightPercent: -100, targetPostId: 'second-content-id' }]); + await addPost(account3, 'third-content-id', [{ weightPercent: -100, targetPostId: 'second-content-id' }]); await initiateValidationPool({ postId: 'third-content-id' }); await time.increase(POOL_DURATION + 1); await dao.evaluateOutcome(2); @@ -137,17 +186,17 @@ describe('Forum', () => { }); it('forum reputation rewards are shared with validation pool policing rewards', async () => { - await dao.addPost(account1, 'content-id', []); + await addPost(account1, 'content-id', []); await initiateValidationPool({ postId: 'content-id' }); await dao.evaluateOutcome(0); expect(await dao.balanceOf(account1)).to.equal(100); - await dao.addPost(account2, 'second-content-id', []); + await addPost(account2, 'second-content-id', []); await initiateValidationPool({ postId: 'second-content-id' }); await time.increase(POOL_DURATION + 1); await dao.evaluateOutcome(1); expect(await dao.balanceOf(account1)).to.equal(100); expect(await dao.balanceOf(account2)).to.equal(100); - await dao.addPost(account3, 'third-content-id', [{ weightPercent: -100, targetPostId: 'second-content-id' }]); + await addPost(account3, 'third-content-id', [{ weightPercent: -100, targetPostId: 'second-content-id' }]); await initiateValidationPool({ postId: 'third-content-id' }); await dao.stakeOnValidationPool(2, 100, true); await time.increase(POOL_DURATION + 1); @@ -159,17 +208,17 @@ describe('Forum', () => { it('should limit effects of negative references on prior positive references', async () => { console.log('First post'); - await dao.addPost(account1, 'content-id', []); + await addPost(account1, 'content-id', []); await initiateValidationPool({ postId: 'content-id' }); await dao.evaluateOutcome(0); expect(await dao.balanceOf(account1)).to.equal(100); - await dao.addPost(account2, 'second-content-id', [{ weightPercent: 50, targetPostId: 'content-id' }]); + await addPost(account2, 'second-content-id', [{ weightPercent: 50, targetPostId: 'content-id' }]); await initiateValidationPool({ postId: 'second-content-id' }); await time.increase(POOL_DURATION + 1); await dao.evaluateOutcome(1); expect(await dao.balanceOf(account1)).to.equal(150); expect(await dao.balanceOf(account2)).to.equal(50); - await dao.addPost(account3, 'third-content-id', [{ weightPercent: -100, targetPostId: 'second-content-id' }]); + await addPost(account3, 'third-content-id', [{ weightPercent: -100, targetPostId: 'second-content-id' }]); await initiateValidationPool({ postId: 'third-content-id', fee: 200 }); await time.increase(POOL_DURATION + 1); await dao.evaluateOutcome(2); @@ -179,17 +228,17 @@ describe('Forum', () => { }); it('should limit effects of negative references on prior negative references', async () => { - await dao.addPost(account1, 'content-id', []); + await addPost(account1, 'content-id', []); await initiateValidationPool({ postId: 'content-id' }); await dao.evaluateOutcome(0); expect(await dao.balanceOf(account1)).to.equal(100); - await dao.addPost(account2, 'second-content-id', [{ weightPercent: -100, targetPostId: 'content-id' }]); + await addPost(account2, 'second-content-id', [{ weightPercent: -100, targetPostId: 'content-id' }]); await initiateValidationPool({ postId: 'second-content-id' }); 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(200); - await dao.addPost(account3, 'third-content-id', [{ weightPercent: -100, targetPostId: 'second-content-id' }]); + await addPost(account3, 'third-content-id', [{ weightPercent: -100, targetPostId: 'second-content-id' }]); await initiateValidationPool({ postId: 'third-content-id', fee: 200 }); await time.increase(POOL_DURATION + 1); await dao.evaluateOutcome(2); @@ -199,10 +248,10 @@ describe('Forum', () => { }); it('should enforce depth limit', async () => { - await dao.addPost(account1, 'content-id-1', []); - await dao.addPost(account1, 'content-id-2', [{ weightPercent: 100, targetPostId: 'content-id-1' }]); - await dao.addPost(account1, 'content-id-3', [{ weightPercent: 100, targetPostId: 'content-id-2' }]); - await dao.addPost(account1, 'content-id-4', [{ weightPercent: 100, targetPostId: 'content-id-3' }]); + await addPost(account1, 'content-id-1', []); + await addPost(account1, 'content-id-2', [{ weightPercent: 100, targetPostId: 'content-id-1' }]); + await addPost(account1, 'content-id-3', [{ weightPercent: 100, targetPostId: 'content-id-2' }]); + await addPost(account1, 'content-id-4', [{ weightPercent: 100, targetPostId: 'content-id-3' }]); await initiateValidationPool({ postId: 'content-id-4' }); await dao.evaluateOutcome(0); const posts = await Promise.all([ @@ -218,7 +267,7 @@ describe('Forum', () => { }); it('should be able to incinerate reputation', async () => { - await dao.addPost(account1, 'content-id-1', [ + await addPost(account1, 'content-id-1', [ { weightPercent: 50, targetPostId: '', @@ -233,14 +282,14 @@ describe('Forum', () => { describe('negative citation of a post, the author having already staked and lost reputation', async () => { beforeEach(async () => { - await dao.addPost(account1, 'content-id', []); + await addPost(account1, 'content-id', []); await initiateValidationPool({ postId: 'content-id' }); await dao.evaluateOutcome(0); expect(await dao.balanceOf(account1)).to.equal(100); expect(await dao.totalSupply()).to.equal(100); expect((await dao.posts('content-id')).reputation).to.equal(100); - await dao.addPost(account2, 'second-content-id', []); + await addPost(account2, 'second-content-id', []); await initiateValidationPool({ postId: 'second-content-id' }); await time.increase(POOL_DURATION + 1); await dao.evaluateOutcome(1); @@ -265,7 +314,7 @@ describe('Forum', () => { it('author and post rep can be completely destroyed', async () => { // account1's post is later strongly negatively referenced - await dao.addPost(account3, 'third-content-id', [{ weightPercent: -100, targetPostId: 'content-id' }]); + await addPost(account3, 'third-content-id', [{ weightPercent: -100, targetPostId: 'content-id' }]); await initiateValidationPool({ postId: 'third-content-id', fee: 200 }); await time.increase(POOL_DURATION + 1); await dao.evaluateOutcome(3); @@ -280,7 +329,7 @@ describe('Forum', () => { it('author rep can be destroyed while some post rep remains', async () => { // account1's post is later strongly negatively referenced - await dao.addPost(account3, 'third-content-id', [{ weightPercent: -100, targetPostId: 'content-id' }]); + await addPost(account3, 'third-content-id', [{ weightPercent: -100, targetPostId: 'content-id' }]); await initiateValidationPool({ postId: 'third-content-id', fee: 70 }); await time.increase(POOL_DURATION + 1); await dao.evaluateOutcome(3); @@ -295,7 +344,7 @@ describe('Forum', () => { it('author rep can be destroyed while some post rep remains (odd amount)', async () => { // account1's post is later strongly negatively referenced - await dao.addPost(account3, 'third-content-id', [{ weightPercent: -100, targetPostId: 'content-id' }]); + await addPost(account3, 'third-content-id', [{ weightPercent: -100, targetPostId: 'content-id' }]); await initiateValidationPool({ postId: 'third-content-id', fee: 75 }); await time.increase(POOL_DURATION + 1); await dao.evaluateOutcome(3); @@ -308,5 +357,59 @@ describe('Forum', () => { expect((await dao.posts('third-content-id')).reputation).to.equal(125); }); }); + + describe('negative citation of a post with multiple authors', async () => { + beforeEach(async () => { + await dao.addPost([ + { weightPercent: 50, authorAddress: account1 }, + { weightPercent: 50, authorAddress: account2 }, + ], 'content-id', []); + await initiateValidationPool({ postId: 'content-id' }); + await dao.evaluateOutcome(0); + expect(await dao.balanceOf(account1)).to.equal(50); + expect(await dao.balanceOf(account2)).to.equal(50); + expect(await dao.totalSupply()).to.equal(100); + expect((await dao.posts('content-id')).reputation).to.equal(100); + + // account1 stakes and loses + await initiateValidationPool({ postId: 'content-id' }); + await dao.stakeOnValidationPool(1, 25, true); + await dao.connect(account2).stakeOnValidationPool(1, 60, false); + await time.increase(POOL_DURATION + 1); + await dao.evaluateOutcome(1); + expect(await dao.balanceOf(account1)).to.equal(25); + expect(await dao.balanceOf(account2)).to.equal(175); + expect(await dao.totalSupply()).to.equal(200); + expect((await dao.posts('content-id')).reputation).to.equal(100); + }); + + it('author and post rep can be completely destroyed', async () => { + // account1's post is later strongly negatively referenced + await addPost(account3, 'second-content-id', [{ weightPercent: -100, targetPostId: 'content-id' }]); + await initiateValidationPool({ postId: 'second-content-id', fee: 400 }); + await time.increase(POOL_DURATION + 1); + await dao.evaluateOutcome(2); + expect(await dao.balanceOf(account1)).to.equal(0); + expect(await dao.balanceOf(account2)).to.equal(125); + expect(await dao.balanceOf(account3)).to.equal(475); + expect(await dao.totalSupply()).to.equal(600); + expect((await dao.posts('content-id')).reputation).to.equal(0); + expect((await dao.posts('second-content-id')).reputation).to.equal(475); + }); + + it('author rep can be destroyed while some post rep remains', async () => { + // account1's post is later strongly negatively referenced + await addPost(account3, 'second-content-id', [{ weightPercent: -100, targetPostId: 'content-id' }]); + await initiateValidationPool({ postId: 'second-content-id', fee: 70 }); + await time.increase(POOL_DURATION + 1); + await dao.evaluateOutcome(2); + expect(await dao.totalSupply()).to.equal(270); + expect(await dao.balanceOf(account1)).to.equal(0); + expect(await dao.balanceOf(account2)).to.equal(140); + expect(await dao.balanceOf(account3)).to.equal(130); + expect((await dao.posts('content-id')).reputation).to.equal(30); + expect((await dao.posts('second-content-id')).reputation).to.equal(130); + }); + }); }); }); diff --git a/ethereum/test/Onboarding.js b/ethereum/test/Onboarding.js index 2b2ae42..8be8280 100644 --- a/ethereum/test/Onboarding.js +++ b/ethereum/test/Onboarding.js @@ -19,7 +19,7 @@ describe('Onboarding', () => { const Onboarding = await ethers.getContractFactory('Onboarding'); const onboarding = await Onboarding.deploy(dao.target, proposals.target, PRICE); - await dao.addPost(account1, 'content-id', []); + await dao.addPost([{ weightPercent: 100, authorAddress: account1 }], 'content-id', []); const callbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []); await dao.initiateValidationPool( 'content-id', @@ -71,9 +71,12 @@ describe('Onboarding', () => { .to.emit(dao, 'ValidationPoolInitiated').withArgs(1) .to.emit(onboarding, 'WorkApprovalSubmitted').withArgs(0, true); const post = await dao.posts('evidence-content-id'); - expect(post.author).to.equal(account1); expect(post.sender).to.equal(onboarding.target); expect(post.id).to.equal('evidence-content-id'); + const postAuthors = await dao.getPostAuthors('evidence-content-id'); + expect(postAuthors).to.have.length(1); + expect(postAuthors[0].weightPercent).to.equal(100); + expect(postAuthors[0].authorAddress).to.equal(account1); const pool = await dao.validationPools(1); expect(pool.postId).to.equal('evidence-content-id'); expect(pool.fee).to.equal(PRICE * 0.9); diff --git a/ethereum/test/Proposals.js b/ethereum/test/Proposals.js index 5aeee6d..bd47e77 100644 --- a/ethereum/test/Proposals.js +++ b/ethereum/test/Proposals.js @@ -16,8 +16,8 @@ describe('Proposal', () => { const Proposals = await ethers.getContractFactory('Proposals'); const proposals = await Proposals.deploy(dao.target); - await dao.addPost(account1, 'some-content-id', []); - await dao.addPost(account2, 'some-other-content-id', []); + await dao.addPost([{ weightPercent: 100, authorAddress: account1 }], 'some-content-id', []); + await dao.addPost([{ weightPercent: 100, authorAddress: account2 }], 'some-other-content-id', []); const callbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []); await dao.initiateValidationPool( 'some-content-id', @@ -87,7 +87,10 @@ describe('Proposal', () => { }); it('Can submit a proposal', async () => { - // Nothing to do here -- this just tests our beforeEach + const postAuthors = await dao.getPostAuthors('proposal-content-id'); + expect(postAuthors).to.have.length(1); + expect(postAuthors[0].weightPercent).to.equal(100); + expect(postAuthors[0].authorAddress).to.equal(account1); }); it('Can attest for a proposal', async () => { diff --git a/ethereum/test/ValidationPools.js b/ethereum/test/ValidationPools.js index 0182413..b9e98eb 100644 --- a/ethereum/test/ValidationPools.js +++ b/ethereum/test/ValidationPools.js @@ -38,7 +38,7 @@ describe('Validation Pools', () => { beforeEach(async () => { ({ dao, account1, account2 } = await loadFixture(deploy)); - await dao.addPost(account1, 'content-id', []); + await dao.addPost([{ weightPercent: 100, authorAddress: account1 }], 'content-id', []); const init = () => initiateValidationPool({ fee: POOL_FEE }); await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(0); expect(await dao.validationPoolCount()).to.equal(1); @@ -192,7 +192,7 @@ describe('Validation Pools', () => { beforeEach(async () => { time.increase(POOL_DURATION + 1); await dao.evaluateOutcome(0); - await dao.addPost(account2, 'content-id-2', []); + await dao.addPost([{ weightPercent: 100, authorAddress: account2 }], 'content-id-2', []); const init = () => initiateValidationPool({ postId: 'content-id-2' }); await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(1); time.increase(POOL_DURATION + 1); diff --git a/ethereum/test/Work1.js b/ethereum/test/Work1.js index cfe2a4b..6ef52ce 100644 --- a/ethereum/test/Work1.js +++ b/ethereum/test/Work1.js @@ -19,7 +19,7 @@ describe('Work1', () => { const Work1 = await ethers.getContractFactory('Work1'); const work1 = await Work1.deploy(dao.target, proposals.target, WORK1_PRICE); - await dao.addPost(account1, 'some-content-id', []); + await dao.addPost([{ weightPercent: 100, authorAddress: account1 }], 'some-content-id', []); const callbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []); await dao.initiateValidationPool( 'some-content-id', @@ -212,9 +212,12 @@ describe('Work1', () => { expect(await dao.balanceOf(work1.target)).to.equal(0); expect(await dao.balanceOf(account1)).to.equal(100); const post = await dao.posts('evidence-content-id'); - expect(post.author).to.equal(account1); expect(post.sender).to.equal(work1.target); expect(post.id).to.equal('evidence-content-id'); + const postAuthors = await dao.getPostAuthors('evidence-content-id'); + expect(postAuthors).to.have.length(1); + expect(postAuthors[0].weightPercent).to.equal(100); + expect(postAuthors[0].authorAddress).to.equal(account1); const pool = await dao.validationPools(1); expect(pool.fee).to.equal(WORK1_PRICE); expect(pool.sender).to.equal(work1.target); diff --git a/frontend/src/utils/Post.js b/frontend/src/utils/Post.js index 019397e..38281c2 100644 --- a/frontend/src/utils/Post.js +++ b/frontend/src/utils/Post.js @@ -83,7 +83,10 @@ class Post { // Upload hash to blockchain async publish(DAO, account) { - await DAO.methods.addPost(account, this.hash, []).send({ + await DAO.methods.addPost([{ + weightPercent: 100, + authorAddress: account, + }], this.hash, []).send({ from: account, gas: 1000000, });