Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Standard Bounties #1081

Merged
merged 12 commits into from
Jul 31, 2018
Merged

Standard Bounties #1081

merged 12 commits into from
Jul 31, 2018

Conversation

mbeylin
Copy link
Contributor

@mbeylin mbeylin commented May 15, 2018

eip: 1081
Title: Standard Bounties
Authors: Mark Beylin <mark.beylin@consensys.net>, Kevin Owocki <kevin.owocki@consensys.net>, Ricardo Guilherme Schmidt (@3esmit)
Discussions-to: https://gitter.im/bounties-network/Lobby
Status: Draft
Type: Standards Track
Category: ERC
Created: 2018-05-14
Requires: 20

Simple Summary

A standard contract and interface for issuing bounties on Ethereum, usable for any type of task, paying in any ERC20 token or in ETH.

Abstract

In order to encourage cross-platform interoperability of bounties on Ethereum, and for easier reputational tracking, StandardBounties can facilitate the administration of funds in exchange for deliverables corresponding to a completed task, in a publicly auditable and immutable fashion.

Motivation

In the absence of a standard for bounties on Ethereum, it would be difficult for platforms to collaborate and share the bounties which users create (thereby recreating the walled gardens which currently exist on Web2.0 task outsourcing platforms). A standardization of these interactions across task types also makes it far easier to track various reputational metrics (such as how frequently you pay for completed submissions, or how frequently your work gets accepted).

Specification

After studying bounties as they've existed for thousands of years (and after implementing and processing over 300 of them on main-net in beta), we've discovered that there are 3 core steps to every bounty:

  • a bounty is issued: an issuer specifies the requirements for the task, describing the desired outcome, and how much they would be willing to pay for the completion of that task (denoted in one or several tokens).
  • a bounty is fulfilled: a bounty fulfiller may see the bounty, complete the task, and produce a deliverable which is itself the desired outcome of the task, or simply a record that it was completed. Hashes of these deliverables should be stored immutably on-chain, to serve as proof after the fact.
  • a fulfillment is accepted: a bounty issuer or arbiter may select one or more submissions to be accepted, thereby releasing payment to the bounty fulfiller(s), and transferring ownership over the given deliverable to the issuer.

To implement these steps, a number of functions are needed:

  • initializeBounty(address _issuer, address _arbiter, string _data, uint _deadline): This is used when deploying a new StandardBounty contract, and is particularly useful when applying the proxy design pattern, whereby bounties cannot be initialized in their constructors. Here, the data string should represent an IPFS hash, corresponding to a JSON object which conforms to the schema (described below).
  • fulfillBounty(address[] _fulfillers, uint[] _numerators, uint _denomenator, string _data): This is called to submit a fulfillment, submitting a string representing an IPFS hash which contains the deliverable for the bounty. Initially fulfillments could only be submitted by one individual at a time, however users consistently told us they desired to be able to collaborate on fulfillments, thereby allowing the credit for submissions to be shared by several parties. The lines along which eventual payouts are split are determined by the fractions of the submission credited to each fulfiller (using the array of numerators and single denominator). Here, a bounty platform may also include themselves as a collaborator to collect a small fee for matching the bounty with fulfillers.
  • acceptFulfillment(uint _fulfillmentId, StandardToken[] _payoutTokens, uint[] _tokenAmounts): This is called by the issuer or the arbiter to pay out a given fulfillment, using an array of tokens, and an array of amounts of each token to be split among the contributors. This allows for the bounty payout amount to move as it needs to based on incoming contributions (which may be transferred directly to the contract address). It also allows for the easy splitting of a given bounty's balance among several fulfillments, if the need should arise.
    • drainBounty(StandardToken[] _payoutTokens): This may be called by the issuer to drain a bounty of it's funds, if the need should arise.
  • changeBounty(address _issuer, address _arbiter, string _data, uint _deadline): This may be called by the issuer to change the issuer, arbiter, data, and deadline fields of their bounty.
  • changeIssuer(address _issuer): This may be called by the issuer to change to a new issuer if need be
  • changeArbiter(address _arbiter): This may be called by the issuer to change to a new arbiter if need be
  • changeData(string _data): This may be called by the issuer to change just the data
  • changeDeadline(uint _deadline): This may be called by the issuer to change just the deadline

