From a7a867f318a0d7dd06a995f9d66fc2325ec1112d Mon Sep 17 00:00:00 2001 From: Artyom Sayadyan Date: Tue, 26 Mar 2019 14:32:26 +0300 Subject: [PATCH] SC-140 Add directives to decompiled script --- .../api/http/UtilsApiRoute.scala | 17 ++++++-- .../smart/script/PolyDecompile.scala | 43 +++++++++++++++++++ .../transaction/smart/script/Script.scala | 28 ++++++++++-- .../wavesplatform/http/UtilsRouteSpec.scala | 30 ++++++++++++- 4 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 src/main/scala/com/wavesplatform/transaction/smart/script/PolyDecompile.scala diff --git a/src/main/scala/com/wavesplatform/api/http/UtilsApiRoute.scala b/src/main/scala/com/wavesplatform/api/http/UtilsApiRoute.scala index 23530c13e1e..6f60ef37ce2 100644 --- a/src/main/scala/com/wavesplatform/api/http/UtilsApiRoute.scala +++ b/src/main/scala/com/wavesplatform/api/http/UtilsApiRoute.scala @@ -47,15 +47,24 @@ case class UtilsApiRoute(timeService: Time, settings: RestAPISettings) extends A new ApiResponse(code = 200, message = "string or error") )) def decompile: Route = path("script" / "decompile") { + import play.api.libs.json.Json.toJsFieldJsValueWrapper + (post & entity(as[String])) { code => Script.fromBase64String(code, checkComplexity = false) match { case Left(err) => complete(err) case Right(script) => - val ret = Script.decompile(script) + val (scriptText, meta) = Script.decompile(script) + val directives: List[(String, JsValue)] = meta.map { + case (k, v) => + (k, v match { + case n: Number => JsNumber(BigDecimal(n.toString)) + case s => JsString(s.toString) + }) + } + val result = directives ::: "script" -> JsString(scriptText) :: Nil + val wrapped = result.map { case (k, v) => (k, toJsFieldJsValueWrapper(v)) } complete( - Json.obj( - "script" -> ret.toString, - ) + Json.obj(wrapped: _*) ) } } diff --git a/src/main/scala/com/wavesplatform/transaction/smart/script/PolyDecompile.scala b/src/main/scala/com/wavesplatform/transaction/smart/script/PolyDecompile.scala new file mode 100644 index 00000000000..2ca649db8f9 --- /dev/null +++ b/src/main/scala/com/wavesplatform/transaction/smart/script/PolyDecompile.scala @@ -0,0 +1,43 @@ +package com.wavesplatform.transaction.smart.script + +import com.wavesplatform.lang.ContentType.{ContentType, DApp, Expression} +import com.wavesplatform.lang.ScriptType.{Account, Asset, ScriptType} +import com.wavesplatform.lang.StdLibVersion.{StdLibVersion, V1, V2, V3} +import shapeless.Poly1 + +object PolyDecompile extends Poly1 { + implicit def decompile[A](implicit d: Decompile[A]) = at[A](d.decompile) +} + +trait Decompile[A] { + def decompile(value: A): (String, Any) = (key, decompileValue(value)) + def key: String + def decompileValue(value: A): Any +} + +object DecompileInstances { + implicit val versionDecompiler = new Decompile[StdLibVersion] { + override def key = "STDLIB_VERSION" + override def decompileValue(value: StdLibVersion): Int = value match { + case V1 => 1 + case V2 => 2 + case V3 => 3 + } + } + + implicit val expressionDecompiler = new Decompile[ContentType] { + override def key = "CONTENT_TYPE" + override def decompileValue(value: ContentType): String = value match { + case Expression => "EXPRESSION" + case DApp => "DAPP" + } + } + + implicit val scriptDecompiler = new Decompile[ScriptType] { + override def key = "SCRIPT_TYPE" + override def decompileValue(value: ScriptType): String = value match { + case Account => "ACCOUNT" + case Asset => "ASSET" + } + } +} diff --git a/src/main/scala/com/wavesplatform/transaction/smart/script/Script.scala b/src/main/scala/com/wavesplatform/transaction/smart/script/Script.scala index be9225d81ca..16398edfeec 100644 --- a/src/main/scala/com/wavesplatform/transaction/smart/script/Script.scala +++ b/src/main/scala/com/wavesplatform/transaction/smart/script/Script.scala @@ -2,11 +2,17 @@ package com.wavesplatform.transaction.smart.script import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.Base64 +import com.wavesplatform.lang.ContentType.{DApp, Expression} +import com.wavesplatform.lang.ScriptType.Account import com.wavesplatform.lang.StdLibVersion._ import com.wavesplatform.lang.v1.compiler.Decompiler import com.wavesplatform.transaction.ValidationError.ScriptParseError +import com.wavesplatform.utils.defaultDecompilerContext +import com.wavesplatform.transaction.smart.script.ContractScript.ContractScriptImpl import monix.eval.Coeval import com.wavesplatform.transaction.smart.script.v1.ExprScript +import DecompileInstances._ +import shapeless.HList trait Script { type Expr @@ -38,9 +44,23 @@ object Script { script <- ScriptReader.fromBytes(bytes, checkComplexity = checkComplexity) } yield script - def decompile(s: Script): String = s match { - case e: ExprScript => Decompiler(e.expr, com.wavesplatform.utils.defaultDecompilerContext) - case com.wavesplatform.transaction.smart.script.ContractScript.ContractScriptImpl(_, contract, _) => - Decompiler(contract, com.wavesplatform.utils.defaultDecompilerContext) + type DirectiveMeta = List[(String, Any)] + + def decompile(s: Script): (String, DirectiveMeta) = { + val (scriptText, directives: DirectiveMeta) = s match { + case e: ExprScript => + val directives = HList(s.stdLibVersion, Expression).map(PolyDecompile).toList + val decompiler = Decompiler(e.expr, defaultDecompilerContext) + (decompiler, directives) + case ContractScriptImpl(_, contract, _) => + val directives = HList(s.stdLibVersion, Account, DApp).map(PolyDecompile).toList + val decompiler = Decompiler(contract, defaultDecompilerContext) + (decompiler, directives) + } + val directivesText = directives + .map { case (key, value) => s"{-#$key $value#-}" } + .mkString(start = "", sep = "\n", end = "\n") + + (directivesText + scriptText, directives) } } diff --git a/src/test/scala/com/wavesplatform/http/UtilsRouteSpec.scala b/src/test/scala/com/wavesplatform/http/UtilsRouteSpec.scala index f0022c7153a..c92db186d4d 100644 --- a/src/test/scala/com/wavesplatform/http/UtilsRouteSpec.scala +++ b/src/test/scala/com/wavesplatform/http/UtilsRouteSpec.scala @@ -33,7 +33,35 @@ class UtilsRouteSpec extends RouteSpec("/utils") with RestAPISettingsHelper with val base64 = ExprScript(script).explicitGet().bytes().base64 Post(routePath("/script/decompile"), base64) ~> route ~> check { val json = responseAs[JsValue] - (json \ "script").as[String] shouldBe "(1 == 2)" + (json \ "STDLIB_VERSION").as[Int] shouldBe 1 + (json \ "CONTENT_TYPE").as[String] shouldBe "EXPRESSION" + (json \ "script").as[String] shouldBe "" + + "{-#STDLIB_VERSION 1#-}\n" + + "{-#CONTENT_TYPE EXPRESSION#-}\n" + + "(1 == 2)" + } + + Post(routePath("/script/decompile"), "AgZ7TN8j") ~> route ~> check { + val json = responseAs[JsValue] + (json \ "STDLIB_VERSION").as[Int] shouldBe 2 + (json \ "CONTENT_TYPE").as[String] shouldBe "EXPRESSION" + (json \ "script").as[String] shouldBe "" + + "{-#STDLIB_VERSION 2#-}\n" + + "{-#CONTENT_TYPE EXPRESSION#-}\n" + + "true" + } + + Post(routePath("/script/decompile"), "AAIDAAAAAAAAAAAAAAAAAAAAAQAAAAJ0eAEAAAAGdmVyaWZ5AAAAAAbAmSEV") ~> route ~> check { + val json = responseAs[JsValue] + (json \ "STDLIB_VERSION").as[Int] shouldBe 3 + (json \ "CONTENT_TYPE").as[String] shouldBe "DAPP" + (json \ "SCRIPT_TYPE").as[String] shouldBe "ACCOUNT" + (json \ "script").as[String] shouldBe "" + + "{-#STDLIB_VERSION 3#-}\n" + + "{-#SCRIPT_TYPE ACCOUNT#-}\n" + + "{-#CONTENT_TYPE DAPP#-}\n\n\n\n" + + "@Verifier(tx)\n" + + "func verify () = true\n" } }