diff --git a/eip-0018.md b/eip-0018.md new file mode 100644 index 00000000..86dd5027 --- /dev/null +++ b/eip-0018.md @@ -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 +*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. \ No newline at end of file