update DAO to use global forum
This commit is contained in:
parent
ed928043ed
commit
da20410f87
|
@ -20,17 +20,16 @@ struct Post {
|
|||
}
|
||||
|
||||
contract GlobalForum {
|
||||
mapping(string => Post) public posts;
|
||||
mapping(string => Post) posts;
|
||||
string[] public postIds;
|
||||
uint public postCount;
|
||||
|
||||
event PostAdded(string id);
|
||||
|
||||
function addPost(
|
||||
string calldata postId,
|
||||
Author[] calldata authors,
|
||||
Reference[] calldata references,
|
||||
string calldata content
|
||||
string calldata postId,
|
||||
Reference[] calldata references
|
||||
) external {
|
||||
require(authors.length > 0, "Post must include at least one author");
|
||||
postCount++;
|
||||
|
@ -42,7 +41,6 @@ contract GlobalForum {
|
|||
);
|
||||
post.sender = msg.sender;
|
||||
post.id = postId;
|
||||
post.content = content;
|
||||
uint authorTotalWeightPPM;
|
||||
for (uint i = 0; i < authors.length; i++) {
|
||||
authorTotalWeightPPM += authors[i].weightPPM;
|
||||
|
@ -87,4 +85,21 @@ contract GlobalForum {
|
|||
Post storage post = posts[postId];
|
||||
return post.authors;
|
||||
}
|
||||
|
||||
function getPost(
|
||||
string calldata postId
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
Author[] memory authors,
|
||||
Reference[] memory references,
|
||||
address sender
|
||||
)
|
||||
{
|
||||
Post storage post = posts[postId];
|
||||
authors = post.authors;
|
||||
references = post.references;
|
||||
sender = post.sender;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,16 +2,16 @@
|
|||
pragma solidity ^0.8.24;
|
||||
|
||||
import "./core/DAO.sol";
|
||||
import "./core/Forum.sol";
|
||||
import "./Work.sol";
|
||||
import "./interfaces/IOnValidate.sol";
|
||||
|
||||
contract Onboarding is Work, IOnValidate {
|
||||
constructor(
|
||||
DAO dao_,
|
||||
Proposals proposals_,
|
||||
uint price_
|
||||
) Work(dao_, proposals_, price_) {}
|
||||
DAO dao,
|
||||
GlobalForum forum,
|
||||
Proposals proposals,
|
||||
uint price
|
||||
) Work(dao, forum, proposals, price) {}
|
||||
|
||||
/// Accept work approval/disapproval from customer
|
||||
function submitWorkApproval(
|
||||
|
@ -29,7 +29,7 @@ contract Onboarding is Work, IOnValidate {
|
|||
// Make work evidence post
|
||||
Author[] memory authors = new Author[](1);
|
||||
authors[0] = Author(1000000, stake.worker);
|
||||
dao.addPost(authors, request.evidencePostId, request.references);
|
||||
forum.addPost(authors, request.evidencePostId, request.references);
|
||||
emit WorkApprovalSubmitted(requestIndex, approval);
|
||||
// Initiate validation pool
|
||||
uint poolIndex = dao.initiateValidationPool{
|
||||
|
@ -76,7 +76,7 @@ contract Onboarding is Work, IOnValidate {
|
|||
Reference[] memory emptyReferences;
|
||||
Author[] memory authors = new Author[](1);
|
||||
authors[0] = Author(1000000, request.customer);
|
||||
dao.addPost(authors, request.requestPostId, emptyReferences);
|
||||
forum.addPost(authors, request.requestPostId, emptyReferences);
|
||||
dao.initiateValidationPool{value: request.fee / 10}(
|
||||
request.requestPostId,
|
||||
POOL_DURATION,
|
||||
|
|
|
@ -59,7 +59,7 @@ contract Proposals is DAOContract, IOnValidate {
|
|||
|
||||
// TODO receive : we want to be able to accept refunds from validation pools
|
||||
|
||||
/// Submit a post as a proposal. DAO.addPost should be called before this.
|
||||
/// Submit a post as a proposal. forum.addPost should be called before this.
|
||||
function propose(
|
||||
string calldata postId,
|
||||
uint[3] calldata durations,
|
||||
|
|
|
@ -9,10 +9,11 @@ abstract contract RollableWork is Work {
|
|||
|
||||
constructor(
|
||||
DAO dao,
|
||||
GlobalForum forum,
|
||||
Proposals proposalsContract,
|
||||
Rollup rollupContract_,
|
||||
uint price
|
||||
) Work(dao, proposalsContract, price) {
|
||||
) Work(dao, forum, proposalsContract, price) {
|
||||
rollupContract = rollupContract_;
|
||||
}
|
||||
|
||||
|
@ -34,7 +35,7 @@ abstract contract RollableWork is Work {
|
|||
// Make work evidence post
|
||||
Author[] memory authors = new Author[](1);
|
||||
authors[0] = Author(1000000, stake.worker);
|
||||
dao.addPost(authors, request.evidencePostId, request.references);
|
||||
forum.addPost(authors, request.evidencePostId, request.references);
|
||||
|
||||
// send worker stakes and customer fee to rollup contract
|
||||
dao.forwardAllowance(
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
pragma solidity ^0.8.24;
|
||||
|
||||
import "./core/DAO.sol";
|
||||
import "./core/Forum.sol";
|
||||
import "./Availability.sol";
|
||||
import "./Proposals.sol";
|
||||
import "./interfaces/IOnProposalAccepted.sol";
|
||||
|
@ -31,6 +30,7 @@ abstract contract Work is Availability, IOnProposalAccepted {
|
|||
uint proposalIndex;
|
||||
}
|
||||
|
||||
GlobalForum forum;
|
||||
Proposals proposalsContract;
|
||||
uint public price;
|
||||
mapping(uint => PriceProposal) public priceProposals;
|
||||
|
@ -48,11 +48,13 @@ abstract contract Work is Availability, IOnProposalAccepted {
|
|||
|
||||
constructor(
|
||||
DAO dao,
|
||||
GlobalForum forum_,
|
||||
Proposals proposalsContract_,
|
||||
uint price_
|
||||
) Availability(dao) {
|
||||
price = price_;
|
||||
proposalsContract = proposalsContract_;
|
||||
forum = forum_;
|
||||
}
|
||||
|
||||
/// Accept work request with fee
|
||||
|
@ -107,7 +109,7 @@ abstract contract Work is Availability, IOnProposalAccepted {
|
|||
// Make work evidence post
|
||||
Author[] memory authors = new Author[](1);
|
||||
authors[0] = Author(1000000, stake.worker);
|
||||
dao.addPost(authors, request.evidencePostId, request.references);
|
||||
forum.addPost(authors, request.evidencePostId, request.references);
|
||||
emit WorkApprovalSubmitted(requestIndex, approval);
|
||||
// Initiate validation pool
|
||||
uint poolIndex = dao.initiateValidationPool{value: request.fee}(
|
||||
|
|
|
@ -7,8 +7,9 @@ import "./Proposals.sol";
|
|||
|
||||
contract Work1 is Work {
|
||||
constructor(
|
||||
DAO dao_,
|
||||
Proposals proposals_,
|
||||
uint price_
|
||||
) Work(dao_, proposals_, price_) {}
|
||||
DAO dao,
|
||||
GlobalForum forum,
|
||||
Proposals proposals,
|
||||
uint price
|
||||
) Work(dao, forum, proposals, price) {}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,9 @@ import "./Rollup.sol";
|
|||
contract Work2 is RollableWork {
|
||||
constructor(
|
||||
DAO dao,
|
||||
GlobalForum forum,
|
||||
Proposals proposals,
|
||||
Rollup rollup,
|
||||
uint price
|
||||
) RollableWork(dao, proposals, rollup, price) {}
|
||||
) RollableWork(dao, forum, proposals, rollup, price) {}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
pragma solidity ^0.8.24;
|
||||
|
||||
import "./DAO.sol";
|
||||
import "./Forum.sol";
|
||||
import "../GlobalForum.sol";
|
||||
|
||||
struct ValidationPoolParams {
|
||||
uint duration;
|
||||
|
@ -43,17 +43,26 @@ contract Bench {
|
|||
mapping(uint => Pool) public validationPools;
|
||||
uint public validationPoolCount;
|
||||
DAO dao;
|
||||
GlobalForum forum;
|
||||
|
||||
// Validation Pool parameters
|
||||
uint constant minDuration = 1; // 1 second
|
||||
uint constant maxDuration = 365_000_000 days; // 1 million years
|
||||
uint[2] minQuorum = [1, 10];
|
||||
|
||||
function registerDAO(DAO dao_) external {
|
||||
// Forum parameters
|
||||
// TODO: Make depth limit configurable; take as param
|
||||
uint depthLimit = 3;
|
||||
|
||||
mapping(string => mapping(string => int)) _edgeBalances;
|
||||
|
||||
function registerDAO(DAO dao_, GlobalForum forum_) external {
|
||||
require(
|
||||
address(dao) == address(0),
|
||||
"A DAO has already been registered"
|
||||
);
|
||||
dao = dao_;
|
||||
forum = forum_;
|
||||
}
|
||||
|
||||
/// Register a stake for/against a validation pool
|
||||
|
@ -245,9 +254,11 @@ contract Bench {
|
|||
}
|
||||
|
||||
// Transfer REP to the forum instead of to the author directly
|
||||
dao.propagateReputation(
|
||||
propagateReputation(
|
||||
pool.props.postId,
|
||||
int(pool.props.minted / 2 + remainder)
|
||||
int(pool.props.minted / 2 + remainder),
|
||||
false,
|
||||
0
|
||||
);
|
||||
} else {
|
||||
// If vote does not pass, divide the losing stake among the winners
|
||||
|
@ -283,4 +294,126 @@ contract Bench {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
function _handleReference(
|
||||
string memory postId,
|
||||
Reference memory ref,
|
||||
int amount,
|
||||
bool initialNegative,
|
||||
uint depth
|
||||
) internal returns (int outboundAmount) {
|
||||
outboundAmount = (amount * ref.weightPPM) / 1000000;
|
||||
if (bytes(ref.targetPostId).length == 0) {
|
||||
// Incineration
|
||||
require(
|
||||
outboundAmount >= 0,
|
||||
"Leaching from incinerator is forbidden"
|
||||
);
|
||||
dao.burn(address(dao), uint(outboundAmount));
|
||||
return outboundAmount;
|
||||
}
|
||||
int balanceToOutbound = _edgeBalances[postId][ref.targetPostId];
|
||||
if (initialNegative) {
|
||||
if (outboundAmount < 0) {
|
||||
outboundAmount = outboundAmount > -balanceToOutbound
|
||||
? outboundAmount
|
||||
: -balanceToOutbound;
|
||||
} else {
|
||||
outboundAmount = outboundAmount < -balanceToOutbound
|
||||
? outboundAmount
|
||||
: -balanceToOutbound;
|
||||
}
|
||||
}
|
||||
int refund = propagateReputation(
|
||||
ref.targetPostId,
|
||||
outboundAmount,
|
||||
initialNegative || (depth == 0 && ref.weightPPM < 0),
|
||||
depth + 1
|
||||
);
|
||||
outboundAmount -= refund;
|
||||
_edgeBalances[postId][ref.targetPostId] += outboundAmount;
|
||||
}
|
||||
|
||||
function _distributeAmongAuthors(
|
||||
Author[] memory authors,
|
||||
int amount
|
||||
) internal returns (int refund) {
|
||||
int allocated;
|
||||
|
||||
for (uint i = 0; i < authors.length; i++) {
|
||||
dao.registerMember(authors[i].authorAddress);
|
||||
}
|
||||
for (uint i = 0; i < authors.length; i++) {
|
||||
Author memory author = authors[i];
|
||||
int share;
|
||||
if (i < authors.length - 1) {
|
||||
share = (amount * int(author.weightPPM)) / 1000000;
|
||||
allocated += share;
|
||||
} else {
|
||||
// For the last author, allocate the remainder.
|
||||
share = amount - allocated;
|
||||
}
|
||||
if (share > 0) {
|
||||
dao.update(address(dao), author.authorAddress, uint(share));
|
||||
} else if (dao.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
|
||||
uint authorBalance = dao.balanceOf(author.authorAddress);
|
||||
refund += share + int(authorBalance);
|
||||
dao.update(
|
||||
author.authorAddress,
|
||||
address(dao),
|
||||
dao.balanceOf(author.authorAddress)
|
||||
);
|
||||
} else {
|
||||
dao.update(author.authorAddress, address(dao), uint(-share));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function propagateReputation(
|
||||
string memory postId,
|
||||
int amount,
|
||||
bool initialNegative,
|
||||
uint depth
|
||||
) internal returns (int refundToInbound) {
|
||||
if (depth >= depthLimit) {
|
||||
return amount;
|
||||
}
|
||||
Reference[] memory references;
|
||||
Author[] memory authors;
|
||||
address sender;
|
||||
(authors, references, sender) = forum.getPost(postId);
|
||||
if (authors.length == 0) {
|
||||
// We most likely got here via a reference to a post that hasn't been added yet.
|
||||
// We support this scenario so that a reference graph can be imported one post at a time.
|
||||
return amount;
|
||||
}
|
||||
// Propagate negative references first
|
||||
for (uint i = 0; i < references.length; i++) {
|
||||
if (references[i].weightPPM < 0) {
|
||||
amount -= _handleReference(
|
||||
postId,
|
||||
references[i],
|
||||
amount,
|
||||
initialNegative,
|
||||
depth
|
||||
);
|
||||
}
|
||||
}
|
||||
// Now propagate positive references
|
||||
for (uint i = 0; i < references.length; i++) {
|
||||
if (references[i].weightPPM > 0) {
|
||||
amount -= _handleReference(
|
||||
postId,
|
||||
references[i],
|
||||
amount,
|
||||
initialNegative,
|
||||
depth
|
||||
);
|
||||
}
|
||||
}
|
||||
refundToInbound = _distributeAmongAuthors(authors, amount);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ pragma solidity ^0.8.24;
|
|||
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "./Reputation.sol";
|
||||
import "./Bench.sol";
|
||||
import "./Forum.sol";
|
||||
import "../GlobalForum.sol";
|
||||
import "../interfaces/IAcceptAvailability.sol";
|
||||
import "../interfaces/IOnValidate.sol";
|
||||
|
||||
|
@ -12,27 +12,36 @@ import "hardhat/console.sol";
|
|||
|
||||
contract DAO {
|
||||
Reputation rep;
|
||||
Forum forum;
|
||||
GlobalForum forum;
|
||||
Bench bench;
|
||||
mapping(uint => address) public members;
|
||||
uint public memberCount;
|
||||
mapping(address => bool) public isMember;
|
||||
|
||||
event PostAdded(string id);
|
||||
event ValidationPoolInitiated(uint poolIndex);
|
||||
event ValidationPoolResolved(
|
||||
uint poolIndex,
|
||||
bool votePasses,
|
||||
bool quorumMet
|
||||
);
|
||||
event PostAdded(string id);
|
||||
event LWValidationPoolInitiated(uint poolIndex);
|
||||
event LWValidationPoolResolved(
|
||||
uint poolIndex,
|
||||
bool votePasses,
|
||||
bool quorumMet
|
||||
);
|
||||
|
||||
constructor(Reputation reputation_, Forum forum_, Bench bench_) {
|
||||
constructor(Reputation reputation_, Bench bench_, GlobalForum forum_) {
|
||||
rep = reputation_;
|
||||
forum = forum_;
|
||||
bench = bench_;
|
||||
forum = forum_;
|
||||
rep.registerDAO(this);
|
||||
forum.registerDAO(this);
|
||||
bench.registerDAO(this);
|
||||
bench.registerDAO(this, forum);
|
||||
}
|
||||
|
||||
function emitPostAdded(string memory id) public {
|
||||
emit PostAdded(id);
|
||||
}
|
||||
|
||||
function emitValidationPoolInitiated(uint poolIndex) public {
|
||||
|
@ -47,13 +56,13 @@ contract DAO {
|
|||
emit ValidationPoolResolved(poolIndex, votePasses, quorumMet);
|
||||
}
|
||||
|
||||
function emitPostAdded(string memory id) public {
|
||||
emit PostAdded(id);
|
||||
function emitLWValidationPoolInitiated(uint poolIndex) public {
|
||||
emit LWValidationPoolInitiated(poolIndex);
|
||||
}
|
||||
|
||||
function update(address from, address to, uint256 value) public {
|
||||
require(
|
||||
msg.sender == address(forum) || msg.sender == address(bench),
|
||||
msg.sender == address(bench),
|
||||
"Only DAO core contracts may call update"
|
||||
);
|
||||
rep.update(from, to, value);
|
||||
|
@ -61,7 +70,7 @@ contract DAO {
|
|||
|
||||
function mint(address account, uint256 value) public {
|
||||
require(
|
||||
msg.sender == address(forum) || msg.sender == address(bench),
|
||||
msg.sender == address(bench),
|
||||
"Only DAO core contracts may call mint"
|
||||
);
|
||||
rep.mint(account, value);
|
||||
|
@ -69,7 +78,7 @@ contract DAO {
|
|||
|
||||
function burn(address account, uint256 value) public {
|
||||
require(
|
||||
msg.sender == address(forum) || msg.sender == address(bench),
|
||||
msg.sender == address(bench),
|
||||
"Only DAO core contracts may call burn"
|
||||
);
|
||||
rep.burn(account, value);
|
||||
|
@ -77,7 +86,7 @@ contract DAO {
|
|||
|
||||
function registerMember(address account) public {
|
||||
require(
|
||||
msg.sender == address(forum) || msg.sender == address(bench),
|
||||
msg.sender == address(bench),
|
||||
"Only DAO core contracts may call registerMember"
|
||||
);
|
||||
if (!isMember[account]) {
|
||||
|
@ -129,10 +138,6 @@ contract DAO {
|
|||
return true;
|
||||
}
|
||||
|
||||
function propagateReputation(string memory postId, int amount) public {
|
||||
forum.propagateReputation(postId, amount, false, 0);
|
||||
}
|
||||
|
||||
function distributeFeeAmongMembers() public payable {
|
||||
uint allocated;
|
||||
for (uint i = 0; i < memberCount; i++) {
|
||||
|
@ -236,7 +241,7 @@ contract DAO {
|
|||
bytes calldata callbackData
|
||||
) public {
|
||||
require(
|
||||
msg.sender == address(forum) || msg.sender == address(bench),
|
||||
msg.sender == address(bench),
|
||||
"Only DAO core contracts may call onValidate"
|
||||
);
|
||||
IOnValidate(target).onValidate(
|
||||
|
@ -247,34 +252,6 @@ contract DAO {
|
|||
callbackData
|
||||
);
|
||||
}
|
||||
|
||||
function addPost(
|
||||
Author[] calldata authors,
|
||||
string calldata postId,
|
||||
Reference[] calldata references
|
||||
) public {
|
||||
forum.addPost(msg.sender, authors, postId, references);
|
||||
}
|
||||
|
||||
function posts(
|
||||
string calldata postId
|
||||
) public view returns (string memory id, address sender, uint reputation) {
|
||||
return forum.posts(postId);
|
||||
}
|
||||
|
||||
function postCount() public view returns (uint) {
|
||||
return forum.postCount();
|
||||
}
|
||||
|
||||
function postIds(uint postIndex) public view returns (string memory) {
|
||||
return forum.postIds(postIndex);
|
||||
}
|
||||
|
||||
function getPostAuthors(
|
||||
string calldata postId
|
||||
) public view returns (Author[] memory) {
|
||||
return forum.getPostAuthors(postId);
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience contract to extend for other contracts that will be initialized to
|
||||
|
|
|
@ -1,251 +0,0 @@
|
|||
// SPDX-License-Identifier: Unlicense
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
import "./DAO.sol";
|
||||
|
||||
struct Reference {
|
||||
int weightPPM;
|
||||
string targetPostId;
|
||||
}
|
||||
|
||||
struct Author {
|
||||
uint weightPPM;
|
||||
address authorAddress;
|
||||
}
|
||||
|
||||
struct Post {
|
||||
string id;
|
||||
address sender;
|
||||
Author[] authors;
|
||||
Reference[] references;
|
||||
uint reputation;
|
||||
// TODO: timestamp
|
||||
}
|
||||
|
||||
contract Forum {
|
||||
mapping(string => Post) public posts;
|
||||
string[] public postIds;
|
||||
uint public postCount;
|
||||
mapping(string => mapping(string => int)) _edgeBalances;
|
||||
DAO dao;
|
||||
|
||||
event PostAdded(string id);
|
||||
|
||||
// Forum parameters
|
||||
// TODO: Make depth limit configurable; take as param in _onValidatePost callback
|
||||
uint depthLimit = 3;
|
||||
|
||||
function registerDAO(DAO dao_) external {
|
||||
require(
|
||||
address(dao) == address(0),
|
||||
"A DAO has already been registered"
|
||||
);
|
||||
dao = dao_;
|
||||
}
|
||||
|
||||
function addPost(
|
||||
address sender,
|
||||
Author[] calldata authors,
|
||||
string calldata postId,
|
||||
Reference[] calldata references
|
||||
) external {
|
||||
require(
|
||||
msg.sender == address(dao),
|
||||
"Only DAO contract may call addPost"
|
||||
);
|
||||
require(authors.length > 0, "Post must include at least one author");
|
||||
postCount++;
|
||||
postIds.push(postId);
|
||||
Post storage post = posts[postId];
|
||||
require(
|
||||
post.authors.length == 0,
|
||||
"A post with this postId already exists"
|
||||
);
|
||||
post.sender = sender;
|
||||
post.id = postId;
|
||||
uint authorTotalWeightPPM;
|
||||
for (uint i = 0; i < authors.length; i++) {
|
||||
authorTotalWeightPPM += authors[i].weightPPM;
|
||||
post.authors.push(authors[i]);
|
||||
}
|
||||
require(
|
||||
authorTotalWeightPPM == 1000000,
|
||||
"Author weights must sum to 1000000"
|
||||
);
|
||||
for (uint i = 0; i < references.length; i++) {
|
||||
post.references.push(references[i]);
|
||||
}
|
||||
int totalReferenceWeightPos;
|
||||
int totalReferenceWeightNeg;
|
||||
for (uint i = 0; i < post.references.length; i++) {
|
||||
int weight = post.references[i].weightPPM;
|
||||
require(
|
||||
weight >= -1000000,
|
||||
"Each reference weight must be >= -1000000"
|
||||
);
|
||||
require(
|
||||
weight <= 1000000,
|
||||
"Each reference weight must be <= 1000000"
|
||||
);
|
||||
if (weight > 0) totalReferenceWeightPos += weight;
|
||||
else totalReferenceWeightNeg += weight;
|
||||
}
|
||||
require(
|
||||
totalReferenceWeightPos <= 1000000,
|
||||
"Sum of positive references must be <= 1000000"
|
||||
);
|
||||
require(
|
||||
totalReferenceWeightNeg >= -1000000,
|
||||
"Sum of negative references must be >= -1000000"
|
||||
);
|
||||
dao.emitPostAdded(postId);
|
||||
}
|
||||
|
||||
function getPostAuthors(
|
||||
string calldata postId
|
||||
) external view returns (Author[] memory) {
|
||||
Post storage post = posts[postId];
|
||||
return post.authors;
|
||||
}
|
||||
|
||||
function _handleReference(
|
||||
string memory postId,
|
||||
Reference memory ref,
|
||||
int amount,
|
||||
bool initialNegative,
|
||||
uint depth
|
||||
) internal returns (int outboundAmount) {
|
||||
outboundAmount = (amount * ref.weightPPM) / 1000000;
|
||||
if (bytes(ref.targetPostId).length == 0) {
|
||||
// Incineration
|
||||
require(
|
||||
outboundAmount >= 0,
|
||||
"Leaching from incinerator is forbidden"
|
||||
);
|
||||
dao.burn(address(dao), uint(outboundAmount));
|
||||
return outboundAmount;
|
||||
}
|
||||
int balanceToOutbound = _edgeBalances[postId][ref.targetPostId];
|
||||
if (initialNegative) {
|
||||
if (outboundAmount < 0) {
|
||||
outboundAmount = outboundAmount > -balanceToOutbound
|
||||
? outboundAmount
|
||||
: -balanceToOutbound;
|
||||
} else {
|
||||
outboundAmount = outboundAmount < -balanceToOutbound
|
||||
? outboundAmount
|
||||
: -balanceToOutbound;
|
||||
}
|
||||
}
|
||||
int refund = propagateReputation(
|
||||
ref.targetPostId,
|
||||
outboundAmount,
|
||||
initialNegative || (depth == 0 && ref.weightPPM < 0),
|
||||
depth + 1
|
||||
);
|
||||
outboundAmount -= refund;
|
||||
_edgeBalances[postId][ref.targetPostId] += outboundAmount;
|
||||
}
|
||||
|
||||
function _distributeAmongAuthors(
|
||||
Post memory post,
|
||||
int amount
|
||||
) internal returns (int refund) {
|
||||
int allocated;
|
||||
|
||||
for (uint i = 0; i < post.authors.length; i++) {
|
||||
dao.registerMember(post.authors[i].authorAddress);
|
||||
}
|
||||
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.weightPPM)) / 1000000;
|
||||
allocated += share;
|
||||
} else {
|
||||
// For the last author, allocate the remainder.
|
||||
share = amount - allocated;
|
||||
}
|
||||
if (share > 0) {
|
||||
dao.update(address(dao), author.authorAddress, uint(share));
|
||||
dao.registerMember(author.authorAddress);
|
||||
} else if (dao.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(dao.balanceOf(author.authorAddress));
|
||||
dao.update(
|
||||
author.authorAddress,
|
||||
address(dao),
|
||||
dao.balanceOf(author.authorAddress)
|
||||
);
|
||||
} else {
|
||||
dao.update(author.authorAddress, address(dao), uint(-share));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function propagateReputation(
|
||||
string memory postId,
|
||||
int amount,
|
||||
bool initialNegative,
|
||||
uint depth
|
||||
) public returns (int refundToInbound) {
|
||||
require(
|
||||
msg.sender == address(dao) || msg.sender == address(this),
|
||||
"Only DAO contract may call propagateReputation"
|
||||
);
|
||||
if (depth >= depthLimit) {
|
||||
return amount;
|
||||
}
|
||||
Post storage post = posts[postId];
|
||||
if (post.authors.length == 0) {
|
||||
// We most likely got here via a reference to a post that hasn't been added yet.
|
||||
// We support this scenario so that a reference graph can be imported one post at a time.
|
||||
return amount;
|
||||
}
|
||||
// Propagate negative references first
|
||||
for (uint i = 0; i < post.references.length; i++) {
|
||||
if (post.references[i].weightPPM < 0) {
|
||||
amount -= _handleReference(
|
||||
postId,
|
||||
post.references[i],
|
||||
amount,
|
||||
initialNegative,
|
||||
depth
|
||||
);
|
||||
}
|
||||
}
|
||||
// Now propagate positive references
|
||||
for (uint i = 0; i < post.references.length; i++) {
|
||||
if (post.references[i].weightPPM > 0) {
|
||||
amount -= _handleReference(
|
||||
postId,
|
||||
post.references[i],
|
||||
amount,
|
||||
initialNegative,
|
||||
depth
|
||||
);
|
||||
}
|
||||
}
|
||||
if (amount > 0) {
|
||||
_distributeAmongAuthors(post, amount);
|
||||
post.reputation += uint(amount);
|
||||
} else {
|
||||
if (int(post.reputation) + amount >= 0) {
|
||||
// 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;
|
||||
refundToInbound += _distributeAmongAuthors(
|
||||
post,
|
||||
-int(post.reputation)
|
||||
);
|
||||
post.reputation = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
const deployCoreContracts = require('./util/deploy-core-contracts');
|
||||
const deployDAOCoreContracts = require('./util/deploy-core-contracts');
|
||||
|
||||
async function main() {
|
||||
await deployCoreContracts();
|
||||
await deployDAOCoreContracts();
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
require('dotenv').config();
|
||||
const deployContract = require('./util/deploy-contract');
|
||||
const deployDAOContract = require('./util/deploy-dao-contract');
|
||||
const deployWorkContract = require('./util/deploy-work-contract');
|
||||
const deployRollableWorkContract = require('./util/deploy-rollable-work-contract');
|
||||
const deployCoreContracts = require('./util/deploy-core-contracts');
|
||||
const deployDAOCoreContracts = require('./util/deploy-core-contracts');
|
||||
|
||||
const { ROLLUP_INTERVAL } = process.env;
|
||||
|
||||
async function main() {
|
||||
await deployCoreContracts();
|
||||
await deployContract('GlobalForum');
|
||||
await deployDAOCoreContracts();
|
||||
await deployDAOContract('Rollup', [ROLLUP_INTERVAL]);
|
||||
await deployDAOContract('Proposals');
|
||||
await deployWorkContract('Work1');
|
||||
|
|
|
@ -4,15 +4,14 @@ const contractAddresses = require('../../contract-addresses.json');
|
|||
|
||||
const network = process.env.HARDHAT_NETWORK;
|
||||
|
||||
const deployCoreContracts = async () => {
|
||||
const deployDAOCoreContracts = async () => {
|
||||
await deployContract('Reputation', [], true);
|
||||
await deployContract('Forum', [], true);
|
||||
await deployContract('Bench', [], true);
|
||||
await deployContract('DAO', [
|
||||
contractAddresses[network].Reputation,
|
||||
contractAddresses[network].Forum,
|
||||
contractAddresses[network].GlobalForum,
|
||||
contractAddresses[network].Bench,
|
||||
], true);
|
||||
};
|
||||
|
||||
module.exports = deployCoreContracts;
|
||||
module.exports = deployDAOCoreContracts;
|
||||
|
|
|
@ -13,6 +13,7 @@ const deployWorkContract = async (name) => {
|
|||
|
||||
await deployContract(name, [
|
||||
contractAddresses[network].DAO,
|
||||
contractAddresses[network].GlobalForum,
|
||||
contractAddresses[network].Proposals,
|
||||
price]);
|
||||
};
|
||||
|
|
|
@ -8,16 +8,18 @@ const deployDAO = require('./util/deploy-dao');
|
|||
|
||||
describe('Forum', () => {
|
||||
async function deploy() {
|
||||
const [account1, account2, account3] = await ethers.getSigners();
|
||||
const { dao } = await deployDAO();
|
||||
const [account1, account2, account3, account4] = await ethers.getSigners();
|
||||
const { dao, forum } = await deployDAO();
|
||||
return {
|
||||
dao, account1, account2, account3,
|
||||
dao, forum, account1, account2, account3, account4,
|
||||
};
|
||||
}
|
||||
let dao;
|
||||
let forum;
|
||||
let account1;
|
||||
let account2;
|
||||
let account3;
|
||||
let account4;
|
||||
const POOL_DURATION = 3600; // 1 hour
|
||||
const POOL_FEE = 100;
|
||||
const emptyCallbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []);
|
||||
|
@ -39,7 +41,7 @@ describe('Forum', () => {
|
|||
{ value: fee ?? POOL_FEE },
|
||||
);
|
||||
|
||||
const addPost = (author, postId, references) => dao.addPost([{
|
||||
const addPost = (author, postId, references) => forum.addPost([{
|
||||
weightPPM: 1000000,
|
||||
authorAddress: author,
|
||||
}], postId, references);
|
||||
|
@ -47,49 +49,43 @@ describe('Forum', () => {
|
|||
describe('Post', () => {
|
||||
beforeEach(async () => {
|
||||
({
|
||||
dao, account1, account2, account3,
|
||||
dao, forum, account1, account2, account3, account4,
|
||||
} = await loadFixture(deploy));
|
||||
});
|
||||
|
||||
it('should be able to add a post', async () => {
|
||||
const postId = 'some-id';
|
||||
await expect(addPost(account1, postId, [])).to.emit(dao, 'PostAdded').withArgs('some-id');
|
||||
const post = await dao.posts(postId);
|
||||
await expect(addPost(account1, postId, [])).to.emit(forum, 'PostAdded').withArgs('some-id');
|
||||
const post = await forum.getPost(postId);
|
||||
expect(post.sender).to.equal(account1);
|
||||
expect(post.id).to.equal(postId);
|
||||
const postAuthors = await dao.getPostAuthors(postId);
|
||||
expect(postAuthors).to.have.length(1);
|
||||
expect(postAuthors[0].weightPPM).to.equal(1000000);
|
||||
expect(postAuthors[0].authorAddress).to.equal(account1);
|
||||
expect(post.authors).to.have.length(1);
|
||||
expect(post.authors[0].weightPPM).to.equal(1000000);
|
||||
expect(post.authors[0].authorAddress).to.equal(account1);
|
||||
});
|
||||
|
||||
it('should be able to add a post on behalf of another account', async () => {
|
||||
const postId = 'some-id';
|
||||
await addPost(account2, postId, []);
|
||||
const post = await dao.posts(postId);
|
||||
const post = await forum.getPost(postId);
|
||||
expect(post.sender).to.equal(account1);
|
||||
expect(post.id).to.equal(postId);
|
||||
const postAuthors = await dao.getPostAuthors(postId);
|
||||
expect(postAuthors).to.have.length(1);
|
||||
expect(postAuthors[0].weightPPM).to.equal(1000000);
|
||||
expect(postAuthors[0].authorAddress).to.equal(account2);
|
||||
expect(post.authors).to.have.length(1);
|
||||
expect(post.authors[0].weightPPM).to.equal(1000000);
|
||||
expect(post.authors[0].authorAddress).to.equal(account2);
|
||||
});
|
||||
|
||||
it('should be able to add a post with multiple authors', async () => {
|
||||
const postId = 'some-id';
|
||||
await expect(dao.addPost([
|
||||
await expect(forum.addPost([
|
||||
{ weightPPM: 500000, authorAddress: account1 },
|
||||
{ weightPPM: 500000, authorAddress: account2 },
|
||||
], postId, [])).to.emit(dao, 'PostAdded').withArgs('some-id');
|
||||
const post = await dao.posts(postId);
|
||||
], postId, [])).to.emit(forum, 'PostAdded').withArgs('some-id');
|
||||
const post = await forum.getPost(postId);
|
||||
expect(post.sender).to.equal(account1);
|
||||
expect(post.id).to.equal(postId);
|
||||
const postAuthors = await dao.getPostAuthors(postId);
|
||||
expect(postAuthors).to.have.length(2);
|
||||
expect(postAuthors[0].weightPPM).to.equal(500000);
|
||||
expect(postAuthors[0].authorAddress).to.equal(account1);
|
||||
expect(postAuthors[1].weightPPM).to.equal(500000);
|
||||
expect(postAuthors[1].authorAddress).to.equal(account2);
|
||||
expect(post.authors).to.have.length(2);
|
||||
expect(post.authors[0].weightPPM).to.equal(500000);
|
||||
expect(post.authors[0].authorAddress).to.equal(account1);
|
||||
expect(post.authors[1].weightPPM).to.equal(500000);
|
||||
expect(post.authors[1].authorAddress).to.equal(account2);
|
||||
await initiateValidationPool({ postId: 'some-id' });
|
||||
await time.increase(POOL_DURATION + 1);
|
||||
await dao.evaluateOutcome(0);
|
||||
|
@ -99,7 +95,7 @@ describe('Forum', () => {
|
|||
|
||||
it('should not be able to add a post with total author weight < 100%', async () => {
|
||||
const postId = 'some-id';
|
||||
await expect(dao.addPost([
|
||||
await expect(forum.addPost([
|
||||
{ weightPPM: 500000, authorAddress: account1 },
|
||||
{ weightPPM: 400000, authorAddress: account2 },
|
||||
], postId, [])).to.be.rejectedWith('Author weights must sum to 1000000');
|
||||
|
@ -107,7 +103,7 @@ describe('Forum', () => {
|
|||
|
||||
it('should not be able to add a post with total author weight > 100%', async () => {
|
||||
const postId = 'some-id';
|
||||
await expect(dao.addPost([
|
||||
await expect(forum.addPost([
|
||||
{ weightPPM: 500000, authorAddress: account1 },
|
||||
{ weightPPM: 600000, authorAddress: account2 },
|
||||
], postId, [])).to.be.rejectedWith('Author weights must sum to 1000000');
|
||||
|
@ -126,13 +122,10 @@ describe('Forum', () => {
|
|||
|
||||
it('should be able to leach reputation via references', async () => {
|
||||
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 addPost(account2, 'second-content-id', [{ weightPPM: -500000, 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);
|
||||
expect(pool.props.postId).to.equal('second-content-id');
|
||||
|
@ -140,8 +133,6 @@ describe('Forum', () => {
|
|||
await dao.evaluateOutcome(1);
|
||||
expect(await dao.balanceOf(account1)).to.equal(50);
|
||||
expect(await dao.balanceOf(account2)).to.equal(150);
|
||||
expect((await dao.posts('content-id')).reputation).to.equal(50);
|
||||
expect((await dao.posts('second-content-id')).reputation).to.equal(150);
|
||||
});
|
||||
|
||||
it('should be able to redistribute power via references', async () => {
|
||||
|
@ -221,7 +212,6 @@ describe('Forum', () => {
|
|||
});
|
||||
|
||||
it('should limit effects of negative references on prior positive references', async () => {
|
||||
console.log('First post');
|
||||
await addPost(account1, 'content-id', []);
|
||||
await initiateValidationPool({ postId: 'content-id' });
|
||||
await dao.evaluateOutcome(0);
|
||||
|
@ -263,21 +253,15 @@ describe('Forum', () => {
|
|||
|
||||
it('should enforce depth limit', async () => {
|
||||
await addPost(account1, 'content-id-1', []);
|
||||
await addPost(account1, 'content-id-2', [{ weightPPM: 1000000, targetPostId: 'content-id-1' }]);
|
||||
await addPost(account1, 'content-id-3', [{ weightPPM: 1000000, targetPostId: 'content-id-2' }]);
|
||||
await addPost(account1, 'content-id-4', [{ weightPPM: 1000000, targetPostId: 'content-id-3' }]);
|
||||
await addPost(account2, 'content-id-2', [{ weightPPM: 1000000, targetPostId: 'content-id-1' }]);
|
||||
await addPost(account3, 'content-id-3', [{ weightPPM: 1000000, targetPostId: 'content-id-2' }]);
|
||||
await addPost(account4, 'content-id-4', [{ weightPPM: 1000000, targetPostId: 'content-id-3' }]);
|
||||
await initiateValidationPool({ postId: 'content-id-4' });
|
||||
await dao.evaluateOutcome(0);
|
||||
const posts = await Promise.all([
|
||||
await dao.posts('content-id-1'),
|
||||
await dao.posts('content-id-2'),
|
||||
await dao.posts('content-id-3'),
|
||||
await dao.posts('content-id-4'),
|
||||
]);
|
||||
expect(posts[0].reputation).to.equal(0);
|
||||
expect(posts[1].reputation).to.equal(100);
|
||||
expect(posts[2].reputation).to.equal(0);
|
||||
expect(posts[3].reputation).to.equal(0);
|
||||
expect(await dao.balanceOf(account1)).to.equal(0);
|
||||
expect(await dao.balanceOf(account2)).to.equal(100);
|
||||
expect(await dao.balanceOf(account3)).to.equal(0);
|
||||
expect(await dao.balanceOf(account4)).to.equal(0);
|
||||
});
|
||||
|
||||
it('should be able to incinerate reputation', async () => {
|
||||
|
@ -290,7 +274,6 @@ describe('Forum', () => {
|
|||
await initiateValidationPool({ postId: 'content-id-1' });
|
||||
expect(await dao.totalSupply()).to.equal(100);
|
||||
await dao.evaluateOutcome(0);
|
||||
expect((await dao.posts('content-id-1')).reputation).to.equal(50);
|
||||
expect(await dao.totalSupply()).to.equal(50);
|
||||
});
|
||||
|
||||
|
@ -301,7 +284,6 @@ describe('Forum', () => {
|
|||
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 addPost(account2, 'second-content-id', []);
|
||||
await initiateValidationPool({ postId: 'second-content-id' });
|
||||
|
@ -310,8 +292,6 @@ describe('Forum', () => {
|
|||
expect(await dao.balanceOf(account1)).to.equal(100);
|
||||
expect(await dao.balanceOf(account2)).to.equal(100);
|
||||
expect(await dao.totalSupply()).to.equal(200);
|
||||
expect((await dao.posts('content-id')).reputation).to.equal(100);
|
||||
expect((await dao.posts('second-content-id')).reputation).to.equal(100);
|
||||
|
||||
// account1 stakes and loses
|
||||
await initiateValidationPool({ postId: 'second-content-id' });
|
||||
|
@ -322,8 +302,6 @@ describe('Forum', () => {
|
|||
expect(await dao.balanceOf(account1)).to.equal(50);
|
||||
expect(await dao.balanceOf(account2)).to.equal(250);
|
||||
expect(await dao.totalSupply()).to.equal(300);
|
||||
expect((await dao.posts('content-id')).reputation).to.equal(100);
|
||||
expect((await dao.posts('second-content-id')).reputation).to.equal(100);
|
||||
});
|
||||
|
||||
it('author and post rep can be completely destroyed', async () => {
|
||||
|
@ -336,9 +314,6 @@ describe('Forum', () => {
|
|||
expect(await dao.balanceOf(account2)).to.equal(250);
|
||||
expect(await dao.balanceOf(account3)).to.equal(250);
|
||||
expect(await dao.totalSupply()).to.equal(500);
|
||||
expect((await dao.posts('content-id')).reputation).to.equal(0);
|
||||
expect((await dao.posts('second-content-id')).reputation).to.equal(100);
|
||||
expect((await dao.posts('third-content-id')).reputation).to.equal(250);
|
||||
});
|
||||
|
||||
it('author rep can be destroyed while some post rep remains', async () => {
|
||||
|
@ -351,9 +326,6 @@ describe('Forum', () => {
|
|||
expect(await dao.balanceOf(account1)).to.equal(0);
|
||||
expect(await dao.balanceOf(account2)).to.equal(250);
|
||||
expect(await dao.balanceOf(account3)).to.equal(120);
|
||||
expect((await dao.posts('content-id')).reputation).to.equal(30);
|
||||
expect((await dao.posts('second-content-id')).reputation).to.equal(100);
|
||||
expect((await dao.posts('third-content-id')).reputation).to.equal(120);
|
||||
});
|
||||
|
||||
it('author rep can be destroyed while some post rep remains (odd amount)', async () => {
|
||||
|
@ -366,15 +338,12 @@ describe('Forum', () => {
|
|||
expect(await dao.balanceOf(account1)).to.equal(0);
|
||||
expect(await dao.balanceOf(account2)).to.equal(250);
|
||||
expect(await dao.balanceOf(account3)).to.equal(125);
|
||||
expect((await dao.posts('content-id')).reputation).to.equal(25);
|
||||
expect((await dao.posts('second-content-id')).reputation).to.equal(100);
|
||||
expect((await dao.posts('third-content-id')).reputation).to.equal(125);
|
||||
});
|
||||
});
|
||||
|
||||
describe('negative reference of a post with multiple authors', async () => {
|
||||
beforeEach(async () => {
|
||||
await dao.addPost([
|
||||
await forum.addPost([
|
||||
{ weightPPM: 500000, authorAddress: account1 },
|
||||
{ weightPPM: 500000, authorAddress: account2 },
|
||||
], 'content-id', []);
|
||||
|
@ -383,7 +352,6 @@ describe('Forum', () => {
|
|||
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' });
|
||||
|
@ -394,7 +362,6 @@ describe('Forum', () => {
|
|||
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 () => {
|
||||
|
@ -404,11 +371,9 @@ describe('Forum', () => {
|
|||
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.balanceOf(account2)).to.equal(0);
|
||||
expect(await dao.balanceOf(account3)).to.equal(600);
|
||||
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 () => {
|
||||
|
@ -421,8 +386,6 @@ describe('Forum', () => {
|
|||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,13 +13,13 @@ describe('Onboarding', () => {
|
|||
// Contracts are deployed using the first signer/account by default
|
||||
const [account1, account2] = await ethers.getSigners();
|
||||
|
||||
const { dao } = await deployDAO();
|
||||
const { dao, forum } = await deployDAO();
|
||||
const Proposals = await ethers.getContractFactory('Proposals');
|
||||
const proposals = await Proposals.deploy(dao.target);
|
||||
const Onboarding = await ethers.getContractFactory('Onboarding');
|
||||
const onboarding = await Onboarding.deploy(dao.target, proposals.target, PRICE);
|
||||
const onboarding = await Onboarding.deploy(dao.target, forum.target, proposals.target, PRICE);
|
||||
|
||||
await dao.addPost([{ weightPPM: 1000000, authorAddress: account1 }], 'content-id', []);
|
||||
await forum.addPost([{ weightPPM: 1000000, authorAddress: account1 }], 'content-id', []);
|
||||
const callbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []);
|
||||
await dao.initiateValidationPool(
|
||||
'content-id',
|
||||
|
@ -37,7 +37,7 @@ describe('Onboarding', () => {
|
|||
expect(await dao.balanceOf(account1)).to.equal(100);
|
||||
|
||||
return {
|
||||
dao, onboarding, account1, account2,
|
||||
dao, forum, onboarding, account1, account2,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -53,13 +53,14 @@ describe('Onboarding', () => {
|
|||
|
||||
describe('Work approval/disapproval', () => {
|
||||
let dao;
|
||||
let forum;
|
||||
let onboarding;
|
||||
let account1;
|
||||
let account2;
|
||||
|
||||
beforeEach(async () => {
|
||||
({
|
||||
dao, onboarding, account1, account2,
|
||||
dao, forum, onboarding, account1, account2,
|
||||
} = await loadFixture(deploy));
|
||||
await dao.stakeAvailability(onboarding.target, 50, STAKE_DURATION);
|
||||
});
|
||||
|
@ -70,13 +71,11 @@ describe('Onboarding', () => {
|
|||
await expect(onboarding.submitWorkApproval(0, true))
|
||||
.to.emit(dao, 'ValidationPoolInitiated').withArgs(1)
|
||||
.to.emit(onboarding, 'WorkApprovalSubmitted').withArgs(0, true);
|
||||
const post = await dao.posts('evidence-content-id');
|
||||
const post = await forum.getPost('evidence-content-id');
|
||||
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].weightPPM).to.equal(1000000);
|
||||
expect(postAuthors[0].authorAddress).to.equal(account1);
|
||||
expect(post.authors).to.have.length(1);
|
||||
expect(post.authors[0].weightPPM).to.equal(1000000);
|
||||
expect(post.authors[0].authorAddress).to.equal(account1);
|
||||
const pool = await dao.validationPools(1);
|
||||
expect(pool.props.postId).to.equal('evidence-content-id');
|
||||
expect(pool.props.fee).to.equal(PRICE * 0.9);
|
||||
|
@ -114,7 +113,7 @@ describe('Onboarding', () => {
|
|||
describe('Onboarding followup', () => {
|
||||
it('resolving the first validation pool should trigger a second pool', async () => {
|
||||
const {
|
||||
dao, onboarding, account2,
|
||||
dao, forum, onboarding, account2,
|
||||
} = await loadFixture(deploy);
|
||||
await dao.stakeAvailability(onboarding.target, 50, STAKE_DURATION);
|
||||
await onboarding.connect(account2).requestWork('req-content-id', { value: PRICE });
|
||||
|
@ -122,14 +121,12 @@ describe('Onboarding', () => {
|
|||
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);
|
||||
expect(await dao.postCount()).to.equal(3);
|
||||
const post = await dao.posts('req-content-id');
|
||||
expect(await forum.postCount()).to.equal(3);
|
||||
const post = await forum.getPost('req-content-id');
|
||||
expect(post.sender).to.equal(onboarding.target);
|
||||
expect(post.id).to.equal('req-content-id');
|
||||
const postAuthors = await dao.getPostAuthors('req-content-id');
|
||||
expect(postAuthors).to.have.length(1);
|
||||
expect(postAuthors[0].weightPPM).to.equal(1000000);
|
||||
expect(postAuthors[0].authorAddress).to.equal(account2);
|
||||
expect(post.authors).to.have.length(1);
|
||||
expect(post.authors[0].weightPPM).to.equal(1000000);
|
||||
expect(post.authors[0].authorAddress).to.equal(account2);
|
||||
const pool = await dao.validationPools(2);
|
||||
expect(pool.props.postId).to.equal('req-content-id');
|
||||
expect(pool.props.fee).to.equal(PRICE * 0.1);
|
||||
|
@ -139,7 +136,7 @@ describe('Onboarding', () => {
|
|||
|
||||
it('if the first validation pool is rejected it should not trigger a second pool', async () => {
|
||||
const {
|
||||
dao, onboarding, account2,
|
||||
dao, forum, onboarding, account2,
|
||||
} = await loadFixture(deploy);
|
||||
await dao.stakeAvailability(onboarding.target, 40, STAKE_DURATION);
|
||||
await onboarding.connect(account2).requestWork('req-content-id', { value: PRICE });
|
||||
|
@ -148,7 +145,7 @@ describe('Onboarding', () => {
|
|||
await dao.stakeOnValidationPool(1, 60, false);
|
||||
await time.increase(86401);
|
||||
await expect(dao.evaluateOutcome(1)).not.to.emit(dao, 'ValidationPoolInitiated');
|
||||
expect(await dao.postCount()).to.equal(2);
|
||||
expect(await forum.postCount()).to.equal(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,12 +12,12 @@ describe('Proposal', () => {
|
|||
// Contracts are deployed using the first signer/account by default
|
||||
const [account1, account2] = await ethers.getSigners();
|
||||
|
||||
const { dao } = await deployDAO();
|
||||
const { dao, forum } = await deployDAO();
|
||||
const Proposals = await ethers.getContractFactory('Proposals');
|
||||
const proposals = await Proposals.deploy(dao.target);
|
||||
|
||||
await dao.addPost([{ weightPPM: 1000000, authorAddress: account1 }], 'some-content-id', []);
|
||||
await dao.addPost([{ weightPPM: 1000000, authorAddress: account2 }], 'some-other-content-id', []);
|
||||
await forum.addPost([{ weightPPM: 1000000, authorAddress: account1 }], 'some-content-id', []);
|
||||
await forum.addPost([{ weightPPM: 1000000, authorAddress: account2 }], 'some-other-content-id', []);
|
||||
const callbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []);
|
||||
await dao.initiateValidationPool(
|
||||
'some-content-id',
|
||||
|
@ -46,7 +46,7 @@ describe('Proposal', () => {
|
|||
await dao.evaluateOutcome(1);
|
||||
|
||||
return {
|
||||
dao, proposals, account1, account2,
|
||||
dao, forum, proposals, account1, account2,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -65,6 +65,7 @@ describe('Proposal', () => {
|
|||
|
||||
describe('Attestation', () => {
|
||||
let dao;
|
||||
let forum;
|
||||
let proposals;
|
||||
let account1;
|
||||
let account2;
|
||||
|
@ -73,13 +74,14 @@ describe('Proposal', () => {
|
|||
beforeEach(async () => {
|
||||
({
|
||||
dao,
|
||||
forum,
|
||||
proposals,
|
||||
account1,
|
||||
account2,
|
||||
} = await loadFixture(deploy));
|
||||
|
||||
const emptyCallbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []);
|
||||
await dao.addPost([{ authorAddress: account1, weightPPM: 1000000 }], 'proposal-content-id', []);
|
||||
await forum.addPost([{ authorAddress: account1, weightPPM: 1000000 }], 'proposal-content-id', []);
|
||||
await proposals.propose('proposal-content-id', [20, 20, 20], false, emptyCallbackData, { value: 100 });
|
||||
expect(await proposals.proposalCount()).to.equal(1);
|
||||
proposal = await proposals.proposals(0);
|
||||
|
@ -88,10 +90,10 @@ describe('Proposal', () => {
|
|||
});
|
||||
|
||||
it('Can submit a proposal', async () => {
|
||||
const postAuthors = await dao.getPostAuthors('proposal-content-id');
|
||||
expect(postAuthors).to.have.length(1);
|
||||
expect(postAuthors[0].weightPPM).to.equal(1000000);
|
||||
expect(postAuthors[0].authorAddress).to.equal(account1);
|
||||
const post = await forum.getPost('proposal-content-id');
|
||||
expect(post.authors).to.have.length(1);
|
||||
expect(post.authors[0].weightPPM).to.equal(1000000);
|
||||
expect(post.authors[0].authorAddress).to.equal(account1);
|
||||
});
|
||||
|
||||
it('Can attest for a proposal', async () => {
|
||||
|
|
|
@ -9,12 +9,13 @@ const deployDAO = require('./util/deploy-dao');
|
|||
describe('Validation Pools', () => {
|
||||
async function deploy() {
|
||||
const [account1, account2] = await ethers.getSigners();
|
||||
const { dao } = await deployDAO();
|
||||
const { dao, forum } = await deployDAO();
|
||||
return {
|
||||
dao, account1, account2,
|
||||
dao, forum, account1, account2,
|
||||
};
|
||||
}
|
||||
let dao;
|
||||
let forum;
|
||||
let account1;
|
||||
let account2;
|
||||
const POOL_DURATION = 3600; // 1 hour
|
||||
|
@ -40,9 +41,9 @@ describe('Validation Pools', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
({
|
||||
dao, account1, account2,
|
||||
dao, forum, account1, account2,
|
||||
} = await loadFixture(deploy));
|
||||
await dao.addPost([{ weightPPM: 1000000, authorAddress: account1 }], 'content-id', []);
|
||||
await forum.addPost([{ weightPPM: 1000000, 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);
|
||||
|
@ -206,7 +207,7 @@ describe('Validation Pools', () => {
|
|||
beforeEach(async () => {
|
||||
time.increase(POOL_DURATION + 1);
|
||||
await dao.evaluateOutcome(0);
|
||||
await dao.addPost([{ weightPPM: 1000000, authorAddress: account2 }], 'content-id-2', []);
|
||||
await forum.addPost([{ weightPPM: 1000000, 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);
|
||||
|
|
|
@ -13,13 +13,13 @@ describe('Work1', () => {
|
|||
// Contracts are deployed using the first signer/account by default
|
||||
const [account1, account2] = await ethers.getSigners();
|
||||
|
||||
const { dao } = await deployDAO();
|
||||
const { dao, forum } = await deployDAO();
|
||||
const Proposals = await ethers.getContractFactory('Proposals');
|
||||
const proposals = await Proposals.deploy(dao.target);
|
||||
const Work1 = await ethers.getContractFactory('Work1');
|
||||
const work1 = await Work1.deploy(dao.target, proposals.target, WORK1_PRICE);
|
||||
const work1 = await Work1.deploy(dao.target, forum.target, proposals.target, WORK1_PRICE);
|
||||
|
||||
await dao.addPost([{ weightPPM: 1000000, authorAddress: account1 }], 'some-content-id', []);
|
||||
await forum.addPost([{ weightPPM: 1000000, authorAddress: account1 }], 'some-content-id', []);
|
||||
const callbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []);
|
||||
await dao.initiateValidationPool(
|
||||
'some-content-id',
|
||||
|
@ -36,7 +36,7 @@ describe('Work1', () => {
|
|||
await dao.evaluateOutcome(0);
|
||||
|
||||
return {
|
||||
dao, work1, proposals, account1, account2,
|
||||
dao, forum, work1, proposals, account1, account2,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -185,13 +185,14 @@ describe('Work1', () => {
|
|||
|
||||
describe('Work evidence and approval/disapproval', () => {
|
||||
let dao;
|
||||
let forum;
|
||||
let work1;
|
||||
let account1;
|
||||
let account2;
|
||||
|
||||
beforeEach(async () => {
|
||||
({
|
||||
dao, work1, account1, account2,
|
||||
dao, forum, work1, account1, account2,
|
||||
} = await loadFixture(deploy));
|
||||
await dao.stakeAvailability(work1.target, 50, STAKE_DURATION);
|
||||
});
|
||||
|
@ -223,13 +224,11 @@ describe('Work1', () => {
|
|||
.to.emit(work1, 'WorkApprovalSubmitted').withArgs(0, true);
|
||||
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');
|
||||
const post = await forum.getPost('evidence-content-id');
|
||||
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].weightPPM).to.equal(1000000);
|
||||
expect(postAuthors[0].authorAddress).to.equal(account1);
|
||||
expect(post.authors).to.have.length(1);
|
||||
expect(post.authors[0].weightPPM).to.equal(1000000);
|
||||
expect(post.authors[0].authorAddress).to.equal(account1);
|
||||
const pool = await dao.validationPools(1);
|
||||
expect(pool.props.fee).to.equal(WORK1_PRICE);
|
||||
expect(pool.sender).to.equal(work1.target);
|
||||
|
|
|
@ -2,21 +2,21 @@ const { ethers } = require('hardhat');
|
|||
|
||||
const deployDAO = async () => {
|
||||
const Reputation = await ethers.getContractFactory('Reputation');
|
||||
const Forum = await ethers.getContractFactory('Forum');
|
||||
const Bench = await ethers.getContractFactory('Bench');
|
||||
const DAO = await ethers.getContractFactory('DAO');
|
||||
const GlobalForum = await ethers.getContractFactory('GlobalForum');
|
||||
const forum = await GlobalForum.deploy();
|
||||
const reputation = await Reputation.deploy();
|
||||
const forum = await Forum.deploy();
|
||||
const bench = await Bench.deploy();
|
||||
const dao = await DAO.deploy(
|
||||
reputation.target,
|
||||
forum.target,
|
||||
bench.target,
|
||||
forum.target,
|
||||
);
|
||||
return {
|
||||
forum,
|
||||
dao,
|
||||
reputation,
|
||||
forum,
|
||||
bench,
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue