Compare commits
3 Commits
c6546b56f1
...
54d4fbc6cc
Author | SHA1 | Date |
---|---|---|
Ladd Hoffman | 54d4fbc6cc | |
Ladd Hoffman | 5cb4cf93d0 | |
Ladd Hoffman | 7b720c7325 |
|
@ -2,295 +2,17 @@
|
|||
pragma solidity ^0.8.24;
|
||||
|
||||
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "./IAcceptAvailability.sol";
|
||||
import "./IOnValidate.sol";
|
||||
import "./core/Reputation.sol";
|
||||
import "./core/ValidationPools.sol";
|
||||
import "./core/Forum.sol";
|
||||
import "./interfaces/IAcceptAvailability.sol";
|
||||
|
||||
import "hardhat/console.sol";
|
||||
|
||||
struct Post {
|
||||
uint id;
|
||||
address sender;
|
||||
address author;
|
||||
string contentId;
|
||||
}
|
||||
|
||||
struct Stake {
|
||||
uint id;
|
||||
bool inFavor;
|
||||
uint256 amount;
|
||||
address sender;
|
||||
bool fromMint;
|
||||
}
|
||||
|
||||
struct ValidationPoolParams {
|
||||
uint duration;
|
||||
uint[2] quorum; // [ Numerator, Denominator ]
|
||||
uint[2] winRatio; // [ Numerator, Denominator ]
|
||||
uint bindingPercent;
|
||||
bool redistributeLosingStakes;
|
||||
}
|
||||
|
||||
struct ValidationPool {
|
||||
uint id;
|
||||
uint postIndex;
|
||||
address sender;
|
||||
mapping(uint => Stake) stakes;
|
||||
uint stakeCount;
|
||||
ValidationPoolParams params;
|
||||
uint256 fee;
|
||||
uint endTime;
|
||||
bool resolved;
|
||||
bool outcome;
|
||||
bool callbackOnValidate;
|
||||
bytes callbackData;
|
||||
}
|
||||
|
||||
/// This contract must manage validation pools and reputation,
|
||||
/// because otherwise there's no way to enforce appropriate permissions on
|
||||
/// transfer of value between reputation NFTs.
|
||||
contract DAO is ERC20("Reputation", "REP") {
|
||||
mapping(uint => address) public members;
|
||||
uint public memberCount;
|
||||
mapping(address => bool) public isMember;
|
||||
mapping(uint => ValidationPool) public validationPools;
|
||||
uint public validationPoolCount;
|
||||
mapping(uint => Post) public posts;
|
||||
uint public postCount;
|
||||
|
||||
// TODO: possible parameter for minting ratio
|
||||
// TODO: possible parameter for stakeForAuthor
|
||||
// TODO: Add forum parameters
|
||||
|
||||
uint constant minDuration = 1; // 1 second
|
||||
uint constant maxDuration = 365_000_000 days; // 1 million years
|
||||
uint[2] minQuorum = [1, 10];
|
||||
|
||||
event PostAdded(uint postIndex);
|
||||
event ValidationPoolInitiated(uint poolIndex);
|
||||
event ValidationPoolResolved(
|
||||
uint poolIndex,
|
||||
bool votePasses,
|
||||
bool quorumMet
|
||||
);
|
||||
|
||||
function decimals() public pure override returns (uint8) {
|
||||
return 9;
|
||||
}
|
||||
|
||||
function addPost(
|
||||
address author,
|
||||
string calldata contentId
|
||||
) external returns (uint postIndex) {
|
||||
postIndex = postCount++;
|
||||
Post storage post = posts[postIndex];
|
||||
post.author = author;
|
||||
post.sender = msg.sender;
|
||||
post.id = postIndex;
|
||||
post.contentId = contentId;
|
||||
emit PostAdded(postIndex);
|
||||
}
|
||||
|
||||
/// Accept fee to initiate a validation pool
|
||||
/// TODO: Handle multiple authors
|
||||
function initiateValidationPool(
|
||||
uint postIndex,
|
||||
uint duration,
|
||||
uint[2] calldata quorum, // [Numerator, Denominator]
|
||||
uint[2] calldata winRatio, // [Numerator, Denominator]
|
||||
uint bindingPercent,
|
||||
bool redistributeLosingStakes,
|
||||
bool callbackOnValidate,
|
||||
bytes calldata callbackData
|
||||
) external payable returns (uint poolIndex) {
|
||||
require(msg.value > 0, "Fee is required to initiate validation pool");
|
||||
require(duration >= minDuration, "Duration is too short");
|
||||
require(duration <= maxDuration, "Duration is too long");
|
||||
require(
|
||||
minQuorum[1] * quorum[0] >= minQuorum[0] * quorum[1],
|
||||
"Quorum is below minimum"
|
||||
);
|
||||
require(quorum[0] <= quorum[1], "Quorum is greater than one");
|
||||
require(winRatio[0] <= winRatio[1], "Win ratio is greater than one");
|
||||
require(bindingPercent <= 100, "Binding percent must be <= 100");
|
||||
Post storage post = posts[postIndex];
|
||||
require(post.author != address(0), "Target post not found");
|
||||
poolIndex = validationPoolCount++;
|
||||
ValidationPool storage pool = validationPools[poolIndex];
|
||||
pool.sender = msg.sender;
|
||||
pool.postIndex = postIndex;
|
||||
pool.fee = msg.value;
|
||||
pool.params.quorum = quorum;
|
||||
pool.params.winRatio = winRatio;
|
||||
pool.params.bindingPercent = bindingPercent;
|
||||
pool.params.redistributeLosingStakes = redistributeLosingStakes;
|
||||
pool.params.duration = duration;
|
||||
pool.endTime = block.timestamp + duration;
|
||||
pool.id = poolIndex;
|
||||
pool.callbackOnValidate = callbackOnValidate;
|
||||
pool.callbackData = callbackData;
|
||||
// We use our privilege as the DAO contract to mint reputation in proportion with the fee.
|
||||
// Here we assume a minting ratio of 1
|
||||
// TODO: Make minting ratio an adjustable parameter
|
||||
_mint(post.author, msg.value);
|
||||
// Here we assume a stakeForAuthor ratio of 0.5
|
||||
// TODO: Make stakeForAuthor an adjustable parameter
|
||||
_stake(pool, post.author, msg.value / 2, true, true);
|
||||
_stake(pool, post.author, msg.value / 2, false, true);
|
||||
emit ValidationPoolInitiated(poolIndex);
|
||||
}
|
||||
|
||||
/// Internal function to register a stake for/against a validation pool
|
||||
function _stake(
|
||||
ValidationPool storage pool,
|
||||
address sender,
|
||||
uint256 amount,
|
||||
bool inFavor,
|
||||
bool fromMint
|
||||
) internal {
|
||||
require(block.timestamp <= pool.endTime, "Pool end time has passed");
|
||||
_update(sender, address(this), amount);
|
||||
uint stakeIndex = pool.stakeCount++;
|
||||
Stake storage s = pool.stakes[stakeIndex];
|
||||
s.sender = sender;
|
||||
s.inFavor = inFavor;
|
||||
s.amount = amount;
|
||||
s.id = stakeIndex;
|
||||
s.fromMint = fromMint;
|
||||
}
|
||||
|
||||
/// Accept reputation stakes toward a validation pool
|
||||
function stake(uint poolIndex, uint256 amount, bool inFavor) public {
|
||||
ValidationPool storage pool = validationPools[poolIndex];
|
||||
_stake(pool, msg.sender, amount, inFavor, false);
|
||||
}
|
||||
|
||||
/// Evaluate outcome of a validation pool
|
||||
function evaluateOutcome(uint poolIndex) public returns (bool votePasses) {
|
||||
ValidationPool storage pool = validationPools[poolIndex];
|
||||
Post storage post = posts[pool.postIndex];
|
||||
require(pool.resolved == false, "Pool is already resolved");
|
||||
uint256 stakedFor;
|
||||
uint256 stakedAgainst;
|
||||
Stake storage s;
|
||||
for (uint i = 0; i < pool.stakeCount; i++) {
|
||||
s = pool.stakes[i];
|
||||
if (s.inFavor) {
|
||||
stakedFor += s.amount;
|
||||
} else {
|
||||
stakedAgainst += s.amount;
|
||||
}
|
||||
}
|
||||
// Special case for early evaluation if dao.totalSupply has been staked
|
||||
require(
|
||||
block.timestamp > pool.endTime ||
|
||||
stakedFor + stakedAgainst == totalSupply(),
|
||||
"Pool end time has not yet arrived"
|
||||
);
|
||||
// Check that quorum is met
|
||||
if (
|
||||
pool.params.quorum[1] * (stakedFor + stakedAgainst) <=
|
||||
totalSupply() * pool.params.quorum[0]
|
||||
) {
|
||||
// Refund fee
|
||||
// TODO: this could be made available for the sender to withdraw
|
||||
// payable(pool.sender).transfer(pool.fee);
|
||||
// Refund stakes
|
||||
for (uint i = 0; i < pool.stakeCount; i++) {
|
||||
s = pool.stakes[i];
|
||||
_update(address(this), s.sender, s.amount);
|
||||
}
|
||||
pool.resolved = true;
|
||||
emit ValidationPoolResolved(poolIndex, false, false);
|
||||
// Callback if requested
|
||||
if (pool.callbackOnValidate) {
|
||||
IOnValidate(pool.sender).onValidate(
|
||||
votePasses,
|
||||
false,
|
||||
stakedFor,
|
||||
stakedAgainst,
|
||||
pool.callbackData
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
// A tie is resolved in favor of the validation pool.
|
||||
// This is especially important so that the DAO's first pool can pass,
|
||||
// when no reputation has yet been minted.
|
||||
|
||||
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;
|
||||
}
|
||||
pool.resolved = true;
|
||||
pool.outcome = votePasses;
|
||||
emit ValidationPoolResolved(poolIndex, votePasses, true);
|
||||
// Value of losing stakes should be distributed among winners, in proportion to their stakes
|
||||
uint256 amountFromWinners = votePasses ? stakedFor : stakedAgainst;
|
||||
uint256 amountFromLosers = votePasses ? stakedAgainst : stakedFor;
|
||||
// Only bindingPercent % should be redistributed
|
||||
// Stake senders should get (100-bindingPercent) % back
|
||||
uint256 totalAllocated;
|
||||
for (uint i = 0; i < pool.stakeCount; i++) {
|
||||
s = pool.stakes[i];
|
||||
uint bindingPercent = s.fromMint ? 100 : pool.params.bindingPercent;
|
||||
if (votePasses == s.inFavor) {
|
||||
// Winning stake
|
||||
// If this stake is from the minted fee, always redistribute it to the winners
|
||||
bool redistributeLosingStakes = s.fromMint ||
|
||||
pool.params.redistributeLosingStakes;
|
||||
uint reward = redistributeLosingStakes
|
||||
? ((s.amount * amountFromLosers) / amountFromWinners) *
|
||||
(bindingPercent / 100)
|
||||
: 0;
|
||||
_update(address(this), s.sender, s.amount + reward);
|
||||
totalAllocated += reward;
|
||||
} else {
|
||||
// Losing stake
|
||||
// If this stake is from the minted fee, don't burn it
|
||||
if (!s.fromMint) {
|
||||
uint refund = (s.amount * (100 - bindingPercent)) / 100;
|
||||
if (refund > 0) {
|
||||
_update(address(this), s.sender, refund);
|
||||
}
|
||||
if (!pool.params.redistributeLosingStakes) {
|
||||
uint amountToBurn = (s.amount *
|
||||
pool.params.bindingPercent) / 100;
|
||||
_burn(address(this), amountToBurn);
|
||||
totalAllocated += amountToBurn;
|
||||
}
|
||||
totalAllocated += refund;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Due to rounding, there may be some REP left over. Include this as a reward to the author.
|
||||
uint256 remainder = amountFromLosers - totalAllocated;
|
||||
if (remainder > 0) {
|
||||
_update(address(this), post.author, remainder);
|
||||
}
|
||||
// Distribute fee proportionatly among all reputation holders
|
||||
for (uint i = 0; i < memberCount; i++) {
|
||||
address member = members[i];
|
||||
uint256 share = (pool.fee * balanceOf(member)) / totalSupply();
|
||||
// TODO: For efficiency this could be modified to hold the funds for recipients to withdraw
|
||||
// TODO: Exclude encumbered reputation from totalSupply
|
||||
payable(member).transfer(share);
|
||||
}
|
||||
// Callback if requested
|
||||
if (pool.callbackOnValidate) {
|
||||
IOnValidate(pool.sender).onValidate(
|
||||
votePasses,
|
||||
true,
|
||||
stakedFor,
|
||||
stakedAgainst,
|
||||
pool.callbackData
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
contract DAO is Reputation, Forum, ValidationPools {
|
||||
/// Transfer REP to a contract, and call that contract's receiveTransfer method
|
||||
function stakeAvailability(
|
||||
address to,
|
||||
|
|
|
@ -3,7 +3,7 @@ pragma solidity ^0.8.24;
|
|||
|
||||
import "./DAO.sol";
|
||||
import "./WorkContract.sol";
|
||||
import "./IOnValidate.sol";
|
||||
import "./interfaces/IOnValidate.sol";
|
||||
|
||||
contract Onboarding is WorkContract, IOnValidate {
|
||||
constructor(
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
pragma solidity ^0.8.24;
|
||||
|
||||
import "./DAO.sol";
|
||||
import "./IOnValidate.sol";
|
||||
import "./IOnProposalAccepted.sol";
|
||||
import "./interfaces/IOnValidate.sol";
|
||||
import "./interfaces/IOnProposalAccepted.sol";
|
||||
|
||||
import "hardhat/console.sol";
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
pragma solidity ^0.8.24;
|
||||
|
||||
import "./DAO.sol";
|
||||
import "./IAcceptAvailability.sol";
|
||||
import "./Proposals.sol";
|
||||
import "./IOnProposalAccepted.sol";
|
||||
import "./interfaces/IAcceptAvailability.sol";
|
||||
import "./interfaces/IOnProposalAccepted.sol";
|
||||
|
||||
abstract contract WorkContract is
|
||||
DAOContract,
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
// SPDX-License-Identifier: Unlicense
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
struct Post {
|
||||
uint id;
|
||||
address sender;
|
||||
address author;
|
||||
string contentId;
|
||||
}
|
||||
|
||||
contract Forum {
|
||||
mapping(uint => Post) public posts;
|
||||
uint public postCount;
|
||||
|
||||
event PostAdded(uint postIndex);
|
||||
|
||||
function addPost(
|
||||
address author,
|
||||
string calldata contentId
|
||||
) external returns (uint postIndex) {
|
||||
postIndex = postCount++;
|
||||
Post storage post = posts[postIndex];
|
||||
post.author = author;
|
||||
post.sender = msg.sender;
|
||||
post.id = postIndex;
|
||||
post.contentId = contentId;
|
||||
emit PostAdded(postIndex);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: Unlicense
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
|
||||
contract Reputation is ERC20("Reputation", "REP") {
|
||||
function decimals() public pure override returns (uint8) {
|
||||
return 9;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,263 @@
|
|||
// SPDX-License-Identifier: Unlicense
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
import "./Reputation.sol";
|
||||
import "./Forum.sol";
|
||||
|
||||
import "../interfaces/IOnValidate.sol";
|
||||
|
||||
struct Stake {
|
||||
uint id;
|
||||
bool inFavor;
|
||||
uint256 amount;
|
||||
address sender;
|
||||
bool fromMint;
|
||||
}
|
||||
|
||||
struct ValidationPoolParams {
|
||||
uint duration;
|
||||
uint[2] quorum; // [ Numerator, Denominator ]
|
||||
uint[2] winRatio; // [ Numerator, Denominator ]
|
||||
uint bindingPercent;
|
||||
bool redistributeLosingStakes;
|
||||
}
|
||||
|
||||
struct ValidationPool {
|
||||
uint id;
|
||||
uint postIndex;
|
||||
address sender;
|
||||
mapping(uint => Stake) stakes;
|
||||
uint stakeCount;
|
||||
ValidationPoolParams params;
|
||||
uint256 fee;
|
||||
uint endTime;
|
||||
bool resolved;
|
||||
bool outcome;
|
||||
bool callbackOnValidate;
|
||||
bytes callbackData;
|
||||
}
|
||||
|
||||
contract ValidationPools is Reputation, Forum {
|
||||
mapping(uint => ValidationPool) public validationPools;
|
||||
uint public validationPoolCount;
|
||||
|
||||
mapping(uint => address) public members;
|
||||
uint public memberCount;
|
||||
mapping(address => bool) public isMember;
|
||||
|
||||
// TODO: possible parameter for minting ratio
|
||||
// TODO: possible parameter for stakeForAuthor
|
||||
|
||||
uint constant minDuration = 1; // 1 second
|
||||
uint constant maxDuration = 365_000_000 days; // 1 million years
|
||||
uint[2] minQuorum = [1, 10];
|
||||
|
||||
event ValidationPoolInitiated(uint poolIndex);
|
||||
event ValidationPoolResolved(
|
||||
uint poolIndex,
|
||||
bool votePasses,
|
||||
bool quorumMet
|
||||
);
|
||||
|
||||
// TODO: Add forum parameters
|
||||
/// Internal function to register a stake for/against a validation pool
|
||||
function _stake(
|
||||
ValidationPool storage pool,
|
||||
address sender,
|
||||
uint256 amount,
|
||||
bool inFavor,
|
||||
bool fromMint
|
||||
) internal {
|
||||
require(block.timestamp <= pool.endTime, "Pool end time has passed");
|
||||
_update(sender, address(this), amount);
|
||||
uint stakeIndex = pool.stakeCount++;
|
||||
Stake storage s = pool.stakes[stakeIndex];
|
||||
s.sender = sender;
|
||||
s.inFavor = inFavor;
|
||||
s.amount = amount;
|
||||
s.id = stakeIndex;
|
||||
s.fromMint = fromMint;
|
||||
}
|
||||
|
||||
/// Accept reputation stakes toward a validation pool
|
||||
function stake(uint poolIndex, uint256 amount, bool inFavor) public {
|
||||
ValidationPool storage pool = validationPools[poolIndex];
|
||||
_stake(pool, msg.sender, amount, inFavor, false);
|
||||
}
|
||||
|
||||
/// Accept fee to initiate a validation pool
|
||||
/// TODO: Handle multiple authors
|
||||
function initiateValidationPool(
|
||||
uint postIndex,
|
||||
uint duration,
|
||||
uint[2] calldata quorum, // [Numerator, Denominator]
|
||||
uint[2] calldata winRatio, // [Numerator, Denominator]
|
||||
uint bindingPercent,
|
||||
bool redistributeLosingStakes,
|
||||
bool callbackOnValidate,
|
||||
bytes calldata callbackData
|
||||
) external payable returns (uint poolIndex) {
|
||||
require(msg.value > 0, "Fee is required to initiate validation pool");
|
||||
require(duration >= minDuration, "Duration is too short");
|
||||
require(duration <= maxDuration, "Duration is too long");
|
||||
require(
|
||||
minQuorum[1] * quorum[0] >= minQuorum[0] * quorum[1],
|
||||
"Quorum is below minimum"
|
||||
);
|
||||
require(quorum[0] <= quorum[1], "Quorum is greater than one");
|
||||
require(winRatio[0] <= winRatio[1], "Win ratio is greater than one");
|
||||
require(bindingPercent <= 100, "Binding percent must be <= 100");
|
||||
Post storage post = posts[postIndex];
|
||||
require(post.author != address(0), "Target post not found");
|
||||
poolIndex = validationPoolCount++;
|
||||
ValidationPool storage pool = validationPools[poolIndex];
|
||||
pool.sender = msg.sender;
|
||||
pool.postIndex = postIndex;
|
||||
pool.fee = msg.value;
|
||||
pool.params.quorum = quorum;
|
||||
pool.params.winRatio = winRatio;
|
||||
pool.params.bindingPercent = bindingPercent;
|
||||
pool.params.redistributeLosingStakes = redistributeLosingStakes;
|
||||
pool.params.duration = duration;
|
||||
pool.endTime = block.timestamp + duration;
|
||||
pool.id = poolIndex;
|
||||
pool.callbackOnValidate = callbackOnValidate;
|
||||
pool.callbackData = callbackData;
|
||||
// We use our privilege as the DAO contract to mint reputation in proportion with the fee.
|
||||
// Here we assume a minting ratio of 1
|
||||
// TODO: Make minting ratio an adjustable parameter
|
||||
_mint(post.author, msg.value);
|
||||
// Here we assume a stakeForAuthor ratio of 0.5
|
||||
// TODO: Make stakeForAuthor an adjustable parameter
|
||||
_stake(pool, post.author, msg.value / 2, true, true);
|
||||
_stake(pool, post.author, msg.value / 2, false, true);
|
||||
emit ValidationPoolInitiated(poolIndex);
|
||||
}
|
||||
|
||||
/// Evaluate outcome of a validation pool
|
||||
function evaluateOutcome(uint poolIndex) public returns (bool votePasses) {
|
||||
ValidationPool storage pool = validationPools[poolIndex];
|
||||
Post storage post = posts[pool.postIndex];
|
||||
require(pool.resolved == false, "Pool is already resolved");
|
||||
uint256 stakedFor;
|
||||
uint256 stakedAgainst;
|
||||
Stake storage s;
|
||||
for (uint i = 0; i < pool.stakeCount; i++) {
|
||||
s = pool.stakes[i];
|
||||
if (s.inFavor) {
|
||||
stakedFor += s.amount;
|
||||
} else {
|
||||
stakedAgainst += s.amount;
|
||||
}
|
||||
}
|
||||
// Special case for early evaluation if dao.totalSupply has been staked
|
||||
require(
|
||||
block.timestamp > pool.endTime ||
|
||||
stakedFor + stakedAgainst == totalSupply(),
|
||||
"Pool end time has not yet arrived"
|
||||
);
|
||||
// Check that quorum is met
|
||||
if (
|
||||
pool.params.quorum[1] * (stakedFor + stakedAgainst) <=
|
||||
totalSupply() * pool.params.quorum[0]
|
||||
) {
|
||||
// Refund fee
|
||||
// TODO: this could be made available for the sender to withdraw
|
||||
// payable(pool.sender).transfer(pool.fee);
|
||||
// Refund stakes
|
||||
for (uint i = 0; i < pool.stakeCount; i++) {
|
||||
s = pool.stakes[i];
|
||||
_update(address(this), s.sender, s.amount);
|
||||
}
|
||||
pool.resolved = true;
|
||||
emit ValidationPoolResolved(poolIndex, false, false);
|
||||
// Callback if requested
|
||||
if (pool.callbackOnValidate) {
|
||||
IOnValidate(pool.sender).onValidate(
|
||||
votePasses,
|
||||
false,
|
||||
stakedFor,
|
||||
stakedAgainst,
|
||||
pool.callbackData
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
// A tie is resolved in favor of the validation pool.
|
||||
// This is especially important so that the DAO's first pool can pass,
|
||||
// when no reputation has yet been minted.
|
||||
|
||||
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;
|
||||
}
|
||||
pool.resolved = true;
|
||||
pool.outcome = votePasses;
|
||||
emit ValidationPoolResolved(poolIndex, votePasses, true);
|
||||
// Value of losing stakes should be distributed among winners, in proportion to their stakes
|
||||
uint256 amountFromWinners = votePasses ? stakedFor : stakedAgainst;
|
||||
uint256 amountFromLosers = votePasses ? stakedAgainst : stakedFor;
|
||||
// Only bindingPercent % should be redistributed
|
||||
// Stake senders should get (100-bindingPercent) % back
|
||||
uint256 totalAllocated;
|
||||
for (uint i = 0; i < pool.stakeCount; i++) {
|
||||
s = pool.stakes[i];
|
||||
uint bindingPercent = s.fromMint ? 100 : pool.params.bindingPercent;
|
||||
if (votePasses == s.inFavor) {
|
||||
// Winning stake
|
||||
// If this stake is from the minted fee, always redistribute it to the winners
|
||||
bool redistributeLosingStakes = s.fromMint ||
|
||||
pool.params.redistributeLosingStakes;
|
||||
uint reward = redistributeLosingStakes
|
||||
? ((s.amount * amountFromLosers) / amountFromWinners) *
|
||||
(bindingPercent / 100)
|
||||
: 0;
|
||||
_update(address(this), s.sender, s.amount + reward);
|
||||
totalAllocated += reward;
|
||||
} else {
|
||||
// Losing stake
|
||||
// If this stake is from the minted fee, don't burn it
|
||||
if (!s.fromMint) {
|
||||
uint refund = (s.amount * (100 - bindingPercent)) / 100;
|
||||
if (refund > 0) {
|
||||
_update(address(this), s.sender, refund);
|
||||
}
|
||||
if (!pool.params.redistributeLosingStakes) {
|
||||
uint amountToBurn = (s.amount *
|
||||
pool.params.bindingPercent) / 100;
|
||||
_burn(address(this), amountToBurn);
|
||||
totalAllocated += amountToBurn;
|
||||
}
|
||||
totalAllocated += refund;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Due to rounding, there may be some REP left over. Include this as a reward to the author.
|
||||
uint256 remainder = amountFromLosers - totalAllocated;
|
||||
if (remainder > 0) {
|
||||
_update(address(this), post.author, remainder);
|
||||
}
|
||||
// Distribute fee proportionatly among all reputation holders
|
||||
for (uint i = 0; i < memberCount; i++) {
|
||||
address member = members[i];
|
||||
uint256 share = (pool.fee * balanceOf(member)) / totalSupply();
|
||||
// TODO: For efficiency this could be modified to hold the funds for recipients to withdraw
|
||||
// TODO: Exclude encumbered reputation from totalSupply
|
||||
payable(member).transfer(share);
|
||||
}
|
||||
// Callback if requested
|
||||
if (pool.callbackOnValidate) {
|
||||
IOnValidate(pool.sender).onValidate(
|
||||
votePasses,
|
||||
true,
|
||||
stakedFor,
|
||||
stakedAgainst,
|
||||
pool.callbackData
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
"scripts": {
|
||||
"test": "hardhat test",
|
||||
"automatic-staking-local": "hardhat run --network localhost scripts/automatic-staking.js",
|
||||
"automatic-staking-sepolia": "hardhat run --network sepolia scripts/automatic-staking.js",
|
||||
"automatic-staking-sepolia": "API_URL='https://demo.dgov.io/api' CA_PATH='' hardhat run --network sepolia scripts/automatic-staking.js",
|
||||
"deploy-local": "hardhat run --network localhost scripts/deploy.js",
|
||||
"deploy-sepolia": "hardhat run --network sepolia scripts/deploy.js",
|
||||
"deploy-work-contracts-local": "hardhat run --network localhost scripts/deploy-work-contracts.js",
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
const { ethers } = require('hardhat');
|
||||
const { execSync } = require('child_process');
|
||||
const { getContractAddressByNetworkName } = require('./contract-config');
|
||||
const readFromApi = require('./util/read-from-api');
|
||||
|
||||
const network = process.env.HARDHAT_NETWORK;
|
||||
let currentVersionProposalId;
|
||||
|
||||
let dao;
|
||||
let work1;
|
||||
|
@ -11,6 +13,22 @@ let account;
|
|||
let validationPools;
|
||||
let reputation;
|
||||
let posts;
|
||||
let proposalsContract;
|
||||
let proposals;
|
||||
|
||||
const getCurrentVersion = () => {
|
||||
const currentCommit = execSync('git rev-parse HEAD');
|
||||
return currentCommit.toString();
|
||||
};
|
||||
|
||||
const fetchCurrentVersionProposal = async () => {
|
||||
const p = await proposalsContract.
|
||||
};
|
||||
|
||||
const getLatestVersion = () => {
|
||||
const latestVersion = 'TBD';
|
||||
return latestVersion;
|
||||
};
|
||||
|
||||
const fetchReputation = async () => {
|
||||
reputation = await dao.balanceOf(account);
|
||||
|
@ -57,6 +75,22 @@ const fetchValidationPools = async () => {
|
|||
await Promise.all(promises);
|
||||
};
|
||||
|
||||
const fetchProposal = async (proposalIndex) => {
|
||||
const proposal = await proposalsContract.proposals(proposalIndex);
|
||||
proposals[proposalIndex] = proposal;
|
||||
};
|
||||
|
||||
const fetchProposals = async () => {
|
||||
const count = await proposalsContract.proposalCount();
|
||||
console.log(`proposal count: ${count}`);
|
||||
const promises = [];
|
||||
proposals = [];
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
promises.push(fetchProposal(i));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
};
|
||||
|
||||
const initialize = async () => {
|
||||
const getContract = (name) => ethers.getContractAt(
|
||||
name,
|
||||
|
@ -65,12 +99,14 @@ const initialize = async () => {
|
|||
dao = await getContract('DAO');
|
||||
work1 = await getContract('Work1');
|
||||
onboarding = await getContract('Onboarding');
|
||||
proposalsContract = await getContract('Proposals');
|
||||
[account] = await ethers.getSigners();
|
||||
const address = await account.getAddress();
|
||||
console.log(`account: ${address}`);
|
||||
posts = [];
|
||||
await fetchReputation();
|
||||
await fetchValidationPools();
|
||||
await fetchProposals();
|
||||
};
|
||||
|
||||
const poolIsActive = (pool) => {
|
||||
|
@ -121,7 +157,6 @@ const conditionalStake = async (pool, amountToStake) => {
|
|||
// We could consider automatic followup staking,
|
||||
// as a convenience if you decide early to favor a proposal
|
||||
} else {
|
||||
console.log('Unrecognized sender %s', pool.sender);
|
||||
await stake(pool, amountToStake, false);
|
||||
}
|
||||
};
|
||||
|
@ -144,6 +179,8 @@ const printPool = (pool) => {
|
|||
};
|
||||
|
||||
async function main() {
|
||||
console.log('Current version:', getCurrentVersion());
|
||||
|
||||
await initialize();
|
||||
|
||||
validationPools.forEach(printPool);
|
||||
|
|
Loading…
Reference in New Issue