Optional Functions:

  • acceptAndFulfill(address[] _fulfillers, uint[] _numerators, uint _denomenator, string _data, StandardToken[] _payoutTokens, uint[] _tokenAmounts): During the course of the development of this standard, we discovered the desire for fulfillers to avoid paying gas fees on their own, entrusting the bounty's issuer to make the submission for them, and at the same time accept it. This is useful since it still immutably stores the exchange of tokens for completed work, but avoids the need for new bounty fulfillers to have any ETH to pay for gas costs in advance of their earnings.
  • changeMasterCopy(StandardBounty _masterCopy): For issuers to be able to change the masterCopy which their proxy contract relies on, if the proxy design pattern is being employed.
  • refundableContribute(uint[] _amounts, StandardToken[] _tokens): While non-refundable contributions may be sent to a bounty simply by transferring those tokens to the address where it resides, one may also desire to contribute to a bounty with the option to refund their contribution, should the bounty never receive a correct submission which is paid out.
    refundContribution(uint _contributionId): If a bounty hasn't yet paid out to any correct submissions and is past it's deadline, those individuals who employed the refundableContribute function may retreive their funds from the contract.

Schemas
Persona Schema:

{
   name: // optional - A string representing the name of the persona
   email: // optional - A string representing the preferred contact email of the persona
   githubUsername: // optional - A string representing the github username of the persona
   address: // required - A string web3 address of the persona
}

Bounty issuance data Schema:

{
  payload: {
    title: // A string representing the title of the bounty
    description: // A string representing the description of the bounty, including all requirements
    issuer: {
       // persona for the issuer of the bounty
    },
    funders:[
       // array of personas of those who funded the issue.
    ],
    categories: // an array of strings, representing the categories of tasks which are being requested
    created: // the timestamp in seconds when the bounty was created
    tokenSymbol: // the symbol for the token which the bounty pays out
    tokenAddress: // the address for the token which the bounty pays out (0x0 if ETH)

    // ------- add optional fields here -------
    sourceFileName: // A string representing the name of the file
    sourceFileHash: // The IPFS hash of the file associated with the bounty
    sourceDirectoryHash: // The IPFS hash of the directory which can be used to access the file
    webReferenceURL: // The link to a relevant web reference (ie github issue)
  },
  meta: {
    platform: // a string representing the original posting platform (ie 'gitcoin')
    schemaVersion: // a string representing the version number (ie '0.1')
    schemaName: // a string representing the name of the schema (ie 'standardSchema' or 'gitcoinSchema')
  }
}

Bounty fulfillment data Schema:

{
  payload: {
    description: // A string representing the description of the fulfillment, and any necessary links to works
    sourceFileName: // A string representing the name of the file being submitted
    sourceFileHash: // A string representing the IPFS hash of the file being submitted
    sourceDirectoryHash: // A string representing the IPFS hash of the directory which holds the file being submitted
    fulfillers: {
      // personas for the individuals whose work is being submitted
    }

    // ------- add optional fields here -------
  },
  meta: {
    platform: // a string representing the original posting platform (ie 'gitcoin')
    schemaVersion: // a string representing the version number (ie '0.1')
    schemaName: // a string representing the name of the schema (ie 'standardSchema' or 'gitcoinSchema')
  }
}

Rationale

The development of this standard began a year ago, with the goal of encouraging interoperability among bounty implementations on Ethereum. The initial version had significantly more restrictions: a bounty's data could not be changed after issuance (it seemed unfair for bounty issuers to change the requirements after work is underway), and the bounty payout could not be changed (all funds needed to be deposited in the bounty contract before it could accept submissions).

