From ff15e6d717eebaced26194f3b35a6666fd306f44 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Thu, 10 Jun 2021 16:46:50 +0300 Subject: [PATCH 01/12] ErgoFund EIP alpha version --- eip-0018.md | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 eip-0018.md diff --git a/eip-0018.md b/eip-0018.md new file mode 100644 index 00000000..7a3c953e --- /dev/null +++ b/eip-0018.md @@ -0,0 +1,64 @@ + +## Campaign Data + +//For campaign, it is enough just to create box with ErgoFund token, campaign ID, campaign desc, script, recommended deadline, min to raise. + +value >= 1 ERG (1000000000 nanoERG) +script = "4MQyMKvMbnCJG3aJ" (false proposition, so no one can spend) + +*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 succesful fundraising (Long) + +// https://explorer.ergoplatform.com/en/transactions/2e25bc0ea4d01108ab1cd76969f49022228b533a2ea50540f6cde6258029a510 + +test token ID: 08fc8bd24f0eaa011db3342131cb06eb890066ac6d7e6f7fd61fcdd138bd1e2c + +example: + +[ + { + "address": "4MQyMKvMbnCJG3aJ", + "value": 100000000, + "assets": [ + { + "tokenId": "08fc8bd24f0eaa011db3342131cb06eb890066ac6d7e6f7fd61fcdd138bd1e2c", + "amount": 1 + } + ], + "registers": { + "R4": "0400", + "R5": "0e00", + "R6": "08cd0327e65711a59378c59359c3e1d0f7abe906479eccb76094e50fe79d743ccc15e6", + "R7": "04a0be49", + "R8": "0580d0acf30e" + } + } +] + + +## Pledge Contract + +{ + 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 +*R6* - campaign script (funds collected will go to this) +*R7* - fundraising deadline (Int, # of block, exclusive) +*R8* - min value for succesful fundraising (Long) \ No newline at end of file From 809d522cc53b41d4ea350cc00122ed3765be1226 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Fri, 11 Jun 2021 20:48:04 +0300 Subject: [PATCH 02/12] motivation --- eip-0018.md | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/eip-0018.md b/eip-0018.md index 7a3c953e..7befb72b 100644 --- a/eip-0018.md +++ b/eip-0018.md @@ -1,7 +1,22 @@ +# ErgoFund contracts -## Campaign Data +* Author: kushti +* Status: Proposed +* Created: 11-June-2021 +* License: CC0 +* Forking: not needed -//For campaign, it is enough just to create box with ErgoFund token, campaign ID, campaign desc, script, recommended deadline, min to raise. +## Motivation + +Collecting funds in different contexts is very much needed for building common infrastructure and projects + in the Ergo ecosystem. This EIP is proposing contracts and standardized boxes formats for announcing + crowdfunding campaigns and collecting funds. + +## Campaign Registration + +## Campaign Box Data + +For campaign, it is enough just to create box with ErgoFund token, campaign ID, campaign desc, script, recommended deadline, min to raise. value >= 1 ERG (1000000000 nanoERG) script = "4MQyMKvMbnCJG3aJ" (false proposition, so no one can spend) @@ -12,7 +27,7 @@ script = "4MQyMKvMbnCJG3aJ" (false proposition, so no one can spend) *R7* - fundraising deadline (Int, # of block, exclusive) *R8* - min value for succesful fundraising (Long) -// https://explorer.ergoplatform.com/en/transactions/2e25bc0ea4d01108ab1cd76969f49022228b533a2ea50540f6cde6258029a510 +https://explorer.ergoplatform.com/en/transactions/2e25bc0ea4d01108ab1cd76969f49022228b533a2ea50540f6cde6258029a510 test token ID: 08fc8bd24f0eaa011db3342131cb06eb890066ac6d7e6f7fd61fcdd138bd1e2c @@ -61,4 +76,10 @@ address: XUFypmadXVvYmBWtiuwDioN1rtj6nSvqgzgWjx1yFmHAVndPaAEgnUvEvEDSkpgZPRmCYeq *R5* - backer script *R6* - campaign script (funds collected will go to this) *R7* - fundraising deadline (Int, # of block, exclusive) -*R8* - min value for succesful fundraising (Long) \ No newline at end of file +*R8* - min value for succesful 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 From 2f72e7b8777c89688f956ea9d089e23f78f85aa3 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Sat, 12 Jun 2021 00:54:35 +0300 Subject: [PATCH 03/12] tokensale contract --- eip-0018.md | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/eip-0018.md b/eip-0018.md index 7befb72b..6f64dfa4 100644 --- a/eip-0018.md +++ b/eip-0018.md @@ -14,6 +14,42 @@ Collecting funds in different contexts is very much needed for building common i ## Campaign Registration +Sell contract: + +```scala +{ + val controlBox = CONTEXT.dataInputs(0) + + // check control box NFT + val properControlBox = controlBox.tokens(0)._1 == fromBase64("Q9z7qAx3AIz6MWMtmJ6Rk8CS/fk4HVXcyRgc7HjKtwA=") + + val price = controlBox.R4[Long].get + val script = controlBox.R5[Coll[Byte]].get + + + val inTokensCount = SELF.tokens(1)._2 + + val selfOut = OUTPUTS(0) + + val validNFT = selfOut.tokens(1)._1 == fromBase64("KPqq2cwwkO4DaGWJsIw5Zb4nHIa2UKiPNW5X2laIYvw=") + val validOutTokenId = selfOut.tokens(1)._1 == fromBase64("CPyL0k8OqgEdszQhMcsG64kAZqxtfm9/1h/N0Ti9Hiw=") + val outTokensCount = selfOut.tokens(1)._2 + + val validTokens = validNFT && validOutTokenId && (outTokensCount == inTokensCount - 1) + + val validScript = SELF.propositionBytes == selfOut.propositionBytes + + val rewardOut = OUTPUTS(1) + val validPayment = rewardOut.value >= 10000000000L && rewardOut.propositionBytes == script + + properControlBox && validTokens && validScript && validPayment +} +``` + + +Control box NFT id: 43dcfba80c77008cfa31632d989e9193c092fdf9381d55dcc9181cec78cab700 +Token sale NFT id: 28faaad9cc3090ee03686589b08c3965be271c86b650a88f356e57da568862fc + ## Campaign Box Data For campaign, it is enough just to create box with ErgoFund token, campaign ID, campaign desc, script, recommended deadline, min to raise. @@ -33,6 +69,7 @@ test token ID: 08fc8bd24f0eaa011db3342131cb06eb890066ac6d7e6f7fd61fcdd138bd1e2c example: +```json [ { "address": "4MQyMKvMbnCJG3aJ", @@ -52,10 +89,11 @@ example: } } ] - +``` ## Pledge Contract +```scala { val campaignId = SELF.R4[Int].get val backerPubKey = SELF.R5[SigmaProp].get @@ -68,7 +106,8 @@ example: val fundraisingSuccess = HEIGHT < deadline && enoughRaised(OUTPUTS(0)) fundraisingFailure || fundraisingSuccess - } +} +``` address: XUFypmadXVvYmBWtiuwDioN1rtj6nSvqgzgWjx1yFmHAVndPaAEgnUvEvEDSkpgZPRmCYeqxewi8ZKZ4Pamp1M9DAdu8d4PgShGRDV9inwzN6TtDeefyQbFXRmKCSJSyzySrGAt16 From 6147201b8a161bc318eee2c8a8d19a7f94ad602a Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Tue, 22 Jun 2021 00:27:06 +0300 Subject: [PATCH 04/12] overall design and campaign reg text --- eip-0018.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/eip-0018.md b/eip-0018.md index 6f64dfa4..7484f177 100644 --- a/eip-0018.md +++ b/eip-0018.md @@ -12,8 +12,24 @@ Collecting funds in different contexts is very much needed for building common i in the Ergo ecosystem. This EIP is proposing contracts and standardized boxes 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 (implementing backend of ErgoFund service). + ## 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 (long value) and script to pay for registration on register R5 +(byte array). + +To register a new crowdfunding campaign, crowdfunding token must be bought (to compensate expenses for scanning, +storing crowdfunding data, doing an UI) + Sell contract: ```scala @@ -50,6 +66,9 @@ Sell contract: Control box NFT id: 43dcfba80c77008cfa31632d989e9193c092fdf9381d55dcc9181cec78cab700 Token sale NFT id: 28faaad9cc3090ee03686589b08c3965be271c86b650a88f356e57da568862fc +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. + ## Campaign Box Data For campaign, it is enough just to create box with ErgoFund token, campaign ID, campaign desc, script, recommended deadline, min to raise. @@ -61,7 +80,7 @@ script = "4MQyMKvMbnCJG3aJ" (false proposition, so no one can spend) *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 succesful fundraising (Long) +*R8* - min value for successful fundraising (Long) https://explorer.ergoplatform.com/en/transactions/2e25bc0ea4d01108ab1cd76969f49022228b533a2ea50540f6cde6258029a510 From c216706a76a8eb2c3fe9eb5a38d11bd66ce6e5b8 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Tue, 22 Jun 2021 17:27:58 +0300 Subject: [PATCH 05/12] tokensale contract fixes --- eip-0018.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eip-0018.md b/eip-0018.md index 7484f177..8d09083e 100644 --- a/eip-0018.md +++ b/eip-0018.md @@ -47,7 +47,7 @@ Sell contract: val selfOut = OUTPUTS(0) - val validNFT = selfOut.tokens(1)._1 == fromBase64("KPqq2cwwkO4DaGWJsIw5Zb4nHIa2UKiPNW5X2laIYvw=") + val validNFT = selfOut.tokens(0)._1 == fromBase64("KPqq2cwwkO4DaGWJsIw5Zb4nHIa2UKiPNW5X2laIYvw=") val validOutTokenId = selfOut.tokens(1)._1 == fromBase64("CPyL0k8OqgEdszQhMcsG64kAZqxtfm9/1h/N0Ti9Hiw=") val outTokensCount = selfOut.tokens(1)._2 @@ -56,7 +56,7 @@ Sell contract: val validScript = SELF.propositionBytes == selfOut.propositionBytes val rewardOut = OUTPUTS(1) - val validPayment = rewardOut.value >= 10000000000L && rewardOut.propositionBytes == script + val validPayment = rewardOut.value >= price && rewardOut.propositionBytes == script properControlBox && validTokens && validScript && validPayment } From 08103d2c5d8c3080d2062b3ff31b0f27ab439025 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Tue, 22 Jun 2021 20:30:29 +0300 Subject: [PATCH 06/12] new test token ids --- eip-0018.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eip-0018.md b/eip-0018.md index 8d09083e..6b7ba253 100644 --- a/eip-0018.md +++ b/eip-0018.md @@ -64,7 +64,8 @@ Sell contract: Control box NFT id: 43dcfba80c77008cfa31632d989e9193c092fdf9381d55dcc9181cec78cab700 -Token sale NFT id: 28faaad9cc3090ee03686589b08c3965be271c86b650a88f356e57da568862fc +Token sale NFT id: 765f0123c14997e9f7b65530ba4e98b810541d23a65035c32aa1af56c9cbff04 +Campaign identification token: 1b1666211a4dc267f7aa3e38bcdb7a55d21311f9f24b05bb63834661710f4aa7 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. From 6f57caa7f3f162422ca420bbe85b18b88d237e34 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Thu, 22 Jul 2021 14:55:20 +0300 Subject: [PATCH 07/12] updating testing ids --- eip-0018.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/eip-0018.md b/eip-0018.md index 6b7ba253..7be97115 100644 --- a/eip-0018.md +++ b/eip-0018.md @@ -37,7 +37,7 @@ Sell contract: val controlBox = CONTEXT.dataInputs(0) // check control box NFT - val properControlBox = controlBox.tokens(0)._1 == fromBase64("Q9z7qAx3AIz6MWMtmJ6Rk8CS/fk4HVXcyRgc7HjKtwA=") + val properControlBox = controlBox.tokens(0)._1 == fromBase64("uuqgee/jRz3XRo5Vsg2pYceRVdqahII27Hpc8NdM+Zo=") val price = controlBox.R4[Long].get val script = controlBox.R5[Coll[Byte]].get @@ -47,8 +47,8 @@ Sell contract: val selfOut = OUTPUTS(0) - val validNFT = selfOut.tokens(0)._1 == fromBase64("KPqq2cwwkO4DaGWJsIw5Zb4nHIa2UKiPNW5X2laIYvw=") - val validOutTokenId = selfOut.tokens(1)._1 == fromBase64("CPyL0k8OqgEdszQhMcsG64kAZqxtfm9/1h/N0Ti9Hiw=") + val validNFT = selfOut.tokens(0)._1 == fromBase64("SKsyra9+sTLHbAd/3rPfamaSQXFo8VQcg6VUXBDGP4E=") + val validOutTokenId = selfOut.tokens(1)._1 == fromBase64("B6V6SJ0YdzStTJYFFP28sXm+rlgid0lUrBVkCF5kHc4=") val outTokensCount = selfOut.tokens(1)._2 val validTokens = validNFT && validOutTokenId && (outTokensCount == inTokensCount - 1) @@ -63,9 +63,11 @@ Sell contract: ``` -Control box NFT id: 43dcfba80c77008cfa31632d989e9193c092fdf9381d55dcc9181cec78cab700 -Token sale NFT id: 765f0123c14997e9f7b65530ba4e98b810541d23a65035c32aa1af56c9cbff04 -Campaign identification token: 1b1666211a4dc267f7aa3e38bcdb7a55d21311f9f24b05bb63834661710f4aa7 +Control box NFT id: baeaa079efe3473dd7468e55b20da961c79155da9a848236ec7a5cf0d74cf99a +Token sale NFT id: 48ab32adaf7eb132c76c077fdeb3df6a6692417168f1541c83a5545c10c63f81 +Campaign identification token: 07a57a489d187734ad4c960514fdbcb179beae5822774954ac1564085e641dce + +Tokensale box P2S address: r5wDC8T2yMcZWW8UzJoEsUD6fKjxe89SQPWCEHKLkjZLYAxLdrxYnBGvqXZDu8bZsurA3SGVhVQjcXYwVbtT2g8i91PmGbD2XNuoxT43bM56RSFGHDuyAzgE9GUtBegYbcYQ2XWy6ks6wHdq3cnBRWzf3WkXuR4LyuuDNN1jkQQE5NBBJfpVD6Eyuh1sSApMoJxf6Hv5jJjsUEMEkpZwyqjXzQBR1KUcp8nw7D88w135PfnDQVTAqmRAim1YEk35dvNtqBDAqaJK1ukRdcfbNe5axr8BRsn4wR9jX3fpuoh9q2FkmyvAWj2VWqYgacFvD9LwBVpRZi7nNBWVtp1 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. @@ -85,7 +87,6 @@ script = "4MQyMKvMbnCJG3aJ" (false proposition, so no one can spend) https://explorer.ergoplatform.com/en/transactions/2e25bc0ea4d01108ab1cd76969f49022228b533a2ea50540f6cde6258029a510 -test token ID: 08fc8bd24f0eaa011db3342131cb06eb890066ac6d7e6f7fd61fcdd138bd1e2c example: @@ -135,7 +136,7 @@ address: XUFypmadXVvYmBWtiuwDioN1rtj6nSvqgzgWjx1yFmHAVndPaAEgnUvEvEDSkpgZPRmCYeq *R5* - backer script *R6* - campaign script (funds collected will go to this) *R7* - fundraising deadline (Int, # of block, exclusive) -*R8* - min value for succesful fundraising (Long) +*R8* - min value for successful fundraising (Long) ## TO-DO From 28c24095c8693ede6ecf40fdcaf0952993385504 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Sat, 24 Jul 2021 01:19:03 +0300 Subject: [PATCH 08/12] campaignId check added --- eip-0018.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/eip-0018.md b/eip-0018.md index 7be97115..39249f32 100644 --- a/eip-0018.md +++ b/eip-0018.md @@ -36,6 +36,8 @@ Sell contract: { val controlBox = CONTEXT.dataInputs(0) + val firstUnusedCampaignId = SELF.R4[Int].get + // check control box NFT val properControlBox = controlBox.tokens(0)._1 == fromBase64("uuqgee/jRz3XRo5Vsg2pYceRVdqahII27Hpc8NdM+Zo=") @@ -47,18 +49,25 @@ Sell contract: val selfOut = OUTPUTS(0) - val validNFT = selfOut.tokens(0)._1 == fromBase64("SKsyra9+sTLHbAd/3rPfamaSQXFo8VQcg6VUXBDGP4E=") - val validOutTokenId = selfOut.tokens(1)._1 == fromBase64("B6V6SJ0YdzStTJYFFP28sXm+rlgid0lUrBVkCF5kHc4=") + val validNFT = selfOut.tokens(1)._1 == fromBase64("SKsyra9+sTLHbAd/3rPfamaSQXFo8VQcg6VUXBDGP4E=") + val tokenId = selfOut.tokens(1)._1 // token the contract is selling + val validOutTokenId = tokenId == fromBase64("B6V6SJ0YdzStTJYFFP28sXm+rlgid0lUrBVkCF5kHc4=") 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 + val validPayment = rewardOut.value >= 10000000000L && rewardOut.propositionBytes == script + + val campaignOut = OUTPUTS(2) + val validCampaign = campaignOut.tokens(0)._1 == tokenId && campaignOut.R4[Int].get == firstUnusedCampaignId + - properControlBox && validTokens && validScript && validPayment + properControlBox && validTokens && validScript && validPayment && validCampaignIdUpdate && validCampaign } ``` @@ -67,7 +76,7 @@ Control box NFT id: baeaa079efe3473dd7468e55b20da961c79155da9a848236ec7a5cf0d74c Token sale NFT id: 48ab32adaf7eb132c76c077fdeb3df6a6692417168f1541c83a5545c10c63f81 Campaign identification token: 07a57a489d187734ad4c960514fdbcb179beae5822774954ac1564085e641dce -Tokensale box P2S address: r5wDC8T2yMcZWW8UzJoEsUD6fKjxe89SQPWCEHKLkjZLYAxLdrxYnBGvqXZDu8bZsurA3SGVhVQjcXYwVbtT2g8i91PmGbD2XNuoxT43bM56RSFGHDuyAzgE9GUtBegYbcYQ2XWy6ks6wHdq3cnBRWzf3WkXuR4LyuuDNN1jkQQE5NBBJfpVD6Eyuh1sSApMoJxf6Hv5jJjsUEMEkpZwyqjXzQBR1KUcp8nw7D88w135PfnDQVTAqmRAim1YEk35dvNtqBDAqaJK1ukRdcfbNe5axr8BRsn4wR9jX3fpuoh9q2FkmyvAWj2VWqYgacFvD9LwBVpRZi7nNBWVtp1 +Tokensale box P2S address: u1JSmVkJbNKB4uAzLp7NkCusTZmu5nC2ogyQJminm7MDjw4Db2ykMTQYr1mPvy7kghqqrNjFUQNZP6cpL6cApjghg8wgx5Dn246zqbwzgACnqQuijY7gQx5HZ2fEEZrWZ5mm84kgrBfA1NpXWmw8h1i2RNsDBnrhPbzcsq5DdXzydQt1sp9UBfHbRCDshLyasTxKKj5QzThWZYvTxTMcAxJfucnok3vC3LvgLGaa67VneJWNshaVwScwiXHXGtFFXSVqh2NqRjQUMse4Y64dec6A4G7gumtifiM9NbSxzPvr5EviiVL5qKxXGmqj8uJgxMGNUEkgNSdW2XZg9TXgRuVDf1PbPesh1kt6cau5kFaRGeoSdwkGmpJx8reiuH2mMSqe7VaeqfqSbMirUG2vh7He47 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. @@ -135,7 +144,7 @@ address: XUFypmadXVvYmBWtiuwDioN1rtj6nSvqgzgWjx1yFmHAVndPaAEgnUvEvEDSkpgZPRmCYeq *R4* - campaign ID (Int) *R5* - backer script *R6* - campaign script (funds collected will go to this) -*R7* - fundraising deadline (Int, # of block, exclusive) +*R7* - fundraising deadline (Int, # of block, inclusive) *R8* - min value for successful fundraising (Long) From 1448ef1fa1165dbfcc414ee425cab3eac8aa05c9 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Tue, 10 Aug 2021 18:57:02 +0300 Subject: [PATCH 09/12] SigmaProp for dev script --- eip-0018.md | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/eip-0018.md b/eip-0018.md index 39249f32..2a0e6a59 100644 --- a/eip-0018.md +++ b/eip-0018.md @@ -8,14 +8,15 @@ ## Motivation -Collecting funds in different contexts is very much needed for building common infrastructure and projects - in the Ergo ecosystem. This EIP is proposing contracts and standardized boxes formats for announcing +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 (implementing backend of ErgoFund service). +efficiency of blockchain scanning by an offchain application (backend of ErgoFund service built on top of the Scanner, +). ## Campaign Registration @@ -24,8 +25,8 @@ contain registration price and address (script) to pay for campaign registration Control box: -Contains campaign registration price in register R4 (long value) and script to pay for registration on register R5 -(byte array). +Contains campaign registration price in register R4 (as long value) and script to pay for registration in register R5 +(as byte array). To register a new crowdfunding campaign, crowdfunding token must be bought (to compensate expenses for scanning, storing crowdfunding data, doing an UI) @@ -39,19 +40,19 @@ Sell contract: val firstUnusedCampaignId = SELF.R4[Int].get // check control box NFT - val properControlBox = controlBox.tokens(0)._1 == fromBase64("uuqgee/jRz3XRo5Vsg2pYceRVdqahII27Hpc8NdM+Zo=") + val properControlBox = controlBox.tokens(0)._1 == fromBase64("yIvSlx5W4Mc9SNf9xGbv4GiktkzfGbXunzjxzYUN4z4=") val price = controlBox.R4[Long].get - val script = controlBox.R5[Coll[Byte]].get + val script = controlBox.R5[SigmaProp].get val inTokensCount = SELF.tokens(1)._2 val selfOut = OUTPUTS(0) - val validNFT = selfOut.tokens(1)._1 == fromBase64("SKsyra9+sTLHbAd/3rPfamaSQXFo8VQcg6VUXBDGP4E=") + val validNFT = selfOut.tokens(0)._1 == fromBase64("HiS07HwkSWJzfT9Y7IOkRKoP1F8lBbQJ6ks8m6hCexI=") val tokenId = selfOut.tokens(1)._1 // token the contract is selling - val validOutTokenId = tokenId == fromBase64("B6V6SJ0YdzStTJYFFP28sXm+rlgid0lUrBVkCF5kHc4=") + val validOutTokenId = tokenId == fromBase64("e2oiizI+3iNhwC1632YiTHm2otIHXmODS5iIbkAEgC8=") val outTokensCount = selfOut.tokens(1)._2 val validTokens = validNFT && validOutTokenId && (outTokensCount == inTokensCount - 1) @@ -61,7 +62,7 @@ Sell contract: val validCampaignIdUpdate = selfOut.R4[Int].get == (firstUnusedCampaignId + 1) val rewardOut = OUTPUTS(1) - val validPayment = rewardOut.value >= 10000000000L && rewardOut.propositionBytes == script + val validPayment = rewardOut.value >= 10000000000L && rewardOut.propositionBytes == script.propBytes val campaignOut = OUTPUTS(2) val validCampaign = campaignOut.tokens(0)._1 == tokenId && campaignOut.R4[Int].get == firstUnusedCampaignId @@ -72,11 +73,11 @@ Sell contract: ``` -Control box NFT id: baeaa079efe3473dd7468e55b20da961c79155da9a848236ec7a5cf0d74cf99a -Token sale NFT id: 48ab32adaf7eb132c76c077fdeb3df6a6692417168f1541c83a5545c10c63f81 -Campaign identification token: 07a57a489d187734ad4c960514fdbcb179beae5822774954ac1564085e641dce +Control box NFT id: c88bd2971e56e0c73d48d7fdc466efe068a4b64cdf19b5ee9f38f1cd850de33e +Tokensale box NFT id: 1e24b4ec7c244962737d3f58ec83a444aa0fd45f2505b409ea4b3c9ba8427b12 +Campaign identification token: 7b6a228b323ede2361c02d7adf66224c79b6a2d2075e63834b98886e4004802f -Tokensale box P2S address: u1JSmVkJbNKB4uAzLp7NkCusTZmu5nC2ogyQJminm7MDjw4Db2ykMTQYr1mPvy7kghqqrNjFUQNZP6cpL6cApjghg8wgx5Dn246zqbwzgACnqQuijY7gQx5HZ2fEEZrWZ5mm84kgrBfA1NpXWmw8h1i2RNsDBnrhPbzcsq5DdXzydQt1sp9UBfHbRCDshLyasTxKKj5QzThWZYvTxTMcAxJfucnok3vC3LvgLGaa67VneJWNshaVwScwiXHXGtFFXSVqh2NqRjQUMse4Y64dec6A4G7gumtifiM9NbSxzPvr5EviiVL5qKxXGmqj8uJgxMGNUEkgNSdW2XZg9TXgRuVDf1PbPesh1kt6cau5kFaRGeoSdwkGmpJx8reiuH2mMSqe7VaeqfqSbMirUG2vh7He47 +Tokensale box P2S address: L7uDNCg9r1Zp6JUTJGEtnb6pPogTDs7Bxm4NUK94PXnhS6se7Mungk1g8a5NA3qwNeJi8mtj7aHmTrUzEe4ErZp2PZLcKAHC2LEdQHMrS8Y68x9wLVqbD5PocKNfD5hGrm3NZAecWmK47QWXSFuJG6M2ciUiVggXiR3Gr1nidtjT5VogZcuUjWHBMyXCh3hCtyaQA3icbAQaJSM2g1GyiFcyZrCFpn1P5B2j2JDGMSSCzQfpgueoC5yN5GMh6Hgwz7YGr7uvAQkFsfLLMAg9HKY7XzCByAUQVdDGetYhNngazmP1YDdaZ5U5VUg1TRtCYBsyZk1JYPJGBRTq5j2zcFLhcZkZBwBs92kp7BpGZcTe8vHvBnRv8h3jfUxGZ4K1h68SiH6Q6mbyvHyNz6vB3PgocArMKBbzAQzCpJio5BoS 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. From 871811032c0131ba125b0c051f2a74cebf3b35f1 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Fri, 13 Aug 2021 01:00:46 +0300 Subject: [PATCH 10/12] upd tokens and tokensale contract fix --- eip-0018.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/eip-0018.md b/eip-0018.md index 2a0e6a59..d5080b3a 100644 --- a/eip-0018.md +++ b/eip-0018.md @@ -40,7 +40,7 @@ Sell contract: val firstUnusedCampaignId = SELF.R4[Int].get // check control box NFT - val properControlBox = controlBox.tokens(0)._1 == fromBase64("yIvSlx5W4Mc9SNf9xGbv4GiktkzfGbXunzjxzYUN4z4=") + val properControlBox = controlBox.tokens(0)._1 == fromBase64("csP7zjJD1JHYHrVkzasWYrH41MfjEriIcM7Hm3z9QyE=") val price = controlBox.R4[Long].get val script = controlBox.R5[SigmaProp].get @@ -50,9 +50,9 @@ Sell contract: val selfOut = OUTPUTS(0) - val validNFT = selfOut.tokens(0)._1 == fromBase64("HiS07HwkSWJzfT9Y7IOkRKoP1F8lBbQJ6ks8m6hCexI=") + val validNFT = selfOut.tokens(0)._1 == fromBase64("FbCuQcJCMAaf+W2susCTKFCsDCoJJNr3KjnojLzzrNU=") val tokenId = selfOut.tokens(1)._1 // token the contract is selling - val validOutTokenId = tokenId == fromBase64("e2oiizI+3iNhwC1632YiTHm2otIHXmODS5iIbkAEgC8=") + val validOutTokenId = tokenId == fromBase64("BbZrl+WAL2RHtn/jDLQFXhTWsXuxT19WPWXJYixDplk=") val outTokensCount = selfOut.tokens(1)._2 val validTokens = validNFT && validOutTokenId && (outTokensCount == inTokensCount - 1) @@ -62,7 +62,7 @@ Sell contract: val validCampaignIdUpdate = selfOut.R4[Int].get == (firstUnusedCampaignId + 1) val rewardOut = OUTPUTS(1) - val validPayment = rewardOut.value >= 10000000000L && rewardOut.propositionBytes == script.propBytes + 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 @@ -73,11 +73,11 @@ Sell contract: ``` -Control box NFT id: c88bd2971e56e0c73d48d7fdc466efe068a4b64cdf19b5ee9f38f1cd850de33e -Tokensale box NFT id: 1e24b4ec7c244962737d3f58ec83a444aa0fd45f2505b409ea4b3c9ba8427b12 -Campaign identification token: 7b6a228b323ede2361c02d7adf66224c79b6a2d2075e63834b98886e4004802f +Control box NFT id: 72c3fbce3243d491d81eb564cdab1662b1f8d4c7e312b88870cec79b7cfd4321 +Tokensale box NFT id: 15b0ae41c24230069ff96dacbac0932850ac0c2a0924daf72a39e88cbcf3acd5 +Campaign identification token: 05b66b97e5802f6447b67fe30cb4055e14d6b17bb14f5f563d65c9622c43a659 -Tokensale box P2S address: L7uDNCg9r1Zp6JUTJGEtnb6pPogTDs7Bxm4NUK94PXnhS6se7Mungk1g8a5NA3qwNeJi8mtj7aHmTrUzEe4ErZp2PZLcKAHC2LEdQHMrS8Y68x9wLVqbD5PocKNfD5hGrm3NZAecWmK47QWXSFuJG6M2ciUiVggXiR3Gr1nidtjT5VogZcuUjWHBMyXCh3hCtyaQA3icbAQaJSM2g1GyiFcyZrCFpn1P5B2j2JDGMSSCzQfpgueoC5yN5GMh6Hgwz7YGr7uvAQkFsfLLMAg9HKY7XzCByAUQVdDGetYhNngazmP1YDdaZ5U5VUg1TRtCYBsyZk1JYPJGBRTq5j2zcFLhcZkZBwBs92kp7BpGZcTe8vHvBnRv8h3jfUxGZ4K1h68SiH6Q6mbyvHyNz6vB3PgocArMKBbzAQzCpJio5BoS +Tokensale box P2S address: yvNXjWe8vBvZTXwyUHemPU59CRfm8AnvnzXowRQkQ9hoiGXsS7oEUGLPof6RoYAdKXEgTWR4qTK44w3GBuNdBjXeWcHFeBvyFxHWdEBsnqnYhtaFC2S71eHRPpLEndDghZebLdx25nufFh8YFJ9D8gTxTvxqahBgzpJT7pbUAUEkE2iRSZwpiXvj5SUmA6vmTrQzMTxicBG2mGV1NLq5rjBCAxjM5FpNSJ1KgqrxeTAfBvs1QnMUZ6CTBtGPLyNUpFoPTaYUPp9PfBni7FAQsumx36tukUHA2p3NdgfhTbYYSJTDQi7eWW2YikFvczXpTCfecbCL7HA1o6Cg6DvntmpJHWfjopWnsbpDdLg6SbP4Fup7wCnQaeL6NcSTVP1K5btmK5JryZc36V8KdHkheyaVzYTMA1Gufp6bAGtZP 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. @@ -143,9 +143,9 @@ example: address: XUFypmadXVvYmBWtiuwDioN1rtj6nSvqgzgWjx1yFmHAVndPaAEgnUvEvEDSkpgZPRmCYeqxewi8ZKZ4Pamp1M9DAdu8d4PgShGRDV9inwzN6TtDeefyQbFXRmKCSJSyzySrGAt16 *R4* - campaign ID (Int) -*R5* - backer script -*R6* - campaign script (funds collected will go to this) -*R7* - fundraising deadline (Int, # of block, inclusive) +*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) From f09e0b45f34b96db336968c90175cf3cd5e6884b Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Fri, 13 Aug 2021 16:15:29 +0300 Subject: [PATCH 11/12] more text --- eip-0018.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/eip-0018.md b/eip-0018.md index d5080b3a..8532d9d9 100644 --- a/eip-0018.md +++ b/eip-0018.md @@ -84,11 +84,14 @@ campaign box data specified below in outputs. ## Campaign Box Data -For campaign, it is enough just to create box with ErgoFund token, campaign ID, campaign desc, script, recommended deadline, min to raise. +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) @@ -107,7 +110,7 @@ example: "value": 100000000, "assets": [ { - "tokenId": "08fc8bd24f0eaa011db3342131cb06eb890066ac6d7e6f7fd61fcdd138bd1e2c", + "tokenId": "05b66b97e5802f6447b67fe30cb4055e14d6b17bb14f5f563d65c9622c43a659", "amount": 1 } ], @@ -124,6 +127,9 @@ example: ## 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 From c5c975401e7457b0e9a77ab69b96d70b89e7cc46 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Mon, 16 Aug 2021 13:21:10 +0300 Subject: [PATCH 12/12] text improvements --- eip-0018.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/eip-0018.md b/eip-0018.md index 8532d9d9..86dd5027 100644 --- a/eip-0018.md +++ b/eip-0018.md @@ -16,7 +16,7 @@ Collecting funds in different contexts is very much needed for building common i 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 @@ -26,10 +26,10 @@ 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 byte array). +(as SigmaProp constant). To register a new crowdfunding campaign, crowdfunding token must be bought (to compensate expenses for scanning, -storing crowdfunding data, doing an UI) +storing crowdfunding data, producing and maintaining UI). Sell contract: @@ -72,6 +72,9 @@ Sell contract: } ``` +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 @@ -79,9 +82,6 @@ Campaign identification token: 05b66b97e5802f6447b67fe30cb4055e14d6b17bb14f5f563 Tokensale box P2S address: yvNXjWe8vBvZTXwyUHemPU59CRfm8AnvnzXowRQkQ9hoiGXsS7oEUGLPof6RoYAdKXEgTWR4qTK44w3GBuNdBjXeWcHFeBvyFxHWdEBsnqnYhtaFC2S71eHRPpLEndDghZebLdx25nufFh8YFJ9D8gTxTvxqahBgzpJT7pbUAUEkE2iRSZwpiXvj5SUmA6vmTrQzMTxicBG2mGV1NLq5rjBCAxjM5FpNSJ1KgqrxeTAfBvs1QnMUZ6CTBtGPLyNUpFoPTaYUPp9PfBni7FAQsumx36tukUHA2p3NdgfhTbYYSJTDQi7eWW2YikFvczXpTCfecbCL7HA1o6Cg6DvntmpJHWfjopWnsbpDdLg6SbP4Fup7wCnQaeL6NcSTVP1K5btmK5JryZc36V8KdHkheyaVzYTMA1Gufp6bAGtZP -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. - ## 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).