From 399539fffd204b36051500e48ce2c3f80ae09a5d Mon Sep 17 00:00:00 2001 From: Artyom Sayadyan Date: Thu, 16 Jul 2020 13:58:47 +0300 Subject: [PATCH] SC-655 Remove illegal reference to extract() in V4 (#3197) --- .../lang/v1/evaluator/EvaluatorV2.scala | 15 ++-- .../evaluator/ctx/impl/waves/Functions.scala | 7 +- .../wavesplatform/lang/EvaluatorV2Test.scala | 8 +-- .../InvokeSmartAssetFailSuite.scala | 68 +++++++++++++++++++ .../smart/predef/ContextFunctionsTest.scala | 25 ++++--- 5 files changed, 101 insertions(+), 22 deletions(-) create mode 100644 node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/InvokeSmartAssetFailSuite.scala diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvaluatorV2.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvaluatorV2.scala index 182237aa463..cb6ff0d4684 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvaluatorV2.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvaluatorV2.scala @@ -157,9 +157,15 @@ class EvaluatorV2( update(argsWithExpr).flatMap(_ => root(argsWithExpr, update, unusedArgsComplexity, parentBlocks)) } .getOrElse { - val objectType = ctx.ec.typeDefs(name).asInstanceOf[CASETYPEREF] // todo handle absence - val fields = objectType.fields.map(_._1) zip fc.args.asInstanceOf[List[EVALUATED]] - root(CaseObj(objectType, fields.toMap), update, unusedArgsComplexity, parentBlocks) + val caseType = + ctx.ec.typeDefs.get(name) match { + case Some(caseType: CASETYPEREF) => Coeval.now(caseType) + case _ => Coeval.raiseError(new NoSuchElementException(s"Function or type '$name' not found")) + } + caseType.flatMap { objectType => + val fields = objectType.fields.map(_._1) zip fc.args.asInstanceOf[List[EVALUATED]] + root(CaseObj(objectType, fields.toMap), update, unusedArgsComplexity, parentBlocks) + } } else Coeval.now(unusedArgsComplexity) } @@ -219,7 +225,8 @@ class EvaluatorV2( } } .onErrorHandle { e => - if (!wasLogged) ctx.l(let.name)(Left(e.getMessage)) + val error = if (e.getMessage != null) e.getMessage else e.toString + if (!wasLogged) ctx.l(let.name)(Left(error)) throw e } } diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/waves/Functions.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/waves/Functions.scala index 64d2ade96a2..693be76280b 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/waves/Functions.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/waves/Functions.scala @@ -471,7 +471,7 @@ object Functions { } } - private def withExtract[C[_[_]]](f: BaseFunction[C]): BaseFunction[C] = { + private def withExtract[C[_[_]]](f: BaseFunction[C], version: StdLibVersion): BaseFunction[C] = { val args = f.signature.args.zip(f.args).map { case ((name, ty), _) => ("@" ++ name, ty) } @@ -482,7 +482,8 @@ object Functions { f.signature.result.asInstanceOf[UNION].typeList.find(_ != UNIT).get, args: _* ) { - FUNCTION_CALL(PureContext.extract, List(FUNCTION_CALL(f.header, args.map(a => REF(a._1)).toList))) + val extractF = if (version >= V4) PureContext.value else PureContext.extract + FUNCTION_CALL(extractF, List(FUNCTION_CALL(f.header, args.map(a => REF(a._1)).toList))) } } @@ -501,7 +502,7 @@ object Functions { getBinaryByIndexF(v), getStringByIndexF(v), if (v >= V4) addressFromStringV4 else addressFromStringF(v) - ).map(withExtract) + ).map(withExtract(_, v)) def txByIdF(proofsEnabled: Boolean, version: StdLibVersion): BaseFunction[Environment] = NativeFunction.withEnvironment[Environment]( diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/EvaluatorV2Test.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/EvaluatorV2Test.scala index f568caad9a8..5af5a5c5800 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/EvaluatorV2Test.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/EvaluatorV2Test.scala @@ -514,7 +514,7 @@ class EvaluatorV2Test extends PropSpec with PropertyChecks with ScriptGen with M ) ) - an[NoSuchElementException] should be thrownBy eval(expr, limit = 100) + (the[NoSuchElementException] thrownBy eval(expr, limit = 100)).getMessage shouldBe "A definition of 'b' not found" val expr2 = BLOCK( @@ -525,7 +525,7 @@ class EvaluatorV2Test extends PropSpec with PropertyChecks with ScriptGen with M ) ) - an[NoSuchElementException] should be thrownBy eval(expr2, limit = 100) + (the[NoSuchElementException] thrownBy eval(expr2, limit = 100)).getMessage shouldBe "Function or type 'b' not found" } property("function context leak") { @@ -548,7 +548,7 @@ class EvaluatorV2Test extends PropSpec with PropertyChecks with ScriptGen with M f() + x */ - an[NoSuchElementException] should be thrownBy eval(expr, limit = 100) + (the[NoSuchElementException] thrownBy eval(expr, limit = 100)).getMessage shouldBe "A definition of 'x' not found" val expr2 = BLOCK( FUNC("f", Nil, BLOCK(FUNC("g", Nil, CONST_LONG(1)), FUNCTION_CALL(FunctionHeader.User("g"), Nil))), @@ -569,7 +569,7 @@ class EvaluatorV2Test extends PropSpec with PropertyChecks with ScriptGen with M f() + g() */ - an[NoSuchElementException] should be thrownBy eval(expr2, limit = 100) + (the[NoSuchElementException] thrownBy eval(expr2, limit = 100)).getMessage shouldBe "Function or type 'g' not found" } property("if block by step") { diff --git a/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/InvokeSmartAssetFailSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/InvokeSmartAssetFailSuite.scala new file mode 100644 index 00000000000..652c12b6b11 --- /dev/null +++ b/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/InvokeSmartAssetFailSuite.scala @@ -0,0 +1,68 @@ +package com.wavesplatform.it.sync.smartcontract +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.common.utils.EitherExt2 +import com.wavesplatform.it.api.SyncHttpApi._ +import com.wavesplatform.it.sync.{setScriptFee, _} +import com.wavesplatform.it.transactions.BaseTransactionSuite +import com.wavesplatform.lang.v1.compiler.Terms.CONST_BOOLEAN +import com.wavesplatform.lang.v1.estimator.v3.ScriptEstimatorV3 +import com.wavesplatform.state.BinaryDataEntry +import com.wavesplatform.transaction.TxVersion +import com.wavesplatform.transaction.smart.script.ScriptCompiler + +// because of SC-655 bug +class InvokeSmartAssetFailSuite extends BaseTransactionSuite { + private val caller = firstAddress + private val dApp = secondAddress + + val dAppText = + """ + |{-# STDLIB_VERSION 4 #-} + |{-# CONTENT_TYPE DAPP #-} + |{-# SCRIPT_TYPE ACCOUNT #-} + | + |let bob = addressFromPublicKey(base58'BzFTfc4TB9s25d8b3sfhptj4STZafEM2FNkR5kQ8mJeA') + | + |@Callable(i) + |func some(fail: Boolean) = { + | let issue = Issue("asset", "test asset", 500, 2, true) + | let assetId = if fail then this.getBinaryValue("assetId") else issue.calculateAssetId() + | + | let result = [ + | BinaryEntry("bin", i.transactionId), + | BooleanEntry("bool", true), + | IntegerEntry("int", i.fee), + | StringEntry("str", i.caller.toString()), + | DeleteEntry("remove") + | ] + | + | if fail then { + | result ++ [ + | Reissue(assetId, 10, false) + | ] + | } else { + | result ++ [ + | issue, + | Reissue(assetId, 10, false), + | Burn(assetId, 5), + | SponsorFee(assetId, 2), + | ScriptTransfer(bob, 7, assetId) + | ] + | } + | + |} + """.stripMargin + + test("extracted funcs") { + val assetId = ByteStr.decodeBase58(sender.issue(caller, waitForTx = true).id).get + val data = List(BinaryDataEntry("assetId", assetId)) + val dataFee = calcDataFee(data, TxVersion.V2) + sender.putData(dApp, data, fee = dataFee, waitForTx = true) + + val script = ScriptCompiler.compile(dAppText, ScriptEstimatorV3).explicitGet()._1.bytes().base64 + sender.setScript(dApp, Some(script), setScriptFee, waitForTx = true) + + val tx = sender.invokeScript(caller, dApp, Some("some"), List(CONST_BOOLEAN(true)), waitForTx = true) + sender.debugStateChanges(tx._1.id).stateChanges.get.error.get.text shouldBe "Asset was issued by other address" + } +} diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/ContextFunctionsTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/ContextFunctionsTest.scala index 81a1b963a68..e45ce5d7fbd 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/ContextFunctionsTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/ContextFunctionsTest.scala @@ -90,8 +90,11 @@ class ContextFunctionsTest extends PropSpec with PropertyChecks with WithState w } property("reading from data transaction array by key") { - forAll(preconditionsAndPayments) { - case (_, _, _, tx, _, _) => + forAll(for { + version <- Gen.oneOf(DirectiveDictionary[StdLibVersion].all.filter(_ >= V3)) + preconditions <- preconditionsAndPayments + } yield (version, preconditions)) { + case (version, (_, _, _, tx, _, _)) => val int = tx.data(0) val bool = tx.data(1) val bin = tx.data(2) @@ -102,10 +105,10 @@ class ContextFunctionsTest extends PropSpec with PropertyChecks with WithState w | case tx: DataTransaction => { | let d = tx.data | - | let int = extract(getInteger(d, "${int.key}")) - | let bool = extract(getBoolean(d, "${bool.key}")) - | let bin = extract(getBinary(d, "${bin.key}")) - | let str = extract(getString(d, "${str.key}")) + | let int = value(getInteger(d, "${int.key}")) + | let bool = value(getBoolean(d, "${bool.key}")) + | let bin = value(getBinary(d, "${bin.key}")) + | let str = value(getString(d, "${str.key}")) | | let intV = getIntegerValue(d, "${int.key}") | let boolV = getBooleanValue(d, "${bool.key}") @@ -117,10 +120,10 @@ class ContextFunctionsTest extends PropSpec with PropertyChecks with WithState w | let okBin = bin == base58'${Base58.encode(bin.asInstanceOf[BinaryDataEntry].value.arr)}' | let okStr = str == "${str.value}" | - | let okIntV = int + 1 == ${int.value} + 1 - | let okBoolV = bool || true == ${bool.value} || true - | let okBinV = bin == base58'${Base58.encode(bin.asInstanceOf[BinaryDataEntry].value.arr)}' - | let okStrV = str + "" == "${str.value}" + | let okIntV = intV + 1 == ${int.value} + 1 + | let okBoolV = boolV || true == ${bool.value} || true + | let okBinV = binV == base58'${Base58.encode(bin.asInstanceOf[BinaryDataEntry].value.arr)}' + | let okStrV = strV + "" == "${str.value}" | | let badInt = isDefined(getInteger(d, "${bool.key}")) | let badBool = isDefined(getBoolean(d, "${bin.key}")) @@ -137,7 +140,7 @@ class ContextFunctionsTest extends PropSpec with PropertyChecks with WithState w |} |""".stripMargin, Coproduct(tx), - V3 + version ) result shouldBe evaluated(true) }