The initial version was also far less extensible, and only allowed for fixed payments to a given set of fulfillments. This new version makes it possible for funds to be split among several correct submissions, for submissions to be shared among several contributors, and for payouts to not only be in a single token as before, but in as many tokens as the issuer of the bounty desires. These design decisions were made after the 8+ months which Gitcoin, the Bounties Network, and Status Open Bounty have been live and meaningfully facilitating bounties for repositories in the Web3.0 ecosystem.

Test Cases

Tests for our implementation can be found here: https://github.com/Bounties-Network/StandardBounties/tree/develop/test

Implementation

A reference implementation can be found here: https://github.com/Bounties-Network/StandardBounties/blob/develop/contracts/StandardBounty.sol
Although this code has been tested, it has not yet been audited or bug-bountied, so we cannot make any assertions about it's correctness, nor can we presently encourage it's use to hold funds on the Ethereum mainnet.

Copyright

Copyright and related rights waived via CC0.

@@ -0,0 +1,122 @@
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please reformat using the standard format dictated in EIP-0 and EIP-X.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only difference was in the formatting of the header, correct? I've fixed it in standard-bounties.md

EIPS/eip-1081.md Outdated
@@ -0,0 +1,123 @@
---
EIP: <to be assigned>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please set to 1081.

EIPS/eip-1081.md Outdated
@@ -0,0 +1,123 @@
---
EIP: <to be assigned>
Title: EIP-1081 Standard Bounties
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove EIP-1081 from the title.

EIPS/eip-1081.md Outdated
EIP: <to be assigned>
Title: EIP-1081 Standard Bounties
Authors: Mark Beylin <mark.beylin@consensys.net>, Kevin Owocki <kevin.owocki@consensys.net>, Ricardo Guilherme Schmidt (@3esmit)
Discussions-to: <mark.beylin@consensys.net>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide a public discussion URL.

EIPS/eip-1081.md Outdated
Status: Draft
Type: Standards Track
Category: ERC
Created: created on 2018-05-14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove 'created on'.

EIPS/eip-1081.md Outdated
Type: Standards Track
Category: ERC
Created: created on 2018-05-14
Requires: EIP20
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove 'EIP'.

@joshorig
Copy link

The Kauri team are currenly working on bounties.network integration and would like to make the following proposals to the EIP.

initializeBounty: Currently, this takes the controller and data as parameters, we propose adding option to inlcude address arbiter and uint deadline

  • This would enable acceptFulfillment to be called by an address which is not the controller (which is necessary in our case)
  • This would mean acceptFulfillment would have an onlyApprover rather than onlyController modifier
  • Adding the ability to set a deadline, before which funds cannot be refunded/drained and after which fulfillments cannot be submitted. This is to protect fufillers and ensure bounties are not drained/refunded whilst fufillers are in the process of fufilling tasks.

changeData - This allows the controller to change data at any time

  • We propose the ability for a fufiller to flag they are working on a bounty which would then require their approval to change data (e.g the requirements)

@mbeylin
Copy link
Contributor Author

mbeylin commented May 31, 2018

I'm strongly in favour of bringing back the optional arbiter address (0x0 if not used), as well as the deadline to moderate bounty draining/refunds.

