diff --git a/client/src/utils/Post.js b/client/src/utils/Post.js index 737a8b2..cdfd4b6 100644 --- a/client/src/utils/Post.js +++ b/client/src/utils/Post.js @@ -83,7 +83,7 @@ class Post { // Upload hash to blockchain async publish(DAO, account) { - await DAO.methods.addPost(account, this.hash).send({ + await DAO.methods.addPost(account, this.hash, []).send({ from: account, gas: 1000000, }); diff --git a/ethereum/contracts/Onboarding.sol b/ethereum/contracts/Onboarding.sol index bb498a2..2678ddd 100644 --- a/ethereum/contracts/Onboarding.sol +++ b/ethereum/contracts/Onboarding.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.24; import "./DAO.sol"; +import "./core/Forum.sol"; import "./WorkContract.sol"; import "./interfaces/IOnValidate.sol"; @@ -26,7 +27,11 @@ contract Onboarding is WorkContract, IOnValidate { request.status = WorkStatus.ApprovalSubmitted; request.approval = approval; // Make work evidence post - uint postIndex = dao.addPost(stake.worker, request.evidenceContentId); + uint postIndex = dao.addPost( + stake.worker, + request.evidenceContentId, + request.citations + ); emit WorkApprovalSubmitted(requestIndex, approval); // Initiate validation pool uint poolIndex = dao.initiateValidationPool{ @@ -69,9 +74,11 @@ contract Onboarding is WorkContract, IOnValidate { payable(request.customer).transfer(request.fee / 10); return 1; } + Citation[] memory emptyCitations; uint postIndex = dao.addPost( request.customer, - request.requestContentId + request.requestContentId, + emptyCitations ); dao.initiateValidationPool{value: request.fee / 10}( postIndex, diff --git a/ethereum/contracts/Proposals.sol b/ethereum/contracts/Proposals.sol index 1911f7d..39037dc 100644 --- a/ethereum/contracts/Proposals.sol +++ b/ethereum/contracts/Proposals.sol @@ -71,7 +71,9 @@ contract Proposals is DAOContract, IOnValidate { // TODO: Consider taking author as a parameter, // or else accepting a postIndex instead of contentId, // or support post lookup by contentId - uint postIndex = dao.addPost(author, contentId); + // TODO: Take citations as a parameter + Citation[] memory emptyCitations; + uint postIndex = dao.addPost(author, 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 3ec89b0..8d9515e 100644 --- a/ethereum/contracts/WorkContract.sol +++ b/ethereum/contracts/WorkContract.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.24; import "./DAO.sol"; import "./Proposals.sol"; +import "./core/Forum.sol"; import "./interfaces/IAcceptAvailability.sol"; import "./interfaces/IOnProposalAccepted.sol"; import "hardhat/console.sol"; @@ -33,6 +34,7 @@ abstract contract WorkContract is uint stakeIndex; string requestContentId; string evidenceContentId; + Citation[] citations; bool approval; } @@ -142,7 +144,8 @@ abstract contract WorkContract is /// Accept work evidence from worker function submitWorkEvidence( uint requestIndex, - string calldata evidenceContentId + string calldata evidenceContentId, + Citation[] calldata citations ) external { WorkRequest storage request = requests[requestIndex]; require( @@ -156,6 +159,9 @@ abstract contract WorkContract is ); request.status = WorkStatus.EvidenceSubmitted; request.evidenceContentId = evidenceContentId; + for (uint i = 0; i < citations.length; i++) { + request.citations.push(citations[i]); + } emit WorkEvidenceSubmitted(requestIndex); } @@ -173,7 +179,11 @@ abstract contract WorkContract is request.status = WorkStatus.ApprovalSubmitted; request.approval = approval; // Make work evidence post - uint postIndex = dao.addPost(stake.worker, request.evidenceContentId); + uint postIndex = dao.addPost( + stake.worker, + 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 65b5183..9da1e95 100644 --- a/ethereum/contracts/core/Forum.sol +++ b/ethereum/contracts/core/Forum.sol @@ -3,13 +3,18 @@ pragma solidity ^0.8.24; import "./Reputation.sol"; +struct Citation { + int[2] weight; + uint targetPostIndex; +} + struct Post { uint id; address sender; address author; string contentId; uint reputation; - // TODO: citations + Citation[] citations; } contract Forum is Reputation { @@ -20,7 +25,8 @@ contract Forum is Reputation { function addPost( address author, - string calldata contentId + string calldata contentId, + Citation[] calldata citations ) external returns (uint postIndex) { postIndex = postCount++; Post storage post = posts[postIndex]; @@ -28,6 +34,9 @@ contract Forum is Reputation { post.sender = msg.sender; post.id = postIndex; post.contentId = contentId; + for (uint i = 0; i < citations.length; i++) { + post.citations.push(citations[i]); + } emit PostAdded(postIndex); } @@ -36,4 +45,6 @@ contract Forum is Reputation { post.reputation = amount; _update(address(this), post.author, amount); } + + function _propagateValue() internal {} } diff --git a/ethereum/test/DAO.js b/ethereum/test/DAO.js index 20c19b0..8c2285d 100644 --- a/ethereum/test/DAO.js +++ b/ethereum/test/DAO.js @@ -23,7 +23,7 @@ describe('DAO', () => { it('should be able to add a post', async () => { const { dao, account1 } = await loadFixture(deploy); const contentId = 'some-id'; - await expect(dao.addPost(account1, contentId)).to.emit(dao, 'PostAdded').withArgs(0); + await expect(dao.addPost(account1, contentId, [])).to.emit(dao, 'PostAdded').withArgs(0); const post = await dao.posts(0); expect(post.author).to.equal(account1); expect(post.sender).to.equal(account1); @@ -33,7 +33,7 @@ describe('DAO', () => { it('should be able to add a post on behalf of another account', async () => { const { dao, account1, account2 } = await loadFixture(deploy); const contentId = 'some-id'; - await dao.addPost(account2, contentId); + await dao.addPost(account2, contentId, []); const post = await dao.posts(0); expect(post.author).to.equal(account2); expect(post.sender).to.equal(account1); @@ -68,7 +68,7 @@ describe('DAO', () => { beforeEach(async () => { ({ dao, account1, account2 } = await loadFixture(deploy)); - await dao.addPost(account1, 'content-id'); + await dao.addPost(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); @@ -232,7 +232,7 @@ describe('DAO', () => { beforeEach(async () => { time.increase(POOL_DURATION + 1); await dao.evaluateOutcome(0); - await dao.addPost(account2, 'content-id'); + await dao.addPost(account2, 'content-id', []); const init = () => initiateValidationPool({ postIndex: 1 }); await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(1); time.increase(POOL_DURATION + 1); diff --git a/ethereum/test/Onboarding.js b/ethereum/test/Onboarding.js index 2af7806..1ce5fa5 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(account1, 'content-id', []); const callbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []); await dao.initiateValidationPool( 0, @@ -66,7 +66,7 @@ describe('Onboarding', () => { it('should be able to submit work approval', async () => { await onboarding.connect(account2).requestWork('req-content-id', { value: PRICE }); - await onboarding.submitWorkEvidence(0, 'evidence-content-id'); + await onboarding.submitWorkEvidence(0, 'evidence-content-id', []); await expect(onboarding.submitWorkApproval(0, true)) .to.emit(dao, 'ValidationPoolInitiated').withArgs(1) .to.emit(onboarding, 'WorkApprovalSubmitted').withArgs(0, true); @@ -82,7 +82,7 @@ describe('Onboarding', () => { it('should be able to submit work disapproval', async () => { await onboarding.connect(account2).requestWork('req-content-id', { value: PRICE }); - await onboarding.submitWorkEvidence(0, 'evidence-content-id'); + await onboarding.submitWorkEvidence(0, 'evidence-content-id', []); await expect(onboarding.submitWorkApproval(0, false)) .to.emit(dao, 'ValidationPoolInitiated').withArgs(1) .to.emit(onboarding, 'WorkApprovalSubmitted').withArgs(0, false); @@ -90,16 +90,16 @@ describe('Onboarding', () => { it('should not be able to submit work approval/disapproval twice', async () => { await onboarding.connect(account2).requestWork('req-content-id', { value: PRICE }); - await onboarding.submitWorkEvidence(0, 'evidence-content-id'); + await onboarding.submitWorkEvidence(0, 'evidence-content-id', []); await expect(onboarding.submitWorkApproval(0, true)).to.emit(dao, 'ValidationPoolInitiated').withArgs(1); await expect(onboarding.submitWorkApproval(0, true)).to.be.revertedWith('Status must be EvidenceSubmitted'); }); it('should not be able to submit work evidence after work approval', async () => { await onboarding.connect(account2).requestWork('req-content-id', { value: PRICE }); - await onboarding.submitWorkEvidence(0, 'evidence-content-id'); + await onboarding.submitWorkEvidence(0, 'evidence-content-id', []); await expect(onboarding.submitWorkApproval(0, true)).to.emit(dao, 'ValidationPoolInitiated').withArgs(1); - await expect(onboarding.submitWorkEvidence(0, 'evidence-content-id')).to.be.revertedWith('Status must be Requested'); + await expect(onboarding.submitWorkEvidence(0, 'evidence-content-id', [])).to.be.revertedWith('Status must be Requested'); }); it('should not be able to submit work approval/disapproval before work evidence', async () => { @@ -115,7 +115,7 @@ describe('Onboarding', () => { } = await loadFixture(deploy); await dao.stakeAvailability(onboarding.target, 50, STAKE_DURATION); await onboarding.connect(account2).requestWork('req-content-id', { value: PRICE }); - await onboarding.submitWorkEvidence(0, 'evidence-content-id'); + await onboarding.submitWorkEvidence(0, 'evidence-content-id', []); await expect(onboarding.submitWorkApproval(0, true)).to.emit(dao, 'ValidationPoolInitiated').withArgs(1); await time.increase(86401); await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolInitiated').withArgs(2); @@ -137,7 +137,7 @@ describe('Onboarding', () => { } = await loadFixture(deploy); await dao.stakeAvailability(onboarding.target, 40, STAKE_DURATION); await onboarding.connect(account2).requestWork('req-content-id', { value: PRICE }); - await onboarding.submitWorkEvidence(0, 'evidence-content-id'); + await onboarding.submitWorkEvidence(0, 'evidence-content-id', []); await expect(onboarding.submitWorkApproval(0, true)).to.emit(dao, 'ValidationPoolInitiated').withArgs(1); await dao.stakeOnValidationPool(1, 60, false); await time.increase(86401); diff --git a/ethereum/test/Proposals.js b/ethereum/test/Proposals.js index f43cd45..e399fc1 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(account1, 'some-content-id', []); + await dao.addPost(account2, 'some-other-content-id', []); const callbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []); await dao.initiateValidationPool( 0, diff --git a/ethereum/test/Work1.js b/ethereum/test/Work1.js index 1ab09c5..153dc4c 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(account1, 'some-content-id', []); const callbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []); await dao.initiateValidationPool( 0, @@ -186,24 +186,24 @@ describe('Work1', () => { it('should be able to submit work evidence', async () => { await work1.connect(account2).requestWork('req-content-id', { value: WORK1_PRICE }); - await expect(work1.submitWorkEvidence(0, 'evidence-content-id')).to.emit(work1, 'WorkEvidenceSubmitted').withArgs(0); + await expect(work1.submitWorkEvidence(0, 'evidence-content-id', [])).to.emit(work1, 'WorkEvidenceSubmitted').withArgs(0); }); it('should not be able to submit work evidence twice', async () => { await work1.connect(account2).requestWork('req-content-id', { value: WORK1_PRICE }); - await expect(work1.submitWorkEvidence(0, 'evidence-content-id')).to.emit(work1, 'WorkEvidenceSubmitted').withArgs(0); - await expect(work1.submitWorkEvidence(0, 'evidence-content-id')).to.be.revertedWith('Status must be Requested'); + await expect(work1.submitWorkEvidence(0, 'evidence-content-id', [])).to.emit(work1, 'WorkEvidenceSubmitted').withArgs(0); + await expect(work1.submitWorkEvidence(0, 'evidence-content-id', [])).to.be.revertedWith('Status must be Requested'); }); it('should not be able to submit work evidence for a different worker', async () => { await work1.connect(account2).requestWork('req-content-id', { value: WORK1_PRICE }); - await expect(work1.connect(account2).submitWorkEvidence(0, 'evidence-content-id')) + await expect(work1.connect(account2).submitWorkEvidence(0, 'evidence-content-id', [])) .to.be.revertedWith('Worker can only submit evidence for work they are assigned'); }); it('should be able to submit work approval', async () => { await work1.connect(account2).requestWork('req-content-id', { value: WORK1_PRICE }); - await work1.submitWorkEvidence(0, 'evidence-content-id'); + await work1.submitWorkEvidence(0, 'evidence-content-id', []); expect(await dao.balanceOf(account1)).to.equal(100); expect(await dao.balanceOf(work1.target)).to.equal(0); await expect(work1.submitWorkApproval(0, true)) @@ -227,7 +227,7 @@ describe('Work1', () => { it('should be able to submit work disapproval', async () => { await work1.connect(account2).requestWork('req-content-id', { value: WORK1_PRICE }); - await work1.submitWorkEvidence(0, 'evidence-content-id'); + await work1.submitWorkEvidence(0, 'evidence-content-id', []); await expect(work1.submitWorkApproval(0, false)) .to.emit(dao, 'ValidationPoolInitiated').withArgs(1) .to.emit(work1, 'WorkApprovalSubmitted').withArgs(0, false); @@ -235,16 +235,16 @@ describe('Work1', () => { it('should not be able to submit work approval/disapproval twice', async () => { await work1.connect(account2).requestWork('req-content-id', { value: WORK1_PRICE }); - await work1.submitWorkEvidence(0, 'evidence-content-id'); + await work1.submitWorkEvidence(0, 'evidence-content-id', []); await expect(work1.submitWorkApproval(0, true)).to.emit(dao, 'ValidationPoolInitiated').withArgs(1); await expect(work1.submitWorkApproval(0, true)).to.be.revertedWith('Status must be EvidenceSubmitted'); }); it('should not be able to submit work evidence after work approval', async () => { await work1.connect(account2).requestWork('req-content-id', { value: WORK1_PRICE }); - await work1.submitWorkEvidence(0, 'evidence-content-id'); + await work1.submitWorkEvidence(0, 'evidence-content-id', []); await expect(work1.submitWorkApproval(0, true)).to.emit(dao, 'ValidationPoolInitiated').withArgs(1); - await expect(work1.submitWorkEvidence(0, 'evidence-content-id')).to.be.revertedWith('Status must be Requested'); + await expect(work1.submitWorkEvidence(0, 'evidence-content-id', [])).to.be.revertedWith('Status must be Requested'); }); it('should not be able to submit work approval/disapproval before work evidence', async () => {