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

[WIP] EIP-0018: ErgoFund contracts #33

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
161 changes: 161 additions & 0 deletions eip-0018.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# ErgoFund contracts

* Author: kushti
* Status: Proposed
* Created: 11-June-2021
* License: CC0
* Forking: not needed

## Motivation

Collecting funds in different contexts is very much needed for building common infrastructure and applications
in the Ergo ecosystem. This EIP is proposing contracts and standardized box formats for announcing
crowdfunding campaigns and collecting funds.

## Overall Design

The design of the crowdfunding contracts and box templates below is centered around
efficiency of blockchain scanning by an offchain application (backend of ErgoFund service built on top of the Scanner,
https://github.com/ergoplatform/scanner ).

## Campaign Registration

Crowdfunding campaign registration is controlled by a control box associated with an NFT which registers R4 and R5
contain registration price and address (script) to pay for campaign registration.

Control box:

Contains campaign registration price in register R4 (as long value) and script to pay for registration in register R5
(as SigmaProp constant).

To register a new crowdfunding campaign, crowdfunding token must be bought (to compensate expenses for scanning,
storing crowdfunding data, producing and maintaining UI).

Sell contract:

```scala
{
val controlBox = CONTEXT.dataInputs(0)

val firstUnusedCampaignId = SELF.R4[Int].get

// check control box NFT
val properControlBox = controlBox.tokens(0)._1 == fromBase64("csP7zjJD1JHYHrVkzasWYrH41MfjEriIcM7Hm3z9QyE=")

val price = controlBox.R4[Long].get
val script = controlBox.R5[SigmaProp].get


val inTokensCount = SELF.tokens(1)._2

val selfOut = OUTPUTS(0)

val validNFT = selfOut.tokens(0)._1 == fromBase64("FbCuQcJCMAaf+W2susCTKFCsDCoJJNr3KjnojLzzrNU=")
val tokenId = selfOut.tokens(1)._1 // token the contract is selling
val validOutTokenId = tokenId == fromBase64("BbZrl+WAL2RHtn/jDLQFXhTWsXuxT19WPWXJYixDplk=")
val outTokensCount = selfOut.tokens(1)._2

val validTokens = validNFT && validOutTokenId && (outTokensCount == inTokensCount - 1)

val validScript = SELF.propositionBytes == selfOut.propositionBytes

val validCampaignIdUpdate = selfOut.R4[Int].get == (firstUnusedCampaignId + 1)

val rewardOut = OUTPUTS(1)
val validPayment = rewardOut.value >= price && rewardOut.propositionBytes == script.propBytes

val campaignOut = OUTPUTS(2)
val validCampaign = campaignOut.tokens(0)._1 == tokenId && campaignOut.R4[Int].get == firstUnusedCampaignId


properControlBox && validTokens && validScript && validPayment && validCampaignIdUpdate && validCampaign
}
```

So to register campaign, one need to pass control box as a data input, sell contract among inputs, and create a box with
campaign box data specified below in outputs, as well as an updated campaign token sale box.


Control box NFT id: 72c3fbce3243d491d81eb564cdab1662b1f8d4c7e312b88870cec79b7cfd4321
Tokensale box NFT id: 15b0ae41c24230069ff96dacbac0932850ac0c2a0924daf72a39e88cbcf3acd5
Campaign identification token: 05b66b97e5802f6447b67fe30cb4055e14d6b17bb14f5f563d65c9622c43a659

Tokensale box P2S address: yvNXjWe8vBvZTXwyUHemPU59CRfm8AnvnzXowRQkQ9hoiGXsS7oEUGLPof6RoYAdKXEgTWR4qTK44w3GBuNdBjXeWcHFeBvyFxHWdEBsnqnYhtaFC2S71eHRPpLEndDghZebLdx25nufFh8YFJ9D8gTxTvxqahBgzpJT7pbUAUEkE2iRSZwpiXvj5SUmA6vmTrQzMTxicBG2mGV1NLq5rjBCAxjM5FpNSJ1KgqrxeTAfBvs1QnMUZ6CTBtGPLyNUpFoPTaYUPp9PfBni7FAQsumx36tukUHA2p3NdgfhTbYYSJTDQi7eWW2YikFvczXpTCfecbCL7HA1o6Cg6DvntmpJHWfjopWnsbpDdLg6SbP4Fup7wCnQaeL6NcSTVP1K5btmK5JryZc36V8KdHkheyaVzYTMA1Gufp6bAGtZP

## Campaign Box Data

For registering campaign, it is enough just to create box with ErgoFund campaign token, proper campaign ID, campaign desc, script, recommended deadline, min to raise. Then offchain scanner will find it (and so UI will be able to display campaign data).

For the scanner, there are following requirements campaign box should satisfy:

value >= 1 ERG (1000000000 nanoERG)
script = "4MQyMKvMbnCJG3aJ" (false proposition, so no one can spend)

Registers:
*R4* - campaign ID (Int)
*R5* - campaign desc (byte array)
*R6* - campaign script (funds collected will go to this)
*R7* - fundraising deadline (Int, # of block, exclusive)
*R8* - min value for successful fundraising (Long)

https://explorer.ergoplatform.com/en/transactions/2e25bc0ea4d01108ab1cd76969f49022228b533a2ea50540f6cde6258029a510


example:

```json
[
{
"address": "4MQyMKvMbnCJG3aJ",
"value": 100000000,
"assets": [
{
"tokenId": "05b66b97e5802f6447b67fe30cb4055e14d6b17bb14f5f563d65c9622c43a659",
"amount": 1
}
],
"registers": {
"R4": "0400",
"R5": "0e00",
"R6": "08cd0327e65711a59378c59359c3e1d0f7abe906479eccb76094e50fe79d743ccc15e6",
"R7": "04a0be49",
"R8": "0580d0acf30e"
}
}
]
```

## Pledge Contract

Pledge contract is an example of self-sovereign DeFi principle. The contract is allowing box being protected by it to be spent if a spending transaction can collect at least *minToRaise* ERGs to *projectPubKey*, and inclusion height of the transaction is less than *deadline*. Otherwise, after *deadline* height is met, funds can be claimed by *projectPubKey*. Please note that both *backerPubKey* and
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think funds can be claimed by projectPubKey Should be, funds can be claimed by backerPubKey

*projectPubKey* can be arbitrary scripts.

```scala
{
val campaignId = SELF.R4[Int].get
val backerPubKey = SELF.R5[SigmaProp].get
val projectPubKey = SELF.R6[SigmaProp].get
val deadline = SELF.R7[Int].get // height
val minToRaise = SELF.R8[Long].get

val fundraisingFailure = HEIGHT >= deadline && OUTPUTS(0).propositionBytes == backerPubKey.propBytes && OUTPUTS(0).value >= SELF.value
val enoughRaised = {(outBox: Box) => outBox.value >= minToRaise && outBox.propositionBytes == projectPubKey.propBytes && outBox.R4[Int].get == campaignId}

val fundraisingSuccess = HEIGHT < deadline && enoughRaised(OUTPUTS(0))
fundraisingFailure || fundraisingSuccess
}
```

address: XUFypmadXVvYmBWtiuwDioN1rtj6nSvqgzgWjx1yFmHAVndPaAEgnUvEvEDSkpgZPRmCYeqxewi8ZKZ4Pamp1M9DAdu8d4PgShGRDV9inwzN6TtDeefyQbFXRmKCSJSyzySrGAt16

*R4* - campaign ID (Int)
*R5* - backer script (SigmaProp)
*R6* - campaign script (funds collected will go to this) (SigmaProp)
*R7* - fundraising deadline (Int, # of block, inclusive) (Int)
*R8* - min value for successful fundraising (Long)


## TO-DO

* Contracts to collect funds in SigUSD and other tokens.
* Contracts for campaigns with thousands of pledges.