The other proposal about requiring approval for changing data is interesting but possibly prohibitive for issuers (right now we're seeing a significant need for arbiters to need to update the data throughout the process).

@owocki do you have thoughts on this?

@owocki
Copy link
Contributor

owocki commented May 31, 2018

I'm strongly in favour of bringing back the optional arbiter address (0x0 if not used), as well as the deadline to moderate bounty draining/refunds.

strongly agree about the arbiter address. this will allow us at Gitcoin to do some really important stuff regarding auto-payout when a PR is merged.. etc
moderately agree about "deadline to moderate bounty draining/refunds" its a nice feature to have.

@mbeylin
Copy link
Contributor Author

mbeylin commented Jun 3, 2018

My general concern with adding in the need for approval from changing data is that it forces the submission of your intention to be on-chain rather than stored elsewhere- is this a good thing? Forcing people to pay gas fees on this action is may actually be beneficial as a disincentive from over-intending to too many bounties

@joshorig
Copy link

joshorig commented Jun 4, 2018

So the flag for changing data is intended to be optional.

The intention is that a user would be able to flag a bounty if they feel it is beneficial for them to do so to provide extra protection from the issuer changing requirements underneath them, (in this case, paying the gas fee is like paying an insurance premium)

@joshorig
Copy link

joshorig commented Jun 4, 2018

drainBounty - this can be called by the controller, since others may also contribute to the funds in the bounty there is a concern here that the controller is able to drain the funds added by others

@mbeylin
Copy link
Contributor Author

mbeylin commented Jun 4, 2018

I believe we should be very intentional about the flagging/"submitting desire to fulfill" of bounties: if this is to live on-chain for some bounties, it should be there for all bounties (so that this information is easily shared among platforms). We've actually run into this issue somewhat, where bounties on Gitcoin have users claiming them, however they remain unclaimed when displayed on the Bounties Network (since the "claiming" is off-chain and stored on Gitcoin's servers).

I believe we may make this "claiming" functionality extremely lightweight from a gas perspective (just flipping a bit in an address->bool mapping).

On the topic of the drainBounty function I believe it is sufficient to allow the controller to drain it at any moment. After all, if they desired to drain the bounty before the deadline elapsed, they could simply fulfill the bounty from a new address (sybil attacking), thereby achieving the same outcome, with far less clarity around what happened. We should be deliberate in warning contributors to bounties that their funds are at the mercy of the controller, explicitly establishing the trusted relationships between these actors.

@Arachnid Arachnid changed the title Standard Bounties [WIP] Standard Bounties Jun 4, 2018
@Arachnid
Copy link
Contributor

Arachnid commented Jun 4, 2018

I have added [WIP] to the PR title. Please remove when this is ready for merge.

@craigwilliams84
Copy link

craigwilliams84 commented Jun 8, 2018

"I believe we should be very intentional about the flagging/"submitting desire to fulfill" of bounties: if this is to live on-chain for some bounties, it should be there for all bounties" - Agree that the flag should exist for all bounties, but a user 'flagging' a bounty should not be a compulsory prerequisite for fulfilling the bounty. They can then make a decision around if it is worth the additional gas cost in order to lock down the scope of the bounty.

On the drainBounty debate, currently in Kauri, it is only an assigned third party (a topic moderator) that has the ability to accept a bounty fulfilment. Therefore the scenario where a controller submits a fake fulfilment can't happen in our system, and so locking down the draining of funds makes more sense. Also, each user that has contributed to a bounty can only withdraw the funds that they put in (and only once the deadline has been reached). If its decided that this is not a generic enough use case, then one route we could go down is to implement a contract in the middle that acts as the bounty controller and contains these additional rules.

@mbeylin
Copy link
Contributor Author

mbeylin commented Jun 11, 2018

a user 'flagging' a bounty should not be a compulsory prerequisite for fulfilling the bounty

I think this makes sense

In your design, who is the decider of correct submissions? In this version of the contract both the issuer and the arbiter have the right to accept submissions. If you believe the controller shouldn't be able to accept submissions alongside the arbiter (or drain the bounty) then yes, I believe that functionality can be included in a separate contract.

EIPS/eip-1081.md Outdated
@@ -0,0 +1,123 @@
---
EIP: 1081
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please lowercase these headers, as in eip-0 and eip-x.

@mbeylin
Copy link
Contributor Author

mbeylin commented Jun 11, 2018

@craigwilliams84 @joshorig how mandatory would it be for you guys to have this approve-data-change feature added?

