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 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..a201cf5 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,36 +21,86 @@ object DEXPlayground { val tokenPrice = $tokenPrice val dexFeePerToken = $dexFeePerToken - val returnBox = OUTPUTS.filter { (b: Box) => + 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 returnBoxes = OUTPUTS.filter { (b: Box) => 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 maxReturnTokenErgValue = returnTokenAmount * tokenPrice - val totalReturnErgValue = 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 } - val coinsSecured = (SELF.value - expectedDexFee) == maxReturnTokenErgValue || { - foundNewOrderBoxes.size == 1 && foundNewOrderBoxes(0).value >= (SELF.value - totalReturnErgValue - expectedDexFee) + 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 tokenIdIsCorrect = returnTokenId == tokenId - - allOf(Coll( - tokenIdIsCorrect, - returnTokenAmount >= 1, - coinsSecured - )) + 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 + + val expectedDexFee = dexFeePerToken * returnTokenAmount + + val foundNewOrderBoxes = OUTPUTS.filter { (b: Box) => + 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 = { + spendingSellOrders.fold((returnTokenAmount, 0L), { (t: (Long, Long), sellOrder: Box) => + val returnTokensLeft = t._1 + val accumulatedFullSpread = t._2 + val sellOrderTokenPrice = sellOrder.R5[Long].get + val sellOrderTokenAmount = sellOrder.tokens(0)._2 + if (sellOrder.creationInfo._1 >= SELF.creationInfo._1 && sellOrderTokenPrice <= tokenPrice) { + // spread is ours + val spreadPerToken = tokenPrice - sellOrderTokenPrice + val tokenAmount = min(returnTokensLeft, sellOrderTokenAmount) + val sellOrderSpread = spreadPerToken * tokenAmount + (returnTokensLeft - tokenAmount, accumulatedFullSpread + sellOrderSpread) + } + else { + // spread is not ours + (returnTokensLeft - min(returnTokensLeft, sellOrderTokenAmount), accumulatedFullSpread) + } + })._2 + } + + val totalMatching = (SELF.value - expectedDexFee) == returnTokenAmount * tokenPrice && + returnBox.value >= fullSpread + val partialMatching = { + foundNewOrderBoxes.size == 1 && + foundNewOrderBoxes(0).value == (SELF.value - returnTokenAmount * tokenPrice - expectedDexFee) && + returnBox.value >= fullSpread + } + + val coinsSecured = partialMatching || totalMatching + + val tokenIdIsCorrect = returnBox.tokens.getOrElse(0, (Coll[Byte](), 0L))._1 == tokenId + + allOf(Coll( + tokenIdIsCorrect, + returnTokenAmount >= 1, + coinsSecured + )) + } } """.stripMargin - ErgoScriptCompiler.compile(buyerContractEnv, buyerScript) + contract(buyerContractEnv, buyerScript) } def sellerOrderContract( @@ -72,33 +121,86 @@ 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 foundNewOrderBoxes = OUTPUTS.filter { (b: Box) => - b.R4[Coll[Byte]].isDefined && b.R4[Coll[Byte]].get == SELF.id && b.propositionBytes == SELF.propositionBytes } - (returnBox.value == selfTokenAmount * tokenPrice) || { - 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 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 + } + } + + returnBoxes.size == 1 && spendingBuyOrders.size > 0 && boxesAreSortedByTokenPrice(spendingBuyOrders) && { + val returnBox = returnBoxes(0) + + val foundNewOrderBoxes = OUTPUTS.filter { (b: Box) => + 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) => + 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 + fullSpread(selfTokenAmount)) + + 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 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 + fullSpread(soldTokenAmount) + tokenIdIsCorrect && soldTokenAmount >= 1 && newOrderValueIsCorrect && returnBoxValueIsCorrect + } } + + (totalMatching || partialMatching) } }""".stripMargin - ErgoScriptCompiler.compile(sellerContractEnv, sellerScript) + contract(sellerContractEnv, sellerScript) } def swapScenario = { @@ -124,7 +226,7 @@ object DEXPlayground { ) val sellerParty = blockchainSim.newParty("seller") - val sellerAskTokenPrice = 5000000L + val sellerAskTokenPrice = 4000000L val sellerAskTokenAmount = 100L val sellerAskNanoErgs = sellerAskTokenPrice * sellerAskTokenAmount val sellerDexFee = 10000000L @@ -148,7 +250,13 @@ 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, + R6 -> buyerDexFeePerToken + ) val buyOrderTransaction = Transaction( inputs = buyerParty.selectUnspentBoxes(toSpend = buyOrderBoxValue + buyOrderTxFee), @@ -169,9 +277,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 +303,31 @@ 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)), + script = newSellOrderContract, + registers = R4 -> token.tokenId, + R5 -> sellerAskTokenPrice, + R6 -> sellerDexFeePerToken, + R7 -> sellOrderTxSigned.outputs(0).id ) 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 + (buyersBidTokenPrice - sellerAskTokenPrice) * buyerTokenAmountBought, + token = (token -> buyerTokenAmountBought), + registers = (R4 -> buyOrderTxSigned.outputs(0).id), + script = contract(buyerParty.wallet.getAddress.pubKey) ) // reuse old contract, nothing is changed @@ -222,9 +335,12 @@ object DEXPlayground { val newBuyOrderBoxValue = buyOrderBox.value - buyerTokenAmountBought * buyersBidTokenPrice - buyerDexFeeForPartialMatch val newBuyOrderBox = Box( - value = newBuyOrderBoxValue, - register = (R4 -> buyOrderTxSigned.outputs(0).id), - script = newBuyOrderContract + value = newBuyOrderBoxValue, + script = newBuyOrderContract, + registers = R4 -> token.tokenId, + R5 -> buyersBidTokenPrice, + R6 -> buyerDexFeePerToken, + R7 -> buyOrderTxSigned.outputs(0).id ) val dexParty = blockchainSim.newParty("DEX") @@ -268,18 +384,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 + (buyersBidTokenPrice - sellerAskTokenPrice) * buyerTokenAmountBoughtInTotalMatching, + token = (token -> buyerTokenAmountBoughtInTotalMatching), + registers = (R4 -> buyOrderAfterPartialMatching.id), + script = contract(buyerParty.wallet.getAddress.pubKey) ) val swapTxFeeForTotalMatching = MinTxFee @@ -342,7 +458,13 @@ 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, + R6 -> buyerDexFeePerToken + ) val buyOrderTransaction = Transaction( inputs = buyerParty.selectUnspentBoxes(toSpend = buyOrderBoxValue + buyOrderTxFee), @@ -360,12 +482,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 - 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) + value = buyOrderBox.value - cancelTxFee, + script = contract(buyerParty.wallet.getAddress.pubKey) ) val cancelBuyTransaction = Transaction( @@ -409,9 +527,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( @@ -433,11 +553,9 @@ object DEXPlayground { val cancelTxFee = MinTxFee val sellerReturnBox = Box( - 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) + value = sellOrderBox.value - cancelTxFee, + token = (token -> sellerAskTokenAmount), + script = contract(sellerParty.wallet.getAddress.pubKey) ) val cancelSellTransaction = Transaction( 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