Merge pull request #14 from ConsenSys/no-new-contract-no-milestones

No new contract no milestones
This commit is contained in:
Mark Beylin 2017-10-02 12:49:03 -04:00 committed by GitHub
commit 6e66beadc6
20 changed files with 5652 additions and 3672 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,6 +1,6 @@
# StandardBounties
`Version 0.0.1`
`Version 1.0.0`
1. [Rationale](#1-rationale)
2. [Implementation](#2-implementation)
@ -13,12 +13,12 @@ A set of standard contracts to be used as interfaces for any kind of bounty, eit
Ethereum smart contracts can trivially facilitate transactions of resources (or tokens) between individuals or groups, but service transactions are more complex. Requesting individuals (issuers) must first approve the work they're receiving before paying out funds, meaning bounty hunters must have trust that the issuer will pay them in the spirit of the original contract.
The _StandardBounty.sol_ contract facilitates transactions on qualitative data (often representing artifacts of completion of some service), allowing bounty issuers to systematically approve the work they receive.
The _StandardBounties.sol_ contract facilitates transactions on qualitative data (often representing artifacts of completion of some service), allowing bounty issuers to systematically approve the work they receive.
## 2. Implementation
A single bounty contract can be used to pay amounts of ETH or a given token, based on the successful completion of specific **Milestones**. The contract aims to reduce the necessary trust in the issuer by forcing them to deposit sufficient Ether (or tokens) to at minimum pay out each milestone once.
A single bounty contract can be used to pay amounts of ETH or a given token, based on the successful completion of the given task. The contract aims to reduce the necessary trust in the issuer by forcing them to deposit sufficient Ether (or tokens) to at minimum pay out each milestone once.
- A bounty begins in the `Draft` state, where the requirements, deadline, arbiter, and reward amounts can still be altered.
@ -28,7 +28,11 @@ A single bounty contract can be used to pay amounts of ETH or a given token, bas
- `killBounty()` [**ONLY ISSUER**]: This will kill the bounty
As well as several functions to alter the bounty details:
- `changeBounty()` [**ONLY ISSUER**]
- `changeBountyDeadline()` [**ONLY ISSUER**]
- `changeBountyData()` [**ONLY ISSUER**]
- `changeBountyFulfillmentAmount()` [**ONLY ISSUER**]
- `changeBountyArbiter()` [**ONLY ISSUER**]
- `changeBountyPaysTokens()` [**ONLY ISSUER**]
- `extendDeadline()` [**ONLY ISSUER**]
- A bounty transitions to the `Active` state when the issuer calls `activateBounty()`.
@ -41,8 +45,12 @@ A single bounty contract can be used to pay amounts of ETH or a given token, bas
In this state, the various functions which can be called are:
- `fulfillBounty()` [**ANYONE BUT ISSUER OR ARBITER**]:
- `updateFulfillment()` [**ONLY FULFILLER**]
- `acceptFulfillment()` [**ONLY ISSUER OR ARBITER**]:
- `fulfillmentPayment()` [**ONLY FULFILLER**]:
- `increasePayout()` [**ONLY ISSUER**]:
- `transferIssuer()` [**ONLY ISSUER**]
- `extendDeadline()` [**ONLY ISSUER**]
- `killBounty()` [**ONLY ISSUER**]:
- A bounty transitions to the `Dead` state when the issuer calls `killBounty()`, which drains the contract of its balance, less the necessary funds to pay out fulfillments which have already been accepted but aren't yet paid out.

View File

@ -1,146 +0,0 @@
pragma solidity ^0.4.11;
import "./StandardBountyFactory.sol";
import "./TokenBountyFactory.sol";
/// @title Bounties factory
/// @author Mark Beylin <mark.beylin@consensys.net>, Gonçalo <goncalo.sa@consensys.net>
contract BountyRegistry{
event ContractInstantiation(address sender, address instantiation);
StandardBountyFactory standardBountyFactory;
TokenBountyFactory tokenBountyFactory;
address[] public instances;
address public owner;
mapping(address => address[]) public instantiations;
mapping(address => bool) public isInstantiation;
modifier onlyOwner(){
require(msg.sender == owner);
_;
}
modifier correctId(uint _bountyId, address _bountyAddress){
require(instances[_bountyId] == _bountyAddress);
_;
}
modifier correctUser(uint _userId, address _userAddress, address _bountyAddress){
require(instantiations[_userAddress][_userId] == _bountyAddress);
_;
}
/// @dev constructor for the factory
/// @param _standardBountyFactory the address of the already deployed standard bounty factory
/// @param _tokenBountyFactory the address of the already deployed token bounty factory
function BountyRegistry(address _standardBountyFactory, address _tokenBountyFactory){
owner = msg.sender;
standardBountyFactory = StandardBountyFactory(_standardBountyFactory);
tokenBountyFactory = TokenBountyFactory(_tokenBountyFactory);
}
/// @dev Allows multiple creations of bounties
/// @param _issuer the address of the issuer
/// @param _deadline the unix timestamp after which fulfillments will no longer be accepted
/// @param _data the requirements of the bounty
/// @param _fulfillmentAmounts the amount of wei to be paid out for each successful fulfillment
/// @param _arbiter the address of the arbiter who can mediate claims
/// @param _tokenContract if the bounty pays out in tokens, the address of the token contract
function create(
address _issuer,
uint _deadline,
string _data,
uint[] _fulfillmentAmounts,
uint _totalFulfillmentAmounts,
address _arbiter,
address _tokenContract
)
public
{
address bounty;
if (_tokenContract == address(0)){
bounty = standardBountyFactory.create(_issuer,
_deadline,
_data,
_fulfillmentAmounts,
_totalFulfillmentAmounts,
_arbiter);
} else {
bounty = tokenBountyFactory.create(_issuer,
_deadline,
_data,
_fulfillmentAmounts,
_totalFulfillmentAmounts,
_arbiter,
_tokenContract);
}
register(bounty);
}
/// @dev Returns number of instantiations by creator.
/// @param creator Contract creator.
/// @return Returns number of instantiations by creator.
function getInstantiationCount(address creator)
public
constant
returns (uint)
{
return instantiations[creator].length;
}
/// @dev Registers contract in factory registry.
/// @param _instantiation Address of contract instantiation.
function register(address _instantiation)
internal
{
isInstantiation[_instantiation] = true;
instances.push(_instantiation);
instantiations[msg.sender].push(_instantiation);
ContractInstantiation(msg.sender, _instantiation);
}
/// @dev Returns number of instances
/// @return Returns number of instantiations by creator.
function getInstanceCount()
public
constant
returns (uint)
{
return instances.length;
}
/// @dev Enables the creator of the factory to remove unwanted bounties
/// @param _bountyId the ID of the bounty
/// @param _bountyAddress the address of the bounty
/// @param _userId the index of the bounty in the user's array
/// @param _userAddress the address of the original bounty
function remove(uint _bountyId, address _bountyAddress, uint _userId, address _userAddress)
public
onlyOwner
correctId(_bountyId, _bountyAddress)
correctUser(_userId, _userAddress, _bountyAddress)
{
delete isInstantiation[_bountyAddress];
delete instances[_bountyId];
delete instantiations[_userAddress][_userId];
}
/// @dev Enables the creator of the factory transfer ownership
/// @param _newOwner the new address of the owner
function transferOwner(address _newOwner)
public
onlyOwner
{
owner = _newOwner;
}
}

View File

@ -0,0 +1,642 @@
pragma solidity ^0.4.11;
import "./inherited/HumanStandardToken.sol";
/// @title StandardBounties
/// @dev Used to pay out individuals or groups for task fulfillment through
/// stepwise work submission, acceptance, and payment
/// @author Mark Beylin <mark.beylin@consensys.net>, Gonçalo <goncalo.sa@consensys.net>
contract StandardBounties {
/*
* Events
*/
event BountyIssued(uint bountyId);
event BountyActivated(uint bountyId, address issuer);
event BountyFulfilled(uint bountyId, address indexed fulfiller, uint256 indexed _fulfillmentId);
event FulfillmentUpdated(uint _bountyId, uint _fulfillmentId);
event FulfillmentAccepted(uint bountyId, address indexed fulfiller, uint256 indexed _fulfillmentId);
event FulfillmentPaid(uint bountyId, address indexed fulfiller, uint256 indexed _fulfillmentId);
event BountyKilled(uint bountyId);
event ContributionAdded(uint bountyId, address indexed contributor, uint256 value);
event DeadlineExtended(uint bountyId, uint newDeadline);
event BountyChanged(uint bountyId);
event IssuerTransferred(uint _bountyId, address indexed _newIssuer);
event PayoutIncreased(uint _bountyId, uint _newFulfillmentAmount);
/*
* Storage
*/
address public owner;
Bounty[] public bounties;
mapping(uint=>Fulfillment[]) fulfillments;
mapping(uint=>uint) numAccepted;
mapping(uint=>uint) numPaid;
mapping(uint=>HumanStandardToken) tokenContracts;
/*
* Enums
*/
enum BountyStages {
Draft,
Active,
Dead
}
/*
* Structs
*/
struct Bounty {
address issuer;
uint deadline;
string data;
uint fulfillmentAmount;
address arbiter;
bool paysTokens;
BountyStages bountyStage;
uint owedAmount;
uint balance;
}
struct Fulfillment {
bool paid;
bool accepted;
address fulfiller;
string data;
}
/*
* Modifiers
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
modifier validateBountyArrayIndex(uint _bountyId){
require(_bountyId < bounties.length);
_;
}
modifier onlyIssuer(uint _bountyId) {
require(msg.sender == bounties[_bountyId].issuer);
_;
}
modifier onlyFulfiller(uint _bountyId, uint _fulfillmentId) {
require(msg.sender == fulfillments[_bountyId][_fulfillmentId].fulfiller);
_;
}
modifier amountIsNotZero(uint _amount) {
require(_amount != 0);
_;
}
modifier transferredAmountEqualsValue(uint _bountyId, uint _amount) {
if (bounties[_bountyId].paysTokens){
uint oldBalance = tokenContracts[_bountyId].balanceOf(this);
if (_amount != 0){
require(tokenContracts[_bountyId].transferFrom(msg.sender, this, _amount));
}
require((tokenContracts[_bountyId].balanceOf(this) - oldBalance) == _amount);
} else {
require((_amount * 1 wei) == msg.value);
}
_;
}
modifier isBeforeDeadline(uint _bountyId) {
require(now < bounties[_bountyId].deadline);
_;
}
modifier validateDeadline(uint _newDeadline) {
require(_newDeadline > now);
_;
}
modifier isAtStage(uint _bountyId, BountyStages _desiredStage) {
require(bounties[_bountyId].bountyStage == _desiredStage);
_;
}
modifier validateFulfillmentArrayIndex(uint _bountyId, uint _index) {
require(_index < fulfillments[_bountyId].length);
_;
}
modifier notYetAccepted(uint _bountyId, uint _fulfillmentId){
require(fulfillments[_bountyId][_fulfillmentId].accepted == false);
_;
}
/*
* Public functions
*/
/// @dev StandardBounties(): instantiates
/// @param _owner the issuer of the standardbounties contract, who has the
/// ability to remove bounties
function StandardBounties(address _owner)
public
{
owner = _owner;
}
/// @dev issueBounty(): instantiates a new draft bounty
/// @param _issuer the address of the intended issuer of the bounty
/// @param _deadline the unix timestamp after which fulfillments will no longer be accepted
/// @param _data the requirements of the bounty
/// @param _fulfillmentAmount the amount of wei to be paid out for each successful fulfillment
/// @param _arbiter the address of the arbiter who can mediate claims
/// @param _paysTokens whether the bounty pays in tokens or in ETH
/// @param _tokenContract the address of the contract if _paysTokens is true
function issueBounty(
address _issuer,
uint _deadline,
string _data,
uint256 _fulfillmentAmount,
address _arbiter,
bool _paysTokens,
address _tokenContract
)
public
validateDeadline(_deadline)
amountIsNotZero(_fulfillmentAmount)
returns (uint)
{
bounties.push(Bounty(_issuer, _deadline, _data, _fulfillmentAmount, _arbiter, _paysTokens, BountyStages.Draft, 0, 0));
if (_paysTokens){
tokenContracts[bounties.length - 1] = HumanStandardToken(_tokenContract);
}
BountyIssued(bounties.length - 1);
return (bounties.length - 1);
}
/// @dev issueAndActivateBounty(): instantiates a new draft bounty
/// @param _issuer the address of the intended issuer of the bounty
/// @param _deadline the unix timestamp after which fulfillments will no longer be accepted
/// @param _data the requirements of the bounty
/// @param _fulfillmentAmount the amount of wei to be paid out for each successful fulfillment
/// @param _arbiter the address of the arbiter who can mediate claims
/// @param _paysTokens whether the bounty pays in tokens or in ETH
/// @param _tokenContract the address of the contract if _paysTokens is true
/// @param _value the total number of tokens being deposited upon activation
function issueAndActivateBounty(
address _issuer,
uint _deadline,
string _data,
uint256 _fulfillmentAmount,
address _arbiter,
bool _paysTokens,
address _tokenContract,
uint256 _value
)
public
payable
validateDeadline(_deadline)
amountIsNotZero(_fulfillmentAmount)
returns (uint)
{
require (_value >= _fulfillmentAmount);
if (_paysTokens){
require(msg.value == 0);
tokenContracts[bounties.length] = HumanStandardToken(_tokenContract);
require(tokenContracts[bounties.length].transferFrom(msg.sender, this, _value));
} else {
require((_value * 1 wei) == msg.value);
}
bounties.push(Bounty(_issuer,
_deadline,
_data,
_fulfillmentAmount,
_arbiter,
_paysTokens,
BountyStages.Active,
0,
_value));
BountyIssued(bounties.length - 1);
ContributionAdded(bounties.length - 1, msg.sender, msg.value);
BountyActivated(bounties.length - 1, msg.sender);
return (bounties.length - 1);
}
modifier isNotDead(uint _bountyId) {
require(bounties[_bountyId].bountyStage != BountyStages.Dead);
_;
}
/// @dev contribute(): a function allowing anyone to contribute tokens to a
/// bounty, as long as it is still before its deadline. Shouldn't keep
/// them by accident (hence 'value').
/// @param _bountyId the index of the bounty
/// @param _value the amount being contributed in ether to prevent accidental deposits
/// @notice Please note you funds will be at the mercy of the issuer
/// and can be drained at any moment. Be careful!
function contribute (uint _bountyId, uint _value)
payable
public
isBeforeDeadline(_bountyId)
isNotDead(_bountyId)
validateBountyArrayIndex(_bountyId)
amountIsNotZero(_value)
transferredAmountEqualsValue(_bountyId, _value)
{
if (bounties[_bountyId].paysTokens){
require(msg.value == 0);
}
bounties[_bountyId].balance += _value;
ContributionAdded(_bountyId, msg.sender, _value);
}
/// @notice Send funds to activate the bug bounty
/// @dev activateBounty(): activate a bounty so it may pay out
/// @param _bountyId the index of the bounty
/// @param _value the amount being contributed in ether to prevent
/// accidental deposits
function activateBounty(uint _bountyId, uint _value)
payable
public
isBeforeDeadline(_bountyId)
onlyIssuer(_bountyId)
validateBountyArrayIndex(_bountyId)
transferredAmountEqualsValue(_bountyId, _value)
{
if (bounties[_bountyId].paysTokens){
require(msg.value == 0);
}
bounties[_bountyId].balance += _value;
require (bounties[_bountyId].balance >=
(bounties[_bountyId].fulfillmentAmount + bounties[_bountyId].owedAmount));
transitionToState(_bountyId, BountyStages.Active);
ContributionAdded(_bountyId, msg.sender, msg.value);
BountyActivated(_bountyId, msg.sender);
}
modifier notIssuerOrArbiter(uint _bountyId) {
require(msg.sender != bounties[_bountyId].issuer && msg.sender != bounties[_bountyId].arbiter);
_;
}
/// @dev fulfillBounty(): submit a fulfillment for the given bounty
/// @param _bountyId the index of the bounty
/// @param _data the data artifacts representing the fulfillment of the bounty
function fulfillBounty(uint _bountyId, string _data)
public
validateBountyArrayIndex(_bountyId)
isAtStage(_bountyId, BountyStages.Active)
isBeforeDeadline(_bountyId)
notIssuerOrArbiter(_bountyId)
{
fulfillments[_bountyId].push(Fulfillment(false, false, msg.sender, _data));
BountyFulfilled(_bountyId, msg.sender, (fulfillments[_bountyId].length - 1));
}
/// @dev updateFulfillment(): Submit updated data for a given fulfillment
/// @param _bountyId the index of the bounty
/// @param _fulfillmentId the index of the fulfillment
/// @param _data the new data being submitted
function updateFulfillment(uint _bountyId, uint _fulfillmentId, string _data)
public
validateBountyArrayIndex(_bountyId)
validateFulfillmentArrayIndex(_bountyId, _fulfillmentId)
onlyFulfiller(_bountyId, _fulfillmentId)
notYetAccepted(_bountyId, _fulfillmentId)
{
fulfillments[_bountyId][_fulfillmentId].data = _data;
FulfillmentUpdated(_bountyId, _fulfillmentId);
}
modifier onlyIssuerOrArbiter(uint _bountyId) {
require(msg.sender == bounties[_bountyId].issuer ||
(msg.sender == bounties[_bountyId].arbiter && bounties[_bountyId].arbiter != address(0)));
_;
}
modifier fulfillmentNotYetAccepted(uint _bountyId, uint _fulfillmentId) {
require(fulfillments[_bountyId][_fulfillmentId].accepted == false);
_;
}
modifier enoughFundsToPay(uint _bountyId) {
require((bounties[_bountyId].owedAmount +
bounties[_bountyId].fulfillmentAmount) <= bounties[_bountyId].balance);
_;
}
/// @dev acceptFulfillment(): accept a given fulfillment
/// @param _bountyId the index of the bounty
/// @param _fulfillmentId the index of the fulfillment being accepted
function acceptFulfillment(uint _bountyId, uint _fulfillmentId)
public
validateBountyArrayIndex(_bountyId)
onlyIssuerOrArbiter(_bountyId)
isAtStage(_bountyId, BountyStages.Active)
validateFulfillmentArrayIndex(_bountyId, _fulfillmentId)
fulfillmentNotYetAccepted(_bountyId, _fulfillmentId)
enoughFundsToPay(_bountyId)
{
fulfillments[_bountyId][_fulfillmentId].accepted = true;
bounties[_bountyId].owedAmount += bounties[_bountyId].fulfillmentAmount;
numAccepted[_bountyId]++;
FulfillmentAccepted(_bountyId, msg.sender, _fulfillmentId);
}
modifier checkFulfillmentIsApprovedAndUnpaid(uint _bountyId, uint _fulfillmentId) {
require(fulfillments[_bountyId][_fulfillmentId].accepted && !fulfillments[_bountyId][_fulfillmentId].paid);
_;
}
/// @dev fulfillmentPayment(): pay the fulfiller for their work
/// @param _bountyId the index of the bounty
/// @param _fulfillmentId the index of the fulfillment being accepted
function fulfillmentPayment(uint _bountyId, uint _fulfillmentId)
public
validateBountyArrayIndex(_bountyId)
validateFulfillmentArrayIndex(_bountyId, _fulfillmentId)
onlyFulfiller(_bountyId, _fulfillmentId)
checkFulfillmentIsApprovedAndUnpaid(_bountyId, _fulfillmentId)
{
fulfillments[_bountyId][_fulfillmentId].paid = true;
numPaid[_bountyId]++;
bounties[_bountyId].owedAmount -= bounties[_bountyId].fulfillmentAmount;
bounties[_bountyId].balance -= bounties[_bountyId].fulfillmentAmount;
if (bounties[_bountyId].paysTokens){
tokenContracts[_bountyId].transfer(fulfillments[_bountyId][_fulfillmentId].fulfiller, bounties[_bountyId].fulfillmentAmount);
} else {
fulfillments[_bountyId][_fulfillmentId].fulfiller.transfer(bounties[_bountyId].fulfillmentAmount);
}
FulfillmentPaid(_bountyId, msg.sender, _fulfillmentId);
}
/// @dev killBounty(): drains the contract of it's remaining
/// funds, and moves the bounty into stage 3 (dead) since it was
/// either killed in draft stage, or never accepted any fulfillments
/// @param _bountyId the index of the bounty
function killBounty(uint _bountyId)
public
validateBountyArrayIndex(_bountyId)
onlyIssuer(_bountyId)
{
transitionToState(_bountyId, BountyStages.Dead);
if (bounties[_bountyId].paysTokens){
tokenContracts[_bountyId].transfer(bounties[_bountyId].issuer,
(bounties[_bountyId].balance - bounties[_bountyId].owedAmount));
} else {
bounties[_bountyId].issuer.transfer(bounties[_bountyId].balance - bounties[_bountyId].owedAmount);
}
bounties[_bountyId].balance = bounties[_bountyId].owedAmount;
BountyKilled(_bountyId);
}
modifier newDeadlineIsValid(uint _bountyId, uint _newDeadline) {
require(_newDeadline > bounties[_bountyId].deadline);
_;
}
/// @dev extendDeadline(): allows the issuer to add more time to the
/// bounty, allowing it to continue accepting fulfillments
/// @param _bountyId the index of the bounty
/// @param _newDeadline the new deadline in timestamp format
function extendDeadline(uint _bountyId, uint _newDeadline)
public
validateBountyArrayIndex(_bountyId)
onlyIssuer(_bountyId)
newDeadlineIsValid(_bountyId, _newDeadline)
{
bounties[_bountyId].deadline = _newDeadline;
DeadlineExtended(_bountyId, _newDeadline);
}
/// @dev transferIssuer(): allows the issuer to transfer ownership of the
/// bounty to some new address
/// @param _bountyId the index of the bounty
/// @param _newIssuer the address of the new issuer
function transferIssuer(uint _bountyId, address _newIssuer)
public
validateBountyArrayIndex(_bountyId)
onlyIssuer(_bountyId)
{
bounties[_bountyId].issuer = _newIssuer;
IssuerTransferred(_bountyId, _newIssuer);
}
/// @dev changeBountyDeadline(): allows the issuer to change a bounty's issuer
/// @param _bountyId the index of the bounty
/// @param _newDeadline the new deadline for the bounty
function changeBountyDeadline(uint _bountyId, uint _newDeadline)
public
validateBountyArrayIndex(_bountyId)
onlyIssuer(_bountyId)
validateDeadline(_newDeadline)
isAtStage(_bountyId, BountyStages.Draft)
{
bounties[_bountyId].deadline = _newDeadline;
BountyChanged(_bountyId);
}
/// @dev changeData(): allows the issuer to change a bounty's issuer
/// @param _bountyId the index of the bounty
/// @param _newData the new requirements of the bounty
function changeBountyData(uint _bountyId, string _newData)
public
validateBountyArrayIndex(_bountyId)
onlyIssuer(_bountyId)
isAtStage(_bountyId, BountyStages.Draft)
{
bounties[_bountyId].data = _newData;
BountyChanged(_bountyId);
}
/// @dev changeBountyfulfillmentAmount(): allows the issuer to change a bounty's issuer
/// @param _bountyId the index of the bounty
/// @param _newFulfillmentAmount the new fulfillment amount
function changeBountyFulfillmentAmount(uint _bountyId, uint _newFulfillmentAmount)
public
validateBountyArrayIndex(_bountyId)
onlyIssuer(_bountyId)
isAtStage(_bountyId, BountyStages.Draft)
{
bounties[_bountyId].fulfillmentAmount = _newFulfillmentAmount;
BountyChanged(_bountyId);
}
/// @dev changeBountyArbiter(): allows the issuer to change a bounty's issuer
/// @param _bountyId the index of the bounty
/// @param _newArbiter the new address of the arbiter
function changeBountyArbiter(uint _bountyId, address _newArbiter)
public
validateBountyArrayIndex(_bountyId)
onlyIssuer(_bountyId)
isAtStage(_bountyId, BountyStages.Draft)
{
bounties[_bountyId].arbiter = _newArbiter;
BountyChanged(_bountyId);
}
/// @dev changeBountyPaysTokens(): allows the issuer to change a bounty's issuer
/// @param _bountyId the index of the bounty
/// @param _newPaysTokens the new bool for whether the contract pays tokens
/// @param _newTokenContract the new address of the token
function changeBountyPaysTokens(uint _bountyId, bool _newPaysTokens, address _newTokenContract)
public
validateBountyArrayIndex(_bountyId)
onlyIssuer(_bountyId)
isAtStage(_bountyId, BountyStages.Draft)
{
if (bounties[_bountyId].balance > 0){
if (bounties[_bountyId].paysTokens){
require(tokenContracts[_bountyId].transfer(bounties[_bountyId].issuer, bounties[_bountyId].balance));
} else {
bounties[_bountyId].issuer.transfer(bounties[_bountyId].balance);
}
bounties[_bountyId].balance = 0;
}
bounties[_bountyId].paysTokens = _newPaysTokens;
tokenContracts[_bountyId] = HumanStandardToken(_newTokenContract);
BountyChanged(_bountyId);
}
modifier newFulfillmentAmountIsIncrease(uint _bountyId, uint _newFulfillmentAmount) {
require(bounties[_bountyId].fulfillmentAmount < _newFulfillmentAmount);
_;
}
modifier fundsRemainToPayOwed(uint _bountyId, uint _difference){
require(bounties[_bountyId].balance >=
(bounties[_bountyId].owedAmount +
(_difference * (numAccepted[_bountyId] - numPaid[_bountyId]))));
_;
}
/// @dev increasePayout(): allows the issuer to increase a given fulfillment
/// amount in the active stage
/// @param _bountyId the index of the bounty
/// @param _newFulfillmentAmount the new fulfillment amount
function increasePayout(uint _bountyId, uint _newFulfillmentAmount)
public
validateBountyArrayIndex(_bountyId)
onlyIssuer(_bountyId)
newFulfillmentAmountIsIncrease(_bountyId, _newFulfillmentAmount)
fundsRemainToPayOwed(_bountyId, (_newFulfillmentAmount - bounties[_bountyId].fulfillmentAmount))
{
bounties[_bountyId].owedAmount += ((numAccepted[_bountyId] - numPaid[_bountyId]) *
(_newFulfillmentAmount - bounties[_bountyId].fulfillmentAmount));
bounties[_bountyId].fulfillmentAmount = _newFulfillmentAmount;
PayoutIncreased(_bountyId, _newFulfillmentAmount);
}
/// @dev getFulfillment(): Returns the fulfillment at a given index
/// @param _bountyId the index of the bounty
/// @param _fulfillmentId the index of the fulfillment to return
/// @return Returns a tuple for the fulfillment
function getFulfillment(uint _bountyId, uint _fulfillmentId)
public
constant
validateBountyArrayIndex(_bountyId)
validateFulfillmentArrayIndex(_bountyId, _fulfillmentId)
returns (bool, bool, address, string)
{
return (fulfillments[_bountyId][_fulfillmentId].paid,
fulfillments[_bountyId][_fulfillmentId].accepted,
fulfillments[_bountyId][_fulfillmentId].fulfiller,
fulfillments[_bountyId][_fulfillmentId].data);
}
/// @dev getBounty(): Returns the details of the bounty
/// @param _bountyId the index of the bounty
/// @return Returns a tuple for the bounty
function getBounty(uint _bountyId)
public
constant
validateBountyArrayIndex(_bountyId)
returns (address, uint, uint, bool, uint, uint, uint)
{
return (bounties[_bountyId].issuer,
bounties[_bountyId].deadline,
bounties[_bountyId].fulfillmentAmount,
bounties[_bountyId].paysTokens,
uint(bounties[_bountyId].bountyStage),
bounties[_bountyId].owedAmount,
bounties[_bountyId].balance);
}
/// @dev getBountyArbiter(): Returns the arbiter of the bounty
/// @param _bountyId the index of the bounty
/// @return Returns an address for the arbiter of the bounty
function getBountyArbiter(uint _bountyId)
public
constant
validateBountyArrayIndex(_bountyId)
returns (address)
{
return (bounties[_bountyId].arbiter);
}
/// @dev getBountyData(): Returns the data of the bounty
/// @param _bountyId the index of the bounty
/// @return Returns a string for the bounty data
function getBountyData(uint _bountyId)
public
constant
validateBountyArrayIndex(_bountyId)
returns (string)
{
return (bounties[_bountyId].data);
}
/// @dev getBountyToken(): Returns the token contract of the bounty
/// @param _bountyId the index of the bounty
/// @return Returns an address for the token that the bounty uses
function getBountyToken(uint _bountyId)
public
constant
validateBountyArrayIndex(_bountyId)
returns (address)
{
return (tokenContracts[_bountyId]);
}
/// @dev getNumFulfillments() returns the number of fulfillments for a given milestone
/// @param _bountyId the index of the bounty
/// @return Returns the number of fulfillments
function getNumFulfillments(uint _bountyId)
public
constant
validateBountyArrayIndex(_bountyId)
returns (uint)
{
return fulfillments[_bountyId].length;
}
/*
* Internal functions
*/
/// @dev transitionToState(): transitions the contract to the
/// state passed in the parameter `_newStage` given the
/// conditions stated in the body of the function
/// @param _bountyId the index of the bounty
/// @param _newStage the new stage to transition to
function transitionToState(uint _bountyId, BountyStages _newStage)
internal
{
bounties[_bountyId].bountyStage = _newStage;
}
}

View File

@ -1,482 +0,0 @@
pragma solidity ^0.4.11;
/// @title StandardBounty
/// @dev Used to pay out individuals or groups for task fulfillment through
/// stepwise work submission, acceptance, and payment
/// @author Mark Beylin <mark.beylin@consensys.net>, Gonçalo <goncalo.sa@consensys.net>
contract StandardBounty {
/*
* Events
*/
event BountyActivated(address issuer);
event BountyFulfilled(address indexed fulfiller, uint256 indexed _milestoneId, uint256 indexed _fulfillmentId);
event FulfillmentAccepted(address indexed fulfiller, uint256 indexed _milestoneId, uint256 indexed _fulfillmentId);
event FulfillmentPaid(address indexed fulfiller, uint256 indexed _milestoneId, uint256 indexed _fulfillmentId);
event BountyKilled();
event ContributionAdded(address indexed contributor, uint256 value);
event DeadlineExtended(uint newDeadline);
/*
* Storage
*/
address public issuer; // the creator of the bounty
address public arbiter; // should mediate conflicts, has the power to accept work
BountyStages public bountyStage; // the stage of the bounty
uint public deadline; // unix timestamp before which all work must be submitted
string public data; // a hash of data representing the requirements for each milestone of the bounty, along with any associated files
uint[] public fulfillmentAmounts; // the amount of wei to be rewarded to the user who fulfills the i'th milestone
mapping(uint=>Fulfillment[]) public fulfillments; // all submitted fulfillments for each milestone
mapping(uint=>uint) public numAccepted; // the number of accepted fulfillments for each milestone
mapping(uint=>uint) public numPaid; // the number of paid fulfillments for each milestone
uint public amountToPay; // the sum of the various outstanding payouts
uint public unpaidAmount; // the sub of the milestone fulfillment amounts which have not yet been paid out
/*
* Enums
*/
enum BountyStages {
Draft,
Active,
Dead
}
/*
* Structs
*/
struct Fulfillment {
bool paid;
bool accepted;
address fulfiller;
string data;
}
/*
* Modifiers
*/
modifier onlyIssuer() {
require(msg.sender == issuer);
_;
}
modifier onlyIssuerOrArbiter() {
require(msg.sender == issuer || (msg.sender == arbiter && arbiter != address(0)));
_;
}
modifier notIssuerOrArbiter() {
require(msg.sender != issuer && msg.sender != arbiter);
_;
}
modifier onlyFulfiller( uint _milestoneId, uint _fulfillmentId) {
require(msg.sender == fulfillments[_milestoneId][_fulfillmentId].fulfiller);
_;
}
modifier amountIsNotZero(uint amount) {
require(amount != 0);
_;
}
modifier amountsNotZeroAndEqualSum(uint[] amounts, uint _sum) {
uint sum = 0;
uint oldSum = 0;
for (uint i = 0; i < amounts.length; i++){
oldSum = sum;
sum = oldSum + amounts[i];
require(sum > oldSum);
}
require (sum == _sum);
_;
}
modifier amountEqualsValue(uint amount) {
require((amount * 1 wei) == msg.value);
_;
}
modifier isBeforeDeadline() {
require(now < deadline);
_;
}
modifier validateDeadline(uint newDeadline) {
require(newDeadline > now);
_;
}
modifier newDeadlineIsValid(uint newDeadline) {
require(newDeadline > deadline);
_;
}
modifier isAtStage(BountyStages desiredStage) {
require(bountyStage == desiredStage);
_;
}
modifier isNotDead() {
require(bountyStage != BountyStages.Dead);
_;
}
modifier validateFulfillmentArrayIndex(uint _milestoneId, uint _index) {
require(_index < fulfillments[_milestoneId].length);
_;
}
modifier validateMilestoneIndex(uint _milestoneId){
require(_milestoneId < fulfillmentAmounts.length);
_;
}
modifier fulfillmentNotYetAccepted(uint _milestoneId, uint _fulfillmentId) {
require(fulfillments[_milestoneId][_fulfillmentId].accepted == false);
_;
}
modifier newFulfillmentAmountIsIncrease(uint _newFulfillmentAmount, uint _milestoneId) {
require(fulfillmentAmounts[_milestoneId] < _newFulfillmentAmount);
_;
}
modifier checkFulfillmentIsApprovedAndUnpaid( uint _milestoneId, uint _fulfillmentId) {
require(fulfillments[_milestoneId][_fulfillmentId].accepted && !fulfillments[_milestoneId][_fulfillmentId].paid);
_;
}
modifier validateFunding() {
uint total = 0;
for (uint i = 0 ; i < fulfillmentAmounts.length; i++){
total = total + fulfillmentAmounts[i];
}
require (this.balance >= (total + amountToPay));
_;
}
modifier amountToPayRemains(uint _milestoneId) {
require((amountToPay + fulfillmentAmounts[_milestoneId]) <= this.balance);
_;
}
modifier unpaidAmountRemains(uint _milestoneId) {
if (numAccepted[_milestoneId] != 0){
require((unpaidAmount + fulfillmentAmounts[_milestoneId]) <= this.balance);
}
_;
}
modifier notYetAccepted( uint _milestoneId, uint _fulfillmentId){
require(fulfillments[_milestoneId][_fulfillmentId].accepted == false);
_;
}
modifier fundsRemainToPayUnpaidAmounts(uint _difference, uint _milestoneId){
if (numAccepted[_milestoneId] == 0){
require(this.balance >= (unpaidAmount + _difference));
}
_;
}
modifier fundsRemainForAmountToPay(uint _difference, uint _milestoneId){
require(this.balance >= (amountToPay + (_difference * (numAccepted[_milestoneId] - numPaid[_milestoneId]))));
_;
}
/*
* Public functions
*/
/// @dev StandardBounty(): instantiates a new draft bounty
/// @param _issuer the address of the intended issuer of the bounty
/// @param _deadline the unix timestamp after which fulfillments will no longer be accepted
/// @param _data the requirements of the bounty
/// @param _fulfillmentAmounts the amount of wei to be paid out for each successful fulfillment
/// @param _totalFulfillmentAmounts the sum of the individual fulfillment amounts
/// @param _arbiter the address of the arbiter who can mediate claims
function StandardBounty(
address _issuer,
uint _deadline,
string _data,
uint256[] _fulfillmentAmounts,
uint _totalFulfillmentAmounts,
address _arbiter
)
amountsNotZeroAndEqualSum(_fulfillmentAmounts, _totalFulfillmentAmounts)
validateDeadline(_deadline)
{
issuer = _issuer;
bountyStage = BountyStages.Draft;
deadline = _deadline;
data = _data;
arbiter = _arbiter;
fulfillmentAmounts = _fulfillmentAmounts;
amountToPay = 0;
unpaidAmount = _totalFulfillmentAmounts;
}
/// @dev contribute(): a function allowing anyone to contribute ether to a
/// bounty, as long as it is still before its deadline. Shouldn't keep
/// ether by accident (hence 'value').
/// @param value the amount being contributed in ether to prevent accidental deposits
/// @notice Please note you funds will be at the mercy of the issuer
/// and can be drained at any moment. Be careful!
function contribute (uint value)
payable
public
isBeforeDeadline
isNotDead
amountIsNotZero(value)
amountEqualsValue(value)
{
ContributionAdded(msg.sender, value);
}
/// @notice Send funds to activate the bug bounty
/// @dev activateBounty(): activate a bounty so it may pay out
/// @param value the amount being contributed in ether to prevent
/// accidental deposits
function activateBounty(uint value)
payable
public
isBeforeDeadline
onlyIssuer
amountEqualsValue(value)
validateFunding
{
transitionToState(BountyStages.Active);
ContributionAdded(msg.sender, msg.value);
BountyActivated(msg.sender);
}
/// @dev fulfillBounty(): submit a fulfillment for the given bounty
/// @param _data the data artifacts representing the fulfillment of the bounty
/// @param _milestoneId the id of the milestone being fulfilled
function fulfillBounty(string _data, uint _milestoneId)
public
isAtStage(BountyStages.Active)
isBeforeDeadline
validateMilestoneIndex(_milestoneId)
notIssuerOrArbiter
{
fulfillments[_milestoneId].push(Fulfillment(false, false, msg.sender, _data));
BountyFulfilled(msg.sender, (fulfillments[_milestoneId].length - 1), _milestoneId);
}
/// @dev updateFulfillment(): Submit updated data for a given fulfillment
/// @param _data the new data being submitted
/// @param _milestoneId the index of the milestone
/// @param _fulfillmentId the index of the fulfillment
function updateFulfillment(string _data, uint _milestoneId, uint _fulfillmentId)
public
validateMilestoneIndex(_milestoneId)
validateFulfillmentArrayIndex(_milestoneId, _fulfillmentId)
onlyFulfiller(_milestoneId, _fulfillmentId)
notYetAccepted(_milestoneId, _fulfillmentId)
{
fulfillments[_milestoneId][_fulfillmentId].data = _data;
}
/// @dev acceptFulfillment(): accept a given fulfillment
/// @param _fulfillmentId the index of the fulfillment being accepted
/// @param _milestoneId the id of the milestone being accepted
function acceptFulfillment( uint _milestoneId, uint _fulfillmentId)
public
onlyIssuerOrArbiter
isAtStage(BountyStages.Active)
validateMilestoneIndex(_milestoneId)
validateFulfillmentArrayIndex(_milestoneId, _fulfillmentId)
fulfillmentNotYetAccepted(_milestoneId, _fulfillmentId)
amountToPayRemains(_milestoneId)
unpaidAmountRemains(_milestoneId)
{
fulfillments[_milestoneId][_fulfillmentId].accepted = true;
amountToPay += fulfillmentAmounts[_milestoneId];
if (numAccepted[_milestoneId] == 0){
unpaidAmount -= fulfillmentAmounts[_milestoneId];
}
numAccepted[_milestoneId]++;
FulfillmentAccepted(msg.sender, _milestoneId, _fulfillmentId);
}
/// @dev fulfillmentPayment(): pay the fulfiller for their work
/// @param _fulfillmentId the index of the fulfillment being accepted
/// @param _milestoneId the id of the milestone being paid
function fulfillmentPayment( uint _milestoneId, uint _fulfillmentId)
public
validateMilestoneIndex(_milestoneId)
validateFulfillmentArrayIndex(_milestoneId, _fulfillmentId)
onlyFulfiller(_milestoneId, _fulfillmentId)
checkFulfillmentIsApprovedAndUnpaid(_milestoneId, _fulfillmentId)
{
fulfillments[_milestoneId][_fulfillmentId].paid = true;
numPaid[_milestoneId]++;
amountToPay -= fulfillmentAmounts[_milestoneId];
fulfillments[_milestoneId][_fulfillmentId].fulfiller.transfer(fulfillmentAmounts[_milestoneId]);
FulfillmentPaid(msg.sender, _milestoneId, _fulfillmentId);
}
/// @dev killBounty(): drains the contract of it's remaining
/// funds, and moves the bounty into stage 3 (dead) since it was
/// either killed in draft stage, or never accepted any fulfillments
function killBounty()
public
onlyIssuer
{
issuer.transfer(this.balance - amountToPay);
transitionToState(BountyStages.Dead);
BountyKilled();
}
/// @dev extendDeadline(): allows the issuer to add more time to the
/// bounty, allowing it to continue accepting fulfillments
/// @param _newDeadline the new deadline in timestamp format
function extendDeadline(uint _newDeadline)
public
onlyIssuer
newDeadlineIsValid(_newDeadline)
{
deadline = _newDeadline;
DeadlineExtended(_newDeadline);
}
/// @dev transferIssuer(): allows the issuer to transfer ownership of the
/// bounty to some new address
/// @param _newIssuer the address of the new issuer
function transferIssuer(address _newIssuer)
public
onlyIssuer
{
issuer = _newIssuer;
}
/// @dev changeBounty(): allows the issuer to change all bounty storage
/// members simultaneously
/// @param _issuer the new address of the issuer
/// @param _newDeadline the new deadline for the bounty
/// @param _newData the new requirements of the bounty
/// @param _newFulfillmentAmounts the new fulfillment amounts
/// @param _totalFulfillmentAmounts the sum of the individual fulfillment amounts
/// @param _newArbiter the new address of the arbiter
function changeBounty(address _issuer,
uint _newDeadline,
string _newData,
uint[] _newFulfillmentAmounts,
uint _totalFulfillmentAmounts,
address _newArbiter)
public
onlyIssuer
validateDeadline(_newDeadline)
amountsNotZeroAndEqualSum(_newFulfillmentAmounts, _totalFulfillmentAmounts)
isAtStage(BountyStages.Draft)
{
issuer = _issuer;
deadline = _newDeadline;
data = _newData;
fulfillmentAmounts = _newFulfillmentAmounts;
unpaidAmount = _totalFulfillmentAmounts;
arbiter = _newArbiter;
}
/// @dev increasePayout(): allows the issuer to increase a given fulfillment
/// amount in the active stage
/// @param _milestoneId the fulfillment in question
/// @param _newFulfillmentAmount the new payout amount for a given fulfillment
function increasePayout(uint _milestoneId, uint _newFulfillmentAmount)
public
onlyIssuer
newFulfillmentAmountIsIncrease(_newFulfillmentAmount, _milestoneId)
fundsRemainToPayUnpaidAmounts(_newFulfillmentAmount - fulfillmentAmounts[_milestoneId], _milestoneId)
fundsRemainForAmountToPay(_newFulfillmentAmount - fulfillmentAmounts[_milestoneId], _milestoneId){
uint difference = _newFulfillmentAmount - fulfillmentAmounts[_milestoneId];
amountToPay += ((numAccepted[_milestoneId] - numPaid[_milestoneId]) * difference);
if (numAccepted[_milestoneId] == 0){
unpaidAmount += difference;
}
fulfillmentAmounts[_milestoneId] = _newFulfillmentAmount;
}
/// @dev getFulfillment(): Returns the fulfillment at a given index
/// @param _fulfillmentId the index of the fulfillment to return
/// @param _milestoneId the index of the milestone to return
/// @return Returns a tuple for the fulfillment
function getFulfillment( uint _milestoneId, uint _fulfillmentId)
public
constant
validateMilestoneIndex(_milestoneId)
validateFulfillmentArrayIndex(_milestoneId, _fulfillmentId)
returns (bool, bool, address, string)
{
return (fulfillments[_milestoneId][_fulfillmentId].paid,
fulfillments[_milestoneId][_fulfillmentId].accepted,
fulfillments[_milestoneId][_fulfillmentId].fulfiller,
fulfillments[_milestoneId][_fulfillmentId].data);
}
/// @dev getBounty(): Returns the details of the bounty
/// @return Returns a tuple for the bounty
function getBounty()
public
constant
returns (address, uint, uint, string)
{
return (issuer,
uint(bountyStage),
deadline,
data);
}
/// @dev getNumFulfillments() returns the number of fulfillments for a given milestone
/// @param _milestoneId the ID of the milestone
/// @return Returns the number of fulfillments
function getNumFulfillments(uint _milestoneId)
public
constant
validateMilestoneIndex(_milestoneId)
returns (uint)
{
return fulfillments[_milestoneId].length;
}
/// @dev getNumMilestones() returns the number of milestones
/// @return Returns the number of fulfillments
function getNumMilestones()
public
constant
returns (uint)
{
return fulfillmentAmounts.length;
}
/*
* Internal functions
*/
/// @dev transitionToState(): transitions the contract to the
/// state passed in the parameter `_newStage` given the
/// conditions stated in the body of the function
/// @param _newStage the new stage to transition to
function transitionToState(BountyStages _newStage)
internal
{
bountyStage = _newStage;
}
}

View File

@ -1,39 +0,0 @@
pragma solidity ^0.4.11;
import "./StandardBounty.sol";
/// @title Standard Bounty factory,
/// @author Mark Beylin <mark.beylin@consensys.net>
contract StandardBountyFactory{
/// @dev Allows for the creation of bounties
/// @param _issuer the address of the intended issuer of the bounty
/// @param _deadline the unix timestamp after which fulfillments will no longer be accepted
/// @param _data the requirements of the bounty
/// @param _fulfillmentAmounts the amount of wei to be paid out for each successful fulfillment
/// @param _totalFulfillmentAmounts the sum of the individual fulfillment amounts
/// @param _arbiter the address of the arbiter who can mediate claims
function create(
address _issuer,
uint _deadline,
string _data,
uint[] _fulfillmentAmounts,
uint _totalFulfillmentAmounts,
address _arbiter
)
public
returns (address bounty)
{
bounty = new StandardBounty(
_issuer,
_deadline,
_data,
_fulfillmentAmounts,
_totalFulfillmentAmounts,
_arbiter
);
}
}

View File

@ -1,201 +0,0 @@
pragma solidity ^0.4.11;
import "./StandardBounty.sol";
import "./inherited/HumanStandardToken.sol";
/// @title TokenBounty
/// @dev Used to pay out individuals or groups in ERC20 tokens for task
/// fulfillment through stepwise work submission, acceptance, and payment
/// @dev extension of StandardBounty to pay out bounties with a given ERC20 token
/// @author Mark Beylin <mark.beylin@consensys.net>
contract TokenBounty is StandardBounty {
/*
* Storage
*/
StandardToken public tokenContract;
/*
* Modifiers
*/
modifier amountEqualsValue(uint value) {
if (value != 0){
require(tokenContract.transferFrom(msg.sender, this, value));
}
require(tokenContract.balanceOf(this) >= value);
_;
}
modifier validateFunding() {
uint total = 0;
for (uint i = 0 ; i < fulfillmentAmounts.length; i++){
total = total + fulfillmentAmounts[i];
}
require (tokenContract.balanceOf(this) >= (total + amountToPay));
_;
}
modifier amountToPayRemains(uint _milestoneId) {
require((amountToPay + fulfillmentAmounts[_milestoneId]) <= tokenContract.balanceOf(this));
_;
}
modifier unpaidAmountRemains(uint _milestoneId) {
if (numAccepted[_milestoneId] != 0){
require((unpaidAmount + fulfillmentAmounts[_milestoneId]) <= tokenContract.balanceOf(this));
}
_;
}
modifier fundsRemainToPayUnpaidAmounts(uint _difference, uint _milestoneId){
if (numAccepted[_milestoneId] == 0){
require(tokenContract.balanceOf(this) >= (unpaidAmount + _difference));
}
_;
}
modifier fundsRemainForAmountToPay(uint _difference, uint _milestoneId){
require(tokenContract.balanceOf(this) >= (amountToPay + (_difference * (numAccepted[_milestoneId] - numPaid[_milestoneId]))));
_;
}
/*
* Public functions
*/
/// @dev TokenBounty(): instantiates a new draft token bounty
/// @param _issuer the address of the intended issuer of the bounty
/// @param _deadline the unix timestamp after which fulfillments will no longer be accepted
/// @param _data the requirements of the bounty
/// @param _fulfillmentAmounts the amount of wei to be paid out for each successful fulfillment
/// @param _totalFulfillmentAmounts the sum of the individual fulfillment amounts
/// @param _arbiter the address of the arbiter who can mediate claims
/// @param _tokenAddress the address of the token contract
function TokenBounty(
address _issuer,
uint _deadline,
string _data,
uint[] _fulfillmentAmounts,
uint _totalFulfillmentAmounts,
address _arbiter,
address _tokenAddress
)
StandardBounty(
_issuer,
_deadline,
_data,
_fulfillmentAmounts,
_totalFulfillmentAmounts,
_arbiter
)
{
tokenContract = StandardToken(_tokenAddress);
}
/// @dev contribute(): a function allowing anyone to contribute ether to a
/// bounty, as long as it is still before its deadline. Shouldn't keep
/// ether by accident (hence 'value').
/// @param value the amount being contributed in ether to prevent accidental deposits
/// @notice Please note you funds will be at the mercy of the issuer
/// and can be drained at any moment. Be careful!
function contribute (uint value)
public
payable
isBeforeDeadline
isNotDead
amountIsNotZero(value)
amountEqualsValue(value)
{
require(msg.value == 0);
ContributionAdded(msg.sender, value);
}
/// @notice Send funds to activate the bug bounty
/// @dev activateBounty(): activate a bounty so it may pay out
/// @param value the amount being contributed in ether to prevent
/// accidental deposits
function activateBounty(uint value)
public
payable
isBeforeDeadline
onlyIssuer
amountEqualsValue(value)
validateFunding
{
require(msg.value == 0);
transitionToState(BountyStages.Active);
ContributionAdded(msg.sender, msg.value);
BountyActivated(msg.sender);
}
/// @dev fulfillmentPayment(): accept a given fulfillment, and send
/// the fulfiller their owed funds
/// @param _milestoneId the id of the milestone being paid
/// @param _fulfillmentId the index of the fulfillment being accepted
function fulfillmentPayment(uint _milestoneId, uint _fulfillmentId)
public
validateMilestoneIndex(_milestoneId)
validateFulfillmentArrayIndex(_milestoneId, _fulfillmentId)
onlyFulfiller(_milestoneId, _fulfillmentId)
checkFulfillmentIsApprovedAndUnpaid(_milestoneId, _fulfillmentId)
{
fulfillments[_milestoneId][_fulfillmentId].paid = true;
numPaid[_milestoneId]++;
amountToPay -= fulfillmentAmounts[_milestoneId];
tokenContract.transfer(fulfillments[_milestoneId][_fulfillmentId].fulfiller, fulfillmentAmounts[_milestoneId]);
FulfillmentPaid(msg.sender, _milestoneId, _fulfillmentId);
}
/// @dev killBounty(): drains the contract of it's remaining
/// funds, and moves the bounty into stage 3 (dead) since it was
/// either killed in draft stage, or never accepted any fulfillments
function killBounty()
public
onlyIssuer
{
tokenContract.transfer(issuer, tokenContract.balanceOf(this) - amountToPay);
transitionToState(BountyStages.Dead);
BountyKilled();
}
/// @dev changeBounty(): allows the issuer to change all bounty storage
/// members simultaneously
/// @param _newIssuer the new address of the issuer
/// @param _newDeadline the new deadline for the bounty
/// @param _newData the new requirements of the bounty
/// @param _newFulfillmentAmounts the new fulfillment amounts
/// @param _totalFulfillmentAmounts the sum of the new fulfillment amounts
/// @param _tokenAddress the address of the token contract
function changeBounty(address _newIssuer,
uint _newDeadline,
string _newData,
uint[] _newFulfillmentAmounts,
uint _totalFulfillmentAmounts,
address _newArbiter,
address _tokenAddress)
public
onlyIssuer
validateDeadline(_newDeadline)
amountsNotZeroAndEqualSum(_newFulfillmentAmounts, _totalFulfillmentAmounts)
isAtStage(BountyStages.Draft)
{
issuer = _newIssuer;
deadline = _newDeadline;
data = _newData;
fulfillmentAmounts = _newFulfillmentAmounts;
arbiter = _newArbiter;
unpaidAmount = _totalFulfillmentAmounts;
tokenContract = HumanStandardToken(_tokenAddress);
}
}

View File

@ -1,40 +0,0 @@
pragma solidity ^0.4.11;
import "./TokenBounty.sol";
/// @title Token Bounty factory,
/// @author Mark Beylin <mark.beylin@consensys.net>
contract TokenBountyFactory{
/// @dev Allows multiple creations of bounties
/// @param _issuer the address of the intended issuer of the bounty
/// @param _deadline the unix timestamp after which fulfillments will no longer be accepted
/// @param _data the requirements of the bounty
/// @param _fulfillmentAmounts the amount of wei to be paid out for each successful fulfillment
/// @param _totalFulfillmentAmounts the sum of the individual fulfillment amounts
/// @param _arbiter the address of the arbiter who can mediate claims
/// @param _tokenAddress the address of the token contract
function create(
address _issuer,
uint _deadline,
string _data,
uint[] _fulfillmentAmounts,
uint _totalFulfillmentAmounts,
address _arbiter,
address _tokenAddress
)
public
returns (address bounty)
{
bounty = new TokenBounty(
_issuer,
_deadline,
_data,
_fulfillmentAmounts,
_totalFulfillmentAmounts,
_arbiter,
_tokenAddress
);
}
}

View File

@ -4,7 +4,7 @@ pragma solidity ^0.4.11;
/// @title User Comments
/// @author Mark Beylin <mark.beylin@consensys.net>
contract UserComments{
contract UserComments {
event CommentAdded(string _comment, address _from, address _to, uint _time);
struct Comment{

View File

@ -1,30 +0,0 @@
pragma solidity ^0.4.8;
/// @title Contracts' factory
/// @author Stefan George - <stefan.george@consensys.net>
contract Factory {
event ContractInstantiation(address sender, address instantiation);
mapping(address => address[]) public instantiations;
/// @dev Returns number of instantiations by creator.
/// @param creator Contract creator.
/// @return Returns number of instantiations by creator.
function getInstantiationCount(address creator)
public
constant
returns (uint)
{
return instantiations[creator].length;
}
/// @dev Registers contract in factory registry.
/// @param instantiation Address of contract instantiation.
function register(address instantiation) {
instantiations[msg.sender].push(instantiation);
ContractInstantiation(msg.sender, instantiation);
}
}

View File

@ -4,136 +4,206 @@
## Summary
A bounty is a simple mechanism for individuals or groups to pay out for the completion of tasks. The issuer of the bounty begins by deploying a new bounty contract, during which time any of the storage variables (like bounty requirements or payout amounts) can be altered. Once sufficient funds have been deposited into the contract, the issuer may activate the bounty, allowing bounty hunters to submit fulfillments for the various milestones. The issuer can then approve the submitted work, releasing the payout funds to the bounty hunter in question.
A bounty is a simple mechanism for individuals or groups to pay out for the completion of tasks. The issuer of the bounty begins by deploying a new bounty contract, during which time any of the storage variables (like bounty requirements or the payout amount) can be altered. Once sufficient funds have been deposited into the contract, the issuer may activate the bounty, allowing bounty hunters to submit fulfillments for the bounty task. The issuer can then approve the submitted work, releasing the payout funds to the bounty hunter in question.
## Contract Details
### Storage
`address public issuer`
`address issuer`
The issuer is the creator of the bounty, and has full control over administering its rewards.
`address public arbiter`
The arbiter is an individual or contract who is able to accept fulfillments on the issuer's behalf. The arbiter is also disallowed from fulfilling the bounty.
`BountyStages public bountyStage`
Bounties are formed in the `Draft` stage, a period during which the issuer can edit any of the bounty's state variables, and attain sufficient funding. In the draft stage, no fulfillments can be submitted, and no funds can be paid out.
Once the bounty state variables are finalized, and the bounty contract holds sufficient funds to pay out each milestone at least once, it can be transitioned to the `Active` stage by only the issuer. During the active stage, requirements or payout amounts cannot be altered, however the deadline may be extended. Fulfillments can only be submitted in the `Active` stage before the deadline, although they may be accepted by the issuer or arbiter even after the deadline has passed.
At any point, the issuer can kill the bounty returning all funds to them (less the amount due for already accepted but unpaid submissions), transitioning the bounty into the `Dead` stage. However, this behaviour is highly discouraged and should be avoided at all costs.
`uint public deadline`
`uint deadline`
A bounty can only be contributed to, activated, or fulfilled before the given deadline, however fulfillments can be accepted even after the deadline has passed. This deadline can be moved forward or backwards in the draft stage, but once the bounty is activated it can only be extended. This helps maintain the contractual nature of the relationship, where issuers cannot move deadlines forward arbitrarily while individuals are fulfilling the tasks.
`string public data`
All data representing the requirements are stored off-chain, and their hash is updated here. Requirements and auxiliary data are mutable while the bounty is in the `Draft` stage, but becomes immutable when the bounty is activated, thereby "locking in" the terms of the contract, the requirements for acceptance for each milestone. These should be as rich as possible from the outset, to avoid conflicts stemming from task fulfillers believing they merited the bounty reward.
`uint[] public fulfillmentAmounts`
The total bounty amount is broken down into different payments for each milestone, allowing different individuals to fulfill different pieces of a bounty task. This array stores the amount of wei (or ERC20 token) which will pay out for each milestone when work is accepted. The length of this array is the number of milestones.
`uint public fulfillmentAmount`
The number of units which the bounty pays out for successful completion, either in wei or in token units for the relevant contract.
`address arbiter`
The arbiter is an individual or contract who is able to accept fulfillments on the issuer's behalf. The arbiter is also disallowed from fulfilling the bounty.
`bool paysTokens`
A representation of whether the given bounty pays in ERC20 tokens or in ETH. When it is `true`, the bounty cannot accept ETH deposits, and vice versa when it is false, it will not transfer tokens to the contract.
`BountyStages bountyStage`
Bounties are formed in the `Draft` stage, a period during which the issuer can edit any of the bounty's state variables, and attain sufficient funding. In the draft stage, no fulfillments can be submitted, and no funds can be paid out.
Once the bounty state variables are finalized, and the bounty contract holds sufficient funds to pay out each milestone at least once, it can be transitioned to the `Active` stage by only the issuer. During the active stage, the requirements or payout amount cannot be altered, however the deadline may be extended. Fulfillments can only be submitted in the `Active` stage before the deadline, although they may be accepted by the issuer or arbiter even after the deadline has passed.
At any point, the issuer can kill the bounty returning all funds to them (less the amount due for already accepted but unpaid submissions), transitioning the bounty into the `Dead` stage. However, this behaviour is highly discouraged and should be avoided at all costs.
`uint owedAmount`
The number of units of tokens or ETH which are owed to bounty fulfillers whose work has been accepted but is yet unpaid.
`uint balance`
The number of units of tokens or ETH which the bounty has under its control. The balance must always be greater than or equal to the owedAmount for a given bounty.
`mapping(uint=>Fulfillment[]) public fulfillments`
Work is submitted and a hash is stored on-chain, allowing any deliverable to be submitted for the same bounty.
Work is submitted and a hash is stored on-chain, allowing any deliverable to be submitted for the same bounty. Fulfillments for a given `_bountyId` are added to the array of fulfillments at that same `_bountyId`.
`mapping(uint=>uint) public numAccepted`
The number of submissions which have been accepted for each milestone.
The number of submissions which have been accepted for each bounty
`mapping(uint=>uint) public numPaid`
The number of submissions which have paid out to task fulfillers for each milestone. `numPaid[i]` is always less than or equal to `numAccepted[i]`.
The number of submissions which have paid out to task fulfillers for each bounty. `numPaid[_bountyId]` is always less than or equal to `numAccepted[_bountyId]`.
### External functions
#### StandardBounty()
Constructs the bounty and instantiates state variables, initializing it in the draft stage. The contract gives `tx.origin` the issuer privileges so that a factory design pattern can be used. The bounty deadline must be after the time of issuance (contract deployment), and none of the milestones can pay out 0 tokens.
#### StandardBounties()
Constructs the StandardBounties registry and instantiates it's owner as given
```
function StandardBounty(
uint _deadline,
string _contactInfo,
string _data,
uint256[] _fulfillmentAmounts,
uint _totalFulfillmentAmounts,
uint _numMilestones,
address _arbiter
)
amountsNotZeroAndEqualSum(_fulfillmentAmounts, _totalFulfillmentAmounts)
validateDeadline(_deadline)
correctLengths(_numMilestones, _fulfillmentAmounts.length)
function StandardBounties(address _owner)
public
{
issuer = tx.origin; //this allows for the issuance of a bounty using a factory
issuerContact = _contactInfo;
bountyStage = BountyStages.Draft;
deadline = _deadline;
data = _data;
numMilestones = _numMilestones;
arbiter = _arbiter;
owner = _owner;
}
```
fulfillmentAmounts = _fulfillmentAmounts;
totalFulfillmentAmounts = _totalFulfillmentAmounts;
#### issueBounty()
Issues the bounty and instantiates state variables, initializing it in the draft stage. The bounty deadline must be after the time of issuance (contract deployment), and none of the milestones can pay out 0 tokens.
```
function issueBounty(
address _issuer,
uint _deadline,
string _data,
uint256 _fulfillmentAmount,
address _arbiter,
bool _paysTokens,
address _tokenContract
)
public
validateDeadline(_deadline)
amountIsNotZero(_fulfillmentAmount)
returns (uint)
{
bounties.push(Bounty(_issuer, _deadline, _data, _fulfillmentAmount, _arbiter, _paysTokens, BountyStages.Draft, 0, 0));
if (_paysTokens){
tokenContracts[bounties.length - 1] = HumanStandardToken(_tokenContract);
}
BountyIssued(bounties.length - 1);
return (bounties.length - 1);
}
```
#### issueAndActivateBounty()
Issues the bounty and instantiates state variables, initializing it in the active stage. The bounty deadline must be after the time of issuance (contract deployment), and none of the milestones can pay out 0 tokens. The issuer must specify their initial deposit amount, which is checked against the deposited tokens or ETH. This new amount is the initial balance of the bounty.
```
function issueAndActivateBounty(
address _issuer,
uint _deadline,
string _data,
uint256 _fulfillmentAmount,
address _arbiter,
bool _paysTokens,
address _tokenContract,
uint256 _value
)
public
payable
validateDeadline(_deadline)
amountIsNotZero(_fulfillmentAmount)
returns (uint)
{
require (_value >= _fulfillmentAmount);
if (_paysTokens){
require(msg.value == 0);
tokenContracts[bounties.length] = HumanStandardToken(_tokenContract);
require(tokenContracts[bounties.length].transferFrom(msg.sender, this, _value));
} else {
require((_value * 1 wei) == msg.value);
}
bounties.push(Bounty(_issuer,
_deadline,
_data,
_fulfillmentAmount,
_arbiter,
_paysTokens,
BountyStages.Active,
0,
_value));
BountyIssued(bounties.length - 1);
return (bounties.length - 1);
}
```
#### Contribute()
This allows a bounty to receive 3rd party contributions from the crowd. This functionality is only available before the deadline has passed, and while the bounty is not in the `Dead` stage. The Ether (or tokens) which are deposited are at the mercy of the issuer, who can at any point call `killBounty()` to drain remaining funds.
```
function contribute (uint value)
function contribute (uint _bountyId, uint _value)
payable
isBeforeDeadline
isNotDead
amountIsNotZero(value)
amountEqualsValue(value)
public
isBeforeDeadline(_bountyId)
isNotDead(_bountyId)
validateBountyArrayIndex(_bountyId)
amountIsNotZero(_value)
transferredAmountEqualsValue(_bountyId, _value)
{
ContributionAdded(msg.sender, value);
if (bounties[_bountyId].paysTokens){
require(msg.value == 0);
}
bounties[_bountyId].balance += _value;
ContributionAdded(_bountyId, msg.sender, _value);
}
```
#### ActivateBounty()
If the bounty has sufficient funds to pay out each milestone at least once, it can be activated, allowing individuals to add submissions. Only the issuer is allowed to activate their bounty.
If the bounty has sufficient funds to pay out at least once, it can be activated, allowing individuals to add submissions. Only the issuer is allowed to activate their bounty.
```
function activateBounty(uint value)
function activateBounty(uint _bountyId, uint _value)
payable
public
isBeforeDeadline
onlyIssuer
amountEqualsValue(value)
validateFunding
isBeforeDeadline(_bountyId)
onlyIssuer(_bountyId)
validateBountyArrayIndex(_bountyId)
transferredAmountEqualsValue(_bountyId, _value)
{
transitionToState(BountyStages.Active);
if (bounties[_bountyId].paysTokens){
require(msg.value == 0);
}
bounties[_bountyId].balance += _value;
require (bounties[_bountyId].balance >=
(bounties[_bountyId].fulfillmentAmount + bounties[_bountyId].owedAmount));
transitionToState(_bountyId, BountyStages.Active);
ContributionAdded(msg.sender, msg.value);
BountyActivated(msg.sender);
ContributionAdded(_bountyId, msg.sender, msg.value);
BountyActivated(_bountyId, msg.sender);
}
```
#### FulfillBounty()
Once the bounty is active, anyone can fulfill it and submit the necessary deliverables for a given milestone (as long as the deadline has not passed). Anyone can fulfill the bounty, except for the issuer and arbiter, who are disallowed from doing so.
Once the bounty is active, anyone can fulfill it and submit the necessary deliverables (as long as the deadline has not passed). Anyone can fulfill the bounty, except for the issuer and arbiter, who are disallowed from doing so.
```
function fulfillBounty(string _data, string _dataType, uint _milestoneId)
function fulfillBounty(uint _bountyId, string _data)
public
isAtStage(BountyStages.Active)
isBeforeDeadline
validateMilestoneIndex(_milestoneId)
checkFulfillmentsNumber(_milestoneId)
notIssuerOrArbiter
validateBountyArrayIndex(_bountyId)
isAtStage(_bountyId, BountyStages.Active)
isBeforeDeadline(_bountyId)
notIssuerOrArbiter(_bountyId)
{
fulfillments[_milestoneId].push(Fulfillment(false, false, msg.sender, _data, _dataType));
fulfillments[_bountyId].push(Fulfillment(false, false, msg.sender, _data));
BountyFulfilled(msg.sender, numFulfillments[_milestoneId]++, _milestoneId);
BountyFulfilled(_bountyId, msg.sender, (fulfillments[_bountyId].length - 1));
}
```
#### updateFulfillment()
After a bounty has been fulfilled, the data representing the fulfillment deliverables can be changed or updated by the fulfiller, but only before the bounty has been accepted or paid. Individuals may only update the fulfillments which they personally submitted.
```
function updateFulfillment(string _data, uint _milestoneId, uint _fulfillmentId)
public
validateMilestoneIndex(_milestoneId)
validateFulfillmentArrayIndex(_milestoneId, _fulfillmentId)
onlyFulfiller(_milestoneId, _fulfillmentId)
notYetAccepted(_milestoneId, _fulfillmentId)
function updateFulfillment(uint _bountyId, uint _fulfillmentId, string _data)
public
validateBountyArrayIndex(_bountyId)
validateFulfillmentArrayIndex(_bountyId, _fulfillmentId)
onlyFulfiller(_bountyId, _fulfillmentId)
notYetAccepted(_bountyId, _fulfillmentId)
{
fulfillments[_milestoneId][_fulfillmentId].data = _data;
fulfillments[_bountyId][_fulfillmentId].data = _data;
}
```
@ -141,198 +211,313 @@ notYetAccepted(_milestoneId, _fulfillmentId)
Submissions can be accepted by the issuer while the bounty is active, and the contract has sufficient funds to pay out all previously accepted submissions. Arbiters also have the ability to accept work, but should only do so after mediating between the issuer and fulfiller to resolve the conflict.
```
function acceptFulfillment(uint _milestoneId, uint _fulfillmentId)
function acceptFulfillment(uint _bountyId, uint _fulfillmentId)
public
onlyIssuerOrArbiter
isAtStage(BountyStages.Active)
validateMilestoneIndex(_milestoneId)
validateFulfillmentArrayIndex(_milestoneId, _fulfillmentId)
unpaidAmountRemains(_milestoneId)
validateBountyArrayIndex(_bountyId)
onlyIssuerOrArbiter(_bountyId)
isAtStage(_bountyId, BountyStages.Active)
validateFulfillmentArrayIndex(_bountyId, _fulfillmentId)
fulfillmentNotYetAccepted(_bountyId, _fulfillmentId)
enoughFundsToPay(_bountyId)
{
fulfillments[_milestoneId][_fulfillmentId].accepted = true;
numAccepted[_milestoneId]++;
fulfillments[_bountyId][_fulfillmentId].accepted = true;
bounties[_bountyId].owedAmount += bounties[_bountyId].fulfillmentAmount;
numAccepted[_bountyId]++;
FulfillmentAccepted(msg.sender, _milestoneId, _fulfillmentId);
FulfillmentAccepted(_bountyId, msg.sender, _fulfillmentId);
}
```
#### FulfillmentPayment()
Once an individuals submission has been accepted, they can claim their reward, transferring the Ether (or tokens) to the successful fulfiller. A payment can only be claimed once for each fulfillment which has been accepted.
Once an individuals submission has been accepted, they can claim their reward, transferring the ETH or tokens to the successful fulfiller. A payment can only be claimed once for each fulfillment which has been accepted.
```
function fulfillmentPayment(uint _milestoneId, uint _fulfillmentId)
function fulfillmentPayment(uint _bountyId, uint _fulfillmentId)
public
validateMilestoneIndex(_milestoneId)
validateFulfillmentArrayIndex(_milestoneId, _fulfillmentId)
onlyFulfiller(_milestoneId, _fulfillmentId)
checkFulfillmentIsApprovedAndUnpaid(_milestoneId, _fulfillmentId)
validateBountyArrayIndex(_bountyId)
validateFulfillmentArrayIndex(_bountyId, _fulfillmentId)
onlyFulfiller(_bountyId, _fulfillmentId)
checkFulfillmentIsApprovedAndUnpaid(_bountyId, _fulfillmentId)
{
fulfillments[_milestoneId][_fulfillmentId].fulfiller.transfer(fulfillmentAmounts[_milestoneId]);
fulfillments[_milestoneId][_fulfillmentId].paid = true;
fulfillments[_bountyId][_fulfillmentId].paid = true;
numPaid[_bountyId]++;
bounties[_bountyId].owedAmount -= bounties[_bountyId].fulfillmentAmount;
bounties[_bountyId].balance -= bounties[_bountyId].fulfillmentAmount;
numPaid[_milestoneId]++;
FulfillmentPaid(msg.sender, _milestoneId, _fulfillmentId);
if (bounties[_bountyId].paysTokens){
tokenContracts[_bountyId].transfer(fulfillments[_bountyId][_fulfillmentId].fulfiller, bounties[_bountyId].fulfillmentAmount);
} else {
fulfillments[_bountyId][_fulfillmentId].fulfiller.transfer(bounties[_bountyId].fulfillmentAmount);
}
FulfillmentPaid(_bountyId, msg.sender, _fulfillmentId);
}
```
#### KillBounty()
The issuer of the bounty can transition it into the `Dead` stage at any point in time, draining the contract of all remaining funds (less the amount still due for successful fulfillments which are yet unpaid).
The issuer of the bounty can transition it into the `Dead` stage at any point in time, draining the bounty of all remaining funds (less the amount still due for successful fulfillments which are yet unpaid).
```
function killBounty()
function killBounty(uint _bountyId)
public
onlyIssuer
validateBountyArrayIndex(_bountyId)
onlyIssuer(_bountyId)
{
issuer.transfer(this.balance - unpaidAmount());
transitionToState(_bountyId, BountyStages.Dead);
if (bounties[_bountyId].paysTokens){
tokenContracts[_bountyId].transfer(bounties[_bountyId].issuer,
(bounties[_bountyId].balance - bounties[_bountyId].owedAmount));
} else {
bounties[_bountyId].issuer.transfer(bounties[_bountyId].balance - bounties[_bountyId].owedAmount);
}
bounties[_bountyId].balance = bounties[_bountyId].owedAmount;
transitionToState(BountyStages.Dead);
BountyKilled();
BountyKilled(_bountyId);
}
```
#### ExtendDeadline()
The issuer of the bounty can extend the deadline at any time, allowing more time for submissions to be fulfilled.
```
function extendDeadline(uint _newDeadline)
function extendDeadline(uint _bountyId, uint _newDeadline)
public
onlyIssuer
newDeadlineIsValid(_newDeadline)
validateBountyArrayIndex(_bountyId)
onlyIssuer(_bountyId)
newDeadlineIsValid(_bountyId, _newDeadline)
{
deadline = _newDeadline;
bounties[_bountyId].deadline = _newDeadline;
DeadlineExtended(_newDeadline);
DeadlineExtended(_bountyId, _newDeadline);
}
```
#### transferIssuer()
At any point, the issuer can transfer ownership of the bounty to a new address that they supply. This gives full power and authority to the new issuer address, and releases the old issuer address from the ability to administer the bounty.
```
function transferIssuer(address _newIssuer)
public
onlyIssuer
function transferIssuer(uint _bountyId, address _newIssuer)
public
validateBountyArrayIndex(_bountyId)
onlyIssuer(_bountyId)
{
issuer = _newIssuer;
bounties[_bountyId].issuer = _newIssuer;
}
```
#### ChangeBounty()
The issuer of the bounty can change all bounty variables at once while the bounty is in the `Draft` stage. This is not allowed when the bounty is in the `Active` or `Dead` stage.
#### changeBountyDeadline()
The issuer of the bounty can change the deadline while the bounty is in the `Draft` stage. This is not allowed when the bounty is in the `Active` or `Dead` stage.
```
function changeBounty(uint _newDeadline,
string _newContactInfo,
string _newData,
uint[] _newFulfillmentAmounts,
uint _totalFulfillmentAmounts,
uint _newNumMilestones,
address _newArbiter)
function changeBountyDeadline(uint _bountyId, uint _newDeadline)
public
onlyIssuer
validateBountyArrayIndex(_bountyId)
onlyIssuer(_bountyId)
validateDeadline(_newDeadline)
amountsNotZeroAndEqualSum(_newFulfillmentAmounts, _totalFulfillmentAmounts)
correctLengths(_newNumMilestones, _newFulfillmentAmounts.length)
isAtStage(BountyStages.Draft)
isAtStage(_bountyId, BountyStages.Draft)
{
deadline = _newDeadline;
issuerContact = _newContactInfo;
data = _newData;
fulfillmentAmounts = _newFulfillmentAmounts;
totalFulfillmentAmounts = _totalFulfillmentAmounts;
numMilestones = _newNumMilestones;
arbiter = _newArbiter;
bounties[_bountyId].deadline = _newDeadline;
BountyChanged(_bountyId);
}
```
#### changeBountyData()
The issuer of the bounty can change the data while the bounty is in the `Draft` stage. This is not allowed when the bounty is in the `Active` or `Dead` stage.
```
function changeBountyData(uint _bountyId, string _newData)
public
validateBountyArrayIndex(_bountyId)
onlyIssuer(_bountyId)
isAtStage(_bountyId, BountyStages.Draft)
{
bounties[_bountyId].data = _newData;
BountyChanged(_bountyId);
}
```
#### changeBountyFulfillmentAmount()
The issuer of the bounty can change the fulfillment amount while the bounty is in the `Draft` stage. This is not allowed when the bounty is in the `Active` or `Dead` stage.
```
function changeBountyFulfillmentAmount(uint _bountyId, uint _newFulfillmentAmount)
public
validateBountyArrayIndex(_bountyId)
onlyIssuer(_bountyId)
isAtStage(_bountyId, BountyStages.Draft)
{
bounties[_bountyId].fulfillmentAmount = _newFulfillmentAmount;
BountyChanged(_bountyId);
}
```
#### changeBountyArbiter()
The issuer of the bounty can change the arbiter while the bounty is in the `Draft` stage. This is not allowed when the bounty is in the `Active` or `Dead` stage.
```
function changeBountyArbiter(uint _bountyId, address _newArbiter)
public
validateBountyArrayIndex(_bountyId)
onlyIssuer(_bountyId)
isAtStage(_bountyId, BountyStages.Draft)
{
bounties[_bountyId].arbiter = _newArbiter;
BountyChanged(_bountyId);
}
```
#### changeBountyPaysTokens()
The issuer of the bounty can change whether the bounty pays in tokens or ETH, or change the token contract, while the bounty is in the `Draft` stage. This is not allowed when the bounty is in the `Active` or `Dead` stage. If the balance of the contract is non-zero, it will return all contributed funds to the issuer.
```
function changeBountyPaysTokens(uint _bountyId, bool _newPaysTokens, address _newTokenContract)
public
validateBountyArrayIndex(_bountyId)
onlyIssuer(_bountyId)
isAtStage(_bountyId, BountyStages.Draft)
{
if (bounties[_bountyId].balance > 0){
if (bounties[_bountyId].paysTokens){
require(tokenContracts[_bountyId].transfer(bounties[_bountyId].issuer, bounties[_bountyId].balance));
} else {
bounties[_bountyId].issuer.transfer(bounties[_bountyId].balance);
}
bounties[_bountyId].balance = 0;
}
bounties[_bountyId].paysTokens = _newPaysTokens;
tokenContracts[_bountyId] = HumanStandardToken(_newTokenContract);
BountyChanged(_bountyId);
}
```
#### increasePayout()
The issuer of the bounty can increase the payout of the bounty even in the `Active` stage, as long as the balance of their bounty is sufficient to pay out any accepted fulfillments.
```
function increasePayout(uint _bountyId, uint _newFulfillmentAmount)
public
validateBountyArrayIndex(_bountyId)
onlyIssuer(_bountyId)
newFulfillmentAmountIsIncrease(_bountyId, _newFulfillmentAmount)
fundsRemainToPayOwed(_bountyId, (_newFulfillmentAmount - bounties[_bountyId].fulfillmentAmount))
{
bounties[_bountyId].owedAmount += ((numAccepted[_bountyId] - numPaid[_bountyId]) *
(_newFulfillmentAmount - bounties[_bountyId].fulfillmentAmount));
bounties[_bountyId].fulfillmentAmount = _newFulfillmentAmount;
}
```
#### getFulfillment()
Returns all of the information describing a given fulfillment for a given milestone.
Returns all of the information describing a given fulfillment for a given bounty.
```
function getFulfillment(uint _milestoneId, uint _fulfillmentId)
function getFulfillment(uint _bountyId, uint _fulfillmentId)
public
constant
validateMilestoneIndex(_milestoneId)
validateFulfillmentArrayIndex(_milestoneId, _fulfillmentId)
returns (bool, bool, address, string, string)
validateBountyArrayIndex(_bountyId)
validateFulfillmentArrayIndex(_bountyId, _fulfillmentId)
returns (bool, bool, address, string)
{
return (fulfillments[_milestoneId][_fulfillmentId].paid,
fulfillments[_milestoneId][_fulfillmentId].accepted,
fulfillments[_milestoneId][_fulfillmentId].fulfiller,
fulfillments[_milestoneId][_fulfillmentId].data,
fulfillments[_milestoneId][_fulfillmentId].dataType);
}
```
#### getBounty()
Returns a tuple of the variables describing the bounty.
```
function getBounty()
public
constant
returns (address, string, uint, uint, string, uint)
{
return (issuer,
issuerContact,
uint(bountyStage),
deadline,
data,
numMilestones);
return (fulfillments[_bountyId][_fulfillmentId].paid,
fulfillments[_bountyId][_fulfillmentId].accepted,
fulfillments[_bountyId][_fulfillmentId].fulfiller,
fulfillments[_bountyId][_fulfillmentId].data);
}
```
#### UnpaidAmount()
Returns the amount of wei or tokens which are owed for submissions which were already accepted, but have not yet been paid.
#### getBounty()
Returns a tuple of the variables describing the bounty, except for the arbiter, data, or token contract.
```
function unpaidAmount()
function getBounty(uint _bountyId)
public
constant
returns (uint amount)
validateBountyArrayIndex(_bountyId)
returns (address, uint, uint, bool, uint, uint, uint)
{
for (uint i = 0; i < numMilestones; i++){
amount = SafeMath.add(amount, SafeMath.mul(fulfillmentAmounts[i], SafeMath.sub(numAccepted[i], numPaid[i])));
}
return (bounties[_bountyId].issuer,
bounties[_bountyId].deadline,
bounties[_bountyId].fulfillmentAmount,
bounties[_bountyId].paysTokens,
uint(bounties[_bountyId].bountyStage),
bounties[_bountyId].owedAmount,
bounties[_bountyId].balance);
}
```
#### getBountyArbiter()
Returns an address of the arbiter for the given bounty.
```
function getBountyArbiter(uint _bountyId)
public
constant
validateBountyArrayIndex(_bountyId)
returns (address)
{
return (bounties[_bountyId].arbiter);
}
```
#### getBountyData()
Returns a string of the data for the given bounty.
```
function getBountyData(uint _bountyId)
public
constant
validateBountyArrayIndex(_bountyId)
returns (string)
{
return (bounties[_bountyId].data);
}
```
#### getBountyToken()
Returns an address of the token for the given bounty.
```
function getBountyToken(uint _bountyId)
public
constant
validateBountyArrayIndex(_bountyId)
returns (address)
{
return (tokenContracts[_bountyId]);
}
```
#### getNumFulfillments()
Returns the number of fulfillments which have been submitted for a given milestone
Returns the number of fulfillments which have been submitted for a given bounty
```
function getNumFulfillments(uint _milestoneId)
function getNumFulfillments(uint _bountyId)
public
constant
validateMilestoneIndex(_milestoneId)
validateBountyArrayIndex(_bountyId)
returns (uint)
{
return fulfillments[_milestoneId].length;
}
```
#### unpaidAmount()
Returns the amount of ETH or tokens which must still be paid out for previously accepted fulfillments.
```
function unpaidAmount()
public
constant
returns (uint amount)
{
for (uint i = 0; i < fulfillmentAmounts.length; i++){
amount = (amount + (fulfillmentAmounts[i]* (numAccepted[i]- numPaid[i])));
}
return fulfillments[_bountyId].length;
}
```
### Events
```event BountyActivated(address issuer);```
```event BountyIssued(uint bountyId);```
This is emitted when a new bounty is issued on the StandardBounties registry
```event BountyActivated(uint bountyId, address issuer);```
This is emitted when the bounty gets activated by the issuer
```event FulfillmentAccepted(address indexed fulfiller, uint256 indexed _fulfillmentId, uint256 indexed _milestoneId);```
This is emitted when the fulfillment for `_milestoneId` at index `_fulfillmentId` is accepted by the issuer.
```event BountyFulfilled(uint bountyId, address indexed fulfiller, uint256 indexed _fulfillmentId);```
This is emitted when a given bounty is fulfilled
```event FulfillmentPaid(address indexed fulfiller, uint256 indexed _fulfillmentId, uint256 indexed _milestoneId);```
This is emitted when the fulfillment or `_milestoneId` at index `_fulfillmentId` is paid out to the fulfiller.
```event FulfillmentUpdated(uint _bountyId, uint _fulfillmentId);```
This is emitted when a given fulfillment is altered
```event BountyKilled();```
This is emitted when the issuer kills the bounty, draining the remaining funds
```event FulfillmentAccepted(uint bountyId, address indexed fulfiller, uint256 indexed _fulfillmentId);```
This is emitted when a given fulfillment for a given bounty is accepted
```event ContributionAdded(address indexed contributor, uint256 value);```
This is emitted when someone at address `contributor` contributes to the bounty
```event FulfillmentPaid(uint bountyId, address indexed fulfiller, uint256 indexed _fulfillmentId);```
This is emitted when a given fulfillment for a given bounty is paid
```event DeadlineExtended(uint newDeadline);```
This is emitted when the issuer extends the deadline of the bounty.
```event BountyKilled(uint bountyId);```
This is emitted when a given bounty is killed
```event ContributionAdded(uint bountyId, address indexed contributor, uint256 value);```
This is emitted when an individual contributes to a given bounty
```event DeadlineExtended(uint bountyId, uint newDeadline);```
This is emitted when the deadline of the bounty has been extended
```event BountyChanged(uint bountyId);```
This is emitted when the bounty has been changed (in the draft stage)
```event IssuerTransferred(uint _bountyId, address indexed _newIssuer);```
This is emitted when the issuer of a bounty transfers the ownership of the bounty to a new issuer.
```event PayoutIncreased(uint _bountyId, uint _newFulfillmentAmount);```
This is emitted when the issuer of a bounty increases the payout amount

View File

@ -1,4 +1,4 @@
var Migrations = artifacts.require("./Migrations.sol");
var Migrations = artifacts.require("../contracts/inherited/Migrations.sol");
module.exports = function(deployer) {
deployer.deploy(Migrations);

View File

@ -1,5 +1,5 @@
var CodeBugBountyFactory = artifacts.require("./CodeBugBountyFactory.sol");
var StandardBounties = artifacts.require("../contacts/StandardBounties.sol");
module.exports = function(deployer) {
deployer.deploy(CodeBugBountyFactory);
deployer.deploy(StandardBounties);
};

View File

@ -1,190 +0,0 @@
const BountyRegistry = artifacts.require("../contracts/BountyRegistry.sol");
const StandardBountyFactory = artifacts.require("../contracts/StandardBountyFactory.sol")
const TokenBountyFactory = artifacts.require("../contracts/TokenBountyFactory.sol")
const StandardBounty = artifacts.require("../contracts/StandardBounty.sol");
const HumanStandardToken = artifacts.require("../contracts/inherited/HumanStandardToken.sol");
const utils = require('./helpers/Utils');
const BN = require(`bn.js`);
contract('BountyFactory', function(accounts) {
it("verifies that the owner is instantiated correctly", async () => {
let stbf = await StandardBountyFactory.new({from: accounts[0]});
let tbf = await TokenBountyFactory.new({from: accounts[0]});
let manager = await BountyRegistry.new(stbf.address, tbf.address, {from: accounts[0]});
let owner = await manager.owner();
assert (owner == accounts[0]);
});
it("verifies that ownership can be transferred properly", async () => {
let stbf = await StandardBountyFactory.new({from: accounts[0]});
let tbf = await TokenBountyFactory.new({from: accounts[0]});
let manager = await BountyRegistry.new(stbf.address, tbf.address, {from: accounts[0]});
let owner = await manager.owner();
assert (owner == accounts[0]);
await manager.transferOwner(accounts[1], {from: accounts[0]});
owner = await manager.owner();
assert (owner == accounts[1]);
});
it("verifies that only the owner can transfer ownership ", async () => {
let stbf = await StandardBountyFactory.new({from: accounts[0]});
let tbf = await TokenBountyFactory.new({from: accounts[0]});
let manager = await BountyRegistry.new(stbf.address, tbf.address, {from: accounts[0]});
let owner = await manager.owner();
assert (owner == accounts[0]);
try {
await manager.transferOwner(accounts[1], {from: accounts[1]});
} catch(error){
return utils.ensureException(error);
}
});
it("verifies that bounty creation works", async () => {
let stbf = await StandardBountyFactory.new({from: accounts[0]});
let tbf = await TokenBountyFactory.new({from: accounts[0]});
let manager = await BountyRegistry.new(stbf.address, tbf.address, {from: accounts[0]});
let owner = await manager.owner();
assert (owner == accounts[0]);
let bounty = await manager.create(accounts[0], 2528821098,"",[1000,1000,1000],3000,0x0,0x0, {from: accounts[0]});
let bounty0 = await manager.instances(0);
assert (bounty.logs[0].args.instantiation == bounty0);
});
it("verifies that bounty creation works for token bounties too", async () => {
let stbf = await StandardBountyFactory.new({from: accounts[0]});
let tbf = await TokenBountyFactory.new({from: accounts[0]});
let manager = await BountyRegistry.new(stbf.address, tbf.address, {from: accounts[0]});
let owner = await manager.owner();
assert (owner == accounts[0]);
let bounty = await manager.create(accounts[0], 2528821098,"",[1000,1000,1000],3000,0x0,0x0, {from: accounts[0]});
let bounty0 = await manager.instances(0);
assert (bounty.logs[0].args.instantiation == bounty0);
let bountyToken = await HumanStandardToken.new(1000000000, "Bounty Token", 18, "BOUNT");
bounty = await manager.create(accounts[0], 2528821098,"",[1000,1000,1000],3000,0x0, bountyToken.address, {from: accounts[0]});
bounty0 = await manager.instances(1);
assert (bounty.logs[0].args.instantiation == bounty0);
});
it("verifies that bounty creation with incorrect args fails", async () => {
let stbf = await StandardBountyFactory.new({from: accounts[0]});
let tbf = await TokenBountyFactory.new({from: accounts[0]});
let manager = await BountyRegistry.new(stbf.address, tbf.address, {from: accounts[0]});
let owner = await manager.owner();
assert (owner == accounts[0]);
try {
await manager.create(accounts[0], 2528821098,"",[0,1000,1000],3000,0x0,0x0, {from: accounts[0]});
} catch(error){
return utils.ensureException(error);
}
});
it("verifies that bounty removal works", async () => {
let stbf = await StandardBountyFactory.new({from: accounts[0]});
let tbf = await TokenBountyFactory.new({from: accounts[0]});
let manager = await BountyRegistry.new(stbf.address, tbf.address, {from: accounts[0]});
let owner = await manager.owner();
assert (owner == accounts[0]);
let bounty = await manager.create(accounts[0], 2528821098,"",[1000,1000,1000],3000,0x0,0x0, {from: accounts[1]});
let bounty2 = await manager.create(accounts[0], 2528821098,"",[1000,1000,1000],3000,0x0,0x0, {from: accounts[1]});
let bounty0 = await manager.instances(0);
assert (bounty.logs[0].args.instantiation == bounty0);
await manager.remove(0, bounty0, 0, accounts[1]);
let instance = await manager.instances(0);
assert(instance == "0x0000000000000000000000000000000000000000");
let instantiation = await manager.instantiations(accounts[1], 0);
assert(instantiation == "0x0000000000000000000000000000000000000000");
});
it("verifies that bounty removal fails for anyone but the owner", async () => {
let stbf = await StandardBountyFactory.new({from: accounts[0]});
let tbf = await TokenBountyFactory.new({from: accounts[0]});
let manager = await BountyRegistry.new(stbf.address, tbf.address, {from: accounts[0]});
let owner = await manager.owner();
assert (owner == accounts[0]);
let bounty = await manager.create(accounts[0], 2528821098,"",[1000,1000,1000],3000,0x0,0x0, {from: accounts[1]});
let bounty2 = await manager.create(accounts[0], 2528821098,"",[1000,1000,1000],3000,0x0,0x0, {from: accounts[1]});
let bounty0 = await manager.instances(0);
assert (bounty.logs[0].args.instantiation == bounty0);
try {
await manager.remove(0, bounty0, 0, accounts[1], {from: accounts[1]});
} catch(error){
return utils.ensureException(error);
}
});
it("verifies that bounty removal with wrong IDs fails", async () => {
let stbf = await StandardBountyFactory.new({from: accounts[0]});
let tbf = await TokenBountyFactory.new({from: accounts[0]});
let manager = await BountyRegistry.new(stbf.address, tbf.address, {from: accounts[0]});
let owner = await manager.owner();
assert (owner == accounts[0]);
let bounty = await manager.create(accounts[0], 2528821098,"",[1000,1000,1000],3000,0x0,0x0, {from: accounts[1]});
let bounty2 = await manager.create(accounts[0], 2528821098,"",[1000,1000,1000],3000,0x0,0x0, {from: accounts[1]});
let bounty0 = await manager.instances(0);
assert (bounty.logs[0].args.instantiation == bounty0);
try {
await manager.remove(1, bounty0, 1, accounts[1], {from: accounts[0]});
} catch(error){
return utils.ensureException(error);
}
});
});

File diff suppressed because it is too large Load Diff

1455
test/standardBountyInEth.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1503
test/standardBountyMany.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff