diff --git a/.bsp/sbt.json b/.bsp/sbt.json deleted file mode 100644 index 061a5e01..00000000 --- a/.bsp/sbt.json +++ /dev/null @@ -1 +0,0 @@ -{"name":"sbt","version":"1.5.5","bspVersion":"2.0.0-M5","languages":["scala"],"argv":["/opt/homebrew/Cellar/openjdk@11/11.0.15/libexec/openjdk.jdk/Contents/Home/bin/java","-Xms100m","-Xmx100m","-classpath","/Users/gusev.t/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/223.8214.52/IntelliJ IDEA.app.plugins/Scala/launcher/sbt-launch.jar","xsbt.boot.Boot","-bsp","--sbt-launch-jar=/Users/gusev.t/Library/Application%20Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/223.8214.52/IntelliJ%20IDEA.app.plugins/Scala/launcher/sbt-launch.jar"]} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 049c42d9..a946c808 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,4 @@ project/metals.sbt config.env -/.bsp/ \ No newline at end of file +/.bsp/ diff --git a/README.md b/README.md index d87d5a44..d8fbbd95 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,10 @@ Windows: cd ergo-dex-backend copy ./config-example.env ./config.env ``` -The 2 values that need to be changed in the `config.env` file are the address you want to recieve fees on and the URI to your node (localhost/127.0.0.1 might not be accessible from within a docker container, it is best to use the local lan ip if the node is running on the same host). +The 2 values that need to be changed in the `config.env` file are the mnemonic ([howto](https://github.com/spectrum-finance/ergo-dex-backend/blob/6c9fccfbd4de921d41343a5937153f1724408a10/modules/amm-executor/src/test/scala/org/ergoplatfrom/dex/executor/amm/HowTo.scala#L14)) from which bot will create the wallet to receive fees on and pay miner fees from (in SPF fee cases) +and the URI to your node (localhost/127.0.0.1 might not be accessible from within a docker container, it is best to use the local lan ip if the node is running on the same host). ### Running the services -Once the `config.env` file is created the only thing left to do is to run the containers: +Once the `config.env` file is created, make sure you have funds on expected address (you can check which address bot will use with `HowTo.scala` script). Next, the only thing left to do is to run the containers: Linux: ``` diff --git a/build.sbt b/build.sbt index 150dbeb4..35e63f40 100644 --- a/build.sbt +++ b/build.sbt @@ -128,6 +128,7 @@ lazy val utxoTracker = utils .settings(commonSettings) .settings(assembly / mainClass := Some("org.ergoplatform.dex.tracker.App")) .settings(nativePackagerSettings("utxo-tracker")) + .settings(dockerBaseImage := "openjdk:11-jre-slim") .enablePlugins(JavaAppPackaging, UniversalPlugin, DockerPlugin) .dependsOn(Seq(core, cache).map(_ % allConfigDependency): _*) @@ -157,6 +158,7 @@ lazy val ammExecutor = utils assembly / mainClass := Some("org.ergoplatform.dex.executor.amm.App"), libraryDependencies ++= SttpClientCE ) + .settings(dockerBaseImage := "openjdk:11-jre-slim") .settings(nativePackagerSettings("amm-executor")) .enablePlugins(JavaAppPackaging, UniversalPlugin, DockerPlugin) .dependsOn(Seq(core, http).map(_ % allConfigDependency): _*) @@ -168,6 +170,7 @@ lazy val poolResolver = utils assembly / mainClass := Some("org.ergoplatform.dex.resolver.App"), libraryDependencies ++= RocksDB ) + .settings(dockerBaseImage := "openjdk:11-jre-slim") .settings(nativePackagerSettings("pool-resolver")) .enablePlugins(JavaAppPackaging, UniversalPlugin, DockerPlugin) .dependsOn(Seq(core, http).map(_ % allConfigDependency): _*) diff --git a/config-example.env b/config-example.env index bc111ba2..b7c4b1d5 100644 --- a/config-example.env +++ b/config-example.env @@ -1 +1 @@ -JAVA_TOOL_OPTIONS="-Dnetwork.node-uri=http://:9053 -Dexchange.reward-address=" \ No newline at end of file +JAVA_TOOL_OPTIONS="-Dnetwork.node-uri=http://:9053 -Dexchange.mnemonic=" \ No newline at end of file diff --git a/modules/amm-executor/src/main/resources/application.conf b/modules/amm-executor/src/main/resources/application.conf index 8c0f99f5..45d433ab 100644 --- a/modules/amm-executor/src/main/resources/application.conf +++ b/modules/amm-executor/src/main/resources/application.conf @@ -1,6 +1,8 @@ rotation.retry-delay = 120s -exchange.reward-address = "9gCigPc9cZNRhKgbgdmTkVxo1ZKgw79G8DvLjCcYWAvEF3XRUKy" +exchange.spectrum-token = "9a06d9e545a41fd51eeffc5e20d818073bf820c635e2a9d922269913e0de369d" +exchange.mnemonic = "" +monetary.min-dex-token-fee = 10 execution.order-lifetime = 300s diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/App.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/App.scala index 5483be3b..ba6c91f3 100644 --- a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/App.scala +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/App.scala @@ -11,10 +11,14 @@ import org.ergoplatform.dex.configs.ConsumerConfig import org.ergoplatform.dex.domain.amm.{CFMMOrder, OrderId} import org.ergoplatform.dex.executor.amm.config.ConfigBundle import org.ergoplatform.dex.executor.amm.context.AppContext -import org.ergoplatform.dex.executor.amm.interpreters.{CFMMInterpreter, N2TCFMMInterpreter, T2TCFMMInterpreter} -import org.ergoplatform.dex.executor.amm.processes.Executor +import org.ergoplatform.dex.executor.amm.interpreters.v1.{InterpreterV1, N2TCFMMInterpreter, T2TCFMMInterpreter} +import org.ergoplatform.dex.executor.amm.interpreters.v3.InterpreterV3 +import org.ergoplatform.dex.executor.amm.interpreters.v3.n2t.N2TV3 +import org.ergoplatform.dex.executor.amm.interpreters.v3.t2t.T2TV3 +import org.ergoplatform.dex.executor.amm.interpreters.CFMMInterpreter +import org.ergoplatform.dex.executor.amm.processes.{Executor, NetworkContextUpdater} import org.ergoplatform.dex.executor.amm.repositories.CFMMPools -import org.ergoplatform.dex.executor.amm.services.Execution +import org.ergoplatform.dex.executor.amm.services.{DexOutputResolver, Execution} import org.ergoplatform.dex.executor.amm.streaming._ import org.ergoplatform.dex.protocol.amm.AMMType.{CFMMType, N2T_CFMM, T2T_CFMM} import org.ergoplatform.ergo.modules.ErgoNetwork @@ -34,12 +38,12 @@ import zio.{ExitCode, URIO, ZEnv} object App extends EnvApp[AppContext] { def run(args: List[String]): URIO[ZEnv, ExitCode] = - init(args.headOption).use { case (executor, ctx) => - val appF = executor.run.compile.drain + init(args.headOption).use { case (tasks, ctx) => + val appF = fs2.Stream(tasks: _*).parJoinUnbounded.compile.drain appF.run(ctx) as ExitCode.success }.orDie - private def init(configPathOpt: Option[String]): Resource[InitF, (Executor[StreamF], AppContext)] = + private def init(configPathOpt: Option[String]) = for { blocker <- Blocker[InitF] configs <- Resource.eval(ConfigBundle.load[InitF](configPathOpt, blocker)) @@ -47,27 +51,37 @@ object App extends EnvApp[AppContext] { implicit0(isoKRun: IsoK[RunF, InitF]) = isoKRunByContext(ctx) implicit0(e: ErgoAddressEncoder) = ErgoAddressEncoder(configs.protocol.networkType.prefix) implicit0(confirmedOrders: CFMMConsumerIn[StreamF, RunF, Confirmed]) = - makeConsumer[OrderId, Confirmed[CFMMOrder.Any]](configs.consumers.confirmedOrders) + makeConsumer[OrderId, Confirmed[CFMMOrder.AnyOrder]](configs.consumers.confirmedOrders) implicit0(unconfirmedOrders: CFMMConsumerIn[StreamF, RunF, Unconfirmed]) = - makeConsumer[OrderId, Unconfirmed[CFMMOrder.Any]](configs.consumers.unconfirmedOrders) + makeConsumer[OrderId, Unconfirmed[CFMMOrder.AnyOrder]](configs.consumers.unconfirmedOrders) implicit0(consumerRetries: CFMMConsumerRetries[StreamF, RunF]) = - makeConsumer[OrderId, Delayed[CFMMOrder.Any]](configs.consumers.ordersRetry) + makeConsumer[OrderId, Delayed[CFMMOrder.AnyOrder]](configs.consumers.ordersRetry) implicit0(orders: CFMMConsumerIn[StreamF, RunF, Id]) = Consumer.combine2(confirmedOrders, unconfirmedOrders)(_.entity, _.entity) implicit0(producerRetries: CFMMProducerRetries[StreamF]) <- - Producer.make[InitF, StreamF, RunF, OrderId, Delayed[CFMMOrder.Any]](configs.producers.ordersRetry) - implicit0(consumer: CFMMCircuit[StreamF, RunF]) = StreamingCircuit.make[StreamF, RunF, OrderId, CFMMOrder.Any] + Producer.make[InitF, StreamF, RunF, OrderId, Delayed[CFMMOrder.AnyOrder]](configs.producers.ordersRetry) + implicit0(consumer: CFMMCircuit[StreamF, RunF]) = + StreamingCircuit.make[StreamF, RunF, OrderId, CFMMOrder.AnyOrder] implicit0(backend: SttpBackend[RunF, Fs2Streams[RunF]]) <- makeBackend(ctx, blocker) implicit0(explorer: ErgoExplorer[RunF]) = ErgoExplorerStreaming.make[StreamF, RunF] implicit0(node: ErgoNode[RunF]) <- Resource.eval(ErgoNode.make[InitF, RunF]) implicit0(network: ErgoNetwork[RunF]) = ErgoNetwork.make[RunF] - implicit0(pools: CFMMPools[RunF]) <- Resource.eval(CFMMPools.make[InitF, RunF]) - implicit0(t2tInt: CFMMInterpreter[T2T_CFMM, RunF]) <- Resource.eval(T2TCFMMInterpreter.make[InitF, RunF]) - implicit0(n2tInt: CFMMInterpreter[N2T_CFMM, RunF]) <- Resource.eval(N2TCFMMInterpreter.make[InitF, RunF]) - implicit0(interpreter: CFMMInterpreter[CFMMType, RunF]) = CFMMInterpreter.make[RunF] + implicit0(pools: CFMMPools[RunF]) <- Resource.eval(CFMMPools.make[InitF, RunF]) + (networkContextUpdater, context) <- Resource.eval(NetworkContextUpdater.make[InitF, StreamF, RunF]) + implicit0(resolver: DexOutputResolver[RunF]) <- + Resource.eval(DexOutputResolver.make[InitF, RunF](configs.exchange)) + implicit0(t2tInt: InterpreterV1[T2T_CFMM, RunF]) <- + Resource.eval(T2TCFMMInterpreter.make[InitF, RunF]) + implicit0(n2tInt: InterpreterV1[N2T_CFMM, RunF]) <- + Resource.eval(N2TCFMMInterpreter.make[InitF, RunF]) + implicit0(n2tInt: InterpreterV3[N2T_CFMM, RunF]) <- + Resource.eval(N2TV3.make[InitF, RunF](configs.exchange, configs.monetary, context)) + implicit0(n2tInt: InterpreterV3[T2T_CFMM, RunF]) <- + Resource.eval(T2TV3.make[InitF, RunF](configs.exchange, configs.monetary, context)) + implicit0(interpreter: CFMMInterpreter[CFMMType, RunF]) <-Resource.eval(CFMMInterpreter.make[InitF, RunF]) implicit0(execution: Execution[RunF]) <- Resource.eval(Execution.make[InitF, RunF]) executor <- Resource.eval(Executor.make[InitF, StreamF, RunF]) - } yield executor -> ctx + } yield List(executor.run, networkContextUpdater.run) -> ctx private def makeBackend( ctx: AppContext, diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/config/ExchangeConfig.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/config/ExchangeConfig.scala index 79b6f16f..d0d37d5f 100644 --- a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/config/ExchangeConfig.scala +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/config/ExchangeConfig.scala @@ -2,11 +2,12 @@ package org.ergoplatform.dex.executor.amm.config import derevo.derive import derevo.pureconfig.pureconfigReader -import org.ergoplatform.ergo.Address +import org.ergoplatform.ergo.{Address, TokenId} +import sigmastate.basics.DLogProtocol.DLogProverInput import tofu.Context import tofu.logging.derivation.loggable @derive(pureconfigReader, loggable) -final case class ExchangeConfig(rewardAddress: Address) +final case class ExchangeConfig(spectrumToken: TokenId, mnemonic: String) object ExchangeConfig extends Context.Companion[ExchangeConfig] diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/domain/errors.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/domain/errors.scala index df504671..59bd4371 100644 --- a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/domain/errors.scala +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/domain/errors.scala @@ -3,6 +3,7 @@ package org.ergoplatform.dex.executor.amm.domain import cats.syntax.show._ import org.ergoplatform.dex.domain.AssetAmount import org.ergoplatform.dex.domain.amm.PoolId +import org.ergoplatform.dex.domain.amm.OrderId import org.ergoplatform.ergo.{BoxId, SErgoTree} import tofu.Errors @@ -17,13 +18,23 @@ object errors { s"Price slipped up too much for Pool{id=$poolId}. {minOutput=${minOutput.show}, actualOutput=${actualOutput.show}}" ) - final case class PriceTooLow(poolId: PoolId, maxDexFee: Long, actualDexFee: Long) + final case class PriceTooLow(poolId: PoolId, maxDexFee: Long, actualDexFee: Long, quote: Long) extends ExecutionFailed( - s"Price slipped down too much for Pool{id=$poolId}. {maxDexFee=${maxDexFee.show}, actualDexFee=${actualDexFee.show}}" + s"Price slipped down too much for Pool{id=$poolId}. {maxDexFee=${maxDexFee.show}, actualDexFee=${actualDexFee.show}, quote=${quote}" ) - final case class IncorrectMultiAddressSwapTree(poolId: PoolId, orderId: BoxId, tree: SErgoTree, err: String) + final case class IncorrectMultiAddressTree(poolId: PoolId, orderId: BoxId, tree: SErgoTree, err: String) extends ExecutionFailed( s"Incorrect multi address tree for pool $poolId and order $orderId: $tree. Err is: $err" ) + + final case class EmptyOutputForDexTokenFee(poolId: PoolId, orderId: BoxId) + extends ExecutionFailed( + s"There is no output for make box with spf fee." + ) + + final case class NegativeDexFee(poolId: PoolId, orderId: OrderId, dexFee: Long) + extends ExecutionFailed( + s"Dex fee is negative ${dexFee} for pool: $poolId, order: $orderId." + ) } diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/CFMMInterpreter.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/CFMMInterpreter.scala index cc6053da..bcb48fa6 100644 --- a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/CFMMInterpreter.scala +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/CFMMInterpreter.scala @@ -1,14 +1,18 @@ package org.ergoplatform.dex.executor.amm.interpreters -import cats.FlatMap +import cats.{Functor, Monad} import org.ergoplatform.ErgoLikeTransaction -import org.ergoplatform.ergo.state.{Predicted, Traced} -import org.ergoplatform.dex.domain.amm.CFMMPool +import org.ergoplatform.dex.domain.DexOperatorOutput import org.ergoplatform.dex.domain.amm.CFMMOrder._ +import org.ergoplatform.dex.domain.amm.CFMMOrderType.{DepositType, RedeemType, SwapType} +import org.ergoplatform.dex.domain.amm.{CFMMOrder, CFMMPool} +import org.ergoplatform.dex.executor.amm.interpreters.v1.InterpreterV1 +import org.ergoplatform.dex.executor.amm.interpreters.v3.InterpreterV3 import org.ergoplatform.dex.protocol.amm.AMMType.{CFMMType, N2T_CFMM, T2T_CFMM} -import org.ergoplatform.dex.protocol.instances._ +import org.ergoplatform.ergo.domain.Output +import org.ergoplatform.ergo.state.{Predicted, Traced} import tofu.higherKind.{Mid, RepresentableK} -import tofu.logging.Logging +import tofu.logging.{Logging, Logs} import tofu.syntax.logging._ import tofu.syntax.monadic._ @@ -16,11 +20,20 @@ import tofu.syntax.monadic._ */ trait CFMMInterpreter[CT <: CFMMType, F[_]] { - def deposit(in: Deposit, pool: CFMMPool): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]])] + def deposit( + in: CFMMOrder.AnyDeposit, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] - def redeem(in: Redeem, pool: CFMMPool): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]])] + def redeem( + in: CFMMOrder.AnyRedeem, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] - def swap(in: SwapAny, pool: CFMMPool): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]])] + def swap( + in: CFMMOrder.AnySwap, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] } object CFMMInterpreter { @@ -30,37 +43,89 @@ object CFMMInterpreter { tofu.higherKind.derived.genRepresentableK[Rep] } - def make[F[_]](implicit - n2t: CFMMInterpreter[N2T_CFMM, F], - t2t: CFMMInterpreter[T2T_CFMM, F] - ): CFMMInterpreter[CFMMType, F] = - new Proxy[F] - - final class Proxy[F[_]](implicit n2t: CFMMInterpreter[N2T_CFMM, F], t2t: CFMMInterpreter[T2T_CFMM, F]) - extends CFMMInterpreter[CFMMType, F] { - - def deposit(in: Deposit, pool: CFMMPool): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]])] = - if (pool.isNative) n2t.deposit(in, pool) - else t2t.deposit(in, pool) + def make[I[_]: Functor, F[_]: Monad](implicit + n2t: InterpreterV1[N2T_CFMM, F], + t2t: InterpreterV1[T2T_CFMM, F], + n2tV3: InterpreterV3[N2T_CFMM, F], + t2tV3: InterpreterV3[T2T_CFMM, F], + logs: Logs[I, F] + ): I[CFMMInterpreter[CFMMType, F]] = + logs.forService[CFMMInterpreter[CFMMType, F]].map { implicit __ => + new Tracing[F] attach new Proxy[F] + } + + final class Proxy[F[_]: Functor](implicit + n2tV1: InterpreterV1[N2T_CFMM, F], + t2tV1: InterpreterV1[T2T_CFMM, F], + n2tV3: InterpreterV3[N2T_CFMM, F], + t2tV3: InterpreterV3[T2T_CFMM, F] + ) extends CFMMInterpreter[CFMMType, F] { + + def deposit( + in: CFMMOrder[DepositType], + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + in match { + case d: DepositErgFee => + if (pool.isNative) n2tV1.deposit(d, pool) + else t2tV1.deposit(d, pool) + case d: DepositTokenFee => if (pool.isNative) n2tV3.deposit(d, pool) else t2tV3.deposit(d, pool) + } + + def redeem( + in: CFMMOrder[RedeemType], + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + in match { + case r: RedeemErgFee => + if (pool.isNative) n2tV1.redeem(r, pool) + else t2tV1.redeem(r, pool) + case r: RedeemTokenFee => if (pool.isNative) n2tV3.redeem(r, pool) else t2tV3.redeem(r, pool) + } + + def swap( + in: CFMMOrder[SwapType], + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + in match { + case s: SwapErg => + if (pool.isNative) n2tV1.swap(s, pool) + else t2tV1.swap(s, pool) + case s: SwapTokenFee => if (pool.isNative) n2tV3.swap(s, pool) else t2tV3.swap(s, pool) + } - def redeem(in: Redeem, pool: CFMMPool): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]])] = - if (pool.isNative) n2t.redeem(in, pool) - else t2t.redeem(in, pool) - - def swap(in: SwapAny, pool: CFMMPool): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]])] = - if (pool.isNative) n2t.swap(in, pool) - else t2t.swap(in, pool) } - final class CFMMInterpreterTracing[CT <: CFMMType, F[_]: FlatMap: Logging] extends CFMMInterpreter[CT, Mid[F, *]] { - - def deposit(in: Deposit, pool: CFMMPool): Mid[F, (ErgoLikeTransaction, Traced[Predicted[CFMMPool]])] = - _ >>= (r => trace"deposit(in=$in, pool=$pool) = (${r._1}, ${r._2})" as r) - - def redeem(in: Redeem, pool: CFMMPool): Mid[F, (ErgoLikeTransaction, Traced[Predicted[CFMMPool]])] = - _ >>= (r => trace"redeem(in=$in, pool=$pool) = (${r._1}, ${r._2})" as r) - - def swap(in: SwapAny, pool: CFMMPool): Mid[F, (ErgoLikeTransaction, Traced[Predicted[CFMMPool]])] = - _ >>= (r => trace"swap(in=$in, pool=$pool) = (${r._1}, ${r._2})" as r) + final private class Tracing[F[_]: Monad: Logging] extends CFMMInterpreter[CFMMType, Mid[F, *]] { + + def deposit( + in: AnyDeposit, + pool: CFMMPool + ): Mid[F, (ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + for { + _ <- info"deposit(${in.box.boxId}, ${pool.box.boxId})" + r <- _ + _ <- info"deposit(${in.box.boxId}, ${pool.box.boxId}) -> ${s"${r._1.id}"}" + } yield r + + def redeem( + in: AnyRedeem, + pool: CFMMPool + ): Mid[F, (ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + for { + _ <- info"redeem(${in.box.boxId}, ${pool.box.boxId})" + r <- _ + _ <- info"redeem(${in.box.boxId}, ${pool.box.boxId}) -> ${s"${r._1.id}"}" + } yield r + + def swap( + in: AnySwap, + pool: CFMMPool + ): Mid[F, (ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + for { + _ <- info"swap(${in.box.boxId}, ${pool.box.boxId})" + r <- _ + _ <- info"swap(${in.box.boxId}, ${pool.box.boxId}) -> ${s"${r._1.id}"}" + } yield r } } diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/CFMMInterpreterHelpers.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/CFMMInterpreterHelpers.scala index 341b53f3..0ff4ccff 100644 --- a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/CFMMInterpreterHelpers.scala +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/CFMMInterpreterHelpers.scala @@ -1,41 +1,60 @@ package org.ergoplatform.dex.executor.amm.interpreters import org.ergoplatform.ErgoBox.{NonMandatoryRegisterId, R4} +import org.ergoplatform._ import org.ergoplatform.dex.configs.MonetaryConfig import org.ergoplatform.dex.domain.AssetAmount -import org.ergoplatform.dex.domain.amm.CFMMOrder.{Swap, SwapMultiAddress} -import org.ergoplatform.dex.domain.amm.{CFMMOrder, CFMMPool} +import org.ergoplatform.dex.domain.amm.CFMMOrder.{SwapErg, SwapMultiAddress, SwapP2Pk, SwapTokenFee} +import org.ergoplatform.dex.domain.amm.CFMMPool import org.ergoplatform.dex.executor.amm.config.ExchangeConfig -import org.ergoplatform.dex.executor.amm.domain.errors.{ExecutionFailed, PriceTooHigh, PriceTooLow} -import org.ergoplatform.ergo.TokenId +import org.ergoplatform.dex.executor.amm.domain.errors.{ExecutionFailed, NegativeDexFee, PriceTooHigh, PriceTooLow} import org.ergoplatform.ergo.syntax._ -import org.ergoplatform.{ErgoAddressEncoder, ErgoBox, ErgoScriptPredef, Pay2SAddress} +import org.ergoplatform.ergo.{PrivKeyGenerator, TokenId} import sigmastate.Values.IntConstant +import sigmastate.basics.DLogProtocol.DLogProverInput import sigmastate.eval._ import sigmastate.{SInt, Values} import special.collection.Coll final class CFMMInterpreterHelpers( - exchange: ExchangeConfig, + val exchange: ExchangeConfig, monetary: MonetaryConfig )(implicit encoder: ErgoAddressEncoder) { val minerFeeProp: Values.ErgoTree = Pay2SAddress(ErgoScriptPredef.feeProposition()).script - val dexFeeProp: Values.ErgoTree = exchange.rewardAddress.toErgoTree - def swapParams(swap: CFMMOrder.SwapAny, pool: CFMMPool): Either[ExecutionFailed, (AssetAmount, AssetAmount, Long)] = { + val sk: DLogProverInput = PrivKeyGenerator.make(exchange.mnemonic)._1 + + def swapParamsErgFee(swap: SwapErg, pool: CFMMPool): Either[ExecutionFailed, (AssetAmount, AssetAmount, Long)] = { val params = swap match { - case s: Swap => s.params + case s: SwapP2Pk => s.params case s: SwapMultiAddress => s.params } - val input = params.input - val output = pool.outputAmount(input) - val dexFee = (BigInt(output.value) * params.dexFeePerTokenNum / - params.dexFeePerTokenDenom - monetary.minerFee).toLong - val maxDexFee = swap.box.value - monetary.minerFee - monetary.minBoxValue - if (output < params.minOutput) Left(PriceTooHigh(swap.poolId, params.minOutput, output)) - else if (dexFee > maxDexFee) Left(PriceTooLow(swap.poolId, maxDexFee, dexFee)) - else Right((input, output, dexFee)) + val baseAmount = params.baseAmount + val quoteAmount = pool.outputAmount(baseAmount) + val feeFactor = BigDecimal(params.dexFeePerTokenNum) / params.dexFeePerTokenDenom + val dexFee = (quoteAmount.value * feeFactor - monetary.minerFee).toLong + val maxDexFee = swap.box.value - monetary.minerFee - monetary.minBoxValue + if (quoteAmount < params.minQuoteAmount) Left(PriceTooHigh(swap.poolId, params.minQuoteAmount, quoteAmount)) + else if (dexFee > maxDexFee) Left(PriceTooLow(swap.poolId, maxDexFee, dexFee, quoteAmount.value)) + else if (dexFee < 0) Left(NegativeDexFee(swap.poolId, swap.id, dexFee)) + else Right((baseAmount, quoteAmount, dexFee)) + } + + def swapParamsTokenFee( + swap: SwapTokenFee, + pool: CFMMPool + ): Either[ExecutionFailed, (AssetAmount, AssetAmount, Long)] = { + val baseAmount: AssetAmount = swap.params.baseAmount + val quoteAmount: AssetAmount = pool.outputAmount(baseAmount) + val feeFactor: BigDecimal = BigDecimal(swap.params.dexFeePerTokenNum) / swap.params.dexFeePerTokenDenom + val dexFee: Long = (quoteAmount.value * feeFactor).toLong + val maxDexFee: Long = swap.reservedExFee + if (quoteAmount < swap.params.minQuoteAmount) + Left(PriceTooHigh(swap.poolId, swap.params.minQuoteAmount, quoteAmount)) + else if (dexFee > maxDexFee) Left(PriceTooLow(swap.poolId, maxDexFee, dexFee, quoteAmount.value)) + else if (dexFee < 0) Left(NegativeDexFee(swap.poolId, swap.id, dexFee)) + else Right((baseAmount, quoteAmount, dexFee)) } def mkTokens(tokens: (TokenId, Long)*): Coll[(ErgoBox.TokenId, Long)] = @@ -45,4 +64,19 @@ final class CFMMInterpreterHelpers( scala.Predef.Map( (R4: NonMandatoryRegisterId) -> IntConstant(pool.feeNum) ) + + def mkPoolTokens(pool: CFMMPool, amountLP: Long, amountY: Long): Coll[(ErgoBox.TokenId, Long)] = + mkTokens( + pool.poolId.value -> 1L, + pool.lp.id -> amountLP, + pool.y.id -> amountY + ) + + def mkPoolTokens(pool: CFMMPool, amountLP: Long, amountX: Long, amountY: Long): Coll[(ErgoBox.TokenId, Long)] = + mkTokens( + pool.poolId.value -> 1L, + pool.lp.id -> amountLP, + pool.x.id -> amountX, + pool.y.id -> amountY + ) } diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/N2TCFMMInterpreter.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/N2TCFMMInterpreter.scala deleted file mode 100644 index 978f43aa..00000000 --- a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/N2TCFMMInterpreter.scala +++ /dev/null @@ -1,201 +0,0 @@ -package org.ergoplatform.dex.executor.amm.interpreters - -import cats.{Functor, Monad} -import org.ergoplatform._ -import org.ergoplatform.dex.configs.MonetaryConfig -import org.ergoplatform.dex.domain.amm.CFMMOrder.{Deposit, Redeem} -import org.ergoplatform.dex.domain.amm._ -import org.ergoplatform.dex.domain.{BoxInfo, NetworkContext} -import org.ergoplatform.dex.executor.amm.config.ExchangeConfig -import org.ergoplatform.dex.executor.amm.domain.errors.{ExecutionFailed, IncorrectMultiAddressSwapTree} -import org.ergoplatform.dex.executor.amm.interpreters.CFMMInterpreter.CFMMInterpreterTracing -import org.ergoplatform.dex.protocol.ErgoTreeSerializer -import org.ergoplatform.dex.protocol.amm.AMMContracts -import org.ergoplatform.dex.protocol.amm.AMMType.N2T_CFMM -import org.ergoplatform.ergo.BoxId -import org.ergoplatform.ergo.services.explorer.ErgoExplorer -import org.ergoplatform.ergo.state.{Predicted, Traced} -import org.ergoplatform.ergo.syntax._ -import sigmastate.interpreter.ProverResult -import tofu.logging.Logs -import tofu.syntax.embed._ -import tofu.syntax.monadic._ -import tofu.syntax.raise._ -import cats.syntax.either._ - -final class N2TCFMMInterpreter[F[_]: Monad: ExecutionFailed.Raise]( - exchange: ExchangeConfig, - execution: MonetaryConfig, - ctx: NetworkContext -)(implicit - contracts: AMMContracts[N2T_CFMM], - encoder: ErgoAddressEncoder -) extends CFMMInterpreter[N2T_CFMM, F] { - - val helpers = new CFMMInterpreterHelpers(exchange, execution) - - import helpers._ - - def deposit(deposit: Deposit, pool: CFMMPool): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]])] = { - val poolBox0 = pool.box - val depositBox = deposit.box - val depositIn = new Input(depositBox.boxId.toErgo, ProverResult.empty) - val poolIn = new Input(poolBox0.boxId.toErgo, ProverResult.empty) - val (inX, inY) = (deposit.params.inX, deposit.params.inY) - val (rewardLP, change) = pool.rewardLP(inX, inY) - val (changeX, changeY) = - (change.filter(_.id == inX.id).map(_.value).sum, change.filter(_.id == inY.id).map(_.value).sum) - val poolBox1 = new ErgoBoxCandidate( - value = poolBox0.value + inX.value - changeX, - ergoTree = contracts.pool, - creationHeight = ctx.currentHeight, - additionalTokens = mkPoolTokens( - pool, - amountLP = pool.lp.value - rewardLP.value, - amountY = pool.y.value + inY.value - changeY - ), - additionalRegisters = mkPoolRegs(pool) - ) - val minerFee = execution.minerFee min deposit.maxMinerFee - val minerFeeBox = new ErgoBoxCandidate(minerFee, minerFeeProp, ctx.currentHeight) - val dexFee = deposit.params.dexFee - minerFee - val dexFeeBox = new ErgoBoxCandidate(dexFee, dexFeeProp, ctx.currentHeight) - - val returnBox = new ErgoBoxCandidate( - value = depositBox.value - inX.value - minerFeeBox.value - dexFeeBox.value + changeX, - ergoTree = deposit.params.redeemer.toErgoTree, - creationHeight = ctx.currentHeight, - additionalTokens = - if (changeY > 0) mkTokens(rewardLP.id -> rewardLP.value, inY.id -> changeY) - else mkTokens(rewardLP.id -> rewardLP.value) - ) - val inputs = Vector(poolIn, depositIn) - val outs = Vector(poolBox1, returnBox, dexFeeBox, minerFeeBox) - val tx = ErgoLikeTransaction(inputs, outs) - val nextPoolBox = poolBox1.toBox(tx.id, 0) - val boxInfo = BoxInfo(BoxId.fromErgo(nextPoolBox.id), nextPoolBox.value) - val nextPool = pool.deposit(inX, inY, boxInfo) - (tx, nextPool).pure - } - - def redeem(redeem: Redeem, pool: CFMMPool): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]])] = { - val poolBox0 = pool.box - val redeemBox = redeem.box - val redeemIn = new Input(redeemBox.boxId.toErgo, ProverResult.empty) - val poolIn = new Input(poolBox0.boxId.toErgo, ProverResult.empty) - val inLP = redeem.params.lp - val (shareX, shareY) = pool.shares(inLP) - val poolBox1 = new ErgoBoxCandidate( - value = poolBox0.value - shareX.value, - ergoTree = contracts.pool, - creationHeight = ctx.currentHeight, - additionalTokens = mkPoolTokens( - pool, - amountLP = pool.lp.value + inLP.value, - amountY = pool.y.value - shareY.value - ), - additionalRegisters = mkPoolRegs(pool) - ) - val minerFee = execution.minerFee min redeem.maxMinerFee - val minerFeeBox = new ErgoBoxCandidate(minerFee, minerFeeProp, ctx.currentHeight) - val dexFee = redeem.params.dexFee - minerFee - val dexFeeBox = new ErgoBoxCandidate(dexFee, dexFeeProp, ctx.currentHeight) - val returnBox = new ErgoBoxCandidate( - value = redeemBox.value + shareX.value - minerFeeBox.value - dexFeeBox.value, - ergoTree = redeem.params.redeemer.toErgoTree, - creationHeight = ctx.currentHeight, - additionalTokens = mkTokens(shareY.id -> shareY.value) - ) - val inputs = Vector(poolIn, redeemIn) - val outs = Vector(poolBox1, returnBox, dexFeeBox, minerFeeBox) - val tx = ErgoLikeTransaction(inputs, outs) - val nextPoolBox = poolBox1.toBox(tx.id, 0) - val boxInfo = BoxInfo(BoxId.fromErgo(nextPoolBox.id), nextPoolBox.value) - val nextPool = pool.redeem(inLP, boxInfo) - (tx, nextPool).pure - } - - def swap(swap: CFMMOrder.SwapAny, pool: CFMMPool): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]])] = - swapParams(swap, pool).toRaise.flatMap { case (input, output, dexFee) => - (swap match { - case CFMMOrder.Swap(_, maxMinerFee, _, params, _) => - (maxMinerFee, params.input, params.redeemer.toErgoTree, params.minOutput).pure[F] - case CFMMOrder.SwapMultiAddress(_, maxMinerFee, _, params, box) => - Either - .catchNonFatal(ErgoTreeSerializer.default.deserialize(params.redeemer)) - .leftMap(s => IncorrectMultiAddressSwapTree(pool.poolId, box.boxId, params.redeemer, s.getMessage)) - .toRaise - .map(tree => (maxMinerFee, params.input, tree, params.minOutput)) - }).map { case (maxMinerFee, inputSwap, redeemer, minOutput) => - val poolBox0 = pool.box - val swapBox = swap.box - val swapIn = new Input(swapBox.boxId.toErgo, ProverResult.empty) - val poolIn = new Input(poolBox0.boxId.toErgo, ProverResult.empty) - val (deltaX, deltaY) = - if (input.id == pool.x.id) input.value -> -output.value - else -output.value -> input.value - val poolBox1 = new ErgoBoxCandidate( - value = poolBox0.value + deltaX, - ergoTree = contracts.pool, - creationHeight = ctx.currentHeight, - additionalTokens = mkPoolTokens( - pool, - amountLP = pool.lp.value, - amountY = pool.y.value + deltaY - ), - additionalRegisters = mkPoolRegs(pool) - ) - val minerFee = execution.minerFee min maxMinerFee - val minerFeeBox = new ErgoBoxCandidate(minerFee, minerFeeProp, ctx.currentHeight) - val dexFeeBox = if (dexFee > 0) Some(new ErgoBoxCandidate(dexFee, dexFeeProp, ctx.currentHeight)) else None - val dexFeeBoxValue = dexFeeBox.map(_.value).getOrElse(0L) - val rewardBox = - if (inputSwap.isNative) - new ErgoBoxCandidate( - value = swapBox.value - input.value - minerFeeBox.value - dexFeeBoxValue, - ergoTree = redeemer, - creationHeight = ctx.currentHeight, - additionalTokens = mkTokens(minOutput.id -> output.value) - ) - else - new ErgoBoxCandidate( - value = swapBox.value + output.value - minerFeeBox.value - dexFee, - ergoTree = redeemer, - creationHeight = ctx.currentHeight - ) - val inputs = Vector(poolIn, swapIn) - val outs = Vector(poolBox1, rewardBox) ++ dexFeeBox ++ Vector(minerFeeBox) - val tx = ErgoLikeTransaction(inputs, outs) - val nextPoolBox = poolBox1.toBox(tx.id, 0) - val boxInfo = BoxInfo(BoxId.fromErgo(nextPoolBox.id), nextPoolBox.value) - val nextPool = pool.swap(input, boxInfo) - tx -> nextPool - } - } - - private def mkPoolTokens(pool: CFMMPool, amountLP: Long, amountY: Long) = - mkTokens( - pool.poolId.value -> 1L, - pool.lp.id -> amountLP, - pool.y.id -> amountY - ) -} - -object N2TCFMMInterpreter { - - def make[I[_]: Functor, F[_]: Monad: ExecutionFailed.Raise: ExchangeConfig.Has: MonetaryConfig.Has](implicit - network: ErgoExplorer[F], - contracts: AMMContracts[N2T_CFMM], - encoder: ErgoAddressEncoder, - logs: Logs[I, F] - ): I[CFMMInterpreter[N2T_CFMM, F]] = - logs.forService[CFMMInterpreter[N2T_CFMM, F]].map { implicit l => - ( - for { - exchange <- ExchangeConfig.access - execution <- MonetaryConfig.access - networkCtx <- NetworkContext.make - } yield new CFMMInterpreterTracing[N2T_CFMM, F] attach new N2TCFMMInterpreter(exchange, execution, networkCtx) - ).embed - } -} diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/T2TCFMMInterpreter.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/T2TCFMMInterpreter.scala deleted file mode 100644 index 446086c4..00000000 --- a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/T2TCFMMInterpreter.scala +++ /dev/null @@ -1,198 +0,0 @@ -package org.ergoplatform.dex.executor.amm.interpreters - -import cats.{Functor, Monad} -import org.ergoplatform._ -import org.ergoplatform.dex.configs.MonetaryConfig -import org.ergoplatform.dex.domain.amm.CFMMOrder.{Deposit, Redeem} -import org.ergoplatform.dex.domain.amm._ -import org.ergoplatform.ergo.state.{Predicted, Traced} -import org.ergoplatform.dex.domain.{BoxInfo, NetworkContext} -import org.ergoplatform.dex.executor.amm.config.ExchangeConfig -import org.ergoplatform.dex.executor.amm.domain.errors.{ExecutionFailed, IncorrectMultiAddressSwapTree} -import org.ergoplatform.dex.executor.amm.interpreters.CFMMInterpreter.CFMMInterpreterTracing -import org.ergoplatform.dex.protocol.ErgoTreeSerializer -import org.ergoplatform.dex.protocol.amm.AMMContracts -import org.ergoplatform.dex.protocol.amm.AMMType.T2T_CFMM -import org.ergoplatform.ergo.syntax._ -import org.ergoplatform.ergo.BoxId -import org.ergoplatform.ergo.services.explorer.ErgoExplorer -import sigmastate.interpreter.ProverResult -import tofu.logging.Logs -import tofu.syntax.embed._ -import tofu.syntax.monadic._ -import tofu.syntax.raise._ -import cats.syntax.either._ - -final class T2TCFMMInterpreter[F[_]: Monad: ExecutionFailed.Raise]( - exchange: ExchangeConfig, - monetary: MonetaryConfig, - ctx: NetworkContext -)(implicit - contracts: AMMContracts[T2T_CFMM], - encoder: ErgoAddressEncoder -) extends CFMMInterpreter[T2T_CFMM, F] { - - val helpers = new CFMMInterpreterHelpers(exchange, monetary) - import helpers._ - - def deposit(deposit: Deposit, pool: CFMMPool): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]])] = { - val poolBox0 = pool.box - val depositBox = deposit.box - val depositIn = new Input(depositBox.boxId.toErgo, ProverResult.empty) - val poolIn = new Input(poolBox0.boxId.toErgo, ProverResult.empty) - val (inX, inY) = (deposit.params.inX, deposit.params.inY) - val (rewardLP, change) = pool.rewardLP(inX, inY) - val (changeX, changeY) = - (change.filter(_.id == inX.id).map(_.value).sum, change.filter(_.id == inY.id).map(_.value).sum) - val poolBox1 = new ErgoBoxCandidate( - value = poolBox0.value, - ergoTree = contracts.pool, - creationHeight = ctx.currentHeight, - additionalTokens = mkPoolTokens( - pool, - amountLP = pool.lp.value - rewardLP.value, - amountX = pool.x.value + inX.value - changeX, - amountY = pool.y.value + inY.value - changeY - ), - additionalRegisters = mkPoolRegs(pool) - ) - val minerFee = monetary.minerFee min deposit.maxMinerFee - val minerFeeBox = new ErgoBoxCandidate(minerFee, minerFeeProp, ctx.currentHeight) - val dexFee = deposit.params.dexFee - minerFee - val dexFeeBox = new ErgoBoxCandidate(dexFee, dexFeeProp, ctx.currentHeight) - val returnBox = new ErgoBoxCandidate( - value = depositBox.value - minerFeeBox.value - dexFeeBox.value, - ergoTree = deposit.params.redeemer.toErgoTree, - creationHeight = ctx.currentHeight, - additionalTokens = - if (changeX > 0) mkTokens(rewardLP.id -> rewardLP.value, inX.id -> changeX) - else if (changeY > 0) mkTokens(rewardLP.id -> rewardLP.value, inY.id -> changeY) - else mkTokens(rewardLP.id -> rewardLP.value) - ) - val inputs = Vector(poolIn, depositIn) - val outs = Vector(poolBox1, returnBox, dexFeeBox, minerFeeBox) - val tx = ErgoLikeTransaction(inputs, outs) - val nextPoolBox = poolBox1.toBox(tx.id, 0) - val boxInfo = BoxInfo(BoxId.fromErgo(nextPoolBox.id), nextPoolBox.value) - val nextPool = pool.deposit(inX, inY, boxInfo) - (tx, nextPool).pure - } - - def redeem(redeem: Redeem, pool: CFMMPool): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]])] = { - val poolBox0 = pool.box - val redeemBox = redeem.box - val redeemIn = new Input(redeemBox.boxId.toErgo, ProverResult.empty) - val poolIn = new Input(poolBox0.boxId.toErgo, ProverResult.empty) - val inLP = redeem.params.lp - val (shareX, shareY) = pool.shares(inLP) - val poolBox1 = new ErgoBoxCandidate( - value = poolBox0.value, - ergoTree = contracts.pool, - creationHeight = ctx.currentHeight, - additionalTokens = mkPoolTokens( - pool, - amountLP = pool.lp.value + inLP.value, - amountX = pool.x.value - shareX.value, - amountY = pool.y.value - shareY.value - ), - additionalRegisters = mkPoolRegs(pool) - ) - val minerFee = monetary.minerFee min redeem.maxMinerFee - val minerFeeBox = new ErgoBoxCandidate(minerFee, minerFeeProp, ctx.currentHeight) - val dexFee = redeem.params.dexFee - minerFee - val dexFeeBox = new ErgoBoxCandidate(dexFee, dexFeeProp, ctx.currentHeight) - val returnBox = new ErgoBoxCandidate( - value = redeemBox.value - minerFeeBox.value - dexFeeBox.value, - ergoTree = redeem.params.redeemer.toErgoTree, - creationHeight = ctx.currentHeight, - additionalTokens = mkTokens( - shareX.id -> shareX.value, - shareY.id -> shareY.value - ) - ) - val inputs = Vector(poolIn, redeemIn) - val outs = Vector(poolBox1, returnBox, dexFeeBox, minerFeeBox) - val tx = ErgoLikeTransaction(inputs, outs) - val nextPoolBox = poolBox1.toBox(tx.id, 0) - val boxInfo = BoxInfo(BoxId.fromErgo(nextPoolBox.id), nextPoolBox.value) - val nextPool = pool.redeem(inLP, boxInfo) - (tx, nextPool).pure - } - - def swap(swap: CFMMOrder.SwapAny, pool: CFMMPool): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]])] = - swapParams(swap, pool).toRaise.flatMap { case (input, output, dexFee) => - (swap match { - case CFMMOrder.Swap(_, maxMinerFee, _, params, _) => - (maxMinerFee, params.redeemer.toErgoTree, params.minOutput).pure[F] - case CFMMOrder.SwapMultiAddress(_, maxMinerFee, _, params, box) => - Either - .catchNonFatal(ErgoTreeSerializer.default.deserialize(params.redeemer)) - .leftMap(s => IncorrectMultiAddressSwapTree(pool.poolId, box.boxId, params.redeemer, s.getMessage)) - .toRaise - .map(tree => (maxMinerFee, tree, params.minOutput)) - }).map { case (maxMinerFee, redeemer, minOutput) => - val poolBox0 = pool.box - val swapBox = swap.box - val swapIn = new Input(swapBox.boxId.toErgo, ProverResult.empty) - val poolIn = new Input(poolBox0.boxId.toErgo, ProverResult.empty) - val (deltaX, deltaY) = - if (input.id == pool.x.id) input.value -> -output.value - else -output.value -> input.value - val poolBox1 = new ErgoBoxCandidate( - value = poolBox0.value, - ergoTree = contracts.pool, - creationHeight = ctx.currentHeight, - additionalTokens = mkPoolTokens( - pool, - amountLP = pool.lp.value, - amountX = pool.x.value + deltaX, - amountY = pool.y.value + deltaY - ), - additionalRegisters = mkPoolRegs(pool) - ) - val minerFee = monetary.minerFee min maxMinerFee - val minerFeeBox = new ErgoBoxCandidate(minerFee, minerFeeProp, ctx.currentHeight) - val dexFeeBox = new ErgoBoxCandidate(dexFee, dexFeeProp, ctx.currentHeight) - val rewardBox = new ErgoBoxCandidate( - value = swapBox.value - minerFeeBox.value - dexFeeBox.value, - ergoTree = redeemer, - creationHeight = ctx.currentHeight, - additionalTokens = mkTokens(minOutput.id -> output.value) - ) - val inputs = Vector(poolIn, swapIn) - val outs = Vector(poolBox1, rewardBox, dexFeeBox, minerFeeBox) - val tx = ErgoLikeTransaction(inputs, outs) - val nextPoolBox = poolBox1.toBox(tx.id, 0) - val boxInfo = BoxInfo(BoxId.fromErgo(nextPoolBox.id), nextPoolBox.value) - val nextPool = pool.swap(input, boxInfo) - tx -> nextPool - } - } - - private def mkPoolTokens(pool: CFMMPool, amountLP: Long, amountX: Long, amountY: Long) = - mkTokens( - pool.poolId.value -> 1L, - pool.lp.id -> amountLP, - pool.x.id -> amountX, - pool.y.id -> amountY - ) -} - -object T2TCFMMInterpreter { - - def make[I[_]: Functor, F[_]: Monad: ExecutionFailed.Raise: ExchangeConfig.Has: MonetaryConfig.Has](implicit - network: ErgoExplorer[F], - contracts: AMMContracts[T2T_CFMM], - encoder: ErgoAddressEncoder, - logs: Logs[I, F] - ): I[CFMMInterpreter[T2T_CFMM, F]] = - logs.forService[CFMMInterpreter[T2T_CFMM, F]].map { implicit l => - ( - for { - exchange <- ExchangeConfig.access - execution <- MonetaryConfig.access - networkCtx <- NetworkContext.make - } yield new CFMMInterpreterTracing[T2T_CFMM, F] attach new T2TCFMMInterpreter(exchange, execution, networkCtx) - ).embed - } -} diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v1/InterpreterV1.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v1/InterpreterV1.scala new file mode 100644 index 00000000..69e1586d --- /dev/null +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v1/InterpreterV1.scala @@ -0,0 +1,62 @@ +package org.ergoplatform.dex.executor.amm.interpreters.v1 + +import cats.FlatMap +import derevo.derive +import org.ergoplatform.ErgoLikeTransaction +import org.ergoplatform.dex.domain.DexOperatorOutput +import org.ergoplatform.dex.domain.amm.CFMMOrder.{DepositErgFee, RedeemErgFee, SwapErg} +import org.ergoplatform.dex.domain.amm.CFMMPool +import org.ergoplatform.dex.protocol.amm.AMMType.CFMMType +import org.ergoplatform.ergo.domain.Output +import org.ergoplatform.ergo.state.{Predicted, Traced} +import tofu.higherKind.Mid +import tofu.higherKind.derived.representableK +import tofu.logging.Logging +import tofu.syntax.logging._ +import tofu.syntax.monadic._ +import org.ergoplatform.dex.protocol.instances._ + +@derive(representableK) +trait InterpreterV1[CT <: CFMMType, F[_]] { + + def deposit( + deposit: DepositErgFee, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] + + def redeem( + redeem: RedeemErgFee, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] + + def swap( + swap: SwapErg, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] + +} + +object InterpreterV1 { + + final class InterpreterTracing[CT <: CFMMType, F[_]: FlatMap: Logging](orderType: String) + extends InterpreterV1[CT, Mid[F, *]] { + + def deposit( + deposit: DepositErgFee, + pool: CFMMPool + ): Mid[F, (ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + _ >>= (r => info"deposit $orderType (in=$deposit, pool=$pool) = (${r._1}, ${r._2}, ${r._3})" as r) + + def redeem( + redeem: RedeemErgFee, + pool: CFMMPool + ): Mid[F, (ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + _ >>= (r => info"redeem $orderType (in=$redeem, pool=$pool) = (${r._1}, ${r._2}, ${r._3})" as r) + + def swap( + swap: SwapErg, + pool: CFMMPool + ): Mid[F, (ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + _ >>= (r => info"swap $orderType (in=$swap, pool=$pool) = (${r._1}, ${r._2}, ${r._3})" as r) + } +} diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v1/N2TCFMMInterpreter.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v1/N2TCFMMInterpreter.scala new file mode 100644 index 00000000..b7103efc --- /dev/null +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v1/N2TCFMMInterpreter.scala @@ -0,0 +1,254 @@ +package org.ergoplatform.dex.executor.amm.interpreters.v1 + +import cats.syntax.either._ +import cats.{Functor, Monad} +import org.ergoplatform._ +import org.ergoplatform.dex.configs.MonetaryConfig +import org.ergoplatform.dex.domain.amm.CFMMOrder._ +import org.ergoplatform.dex.domain.amm._ +import org.ergoplatform.dex.domain.{BoxInfo, DexOperatorOutput, NetworkContext} +import org.ergoplatform.dex.executor.amm.config.ExchangeConfig +import org.ergoplatform.dex.executor.amm.domain.errors._ +import org.ergoplatform.dex.executor.amm.interpreters.CFMMInterpreterHelpers +import InterpreterV1.InterpreterTracing +import org.ergoplatform.dex.executor.amm.services.DexOutputResolver +import org.ergoplatform.dex.protocol.ErgoTreeSerializer +import org.ergoplatform.dex.protocol.amm.AMMContracts +import org.ergoplatform.dex.protocol.amm.AMMType.N2T_CFMM +import org.ergoplatform.ergo.BoxId +import org.ergoplatform.ergo.domain.Output +import org.ergoplatform.ergo.services.explorer.ErgoExplorer +import org.ergoplatform.ergo.state.{Predicted, Traced} +import org.ergoplatform.ergo.syntax._ +import org.ergoplatform.wallet.interpreter.ErgoUnsafeProver +import sigmastate.interpreter.ProverResult +import tofu.logging.Logs +import tofu.syntax.embed._ +import tofu.syntax.monadic._ +import tofu.syntax.raise._ + +final class N2TCFMMInterpreter[F[_]: Monad: ExecutionFailed.Raise]( + exchange: ExchangeConfig, + execution: MonetaryConfig, + ctx: NetworkContext +)(implicit + contracts: AMMContracts[N2T_CFMM], + encoder: ErgoAddressEncoder, + resolver: DexOutputResolver[F] +) extends InterpreterV1[N2T_CFMM, F] { + + val helpers = new CFMMInterpreterHelpers(exchange, execution) + + import helpers._ + + def deposit( + deposit: DepositErgFee, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + resolver.getLatest + .flatMap(_.orRaise[F](EmptyOutputForDexTokenFee(pool.poolId, deposit.box.boxId))) + .map { dexFeeOutput => + val poolBox0 = pool.box + val depositBox = deposit.box + val depositIn = new Input(depositBox.boxId.toErgo, ProverResult.empty) + val poolIn = new Input(poolBox0.boxId.toErgo, ProverResult.empty) + val dexFeeIn = new Input(dexFeeOutput.boxId.toErgo, ProverResult.empty) + val (inX, inY) = (deposit.params.inX, deposit.params.inY) + val (rewardLP, change) = pool.rewardLP(inX, inY) + val (changeX, changeY) = + (change.filter(_.id == inX.id).map(_.value).sum, change.filter(_.id == inY.id).map(_.value).sum) + val poolBox1 = new ErgoBoxCandidate( + value = poolBox0.value + inX.value - changeX, + ergoTree = contracts.pool, + creationHeight = ctx.currentHeight, + additionalTokens = mkPoolTokens( + pool, + amountLP = pool.lp.value - rewardLP.value, + amountY = pool.y.value + inY.value - changeY + ), + additionalRegisters = mkPoolRegs(pool) + ) + + val minerFee = execution.minerFee min deposit.maxMinerFee + val minerFeeBox = new ErgoBoxCandidate(minerFee, minerFeeProp, ctx.currentHeight) + + val dexFee = deposit.params.dexFee - minerFee + + val dexFeeBox = new ErgoBoxCandidate( + dexFeeOutput.value + dexFee, + P2PKAddress(sk.publicImage).script, + ctx.currentHeight, + additionalTokens = mkTokens(dexFeeOutput.assets.map(asset => asset.tokenId -> asset.amount): _*) + ) + + val returnBox = new ErgoBoxCandidate( + value = depositBox.value - inX.value - minerFeeBox.value - dexFee + changeX, + ergoTree = deposit.params.redeemer.toErgoTree, + creationHeight = ctx.currentHeight, + additionalTokens = + if (changeY > 0) mkTokens(rewardLP.id -> rewardLP.value, inY.id -> changeY) + else mkTokens(rewardLP.id -> rewardLP.value) + ) + val inputs = Vector(poolIn, depositIn, dexFeeIn) + val outs = Vector(poolBox1, returnBox, dexFeeBox, minerFeeBox) + val tx = ErgoUnsafeProver.prove(UnsignedErgoLikeTransaction(inputs, outs), sk) + val nextPoolBox = poolBox1.toBox(tx.id, 0) + val boxInfo = BoxInfo(BoxId.fromErgo(nextPoolBox.id), nextPoolBox.value) + val nextPool = pool.deposit(inX, inY, boxInfo) + val predictedDexOutput = Output.predicted(Output.fromErgoBox(tx.outputs(2)), dexFeeOutput.boxId) + (tx, nextPool, predictedDexOutput) + } + + def redeem( + redeem: RedeemErgFee, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + resolver.getLatest + .flatMap(_.orRaise[F](EmptyOutputForDexTokenFee(pool.poolId, redeem.box.boxId))) + .map { dexFeeOutput => + val poolBox0 = pool.box + val redeemBox = redeem.box + val redeemIn = new Input(redeemBox.boxId.toErgo, ProverResult.empty) + val poolIn = new Input(poolBox0.boxId.toErgo, ProverResult.empty) + val dexFeeIn = new Input(dexFeeOutput.boxId.toErgo, ProverResult.empty) + val inLP = redeem.params.lp + val (shareX, shareY) = pool.shares(inLP) + val poolBox1 = new ErgoBoxCandidate( + value = poolBox0.value - shareX.value, + ergoTree = contracts.pool, + creationHeight = ctx.currentHeight, + additionalTokens = mkPoolTokens( + pool, + amountLP = pool.lp.value + inLP.value, + amountY = pool.y.value - shareY.value + ), + additionalRegisters = mkPoolRegs(pool) + ) + val minerFee = execution.minerFee min redeem.maxMinerFee + val minerFeeBox = new ErgoBoxCandidate(minerFee, minerFeeProp, ctx.currentHeight) + val dexFee = redeem.params.dexFee - minerFee + + val dexFeeBox = new ErgoBoxCandidate( + dexFeeOutput.value + dexFee, + P2PKAddress(sk.publicImage).script, + ctx.currentHeight, + additionalTokens = mkTokens(dexFeeOutput.assets.map(asset => asset.tokenId -> asset.amount): _*) + ) + val returnBox = new ErgoBoxCandidate( + value = redeemBox.value + shareX.value - minerFeeBox.value - dexFee, + ergoTree = redeem.params.redeemer.toErgoTree, + creationHeight = ctx.currentHeight, + additionalTokens = mkTokens(shareY.id -> shareY.value) + ) + val inputs = Vector(poolIn, redeemIn, dexFeeIn) + val outs = Vector(poolBox1, returnBox, dexFeeBox, minerFeeBox) + val tx = ErgoUnsafeProver.prove(UnsignedErgoLikeTransaction(inputs, outs), sk) + val nextPoolBox = poolBox1.toBox(tx.id, 0) + val boxInfo = BoxInfo(BoxId.fromErgo(nextPoolBox.id), nextPoolBox.value) + val nextPool = pool.redeem(inLP, boxInfo) + val predictedDexOutput = Output.predicted(Output.fromErgoBox(tx.outputs(2)), dexFeeOutput.boxId) + (tx, nextPool, predictedDexOutput) + } + + def swap( + swap: SwapErg, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + resolver.getLatest + .flatMap(_.orRaise[F](EmptyOutputForDexTokenFee(pool.poolId, swap.box.boxId))) + .flatMap { dexFeeOutput => + swapParamsErgFee(swap, pool).toRaise.flatMap { case (input, output, dexFee) => + (swap match { + case s: SwapP2Pk => + (s.maxMinerFee, s.params.baseAmount, s.params.redeemer.toErgoTree, s.params.minQuoteAmount).pure[F] + case s: SwapMultiAddress => + Either + .catchNonFatal(ErgoTreeSerializer.default.deserialize(s.params.redeemer)) + .leftMap(err => IncorrectMultiAddressTree(pool.poolId, s.box.boxId, s.params.redeemer, err.getMessage)) + .toRaise + .map(tree => (s.maxMinerFee, s.params.baseAmount, tree, s.params.minQuoteAmount)) + }).map { case (maxMinerFee, inputSwap, redeemer, minOutput) => + val poolBox0 = pool.box + val swapBox = swap.box + val swapIn = new Input(swapBox.boxId.toErgo, ProverResult.empty) + val poolIn = new Input(poolBox0.boxId.toErgo, ProverResult.empty) + val dexFeeIn = new Input(dexFeeOutput.boxId.toErgo, ProverResult.empty) + val (deltaX, deltaY) = + if (input.id == pool.x.id) input.value -> -output.value + else -output.value -> input.value + val poolBox1 = new ErgoBoxCandidate( + value = poolBox0.value + deltaX, + ergoTree = contracts.pool, + creationHeight = ctx.currentHeight, + additionalTokens = mkPoolTokens( + pool, + amountLP = pool.lp.value, + amountY = pool.y.value + deltaY + ), + additionalRegisters = mkPoolRegs(pool) + ) + val minerFee = execution.minerFee min maxMinerFee + val minerFeeBox = new ErgoBoxCandidate(minerFee, minerFeeProp, ctx.currentHeight) + val dexFeeBox = new ErgoBoxCandidate( + dexFeeOutput.value + dexFee, + P2PKAddress(sk.publicImage).script, + ctx.currentHeight, + additionalTokens = mkTokens(dexFeeOutput.assets.map(asset => asset.tokenId -> asset.amount): _*) + ) + val rewardBox = + if (inputSwap.isNative) + new ErgoBoxCandidate( + value = swapBox.value - input.value - minerFeeBox.value - dexFee, + ergoTree = redeemer, + creationHeight = ctx.currentHeight, + additionalTokens = mkTokens(minOutput.id -> output.value) + ) + else + new ErgoBoxCandidate( + value = swapBox.value + output.value - minerFeeBox.value - dexFee, + ergoTree = redeemer, + creationHeight = ctx.currentHeight + ) + val inputs = Vector(poolIn, swapIn, dexFeeIn) + val outs = Vector(poolBox1, rewardBox, dexFeeBox, minerFeeBox) + val tx = ErgoUnsafeProver.prove(UnsignedErgoLikeTransaction(inputs, outs), sk) + val nextPoolBox = poolBox1.toBox(tx.id, 0) + val boxInfo = BoxInfo(BoxId.fromErgo(nextPoolBox.id), nextPoolBox.value) + val nextPool = pool.swap(input, boxInfo) + val predictedDexOutput = Output.predicted(Output.fromErgoBox(tx.outputs(2)), dexFeeOutput.boxId) + (tx, nextPool, predictedDexOutput) + } + } + } + + private def mkPoolTokens(pool: CFMMPool, amountLP: Long, amountY: Long) = + mkTokens( + pool.poolId.value -> 1L, + pool.lp.id -> amountLP, + pool.y.id -> amountY + ) +} + +object N2TCFMMInterpreter { + + def make[I[_]: Functor, F[_]: Monad: ExecutionFailed.Raise: ExchangeConfig.Has: MonetaryConfig.Has](implicit + network: ErgoExplorer[F], + contracts: AMMContracts[N2T_CFMM], + encoder: ErgoAddressEncoder, + resolver: DexOutputResolver[F], + logs: Logs[I, F] + ): I[InterpreterV1[N2T_CFMM, F]] = + logs.forService[InterpreterV1[N2T_CFMM, F]].map { implicit l => + ( + for { + exchange <- ExchangeConfig.access + execution <- MonetaryConfig.access + networkCtx <- NetworkContext.make + } yield new InterpreterTracing[N2T_CFMM, F]("N2T_CFMM") attach new N2TCFMMInterpreter( + exchange, + execution, + networkCtx + ) + ).embed + } +} diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v1/T2TCFMMInterpreter.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v1/T2TCFMMInterpreter.scala new file mode 100644 index 00000000..0e51fe42 --- /dev/null +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v1/T2TCFMMInterpreter.scala @@ -0,0 +1,246 @@ +package org.ergoplatform.dex.executor.amm.interpreters.v1 + +import cats.syntax.either._ +import cats.{Functor, Monad} +import org.ergoplatform._ +import org.ergoplatform.dex.configs.MonetaryConfig +import org.ergoplatform.dex.domain.amm.CFMMOrder._ +import org.ergoplatform.dex.domain.amm._ +import org.ergoplatform.dex.domain.{BoxInfo, DexOperatorOutput, NetworkContext} +import org.ergoplatform.dex.executor.amm.config.ExchangeConfig +import org.ergoplatform.dex.executor.amm.domain.errors._ +import InterpreterV1.InterpreterTracing +import org.ergoplatform.dex.executor.amm.interpreters.CFMMInterpreterHelpers +import org.ergoplatform.dex.executor.amm.services.DexOutputResolver +import org.ergoplatform.dex.protocol.ErgoTreeSerializer +import org.ergoplatform.dex.protocol.amm.AMMContracts +import org.ergoplatform.dex.protocol.amm.AMMType.T2T_CFMM +import org.ergoplatform.ergo.BoxId +import org.ergoplatform.ergo.domain.Output +import org.ergoplatform.ergo.services.explorer.ErgoExplorer +import org.ergoplatform.ergo.state.{Predicted, Traced} +import org.ergoplatform.ergo.syntax._ +import org.ergoplatform.wallet.interpreter.ErgoUnsafeProver +import sigmastate.interpreter.ProverResult +import tofu.logging.Logs +import tofu.syntax.embed._ +import tofu.syntax.monadic._ +import tofu.syntax.raise._ + +final class T2TCFMMInterpreter[F[_]: Monad: ExecutionFailed.Raise]( + exchange: ExchangeConfig, + monetary: MonetaryConfig, + ctx: NetworkContext +)(implicit + contracts: AMMContracts[T2T_CFMM], + encoder: ErgoAddressEncoder, + resolver: DexOutputResolver[F] +) extends InterpreterV1[T2T_CFMM, F] { + + val helpers = new CFMMInterpreterHelpers(exchange, monetary) + import helpers._ + + def deposit( + deposit: DepositErgFee, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + resolver.getLatest + .flatMap(_.orRaise[F](EmptyOutputForDexTokenFee(pool.poolId, deposit.box.boxId))) + .map { dexFeeOutput => + val poolBox0 = pool.box + val depositBox = deposit.box + val depositIn = new Input(depositBox.boxId.toErgo, ProverResult.empty) + val poolIn = new Input(poolBox0.boxId.toErgo, ProverResult.empty) + val dexFeeIn = new Input(dexFeeOutput.boxId.toErgo, ProverResult.empty) + val (inX, inY) = (deposit.params.inX, deposit.params.inY) + val (rewardLP, change) = pool.rewardLP(inX, inY) + val (changeX, changeY) = + (change.filter(_.id == inX.id).map(_.value).sum, change.filter(_.id == inY.id).map(_.value).sum) + val poolBox1 = new ErgoBoxCandidate( + value = poolBox0.value, + ergoTree = contracts.pool, + creationHeight = ctx.currentHeight, + additionalTokens = mkPoolTokens( + pool, + amountLP = pool.lp.value - rewardLP.value, + amountX = pool.x.value + inX.value - changeX, + amountY = pool.y.value + inY.value - changeY + ), + additionalRegisters = mkPoolRegs(pool) + ) + val minerFee = monetary.minerFee min deposit.maxMinerFee + val minerFeeBox = new ErgoBoxCandidate(minerFee, minerFeeProp, ctx.currentHeight) + val dexFee = deposit.params.dexFee - minerFee + val dexFeeBox = new ErgoBoxCandidate( + dexFeeOutput.value + dexFee, + P2PKAddress(sk.publicImage).script, + ctx.currentHeight, + additionalTokens = mkTokens(dexFeeOutput.assets.map(asset => asset.tokenId -> asset.amount): _*) + ) + val returnBox = new ErgoBoxCandidate( + value = depositBox.value - minerFeeBox.value - dexFee, + ergoTree = deposit.params.redeemer.toErgoTree, + creationHeight = ctx.currentHeight, + additionalTokens = + if (changeX > 0) mkTokens(rewardLP.id -> rewardLP.value, inX.id -> changeX) + else if (changeY > 0) mkTokens(rewardLP.id -> rewardLP.value, inY.id -> changeY) + else mkTokens(rewardLP.id -> rewardLP.value) + ) + val inputs = Vector(poolIn, depositIn, dexFeeIn) + val outs = Vector(poolBox1, returnBox, dexFeeBox, minerFeeBox) + val tx = ErgoUnsafeProver.prove(UnsignedErgoLikeTransaction(inputs, outs), sk) + val nextPoolBox = poolBox1.toBox(tx.id, 0) + val boxInfo = BoxInfo(BoxId.fromErgo(nextPoolBox.id), nextPoolBox.value) + val nextPool = pool.deposit(inX, inY, boxInfo) + val predictedDexOutput = Output.predicted(Output.fromErgoBox(tx.outputs(2)), dexFeeOutput.boxId) + (tx, nextPool, predictedDexOutput) + } + + def redeem( + redeem: RedeemErgFee, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = resolver.getLatest + .flatMap(_.orRaise[F](EmptyOutputForDexTokenFee(pool.poolId, redeem.box.boxId))) + .map { dexFeeOutput => + val poolBox0 = pool.box + val redeemBox = redeem.box + val redeemIn = new Input(redeemBox.boxId.toErgo, ProverResult.empty) + val poolIn = new Input(poolBox0.boxId.toErgo, ProverResult.empty) + val dexFeeIn = new Input(dexFeeOutput.boxId.toErgo, ProverResult.empty) + val inLP = redeem.params.lp + val (shareX, shareY) = pool.shares(inLP) + val poolBox1 = new ErgoBoxCandidate( + value = poolBox0.value, + ergoTree = contracts.pool, + creationHeight = ctx.currentHeight, + additionalTokens = mkPoolTokens( + pool, + amountLP = pool.lp.value + inLP.value, + amountX = pool.x.value - shareX.value, + amountY = pool.y.value - shareY.value + ), + additionalRegisters = mkPoolRegs(pool) + ) + val minerFee = monetary.minerFee min redeem.maxMinerFee + val minerFeeBox = new ErgoBoxCandidate(minerFee, minerFeeProp, ctx.currentHeight) + val dexFee = redeem.params.dexFee - minerFee + val dexFeeBox = new ErgoBoxCandidate( + dexFeeOutput.value + dexFee, + P2PKAddress(sk.publicImage).script, + ctx.currentHeight, + additionalTokens = mkTokens(dexFeeOutput.assets.map(asset => asset.tokenId -> asset.amount): _*) + ) + val returnBox = new ErgoBoxCandidate( + value = redeemBox.value - minerFeeBox.value - dexFee, + ergoTree = redeem.params.redeemer.toErgoTree, + creationHeight = ctx.currentHeight, + additionalTokens = mkTokens( + shareX.id -> shareX.value, + shareY.id -> shareY.value + ) + ) + val inputs = Vector(poolIn, redeemIn, dexFeeIn) + val outs = Vector(poolBox1, returnBox, dexFeeBox, minerFeeBox) + val tx = ErgoUnsafeProver.prove(UnsignedErgoLikeTransaction(inputs, outs), sk) + val nextPoolBox = poolBox1.toBox(tx.id, 0) + val boxInfo = BoxInfo(BoxId.fromErgo(nextPoolBox.id), nextPoolBox.value) + val nextPool = pool.redeem(inLP, boxInfo) + val predictedDexOutput = Output.predicted(Output.fromErgoBox(tx.outputs(2)), dexFeeOutput.boxId) + (tx, nextPool, predictedDexOutput) + } + + def swap( + swap: SwapErg, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = resolver.getLatest + .flatMap(_.orRaise[F](EmptyOutputForDexTokenFee(pool.poolId, swap.box.boxId))) + .flatMap { dexFeeOutput => + swapParamsErgFee(swap, pool).toRaise.flatMap { case (input, output, dexFee) => + (swap match { + case s: SwapP2Pk => + (s.maxMinerFee, s.params.baseAmount, s.params.redeemer.toErgoTree, s.params.minQuoteAmount).pure[F] + case s: SwapMultiAddress => + Either + .catchNonFatal(ErgoTreeSerializer.default.deserialize(s.params.redeemer)) + .leftMap(err => IncorrectMultiAddressTree(pool.poolId, s.box.boxId, s.params.redeemer, err.getMessage)) + .toRaise + .map(tree => (s.maxMinerFee, s.params.baseAmount, tree, s.params.minQuoteAmount)) + }).map { case (maxMinerFee, _, redeemer, minOutput) => + val poolBox0 = pool.box + val swapBox = swap.box + val swapIn = new Input(swapBox.boxId.toErgo, ProverResult.empty) + val poolIn = new Input(poolBox0.boxId.toErgo, ProverResult.empty) + val dexFeeIn = new Input(dexFeeOutput.boxId.toErgo, ProverResult.empty) + val (deltaX, deltaY) = + if (input.id == pool.x.id) input.value -> -output.value + else -output.value -> input.value + val poolBox1 = new ErgoBoxCandidate( + value = poolBox0.value, + ergoTree = contracts.pool, + creationHeight = ctx.currentHeight, + additionalTokens = mkPoolTokens( + pool, + amountLP = pool.lp.value, + amountX = pool.x.value + deltaX, + amountY = pool.y.value + deltaY + ), + additionalRegisters = mkPoolRegs(pool) + ) + val minerFee = monetary.minerFee min maxMinerFee + val minerFeeBox = new ErgoBoxCandidate(minerFee, minerFeeProp, ctx.currentHeight) + val dexFeeBox = new ErgoBoxCandidate( + dexFeeOutput.value + dexFee, + P2PKAddress(sk.publicImage).script, + ctx.currentHeight, + additionalTokens = mkTokens(dexFeeOutput.assets.map(asset => asset.tokenId -> asset.amount): _*) + ) + val rewardBox = new ErgoBoxCandidate( + value = swapBox.value - minerFeeBox.value - dexFee, + ergoTree = redeemer, + creationHeight = ctx.currentHeight, + additionalTokens = mkTokens(minOutput.id -> output.value) + ) + val inputs = Vector(poolIn, swapIn, dexFeeIn) + val outs = Vector(poolBox1, rewardBox, dexFeeBox, minerFeeBox) + val tx = ErgoUnsafeProver.prove(UnsignedErgoLikeTransaction(inputs, outs), sk) + val nextPoolBox = poolBox1.toBox(tx.id, 0) + val boxInfo = BoxInfo(BoxId.fromErgo(nextPoolBox.id), nextPoolBox.value) + val nextPool = pool.swap(input, boxInfo) + val predictedDexOutput = Output.predicted(Output.fromErgoBox(tx.outputs(2)), dexFeeOutput.boxId) + (tx, nextPool, predictedDexOutput) + } + } + } + + private def mkPoolTokens(pool: CFMMPool, amountLP: Long, amountX: Long, amountY: Long) = + mkTokens( + pool.poolId.value -> 1L, + pool.lp.id -> amountLP, + pool.x.id -> amountX, + pool.y.id -> amountY + ) +} + +object T2TCFMMInterpreter { + + def make[I[_]: Functor, F[_]: Monad: ExecutionFailed.Raise: ExchangeConfig.Has: MonetaryConfig.Has](implicit + network: ErgoExplorer[F], + contracts: AMMContracts[T2T_CFMM], + encoder: ErgoAddressEncoder, + resolver: DexOutputResolver[F], + logs: Logs[I, F] + ): I[InterpreterV1[T2T_CFMM, F]] = + logs.forService[InterpreterV1[T2T_CFMM, F]].map { implicit l => + ( + for { + exchange <- ExchangeConfig.access + execution <- MonetaryConfig.access + networkCtx <- NetworkContext.make + } yield new InterpreterTracing[T2T_CFMM, F]("T2T_CFMM") attach new T2TCFMMInterpreter( + exchange, + execution, + networkCtx + ) + ).embed + } +} diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/InterpreterV3.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/InterpreterV3.scala new file mode 100644 index 00000000..3255f9ae --- /dev/null +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/InterpreterV3.scala @@ -0,0 +1,60 @@ +package org.ergoplatform.dex.executor.amm.interpreters.v3 + +import cats.FlatMap +import derevo.derive +import org.ergoplatform.ErgoLikeTransaction +import org.ergoplatform.dex.domain.DexOperatorOutput +import org.ergoplatform.dex.domain.amm.CFMMOrder.{DepositTokenFee, RedeemTokenFee, SwapTokenFee} +import org.ergoplatform.dex.domain.amm.CFMMPool +import org.ergoplatform.dex.protocol.amm.AMMType.CFMMType +import org.ergoplatform.ergo.domain.Output +import org.ergoplatform.ergo.state.{Predicted, Traced} +import tofu.higherKind.Mid +import tofu.logging.Logging +import tofu.syntax.monadic._ +import tofu.syntax.logging._ +import org.ergoplatform.dex.protocol.instances._ +import tofu.higherKind.derived.representableK + +@derive(representableK) +trait InterpreterV3[CT <: CFMMType, F[_]] { + + def deposit( + deposit: DepositTokenFee, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] + + def redeem( + redeem: RedeemTokenFee, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] + + def swap( + swap: SwapTokenFee, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] +} + +object InterpreterV3 { + + final class InterpreterV3Tracing[CT <: CFMMType, F[_]: FlatMap: Logging] extends InterpreterV3[CT, Mid[F, *]] { + + def deposit( + deposit: DepositTokenFee, + pool: CFMMPool + ): Mid[F, (ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + _ >>= (r => trace"deposit(in=$deposit, pool=$pool) = (${r._1}, ${r._2})" as r) + + def redeem( + redeem: RedeemTokenFee, + pool: CFMMPool + ): Mid[F, (ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + _ >>= (r => trace"redeem(in=$redeem, pool=$pool) = (${r._1}, ${r._2})" as r) + + def swap( + swap: SwapTokenFee, + pool: CFMMPool + ): Mid[F, (ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + _ >>= (r => trace"swap(in=$swap, pool=$pool) = (${r._1}, ${r._2})" as r) + } +} diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/n2t/N2TDepositTokenFeeInterpreter.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/n2t/N2TDepositTokenFeeInterpreter.scala new file mode 100644 index 00000000..9423d324 --- /dev/null +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/n2t/N2TDepositTokenFeeInterpreter.scala @@ -0,0 +1,111 @@ +package org.ergoplatform.dex.executor.amm.interpreters.v3.n2t + +import cats.Monad +import cats.effect.concurrent.Ref +import cats.syntax.either._ +import cats.syntax.semigroup._ +import org.ergoplatform._ +import org.ergoplatform.dex.configs.MonetaryConfig +import org.ergoplatform.dex.domain.amm.CFMMOrder._ +import org.ergoplatform.dex.domain.amm.CFMMPool +import org.ergoplatform.dex.domain.{BoxInfo, DexOperatorOutput, NetworkContext} +import org.ergoplatform.dex.executor.amm.config.ExchangeConfig +import org.ergoplatform.dex.executor.amm.domain.errors.{ExecutionFailed, IncorrectMultiAddressTree} +import org.ergoplatform.dex.executor.amm.interpreters.CFMMInterpreterHelpers +import org.ergoplatform.dex.protocol.ErgoTreeSerializer +import org.ergoplatform.dex.protocol.amm.AMMContracts +import org.ergoplatform.dex.protocol.amm.AMMType.N2T_CFMM +import org.ergoplatform.ergo.BoxId +import org.ergoplatform.ergo.domain.Output +import org.ergoplatform.ergo.state.{Predicted, Traced} +import org.ergoplatform.ergo.syntax._ +import org.ergoplatform.wallet.interpreter.ErgoUnsafeProver +import sigmastate.interpreter.ProverResult +import tofu.syntax.monadic._ +import tofu.syntax.raise._ + +final class N2TDepositTokenFeeInterpreter[F[_]: Monad: ExecutionFailed.Raise]( + exchange: ExchangeConfig, + execution: MonetaryConfig, + ref: Ref[F, NetworkContext], + helpers: CFMMInterpreterHelpers +)(implicit contracts: AMMContracts[N2T_CFMM], e: ErgoAddressEncoder) { + + import helpers._ + + def deposit( + deposit: DepositTokenFee, + pool: CFMMPool, + dexFeeOutput: Output + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = ref.get.flatMap { ctx => + Either + .catchNonFatal(ErgoTreeSerializer.default.deserialize(deposit.params.redeemer)) + .leftMap(s => IncorrectMultiAddressTree(pool.poolId, deposit.box.boxId, deposit.params.redeemer, s.getMessage)) + .toRaise + .map { redeemer => + val poolBox: BoxInfo = pool.box + val depositBox: Output = deposit.box + + val depositIn = new Input(depositBox.boxId.toErgo, ProverResult.empty) + val poolIn = new Input(poolBox.boxId.toErgo, ProverResult.empty) + val dexFeeIn = new Input(dexFeeOutput.boxId.toErgo, ProverResult.empty) + + val (inX, inY) = (deposit.params.inX, deposit.params.inY) + + val (rewardLP, change) = pool.rewardLP(inX, inY) + val (changeX, changeY) = + (change.filter(_.id == inX.id).map(_.value).sum, change.filter(_.id == inY.id).map(_.value).sum) + + val poolOut = new ErgoBoxCandidate( + value = poolBox.value + inX.value - changeX, + ergoTree = contracts.pool, + creationHeight = ctx.currentHeight, + additionalTokens = mkPoolTokens( + pool, + amountLP = pool.lp.value - rewardLP.value, + amountY = pool.y.value + inY.value - changeY + ), + additionalRegisters = mkPoolRegs(pool) + ) + + val minerFee = execution.minerFee min deposit.maxMinerFee + val minerFeeBox = new ErgoBoxCandidate(minerFee, minerFeeProp, ctx.currentHeight) + + val dexFee = deposit.params.dexFee + + val dexFeeTokensReturn: Seq[(ergo.TokenId, Long)] = + (Map(exchange.spectrumToken -> dexFee) |+| dexFeeOutput.assets + .map(asset => asset.tokenId -> asset.amount) + .toMap).toSeq + + val dexFeeBox = new ErgoBoxCandidate( + dexFeeOutput.value - minerFeeBox.value, + P2PKAddress(sk.publicImage).script, + ctx.currentHeight, + additionalTokens = mkTokens(dexFeeTokensReturn: _*) + ) + + val returnBox = new ErgoBoxCandidate( + value = depositBox.value - inX.value + changeX, + ergoTree = redeemer, + creationHeight = ctx.currentHeight, + additionalTokens = + if (changeY > 0) + mkTokens(rewardLP.id -> rewardLP.value, inY.id -> changeY) + else + mkTokens(rewardLP.id -> rewardLP.value) + ) + + val inputs = Vector(poolIn, depositIn, dexFeeIn) + val outs = Vector(poolOut, returnBox, dexFeeBox, minerFeeBox) + val tx = ErgoUnsafeProver.prove(UnsignedErgoLikeTransaction(inputs, outs), sk) + val nextPoolBox = poolOut.toBox(tx.id, 0) + val boxInfo = BoxInfo(BoxId.fromErgo(nextPoolBox.id), nextPoolBox.value) + val nextPool = pool.deposit(inX, inY, boxInfo) + + val predictedDexOutput = Output.predicted(Output.fromErgoBox(tx.outputs(2)), dexFeeOutput.boxId) + + (tx, nextPool, predictedDexOutput) + } + } +} diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/n2t/N2TRedeemTokenFeeInterpreter.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/n2t/N2TRedeemTokenFeeInterpreter.scala new file mode 100644 index 00000000..8ce0f3ee --- /dev/null +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/n2t/N2TRedeemTokenFeeInterpreter.scala @@ -0,0 +1,99 @@ +package org.ergoplatform.dex.executor.amm.interpreters.v3.n2t + +import cats.Monad +import cats.effect.concurrent.Ref +import cats.syntax.either._ +import cats.syntax.semigroup._ +import org.ergoplatform._ +import org.ergoplatform.dex.configs.MonetaryConfig +import org.ergoplatform.dex.domain.amm.CFMMOrder._ +import org.ergoplatform.dex.domain.amm.CFMMPool +import org.ergoplatform.dex.domain.{BoxInfo, DexOperatorOutput, NetworkContext} +import org.ergoplatform.dex.executor.amm.config.ExchangeConfig +import org.ergoplatform.dex.executor.amm.domain.errors.{ExecutionFailed, IncorrectMultiAddressTree} +import org.ergoplatform.dex.executor.amm.interpreters.CFMMInterpreterHelpers +import org.ergoplatform.dex.protocol.ErgoTreeSerializer +import org.ergoplatform.dex.protocol.amm.AMMContracts +import org.ergoplatform.dex.protocol.amm.AMMType.N2T_CFMM +import org.ergoplatform.ergo.BoxId +import org.ergoplatform.ergo.domain.Output +import org.ergoplatform.ergo.state.{Predicted, Traced} +import org.ergoplatform.ergo.syntax._ +import org.ergoplatform.wallet.interpreter.ErgoUnsafeProver +import sigmastate.interpreter.ProverResult +import tofu.syntax.monadic._ +import tofu.syntax.raise._ + +final class N2TRedeemTokenFeeInterpreter[F[_]: Monad: ExecutionFailed.Raise]( + exchange: ExchangeConfig, + execution: MonetaryConfig, + ref: Ref[F, NetworkContext], + helpers: CFMMInterpreterHelpers +)(implicit contracts: AMMContracts[N2T_CFMM], e: ErgoAddressEncoder) { + import helpers._ + + def redeem( + redeem: RedeemTokenFee, + pool: CFMMPool, + dexFeeOutput: Output + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = ref.get.flatMap { ctx => + Either + .catchNonFatal(ErgoTreeSerializer.default.deserialize(redeem.params.redeemer)) + .leftMap(s => IncorrectMultiAddressTree(pool.poolId, redeem.box.boxId, redeem.params.redeemer, s.getMessage)) + .toRaise + .map { redeemer => + val poolBox = pool.box + val redeemBox = redeem.box + + val redeemIn = new Input(redeemBox.boxId.toErgo, ProverResult.empty) + val poolIn = new Input(poolBox.boxId.toErgo, ProverResult.empty) + val dexFeeIn = new Input(dexFeeOutput.boxId.toErgo, ProverResult.empty) + + val inLP = redeem.params.lp + val (shareX, shareY) = pool.shares(inLP) + + val poolOut = new ErgoBoxCandidate( + value = poolBox.value - shareX.value, + ergoTree = contracts.pool, + creationHeight = ctx.currentHeight, + additionalTokens = mkPoolTokens( + pool, + amountLP = pool.lp.value + inLP.value, + amountY = pool.y.value - shareY.value + ), + additionalRegisters = mkPoolRegs(pool) + ) + + val minerFee = execution.minerFee min redeem.maxMinerFee + val minerFeeBox = new ErgoBoxCandidate(minerFee, minerFeeProp, ctx.currentHeight) + + val dexFeeTokensReturn: Seq[(ergo.TokenId, Long)] = + (Map(exchange.spectrumToken -> redeem.params.dexFee) |+| dexFeeOutput.assets + .map(asset => asset.tokenId -> asset.amount) + .toMap).toSeq + + val dexFeeBox = new ErgoBoxCandidate( + dexFeeOutput.value - minerFeeBox.value, + P2PKAddress(sk.publicImage).script, + ctx.currentHeight, + additionalTokens = mkTokens(dexFeeTokensReturn: _*) + ) + + val returnBox = new ErgoBoxCandidate( + value = redeemBox.value + shareX.value, + ergoTree = redeemer, + creationHeight = ctx.currentHeight, + additionalTokens = mkTokens(shareY.id -> shareY.value) + ) + + val inputs = Vector(poolIn, redeemIn, dexFeeIn) + val outs = Vector(poolOut, returnBox, dexFeeBox, minerFeeBox) + val tx = ErgoUnsafeProver.prove(UnsignedErgoLikeTransaction(inputs, outs), sk) + val nextPoolBox = poolOut.toBox(tx.id, 0) + val boxInfo = BoxInfo(BoxId.fromErgo(nextPoolBox.id), nextPoolBox.value) + val nextPool = pool.redeem(inLP, boxInfo) + val predictedDexOutput = Output.predicted(Output.fromErgoBox(tx.outputs(2)), dexFeeOutput.boxId) + (tx, nextPool, predictedDexOutput) + } + } +} diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/n2t/N2TSwapTokenFeeInterpreter.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/n2t/N2TSwapTokenFeeInterpreter.scala new file mode 100644 index 00000000..b124ee21 --- /dev/null +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/n2t/N2TSwapTokenFeeInterpreter.scala @@ -0,0 +1,122 @@ +package org.ergoplatform.dex.executor.amm.interpreters.v3.n2t + +import cats.Monad +import cats.effect.concurrent.Ref +import cats.syntax.either._ +import org.ergoplatform.dex.configs.MonetaryConfig +import org.ergoplatform.dex.domain.amm.CFMMOrder._ +import org.ergoplatform.dex.domain.amm.CFMMPool +import org.ergoplatform.dex.domain.{BoxInfo, DexOperatorOutput, NetworkContext} +import org.ergoplatform.dex.executor.amm.config.ExchangeConfig +import org.ergoplatform.dex.executor.amm.domain.errors.{ExecutionFailed, IncorrectMultiAddressTree} +import org.ergoplatform.dex.executor.amm.interpreters.CFMMInterpreterHelpers +import org.ergoplatform.dex.protocol.ErgoTreeSerializer +import org.ergoplatform.dex.protocol.amm.AMMContracts +import org.ergoplatform.dex.protocol.amm.AMMType.N2T_CFMM +import org.ergoplatform.ergo.BoxId +import org.ergoplatform.ergo.domain.Output +import org.ergoplatform.ergo.state.{Predicted, Traced} +import org.ergoplatform.ergo.syntax._ +import org.ergoplatform.wallet.interpreter.ErgoUnsafeProver +import org.ergoplatform._ +import sigmastate.interpreter.ProverResult +import tofu.syntax.monadic._ +import tofu.syntax.raise._ +import cats.syntax.semigroup._ + +class N2TSwapTokenFeeInterpreter[F[_]: Monad: ExecutionFailed.Raise]( + exchange: ExchangeConfig, + execution: MonetaryConfig, + ref: Ref[F, NetworkContext], + helpers: CFMMInterpreterHelpers +)(implicit contracts: AMMContracts[N2T_CFMM], e: ErgoAddressEncoder) { + import helpers._ + + def swap( + swap: SwapTokenFee, + pool: CFMMPool, + dexFeeOutput: Output + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = ref.get.flatMap { ctx => + swapParamsTokenFee(swap, pool).toRaise + .flatMap { case (baseAmount, quoteAmount, dexFee) => + Either + .catchNonFatal(ErgoTreeSerializer.default.deserialize(swap.params.redeemer)) + .leftMap(s => IncorrectMultiAddressTree(pool.poolId, swap.box.boxId, swap.params.redeemer, s.getMessage)) + .toRaise + .map { redeemer => + val poolBox = pool.box + val swapBox = swap.box + + val swapIn = new Input(swapBox.boxId.toErgo, ProverResult.empty) + val poolIn = new Input(poolBox.boxId.toErgo, ProverResult.empty) + val dexFeeIn = new Input(dexFeeOutput.boxId.toErgo, ProverResult.empty) + + val (deltaX, deltaY) = + if (baseAmount.id == pool.x.id) + baseAmount.value -> -quoteAmount.value + else + -quoteAmount.value -> baseAmount.value + + val poolBox1 = new ErgoBoxCandidate( + value = poolBox.value + deltaX, + ergoTree = contracts.pool, + creationHeight = ctx.currentHeight, + additionalTokens = mkPoolTokens( + pool, + amountLP = pool.lp.value, + amountY = pool.y.value + deltaY + ), + additionalRegisters = mkPoolRegs(pool) + ) + + val minerFee = execution.minerFee min swap.maxMinerFee + val minerFeeBox = new ErgoBoxCandidate(minerFee, minerFeeProp, ctx.currentHeight) + val dexFeeTokensReturn: Seq[(ergo.TokenId, Long)] = + (Map(exchange.spectrumToken -> dexFee) |+| dexFeeOutput.assets + .map(asset => asset.tokenId -> asset.amount) + .toMap).toSeq + + val dexFeeBox = new ErgoBoxCandidate( + dexFeeOutput.value - minerFeeBox.value, + P2PKAddress(sk.publicImage).script, + ctx.currentHeight, + additionalTokens = mkTokens(dexFeeTokensReturn:_*) + ) + + def quoteResult = + if (swap.params.minQuoteAmount.id == exchange.spectrumToken) + mkTokens(swap.params.minQuoteAmount.id -> (quoteAmount.value + swap.reservedExFee - dexFee)) + else + mkTokens( + swap.params.minQuoteAmount.id -> quoteAmount.value, + exchange.spectrumToken -> (swap.reservedExFee - dexFee) + ) + + val rewardBox = + if (swap.params.baseAmount.isNative) + new ErgoBoxCandidate( + value = swapBox.value - baseAmount.value, + ergoTree = redeemer, + creationHeight = ctx.currentHeight, + additionalTokens = quoteResult + ) + else + new ErgoBoxCandidate( + value = swapBox.value + quoteAmount.value, + ergoTree = redeemer, + creationHeight = ctx.currentHeight, + additionalTokens = mkTokens(exchange.spectrumToken -> (swap.reservedExFee - dexFee)) + ) + + val inputs = Vector(poolIn, swapIn, dexFeeIn) + val outs = Vector(poolBox1, rewardBox, dexFeeBox, minerFeeBox) + val tx = ErgoUnsafeProver.prove(UnsignedErgoLikeTransaction(inputs, outs), sk) + val nextPoolBox = poolBox1.toBox(tx.id, 0) + val boxInfo = BoxInfo(BoxId.fromErgo(nextPoolBox.id), nextPoolBox.value) + val nextPool = pool.swap(baseAmount, boxInfo) + val predictedDexOutput = Output.predicted(Output.fromErgoBox(tx.outputs(2)), dexFeeOutput.boxId) + (tx, nextPool, predictedDexOutput) + } + } + } +} diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/n2t/N2TV3.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/n2t/N2TV3.scala new file mode 100644 index 00000000..3feb4ffc --- /dev/null +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/n2t/N2TV3.scala @@ -0,0 +1,70 @@ +package org.ergoplatform.dex.executor.amm.interpreters.v3.n2t + +import cats.effect.concurrent.Ref +import cats.{Functor, Monad} +import org.ergoplatform.dex.configs.MonetaryConfig +import org.ergoplatform.dex.domain.{DexOperatorOutput, NetworkContext} +import org.ergoplatform.dex.domain.amm.CFMMOrder._ +import org.ergoplatform.dex.domain.amm.CFMMPool +import org.ergoplatform.dex.executor.amm.config.ExchangeConfig +import org.ergoplatform.dex.executor.amm.domain.errors.{EmptyOutputForDexTokenFee, ExecutionFailed} +import org.ergoplatform.dex.executor.amm.interpreters.v3.InterpreterV3 +import org.ergoplatform.dex.executor.amm.interpreters.{CFMMInterpreter, CFMMInterpreterHelpers} +import org.ergoplatform.dex.executor.amm.services.DexOutputResolver +import org.ergoplatform.dex.protocol.amm.AMMContracts +import org.ergoplatform.dex.protocol.amm.AMMType.{N2T_CFMM, T2T_CFMM} +import org.ergoplatform.ergo.domain.Output +import org.ergoplatform.ergo.state.{Predicted, Traced} +import org.ergoplatform.{ErgoAddressEncoder, ErgoLikeTransaction} +import tofu.logging.Logs +import tofu.syntax.monadic._ +import tofu.syntax.raise._ +import cats.syntax.option._ +import org.ergoplatform.dex.executor.amm.interpreters.v3.InterpreterV3.InterpreterV3Tracing + +object N2TV3 { + + def make[I[_]: Functor, F[_]: Monad: ExecutionFailed.Raise]( + exchange: ExchangeConfig, + monetary: MonetaryConfig, + ref: Ref[F, NetworkContext] + )(implicit + contracts: AMMContracts[N2T_CFMM], + encoder: ErgoAddressEncoder, + resolver: DexOutputResolver[F], + logs: Logs[I, F] + ): I[InterpreterV3[N2T_CFMM, F]] = + logs.forService[CFMMInterpreter[N2T_CFMM, F]].map { implicit l => + val helpers = new CFMMInterpreterHelpers(exchange, monetary) + val depositI = new N2TDepositTokenFeeInterpreter[F](exchange, monetary, ref, helpers) + val redeemI = new N2TRedeemTokenFeeInterpreter[F](exchange, monetary, ref, helpers) + val swapI = new N2TSwapTokenFeeInterpreter[F](exchange, monetary, ref, helpers) + + new InterpreterV3Tracing[N2T_CFMM, F] attach new InterpreterV3[N2T_CFMM, F] { + def deposit( + deposit: DepositTokenFee, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + resolver.getLatest + .flatMap(_.orRaise[F](EmptyOutputForDexTokenFee(pool.poolId, deposit.box.boxId))) + .flatMap(depositI.deposit(deposit, pool, _)) + + def redeem( + redeem: RedeemTokenFee, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + resolver.getLatest + .flatMap(_.orRaise[F](EmptyOutputForDexTokenFee(pool.poolId, redeem.box.boxId))) + .flatMap(redeemI.redeem(redeem, pool, _)) + + def swap( + swap: SwapTokenFee, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + resolver.getLatest + .flatMap(_.orRaise[F](EmptyOutputForDexTokenFee(pool.poolId, swap.box.boxId))) + .flatMap(swapI.swap(swap, pool, _)) + } + } + +} diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/t2t/T2TDepositTokenFeeInterpreter.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/t2t/T2TDepositTokenFeeInterpreter.scala new file mode 100644 index 00000000..cfc9f073 --- /dev/null +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/t2t/T2TDepositTokenFeeInterpreter.scala @@ -0,0 +1,114 @@ +package org.ergoplatform.dex.executor.amm.interpreters.v3.t2t + +import cats.Monad +import cats.effect.concurrent.Ref +import cats.syntax.either._ +import org.ergoplatform._ +import org.ergoplatform.dex.configs.MonetaryConfig +import org.ergoplatform.dex.domain.amm.CFMMOrder._ +import org.ergoplatform.dex.domain.amm.CFMMPool +import org.ergoplatform.dex.domain.{BoxInfo, DexOperatorOutput, NetworkContext} +import org.ergoplatform.dex.executor.amm.config.ExchangeConfig +import org.ergoplatform.dex.executor.amm.domain.errors.{ExecutionFailed, IncorrectMultiAddressTree} +import org.ergoplatform.dex.executor.amm.interpreters.CFMMInterpreterHelpers +import org.ergoplatform.dex.protocol.ErgoTreeSerializer +import org.ergoplatform.dex.protocol.amm.AMMContracts +import org.ergoplatform.dex.protocol.amm.AMMType.T2T_CFMM +import org.ergoplatform.ergo.BoxId +import org.ergoplatform.ergo.domain.Output +import org.ergoplatform.ergo.state.{Predicted, Traced} +import org.ergoplatform.ergo.syntax._ +import org.ergoplatform.wallet.interpreter.ErgoUnsafeProver +import sigmastate.interpreter.ProverResult +import tofu.syntax.monadic._ +import tofu.syntax.raise._ +import cats.syntax.semigroup._ + +class T2TDepositTokenFeeInterpreter[F[_]: Monad: ExecutionFailed.Raise]( + exchange: ExchangeConfig, + monetary: MonetaryConfig, + ref: Ref[F, NetworkContext], + helpers: CFMMInterpreterHelpers +)(implicit contracts: AMMContracts[T2T_CFMM], e: ErgoAddressEncoder) { + import helpers._ + + def deposit( + deposit: DepositTokenFee, + pool: CFMMPool, + dexFeeOutput: Output + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + ref.get.flatMap { ctx => + Either + .catchNonFatal(ErgoTreeSerializer.default.deserialize(deposit.params.redeemer)) + .leftMap(s => IncorrectMultiAddressTree(pool.poolId, deposit.box.boxId, deposit.params.redeemer, s.getMessage)) + .toRaise + .map { redeemer => + val poolBox0 = pool.box + val depositBox = deposit.box + + val depositIn = new Input(depositBox.boxId.toErgo, ProverResult.empty) + val poolIn = new Input(poolBox0.boxId.toErgo, ProverResult.empty) + val dexFeeIn = new Input(dexFeeOutput.boxId.toErgo, ProverResult.empty) + + val (inX, inY) = (deposit.params.inX, deposit.params.inY) + val (rewardLP, change) = pool.rewardLP(inX, inY) + val (changeX, changeY) = + (change.filter(_.id == inX.id).map(_.value).sum, change.filter(_.id == inY.id).map(_.value).sum) + + val poolBox1 = new ErgoBoxCandidate( + value = poolBox0.value, + ergoTree = contracts.pool, + creationHeight = ctx.currentHeight, + additionalTokens = mkPoolTokens( + pool, + amountLP = pool.lp.value - rewardLP.value, + amountX = pool.x.value + inX.value - changeX, + amountY = pool.y.value + inY.value - changeY + ), + additionalRegisters = mkPoolRegs(pool) + ) + + val minerFee = monetary.minerFee min deposit.maxMinerFee + val minerFeeBox = new ErgoBoxCandidate(minerFee, minerFeeProp, ctx.currentHeight) + + val feeChange = + if (inX.id == helpers.exchange.spectrumToken) changeX + else if (inY.id == helpers.exchange.spectrumToken) changeY + else 0 + + val dexFee = deposit.params.dexFee - feeChange + val dexFeeTokensReturn: Seq[(ergo.TokenId, Long)] = + (Map(exchange.spectrumToken -> dexFee) |+| dexFeeOutput.assets + .map(asset => asset.tokenId -> asset.amount) + .toMap).toSeq + val dexFeeBox = new ErgoBoxCandidate( + dexFeeOutput.value - minerFeeBox.value, + P2PKAddress(sk.publicImage).script, + ctx.currentHeight, + additionalTokens = mkTokens(dexFeeTokensReturn: _*) + ) + + val returnBox = new ErgoBoxCandidate( + value = depositBox.value, + ergoTree = redeemer, + creationHeight = ctx.currentHeight, + additionalTokens = + if (changeX > 0) + mkTokens(rewardLP.id -> rewardLP.value, inX.id -> changeX) + else if (changeY > 0) + mkTokens(rewardLP.id -> rewardLP.value, inY.id -> changeY) + else + mkTokens(rewardLP.id -> rewardLP.value) + ) + + val inputs = Vector(poolIn, depositIn, dexFeeIn) + val outs = Vector(poolBox1, returnBox, dexFeeBox, minerFeeBox) + val tx = ErgoUnsafeProver.prove(UnsignedErgoLikeTransaction(inputs, outs), sk) + val nextPoolBox = poolBox1.toBox(tx.id, 0) + val boxInfo = BoxInfo(BoxId.fromErgo(nextPoolBox.id), nextPoolBox.value) + val nextPool = pool.deposit(inX, inY, boxInfo) + val predictedDexOutput = Output.predicted(Output.fromErgoBox(tx.outputs(2)), dexFeeOutput.boxId) + (tx, nextPool, predictedDexOutput) + } + } +} diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/t2t/T2TRedeemTokenFeeInterpreter.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/t2t/T2TRedeemTokenFeeInterpreter.scala new file mode 100644 index 00000000..d5699bab --- /dev/null +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/t2t/T2TRedeemTokenFeeInterpreter.scala @@ -0,0 +1,98 @@ +package org.ergoplatform.dex.executor.amm.interpreters.v3.t2t + +import cats.Monad +import cats.effect.concurrent.Ref +import cats.syntax.either._ +import org.ergoplatform._ +import org.ergoplatform.dex.configs.MonetaryConfig +import org.ergoplatform.dex.domain.amm.CFMMOrder._ +import org.ergoplatform.dex.domain.amm.CFMMPool +import org.ergoplatform.dex.domain.{BoxInfo, DexOperatorOutput, NetworkContext} +import org.ergoplatform.dex.executor.amm.config.ExchangeConfig +import org.ergoplatform.dex.executor.amm.domain.errors.{ExecutionFailed, IncorrectMultiAddressTree} +import org.ergoplatform.dex.executor.amm.interpreters.CFMMInterpreterHelpers +import org.ergoplatform.dex.protocol.ErgoTreeSerializer +import org.ergoplatform.dex.protocol.amm.AMMContracts +import org.ergoplatform.dex.protocol.amm.AMMType.T2T_CFMM +import org.ergoplatform.ergo.BoxId +import org.ergoplatform.ergo.domain.Output +import org.ergoplatform.ergo.state.{Predicted, Traced} +import org.ergoplatform.ergo.syntax._ +import org.ergoplatform.wallet.interpreter.ErgoUnsafeProver +import sigmastate.interpreter.ProverResult +import tofu.syntax.monadic._ +import tofu.syntax.raise._ +import cats.syntax.semigroup._ + +final class T2TRedeemTokenFeeInterpreter[F[_]: Monad: ExecutionFailed.Raise]( + exchange: ExchangeConfig, + monetary: MonetaryConfig, + ref: Ref[F, NetworkContext], + helpers: CFMMInterpreterHelpers +)(implicit contracts: AMMContracts[T2T_CFMM], e: ErgoAddressEncoder) { + import helpers._ + + def redeem( + redeem: RedeemTokenFee, + pool: CFMMPool, + dexFeeOutput: Output + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = ref.get.flatMap { ctx => + Either + .catchNonFatal(ErgoTreeSerializer.default.deserialize(redeem.params.redeemer)) + .leftMap(s => IncorrectMultiAddressTree(pool.poolId, redeem.box.boxId, redeem.params.redeemer, s.getMessage)) + .toRaise + .map { redeemer => + val poolBox0 = pool.box + val redeemBox = redeem.box + val redeemIn = new Input(redeemBox.boxId.toErgo, ProverResult.empty) + val poolIn = new Input(poolBox0.boxId.toErgo, ProverResult.empty) + val dexFeeIn = new Input(dexFeeOutput.boxId.toErgo, ProverResult.empty) + val inLP = redeem.params.lp + val (shareX, shareY) = pool.shares(inLP) + val poolBox1 = new ErgoBoxCandidate( + value = poolBox0.value, + ergoTree = contracts.pool, + creationHeight = ctx.currentHeight, + additionalTokens = mkPoolTokens( + pool, + amountLP = pool.lp.value + inLP.value, + amountX = pool.x.value - shareX.value, + amountY = pool.y.value - shareY.value + ), + additionalRegisters = mkPoolRegs(pool) + ) + val minerFee = monetary.minerFee min redeem.maxMinerFee + val minerFeeBox = new ErgoBoxCandidate(minerFee, minerFeeProp, ctx.currentHeight) + val dexFee = redeem.params.dexFee + val dexFeeTokensReturn: Seq[(ergo.TokenId, Long)] = + (Map(exchange.spectrumToken -> dexFee) |+| dexFeeOutput.assets + .map(asset => asset.tokenId -> asset.amount) + .toMap).toSeq + val dexFeeBox = new ErgoBoxCandidate( + dexFeeOutput.value - minerFeeBox.value, + P2PKAddress(sk.publicImage).script, + ctx.currentHeight, + additionalTokens = mkTokens(dexFeeTokensReturn: _*) + ) + val returnBox = new ErgoBoxCandidate( + value = redeemBox.value, + ergoTree = redeemer, + creationHeight = ctx.currentHeight, + additionalTokens = mkTokens( + shareX.id -> shareX.value, + shareY.id -> shareY.value + ) + ) + + val inputs = Vector(poolIn, redeemIn, dexFeeIn) + val outs = Vector(poolBox1, returnBox, dexFeeBox, minerFeeBox) + val tx = ErgoUnsafeProver.prove(UnsignedErgoLikeTransaction(inputs, outs), sk) + val nextPoolBox = poolBox1.toBox(tx.id, 0) + val boxInfo = BoxInfo(BoxId.fromErgo(nextPoolBox.id), nextPoolBox.value) + val nextPool = pool.redeem(inLP, boxInfo) + val predictedDexOutput = Output.predicted(Output.fromErgoBox(tx.outputs(2)), dexFeeOutput.boxId) + (tx, nextPool, predictedDexOutput) + } + } + +} diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/t2t/T2TSwapTokenFeeInterpreter.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/t2t/T2TSwapTokenFeeInterpreter.scala new file mode 100644 index 00000000..3693a66c --- /dev/null +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/t2t/T2TSwapTokenFeeInterpreter.scala @@ -0,0 +1,107 @@ +package org.ergoplatform.dex.executor.amm.interpreters.v3.t2t + +import cats.Monad +import cats.effect.concurrent.Ref +import cats.syntax.either._ +import org.ergoplatform.dex.configs.MonetaryConfig +import org.ergoplatform.dex.domain.amm.CFMMOrder._ +import org.ergoplatform.dex.domain.amm.CFMMPool +import org.ergoplatform.dex.domain.{BoxInfo, DexOperatorOutput, NetworkContext} +import org.ergoplatform.dex.executor.amm.config.ExchangeConfig +import org.ergoplatform.dex.executor.amm.domain.errors.{ExecutionFailed, IncorrectMultiAddressTree} +import org.ergoplatform.dex.executor.amm.interpreters.CFMMInterpreterHelpers +import org.ergoplatform.dex.protocol.ErgoTreeSerializer +import org.ergoplatform.dex.protocol.amm.AMMContracts +import org.ergoplatform.dex.protocol.amm.AMMType.T2T_CFMM +import org.ergoplatform.ergo.BoxId +import org.ergoplatform.ergo.domain.Output +import org.ergoplatform.ergo.state.{Predicted, Traced} +import org.ergoplatform.ergo.syntax._ +import org.ergoplatform.wallet.interpreter.ErgoUnsafeProver +import org.ergoplatform._ +import sigmastate.interpreter.ProverResult +import tofu.syntax.monadic._ +import tofu.syntax.raise._ +import cats.syntax.semigroup._ + +class T2TSwapTokenFeeInterpreter[F[_]: Monad: ExecutionFailed.Raise]( + exchange: ExchangeConfig, + monetary: MonetaryConfig, + ref: Ref[F, NetworkContext], + helpers: CFMMInterpreterHelpers +)(implicit contracts: AMMContracts[T2T_CFMM], e: ErgoAddressEncoder) { + import helpers._ + + def swap( + swap: SwapTokenFee, + pool: CFMMPool, + dexFeeOutput: Output + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = ref.get.flatMap { ctx => + swapParamsTokenFee(swap, pool).toRaise.flatMap { case (baseAmount, quoteAmount, dexFee) => + Either + .catchNonFatal(ErgoTreeSerializer.default.deserialize(swap.params.redeemer)) + .leftMap(s => IncorrectMultiAddressTree(pool.poolId, swap.box.boxId, swap.params.redeemer, s.getMessage)) + .toRaise + .map { redeemer => + val poolBox0 = pool.box + val swapBox = swap.box + val swapIn = new Input(swapBox.boxId.toErgo, ProverResult.empty) + val poolIn = new Input(poolBox0.boxId.toErgo, ProverResult.empty) + val dexFeeIn = new Input(dexFeeOutput.boxId.toErgo, ProverResult.empty) + val (deltaX, deltaY) = + if (baseAmount.id == pool.x.id) baseAmount.value -> -quoteAmount.value + else -quoteAmount.value -> baseAmount.value + val poolBox1 = new ErgoBoxCandidate( + value = poolBox0.value, + ergoTree = contracts.pool, + creationHeight = ctx.currentHeight, + additionalTokens = mkPoolTokens( + pool, + amountLP = pool.lp.value, + amountX = pool.x.value + deltaX, + amountY = pool.y.value + deltaY + ), + additionalRegisters = mkPoolRegs(pool) + ) + val minerFee = monetary.minerFee min swap.maxMinerFee + val minerFeeBox = new ErgoBoxCandidate(minerFee, minerFeeProp, ctx.currentHeight) + val dexFeeTokensReturn: Seq[(ergo.TokenId, Long)] = + (Map(exchange.spectrumToken -> dexFee) |+| dexFeeOutput.assets + .map(asset => asset.tokenId -> asset.amount) + .toMap).toSeq + val dexFeeBox = new ErgoBoxCandidate( + dexFeeOutput.value - minerFeeBox.value, + P2PKAddress(sk.publicImage).script, + ctx.currentHeight, + additionalTokens = mkTokens(dexFeeTokensReturn: _*) + ) + + val rewardBox = new ErgoBoxCandidate( + value = swapBox.value, + ergoTree = redeemer, + creationHeight = ctx.currentHeight, + additionalTokens = + if (quoteAmount.id == exchange.spectrumToken) + mkTokens( + swap.params.minQuoteAmount.id -> (quoteAmount.value + swap.reservedExFee - dexFee) + ) + else + mkTokens( + swap.params.minQuoteAmount.id -> quoteAmount.value, + exchange.spectrumToken -> (swap.reservedExFee - dexFee) + ) + ) + + val inputs = Vector(poolIn, swapIn, dexFeeIn) + val outs = Vector(poolBox1, rewardBox, dexFeeBox, minerFeeBox) + val tx = ErgoUnsafeProver.prove(UnsignedErgoLikeTransaction(inputs, outs), sk) + val nextPoolBox = poolBox1.toBox(tx.id, 0) + val boxInfo = BoxInfo(BoxId.fromErgo(nextPoolBox.id), nextPoolBox.value) + val nextPool = pool.swap(baseAmount, boxInfo) + val predictedDexOutput = Output.predicted(Output.fromErgoBox(tx.outputs(2)), dexFeeOutput.boxId) + (tx, nextPool, predictedDexOutput) + } + } + } + +} diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/t2t/T2TV3.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/t2t/T2TV3.scala new file mode 100644 index 00000000..7a2e6301 --- /dev/null +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/interpreters/v3/t2t/T2TV3.scala @@ -0,0 +1,70 @@ +package org.ergoplatform.dex.executor.amm.interpreters.v3.t2t + +import cats.effect.concurrent.Ref +import cats.{Functor, Monad} +import org.ergoplatform.dex.configs.MonetaryConfig +import org.ergoplatform.dex.domain.{DexOperatorOutput, NetworkContext} +import org.ergoplatform.dex.domain.amm.CFMMOrder._ +import org.ergoplatform.dex.domain.amm.CFMMPool +import org.ergoplatform.dex.executor.amm.config.ExchangeConfig +import org.ergoplatform.dex.executor.amm.domain.errors.{EmptyOutputForDexTokenFee, ExecutionFailed} +import org.ergoplatform.dex.executor.amm.interpreters.CFMMInterpreterHelpers +import org.ergoplatform.dex.executor.amm.interpreters.v3.InterpreterV3 +import org.ergoplatform.dex.executor.amm.services.DexOutputResolver +import org.ergoplatform.dex.protocol.amm.AMMContracts +import org.ergoplatform.dex.protocol.amm.AMMType.T2T_CFMM +import org.ergoplatform.ergo.domain.Output +import org.ergoplatform.ergo.state.{Predicted, Traced} +import org.ergoplatform.{ErgoAddressEncoder, ErgoLikeTransaction} +import tofu.logging.Logs +import tofu.syntax.monadic._ +import tofu.syntax.raise._ +import cats.syntax.option._ +import org.ergoplatform.dex.executor.amm.interpreters.v3.InterpreterV3.InterpreterV3Tracing + +object T2TV3 { + + def make[I[_]: Functor, F[_]: Monad: ExecutionFailed.Raise]( + exchange: ExchangeConfig, + monetary: MonetaryConfig, + ref: Ref[F, NetworkContext] + )(implicit + contracts: AMMContracts[T2T_CFMM], + encoder: ErgoAddressEncoder, + resolver: DexOutputResolver[F], + logs: Logs[I, F] + ): I[InterpreterV3[T2T_CFMM, F]] = + logs.forService[InterpreterV3[T2T_CFMM, F]].map { implicit l => + val helpers = new CFMMInterpreterHelpers(exchange, monetary) + val depositI = new T2TDepositTokenFeeInterpreter[F](exchange, monetary, ref, helpers) + val redeemI = new T2TRedeemTokenFeeInterpreter[F](exchange, monetary, ref, helpers) + val swapI = new T2TSwapTokenFeeInterpreter[F](exchange, monetary, ref, helpers) + + new InterpreterV3Tracing[T2T_CFMM, F] attach new InterpreterV3[T2T_CFMM, F] { + def deposit( + deposit: DepositTokenFee, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + resolver.getLatest + .flatMap(_.orRaise[F](EmptyOutputForDexTokenFee(pool.poolId, deposit.box.boxId))) + .flatMap(depositI.deposit(deposit, pool, _)) + + def redeem( + redeem: RedeemTokenFee, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + resolver.getLatest + .flatMap(_.orRaise[F](EmptyOutputForDexTokenFee(pool.poolId, redeem.box.boxId))) + .flatMap(redeemI.redeem(redeem, pool, _)) + + def swap( + swap: SwapTokenFee, + pool: CFMMPool + ): F[(ErgoLikeTransaction, Traced[Predicted[CFMMPool]], Traced[Predicted[DexOperatorOutput]])] = + resolver.getLatest + .flatMap(_.orRaise[F](EmptyOutputForDexTokenFee(pool.poolId, swap.box.boxId))) + .flatMap(swapI.swap(swap, pool, _)) + } + } + +} diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/modules/CFMMBacklog.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/modules/CFMMBacklog.scala index c6577e2a..5a4a2b90 100644 --- a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/modules/CFMMBacklog.scala +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/modules/CFMMBacklog.scala @@ -6,11 +6,11 @@ trait CFMMBacklog[F[_]] { /** Put an order to the backlog. */ - def put(order: CFMMOrder.Any): F[Unit] + def put(order: CFMMOrder.AnyOrder): F[Unit] /** Get candidate order for execution. Blocks until an order is available. */ - def get: F[CFMMOrder.Any] + def get: F[CFMMOrder.AnyOrder] /** Put an order from the backlog. */ diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/processes/Executor.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/processes/Executor.scala index cd688a6f..9560dfe2 100644 --- a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/processes/Executor.scala +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/processes/Executor.scala @@ -61,7 +61,7 @@ object Executor { .evalMap { rec => service .executeAttempt(rec.message) - .handleWith[Throwable](e => warnCause"Order execution failed fatally" (e) as none[CFMMOrder.Any]) + .handleWith[Throwable](e => warnCause"Order execution failed fatally" (e) as none[CFMMOrder.AnyOrder]) .local(_ => TraceId.fromString(rec.message.id.value)) .tupleLeft(rec) } @@ -70,10 +70,10 @@ object Executor { case (_, Some(order)) => eval(now.millis) >>= { case ts if ts - order.timestamp < conf.orderLifetime.toMillis => - eval(warn"Failed to execute $order. Going to retry.") >> + eval(info"Failed to execute $order. Going to retry. Order ts: ${order.timestamp}, $ts, ${conf.orderLifetime.toMillis}") >> orders.retry((order.id -> order).pure[F]) case _ => - eval(warn"Failed to execute $order. Order expired.") + eval(info"Failed to execute $order. Order expired.") } } .evalMap { case (rec, _) => rec.commit } diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/processes/NetworkContextUpdater.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/processes/NetworkContextUpdater.scala new file mode 100644 index 00000000..842b972d --- /dev/null +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/processes/NetworkContextUpdater.scala @@ -0,0 +1,51 @@ +package org.ergoplatform.dex.executor.amm.processes + +import cats.Monad +import cats.effect.concurrent.Ref +import cats.effect.{Sync, Timer} +import org.ergoplatform.dex.domain.NetworkContext +import org.ergoplatform.ergo.domain.EpochParams +import org.ergoplatform.ergo.services.explorer.ErgoExplorer +import tofu.Catches +import tofu.logging.{Logging, Logs} +import tofu.streams.Evals +import tofu.syntax.logging._ +import tofu.syntax.handle._ +import tofu.syntax.monadic._ +import tofu.syntax.streams.all._ + +import scala.concurrent.duration.DurationInt + +trait NetworkContextUpdater[S[_]] { + def run: S[Unit] +} + +object NetworkContextUpdater { + + def make[I[_]: Sync, S[_]: Evals[*[_], F]: Monad: Catches, F[_]: Sync: Timer](implicit + network: ErgoExplorer[F], + logs: Logs[I, F] + ): I[(NetworkContextUpdater[S], Ref[F, NetworkContext])] = + Ref.in[I, F, NetworkContext](NetworkContext(0, EpochParams.empty)).flatMap { ref => + logs.forService[NetworkContextUpdater[S]].map(implicit __ => new Live[S, F](ref) -> ref) + } + + final private class Live[S[_]: Evals[*[_], F]: Monad: Catches, F[_]: Monad: Timer: Logging](context: Ref[F, NetworkContext])( + implicit network: ErgoExplorer[F] + ) extends NetworkContextUpdater[S] { + + def run: S[Unit] = { + def execute = eval(for { + height <- network.getCurrentHeight + params <- network.getEpochParams + _ <- info"New height is: $height" + _ <- context.set(NetworkContext(height, params)) + _ <- Timer[F].sleep(5.seconds) + } yield ()).handleWith { err: Throwable => + eval(info"The error: ${err.getMessage} occurred on context update process") + } + execute >> run + } + + } +} diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/services/DexOutputResolver.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/services/DexOutputResolver.scala new file mode 100644 index 00000000..806a9c0b --- /dev/null +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/services/DexOutputResolver.scala @@ -0,0 +1,93 @@ +package org.ergoplatform.dex.executor.amm.services + +import cats.Monad +import cats.effect.Sync +import cats.effect.concurrent.Ref +import derevo.derive +import org.ergoplatform.ErgoAddressEncoder +import org.ergoplatform.dex.executor.amm.config.ExchangeConfig +import org.ergoplatform.ergo.domain.Output +import org.ergoplatform.ergo.services.explorer.ErgoExplorer +import org.ergoplatform.ergo.{Address, PrivKeyGenerator} +import tofu.higherKind.Mid +import tofu.higherKind.derived.representableK +import tofu.lift.IsoK +import tofu.logging.{Logging, Logs} +import tofu.syntax.logging._ +import tofu.syntax.monadic._ + +@derive(representableK) +trait DexOutputResolver[F[_]] { + def getLatest: F[Option[Output]] + + def setPredicted(output: Output): F[Unit] + + def invalidateAndUpdate: F[Unit] +} + +object DexOutputResolver { + + def make[I[_]: Sync, F[_]: Sync]( + exchange: ExchangeConfig + )(implicit + explorer: ErgoExplorer[F], + e: ErgoAddressEncoder, + logs: Logs[I, F], + iso: IsoK[F, I] + ): I[DexOutputResolver[F]] = + logs.forService[DexOutputResolver[F]].flatMap { implicit __ => + Ref.in[I, F, Option[Output]](None).flatMap { ref => + val address = PrivKeyGenerator.make(exchange.mnemonic)._2 + + iso + .to( + explorer + .getUnspentOutputByAddress(address) + .flatMap { maybeOutput => + ref.set(maybeOutput.map(Output.fromExplorer)) + } + ) + .map { _ => + new Tracing[F] attach new InMemory[F](address, ref): DexOutputResolver[F] + } + } + } + + final private class InMemory[F[_]: Monad](address: Address, cache: Ref[F, Option[Output]])(implicit + explorer: ErgoExplorer[F] + ) extends DexOutputResolver[F] { + def getLatest: F[Option[Output]] = cache.get + + def invalidateAndUpdate: F[Unit] = + explorer.getUnspentOutputByAddress(address).flatMap { output => + output.fold(unit)(output => cache.set(Some(Output.fromExplorer(output)))) + } + + def setPredicted(output: Output): F[Unit] = + cache.set(Some(output)) + } + + final private class Tracing[F[_]: Monad: Logging] extends DexOutputResolver[Mid[F, *]] { + + def getLatest: Mid[F, Option[Output]] = + for { + _ <- info"getLatest()" + r <- _ + _ <- info"getLatest() -> $r" + } yield r + + def setPredicted(output: Output): Mid[F, Unit] = + for { + _ <- info"setPredicted(${output.boxId})" + r <- _ + _ <- info"setPredicted() -> success" + } yield r + + def invalidateAndUpdate: Mid[F, Unit] = + for { + _ <- info"invalidateAndUpdate()" + r <- _ + _ <- info"invalidateAndUpdate() -> success" + } yield r + } +} diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/services/Execution.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/services/Execution.scala index f69475cc..3a54d516 100644 --- a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/services/Execution.scala +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/services/Execution.scala @@ -2,10 +2,10 @@ package org.ergoplatform.dex.executor.amm.services import cats.syntax.option._ import cats.{Functor, Monad} -import org.ergoplatform.dex.domain.amm.CFMMOrder -import org.ergoplatform.dex.domain.amm.CFMMOrder._ +import org.ergoplatform.dex.domain.amm.CFMMOrder.AnyOrder +import org.ergoplatform.dex.domain.amm.{CFMMOrder, CFMMOrderType} import org.ergoplatform.dex.domain.errors.TxFailed -import org.ergoplatform.dex.executor.amm.domain.errors.{ExecutionFailed, IncorrectMultiAddressSwapTree} +import org.ergoplatform.dex.executor.amm.domain.errors.{ExecutionFailed, IncorrectMultiAddressTree} import org.ergoplatform.dex.executor.amm.interpreters.CFMMInterpreter import org.ergoplatform.dex.executor.amm.repositories.CFMMPools import org.ergoplatform.dex.protocol.amm.AMMType.CFMMType @@ -21,7 +21,7 @@ trait Execution[F[_]] { /** Try to execute a given order if possible. * @return `None` in case the order is executed, `Some(order)` otherwise. */ - def executeAttempt(op: CFMMOrder.Any): F[Option[CFMMOrder.Any]] + def executeAttempt(op: CFMMOrder.AnyOrder): F[Option[CFMMOrder.AnyOrder]] } object Execution { @@ -30,6 +30,7 @@ object Execution { pools: CFMMPools[F], interpreter: CFMMInterpreter[CFMMType, F], network: ErgoNetwork[F], + resolver: DexOutputResolver[F], logs: Logs[I, F] ): I[Execution[F]] = logs.forService[Execution[F]].map(implicit l => new Live[F]) @@ -38,47 +39,55 @@ object Execution { pools: CFMMPools[F], interpreter: CFMMInterpreter[CFMMType, F], network: ErgoNetwork[F], + resolver: DexOutputResolver[F], errParser: TxSubmissionErrorParser ) extends Execution[F] { - def executeAttempt(order: CFMMOrder.Any): F[Option[CFMMOrder.Any]] = + def executeAttempt(order: CFMMOrder.AnyOrder): F[Option[CFMMOrder.AnyOrder]] = pools.get(order.poolId) >>= { case Some(pool) => - val interpretF = - order match { - case deposit: Deposit => interpreter.deposit(deposit, pool) - case redeem: Redeem => interpreter.redeem(redeem, pool) - case swap: SwapAny => interpreter.swap(swap, pool) + val interpretF = { + (order, order.orderType) match { + case (swap: CFMMOrder.AnySwap, _: CFMMOrderType.SwapType) => interpreter.swap(swap, pool) + case (redeem: CFMMOrder.AnyRedeem, _: CFMMOrderType.RedeemType) => interpreter.redeem(redeem, pool) + case (deposit: CFMMOrder.AnyDeposit, _: CFMMOrderType.DepositType) => interpreter.deposit(deposit, pool) } + } val executeF = for { - (transaction, nextPool) <- interpretF - finalizeF = network.submitTransaction(transaction) >> pools.put(nextPool) - res <- (finalizeF as none[CFMMOrder.Any]) + _ <- info"Pool is: $pool ${pool.isNative} -> $order " + (transaction, nextPool, nextOrder) <- interpretF + finalizeF = + network.submitTransaction(transaction) >> pools.put(nextPool) >> resolver.setPredicted( + nextOrder.state.entity.output + ) + res <- (finalizeF as none[CFMMOrder.AnyOrder]) .handleWith[TxFailed] { e => - network.checkTransaction(transaction).flatMap { - case Some(errText) => - val invalidInputs = errParser.missedInputs(errText) - val poolBoxId = pool.box.boxId - val invalidPool = invalidInputs.exists { case (boxId, _) => boxId == poolBoxId } - if (invalidPool) - warnCause"PoolState{poolId=${pool.poolId}, boxId=$poolBoxId} is invalidated. Validation result: $errText" ( - e - ) >> - pools.invalidate(pool.poolId, poolBoxId) as Some(order) - else - warnCause"Order{id=${order.id}} is discarded due to TX error. Validation result: $errText" ( - e - ) as none - case _ => - warnCause"Order{id=${order.id}} is discarded due to TX error." (e) as none + info"Error is: ${e.getMessage}" >> + resolver.getLatest.flatMap { output => + val invalidInputs = errParser.missedInputs(e.reason) + val poolBoxId = pool.box.boxId + val invalidPool = invalidInputs.contains(TxSubmissionErrorParser.InvalidPoolIndex) + val invalidDexOutput = invalidInputs.contains(TxSubmissionErrorParser.InvalidDexOutputIndex) + if (invalidPool && invalidDexOutput) { + warnCause"PoolState{poolId=${pool.poolId}, boxId=$poolBoxId} is invalidated. And dex output ${output + .map(_.boxId)} is invalidated" (e) >> + pools.invalidate(pool.poolId, poolBoxId) >> resolver.invalidateAndUpdate as order.some + } else if (invalidPool) + warnCause"PoolState{poolId=${pool.poolId}, boxId=$poolBoxId} is invalidated" (e) >> + pools.invalidate(pool.poolId, poolBoxId) as order.some + else if (invalidDexOutput) + warnCause"Dex output ${output.map(_.boxId)} is invalidated" (e) >> + resolver.invalidateAndUpdate as order.some + else + warnCause"Order{id=${order.id}} is discarded due to TX error" (e) as none[AnyOrder] } } } yield res executeF.handleWith[ExecutionFailed] { - case e: IncorrectMultiAddressSwapTree => warnCause"Order execution failed" (e) as none - case e => warnCause"Order execution failed" (e) as Some(order) + case e: IncorrectMultiAddressTree => warnCause"Order execution failed critically" (e) as none + case e => warnCause"Order execution failed" (e) as Some(order) } case None => warn"Order{id=${order.id}} references an unknown Pool{id=${order.poolId}}" as Some(order) diff --git a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/streaming.scala b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/streaming.scala index 3ae0a6fe..d8665ab0 100644 --- a/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/streaming.scala +++ b/modules/amm-executor/src/main/scala/org/ergoplatform/dex/executor/amm/streaming.scala @@ -6,8 +6,8 @@ import org.ergoplatform.dex.domain.amm.{CFMMOrder, OrderId} object streaming { - type CFMMConsumerIn[F[_], G[_], Status[_]] = Consumer.Aux[OrderId, Status[CFMMOrder.Any], KafkaOffset, F, G] - type CFMMConsumerRetries[F[_], G[_]] = Consumer.Aux[OrderId, Delayed[CFMMOrder.Any], KafkaOffset, F, G] - type CFMMProducerRetries[F[_]] = Producer[OrderId, Delayed[CFMMOrder.Any], F] - type CFMMCircuit[F[_], G[_]] = StreamingCircuit[OrderId, CFMMOrder.Any, F, G] + type CFMMConsumerIn[F[_], G[_], Status[_]] = Consumer.Aux[OrderId, Status[CFMMOrder.AnyOrder], KafkaOffset, F, G] + type CFMMConsumerRetries[F[_], G[_]] = Consumer.Aux[OrderId, Delayed[CFMMOrder.AnyOrder], KafkaOffset, F, G] + type CFMMProducerRetries[F[_]] = Producer[OrderId, Delayed[CFMMOrder.AnyOrder], F] + type CFMMCircuit[F[_], G[_]] = StreamingCircuit[OrderId, CFMMOrder.AnyOrder, F, G] } diff --git a/modules/amm-executor/src/test/scala/org/ergoplatfrom/dex/executor/amm/HowTo.scala b/modules/amm-executor/src/test/scala/org/ergoplatfrom/dex/executor/amm/HowTo.scala new file mode 100644 index 00000000..4758976c --- /dev/null +++ b/modules/amm-executor/src/test/scala/org/ergoplatfrom/dex/executor/amm/HowTo.scala @@ -0,0 +1,75 @@ +package org.ergoplatfrom.dex.executor.amm + +import org.ergoplatform.{ErgoAddressEncoder, P2PKAddress} +import org.ergoplatform.dex.CatsPlatform +import org.ergoplatform.ergo.Address +import org.ergoplatform.wallet.mnemonic.Mnemonic +import org.ergoplatform.wallet.secrets.{DerivationPath, ExtendedSecretKey} +import org.ergoplatform.wallet.secrets.ExtendedSecretKey.deriveMasterKey +import org.scalatest.matchers.should +import org.scalatest.propspec.AnyPropSpec +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks +import scorex.util.encode.Base16 + +final class HowTo + extends AnyPropSpec + with should.Matchers + with ScalaCheckPropertyChecks + with CatsPlatform { + + implicit val addressEncoder: ErgoAddressEncoder = new ErgoAddressEncoder(ErgoAddressEncoder.MainnetNetworkPrefix) + + /** How to generate a proper seed phrase for AMM bots. + * + * Seed phrase is the same as mnemonic key. + * There is no way to generate seed phrase from your address. + * There is no way to use address you wanted if you don't have seed phrase for it. + * You can only use the address generated by the bots from the seed phrase you provided using eip3. + * Save your seed phrase for every wallet you care about!!! + * It will be safer and better for you not to use your regular wallet but create a new one for bots. + * Bots can take funds only from the first address, generated from provided seed phrase using eip3 standard. + * It's crucial to have exactly 1 (one) box on address with all funds(ERGs for miner fees), otherwise, bots behaviour may be unpredictable. + * + * If you don't have your seed phrase: + * 1. Generate a new unique seed phrase via wallet (e.g. nautilus - just create a new wallet and save seed phrase) + * or run(section `How to run script`) this script and check line `Seed phrase is:` and save seed phrase (24 words) from stdout. + * 2. SAVE YOUR SEED PHRASE SOMEWHERE. IF YOU WILL LOSE IT, YOU WON'T BE ABLE TO RESTORE IT FROM ADDRESS. + * 3. Deposit funds on exactly the address, you see in `Address is:` line in stdout. + * 4. Put your seed phrase into config file and run bots. + * + * + * If you already have your seed phrase key (CAUTION: DON'T USE YOUR REGULAR HOT WALLET, ALWAYS GENERATE NEW ONE FOR BOTS): + * 1. Ensure you have funds on the same address bot will use (bots will always take exactly the first address from your wallet, generated using eip3) + * To check it, put your seed phrase in the `seedPhrase` variable (val seedPhrase: String = "YOUR SEED PHRASE"), + * run the script and check the address stdout. If address, printed by script, is the same, as address you keep required funds on, + * you are allowed to use this seed phrase for bots, otherwise, move your funds to required address (from script stdout). + * 2. Put your seed phrase into config file and run bots. + * + * + * -- How to run script: + * 1. Install git: `https://git-scm.com/` + * 2. Install java (e.g. for mac os `brew install openjdk@11` or `https://www.java.com/en/download/help/linux_x64_install.html`) + * 3. Install sbt: `https://www.scala-sbt.org/download.html` + * 4. Clone repo: `git clone https://github.com/spectrum-finance/ergo-dex-backend.git` + * 5. Move from cli into this repo (e.g. cd path_to_folder/ergo-dex-backend/ in linux or just open terminal in cloned folder) + * and type sbt "; project amm-executor; test" into CLI. + * + */ + + val seedPhrase: String = new Mnemonic("english", 256).generate.get + val seed: Array[Byte] = Mnemonic.toSeed(seedPhrase) + val SK: ExtendedSecretKey = deriveMasterKey(seed) + val path = "m/44'/429'/0'/0/0" + val derivationPath = DerivationPath.fromEncoded(path).get + val nextSK = SK.derive(derivationPath).asInstanceOf[ExtendedSecretKey] + val address = Address.fromStringUnsafe(addressEncoder.toString(P2PKAddress(nextSK.publicImage))) + val SKHex = Base16.encode(nextSK.keyBytes) + + println("---------------------------") + println(s"Seed phrase is: $seedPhrase") + println("---------------------------") + println(s"Address is: $address") + println("---------------------------") + println(s"Secrete key hex is: $SKHex") + println("---------------------------") +} diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/common/package.scala b/modules/dex-core/src/main/scala/org/ergoplatform/common/package.scala index b7e9f602..9788554f 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/common/package.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/common/package.scala @@ -39,6 +39,7 @@ package object common { implicit val show: Show[HexString] = _.unwrapped implicit val loggable: Loggable[HexString] = Loggable.show + implicit def plainCodec: Codec.PlainCodec[HexString] = deriveCodec[String, CodecFormat.TextPlain, HexString]( fromString[Either[Throwable, *]](_), diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/dex/configs/MonetaryConfig.scala b/modules/dex-core/src/main/scala/org/ergoplatform/dex/configs/MonetaryConfig.scala index 525e56c3..e62bca76 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/dex/configs/MonetaryConfig.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/dex/configs/MonetaryConfig.scala @@ -6,6 +6,6 @@ import tofu.Context import tofu.logging.derivation.loggable @derive(pureconfigReader, loggable) -final case class MonetaryConfig(minerFee: Long, minDexFee: Long, minBoxValue: Long) +final case class MonetaryConfig(minerFee: Long, minDexFee: Long, minBoxValue: Long, minDexTokenFee: Long) object MonetaryConfig extends Context.Companion[MonetaryConfig] diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/CFMMOrder.scala b/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/CFMMOrder.scala index b1fe5867..5586833c 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/CFMMOrder.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/CFMMOrder.scala @@ -1,14 +1,13 @@ package org.ergoplatform.dex.domain.amm import cats.Show +import derevo.cats.show import derevo.circe.{decoder, encoder} import derevo.derive -import io.circe.derivation.{deriveDecoder, deriveEncoder} +import io.circe.derivation.{deriveCodec, deriveDecoder, deriveEncoder} import io.circe.syntax._ -import cats.syntax.either._ -import cats.syntax.show._ -import derevo.cats.show import io.circe.{Decoder, Encoder} +import org.ergoplatform.dex.domain.amm.CFMMOrderType.{DepositType, RedeemType, SwapType} import org.ergoplatform.ergo.domain.Output import org.ergoplatform.ergo.{PubKey, SErgoTree} import tofu.logging.Loggable @@ -26,95 +25,186 @@ sealed trait CFMMOrder[+O <: CFMMOrderType] { object CFMMOrder { - type Any = CFMMOrder[CFMMOrderType.Any] - type SwapAny = CFMMOrder[CFMMOrderType.Swap] + type AnyOrder = CFMMOrder[CFMMOrderType] + type AnySwap = CFMMOrder[SwapType] + type AnyDeposit = CFMMOrder[DepositType] + type AnyRedeem = CFMMOrder[RedeemType] - implicit val encoderAny: Encoder[CFMMOrder.Any] = { - case deposit: Deposit => deposit.asJson - case redeem: Redeem => redeem.asJson - case swap: Swap => swap.asJson - case swapMulti: SwapMultiAddress => swapMulti.asJson + implicit def decoderOrder: Decoder[CFMMOrder[CFMMOrderType]] = deriveDecoder + implicit def encoderOrder: Encoder[CFMMOrder[CFMMOrderType]] = deriveEncoder + + @derive(loggable) + final case class DepositErgFee( + poolId: PoolId, + maxMinerFee: Long, + timestamp: Long, + params: DepositParams[PubKey], + box: Output + ) extends CFMMOrder[DepositType] { + val orderType: DepositType = DepositType.depositErgFee } - implicit val decoderAny: Decoder[CFMMOrder.Any] = x => - x.as[Deposit] - .leftFlatMap(_ => x.as[Redeem]) - .leftFlatMap(_ => x.as[Swap]) - .leftFlatMap(_ => x.as[SwapMultiAddress]) - - implicit val showCFMMOrder: Show[CFMMOrder.Any] = { - case d: Deposit => d.show - case r: Redeem => r.show - case s: Swap => s.show - case s: SwapMultiAddress => s.show + object DepositErgFee { + + implicit val encoder: Encoder.AsObject[DepositErgFee] = + deriveEncoder.mapJsonObject(_.add("orderType", DepositType.depositErgFee.asJson)) + + implicit val decoder: Decoder[DepositErgFee] = + deriveDecoder + .validate( + _.downField("orderType").as[DepositType.DepositErgFee].toOption.nonEmpty, + "Incorrect DepositErgFee order. There is no order type in json." + ) } - implicit val showSwapAny: Show[SwapAny] = { - case s: Swap => s.show - case s: SwapMultiAddress => s.show + @derive(loggable) + final case class DepositTokenFee( + poolId: PoolId, + maxMinerFee: Long, + timestamp: Long, + params: DepositParams[SErgoTree], + box: Output + ) extends CFMMOrder[DepositType] { + val orderType: DepositType = DepositType.depositTokenFee } - implicit val loggableCFMMOrder: Loggable[CFMMOrder.Any] = Loggable.show + object DepositTokenFee { - implicit val loggableSwapAny: Loggable[SwapAny] = Loggable.show + implicit val encoder: Encoder.AsObject[DepositTokenFee] = + deriveEncoder.mapJsonObject(_.add("orderType", DepositType.depositTokenFee.asJson)) - @derive(show, loggable, encoder, decoder) - final case class Deposit(poolId: PoolId, maxMinerFee: Long, timestamp: Long, params: DepositParams, box: Output) - extends CFMMOrder[CFMMOrderType.Deposit] { - val orderType: CFMMOrderType.Deposit = CFMMOrderType.deposit + implicit val decoder: Decoder[DepositTokenFee] = + deriveDecoder + .validate( + _.downField("orderType").as[DepositType.DepositTokenFee].toOption.nonEmpty, + "Incorrect DepositTokenFee order. There is no order type in json." + ) } - @derive(show, loggable, encoder, decoder) - final case class Redeem(poolId: PoolId, maxMinerFee: Long, timestamp: Long, params: RedeemParams, box: Output) - extends CFMMOrder[CFMMOrderType.Redeem] { - val orderType: CFMMOrderType.Redeem = CFMMOrderType.redeem + @derive(loggable) + final case class RedeemErgFee( + poolId: PoolId, + maxMinerFee: Long, + timestamp: Long, + params: RedeemParams[PubKey], + box: Output + ) extends CFMMOrder[RedeemType] { + val orderType: RedeemType = RedeemType.redeemErgFee } - @derive(show, loggable) - final case class Swap( + object RedeemErgFee { + + implicit val encoder: Encoder.AsObject[RedeemErgFee] = + deriveEncoder.mapJsonObject(_.add("orderType", RedeemType.redeemErgFee.asJson)) + + implicit val decoder: Decoder[RedeemErgFee] = + deriveDecoder + .validate( + _.downField("orderType").as[RedeemType.RedeemErgFee].toOption.nonEmpty, + "Incorrect RedeemErgFee order. There is no order type in json." + ) + } + + @derive(loggable) + final case class RedeemTokenFee( + poolId: PoolId, + maxMinerFee: Long, + timestamp: Long, + params: RedeemParams[SErgoTree], + box: Output + ) extends CFMMOrder[RedeemType] { + val orderType: RedeemType = RedeemType.redeemTokenFee + } + + object RedeemTokenFee { + + implicit val encoder: Encoder.AsObject[RedeemTokenFee] = + deriveEncoder.mapJsonObject(_.add("orderType", RedeemType.redeemTokenFee.asJson)) + + implicit val decoder: Decoder[RedeemTokenFee] = + deriveDecoder + .validate( + _.downField("orderType").as[RedeemType.RedeemTokenFee].toOption.nonEmpty, + "Incorrect RedeemTokenFee order. There is no order type in json." + ) + } + + @derive(loggable, encoder, decoder) + sealed trait SwapErg extends CFMMOrder[SwapType] + + @derive(loggable) + final case class SwapP2Pk( poolId: PoolId, maxMinerFee: Long, timestamp: Long, params: SwapParams[PubKey], box: Output - ) extends CFMMOrder[CFMMOrderType.P2Pk] { - val orderType: CFMMOrderType.P2Pk = CFMMOrderType.swapP2Pk + ) extends SwapErg { + val orderType: SwapType = SwapType.swapP2Pk } - object Swap { + object SwapP2Pk { - implicit val encoderSwap: Encoder[Swap] = - deriveEncoder.mapJsonObject(_.add("orderType", CFMMOrderType.swapP2Pk.asJson)) + implicit val encoder: Encoder.AsObject[SwapP2Pk] = + deriveEncoder.mapJsonObject(_.add("orderType", SwapType.swapP2Pk.asJson)) - implicit val decoderSwap: Decoder[Swap] = + implicit val decoder: Decoder[SwapP2Pk] = deriveDecoder .validate( - _.downField("orderType").as[CFMMOrderType.P2Pk].toOption.nonEmpty, - "Incorrect swap p2pk order. There is no order type in json." + _.downField("orderType").as[SwapType.SwapP2Pk].toOption.nonEmpty, + "Incorrect SwapP2Pk order. There is no order type in json." ) } - @derive(show, loggable) + @derive(loggable) final case class SwapMultiAddress( poolId: PoolId, maxMinerFee: Long, timestamp: Long, params: SwapParams[SErgoTree], box: Output - ) extends CFMMOrder[CFMMOrderType.MultiAddress] { - val orderType: CFMMOrderType.MultiAddress = CFMMOrderType.swapMultiAddress + ) extends SwapErg { + val orderType: SwapType = SwapType.swapMultiAddress } object SwapMultiAddress { - implicit val encoderSwapMultiAddress: Encoder[SwapMultiAddress] = - deriveEncoder.mapJsonObject(_.add("orderType", CFMMOrderType.swapMultiAddress.asJson)) + implicit val encoder: Encoder.AsObject[SwapMultiAddress] = + deriveEncoder.mapJsonObject(_.add("orderType", SwapType.swapMultiAddress.asJson)) - implicit val decoderSwapMultiAddress: Decoder[SwapMultiAddress] = + implicit val decoder: Decoder[SwapMultiAddress] = deriveDecoder .validate( - _.downField("orderType").as[CFMMOrderType.MultiAddress].toOption.nonEmpty, - "Incorrect swap multi address order. There is no order type in json." + _.downField("orderType").as[SwapType.SwapMultiAddress].toOption.nonEmpty, + "Incorrect SwapMultiAddress order. There is no order type in json." ) } + + @derive(show, loggable, encoder, decoder) + final case class SwapTokenFee( + poolId: PoolId, + maxMinerFee: Long, + timestamp: Long, + params: SwapParams[SErgoTree], + box: Output, + reservedExFee: Long + ) extends CFMMOrder[SwapType.SwapTokenFee] { + val orderType: SwapType.SwapTokenFee = SwapType.swapTokenFee + } + + implicit def showAny: Show[AnyOrder] = _.toString + + implicit def loggableAny: Loggable[CFMMOrder.AnyOrder] = Loggable.show + + implicit def showAnySwap: Show[AnySwap] = _.toString + + implicit def loggableAnySwap: Loggable[CFMMOrder.AnySwap] = Loggable.show + + implicit def showAnyDeposit: Show[AnyDeposit] = _.toString + + implicit def loggableAnyDeposit: Loggable[CFMMOrder.AnyDeposit] = Loggable.show + + implicit def showAnyRedeem: Show[AnyRedeem] = _.toString + + implicit def loggableAnyRedeem: Loggable[CFMMOrder.AnyRedeem] = Loggable.show } diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/CFMMOrderType.scala b/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/CFMMOrderType.scala index 60dfc0a1..d9dccaaf 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/CFMMOrderType.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/CFMMOrderType.scala @@ -1,74 +1,116 @@ package org.ergoplatform.dex.domain.amm -import cats.Show -import io.circe.{Codec, Decoder, Encoder} -import tofu.logging.Loggable +import cats.syntax.either._ +import io.circe.{Decoder, Encoder} sealed trait CFMMOrderType object CFMMOrderType { - sealed trait Swap extends CFMMOrderType + sealed trait SwapType extends CFMMOrderType - sealed trait MultiAddress extends Swap + object SwapType { - object MultiAddress { + sealed trait SwapErgFee extends SwapType - implicit val show: Show[MultiAddress] = _ => "SwapMultiAddress" + trait SwapMultiAddress extends SwapErgFee - implicit val loggable: Loggable[MultiAddress] = Loggable.show + object SwapMultiAddress { - implicit val codecMultiAddressSwapType: Codec[MultiAddress] = Codec - .from(Decoder[String], Encoder[String]) - .iemap(str => - Either.cond(str == "swapMultiAddress", swapMultiAddress, s"incorrect swap multi address type: $str") - )(_ => "swapMultiAddress") - } + implicit val encoderSwapMultiAddress: Encoder[SwapMultiAddress] = + Encoder[String].contramap(_ => "swapMultiAddress") + + implicit val decoderSwapMultiAddress: Decoder[SwapMultiAddress] = Decoder[String].emap { + case "swapMultiAddress" => swapMultiAddress.asRight + case nonsense => s"Invalid type in SwapMultiAddress: $nonsense".asLeft + } + } + + trait SwapP2Pk extends SwapErgFee + + object SwapP2Pk { + implicit val encoderSwapP2Pk: Encoder[SwapP2Pk] = Encoder[String].contramap(_ => "swapP2Pk") - sealed trait P2Pk extends Swap + implicit val decoderSwapP2Pk: Decoder[SwapP2Pk] = Decoder[String].emap { + case "swapP2Pk" => swapP2Pk.asRight + case nonsense => s"Invalid type in SwapP2Pk: $nonsense".asLeft + } + } - object P2Pk { + trait SwapTokenFee extends SwapType - implicit val show: Show[P2Pk] = _ => "SwapP2Pk" + def swapMultiAddress: SwapMultiAddress = new SwapMultiAddress {} - implicit val loggable: Loggable[P2Pk] = Loggable.show + def swapP2Pk: SwapP2Pk = new SwapP2Pk {} - implicit val codecP2PkSwapType: Codec[P2Pk] = Codec - .from(Decoder[String], Encoder[String]) - .iemap(str => Either.cond(str == "swapP2Pk", swapP2Pk, s"incorrect swap p2pk type: $str"))(_ => "swapP2Pk") + def swapTokenFee: SwapTokenFee = new SwapTokenFee {} } - sealed abstract class Redeem extends CFMMOrderType + sealed trait RedeemType extends CFMMOrderType - object Redeem { + object RedeemType { + sealed trait RedeemErgFee extends RedeemType - implicit val show: Show[Redeem] = _ => "Redeem" + object RedeemErgFee { - implicit val loggable: Loggable[Redeem] = Loggable.show + implicit val encoder: Encoder[RedeemErgFee] = + Encoder[String].contramap(_ => "redeemErgFee") - implicit val codecRedeemType: Codec[Redeem] = Codec - .from(Decoder[String], Encoder[String]) - .iemap(str => Either.cond(str == "redeem", redeem, s"incorrect redeem type: $str"))(_ => "redeem") - } + implicit val decoder: Decoder[RedeemErgFee] = Decoder[String].emap { + case "redeemErgFee" => redeemErgFee.asRight + case nonsense => s"Invalid type in RedeemErgFee: $nonsense".asLeft + } + } + + sealed trait RedeemTokenFee extends RedeemType - sealed abstract class Deposit extends CFMMOrderType + object RedeemTokenFee { - object Deposit { + implicit val encoder: Encoder[RedeemTokenFee] = + Encoder[String].contramap(_ => "redeemTokenFee") - implicit val show: Show[Deposit] = _ => "Deposit" + implicit val decoder: Decoder[RedeemTokenFee] = Decoder[String].emap { + case "redeemTokenFee" => redeemTokenFee.asRight + case nonsense => s"Invalid type in RedeemTokenFee: $nonsense".asLeft + } + } - implicit val loggable: Loggable[Deposit] = Loggable.show + def redeemErgFee: RedeemErgFee = new RedeemErgFee {} - implicit val codecDepositType: Codec[Deposit] = Codec - .from(Decoder[String], Encoder[String]) - .iemap(str => Either.cond(str == "deposit", deposit, s"incorrect deposit type: $str"))(_ => "deposit") + def redeemTokenFee: RedeemTokenFee = new RedeemTokenFee {} } - type Any = CFMMOrderType + sealed trait DepositType extends CFMMOrderType + + object DepositType { + + sealed trait DepositErgFee extends DepositType - def deposit: Deposit = new Deposit {} - def redeem: Redeem = new Redeem {} - def swap: Swap = new Swap {} - def swapMultiAddress: MultiAddress = new MultiAddress {} - def swapP2Pk: P2Pk = new P2Pk {} + object DepositErgFee { + + implicit val encoder: Encoder[DepositErgFee] = + Encoder[String].contramap(_ => "depositErgFee") + + implicit val decoder: Decoder[DepositErgFee] = Decoder[String].emap { + case "depositErgFee" => depositErgFee.asRight + case nonsense => s"Invalid type in DepositErgFee: $nonsense".asLeft + } + } + + sealed trait DepositTokenFee extends DepositType + + object DepositTokenFee { + + implicit val encoder: Encoder[DepositTokenFee] = + Encoder[String].contramap(_ => "depositTokenFee") + + implicit val decoder: Decoder[DepositTokenFee] = Decoder[String].emap { + case "depositTokenFee" => depositTokenFee.asRight + case nonsense => s"Invalid type in DepositTokenFee: $nonsense".asLeft + } + } + + def depositErgFee: DepositErgFee = new DepositErgFee {} + def depositTokenFee: DepositTokenFee = new DepositTokenFee {} + } } diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/CFMMVersionedOrder.scala b/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/CFMMVersionedOrder.scala index 069b7f3d..c1a59d59 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/CFMMVersionedOrder.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/CFMMVersionedOrder.scala @@ -45,15 +45,15 @@ object CFMMVersionedOrder { type Any = CFMMVersionedOrder[CFMMOrderVersion, CFMMOrderType] - type AnySwap = CFMMVersionedOrder[CFMMOrderVersion, CFMMOrderType.Swap] + type AnySwap = CFMMVersionedOrder[CFMMOrderVersion, CFMMOrderType.SwapType] - type AnyRedeem = CFMMVersionedOrder[CFMMOrderVersion, CFMMOrderType.Redeem] + type AnyRedeem = CFMMVersionedOrder[CFMMOrderVersion, CFMMOrderType.RedeemType] - type AnyDeposit = CFMMVersionedOrder[CFMMOrderVersion, CFMMOrderType.Deposit] + type AnyDeposit = CFMMVersionedOrder[CFMMOrderVersion, CFMMOrderType.DepositType] @derive(loggable) final case class SwapP2Pk(poolId: PoolId, maxMinerFee: Long, timestamp: Long, params: SwapParams[PubKey], box: Output) - extends CFMMVersionedOrder[CFMMOrderVersion.V1, CFMMOrderType.Swap] { + extends CFMMVersionedOrder[CFMMOrderVersion.V1, CFMMOrderType.SwapType] { val version: CFMMOrderVersion.V1 = CFMMOrderVersion.v1 } @@ -84,7 +84,7 @@ object CFMMVersionedOrder { timestamp: Long, params: SwapParams[SErgoTree], box: Output - ) extends CFMMVersionedOrder[CFMMOrderVersion.V2, CFMMOrderType.Swap] { + ) extends CFMMVersionedOrder[CFMMOrderVersion.V2, CFMMOrderType.SwapType] { val version: CFMMOrderVersion.V2 = CFMMOrderVersion.v2 } @@ -110,25 +110,25 @@ object CFMMVersionedOrder { @derive(encoder, decoder, loggable) final case class SwapV0(poolId: PoolId, timestamp: Long, params: SwapParams[PubKey], box: Output) - extends CFMMVersionedOrder[CFMMOrderVersion.V0, CFMMOrderType.Swap] { + extends CFMMVersionedOrder[CFMMOrderVersion.V0, CFMMOrderType.SwapType] { val version: CFMMOrderVersion.V0 = CFMMOrderVersion.v0 } @derive(encoder, decoder, loggable) - final case class DepositV2(poolId: PoolId, maxMinerFee: Long, timestamp: Long, params: DepositParams, box: Output) - extends CFMMVersionedOrder[CFMMOrderVersion.V2, CFMMOrderType.Deposit] { + final case class DepositV2(poolId: PoolId, maxMinerFee: Long, timestamp: Long, params: DepositParams[PubKey], box: Output) + extends CFMMVersionedOrder[CFMMOrderVersion.V2, CFMMOrderType.DepositType] { val version: CFMMOrderVersion.V2 = CFMMOrderVersion.v2 } @derive(encoder, decoder, loggable) - final case class DepositV1(poolId: PoolId, timestamp: Long, params: DepositParams, box: Output) - extends CFMMVersionedOrder[CFMMOrderVersion.V1, CFMMOrderType.Deposit] { + final case class DepositV1(poolId: PoolId, timestamp: Long, params: DepositParams[PubKey], box: Output) + extends CFMMVersionedOrder[CFMMOrderVersion.V1, CFMMOrderType.DepositType] { val version: CFMMOrderVersion.V1 = CFMMOrderVersion.v1 } @derive(loggable) - final case class DepositV0(poolId: PoolId, timestamp: Long, params: DepositParams, box: Output) - extends CFMMVersionedOrder[CFMMOrderVersion.V0, CFMMOrderType.Deposit] { + final case class DepositV0(poolId: PoolId, timestamp: Long, params: DepositParams[PubKey], box: Output) + extends CFMMVersionedOrder[CFMMOrderVersion.V0, CFMMOrderType.DepositType] { val version: CFMMOrderVersion.V0 = CFMMOrderVersion.v0 } @@ -153,14 +153,14 @@ object CFMMVersionedOrder { } @derive(encoder, decoder, loggable) - final case class RedeemV1(poolId: PoolId, maxMinerFee: Long, timestamp: Long, params: RedeemParams, box: Output) - extends CFMMVersionedOrder[CFMMOrderVersion.V1, CFMMOrderType.Redeem] { + final case class RedeemV1(poolId: PoolId, maxMinerFee: Long, timestamp: Long, params: RedeemParams[PubKey], box: Output) + extends CFMMVersionedOrder[CFMMOrderVersion.V1, CFMMOrderType.RedeemType] { val version: CFMMOrderVersion.V1 = CFMMOrderVersion.v1 } @derive(encoder, decoder, loggable) - final case class RedeemV0(poolId: PoolId, timestamp: Long, params: RedeemParams, box: Output) - extends CFMMVersionedOrder[CFMMOrderVersion.V0, CFMMOrderType.Redeem] { + final case class RedeemV0(poolId: PoolId, timestamp: Long, params: RedeemParams[PubKey], box: Output) + extends CFMMVersionedOrder[CFMMOrderVersion.V0, CFMMOrderType.RedeemType] { val version: CFMMOrderVersion.V0 = CFMMOrderVersion.v0 } diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/DepositParams.scala b/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/DepositParams.scala index c644e40b..06e55537 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/DepositParams.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/DepositParams.scala @@ -4,13 +4,12 @@ import derevo.cats.show import derevo.circe.{decoder, encoder} import derevo.derive import org.ergoplatform.dex.domain.AssetAmount -import org.ergoplatform.ergo.PubKey import tofu.logging.derivation.loggable @derive(show, encoder, decoder, loggable) -final case class DepositParams( +final case class DepositParams[T]( inX: AssetAmount, inY: AssetAmount, dexFee: Long, - redeemer: PubKey + redeemer: T ) diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/RedeemParams.scala b/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/RedeemParams.scala index 46c79002..69a6608c 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/RedeemParams.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/RedeemParams.scala @@ -8,4 +8,4 @@ import org.ergoplatform.ergo.PubKey import tofu.logging.derivation.loggable @derive(show, encoder, decoder, loggable) -final case class RedeemParams(lp: AssetAmount, dexFee: Long, redeemer: PubKey) +final case class RedeemParams[T](lp: AssetAmount, dexFee: Long, redeemer: T) diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/SwapParams.scala b/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/SwapParams.scala index 6ac478ef..a4235ef6 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/SwapParams.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/amm/SwapParams.scala @@ -8,8 +8,8 @@ import tofu.logging.derivation.loggable @derive(show, encoder, decoder, loggable) final case class SwapParams[T]( - input: AssetAmount, - minOutput: AssetAmount, + baseAmount: AssetAmount, + minQuoteAmount: AssetAmount, dexFeePerTokenNum: Long, dexFeePerTokenDenom: Long, redeemer: T diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/package.scala b/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/package.scala index 188145a0..484e606d 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/package.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/dex/domain/package.scala @@ -7,6 +7,7 @@ import io.circe.{Decoder, Encoder} import io.estatico.newtype.macros.newtype import org.ergoplatform.dex.domain.Ticker import org.ergoplatform.ergo.TokenId +import org.ergoplatform.ergo.domain.Output import scodec.Codec import scodec.codecs.{uint16, utf8, variableSizeBits} import sttp.tapir.{Schema, Validator} @@ -15,6 +16,12 @@ import tofu.logging.derivation.loggable package object domain { + @derive(loggable) + @newtype + final case class DexOperatorOutput(output: Output) + + object DexOperatorOutput + @derive(loggable) final case class Price(byX: BigDecimal, byY: BigDecimal) diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/dex/protocol/amm/N2TCFMMTemplates.scala b/modules/dex-core/src/main/scala/org/ergoplatform/dex/protocol/amm/N2TCFMMTemplates.scala index b9d345b7..863b85ea 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/dex/protocol/amm/N2TCFMMTemplates.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/dex/protocol/amm/N2TCFMMTemplates.scala @@ -4,17 +4,17 @@ import org.ergoplatform.ergo.ErgoTreeTemplate object N2TCFMMTemplates { - def depositLatest: ErgoTreeTemplate = + def depositV3: ErgoTreeTemplate = ErgoTreeTemplate.unsafeFromString( - "d803d6017300d602b2a4730100d6037302eb027201d195ed93b1a4730393b1db630872027304d80bd604db63087202d605b2a5730500d6" + - "06b27204730600d6077e9973078c72060206d6087ec1720206d6099d9c7e72030672077208d60ab27204730800d60b7e8c720a0206d60c" + - "9d9c7e8cb2db6308a773090002067207720bd60ddb63087205d60eb2720d730a00ededededed938cb27204730b0001730c93c27205d072" + - "0195ed8f7209720c93b1720d730dd801d60fb2720d730e00eded92c172059999c1a7730f7310938c720f018c720a01927e8c720f02069d" + - "9c99720c7209720b720795927209720c927ec1720506997e99c1a7731106997e7203069d9c997209720c720872077312938c720e018c72" + - "0601927e8c720e0206a17209720c90b0ada5d9010f639593c2720f7313c1720f73147315d9010f599a8c720f018c720f0273167317" + "d802d601b2a4730000d6027301eb027302d195ed92b1a4730393b1db630872017304d80bd603db63087201d604b2a5730500d605b27203730600d6067e9973078c72050206d6077ec1720106d6089d9c7e72020672067207d609b27203730800d60a7e8c72090206d60b9d9c7e7309067206720ad60cdb63087204d60db2720c730a00ededededed938cb27203730b0001730c93c27204730d95ed8f7208720b93b1720c730ed801d60eb2720c730f00eded92c1720499c1a77310938c720e018c720901927e8c720e02069d9c99720b7208720a720695927208720b927ec1720406997ec1a706997e7202069d9c997208720b720772067311938c720d018c720501927e8c720d0206a17208720b90b0ada5d9010e639593c2720e7312c1720e73137314d9010e599a8c720e018c720e0273157316" ) def depositV1: ErgoTreeTemplate = + ErgoTreeTemplate.unsafeFromString( + "d803d6017300d602b2a4730100d6037302eb027201d195ed92b1a4730393b1db630872027304d80bd604db63087202d605b2a5730500d606b27204730600d6077e9973078c72060206d6087ec1720206d6099d9c7e72030672077208d60ab27204730800d60b7e8c720a0206d60c9d9c7e8cb2db6308a773090002067207720bd60ddb63087205d60eb2720d730a00ededededed938cb27204730b0001730c93c27205d0720195ed8f7209720c93b1720d730dd801d60fb2720d730e00eded92c172059999c1a7730f7310938c720f018c720a01927e8c720f02069d9c99720c7209720b720795927209720c927ec1720506997e99c1a7731106997e7203069d9c997209720c720872077312938c720e018c720601927e8c720e0206a17209720c90b0ada5d9010f639593c2720f7313c1720f73147315d9010f599a8c720f018c720f0273167317" + ) + + def depositLegacyV1: ErgoTreeTemplate = ErgoTreeTemplate.unsafeFromString( "d803d6017300d602b2a4730100d6037302eb027201d195ed93b1a4730393b1db630872027304d80bd604db63087202d605b2a5730500d6" + "06b27204730600d6077e9973078c72060206d6087ec1720206d6099d9c7e72030672077208d60ab27204730800d60b7e8c720a0206d60c" + @@ -24,7 +24,7 @@ object N2TCFMMTemplates { "01927e8c720e0206a17209720c7313" ) - def depositV0: ErgoTreeTemplate = + def depositLegacyV0: ErgoTreeTemplate = ErgoTreeTemplate.unsafeFromString( "d802d6017300d602b2a4730100eb027201d195ed93b1a4730293b1db630872027303d805d603db63087202d604b2a5730400d605b2db63" + "087204730500d606b27203730600d6077e9973078c72060206edededed938cb2720373080001730993c27204d0720192c172049999c1a7" + @@ -32,15 +32,17 @@ object N2TCFMMTemplates { "730e000206730f" ) - def redeemLatest: ErgoTreeTemplate = + def redeemV3: ErgoTreeTemplate = ErgoTreeTemplate.unsafeFromString( - "d802d6017300d602b2a4730100eb027201d195ed93b1a4730293b1db630872027303d806d603db63087202d604b2a5730400d605b2db63" + - "087204730500d606b27203730600d6077e8cb2db6308a77307000206d6087e9973088cb272037309000206ededededed938cb27203730a" + - "0001730b93c27204d07201938c7205018c720601927e9a99c17204c1a7730c069d9c72077ec17202067208927e8c720502069d9c72077e" + - "8c72060206720890b0ada5d90109639593c27209730dc17209730e730fd90109599a8c7209018c72090273107311" + "d801d601b2a4730000eb027301d195ed92b1a4730293b1db630872017303d806d602db63087201d603b2a5730400d604b2db63087203730500d605b27202730600d6067e8cb2db6308a77307000206d6077e9973088cb272027309000206ededededed938cb27202730a0001730b93c27203730c938c7204018c720501927e99c17203c1a7069d9c72067ec17201067207927e8c720402069d9c72067e8c72050206720790b0ada5d90108639593c27208730dc17208730e730fd90108599a8c7208018c72080273107311" + ) + + def redeemV1: ErgoTreeTemplate = + ErgoTreeTemplate.unsafeFromString( + "d802d6017300d602b2a4730100eb027201d195ed92b1a4730293b1db630872027303d806d603db63087202d604b2a5730400d605b2db63087204730500d606b27203730600d6077e8cb2db6308a77307000206d6087e9973088cb272037309000206ededededed938cb27203730a0001730b93c27204d07201938c7205018c720601927e9a99c17204c1a7730c069d9c72077ec17202067208927e8c720502069d9c72077e8c72060206720890b0ada5d90109639593c27209730dc17209730e730fd90109599a8c7209018c72090273107311" ) - def redeemV0: ErgoTreeTemplate = + def redeemLegacyV0: ErgoTreeTemplate = ErgoTreeTemplate.unsafeFromString( "d802d6017300d602b2a4730100eb027201d195ed93b1a4730293b1db630872027303d806d603db63087202d604b2a5730400d605b2db63" + "087204730500d606b27203730600d6077e8cb2db6308a77307000206d6087e9973088cb272037309000206edededed938cb27203730a00" + @@ -48,40 +50,43 @@ object N2TCFMMTemplates { "720602067208730d" ) - def swapSellLatest: ErgoTreeTemplate = + def swapSellV3: ErgoTreeTemplate = ErgoTreeTemplate.unsafeFromString( - "d803d6017300d602b2a4730100d6037302eb027201d195ed93b1a4730393b1db630872027304d804d604db63087202d605b2a5730500d6" + - "06b2db63087205730600d6077e8c72060206edededededed938cb2720473070001730893c27205d07201938c72060173099272077e730a" + - "06927ec172050699997ec1a7069d9c72077e730b067e730c067e720306909c9c7e8cb27204730d0002067e7203067e730e069c9a720773" + - "0f9a9c7ec17202067e7310067e9c73117e7312050690b0ada5d90108639593c272087313c1720873147315d90108599a8c7208018c7208" + - "0273167317" + "d804d601b2a4730000d6027301d6037302d6049c73037e730405eb027305d195ed92b1a4730693b1db630872017307d806d605db63087201d606b2a5730800d607db63087206d608b27207730900d6098c720802d60a95730a9d9c7e997209730b067e7202067e7203067e720906edededededed938cb27205730c0001730d93c27206730e938c720801730f92720a7e7310069573117312d801d60b997e7313069d9c720a7e7203067e72020695ed91720b731492b172077315d801d60cb27207731600ed938c720c017317927e8c720c0206720b7318909c7e8cb2720573190002067e7204069c9a720a731a9a9c7ec17201067e731b067e72040690b0ada5d9010b639593c2720b731cc1720b731d731ed9010b599a8c720b018c720b02731f7320" ) - def swapBuyLatest: ErgoTreeTemplate = + def swapSellV1: ErgoTreeTemplate = ErgoTreeTemplate.unsafeFromString( - "d802d6017300d602b2a4730100eb027201d195ed93b1a4730293b1db630872027303d804d603db63087202d604b2a5730400d6059d9c7e" + - "99c17204c1a7067e7305067e730606d6068cb2db6308a773070002edededed938cb2720373080001730993c27204d072019272057e730a" + - "06909c9c7ec17202067e7206067e730b069c9a7205730c9a9c7e8cb27203730d0002067e730e067e9c72067e730f050690b0ada5d90107" + - "639593c272077310c1720773117312d90107599a8c7207018c72070273137314" + "d803d6017300d602b2a4730100d6037302eb027201d195ed92b1a4730393b1db630872027304d804d604db63087202d605b2a5730500d606b2db63087205730600d6077e8c72060206edededededed938cb2720473070001730893c27205d07201938c72060173099272077e730a06927ec172050699997ec1a7069d9c72077e730b067e730c067e720306909c9c7e8cb27204730d0002067e7203067e730e069c9a7207730f9a9c7ec17202067e7310067e9c73117e7312050690b0ada5d90108639593c272087313c1720873147315d90108599a8c7208018c72080273167317" + ) + + def swapBuyV3: ErgoTreeTemplate = + ErgoTreeTemplate.unsafeFromString( + "d802d601b2a4730000d6029c73017e730205eb027303d195ed92b1a4730493b1db630872017305d804d603db63087201d604b2a5730600d60599c17204c1a7d606997e7307069d9c7e7205067e7308067e730906ededededed938cb27203730a0001730b93c27204730c927205730d95917206730ed801d607b2db63087204730f00ed938c7207017310927e8c7207020672067311909c7ec17201067e7202069c7e9a72057312069a9c7e8cb2720373130002067e7314067e72020690b0ada5d90107639593c272077315c1720773167317d90107599a8c7207018c72070273187319" + ) + + def swapBuyV1: ErgoTreeTemplate = + ErgoTreeTemplate.unsafeFromString( + "d802d6017300d602b2a4730100eb027201d195ed92b1a4730293b1db630872027303d804d603db63087202d604b2a5730400d6059d9c7e99c17204c1a7067e7305067e730606d6068cb2db6308a773070002edededed938cb2720373080001730993c27204d072019272057e730a06909c9c7ec17202067e7206067e730b069c9a7205730c9a9c7e8cb27203730d0002067e730e067e9c72067e730f050690b0ada5d90107639593c272077310c1720773117312d90107599a8c7207018c72070273137314" ) - def swapSellMultiAddress: ErgoTreeTemplate = + def swapSellMultiAddressV2: ErgoTreeTemplate = ErgoTreeTemplate.unsafeFromString( "d802d601b2a4730000d6027301d1ec730295ed93b1a4730393b1db630872017304d804d603db63087201d604b2a5730500d605b2db6308" + - "7204730600d6067e8c72050206edededededed938cb2720373070001730893c272047309938c720501730a9272067e730b06927ec172" + - "040699997ec1a7069d9c72067e730c067e730d067202909c9c7e8cb27203730e00020672027e730f069c9a720673109a9c7ec1720106" + - "7e7311067e9c73127e7313050690b0ada5d90107639593c272077314c1720773157316d90107599a8c7207018c72070273177318" + "7204730600d6067e8c72050206edededededed938cb2720373070001730893c272047309938c720501730a9272067e730b06927ec172" + + "040699997ec1a7069d9c72067e730c067e730d067202909c9c7e8cb27203730e00020672027e730f069c9a720673109a9c7ec1720106" + + "7e7311067e9c73127e7313050690b0ada5d90107639593c272077314c1720773157316d90107599a8c7207018c72070273177318" ) - def swapBuyMultiAddress: ErgoTreeTemplate = + def swapBuyMultiAddressV2: ErgoTreeTemplate = ErgoTreeTemplate.unsafeFromString( "d801d601b2a4730000d1ec730195ed93b1a4730293b1db630872017303d804d602db63087201d603b2a5730400d6049d9c7e99c17203c1" + - "a7067e7305067e730606d6058cb2db6308a773070002edededed938cb2720273080001730993c27203730a9272047e730b06909c9c7e" + - "c17201067e7205067e730c069c9a7204730d9a9c7e8cb27202730e0002067e730f067e9c72057e7310050690b0ada5d90106639593c2" + - "72067311c1720673127313d90106599a8c7206018c72060273147315" + "a7067e7305067e730606d6058cb2db6308a773070002edededed938cb2720273080001730993c27203730a9272047e730b06909c9c7e" + + "c17201067e7205067e730c069c9a7204730d9a9c7e8cb27202730e0002067e730f067e9c72057e7310050690b0ada5d90106639593c2" + + "72067311c1720673127313d90106599a8c7206018c72060273147315" ) - def swapSellV0: ErgoTreeTemplate = + def swapSellLegacyV0: ErgoTreeTemplate = ErgoTreeTemplate.unsafeFromString( "d803d6017300d602b2a4730100d6037302eb027201d195ed93b1a4730393b1db630872027304d804d604db63087202d605b2a5730500d6" + "06b2db63087205730600d6077e8c72060206ededededed938cb2720473070001730893c27205d07201938c72060173099272077e730a06" + @@ -89,7 +94,7 @@ object N2TCFMMTemplates { "9a9c7ec17202067e7310067e9c73117e731205067313" ) - def swapBuyV0: ErgoTreeTemplate = + def swapBuyLegacyV0: ErgoTreeTemplate = ErgoTreeTemplate.unsafeFromString( "d802d6017300d602b2a4730100eb027201d195ed93b1a4730293b1db630872027303d804d603db63087202d604b2a5730400d6059d9c7e" + "99c17204c1a7067e7305067e730606d6068cb2db6308a773070002ededed938cb2720373080001730993c27204d072019272057e730a06" + diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/dex/protocol/amm/ParserType.scala b/modules/dex-core/src/main/scala/org/ergoplatform/dex/protocol/amm/ParserType.scala deleted file mode 100644 index 762628d3..00000000 --- a/modules/dex-core/src/main/scala/org/ergoplatform/dex/protocol/amm/ParserType.scala +++ /dev/null @@ -1,10 +0,0 @@ -package org.ergoplatform.dex.protocol.amm - -sealed trait ParserType - -object ParserType { - sealed trait Default extends ParserType - sealed trait MultiAddress extends ParserType - - type Any = ParserType -} \ No newline at end of file diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/dex/protocol/amm/ParserVersion.scala b/modules/dex-core/src/main/scala/org/ergoplatform/dex/protocol/amm/ParserVersion.scala new file mode 100644 index 00000000..262ab3c6 --- /dev/null +++ b/modules/dex-core/src/main/scala/org/ergoplatform/dex/protocol/amm/ParserVersion.scala @@ -0,0 +1,13 @@ +package org.ergoplatform.dex.protocol.amm + +sealed trait ParserVersion + +object ParserVersion { + sealed trait V1 extends ParserVersion + + sealed trait V2 extends ParserVersion + + sealed trait V3 extends ParserVersion + + type Any = ParserVersion +} diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/dex/protocol/amm/T2TCFMMTemplates.scala b/modules/dex-core/src/main/scala/org/ergoplatform/dex/protocol/amm/T2TCFMMTemplates.scala index 6ec8523c..69c99fbd 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/dex/protocol/amm/T2TCFMMTemplates.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/dex/protocol/amm/T2TCFMMTemplates.scala @@ -4,18 +4,17 @@ import org.ergoplatform.ergo.ErgoTreeTemplate object T2TCFMMTemplates { - def depositLatest: ErgoTreeTemplate = + def depositV3: ErgoTreeTemplate = ErgoTreeTemplate.unsafeFromString( - "d803d6017300d602b2a4730100d603db6308a7eb027201d195ed93b1a4730293b1db630872027303d80cd604db63087202d605b2a57304" + - "00d606b27204730500d6077e9973068c72060206d608b27204730700d6097e8c72080206d60a9d9c7e8cb27203730800020672077209d6" + - "0bb27204730900d60c7e8c720b0206d60d9d9c7e8cb27203730a0002067207720cd60edb63087205d60fb2720e730b00edededededed93" + - "b27204730c008602730d730e93c27205d0720192c1720599c1a7730f95ed8f720a720d93b1720e7310d801d610b2720e731100ed938c72" + - "10018c720b01927e8c721002069d9c99720d720a720c720795ed91720a720d93b1720e7312d801d610b2720e731300ed938c7210018c72" + - "0801927e8c721002069d9c99720a720d720972079593720a720d73147315938c720f018c720601927e8c720f0206a1720a720d90b0ada5" + - "d90110639593c272107316c1721073177318d90110599a8c7210018c7210027319731a" + "d801d601b2a4730000eb027301d195ed92b1a4730293b1db630872017303d80cd602db63087201d603b2a5730400d604b27202730500d6057e9973068c72040206d606b27202730700d6077e8c72060206d6089d9c7e73080672057207d609b27202730900d60a7e8c72090206d60b9d9c7e730a067205720ad60cdb63087203d60db2720c730b00edededededed938cb27202730c0001730d93c27203730e92c17203c1a795ed8f7208720b93b1720c730fd801d60eb2720c731000ed938c720e018c720901927e8c720e02069d9c99720b7208720a720595ed917208720b93b1720c7311d801d60eb2720c731200ed938c720e018c720601927e8c720e02069d9c997208720b7207720595937208720b73137314938c720d018c720401927e8c720d0206a17208720b90b0ada5d9010e639593c2720e7315c1720e73167317d9010e599a8c720e018c720e0273187319" ) def depositV1: ErgoTreeTemplate = + ErgoTreeTemplate.unsafeFromString( + "d803d6017300d602b2a4730100d603db6308a7eb027201d195ed92b1a4730293b1db630872027303d80cd604db63087202d605b2a5730400d606b27204730500d6077e9973068c72060206d608b27204730700d6097e8c72080206d60a9d9c7e8cb27203730800020672077209d60bb27204730900d60c7e8c720b0206d60d9d9c7e8cb27203730a0002067207720cd60edb63087205d60fb2720e730b00edededededed93b27204730c008602730d730e93c27205d0720192c1720599c1a7730f95ed8f720a720d93b1720e7310d801d610b2720e731100ed938c7210018c720b01927e8c721002069d9c99720d720a720c720795ed91720a720d93b1720e7312d801d610b2720e731300ed938c7210018c720801927e8c721002069d9c99720a720d720972079593720a720d73147315938c720f018c720601927e8c720f0206a1720a720d90b0ada5d90110639593c272107316c1721073177318d90110599a8c7210018c7210027319731a" + ) + + def depositLegacyV1: ErgoTreeTemplate = ErgoTreeTemplate.unsafeFromString( "d803d6017300d602b2a4730100d603db6308a7eb027201d195ed93b1a4730293b1db630872027303d80cd604db63087202d605b2a57304" + "00d606b27204730500d6077e9973068c72060206d608b27204730700d6097e8c72080206d60a9d9c7e8cb27203730800020672077209d6" + @@ -25,7 +24,7 @@ object T2TCFMMTemplates { "01927e8c721002069d9c99720a720d720972079593720a720d73147315938c720f018c720601927e8c720f0206a1720a720d7316" ) - def depositV0: ErgoTreeTemplate = + def depositLegacyV0: ErgoTreeTemplate = ErgoTreeTemplate.unsafeFromString( "d803d6017300d602b2a4730100d603db6308a7eb027201d195ed93b1a4730293b1db630872027303d805d604db63087202d605b2a57304" + "00d606b2db63087205730500d607b27204730600d6087e9973078c72070206edededed93b2720473080086027309730a93c27205d07201" + @@ -33,16 +32,17 @@ object T2TCFMMTemplates { "7203730e00020672087e8cb27204730f0002067310" ) - def redeemLatest: ErgoTreeTemplate = + def redeemV3: ErgoTreeTemplate = ErgoTreeTemplate.unsafeFromString( - "d802d6017300d602b2a4730100eb027201d195ed93b1a4730293b1db630872027303d809d603db63087202d604b2a5730400d605db6308" + - "7204d606b27205730500d607b27203730600d608b27205730700d609b27203730800d60a7e8cb2db6308a77309000206d60b7e99730a8c" + - "b27203730b000206ededededededed93b27203730c008602730d730e93c27204d0720192c1720499c1a7730f938c7206018c720701938c" + - "7208018c720901927e8c720602069d9c720a7e8c72070206720b927e8c720802069d9c720a7e8c72090206720b90b0ada5d9010c639593" + - "c2720c7310c1720c73117312d9010c599a8c720c018c720c0273137314" + "d801d601b2a4730000eb027301d195ed92b1a4730293b1db630872017303d809d602db63087201d603b2a5730400d604db63087203d605b27204730500d606b27202730600d607b27204730700d608b27202730800d6097e8cb2db6308a77309000206d60a7e99730a8cb27202730b000206edededededed938cb27202730c0001730d93c27203730e938c7205018c720601938c7207018c720801927e8c720502069d9c72097e8c72060206720a927e8c720702069d9c72097e8c72080206720a90b0ada5d9010b639593c2720b730fc1720b73107311d9010b599a8c720b018c720b0273127313" ) - def redeemV0: ErgoTreeTemplate = + def redeemV1: ErgoTreeTemplate = + ErgoTreeTemplate.unsafeFromString( + "d802d6017300d602b2a4730100eb027201d195ed92b1a4730293b1db630872027303d809d603db63087202d604b2a5730400d605db63087204d606b27205730500d607b27203730600d608b27205730700d609b27203730800d60a7e8cb2db6308a77309000206d60b7e99730a8cb27203730b000206ededededededed93b27203730c008602730d730e93c27204d0720192c1720499c1a7730f938c7206018c720701938c7208018c720901927e8c720602069d9c720a7e8c72070206720b927e8c720802069d9c720a7e8c72090206720b90b0ada5d9010c639593c2720c7310c1720c73117312d9010c599a8c720c018c720c0273137314" + ) + + def redeemLegacyV0: ErgoTreeTemplate = ErgoTreeTemplate.unsafeFromString( "d802d6017300d602b2a4730100eb027201d195ed93b1a4730293b1db630872027303d809d603db63087202d604b2a5730400d605db6308" + "7204d606b27205730500d607b27203730600d608b27205730700d609b27203730800d60a7e8cb2db6308a77309000206d60b7e99730a8c" + @@ -50,27 +50,27 @@ object T2TCFMMTemplates { "08018c720901927e8c720602069d9c720a7e8c72070206720b927e8c720802069d9c720a7e8c72090206720b7310" ) - def swapLatest: ErgoTreeTemplate = + def swapV3: ErgoTreeTemplate = ErgoTreeTemplate.unsafeFromString( - "d805d6017300d602b2a4730100d6037302d6047303d6057304eb027201d195ed93b1a4730593b1db630872027306d80ad606db63087202" + - "d607b2a5730700d608b2db63087207730800d6098c720802d60a7e720906d60bb27206730900d60c7e8c720b0206d60d7e8cb2db6308a7" + - "730a000206d60e7e8cb27206730b000206d60f9a720a730cedededededed938cb27206730d0001730e93c27207d07201938c7208017203" + - "927209730f927ec1720706997ec1a7069d9c720a7e7310067e73110695938c720b017203909c9c720c720d7e7204069c720f9a9c720e7e" + - "7205069c720d7e720406909c9c720e720d7e7204069c720f9a9c720c7e7205069c720d7e72040690b0ada5d90110639593c272107312c1" + - "721073137314d90110599a8c7210018c72100273157316" + "d805d601b2a4730000d6027301d6037302d6049c73037e730405d6057305eb027306d195ed92b1a4730793b1db630872017308d80ad606db63087201d607b2a5730900d608db63087207d609b27208730a00d60a8c720902d60b95730b9d9c7e99720a730c067e7203067e730d067e720a06d60cb27206730e00d60d7e8c720c0206d60e7e8cb27206730f000206d60f9a720b7310ededededededed938cb2720673110001731293c272077313938c720901720292720a731492c17207c1a79573157316d801d610997e7317069d9c720b7e7318067e72030695ed917210731992b17208731ad801d611b27208731b00ed938c721101731c927e8c721102067210731d95938c720c017202909c720d7e7204069c720f9a9c720e7e7205067e720406909c720e7e7204069c720f9a9c720d7e7205067e72040690b0ada5d90110639593c27210731ec17210731f7320d90110599a8c7210018c72100273217322" + ) + + def swapV1: ErgoTreeTemplate = + ErgoTreeTemplate.unsafeFromString( + "d805d6017300d602b2a4730100d6037302d6047303d6057304eb027201d195ed92b1a4730593b1db630872027306d80ad606db63087202d607b2a5730700d608b2db63087207730800d6098c720802d60a7e720906d60bb27206730900d60c7e8c720b0206d60d7e8cb2db6308a7730a000206d60e7e8cb27206730b000206d60f9a720a730cedededededed938cb27206730d0001730e93c27207d07201938c7208017203927209730f927ec1720706997ec1a7069d9c720a7e7310067e73110695938c720b017203909c9c720c720d7e7204069c720f9a9c720e7e7205069c720d7e720406909c9c720e720d7e7204069c720f9a9c720c7e7205069c720d7e72040690b0ada5d90110639593c272107312c1721073137314d90110599a8c7210018c72100273157316" ) - def swapMultiAddress: ErgoTreeTemplate = + def swapV2: ErgoTreeTemplate = ErgoTreeTemplate.unsafeFromString( "d804d601b2a4730000d6027301d6037302d6047303d1ec730495ed93b1a4730593b1db630872017306d80ad605db63087201d606b2a5" + - "730700d607b2db63087206730800d6088c720702d6097e720806d60ab27205730900d60b7e8c720a0206d60c7e8cb2db6308a7730a" + - "000206d60d7e8cb27205730b000206d60e9a7209730cedededededed938cb27205730d0001730e93c27206730f938c720701720292" + - "72087310927ec1720606997ec1a7069d9c72097e7311067e73120695938c720a017202909c9c720b720c7e7203069c720e9a9c720d" + - "7e7204069c720c7e720306909c9c720d720c7e7203069c720e9a9c720b7e7204069c720c7e72030690b0ada5d9010f639593c2720f" + - "7313c1720f73147315d9010f599a8c720f018c720f0273167317" + "730700d607b2db63087206730800d6088c720702d6097e720806d60ab27205730900d60b7e8c720a0206d60c7e8cb2db6308a7730a" + + "000206d60d7e8cb27205730b000206d60e9a7209730cedededededed938cb27205730d0001730e93c27206730f938c720701720292" + + "72087310927ec1720606997ec1a7069d9c72097e7311067e73120695938c720a017202909c9c720b720c7e7203069c720e9a9c720d" + + "7e7204069c720c7e720306909c9c720d720c7e7203069c720e9a9c720b7e7204069c720c7e72030690b0ada5d9010f639593c2720f" + + "7313c1720f73147315d9010f599a8c720f018c720f0273167317" ) - def swapV0: ErgoTreeTemplate = + def swapLegacyV0: ErgoTreeTemplate = ErgoTreeTemplate.unsafeFromString( "d805d6017300d602b2a4730100d6037302d6047303d6057304eb027201d195ed93b1a4730593b1db630872027306d80ad606db63087202" + "d607b2a5730700d608b2db63087207730800d6098c720802d60a7e720906d60bb27206730900d60c7e8c720b0206d60d7e8cb2db6308a7" + diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/dex/protocol/amm/constants.scala b/modules/dex-core/src/main/scala/org/ergoplatform/dex/protocol/amm/constants.scala index 22878b30..65a2a79e 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/dex/protocol/amm/constants.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/dex/protocol/amm/constants.scala @@ -5,24 +5,24 @@ import org.ergoplatform.ergo.ErgoTreeTemplate object constants { val reservedErgoTrees: List[ErgoTreeTemplate] = List( - N2TCFMMTemplates.depositLatest, - N2TCFMMTemplates.redeemLatest, - N2TCFMMTemplates.swapBuyV0, - N2TCFMMTemplates.swapBuyLatest, - N2TCFMMTemplates.swapSellV0, - N2TCFMMTemplates.swapSellLatest, N2TCFMMTemplates.depositV1, - N2TCFMMTemplates.redeemV0, - N2TCFMMTemplates.swapBuyMultiAddress, - N2TCFMMTemplates.swapSellMultiAddress, - T2TCFMMTemplates.swapLatest, - T2TCFMMTemplates.swapMultiAddress, - T2TCFMMTemplates.swapV0, - T2TCFMMTemplates.redeemLatest, - T2TCFMMTemplates.redeemV0, - T2TCFMMTemplates.depositLatest, + N2TCFMMTemplates.redeemV1, + N2TCFMMTemplates.swapBuyLegacyV0, + N2TCFMMTemplates.swapBuyV1, + N2TCFMMTemplates.swapSellLegacyV0, + N2TCFMMTemplates.swapSellV1, + N2TCFMMTemplates.depositLegacyV1, + N2TCFMMTemplates.redeemLegacyV0, + N2TCFMMTemplates.swapBuyMultiAddressV2, + N2TCFMMTemplates.swapSellMultiAddressV2, + T2TCFMMTemplates.swapV1, + T2TCFMMTemplates.swapV2, + T2TCFMMTemplates.swapLegacyV0, + T2TCFMMTemplates.redeemV1, + T2TCFMMTemplates.redeemLegacyV0, T2TCFMMTemplates.depositV1, - T2TCFMMTemplates.depositV0, + T2TCFMMTemplates.depositLegacyV1, + T2TCFMMTemplates.depositLegacyV0, ergoBaseOutput, ErgoTreeTemplate.fromBytes(AMMContracts.N2TCFMMContracts.pool.template), ErgoTreeTemplate.fromBytes(AMMContracts.T2TCFMMContracts.pool.template) diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/PrivKeyGenerator.scala b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/PrivKeyGenerator.scala new file mode 100644 index 00000000..06b31540 --- /dev/null +++ b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/PrivKeyGenerator.scala @@ -0,0 +1,26 @@ +package org.ergoplatform.ergo + +import org.bouncycastle.util.BigIntegers +import org.ergoplatform.{ErgoAddressEncoder, P2PKAddress} +import org.ergoplatform.wallet.mnemonic.Mnemonic +import org.ergoplatform.wallet.secrets.{DerivationPath, ExtendedSecretKey} +import org.ergoplatform.wallet.secrets.ExtendedSecretKey.deriveMasterKey +import scorex.util.encode.Base16 +import sigmastate.basics.DLogProtocol.DLogProverInput + +object PrivKeyGenerator { + + def make(mnemonic: String)(implicit e: ErgoAddressEncoder): (DLogProverInput, Address) = { + val seed: Array[Byte] = Mnemonic.toSeed(mnemonic) + val SK: ExtendedSecretKey = deriveMasterKey(seed) + val path = "m/44'/429'/0'/0/0" + val derivationPath = DerivationPath.fromEncoded(path).get + val nextSK = SK.derive(derivationPath).asInstanceOf[ExtendedSecretKey] + val address = Address.fromStringUnsafe(e.toString(P2PKAddress(nextSK.publicImage))) + val SKHex = Base16.encode(nextSK.keyBytes) + val sk: DLogProverInput = DLogProverInput(BigIntegers.fromUnsignedByteArray(Base16.decode(SKHex).get)) + + (sk, address) + } + +} diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/domain/EpochParams.scala b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/domain/EpochParams.scala index 9146fc94..1159a5b4 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/domain/EpochParams.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/domain/EpochParams.scala @@ -19,3 +19,7 @@ final case class EpochParams( ) { val safeMinValue: Long = (minValuePerByte * 1500).toLong } + +object EpochParams { + def empty: EpochParams = EpochParams(0, 0, 0, 0, 0, 0, 0, 0, 0, 0) +} diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/domain/Output.scala b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/domain/Output.scala index c5f39d6e..941766aa 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/domain/Output.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/domain/Output.scala @@ -3,9 +3,15 @@ package org.ergoplatform.ergo.domain import derevo.cats.show import derevo.circe.{decoder, encoder} import derevo.derive +import org.ergoplatform.ErgoBox +import org.ergoplatform.dex.domain.DexOperatorOutput import org.ergoplatform.ergo.services.explorer.models.{Output => ExplorerOutput} import org.ergoplatform.ergo.services.node.models.{Output => NodeOutput} -import org.ergoplatform.ergo.{BoxId, SErgoTree, TxId} +import org.ergoplatform.ergo.state.{Predicted, Traced} +import org.ergoplatform.ergo.{BoxId, SErgoTree, TokenId, TxId} +import scorex.crypto.authds.ADKey +import scorex.util.ModifierId +import scorex.util.encode.Base16 import tofu.logging.derivation.loggable @derive(show, encoder, decoder, loggable) @@ -21,6 +27,8 @@ final case class Output( ) object Output { + def predicted(output: Output, prevBoxId: BoxId): Traced[Predicted[DexOperatorOutput]] = + Traced(Predicted(DexOperatorOutput(output)), prevBoxId) def fromExplorer(o: ExplorerOutput): Output = Output( @@ -45,4 +53,18 @@ object Output { o.assets.map(BoxAsset.fromNode), Map.empty // todo ) + + def fromErgoBox(b: ErgoBox): Output = { + val bId = ADKey !@@ b.id + Output( + BoxId(Base16.encode(bId)), + TxId(ModifierId !@@ b.transactionId), + b.value, + b.index, + b.creationHeight, + SErgoTree.fromBytes(b.ergoTree.bytes), + b.additionalTokens.toMap.map { case (id, l) => BoxAsset(TokenId.fromBytes(id), l) }.toList, + Map.empty + ) + } } diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/modules/MempoolStreaming.scala b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/modules/MempoolStreaming.scala index 711256c7..ce1af5bd 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/modules/MempoolStreaming.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/modules/MempoolStreaming.scala @@ -6,11 +6,12 @@ import org.ergoplatform.ergo.domain.Output import org.ergoplatform.ergo.services.node.ErgoNode import org.ergoplatform.ergo.services.node.models.Transaction import tofu.higherKind.derived.representableK +import tofu.logging.{Logging, Logs} import tofu.streams.Evals import tofu.syntax.monadic._ +import tofu.syntax.logging._ import tofu.syntax.streams.all._ -@derive(representableK) trait MempoolStreaming[F[_]] { def streamUnspentOutputs: F[Output] @@ -20,10 +21,15 @@ object MempoolStreaming { val Limit = 100 - def make[F[_]: Functor: Evals[*[_], G], G[_]: Monad](implicit node: ErgoNode[G]): MempoolStreaming[F] = - new Live(node) + def make[I[_]: Functor, F[_]: Functor: Evals[*[_], G], G[_]: Monad](implicit + node: ErgoNode[G], + logs: Logs[I, G] + ): I[MempoolStreaming[F]] = + logs.forService[MempoolStreaming[F]].map { implicit __ => + new Live(node) + } - final class Live[F[_]: Functor: Evals[*[_], G], G[_]: Monad](node: ErgoNode[G]) extends MempoolStreaming[F] { + final class Live[F[_]: Functor: Evals[*[_], G], G[_]: Monad: Logging](node: ErgoNode[G]) extends MempoolStreaming[F] { def streamUnspentOutputs: F[Output] = { def fetchMempool(offset: Int, acc: Vector[Transaction]): G[Vector[Transaction]] = @@ -32,7 +38,7 @@ object MempoolStreaming { evals(fetchMempool(0, Vector.empty).map(extractUtxos)) } - private def extractUtxos(txs: Vector[Transaction]): Vector[Output] = { + def extractUtxos(txs: Vector[Transaction]): Vector[Output] = { val spent = txs.flatMap(_.inputs.map(_.boxId)).toSet val unspent = txs.flatMap(_.outputs).filterNot(o => spent.contains(o.boxId)) unspent.map(Output.fromNode) diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/package.scala b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/package.scala index 827736c9..c90fa160 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/package.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/package.scala @@ -9,6 +9,7 @@ import cats.{Applicative, Show} import derevo.cats.show import derevo.circe.{decoder, encoder} import derevo.derive +import derevo.pureconfig.pureconfigReader import doobie._ import scodec.codecs._ import eu.timepit.refined.api.Refined @@ -25,7 +26,7 @@ import org.ergoplatform.common.errors.RefinementFailed import org.ergoplatform.ergo.CurrencyId import org.ergoplatform.ergo.syntax.PubKeyOps import pureconfig.ConfigReader -import pureconfig.error.CannotConvert +import pureconfig.error.{CannotConvert, FailureReason} import scodec.bits.ByteVector import scorex.crypto.hash.Sha256 import scorex.util.encode.Base16 @@ -124,6 +125,10 @@ package object ergo { object TokenId { + implicit val configReader: ConfigReader[TokenId] = ConfigReader.fromString { str => + TokenId.fromString[Try](str).toEither.leftMap(err => CannotConvert(str, "token id", err.getMessage)) + } + implicit val get: Get[TokenId] = Get[HexString].map(TokenId(_)) diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/services/explorer/ErgoExplorer.scala b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/services/explorer/ErgoExplorer.scala index bd428948..ab028fc8 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/services/explorer/ErgoExplorer.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/services/explorer/ErgoExplorer.scala @@ -20,7 +20,7 @@ import org.ergoplatform.dex.protocol.instances._ import org.ergoplatform.ergo.errors.ResponseError import org.ergoplatform.ergo.services.explorer.models._ import org.ergoplatform.ergo.services.explorer.paths._ -import org.ergoplatform.ergo.TokenId +import org.ergoplatform.ergo.{Address, TokenId} import org.ergoplatform.ergo.domain.{EpochParams, NetworkInfo} import org.typelevel.jawn.Facade import sttp.capabilities.fs2.Fs2Streams @@ -70,6 +70,10 @@ trait ErgoExplorer[F[_]] { /** Get token info by token id. */ def getTokenInfo(tokenId: TokenId): F[Option[TokenInfo]] + + /** get unspent output by address + */ + def getUnspentOutputByAddress(address: Address): F[Option[Output]] } object ErgoExplorer { @@ -120,6 +124,13 @@ final class ErgoExplorerTracing[F[_]: Logging: FlatMap] extends ErgoExplorer[Mid os <- _ _ <- trace"getTokenInfo(..) -> $os" } yield os + + def getUnspentOutputByAddress(address: Address): Mid[F, Option[Output]] = + for { + _ <- trace"getUnspentOutputByAddress(address=$address)" + os <- _ + _ <- trace"getUnspentOutputByAddress(address=$address) -> $os" + } yield os } class CombinedErgoExplorer[F[_]: MonadThrow](config: NetworkConfig)(implicit backend: SttpBackend[F, Any]) @@ -198,6 +209,14 @@ class CombinedErgoExplorer[F[_]: MonadThrow](config: NetworkConfig)(implicit bac .response(asJson[TokenInfo]) .send(backend) .absorbServerError + + def getUnspentOutputByAddress(address: Address): F[Option[Output]] = + basicRequest + .get(explorerUri.withPathSegment(unspentByAddress(address)).addParams("limit" -> 1.toString)) + .response(asJson[Items[Output]]) + .send(backend) + .absorbServerError + .map(_.flatMap(_.items.headOption)) } trait ErgoExplorerStreaming[S[_], F[_]] extends ErgoExplorer[F] { @@ -225,6 +244,9 @@ object ErgoExplorerStreaming { evals: Evals[S, F] ) extends ErgoExplorerStreaming[S, F] { + def getUnspentOutputByAddress(address: Address): F[Option[Output]] = + tft.flatMap(_.getUnspentOutputByAddress(address)) + def checkTransaction(tx: ErgoLikeTransaction): F[Option[String]] = tft.flatMap(_.checkTransaction(tx)) @@ -317,7 +339,9 @@ object ErgoExplorerStreaming { def streamBlocks(offset: Long, limit: Int): Stream[F, BlockInfo] = { val req = basicRequest - .get(uri.withPathSegment(blocksStreamPathSeg).addParams("minGix" -> offset.toString, "limit" -> limit.toString)) + .get( + uri.withPathSegment(blocksStreamPathSeg).addParams("minGix" -> offset.toString, "limit" -> limit.toString) + ) .response(asStreamAlwaysUnsafe(Fs2Streams[F])) .send(backend) .map(_.body) diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/services/explorer/TxSubmissionErrorParser.scala b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/services/explorer/TxSubmissionErrorParser.scala index c5a8d6ba..7aceda31 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/services/explorer/TxSubmissionErrorParser.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/services/explorer/TxSubmissionErrorParser.scala @@ -1,32 +1,28 @@ package org.ergoplatform.ergo.services.explorer -import org.ergoplatform.ergo.BoxId - trait TxSubmissionErrorParser { /** Extract an index of a missed input. */ - def missedInputs(error: String): List[(BoxId, Int)] + def missedInputs(error: String): List[Int] } object TxSubmissionErrorParser { + val InvalidPoolIndex = 0 + val InvalidDexOutputIndex = 2 + implicit val default: TxSubmissionErrorParser = new TxSubmissionErrorParser { - val TxErrP = """^Bad request: Transaction is invalid. (.+)""".r - val InputNotFoundP = """^Input ([0-9]+):([0-9a-fA-F]+) not found$""".r - val InputSpentP = """^Input ([0-9]+):([0-9a-fA-F]+) was spent$""".r - def missedInputs(error: String): List[(BoxId, Int)] = - error match { - case TxErrP(errs) => - errs.split("; ").toList.collect { - case InputNotFoundP(i, idHex) => - BoxId.fromStringUnsafe(idHex) -> i.toInt - case InputSpentP(i, idHex) => - BoxId.fromStringUnsafe(idHex) -> i.toInt - } - case _ => List.empty - } + def missedInputs(error: String): List[Int] = { + val requiredPrefix = "Missing inputs" + val split = error.split(":").toList + if (split.dropRight(1).lastOption.exists(_.contains(requiredPrefix))) { + split.lastOption + .map(_.replace(" ", "").toList.map(_.getNumericValue)) + .getOrElse(List.empty[Int]) + } else List.empty[Int] + } } } diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/services/explorer/paths.scala b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/services/explorer/paths.scala index 9141186c..8db47fe9 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/services/explorer/paths.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/services/explorer/paths.scala @@ -1,7 +1,7 @@ package org.ergoplatform.ergo.services.explorer import org.ergoplatform.common.HexString -import org.ergoplatform.ergo.TokenId +import org.ergoplatform.ergo.{Address, TokenId} import sttp.model.Uri.Segment object paths { @@ -23,4 +23,7 @@ object paths { def tokenInfoPathSeg(tokenId: TokenId): Segment = Segment(s"api/v1/tokens/$tokenId", identity) + + def unspentByAddress(address: Address): Segment = + Segment(s"api/v1/boxes/unspent/byAddress/$address", identity) } diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/services/node/ErgoNode.scala b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/services/node/ErgoNode.scala index 9adcb601..b2566081 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/services/node/ErgoNode.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/services/node/ErgoNode.scala @@ -70,7 +70,12 @@ object ErgoNode { } def unconfirmedTransactions(offset: Int, limit: Int): F[Vector[Transaction]] = - basicRequest.get(config.nodeUri withPathSegment paths.unconfirmedTransactionsPathSeg) + basicRequest + .get( + config.nodeUri + .withPathSegment(paths.unconfirmedTransactionsPathSeg) + .addParams("offset" -> s"$offset", "limit" -> s"$limit") + ) .response(asJson[Vector[Transaction]]) .send(backend) .absorbError diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/state/Traced.scala b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/state/Traced.scala index 3dc7f94f..c75adaef 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/state/Traced.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/state/Traced.scala @@ -14,7 +14,8 @@ import tofu.logging.derivation.loggable final case class Traced[T](state: T, predecessorBoxId: BoxId) object Traced { - implicit def schema[T: Schema]: Schema[Traced[T]] = Schema.derived[Traced[T]] + implicit def schema[T: Schema]: Schema[Traced[T]] = Schema.derived[Traced[T]] + implicit def validator[T: Validator]: Validator[Traced[T]] = Validator.pass implicit def codec[T: Codec]: Codec[Traced[T]] = (implicitly[Codec[T]] :: implicitly[Codec[BoxId]]).as[Traced[T]] diff --git a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/syntax.scala b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/syntax.scala index e8bcf280..657aee1a 100644 --- a/modules/dex-core/src/main/scala/org/ergoplatform/ergo/syntax.scala +++ b/modules/dex-core/src/main/scala/org/ergoplatform/ergo/syntax.scala @@ -7,7 +7,7 @@ import scorex.util.encode.Base16 import sigmastate.Values.{ByteArrayConstant, Constant, ErgoTree, SigmaPropConstant} import sigmastate.basics.DLogProtocol import sigmastate.basics.DLogProtocol.{ProveDlog, ProveDlogProp} -import sigmastate.{SLong, SType, Values} +import sigmastate.{SBoolean, SLong, SType, Values} import special.collection.Coll import sigmastate.eval.Extensions._ import sigmastate.serialization.{GroupElementSerializer, SigmaSerializer} @@ -44,6 +44,11 @@ object syntax { value.asInstanceOf[Long] } + def parseBoolean(idx: Int): Option[Boolean] = + constants.lift(idx).collect { case Values.ConstantNode(value, SBoolean) => + value.asInstanceOf[Boolean] + } + def parseBytea(idx: Int): Option[Array[Byte]] = constants.lift(idx).collect { case ByteArrayConstant(coll) => coll.toArray } diff --git a/modules/dex-core/src/test/scala/org/ergoplatform/dex/demo/TreePrinter.scala b/modules/dex-core/src/test/scala/org/ergoplatform/dex/demo/TreePrinter.scala index 3c228638..bd237150 100644 --- a/modules/dex-core/src/test/scala/org/ergoplatform/dex/demo/TreePrinter.scala +++ b/modules/dex-core/src/test/scala/org/ergoplatform/dex/demo/TreePrinter.scala @@ -1,7 +1,7 @@ package org.ergoplatform.dex.demo import org.ergoplatform.ErgoAddressEncoder -import org.ergoplatform.dex.protocol.{ErgoTreeSerializer, sigmaUtils} +import org.ergoplatform.dex.protocol.{sigmaUtils, ErgoTreeSerializer} import org.ergoplatform.dex.sources.{lockContracts, n2tContracts, t2tContracts} import org.ergoplatform.ergo.{ErgoTreeTemplate, SErgoTree} import scorex.crypto.hash.Sha256 @@ -18,13 +18,27 @@ object TreePrinter extends App { val sigma = SigmaCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) val env = Map( - "Pk" -> DLogProverInput(BigInt(Long.MaxValue).bigInteger).publicImage, - "PoolNFT" -> Array.fill(32)(0: Byte), - "QuoteId" -> Array.fill(32)(1.toByte), - "DexFee" -> 999999L, - "SelfX" -> 888888L, - "MaxMinerFee" -> 777777L, - "MinerPropBytes" -> Base16.decode("1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304").get + "Pk" -> DLogProverInput(BigInt(Long.MaxValue).bigInteger).publicImage, + "PoolNFT" -> Array.fill(32)(0: Byte), + "QuoteId" -> Array.fill(32)(1.toByte), + "DexFee" -> 999999L, + "SelfX" -> 888888L, + "MaxMinerFee" -> 777777L, + "SpectrumId" -> Array.fill(32)(2.toByte), + "RedeemerPropBytes" -> Array.fill(32)(3.toByte), + "RefundProp" -> false, + "MinerPropBytes" -> Base16 + .decode( + "1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304" + ) + .get, + "SelfXAmount" -> 12345L, + "SelfYAmount" -> 5432L, + "SpectrumIsY" -> false, + "SpectrumIsX" -> false, + "ExFee" -> 123L, +// "MinQuoteAmount" -> 333L, +// "BaseAmount" -> 1400 ) def parseTree(raw: String): Unit = { @@ -65,7 +79,7 @@ object TreePrinter extends App { ) val Primitives_Trees = List( - "LqLock" -> lockContracts.lock, + "LqLock" -> lockContracts.lock ) def printAll(cat: String, trees: List[(String, String)]): Unit = { @@ -74,12 +88,14 @@ object TreePrinter extends App { trees.foreach { case (sign, tree) => printTree(sign, tree) } } - printAll("N2T", N2T_Trees) - printAll("T2T", T2T_Trees) - printAll("DeFi_Prims", Primitives_Trees) +// printAll("N2T", N2T_Trees) +// printAll("T2T", T2T_Trees) +// printAll("DeFi_Prims", Primitives_Trees) - //printTree("deposit", n2tContracts.deposit) - val src = "1999030f0400040204020404040405feffffffffffffffff0105feffffffffffffffff01050004d00f040004000406050005000580dac409d819d601b2a5730000d602e4c6a70404d603db63087201d604db6308a7d605b27203730100d606b27204730200d607b27203730300d608b27204730400d6099973058c720602d60a999973068c7205027209d60bc17201d60cc1a7d60d99720b720cd60e91720d7307d60f8c720802d6107e720f06d6117e720d06d612998c720702720fd6137e720c06d6147308d6157e721206d6167e720a06d6177e720906d6189c72117217d6199c72157217d1ededededededed93c27201c2a793e4c672010404720293b27203730900b27204730a00938c7205018c720601938c7207018c72080193b17203730b9593720a730c95720e929c9c721072117e7202069c7ef07212069a9c72137e7214067e9c720d7e72020506929c9c721372157e7202069c7ef0720d069a9c72107e7214067e9c72127e7202050695ed720e917212730d907216a19d721872139d72197210ed9272189c721672139272199c7216721091720b730e" + printTree("swapSellV1", t2tContracts.swap) + + val src = + "1999030f0400040204020404040405feffffffffffffffff0105feffffffffffffffff01050004d00f040004000406050005000580dac409d819d601b2a5730000d602e4c6a70404d603db63087201d604db6308a7d605b27203730100d606b27204730200d607b27203730300d608b27204730400d6099973058c720602d60a999973068c7205027209d60bc17201d60cc1a7d60d99720b720cd60e91720d7307d60f8c720802d6107e720f06d6117e720d06d612998c720702720fd6137e720c06d6147308d6157e721206d6167e720a06d6177e720906d6189c72117217d6199c72157217d1ededededededed93c27201c2a793e4c672010404720293b27203730900b27204730a00938c7205018c720601938c7207018c72080193b17203730b9593720a730c95720e929c9c721072117e7202069c7ef07212069a9c72137e7214067e9c720d7e72020506929c9c721372157e7202069c7ef0720d069a9c72107e7214067e9c72127e7202050695ed720e917212730d907216a19d721872139d72197210ed9272189c721672139272199c7216721091720b730e" val tree = ErgoTreeSerializer.default.deserialize(SErgoTree.unsafeFromString(src)) tree.constants.zipWithIndex.foreach { case (c, i) => println(s"{$i} -> $c") } -} \ No newline at end of file +} diff --git a/modules/dex-core/src/test/scala/org/ergoplatform/dex/sources/n2tContracts.scala b/modules/dex-core/src/test/scala/org/ergoplatform/dex/sources/n2tContracts.scala index 8ea7d479..f8c536d6 100644 --- a/modules/dex-core/src/test/scala/org/ergoplatform/dex/sources/n2tContracts.scala +++ b/modules/dex-core/src/test/scala/org/ergoplatform/dex/sources/n2tContracts.scala @@ -2,6 +2,335 @@ package org.ergoplatform.dex.sources object n2tContracts { + val depositV3 = + s""" + |{ // ===== Contract Information ===== // + | // Name: Deposit + | // Description: Contract that validates user's deposit into the CFMM n2t Pool. + | // + | // Constants: + | // + | // {1} -> SelfXAmount[Long] + | // {2} -> RefundProp[ProveDlog] + | // {9} -> SelfYAmount[Long] SELF.tokens(1)._2 - ExFee if Y is SPF else SELF.tokens(1)._2 + | // {12} -> PoolNFT[Coll[Byte]] + | // {13} -> RedeemerPropBytes[Coll[Byte]] + | // {18} -> MinerPropBytes[Coll[Byte]] + | // {21} -> MaxMinerFee[Long] + | // + | // ErgoTree: 19bf0417040005c0b80208cd02217daf90deb73bdf8b6709bb42093fdfaff6573fd47b630e2d3fdd4a8193a74d040404060402040205feffffffffffffffff01040405e0d403040004000e2002020202020202020202020202020202020202020202020202020202020202020e2001010101010101010101010101010101010101010101010101010101010101010404040205c0b80201000e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d802d601b2a4730000d6027301eb027302d195ed92b1a4730393b1db630872017304d80bd603db63087201d604b2a5730500d605b27203730600d6067e9973078c72050206d6077ec1720106d6089d9c7e72020672067207d609b27203730800d60a7e8c72090206d60b9d9c7e7309067206720ad60cdb63087204d60db2720c730a00ededededed938cb27203730b0001730c93c27204730d95ed8f7208720b93b1720c730ed801d60eb2720c730f00eded92c1720499c1a77310938c720e018c720901927e8c720e02069d9c99720b7208720a720695927208720b927ec1720406997ec1a706997e7202069d9c997208720b720772067311938c720d018c720501927e8c720d0206a17208720b90b0ada5d9010e639593c2720e7312c1720e73137314d9010e599a8c720e018c720e0273157316 + | // + | // ErgoTreeTemplate: d802d601b2a4730000d6027301eb027302d195ed92b1a4730393b1db630872017304d80bd603db63087201d604b2a5730500d605b27203730600d6067e9973078c72050206d6077ec1720106d6089d9c7e72020672067207d609b27203730800d60a7e8c72090206d60b9d9c7e7309067206720ad60cdb63087204d60db2720c730a00ededededed938cb27203730b0001730c93c27204730d95ed8f7208720b93b1720c730ed801d60eb2720c730f00eded92c1720499c1a77310938c720e018c720901927e8c720e02069d9c99720b7208720a720695927208720b927ec1720406997ec1a706997e7202069d9c997208720b720772067311938c720d018c720501927e8c720d0206a17208720b90b0ada5d9010e639593c2720e7312c1720e73137314d9010e599a8c720e018c720e0273157316 + | + | val InitiallyLockedLP = 0x7fffffffffffffffL + | val selfXAmount = SelfXAmount + | val selfYAmount = SelfYAmount + | + | val poolIn = INPUTS(0) + | + | // Validations + | // 1. + | val validDeposit = + | if (INPUTS.size >= 2 && poolIn.tokens.size == 3) { + | + | // 1.1. + | val validPoolIn = poolIn.tokens(0)._1 == PoolNFT + | + | val poolLP = poolIn.tokens(1) + | val reservesXAmount = poolIn.value + | val reservesY = poolIn.tokens(2) + | val reservesYAmount = reservesY._2 + | + | val supplyLP = InitiallyLockedLP - poolLP._2 + | + | val minByX = selfXAmount.toBigInt * supplyLP / reservesXAmount + | val minByY = selfYAmount.toBigInt * supplyLP / reservesYAmount + | + | val minimalReward = min(minByX, minByY) + | + | val rewardOut = OUTPUTS(1) + | val rewardLP = rewardOut.tokens(0) + | // 1.2. + | val validChange = + | if (minByX < minByY && rewardOut.tokens.size == 2) { + | val diff = minByY - minByX + | val excessY = diff * reservesYAmount / supplyLP + | val changeY = rewardOut.tokens(1) + | + | rewardOut.value >= SELF.value - selfXAmount && + | changeY._1 == reservesY._1 && + | changeY._2 >= excessY + | + | } else if (minByX >= minByY) { + | val diff = minByX - minByY + | val excessX = diff * reservesXAmount / supplyLP + | + | rewardOut.value >= SELF.value - (selfXAmount - excessX) + | + | } else { + | false + | } + | // 1.3. + | val validMinerFee = OUTPUTS.map { (o: Box) => + | if (o.propositionBytes == MinerPropBytes) o.value else 0L + | }.fold(0L, { (a: Long, b: Long) => a + b }) <= MaxMinerFee + | + | validPoolIn && + | rewardOut.propositionBytes == RedeemerPropBytes && + | validChange && + | rewardLP._1 == poolLP._1 && + | rewardLP._2 >= minimalReward && + | validMinerFee + | + | } else false + | + | sigmaProp(RefundProp || validDeposit) + |} + |""".stripMargin + + val redeemV3 = + s""" + |{ // ===== Contract Information ===== // + | // Name: Redeem + | // Description: Contract that validates user's redeem from the CFMM n2t Pool. + | // + | // Constants: + | // + | // {1} -> RefundProp[ProveDlog] + | // {11} -> PoolNFT[Coll[Byte]] + | // {12} -> RedeemerPropBytes[Coll[Byte]] + | // {13} -> MinerPropBytes[Coll[Byte]] + | // {16} -> MaxMinerFee[Long] + | // + | // ErgoTree: 19c50312040008cd02217daf90deb73bdf8b6709bb42093fdfaff6573fd47b630e2d3fdd4a8193a74d04040406040204000404040005feffffffffffffffff01040204000e2002020202020202020202020202020202020202020202020202020202020202020e2001010101010101010101010101010101010101010101010101010101010101010e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d801d601b2a4730000eb027301d195ed92b1a4730293b1db630872017303d806d602db63087201d603b2a5730400d604b2db63087203730500d605b27202730600d6067e8cb2db6308a77307000206d6077e9973088cb272027309000206ededededed938cb27202730a0001730b93c27203730c938c7204018c720501927e99c17203c1a7069d9c72067ec17201067207927e8c720402069d9c72067e8c72050206720790b0ada5d90108639593c27208730dc17208730e730fd90108599a8c7208018c72080273107311 + | // + | // ErgoTreeTemplate: d801d601b2a4730000eb027301d195ed92b1a4730293b1db630872017303d806d602db63087201d603b2a5730400d604b2db63087203730500d605b27202730600d6067e8cb2db6308a77307000206d6077e9973088cb272027309000206ededededed938cb27202730a0001730b93c27203730c938c7204018c720501927e99c17203c1a7069d9c72067ec17201067207927e8c720402069d9c72067e8c72050206720790b0ada5d90108639593c27208730dc17208730e730fd90108599a8c7208018c72080273107311 + | + | val InitiallyLockedLP = 0x7fffffffffffffffL + | + | val poolIn = INPUTS(0) + | + | // Validations + | // 1. + | val validRedeem = + | if (INPUTS.size >= 2 && poolIn.tokens.size == 3) { + | val selfLP = SELF.tokens(0) + | // 1.1. + | val validPoolIn = poolIn.tokens(0)._1 == PoolNFT + | + | val poolLP = poolIn.tokens(1) + | val reservesXAmount = poolIn.value + | val reservesY = poolIn.tokens(2) + | + | val supplyLP = InitiallyLockedLP - poolLP._2 + | + | val minReturnX = selfLP._2.toBigInt * reservesXAmount / supplyLP + | val minReturnY = selfLP._2.toBigInt * reservesY._2 / supplyLP + | + | val returnOut = OUTPUTS(1) + | + | val returnXAmount = returnOut.value - SELF.value + | val returnY = returnOut.tokens(0) + | // 1.2. + | val validMinerFee = OUTPUTS.map { (o: Box) => + | if (o.propositionBytes == MinerPropBytes) o.value else 0L} + | .fold(0L, { ( a: Long, b: Long) => a + b } ) <= MaxMinerFee + | + | validPoolIn && + | returnOut.propositionBytes == RedeemerPropBytes && + | returnY._1 == reservesY._1 && // token id matches + | returnXAmount >= minReturnX && + | returnY._2 >= minReturnY && + | validMinerFee + | + | } else false + | + | sigmaProp(RefundProp || validRedeem) + |} + |""".stripMargin + + val swapBuyV3: String = + s""" + |{ // ===== Contract Information ===== // + | // Name: SwapBuy + | // Description: Contract that validates user's swap from token to ERG in the CFMM n2t Pool. + | // + | // Constants: + | // + | // {1} -> BaseAmount[Long] + | // {2} -> FeeNum[Int] + | // {3} -> RefundProp[ProveDlog] + | // {7} -> MaxExFee[Long] + | // {8} -> ExFeePerTokenDenom[Long] + | // {9} -> ExFeePerTokenNum[Long] + | // {11} -> PoolNFT[Coll[Byte]] + | // {12} -> RedeemerPropBytes[Coll[Byte]] + | // {13} -> MinQuoteAmount[Long] + | // {16} -> SpectrumId[Coll[Byte]] + | // {20} -> FeeDenom[Int] + | // {21} -> MinerPropBytes[Coll[Byte]] + | // {24} -> MaxMinerFee[Long] + | // + | // ErgoTree: 198b041a040005e01204c80f08cd02217daf90deb73bdf8b6709bb42093fdfaff6573fd47b630e2d3fdd4a8193a74d04040406040205f015052c05c80104000e2002020202020202020202020202020202020202020202020202020202020202020e20010101010101010101010101010101010101010101010101010101010101010105c00c06010004000e20030303030303030303030303030303030303030303030303030303030303030301010502040404d00f0e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d802d601b2a4730000d6029c73017e730205eb027303d195ed92b1a4730493b1db630872017305d804d603db63087201d604b2a5730600d60599c17204c1a7d606997e7307069d9c7e7205067e7308067e730906ededededed938cb27203730a0001730b93c27204730c927205730d95917206730ed801d607b2db63087204730f00ed938c7207017310927e8c7207020672067311909c7ec17201067e7202069c7e9a72057312069a9c7e8cb2720373130002067e7314067e72020690b0ada5d90107639593c272077315c1720773167317d90107599a8c7207018c72070273187319 + | // + | // ErgoTreeTemplate: d802d601b2a4730000d6029c73017e730205eb027303d195ed92b1a4730493b1db630872017305d804d603db63087201d604b2a5730600d60599c17204c1a7d606997e7307069d9c7e7205067e7308067e730906ededededed938cb27203730a0001730b93c27204730c927205730d95917206730ed801d607b2db63087204730f00ed938c7207017310927e8c7207020672067311909c7ec17201067e7202069c7e9a72057312069a9c7e8cb2720373130002067e7314067e72020690b0ada5d90107639593c272077315c1720773167317d90107599a8c7207018c72070273187319 + | + | val baseAmount = BaseAmount + | val feeNum = FeeNum + | val feeDenom = FeeDenom + | val maxExFee = MaxExFee + | val exFeePerTokenDenom = ExFeePerTokenDenom + | val exFeePerTokenNum = ExFeePerTokenNum + | val minQuoteAmount = MinQuoteAmount + | + | val poolIn = INPUTS(0) + | + | // Validations + | // 1. + | val validTrade = + | if (INPUTS.size >= 2 && poolIn.tokens.size == 3) { + | val poolNFT = poolIn.tokens(0)._1 // first token id is NFT + | + | val poolReservesX = poolIn.value.toBigInt // nanoErgs is X asset amount + | val poolReservesY = poolIn.tokens(2)._2.toBigInt // third token amount is Y asset amount + | + | val validPoolIn = poolNFT == PoolNFT + | + | val rewardBox = OUTPUTS(1) + | + | val quoteAmount = rewardBox.value - SELF.value + | // 1.1. + | val fairExFee = { + | val exFee = quoteAmount.toBigInt * exFeePerTokenNum / exFeePerTokenDenom + | val remainder = maxExFee - exFee + | if (remainder > 0) { + | val spectrumRem = rewardBox.tokens(0) + | spectrumRem._1 == SpectrumId && spectrumRem._2 >= remainder + | } else { + | true + | } + | } + | val relaxedOutput = quoteAmount + 1L // handle rounding loss + | val base_x_feeNum = baseAmount.toBigInt * feeNum + | // 1.2. + | val fairPrice = poolReservesX * base_x_feeNum <= relaxedOutput * (poolReservesY * feeDenom + base_x_feeNum) + | // 1.3. + | val validMinerFee = OUTPUTS.map { (o: Box) => + | if (o.propositionBytes == MinerPropBytes) o.value else 0L + | }.fold(0L, { (a: Long, b: Long) => a + b }) <= MaxMinerFee + | + | validPoolIn && + | rewardBox.propositionBytes == RedeemerPropBytes && + | quoteAmount >= minQuoteAmount && + | fairExFee && + | fairPrice && + | validMinerFee + | + | } else false + | + | sigmaProp(RefundProp || validTrade) + |} + |""".stripMargin + + val swapSellV3: String = + """ + |{ // ===== Contract Information ===== // + | // Name: SwapSell + | // Description: Contract that validates user's swap from ERG to token in the CFMM n2t Pool. + | // + | // Constants: + | // + | // {1} -> ExFeePerTokenDenom[Long] + | // {2} -> Delta[Long] + | // {3} -> BaseAmount[Long] + | // {4} -> FeeNum[Int] + | // {5} -> RefundProp[ProveDlog] + | // {10} -> SpectrumIsQuote[Boolean] + | // {11} -> MaxExFee[Long] + | // {13} -> PoolNFT[Coll[Byte]] + | // {14} -> RedeemerPropBytes[Coll[Byte]] + | // {15} -> QuoteId[Coll[Byte]] + | // {16} -> MinQuoteAmount[Long] + | // {23} -> SpectrumId[Coll[Byte]] + | // {27} -> FeeDenom[Int] + | // {28} -> MinerPropBytes[Coll[Byte]] + | // {31} -> MaxMinerFee[Long] + | // + | // ErgoTree: 19fe04210400059cdb0205cead0105e01204c80f08cd02217daf90deb73bdf8b6709bb42093fdfaff6573fd47b630e2d3fdd4a8193a74d0404040604020400010105f01504000e2002020202020202020202020202020202020202020202020202020202020202020e2001010101010101010101010101010101010101010101010101010101010101010e20040404040404040404040404040404040404040404040404040404040404040405c00c0101010105f015060100040404020e2003030303030303030303030303030303030303030303030303030303030303030101040406010104d00f0e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d804d601b2a4730000d6027301d6037302d6049c73037e730405eb027305d195ed92b1a4730693b1db630872017307d806d605db63087201d606b2a5730800d607db63087206d608b27207730900d6098c720802d60a95730a9d9c7e997209730b067e7202067e7203067e720906edededededed938cb27205730c0001730d93c27206730e938c720801730f92720a7e7310069573117312d801d60b997e7313069d9c720a7e7203067e72020695ed91720b731492b172077315d801d60cb27207731600ed938c720c017317927e8c720c0206720b7318909c7e8cb2720573190002067e7204069c9a720a731a9a9c7ec17201067e731b067e72040690b0ada5d9010b639593c2720b731cc1720b731d731ed9010b599a8c720b018c720b02731f7320 + | // + | // ErgoTreeTemplate: d804d601b2a4730000d6027301d6037302d6049c73037e730405eb027305d195ed92b1a4730693b1db630872017307d806d605db63087201d606b2a5730800d607db63087206d608b27207730900d6098c720802d60a95730a9d9c7e997209730b067e7202067e7203067e720906edededededed938cb27205730c0001730d93c27206730e938c720801730f92720a7e7310069573117312d801d60b997e7313069d9c720a7e7203067e72020695ed91720b731492b172077315d801d60cb27207731600ed938c720c017317927e8c720c0206720b7318909c7e8cb2720573190002067e7204069c9a720a731a9a9c7ec17201067e731b067e72040690b0ada5d9010b639593c2720b731cc1720b731d731ed9010b599a8c720b018c720b02731f7320 + | + | val baseAmount = BaseAmount + | val feeNum = FeeNum + | val feeDenom = FeeDenom + | val maxExFee = MaxExFee + | val exFeePerTokenDenom = ExFeePerTokenDenom + | val delta = Delta + | val minQuoteAmount = MinQuoteAmount + | + | val poolIn = INPUTS(0) + | + | // Validations + | // 1. + | val validTrade = + | if (INPUTS.size >= 2 && poolIn.tokens.size == 3) { + | val poolNFT = poolIn.tokens(0)._1 + | + | val poolY = poolIn.tokens(2) + | + | val poolReservesX = poolIn.value.toBigInt + | val poolReservesY = poolY._2.toBigInt + | val validPoolIn = poolNFT == PoolNFT + | + | val rewardBox = OUTPUTS(1) + | + | val quoteAsset = rewardBox.tokens(0) + | val quoteAmount = + | if (SpectrumIsQuote) { + | val quoteAssetAmount = quoteAsset._2 + | val deltaQuote = quoteAssetAmount - maxExFee + | (deltaQuote.toBigInt * exFeePerTokenDenom) / delta + | } else { + | quoteAsset._2.toBigInt + | } + | // 1.1. + | val fairExFee = + | if (SpectrumIsQuote) true + | else { + | val exFeePerTokenNum = exFeePerTokenDenom - delta + | val exFee = quoteAmount * exFeePerTokenNum / exFeePerTokenDenom + | val remainder = maxExFee - exFee + | if (remainder > 0 && rewardBox.tokens.size >= 2) { + | val spectrumRem = rewardBox.tokens(1) + | spectrumRem._1 == SpectrumId && spectrumRem._2 >= remainder + | } else { + | true + | } + | } + | + | val relaxedOutput = quoteAmount + 1L // handle rounding loss + | + | val base_x_feeNum = baseAmount.toBigInt * feeNum + | // 1.2. + | val fairPrice = poolReservesY * base_x_feeNum <= relaxedOutput * (poolReservesX * feeDenom + base_x_feeNum) + | // 1.3. + | val validMinerFee = OUTPUTS.map { (o: Box) => + | if (o.propositionBytes == MinerPropBytes) o.value else 0L + | }.fold(0L, { (a: Long, b: Long) => a + b }) <= MaxMinerFee + | + | validPoolIn && + | rewardBox.propositionBytes == RedeemerPropBytes && + | quoteAsset._1 == QuoteId && + | quoteAmount >= minQuoteAmount && + | fairExFee && + | fairPrice && + | validMinerFee + | + | } else false + | + | sigmaProp(RefundProp || validTrade) + |}""".stripMargin + val deposit: String = """ |{ @@ -286,7 +615,7 @@ object n2tContracts { |} |""".stripMargin - val swapBuyV2: String = + val swapBuyV2: String = """ |{ // Token -> ERG | val FeeDenom = 1000 diff --git a/modules/dex-core/src/test/scala/org/ergoplatform/dex/sources/t2tContracts.scala b/modules/dex-core/src/test/scala/org/ergoplatform/dex/sources/t2tContracts.scala index 9d163a16..c7988392 100644 --- a/modules/dex-core/src/test/scala/org/ergoplatform/dex/sources/t2tContracts.scala +++ b/modules/dex-core/src/test/scala/org/ergoplatform/dex/sources/t2tContracts.scala @@ -36,27 +36,27 @@ object t2tContracts { | val validErgChange = rewardOut.value >= SELF.value - DexFee | | val validTokenChange = - | if (minByX < minByY && rewardOut.tokens.size == 2) { - | val diff = minByY - minByX - | val excessY = diff * reservesYAmount / supplyLP + | if (minByX < minByY && rewardOut.tokens.size == 2) { + | val diff = minByY - minByX + | val excessY = diff * reservesYAmount / supplyLP | - | val changeY = rewardOut.tokens(1) + | val changeY = rewardOut.tokens(1) | - | changeY._1 == reservesY._1 && - | changeY._2 >= excessY - | } else if (minByX > minByY && rewardOut.tokens.size == 2) { - | val diff = minByX - minByY - | val excessX = diff * reservesXAmount / supplyLP + | changeY._1 == reservesY._1 && + | changeY._2 >= excessY + | } else if (minByX > minByY && rewardOut.tokens.size == 2) { + | val diff = minByX - minByY + | val excessX = diff * reservesXAmount / supplyLP | - | val changeX = rewardOut.tokens(1) + | val changeX = rewardOut.tokens(1) | - | changeX._1 == reservesX._1 && - | changeX._2 >= excessX - | } else if (minByX == minByY) { - | true - | } else { - | false - | } + | changeX._1 == reservesX._1 && + | changeX._2 >= excessX + | } else if (minByX == minByY) { + | true + | } else { + | false + | } | | val validMinerFee = OUTPUTS.map { (o: Box) => | if (o.propositionBytes == MinerPropBytes) o.value else 0L @@ -255,6 +255,166 @@ object t2tContracts { |} |""".stripMargin + val depositV3: String = + s""" + |{ // ===== Contract Information ===== // + | // Name: Deposit + | // Description: Contract that validates user's deposit into the CFMM t2t Pool. + | // + | // Constants: + | // + | // {1} -> RefundProp[ProveDlog] + | // {8} -> SelfXAmount[Long] // SELF.tokens(0)._2 - ExFee if X is SPF else SELF.tokens(0)._2 + | // {10} -> SelfYAmount[Long] // SELF.tokens(1)._2 - ExFee if Y is SPF else SELF.tokens(1)._2 + | // {13} -> PoolNFT[Coll[Byte]] + | // {14} -> RedeemerPropBytes[Coll[Byte]] + | // {21} -> MinerPropBytes[Coll[Byte]] + | // {24} -> MaxMinerFee[Long] + | // + | // ErgoTree: 19dd041a040008cd02217daf90deb73bdf8b6709bb42093fdfaff6573fd47b630e2d3fdd4a8193a74d040404080402040205feffffffffffffffff01040405a01f040605f02e040004000e2002020202020202020202020202020202020202020202020202020202020202020e2001010101010101010101010101010101010101010101010101010101010101010404040204040402010101000e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d801d601b2a4730000eb027301d195ed92b1a4730293b1db630872017303d80cd602db63087201d603b2a5730400d604b27202730500d6057e9973068c72040206d606b27202730700d6077e8c72060206d6089d9c7e73080672057207d609b27202730900d60a7e8c72090206d60b9d9c7e730a067205720ad60cdb63087203d60db2720c730b00edededededed938cb27202730c0001730d93c27203730e92c17203c1a795ed8f7208720b93b1720c730fd801d60eb2720c731000ed938c720e018c720901927e8c720e02069d9c99720b7208720a720595ed917208720b93b1720c7311d801d60eb2720c731200ed938c720e018c720601927e8c720e02069d9c997208720b7207720595937208720b73137314938c720d018c720401927e8c720d0206a17208720b90b0ada5d9010e639593c2720e7315c1720e73167317d9010e599a8c720e018c720e0273187319 + | // + | // ErgoTreeTemplate: d801d601b2a4730000eb027301d195ed92b1a4730293b1db630872017303d80cd602db63087201d603b2a5730400d604b27202730500d6057e9973068c72040206d606b27202730700d6077e8c72060206d6089d9c7e73080672057207d609b27202730900d60a7e8c72090206d60b9d9c7e730a067205720ad60cdb63087203d60db2720c730b00edededededed938cb27202730c0001730d93c27203730e92c17203c1a795ed8f7208720b93b1720c730fd801d60eb2720c731000ed938c720e018c720901927e8c720e02069d9c99720b7208720a720595ed917208720b93b1720c7311d801d60eb2720c731200ed938c720e018c720601927e8c720e02069d9c997208720b7207720595937208720b73137314938c720d018c720401927e8c720d0206a17208720b90b0ada5d9010e639593c2720e7315c1720e73167317d9010e599a8c720e018c720e0273187319 + | + | val InitiallyLockedLP = 0x7fffffffffffffffL + | val selfXAmount = SelfXAmount + | val selfYAmount = SelfYAmount + | + | val poolIn = INPUTS(0) + | + | // Validations + | // 1. + | val validDeposit = + | if (INPUTS.size >= 2 && poolIn.tokens.size == 4) { + | // 1.1. + | val validPoolIn = poolIn.tokens(0)._1 == PoolNFT + | + | val poolLP = poolIn.tokens(1) + | val reservesX = poolIn.tokens(2) + | val reservesY = poolIn.tokens(3) + | + | val reservesXAmount = reservesX._2 + | val reservesYAmount = reservesY._2 + | + | val supplyLP = InitiallyLockedLP - poolLP._2 + | + | val minByX = selfXAmount.toBigInt * supplyLP / reservesXAmount + | val minByY = selfYAmount.toBigInt * supplyLP / reservesYAmount + | + | val minimalReward = min(minByX, minByY) + | + | val rewardOut = OUTPUTS(1) + | val rewardLP = rewardOut.tokens(0) + | // 1.2. + | val validErgChange = rewardOut.value >= SELF.value + | // 1.3. + | val validTokenChange = + | if (minByX < minByY && rewardOut.tokens.size == 2) { + | val diff = minByY - minByX + | val excessY = diff * reservesYAmount / supplyLP + | + | val changeY = rewardOut.tokens(1) + | + | changeY._1 == reservesY._1 && + | changeY._2 >= excessY + | + | } else if (minByX > minByY && rewardOut.tokens.size == 2) { + | val diff = minByX - minByY + | val excessX = diff * reservesXAmount / supplyLP + | + | val changeX = rewardOut.tokens(1) + | + | changeX._1 == reservesX._1 && + | changeX._2 >= excessX + | + | } else if (minByX == minByY) { + | true + | + | } else { + | false + | } + | + | // 1.4. + | val validMinerFee = OUTPUTS.map { (o: Box) => + | if (o.propositionBytes == MinerPropBytes) o.value else 0L + | }.fold(0L, { (a: Long, b: Long) => a + b }) <= MaxMinerFee + | + | validPoolIn && + | rewardOut.propositionBytes == RedeemerPropBytes && + | validErgChange && + | validTokenChange && + | rewardLP._1 == poolLP._1 && + | rewardLP._2 >= minimalReward && + | validMinerFee + | + | } else false + | + | sigmaProp(RefundProp || validDeposit) + |}""" + .stripMargin + + val redeemV3 = + """ + |{ // ===== Contract Information ===== // + | // Name: Redeem + | // Description: Contract that validates user's redeem from the CFMM t2t Pool. + | // + | // Constants: + | // + | // {1} -> RefundProp[ProveDlog] + | // {13} -> PoolNFT[Coll[Byte]] + | // {14} -> RedeemerPropBytes[Coll[Byte]] + | // {15} -> MinerPropBytes[Coll[Byte]] + | // {18} -> MaxMinerFee[Long] + | // + | // ErgoTree: 19e60314040008cd02217daf90deb73bdf8b6709bb42093fdfaff6573fd47b630e2d3fdd4a8193a74d0404040804020400040404020406040005feffffffffffffffff01040204000e2002020202020202020202020202020202020202020202020202020202020202020e2001010101010101010101010101010101010101010101010101010101010101010e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d801d601b2a4730000eb027301d195ed92b1a4730293b1db630872017303d809d602db63087201d603b2a5730400d604db63087203d605b27204730500d606b27202730600d607b27204730700d608b27202730800d6097e8cb2db6308a77309000206d60a7e99730a8cb27202730b000206edededededed938cb27202730c0001730d93c27203730e938c7205018c720601938c7207018c720801927e8c720502069d9c72097e8c72060206720a927e8c720702069d9c72097e8c72080206720a90b0ada5d9010b639593c2720b730fc1720b73107311d9010b599a8c720b018c720b0273127313 + | // + | // ErgoTreeTemplate: d801d601b2a4730000eb027301d195ed92b1a4730293b1db630872017303d809d602db63087201d603b2a5730400d604db63087203d605b27204730500d606b27202730600d607b27204730700d608b27202730800d6097e8cb2db6308a77309000206d60a7e99730a8cb27202730b000206edededededed938cb27202730c0001730d93c27203730e938c7205018c720601938c7207018c720801927e8c720502069d9c72097e8c72060206720a927e8c720702069d9c72097e8c72080206720a90b0ada5d9010b639593c2720b730fc1720b73107311d9010b599a8c720b018c720b0273127313 + | + | val InitiallyLockedLP = 0x7fffffffffffffffL + | + | val selfLP = SELF.tokens(0) + | + | val poolIn = INPUTS(0) + | + | // Validations + | // 1. + | val validRedeem = + | if (INPUTS.size >= 2 && poolIn.tokens.size == 4) { + | val validPoolIn = poolIn.tokens(0)._1 == PoolNFT + | + | val poolLP = poolIn.tokens(1) + | val reservesX = poolIn.tokens(2) + | val reservesY = poolIn.tokens(3) + | + | val supplyLP = InitiallyLockedLP - poolLP._2 + | + | val selfLPAmount = selfLP._2.toBigInt + | val minReturnX = selfLPAmount * reservesX._2 / supplyLP + | val minReturnY = selfLPAmount * reservesY._2 / supplyLP + | + | val returnOut = OUTPUTS(1) + | + | val returnX = returnOut.tokens(0) + | val returnY = returnOut.tokens(1) + | + | val validMinerFee = OUTPUTS.map { (o: Box) => + | if (o.propositionBytes == MinerPropBytes) o.value else 0L + | }.fold(0L, { (a: Long, b: Long) => a + b }) <= MaxMinerFee + | + | validPoolIn && + | returnOut.propositionBytes == RedeemerPropBytes && + | returnX._1 == reservesX._1 && + | returnY._1 == reservesY._1 && + | returnX._2 >= minReturnX && + | returnY._2 >= minReturnY && + | validMinerFee + | + | } else false + | + | sigmaProp(RefundProp || validRedeem) + |} + |""".stripMargin + val swapV2: String = """ |{ @@ -308,4 +468,108 @@ object t2tContracts { | sigmaProp(RefundProp || validTrade) |} |""".stripMargin + + val swapV3: String = + s""" + |{ // ===== Contract Information ===== // + | // Name: Swap + | // Description: Contract that validates user's swap from token to token in the CFMM t2t Pool. + | // + | // Constants: + | // + | // {1} -> QuoteId[Coll[Byte]] + | // {2} -> MaxExFee[Long] + | // {3} -> ExFeePerTokenDenom[Long] + | // {4} -> BaseAmount[Long] + | // {5} -> FeeNum[Int] + | // {6} -> FeeDenom[Int] + | // {7} -> RefundProp[ProveDlog] + | // {12} -> SpectrumIsQuote[Boolean] + | // {18} -> PoolNFT[Coll[Byte]] + | // {19} -> RedeemerPropBytes[Coll[Byte]] + | // {20} -> MinQuoteAmount[Long] + | // {23} -> ExFeePerTokenNum[Long] + | // {26} -> SpectrumId[Coll[Byte]] + | // {28} -> MinerPropBytes[Coll[Byte]] + | // {31} -> MaxMinerFee[Long] + | // + | // ErgoTree: 19b7052104000e20040404040404040404040404040404040404040404040404040404040404040405f01505c80105e01204c80f04d00f08cd02217daf90deb73bdf8b6709bb42093fdfaff6573fd47b630e2d3fdd4a8193a74d04040408040204000100059c010404040606010104000e2002020202020202020202020202020202020202020202020202020202020202020e20010101010101010101010101010101010101010101010101010101010101010105c00c01000101052c06010004020e20030303030303030303030303030303030303030303030303030303030303030301010e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d806d601b2a4730000d6027301d6037302d6047303d6059c73047e730505d6067306eb027307d195ed92b1a4730893b1db630872017309d80ad607db63087201d608b2a5730a00d609db63087208d60ab27209730b00d60b8c720a02d60c95730c9d9c997e720b067e7203067e7204067e730d067e720b06d60db27207730e00d60e7e8c720d0206d60f7e8cb27207730f000206d6109a720c7310ededededededed938cb2720773110001731293c272087313938c720a01720292720b731492c17208c1a79573157316d801d611997e7203069d9c720c7e7317067e720406959172117318d801d612b27209731900ed938c721201731a927e8c721202067211731b95938c720d017202909c720e7e7205069c72109a9c720f7e7206067e720506909c720f7e7205069c72109a9c720e7e7206067e72050690b0ada5d90111639593c27211731cc17211731d731ed90111599a8c7211018c721102731f7320 + | // + | // ErgoTreeTemplate: d806d601b2a4730000d6027301d6037302d6047303d6059c73047e730505d6067306eb027307d195ed92b1a4730893b1db630872017309d80ad607db63087201d608b2a5730a00d609db63087208d60ab27209730b00d60b8c720a02d60c95730c9d9c997e720b067e7203067e7204067e730d067e720b06d60db27207730e00d60e7e8c720d0206d60f7e8cb27207730f000206d6109a720c7310ededededededed938cb2720773110001731293c272087313938c720a01720292720b731492c17208c1a79573157316d801d611997e7203069d9c720c7e7317067e720406959172117318d801d612b27209731900ed938c721201731a927e8c721202067211731b95938c720d017202909c720e7e7205069c72109a9c720f7e7206067e720506909c720f7e7205069c72109a9c720e7e7206067e72050690b0ada5d90111639593c27211731cc17211731d731ed90111599a8c7211018c721102731f7320 + | + | val baseAmount = BaseAmount + | val feeNum = FeeNum + | val feeDenom = FeeDenom + | val maxExFee = MaxExFee + | val exFeePerTokenDenom = ExFeePerTokenDenom + | val exFeePerTokenNum = ExFeePerTokenNum + | val minQuoteAmount = MinQuoteAmount + | + | val poolIn = INPUTS(0) + | + | // Validations + | // 1. + | val validTrade = + | if (INPUTS.size >= 2 && poolIn.tokens.size == 4) { + | + | val poolNFT = poolIn.tokens(0)._1 + | val poolAssetX = poolIn.tokens(2) + | val poolAssetY = poolIn.tokens(3) + | + | val validPoolIn = poolNFT == PoolNFT + | + | val rewardBox = OUTPUTS(1) + | val quoteAsset = rewardBox.tokens(0) + | val quoteAmount = + | if (SpectrumIsQuote) { + | val deltaQuote = quoteAsset._2.toBigInt - maxExFee + | deltaQuote.toBigInt * exFeePerTokenDenom / (exFeePerTokenDenom - exFeePerTokenNum) + | } else { + | quoteAsset._2.toBigInt + | } + | // 1.1. + | val valuePreserved = rewardBox.value >= SELF.value + | // 1.2. + | val fairExFee = + | if (SpectrumIsQuote) true + | else { + | val exFee = quoteAmount * exFeePerTokenNum / exFeePerTokenDenom + | val remainder = maxExFee - exFee + | if (remainder > 0) { + | val spectrumRem = rewardBox.tokens(1) + | spectrumRem._1 == SpectrumId && spectrumRem._2 >= remainder + | } else { + | true + | } + | } + | + | val relaxedOutput = quoteAmount + 1L // handle rounding loss + | val poolX = poolAssetX._2.toBigInt + | val poolY = poolAssetY._2.toBigInt + | val base_x_feeNum = baseAmount.toBigInt * feeNum + | // 1.3. + | val fairPrice = + | if (poolAssetX._1 == QuoteId) { + | poolX * base_x_feeNum <= relaxedOutput * (poolY * feeDenom + base_x_feeNum) + | } else { + | poolY * base_x_feeNum <= relaxedOutput * (poolX * feeDenom + base_x_feeNum) + | } + | // 1.4. + | val validMinerFee = OUTPUTS.map { (o: Box) => + | if (o.propositionBytes == MinerPropBytes) o.value else 0L + | }.fold(0L, { (a: Long, b: Long) => a + b }) <= MaxMinerFee + | + | validPoolIn && + | rewardBox.propositionBytes == RedeemerPropBytes && + | quoteAsset._1 == QuoteId && + | quoteAsset._2 >= minQuoteAmount && + | valuePreserved && + | fairExFee && + | fairPrice && + | validMinerFee + | + | } else false + | + | sigmaProp(RefundProp || validTrade) + |}""".stripMargin } diff --git a/modules/dex-core/src/test/scala/org/ergoplatform/ergo/modules/MempoolStreamingDemo.scala b/modules/dex-core/src/test/scala/org/ergoplatform/ergo/modules/MempoolStreamingDemo.scala index 0a6266b3..cdf42808 100644 --- a/modules/dex-core/src/test/scala/org/ergoplatform/ergo/modules/MempoolStreamingDemo.scala +++ b/modules/dex-core/src/test/scala/org/ergoplatform/ergo/modules/MempoolStreamingDemo.scala @@ -27,6 +27,6 @@ object MempoolStreamingDemo extends IOApp { implicit0(hasConf: NetworkConfig.Has[IO]) = Context.const[IO, NetworkConfig](NetworkConfig(uri"https://api.ergoplatform.com", uri"http://localhost:9053")) implicit0(node: ErgoNode[IO]) <- Resource.eval(ErgoNode.make[IO, IO]) - streaming = MempoolStreaming.make[fs2.Stream[IO, *], IO] + streaming <- Resource.eval(MempoolStreaming.make[IO, fs2.Stream[IO, *], IO]) } yield streaming } diff --git a/modules/dex-tools/src/main/scala/org/ergoplatform/dex/tools/ExecSwap.scala b/modules/dex-tools/src/main/scala/org/ergoplatform/dex/tools/ExecSwap.scala index 7362487f..e7c59a0a 100644 --- a/modules/dex-tools/src/main/scala/org/ergoplatform/dex/tools/ExecSwap.scala +++ b/modules/dex-tools/src/main/scala/org/ergoplatform/dex/tools/ExecSwap.scala @@ -4,15 +4,17 @@ import cats.effect.{Clock, ExitCode, IO, IOApp} import io.circe.syntax.EncoderOps import org.ergoplatform.{UnsignedErgoLikeTransaction, UnsignedInput} import org.ergoplatform.dex.configs.MonetaryConfig -import org.ergoplatform.dex.domain.amm.CFMMOrder.Swap +import org.ergoplatform.dex.domain.amm.CFMMOrder.SwapP2Pk import org.ergoplatform.dex.domain.{AssetAmount, NetworkContext} import org.ergoplatform.dex.domain.amm.SwapParams import org.ergoplatform.dex.executor.amm.config.ExchangeConfig -import org.ergoplatform.dex.executor.amm.interpreters.N2TCFMMInterpreter -import org.ergoplatform.dex.tracker.parsers.amm.N2TCFMMPoolsParser +import org.ergoplatform.dex.executor.amm.interpreters.v1.N2TCFMMInterpreter +import org.ergoplatform.dex.executor.amm.services.DexOutputResolver import org.ergoplatform.ergo.{Address, PubKey, TokenId} import org.ergoplatform.wallet.interpreter.ErgoUnsafeProver import org.ergoplatform.dex.protocol.codecs._ +import org.ergoplatform.dex.tracker.parsers.amm.pools.N2TCFMMPoolsParser +import org.ergoplatform.ergo.domain.Output import java.util.concurrent.TimeUnit @@ -26,7 +28,15 @@ object ExecSwap extends IOApp with SigmaPlatform { val RecvAddr: Address = Address.fromStringUnsafe("9hqBvFiHGCimMpwG5t1KbGhHXKkx5c41RNvFtjotSZ4Lh7pXLGM") val SigUSD: TokenId = TokenId.fromStringUnsafe("03faf2cb329f2e90d6d23b58d91bbb6c046aa143261cc21f52fbe2824bfcbf04") - val monetaryConfig: MonetaryConfig = MonetaryConfig(1000000L, 0L, 60000L) + val monetaryConfig: MonetaryConfig = MonetaryConfig(1000000L, 0L, 60000L, 1) + + implicit val resolve = new DexOutputResolver[IO] { + def getLatest: IO[Option[Output]] = IO(None) + + def setPredicted(output: Output): IO[Unit] = IO.unit + + def invalidateAndUpdate: IO[Unit] = IO.unit + } def run(args: List[String]): IO[ExitCode] = for { @@ -38,21 +48,21 @@ object ExecSwap extends IOApp with SigmaPlatform { pool = N2TCFMMPoolsParser.pool(poolIn).get swap = SwapParams[PubKey]( - input = AssetAmount.native(userIn.value - monetaryConfig.minerFee - monetaryConfig.minBoxValue), - minOutput = AssetAmount(SigUSD, 0L), + baseAmount = AssetAmount.native(userIn.value - monetaryConfig.minerFee - monetaryConfig.minBoxValue), + minQuoteAmount = AssetAmount(SigUSD, 0L), dexFeePerTokenNum = 0L, dexFeePerTokenDenom = 1L, redeemer = ??? ) - order = Swap(pool.poolId, monetaryConfig.minerFee, ts, swap, userIn) + order = SwapP2Pk(pool.poolId, monetaryConfig.minerFee, ts, swap, userIn) interpreter = new N2TCFMMInterpreter[IO]( - ExchangeConfig(RecvAddr), + ExchangeConfig(TokenId.fromStringUnsafe(""), ""), monetaryConfig, NetworkContext(curHeight, null) ) - (txc, _) <- interpreter.swap(order, pool) + (txc, _, _) <- interpreter.swap(order, pool) uInputs = txc.inputs.map(i => new UnsignedInput(i.boxId)) utx = UnsignedErgoLikeTransaction(uInputs, txc.dataInputs, txc.outputCandidates) diff --git a/modules/markets-index/src/main/scala/org/ergoplatform/dex/index/db/models.scala b/modules/markets-index/src/main/scala/org/ergoplatform/dex/index/db/models.scala index b22cc096..b4155fe0 100644 --- a/modules/markets-index/src/main/scala/org/ergoplatform/dex/index/db/models.scala +++ b/modules/markets-index/src/main/scala/org/ergoplatform/dex/index/db/models.scala @@ -113,10 +113,10 @@ object models { pool.map(p => PoolStateId(p.box.boxId)), minerFee, swap.timestamp, - params.input.id, - params.input.value, - params.minOutput.id, - params.minOutput.value, + params.baseAmount.id, + params.baseAmount.value, + params.minQuoteAmount.id, + params.minQuoteAmount.value, ev.map(_.output.value), params.dexFeePerTokenNum, params.dexFeePerTokenDenom, diff --git a/modules/utxo-tracker/src/main/resources/application.conf b/modules/utxo-tracker/src/main/resources/application.conf index d999b373..9c225e1f 100644 --- a/modules/utxo-tracker/src/main/resources/application.conf +++ b/modules/utxo-tracker/src/main/resources/application.conf @@ -23,9 +23,12 @@ ledger-tracking.retry-delay = 5s mempool-tracking.sampling-interval = 2s +token-id = "9a06d9e545a41fd51eeffc5e20d818073bf820c635e2a9d922269913e0de369d" + monetary.miner-fee = 2000000 monetary.min-dex-fee = 1000000 monetary.min-box-value = 60000 +monetary.min-dex-token-fee = 100000 network.explorer-uri = "https://api.ergoplatform.com" network.node-uri = "http://localhost:9053" diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/App.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/App.scala index a74f4bf1..84e03510 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/App.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/App.scala @@ -46,12 +46,12 @@ object App extends EnvApp[ConfigBundle] { configs <- Resource.eval(ConfigBundle.load[InitF](configPathOpt, blocker)) implicit0(e: ErgoAddressEncoder) = configs.protocol.networkType.addressEncoder implicit0(isoKRun: IsoK[RunF, InitF]) = isoKRunByContext(configs) - implicit0(producer1: Producer[OrderId, Confirmed[CFMMOrder.Any], StreamF]) <- - Producer.make[InitF, StreamF, RunF, OrderId, Confirmed[CFMMOrder.Any]](configs.producers.confirmedAmmOrders) + implicit0(producer1: Producer[OrderId, Confirmed[CFMMOrder.AnyOrder], StreamF]) <- + Producer.make[InitF, StreamF, RunF, OrderId, Confirmed[CFMMOrder.AnyOrder]](configs.producers.confirmedAmmOrders) implicit0(producer2: Producer[PoolId, ConfirmedIndexed[CFMMPool], StreamF]) <- Producer.make[InitF, StreamF, RunF, PoolId, ConfirmedIndexed[CFMMPool]](configs.producers.confirmedAmmPools) - implicit0(producer3: Producer[OrderId, Unconfirmed[CFMMOrder.Any], StreamF]) <- - Producer.make[InitF, StreamF, RunF, OrderId, Unconfirmed[CFMMOrder.Any]](configs.producers.unconfirmedAmmOrders) + implicit0(producer3: Producer[OrderId, Unconfirmed[CFMMOrder.AnyOrder], StreamF]) <- + Producer.make[InitF, StreamF, RunF, OrderId, Unconfirmed[CFMMOrder.AnyOrder]](configs.producers.unconfirmedAmmOrders) implicit0(producer4: Producer[PoolId, Unconfirmed[CFMMPool], StreamF]) <- Producer.make[InitF, StreamF, RunF, PoolId, Unconfirmed[CFMMPool]](configs.producers.unconfirmedAmmPools) implicit0(backend: SttpBackend[RunF, Fs2Streams[RunF]]) <- makeBackend(configs, blocker) @@ -59,10 +59,10 @@ object App extends EnvApp[ConfigBundle] { implicit0(node: ErgoNode[RunF]) <- Resource.eval(ErgoNode.make[InitF, RunF]) implicit0(network: ErgoNetwork[RunF]) = ErgoNetwork.make[RunF] implicit0(ledger: LedgerStreaming[StreamF]) = LedgerStreaming.make[StreamF, RunF] - implicit0(mempool: MempoolStreaming[StreamF]) = MempoolStreaming.make[StreamF, RunF] - implicit0(cfmmRules: CFMMRules[RunF]) = CFMMRules.make[RunF] - confirmedAmmOrderHandler <- Resource.eval(CFMMOpsHandler.make[InitF, StreamF, RunF, Confirmed]) - unconfirmedAmmOrderHandler <- Resource.eval(CFMMOpsHandler.make[InitF, StreamF, RunF, Unconfirmed]) + implicit0(mempool: MempoolStreaming[StreamF]) <- Resource.eval(MempoolStreaming.make[InitF, StreamF, RunF]) + implicit0(cfmmRules: CFMMRules[RunF]) = CFMMRules.make[RunF](configs.tokenId) + confirmedAmmOrderHandler <- Resource.eval(CFMMOpsHandler.make[InitF, StreamF, RunF, Confirmed](configs.tokenId)) + unconfirmedAmmOrderHandler <- Resource.eval(CFMMOpsHandler.make[InitF, StreamF, RunF, Unconfirmed](configs.tokenId)) confirmedAmmPoolsHandler <- Resource.eval(SettledCFMMPoolsHandler.make[InitF, StreamF, RunF]) unconfirmedAmmPoolsHandler <- Resource.eval(CFMMPoolsHandler.make[InitF, StreamF, RunF, Unconfirmed]) implicit0(redis: Redis.Plain[RunF]) <- Redis.make[InitF, RunF](configs.redis) diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/configs/ConfigBundle.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/configs/ConfigBundle.scala index cab917ca..dc1d0143 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/configs/ConfigBundle.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/configs/ConfigBundle.scala @@ -5,9 +5,10 @@ import derevo.pureconfig.pureconfigReader import org.ergoplatform.common.cache.RedisConfig import org.ergoplatform.common.streaming.CommitPolicy import org.ergoplatform.dex.configs._ +import org.ergoplatform.ergo.TokenId import tofu.Context import tofu.logging.Loggable -import tofu.optics.macros.{promote, ClassyOptics} +import tofu.optics.macros.{ClassyOptics, promote} @derive(pureconfigReader) @ClassyOptics @@ -20,7 +21,8 @@ final case class ConfigBundle( @promote ledgerTracking: LedgerTrackingConfig, @promote mempoolTracking: MempoolTrackingConfig, @promote monetary: MonetaryConfig, - redis: RedisConfig + redis: RedisConfig, + tokenId: TokenId ) object ConfigBundle extends Context.Companion[ConfigBundle] with ConfigBundleCompanion[ConfigBundle] { diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/handlers/CFMMHistoryHandler.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/handlers/CFMMHistoryHandler.scala index ca698161..2681359d 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/handlers/CFMMHistoryHandler.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/handlers/CFMMHistoryHandler.scala @@ -9,8 +9,8 @@ import org.ergoplatform.ErgoAddressEncoder import org.ergoplatform.common.streaming.{Producer, Record} import org.ergoplatform.dex.domain.amm._ import org.ergoplatform.dex.protocol.amm.AMMType.{CFMMType, N2T_CFMM, T2T_CFMM} -import org.ergoplatform.dex.tracker.parsers.amm.CFMMHistoryParser -import org.ergoplatform.dex.tracker.parsers.amm.CFMMHistoryParser._ +import org.ergoplatform.dex.tracker.parsers.amm.analytics.CFMMHistoryParser._ +import org.ergoplatform.dex.tracker.parsers.amm.analytics.CFMMHistoryParser import tofu.logging.{Logging, Logs} import tofu.streams.Evals import tofu.syntax.foption._ diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/handlers/CFMMOpsHandler.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/handlers/CFMMOpsHandler.scala index 1a515d54..19d126e4 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/handlers/CFMMOpsHandler.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/handlers/CFMMOpsHandler.scala @@ -10,9 +10,10 @@ import org.ergoplatform.ErgoAddressEncoder import org.ergoplatform.common.streaming.{Producer, Record} import org.ergoplatform.dex.domain.amm._ import org.ergoplatform.dex.protocol.amm.AMMType.{CFMMType, N2T_CFMM, T2T_CFMM} -import org.ergoplatform.dex.protocol.amm.ParserType +import org.ergoplatform.dex.protocol.amm.ParserVersion import org.ergoplatform.dex.tracker.parsers.amm.CFMMOrdersParser import org.ergoplatform.dex.tracker.validation.amm.CFMMRules +import org.ergoplatform.ergo.TokenId import org.ergoplatform.ergo.state.LedgerStatus import tofu.logging.{Logging, Logs} import tofu.streams.Evals @@ -24,8 +25,8 @@ final class CFMMOpsHandler[ F[_]: Monad: Evals[*[_], G]: FunctorFilter, G[_]: Monad: Logging, Status[_]: LedgerStatus -](parsers: List[CFMMOrdersParser[CFMMType, ParserType.Any, G]])(implicit - producer: Producer[OrderId, Status[CFMMOrder[CFMMOrderType.Any]], F], +](parsers: List[CFMMOrdersParser[CFMMType, ParserVersion.Any, G]])(implicit + producer: Producer[OrderId, Status[CFMMOrder[CFMMOrderType]], F], rules: CFMMRules[G] ) { @@ -37,7 +38,7 @@ final class CFMMOpsHandler[ deposit <- parser.deposit(out) redeem <- parser.redeem(out) swap <- parser.swap(out) - } yield deposit orElse redeem orElse swap orElse none[CFMMOrder[CFMMOrderType.Any]] + } yield deposit orElse redeem orElse swap orElse none[CFMMOrder[CFMMOrderType]] } .map(_.reduce(_ orElse _)) }.unNone @@ -46,7 +47,7 @@ final class CFMMOpsHandler[ eval(rules(op)) >>= (_.fold(op.pure[F])(e => eval(debug"Rule violation: $e") >> Evals[F, G].monoidK.empty)) } - .map(op => Record[OrderId, Status[CFMMOrder[CFMMOrderType.Any]]](op.id, LedgerStatus.lift(op))) + .map(op => Record[OrderId, Status[CFMMOrder[CFMMOrderType]]](op.id, LedgerStatus.lift(op))) .thrush(producer.produce) } @@ -57,18 +58,21 @@ object CFMMOpsHandler { F[_]: Monad: Evals[*[_], G]: FunctorFilter, G[_]: Monad: Clock, Status[_]: LedgerStatus - ](implicit - producer: Producer[OrderId, Status[CFMMOrder.Any], F], + ](spf: TokenId)(implicit + producer: Producer[OrderId, Status[CFMMOrder.AnyOrder], F], rules: CFMMRules[G], logs: Logs[I, G], encoder: ErgoAddressEncoder ): I[BoxHandler[F]] = logs.forService[CFMMOpsHandler[F, G, Status]].map { implicit log => + implicit val token: TokenId = spf val parsers = - CFMMOrdersParser[T2T_CFMM, G, ParserType.Default] :: - CFMMOrdersParser[N2T_CFMM, G, ParserType.Default] :: - CFMMOrdersParser[T2T_CFMM, G, ParserType.MultiAddress] :: - CFMMOrdersParser[N2T_CFMM, G, ParserType.MultiAddress] :: + CFMMOrdersParser[T2T_CFMM, G, ParserVersion.V1] :: + CFMMOrdersParser[N2T_CFMM, G, ParserVersion.V1] :: + CFMMOrdersParser[T2T_CFMM, G, ParserVersion.V2] :: + CFMMOrdersParser[N2T_CFMM, G, ParserVersion.V2] :: + CFMMOrdersParser[T2T_CFMM, G, ParserVersion.V3] :: + CFMMOrdersParser[N2T_CFMM, G, ParserVersion.V3] :: Nil new CFMMOpsHandler[F, G, Status](parsers).handler } diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/handlers/CFMMPoolsHandler.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/handlers/CFMMPoolsHandler.scala index a8b1e333..7d6ab2c0 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/handlers/CFMMPoolsHandler.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/handlers/CFMMPoolsHandler.scala @@ -6,7 +6,7 @@ import org.ergoplatform.common.streaming.{Producer, Record} import org.ergoplatform.ergo.state.{Confirmed, LedgerStatus} import org.ergoplatform.dex.domain.amm.{CFMMPool, PoolId} import org.ergoplatform.dex.protocol.amm.AMMType.{CFMMType, N2T_CFMM, T2T_CFMM} -import org.ergoplatform.dex.tracker.parsers.amm.CFMMPoolsParser +import org.ergoplatform.dex.tracker.parsers.amm.pools.CFMMPoolsParser import tofu.logging.{Logging, Logs} import tofu.streams.Evals import tofu.syntax.logging._ diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/handlers/SettledCFMMPoolsHandler.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/handlers/SettledCFMMPoolsHandler.scala index ebe26f61..bec4d062 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/handlers/SettledCFMMPoolsHandler.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/handlers/SettledCFMMPoolsHandler.scala @@ -5,7 +5,7 @@ import mouse.any._ import org.ergoplatform.common.streaming.{Producer, Record} import org.ergoplatform.dex.domain.amm.{CFMMPool, PoolId} import org.ergoplatform.dex.protocol.amm.AMMType.{CFMMType, N2T_CFMM, T2T_CFMM} -import org.ergoplatform.dex.tracker.parsers.amm.CFMMPoolsParser +import org.ergoplatform.dex.tracker.parsers.amm.pools.CFMMPoolsParser import org.ergoplatform.ergo.domain.LedgerMetadata import org.ergoplatform.ergo.state.ConfirmedIndexed import tofu.logging.{Logging, Logs} diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/CFMMOrdersParser.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/CFMMOrdersParser.scala index aa5df747..e6004916 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/CFMMOrdersParser.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/CFMMOrdersParser.scala @@ -3,50 +3,66 @@ package org.ergoplatform.dex.tracker.parsers.amm import cats.Monad import cats.effect.Clock import org.ergoplatform.ErgoAddressEncoder -import org.ergoplatform.dex.domain.amm._ +import org.ergoplatform.dex.domain.amm.CFMMOrder import org.ergoplatform.dex.protocol.amm.AMMType.{CFMMType, N2T_CFMM, T2T_CFMM} -import org.ergoplatform.dex.protocol.amm.ParserType +import org.ergoplatform.dex.protocol.amm.ParserVersion +import org.ergoplatform.dex.tracker.parsers.amm.v1.{N2TOrdersV1Parser, T2TOrdersV1Parser} +import org.ergoplatform.dex.tracker.parsers.amm.v2.{N2TOrdersV2Parser, T2TOrdersV2Parser} +import org.ergoplatform.dex.tracker.parsers.amm.v3.{N2TOrdersV3Parser, T2TOrdersV3Parser} +import org.ergoplatform.ergo.TokenId import org.ergoplatform.ergo.domain.Output import tofu.higherKind.Embed -trait CFMMOrdersParser[+CT <: CFMMType, +T <: ParserType, F[_]] { +trait CFMMOrdersParser[+CT <: CFMMType, +T <: ParserVersion, F[_]] { - def deposit(box: Output): F[Option[CFMMOrder.Deposit]] + def deposit(box: Output): F[Option[CFMMOrder.AnyDeposit]] - def redeem(box: Output): F[Option[CFMMOrder.Redeem]] + def redeem(box: Output): F[Option[CFMMOrder.AnyRedeem]] - def swap(box: Output): F[Option[CFMMOrder.SwapAny]] + def swap(box: Output): F[Option[CFMMOrder.AnySwap]] } object CFMMOrdersParser { - def apply[CT <: CFMMType, F[_], T <: ParserType](implicit + def apply[CT <: CFMMType, F[_], T <: ParserVersion](implicit ev: CFMMOrdersParser[CT, T, F] ): CFMMOrdersParser[CT, T, F] = ev - implicit def embed[CT <: CFMMType, T <: ParserType]: Embed[CFMMOrdersParser[CT, T, *[_]]] = { + implicit def embed[CT <: CFMMType, T <: ParserVersion]: Embed[CFMMOrdersParser[CT, T, *[_]]] = { type Rep[F[_]] = CFMMOrdersParser[CT, T, F] tofu.higherKind.derived.genEmbed[Rep] } - implicit def t2tCfmmOps[F[_]: Monad: Clock](implicit + implicit def v1T2TParser[F[_]: Monad: Clock](implicit e: ErgoAddressEncoder - ): CFMMOrdersParser[T2T_CFMM, ParserType.Default, F] = - T2TCFMMOrdersParserP2Pk.make[F] + ): CFMMOrdersParser[T2T_CFMM, ParserVersion.V1, F] = + T2TOrdersV1Parser.make[F] - implicit def n2tCfmmOps[F[_]: Monad: Clock](implicit + implicit def v1N2TParser[F[_]: Monad: Clock](implicit e: ErgoAddressEncoder - ): CFMMOrdersParser[N2T_CFMM, ParserType.Default, F] = - N2TCFMMOrdersParserP2Pk.make[F] + ): CFMMOrdersParser[N2T_CFMM, ParserVersion.V1, F] = + N2TOrdersV1Parser.make[F] - implicit def t2tCfmmOpsMultiAddr[F[_]: Monad: Clock](implicit + implicit def v2T2TParser[F[_]: Monad: Clock](implicit e: ErgoAddressEncoder - ): CFMMOrdersParser[T2T_CFMM, ParserType.MultiAddress, F] = - T2TCFMMOrdersParserMultiAddress.make[F] + ): CFMMOrdersParser[T2T_CFMM, ParserVersion.V2, F] = + T2TOrdersV2Parser.make[F] - implicit def n2tCfmmOpsMultiAddr[F[_]: Monad: Clock](implicit + implicit def v2N2TParser[F[_]: Monad: Clock](implicit e: ErgoAddressEncoder - ): CFMMOrdersParser[N2T_CFMM, ParserType.MultiAddress, F] = - N2TCFMMOrdersParserMultiAddress.make[F] + ): CFMMOrdersParser[N2T_CFMM, ParserVersion.V2, F] = + N2TOrdersV2Parser.make[F] + + implicit def v3T2TParser[F[_]: Monad: Clock](implicit + spf: TokenId, + e: ErgoAddressEncoder + ): CFMMOrdersParser[T2T_CFMM, ParserVersion.V3, F] = + T2TOrdersV3Parser.make[F](spf) + + implicit def v3N2TParser[F[_]: Monad: Clock](implicit + spf: TokenId, + e: ErgoAddressEncoder + ): CFMMOrdersParser[N2T_CFMM, ParserVersion.V3, F] = + N2TOrdersV3Parser.make[F](spf) } diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/CFMMHistoryParser.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/CFMMHistoryParser.scala similarity index 95% rename from modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/CFMMHistoryParser.scala rename to modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/CFMMHistoryParser.scala index 377e6dec..82e19e50 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/CFMMHistoryParser.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/CFMMHistoryParser.scala @@ -1,4 +1,4 @@ -package org.ergoplatform.dex.tracker.parsers.amm +package org.ergoplatform.dex.tracker.parsers.amm.analytics import cats.Monad import cats.instances.list._ @@ -7,6 +7,7 @@ import cats.syntax.traverse._ import org.ergoplatform.dex.domain.amm.OrderEvaluation.{DepositEvaluation, RedeemEvaluation, SwapEvaluation} import org.ergoplatform.dex.domain.amm._ import org.ergoplatform.dex.protocol.amm.AMMType.{CFMMType, N2T_CFMM, T2T_CFMM} +import org.ergoplatform.dex.tracker.parsers.amm.pools.CFMMPoolsParser import org.ergoplatform.ergo.domain.{Output, SettledTransaction} import tofu.higherKind.Embed import tofu.syntax.foption._ @@ -60,7 +61,7 @@ object CFMMHistoryParser { x.copy(order = o.copy(timestamp = tx.timestamp)) case x @ EvaluatedCFMMOrder(o: CFMMVersionedOrder.SwapP2Pk, _, _, _) => x.copy(order = o.copy(timestamp = tx.timestamp)) - case x@EvaluatedCFMMOrder(o: CFMMVersionedOrder.SwapMultiAddress, _, _, _) => + case x @ EvaluatedCFMMOrder(o: CFMMVersionedOrder.SwapMultiAddress, _, _, _) => x.copy(order = o.copy(timestamp = tx.timestamp)) } diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/CFMMOrderEvaluationParser.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/CFMMOrderEvaluationParser.scala similarity index 93% rename from modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/CFMMOrderEvaluationParser.scala rename to modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/CFMMOrderEvaluationParser.scala index aa986044..a9c9d5a4 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/CFMMOrderEvaluationParser.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/CFMMOrderEvaluationParser.scala @@ -1,11 +1,10 @@ -package org.ergoplatform.dex.tracker.parsers.amm +package org.ergoplatform.dex.tracker.parsers.amm.analytics import cats.Applicative import cats.syntax.option._ import org.ergoplatform.dex.domain.AssetAmount -import org.ergoplatform.dex.domain.amm.{CFMMPool, CFMMVersionedOrder} import org.ergoplatform.dex.domain.amm.OrderEvaluation.{DepositEvaluation, RedeemEvaluation, SwapEvaluation} -import org.ergoplatform.ergo.PubKey +import org.ergoplatform.dex.domain.amm.{CFMMPool, CFMMVersionedOrder} import org.ergoplatform.ergo.domain.Output import tofu.syntax.monadic._ @@ -31,10 +30,10 @@ object CFMMOrderEvaluationParser { def parseSwapEval(output: Output, order: CFMMVersionedOrder.AnySwap): F[Option[SwapEvaluation]] = { val (redeemer, minOutput) = order match { - case swap: CFMMVersionedOrder.SwapP2Pk => (swap.params.redeemer.ergoTree, swap.params.minOutput) + case swap: CFMMVersionedOrder.SwapP2Pk => (swap.params.redeemer.ergoTree, swap.params.minQuoteAmount) case swap: CFMMVersionedOrder.SwapMultiAddress => - (swap.params.redeemer, swap.params.minOutput) - case swap: CFMMVersionedOrder.SwapV0 => (swap.params.redeemer.ergoTree, swap.params.minOutput) + (swap.params.redeemer, swap.params.minQuoteAmount) + case swap: CFMMVersionedOrder.SwapV0 => (swap.params.redeemer.ergoTree, swap.params.minQuoteAmount) } if (output.ergoTree == redeemer) { val outputAmount = diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/CFMMParser.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/CFMMParser.scala similarity index 61% rename from modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/CFMMParser.scala rename to modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/CFMMParser.scala index 7ad7bc8e..2aaf5ed9 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/CFMMParser.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/CFMMParser.scala @@ -1,11 +1,13 @@ -package org.ergoplatform.dex.tracker.parsers.amm +package org.ergoplatform.dex.tracker.parsers.amm.analytics import cats.Monad import cats.data.OptionT +import org.ergoplatform.dex.domain.amm.CFMMOrder.{DepositErgFee, RedeemErgFee} import org.ergoplatform.dex.domain.amm.CFMMVersionedOrder._ import org.ergoplatform.dex.domain.amm._ import org.ergoplatform.dex.protocol.amm.AMMType.{CFMMType, N2T_CFMM, T2T_CFMM} -import org.ergoplatform.dex.protocol.amm.ParserType +import org.ergoplatform.dex.protocol.amm.ParserVersion +import org.ergoplatform.dex.tracker.parsers.amm.CFMMOrdersParser import org.ergoplatform.ergo.domain.Output import tofu.syntax.monadic._ @@ -20,45 +22,50 @@ trait CFMMParser[+CT <: CFMMType, F[_]] { object CFMMParser { implicit def makeT2TCFMMVersionedParser[F[_]: Monad](implicit - default: CFMMOrdersParser[T2T_CFMM, ParserType.Default, F], - multiAddress: CFMMOrdersParser[T2T_CFMM, ParserType.MultiAddress, F], + default: CFMMOrdersParser[T2T_CFMM, ParserVersion.V1, F], + multiAddress: CFMMOrdersParser[T2T_CFMM, ParserVersion.V2, F], v0: LegacyContractsParser[T2T_CFMM, F] ): CFMMParser[T2T_CFMM, F] = make[T2T_CFMM, F] implicit def makeN2TCFMMVersionedParser[F[_]: Monad](implicit - default: CFMMOrdersParser[N2T_CFMM, ParserType.Default, F], - multiAddress: CFMMOrdersParser[N2T_CFMM, ParserType.MultiAddress, F], + default: CFMMOrdersParser[N2T_CFMM, ParserVersion.V1, F], + multiAddress: CFMMOrdersParser[N2T_CFMM, ParserVersion.V2, F], v0: LegacyContractsParser[N2T_CFMM, F] ): CFMMParser[N2T_CFMM, F] = make[N2T_CFMM, F] private def make[CT <: CFMMType, F[_]: Monad](implicit - default: CFMMOrdersParser[CT, ParserType.Default, F], - multiAddress: CFMMOrdersParser[CT, ParserType.MultiAddress, F], - v0: LegacyContractsParser[CT, F] + v0: LegacyContractsParser[CT, F], + v1: CFMMOrdersParser[CT, ParserVersion.V1, F], + v2: CFMMOrdersParser[CT, ParserVersion.V2, F] ): CFMMParser[CT, F] = new CFMMParser[CT, F] { def deposit(box: Output): F[Option[CFMMVersionedOrder.AnyDeposit]] = - OptionT(default.deposit(box)) - .map(s => DepositV2(s.poolId, s.maxMinerFee, s.timestamp, s.params, s.box): CFMMVersionedOrder.AnyDeposit) + OptionT(v1.deposit(box)) + .map { case s: DepositErgFee => + DepositV2(s.poolId, s.maxMinerFee, s.timestamp, s.params, s.box): CFMMVersionedOrder.AnyDeposit + } .orElseF(v0.depositV1(box).map(r => r: Option[CFMMVersionedOrder.AnyDeposit])) .orElseF(v0.depositV0(box).map(r => r: Option[CFMMVersionedOrder.AnyDeposit])) .value def redeem(box: Output): F[Option[CFMMVersionedOrder.AnyRedeem]] = - OptionT(default.redeem(box)) - .map(s => RedeemV1(s.poolId, s.maxMinerFee, s.timestamp, s.params, s.box): CFMMVersionedOrder.AnyRedeem) + OptionT(v1.redeem(box)) + .map { case r: RedeemErgFee => + RedeemV1(r.poolId, r.maxMinerFee, r.timestamp, r.params, r.box): CFMMVersionedOrder.AnyRedeem + } .orElseF(v0.redeemV0(box).map(r => r: Option[CFMMVersionedOrder.AnyRedeem])) .value def swap(box: Output): F[Option[CFMMVersionedOrder.AnySwap]] = - OptionT(default.swap(box)) - .orElseF(multiAddress.swap(box)) + OptionT(v1.swap(box)) + .orElseF(v2.swap(box)) .map { + case s: CFMMOrder.SwapP2Pk => + SwapP2Pk(s.poolId, s.maxMinerFee, s.timestamp, s.params, s.box): CFMMVersionedOrder.AnySwap case s: CFMMOrder.SwapMultiAddress => SwapMultiAddress(s.poolId, s.maxMinerFee, s.timestamp, s.params, s.box): CFMMVersionedOrder.AnySwap - case s: CFMMOrder.Swap => - SwapP2Pk(s.poolId, s.maxMinerFee, s.timestamp, s.params, s.box): CFMMVersionedOrder.AnySwap + case s: CFMMOrder.SwapTokenFee => ??? } .orElseF(v0.swapV0(box).map(r => r: Option[CFMMVersionedOrder.AnySwap])) .value diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/LegacyContractsParser.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/LegacyContractsParser.scala similarity index 94% rename from modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/LegacyContractsParser.scala rename to modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/LegacyContractsParser.scala index 4191b883..c8365aa4 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/LegacyContractsParser.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/LegacyContractsParser.scala @@ -1,4 +1,4 @@ -package org.ergoplatform.dex.tracker.parsers.amm +package org.ergoplatform.dex.tracker.parsers.amm.analytics import cats.Monad import cats.effect.Clock diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TCFMMOrdersLegacyContractsParser.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/N2TCFMMOrdersLegacyContractsParser.scala similarity index 93% rename from modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TCFMMOrdersLegacyContractsParser.scala rename to modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/N2TCFMMOrdersLegacyContractsParser.scala index db4e782e..902adf46 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TCFMMOrdersLegacyContractsParser.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/N2TCFMMOrdersLegacyContractsParser.scala @@ -1,4 +1,4 @@ -package org.ergoplatform.dex.tracker.parsers.amm +package org.ergoplatform.dex.tracker.parsers.amm.analytics import cats.effect.Clock import cats.{Applicative, Monad} @@ -25,7 +25,7 @@ final class N2TCFMMOrdersLegacyContractsParser[F[_]: Applicative](ts: Long)(impl val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) val template = ErgoTreeTemplate.fromBytes(tree.template) val parsed = - if (template == templates.depositV1) { + if (template == templates.depositLegacyV1) { for { poolId <- tree.constants.parseBytea(12).map(PoolId.fromBytes) inX <- tree.constants.parseLong(16).map(AssetAmount.native) @@ -42,7 +42,7 @@ final class N2TCFMMOrdersLegacyContractsParser[F[_]: Applicative](ts: Long)(impl val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) val template = ErgoTreeTemplate.fromBytes(tree.template) val parsed = - if (template == templates.depositV0) { + if (template == templates.depositLegacyV0) { for { poolId <- tree.constants.parseBytea(9).map(PoolId.fromBytes) inX <- tree.constants.parseLong(11).map(AssetAmount.native) @@ -59,7 +59,7 @@ final class N2TCFMMOrdersLegacyContractsParser[F[_]: Applicative](ts: Long)(impl val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) val template = ErgoTreeTemplate.fromBytes(tree.template) val parsed = - if (template == templates.redeemV0) { + if (template == templates.redeemLegacyV0) { for { poolId <- tree.constants.parseBytea(11).map(PoolId.fromBytes) inLP <- box.assets.lift(0).map(a => AssetAmount(a.tokenId, a.amount)) @@ -75,8 +75,8 @@ final class N2TCFMMOrdersLegacyContractsParser[F[_]: Applicative](ts: Long)(impl val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) val template = ErgoTreeTemplate.fromBytes(tree.template) val parsed = - if (template == templates.swapSellV0) swapSellV0(box, tree) - else if (template == templates.swapBuyV0) swapBuyV0(box, tree) + if (template == templates.swapSellLegacyV0) swapSellV0(box, tree) + else if (template == templates.swapBuyLegacyV0) swapBuyV0(box, tree) else None parsed.pure } diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/OrderExecutorFeeParser.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/OrderExecutorFeeParser.scala similarity index 96% rename from modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/OrderExecutorFeeParser.scala rename to modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/OrderExecutorFeeParser.scala index 789aad68..8317771c 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/OrderExecutorFeeParser.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/OrderExecutorFeeParser.scala @@ -1,12 +1,12 @@ -package org.ergoplatform.dex.tracker.parsers.amm +package org.ergoplatform.dex.tracker.parsers.amm.analytics import cats.syntax.option._ -import org.ergoplatform.{ErgoAddressEncoder, P2PKAddress} import org.ergoplatform.dex.domain.amm.{CFMMPool, CFMMVersionedOrder, OrderExecutorFee} import org.ergoplatform.dex.protocol.ErgoTreeSerializer import org.ergoplatform.dex.protocol.amm.constants._ -import org.ergoplatform.ergo.{ErgoTreeTemplate, PubKey} +import org.ergoplatform.ergo.ErgoTreeTemplate import org.ergoplatform.ergo.domain.Output +import org.ergoplatform.{ErgoAddressEncoder, P2PKAddress} trait OrderExecutorFeeParser { diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TCFMMOrdersLegacyContractsParser.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/T2TCFMMOrdersLegacyContractsParser.scala similarity index 94% rename from modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TCFMMOrdersLegacyContractsParser.scala rename to modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/T2TCFMMOrdersLegacyContractsParser.scala index 8e4fb7b0..511a9db9 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TCFMMOrdersLegacyContractsParser.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/analytics/T2TCFMMOrdersLegacyContractsParser.scala @@ -1,4 +1,4 @@ -package org.ergoplatform.dex.tracker.parsers.amm +package org.ergoplatform.dex.tracker.parsers.amm.analytics import cats.effect.Clock import cats.{Applicative, Monad} @@ -24,7 +24,7 @@ final class T2TCFMMOrdersLegacyContractsParser[F[_]: Applicative: Clock](ts: Lon val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) val template = ErgoTreeTemplate.fromBytes(tree.template) val parsed = - if (template == templates.depositV1) { + if (template == templates.depositLegacyV1) { for { poolId <- tree.constants.parseBytea(13).map(PoolId.fromBytes) inX <- box.assets.lift(0).map(a => AssetAmount(a.tokenId, a.amount)) @@ -41,7 +41,7 @@ final class T2TCFMMOrdersLegacyContractsParser[F[_]: Applicative: Clock](ts: Lon val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) val template = ErgoTreeTemplate.fromBytes(tree.template) val parsed = - if (template == templates.depositV0) { + if (template == templates.depositLegacyV0) { for { poolId <- tree.constants.parseBytea(9).map(PoolId.fromBytes) inX <- box.assets.lift(0).map(a => AssetAmount(a.tokenId, a.amount)) @@ -58,7 +58,7 @@ final class T2TCFMMOrdersLegacyContractsParser[F[_]: Applicative: Clock](ts: Lon val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) val template = ErgoTreeTemplate.fromBytes(tree.template) val parsed = - if (template == templates.redeemV0) { + if (template == templates.redeemLegacyV0) { for { poolId <- tree.constants.parseBytea(13).map(PoolId.fromBytes) inLP <- box.assets.lift(0).map(a => AssetAmount(a.tokenId, a.amount)) @@ -74,7 +74,7 @@ final class T2TCFMMOrdersLegacyContractsParser[F[_]: Applicative: Clock](ts: Lon val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) val template = ErgoTreeTemplate.fromBytes(tree.template) val parsed = - if (template == templates.swapV0) { + if (template == templates.swapLegacyV0) { for { poolId <- tree.constants.parseBytea(14).map(PoolId.fromBytes) inAmount <- box.assets.lift(0).map(a => AssetAmount(a.tokenId, a.amount)) diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/CFMMPoolsParser.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/pools/CFMMPoolsParser.scala similarity index 90% rename from modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/CFMMPoolsParser.scala rename to modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/pools/CFMMPoolsParser.scala index 5e89e0b9..2014f5f0 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/CFMMPoolsParser.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/pools/CFMMPoolsParser.scala @@ -1,4 +1,4 @@ -package org.ergoplatform.dex.tracker.parsers.amm +package org.ergoplatform.dex.tracker.parsers.amm.pools import org.ergoplatform.dex.domain.amm.CFMMPool import org.ergoplatform.dex.protocol.amm.AMMType.{CFMMType, N2T_CFMM, T2T_CFMM} diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TCFMMPoolsParser.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/pools/N2TCFMMPoolsParser.scala similarity index 94% rename from modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TCFMMPoolsParser.scala rename to modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/pools/N2TCFMMPoolsParser.scala index 4a2afe74..b9bc96a6 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TCFMMPoolsParser.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/pools/N2TCFMMPoolsParser.scala @@ -1,4 +1,4 @@ -package org.ergoplatform.dex.tracker.parsers.amm +package org.ergoplatform.dex.tracker.parsers.amm.pools import org.ergoplatform.dex.domain.amm.{CFMMPool, PoolId} import org.ergoplatform.dex.domain.{AssetAmount, BoxInfo} diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TCFMMPoolsParser.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/pools/T2TCFMMPoolsParser.scala similarity index 94% rename from modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TCFMMPoolsParser.scala rename to modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/pools/T2TCFMMPoolsParser.scala index ffeecfc9..de03b070 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TCFMMPoolsParser.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/pools/T2TCFMMPoolsParser.scala @@ -1,4 +1,4 @@ -package org.ergoplatform.dex.tracker.parsers.amm +package org.ergoplatform.dex.tracker.parsers.amm.pools import org.ergoplatform.dex.domain.amm.{CFMMPool, PoolId} import org.ergoplatform.dex.domain.{AssetAmount, BoxInfo} diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TCFMMOrdersParserP2Pk.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/v1/N2TOrdersV1Parser.scala similarity index 72% rename from modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TCFMMOrdersParserP2Pk.scala rename to modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/v1/N2TOrdersV1Parser.scala index caf90d8a..5dbb0cf4 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TCFMMOrdersParserP2Pk.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/v1/N2TOrdersV1Parser.scala @@ -1,4 +1,4 @@ -package org.ergoplatform.dex.tracker.parsers.amm +package org.ergoplatform.dex.tracker.parsers.amm.v1 import cats.effect.Clock import cats.{Applicative, Monad} @@ -7,7 +7,8 @@ import org.ergoplatform.dex.domain.AssetAmount import org.ergoplatform.dex.domain.amm._ import org.ergoplatform.dex.protocol.ErgoTreeSerializer import org.ergoplatform.dex.protocol.amm.AMMType.N2T_CFMM -import org.ergoplatform.dex.protocol.amm.{ParserType, N2TCFMMTemplates => templates} +import org.ergoplatform.dex.protocol.amm.{ParserVersion, N2TCFMMTemplates => templates} +import org.ergoplatform.dex.tracker.parsers.amm.CFMMOrdersParser import org.ergoplatform.ergo.domain.Output import org.ergoplatform.ergo.syntax._ import org.ergoplatform.ergo.{ErgoTreeTemplate, PubKey, TokenId} @@ -16,15 +17,15 @@ import tofu.syntax.embed._ import tofu.syntax.monadic._ import tofu.syntax.time._ -final class N2TCFMMOrdersParserP2Pk[F[_]: Applicative](ts: Long)(implicit +final class N2TOrdersV1Parser[F[_]: Applicative](ts: Long)(implicit e: ErgoAddressEncoder -) extends CFMMOrdersParser[N2T_CFMM, ParserType.Default, F] { +) extends CFMMOrdersParser[N2T_CFMM, ParserVersion.V1, F] { - def deposit(box: Output): F[Option[CFMMOrder.Deposit]] = { + def deposit(box: Output): F[Option[CFMMOrder.AnyDeposit]] = { val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) val template = ErgoTreeTemplate.fromBytes(tree.template) - val parsed: Option[CFMMOrder.Deposit] = - if (template == templates.depositLatest) { + val parsed: Option[CFMMOrder.AnyDeposit] = + if (template == templates.depositV1) { for { poolId <- tree.constants.parseBytea(12).map(PoolId.fromBytes) maxMinerFee <- tree.constants.parseLong(22) @@ -33,16 +34,16 @@ final class N2TCFMMOrdersParserP2Pk[F[_]: Applicative](ts: Long)(implicit dexFee <- tree.constants.parseLong(15) redeemer <- tree.constants.parsePk(0).map(pk => PubKey.fromBytes(pk.pkBytes)) params = DepositParams(inX, inY, dexFee, redeemer) - } yield CFMMOrder.Deposit(poolId, maxMinerFee, ts, params, box) + } yield CFMMOrder.DepositErgFee(poolId, maxMinerFee, ts, params, box) } else None parsed.pure } - def redeem(box: Output): F[Option[CFMMOrder.Redeem]] = { + def redeem(box: Output): F[Option[CFMMOrder.AnyRedeem]] = { val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) val template = ErgoTreeTemplate.fromBytes(tree.template) - val parsed: Option[CFMMOrder.Redeem] = - if (template == templates.redeemLatest) { + val parsed: Option[CFMMOrder.AnyRedeem] = + if (template == templates.redeemV1) { for { poolId <- tree.constants.parseBytea(11).map(PoolId.fromBytes) maxMinerFee <- tree.constants.parseLong(16) @@ -50,22 +51,22 @@ final class N2TCFMMOrdersParserP2Pk[F[_]: Applicative](ts: Long)(implicit dexFee <- tree.constants.parseLong(12) redeemer <- tree.constants.parsePk(0).map(pk => PubKey.fromBytes(pk.pkBytes)) params = RedeemParams(inLP, dexFee, redeemer) - } yield CFMMOrder.Redeem(poolId, maxMinerFee, ts, params, box) + } yield CFMMOrder.RedeemErgFee(poolId, maxMinerFee, ts, params, box) } else None parsed.pure } - def swap(box: Output): F[Option[CFMMOrder.SwapAny]] = { + def swap(box: Output): F[Option[CFMMOrder.AnySwap]] = { val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) val template = ErgoTreeTemplate.fromBytes(tree.template) - val parsed: Option[CFMMOrder.SwapAny] = - if (template == templates.swapSellLatest) swapSell(box, tree) - else if (template == templates.swapBuyLatest) swapBuy(box, tree) + val parsed: Option[CFMMOrder.AnySwap] = + if (template == templates.swapSellV1) swapSell(box, tree) + else if (template == templates.swapBuyV1) swapBuy(box, tree) else None parsed.pure } - private def swapSell(box: Output, tree: ErgoTree): Option[CFMMOrder.Swap] = + private def swapSell(box: Output, tree: ErgoTree): Option[CFMMOrder.AnySwap] = for { poolId <- tree.constants.parseBytea(8).map(PoolId.fromBytes) maxMinerFee <- tree.constants.parseLong(22) @@ -77,9 +78,9 @@ final class N2TCFMMOrdersParserP2Pk[F[_]: Applicative](ts: Long)(implicit dexFeePerTokenDenom <- tree.constants.parseLong(12) redeemer <- tree.constants.parsePk(0).map(pk => PubKey.fromBytes(pk.pkBytes)) params = SwapParams(baseAmount, outAmount, dexFeePerTokenNum, dexFeePerTokenDenom, redeemer) - } yield CFMMOrder.Swap(poolId, maxMinerFee, ts, params, box) + } yield CFMMOrder.SwapP2Pk(poolId, maxMinerFee, ts, params, box) - private def swapBuy(box: Output, tree: ErgoTree): Option[CFMMOrder.Swap] = + private def swapBuy(box: Output, tree: ErgoTree): Option[CFMMOrder.AnySwap] = for { poolId <- tree.constants.parseBytea(9).map(PoolId.fromBytes) maxMinerFee <- tree.constants.parseLong(19) @@ -91,11 +92,11 @@ final class N2TCFMMOrdersParserP2Pk[F[_]: Applicative](ts: Long)(implicit dexFeePerTokenNum = dexFeePerTokenDenom - dexFeePerTokenNumDiff redeemer <- tree.constants.parsePk(0).map(pk => PubKey.fromBytes(pk.pkBytes)) params = SwapParams(inAmount, outAmount, dexFeePerTokenNum, dexFeePerTokenDenom, redeemer) - } yield CFMMOrder.Swap(poolId, maxMinerFee, ts, params, box) + } yield CFMMOrder.SwapP2Pk(poolId, maxMinerFee, ts, params, box) } -object N2TCFMMOrdersParserP2Pk { +object N2TOrdersV1Parser { - def make[F[_]: Monad: Clock](implicit e: ErgoAddressEncoder): CFMMOrdersParser[N2T_CFMM, ParserType.Default, F] = - now.millis.map(ts => new N2TCFMMOrdersParserP2Pk(ts): CFMMOrdersParser[N2T_CFMM, ParserType.Default, F]).embed + def make[F[_]: Monad: Clock](implicit e: ErgoAddressEncoder): CFMMOrdersParser[N2T_CFMM, ParserVersion.V1, F] = + now.millis.map(ts => new N2TOrdersV1Parser(ts): CFMMOrdersParser[N2T_CFMM, ParserVersion.V1, F]).embed } diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TCFMMOrdersParserP2Pk.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/v1/T2TOrdersV1Parser.scala similarity index 69% rename from modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TCFMMOrdersParserP2Pk.scala rename to modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/v1/T2TOrdersV1Parser.scala index 1ce3ea04..ab313604 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TCFMMOrdersParserP2Pk.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/v1/T2TOrdersV1Parser.scala @@ -1,13 +1,15 @@ -package org.ergoplatform.dex.tracker.parsers.amm +package org.ergoplatform.dex.tracker.parsers.amm.v1 import cats.effect.Clock import cats.{Applicative, Monad} import org.ergoplatform.ErgoAddressEncoder import org.ergoplatform.dex.domain.AssetAmount +import org.ergoplatform.dex.domain.amm.CFMMOrderType.SwapType import org.ergoplatform.dex.domain.amm._ import org.ergoplatform.dex.protocol.ErgoTreeSerializer import org.ergoplatform.dex.protocol.amm.AMMType.T2T_CFMM -import org.ergoplatform.dex.protocol.amm.{ParserType, T2TCFMMTemplates => templates} +import org.ergoplatform.dex.protocol.amm.{ParserVersion, T2TCFMMTemplates => templates} +import org.ergoplatform.dex.tracker.parsers.amm.CFMMOrdersParser import org.ergoplatform.ergo.domain.Output import org.ergoplatform.ergo.syntax._ import org.ergoplatform.ergo.{ErgoTreeTemplate, PubKey, TokenId} @@ -15,15 +17,15 @@ import tofu.syntax.embed._ import tofu.syntax.monadic._ import tofu.syntax.time.now -final class T2TCFMMOrdersParserP2Pk[F[_]: Applicative: Clock](ts: Long)(implicit +final class T2TOrdersV1Parser[F[_]: Applicative: Clock](ts: Long)(implicit e: ErgoAddressEncoder -) extends CFMMOrdersParser[T2T_CFMM, ParserType.Default, F] { +) extends CFMMOrdersParser[T2T_CFMM, ParserVersion.V1, F] { - def deposit(box: Output): F[Option[CFMMOrder.Deposit]] = { + def deposit(box: Output): F[Option[CFMMOrder.AnyDeposit]] = { val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) val template = ErgoTreeTemplate.fromBytes(tree.template) - val parsed: Option[CFMMOrder.Deposit] = - if (template == templates.depositLatest) { + val parsed: Option[CFMMOrder.AnyDeposit] = + if (template == templates.depositV1) { for { poolId <- tree.constants.parseBytea(13).map(PoolId.fromBytes) maxMinerFee <- tree.constants.parseLong(25) @@ -32,16 +34,16 @@ final class T2TCFMMOrdersParserP2Pk[F[_]: Applicative: Clock](ts: Long)(implicit dexFee <- tree.constants.parseLong(15) redeemer <- tree.constants.parsePk(0).map(pk => PubKey.fromBytes(pk.pkBytes)) params = DepositParams(inX, inY, dexFee, redeemer) - } yield CFMMOrder.Deposit(poolId, maxMinerFee, ts, params, box) + } yield CFMMOrder.DepositErgFee(poolId, maxMinerFee, ts, params, box) } else None parsed.pure } - def redeem(box: Output): F[Option[CFMMOrder.Redeem]] = { + def redeem(box: Output): F[Option[CFMMOrder.AnyRedeem]] = { val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) val template = ErgoTreeTemplate.fromBytes(tree.template) - val parsed: Option[CFMMOrder.Redeem] = - if (template == templates.redeemLatest) { + val parsed: Option[CFMMOrder.AnyRedeem] = + if (template == templates.redeemV1) { for { poolId <- tree.constants.parseBytea(13).map(PoolId.fromBytes) maxMinerFee <- tree.constants.parseLong(19) @@ -49,16 +51,16 @@ final class T2TCFMMOrdersParserP2Pk[F[_]: Applicative: Clock](ts: Long)(implicit dexFee <- tree.constants.parseLong(15) redeemer <- tree.constants.parsePk(0).map(pk => PubKey.fromBytes(pk.pkBytes)) params = RedeemParams(inLP, dexFee, redeemer) - } yield CFMMOrder.Redeem(poolId, maxMinerFee, ts, params, box) + } yield CFMMOrder.RedeemErgFee(poolId, maxMinerFee, ts, params, box) } else None parsed.pure } - def swap(box: Output): F[Option[CFMMOrder.SwapAny]] = { + def swap(box: Output): F[Option[CFMMOrder.AnySwap]] = { val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) val template = ErgoTreeTemplate.fromBytes(tree.template) - val parsed: Option[CFMMOrder.SwapAny] = - if (template == templates.swapLatest) { + val parsed: Option[CFMMOrder.AnySwap] = + if (template == templates.swapV1) { for { poolId <- tree.constants.parseBytea(14).map(PoolId.fromBytes) maxMinerFee <- tree.constants.parseLong(21) @@ -70,14 +72,14 @@ final class T2TCFMMOrdersParserP2Pk[F[_]: Applicative: Clock](ts: Long)(implicit dexFeePerTokenDenom <- tree.constants.parseLong(17) redeemer <- tree.constants.parsePk(0).map(pk => PubKey.fromBytes(pk.pkBytes)) params = SwapParams(inAmount, outAmount, dexFeePerTokenNum, dexFeePerTokenDenom, redeemer) - } yield CFMMOrder.Swap(poolId, maxMinerFee, ts, params, box) + } yield CFMMOrder.SwapP2Pk(poolId, maxMinerFee, ts, params, box) } else None parsed.pure } } -object T2TCFMMOrdersParserP2Pk { +object T2TOrdersV1Parser { - def make[F[_]: Monad: Clock](implicit e: ErgoAddressEncoder): CFMMOrdersParser[T2T_CFMM, ParserType.Default, F] = - now.millis.map(ts => new T2TCFMMOrdersParserP2Pk(ts): CFMMOrdersParser[T2T_CFMM, ParserType.Default, F]).embed + def make[F[_]: Monad: Clock](implicit e: ErgoAddressEncoder): CFMMOrdersParser[T2T_CFMM, ParserVersion.V1, F] = + now.millis.map(ts => new T2TOrdersV1Parser(ts): CFMMOrdersParser[T2T_CFMM, ParserVersion.V1, F]).embed } diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TCFMMOrdersParserMultiAddress.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/v2/N2TOrdersV2Parser.scala similarity index 71% rename from modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TCFMMOrdersParserMultiAddress.scala rename to modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/v2/N2TOrdersV2Parser.scala index f3479395..e9bb91ea 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TCFMMOrdersParserMultiAddress.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/v2/N2TOrdersV2Parser.scala @@ -1,13 +1,15 @@ -package org.ergoplatform.dex.tracker.parsers.amm +package org.ergoplatform.dex.tracker.parsers.amm.v2 import cats.effect.Clock import cats.{Applicative, Monad} import org.ergoplatform.ErgoAddressEncoder import org.ergoplatform.dex.domain.AssetAmount +import org.ergoplatform.dex.domain.amm.CFMMOrderType.SwapType import org.ergoplatform.dex.domain.amm._ import org.ergoplatform.dex.protocol.ErgoTreeSerializer import org.ergoplatform.dex.protocol.amm.AMMType.N2T_CFMM -import org.ergoplatform.dex.protocol.amm.{ParserType, N2TCFMMTemplates => templates} +import org.ergoplatform.dex.protocol.amm.{ParserVersion, N2TCFMMTemplates => templates} +import org.ergoplatform.dex.tracker.parsers.amm.CFMMOrdersParser import org.ergoplatform.ergo.domain.Output import org.ergoplatform.ergo.syntax._ import org.ergoplatform.ergo.{ErgoTreeTemplate, SErgoTree, TokenId} @@ -17,25 +19,25 @@ import tofu.syntax.foption.noneF import tofu.syntax.monadic._ import tofu.syntax.time.now -final class N2TCFMMOrdersParserMultiAddress[F[_]: Applicative: Clock](ts: Long)(implicit +final class N2TOrdersV2Parser[F[_]: Applicative: Clock](ts: Long)(implicit e: ErgoAddressEncoder -) extends CFMMOrdersParser[N2T_CFMM, ParserType.MultiAddress, F] { +) extends CFMMOrdersParser[N2T_CFMM, ParserVersion.V2, F] { - def deposit(box: Output): F[Option[CFMMOrder.Deposit]] = noneF + def deposit(box: Output): F[Option[CFMMOrder.AnyDeposit]] = noneF - def redeem(box: Output): F[Option[CFMMOrder.Redeem]] = noneF + def redeem(box: Output): F[Option[CFMMOrder.AnyRedeem]] = noneF - def swap(box: Output): F[Option[CFMMOrder.SwapAny]] = { + def swap(box: Output): F[Option[CFMMOrder.AnySwap]] = { val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) val template = ErgoTreeTemplate.fromBytes(tree.template) - val parsed: Option[CFMMOrder.SwapAny] = - if (template == templates.swapSellMultiAddress) swapSell(box, tree) - else if (template == templates.swapBuyMultiAddress) swapBuy(box, tree) + val parsed: Option[CFMMOrder.AnySwap] = + if (template == templates.swapSellMultiAddressV2) swapSell(box, tree) + else if (template == templates.swapBuyMultiAddressV2) swapBuy(box, tree) else None parsed.pure } - private def swapSell(box: Output, tree: ErgoTree): Option[CFMMOrder.SwapMultiAddress] = + private def swapSell(box: Output, tree: ErgoTree): Option[CFMMOrder.AnySwap] = for { poolId <- tree.constants.parseBytea(8).map(PoolId.fromBytes) maxMinerFee <- tree.constants.parseLong(23) @@ -49,7 +51,7 @@ final class N2TCFMMOrdersParserMultiAddress[F[_]: Applicative: Clock](ts: Long)( params = SwapParams(baseAmount, outAmount, dexFeePerTokenNum, dexFeePerTokenDenom, redeemer) } yield CFMMOrder.SwapMultiAddress(poolId, maxMinerFee, ts, params, box) - private def swapBuy(box: Output, tree: ErgoTree): Option[CFMMOrder.SwapMultiAddress] = + private def swapBuy(box: Output, tree: ErgoTree): Option[CFMMOrder.AnySwap] = for { poolId <- tree.constants.parseBytea(9).map(PoolId.fromBytes) maxMinerFee <- tree.constants.parseLong(20) @@ -64,10 +66,10 @@ final class N2TCFMMOrdersParserMultiAddress[F[_]: Applicative: Clock](ts: Long)( } yield CFMMOrder.SwapMultiAddress(poolId, maxMinerFee, ts, params, box) } -object N2TCFMMOrdersParserMultiAddress { +object N2TOrdersV2Parser { - def make[F[_]: Monad: Clock](implicit e: ErgoAddressEncoder): CFMMOrdersParser[N2T_CFMM, ParserType.MultiAddress, F] = + def make[F[_]: Monad: Clock](implicit e: ErgoAddressEncoder): CFMMOrdersParser[N2T_CFMM, ParserVersion.V2, F] = now.millis - .map(ts => new N2TCFMMOrdersParserMultiAddress(ts): CFMMOrdersParser[N2T_CFMM, ParserType.MultiAddress, F]) + .map(ts => new N2TOrdersV2Parser(ts): CFMMOrdersParser[N2T_CFMM, ParserVersion.V2, F]) .embed } diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TCFMMOrdersParserMultiAddress.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/v2/T2TOrdersV2Parser.scala similarity index 68% rename from modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TCFMMOrdersParserMultiAddress.scala rename to modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/v2/T2TOrdersV2Parser.scala index ee8a43a5..3fcfb070 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TCFMMOrdersParserMultiAddress.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/v2/T2TOrdersV2Parser.scala @@ -1,13 +1,15 @@ -package org.ergoplatform.dex.tracker.parsers.amm +package org.ergoplatform.dex.tracker.parsers.amm.v2 import cats.effect.Clock import cats.{Applicative, Monad} import org.ergoplatform.ErgoAddressEncoder import org.ergoplatform.dex.domain.AssetAmount +import org.ergoplatform.dex.domain.amm.CFMMOrderType.SwapType import org.ergoplatform.dex.domain.amm._ import org.ergoplatform.dex.protocol.ErgoTreeSerializer import org.ergoplatform.dex.protocol.amm.AMMType.T2T_CFMM -import org.ergoplatform.dex.protocol.amm.{ParserType, T2TCFMMTemplates => templates} +import org.ergoplatform.dex.protocol.amm.{ParserVersion, T2TCFMMTemplates => templates} +import org.ergoplatform.dex.tracker.parsers.amm.CFMMOrdersParser import org.ergoplatform.ergo.domain.Output import org.ergoplatform.ergo.syntax._ import org.ergoplatform.ergo.{ErgoTreeTemplate, SErgoTree, TokenId} @@ -16,19 +18,19 @@ import tofu.syntax.foption.noneF import tofu.syntax.monadic._ import tofu.syntax.time.now -final class T2TCFMMOrdersParserMultiAddress[F[_]: Applicative: Clock](ts: Long)(implicit +final class T2TOrdersV2Parser[F[_]: Applicative: Clock](ts: Long)(implicit e: ErgoAddressEncoder -) extends CFMMOrdersParser[T2T_CFMM, ParserType.MultiAddress, F] { +) extends CFMMOrdersParser[T2T_CFMM, ParserVersion.V2, F] { - def deposit(box: Output): F[Option[CFMMOrder.Deposit]] = noneF + def deposit(box: Output): F[Option[CFMMOrder.AnyDeposit]] = noneF - def redeem(box: Output): F[Option[CFMMOrder.Redeem]] = noneF + def redeem(box: Output): F[Option[CFMMOrder.AnyRedeem]] = noneF - def swap(box: Output): F[Option[CFMMOrder.SwapAny]] = { + def swap(box: Output): F[Option[CFMMOrder.AnySwap]] = { val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) val template = ErgoTreeTemplate.fromBytes(tree.template) - val parsed: Option[CFMMOrder.SwapAny] = - if (template == templates.swapMultiAddress) { + val parsed: Option[CFMMOrder.AnySwap] = + if (template == templates.swapV2) { for { poolId <- tree.constants.parseBytea(14).map(PoolId.fromBytes) maxMinerFee <- tree.constants.parseLong(22) @@ -52,10 +54,10 @@ final class T2TCFMMOrdersParserMultiAddress[F[_]: Applicative: Clock](ts: Long)( } } -object T2TCFMMOrdersParserMultiAddress { +object T2TOrdersV2Parser { - def make[F[_]: Monad: Clock](implicit e: ErgoAddressEncoder): CFMMOrdersParser[T2T_CFMM, ParserType.MultiAddress, F] = + def make[F[_]: Monad: Clock](implicit e: ErgoAddressEncoder): CFMMOrdersParser[T2T_CFMM, ParserVersion.V2, F] = now.millis - .map(ts => new T2TCFMMOrdersParserMultiAddress(ts): CFMMOrdersParser[T2T_CFMM, ParserType.MultiAddress, F]) + .map(ts => new T2TOrdersV2Parser(ts): CFMMOrdersParser[T2T_CFMM, ParserVersion.V2, F]) .embed } diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/v3/N2TOrdersV3Parser.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/v3/N2TOrdersV3Parser.scala new file mode 100644 index 00000000..e1fec25a --- /dev/null +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/v3/N2TOrdersV3Parser.scala @@ -0,0 +1,102 @@ +package org.ergoplatform.dex.tracker.parsers.amm.v3 + +import cats.Functor +import cats.effect.Clock +import org.ergoplatform.dex.domain.AssetAmount +import org.ergoplatform.dex.domain.amm.CFMMOrder.{DepositTokenFee, RedeemTokenFee} +import org.ergoplatform.dex.domain.amm._ +import org.ergoplatform.dex.protocol.ErgoTreeSerializer +import org.ergoplatform.dex.protocol.amm.AMMType.N2T_CFMM +import org.ergoplatform.dex.protocol.amm.{N2TCFMMTemplates, ParserVersion} +import org.ergoplatform.dex.tracker.parsers.amm.CFMMOrdersParser +import org.ergoplatform.ergo.domain.Output +import org.ergoplatform.ergo.syntax._ +import org.ergoplatform.ergo.{ErgoTreeTemplate, SErgoTree, TokenId} +import sigmastate.Values.ErgoTree +import tofu.syntax.monadic._ +import tofu.syntax.time.now.millis +import cats.syntax.option._ + +final class N2TOrdersV3Parser[F[_]: Functor: Clock](spf: TokenId) + extends CFMMOrdersParser[N2T_CFMM, ParserVersion.V3, F] { + + def deposit(box: Output): F[Option[CFMMOrder.AnyDeposit]] = millis.map { ts => + val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) + val template = ErgoTreeTemplate.fromBytes(tree.template) + if (template == N2TCFMMTemplates.depositV3) { + for { + poolId <- tree.constants.parseBytea(12).map(PoolId.fromBytes) + maxMinerFee <- tree.constants.parseLong(21) + selfX <- tree.constants.parseLong(1).map(AssetAmount.native) + selfYAmount <- tree.constants.parseLong(9) + selfYBoxAmount <- box.assets.headOption.map(a => AssetAmount(a.tokenId, a.amount)) + selfY = selfYBoxAmount.withAmount(selfYAmount) + dexFee <- if (selfY.id == spf) (selfYBoxAmount.value - selfY.value).some + else box.assets.find(_.tokenId == spf).map(_.amount) + redeemer <- tree.constants.parseBytea(13).map(SErgoTree.fromBytes) + params = DepositParams(selfX, selfY, dexFee, redeemer) + } yield DepositTokenFee(poolId, maxMinerFee, ts, params, box) + } else None + } + + def redeem(box: Output): F[Option[CFMMOrder.AnyRedeem]] = millis.map { ts => + val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) + val template = ErgoTreeTemplate.fromBytes(tree.template) + if (template == N2TCFMMTemplates.redeemV3) { + for { + poolId <- tree.constants.parseBytea(11).map(PoolId.fromBytes) + maxMinerFee <- tree.constants.parseLong(16) + inLP <- box.assets.lift(0).map(a => AssetAmount(a.tokenId, a.amount)) + dexFee <- box.assets.lift(1).map(a => AssetAmount(a.tokenId, a.amount)) + redeemer <- tree.constants.parseBytea(12).map(SErgoTree.fromBytes) + params = RedeemParams(inLP, dexFee.value, redeemer) + } yield RedeemTokenFee(poolId, maxMinerFee, ts, params, box) + } else None + } + + def swap(box: Output): F[Option[CFMMOrder.AnySwap]] = millis.map { ts => + val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) + val template = ErgoTreeTemplate.fromBytes(tree.template) + if (template == N2TCFMMTemplates.swapSellV3) swapSell(box, tree, ts) + else if (template == N2TCFMMTemplates.swapBuyV3) swapBuy(box, tree, ts) + else None + } + + private def swapSell(box: Output, tree: ErgoTree, ts: Long): Option[CFMMOrder.AnySwap] = + for { + baseAmount <- tree.constants.parseLong(3).map(AssetAmount.native) + poolId <- tree.constants.parseBytea(13).map(PoolId.fromBytes) + maxMinerFee <- tree.constants.parseLong(31) + outId <- tree.constants.parseBytea(15).map(TokenId.fromBytes) + minOutAmount <- tree.constants.parseLong(16) + outAmount = AssetAmount(outId, minOutAmount) + dexFeePerTokenDenom <- tree.constants.parseLong(1) + dexFeePerTokenNumDiff <- tree.constants.parseLong(2) + dexFeePerTokenNum = dexFeePerTokenDenom - dexFeePerTokenNumDiff + redeemer <- tree.constants.parseBytea(14).map(SErgoTree.fromBytes) + params = SwapParams(baseAmount, outAmount, dexFeePerTokenNum, dexFeePerTokenDenom, redeemer) + reserveExFee <- tree.constants.parseLong(11) + } yield CFMMOrder.SwapTokenFee(poolId, maxMinerFee, ts, params, box, reserveExFee) + + private def swapBuy(box: Output, tree: ErgoTree, ts: Long): Option[CFMMOrder.AnySwap] = + for { + poolId <- tree.constants.parseBytea(11).map(PoolId.fromBytes) + maxMinerFee <- tree.constants.parseLong(24) + inAmount <- tree.constants.parseLong(1) + inAssetAmount <- box.assets.headOption.map(a => AssetAmount(a.tokenId, a.amount)).map(_.withAmount(inAmount)) + minQuoteAmount <- tree.constants.parseLong(13) + outAmount = AssetAmount.native(minQuoteAmount) + dexFeePerTokenDenom <- tree.constants.parseLong(8) + dexFeePerTokenNum <- tree.constants.parseLong(9) + reserveExFee <- tree.constants.parseLong(7) + redeemer <- tree.constants.parseBytea(12).map(SErgoTree.fromBytes) + baseAmount = inAssetAmount + params = SwapParams(baseAmount, outAmount, dexFeePerTokenNum, dexFeePerTokenDenom, redeemer) + } yield CFMMOrder.SwapTokenFee(poolId, maxMinerFee, ts, params, box, reserveExFee) +} + +object N2TOrdersV3Parser { + + def make[F[_]: Functor: Clock](spf: TokenId): CFMMOrdersParser[N2T_CFMM, ParserVersion.V3, F] = + new N2TOrdersV3Parser(spf) +} diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/v3/T2TOrdersV3Parser.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/v3/T2TOrdersV3Parser.scala new file mode 100644 index 00000000..2d76db96 --- /dev/null +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/parsers/amm/v3/T2TOrdersV3Parser.scala @@ -0,0 +1,90 @@ +package org.ergoplatform.dex.tracker.parsers.amm.v3 + +import cats.Functor +import cats.effect.Clock +import org.ergoplatform.dex.domain.AssetAmount +import org.ergoplatform.dex.domain.amm.CFMMOrder.{DepositTokenFee, RedeemTokenFee, SwapTokenFee} +import org.ergoplatform.dex.domain.amm._ +import org.ergoplatform.dex.protocol.ErgoTreeSerializer +import org.ergoplatform.dex.protocol.amm.AMMType.T2T_CFMM +import org.ergoplatform.dex.protocol.amm.{ParserVersion, T2TCFMMTemplates} +import org.ergoplatform.dex.tracker.parsers.amm.CFMMOrdersParser +import org.ergoplatform.ergo.domain.Output +import org.ergoplatform.ergo.syntax._ +import org.ergoplatform.ergo.{ErgoTreeTemplate, SErgoTree, TokenId} +import tofu.syntax.monadic._ +import tofu.syntax.time.now.millis +import cats.syntax.option._ + +class T2TOrdersV3Parser[F[_]: Functor: Clock](spf: TokenId) extends CFMMOrdersParser[T2T_CFMM, ParserVersion.V3, F] { + + def deposit(box: Output): F[Option[CFMMOrder.AnyDeposit]] = millis.map { ts => + val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) + val template = ErgoTreeTemplate.fromBytes(tree.template) + if (template == T2TCFMMTemplates.depositV3) { + for { + poolId <- tree.constants.parseBytea(13).map(PoolId.fromBytes) + maxMinerFee <- tree.constants.parseLong(24) + selfX <- tree.constants.parseLong(8) + selfY <- tree.constants.parseLong(10) + inX <- box.assets.headOption.map(a => AssetAmount(a.tokenId, a.amount)) + inY <- box.assets.lift(1).map(a => AssetAmount(a.tokenId, a.amount)) + dexFee <- if (inX.id == spf) (inX.value - selfX).some + else if (inY.id == spf) (inY.value - selfY).some + else box.assets.find(_.tokenId == spf).map(_.amount) + redeemer <- tree.constants.parseBytea(14).map(SErgoTree.fromBytes) + params = DepositParams(inX.withAmount(selfX), inY.withAmount(selfY), dexFee, redeemer) + } yield DepositTokenFee(poolId, maxMinerFee, ts, params, box) + } else None + } + + def redeem(box: Output): F[Option[CFMMOrder.AnyRedeem]] = millis.map { ts => + val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) + val template = ErgoTreeTemplate.fromBytes(tree.template) + if (template == T2TCFMMTemplates.redeemV3) { + for { + poolId <- tree.constants.parseBytea(13).map(PoolId.fromBytes) + maxMinerFee <- tree.constants.parseLong(18) + inLP <- box.assets.lift(0).map(a => AssetAmount(a.tokenId, a.amount)) + dexFee <- box.assets.lift(1).map(a => AssetAmount(a.tokenId, a.amount)) + redeemer <- tree.constants.parseBytea(14).map(SErgoTree.fromBytes) + params = RedeemParams(inLP, dexFee.value, redeemer) + } yield RedeemTokenFee(poolId, maxMinerFee, ts, params, box) + } else None + } + + def swap(box: Output): F[Option[CFMMOrder.AnySwap]] = millis.map { ts => + val tree = ErgoTreeSerializer.default.deserialize(box.ergoTree) + val template = ErgoTreeTemplate.fromBytes(tree.template) + if (template == T2TCFMMTemplates.swapV3) { + for { + poolId <- tree.constants.parseBytea(18).map(PoolId.fromBytes) + maxMinerFee <- tree.constants.parseLong(33) + maxExFee <- tree.constants.parseLong(12) + baseAmount <- tree.constants.parseLong(3) + inAmount <- box.assets.headOption.map(a => AssetAmount(a.tokenId, baseAmount)) + outId <- tree.constants.parseBytea(1).map(TokenId.fromBytes) + minOutAmount <- tree.constants.parseLong(20) + outAmount = AssetAmount(outId, minOutAmount) + dexFeePerTokenDenom <- tree.constants.parseLong(2) + dexFeePerTokenNumDiff <- tree.constants.parseLong(13) + dexFeePerTokenNum = dexFeePerTokenDenom - dexFeePerTokenNumDiff + redeemer <- tree.constants.parseBytea(19).map(SErgoTree.fromBytes) + params = SwapParams( + inAmount, + outAmount, + dexFeePerTokenNum, + dexFeePerTokenDenom, + redeemer + ) + } yield SwapTokenFee(poolId, maxMinerFee, ts, params, box, maxExFee) + } else None + } + +} + +object T2TOrdersV3Parser { + + def make[F[_]: Functor: Clock](spf: TokenId): CFMMOrdersParser[T2T_CFMM, ParserVersion.V3, F] = + new T2TOrdersV3Parser(spf) +} diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/processes/MempoolTracker.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/processes/MempoolTracker.scala index c1e2ee64..74b4495e 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/processes/MempoolTracker.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/processes/MempoolTracker.scala @@ -1,11 +1,14 @@ package org.ergoplatform.dex.tracker.processes -import cats.effect.Clock +import cats.effect.{Clock, Timer} import cats.{Defer, FlatMap, Monad, MonoidK} import org.ergoplatform.common.data.TemporalFilter import org.ergoplatform.dex.tracker.configs.MempoolTrackingConfig import org.ergoplatform.dex.tracker.handlers.BoxHandler +import org.ergoplatform.ergo.domain.Output import org.ergoplatform.ergo.modules.MempoolStreaming +import org.ergoplatform.ergo.services.explorer.models.Transaction +import org.ergoplatform.ergo.services.node.ErgoNode import tofu.Catches import tofu.concurrent.MakeRef import tofu.logging.{Logging, Logs} @@ -14,7 +17,8 @@ import tofu.syntax.embed._ import tofu.syntax.logging._ import tofu.syntax.monadic._ import tofu.syntax.handle._ -import tofu.syntax.streams.all._ +import tofu.syntax.streams.all.{eval, _} +import cats.syntax.traverse._ import scala.concurrent.duration._ @@ -22,7 +26,7 @@ import scala.concurrent.duration._ */ final class MempoolTracker[ F[_]: Monad: Evals[*[_], G]: ParFlatten: Pace: Defer: MonoidK: Catches, - G[_]: Monad: Logging + G[_]: Monad: Logging: Timer ](conf: MempoolTrackingConfig, filter: TemporalFilter[G], handlers: List[BoxHandler[F]])(implicit mempool: MempoolStreaming[F] ) extends UtxoTracker[F] { @@ -39,6 +43,7 @@ final class MempoolTracker[ emits(handlers.map(_(output.pure[F]))).parFlattenUnbounded else unit[F] } yield () + sync.repeat .throttled(conf.samplingInterval) .handleWith[Throwable](e => eval(warnCause"Mempool Tracker failed, restarting .." (e)) >> run) @@ -50,7 +55,7 @@ object MempoolTracker { def make[ I[_]: FlatMap, F[_]: Monad: Evals[*[_], G]: ParFlatten: Pace: Defer: MonoidK: MempoolTrackingConfig.Has: Catches, - G[_]: Monad: Clock + G[_]: Monad: Clock: Timer ](handlers: BoxHandler[F]*)(implicit mempool: MempoolStreaming[F], logs: Logs[I, G], diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/validation/amm/CfmmRuleDefs.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/validation/amm/CfmmRuleDefs.scala index 7cdd66ba..e394fde1 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/validation/amm/CfmmRuleDefs.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/validation/amm/CfmmRuleDefs.scala @@ -4,38 +4,56 @@ import cats.Applicative import org.ergoplatform.dex.configs.MonetaryConfig import org.ergoplatform.dex.domain.amm.CFMMOrder import org.ergoplatform.dex.domain.amm.CFMMOrder._ -import tofu.syntax.embed._ +import org.ergoplatform.ergo.TokenId import tofu.syntax.monadic._ import scala.{PartialFunction => ?=>} -final class CfmmRuleDefs[F[_]: Applicative](conf: MonetaryConfig) { +final class CfmmRuleDefs[F[_]: Applicative](conf: MonetaryConfig, spf: TokenId) { - type CFMMRule = CFMMOrder.Any ?=> Option[RuleViolation] + type CFMMRule = CFMMOrder.AnyOrder ?=> Option[RuleViolation] def rules: CFMMRules[F] = op => allRules.lift(op).flatten.pure private val allRules = sufficientValueDepositRedeem orElse sufficientValueSwap private def sufficientValueDepositRedeem: CFMMRule = { - case Deposit(_, _, _, params, _) => checkFee(params.dexFee) - case Redeem(_, _, _, params, _) => checkFee(params.dexFee) + case deposit: DepositErgFee => checkFee(deposit.params.dexFee, false) + case deposit: DepositTokenFee => checkFee(deposit.params.dexFee, true) + case redeem: RedeemErgFee => checkFee(redeem.params.dexFee, false) + case redeem: RedeemTokenFee => checkFee(redeem.params.dexFee, true) } - private def sufficientValueSwap: CFMMRule = { case Swap(_, maxMinerFee, _, params, box) => - val minDexFee = BigInt(params.dexFeePerTokenNum) * params.minOutput.value / params.dexFeePerTokenDenom - val nativeInput = if (params.input.isNative) params.input.value else 0L - val minerFee = conf.minerFee min maxMinerFee - val maxDexFee = box.value - conf.minBoxValue - nativeInput - val insufficientValue = - if (maxDexFee >= minDexFee) None - else Some(s"Actual fee '$maxDexFee' is less than declared minimum '$minDexFee'") - val maxDexFeeNet = maxDexFee - minerFee - val insufficientFee = checkFee(maxDexFeeNet) - insufficientFee orElse insufficientValue + private def sufficientValueSwap: CFMMRule = { order => + (order match { + case SwapP2Pk(_, maxMinerFee, _, params, box) => Some((params, false, maxMinerFee, box, 0L)) + case SwapMultiAddress(_, maxMinerFee, _, params, box) => Some((params, false, maxMinerFee, box, 0L)) + case SwapTokenFee(_, maxMinerFee, _, params, box, reservedExFee) => + Some((params, true, maxMinerFee, box, reservedExFee)) + case _ => None + }) match { + case Some((params, isTokenFee, maxMinerFee, box, reservedExFee)) => + val feeFactor = BigDecimal(params.dexFeePerTokenNum) / params.dexFeePerTokenDenom + val minDexFee = params.minQuoteAmount.value * feeFactor + val nativeInput = if (params.baseAmount.isNative) params.baseAmount.value else 0L + val maxTokenFee = reservedExFee + val minerFee = conf.minerFee min maxMinerFee + val maxDexFeeErg = box.value - conf.minBoxValue - nativeInput + val insufficientValue = { + if (isTokenFee && maxTokenFee >= minDexFee) None + else if (maxDexFeeErg >= minDexFee) None + else Some(s"Actual fee '$maxDexFeeErg' or '$maxTokenFee' is less than declared minimum '$minDexFee'") + } + val maxDexFeeNetErg = maxDexFeeErg - minerFee + val insufficientFee = if (isTokenFee) checkFee(maxTokenFee, true) else checkFee(maxDexFeeNetErg, false) + insufficientFee orElse insufficientValue + case None => None + } + } - private def checkFee(givenFee: BigInt): Option[RuleViolation] = - if (givenFee >= conf.minDexFee) None + private def checkFee(givenFee: BigInt, tokenFee: Boolean): Option[RuleViolation] = + if (tokenFee && givenFee >= conf.minDexTokenFee) None + else if (givenFee >= conf.minDexFee) None else Some(s"Declared fee '$givenFee' is less than configured minimum '${conf.minDexFee}'") } diff --git a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/validation/amm/package.scala b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/validation/amm/package.scala index cd60205d..f6f46ede 100644 --- a/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/validation/amm/package.scala +++ b/modules/utxo-tracker/src/main/scala/org/ergoplatform/dex/tracker/validation/amm/package.scala @@ -3,6 +3,7 @@ package org.ergoplatform.dex.tracker.validation import cats.{FlatMap, Monad} import org.ergoplatform.dex.configs.MonetaryConfig import org.ergoplatform.dex.domain.amm.CFMMOrder +import org.ergoplatform.ergo.TokenId import org.ergoplatform.ergo.services.explorer.ErgoExplorer import tofu.higherKind.Embed import tofu.syntax.context._ @@ -13,7 +14,7 @@ package object amm { type RuleViolation = String - type CFMMRules[F[_]] = CFMMOrder.Any => F[Option[RuleViolation]] + type CFMMRules[F[_]] = CFMMOrder.AnyOrder => F[Option[RuleViolation]] implicit def embed: Embed[CFMMRules] = new Embed[CFMMRules] { @@ -24,7 +25,7 @@ package object amm { object CFMMRules { - def make[F[_]: Monad: MonetaryConfig.Has]: CFMMRules[F] = - (MonetaryConfig.access map (conf => new CfmmRuleDefs[F](conf).rules)).embed + def make[F[_]: Monad: MonetaryConfig.Has](spf: TokenId): CFMMRules[F] = + (MonetaryConfig.access map (conf => new CfmmRuleDefs[F](conf, spf).rules)).embed } } diff --git a/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/CFMMHistoryParserSpec.scala b/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/CFMMHistoryParserSpec.scala index 73e4a8b5..be9553fa 100644 --- a/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/CFMMHistoryParserSpec.scala +++ b/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/CFMMHistoryParserSpec.scala @@ -3,6 +3,7 @@ package org.ergoplatform.dex.tracker.parsers.amm import cats.effect.IO import org.ergoplatform.ErgoAddressEncoder import org.ergoplatform.dex.CatsPlatform +import org.ergoplatform.dex.tracker.parsers.amm.analytics.CFMMHistoryParser import org.ergoplatform.ergo.domain.SettledTransaction import org.ergoplatform.ergo.services.explorer.models.{Transaction => ExplorerTX} import org.scalatest.matchers.should @@ -16,7 +17,6 @@ class CFMMHistoryParserSpec extends AnyPropSpec with should.Matchers with ScalaC property("AMM Swap parsing") { val p = CFMMHistoryParser.t2tCFMMHistory[IO] val parseF = p.swap(SettledTransaction.fromExplorer(txSample)) - println(parseF.unsafeRunSync()) } val txSample = diff --git a/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TCFMMOrdersParserP2PkSpec.scala b/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TCFMMOrdersParserP2PkSpec.scala index 07a5b494..e061bd09 100644 --- a/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TCFMMOrdersParserP2PkSpec.scala +++ b/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TCFMMOrdersParserP2PkSpec.scala @@ -3,6 +3,7 @@ package org.ergoplatform.dex.tracker.parsers.amm import cats.effect.IO import org.ergoplatform.ErgoAddressEncoder import org.ergoplatform.dex.CatsPlatform +import org.ergoplatform.dex.tracker.parsers.amm.v1.N2TOrdersV1Parser import org.ergoplatform.ergo.domain.Output import org.ergoplatform.ergo._ import org.scalatest.matchers.should @@ -13,11 +14,10 @@ class N2TCFMMOrdersParserP2PkSpec extends AnyPropSpec with should.Matchers with property("N2T Deposit order parsing") { val res = parser.deposit(boxSample).unsafeRunSync() - println(res) } implicit val e: ErgoAddressEncoder = new ErgoAddressEncoder(ErgoAddressEncoder.MainnetNetworkPrefix) - def parser = N2TCFMMOrdersParserP2Pk.make[IO] + def parser = N2TOrdersV1Parser.make[IO] def boxSample = io.circe.parser diff --git a/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TMultiAddressSwapParserSpec.scala b/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TV2SwapParserSpec.scala similarity index 97% rename from modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TMultiAddressSwapParserSpec.scala rename to modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TV2SwapParserSpec.scala index 28e5edd5..f3492051 100644 --- a/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TMultiAddressSwapParserSpec.scala +++ b/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TV2SwapParserSpec.scala @@ -7,14 +7,15 @@ import org.ergoplatform.dex.domain.AssetAmount import org.ergoplatform.dex.domain.amm.CFMMOrder.SwapMultiAddress import org.ergoplatform.dex.domain.amm.{CFMMOrder, PoolId, SwapParams} import org.ergoplatform.dex.protocol.ErgoTreeSerializer -import org.ergoplatform.dex.protocol.amm.{AMMType, N2TCFMMTemplates, ParserType} +import org.ergoplatform.dex.protocol.amm.{AMMType, N2TCFMMTemplates, ParserVersion} +import org.ergoplatform.dex.tracker.parsers.amm.v2.N2TOrdersV2Parser import org.ergoplatform.ergo._ import org.ergoplatform.ergo.domain.Output import org.scalatest.matchers.should import org.scalatest.propspec.AnyPropSpec import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks -class N2TMultiAddressSwapParserSpec +class N2TV2SwapParserSpec extends AnyPropSpec with should.Matchers with ScalaCheckPropertyChecks @@ -43,7 +44,7 @@ class N2TMultiAddressSwapParserSpec val template = ErgoTreeTemplate.fromBytes(tree.template) - N2TCFMMTemplates.swapBuyMultiAddress shouldEqual template + N2TCFMMTemplates.swapBuyMultiAddressV2 shouldEqual template val order = parser.swap(boxSampleSwapBuy).unsafeRunSync().get @@ -74,7 +75,7 @@ class N2TMultiAddressSwapParserSpec val tree = ErgoTreeSerializer.default.deserialize(sErgoTreeSwapSell) val template = ErgoTreeTemplate.fromBytes(tree.template) - N2TCFMMTemplates.swapSellMultiAddress shouldEqual template + N2TCFMMTemplates.swapSellMultiAddressV2 shouldEqual template val order = parser.swap(boxSampleSwapSell).unsafeRunSync().get @@ -103,8 +104,8 @@ class N2TMultiAddressSwapParserSpec implicit val e: ErgoAddressEncoder = new ErgoAddressEncoder(ErgoAddressEncoder.MainnetNetworkPrefix) - def parser: CFMMOrdersParser[AMMType.N2T_CFMM, ParserType.MultiAddress, SyncIO] = - N2TCFMMOrdersParserMultiAddress.make[SyncIO] + def parser: CFMMOrdersParser[AMMType.N2T_CFMM, ParserVersion.V2, SyncIO] = + N2TOrdersV2Parser.make[SyncIO] def sErgoTreeSwapBuy = SErgoTree.unsafeFromString( diff --git a/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TV3ParserSpec.scala b/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TV3ParserSpec.scala new file mode 100644 index 00000000..8317f0b2 --- /dev/null +++ b/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TV3ParserSpec.scala @@ -0,0 +1,267 @@ +package org.ergoplatform.dex.tracker.parsers.amm + +import cats.effect.{Clock, SyncIO} +import org.ergoplatform.ErgoAddressEncoder +import org.ergoplatform.dex.CatsPlatform +import org.ergoplatform.dex.domain.AssetAmount +import org.ergoplatform.dex.domain.amm.CFMMOrder._ +import org.ergoplatform.dex.domain.amm._ +import org.ergoplatform.dex.protocol.amm.{AMMType, ParserVersion} +import org.ergoplatform.dex.tracker.parsers.amm.v3.N2TOrdersV3Parser +import org.ergoplatform.ergo._ +import org.ergoplatform.ergo.domain.Output +import org.scalatest.matchers.should +import org.scalatest.propspec.AnyPropSpec +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks + +class N2TV3ParserSpec extends AnyPropSpec with should.Matchers with ScalaCheckPropertyChecks with CatsPlatform { + + implicit val clock: Clock[SyncIO] = Clock.create + + implicit val e: ErgoAddressEncoder = new ErgoAddressEncoder(ErgoAddressEncoder.MainnetNetworkPrefix) + + def parser: CFMMOrdersParser[AMMType.N2T_CFMM, ParserVersion.V3, SyncIO] = + N2TOrdersV3Parser.make[SyncIO]( + TokenId.fromStringUnsafe("003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0") + ) + + property("Parse deposit correct") { + val deposit = parser.deposit(depositV3).unsafeRunSync().get.asInstanceOf[DepositTokenFee] + + val expected = CFMMOrder.DepositTokenFee( + PoolId.fromStringUnsafe("9916d75132593c8b07fe18bd8d583bda1652eed7565cf41a4738ddd90fc992ec"), + 2000000, + deposit.timestamp, + DepositParams( + AssetAmount( + TokenId.fromStringUnsafe("0000000000000000000000000000000000000000000000000000000000000000"), + 12263164 + ), + AssetAmount(TokenId.fromStringUnsafe("03faf2cb329f2e90d6d23b58d91bbb6c046aa143261cc21f52fbe2824bfcbf04"), 2), + dexFee = 15, + redeemer = + SErgoTree.unsafeFromString("0008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec") + ), + depositV3 + ) + + deposit shouldEqual expected + } + + property("Parse redeem correct") { + val redeem = parser.redeem(redeemV3).unsafeRunSync().get.asInstanceOf[RedeemTokenFee] + + val expectedRedeem = CFMMOrder.RedeemTokenFee( + PoolId.fromStringUnsafe("9916d75132593c8b07fe18bd8d583bda1652eed7565cf41a4738ddd90fc992ec"), + 2000000, + redeem.timestamp, + RedeemParams( + AssetAmount( + TokenId.fromStringUnsafe("303f39026572bcb4060b51fafc93787a236bb243744babaa99fceb833d61e198"), + 11439 + ), + dexFee = 15, + redeemer = + SErgoTree.unsafeFromString("0008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec") + ), + redeemV3 + ) + + redeem shouldEqual expectedRedeem + + } + + property("Parse swap buy correct") { + val swap = parser.swap(swapBuyV3).unsafeRunSync().get.asInstanceOf[SwapTokenFee] + + swap.params shouldEqual SwapParams( + AssetAmount( + TokenId.fromStringUnsafe("003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0"), + 22 + ), + AssetAmount( + TokenId.fromStringUnsafe("0000000000000000000000000000000000000000000000000000000000000000"), + 9852048 + ), + 1522526077827L, + 1000000000000000000L, + SErgoTree.unsafeFromString("0008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec") + ) + swap.poolId shouldEqual PoolId.fromStringUnsafe("1d5afc59838920bb5ef2a8f9d63825a55b1d48e269d7cecee335d637c3ff5f3f") + swap.maxMinerFee shouldEqual 2000000 + } + + property("Parse swap sell correct") { + val swap = parser.swap(swapSellV3).unsafeRunSync().get.asInstanceOf[SwapTokenFee] + + swap.params shouldEqual SwapParams( + AssetAmount( + TokenId.fromStringUnsafe("0000000000000000000000000000000000000000000000000000000000000000"), + 10000000 + ), + AssetAmount( + TokenId.fromStringUnsafe("003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0"), + 20 + ), + 75, + 100, + SErgoTree.unsafeFromString("0008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec") + ) + swap.poolId shouldEqual PoolId.fromStringUnsafe("1d5afc59838920bb5ef2a8f9d63825a55b1d48e269d7cecee335d637c3ff5f3f") + swap.maxMinerFee shouldEqual 2000000 + + } + + def swapSellV3 = + io.circe.parser + .decode[Output]( + s""" + |{ + | "boxId": "a3673cf919314fc4ac42929a076ba8c2cbda0193878b2fc739a7fe557e52e70c", + | "transactionId": "14556ddac7ba7e8a4fd13b691c2eaf3836a265c9d11a1ba20c5496f50b43a510", + | "blockId": "9a9af9e7041985dc09309829bf955a5fe6b63bfaaf0acc9b11474d12d174a500", + | "value": 10310000, + | "index": 0, + | "globalIndex": 26613367, + | "creationHeight": 941734, + | "settlementHeight": 941736, + | "ergoTree": "19800521040005c80105320580dac40904c60f08cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec04040406040204000101052404000e201d5afc59838920bb5ef2a8f9d63825a55b1d48e269d7cecee335d637c3ff5f3f0e240008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec0e20003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d005280101010105f015060100040404020e20003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d00101040406010104d00f0e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a5730405000500058092f4010100d804d601b2a4730000d6027301d6037302d6049c73037e730405eb027305d195ed92b1a4730693b1db630872017307d806d605db63087201d606b2a5730800d607db63087206d608b27207730900d6098c720802d60a95730a9d9c7e997209730b067e7202067e7203067e720906edededededed938cb27205730c0001730d93c27206730e938c720801730f92720a7e7310069573117312d801d60b997e7313069d9c720a7e7203067e72020695ed91720b731492b172077315d801d60cb27207731600ed938c720c017317927e8c720c0206720b7318909c7e8cb2720573190002067e7204069c9a720a731a9a9c7ec17201067e731b067e72040690b0ada5d9010b639593c2720b731cc1720b731d731ed9010b599a8c720b018c720b02731f7320", + | "address": "ZnQPhHYW6FNzr1CpzGutiwrC1RCaoZxrhwTt7NjasvE5rMC1YQptuMqdApAnt8rNqqGV38j9E6W7dLRrBUqzzGZFh5KUhAbowhJjoiaH5EovkEhNWVfGQnw5LL9cdsYBHBk7aopYFKgcNTYqcRCPi1jEs5PAMrZzLqxFQf9zFJUmH4kmRzhEnfp4TzC1csg247kMqEp2piYFXDLQcDxPpw1KTLykzERp3rH8RWgL9STCM7FCXUTwBL2yFo9tfbM7ZfH9ngNgJ6yaEQFS52cyEDTiWQpbSqCD9vy9Yqtth93xY57etuq65EJu4vcC7TfFrjjnxgAzGR41xjhZaXSkGNcSDE2Zn9LZm9kpE8whDhsgKmA1LonFRfvvusa4KJbFAHLK779MeATJEBEHSYFAVapwLKEPvaYJZ9TDhoaCfjG8Afj4Tp1RpWu3S7C3tJRsjsR9rrejqsDSH1XaxcjG4VJeAm9BDX4SiNnNm7pUTS7LQP6YTET49k2XHrzv8y5yavPJqFSEVD4X7K8QHiRWex7Axiqc9fduQLQ4qmmGDzErdNUnasEUoyNGzPFuTjq3uvroXkuBoGC1vzMmNfQhuV89gNFweDaEULH124Z9fzkJ39KxCXqzeAqhE8y84YFzb6hM1t3FqYM5cZdC4Vos5bWrkLu8qVFoYuVWT1EC4dLdMbRP1pZDSDQvv1a54xXvhWxiTPFhBTSnx3VphpiAxeoqsQRXc14L7n79WUc935fe4mHktk6zXasKewQmaaT4MnunDruqnDBVBoBBGWhxJc3RLSkJTv73qBHyYbBMgz6zzxtfbEzpi7RvM8asREfaTwBWZ4nqxoXVYCQgP7cpaoAw7ZCWKiZeHghujNpZs9ZAnUUtNrAy", + | "assets": [ + | { + | "tokenId": "003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0", + | "index": 0, + | "amount": 18, + | "name": "SigRSV", + | "decimals": 0, + | "type": "EIP-004" + | } + | ], + | "additionalRegisters": {}, + | "spentTransactionId": "e05ca4f19a066e24ca53a17d9511e7395c9ca16be37296fb41d778bc26310c34", + | "mainChain": true + |} + |""".stripMargin + ) + .toOption + .get + + def swapBuyV3 = + io.circe.parser + .decode[Output]( + s""" + |{ + | "boxId": "7134a244040ce51f1cdd6e8244414473cee6ed6f69efe3cd7829748b56770623", + | "transactionId": "0c4648c2152036a2ff15aa9094fdea094e8fb6c9031904b4d6e8b590a75abda8", + | "blockId": "4ea049c11f76c50a2b864472283743fa57ca677aef29981a8804493acca5e3ea", + | "value": 310000, + | "index": 0, + | "globalIndex": 26543227, + | "creationHeight": 940121, + | "settlementHeight": 940123, + | "ergoTree": "199c041a0400052c04c60f08cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec0404040604020524058080a0f6f4acdbe01b05868e82dacf5804000e201d5afc59838920bb5ef2a8f9d63825a55b1d48e269d7cecee335d637c3ff5f3f0e240008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec05a0d2b20906010004000e20003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d001010502040404d00f0e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a5730405000500058092f4010100d802d601b2a4730000d6029c73017e730205eb027303d195ed92b1a4730493b1db630872017305d804d603db63087201d604b2a5730600d60599c17204c1a7d606997e7307069d9c7e7205067e7308067e730906ededededed938cb27203730a0001730b93c27204730c927205730d95917206730ed801d607b2db63087204730f00ed938c7207017310927e8c7207020672067311909c7ec17201067e7202069c7e9a72057312069a9c7e8cb2720373130002067e7314067e72020690b0ada5d90107639593c272077315c1720773167317d90107599a8c7207018c72070273187319", + | "address": "4J8wkYtoeJ2gHH5jPzihSRcHcB5tgGFAEnL6kqJFeiAg3AcGcCbrqacccrU1rzJPfZLUyy7eBnbKUadAv7qnVboyYZFxKCbGTNg9N8ChGSTuk499nsA7ipKzgTxLbLwt9KjnJoXmqoFfBXFiiSWFWpw8rdnfpRoXZCGEbzmHvUWmwFMnMNrP6kGF769xooFTdWXnzA3YqKNdBr1wipvCFbYHzvW6pZukgRk8XNwby4Y51akY4EkSF85aWsTjhQThKrAXYCymGPAEDSbyrgyt8R3xpuBBXfvw2RXAFoj7tiXbetvMUx8wYc21aBdyTkZ3XMZc6rg4gRBS55uN9W3U85YjrKVQnSNR5MzuVinqBoHTh5CgC5AMCpVdH9LwWGoHzWMWacu5qrKJow95oG5Qs76sznCDdsmDJRtUCmAW7dGHevj33xXdHicdU4PxWTkenursT7Hk8NHwVwNMeUpnNqnty2RTZdys58U5HJ9ftsVhuuJ9PugUxSZTXQNbwEG7DhtK1oBynpvncp6fzkgf5BXjiXjr9FnDYsrbCZb6DtK9ydVFCSyWCkWBZrPdPsAjSadqeKKv2tgsy8g77NNTCwSuugosRWk3eCcnvPuEMiTE1S1wGS9F9Vib9isipMUngJkLNSGBMUGV1RkNVn6h3EKNqxAqtDpjPSd341vDsBsnbdqK3KnLszbJkSxJyw7Ktm5VFsbqtJcXf7wmchgMrq1MUYs7SiJwaGHmtcjr8o9b", + | "assets": [ + | { + | "tokenId": "003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0", + | "index": 0, + | "amount": 40, + | "name": "SigRSV", + | "decimals": 0, + | "type": "EIP-004" + | } + | ], + | "additionalRegisters": {}, + | "spentTransactionId": "90c4848da58aa3b8ac6527fef8deb9844b66ea41ed42f85a3f5aa96493a0c61e", + | "mainChain": true + |} + |""".stripMargin + ) + .toOption + .get + + def depositV3 = + io.circe.parser + .decode[Output]( + s""" + |{ + | "boxId": "374f037640bf0dbf6e34244871937e75aaf110de9254ffcf09aa06291c11d375", + | "transactionId": "3da3aae612feee5ecf8d0af01e1205bdf25c01b09e5774517c8abd9387f81c03", + | "blockId": "258667b9c516bc0610017e92b3690c137d7aec673f6b6ad98d2c90fd2c31d780", + | "value": 12323164, + | "index": 0, + | "globalIndex": 26670124, + | "creationHeight": 942994, + | "settlementHeight": 942996, + | "ergoTree": "19c30417040005f8fbd80b08cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec040404060402040205feffffffffffffffff0104040504040004000e209916d75132593c8b07fe18bd8d583bda1652eed7565cf41a4738ddd90fc992ec0e240008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec0404040205c0b80201000e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a5730405000500058092f4010100d802d601b2a4730000d6027301eb027302d195ed92b1a4730393b1db630872017304d80bd603db63087201d604b2a5730500d605b27203730600d6067e9973078c72050206d6077ec1720106d6089d9c7e72020672067207d609b27203730800d60a7e8c72090206d60b9d9c7e7309067206720ad60cdb63087204d60db2720c730a00ededededed938cb27203730b0001730c93c27204730d95ed8f7208720b93b1720c730ed801d60eb2720c730f00eded92c1720499c1a77310938c720e018c720901927e8c720e02069d9c99720b7208720a720695927208720b927ec1720406997ec1a706997e7202069d9c997208720b720772067311938c720d018c720501927e8c720d0206a17208720b90b0ada5d9010e639593c2720e7312c1720e73137314d9010e599a8c720e018c720e0273157316", + | "address": "AVwRjyvectUWbmNGFBDonnfZue2PmZwhZtn3jBssdMdzBvnRw6moHaPGRAkesgkmfgs4cWU11Jxg1NQzNYzFE2VNwf4kVfxZfo7d2xD4YKEfP2oMMNnPyWVhTRt5vbCE4gEpbbcCDhY68dmx4Bx9bTd8eQQxJG2NzmSpuzU4qzVhrrqwg3Mq1sbc6zdADqzs2k8GQQt38GxG5QQLhJSnZEAB8TvEiMZGEKqPMATqY9HETxr13959bHLTyn2SvDdqDVKF7ZgpjEWmiqoikTaSLTGwfMopBXrE3bWTY26tFaL698zuPZ9zK6Ruz6kK3B2MCbFxemzxoiaoYnLfAjyjzczajp2ZP5eTasE76Ly2GtnAYnpfFa4VUAaSnCY2CQ2doSGwNCGd3DZE6e1btpewLUV33ZWAYj5NhxnrnXGPg1V9gz31HfbPiDMGdQcip41R99GmJLXfY3CoxLkJLPVYBXTvaRyNZHziE6JFHeMn41yTgKDBy9zoQLXsEn9v8vRJX9N44Ftc3wMR7hcfxgE2AdaH591ZFjEyV6FzKD6g53mGxfDNBaiqcLmvoE1Q26XyYzoac5weU9T39BbJaeETbZiJ81WH5CrcQmfZyYkRzypRKCD7cHnJ4MXQojo7Ff5gF5EP3Ta15eoFgcUDLKWCQTxirz5nBA3tqsZQuSD2A4FkQG1UZ3d27JJM7vjy5k1NJ6w3Ty4YaTUX2e2zm16sDN6L58oTSp21aeFcc952vX6FtbdYWUr68ifBux9ZCoS2sb7ZpacNz3NQvr8YnWY51QGPATZG4qagz", + | "assets": [ + | { + | "tokenId": "03faf2cb329f2e90d6d23b58d91bbb6c046aa143261cc21f52fbe2824bfcbf04", + | "index": 0, + | "amount": 2, + | "name": "SigUSD", + | "decimals": 2, + | "type": "EIP-004" + | }, + | { + | "tokenId": "003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0", + | "index": 1, + | "amount": 15, + | "name": "SigRSV", + | "decimals": 0, + | "type": "EIP-004" + | } + | ], + | "additionalRegisters": {}, + | "spentTransactionId": "e3023afd988ec2e10a4355edf346f7a6cfbd8d7ed9bd99c51702190b58163caa", + | "mainChain": true + |} + |""".stripMargin + ) + .toOption + .get + + def redeemV3 = + io.circe.parser + .decode[Output]( + s""" + |{ + | "boxId": "29caadfbee140a978bd6200d807292160eb69b67a605fe597e887d8aae870faf", + | "transactionId": "dfdd1038cd12ad5615f0dc915de1f522359e45ab0fb1e54e6ee3f2a30ade7d24", + | "blockId": "7103bfc791be82d4b392292d23dc4b9808dd857ff728d019640c86490df41dc2", + | "value": 310000, + | "index": 0, + | "globalIndex": 26672272, + | "creationHeight": 943026, + | "settlementHeight": 943028, + | "ergoTree": "19ca0312040008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec04040406040204000404040005feffffffffffffffff01040204000e209916d75132593c8b07fe18bd8d583bda1652eed7565cf41a4738ddd90fc992ec0e240008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec0e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a5730405000500058092f4010100d801d601b2a4730000eb027301d195ed92b1a4730293b1db630872017303d806d602db63087201d603b2a5730400d604b2db63087203730500d605b27202730600d6067e8cb2db6308a77307000206d6077e9973088cb272027309000206ededededed938cb27202730a0001730b93c27203730c938c7204018c720501927e99c17203c1a7069d9c72067ec17201067207927e8c720402069d9c72067e8c72050206720790b0ada5d90108639593c27208730dc17208730e730fd90108599a8c7208018c72080273107311", + | "address": "4X2w8UZHL14TJyPYMRq5ABA4gUfetNKCQbgStifCP1zk8qmjxapNrQshQJ7ojuJwY3mRwBcxjYEBNNdnxPXkzExRnYi4bJJ86gD7eMEotAh2TnwGZKQ9wC8Gf3Faat6K2x8Lq4xJWZ1AYcJkXPDbEv5JTbtTRcudjfYkybdWMgbsB3Yz8dW3Yquju6L7t1y18qQZBjo4BcYuAsHUGmWpAvMYV31ywJsfuEMuxtMB8qE7PjW8mGqA1oQmxLc3wWC5yd17P7VArXGNoMdESKSs8EXA8wHBag2iR7Uy3wbQkTt3mXzi1KNm7PJgCw2QbGxvhgcwNQGtS8qJV4RkEabqKprfMozv2gECbv3371HrEhNVZHd4enejj3LphZfrTBSyNPWpAoGmxsLNVN7cakeCUApVhdebazfR8TAKXR6922QRojn7JGEuQauhARASTSqYsHTZZj11romGqRH5DY4YMZrcZvKJPB3fubyHwhCicirfYX745k2ZgjfsRCuQPiXrEaEziLRnpkbT8BDHoEGPX8to5kNH5TAhG1C2n24PeXhmUL9s3mrZ5XSvhY1WD6BXHXmHof6SouSnFiTMEWmtJ2FZeoizYVxS3McgPznyfioZCK1EYqZ7Tq3endLC", + | "assets": [ + | { + | "tokenId": "303f39026572bcb4060b51fafc93787a236bb243744babaa99fceb833d61e198", + | "index": 0, + | "amount": 11439, + | "name": null, + | "decimals": null, + | "type": null + | }, + | { + | "tokenId": "003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0", + | "index": 1, + | "amount": 15, + | "name": "SigRSV", + | "decimals": 0, + | "type": "EIP-004" + | } + | ], + | "additionalRegisters": {}, + | "spentTransactionId": "bcd6c04442b8b5161d0a69c0ac6fe2ed20f812a135984824e6ca83e9b58aade1", + | "mainChain": true + |} + |""".stripMargin + ) + .toOption + .get + +} diff --git a/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TV3ParserSpec2.scala b/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TV3ParserSpec2.scala new file mode 100644 index 00000000..2d5d13ff --- /dev/null +++ b/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/N2TV3ParserSpec2.scala @@ -0,0 +1,36 @@ +package org.ergoplatform.dex.tracker.parsers.amm + +import cats.effect.{Clock, SyncIO} +import org.ergoplatform.ErgoAddressEncoder +import org.ergoplatform.dex.CatsPlatform +import org.ergoplatform.dex.configs.MonetaryConfig +import org.ergoplatform.dex.protocol.amm.{AMMType, ParserVersion} +import org.ergoplatform.dex.tracker.parsers.amm.v3.N2TOrdersV3Parser +import org.ergoplatform.dex.tracker.validation.amm.CfmmRuleDefs +import org.ergoplatform.ergo.TokenId +import org.ergoplatform.ergo.domain.Output +import org.scalatest.matchers.should +import org.scalatest.propspec.AnyPropSpec +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks + +class N2TV3ParserSpec2 extends AnyPropSpec with should.Matchers with ScalaCheckPropertyChecks with CatsPlatform { + + implicit val clock: Clock[SyncIO] = Clock.create + + implicit val e: ErgoAddressEncoder = new ErgoAddressEncoder(ErgoAddressEncoder.MainnetNetworkPrefix) + + val spf = TokenId.fromStringUnsafe("003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0") + + property("Validate v3 n2t swap correct") { + def parser: CFMMOrdersParser[AMMType.N2T_CFMM, ParserVersion.V3, SyncIO] = + N2TOrdersV3Parser.make[SyncIO](spf) + + val res = parser.swap(Output.fromExplorer(V3Orders.deployV3SwapSell)).unsafeRunSync().get + + val rules = new CfmmRuleDefs[SyncIO](MonetaryConfig(10000000, 600, 60000, 10), spf) + + val res2 = rules.rules(res).unsafeRunSync() + + res2 shouldEqual None + } +} diff --git a/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/OrdersJsonCodecsSpecs.scala b/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/OrdersJsonCodecsSpecs.scala new file mode 100644 index 00000000..647c08e9 --- /dev/null +++ b/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/OrdersJsonCodecsSpecs.scala @@ -0,0 +1,259 @@ +package org.ergoplatform.dex.tracker.parsers.amm + +import cats.effect.{Clock, SyncIO} +import io.circe.syntax._ +import org.ergoplatform.ErgoAddressEncoder +import org.ergoplatform.dex.CatsPlatform +import org.ergoplatform.dex.domain.amm.CFMMOrder._ +import org.ergoplatform.dex.domain.amm._ +import org.ergoplatform.dex.protocol.amm.{AMMType, ParserVersion} +import org.ergoplatform.dex.tracker.parsers.amm.v2.{N2TOrdersV2Parser, T2TOrdersV2Parser} +import org.ergoplatform.dex.tracker.parsers.amm.v3.{N2TOrdersV3Parser, T2TOrdersV3Parser} +import org.ergoplatform.ergo.TokenId +import org.ergoplatform.ergo.domain.Output +import org.scalatest.matchers.should +import org.scalatest.propspec.AnyPropSpec +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks + +class OrdersJsonCodecsSpecs extends AnyPropSpec with should.Matchers with ScalaCheckPropertyChecks with CatsPlatform { + + implicit val clock: Clock[SyncIO] = Clock.create + + implicit val e: ErgoAddressEncoder = new ErgoAddressEncoder(ErgoAddressEncoder.MainnetNetworkPrefix) + + def parser(tid: TokenId): CFMMOrdersParser[AMMType.T2T_CFMM, ParserVersion.V3, SyncIO] = + T2TOrdersV3Parser.make[SyncIO](tid) + + def parserN2TOrdersV3Parser: CFMMOrdersParser[AMMType.N2T_CFMM, ParserVersion.V3, SyncIO] = + N2TOrdersV3Parser.make[SyncIO](TokenId.fromStringUnsafe("")) + + def parserN2TOrdersV2Parser: CFMMOrdersParser[AMMType.N2T_CFMM, ParserVersion.V2, SyncIO] = + N2TOrdersV2Parser.make[SyncIO] + + def parserT2TOrdersV2Parser: CFMMOrdersParser[AMMType.T2T_CFMM, ParserVersion.V2, SyncIO] = + T2TOrdersV2Parser.make[SyncIO] + + property("Encode decode correctly") { + val depositYIsSpectrum = parser(TokenId.fromStringUnsafe("003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0")).deposit(depositV3).unsafeRunSync().get + + val swap = parser(TokenId.fromStringUnsafe("003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0")).swap(swapV3).unsafeRunSync().get + + val swapBuyErgFeeV2 = parserN2TOrdersV2Parser.swap(swapBuyErgFee).unsafeRunSync().get + + val swapSellTokenV3 = parserN2TOrdersV3Parser.swap(swapSellTokenFee).unsafeRunSync().get + + val resList: List[CFMMOrder.AnyOrder] = + List(depositYIsSpectrum, swap, swapSellTokenV3, swapBuyErgFeeV2) + + val json = resList.map(_.asJson) + + val decoded = json.map(_.as[CFMMOrder.AnyOrder]) + + resList.zip(decoded).foreach { case (order, result) => + order shouldEqual result.toOption.get + } + + } + + def swapSellTokenFee = + io.circe.parser + .decode[Output]( + s""" + |{ + | "boxId": "a3673cf919314fc4ac42929a076ba8c2cbda0193878b2fc739a7fe557e52e70c", + | "transactionId": "14556ddac7ba7e8a4fd13b691c2eaf3836a265c9d11a1ba20c5496f50b43a510", + | "blockId": "9a9af9e7041985dc09309829bf955a5fe6b63bfaaf0acc9b11474d12d174a500", + | "value": 10310000, + | "index": 0, + | "globalIndex": 26613367, + | "creationHeight": 941734, + | "settlementHeight": 941736, + | "ergoTree": "19800521040005c80105320580dac40904c60f08cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec04040406040204000101052404000e201d5afc59838920bb5ef2a8f9d63825a55b1d48e269d7cecee335d637c3ff5f3f0e240008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec0e20003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d005280101010105f015060100040404020e20003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d00101040406010104d00f0e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a5730405000500058092f4010100d804d601b2a4730000d6027301d6037302d6049c73037e730405eb027305d195ed92b1a4730693b1db630872017307d806d605db63087201d606b2a5730800d607db63087206d608b27207730900d6098c720802d60a95730a9d9c7e997209730b067e7202067e7203067e720906edededededed938cb27205730c0001730d93c27206730e938c720801730f92720a7e7310069573117312d801d60b997e7313069d9c720a7e7203067e72020695ed91720b731492b172077315d801d60cb27207731600ed938c720c017317927e8c720c0206720b7318909c7e8cb2720573190002067e7204069c9a720a731a9a9c7ec17201067e731b067e72040690b0ada5d9010b639593c2720b731cc1720b731d731ed9010b599a8c720b018c720b02731f7320", + | "address": "ZnQPhHYW6FNzr1CpzGutiwrC1RCaoZxrhwTt7NjasvE5rMC1YQptuMqdApAnt8rNqqGV38j9E6W7dLRrBUqzzGZFh5KUhAbowhJjoiaH5EovkEhNWVfGQnw5LL9cdsYBHBk7aopYFKgcNTYqcRCPi1jEs5PAMrZzLqxFQf9zFJUmH4kmRzhEnfp4TzC1csg247kMqEp2piYFXDLQcDxPpw1KTLykzERp3rH8RWgL9STCM7FCXUTwBL2yFo9tfbM7ZfH9ngNgJ6yaEQFS52cyEDTiWQpbSqCD9vy9Yqtth93xY57etuq65EJu4vcC7TfFrjjnxgAzGR41xjhZaXSkGNcSDE2Zn9LZm9kpE8whDhsgKmA1LonFRfvvusa4KJbFAHLK779MeATJEBEHSYFAVapwLKEPvaYJZ9TDhoaCfjG8Afj4Tp1RpWu3S7C3tJRsjsR9rrejqsDSH1XaxcjG4VJeAm9BDX4SiNnNm7pUTS7LQP6YTET49k2XHrzv8y5yavPJqFSEVD4X7K8QHiRWex7Axiqc9fduQLQ4qmmGDzErdNUnasEUoyNGzPFuTjq3uvroXkuBoGC1vzMmNfQhuV89gNFweDaEULH124Z9fzkJ39KxCXqzeAqhE8y84YFzb6hM1t3FqYM5cZdC4Vos5bWrkLu8qVFoYuVWT1EC4dLdMbRP1pZDSDQvv1a54xXvhWxiTPFhBTSnx3VphpiAxeoqsQRXc14L7n79WUc935fe4mHktk6zXasKewQmaaT4MnunDruqnDBVBoBBGWhxJc3RLSkJTv73qBHyYbBMgz6zzxtfbEzpi7RvM8asREfaTwBWZ4nqxoXVYCQgP7cpaoAw7ZCWKiZeHghujNpZs9ZAnUUtNrAy", + | "assets": [ + | { + | "tokenId": "003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0", + | "index": 0, + | "amount": 18, + | "name": "SigRSV", + | "decimals": 0, + | "type": "EIP-004" + | } + | ], + | "additionalRegisters": {}, + | "spentTransactionId": "e05ca4f19a066e24ca53a17d9511e7395c9ca16be37296fb41d778bc26310c34", + | "mainChain": true + |} + |""".stripMargin + ) + .toOption + .get + + def swapBuyErgFee = + io.circe.parser + .decode[Output]( + s""" + |{ + | "boxId": "1df6f07bf4334812ee8376c035e62795567fd9904195a972c25425d15321aab7", + | "transactionId": "e611dec1c41f5efe281092bd72f63cfe0c5c0cd7639fe78d832807dca923c559", + | "blockId": "0b36bf5e78c677446726af56a65a6bf3533dee2ff6b795de407cb4f71312e0e8", + | "value": 51650000, + | "index": 0, + | "globalIndex": 23349051, + | "creationHeight": 508928, + | "settlementHeight": 868982, + | "ergoTree": "101604000100040404060402050a05f5d9c409040004000e204ad32364b11b0fc1cc66e6528b5ad8cbc31f2f63793357316bf15c8ef283ad4c0eb40319b1031208cd033d6ab05cfb8a65938e116cb863cad577e560bb8e110113bf395fbe98649dbb59040004040406040204000404040005feffffffffffffffff01040204000e209916d75132593c8b07fe18bd8d583bda1652eed7565cf41a4738ddd90fc992ec0580b6dc050e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a5730405000500058092f4010100d802d6017300d602b2a4730100eb027201d195ed93b1a4730293b1db630872027303d806d603db63087202d604b2a5730400d605b2db63087204730500d606b27203730600d6077e8cb2db6308a77307000206d6087e9973088cb272037309000206ededededed938cb27203730a0001730b93c27204d07201938c7205018c720601927e9a99c17204c1a7730c069d9c72077ec17202067208927e8c720502069d9c72077e8c72060206720890b0ada5d90109639593c27209730dc17209730e730fd90109599a8c7209018c72090273107311050604c80f060101040404d00f04c80f0e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304050005000580dac4090100d801d601b2a4730000d1ec730195ed93b1a4730293b1db630872017303d804d602db63087201d603b2a5730400d6049d9c7e99c17203c1a7067e7305067e730606d6058cb2db6308a773070002edededed938cb2720273080001730993c27203730a9272047e730b06909c9c7ec17201067e7205067e730c069c9a7204730d9a9c7e8cb27202730e0002067e730f067e9c72057e7310050690b0ada5d90106639593c272067311c1720673127313d90106599a8c7206018c72060273147315", + | "address": "o9iYvHqCdt9nYCKYgs2md7cQUgfVHYtYz5rt5CdWcJ322bc4LEGCwfUbb3yEANBM9yGK7qriwCkuFEeseLJABcncPaXkeZXKfQ9PfjnK8nCFKWgqrVd3ob8Cc1bhKK2eUzC5Rzpd9xxABcDuuBrcxNNCocT9qBwHh1FtuDr3iFQowbPuAG5xyNowfzrkTCm2nJ3HvhdYgDQTsJnkfSrAoUuAvvYKH4AqfnURYPpXxWK2ENzxoVtQB8GVCU6K4xGLftzGBMWisuucuMdhFuHA6rMgzjyj1CdNwYrN2sLjFoNB78QptXBZTQowncLGQd3MieYd5LN1VdFDvfAHfShTeufTntNAQJZsczaExKVcnraVqqbqgegar7yxqYajFzaBgumcGKHBqArVscDodUDRxJWkBnQP6zqX9Z9zztX4THysfiKBMdWTKCRTwAAVAmupZnaUVfELmcQnMtfk52fDWht66sHodRsbuaRVLiGW6g8dbbwagxqBYy9L98uKMqHaA1ZyYpFuYKza7HvUGZrFWQjVmsacPgk34PjJhWhDYAJjE4pGyZ47dFmvGFrS781yXiryaWceDXKQoSSpPkXuyBFN1Zc7REtjHD55SZVKL7H1jpP8Ex7sFYhjbc879J9BQz1EZiaV9gNHbxjyf3mhUaxZdBTs2syEW3V2PUptviUp6SbxWkDnusk9tksTUbYbWHEHtiJc9Bqyu1ijFvh4ooicFuHdyQJi3njpqSe6rBznZiGF2ePyAdB52NMRAZ7MdoTuVvWrTDGvF4DATnjn4vV7v1ngyYHyNarXo48NytN9pET1sbdnfhvtfc2hKZzWLPdGyyvSAYykcDXG6C3CR3fEL9wYfHXbQNKgtTGiehs1ByXhsQHJg6PJsTrV6hAHquiruuC9y86PcMsKydrveRTbN5WZK9Vi22tEiN5GeF2Y4j3zx8ZRAdJ6gnqXUraEX8Ds1Zdp4QSptVAbhVmx6TmD9rApSwk26f5s4TJj2jZvDRKZd4p1u36cq5fVAsqZrhwVv8bRkuu8QKEqt2ADyDLotmerovhE544nJ8U6nMMh3WxEFvCbh9ANgYVyeExLWGPZ9i9MSmpW2fmniqpaXHJevrwFd3B94A2PEQF", + | "assets": [ + | { + | "tokenId": "30974274078845f263b4f21787e33cc99e9ec19a17ad85a5bc6da2cca91c5a2e", + | "index": 0, + | "amount": 10000000, + | "name": "WT_ADA", + | "decimals": 8, + | "type": "EIP-004" + | } + | ], + | "additionalRegisters": {}, + | "spentTransactionId": "8dde27e825c347334d30d6be7078df736c450e015cc243156878056f709d62e6", + | "mainChain": true + |} + |""".stripMargin + ) + .toOption + .get + + def redeemV3 = + io.circe.parser + .decode[Output]( + s""" + |{ + | "boxId": "e8224097dbf46a6f9e90d059917bba2b5ea6714ac895d47a35d15346988a7de7", + | "transactionId": "fc8266f5c7893f75f28575818bdc0803f195532609b9f404e629ae35d6348e51", + | "blockId": "0fe442d7620bbd1d64ffb77e262de74647617e45cc7bb05db15213c195dbd982", + | "value": 310000, + | "index": 0, + | "globalIndex": 26671828, + | "creationHeight": 943019, + | "settlementHeight": 943021, + | "ergoTree": "19eb0314040008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec0404040804020400040404020406040005feffffffffffffffff01040204000e205703a5b955c4902f165215f5ce1426816b4c6dca5300cfae35c8a356492871540e240008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec0e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a5730405000500058092f4010100d801d601b2a4730000eb027301d195ed92b1a4730293b1db630872017303d809d602db63087201d603b2a5730400d604db63087203d605b27204730500d606b27202730600d607b27204730700d608b27202730800d6097e8cb2db6308a77309000206d60a7e99730a8cb27202730b000206edededededed938cb27202730c0001730d93c27203730e938c7205018c720601938c7207018c720801927e8c720502069d9c72097e8c72060206720a927e8c720702069d9c72097e8c72080206720a90b0ada5d9010b639593c2720b730fc1720b73107311d9010b599a8c720b018c720b0273127313", + | "address": "5cZpjmfCNTqef1W69zmP1CqswQeB1eBQNEKdvvbf9Ei8Y7kSCRmUvSJmtbuRDSSZxSWTt7w5PXXFrcuZEBxHYuyFqcU483BcmVqHdY3ExjvmpuHYuuMF912TNji6SZZQehiXT1L3gXZeLVZ3zvC3KwVw1fZXrpLfGeutmR358KzMcnWVH4T2LPf8fTk6WsP7y4ygjHpzYqbo3oyxAnyFfRkgQNZ9aUtYVrdkVTpecsQ2p4pB5jWsqnv3kJeMsNhDKxYATLqDwquWoRUw9nExRF8HsXqMdC8WRvbwyxgYppnaFqLaSi41k88s7RUrUjEdGicHiSBSoVrxtrB3ozLWFZNoJjPw169xUB1Gpb64EZkKVqLcnMRRdAsWG2F8GgU3Mi3euv2mBjpEBo7cNu9cTkaniarSksnBi7NTB1MuerTZkuKD6vVEr3HhTkhU97BqP9VUHiUqwmRJuVGyd9JkD3ZsNfR734mohFhgkt1vwKEh57goQvFx2tpAiZZGYbMQoZ6D4hQ5cLieGFMRx633QG2tX7ErswQ74mgD8hTJbu3L64vGnh7YewVaBZMgFX55ABYscEnbsqFgEQCRteHogcd1hbdjq53YBS8Q9yZvnPgofNeKKuQyt2XmpC6H1BLhZWtxxpT1k6dfqsBgiK5ZSzS7Z45UJGgziQTiez2Aq", + | "assets": [ + | { + | "tokenId": "7338937c785551c3db0d6db218d8b7df86960d9a5720c2c97c968c21cf5c54f2", + | "index": 0, + | "amount": 716, + | "name": "NETA_ergopad_LP", + | "decimals": 0, + | "type": "EIP-004" + | }, + | { + | "tokenId": "003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0", + | "index": 1, + | "amount": 15, + | "name": "SigRSV", + | "decimals": 0, + | "type": "EIP-004" + | } + | ], + | "additionalRegisters": {}, + | "spentTransactionId": "186d63efe0afa5e89181c1da28681bc4c8d8c68d5c03bc4cf75cf9e82401bdd4", + | "mainChain": true + |} + |""".stripMargin + ) + .toOption + .get + + def depositV3 = + io.circe.parser + .decode[Output]( + s""" + |{ + | "boxId": "9efb46584856e7295cee7aacd41da2c3c7dc4b9aafb5ea5c5b4a983303a835c9", + | "transactionId": "dc3aa8a43254ee984cf61b9a30026b3ed7abf9ea1866f8d170c192b3ae21b118", + | "blockId": "47c3859bc1e2646bd73c82258e9ad9919d090a1d4a9b27e21ef22978d40d0eb2", + | "value": 310000, + | "index": 0, + | "globalIndex": 26669245, + | "creationHeight": 942973, + | "settlementHeight": 942975, + | "ergoTree": "19e2041a040008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec040404080402040205feffffffffffffffff0104040580897a04060550040004000e205703a5b955c4902f165215f5ce1426816b4c6dca5300cfae35c8a356492871540e240008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec0404040204040402010101000e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a5730405000500058092f4010100d801d601b2a4730000eb027301d195ed92b1a4730293b1db630872017303d80cd602db63087201d603b2a5730400d604b27202730500d6057e9973068c72040206d606b27202730700d6077e8c72060206d6089d9c7e73080672057207d609b27202730900d60a7e8c72090206d60b9d9c7e730a067205720ad60cdb63087203d60db2720c730b00edededededed938cb27202730c0001730d93c27203730e92c17203c1a795ed8f7208720b93b1720c730fd801d60eb2720c731000ed938c720e018c720901927e8c720e02069d9c99720b7208720a720595ed917208720b93b1720c7311d801d60eb2720c731200ed938c720e018c720601927e8c720e02069d9c997208720b7207720595937208720b73137314938c720d018c720401927e8c720d0206a17208720b90b0ada5d9010e639593c2720e7315c1720e73167317d9010e599a8c720e018c720e0273187319", + | "address": "e5xiryCNuYas2qLeihB1qHYZ2mM21u1wMEKuFD8xJ8Ldu7eFoUH3Hroh39dKRxjZpzu2C5GxdtHinr7miafrQApdwsV81633E3Lh3tZq2tA6nFMsVHkr2Jtbj3PM8MczxuHvhbDNFFU6sSFp6X35pdjkJUknirzeVnG2y6P7UuJ93KT6a6pARrKc21eCgUAYcpc5hFG6Y4zCRDUCRsCE7yBvvkuSpfDXnfULwmPyZTE8ey7R81yuNBkdxeYJ7gwGeE3nFcbCdBEZ1EJTXCiXYNYjYrM9P6uhhQXPL5ueJdPwtBoA8aM6ELJhopnZeNPQ6NiUxStPqAhBFcCwSYYdQp22HPS8CV5RkiTrYvW6egvG7Y6ouu7kwTcSaWZXat9KStcs4c4PHm68BvazFy1Jsu9XH5mGnryy2Riivr9wcHQxa8r4YD2EjLshZGpZomGhZgkymwvJL5FbikLSevH1ok7cMS2BYBKPVbZFbhSAwDpfxQimff32BrnBRmki8prdSxojjApx3YoADmCBKLcpEaqW1eZGcEHHWrGN2d8e6zUfkHsfkcyobUxMUx1DsM5y6tM9PHgiUHYQ46JiMxb1LK91cuPjfMu7sa9agoLxF6tNKNL4uHWzZB4J9GsygxgpaiY5k9grqBMCfQJ9AdKdwfKrZiAE4PPXHX1P6ri93LjfVpwFBkYxEDz9vZPeieDoaqWQquhXvMGmDfYuyQ1u3xw7ya1aS3xSsRt4QHR4t3aurL7dFRMzAV4VUD3nJza58e7U4xFECQJEJuu6Wc46mhQG2oDXmS3tEvFH5jZc83BzXhh2VVEBEWLB7mFtYKN33KGFDs7Kk9Y", + | "assets": [ + | { + | "tokenId": "472c3d4ecaa08fb7392ff041ee2e6af75f4a558810a74b28600549d5392810e8", + | "index": 0, + | "amount": 1000000, + | "name": "NETA", + | "decimals": 6, + | "type": "EIP-004" + | }, + | { + | "tokenId": "d71693c49a84fbbecd4908c94813b46514b18b67a99952dc1e6e4791556de413", + | "index": 1, + | "amount": 40, + | "name": "ergopad", + | "decimals": 2, + | "type": "EIP-004" + | }, + | { + | "tokenId": "003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0", + | "index": 2, + | "amount": 15, + | "name": "SigRSV", + | "decimals": 0, + | "type": "EIP-004" + | } + | ], + | "additionalRegisters": {}, + | "spentTransactionId": "867843e396896a54c4cc3754fda591dd3d0a1880d2100876550f28d7f44581c3", + | "mainChain": true + |} + |""".stripMargin + ) + .toOption + .get + + def swapV3 = + io.circe.parser + .decode[Output]( + s""" + |{ + | "boxId": "436b80973fe567fba7e6179272835071ac92bfb20c54011d114b8e076506e251", + | "transactionId": "41ad07b62938750772f1331be0f55d53ab51fe2a7b9480db6fa3376da52a43a3", + | "blockId": "6fc118469010e5c1bc2a88c5307f0f3d8a54de7f37e11f070dd161364f7c0efa", + | "value": 310000, + | "index": 0, + | "globalIndex": 26637735, + | "creationHeight": 942255, + | "settlementHeight": 942257, + | "ergoTree": "19cc052304000e20003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d005808088fccdbcc323050404c60f04d00f08cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec040404080402040001010524059eb1839297f7830f0404040606010104000e20080e453271ff4d2f85f97569b09755e67537bf2a1bc1cd09411b459cf901b9020e240008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec05340100010105f015059c01060100040404020e20003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d001010e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a5730405000500058092f4010100d805d601b2a4730000d6027301d6037302d6049c73037e730405d6057305eb027306d195ed92b1a4730793b1db630872017308d80ad606db63087201d607b2a5730900d608db63087207d609b27208730a00d60a8c720902d60b95730b9d9c7e99720a730c067e7203067e730d067e720a06d60cb27206730e00d60d7e8c720c0206d60e7e8cb27206730f000206d60f9a720b7310ededededededed938cb2720673110001731293c272077313938c720901720292720a731492c17207c1a79573157316d801d610997e7317069d9c720b7e7318067e72030695ed917210731992b17208731ad801d611b27208731b00ed938c721101731c927e8c721102067210731d95938c720c017202909c720d7e7204069c720f9a9c720e7e7205067e720406909c720e7e7204069c720f9a9c720d7e7205067e72040690b0ada5d90110639593c27210731ec17210731f7320d90110599a8c7210018c72100273217322", + | "address": "EythtiXp53PKzShomQcugMHAUeCJrX1LGoSowRAbwH7iVzGwVG4p7ougpD9P4QpshLVhdoyjQ4FaHaPHn3p3fFGfLh1LnwzswFYoHZD7RUvv9A9MSTbmNuipZy6e6S9P1gptkyevtAYPgx6BddwyqePwjWhjT9ew9ZbmPN1fsof8ZJHVWVEv5GUJsBtyXrZhUs3abhJHuFvUpvExWEgSRBSEqn3E92xMMTPs81gDjAcDEdmWAJypNnGSp7q6PTVkDVq3YQPDbQn9DknKTmUs9frRCtUwNjWRZQ2WYBE8qxEbttM6cKujnv2BJjgDrxwRGsXnr6WG6dp91ZXgLU3aE5zMSsgUUHw5NMXQQBHrRs1pdqY5jBh3LjcoBJ1QLf6udA4ZjnmeRpy5vtJA2ZTFLgbKPhBmGRhDNTz7LTdMACyR2CKTv9Df7AZmNko1FEff7nMBR7R8s39TpVFZP74iYsFtFMhL9FfFneY3Np3JEu3pwpe43yiso4E5enb5M9fNXMctkJ7aW6edsBKZnPyVcwMgUTS5DPm6g9LJhXBt8ouwbyShE6T2TSpBSwo6gX9AvjMMVXYJ4Z317JGJNXE1u8U5DVWHLAB4WGWrUa7gjBqmrHeBu6PoCyPYcHCFHYaBcHUoV4sX64cbSfML8UZnQ4sd3YWGuGS9YjT74WXsgJYnaMY8ummGMaLDax5EqgAEQ1RzhEinC5j7MmoPdqg2kMLHjvYmfykcjJohaP7adzELnK2TxATj39mw9mpdADGMDpq5BFHFoAdeNPg24K8t9ZK2qSvPW4GWZuM5YxaW1qLyCvAuE5s5Zd4zFvQxVQesGvLNXkcSWVwCp34sqf4XbjsVPqwmMa77KXz5rnnVjwB4u8JwBe3LLqua5pTpnPkSwTtuJyGBDFHtwwCv7cRaHRkZZZ45Fo1ZhWuuep9cgpijyTEWSW6Qqa7NPejgQnwmd9ts1XxrsFHS9KurxWvF1s2iPAAJ", + | "assets": [ + | { + | "tokenId": "03faf2cb329f2e90d6d23b58d91bbb6c046aa143261cc21f52fbe2824bfcbf04", + | "index": 0, + | "amount": 2, + | "name": "SigUSD", + | "decimals": 2, + | "type": "EIP-004" + | }, + | { + | "tokenId": "003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0", + | "index": 1, + | "amount": 18, + | "name": "SigRSV", + | "decimals": 0, + | "type": "EIP-004" + | } + | ], + | "additionalRegisters": {}, + | "spentTransactionId": "8fe0518b8c0ed7069e8f0040ac8d97da869fff41b12cd97a399584957065fcf8", + | "mainChain": true + |} + |""".stripMargin + ) + .toOption + .get +} diff --git a/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TCFMMOrdersParserP2PkSpec.scala b/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TCFMMOrdersParserP2PkSpec.scala index 6831d202..87f3932f 100644 --- a/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TCFMMOrdersParserP2PkSpec.scala +++ b/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TCFMMOrdersParserP2PkSpec.scala @@ -4,49 +4,57 @@ import cats.effect.IO import org.ergoplatform.ErgoAddressEncoder import org.ergoplatform.dex.CatsPlatform import org.ergoplatform.dex.domain.AssetAmount -import org.ergoplatform.dex.domain.amm.CFMMOrder.Swap +import org.ergoplatform.dex.domain.amm.CFMMOrder.SwapP2Pk import org.ergoplatform.dex.domain.amm.{PoolId, SwapParams} +import org.ergoplatform.dex.tracker.parsers.amm.v1.T2TOrdersV1Parser import org.ergoplatform.ergo.domain.{BoxAsset, Output} import org.ergoplatform.ergo._ import org.scalatest.matchers.should import org.scalatest.propspec.AnyPropSpec import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks -class T2TCFMMOrdersParserP2PkSpec extends AnyPropSpec with should.Matchers with ScalaCheckPropertyChecks with CatsPlatform { +class T2TCFMMOrdersParserP2PkSpec + extends AnyPropSpec + with should.Matchers + with ScalaCheckPropertyChecks + with CatsPlatform { + + implicit val e: ErgoAddressEncoder = new ErgoAddressEncoder(ErgoAddressEncoder.MainnetNetworkPrefix) property("Swap order parsing") { + val address = PubKey.unsafeFromString("025e690b6094c8ca62a61aa59a549c3b48b7631928db8ff32345bead80f9247a5b") val res = parser.swap(boxSample).unsafeRunSync() res shouldBe Some( - Swap( - PoolId.fromStringUnsafe("f1fb942ebd039dc782fd9109acdb60aabea4dc7e75e9c813b6528c62692fc781"), - 0L, - FixedTs, + SwapP2Pk( + PoolId.fromStringUnsafe("bfb483069f2809349a9378e8a746cbdcb44c66f94d0e839fd14158e8829eea68"), + 2000000, + 1628107666776L, SwapParams( AssetAmount( - TokenId.fromStringUnsafe("ef802b475c06189fdbf844153cdc1d449a5ba87cce13d11bb47b5a539f27f12b"), - 1000000000 + TokenId.fromStringUnsafe("03faf2cb329f2e90d6d23b58d91bbb6c046aa143261cc21f52fbe2824bfcbf04"), + 100 ), AssetAmount( - TokenId.fromStringUnsafe("30974274078845f263b4f21787e33cc99e9ec19a17ad85a5bc6da2cca91c5a2e"), - 1901019262 + TokenId.fromStringUnsafe("472c3d4ecaa08fb7392ff041ee2e6af75f4a558810a74b28600549d5392810e8"), + 139670010 ), - dexFeePerTokenNum = 2630167984063278L, - dexFeePerTokenDenom = 1000000000000000000L, - redeemer = ??? + dexFeePerTokenNum = 4295839887174061L, + dexFeePerTokenDenom = 100000000000000000L, + redeemer = address ), Output( - BoxId("e419674609fe037d98d07e9c7074b3ad25f2c4e69a9bf844c389117a332fa87d"), - TxId("0203eb80c9c8ebe09bdc466c779eb687e3f6b6f8f0c176f01a61fc10aca6cdbd"), - 16050000, + BoxId("cd29cc599a8a53d28504f2752fc075286359d6b7f1e82582b7e6cd45468a6b29"), + TxId("52cdef65d1a93e29774904b5457577e7ec351a6fded878f5ee63733020865495"), + 7260000, 0, - 5972866, + 825489, SErgoTree.unsafeFromString( - "19a2031308cd02c3f56e66191a903758f53a4b90d07cef80f93e7a4f17d106098ad0caf189722a04000e2030974274078845f263b4f21787e33cc99e9ec19a17ad85a5bc6da2cca91c5a2e04c80f04d00f040404080402040004040400040606010104000e20f1fb942ebd039dc782fd9109acdb60aabea4dc7e75e9c813b6528c62692fc78105fc81fa940e05dc8c9ec6f687ac09058080a0f6f4acdbe01b0100d805d6017300d602b2a4730100d6037302d6047303d6057304eb027201d195ed93b1a4730593b1db630872027306d80ad606db63087202d607b2a5730700d608b2db63087207730800d6098c720802d60a7e720906d60bb27206730900d60c7e8c720b0206d60d7e8cb2db6308a7730a000206d60e7e8cb27206730b000206d60f9a720a730cededededed938cb27206730d0001730e93c27207d07201938c7208017203927209730f927ec1720706997ec1a7069d9c720a7e7310067e73110695938c720b017203909c9c720c720d7e7204069c720f9a9c720e7e7205069c720d7e720406909c9c720e720d7e7204069c720f9a9c720c7e7205069c720d7e7204067312" + "19bc041708cd025e690b6094c8ca62a61aa59a549c3b48b7631928db8ff32345bead80f9247a5b04000e20472c3d4ecaa08fb7392ff041ee2e6af75f4a558810a74b28600549d5392810e804c80f04d00f040404080402040004040400040606010104000e20bfb483069f2809349a9378e8a746cbdcb44c66f94d0e839fd14158e8829eea6805f4c799850105daa6e5a7e5c2a10f058080d0d88bdea2e3020e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a5730405000500058092f4010100d805d6017300d602b2a4730100d6037302d6047303d6057304eb027201d195ed93b1a4730593b1db630872027306d80ad606db63087202d607b2a5730700d608b2db63087207730800d6098c720802d60a7e720906d60bb27206730900d60c7e8c720b0206d60d7e8cb2db6308a7730a000206d60e7e8cb27206730b000206d60f9a720a730cedededededed938cb27206730d0001730e93c27207d07201938c7208017203927209730f927ec1720706997ec1a7069d9c720a7e7310067e73110695938c720b017203909c9c720c720d7e7204069c720f9a9c720e7e7205069c720d7e720406909c9c720e720d7e7204069c720f9a9c720c7e7205069c720d7e72040690b0ada5d90110639593c272107312c1721073137314d90110599a8c7210018c72100273157316" ), List( BoxAsset( - TokenId.fromStringUnsafe("ef802b475c06189fdbf844153cdc1d449a5ba87cce13d11bb47b5a539f27f12b"), - 1000000000L + TokenId.fromStringUnsafe("03faf2cb329f2e90d6d23b58d91bbb6c046aa143261cc21f52fbe2824bfcbf04"), + 100 ) ), Map() @@ -56,36 +64,35 @@ class T2TCFMMOrdersParserP2PkSpec extends AnyPropSpec with should.Matchers with } - implicit val e: ErgoAddressEncoder = new ErgoAddressEncoder(ErgoAddressEncoder.MainnetNetworkPrefix) - def parser = T2TCFMMOrdersParserP2Pk.make[IO] + def parser = T2TOrdersV1Parser.make[IO] def boxSample = io.circe.parser .decode[Output]( """ |{ - | "boxId": "e419674609fe037d98d07e9c7074b3ad25f2c4e69a9bf844c389117a332fa87d", - | "transactionId": "0203eb80c9c8ebe09bdc466c779eb687e3f6b6f8f0c176f01a61fc10aca6cdbd", - | "blockId": "ba6012b63585a48610ef9355bc0337bfd0c523841a1871a2e841c9b5e388f191", - | "value": 16050000, + | "boxId": "cd29cc599a8a53d28504f2752fc075286359d6b7f1e82582b7e6cd45468a6b29", + | "transactionId": "52cdef65d1a93e29774904b5457577e7ec351a6fded878f5ee63733020865495", + | "blockId": "274b42d256323aa61a1735f55ca15071a789704b9c66d5daba19151f4199e7e3", + | "value": 7260000, | "index": 0, - | "globalIndex": 5972866, - | "creationHeight": 508928, - | "settlementHeight": 547184, - | "ergoTree": "19a2031308cd02c3f56e66191a903758f53a4b90d07cef80f93e7a4f17d106098ad0caf189722a04000e2030974274078845f263b4f21787e33cc99e9ec19a17ad85a5bc6da2cca91c5a2e04c80f04d00f040404080402040004040400040606010104000e20f1fb942ebd039dc782fd9109acdb60aabea4dc7e75e9c813b6528c62692fc78105fc81fa940e05dc8c9ec6f687ac09058080a0f6f4acdbe01b0100d805d6017300d602b2a4730100d6037302d6047303d6057304eb027201d195ed93b1a4730593b1db630872027306d80ad606db63087202d607b2a5730700d608b2db63087207730800d6098c720802d60a7e720906d60bb27206730900d60c7e8c720b0206d60d7e8cb2db6308a7730a000206d60e7e8cb27206730b000206d60f9a720a730cededededed938cb27206730d0001730e93c27207d07201938c7208017203927209730f927ec1720706997ec1a7069d9c720a7e7310067e73110695938c720b017203909c9c720c720d7e7204069c720f9a9c720e7e7205069c720d7e720406909c9c720e720d7e7204069c720f9a9c720c7e7205069c720d7e7204067312", - | "address": "H3AA3N1iexioN5zbQ5SzNBVF5GAobDaCxpeUyVmw97EoZhy9Vp7px22pEvB7h2nWNoHC1sjnSkJBg3CqAWYCNierqqxvB1snntGLWQnXRFzVGRRHochsKNVaTEDXJW6jCWdamUH4ss25A37dQwRZn4DN7D8JMNu7pr7bYkfpWtM1dFWRjxiUoWYQiXCST8W8VmS4ns7AEb28kAmCjXssCQPsnZg7epE9MEcmrGnpYdKM4beHsvSJEEPT4vWybvNyugf2gtUKyyVydKLcFgDmx6wYnvZ3odpLqDR14mGAHdBFZyYwBSdhQsPHa3CptTgYF5DAbcckYAX7qP6RS3HBCEH4RnkFy6n64boJZLYEh85Exgm1xvXbrK4qXfH3WbFEhwnFQniE3pRyRSRkjVtw9Zz8SL1qXpzCqtrhgxG7uXX8MasuPi5pnvfXrgEL9CKp1JXkbspzMF5GjGQu78QVRpFdmvTR8M9xULsXXicM4HJAi2ANzkEuUGUsCEVrnn9hjWBNZhMi9iGt5KXoqYsPyZUJKes7rE165D3u6iHJChfpmFC4cKCEsBHEr5SxT8TfAoykE", + | "globalIndex": 20758110, + | "creationHeight": 825489, + | "settlementHeight": 825491, + | "ergoTree": "19bc041708cd025e690b6094c8ca62a61aa59a549c3b48b7631928db8ff32345bead80f9247a5b04000e20472c3d4ecaa08fb7392ff041ee2e6af75f4a558810a74b28600549d5392810e804c80f04d00f040404080402040004040400040606010104000e20bfb483069f2809349a9378e8a746cbdcb44c66f94d0e839fd14158e8829eea6805f4c799850105daa6e5a7e5c2a10f058080d0d88bdea2e3020e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a5730405000500058092f4010100d805d6017300d602b2a4730100d6037302d6047303d6057304eb027201d195ed93b1a4730593b1db630872027306d80ad606db63087202d607b2a5730700d608b2db63087207730800d6098c720802d60a7e720906d60bb27206730900d60c7e8c720b0206d60d7e8cb2db6308a7730a000206d60e7e8cb27206730b000206d60f9a720a730cedededededed938cb27206730d0001730e93c27207d07201938c7208017203927209730f927ec1720706997ec1a7069d9c720a7e7310067e73110695938c720b017203909c9c720c720d7e7204069c720f9a9c720e7e7205069c720d7e720406909c9c720e720d7e7204069c720f9a9c720c7e7205069c720d7e72040690b0ada5d90110639593c272107312c1721073137314d90110599a8c7210018c72100273157316", + | "address": "ynjTGtEt9xdN4LPAreFLJVwwdzZgP1tG5jqMBtvhF1Rr7bsMjBioWPLamCZBkytNPRuKCvzFq2Ghr76X2wVvzwPEiGUDEyykJDTbrChB8iNS8xARPwijRGZGymCG7E3xMNn7tCX2yZU3qT5a4ngG8RCJrvVjTef2kuU4jKARdXY8xYEJUDLBsUnssoT54ma2N3xzQRi4cAhA5Ap5AbM5YxV63GaBewRuEshjrTJH4dLDx6ApyVNmpVRwaZUuiLHFvdUtqcFsyakNyACaWTiJfPCWygFsBznUnMmfKB84eRjdmdL1jPB8TF4Heus2Hh82KVThdfWxAe1P5TVkovNUgLw7t4DyuTh6Mr9sMxzTPiidDriv1mKhrDGGru6w7uFhJ5fKZFQHskZ9ei2ybz6D9Vmz9tuUtcEWUGn5Jh8hjbYVHe9ckCCT2Sh56bV5DEx4hZ7Egnprt6ADdMU1sLoN4N5youbByQsZBmDxrbRmGEyMQaubBFJfEKGGk8EUjENvXgCiZ14iXhiyWQaoLFP1QzNrAJHeCtYNLq1XTAH1j2ciMw8CChJELXeAAd9nvUheizsg6kUqzsraCscg6FrEntCiC9ZtPkvcD8KEmtzj5bUDh6WNzMkGUuMtWVr3qto1Eu94DQDkzSP6dCZvRHRjribkHeYTQvhyjREEUzpS2a5zsAY1UxBsRJt45pcHFwanfYTg2kAJnhTCm9rE13kgyHcgZGnKpKwCJxEoYLXUiMRZ6icHW1eKCqjnAkT6tQik9fBWXZwkoKLmFVbbvfcT51a", | "assets": [ | { - | "tokenId": "ef802b475c06189fdbf844153cdc1d449a5ba87cce13d11bb47b5a539f27f12b", + | "tokenId": "03faf2cb329f2e90d6d23b58d91bbb6c046aa143261cc21f52fbe2824bfcbf04", | "index": 0, - | "amount": 1000000000, - | "name": "WT_ERG", - | "decimals": 9, + | "amount": 100, + | "name": "SigUSD", + | "decimals": 2, | "type": "EIP-004" | } | ], | "additionalRegisters": {}, - | "spentTransactionId": null, + | "spentTransactionId": "b2799d2dfa796a4e99f92ff48f85cdb80cb513333ab04ceb217e7f68f97190ad", | "mainChain": true |} |""".stripMargin diff --git a/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TMultiAddressSwapParserSpec.scala b/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TMultiAddressSwapParserSpec.scala index e7a19820..fa767239 100644 --- a/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TMultiAddressSwapParserSpec.scala +++ b/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TMultiAddressSwapParserSpec.scala @@ -6,7 +6,8 @@ import org.ergoplatform.dex.domain.AssetAmount import org.ergoplatform.dex.domain.amm.CFMMOrder.SwapMultiAddress import org.ergoplatform.dex.domain.amm.{CFMMOrder, PoolId, SwapParams} import org.ergoplatform.dex.protocol.ErgoTreeSerializer -import org.ergoplatform.dex.protocol.amm.{AMMType, ParserType} +import org.ergoplatform.dex.protocol.amm.{AMMType, ParserVersion} +import org.ergoplatform.dex.tracker.parsers.amm.v2.T2TOrdersV2Parser import org.ergoplatform.ergo._ import org.ergoplatform.ergo.domain.Output import org.ergoplatform.{ErgoAddress, ErgoAddressEncoder} @@ -106,8 +107,8 @@ class T2TMultiAddressSwapParserSpec def p2pk: ErgoAddress = e.fromString("9gCigPc9cZNRhKgbgdmTkVxo1ZKgw79G8DvLjCcYWAvEF3XRUKy").get - def parser: CFMMOrdersParser[AMMType.T2T_CFMM, ParserType.MultiAddress, SyncIO] = - T2TCFMMOrdersParserMultiAddress.make[SyncIO] + def parser: CFMMOrdersParser[AMMType.T2T_CFMM, ParserVersion.V2, SyncIO] = + T2TOrdersV2Parser.make[SyncIO] def boxSample(tree: String) = io.circe.parser diff --git a/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TV3ParserSpec.scala b/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TV3ParserSpec.scala new file mode 100644 index 00000000..ff9e4a23 --- /dev/null +++ b/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/T2TV3ParserSpec.scala @@ -0,0 +1,218 @@ +package org.ergoplatform.dex.tracker.parsers.amm + +import cats.effect.{Clock, SyncIO} +import org.ergoplatform.ErgoAddressEncoder +import org.ergoplatform.dex.CatsPlatform +import org.ergoplatform.dex.domain.AssetAmount +import org.ergoplatform.dex.domain.amm.CFMMOrder._ +import org.ergoplatform.dex.domain.amm._ +import org.ergoplatform.dex.protocol.amm.{AMMType, ParserVersion} +import org.ergoplatform.dex.tracker.parsers.amm.v3.T2TOrdersV3Parser +import org.ergoplatform.ergo._ +import org.ergoplatform.ergo.domain.Output +import org.scalatest.matchers.should +import org.scalatest.propspec.AnyPropSpec +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks + +class T2TV3ParserSpec extends AnyPropSpec with should.Matchers with ScalaCheckPropertyChecks with CatsPlatform { + + implicit val clock: Clock[SyncIO] = Clock.create + + implicit val e: ErgoAddressEncoder = new ErgoAddressEncoder(ErgoAddressEncoder.MainnetNetworkPrefix) + + def parser: CFMMOrdersParser[AMMType.T2T_CFMM, ParserVersion.V3, SyncIO] = + T2TOrdersV3Parser.make[SyncIO]( + TokenId.fromStringUnsafe("003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0") + ) + + property("Parse deposit correct") { + val deposit = parser.deposit(depositV3).unsafeRunSync().get.asInstanceOf[DepositTokenFee] + + val expected = CFMMOrder.DepositTokenFee( + PoolId.fromStringUnsafe("5703a5b955c4902f165215f5ce1426816b4c6dca5300cfae35c8a35649287154"), + 2000000, + deposit.timestamp, + DepositParams( + AssetAmount( + TokenId.fromStringUnsafe("472c3d4ecaa08fb7392ff041ee2e6af75f4a558810a74b28600549d5392810e8"), + 1000000 + ), + AssetAmount(TokenId.fromStringUnsafe("d71693c49a84fbbecd4908c94813b46514b18b67a99952dc1e6e4791556de413"), 40), + dexFee = 15, + redeemer = + SErgoTree.unsafeFromString("0008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec") + ), + depositV3 + ) + + deposit shouldEqual expected + } + + property("Parse redeem correct") { + val redeem = parser.redeem(redeemV3).unsafeRunSync().get.asInstanceOf[RedeemTokenFee] + + redeem.params shouldEqual RedeemParams( + AssetAmount(TokenId.fromStringUnsafe("7338937c785551c3db0d6db218d8b7df86960d9a5720c2c97c968c21cf5c54f2"), 716), + 15, + SErgoTree.unsafeFromString("0008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec") + ) + redeem.poolId shouldEqual PoolId.fromStringUnsafe( + "5703a5b955c4902f165215f5ce1426816b4c6dca5300cfae35c8a35649287154" + ) + redeem.maxMinerFee shouldEqual 2000000 + } + + property("Parse swap correct") { + val swap = parser.swap(swapV3).unsafeRunSync().get.asInstanceOf[SwapTokenFee] + + swap.params shouldEqual SwapParams( + AssetAmount( + TokenId.fromStringUnsafe("03faf2cb329f2e90d6d23b58d91bbb6c046aa143261cc21f52fbe2824bfcbf04"), + 2 + ), + AssetAmount( + TokenId.fromStringUnsafe("003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0"), + 26 + ), + 5769230769230769L, + 10000000000000000L, + SErgoTree.unsafeFromString("0008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec") + ) + swap.poolId shouldEqual PoolId.fromStringUnsafe("080e453271ff4d2f85f97569b09755e67537bf2a1bc1cd09411b459cf901b902") + swap.maxMinerFee shouldEqual 2000000 + } + + def swapV3 = + io.circe.parser + .decode[Output]( + s""" + |{ + | "boxId": "436b80973fe567fba7e6179272835071ac92bfb20c54011d114b8e076506e251", + | "transactionId": "41ad07b62938750772f1331be0f55d53ab51fe2a7b9480db6fa3376da52a43a3", + | "blockId": "6fc118469010e5c1bc2a88c5307f0f3d8a54de7f37e11f070dd161364f7c0efa", + | "value": 310000, + | "index": 0, + | "globalIndex": 26637735, + | "creationHeight": 942255, + | "settlementHeight": 942257, + | "ergoTree": "19cc052304000e20003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d005808088fccdbcc323050404c60f04d00f08cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec040404080402040001010524059eb1839297f7830f0404040606010104000e20080e453271ff4d2f85f97569b09755e67537bf2a1bc1cd09411b459cf901b9020e240008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec05340100010105f015059c01060100040404020e20003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d001010e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a5730405000500058092f4010100d805d601b2a4730000d6027301d6037302d6049c73037e730405d6057305eb027306d195ed92b1a4730793b1db630872017308d80ad606db63087201d607b2a5730900d608db63087207d609b27208730a00d60a8c720902d60b95730b9d9c7e99720a730c067e7203067e730d067e720a06d60cb27206730e00d60d7e8c720c0206d60e7e8cb27206730f000206d60f9a720b7310ededededededed938cb2720673110001731293c272077313938c720901720292720a731492c17207c1a79573157316d801d610997e7317069d9c720b7e7318067e72030695ed917210731992b17208731ad801d611b27208731b00ed938c721101731c927e8c721102067210731d95938c720c017202909c720d7e7204069c720f9a9c720e7e7205067e720406909c720e7e7204069c720f9a9c720d7e7205067e72040690b0ada5d90110639593c27210731ec17210731f7320d90110599a8c7210018c72100273217322", + | "address": "EythtiXp53PKzShomQcugMHAUeCJrX1LGoSowRAbwH7iVzGwVG4p7ougpD9P4QpshLVhdoyjQ4FaHaPHn3p3fFGfLh1LnwzswFYoHZD7RUvv9A9MSTbmNuipZy6e6S9P1gptkyevtAYPgx6BddwyqePwjWhjT9ew9ZbmPN1fsof8ZJHVWVEv5GUJsBtyXrZhUs3abhJHuFvUpvExWEgSRBSEqn3E92xMMTPs81gDjAcDEdmWAJypNnGSp7q6PTVkDVq3YQPDbQn9DknKTmUs9frRCtUwNjWRZQ2WYBE8qxEbttM6cKujnv2BJjgDrxwRGsXnr6WG6dp91ZXgLU3aE5zMSsgUUHw5NMXQQBHrRs1pdqY5jBh3LjcoBJ1QLf6udA4ZjnmeRpy5vtJA2ZTFLgbKPhBmGRhDNTz7LTdMACyR2CKTv9Df7AZmNko1FEff7nMBR7R8s39TpVFZP74iYsFtFMhL9FfFneY3Np3JEu3pwpe43yiso4E5enb5M9fNXMctkJ7aW6edsBKZnPyVcwMgUTS5DPm6g9LJhXBt8ouwbyShE6T2TSpBSwo6gX9AvjMMVXYJ4Z317JGJNXE1u8U5DVWHLAB4WGWrUa7gjBqmrHeBu6PoCyPYcHCFHYaBcHUoV4sX64cbSfML8UZnQ4sd3YWGuGS9YjT74WXsgJYnaMY8ummGMaLDax5EqgAEQ1RzhEinC5j7MmoPdqg2kMLHjvYmfykcjJohaP7adzELnK2TxATj39mw9mpdADGMDpq5BFHFoAdeNPg24K8t9ZK2qSvPW4GWZuM5YxaW1qLyCvAuE5s5Zd4zFvQxVQesGvLNXkcSWVwCp34sqf4XbjsVPqwmMa77KXz5rnnVjwB4u8JwBe3LLqua5pTpnPkSwTtuJyGBDFHtwwCv7cRaHRkZZZ45Fo1ZhWuuep9cgpijyTEWSW6Qqa7NPejgQnwmd9ts1XxrsFHS9KurxWvF1s2iPAAJ", + | "assets": [ + | { + | "tokenId": "03faf2cb329f2e90d6d23b58d91bbb6c046aa143261cc21f52fbe2824bfcbf04", + | "index": 0, + | "amount": 2, + | "name": "SigUSD", + | "decimals": 2, + | "type": "EIP-004" + | }, + | { + | "tokenId": "003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0", + | "index": 1, + | "amount": 18, + | "name": "SigRSV", + | "decimals": 0, + | "type": "EIP-004" + | } + | ], + | "additionalRegisters": {}, + | "spentTransactionId": "8fe0518b8c0ed7069e8f0040ac8d97da869fff41b12cd97a399584957065fcf8", + | "mainChain": true + |} + |""".stripMargin + ) + .toOption + .get + + def redeemV3 = + io.circe.parser + .decode[Output]( + s""" + |{ + | "boxId": "e8224097dbf46a6f9e90d059917bba2b5ea6714ac895d47a35d15346988a7de7", + | "transactionId": "fc8266f5c7893f75f28575818bdc0803f195532609b9f404e629ae35d6348e51", + | "blockId": "0fe442d7620bbd1d64ffb77e262de74647617e45cc7bb05db15213c195dbd982", + | "value": 310000, + | "index": 0, + | "globalIndex": 26671828, + | "creationHeight": 943019, + | "settlementHeight": 943021, + | "ergoTree": "19eb0314040008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec0404040804020400040404020406040005feffffffffffffffff01040204000e205703a5b955c4902f165215f5ce1426816b4c6dca5300cfae35c8a356492871540e240008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec0e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a5730405000500058092f4010100d801d601b2a4730000eb027301d195ed92b1a4730293b1db630872017303d809d602db63087201d603b2a5730400d604db63087203d605b27204730500d606b27202730600d607b27204730700d608b27202730800d6097e8cb2db6308a77309000206d60a7e99730a8cb27202730b000206edededededed938cb27202730c0001730d93c27203730e938c7205018c720601938c7207018c720801927e8c720502069d9c72097e8c72060206720a927e8c720702069d9c72097e8c72080206720a90b0ada5d9010b639593c2720b730fc1720b73107311d9010b599a8c720b018c720b0273127313", + | "address": "5cZpjmfCNTqef1W69zmP1CqswQeB1eBQNEKdvvbf9Ei8Y7kSCRmUvSJmtbuRDSSZxSWTt7w5PXXFrcuZEBxHYuyFqcU483BcmVqHdY3ExjvmpuHYuuMF912TNji6SZZQehiXT1L3gXZeLVZ3zvC3KwVw1fZXrpLfGeutmR358KzMcnWVH4T2LPf8fTk6WsP7y4ygjHpzYqbo3oyxAnyFfRkgQNZ9aUtYVrdkVTpecsQ2p4pB5jWsqnv3kJeMsNhDKxYATLqDwquWoRUw9nExRF8HsXqMdC8WRvbwyxgYppnaFqLaSi41k88s7RUrUjEdGicHiSBSoVrxtrB3ozLWFZNoJjPw169xUB1Gpb64EZkKVqLcnMRRdAsWG2F8GgU3Mi3euv2mBjpEBo7cNu9cTkaniarSksnBi7NTB1MuerTZkuKD6vVEr3HhTkhU97BqP9VUHiUqwmRJuVGyd9JkD3ZsNfR734mohFhgkt1vwKEh57goQvFx2tpAiZZGYbMQoZ6D4hQ5cLieGFMRx633QG2tX7ErswQ74mgD8hTJbu3L64vGnh7YewVaBZMgFX55ABYscEnbsqFgEQCRteHogcd1hbdjq53YBS8Q9yZvnPgofNeKKuQyt2XmpC6H1BLhZWtxxpT1k6dfqsBgiK5ZSzS7Z45UJGgziQTiez2Aq", + | "assets": [ + | { + | "tokenId": "7338937c785551c3db0d6db218d8b7df86960d9a5720c2c97c968c21cf5c54f2", + | "index": 0, + | "amount": 716, + | "name": "NETA_ergopad_LP", + | "decimals": 0, + | "type": "EIP-004" + | }, + | { + | "tokenId": "003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0", + | "index": 1, + | "amount": 15, + | "name": "SigRSV", + | "decimals": 0, + | "type": "EIP-004" + | } + | ], + | "additionalRegisters": {}, + | "spentTransactionId": "186d63efe0afa5e89181c1da28681bc4c8d8c68d5c03bc4cf75cf9e82401bdd4", + | "mainChain": true + |} + |""".stripMargin + ) + .toOption + .get + + def depositV3 = + io.circe.parser + .decode[Output]( + s""" + |{ + | "boxId": "9efb46584856e7295cee7aacd41da2c3c7dc4b9aafb5ea5c5b4a983303a835c9", + | "transactionId": "dc3aa8a43254ee984cf61b9a30026b3ed7abf9ea1866f8d170c192b3ae21b118", + | "blockId": "47c3859bc1e2646bd73c82258e9ad9919d090a1d4a9b27e21ef22978d40d0eb2", + | "value": 310000, + | "index": 0, + | "globalIndex": 26669245, + | "creationHeight": 942973, + | "settlementHeight": 942975, + | "ergoTree": "19e2041a040008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec040404080402040205feffffffffffffffff0104040580897a04060550040004000e205703a5b955c4902f165215f5ce1426816b4c6dca5300cfae35c8a356492871540e240008cd03b196b978d77488fba3138876a40a40b9a046c2fbb5ecfa13d4ecf8f1eec52aec0404040204040402010101000e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a5730405000500058092f4010100d801d601b2a4730000eb027301d195ed92b1a4730293b1db630872017303d80cd602db63087201d603b2a5730400d604b27202730500d6057e9973068c72040206d606b27202730700d6077e8c72060206d6089d9c7e73080672057207d609b27202730900d60a7e8c72090206d60b9d9c7e730a067205720ad60cdb63087203d60db2720c730b00edededededed938cb27202730c0001730d93c27203730e92c17203c1a795ed8f7208720b93b1720c730fd801d60eb2720c731000ed938c720e018c720901927e8c720e02069d9c99720b7208720a720595ed917208720b93b1720c7311d801d60eb2720c731200ed938c720e018c720601927e8c720e02069d9c997208720b7207720595937208720b73137314938c720d018c720401927e8c720d0206a17208720b90b0ada5d9010e639593c2720e7315c1720e73167317d9010e599a8c720e018c720e0273187319", + | "address": "e5xiryCNuYas2qLeihB1qHYZ2mM21u1wMEKuFD8xJ8Ldu7eFoUH3Hroh39dKRxjZpzu2C5GxdtHinr7miafrQApdwsV81633E3Lh3tZq2tA6nFMsVHkr2Jtbj3PM8MczxuHvhbDNFFU6sSFp6X35pdjkJUknirzeVnG2y6P7UuJ93KT6a6pARrKc21eCgUAYcpc5hFG6Y4zCRDUCRsCE7yBvvkuSpfDXnfULwmPyZTE8ey7R81yuNBkdxeYJ7gwGeE3nFcbCdBEZ1EJTXCiXYNYjYrM9P6uhhQXPL5ueJdPwtBoA8aM6ELJhopnZeNPQ6NiUxStPqAhBFcCwSYYdQp22HPS8CV5RkiTrYvW6egvG7Y6ouu7kwTcSaWZXat9KStcs4c4PHm68BvazFy1Jsu9XH5mGnryy2Riivr9wcHQxa8r4YD2EjLshZGpZomGhZgkymwvJL5FbikLSevH1ok7cMS2BYBKPVbZFbhSAwDpfxQimff32BrnBRmki8prdSxojjApx3YoADmCBKLcpEaqW1eZGcEHHWrGN2d8e6zUfkHsfkcyobUxMUx1DsM5y6tM9PHgiUHYQ46JiMxb1LK91cuPjfMu7sa9agoLxF6tNKNL4uHWzZB4J9GsygxgpaiY5k9grqBMCfQJ9AdKdwfKrZiAE4PPXHX1P6ri93LjfVpwFBkYxEDz9vZPeieDoaqWQquhXvMGmDfYuyQ1u3xw7ya1aS3xSsRt4QHR4t3aurL7dFRMzAV4VUD3nJza58e7U4xFECQJEJuu6Wc46mhQG2oDXmS3tEvFH5jZc83BzXhh2VVEBEWLB7mFtYKN33KGFDs7Kk9Y", + | "assets": [ + | { + | "tokenId": "472c3d4ecaa08fb7392ff041ee2e6af75f4a558810a74b28600549d5392810e8", + | "index": 0, + | "amount": 1000000, + | "name": "NETA", + | "decimals": 6, + | "type": "EIP-004" + | }, + | { + | "tokenId": "d71693c49a84fbbecd4908c94813b46514b18b67a99952dc1e6e4791556de413", + | "index": 1, + | "amount": 40, + | "name": "ergopad", + | "decimals": 2, + | "type": "EIP-004" + | }, + | { + | "tokenId": "003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0", + | "index": 2, + | "amount": 15, + | "name": "SigRSV", + | "decimals": 0, + | "type": "EIP-004" + | } + | ], + | "additionalRegisters": {}, + | "spentTransactionId": "867843e396896a54c4cc3754fda591dd3d0a1880d2100876550f28d7f44581c3", + | "mainChain": true + |} + |""".stripMargin + ) + .toOption + .get +} diff --git a/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/V3Orders.scala b/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/V3Orders.scala new file mode 100644 index 00000000..92c71027 --- /dev/null +++ b/modules/utxo-tracker/src/test/scala/org/ergoplatform/dex/tracker/parsers/amm/V3Orders.scala @@ -0,0 +1,39 @@ +package org.ergoplatform.dex.tracker.parsers.amm + +import io.circe.parser.decode +import org.ergoplatform.ergo.services.explorer.models.Output + +object V3Orders { + + val deployV3SwapSell = + decode[Output]( + """ + |{ + | "boxId": "f027c8ec3632686b6f13a3f9bc7841114db759d0901c3efe5976ea6d3729c905", + | "transactionId": "4b9a9ab331777a51a2f1d706ffa47688ff0c48441385a0005ba382071d0514b3", + | "blockId": "f23b065123a51b94a4f9e1dcfa017c8561aecd63682e140d68d0bd54938b5d67", + | "value": 300400000, + | "index": 0, + | "globalIndex": 27082285, + | "creationHeight": 952305, + | "settlementHeight": 952307, + | "ergoTree": "199505210400058080d0d88bdea2e30205a2d4e0f5a18c87da0205808c8d9e0204ca0f08cd034153f4c52b1d3e301236cfc939f27dc6c58822c160387865ee1e41d407827cb40404040604020400010105e2f81304000e20f40afb6f877c40a30c8637dd5362227285738174151ce66d6684bc1b727ab6cf0e240008cd034153f4c52b1d3e301236cfc939f27dc6c58822c160387865ee1e41d407827cb40e209a06d9e545a41fd51eeffc5e20d818073bf820c635e2a9d922269913e0de369d05a0d780050101010105f015060100040404020e209a06d9e545a41fd51eeffc5e20d818073bf820c635e2a9d922269913e0de369d0101040406010104d00f0e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a5730405000500058092f4010100d804d601b2a4730000d6027301d6037302d6049c73037e730405eb027305d195ed92b1a4730693b1db630872017307d806d605db63087201d606b2a5730800d607db63087206d608b27207730900d6098c720802d60a95730a9d9c7e997209730b067e7202067e7203067e720906edededededed938cb27205730c0001730d93c27206730e938c720801730f92720a7e7310069573117312d801d60b997e7313069d9c720a7e7203067e72020695ed91720b731492b172077315d801d60cb27207731600ed938c720c017317927e8c720c0206720b7318909c7e8cb2720573190002067e7204069c9a720a731a9a9c7ec17201067e731b067e72040690b0ada5d9010b639593c2720b731cc1720b731d731ed9010b599a8c720b018c720b02731f7320", + | "address": "9u7gKQspeuiD4PLaxV56gsMncqj7VxhF19n8t9HzQE1n8w4oCbgfYEQvqfpuDQhVGAinQtgpyym5SsBt8aqynZE2uDeBcbKpovxfRuzirXcDWxk97BXCYXgcyqZb9wxphG5L3ZyWbWTRFGvh8XU9N3zVHsk6gYAuwctus6KRGQCMueSigGLWv6S94nMAKu2iiThMQQ5Er4BaG4ePXP2P3t1fngRfFupqBcw77tLDUipD9wsBsWcsaf766356ikZ1jaPZkX4aJPnVJsHNhxRWgb7yZqG8NJEdepXyvh2yqFmRXo4gmugYryAE9UfpKmaKJ6E2BZfyUFn4tyEemPUb7FDrp1s1kSYpa2v2BcYbGkQeiRVPa5ipEJ2bpfAtjaTVQkstoLqNL4SwepaVGHfjDC5X2bJ3YdNAxSiKuPPikEkuHoY5G3WZGcsZRceQ5AYgmi34pAPzHqHTRKfRMvwcPfrEdmsuBCHi9dvANxHShMLNEV6cULYBjRx4b78TocwNb6LHo87YgFm7C4jXM1NAQT4Dt74SfrmyA8AW3QyvV4a7T2uHhrkRucMXHkW4UiQdRT1iV99WEp5AP8zwvfaLo13JE7sNYR49XQVHd21p1WsV3Am8GrSAMmA9LUMLmkvgKngh2TbyEu14rrdmGCpBWnWYq1XVQiBWHferyzaDjscLkdhckNZKzSjBpF9rDRZLRTjcqFwsJxmevNkhi2MwTjXernXVJVY1Bakr1LrnycRdH7HyV35nBauoqJrC3jLuJgE8VMAF2cAtdSSkYGXDSEPLZpLkyN5KnbHtNcMmKz6PBZodyWeVkpbGjMce7uv6LdWhGABSDbvtZj5ty3FY3K4QGGfBcTz4c7mod6g8bP2fVqLXDJkbB2mMPUFxXHZqjc1FudHRTuo1ZREw4", + | "assets": [ + | { + | "tokenId": "9a06d9e545a41fd51eeffc5e20d818073bf820c635e2a9d922269913e0de369d", + | "index": 0, + | "amount": 163377, + | "name": "SPF", + | "decimals": 6, + | "type": "EIP-004" + | } + | ], + | "additionalRegisters": {}, + | "spentTransactionId": null, + | "mainChain": true + |} + |""".stripMargin + ).toOption.get + +}