From 0626d306b3928648a9fa17dcce6fbed7a3cb0745 Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Thu, 30 Apr 2020 18:25:25 +0300 Subject: [PATCH 01/23] start drafting buy order contract; update compiler; --- build.sbt | 2 +- .../scala/org/ergoplatform/playground.scala | 3 + .../playgroundenv/dsl/BoxDsl.scala | 31 +++++-- .../playgroundenv/dsl/TypesDsl.scala | 4 + .../utils/ErgoScriptCompiler.scala | 27 ------ .../AssetsAtomicExchangePlayground.scala | 14 +-- .../playgrounds/examples/DEXPlayground.scala | 92 ++++++++++++------- 7 files changed, 95 insertions(+), 78 deletions(-) delete mode 100644 playground-env/src/main/scala/org/ergoplatform/playgroundenv/utils/ErgoScriptCompiler.scala diff --git a/build.sbt b/build.sbt index f2748ca..877e5e2 100644 --- a/build.sbt +++ b/build.sbt @@ -27,7 +27,7 @@ dynverSeparator in ThisBuild := "-" lazy val allConfigDependency = "compile->compile;test->test" lazy val dependencies = Seq( - "org.ergoplatform" %% "ergo-scala-compiler" % "0.0.0-32-aaadbee1-SNAPSHOT", + "org.ergoplatform" %% "ergo-scala-compiler" % "0.0.0-41-13995374-SNAPSHOT", "org.ergoplatform" %% "ergo-appkit" % "develop-d77acfb8-SNAPSHOT" ) diff --git a/playground-env/src/main/scala/org/ergoplatform/playground.scala b/playground-env/src/main/scala/org/ergoplatform/playground.scala index d47dee0..0ef8751 100644 --- a/playground-env/src/main/scala/org/ergoplatform/playground.scala +++ b/playground-env/src/main/scala/org/ergoplatform/playground.scala @@ -7,9 +7,12 @@ import org.ergoplatform.playgroundenv.dsl.{ TypesDsl } import sigmastate.Values.{SigmaPropConstant, SigmaPropValue} +import sigmastate.eval.CompiletimeIRContext object playground extends GeneratorsDsl with TypesDsl with BoxDsl with TransactionDsl { + implicit override protected def IR = new CompiletimeIRContext() + val MinTxFee: Long = 1000 * 1000 val MinErg: Long = 1000 * 1000 diff --git a/playground-env/src/main/scala/org/ergoplatform/playgroundenv/dsl/BoxDsl.scala b/playground-env/src/main/scala/org/ergoplatform/playgroundenv/dsl/BoxDsl.scala index 6693867..86f65ec 100644 --- a/playground-env/src/main/scala/org/ergoplatform/playgroundenv/dsl/BoxDsl.scala +++ b/playground-env/src/main/scala/org/ergoplatform/playgroundenv/dsl/BoxDsl.scala @@ -5,6 +5,7 @@ import org.ergoplatform.playgroundenv.models.TokenAmount import org.ergoplatform.{ErgoBox, ErgoBoxCandidate} import scorex.crypto.hash.Digest32 import sigmastate.SType +import sigmastate.SType.AnyOps import sigmastate.Values.{ByteArrayConstant, EvaluatedValue} import sigmastate.eval.Extensions._ import sigmastate.eval._ @@ -35,14 +36,26 @@ trait BoxDsl extends TypesDsl { ) } - private def liftVal[T](v: T): EvaluatedValue[SType] = v match { - case ba: Array[Byte] => ByteArrayConstant(ba) + private def liftVal(v: Any): EvaluatedValue[SType] = { + val (tV, newV) = v match { + case a: Array[Byte] => (Evaluation.rtypeOf(a.toColl).get, a.toColl) + case _ => (Evaluation.rtypeOf(v).get, v) + } + val elemTpe = Evaluation.rtypeToSType(tV) + IR.builder.mkConstant[SType](newV.asWrappedType, elemTpe) } - def Box[T]( + private def liftRegVals( + regs: Seq[(NonMandatoryRegisterId, Any)] + ): Map[NonMandatoryRegisterId, EvaluatedValue[SType]] = + regs.map { t => + (t._1, liftVal(t._2)) + }.toMap + + def Box( value: Long, - register: (NonMandatoryRegisterId, T), - script: ErgoContract + script: ErgoContract, + registers: (NonMandatoryRegisterId, Any)* ): ErgoBoxCandidate = { require(value > 0, s"box value shoulde be > 0, got $value") new ErgoBoxCandidate( @@ -50,15 +63,15 @@ trait BoxDsl extends TypesDsl { script.ergoTree, 0, Array[(TokenId, Long)]().toColl, - Map((register._1, liftVal(register._2))) + liftRegVals(registers) ) } def Box( value: Long, token: (TokenInfo, Long), - register: (NonMandatoryRegisterId, Any), - script: ErgoContract + script: ErgoContract, + registers: (NonMandatoryRegisterId, Any)* ): ErgoBoxCandidate = { require(value > 0, s"box value shoulde be > 0, got $value") new ErgoBoxCandidate( @@ -66,7 +79,7 @@ trait BoxDsl extends TypesDsl { script.ergoTree, 0, Array[(TokenId, Long)]((Digest32 @@ token._1.tokenId.toArray, token._2)).toColl, - Map((register._1, liftVal(register._2))) + liftRegVals(registers) ) } } diff --git a/playground-env/src/main/scala/org/ergoplatform/playgroundenv/dsl/TypesDsl.scala b/playground-env/src/main/scala/org/ergoplatform/playgroundenv/dsl/TypesDsl.scala index 3d05aab..b7182d4 100644 --- a/playground-env/src/main/scala/org/ergoplatform/playgroundenv/dsl/TypesDsl.scala +++ b/playground-env/src/main/scala/org/ergoplatform/playgroundenv/dsl/TypesDsl.scala @@ -1,7 +1,11 @@ package org.ergoplatform.playgroundenv.dsl +import sigmastate.eval.CompiletimeIRContext + trait TypesDsl { + implicit protected def IR: CompiletimeIRContext; + type Coll[A] = special.collection.Coll[A] type SigmaProp = special.sigma.SigmaProp type ErgoContract = org.ergoplatform.compiler.ErgoContract diff --git a/playground-env/src/main/scala/org/ergoplatform/playgroundenv/utils/ErgoScriptCompiler.scala b/playground-env/src/main/scala/org/ergoplatform/playgroundenv/utils/ErgoScriptCompiler.scala deleted file mode 100644 index b539cca..0000000 --- a/playground-env/src/main/scala/org/ergoplatform/playgroundenv/utils/ErgoScriptCompiler.scala +++ /dev/null @@ -1,27 +0,0 @@ -package org.ergoplatform.playgroundenv.utils - -import org.ergoplatform.compiler.ErgoContract -import sigmastate.interpreter.Interpreter.ScriptEnv -import sigmastate.lang.{SigmaCompiler, TransformingSigmaBuilder} -import org.ergoplatform.ErgoAddressEncoder.TestnetNetworkPrefix -import sigmastate.eval.CompiletimeIRContext -import sigmastate.eval.Evaluation -import sigmastate.SType -import sigmastate.SType.AnyOps - -object ErgoScriptCompiler { - - val compiler = SigmaCompiler(TestnetNetworkPrefix, TransformingSigmaBuilder) - - implicit var IR: CompiletimeIRContext = new CompiletimeIRContext() - - def compile(env: ScriptEnv, ergoScript: String): ErgoContract = { - val liftedEnv = env.mapValues { v => - val tV = Evaluation.rtypeOf(v).get - val elemTpe = Evaluation.rtypeToSType(tV) - IR.builder.mkConstant[SType](v.asWrappedType, elemTpe) - } - val prop = compiler.compile(liftedEnv, ergoScript) - ErgoContract(_ => ???, prop) - } -} diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/AssetsAtomicExchangePlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/AssetsAtomicExchangePlayground.scala index 317fa1d..197a345 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/AssetsAtomicExchangePlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/AssetsAtomicExchangePlayground.scala @@ -138,16 +138,16 @@ object AssetsAtomicExchangePlayground { val sellerOutBox = Box( - value = sellerAskNanoErgs, - register = (R4 -> sellOrderTransactionSigned.outputs(0).id), - script = contract(sellerParty.wallet.getAddress.pubKey) + value = sellerAskNanoErgs, + registers = (R4 -> sellOrderTransactionSigned.outputs(0).id), + script = contract(sellerParty.wallet.getAddress.pubKey) ) val buyerOutBox = Box( - value = buyerSwapBoxValue, - token = (token -> buyerBidTokenAmount), - register = (R4 -> buyOrderTransactionSigned.outputs(0).id), - script = contract(buyerParty.wallet.getAddress.pubKey) + value = buyerSwapBoxValue, + token = (token -> buyerBidTokenAmount), + registers = (R4 -> buyOrderTransactionSigned.outputs(0).id), + script = contract(buyerParty.wallet.getAddress.pubKey) ) val dexParty = blockchainSim.newParty("DEX") diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index 71b07bb..eb75aaf 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -2,7 +2,6 @@ package org.ergoplatform.playgrounds.examples object DEXPlayground { import org.ergoplatform.compiler.ErgoScalaCompiler._ - import org.ergoplatform.playgroundenv.utils.ErgoScriptCompiler import org.ergoplatform.playground._ def buyerContract( @@ -22,6 +21,26 @@ object DEXPlayground { val tokenPrice = $tokenPrice val dexFeePerToken = $dexFeePerToken + // TODO: use flatmap? + val spendingSellOrders = INPUTS.filter { (b: Box) => + b.R4[Coll[Byte]].isDefined && b.R5[Long].isDefined && { + val sellOrderTokenId = b.R4[Coll[Byte]].get + sellOrderTokenId == tokenId && { + b.tokens.size == 1 && b.tokens(0)._1 == tokenId + } + } + } + + val spendingSellOrderTokenInfo = spendingSellOrders.map { (b: Box) => + (b.tokens(0)._2, b.R5[Long].get) + } + + // TODO: only part of it is matched + // TODO: sort by price + val totalSpendingSellOrdersValue = spendingSellOrderTokenInfo.fold(0L, { (acc: Long, t: (Long, Long)) => + acc + (t._1 * t._2) + }) + val returnBox = OUTPUTS.filter { (b: Box) => b.R4[Coll[Byte]].isDefined && b.R4[Coll[Byte]].get == SELF.id && b.propositionBytes == buyerPk.propBytes }(0) @@ -30,7 +49,7 @@ object DEXPlayground { val returnTokenId = returnTokenData._1 val returnTokenAmount = returnTokenData._2 val maxReturnTokenErgValue = returnTokenAmount * tokenPrice - val totalReturnErgValue = maxReturnTokenErgValue + returnBox.value + val totalReturnErgValueEq = maxReturnTokenErgValue + returnBox.value val expectedDexFee = dexFeePerToken * returnTokenAmount val foundNewOrderBoxes = OUTPUTS.filter { (b: Box) => @@ -38,20 +57,23 @@ object DEXPlayground { } val coinsSecured = (SELF.value - expectedDexFee) == maxReturnTokenErgValue || { - foundNewOrderBoxes.size == 1 && foundNewOrderBoxes(0).value >= (SELF.value - totalReturnErgValue - expectedDexFee) + foundNewOrderBoxes.size == 1 && foundNewOrderBoxes(0).value >= (SELF.value - totalReturnErgValueEq - expectedDexFee) } + val bestPriceDelta = maxReturnTokenErgValue - totalSpendingSellOrdersValue + val tokenIdIsCorrect = returnTokenId == tokenId allOf(Coll( tokenIdIsCorrect, returnTokenAmount >= 1, - coinsSecured + coinsSecured, + returnBox.value >= bestPriceDelta )) } """.stripMargin - ErgoScriptCompiler.compile(buyerContractEnv, buyerScript) + contract(buyerContractEnv, buyerScript) } def sellerOrderContract( @@ -98,7 +120,7 @@ object DEXPlayground { }""".stripMargin - ErgoScriptCompiler.compile(sellerContractEnv, sellerScript) + contract(sellerContractEnv, sellerScript) } def swapScenario = { @@ -169,9 +191,11 @@ object DEXPlayground { ) val sellOrderBox = Box( - value = sellerDexFee, - token = (token -> sellerAskTokenAmount), - script = sellOrderContract + value = sellerDexFee, + token = (token -> sellerAskTokenAmount), + script = sellOrderContract, + registers = R4 -> token.tokenId, + R5 -> sellerAskTokenPrice ) val sellerBalanceBoxes = sellerParty.selectUnspentBoxes( @@ -193,28 +217,28 @@ object DEXPlayground { val sellerTokenAmountSold = sellerAskTokenAmount / 2 val sellerDexFeeForPartialMatch = sellerDexFeePerToken * sellerTokenAmountSold val sellerOutBoxPartialMatch = Box( - value = sellerTokenAmountSold * sellerAskTokenPrice, - register = (R4 -> sellOrderTxSigned.outputs(0).id), - script = contract(sellerParty.wallet.getAddress.pubKey) + value = sellerTokenAmountSold * sellerAskTokenPrice, + registers = (R4 -> sellOrderTxSigned.outputs(0).id), + script = contract(sellerParty.wallet.getAddress.pubKey) ) // reuse old contract, nothing is changed val newSellOrderContract = sellOrderContract val newSellOrderBox = Box( - value = sellOrderBox.value - sellerDexFeeForPartialMatch, - token = (token -> (sellerAskTokenAmount - sellerTokenAmountSold)), - register = (R4 -> sellOrderTxSigned.outputs(0).id), - script = newSellOrderContract + value = sellOrderBox.value - sellerDexFeeForPartialMatch, + token = (token -> (sellerAskTokenAmount - sellerTokenAmountSold)), + registers = (R4 -> sellOrderTxSigned.outputs(0).id), + script = newSellOrderContract ) val buyerTokenAmountBought = buyerBidTokenAmount / 2 val buyerDexFeeForPartialMatch = buyerDexFeePerToken * buyerTokenAmountBought val buyerOutBoxPartialMatch = Box( - value = buyerSwapBoxValue, - token = (token -> buyerTokenAmountBought), - register = (R4 -> buyOrderTxSigned.outputs(0).id), - script = contract(buyerParty.wallet.getAddress.pubKey) + value = buyerSwapBoxValue, + token = (token -> buyerTokenAmountBought), + registers = (R4 -> buyOrderTxSigned.outputs(0).id), + script = contract(buyerParty.wallet.getAddress.pubKey) ) // reuse old contract, nothing is changed @@ -222,9 +246,9 @@ object DEXPlayground { val newBuyOrderBoxValue = buyOrderBox.value - buyerTokenAmountBought * buyersBidTokenPrice - buyerDexFeeForPartialMatch val newBuyOrderBox = Box( - value = newBuyOrderBoxValue, - register = (R4 -> buyOrderTxSigned.outputs(0).id), - script = newBuyOrderContract + value = newBuyOrderBoxValue, + registers = (R4 -> buyOrderTxSigned.outputs(0).id), + script = newBuyOrderContract ) val dexParty = blockchainSim.newParty("DEX") @@ -268,18 +292,18 @@ object DEXPlayground { sellOrderAfterPartialMatching.additionalTokens(0)._2 val sellerDexFeeForTotalMatching = sellerDexFeePerToken * sellerTokenAmountSoldInTotalMatching val sellerOutBoxForTotalMatching = Box( - value = sellerTokenAmountSoldInTotalMatching * sellerAskTokenPrice, - register = (R4 -> sellOrderAfterPartialMatching.id), - script = contract(sellerParty.wallet.getAddress.pubKey) + value = sellerTokenAmountSoldInTotalMatching * sellerAskTokenPrice, + registers = (R4 -> sellOrderAfterPartialMatching.id), + script = contract(sellerParty.wallet.getAddress.pubKey) ) val buyerTokenAmountBoughtInTotalMatching = sellerTokenAmountSoldInTotalMatching val buyerDexFeeForTotalMatching = buyerDexFeePerToken * buyerTokenAmountBoughtInTotalMatching val buyerOutBoxForTotalMatching = Box( - value = buyerSwapBoxValue, - token = (token -> buyerTokenAmountBoughtInTotalMatching), - register = (R4 -> buyOrderAfterPartialMatching.id), - script = contract(buyerParty.wallet.getAddress.pubKey) + value = buyerSwapBoxValue, + token = (token -> buyerTokenAmountBoughtInTotalMatching), + registers = (R4 -> buyOrderAfterPartialMatching.id), + script = contract(buyerParty.wallet.getAddress.pubKey) ) val swapTxFeeForTotalMatching = MinTxFee @@ -364,8 +388,8 @@ object DEXPlayground { // as a workaround for https://github.com/ScorexFoundation/sigmastate-interpreter/issues/628 token = (blockchainSim.newToken("DEXCNCL") -> 1L), // as a workaround for https://github.com/ScorexFoundation/sigmastate-interpreter/issues/628 - register = (R4 -> buyOrderTxSigned.outputs(0).id), - script = contract(buyerParty.wallet.getAddress.pubKey) + registers = (R4 -> buyOrderTxSigned.outputs(0).id), + script = contract(buyerParty.wallet.getAddress.pubKey) ) val cancelBuyTransaction = Transaction( @@ -436,8 +460,8 @@ object DEXPlayground { value = sellOrderBox.value - cancelTxFee, token = (token -> sellerAskTokenAmount), // as a workaround for https://github.com/ScorexFoundation/sigmastate-interpreter/issues/628 - register = (R4 -> sellOrderTxSigned.outputs(0).id), - script = contract(sellerParty.wallet.getAddress.pubKey) + registers = (R4 -> sellOrderTxSigned.outputs(0).id), + script = contract(sellerParty.wallet.getAddress.pubKey) ) val cancelSellTransaction = Transaction( From 0aa01a1fe6fb392178a3094fe233ae5abfdfb2f2 Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Sun, 3 May 2020 13:30:49 +0300 Subject: [PATCH 02/23] move replication check (SELF.id) to R6; --- .../ergoplatform/playgrounds/examples/DEXPlayground.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index eb75aaf..395131c 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -99,7 +99,7 @@ object DEXPlayground { }(0) val foundNewOrderBoxes = OUTPUTS.filter { (b: Box) => - b.R4[Coll[Byte]].isDefined && b.R4[Coll[Byte]].get == SELF.id && b.propositionBytes == SELF.propositionBytes + b.R6[Coll[Byte]].isDefined && b.R6[Coll[Byte]].get == SELF.id && b.propositionBytes == SELF.propositionBytes } (returnBox.value == selfTokenAmount * tokenPrice) || { @@ -228,8 +228,10 @@ object DEXPlayground { val newSellOrderBox = Box( value = sellOrderBox.value - sellerDexFeeForPartialMatch, token = (token -> (sellerAskTokenAmount - sellerTokenAmountSold)), - registers = (R4 -> sellOrderTxSigned.outputs(0).id), - script = newSellOrderContract + script = newSellOrderContract, + registers = R4 -> token.tokenId, + R5 -> sellerAskTokenPrice, + R6 -> sellOrderTxSigned.outputs(0).id ) val buyerTokenAmountBought = buyerBidTokenAmount / 2 From b55547076a95f410e61011513c94bf639f46d787 Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Sun, 3 May 2020 13:46:11 +0300 Subject: [PATCH 03/23] finish buyer contract with single spending sell order; --- .../playgrounds/examples/DEXPlayground.scala | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index 395131c..41207d8 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -21,7 +21,6 @@ object DEXPlayground { val tokenPrice = $tokenPrice val dexFeePerToken = $dexFeePerToken - // TODO: use flatmap? val spendingSellOrders = INPUTS.filter { (b: Box) => b.R4[Coll[Byte]].isDefined && b.R5[Long].isDefined && { val sellOrderTokenId = b.R4[Coll[Byte]].get @@ -31,15 +30,14 @@ object DEXPlayground { } } - val spendingSellOrderTokenInfo = spendingSellOrders.map { (b: Box) => - (b.tokens(0)._2, b.R5[Long].get) - } - // TODO: only part of it is matched - // TODO: sort by price - val totalSpendingSellOrdersValue = spendingSellOrderTokenInfo.fold(0L, { (acc: Long, t: (Long, Long)) => - acc + (t._1 * t._2) - }) + // TODO: for multiplse sell orders sort by price + // val spendingSellOrderTokenInfo = spendingSellOrders.map { (b: Box) => + // (b.tokens(0)._2, b.R5[Long].get) + // } + // val totalSpendingSellOrdersValue = spendingSellOrderTokenInfo.fold(0L, { (acc: Long, t: (Long, Long)) => + // acc + (t._1 * t._2) + // }) val returnBox = OUTPUTS.filter { (b: Box) => b.R4[Coll[Byte]].isDefined && b.R4[Coll[Byte]].get == SELF.id && b.propositionBytes == buyerPk.propBytes @@ -60,7 +58,12 @@ object DEXPlayground { foundNewOrderBoxes.size == 1 && foundNewOrderBoxes(0).value >= (SELF.value - totalReturnErgValueEq - expectedDexFee) } - val bestPriceDelta = maxReturnTokenErgValue - totalSpendingSellOrdersValue + // TODO: test it cannot be stolen on partial and total matching + // fixed spending sell orders to 1 + val bestPriceDeltaReturned = spendingSellOrders.size == 1 && { + val spendingSellOrdersValue = spendingSellOrders(0).R5[Long].get * returnTokenAmount + returnBox.value >= maxReturnTokenErgValue - spendingSellOrdersValue + } val tokenIdIsCorrect = returnTokenId == tokenId @@ -68,7 +71,7 @@ object DEXPlayground { tokenIdIsCorrect, returnTokenAmount >= 1, coinsSecured, - returnBox.value >= bestPriceDelta + bestPriceDeltaReturned )) } """.stripMargin @@ -146,7 +149,7 @@ object DEXPlayground { ) val sellerParty = blockchainSim.newParty("seller") - val sellerAskTokenPrice = 5000000L + val sellerAskTokenPrice = 4000000L val sellerAskTokenAmount = 100L val sellerAskNanoErgs = sellerAskTokenPrice * sellerAskTokenAmount val sellerDexFee = 10000000L @@ -237,7 +240,7 @@ object DEXPlayground { val buyerTokenAmountBought = buyerBidTokenAmount / 2 val buyerDexFeeForPartialMatch = buyerDexFeePerToken * buyerTokenAmountBought val buyerOutBoxPartialMatch = Box( - value = buyerSwapBoxValue, + value = buyerSwapBoxValue + (buyersBidTokenPrice - sellerAskTokenPrice) * buyerTokenAmountBought, token = (token -> buyerTokenAmountBought), registers = (R4 -> buyOrderTxSigned.outputs(0).id), script = contract(buyerParty.wallet.getAddress.pubKey) @@ -302,7 +305,7 @@ object DEXPlayground { val buyerTokenAmountBoughtInTotalMatching = sellerTokenAmountSoldInTotalMatching val buyerDexFeeForTotalMatching = buyerDexFeePerToken * buyerTokenAmountBoughtInTotalMatching val buyerOutBoxForTotalMatching = Box( - value = buyerSwapBoxValue, + value = buyerSwapBoxValue + (buyersBidTokenPrice - sellerAskTokenPrice) * buyerTokenAmountBoughtInTotalMatching, token = (token -> buyerTokenAmountBoughtInTotalMatching), registers = (R4 -> buyOrderAfterPartialMatching.id), script = contract(buyerParty.wallet.getAddress.pubKey) From 2343110c198f0e8580d0876e4189394e955b78ea Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Tue, 5 May 2020 17:03:31 +0300 Subject: [PATCH 04/23] draft buy order; --- .../playgrounds/examples/DEXPlayground.scala | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index 41207d8..3aa07cc 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -46,32 +46,37 @@ object DEXPlayground { val returnTokenData = returnBox.tokens(0) val returnTokenId = returnTokenData._1 val returnTokenAmount = returnTokenData._2 - val maxReturnTokenErgValue = returnTokenAmount * tokenPrice - val totalReturnErgValueEq = maxReturnTokenErgValue + returnBox.value val expectedDexFee = dexFeePerToken * returnTokenAmount val foundNewOrderBoxes = OUTPUTS.filter { (b: Box) => - b.R4[Coll[Byte]].isDefined && b.R4[Coll[Byte]].get == SELF.id && b.propositionBytes == SELF.propositionBytes + b.R6[Coll[Byte]].isDefined && b.R6[Coll[Byte]].get == SELF.id && b.propositionBytes == SELF.propositionBytes } - val coinsSecured = (SELF.value - expectedDexFee) == maxReturnTokenErgValue || { - foundNewOrderBoxes.size == 1 && foundNewOrderBoxes(0).value >= (SELF.value - totalReturnErgValueEq - expectedDexFee) + val sellOrder = spendingSellOrders(0) + val sellOrderTokenPrice = sellOrder.R5[Long].get + // TODO: if both orders were in the same block who gets the spread? + val spreadPerToken = if (sellOrder.creationInfo._1 >=SELF.creationInfo._1) + tokenPrice - sellOrderTokenPrice + else + 0L + + val totalMatching = (SELF.value - expectedDexFee) == returnTokenAmount * tokenPrice && + returnBox.value >=returnTokenAmount * spreadPerToken + val partialMatching = { + foundNewOrderBoxes.size == 1 && + foundNewOrderBoxes(0).value == (SELF.value - returnTokenAmount * tokenPrice - expectedDexFee) && + returnBox.value >=returnTokenAmount * spreadPerToken } - // TODO: test it cannot be stolen on partial and total matching - // fixed spending sell orders to 1 - val bestPriceDeltaReturned = spendingSellOrders.size == 1 && { - val spendingSellOrdersValue = spendingSellOrders(0).R5[Long].get * returnTokenAmount - returnBox.value >= maxReturnTokenErgValue - spendingSellOrdersValue - } + val coinsSecured = partialMatching ||totalMatching val tokenIdIsCorrect = returnTokenId == tokenId allOf(Coll( tokenIdIsCorrect, returnTokenAmount >= 1, - coinsSecured, - bestPriceDeltaReturned + sellOrderTokenPrice <=tokenPrice, + coinsSecured )) } """.stripMargin @@ -101,9 +106,19 @@ object DEXPlayground { b.R4[Coll[Byte]].isDefined && b.R4[Coll[Byte]].get == SELF.id && b.propositionBytes == sellerPk.propBytes }(0) + val spendingBuyOrders = INPUTS.filter { (b: Box) => + b.R4[Coll[Byte]].isDefined && b.R5[Long].isDefined && { + val buyOrderTokenId = b.R4[Coll[Byte]].get + buyOrderTokenId == tokenId && { + b.tokens.size == 1 && b.tokens(0)._1 == tokenId + } + } + } + val foundNewOrderBoxes = OUTPUTS.filter { (b: Box) => b.R6[Coll[Byte]].isDefined && b.R6[Coll[Byte]].get == SELF.id && b.propositionBytes == SELF.propositionBytes } + // TODO: require spread only if we were earlier (returnBox.value == selfTokenAmount * tokenPrice) || { foundNewOrderBoxes.size == 1 && { From 0fc52d139093fb29f5a24ef804e769aff7472220 Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Tue, 5 May 2020 17:53:06 +0300 Subject: [PATCH 05/23] draft sell order contract; --- .../playgrounds/examples/DEXPlayground.scala | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index 3aa07cc..3c91789 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -49,6 +49,7 @@ object DEXPlayground { val expectedDexFee = dexFeePerToken * returnTokenAmount val foundNewOrderBoxes = OUTPUTS.filter { (b: Box) => + val contractParametersAreCorrect = b.R4[Coll[Byte]].get == tokenId && b.R5[Long].get == tokenPrice b.R6[Coll[Byte]].isDefined && b.R6[Coll[Byte]].get == SELF.id && b.propositionBytes == SELF.propositionBytes } @@ -115,12 +116,23 @@ object DEXPlayground { } } + val buyOrder = spendingBuyOrders(0) + val foundNewOrderBoxes = OUTPUTS.filter { (b: Box) => - b.R6[Coll[Byte]].isDefined && b.R6[Coll[Byte]].get == SELF.id && b.propositionBytes == SELF.propositionBytes + val contractParametersAreCorrect = b.R4[Coll[Byte]].get == tokenId && b.R5[Long].get == tokenPrice + val contractIsTheSame = b.propositionBytes == SELF.propositionBytes + b.R6[Coll[Byte]].isDefined && b.R6[Coll[Byte]].get == SELF.id && contractIsTheSame } - // TODO: require spread only if we were earlier - (returnBox.value == selfTokenAmount * tokenPrice) || { + val buyOrderTokenPrice = buyOrder.R5[Long].get + val spreadPerToken = if (buyOrder.creationInfo._1 > SELF.creationInfo._1) + buyOrderTokenPrice - tokenPrice + else + 0L + + val totalMatching = (returnBox.value == selfTokenAmount * (tokenPrice + spreadPerToken)) + + val partialMatching = { foundNewOrderBoxes.size == 1 && { val newOrderBox = foundNewOrderBoxes(0) val newOrderTokenData = newOrderBox.tokens(0) @@ -132,10 +144,14 @@ object DEXPlayground { val newOrderTokenId = newOrderTokenData._1 val tokenIdIsCorrect = newOrderTokenId == tokenId - tokenIdIsCorrect && soldTokenAmount >= 1 && newOrderBox.value >= (SELF.value - minSoldTokenErgValue - expectedDexFee) + val newOrderValueIsCorrect = newOrderBox.value == (SELF.value - expectedDexFee) + val returnBoxValueIsCorrect = returnBox.value == soldTokenAmount * (tokenPrice + spreadPerToken) + tokenIdIsCorrect && soldTokenAmount >= 1 && newOrderValueIsCorrect && returnBoxValueIsCorrect } } + (totalMatching ||partialMatching) && buyOrderTokenPrice >=tokenPrice + }""".stripMargin contract(sellerContractEnv, sellerScript) From aebab81a0cb3975583973acdb2f31fc77e600ea0 Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Tue, 5 May 2020 18:15:34 +0300 Subject: [PATCH 06/23] add contract parameters in registers for order boxes; --- .../playgrounds/examples/DEXPlayground.scala | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index 3c91789..8db532b 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -204,7 +204,12 @@ object DEXPlayground { ) val buyOrderBoxValue = buyersBidTokenPrice * buyerBidTokenAmount + buyerDexFee - val buyOrderBox = Box(value = buyOrderBoxValue, script = buyOrderContract) + val buyOrderBox = Box( + value = buyOrderBoxValue, + script = buyOrderContract, + registers = R4 -> token.tokenId, + R5 -> buyersBidTokenPrice + ) val buyOrderTransaction = Transaction( inputs = buyerParty.selectUnspentBoxes(toSpend = buyOrderBoxValue + buyOrderTxFee), @@ -283,8 +288,10 @@ object DEXPlayground { val newBuyOrderBoxValue = buyOrderBox.value - buyerTokenAmountBought * buyersBidTokenPrice - buyerDexFeeForPartialMatch val newBuyOrderBox = Box( value = newBuyOrderBoxValue, - registers = (R4 -> buyOrderTxSigned.outputs(0).id), - script = newBuyOrderContract + script = newBuyOrderContract, + registers = R4 -> token.tokenId, + R5 -> sellerAskTokenPrice, + R6 -> buyOrderTxSigned.outputs(0).id ) val dexParty = blockchainSim.newParty("DEX") From de3d69de70a7fd45dbd70019fa99e5fb899c31a0 Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Wed, 6 May 2020 16:51:30 +0300 Subject: [PATCH 07/23] guarded counter order presence in buyer contract; added regs to boxes in both cancel scenarios; --- .../playgrounds/examples/DEXPlayground.scala | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index 8db532b..a887c7a 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -53,13 +53,18 @@ object DEXPlayground { b.R6[Coll[Byte]].isDefined && b.R6[Coll[Byte]].get == SELF.id && b.propositionBytes == SELF.propositionBytes } - val sellOrder = spendingSellOrders(0) - val sellOrderTokenPrice = sellOrder.R5[Long].get - // TODO: if both orders were in the same block who gets the spread? - val spreadPerToken = if (sellOrder.creationInfo._1 >=SELF.creationInfo._1) - tokenPrice - sellOrderTokenPrice - else - 0L + val spreadPerToken = { + if (spendingSellOrders.size == 1) { + val sellOrder = spendingSellOrders(0) + val sellOrderTokenPrice = sellOrder.R5[Long].get + // TODO: if both orders were in the same block who gets the spread? + if (sellOrder.creationInfo._1 >=SELF.creationInfo._1 && sellOrderTokenPrice <=tokenPrice) + tokenPrice - sellOrderTokenPrice + else + 0L + } else + 0L + } val totalMatching = (SELF.value - expectedDexFee) == returnTokenAmount * tokenPrice && returnBox.value >=returnTokenAmount * spreadPerToken @@ -76,7 +81,6 @@ object DEXPlayground { allOf(Coll( tokenIdIsCorrect, returnTokenAmount >= 1, - sellOrderTokenPrice <=tokenPrice, coinsSecured )) } @@ -409,7 +413,12 @@ object DEXPlayground { ) val buyOrderBoxValue = buyersBidTokenPrice * buyerBidTokenAmount + buyerDexFee - val buyOrderBox = Box(value = buyOrderBoxValue, script = buyOrderContract) + val buyOrderBox = Box( + value = buyOrderBoxValue, + script = buyOrderContract, + registers = R4 -> token.tokenId, + R5 -> buyersBidTokenPrice + ) val buyOrderTransaction = Transaction( inputs = buyerParty.selectUnspentBoxes(toSpend = buyOrderBoxValue + buyOrderTxFee), @@ -476,9 +485,11 @@ object DEXPlayground { ) val sellOrderBox = Box( - value = sellerDexFee, - token = (token -> sellerAskTokenAmount), - script = sellOrderContract + value = sellerDexFee, + token = (token -> sellerAskTokenAmount), + script = sellOrderContract, + registers = R4 -> token.tokenId, + R5 -> sellerAskTokenPrice ) val sellerBalanceBoxes = sellerParty.selectUnspentBoxes( From 9f9835cbd70ce1df91edf4b7b4fd49dd2fa14f05 Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Thu, 7 May 2020 09:16:54 +0300 Subject: [PATCH 08/23] comments; --- .../org/ergoplatform/playgrounds/examples/DEXPlayground.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index a887c7a..8b5e190 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -30,6 +30,8 @@ object DEXPlayground { } } + // TODO: check that counter orders are sorted by token price + // TODO: only part of it is matched // TODO: for multiplse sell orders sort by price // val spendingSellOrderTokenInfo = spendingSellOrders.map { (b: Box) => From 845bda8f1cfbfc25762d8fc899a59169358115bf Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Thu, 7 May 2020 09:39:35 +0300 Subject: [PATCH 09/23] get the token id from return box via getOrElse in buyer contract and avoid fake token minting on buy order cancel --- .../playgrounds/examples/DEXPlayground.scala | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index a887c7a..949584a 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -43,9 +43,8 @@ object DEXPlayground { b.R4[Coll[Byte]].isDefined && b.R4[Coll[Byte]].get == SELF.id && b.propositionBytes == buyerPk.propBytes }(0) - val returnTokenData = returnBox.tokens(0) - val returnTokenId = returnTokenData._1 - val returnTokenAmount = returnTokenData._2 + val returnTokenAmount = if (returnBox.tokens.size == 1) returnBox.tokens(0)._2 else 0L + val expectedDexFee = dexFeePerToken * returnTokenAmount val foundNewOrderBoxes = OUTPUTS.filter { (b: Box) => @@ -76,8 +75,8 @@ object DEXPlayground { val coinsSecured = partialMatching ||totalMatching - val tokenIdIsCorrect = returnTokenId == tokenId - + val tokenIdIsCorrect = returnBox.tokens.getOrElse(0, (Coll[Byte](), 0L))._1 == tokenId + allOf(Coll( tokenIdIsCorrect, returnTokenAmount >= 1, @@ -438,8 +437,6 @@ object DEXPlayground { val buyerReturnBox = Box( value = buyOrderBox.value - cancelTxFee, // as a workaround for https://github.com/ScorexFoundation/sigmastate-interpreter/issues/628 - token = (blockchainSim.newToken("DEXCNCL") -> 1L), - // as a workaround for https://github.com/ScorexFoundation/sigmastate-interpreter/issues/628 registers = (R4 -> buyOrderTxSigned.outputs(0).id), script = contract(buyerParty.wallet.getAddress.pubKey) ) From cefa7b497321afcdc7944bcff19dd07b627200a6 Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Thu, 7 May 2020 12:17:41 +0300 Subject: [PATCH 10/23] remove R4 in return box on buy order cancellation; --- .../playgrounds/examples/DEXPlayground.scala | 79 ++++++++++--------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index 949584a..c49a5ee 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -39,49 +39,52 @@ object DEXPlayground { // acc + (t._1 * t._2) // }) - val returnBox = OUTPUTS.filter { (b: Box) => + val returnBoxes = OUTPUTS.filter { (b: Box) => b.R4[Coll[Byte]].isDefined && b.R4[Coll[Byte]].get == SELF.id && b.propositionBytes == buyerPk.propBytes - }(0) - - val returnTokenAmount = if (returnBox.tokens.size == 1) returnBox.tokens(0)._2 else 0L - - val expectedDexFee = dexFeePerToken * returnTokenAmount - - val foundNewOrderBoxes = OUTPUTS.filter { (b: Box) => - val contractParametersAreCorrect = b.R4[Coll[Byte]].get == tokenId && b.R5[Long].get == tokenPrice - b.R6[Coll[Byte]].isDefined && b.R6[Coll[Byte]].get == SELF.id && b.propositionBytes == SELF.propositionBytes } - val spreadPerToken = { - if (spendingSellOrders.size == 1) { - val sellOrder = spendingSellOrders(0) - val sellOrderTokenPrice = sellOrder.R5[Long].get - // TODO: if both orders were in the same block who gets the spread? - if (sellOrder.creationInfo._1 >=SELF.creationInfo._1 && sellOrderTokenPrice <=tokenPrice) - tokenPrice - sellOrderTokenPrice - else + returnBoxes.size == 1 && { + val returnBox = returnBoxes(0) + val returnTokenAmount = if (returnBox.tokens.size == 1) returnBox.tokens(0)._2 else 0L + + val expectedDexFee = dexFeePerToken * returnTokenAmount + + val foundNewOrderBoxes = OUTPUTS.filter { (b: Box) => + val contractParametersAreCorrect = b.R4[Coll[Byte]].get == tokenId && b.R5[Long].get == tokenPrice + b.R6[Coll[Byte]].isDefined && b.R6[Coll[Byte]].get == SELF.id && b.propositionBytes == SELF.propositionBytes + } + + val spreadPerToken = { + if (spendingSellOrders.size == 1) { + val sellOrder = spendingSellOrders(0) + val sellOrderTokenPrice = sellOrder.R5[Long].get + // TODO: if both orders were in the same block who gets the spread? + if (sellOrder.creationInfo._1 >=SELF.creationInfo._1 && sellOrderTokenPrice <=tokenPrice) + tokenPrice - sellOrderTokenPrice + else + 0L + } else 0L - } else - 0L - } + } - val totalMatching = (SELF.value - expectedDexFee) == returnTokenAmount * tokenPrice && - returnBox.value >=returnTokenAmount * spreadPerToken - val partialMatching = { - foundNewOrderBoxes.size == 1 && - foundNewOrderBoxes(0).value == (SELF.value - returnTokenAmount * tokenPrice - expectedDexFee) && + val totalMatching = (SELF.value - expectedDexFee) == returnTokenAmount * tokenPrice && returnBox.value >=returnTokenAmount * spreadPerToken - } + val partialMatching = { + foundNewOrderBoxes.size == 1 && + foundNewOrderBoxes(0).value == (SELF.value - returnTokenAmount * tokenPrice - expectedDexFee) && + returnBox.value >=returnTokenAmount * spreadPerToken + } - val coinsSecured = partialMatching ||totalMatching + val coinsSecured = partialMatching ||totalMatching - val tokenIdIsCorrect = returnBox.tokens.getOrElse(0, (Coll[Byte](), 0L))._1 == tokenId - - allOf(Coll( - tokenIdIsCorrect, - returnTokenAmount >= 1, - coinsSecured - )) + val tokenIdIsCorrect = returnBox.tokens.getOrElse(0, (Coll[Byte](), 0L))._1 == tokenId + + allOf(Coll( + tokenIdIsCorrect, + returnTokenAmount >= 1, + coinsSecured + )) + } } """.stripMargin @@ -435,10 +438,8 @@ object DEXPlayground { val cancelTxFee = MinTxFee val buyerReturnBox = Box( - value = buyOrderBox.value - cancelTxFee, - // as a workaround for https://github.com/ScorexFoundation/sigmastate-interpreter/issues/628 - registers = (R4 -> buyOrderTxSigned.outputs(0).id), - script = contract(buyerParty.wallet.getAddress.pubKey) + value = buyOrderBox.value - cancelTxFee, + script = contract(buyerParty.wallet.getAddress.pubKey) ) val cancelBuyTransaction = Transaction( From d1d11fd0369c3c8e71a33334b3140972adf9f62c Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Thu, 7 May 2020 12:24:51 +0300 Subject: [PATCH 11/23] remove R4 in sell order cancellation box; --- .../playgrounds/examples/DEXPlayground.scala | 85 ++++++++++--------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index c49a5ee..7b98073 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -109,54 +109,57 @@ object DEXPlayground { val selfTokenAmount = SELF.tokens(0)._2 - val returnBox = OUTPUTS.filter { (b: Box) => + val returnBoxes = OUTPUTS.filter { (b: Box) => b.R4[Coll[Byte]].isDefined && b.R4[Coll[Byte]].get == SELF.id && b.propositionBytes == sellerPk.propBytes - }(0) - - val spendingBuyOrders = INPUTS.filter { (b: Box) => - b.R4[Coll[Byte]].isDefined && b.R5[Long].isDefined && { - val buyOrderTokenId = b.R4[Coll[Byte]].get - buyOrderTokenId == tokenId && { - b.tokens.size == 1 && b.tokens(0)._1 == tokenId + } + + returnBoxes.size == 1 && { + val returnBox = returnBoxes(0) + val spendingBuyOrders = INPUTS.filter { (b: Box) => + b.R4[Coll[Byte]].isDefined && b.R5[Long].isDefined && { + val buyOrderTokenId = b.R4[Coll[Byte]].get + buyOrderTokenId == tokenId && { + b.tokens.size == 1 && b.tokens(0)._1 == tokenId + } } } - } - val buyOrder = spendingBuyOrders(0) + val buyOrder = spendingBuyOrders(0) - val foundNewOrderBoxes = OUTPUTS.filter { (b: Box) => - val contractParametersAreCorrect = b.R4[Coll[Byte]].get == tokenId && b.R5[Long].get == tokenPrice - val contractIsTheSame = b.propositionBytes == SELF.propositionBytes - b.R6[Coll[Byte]].isDefined && b.R6[Coll[Byte]].get == SELF.id && contractIsTheSame - } + val foundNewOrderBoxes = OUTPUTS.filter { (b: Box) => + val contractParametersAreCorrect = b.R4[Coll[Byte]].get == tokenId && b.R5[Long].get == tokenPrice + val contractIsTheSame = b.propositionBytes == SELF.propositionBytes + b.R6[Coll[Byte]].isDefined && b.R6[Coll[Byte]].get == SELF.id && contractIsTheSame + } + + val buyOrderTokenPrice = buyOrder.R5[Long].get + val spreadPerToken = if (buyOrder.creationInfo._1 > SELF.creationInfo._1) + buyOrderTokenPrice - tokenPrice + else + 0L - val buyOrderTokenPrice = buyOrder.R5[Long].get - val spreadPerToken = if (buyOrder.creationInfo._1 > SELF.creationInfo._1) - buyOrderTokenPrice - tokenPrice - else - 0L - - val totalMatching = (returnBox.value == selfTokenAmount * (tokenPrice + spreadPerToken)) - - val partialMatching = { - foundNewOrderBoxes.size == 1 && { - val newOrderBox = foundNewOrderBoxes(0) - val newOrderTokenData = newOrderBox.tokens(0) - val newOrderTokenAmount = newOrderTokenData._2 - val soldTokenAmount = selfTokenAmount - newOrderTokenAmount - val minSoldTokenErgValue = soldTokenAmount * tokenPrice - val expectedDexFee = dexFeePerToken * soldTokenAmount - - val newOrderTokenId = newOrderTokenData._1 - val tokenIdIsCorrect = newOrderTokenId == tokenId - - val newOrderValueIsCorrect = newOrderBox.value == (SELF.value - expectedDexFee) - val returnBoxValueIsCorrect = returnBox.value == soldTokenAmount * (tokenPrice + spreadPerToken) - tokenIdIsCorrect && soldTokenAmount >= 1 && newOrderValueIsCorrect && returnBoxValueIsCorrect + val totalMatching = (returnBox.value == selfTokenAmount * (tokenPrice + spreadPerToken)) + + val partialMatching = { + foundNewOrderBoxes.size == 1 && { + val newOrderBox = foundNewOrderBoxes(0) + val newOrderTokenData = newOrderBox.tokens(0) + val newOrderTokenAmount = newOrderTokenData._2 + val soldTokenAmount = selfTokenAmount - newOrderTokenAmount + val minSoldTokenErgValue = soldTokenAmount * tokenPrice + val expectedDexFee = dexFeePerToken * soldTokenAmount + + val newOrderTokenId = newOrderTokenData._1 + val tokenIdIsCorrect = newOrderTokenId == tokenId + + val newOrderValueIsCorrect = newOrderBox.value == (SELF.value - expectedDexFee) + val returnBoxValueIsCorrect = returnBox.value == soldTokenAmount * (tokenPrice + spreadPerToken) + tokenIdIsCorrect && soldTokenAmount >= 1 && newOrderValueIsCorrect && returnBoxValueIsCorrect + } } - } - (totalMatching ||partialMatching) && buyOrderTokenPrice >=tokenPrice + (totalMatching ||partialMatching) && buyOrderTokenPrice >=tokenPrice + } }""".stripMargin @@ -511,8 +514,6 @@ object DEXPlayground { val sellerReturnBox = Box( value = sellOrderBox.value - cancelTxFee, token = (token -> sellerAskTokenAmount), - // as a workaround for https://github.com/ScorexFoundation/sigmastate-interpreter/issues/628 - registers = (R4 -> sellOrderTxSigned.outputs(0).id), script = contract(sellerParty.wallet.getAddress.pubKey) ) From ca88e94a191f38f268c97a01e5bf1051891ce0ee Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Thu, 7 May 2020 17:22:26 +0300 Subject: [PATCH 12/23] calc full spread instead of spread per token; formatting; --- .../playgrounds/examples/DEXPlayground.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index f0ed551..cd57194 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -56,13 +56,13 @@ object DEXPlayground { b.R6[Coll[Byte]].isDefined && b.R6[Coll[Byte]].get == SELF.id && b.propositionBytes == SELF.propositionBytes } - val spreadPerToken = { + val fullSpread = { if (spendingSellOrders.size == 1) { val sellOrder = spendingSellOrders(0) val sellOrderTokenPrice = sellOrder.R5[Long].get // TODO: if both orders were in the same block who gets the spread? - if (sellOrder.creationInfo._1 >=SELF.creationInfo._1 && sellOrderTokenPrice <=tokenPrice) - tokenPrice - sellOrderTokenPrice + if (sellOrder.creationInfo._1 >= SELF.creationInfo._1 && sellOrderTokenPrice <= tokenPrice) + (tokenPrice - sellOrderTokenPrice) * returnTokenAmount else 0L } else @@ -70,11 +70,11 @@ object DEXPlayground { } val totalMatching = (SELF.value - expectedDexFee) == returnTokenAmount * tokenPrice && - returnBox.value >=returnTokenAmount * spreadPerToken + returnBox.value >= fullSpread val partialMatching = { foundNewOrderBoxes.size == 1 && foundNewOrderBoxes(0).value == (SELF.value - returnTokenAmount * tokenPrice - expectedDexFee) && - returnBox.value >=returnTokenAmount * spreadPerToken + returnBox.value >= fullSpread } val coinsSecured = partialMatching ||totalMatching @@ -514,9 +514,9 @@ object DEXPlayground { val cancelTxFee = MinTxFee val sellerReturnBox = Box( - value = sellOrderBox.value - cancelTxFee, - token = (token -> sellerAskTokenAmount), - script = contract(sellerParty.wallet.getAddress.pubKey) + value = sellOrderBox.value - cancelTxFee, + token = (token -> sellerAskTokenAmount), + script = contract(sellerParty.wallet.getAddress.pubKey) ) val cancelSellTransaction = Transaction( From fd95cc3cdcb98aaea5e30447529c98ad83f1de36 Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Thu, 7 May 2020 18:06:12 +0300 Subject: [PATCH 13/23] draft buyer contract for multiple counter orders; --- .../playgrounds/examples/DEXPlayground.scala | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index cd57194..885554d 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -16,6 +16,9 @@ object DEXPlayground { val buyerContractEnv: ScriptEnv = Map("buyerPk" -> buyerPk, "tokenId" -> token.tokenId) + // TODO : check that counter orders are sorted by token price + // TODO: if both orders were in the same block who gets the spread? + // TODO: move price check (from fullSpread) to boxesAreSortedByTokenPrice? val buyerScript = s"""buyerPk || { val tokenPrice = $tokenPrice @@ -30,22 +33,13 @@ object DEXPlayground { } } - // TODO: check that counter orders are sorted by token price - - // TODO: only part of it is matched - // TODO: for multiplse sell orders sort by price - // val spendingSellOrderTokenInfo = spendingSellOrders.map { (b: Box) => - // (b.tokens(0)._2, b.R5[Long].get) - // } - // val totalSpendingSellOrdersValue = spendingSellOrderTokenInfo.fold(0L, { (acc: Long, t: (Long, Long)) => - // acc + (t._1 * t._2) - // }) - val returnBoxes = OUTPUTS.filter { (b: Box) => b.R4[Coll[Byte]].isDefined && b.R4[Coll[Byte]].get == SELF.id && b.propositionBytes == buyerPk.propBytes } - returnBoxes.size == 1 && { + val boxesAreSortedByTokenPrice = { (boxes: Coll[Box]) => true } + + returnBoxes.size == 1 && spendingSellOrders.size > 0 && boxesAreSortedByTokenPrice(spendingSellOrders) && { val returnBox = returnBoxes(0) val returnTokenAmount = if (returnBox.tokens.size == 1) returnBox.tokens(0)._2 else 0L @@ -57,16 +51,29 @@ object DEXPlayground { } val fullSpread = { - if (spendingSellOrders.size == 1) { - val sellOrder = spendingSellOrders(0) + spendingSellOrders.fold((returnTokenAmount, 0L), { (t: (Long, Long), sellOrder: Box) => + val returnTokensLeft = t._1 + val accumulatedFullSpread = t._2 val sellOrderTokenPrice = sellOrder.R5[Long].get - // TODO: if both orders were in the same block who gets the spread? - if (sellOrder.creationInfo._1 >= SELF.creationInfo._1 && sellOrderTokenPrice <= tokenPrice) - (tokenPrice - sellOrderTokenPrice) * returnTokenAmount - else - 0L - } else - 0L + val sellOrderTokenAmount = sellOrder.tokens(0)._2 + if (sellOrder.creationInfo._1 >= SELF.creationInfo._1 && sellOrderTokenPrice <= tokenPrice) { + // spread is ours + val spreadPerToken = tokenPrice - sellOrderTokenPrice + // TODO: rewrite with min(returnTokensLeft, sellOrderTokenAmount)? + if (returnTokensLeft < sellOrderTokenAmount) { + val sellOrderSpread = spreadPerToken * returnTokensLeft + (0L, accumulatedFullSpread + sellOrderSpread) + } else { + val sellOrderSpread = spreadPerToken * sellOrderTokenAmount + (returnTokensLeft - sellOrderTokenAmount, accumulatedFullSpread + sellOrderSpread) + } + } + else { + // spread is not ours + (returnTokensLeft - min(returnTokensLeft, sellOrderTokenAmount), accumulatedFullSpread) + } + })._2 + } val totalMatching = (SELF.value - expectedDexFee) == returnTokenAmount * tokenPrice && From be35c61b2c96536a804d8b7b1c8f484aec8ea84f Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Thu, 7 May 2020 18:39:04 +0300 Subject: [PATCH 14/23] rewrite buy order contract full spread accumulation; --- .../playgrounds/examples/DEXPlayground.scala | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index 885554d..a262d7d 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -16,9 +16,9 @@ object DEXPlayground { val buyerContractEnv: ScriptEnv = Map("buyerPk" -> buyerPk, "tokenId" -> token.tokenId) - // TODO : check that counter orders are sorted by token price - // TODO: if both orders were in the same block who gets the spread? + // TODO: check that counter orders are sorted by token price // TODO: move price check (from fullSpread) to boxesAreSortedByTokenPrice? + // TODO: if both orders were in the same block who gets the spread? val buyerScript = s"""buyerPk || { val tokenPrice = $tokenPrice @@ -59,14 +59,9 @@ object DEXPlayground { if (sellOrder.creationInfo._1 >= SELF.creationInfo._1 && sellOrderTokenPrice <= tokenPrice) { // spread is ours val spreadPerToken = tokenPrice - sellOrderTokenPrice - // TODO: rewrite with min(returnTokensLeft, sellOrderTokenAmount)? - if (returnTokensLeft < sellOrderTokenAmount) { - val sellOrderSpread = spreadPerToken * returnTokensLeft - (0L, accumulatedFullSpread + sellOrderSpread) - } else { - val sellOrderSpread = spreadPerToken * sellOrderTokenAmount - (returnTokensLeft - sellOrderTokenAmount, accumulatedFullSpread + sellOrderSpread) - } + val tokenAmount = min(returnTokensLeft, sellOrderTokenAmount) + val sellOrderSpread = spreadPerToken * tokenAmount + (returnTokensLeft - tokenAmount, accumulatedFullSpread + sellOrderSpread) } else { // spread is not ours From 495c5ec0a1dde6c7976daca3ebc2c0b57631b305 Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Sat, 9 May 2020 12:01:57 +0300 Subject: [PATCH 15/23] remove unused import; --- .../playgrounds/examples/test/DEXPlaygroundSpec.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/playgrounds/src/test/scala/org/ergoplatform/playgrounds/examples/test/DEXPlaygroundSpec.scala b/playgrounds/src/test/scala/org/ergoplatform/playgrounds/examples/test/DEXPlaygroundSpec.scala index 6bb9bc8..5f65ae0 100644 --- a/playgrounds/src/test/scala/org/ergoplatform/playgrounds/examples/test/DEXPlaygroundSpec.scala +++ b/playgrounds/src/test/scala/org/ergoplatform/playgrounds/examples/test/DEXPlaygroundSpec.scala @@ -1,6 +1,5 @@ package org.ergoplatform.playgrounds.examples.test -import org.ergoplatform.playgrounds.examples.AssetsAtomicExchangePlayground import org.scalatest.PropSpec import org.ergoplatform.playgrounds.examples.DEXPlayground From f9fd75c5d1f6f14f937de278e1f30c146a803be3 Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Sun, 10 May 2020 16:48:54 +0300 Subject: [PATCH 16/23] implement boxesAreSortedByTokenPrice; --- .../playgrounds/examples/DEXPlayground.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index a262d7d..6966d06 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -17,6 +17,7 @@ object DEXPlayground { Map("buyerPk" -> buyerPk, "tokenId" -> token.tokenId) // TODO: check that counter orders are sorted by token price + // TODO: show contract cost // TODO: move price check (from fullSpread) to boxesAreSortedByTokenPrice? // TODO: if both orders were in the same block who gets the spread? val buyerScript = s"""buyerPk || { @@ -37,7 +38,14 @@ object DEXPlayground { b.R4[Coll[Byte]].isDefined && b.R4[Coll[Byte]].get == SELF.id && b.propositionBytes == buyerPk.propBytes } - val boxesAreSortedByTokenPrice = { (boxes: Coll[Box]) => true } + val boxesAreSortedByTokenPrice = { (boxes: Coll[Box]) => + boxes.fold((0L, true), { (t: (Long, Boolean), box: Box) => + val prevBoxTokenPrice = t._1 + val isSorted = t._2 + val boxTokenPrice = box.R5[Long].getOrElse(0L) + (boxTokenPrice, isSorted && boxTokenPrice >= prevBoxTokenPrice) + })._2 + } returnBoxes.size == 1 && spendingSellOrders.size > 0 && boxesAreSortedByTokenPrice(spendingSellOrders) && { val returnBox = returnBoxes(0) From 65db8a89903f517f668194bc00bb0e700cb2a41c Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Sun, 10 May 2020 18:08:21 +0300 Subject: [PATCH 17/23] draft fullSpread for seller contract; draft multiple counter orders support in seller contract; expose dex fee per token in R6; --- .../playgrounds/examples/DEXPlayground.scala | 76 ++++++++++++------- 1 file changed, 50 insertions(+), 26 deletions(-) diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index 6966d06..997e584 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -16,7 +16,7 @@ object DEXPlayground { val buyerContractEnv: ScriptEnv = Map("buyerPk" -> buyerPk, "tokenId" -> token.tokenId) - // TODO: check that counter orders are sorted by token price + // TODO: put contract type (sell/buy) in register and check in INPUTS filter? // TODO: show contract cost // TODO: move price check (from fullSpread) to boxesAreSortedByTokenPrice? // TODO: if both orders were in the same block who gets the spread? @@ -55,7 +55,7 @@ object DEXPlayground { val foundNewOrderBoxes = OUTPUTS.filter { (b: Box) => val contractParametersAreCorrect = b.R4[Coll[Byte]].get == tokenId && b.R5[Long].get == tokenPrice - b.R6[Coll[Byte]].isDefined && b.R6[Coll[Byte]].get == SELF.id && b.propositionBytes == SELF.propositionBytes + b.R7[Coll[Byte]].isDefined && b.R7[Coll[Byte]].get == SELF.id && b.propositionBytes == SELF.propositionBytes } val fullSpread = { @@ -76,7 +76,6 @@ object DEXPlayground { (returnTokensLeft - min(returnTokensLeft, sellOrderTokenAmount), accumulatedFullSpread) } })._2 - } val totalMatching = (SELF.value - expectedDexFee) == returnTokenAmount * tokenPrice && @@ -125,32 +124,53 @@ object DEXPlayground { b.R4[Coll[Byte]].isDefined && b.R4[Coll[Byte]].get == SELF.id && b.propositionBytes == sellerPk.propBytes } - returnBoxes.size == 1 && { - val returnBox = returnBoxes(0) - val spendingBuyOrders = INPUTS.filter { (b: Box) => - b.R4[Coll[Byte]].isDefined && b.R5[Long].isDefined && { - val buyOrderTokenId = b.R4[Coll[Byte]].get - buyOrderTokenId == tokenId && { - b.tokens.size == 1 && b.tokens(0)._1 == tokenId - } - } + val boxesAreSortedByTokenPrice = { (boxes: Coll[Box]) => + boxes.fold((0L, true), { (t: (Long, Boolean), box: Box) => + val prevBoxTokenPrice = t._1 + val isSorted = t._2 + val boxTokenPrice = box.R5[Long].getOrElse(0L) + (boxTokenPrice, isSorted && boxTokenPrice >= prevBoxTokenPrice) + })._2 + } + + val spendingBuyOrders = INPUTS.filter { (b: Box) => + b.R4[Coll[Byte]].isDefined && b.R5[Long].isDefined && b.R6[Long].isDefined && { + val buyOrderTokenId = b.R4[Coll[Byte]].get + buyOrderTokenId == tokenId && b.tokens.size == 0 } + } - val buyOrder = spendingBuyOrders(0) + returnBoxes.size == 1 && spendingBuyOrders.size > 0 && boxesAreSortedByTokenPrice(spendingBuyOrders) && { + val returnBox = returnBoxes(0) val foundNewOrderBoxes = OUTPUTS.filter { (b: Box) => val contractParametersAreCorrect = b.R4[Coll[Byte]].get == tokenId && b.R5[Long].get == tokenPrice val contractIsTheSame = b.propositionBytes == SELF.propositionBytes - b.R6[Coll[Byte]].isDefined && b.R6[Coll[Byte]].get == SELF.id && contractIsTheSame + b.R7[Coll[Byte]].isDefined && b.R7[Coll[Byte]].get == SELF.id && contractIsTheSame } - val buyOrderTokenPrice = buyOrder.R5[Long].get - val spreadPerToken = if (buyOrder.creationInfo._1 > SELF.creationInfo._1) - buyOrderTokenPrice - tokenPrice - else - 0L + val fullSpread = { (tokenAmount: Long) => + spendingBuyOrders.fold((tokenAmount, 0L), { (t: (Long, Long), buyOrder: Box) => + val returnTokensLeft = t._1 + val accumulatedFullSpread = t._2 + val buyOrderTokenPrice = buyOrder.R5[Long].get + val buyOrderDexFeePerToken = buyOrder.R6[Long].get + val buyOrderTokenAmount = buyOrder.value / (buyOrderTokenPrice + buyOrderDexFeePerToken) + if (buyOrder.creationInfo._1 >= SELF.creationInfo._1 && buyOrderTokenPrice <= tokenPrice) { + // spread is ours + val spreadPerToken = tokenPrice - buyOrderTokenPrice + val tokenAmountLeft = min(returnTokensLeft, buyOrderTokenAmount) + val buyOrderSpread = spreadPerToken * tokenAmountLeft + (returnTokensLeft - tokenAmountLeft, accumulatedFullSpread + buyOrderSpread) + } + else { + // spread is not ours + (returnTokensLeft - min(returnTokensLeft, buyOrderTokenAmount), accumulatedFullSpread) + } + })._2 + } - val totalMatching = (returnBox.value == selfTokenAmount * (tokenPrice + spreadPerToken)) + val totalMatching = (returnBox.value == selfTokenAmount * tokenPrice + fullSpread(selfTokenAmount)) val partialMatching = { foundNewOrderBoxes.size == 1 && { @@ -165,12 +185,12 @@ object DEXPlayground { val tokenIdIsCorrect = newOrderTokenId == tokenId val newOrderValueIsCorrect = newOrderBox.value == (SELF.value - expectedDexFee) - val returnBoxValueIsCorrect = returnBox.value == soldTokenAmount * (tokenPrice + spreadPerToken) + val returnBoxValueIsCorrect = returnBox.value == soldTokenAmount * tokenPrice + fullSpread(soldTokenAmount) tokenIdIsCorrect && soldTokenAmount >= 1 && newOrderValueIsCorrect && returnBoxValueIsCorrect } } - (totalMatching ||partialMatching) && buyOrderTokenPrice >=tokenPrice + (totalMatching || partialMatching) } }""".stripMargin @@ -229,7 +249,8 @@ object DEXPlayground { value = buyOrderBoxValue, script = buyOrderContract, registers = R4 -> token.tokenId, - R5 -> buyersBidTokenPrice + R5 -> buyersBidTokenPrice, + R6 -> buyerDexFeePerToken ) val buyOrderTransaction = Transaction( @@ -291,7 +312,8 @@ object DEXPlayground { script = newSellOrderContract, registers = R4 -> token.tokenId, R5 -> sellerAskTokenPrice, - R6 -> sellOrderTxSigned.outputs(0).id + R6 -> sellerDexFeePerToken, + R7 -> sellOrderTxSigned.outputs(0).id ) val buyerTokenAmountBought = buyerBidTokenAmount / 2 @@ -312,7 +334,8 @@ object DEXPlayground { script = newBuyOrderContract, registers = R4 -> token.tokenId, R5 -> sellerAskTokenPrice, - R6 -> buyOrderTxSigned.outputs(0).id + R6 -> buyerDexFeePerToken, + R7 -> buyOrderTxSigned.outputs(0).id ) val dexParty = blockchainSim.newParty("DEX") @@ -434,7 +457,8 @@ object DEXPlayground { value = buyOrderBoxValue, script = buyOrderContract, registers = R4 -> token.tokenId, - R5 -> buyersBidTokenPrice + R5 -> buyersBidTokenPrice, + R6 -> buyerDexFeePerToken ) val buyOrderTransaction = Transaction( From 43095cb35ee0ef8782f9377c69de262c9dc31962 Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Sun, 10 May 2020 18:21:21 +0300 Subject: [PATCH 18/23] move TODOs to PR desc; --- .../org/ergoplatform/playgrounds/examples/DEXPlayground.scala | 4 ---- 1 file changed, 4 deletions(-) diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index 997e584..0e768a4 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -16,10 +16,6 @@ object DEXPlayground { val buyerContractEnv: ScriptEnv = Map("buyerPk" -> buyerPk, "tokenId" -> token.tokenId) - // TODO: put contract type (sell/buy) in register and check in INPUTS filter? - // TODO: show contract cost - // TODO: move price check (from fullSpread) to boxesAreSortedByTokenPrice? - // TODO: if both orders were in the same block who gets the spread? val buyerScript = s"""buyerPk || { val tokenPrice = $tokenPrice From e9e9505bc3d6ab76b8f6364c46fd008d9c31d51c Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Mon, 11 May 2020 14:49:41 +0300 Subject: [PATCH 19/23] formatting; --- .../org/ergoplatform/playgrounds/examples/DEXPlayground.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index 0e768a4..64a7677 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -82,7 +82,7 @@ object DEXPlayground { returnBox.value >= fullSpread } - val coinsSecured = partialMatching ||totalMatching + val coinsSecured = partialMatching || totalMatching val tokenIdIsCorrect = returnBox.tokens.getOrElse(0, (Coll[Byte](), 0L))._1 == tokenId From e4798629a82e13241348f0f81769a2bb983f1710 Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Mon, 18 May 2020 13:23:09 +0300 Subject: [PATCH 20/23] add missing new(residual) order box requirements(registers) --- .../playgrounds/examples/DEXPlayground.scala | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index 64a7677..a4a72e1 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -50,8 +50,13 @@ object DEXPlayground { val expectedDexFee = dexFeePerToken * returnTokenAmount val foundNewOrderBoxes = OUTPUTS.filter { (b: Box) => - val contractParametersAreCorrect = b.R4[Coll[Byte]].get == tokenId && b.R5[Long].get == tokenPrice - b.R7[Coll[Byte]].isDefined && b.R7[Coll[Byte]].get == SELF.id && b.propositionBytes == SELF.propositionBytes + val tokenIdParameterIsCorrect = b.R4[Coll[Byte]].isDefined && b.R4[Coll[Byte]].get == tokenId + val tokenPriceParameterIsCorrect = b.R5[Long].isDefined && b.R5[Long].get == tokenPrice + val dexFeePerTokenParameterIsCorrect = b.R6[Long].isDefined && b.R6[Long].get == dexFeePerToken + val contractParametersAreCorrect = tokenIdParameterIsCorrect && tokenPriceParameterIsCorrect + val referenceMe = b.R7[Coll[Byte]].isDefined && b.R7[Coll[Byte]].get == SELF.id + val guardedByTheSameContract = b.propositionBytes == SELF.propositionBytes + contractParametersAreCorrect && referenceMe && guardedByTheSameContract } val fullSpread = { @@ -329,7 +334,7 @@ object DEXPlayground { value = newBuyOrderBoxValue, script = newBuyOrderContract, registers = R4 -> token.tokenId, - R5 -> sellerAskTokenPrice, + R5 -> buyersBidTokenPrice, R6 -> buyerDexFeePerToken, R7 -> buyOrderTxSigned.outputs(0).id ) From 31b6bac7a184d3b479dd72ba3434a41fa6cf36dc Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Mon, 18 May 2020 14:25:08 +0300 Subject: [PATCH 21/23] fix missing checks for residual sell order box; --- .../playgrounds/examples/DEXPlayground.scala | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index a4a72e1..588d404 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -145,9 +145,13 @@ object DEXPlayground { val returnBox = returnBoxes(0) val foundNewOrderBoxes = OUTPUTS.filter { (b: Box) => - val contractParametersAreCorrect = b.R4[Coll[Byte]].get == tokenId && b.R5[Long].get == tokenPrice - val contractIsTheSame = b.propositionBytes == SELF.propositionBytes - b.R7[Coll[Byte]].isDefined && b.R7[Coll[Byte]].get == SELF.id && contractIsTheSame + val tokenIdParameterIsCorrect = b.R4[Coll[Byte]].isDefined && b.R4[Coll[Byte]].get == tokenId + val tokenPriceParameterIsCorrect = b.R5[Long].isDefined && b.R5[Long].get == tokenPrice + val dexFeePerTokenParameterIsCorrect = b.R6[Long].isDefined && b.R6[Long].get == dexFeePerToken + val contractParametersAreCorrect = tokenIdParameterIsCorrect && tokenPriceParameterIsCorrect + val referenceMe = b.R7[Coll[Byte]].isDefined && b.R7[Coll[Byte]].get == SELF.id + val guardedByTheSameContract = b.propositionBytes == SELF.propositionBytes + contractParametersAreCorrect && referenceMe && guardedByTheSameContract } val fullSpread = { (tokenAmount: Long) => From e42b5f14e9bfc50be27d69db44e40e17a3d7e58f Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Mon, 18 May 2020 14:36:45 +0300 Subject: [PATCH 22/23] for sell order contract require spread only if strictly "younger" than the counter order; --- .../org/ergoplatform/playgrounds/examples/DEXPlayground.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala index 588d404..a201cf5 100644 --- a/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala +++ b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala @@ -161,7 +161,7 @@ object DEXPlayground { val buyOrderTokenPrice = buyOrder.R5[Long].get val buyOrderDexFeePerToken = buyOrder.R6[Long].get val buyOrderTokenAmount = buyOrder.value / (buyOrderTokenPrice + buyOrderDexFeePerToken) - if (buyOrder.creationInfo._1 >= SELF.creationInfo._1 && buyOrderTokenPrice <= tokenPrice) { + if (buyOrder.creationInfo._1 > SELF.creationInfo._1 && buyOrderTokenPrice <= tokenPrice) { // spread is ours val spreadPerToken = tokenPrice - buyOrderTokenPrice val tokenAmountLeft = min(returnTokensLeft, buyOrderTokenAmount) From 152ede5189604b7da34535837747fc03a88eb3e2 Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Tue, 2 Jun 2020 08:00:46 +0300 Subject: [PATCH 23/23] update Scastie link; --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index efb9604..e2511bd 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ Enjoy: ### DEX with partial filling contracts -[Run in Scastie](https://scastie.scala-lang.org/YCzvl8NBQwa7R0pVI5mHnA) +[Run in Scastie](https://scastie.scala-lang.org/mh3h6SrESnKJwdqZjKnVkw) -[Source code](https://github.com/ergoplatform/ergo-playgrounds/blob/c91117ae0b1434b7a554028592e30a5bba15a14b/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/AssetsAtomicExchangePlayground.scala#L1-L1) +[Source code](https://github.com/ergoplatform/ergo-playgrounds/blob/e42b5f14e9bfc50be27d69db44e40e17a3d7e58f/playgrounds/src/main/scala/org/ergoplatform/playgrounds/examples/DEXPlayground.scala#L3) ### Assets Atomic Exchange contracts