While I generally agree that issuers changing data drastically is bad manners and can lead to a dispute, should we forcefully stop them? I've begun implementing these changes and have realized that this approvals process would likely be so gas expensive that it wouldn't make sense. It would also let someone attack someone's bounty by submitting intention, thereby locking their data when they never accept changes.

This is compounded that the changes in data may actually just be additive, i.e. the inclusion of more links or specific details, which is a good thing for bounty fulfillers.

My gut says we should let issuers change data as they desire, and rely on a dispute resolution system (like Delphi) to pay out fulfillers if they feel aggrieved (i.e. if bounty data was changed substantially after they publicly started work, and performed a significant portion of it).

@joshorig
Copy link

@mbeylin you raise valid points, likely dispute resolution is a better way to solve such an issue.
Griefing an issue via flagging and never completing work/accepting changes is a concern with the data lock scheme, also again increasing the number of transactions for the user.

It's not a mandatory feature for us it worth us looking at other ways to solve that particular issue

@craigwilliams84
Copy link

@mbeylin As it currently stands, the spec is tightly coupled to a contract-per-bounty approach. For example, fulfillBounty(...) does not have a bounty identifier argument. This means that someone attempting to implement the standard would be forced to design their contracts in this way...is this the intention?

@craigwilliams84
Copy link

In your design, who is the decider of correct submissions?

We have the concept of a 'topic' associated with bounties, and each topic has a defined set of moderators (who should be subject matter experts) that decide if a submissions is accurate and of a high enough quality. Currently we are in control of assigning moderators, but will move to a community based approach over time.

@mbeylin
Copy link
Contributor Author

mbeylin commented Jun 19, 2018

@craigwilliams84 With regards to using a contract-per-bounty approach, there are a number of benefits, namely:

  • you may contribute tokens or ETH directly to a bounty simply by sending it to that bounty's dedicated address
  • in the event that a bug is potentially discovered in any code, there's no possibility that people could potentially spend tokens from someone else's bounty
  • bounties don't have to track their various balances in different tokens (potentially opening up holes due to malicious ERC20 contracts), and people can simply choose which tokens to pay with at the time of fulfillment acceptance

In terms of your moderator system, I believe this functionality should likely live within a separate contract, which then has the ability to be the "arbiter" and accept submissions.

@mbeylin
Copy link
Contributor Author

mbeylin commented Jun 28, 2018

I wanted to share progress on this effort, as the contracts have fully implemented based on the needs specified in this thread.

@owocki has requested that the contracts also be able to support ERC721 NFT tokens, however I've run into an issue when implementing this feature. Because NFTs are un-splittable, we can't calculate fractions for them like we might for ERC20 tokens being paid for a fulfillment which is shared by several participants (at various ratios). This has required us to change the fields on the acceptFulfillment() method to include a 2D array of token amounts for each of the fulfillers. While this provides an elegant solution (so that, for instance, each contributor to the bounty could get one of the crypto kitties being paid out for a bounty), it is only available currently in the experimental ABIEncoderV2. This means that the web3 library doesn't currently support 2d array parameters (as documented in this issue: web3/web3.js#1738).

There are several alternatives we can consider now so that we may move forward quickly (without letting this be a blocker for us). It's worth remembering that in this new architecture, migrating to new contracts is as easy as changing the masterCopy address on new bounties, taking advantage of the proxy architecture.

  • we move forward without NFTs for now and keep the several fulfillers per fulfillment
  • we include NFTs and remove several fulfillers per fulfillment
  • we remove the ability to pay in multiple tokens

In my opinion the first of these options seems the most reasonable based on the needs we've heard from users, but I want to hear your thoughts as well @craigwilliams84 @joshorig

@owocki
Copy link
Contributor

owocki commented Jul 2, 2018

My first reaction is to challenge this statement :

This has required us to change the fields on the acceptFulfillment() method to include a 2D array of token amounts for each of the fulfillers

I dont see why this follows from

