Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1035 lines (879 sloc) 36.6 KB
pragma solidity ^0.5.0;
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "openzeppelin-solidity/contracts/lifecycle/Pausable.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol";
import "./ArbiterStaking.sol";
import "./NectarToken.sol";
contract BountyRegistry is Pausable, Ownable {
using SafeMath for uint256;
using SafeERC20 for NectarToken;
string public constant VERSION = "1.2.0";
enum ArtifactType {FILE, URL, _END}
struct Bounty {
uint128 guid;
ArtifactType artifactType;
address author;
uint256 amount;
string artifactURI;
uint256 numArtifacts;
uint256 expirationBlock;
address assignedArbiter;
bool quorumReached;
uint256 quorumBlock;
uint256 quorumMask;
}
struct Assertion {
address author;
uint256 bid;
uint256 mask;
uint256 commitment;
uint256 nonce;
uint256 verdicts;
string metadata;
}
struct Vote {
address author;
uint256 votes;
bool validBloom;
}
event AddedArbiter(
address arbiter,
uint256 blockNumber
);
event RemovedArbiter(
address arbiter,
uint256 blockNumber
);
event NewBounty(
uint128 guid,
uint256 artifactType,
address author,
uint256 amount,
string artifactURI,
uint256 expirationBlock
);
event NewAssertion(
uint128 bountyGuid,
address author,
uint256 index,
uint256 bid,
uint256 mask,
uint256 numArtifacts,
uint256 commitment
);
event RevealedAssertion(
uint128 bountyGuid,
address author,
uint256 index,
uint256 nonce,
uint256 verdicts,
uint256 numArtifacts,
string metadata
);
event NewVote(
uint128 bountyGuid,
uint256 votes,
uint256 numArtifacts,
address voter
);
event QuorumReached(
uint128 bountyGuid
);
event SettledBounty(
uint128 bountyGuid,
address settler,
uint256 payout
);
ArbiterStaking public staking;
NectarToken internal token;
uint256 public constant BOUNTY_AMOUNT_MINIMUM = 62500000000000000;
uint256 public constant ASSERTION_BID_MINIMUM = 62500000000000000;
uint256 public constant DEFAULT_BOUNTY_FEE = 62500000000000000;
uint256 public constant DEFAULT_ASSERTION_FEE = 31250000000000000;
uint256 public constant ARBITER_LOOKBACK_RANGE = 100;
uint256 public constant MAX_DURATION = 100; // BLOCKS
uint256 public constant ASSERTION_REVEAL_WINDOW = 25; // BLOCKS
uint256 public constant MALICIOUS_VOTE_COEFFICIENT = 10;
uint256 public constant BENIGN_VOTE_COEFFICIENT = 1;
uint256 public constant VALID_HASH_PERIOD = 256; // number of blocks in the past you can still get a blockhash
uint256 public bountyFee;
uint256 public assertionFee;
address public feeManager;
address public windowManager;
event NewFeeManager(
address indexed previousManager,
address indexed newManager
);
event FeesUpdated(
uint256 bountyFee,
uint256 assertionFee
);
event NewWindowManager(
address indexed previousManager,
address indexed newManager
);
event WindowsUpdated(
uint256 assertionRevealWindow,
uint256 arbiterVoteWindow
);
uint256 public arbiterCount;
uint256 public arbiterVoteWindow;
uint128[] public bountyGuids;
mapping(uint128 => Bounty) public bountiesByGuid;
mapping(uint128 => Assertion[]) public assertionsByGuid;
mapping(uint128 => Vote[]) public votesByGuid;
mapping(uint128 => uint256[8]) public bloomByGuid;
mapping(uint128 => mapping(uint256 => uint256)) public quorumVotesByGuid;
mapping(address => bool) public arbiters;
mapping(uint256 => mapping(uint256 => uint256)) public voteCountByGuid;
mapping(uint256 => mapping(address => bool)) public arbiterVoteRegistryByGuid;
mapping(uint256 => mapping(address => bool)) public expertAssertionResgistryByGuid;
mapping(uint128 => mapping(address => bool)) public bountySettled;
/**
* Construct a new BountyRegistry
*
* @param _token address of NCT token to use
*/
constructor(address _token, address _arbiterStaking, uint256 _arbiterVoteWindow) Ownable() public {
bountyFee = DEFAULT_BOUNTY_FEE;
assertionFee = DEFAULT_ASSERTION_FEE;
token = NectarToken(_token);
staking = ArbiterStaking(_arbiterStaking);
arbiterVoteWindow = _arbiterVoteWindow;
}
/** Function only callable by fee manager */
modifier onlyFeeManager() {
if (feeManager == address(0)) {
require(msg.sender == owner(), "Not a fee manager");
} else {
require(msg.sender == feeManager, "Not a fee manager");
}
_;
}
/**
* Set account which can update fees
*
* @param newFeeManager The new fee manager
*/
function setFeeManager(address newFeeManager) external onlyOwner {
emit NewFeeManager(feeManager, newFeeManager);
feeManager = newFeeManager;
}
/**
* Set bounty fee in NCT
*
* @param newBountyFee The new bounty fee in NCT
*/
function setBountyFee(uint256 newBountyFee) external onlyFeeManager {
bountyFee = newBountyFee;
emit FeesUpdated(bountyFee, assertionFee);
}
/**
* Set assertion fee in NCT
*
* @param newAssertionFee The new assertion fee in NCT
*/
function setAssertionFee(uint256 newAssertionFee) external onlyFeeManager {
assertionFee = newAssertionFee;
emit FeesUpdated(bountyFee, assertionFee);
}
/** Function only callable by fee manager */
modifier onlyWindowManager() {
if (windowManager == address(0)) {
require(msg.sender == owner(), "Not a window manager");
} else {
require(msg.sender == windowManager, "Not a window manager");
}
_;
}
/**
* Set account which can update windows
*
* @param newWindowManager The new fee manager
*/
function setWindowManager(address newWindowManager) external onlyOwner {
emit NewWindowManager(windowManager, newWindowManager);
windowManager = newWindowManager;
}
/**
* Set arbiter voting window in blocks
*
* @param newArbiterVoteWindow The new arbiter voting window in blocks
*/
function setArbiterVoteWindow(uint256 newArbiterVoteWindow) external onlyWindowManager {
arbiterVoteWindow = newArbiterVoteWindow;
// ASSERTION_REVEAL_WINDOW is a constant but emit it here for completeness
emit WindowsUpdated(ASSERTION_REVEAL_WINDOW, arbiterVoteWindow);
}
/**
* Function to check if an address is a valid arbiter
*
* @param addr The address to check
* @return true if addr is a valid arbiter else false
*/
function isArbiter(address addr) public view returns (bool) {
// Remove arbiter requirements for now, while we are whitelisting
// arbiters on the platform
//return arbiters[addr] && staking.isEligible(addr);
return arbiters[addr];
}
/** Function only callable by arbiter */
modifier onlyArbiter() {
require(isArbiter(msg.sender), "msg.sender is not an arbiter");
_;
}
/**
* Function called to add an arbiter, emits an evevnt with the added arbiter
* and block number used to calculate their arbiter status based on public
* arbiter selection algorithm.
*
* @param newArbiter the arbiter to add
* @param blockNumber the block number the determination to add was
* calculated from
*/
function addArbiter(address newArbiter, uint256 blockNumber) external whenNotPaused onlyOwner {
require(newArbiter != address(0), "Invalid arbiter address");
require(!arbiters[newArbiter], "Address is already an arbiter");
arbiterCount = arbiterCount.add(1);
arbiters[newArbiter] = true;
emit AddedArbiter(newArbiter, blockNumber);
}
/**
* Function called to remove an arbiter, emits an evevnt with the removed
* arbiter and block number used to calculate their arbiter status based on
* public arbiter selection algorithm.
*
* @param arbiter the arbiter to remove
* @param blockNumber the block number the determination to remove was
* calculated from
*/
function removeArbiter(address arbiter, uint256 blockNumber) external whenNotPaused onlyOwner {
arbiters[arbiter] = false;
arbiterCount = arbiterCount.sub(1);
emit RemovedArbiter(arbiter, blockNumber);
}
/**
* Function called by end users and ambassadors to post a bounty
*
* @param guid the guid of the bounty, must be unique
* @param amount the amount of NCT to post as a reward
* @param artifactURI uri of the artifacts comprising this bounty
* @param durationBlocks duration of this bounty in blocks
*/
function postBounty(
uint128 guid,
uint256 artifactType,
uint256 amount,
string calldata artifactURI,
uint256 numArtifacts,
uint256 durationBlocks,
uint256[8] calldata bloom
)
external
whenNotPaused
{
// Check if a bounty with this GUID has already been initialized
require(bountiesByGuid[guid].author == address(0), "GUID already in use");
// Check that our bounty amount is sufficient
require(amount >= BOUNTY_AMOUNT_MINIMUM, "Bounty amount below minimum");
// Check that our URI is non-empty
require(bytes(artifactURI).length > 0, "Invalid artifact URI");
// Check that our number of artifacts is valid
require(numArtifacts <= 256, "Too many artifacts in bounty");
require(numArtifacts > 0, "Not enough artifacts in bounty");
// Check that our duration is non-zero and less than or equal to the max
require(durationBlocks > 0 && durationBlocks <= MAX_DURATION, "Invalid bounty duration");
// Check that artifactType int does not exceed number of values
require(uint(ArtifactType._END) > artifactType, "Invalid artifact type");
// Assess fees and transfer bounty amount into escrow
token.safeTransferFrom(msg.sender, address(this), amount.add(bountyFee));
bountiesByGuid[guid].guid = guid;
bountiesByGuid[guid].artifactType = ArtifactType(artifactType);
bountiesByGuid[guid].author = msg.sender;
bountiesByGuid[guid].amount = amount;
bountiesByGuid[guid].artifactURI = artifactURI;
// Number of artifacts is submitted as part of the bounty, we have no
// way to check how many exist in this IPFS resource. For an IPFS
// resource with N artifacts, if numArtifacts < N only the first
// numArtifacts artifacts are included in this bounty, if numArtifacts >
// N then the last N - numArtifacts bounties are considered benign.
bountiesByGuid[guid].numArtifacts = numArtifacts;
bountiesByGuid[guid].expirationBlock = durationBlocks.add(block.number);
bountyGuids.push(guid);
bloomByGuid[guid] = bloom;
Bounty storage b = bountiesByGuid[guid];
emit NewBounty(
b.guid,
uint256(b.artifactType),
b.author,
b.amount,
b.artifactURI,
b.expirationBlock
);
}
/**
* Function called by security experts to post an assertion on a bounty
*
* @param bountyGuid the guid of the bounty to assert on
* @param bid the amount of NCT to stake
* @param mask the artifacts to assert on from the set in the bounty
* @param commitment a commitment hash of the verdicts being asserted, equal
* to keccak256(verdicts ^ keccak256(nonce)) where nonce != 0
*/
function postAssertion(
uint128 bountyGuid,
uint256 bid,
uint256 mask,
uint256 commitment
)
external
whenNotPaused
{
// Check if this bounty has been initialized
require(bountiesByGuid[bountyGuid].author != address(0), "Bounty has not been initialized");
// Check that our bid amount is sufficient
require(bid >= ASSERTION_BID_MINIMUM, "Assertion bid below minimum");
// Check if this bounty is active
require(bountiesByGuid[bountyGuid].expirationBlock > block.number, "Bounty inactive");
// Check if the sender has already made an assertion
require(expertAssertionResgistryByGuid[bountyGuid][msg.sender] == false, "Sender has already asserted");
// Assess fees and transfer bid amount into escrow
token.safeTransferFrom(msg.sender, address(this), bid.add(assertionFee));
expertAssertionResgistryByGuid[bountyGuid][msg.sender] = true;
Assertion memory a = Assertion(
msg.sender,
bid,
mask,
commitment,
0,
0,
""
);
uint256 index = assertionsByGuid[bountyGuid].push(a) - 1;
uint256 numArtifacts = bountiesByGuid[bountyGuid].numArtifacts;
emit NewAssertion(
bountyGuid,
a.author,
index,
a.bid,
a.mask,
numArtifacts,
a.commitment
);
}
// https://ethereum.stackexchange.com/questions/4170/how-to-convert-a-uint-to-bytes-in-solidity
function uint256_to_bytes(uint256 x) internal pure returns (bytes memory b) {
b = new bytes(32);
// solium-disable-next-line security/no-inline-assembly
assembly { mstore(add(b, 32), x) }
}
/**
* Function called by security experts to reveal an assertion after bounty
* expiration
*
* @param bountyGuid the guid of the bounty to assert on
* @param assertionId the id of the assertion to reveal
* @param assertionId the id of the assertion to reveal
* @param nonce the nonce used to generate the commitment hash
* @param verdicts the verdicts making up this assertion
* @param metadata optional metadata to include in the assertion
*/
function revealAssertion(
uint128 bountyGuid,
uint256 assertionId,
uint256 nonce,
uint256 verdicts,
string calldata metadata
)
external
whenNotPaused
{
// Check if this bounty has been initialized
require(bountiesByGuid[bountyGuid].author != address(0), "Bounty has not been initialized");
// Check that the bounty is no longer active
require(bountiesByGuid[bountyGuid].expirationBlock <= block.number, "Bounty is still active");
// Check if the reveal round has closed
require(bountiesByGuid[bountyGuid].expirationBlock.add(ASSERTION_REVEAL_WINDOW) > block.number, "Reveal round has closed");
// Get numArtifacts to help decode all zero verdicts
uint256 numArtifacts = bountiesByGuid[bountyGuid].numArtifacts;
// Zero is defined as an invalid nonce
require(nonce != 0, "Invalid nonce");
// Check our id
require(assertionId < assertionsByGuid[bountyGuid].length, "Invalid assertion ID");
Assertion storage a = assertionsByGuid[bountyGuid][assertionId];
require(a.author == msg.sender, "Incorrect assertion author");
require(a.nonce == 0, "Bounty already revealed");
// Check our commitment hash, by xor-ing verdicts with the hashed nonce
// and the sender's address prevent copying assertions by submitting the
// same commitment hash and nonce during the reveal round
uint256 hashed_nonce = uint256(keccak256(uint256_to_bytes(nonce)));
uint256 commitment = uint256(keccak256(uint256_to_bytes(verdicts ^ hashed_nonce ^ uint256(msg.sender))));
require(commitment == a.commitment, "Commitment hash mismatch");
a.nonce = nonce;
a.verdicts = verdicts;
a.metadata = metadata;
emit RevealedAssertion(
bountyGuid,
a.author,
assertionId,
a.nonce,
a.verdicts,
numArtifacts,
a.metadata
);
}
/**
* Function called by arbiter after bounty expiration to settle with their
* ground truth determination and pay out assertion rewards
*
* @param bountyGuid the guid of the bounty to settle
* @param votes bitset of votes representing ground truth for the
* bounty's artifacts
*/
function voteOnBounty(
uint128 bountyGuid,
uint256 votes,
bool validBloom
)
external
onlyArbiter
whenNotPaused
{
Bounty storage bounty = bountiesByGuid[bountyGuid];
Vote[] storage bountyVotes = votesByGuid[bountyGuid];
// Check if this bounty has been initialized
require(bounty.author != address(0), "Bounty has not been initialized");
// Check that the reveal round has closed
require(bounty.expirationBlock.add(ASSERTION_REVEAL_WINDOW) <= block.number, "Reveal round is still active");
// Check if the voting round has closed
require(bounty.expirationBlock.add(ASSERTION_REVEAL_WINDOW).add(arbiterVoteWindow) > block.number, "Voting round has closed");
// Check to make sure arbiters can't double vote
require(arbiterVoteRegistryByGuid[bountyGuid][msg.sender] == false, "Arbiter has already voted");
Vote memory a = Vote(
msg.sender,
votes,
validBloom
);
votesByGuid[bountyGuid].push(a);
staking.recordBounty(msg.sender, bountyGuid, block.number);
arbiterVoteRegistryByGuid[bountyGuid][msg.sender] = true;
uint256 tempQuorumMask = 0;
uint256 quorumCount = 0;
mapping (uint256 => uint256) storage quorumVotes = quorumVotesByGuid[bountyGuid];
for (uint256 i = 0; i < bounty.numArtifacts; i++) {
if (bounty.quorumMask != 0 && (bounty.quorumMask & (1 << i) != 0)) {
tempQuorumMask = tempQuorumMask.add(calculateMask(i, 1));
quorumCount = quorumCount.add(1);
continue;
}
if (votes & (1 << i) != 0) {
quorumVotes[i] = quorumVotes[i].add(1);
}
uint256 benignVotes = bountyVotes.length.sub(quorumVotes[i]);
uint256 maxBenignValue = arbiterCount.sub(quorumVotes[i]).mul(BENIGN_VOTE_COEFFICIENT);
uint256 maxMalValue = arbiterCount.sub(benignVotes).mul(MALICIOUS_VOTE_COEFFICIENT);
if (quorumVotes[i].mul(MALICIOUS_VOTE_COEFFICIENT) >= maxBenignValue || benignVotes.mul(BENIGN_VOTE_COEFFICIENT) > maxMalValue) {
tempQuorumMask = tempQuorumMask.add(calculateMask(i, 1));
quorumCount = quorumCount.add(1);
}
}
// set new mask
bounty.quorumMask = tempQuorumMask;
// check if all arbiters have voted or if we have quorum for all the artifacts
if ((bountyVotes.length == arbiterCount || quorumCount == bounty.numArtifacts) && !bounty.quorumReached) {
bounty.quorumReached = true;
bounty.quorumBlock = block.number.sub(bountiesByGuid[bountyGuid].expirationBlock);
emit QuorumReached(bountyGuid);
}
emit NewVote(bountyGuid, votes, bounty.numArtifacts, msg.sender);
}
// This struct exists to move state from settleBounty into memory from stack
// to avoid solidity limitations
struct ArtifactPot {
uint256 numWinners;
uint256 numLosers;
uint256 winnerPool;
uint256 loserPool;
}
/**
* Function to calculate the reward disbursment of a bounty
*
* @param bountyGuid the guid of the bounty to calculate
* @return Rewards distributed by the bounty
*/
function calculateBountyRewards(
uint128 bountyGuid
)
public
view
returns (uint256 bountyRefund, uint256 arbiterReward, uint256[] memory expertRewards)
{
Bounty storage bounty = bountiesByGuid[bountyGuid];
Assertion[] storage assertions = assertionsByGuid[bountyGuid];
Vote[] storage votes = votesByGuid[bountyGuid];
mapping (uint256 => uint256) storage quorumVotes = quorumVotesByGuid[bountyGuid];
// Check if this bountiesByGuid[bountyGuid] has been initialized
require(bounty.author != address(0), "Bounty has not been initialized");
// Check if this bounty has been previously resolved for the sender
require(!bountySettled[bountyGuid][msg.sender], "Bounty has already been settled for sender");
// Check that the voting round has closed
// solium-disable-next-line indentation
require(bounty.expirationBlock.add(ASSERTION_REVEAL_WINDOW).add(arbiterVoteWindow) <= block.number || bounty.quorumReached,
"Voting round is still active and quorum has not been reached");
expertRewards = new uint256[](assertions.length);
ArtifactPot memory ap = ArtifactPot({numWinners: 0, numLosers: 0, winnerPool: 0, loserPool: 0});
uint256 i = 0;
uint256 j = 0;
if (assertions.length == 0 && votes.length == 0) {
// Refund the bounty amount and fees to ambassador
bountyRefund = bounty.numArtifacts.mul(bounty.amount.add(bountyFee));
} else if (assertions.length == 0) {
// Refund the bounty amount ambassador
bountyRefund = bounty.amount.mul(bounty.numArtifacts);
} else if (votes.length == 0) {
// Refund bids, fees, and distribute the bounty amount evenly to experts
bountyRefund = bountyFee.mul(bounty.numArtifacts);
for (j = 0; j < assertions.length; j++) {
expertRewards[j] = expertRewards[j].add(assertionFee);
expertRewards[j] = expertRewards[j].add(assertions[j].bid);
expertRewards[j] = expertRewards[j].add(bounty.amount.div(assertions.length));
expertRewards[j] = expertRewards[j].mul(bounty.numArtifacts);
}
} else {
for (i = 0; i < bounty.numArtifacts; i++) {
ap = ArtifactPot({numWinners: 0, numLosers: 0, winnerPool: 0, loserPool: 0});
bool consensus = quorumVotes[i].mul(MALICIOUS_VOTE_COEFFICIENT) >= votes.length.sub(quorumVotes[i]).mul(BENIGN_VOTE_COEFFICIENT);
bool malicious;
for (j = 0; j < assertions.length; j++) {
// If we didn't assert on this artifact
if (assertions[j].mask & (1 << i) == 0) {
continue;
}
// If we haven't revealed set to incorrect value
if (assertions[j].nonce == 0) {
malicious = !consensus;
} else {
malicious = (assertions[j].verdicts & assertions[j].mask) & (1 << i) != 0;
}
if (malicious == consensus) {
ap.numWinners = ap.numWinners.add(1);
ap.winnerPool = ap.winnerPool.add(assertions[j].bid);
} else {
ap.numLosers = ap.numLosers.add(1);
ap.loserPool = ap.loserPool.add(assertions[j].bid);
}
}
// If nobody asserted on this artifact, refund the ambassador
if (ap.numWinners == 0 && ap.numLosers == 0) {
bountyRefund = bountyRefund.add(bounty.amount);
for (j = 0; j < assertions.length; j++) {
expertRewards[j] = expertRewards[j].add(assertions[j].bid);
}
} else {
for (j = 0; j < assertions.length; j++) {
expertRewards[j] = expertRewards[j].add(assertions[j].bid);
// If we didn't assert on this artifact
if (assertions[j].mask & (1 << i) == 0) {
continue;
}
// If we haven't revealed set to incorrect value
if (assertions[j].nonce == 0) {
malicious = !consensus;
} else {
malicious = (assertions[j].verdicts & assertions[j].mask) & (1 << i) != 0;
}
if (malicious == consensus) {
expertRewards[j] = expertRewards[j].add(assertions[j].bid.mul(ap.loserPool).div(ap.winnerPool));
expertRewards[j] = expertRewards[j].add(bounty.amount.mul(assertions[j].bid).div(ap.winnerPool));
} else {
expertRewards[j] = expertRewards[j].sub(assertions[j].bid);
}
}
}
}
}
// Calculate rewards
uint256 pot = bounty.amount.add(bountyFee.add(assertionFee.mul(assertions.length)));
for (i = 0; i < assertions.length; i++) {
pot = pot.add(assertions[i].bid);
}
bountyRefund = bountyRefund.div(bounty.numArtifacts);
pot = pot.sub(bountyRefund);
for (i = 0; i < assertions.length; i++) {
expertRewards[i] = expertRewards[i].div(bounty.numArtifacts);
pot = pot.sub(expertRewards[i]);
}
arbiterReward = pot;
}
/**
* Function called after window has closed to handle reward disbursal
*
* This function will pay out rewards if the the bounty has a super majority
* @param bountyGuid the guid of the bounty to settle
*/
function settleBounty(uint128 bountyGuid) external whenNotPaused {
Bounty storage bounty = bountiesByGuid[bountyGuid];
Assertion[] storage assertions = assertionsByGuid[bountyGuid];
// Check if this bountiesByGuid[bountyGuid] has been initialized
require(bounty.author != address(0), "Bounty has not been initialized");
// Check if this bounty has been previously resolved for the sender
require(!bountySettled[bountyGuid][msg.sender], "Bounty has already been settled for sender");
// Check that the voting round has closed
// solium-disable-next-line indentation
require(bounty.expirationBlock.add(ASSERTION_REVEAL_WINDOW).add(arbiterVoteWindow) <= block.number || bounty.quorumReached,
"Voting round is still active and quorum has not been reached");
if (isArbiter(msg.sender)) {
require(bounty.expirationBlock.add(ASSERTION_REVEAL_WINDOW).add(arbiterVoteWindow) <= block.number, "Voting round still active");
if (bounty.assignedArbiter == address(0)) {
if (bounty.expirationBlock.add(ASSERTION_REVEAL_WINDOW).add(arbiterVoteWindow).add(VALID_HASH_PERIOD) >= block.number) {
bounty.assignedArbiter = getWeightedRandomArbiter(bountyGuid);
} else {
bounty.assignedArbiter = msg.sender;
}
}
}
uint256 payout = 0;
uint256 bountyRefund;
uint256 arbiterReward;
uint256[] memory expertRewards;
(bountyRefund, arbiterReward, expertRewards) = calculateBountyRewards(bountyGuid);
bountySettled[bountyGuid][msg.sender] = true;
// Disburse rewards
if (bountyRefund != 0 && bounty.author == msg.sender) {
token.safeTransfer(bounty.author, bountyRefund);
payout = payout.add(bountyRefund);
}
for (uint256 i = 0; i < assertions.length; i++) {
if (expertRewards[i] != 0 && assertions[i].author == msg.sender) {
token.safeTransfer(assertions[i].author, expertRewards[i]);
payout = payout.add(expertRewards[i]);
}
}
if (arbiterReward != 0 && bounty.assignedArbiter == msg.sender) {
token.safeTransfer(bounty.assignedArbiter, arbiterReward);
payout = payout.add(arbiterReward);
}
emit SettledBounty(bountyGuid, msg.sender, payout);
}
/**
* Generates a random number from 0 to range based on the last block hash
*
* @param seed random number for reproducing
* @param range end range for random number
*/
function randomGen(uint256 targetBlock, uint seed, uint256 range) private view returns (int256 randomNumber) {
return int256(uint256(keccak256(abi.encodePacked(blockhash(targetBlock), seed))) % range);
}
/**
* Gets a random Arbiter weighted by the amount of Nectar they have
*
* @param bountyGuid the guid of the bounty
*/
function getWeightedRandomArbiter(uint128 bountyGuid) public view returns (address voter) {
require(bountiesByGuid[bountyGuid].author != address(0), "Bounty has not been initialized");
Bounty memory bounty = bountiesByGuid[bountyGuid];
Vote[] memory votes = votesByGuid[bountyGuid];
if (votes.length == 0) {
return address(0);
}
uint i;
uint256 sum = 0;
int256 randomNum;
for (i = 0; i < votes.length; i++) {
sum = sum.add(staking.balanceOf(votes[i].author));
}
randomNum = randomGen(bounty.expirationBlock.add(ASSERTION_REVEAL_WINDOW).add(arbiterVoteWindow), block.number, sum);
for (i = 0; i < votes.length; i++) {
randomNum -= int256(staking.balanceOf(votes[i].author));
if (randomNum <= 0) {
voter = votes[i].author;
break;
}
}
}
/**
* Get the total number of bounties tracked by the contract
* @return total number of bounties
*/
function getNumberOfBounties() external view returns (uint) {
return bountyGuids.length;
}
/**
* Get the current round for a bounty
*
* @param bountyGuid the guid of the bounty
* @return the current round
* 0 = assertions being accepted
* 1 = assertions being revealed
* 2 = arbiters voting
* 3 = bounty finished
*/
function getCurrentRound(uint128 bountyGuid) external view returns (uint) {
// Check if this bounty has been initialized
require(bountiesByGuid[bountyGuid].author != address(0), "Bounty has not been initialized");
Bounty memory bounty = bountiesByGuid[bountyGuid];
if (bounty.expirationBlock > block.number) {
return 0;
} else if (bounty.expirationBlock.add(ASSERTION_REVEAL_WINDOW) > block.number) {
return 1;
} else if (bounty.expirationBlock.add(ASSERTION_REVEAL_WINDOW).add(arbiterVoteWindow) > block.number &&
!bounty.quorumReached) {
return 2;
} else {
return 3;
}
}
/**
* Gets the number of assertions for a bounty
*
* @param bountyGuid the guid of the bounty
* @return number of assertions for the given bounty
*/
function getNumberOfAssertions(uint128 bountyGuid) external view returns (uint) {
// Check if this bounty has been initialized
require(bountiesByGuid[bountyGuid].author != address(0), "Bounty has not been initialized");
return assertionsByGuid[bountyGuid].length;
}
/**
* Gets the vote count for a specific bounty
*
* @param bountyGuid the guid of the bounty
*/
function getNumberOfVotes(uint128 bountyGuid) external view returns (uint) {
require(bountiesByGuid[bountyGuid].author != address(0), "Bounty has not been initialized");
return votesByGuid[bountyGuid].length;
}
/**
* Gets all the voters for a specific bounty
*
* @param bountyGuid the guid of the bounty
*/
function getVoters(uint128 bountyGuid) external view returns (address[] memory) {
require(bountiesByGuid[bountyGuid].author != address(0), "Bounty has not been initialized");
Vote[] memory votes = votesByGuid[bountyGuid];
uint count = votes.length;
address[] memory voters = new address[](count);
for (uint i = 0; i < count; i++) {
voters[i] = votes[i].author;
}
return voters;
}
/** Candidate for future arbiter */
struct Candidate {
address addr;
uint256 count;
}
/**
* View function displays most active bounty posters over past
* ARBITER_LOOKBACK_RANGE bounties to select future arbiters
*
* @return sorted array of most active bounty posters
*/
function getArbiterCandidates() external view returns (address[] memory) {
require(bountyGuids.length > 0, "No bounties have been placed");
uint256 count = 0;
uint256 i = 0;
uint256 j = 0;
Candidate[] memory candidates = new Candidate[](ARBITER_LOOKBACK_RANGE);
uint256 lastBounty = 0;
if (bountyGuids.length > ARBITER_LOOKBACK_RANGE) {
lastBounty = bountyGuids.length.sub(ARBITER_LOOKBACK_RANGE);
}
for (i = bountyGuids.length; i > lastBounty; i--) {
address addr = bountiesByGuid[bountyGuids[i.sub(1)]].author;
bool found = false;
for (j = 0; j < count; j++) {
if (candidates[j].addr == addr) {
candidates[j].count = candidates[j].count.add(1);
found = true;
break;
}
}
if (!found) {
candidates[count] = Candidate(addr, 1);
count = count.add(1);
}
}
address[] memory ret = new address[](count);
for (i = 0; i < ret.length; i++) {
uint256 next = 0;
uint256 value = candidates[0].count;
for (j = 0; j < count; j++) {
if (candidates[j].count > value) {
next = j;
value = candidates[j].count;
}
}
ret[i] = candidates[next].addr;
candidates[next] = candidates[count.sub(1)];
count = count.sub(1);
}
return ret;
}
function calculateMask(uint256 i, uint256 b) public pure returns(uint256) {
if (b != 0) {
return 1 << i;
}
return 0;
}
/**
* View function displays the most active bounty voters over past
* ARBITER_LOOKBACK_RANGE bounties to select future arbiters
*
* @return a sorted array of most active bounty voters and a boolean array of whether
* or not they were active in 90% of bounty votes
*/
function getActiveArbiters() external view returns (address[] memory, bool[] memory) {
require(bountyGuids.length > 0, "No bounties have been placed");
uint256 count = 0;
uint256 threshold = bountyGuids.length.div(10).mul(9);
address[] memory ret_addr = new address[](count);
bool[] memory ret_arbiter_ativity_threshold = new bool[](count);
Candidate[] memory candidates = new Candidate[](ARBITER_LOOKBACK_RANGE);
uint256 lastBounty = 0;
uint256 i = 0;
uint256 j = 0;
if (bountyGuids.length > ARBITER_LOOKBACK_RANGE) {
lastBounty = bountyGuids.length.sub(ARBITER_LOOKBACK_RANGE);
threshold = lastBounty.div(10).mul(9);
}
for (i = bountyGuids.length.sub(1); i > lastBounty; i--) {
Vote[] memory votes = votesByGuid[bountyGuids[i]];
for (j = 0; j < votes.length; j++) {
bool found = false;
address addr = votes[j].author;
for (uint256 k = 0; k < count; k++) {
if (candidates[k].addr == addr) {
candidates[k].count = candidates[k].count.add(1);
found = true;
break;
}
}
if (!found) {
candidates[count] = Candidate(addr, 1);
count = count.add(1);
}
}
}
for (i = 0; i < ret_addr.length; i++) {
uint256 next = 0;
uint256 value = candidates[0].count;
for (j = 0; j < count; j++) {
if (candidates[j].count > value) {
next = j;
value = candidates[j].count;
}
}
ret_addr[i] = candidates[next].addr;
if (candidates[next].count.div(10).mul(9) < threshold) {
ret_arbiter_ativity_threshold[i] = false;
} else {
ret_arbiter_ativity_threshold[i] = true;
}
count = count.sub(1);
candidates[next] = candidates[count];
}
return (ret_addr, ret_arbiter_ativity_threshold);
}
}
You can’t perform that action at this time.