Because NFTs are un-splittable, we can't calculate fractions for them like we might for ERC20 tokens being paid for a fulfillment which is shared by several participants (at various ratios).

Why not just only accept whole integer payouts for ERC721 tokens, and throw if someone tries to do a fractional payout.

====================================================

Beyond that, if i was forced to choose: i think that multiple tokens are important. i also think that several fulfillers per fulfillment are important. my vote would be "we move forward without NFTs for now and keep the several fulfillers per fulfillment"

@owocki
Copy link
Contributor

owocki commented Jul 2, 2018

@karelstriegel i'd love to get your guys' feedback on this standard.

@mbeylin
Copy link
Contributor Author

mbeylin commented Jul 2, 2018

@owocki I'm happy to explain.

Currently, the way that the payout amounts are entered is that upon acceptance of a fulfillment, the issuer of the bounty must send in the addresses of the particular tokens they want to pay out, along with the amount of the token being disbursed (two 1D arrays). However there's an issue: when a fulfillment is submitted it's possible that you'd have 2 fulfillers, each getting half of the credit for the submission.

For fungible tokens this works well: if there is a submission with multiple fulfillers, it just divides the tokens up as best as it can among the fulfillers, and disburses them accordingly. However, in the case of NFTs, the value isn't the number of fungible tokens being sent, but actually the ID of the NFT in question.

This creates a serious issue: how should non fungible assets be handled in the case where there are multiple fulfillers? Should they just automatically be sent to the first fulfiller in the array? This seems like a bad solution. The only way I could foresee getting around this problem was doing away with the storage of the fractions (for splitting fulfillment credit), moving it into the JSON object on IPFS, and instead allowing fulfillers to disburse different token amounts to different participants (presumably following the credit splitting outlined in the submission). This would require specifically outlining how much each individual should receive in each token. This is a 2D array.

@owocki
Copy link
Contributor

owocki commented Jul 3, 2018

makes sense to me. thanks for the thorough expalnation. my vote remains what it was above

my vote would be "we move forward without NFTs for now and keep the several fulfillers per fulfillment"

@craigwilliams84
Copy link

We're looking into incorporating NFTs into Kauri right now, but it will be more along the lines of 'badges' received at certain milestones rather than something attached to a specific bounty. I vote to move forward without NFTs.

@joshorig
Copy link

joshorig commented Jul 4, 2018

I would go with the following option:

we move forward without NFTs for now and keep the several fulfillers per fulfillment

Allowing for multiple fulfillers seems to be essential, I'm not sure how well that lends itself to an NFT being the reward for a bounty. Seems like that needs some more thought.

@mbeylin
Copy link
Contributor Author

mbeylin commented Jul 7, 2018

Great. I've finished updating the EIP to document these amendments and the develop branch on the StandardBounties repo is now up to date with this design.

@mbeylin
Copy link
Contributor Author

mbeylin commented Jul 7, 2018

Before we begin a code freeze and audit (so that we may deploy these new contracts as soon as possible), I would like to publicly ask one final time for review from other bounty platforms which haven't yet weighed in on this EIP (in no particular order):

If you are interested in employing standardized, cross platform bounties, now is your chance to have your voice heard before it's too late!!!

@mbeylin
Copy link
Contributor Author

mbeylin commented Jul 31, 2018

After a fruitful code review on the Gitcoin livestream last week, some final fixes have been made to the contract, and it's finally ready for code freeze and audit. I believe now is a suitable time to remove the WIP status from this EIP and get it merged in as a draft.

@mbeylin mbeylin changed the title [WIP] Standard Bounties Standard Bounties Jul 31, 2018
@mbeylin
Copy link
Contributor Author

mbeylin commented Jul 31, 2018

@Arachnid this EIP should now be ready for merging.

@Arachnid Arachnid merged commit 7bccb19 into ethereum:master Jul 31, 2018
Copy link

@kriszrtina kriszrtina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ethereum ethereum deleted a comment from Kennydoneit Jul 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants