diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/ExecutionError.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/ExecutionError.scala index effb215ec26..d02bdd7a911 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/ExecutionError.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/ExecutionError.scala @@ -3,5 +3,8 @@ package com.wavesplatform.lang sealed trait ExecutionError { def message: String } -case class CommonError(message: String) extends ExecutionError +case class CommonError(details: String, cause: Option[ValidationError] = None) extends ExecutionError { + override def toString: String = s"CommonError($message)" + override def message: String = cause.map(_.toString).getOrElse(details) +} case class FailOrRejectError(message: String, skipInvokeComplexity: Boolean = true) extends ExecutionError with ValidationError diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/utils/package.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/utils/package.scala index b5bd057d209..61a5a570a34 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/utils/package.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/utils/package.scala @@ -12,7 +12,7 @@ import com.wavesplatform.lang.v1.FunctionHeader.Native import com.wavesplatform.lang.v1.compiler.Terms.EVALUATED import com.wavesplatform.lang.v1.compiler.Types.CASETYPEREF import com.wavesplatform.lang.v1.compiler.{CompilerContext, DecompilerContext} -import com.wavesplatform.lang.v1.evaluator.FunctionIds +import com.wavesplatform.lang.v1.evaluator.{FunctionIds, Log} import com.wavesplatform.lang.v1.evaluator.ctx.EvaluationContext import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.WavesContext import com.wavesplatform.lang.v1.evaluator.ctx.impl.{CryptoContext, PureContext} @@ -60,7 +60,7 @@ package object utils { payments: Seq[(Option[Array[Byte]], Long)], availableComplexity: Int, reentrant: Boolean - ): Coeval[(Either[ValidationError, EVALUATED], Int)] = ??? + ): Coeval[(Either[ValidationError, (EVALUATED, Log[Id])], Int)] = ??? } val lazyContexts: Map[(DirectiveSet, Boolean), Coeval[CTX[Environment]]] = diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/TermPrinter.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/TermPrinter.scala index fde39986ab0..20ea31a5da8 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/TermPrinter.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/TermPrinter.scala @@ -1,8 +1,25 @@ package com.wavesplatform.lang.v1.compiler -import com.wavesplatform.lang.v1.compiler.Terms.{ARR, CaseObj, EVALUATED} +import com.wavesplatform.common.utils.{Base58, Base64} +import com.wavesplatform.lang.v1.compiler.Terms.{ARR, CONST_BYTESTR, CONST_STRING, CaseObj, EVALUATED} + +case class TermPrinter(fixArrIndentation: Boolean = false) { + def prettyString(e: EVALUATED, depth: Int): String = { + e match { + case obj: CaseObj => indentObjString(obj, depth) + case arr: ARR => indentArrString(arr, depth) + case CONST_BYTESTR(bs) => + if (bs.size > 1024) { + "base64'" ++ Base64.encode(bs.arr) ++ "'" + } else { + "base58'" ++ Base58.encode(bs.arr) ++ "'" + } + case CONST_STRING(s) => + "\"" ++ escape(s) ++ "\"" + case other => other.toString + } + } -object TermPrinter { def string(e: EVALUATED): String = { val sb = new StringBuilder() print(s => sb.append(s), e) @@ -15,13 +32,41 @@ object TermPrinter { sb.mkString } + def indentArrString(e: ARR, depth: Int): String = { + val sb = new StringBuilder() + if (fixArrIndentation) printArr(s => sb.append(s), e, depth) else printArr(s => sb.append(s), e) + sb.mkString + } + def print(toDest: String => Unit, e: EVALUATED, depth: Int = 0): Unit = e match { case obj: CaseObj => printObj(toDest, obj, depth) - case arr: ARR => printArr(toDest, arr) - case a => toDest(a.prettyString(depth)) + case arr: ARR => if (fixArrIndentation) printArr(toDest, arr, depth) else printArr(toDest, arr) + case a => toDest(prettyString(a, depth)) } + private def printArr(toDest: String => Unit, arr: ARR, depth: Int): Unit = { + toDest("[") + val length = arr.xs.length + if (length > 0) { + var i = 0 + toDest("\n") + while (i < length - 1) { + indent(toDest, depth + 1) + print(toDest, arr.xs(i), depth + 1) + toDest(",\n") + i = i + 1 + } + indent(toDest, depth + 1) + print(toDest, arr.xs(length - 1), depth + 1) + toDest("\n") + indent(toDest, depth) + toDest("]") + } else { + toDest("]") + } + } + private def printArr(toDest: String => Unit, arr: ARR): Unit = { toDest("[") val length = arr.xs.length @@ -61,4 +106,22 @@ object TermPrinter { i = i - 1 } } + + private def escape(s: String): String = { + // Simple and very naive implementation based on + // https://github.com/linkedin/dustjs/blob/3fc12efd153433a21fd79ac81e8c5f5d6f273a1c/dist/dust-core.js#L1099 + + // Note this might not be the most efficient since Scala.js compiles this to a bunch of .split and .join calls + s.replace("\\", "\\\\") + .replace("/", "\\/") + .replace("'", "\\'") + .replace("\"", "\\\"") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t") + .replace("\b", "\\b") + .replace("\f", "\\f") + .replace("\u2028", "\\u2028") + .replace("\u2029", "\\u2029") + } } diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/Terms.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/Terms.scala index baafb19fe94..f1d1a8ec214 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/Terms.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/Terms.scala @@ -3,21 +3,20 @@ package com.wavesplatform.lang.v1.compiler import java.nio.charset.StandardCharsets import cats.Eval -import cats.instances.list._ -import cats.syntax.traverse._ +import cats.instances.list.* +import cats.syntax.traverse.* import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.common.utils._ import com.wavesplatform.lang.{ExecutionError, CommonError} -import com.wavesplatform.lang.v1.ContractLimits._ +import com.wavesplatform.lang.v1.ContractLimits.* import com.wavesplatform.lang.v1.FunctionHeader -import com.wavesplatform.lang.v1.compiler.Types._ +import com.wavesplatform.lang.v1.compiler.Types.* import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext.MaxListLengthV4 import monix.eval.Coeval object Terms { - val DataTxMaxBytes: Int = 150 * 1024 // should be the same as DataTransaction.MaxBytes - val DataTxMaxProtoBytes: Int = 165947 // depends from DataTransaction.MaxProtoBytes - val DataEntryValueMax: Int = Short.MaxValue // should be the same as DataEntry.MaxValueSize + val DataTxMaxBytes: Int = 150 * 1024 // should be the same as DataTransaction.MaxBytes + val DataTxMaxProtoBytes: Int = 165947 // depends from DataTransaction.MaxProtoBytes + val DataEntryValueMax: Int = Short.MaxValue // should be the same as DataEntry.MaxValueSize sealed abstract class DECLARATION { def name: String @@ -147,8 +146,7 @@ object Terms { } sealed trait EVALUATED extends EXPR { - def prettyString(level: Int): String = toString - def toStr: Coeval[String] = Coeval.now(toString) + def toStr: Coeval[String] = Coeval.now(toString) def weight: Long val getType: REAL // used for _isInstanceOf and therefore for match @@ -159,23 +157,17 @@ object Terms { case class CONST_LONG(t: Long) extends EVALUATED { override def toString: String = t.toString override val weight: Long = 8L - override val getType: REAL = LONG + override val getType: REAL = LONG } case class CONST_BIGINT(t: BigInt) extends EVALUATED { override def toString: String = t.toString override val weight: Long = 64L - override val getType: REAL = BIGINT + override val getType: REAL = BIGINT } class CONST_BYTESTR private (val bs: ByteStr) extends EVALUATED { override def toString: String = bs.toString - override def prettyString(level: Int): String = { - if (bs.size > 1024) { - "base64'" ++ Base64.encode(bs.arr) ++ "'" - } else { - "base58'" ++ Base58.encode(bs.arr) ++ "'" - } - } + override val weight: Long = bs.size override val getType: REAL = BYTESTR @@ -191,9 +183,9 @@ object Terms { object CONST_BYTESTR { sealed abstract class Limit(val value: Int) - case object DataEntrySize extends Limit(DataEntryValueMax) - case object DataTxSize extends Limit(DataTxMaxBytes) - case object NoLimit extends Limit(Int.MaxValue) + case object DataEntrySize extends Limit(DataEntryValueMax) + case object DataTxSize extends Limit(DataTxMaxBytes) + case object NoLimit extends Limit(Int.MaxValue) def apply(bs: ByteStr, limit: Limit = DataEntrySize): Either[CommonError, EVALUATED] = Either.cond( @@ -207,9 +199,8 @@ object Terms { } class CONST_STRING private (val s: String, bytesLength: Int) extends EVALUATED { - override def toString: String = s - override def prettyString(level: Int): String = "\"" ++ escape(s) ++ "\"" - override lazy val weight: Long = bytesLength + override def toString: String = s + override lazy val weight: Long = bytesLength override val getType: REAL = STRING @@ -241,24 +232,6 @@ object Terms { Some(arg.s) } - private def escape(s: String): String = { - // Simple and very naive implementation based on - // https://github.com/linkedin/dustjs/blob/3fc12efd153433a21fd79ac81e8c5f5d6f273a1c/dist/dust-core.js#L1099 - - // Note this might not be the most efficient since Scala.js compiles this to a bunch of .split and .join calls - s.replace("\\", "\\\\") - .replace("/", "\\/") - .replace("'", "\\'") - .replace("\"", "\\\"") - .replace("\n", "\\n") - .replace("\r", "\\r") - .replace("\t", "\\t") - .replace("\b", "\\b") - .replace("\f", "\\f") - .replace("\u2028", "\\u2028") - .replace("\u2029", "\\u2029") - } - case class CONST_BOOLEAN(b: Boolean) extends EVALUATED { override def toString: String = b.toString override val weight: Long = 1L @@ -270,9 +243,8 @@ object Terms { lazy val FALSE: CONST_BOOLEAN = CONST_BOOLEAN(false) case class CaseObj private (caseType: CASETYPEREF, fields: Map[String, EVALUATED]) extends EVALUATED { - override def toString: String = TermPrinter.string(this) - - override def prettyString(depth: Int): String = TermPrinter.indentObjString(this, depth) + // must be with fixArrIndentation = false, because of makeString behavior before RideV6 (NODE-2370) + override def toString: String = TermPrinter().string(this) override val weight: Long = OBJ_WEIGHT + FIELD_WEIGHT * fields.size + fields.map(_._2.weight).sum @@ -295,7 +267,8 @@ object Terms { } abstract case class ARR private (xs: IndexedSeq[EVALUATED]) extends EVALUATED { - override def toString: String = TermPrinter.string(this) + // must be with fixArrIndentation = false, because of makeString behavior before RideV6 (NODE-2370) + override def toString: String = TermPrinter().string(this) lazy val elementsWeightSum: Long = weight - EMPTYARR_WEIGHT - ELEM_WEIGHT * xs.size @@ -323,8 +296,8 @@ object Terms { case class FAIL(reason: String) extends EVALUATED { override def toString: String = "Evaluation failed: " ++ reason - def weight: Long = 0 - override val getType: REAL = NOTHING + def weight: Long = 0 + override val getType: REAL = NOTHING } val runtimeTupleType: CASETYPEREF = CASETYPEREF("Tuple", Nil) diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/Contextful.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/Contextful.scala index 5ec2828f39d..05b37eb723c 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/Contextful.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/Contextful.scala @@ -38,7 +38,7 @@ object ContextfulNativeFunction { env: C[F], evaluatedArgs: List[EVALUATED], availableComplexity: Int - )(implicit m: Monad[CoevalF[F, *]]): Coeval[F[(Either[ExecutionError, EVALUATED], Int)]] + )(implicit m: Monad[CoevalF[F, *]]): Coeval[F[(Either[ExecutionError, (EVALUATED, Log[F])], Int)]] } } diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ContractEvaluator.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ContractEvaluator.scala index f13040fad14..e543319c7b7 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ContractEvaluator.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ContractEvaluator.scala @@ -11,6 +11,7 @@ import com.wavesplatform.lang.v1.compiler.Terms.* import com.wavesplatform.lang.v1.evaluator.ctx.EvaluationContext import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.Bindings import com.wavesplatform.lang.v1.traits.Environment +import com.wavesplatform.lang.v1.traits.domain.Recipient.Address import com.wavesplatform.lang.v1.traits.domain.{AttachedPayments, Recipient} import com.wavesplatform.lang.{CommonError, ExecutionError} import monix.eval.Coeval @@ -31,6 +32,10 @@ object ContractEvaluator { feeAssetId: Option[ByteStr] ) + // Class for passing invocation argument object in error log during script execution + case class ExprWithInvArg(expr: EXPR, invArg: Option[LET]) + case class LogExtraInfo(invokedFuncName: Option[String] = None, invArg: Option[LET] = None, dAppAddress: Option[Address] = None) + def buildSyntheticCall(contract: DApp, call: EXPR): EXPR = { val callables = contract.callableFuncs.flatMap { cf => val argName = cf.annotation.invocationArgName @@ -51,7 +56,7 @@ object ContractEvaluator { foldDeclarations(contract.decs ++ callables, BLOCK(LET("__synthetic_call", TRUE), call)) } - def buildExprFromInvocation(c: DApp, i: Invocation, version: StdLibVersion): Either[ExecutionError, EXPR] = { + def buildExprFromInvocation(c: DApp, i: Invocation, version: StdLibVersion): Either[ExecutionError, ExprWithInvArg] = { val functionName = i.funcCall.function.funcName val contractFuncAndCallOpt = c.callableFuncs.find(_.u.name == functionName).map((_, i.funcCall)) @@ -63,22 +68,26 @@ object ContractEvaluator { if (otherFuncs contains functionName) s"function '$functionName exists in the script but is not marked as @Callable, therefore cannot not be invoked" else s"@Callable function '$functionName' doesn't exist in the script" - CommonError(message).asLeft[EXPR] + CommonError(message).asLeft[ExprWithInvArg] case Some((f, fc)) => val takingArgsNumber = f.u.args.size val passedArgsNumber = fc.args.size if (takingArgsNumber == passedArgsNumber) { - foldDeclarations( - c.decs, - BLOCK( - LET(f.annotation.invocationArgName, Bindings.buildInvocation(i, version)), - BLOCK(f.u, fc) - ) + val invocationArgLet = LET(f.annotation.invocationArgName, Bindings.buildInvocation(i, version)) + ExprWithInvArg( + foldDeclarations( + c.decs, + BLOCK( + invocationArgLet, + BLOCK(f.u, fc) + ) + ), + Some(invocationArgLet) ).asRight[ExecutionError] } else { CommonError(s"function '$functionName takes $takingArgsNumber args but $passedArgsNumber were(was) given") - .asLeft[EXPR] + .asLeft[ExprWithInvArg] } } } @@ -89,21 +98,23 @@ object ContractEvaluator { def verify( decls: List[DECLARATION], v: VerifierFunction, - evaluate: EXPR => (Log[Id], Int, Either[ExecutionError, EVALUATED]), + evaluate: (EXPR, LogExtraInfo) => (Log[Id], Int, Either[ExecutionError, EVALUATED]), entity: CaseObj ): (Log[Id], Int, Either[ExecutionError, EVALUATED]) = { + val invocationArgLet = LET(v.annotation.invocationArgName, entity) val verifierBlock = BLOCK( - LET(v.annotation.invocationArgName, entity), + invocationArgLet, BLOCK(v.u, FUNCTION_CALL(FunctionHeader.User(v.u.name), List(entity))) ) - evaluate(foldDeclarations(decls, verifierBlock)) + evaluate(foldDeclarations(decls, verifierBlock), LogExtraInfo(invokedFuncName = Some(v.u.name), invArg = Some(invocationArgLet))) } def applyV2Coeval( ctx: EvaluationContext[Environment, Id], dApp: DApp, + dAppAddress: ByteStr, i: Invocation, version: StdLibVersion, limit: Int, @@ -113,13 +124,24 @@ object ContractEvaluator { Coeval .now(buildExprFromInvocation(dApp, i, version).leftMap((_, limit, Nil))) .flatMap { - case Right(value) => applyV2Coeval(ctx, value, version, i.transactionId, limit, correctFunctionCallScope, newMode) - case Left(error) => Coeval.now(Left(error)) + case Right(value) => + applyV2Coeval( + ctx, + value.expr, + LogExtraInfo(invokedFuncName = Some(i.funcCall.function.funcName), invArg = value.invArg, dAppAddress = Some(Address(dAppAddress))), + version, + i.transactionId, + limit, + correctFunctionCallScope, + newMode + ) + case Left(error) => Coeval.now(Left(error)) } private def applyV2Coeval( ctx: EvaluationContext[Environment, Id], expr: EXPR, + logExtraInfo: LogExtraInfo, version: StdLibVersion, transactionId: ByteStr, limit: Int, @@ -127,7 +149,7 @@ object ContractEvaluator { newMode: Boolean ): Coeval[Either[(ExecutionError, Int, Log[Id]), (ScriptResult, Log[Id])]] = EvaluatorV2 - .applyLimitedCoeval(expr, limit, ctx, version, correctFunctionCallScope, newMode) + .applyLimitedCoeval(expr, logExtraInfo, limit, ctx, version, correctFunctionCallScope, newMode) .map(_.flatMap { case (expr, unusedComplexity, log) => val result = expr match { diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvaluatorV1.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvaluatorV1.scala index e147ef504a0..2b9b4c12ac8 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvaluatorV1.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvaluatorV1.scala @@ -25,11 +25,11 @@ object EvaluatorV1 { Eval.now(x) } - private val evaluator = new EvaluatorV1[Id, Environment] + private val evaluator = new EvaluatorV1[Id, Environment] def apply(): EvaluatorV1[Id, Environment] = evaluator } -class EvaluatorV1[F[_] : Monad, C[_[_]]](implicit ev: Monad[EvalF[F, *]], ev2: Monad[CoevalF[F, *]]) { +class EvaluatorV1[F[_]: Monad, C[_[_]]](implicit ev: Monad[EvalF[F, *]], ev2: Monad[CoevalF[F, *]]) { private val lenses = new Lenses[F, C] import lenses.* @@ -47,7 +47,7 @@ class EvaluatorV1[F[_] : Monad, C[_[_]]](implicit ev: Monad[EvalF[F, *]], ev2: M private def evalFuncBlock(func: FUNC, inner: EXPR): EvalM[F, C, (EvaluationContext[C, F], EVALUATED)] = { val funcHeader = FunctionHeader.User(func.name) val function = UserFunction(func.name, 0, NOTHING, func.args.map(n => (n, NOTHING))*)(func.body) - .asInstanceOf[UserFunction[C]] + .asInstanceOf[UserFunction[C]] local { modify[F, LoggedEvaluationContext[C, F], ExecutionError](funcs.modify(_)(_.updated(funcHeader, function))) .flatMap(_ => evalExprWithCtx(inner)) @@ -57,7 +57,7 @@ class EvaluatorV1[F[_] : Monad, C[_[_]]](implicit ev: Monad[EvalF[F, *]], ev2: M private def evalRef(key: String): EvalM[F, C, (EvaluationContext[C, F], EVALUATED)] = for { ctx <- get[F, LoggedEvaluationContext[C, F], ExecutionError] - r <- lets.get(ctx).get(key) match { + r <- lets.get(ctx).get(key) match { case Some(lzy) => liftTER[F, C, EVALUATED](lzy.value) case None => raiseError[F, LoggedEvaluationContext[C, F], ExecutionError, EVALUATED](s"A definition of '$key' not found") } @@ -89,9 +89,8 @@ class EvaluatorV1[F[_] : Monad, C[_[_]]](implicit ev: Monad[EvalF[F, *]], ev2: M .map { case func: UserFunction[C] => Monad[EvalM[F, C, *]].flatMap(args.traverse(evalExpr)) { args => - val letDefsWithArgs = args.zip(func.signature.args).foldLeft(ctx.ec.letDefs) { - case (r, (argValue, (argName, _))) => - r + (argName -> LazyVal.fromEvaluated(argValue, ctx.l(s"$argName"))) + val letDefsWithArgs = args.zip(func.signature.args).foldLeft(ctx.ec.letDefs) { case (r, (argValue, (argName, _))) => + r + (argName -> LazyVal.fromEvaluated(argValue, ctx.l(s"$argName"))) } local { val newState: EvalM[F, C, Unit] = set[F, LoggedEvaluationContext[C, F], ExecutionError](lets.set(ctx)(letDefsWithArgs)).map(_.pure[F]) @@ -101,15 +100,14 @@ class EvaluatorV1[F[_] : Monad, C[_[_]]](implicit ev: Monad[EvalF[F, *]], ev2: M case func: NativeFunction[C] => Monad[EvalM[F, C, *]].flatMap(args.traverse(evalExpr)) { args => val evaluated = func.ev match { - case f: Simple[C] => - val r = Try(f.evaluate(ctx.ec.environment, args)) - .toEither + case f: Simple[C] => + val r = Try(f.evaluate(ctx.ec.environment, args)).toEither .bimap(e => CommonError(e.toString): ExecutionError, EitherT(_)) .pure[F] EitherT(r).flatten.value.pure[Eval] case f: Extended[C] => f.evaluate(ctx.ec.environment, args, Int.MaxValue) - .map(_.map(_._1)) + .map(_.map(_._1.map(_._1))) .to[Eval] } liftTER[F, C, EVALUATED](evaluated) @@ -119,11 +117,10 @@ class EvaluatorV1[F[_] : Monad, C[_[_]]](implicit ev: Monad[EvalF[F, *]], ev2: M // no such function, try data constructor header match { case FunctionHeader.User(typeName, _) => - types.get(ctx).get(typeName).collect { - case t @ CASETYPEREF(_, fields, _) => - args - .traverse[EvalM[F, C, *], EVALUATED](evalExpr) - .map(values => CaseObj(t, fields.map(_._1).zip(values).toMap): EVALUATED) + types.get(ctx).get(typeName).collect { case t @ CASETYPEREF(_, fields, _) => + args + .traverse[EvalM[F, C, *], EVALUATED](evalExpr) + .map(values => CaseObj(t, fields.map(_._1).zip(values).toMap): EVALUATED) } case _ => None } @@ -136,8 +133,8 @@ class EvaluatorV1[F[_] : Monad, C[_[_]]](implicit ev: Monad[EvalF[F, *]], ev2: M case LET_BLOCK(let, inner) => evalLetBlock(let, inner) case BLOCK(dec, inner) => dec match { - case l: LET => evalLetBlock(l, inner) - case f: FUNC => evalFuncBlock(f, inner) + case l: LET => evalLetBlock(l, inner) + case f: FUNC => evalFuncBlock(f, inner) case _: FAILED_DEC => raiseError("Attempt to evaluate failed declaration.") } case REF(str) => evalRef(str) @@ -145,7 +142,7 @@ class EvaluatorV1[F[_] : Monad, C[_[_]]](implicit ev: Monad[EvalF[F, *]], ev2: M case IF(cond, t1, t2) => evalIF(cond, t1, t2) case GETTER(expr, field) => evalGetter(expr, field) case FUNCTION_CALL(header, args) => evalFunctionCall(header, args) - case _: FAILED_EXPR => raiseError("Attempt to evaluate failed expression.") + case _: FAILED_EXPR => raiseError("Attempt to evaluate failed expression.") } private def evalExpr(t: EXPR): EvalM[F, C, EVALUATED] = @@ -154,7 +151,7 @@ class EvaluatorV1[F[_] : Monad, C[_[_]]](implicit ev: Monad[EvalF[F, *]], ev2: M def applyWithLogging[A <: EVALUATED](c: EvaluationContext[C, F], expr: EXPR): F[Either[(ExecutionError, Log[F]), (A, Log[F])]] = { val log = ListBuffer[LogItem[F]]() val lec = LoggedEvaluationContext[C, F]((str: String) => (v: LetExecResult[F]) => log.append((str, v)), c) - val r = evalExpr(expr).map(_.asInstanceOf[A]).run(lec).value._2 + val r = evalExpr(expr).map(_.asInstanceOf[A]).run(lec).value._2 r.map(_.bimap((_, log.toList), (_, log.toList))) } 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 a84c7558bf2..0f8ad79c266 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 @@ -9,6 +9,10 @@ import com.wavesplatform.lang.v1.FunctionHeader import com.wavesplatform.lang.v1.compiler.Terms.* import com.wavesplatform.lang.v1.compiler.Types.CASETYPEREF import com.wavesplatform.lang.v1.evaluator.ContextfulNativeFunction.{Extended, Simple} +import com.wavesplatform.lang.v1.evaluator.ContractEvaluator.LogExtraInfo +import com.wavesplatform.lang.v1.evaluator.EvaluatorV2.logFunc +import com.wavesplatform.lang.v1.evaluator.EvaluatorV2.LogKeys.* +import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.Bindings import com.wavesplatform.lang.v1.evaluator.ctx.{EvaluationContext, LoggedEvaluationContext, NativeFunction, UserFunction} import com.wavesplatform.lang.v1.traits.Environment import com.wavesplatform.lang.{CommonError, ExecutionError} @@ -58,9 +62,18 @@ class EvaluatorV2( evaluateUserFunction(fc, limit, name, startArgs) .getOrElse(evaluateConstructor(fc, limit, name)) } - if (newMode) - r.map(unused => if (unused == limit) unused - 1 else unused) - else + + if (newMode) { + r.map { unused => + if (unused == limit) { + if (unused >= 1 && fc.function.isInstanceOf[FunctionHeader.User]) { + ctx.log(LET(s"${fc.function.funcName}.$Complexity", TRUE), CONST_LONG(1).asRight[ExecutionError]) + ctx.log(LET(ComplexityLimit, TRUE), CONST_LONG(unused - 1).asRight[ExecutionError]) + } + unused - 1 + } else unused + } + } else r } @@ -73,17 +86,23 @@ class EvaluatorV2( }) cost = function.costByLibVersion(stdLibVersion).toInt result <- - if (limit < cost) + if (limit < cost) { EvaluationResult(limit) - else + } else doEvaluateNativeFunction(fc, function.asInstanceOf[NativeFunction[Environment]], limit, cost) } yield result def doEvaluateNativeFunction(fc: FUNCTION_CALL, function: NativeFunction[Environment], limit: Int, cost: Int): EvaluationResult[Int] = { val args = fc.args.asInstanceOf[List[EVALUATED]] val evaluation = function.ev match { - case f: Extended[Environment] => f.evaluate[Id](ctx.ec.environment, args, limit - cost) - case f: Simple[Environment] => Coeval((f.evaluate(ctx.ec.environment, args), limit - cost)) + case f: Extended[Environment] => + f.evaluate[Id](ctx.ec.environment, args, limit - cost).map { case (result, unusedComplexity) => + result.map { case (evaluated, log) => + log.foreach { case (logItemName, logItemValue) => ctx.log(LET(logItemName, TRUE), logItemValue) } + evaluated + } -> unusedComplexity + } + case f: Simple[Environment] => Coeval((f.evaluate(ctx.ec.environment, args), limit - cost)) } for { (result, unusedComplexity) <- EvaluationResult( @@ -209,7 +228,7 @@ class EvaluatorV2( update = update, limit = unused - overheadCost, parentBlocks = parentBlocks - ) + ) ) case FALSE if unused > 0 => update(i.ifFalse).flatMap(_ => @@ -218,7 +237,7 @@ class EvaluatorV2( update = update, limit = unused - overheadCost, parentBlocks = parentBlocks - ) + ) ) case _: EVALUATED => EvaluationResult("Non-boolean result in cond", unused) case _ => EvaluationResult(unused) @@ -238,9 +257,10 @@ class EvaluatorV2( evaluateFunctionArgs(fc) .flatMap { unusedArgsComplexity => val argsEvaluated = fc.args.forall(_.isInstanceOf[EVALUATED]) - if (argsEvaluated && unusedArgsComplexity > 0) + if (argsEvaluated && unusedArgsComplexity > 0) { + logFunc(fc, ctx, stdLibVersion, unusedArgsComplexity) evaluateFunction(fc, startArgs, unusedArgsComplexity) - else + } else EvaluationResult(unusedArgsComplexity) } @@ -324,6 +344,7 @@ class EvaluatorV2( object EvaluatorV2 { def applyLimitedCoeval( expr: EXPR, + logExtraInfo: LogExtraInfo, limit: Int, ctx: EvaluationContext[Environment, Id], stdLibVersion: StdLibVersion, @@ -334,6 +355,7 @@ object EvaluatorV2 { val log = ListBuffer[LogItem[Id]]() val loggedCtx = LoggedEvaluationContext[Environment, Id](name => value => log.append((name, value)), ctx) var ref = expr.deepCopy.value + logCall(loggedCtx, logExtraInfo, ref) new EvaluatorV2(loggedCtx, stdLibVersion, correctFunctionCallScope, newMode, checkConstructorArgsTypes) .root(ref, v => EvaluationResult { ref = v }, limit, Nil) .map((ref, _)) @@ -347,6 +369,7 @@ object EvaluatorV2 { def applyOrDefault( ctx: EvaluationContext[Environment, Id], expr: EXPR, + logExtraInfo: LogExtraInfo, stdLibVersion: StdLibVersion, complexityLimit: Int, correctFunctionCallScope: Boolean, @@ -354,7 +377,7 @@ object EvaluatorV2 { handleExpr: EXPR => Either[ExecutionError, EVALUATED] ): (Log[Id], Int, Either[ExecutionError, EVALUATED]) = EvaluatorV2 - .applyLimitedCoeval(expr, complexityLimit, ctx, stdLibVersion, correctFunctionCallScope, newMode) + .applyLimitedCoeval(expr, logExtraInfo, complexityLimit, ctx, stdLibVersion, correctFunctionCallScope, newMode) .value() .fold( { case (error, complexity, log) => (log, complexity, Left(error)) }, @@ -369,6 +392,7 @@ object EvaluatorV2 { def applyCompleted( ctx: EvaluationContext[Environment, Id], expr: EXPR, + logExtraInfo: LogExtraInfo, stdLibVersion: StdLibVersion, correctFunctionCallScope: Boolean, newMode: Boolean @@ -376,10 +400,68 @@ object EvaluatorV2 { applyOrDefault( ctx, expr, + logExtraInfo, stdLibVersion, Int.MaxValue, correctFunctionCallScope, newMode, expr => Left(s"Unexpected incomplete evaluation result $expr") ) + + private def logCall(loggedCtx: LoggedEvaluationContext[Environment, Id], logExtraInfo: LogExtraInfo, exprCopy: EXPR): Unit = { + @tailrec + def findInvArgLet(expr: EXPR, let: LET): Option[LET] = { + expr match { + case BLOCK(res @ LET(let.name, value), _) if value == let.value => Some(res) + case BLOCK(_, body) => findInvArgLet(body, let) + case _ => None + } + } + logExtraInfo.dAppAddress.foreach { addr => + val addrObj = Bindings.senderObject(addr) + loggedCtx.log(LET(InvokedDApp, addrObj), addrObj.asRight[ExecutionError]) + } + + logExtraInfo.invokedFuncName.foreach { funcName => + val invokedFuncName = CONST_STRING(funcName) + invokedFuncName.foreach(name => loggedCtx.log(LET(InvokedFuncName, name), invokedFuncName)) + } + + logExtraInfo.invArg.flatMap(findInvArgLet(exprCopy, _)).foreach { + case let @ LET(_, obj: CaseObj) => loggedCtx.log(let, obj.asRight[ExecutionError]) + case _ => + } + } + + private def logFunc(fc: FUNCTION_CALL, ctx: LoggedEvaluationContext[Environment, Id], stdLibVersion: StdLibVersion, limit: Int): Unit = { + val func = ctx.ec.functions.get(fc.function) + val funcName = func.map(_.name).getOrElse(fc.function.funcName) + func match { + case Some(f) => + val cost = f.costByLibVersion(stdLibVersion) + if (limit >= cost) { + logFuncArgs(fc, funcName, ctx) + ctx.log(LET(s"$funcName.$Complexity", TRUE), CONST_LONG(cost).asRight[ExecutionError]) + ctx.log(LET(ComplexityLimit, TRUE), CONST_LONG(limit - cost).asRight[ExecutionError]) + } + case None => + logFuncArgs(fc, funcName, ctx) + } + } + + private def logFuncArgs(fc: FUNCTION_CALL, name: String, ctx: LoggedEvaluationContext[Environment, Id]): Unit = { + val argsArr = ARR(fc.args.collect { case arg: EVALUATED => arg }.toIndexedSeq, false) + argsArr.foreach(_ => ctx.log(LET(s"$name.$Args", TRUE), argsArr)) + } + + object LogKeys { + val InvokedDApp = "@invokedDApp" + val InvokedFuncName = "@invokedFuncName" + val ComplexityLimit = "@complexityLimit" + val Complexity = "@complexity" + val Args = "@args" + val StateChanges = "@stateChanges" + + val TraceExcluded = Seq(InvokedDApp, InvokedFuncName, StateChanges) + } } diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ScriptResult.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ScriptResult.scala index 41b147e79d6..5d11ddccf88 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ScriptResult.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ScriptResult.scala @@ -36,7 +36,7 @@ object ScriptResult { private def err[A](actual: AnyRef, version: StdLibVersion, expected: String = ""): Either[ExecutionError, A] = Types .callableReturnType(version) - .leftMap(CommonError) + .leftMap(CommonError(_)) .flatMap(t => Left( callableResultError(t, actual, CallableFunction) + (if (expected.isEmpty) "" else s" instead of $expected") @@ -112,7 +112,7 @@ object ScriptResult { recipient <- processRecipient(recipient, ctx, version) address <- recipient match { case a: Address => Right(a) - case Alias(name) => ctx.environment.resolveAlias(name).leftMap(CommonError) + case Alias(name) => ctx.environment.resolveAlias(name).leftMap(CommonError(_)) } } yield AssetTransfer(address, recipient, b, token) case other => diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/EvaluationContext.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/EvaluationContext.scala index 4d3e50e0f3d..c289844d1be 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/EvaluationContext.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/EvaluationContext.scala @@ -1,6 +1,8 @@ package com.wavesplatform.lang.v1.evaluator.ctx -import cats._ +import cats.* +import cats.syntax.functor.* +import com.wavesplatform.lang.ExecutionError import com.wavesplatform.lang.v1.FunctionHeader import com.wavesplatform.lang.v1.compiler.Terms.LET import com.wavesplatform.lang.v1.compiler.Types.FINAL @@ -11,12 +13,12 @@ import shapeless.{Lens, lens} import java.util case class EvaluationContext[C[_[_]], F[_]]( - environment: C[F], - typeDefs : Map[String, FINAL], - letDefs : Map[String, LazyVal[F]], - functions: Map[FunctionHeader, BaseFunction[C]] + environment: C[F], + typeDefs: Map[String, FINAL], + letDefs: Map[String, LazyVal[F]], + functions: Map[FunctionHeader, BaseFunction[C]] ) { - def mapK[G[_] : Monad](f: F ~> G): EvaluationContext[C, G] = + def mapK[G[_]: Monad](f: F ~> G): EvaluationContext[C, G] = EvaluationContext( environment.asInstanceOf[C[G]], typeDefs, @@ -25,18 +27,30 @@ case class EvaluationContext[C[_[_]], F[_]]( ) } -case class LoggedEvaluationContext[C[_[_]], F[_]](l: LetLogCallback[F], ec: EvaluationContext[C, F]) { - val loggedLets: util.IdentityHashMap[LET, Unit] = new util.IdentityHashMap() +case class LoggedEvaluationContext[C[_[_]], F[_]: Monad](l: LetLogCallback[F], ec: EvaluationContext[C, F]) { + val loggedLets: util.IdentityHashMap[LET, Unit] = new util.IdentityHashMap() + val loggedErrors: collection.mutable.Set[ExecutionError] = collection.mutable.Set() - def log(let: LET, result: LetExecResult[F]): Unit = + def log(let: LET, result: LetExecResult[F]): F[Unit] = { + result.map { + case Left(err) if !loggedErrors.contains(err) => + loggedErrors.addOne(err) + add(let, result) + case Left(_) => () + case _ => add(let, result) + } + } + + private def add(let: LET, result: LetExecResult[F]): Unit = loggedLets.computeIfAbsent(let, _ => l(let.name)(result)) } object LoggedEvaluationContext { - class Lenses[F[_], C[_[_]]] { - val types: Lens[LoggedEvaluationContext[C, F], Map[String, FINAL]] = lens[LoggedEvaluationContext[C, F]] >> Symbol("ec") >> Symbol("typeDefs") - val lets: Lens[LoggedEvaluationContext[C, F], Map[String, LazyVal[F]]] = lens[LoggedEvaluationContext[C, F]] >> Symbol("ec") >> Symbol("letDefs") - val funcs: Lens[LoggedEvaluationContext[C, F], Map[FunctionHeader, BaseFunction[C]]] = lens[LoggedEvaluationContext[C, F]] >> Symbol("ec") >> Symbol("functions") + class Lenses[F[_]: Monad, C[_[_]]] { + val types: Lens[LoggedEvaluationContext[C, F], Map[String, FINAL]] = lens[LoggedEvaluationContext[C, F]] >> Symbol("ec") >> Symbol("typeDefs") + val lets: Lens[LoggedEvaluationContext[C, F], Map[String, LazyVal[F]]] = lens[LoggedEvaluationContext[C, F]] >> Symbol("ec") >> Symbol("letDefs") + val funcs: Lens[LoggedEvaluationContext[C, F], Map[FunctionHeader, BaseFunction[C]]] = + lens[LoggedEvaluationContext[C, F]] >> Symbol("ec") >> Symbol("functions") } } @@ -57,10 +71,10 @@ object EvaluationContext { } def build[F[_], C[_[_]]]( - environment: C[F], - typeDefs: Map[String, FINAL], - letDefs: Map[String, LazyVal[F]], - functions: Seq[BaseFunction[C]] + environment: C[F], + typeDefs: Map[String, FINAL], + letDefs: Map[String, LazyVal[F]], + functions: Seq[BaseFunction[C]] ): EvaluationContext[C, F] = { if (functions.distinct.size != functions.size) { val dups = functions.groupBy(_.header).filter(_._2.size != 1) @@ -70,9 +84,9 @@ object EvaluationContext { } def build( - typeDefs: Map[String, FINAL], - letDefs: Map[String, LazyVal[Id]], - functions: Seq[BaseFunction[NoContext]] = Seq() + typeDefs: Map[String, FINAL], + letDefs: Map[String, LazyVal[Id]], + functions: Seq[BaseFunction[NoContext]] = Seq() ): EvaluationContext[NoContext, Id] = { if (functions.distinct.size != functions.size) { val dups = functions.groupBy(_.header).filter(_._2.size != 1) diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/CryptoContext.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/CryptoContext.scala index fdb836504ae..9e500865016 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/CryptoContext.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/CryptoContext.scala @@ -42,9 +42,12 @@ object CryptoContext { ContextfulVal.pure(CaseObj(tpe, Map.empty)) def build(global: BaseGlobal, version: StdLibVersion): CTX[NoContext] = - ctxCache.getOrElse((global, version), ctxCache.synchronized { - ctxCache.getOrElseUpdate((global, version), buildNew(global, version)) - }) + ctxCache.getOrElse( + (global, version), + ctxCache.synchronized { + ctxCache.getOrElseUpdate((global, version), buildNew(global, version)) + } + ) private val ctxCache = mutable.AnyRefMap.empty[(BaseGlobal, StdLibVersion), CTX[NoContext]] @@ -56,11 +59,10 @@ object CryptoContext { returnType: TYPE, args: (String, TYPE)* )(body: (Int, List[EVALUATED]) => Either[ExecutionError, EVALUATED]): Array[BaseFunction[NoContext]] = - costByLimit.mapWithIndex { - case ((limit, cost), i) => - val name = nameByLimit(limit) - val id = (startId + i).toShort - NativeFunction[NoContext](name, cost, id, returnType, args*)(args => body(limit, args)) + costByLimit.mapWithIndex { case ((limit, cost), i) => + val name = nameByLimit(limit) + val id = (startId + i).toShort + NativeFunction[NoContext](name, cost, id, returnType, args*)(args => body(limit, args)) }.toArray def hashFunction(name: String, internalName: Short, cost: Long)(h: Array[Byte] => Array[Byte]): BaseFunction[NoContext] = @@ -235,7 +237,7 @@ object CryptoContext { ) = for { alg <- algFromCO(digestAlg) - result <- global.rsaVerify(alg, msg.arr, sig.arr, pub.arr).leftMap(CommonError) + result <- global.rsaVerify(alg, msg.arr, sig.arr, pub.arr).leftMap(CommonError(_)) } yield CONST_BOOLEAN(result) val rsaVerifyF: BaseFunction[NoContext] = { @@ -284,8 +286,10 @@ object CryptoContext { else Left(s"Invalid message size = ${msg.size} bytes, must be not greater than $limit KB") case (limit, xs) => - notImplemented[Id, EVALUATED](s"rsaVerify_${limit}Kb(digest: DigestAlgorithmType, message: ByteVector, sig: ByteVector, pub: ByteVector)", - xs) + notImplemented[Id, EVALUATED]( + s"rsaVerify_${limit}Kb(digest: DigestAlgorithmType, message: ByteVector, sig: ByteVector, pub: ByteVector)", + xs + ) } def toBase58StringF: BaseFunction[NoContext] = @@ -296,8 +300,9 @@ object CryptoContext { STRING, ("bytes", BYTESTR) ) { - case CONST_BYTESTR(bytes) :: Nil => global.base58Encode(bytes.arr).leftMap(CommonError).flatMap(CONST_STRING(_, reduceLimit = version >= V4)) - case xs => notImplemented[Id, EVALUATED]("toBase58String(bytes: ByteVector)", xs) + case CONST_BYTESTR(bytes) :: Nil => + global.base58Encode(bytes.arr).leftMap(CommonError(_)).flatMap(CONST_STRING(_, reduceLimit = version >= V4)) + case xs => notImplemented[Id, EVALUATED]("toBase58String(bytes: ByteVector)", xs) } def fromBase58StringF: BaseFunction[NoContext] = @@ -308,8 +313,9 @@ object CryptoContext { BYTESTR, ("str", STRING) ) { - case CONST_STRING(str: String) :: Nil => global.base58Decode(str, global.MaxBase58String).leftMap(CommonError).flatMap(x => CONST_BYTESTR(ByteStr(x))) - case xs => notImplemented[Id, EVALUATED]("fromBase58String(str: String)", xs) + case CONST_STRING(str: String) :: Nil => + global.base58Decode(str, global.MaxBase58String).leftMap(CommonError(_)).flatMap(x => CONST_BYTESTR(ByteStr(x))) + case xs => notImplemented[Id, EVALUATED]("fromBase58String(str: String)", xs) } def toBase64StringF: BaseFunction[NoContext] = @@ -320,8 +326,9 @@ object CryptoContext { STRING, ("bytes", BYTESTR) ) { - case CONST_BYTESTR(bytes) :: Nil => global.base64Encode(bytes.arr).leftMap(CommonError).flatMap(CONST_STRING(_, reduceLimit = version >= V4)) - case xs => notImplemented[Id, EVALUATED]("toBase64String(bytes: ByteVector)", xs) + case CONST_BYTESTR(bytes) :: Nil => + global.base64Encode(bytes.arr).leftMap(CommonError(_)).flatMap(CONST_STRING(_, reduceLimit = version >= V4)) + case xs => notImplemented[Id, EVALUATED]("toBase64String(bytes: ByteVector)", xs) } def fromBase64StringF: BaseFunction[NoContext] = @@ -332,8 +339,9 @@ object CryptoContext { BYTESTR, ("str", STRING) ) { - case CONST_STRING(str: String) :: Nil => global.base64Decode(str, global.MaxBase64String).leftMap(CommonError).flatMap(x => CONST_BYTESTR(ByteStr(x))) - case xs => notImplemented[Id, EVALUATED]("fromBase64String(str: String)", xs) + case CONST_STRING(str: String) :: Nil => + global.base64Decode(str, global.MaxBase64String).leftMap(CommonError(_)).flatMap(x => CONST_BYTESTR(ByteStr(x))) + case xs => notImplemented[Id, EVALUATED]("fromBase64String(str: String)", xs) } val checkMerkleProofF: BaseFunction[NoContext] = @@ -375,13 +383,13 @@ object CryptoContext { } def toBase16StringF(checkLength: Boolean): BaseFunction[NoContext] = NativeFunction("toBase16String", 10, TOBASE16, STRING, ("bytes", BYTESTR)) { - case CONST_BYTESTR(bytes) :: Nil => global.base16Encode(bytes.arr, checkLength).leftMap(CommonError).flatMap(CONST_STRING(_)) + case CONST_BYTESTR(bytes) :: Nil => global.base16Encode(bytes.arr, checkLength).leftMap(CommonError(_)).flatMap(CONST_STRING(_)) case xs => notImplemented[Id, EVALUATED]("toBase16String(bytes: ByteVector)", xs) } def fromBase16StringF(checkLength: Boolean): BaseFunction[NoContext] = NativeFunction("fromBase16String", 10, FROMBASE16, BYTESTR, ("str", STRING)) { - case CONST_STRING(str: String) :: Nil => global.base16Decode(str, checkLength).leftMap(CommonError).flatMap(x => CONST_BYTESTR(ByteStr(x))) + case CONST_STRING(str: String) :: Nil => global.base16Decode(str, checkLength).leftMap(CommonError(_)).flatMap(x => CONST_BYTESTR(ByteStr(x))) case xs => notImplemented[Id, EVALUATED]("fromBase16String(str: String)", xs) } diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/EnvironmentFunctions.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/EnvironmentFunctions.scala index fb5abbbcd5f..a13a3e29cfa 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/EnvironmentFunctions.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/EnvironmentFunctions.scala @@ -31,15 +31,15 @@ class EnvironmentFunctions[F[_]: Monad](environment: Environment[F]) { } def getData(addressOrAlias: CaseObj, key: String, dataType: DataType): F[Either[ExecutionError, Option[Any]]] = { - toScala(addressOrAlias).leftMap(CommonError).traverse(environment.data(_, key, dataType)) + toScala(addressOrAlias).leftMap(CommonError(_)).traverse(environment.data(_, key, dataType)) } def hasData(addressOrAlias: CaseObj): F[Either[ExecutionError, Boolean]] = { - toScala(addressOrAlias).leftMap(CommonError).traverse(environment.hasData) + toScala(addressOrAlias).leftMap(CommonError(_)).traverse(environment.hasData) } def addressFromAlias(name: String): F[Either[ExecutionError, Recipient.Address]] = - environment.resolveAlias(name).map(_.leftMap(CommonError)) + environment.resolveAlias(name).map(_.leftMap(CommonError(_))) } object EnvironmentFunctions { diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala index 391ec39bf27..9514757792b 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala @@ -148,7 +148,7 @@ object PureContext { .cond(n.length <= 155, BigInt(n), s"String too long for 512-bits big integers (${n.length} when max is 155)") .filterOrElse(v => v <= BigIntMax && v >= BigIntMin, "Value too big for 512-bits big integer") .map(CONST_BIGINT.apply) - .leftMap(CommonError) + .leftMap(CommonError(_)) case xs => notImplemented[Id, EVALUATED]("parseBigIntValue(n: String)", xs) } @@ -214,7 +214,7 @@ object PureContext { s"$a ${op.func} $b is out of range." ) .map(CONST_BIGINT) - .leftMap(CommonError) + .leftMap(CommonError(_)) case args => Left(s"Unexpected args $args for BigInt operator '${op.func}'") } @@ -363,7 +363,7 @@ object PureContext { _ <- Either.cond(checkMax(result), (), s"Long overflow: value `$result` greater than 2^63-1") _ <- Either.cond(checkMin(result), (), s"Long overflow: value `$result` less than -2^63-1") } yield CONST_LONG(result.toLong) - r.leftMap(CommonError) + r.leftMap(CommonError(_)) case xs => notImplemented[Id, EVALUATED]("fraction(value: Int, numerator: Int, denominator: Int)", xs) } @@ -406,7 +406,7 @@ object PureContext { division <- global.divide(BigInt(v) * BigInt(n), d, Rounding.byValue(round)) _ <- Either.cond(division.isValidLong, (), s"Fraction result $division out of integers range") } yield CONST_LONG(division.longValue) - r.leftMap(CommonError) + r.leftMap(CommonError(_)) case xs => notImplemented[Id, EVALUATED]( "fraction(value: Int, numerator: Int, denominator: Int, round: Ceiling|Down|Floor|HalfEven|HalfUp)", @@ -431,7 +431,7 @@ object PureContext { _ <- Either.cond(result <= BigIntMax, (), s"Long overflow: value `$result` greater than 2^511-1") _ <- Either.cond(result >= BigIntMin, (), s"Long overflow: value `$result` less than -2^511") } yield CONST_BIGINT(result) - r.leftMap(CommonError) + r.leftMap(CommonError(_)) case xs => notImplemented[Id, EVALUATED]("fraction(value: BigInt, numerator: BigInt, denominator: BigInt)", xs) } @@ -453,7 +453,7 @@ object PureContext { _ <- Either.cond(r <= BigIntMax, (), s"Long overflow: value `$r` greater than 2^511-1") _ <- Either.cond(r >= BigIntMin, (), s"Long overflow: value `$r` less than -2^511") } yield CONST_BIGINT(r) - r.leftMap(CommonError) + r.leftMap(CommonError(_)) case xs => notImplemented[Id, EVALUATED]( "fraction(value: BigInt, numerator: BigInt, denominator: BigInt, round: Ceiling|Down|Floor|HalfEven|HalfUp)", @@ -1564,7 +1564,7 @@ object PureContext { ) { Left("pow: scale out of range 0-8") } else { - global.pow(b, bp.toInt, e, ep.toInt, rp.toInt, Rounding.byValue(round), useNewPrecision).map(CONST_LONG).leftMap(CommonError) + global.pow(b, bp.toInt, e, ep.toInt, rp.toInt, Rounding.byValue(round), useNewPrecision).map(CONST_LONG).leftMap(CommonError(_)) } case xs => notImplemented[Id, EVALUATED]("pow(base: Int, bp: Int, exponent: Int, ep: Int, rp: Int, round: Rounds)", xs) } @@ -1598,7 +1598,7 @@ object PureContext { ) { Left(CommonError("log: scale out of range 0-8")) } else { - global.log(b, bp, e, ep, rp, Rounding.byValue(round)).map(CONST_LONG).leftMap(CommonError) + global.log(b, bp, e, ep, rp, Rounding.byValue(round)).map(CONST_LONG).leftMap(CommonError(_)) } case xs => notImplemented[Id, EVALUATED]("log(exponent: Int, ep: Int, base: Int, bp: Int, rp: Int, round: Rounds)", xs) } 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 e6700bdbc9d..230a9351c29 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 @@ -16,7 +16,7 @@ import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.Bindings.{scriptTransf import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.Types.* import com.wavesplatform.lang.v1.evaluator.ctx.impl.{EnvironmentFunctions, PureContext, notImplemented, unit} import com.wavesplatform.lang.v1.evaluator.ctx.{BaseFunction, NativeFunction, UserFunction} -import com.wavesplatform.lang.v1.evaluator.{ContextfulNativeFunction, ContextfulUserFunction, FunctionIds} +import com.wavesplatform.lang.v1.evaluator.{ContextfulNativeFunction, ContextfulUserFunction, FunctionIds, Log} import com.wavesplatform.lang.v1.traits.domain.{Issue, Lease, Recipient} import com.wavesplatform.lang.v1.traits.{DataType, Environment} import com.wavesplatform.lang.v1.{BaseGlobal, FunctionHeader} @@ -433,9 +433,9 @@ object Functions { override def evaluate[F[_]: Monad](env: Environment[F], args: List[EVALUATED]): F[Either[ExecutionError, EVALUATED]] = args match { case (c: CaseObj) :: u :: Nil if u == unit => - env.accountBalanceOf(caseObjToRecipient(c), None).map(_.map(CONST_LONG).leftMap(CommonError)) + env.accountBalanceOf(caseObjToRecipient(c), None).map(_.map(CONST_LONG).leftMap(CommonError(_))) case (c: CaseObj) :: CONST_BYTESTR(assetId: ByteStr) :: Nil => - env.accountBalanceOf(caseObjToRecipient(c), Some(assetId.arr)).map(_.map(CONST_LONG).leftMap(CommonError)) + env.accountBalanceOf(caseObjToRecipient(c), Some(assetId.arr)).map(_.map(CONST_LONG).leftMap(CommonError(_))) case xs => notImplemented[F, EVALUATED](s"assetBalance(a: Address|Alias, u: ByteVector|Unit)", xs) } @@ -455,7 +455,7 @@ object Functions { override def evaluate[F[_]: Monad](env: Environment[F], args: List[EVALUATED]): F[Either[ExecutionError, EVALUATED]] = args match { case (c: CaseObj) :: CONST_BYTESTR(assetId: ByteStr) :: Nil => - env.accountBalanceOf(caseObjToRecipient(c), Some(assetId.arr)).map(_.map(CONST_LONG).leftMap(CommonError)) + env.accountBalanceOf(caseObjToRecipient(c), Some(assetId.arr)).map(_.map(CONST_LONG).leftMap(CommonError(_))) case xs => notImplemented[F, EVALUATED](s"assetBalance(a: Address|Alias, u: ByteVector)", xs) } @@ -487,7 +487,7 @@ object Functions { "effective" -> CONST_LONG(b.effective) ) ) - ).leftMap(CommonError) + ).leftMap(CommonError(_)) ) case xs => notImplemented[F, EVALUATED](s"wavesBalance(a: Address|Alias)", xs) @@ -593,7 +593,7 @@ object Functions { env: Environment[F], args: List[EVALUATED], availableComplexity: Int - )(implicit m: Monad[CoevalF[F, *]]): Coeval[F[(Either[ExecutionError, EVALUATED], Int)]] = { + )(implicit m: Monad[CoevalF[F, *]]): Coeval[F[(Either[ExecutionError, (EVALUATED, Log[F])], Int)]] = { val dAppBytes = args match { case (dApp: CaseObj) :: _ if dApp.caseType == addressType => dApp.fields("bytes") match { @@ -632,12 +632,13 @@ object Functions { .map(_.map { case (result, spentComplexity) => val mappedError = result.leftMap { case reject: FailOrRejectError => reject - case other => CommonError(other.toString) + case other => CommonError("Nested invoke error", Some(other)) } (mappedError, availableComplexity - spentComplexity) }) case xs => - val err = notImplemented[F, EVALUATED](s"invoke(dApp: Address, function: String, args: List[Any], payments: List[Payment])", xs) + val err = + notImplemented[F, (EVALUATED, Log[F])](s"invoke(dApp: Address, function: String, args: List[Any], payments: List[Payment])", xs) Coeval.now(err.map((_, 0))) } } diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/traits/Environment.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/traits/Environment.scala index da675ba7c4c..e4d86ed6ae5 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/traits/Environment.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/traits/Environment.scala @@ -4,10 +4,11 @@ import com.wavesplatform.common.state.ByteStr import com.wavesplatform.lang.ValidationError import com.wavesplatform.lang.script.Script import com.wavesplatform.lang.v1.compiler.Terms.EVALUATED +import com.wavesplatform.lang.v1.evaluator.Log import com.wavesplatform.lang.v1.traits.domain.Recipient.Address -import com.wavesplatform.lang.v1.traits.domain._ +import com.wavesplatform.lang.v1.traits.domain.* import monix.eval.Coeval -import shapeless._ +import shapeless.* object Environment { case class BalanceDetails(available: Long, regular: Long, generating: Long, effective: Long) @@ -48,5 +49,5 @@ trait Environment[F[_]] { payments: Seq[(Option[Array[Byte]], Long)], availableComplexity: Int, reentrant: Boolean - ): Coeval[F[(Either[ValidationError, EVALUATED], Int)]] + ): Coeval[F[(Either[ValidationError, (EVALUATED, Log[F])], Int)]] } diff --git a/lang/testkit/src/main/scala/com/wavesplatform/lang/Common.scala b/lang/testkit/src/main/scala/com/wavesplatform/lang/Common.scala index 3061b533af0..cee0a77dffa 100644 --- a/lang/testkit/src/main/scala/com/wavesplatform/lang/Common.scala +++ b/lang/testkit/src/main/scala/com/wavesplatform/lang/Common.scala @@ -9,7 +9,7 @@ import com.wavesplatform.lang.v1.CTX import com.wavesplatform.lang.v1.compiler.Terms.* import com.wavesplatform.lang.v1.compiler.Types.* import com.wavesplatform.lang.v1.evaluator.Contextful.NoContext -import com.wavesplatform.lang.v1.evaluator.EvaluatorV1 +import com.wavesplatform.lang.v1.evaluator.{EvaluatorV1, Log} import com.wavesplatform.lang.v1.evaluator.EvaluatorV1.* import com.wavesplatform.lang.v1.evaluator.ctx.* import com.wavesplatform.lang.v1.evaluator.ctx.impl.{EnvironmentFunctions, PureContext, *} @@ -42,8 +42,8 @@ object Common { case _ => ??? // suppress pattern match warning } - val pointTypeA = CASETYPEREF("PointA", List("X" -> LONG, "YA" -> LONG)) - val pointTypeB = CASETYPEREF("PointB", List("X" -> LONG, "YB" -> LONG)) + val pointTypeA = CASETYPEREF("PointA", List("X" -> LONG, "YA" -> LONG)) + val pointTypeB = CASETYPEREF("PointB", List("X" -> LONG, "YB" -> LONG)) val pointTypeC = CASETYPEREF("PointC", List("YB" -> LONG)) val pointTypeD = CASETYPEREF("PointD", List("YB" -> UNION(LONG, UNIT))) @@ -52,8 +52,8 @@ object Common { val BorC = UNION(pointTypeB, pointTypeC) val CorD = UNION(pointTypeC, pointTypeD) - val pointAInstance = CaseObj(pointTypeA, Map("X" -> 3L, "YA" -> 40L)) - val pointBInstance = CaseObj(pointTypeB, Map("X" -> 3L, "YB" -> 41L)) + val pointAInstance = CaseObj(pointTypeA, Map("X" -> 3L, "YA" -> 40L)) + val pointBInstance = CaseObj(pointTypeB, Map("X" -> 3L, "YB" -> 41L)) val pointCInstance = CaseObj(pointTypeC, Map("YB" -> 42L)) val pointDInstance1 = CaseObj(pointTypeD, Map("YB" -> 43L)) @@ -72,30 +72,38 @@ object Common { Seq.empty[BaseFunction[NoContext]] ) - def emptyBlockchainEnvironment(h: Int = 1, in: Coeval[Environment.InputEntity] = Coeval(???), nByte: Byte = 'T'): Environment[Id] = new Environment[Id] { - override def height: Long = h - override def chainId: Byte = nByte - override def inputEntity = in() - - override def transactionById(id: Array[Byte]): Option[Tx] = ??? - override def transferTransactionById(id: Array[Byte]): Option[Tx.Transfer] = ??? - override def transactionHeightById(id: Array[Byte]): Option[Long] = ??? - override def assetInfoById(id: Array[Byte]): Option[ScriptAssetInfo] = ??? - override def lastBlockOpt(): Option[BlockInfo] = ??? - override def blockInfoByHeight(height: Int): Option[BlockInfo] = ??? - override def data(recipient: Recipient, key: String, dataType: DataType): Option[Any] = None - override def hasData(recipient: Recipient): Boolean = false - override def resolveAlias(name: String): Either[String, Recipient.Address] = ??? - override def accountBalanceOf(addressOrAlias: Recipient, assetId: Option[Array[Byte]]): Either[String, Long] = ??? - override def accountWavesBalanceOf(addressOrAlias: Recipient): Either[String, Environment.BalanceDetails] = ??? - override def tthis: Environment.Tthis = Coproduct(Address(ByteStr.empty)) - override def multiPaymentAllowed: Boolean = true - override def txId: ByteStr = ??? - override def transferTransactionFromProto(b: Array[Byte]): Option[Tx.Transfer] = ??? - override def addressFromString(address: String): Either[String, Recipient.Address] = ??? - override def addressFromPublicKey(publicKey: ByteStr): Either[String, Address] = ??? - def accountScript(addressOrAlias: Recipient): Option[Script] = ??? - override def callScript(dApp: Address, func: String, args: List[EVALUATED], payments: Seq[(Option[Array[Byte]], Long)], remainingComplexity: Int, reentrant: Boolean): Coeval[(Either[ValidationError, EVALUATED], Int)] = ??? + def emptyBlockchainEnvironment(h: Int = 1, in: Coeval[Environment.InputEntity] = Coeval(???), nByte: Byte = 'T'): Environment[Id] = + new Environment[Id] { + override def height: Long = h + override def chainId: Byte = nByte + override def inputEntity = in() + + override def transactionById(id: Array[Byte]): Option[Tx] = ??? + override def transferTransactionById(id: Array[Byte]): Option[Tx.Transfer] = ??? + override def transactionHeightById(id: Array[Byte]): Option[Long] = ??? + override def assetInfoById(id: Array[Byte]): Option[ScriptAssetInfo] = ??? + override def lastBlockOpt(): Option[BlockInfo] = ??? + override def blockInfoByHeight(height: Int): Option[BlockInfo] = ??? + override def data(recipient: Recipient, key: String, dataType: DataType): Option[Any] = None + override def hasData(recipient: Recipient): Boolean = false + override def resolveAlias(name: String): Either[String, Recipient.Address] = ??? + override def accountBalanceOf(addressOrAlias: Recipient, assetId: Option[Array[Byte]]): Either[String, Long] = ??? + override def accountWavesBalanceOf(addressOrAlias: Recipient): Either[String, Environment.BalanceDetails] = ??? + override def tthis: Environment.Tthis = Coproduct(Address(ByteStr.empty)) + override def multiPaymentAllowed: Boolean = true + override def txId: ByteStr = ??? + override def transferTransactionFromProto(b: Array[Byte]): Option[Tx.Transfer] = ??? + override def addressFromString(address: String): Either[String, Recipient.Address] = ??? + override def addressFromPublicKey(publicKey: ByteStr): Either[String, Address] = ??? + def accountScript(addressOrAlias: Recipient): Option[Script] = ??? + override def callScript( + dApp: Address, + func: String, + args: List[EVALUATED], + payments: Seq[(Option[Array[Byte]], Long)], + remainingComplexity: Int, + reentrant: Boolean + ): Coeval[(Either[ValidationError, (EVALUATED, Log[Id])], Int)] = ??? } def addressFromPublicKey(chainId: Byte, pk: Array[Byte], addressVersion: Byte = EnvironmentFunctions.AddressVersion): Array[Byte] = { @@ -118,7 +126,9 @@ object Common { checkSum sameElements checkSumGenerated } - if (version == EnvironmentFunctions.AddressVersion && network == chainId && addressBytes.length == EnvironmentFunctions.AddressLength && checksumCorrect) + if ( + version == EnvironmentFunctions.AddressVersion && network == chainId && addressBytes.length == EnvironmentFunctions.AddressLength && checksumCorrect + ) Right(Some(addressBytes)) else Right(None) } diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/ContractIntegrationTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/ContractIntegrationTest.scala index 7d454baf3aa..d2d588951db 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/ContractIntegrationTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/ContractIntegrationTest.scala @@ -122,14 +122,13 @@ class ContractIntegrationTest extends PropSpec with Inside { "foo", args = Nil ) - inside(evalResult) { - case Left((CommonError(error), log)) => - error shouldBe "exception message" - log should contain.allOf( - ("a", Right(CONST_LONG(1))), - ("b", Right(CONST_LONG(2))), - ("isError", Right(TRUE)) - ) + inside(evalResult) { case Left((err @ CommonError(_, _), log)) => + err.message shouldBe "exception message" + log should contain.allOf( + ("a", Right(CONST_LONG(1))), + ("b", Right(CONST_LONG(2))), + ("isError", Right(TRUE)) + ) } } @@ -159,6 +158,7 @@ class ContractIntegrationTest extends PropSpec with Inside { .applyV2Coeval( ctx.evaluationContext(environment), compiled, + ByteStr.fill(32)(1), Invocation( Terms.FUNCTION_CALL(FunctionHeader.User(func), args), Recipient.Address(callerAddress), @@ -187,7 +187,7 @@ class ContractIntegrationTest extends PropSpec with Inside { .verify( compiled.decs, compiled.verifierFuncOpt.get, - EvaluatorV2.applyCompleted(ctx.evaluationContext(environment), _, V3, correctFunctionCallScope = true, newMode = false), + EvaluatorV2.applyCompleted(ctx.evaluationContext(environment), _, _, V3, correctFunctionCallScope = true, newMode = false), txObject ) ._3 diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala index a3992c3e20b..74b82644231 100755 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala @@ -15,6 +15,7 @@ import com.wavesplatform.lang.v1.compiler.Terms.* import com.wavesplatform.lang.v1.compiler.Types.{BYTESTR, FINAL, LONG} import com.wavesplatform.lang.v1.compiler.{ExpressionCompiler, Terms} import com.wavesplatform.lang.v1.evaluator.Contextful.NoContext +import com.wavesplatform.lang.v1.evaluator.ContractEvaluator.LogExtraInfo import com.wavesplatform.lang.v1.evaluator.ctx.* import com.wavesplatform.lang.v1.evaluator.ctx.impl.* import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext.MaxListLengthV4 @@ -93,7 +94,10 @@ class IntegrationTest extends PropSpec with Inside { val compiled = ExpressionCompiler(ctx.compilerContext, untyped) val evalCtx = ctx.evaluationContext(env).asInstanceOf[EvaluationContext[Environment, Id]] compiled.flatMap(v => - EvaluatorV2.applyCompleted(evalCtx, v._1, version, correctFunctionCallScope = true, newMode = true)._3.bimap(_.message, _.asInstanceOf[T]) + EvaluatorV2 + .applyCompleted(evalCtx, v._1, LogExtraInfo(), version, correctFunctionCallScope = true, newMode = true) + ._3 + .bimap(_.message, _.asInstanceOf[T]) ) } diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorSpec.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorSpec.scala index fe6cd14c0f1..85d4b2674c5 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorSpec.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorSpec.scala @@ -9,6 +9,7 @@ import com.wavesplatform.lang.directives.values.* import com.wavesplatform.lang.utils.lazyContexts import com.wavesplatform.lang.v1.compiler.ExpressionCompiler import com.wavesplatform.lang.v1.compiler.Terms.{EVALUATED, EXPR} +import com.wavesplatform.lang.v1.evaluator.ContractEvaluator.LogExtraInfo import com.wavesplatform.lang.v1.evaluator.{EvaluatorV2, Log} import com.wavesplatform.lang.v1.parser.Parser import com.wavesplatform.lang.v1.testing.ScriptGen @@ -74,7 +75,7 @@ abstract class EvaluatorSpec extends PropSpec with ScriptGen with Inside { private def evalExpr(expr: EXPR, version: StdLibVersion, useNewPowPrecision: Boolean): (Log[Id], Int, Either[ExecutionError, EVALUATED]) = { val ctx = lazyContexts(DirectiveSet(version, Account, Expression).explicitGet() -> useNewPowPrecision).value() val evalCtx = ctx.evaluationContext(Common.emptyBlockchainEnvironment()) - EvaluatorV2.applyCompleted(evalCtx, expr, version, correctFunctionCallScope = true, newMode = true) + EvaluatorV2.applyCompleted(evalCtx, expr, LogExtraInfo(), version, correctFunctionCallScope = true, newMode = true) } def compile(code: String, version: StdLibVersion): Either[String, EXPR] = { diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorV1V2Test.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorV1V2Test.scala index c4654395550..1d1cf1d9496 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorV1V2Test.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorV1V2Test.scala @@ -1,7 +1,6 @@ package com.wavesplatform.lang.evaluator import java.nio.ByteBuffer - import cats.Id import cats.data.EitherT import cats.kernel.Monoid @@ -19,6 +18,7 @@ import com.wavesplatform.lang.v1.compiler.ExpressionCompiler import com.wavesplatform.lang.v1.compiler.Terms.* import com.wavesplatform.lang.v1.compiler.Types.* import com.wavesplatform.lang.v1.evaluator.Contextful.NoContext +import com.wavesplatform.lang.v1.evaluator.ContractEvaluator.LogExtraInfo import com.wavesplatform.lang.v1.evaluator.EvaluatorV1.* import com.wavesplatform.lang.v1.evaluator.FunctionIds.* import com.wavesplatform.lang.v1.evaluator.ctx.* @@ -65,7 +65,9 @@ class EvaluatorV1V2Test extends PropSpec with EitherValues { defaultEvaluator[T](context, expr) private def evalV2[T <: EVALUATED](context: EvaluationContext[Environment, Id], expr: EXPR): Either[ExecutionError, T] = - EvaluatorV2.applyCompleted(context, expr, implicitly[StdLibVersion], correctFunctionCallScope = true, newMode = true)._3 + EvaluatorV2 + .applyCompleted(context, expr, LogExtraInfo(), implicitly[StdLibVersion], correctFunctionCallScope = true, newMode = true) + ._3 .asInstanceOf[Either[ExecutionError, T]] private def eval[T <: EVALUATED](context: EvaluationContext[Environment, Id], expr: EXPR): Either[ExecutionError, T] = { @@ -80,10 +82,19 @@ class EvaluatorV1V2Test extends PropSpec with EitherValues { eval[T](context.asInstanceOf[EvaluationContext[Environment, Id]], expr) private def evalWithLogging(context: EvaluationContext[Environment, Id], expr: EXPR): Either[(ExecutionError, Log[Id]), (EVALUATED, Log[Id])] = { - val evaluatorV1Result = defaultEvaluator.applyWithLogging[EVALUATED](context, expr) - val (evaluatorV2Log, _, evaluatorV2Result) = EvaluatorV2.applyCompleted(context, expr, implicitly[StdLibVersion], correctFunctionCallScope = true, newMode = true) + val evaluatorV1Result = defaultEvaluator.applyWithLogging[EVALUATED](context, expr) + val (evaluatorV2Log, _, evaluatorV2Result) = + EvaluatorV2.applyCompleted( + context, + expr, + LogExtraInfo(), + implicitly[StdLibVersion], + correctFunctionCallScope = true, + newMode = true + ) - evaluatorV2Result.bimap((_, evaluatorV2Log), (_, evaluatorV2Log)) shouldBe evaluatorV1Result + evaluatorV2Result shouldBe evaluatorV1Result.bimap(_._1, _._1) + evaluatorV2Log should contain allElementsOf evaluatorV1Result.fold(_._2, _._2) evaluatorV1Result } @@ -206,7 +217,7 @@ class EvaluatorV1V2Test extends PropSpec with EitherValues { property("custom type field access") { val pointType = CASETYPEREF("Point", List("X" -> LONG, "Y" -> LONG)) - val pointInstance = CaseObj(pointType, Map("X" -> 3L, "Y" -> 4L)) + val pointInstance = CaseObj(pointType, Map("X" -> 3L, "Y" -> 4L)) evalPure[EVALUATED]( context = Monoid.combine( pureEvalContext, @@ -258,10 +269,9 @@ class EvaluatorV1V2Test extends PropSpec with EitherValues { forAll(blockBuilder) { block => var functionEvaluated = 0 - val f = NativeFunction[NoContext]("F", 1: Long, 258: Short, LONG: TYPE, Seq(("_", LONG))*) { - _ => - functionEvaluated = functionEvaluated + 1 - evaluated(1L) + val f = NativeFunction[NoContext]("F", 1: Long, 258: Short, LONG: TYPE, Seq(("_", LONG))*) { _ => + functionEvaluated = functionEvaluated + 1 + evaluated(1L) } val context = Monoid @@ -305,7 +315,8 @@ class EvaluatorV1V2Test extends PropSpec with EitherValues { property("successful on function call getter evaluation") { val fooType = CASETYPEREF("Foo", List(("bar", STRING), ("buz", LONG))) val fooCtor = NativeFunction[NoContext]("createFoo", 1: Long, 259: Short, fooType, List.empty*)(_ => - evaluated(CaseObj(fooType, Map("bar" -> "bAr", "buz" -> 1L)))) + evaluated(CaseObj(fooType, Map("bar" -> "bAr", "buz" -> 1L))) + ) val context = EvaluationContext.build( typeDefs = Map.empty, @@ -320,17 +331,16 @@ class EvaluatorV1V2Test extends PropSpec with EitherValues { property("successful on block getter evaluation") { val fooType = CASETYPEREF("Foo", List(("bar", STRING), ("buz", LONG))) - val fooCtor = NativeFunction[NoContext]("createFoo", 1: Long, 259: Short, fooType, List.empty*) { - _ => - evaluated( - CaseObj( - fooType, - Map( - "bar" -> "bAr", - "buz" -> 1L - ) + val fooCtor = NativeFunction[NoContext]("createFoo", 1: Long, 259: Short, fooType, List.empty*) { _ => + evaluated( + CaseObj( + fooType, + Map( + "bar" -> "bAr", + "buz" -> 1L ) ) + ) } val fooTransform = NativeFunction[NoContext]("transformFoo", 1: Long, 260: Short, fooType, ("foo", fooType)) { @@ -424,7 +434,7 @@ class EvaluatorV1V2Test extends PropSpec with EitherValues { result shouldBe false - //it false, because script fails on Alice's signature check, and bobSigned is not evaluated + // it false, because script fails on Alice's signature check, and bobSigned is not evaluated log.find(_._1 == "bobSigned") shouldBe None log.find(_._1 == "aliceSigned") shouldBe Some(("aliceSigned", evaluated(false))) } @@ -444,54 +454,50 @@ class EvaluatorV1V2Test extends PropSpec with EitherValues { } yield (ByteStr(xs), number) property("drop(ByteStr, Long) works as the native one") { - forAll(genBytesAndNumber) { - case (xs, number) => - val expr = FUNCTION_CALL(Native(FunctionIds.DROP_BYTES), List(CONST_BYTESTR(xs).explicitGet(), CONST_LONG(number))) - val actual = evalPure[EVALUATED](pureEvalContext, expr) - actual shouldBe evaluated(xs.drop(number)) + forAll(genBytesAndNumber) { case (xs, number) => + val expr = FUNCTION_CALL(Native(FunctionIds.DROP_BYTES), List(CONST_BYTESTR(xs).explicitGet(), CONST_LONG(number))) + val actual = evalPure[EVALUATED](pureEvalContext, expr) + actual shouldBe evaluated(xs.drop(number)) } } property("take(ByteStr, Long) works as the native one") { - forAll(genBytesAndNumber) { - case (xs, number) => - val expr = FUNCTION_CALL(FunctionHeader.Native(TAKE_BYTES), List(CONST_BYTESTR(xs).explicitGet(), CONST_LONG(number))) - val actual = evalPure[EVALUATED](pureEvalContext, expr) - actual shouldBe evaluated(xs.take(number)) + forAll(genBytesAndNumber) { case (xs, number) => + val expr = FUNCTION_CALL(FunctionHeader.Native(TAKE_BYTES), List(CONST_BYTESTR(xs).explicitGet(), CONST_LONG(number))) + val actual = evalPure[EVALUATED](pureEvalContext, expr) + actual shouldBe evaluated(xs.take(number)) } } property("dropRightBytes(ByteStr, Long) works as the native one") { - forAll(genBytesAndNumber) { - case (xs, number) => - val expr = FUNCTION_CALL(Native(FunctionIds.DROP_RIGHT_BYTES), List(CONST_BYTESTR(xs).explicitGet(), CONST_LONG(number))) - val actual = evalPure[EVALUATED](pureContext(V6).evaluationContext, expr).leftMap(_.message) - val limit = 165947 - actual shouldBe ( - if (number < 0) - Left(s"Unexpected negative number = $number passed to dropRight()") - else if (number > limit) - Left(s"Number = $number passed to dropRight() exceeds ByteVector limit = $limit") - else - evaluated(xs.dropRight(number)) - ) + forAll(genBytesAndNumber) { case (xs, number) => + val expr = FUNCTION_CALL(Native(FunctionIds.DROP_RIGHT_BYTES), List(CONST_BYTESTR(xs).explicitGet(), CONST_LONG(number))) + val actual = evalPure[EVALUATED](pureContext(V6).evaluationContext, expr).leftMap(_.message) + val limit = 165947 + actual shouldBe ( + if (number < 0) + Left(s"Unexpected negative number = $number passed to dropRight()") + else if (number > limit) + Left(s"Number = $number passed to dropRight() exceeds ByteVector limit = $limit") + else + evaluated(xs.dropRight(number)) + ) } } property("takeRightBytes(ByteStr, Long) works as the native one") { - forAll(genBytesAndNumber) { - case (xs, number) => - val expr = FUNCTION_CALL(Native(FunctionIds.TAKE_RIGHT_BYTES), List(CONST_BYTESTR(xs).explicitGet(), CONST_LONG(number))) - val actual = evalPure[EVALUATED](pureContext(V6).evaluationContext, expr).leftMap(_.message) - val limit = 165947 - actual shouldBe ( - if (number < 0) - Left(s"Unexpected negative number = $number passed to takeRight()") - else if (number > limit) - Left(s"Number = $number passed to takeRight() exceeds ByteVector limit = $limit") - else - evaluated(xs.takeRight(number)) - ) + forAll(genBytesAndNumber) { case (xs, number) => + val expr = FUNCTION_CALL(Native(FunctionIds.TAKE_RIGHT_BYTES), List(CONST_BYTESTR(xs).explicitGet(), CONST_LONG(number))) + val actual = evalPure[EVALUATED](pureContext(V6).evaluationContext, expr).leftMap(_.message) + val limit = 165947 + actual shouldBe ( + if (number < 0) + Left(s"Unexpected negative number = $number passed to takeRight()") + else if (number > limit) + Left(s"Number = $number passed to takeRight() exceeds ByteVector limit = $limit") + else + evaluated(xs.takeRight(number)) + ) } } @@ -501,38 +507,34 @@ class EvaluatorV1V2Test extends PropSpec with EitherValues { } yield (xs, number) property("drop(String, Long) works as the native one") { - forAll(genStringAndNumber) { - case (xs, number) => - val expr = FUNCTION_CALL(FunctionHeader.Native(DROP_STRING), List(CONST_STRING(xs).explicitGet(), CONST_LONG(number))) - val actual = evalPure[EVALUATED](pureEvalContext, expr) - actual shouldBe evaluated(xs.drop(number)) + forAll(genStringAndNumber) { case (xs, number) => + val expr = FUNCTION_CALL(FunctionHeader.Native(DROP_STRING), List(CONST_STRING(xs).explicitGet(), CONST_LONG(number))) + val actual = evalPure[EVALUATED](pureEvalContext, expr) + actual shouldBe evaluated(xs.drop(number)) } } property("take(String, Long) works as the native one") { - forAll(genStringAndNumber) { - case (xs, number) => - val expr = FUNCTION_CALL(FunctionHeader.Native(TAKE_STRING), List(CONST_STRING(xs).explicitGet(), CONST_LONG(number))) - val actual = evalPure[EVALUATED](pureEvalContext, expr) - actual shouldBe evaluated(xs.take(number)) + forAll(genStringAndNumber) { case (xs, number) => + val expr = FUNCTION_CALL(FunctionHeader.Native(TAKE_STRING), List(CONST_STRING(xs).explicitGet(), CONST_LONG(number))) + val actual = evalPure[EVALUATED](pureEvalContext, expr) + actual shouldBe evaluated(xs.take(number)) } } property("dropRight(String, Long) works as the native one") { - forAll(genStringAndNumber) { - case (xs, number) => - val expr = FUNCTION_CALL(User("dropRight"), List(CONST_STRING(xs).explicitGet(), CONST_LONG(number))) - val actual = evalPure[EVALUATED](pureEvalContext, expr) - actual shouldBe evaluated(xs.dropRight(number)) + forAll(genStringAndNumber) { case (xs, number) => + val expr = FUNCTION_CALL(User("dropRight"), List(CONST_STRING(xs).explicitGet(), CONST_LONG(number))) + val actual = evalPure[EVALUATED](pureEvalContext, expr) + actual shouldBe evaluated(xs.dropRight(number)) } } property("takeRight(String, Long) works as the native one") { - forAll(genStringAndNumber) { - case (xs, number) => - val expr = FUNCTION_CALL(User("takeRight"), List(CONST_STRING(xs).explicitGet(), CONST_LONG(number))) - val actual = evalPure[EVALUATED](pureEvalContext, expr) - actual shouldBe evaluated(xs.takeRight(number)) + forAll(genStringAndNumber) { case (xs, number) => + val expr = FUNCTION_CALL(User("takeRight"), List(CONST_STRING(xs).explicitGet(), CONST_LONG(number))) + val actual = evalPure[EVALUATED](pureEvalContext, expr) + actual shouldBe evaluated(xs.takeRight(number)) } } @@ -804,8 +806,8 @@ class EvaluatorV1V2Test extends PropSpec with EitherValues { } } - private def sigVerifyTest(bodyBytes: Array[Byte], publicKey: Array[Byte], signature: Array[Byte], lim_n: Option[Short] = None)( - implicit version: StdLibVersion + private def sigVerifyTest(bodyBytes: Array[Byte], publicKey: Array[Byte], signature: Array[Byte], lim_n: Option[Short] = None)(implicit + version: StdLibVersion ): Either[ExecutionError, Boolean] = { val txType = CASETYPEREF( "Transaction", @@ -909,7 +911,7 @@ class EvaluatorV1V2Test extends PropSpec with EitherValues { result shouldBe true - //it false, because script fails on Alice's signature check, and bobSigned is not evaluated + // it false, because script fails on Alice's signature check, and bobSigned is not evaluated log.find(_._1 == "bobSigned") shouldBe None log.find(_._1 == "x0") shouldBe Some(("x0", evaluated("qqqq"))) } @@ -1120,10 +1122,9 @@ class EvaluatorV1V2Test extends PropSpec with EitherValues { property("each argument is evaluated maximum once for user function") { var functionEvaluated = 0 - val f = NativeFunction[NoContext]("F", 1, 258: Short, LONG, ("_", LONG)) { - case _ => - functionEvaluated = functionEvaluated + 1 - evaluated(1L) + val f = NativeFunction[NoContext]("F", 1, 258: Short, LONG, ("_", LONG)) { case _ => + functionEvaluated = functionEvaluated + 1 + evaluated(1L) } val doubleFst = UserFunction[NoContext]("ID", 0, LONG, ("x", LONG)) { @@ -1142,8 +1143,8 @@ class EvaluatorV1V2Test extends PropSpec with EitherValues { .asInstanceOf[EvaluationContext[Environment, Id]] // g(...(g(f(1000))))) - val expr = (1 to 6).foldLeft(FUNCTION_CALL(f.header, List(CONST_LONG(1000)))) { - case (r, _) => FUNCTION_CALL(doubleFst.header, List(r)) + val expr = (1 to 6).foldLeft(FUNCTION_CALL(f.header, List(CONST_LONG(1000)))) { case (r, _) => + FUNCTION_CALL(doubleFst.header, List(r)) } evalV1[EVALUATED](context, expr) shouldBe evaluated(64L) @@ -1231,25 +1232,23 @@ class EvaluatorV1V2Test extends PropSpec with EitherValues { property("Rounding modes DOWN, HALFUP, HALFEVEN, CEILING, FLOOR are available for all versions") { DirectiveDictionary[StdLibVersion].all - .foreach( - version => - Rounding.fromV5.foreach { rounding => - evalPure(pureContext(version).evaluationContext, REF(rounding.`type`.name.toUpperCase)) shouldBe Right(rounding.value) + .foreach(version => + Rounding.fromV5.foreach { rounding => + evalPure(pureContext(version).evaluationContext, REF(rounding.`type`.name.toUpperCase)) shouldBe Right(rounding.value) } ) } property("Rounding modes UP, HALFDOWN are not available from V5") { DirectiveDictionary[StdLibVersion].all - .foreach( - version => - Rounding.all.filterNot(Rounding.fromV5.contains).foreach { rounding => - val ref = rounding.`type`.name.toUpperCase - val r = evalPure(pureContext(version).evaluationContext, REF(ref)) - if (version < V5) - r shouldBe Right(rounding.value) - else - r should produce(s"A definition of '$ref' not found") + .foreach(version => + Rounding.all.filterNot(Rounding.fromV5.contains).foreach { rounding => + val ref = rounding.`type`.name.toUpperCase + val r = evalPure(pureContext(version).evaluationContext, REF(ref)) + if (version < V5) + r shouldBe Right(rounding.value) + else + r should produce(s"A definition of '$ref' not found") } ) } diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorV2Test.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorV2Test.scala index 7f59414a923..32372cc06db 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorV2Test.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorV2Test.scala @@ -9,6 +9,7 @@ import com.wavesplatform.lang.utils.lazyContexts import com.wavesplatform.lang.v1.FunctionHeader import com.wavesplatform.lang.v1.compiler.Terms.{CONST_LONG, *} import com.wavesplatform.lang.v1.compiler.{Decompiler, ExpressionCompiler} +import com.wavesplatform.lang.v1.evaluator.ContractEvaluator.LogExtraInfo import com.wavesplatform.lang.v1.evaluator.{EvaluatorV2, FunctionIds} import com.wavesplatform.lang.v1.parser.Parser import com.wavesplatform.test.* @@ -24,7 +25,15 @@ class EvaluatorV2Test extends PropSpec with Inside { private def evalEither(expr: EXPR, limit: Int, newMode: Boolean): Either[String, (EXPR, Int)] = EvaluatorV2 - .applyLimitedCoeval(expr, limit, ctx.evaluationContext(environment), version, correctFunctionCallScope = true, newMode) + .applyLimitedCoeval( + expr, + LogExtraInfo(), + limit, + ctx.evaluationContext(environment), + version, + correctFunctionCallScope = true, + newMode + ) .value() .bimap(_._1.message, { case (result, complexity, _) => (result, complexity) }) diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/package.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/package.scala index 6d457561ed7..a5f824b1a64 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/package.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/package.scala @@ -5,6 +5,7 @@ import com.wavesplatform.lang.directives.DirectiveSet import com.wavesplatform.lang.directives.values.V3 import com.wavesplatform.lang.v1.compiler.Terms import com.wavesplatform.lang.v1.compiler.Terms.EXPR +import com.wavesplatform.lang.v1.evaluator.ContractEvaluator.LogExtraInfo import com.wavesplatform.lang.v1.evaluator.EvaluatorV2 import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.WavesContext @@ -19,7 +20,7 @@ package object estimator { private val environment = Common.emptyBlockchainEnvironment() private def evaluator(overhead: Boolean, expr: EXPR) = - EvaluatorV2.applyCompleted(ctx.evaluationContext(environment), expr, V3, correctFunctionCallScope = true, overhead) + EvaluatorV2.applyCompleted(ctx.evaluationContext(environment), expr, LogExtraInfo(), V3, correctFunctionCallScope = true, overhead) def evaluatorV2AsEstimator(overhead: Boolean): ScriptEstimator = new ScriptEstimator { override val version: Int = 0 diff --git a/lang/tests/src/test/scala/com/wavesplatform/utils/MerkleTest.scala b/lang/tests/src/test/scala/com/wavesplatform/utils/MerkleTest.scala index bddd012768a..b707d1f9e33 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/utils/MerkleTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/utils/MerkleTest.scala @@ -13,6 +13,7 @@ import com.wavesplatform.lang.directives.values.* import com.wavesplatform.lang.utils.lazyContexts import com.wavesplatform.lang.v1.compiler.ExpressionCompiler import com.wavesplatform.lang.v1.compiler.Terms.* +import com.wavesplatform.lang.v1.evaluator.ContractEvaluator.LogExtraInfo import com.wavesplatform.lang.v1.evaluator.EvaluatorV2 import com.wavesplatform.lang.v1.parser.Parser import com.wavesplatform.test.* @@ -109,7 +110,7 @@ class MerkleTest extends PropSpec { val ctx = lazyContexts(DirectiveSet(version, Account, Expression).explicitGet() -> true)() val evalCtx = ctx.evaluationContext[Id](Common.emptyBlockchainEnvironment()) val typed = ExpressionCompiler(ctx.compilerContext, untyped) - typed.flatMap(v => EvaluatorV2.applyCompleted(evalCtx, v._1, version, true, true)._3.leftMap(_.toString)) + typed.flatMap(v => EvaluatorV2.applyCompleted(evalCtx, v._1, LogExtraInfo(), version, true, true)._3.leftMap(_.toString)) } private def scriptSrc(root: Array[Byte], proof: Array[Byte], value: Array[Byte]): String = { diff --git a/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/InvokeScriptTransactionStateChangesSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/InvokeScriptTransactionStateChangesSuite.scala index e39686b983d..ce3bbefead9 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/InvokeScriptTransactionStateChangesSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/InvokeScriptTransactionStateChangesSuite.scala @@ -1,13 +1,13 @@ package com.wavesplatform.it.sync.smartcontract import com.wavesplatform.common.utils.EitherExt2 -import com.wavesplatform.it.api.SyncHttpApi._ -import com.wavesplatform.it.api._ -import com.wavesplatform.it.sync._ +import com.wavesplatform.it.api.SyncHttpApi.* +import com.wavesplatform.it.api.* +import com.wavesplatform.it.sync.* import com.wavesplatform.it.transactions.BaseTransactionSuite import com.wavesplatform.lang.v1.compiler.Terms.{CONST_LONG, CONST_STRING} import com.wavesplatform.lang.v1.estimator.v2.ScriptEstimatorV2 -import com.wavesplatform.test._ +import com.wavesplatform.test.* import com.wavesplatform.transaction.smart.script.ScriptCompiler import com.wavesplatform.transaction.transfer.MassTransferTransaction.Transfer import org.scalactic.source.Position @@ -46,9 +46,9 @@ class InvokeScriptTransactionStateChangesSuite extends BaseTransactionSuite with val js = invokeTx._2 - (js \ "trace" \ 0 \ "vars" \ 0 \ "name").as[String] shouldBe "value" - (js \ "trace" \ 0 \ "vars" \ 0 \ "type").as[String] shouldBe "Int" - (js \ "trace" \ 0 \ "vars" \ 0 \ "value").as[Int] shouldBe data + (js \ "trace" \ 0 \ "vars" \ 2 \ "name").as[String] shouldBe "value" + (js \ "trace" \ 0 \ "vars" \ 2 \ "type").as[String] shouldBe "Int" + (js \ "trace" \ 0 \ "vars" \ 2 \ "value").as[Int] shouldBe data val id = sender.signedBroadcast(invokeTx._1, waitForTx = true).id diff --git a/node/src/main/resources/application.conf b/node/src/main/resources/application.conf index b696cfeabdf..64e940ec63a 100644 --- a/node/src/main/resources/application.conf +++ b/node/src/main/resources/application.conf @@ -335,6 +335,9 @@ waves { # How much time to wait for extensions' shutdown extensions-shutdown-timeout = 5 minutes + + # Maximum size of transaction validation error log + max-tx-error-log-size = 1048576 // 1MB } # WARNING: No user-configurable settings below this line. diff --git a/node/src/main/scala/com/wavesplatform/Application.scala b/node/src/main/scala/com/wavesplatform/Application.scala index 06e2c772129..bcb8f51e929 100644 --- a/node/src/main/scala/com/wavesplatform/Application.scala +++ b/node/src/main/scala/com/wavesplatform/Application.scala @@ -124,7 +124,7 @@ class Application(val actorSystem: ActorSystem, val settings: WavesSettings, con val establishedConnections = new ConcurrentHashMap[Channel, PeerInfo] val allChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE) - val utxStorage = new UtxPoolImpl(time, blockchainUpdater, settings.utxSettings, settings.minerSettings.enable, utxEvents.onNext) + val utxStorage = new UtxPoolImpl(time, blockchainUpdater, settings.utxSettings, settings.maxTxErrorLogSize, settings.minerSettings.enable, utxEvents.onNext) maybeUtx = Some(utxStorage) def blockchainWithDiscardedDiffs(): CompositeBlockchain = { @@ -388,6 +388,7 @@ class Application(val actorSystem: ActorSystem, val settings: WavesSettings, con UtilsApiRoute( time, settings.restAPISettings, + settings.maxTxErrorLogSize, () => blockchainUpdater.estimator, limitedScheduler, blockchainUpdater diff --git a/node/src/main/scala/com/wavesplatform/Importer.scala b/node/src/main/scala/com/wavesplatform/Importer.scala index 2d1a01609c4..caf44697dfc 100644 --- a/node/src/main/scala/com/wavesplatform/Importer.scala +++ b/node/src/main/scala/com/wavesplatform/Importer.scala @@ -169,7 +169,7 @@ object Importer extends ScorexLogging { @volatile private var quit = false private val lock = new Object - //noinspection UnstableApiUsage + // noinspection UnstableApiUsage def startImport( inputStream: BufferedInputStream, blockchain: Blockchain, @@ -278,7 +278,7 @@ object Importer extends ScorexLogging { val db = openDB(settings.dbSettings.directory) val (blockchainUpdater, levelDb) = StorageFactory(settings, db, time, Observer.empty, BlockchainUpdateTriggers.combined(triggers)) - val utxPool = new UtxPoolImpl(time, blockchainUpdater, settings.utxSettings, settings.minerSettings.enable) + val utxPool = new UtxPoolImpl(time, blockchainUpdater, settings.utxSettings, settings.maxTxErrorLogSize, settings.minerSettings.enable) val pos = PoSSelector(blockchainUpdater, settings.synchronizationSettings.maxBaseTarget) val extAppender = BlockAppender(blockchainUpdater, time, utxPool, pos, scheduler, importOptions.verify) _ diff --git a/node/src/main/scala/com/wavesplatform/api/http/UtilsApiRoute.scala b/node/src/main/scala/com/wavesplatform/api/http/UtilsApiRoute.scala index 02e604d8af9..e7bb77edc18 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/UtilsApiRoute.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/UtilsApiRoute.scala @@ -23,6 +23,7 @@ import com.wavesplatform.lang.script.Script.ComplexityInfo import com.wavesplatform.lang.v1.compiler.Terms.{EVALUATED, EXPR} import com.wavesplatform.lang.v1.compiler.{ContractScriptCompactor, ExpressionCompiler, Terms} import com.wavesplatform.lang.v1.estimator.ScriptEstimator +import com.wavesplatform.lang.v1.evaluator.ContractEvaluator.LogExtraInfo import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.WavesContext import com.wavesplatform.lang.v1.evaluator.ctx.impl.{CryptoContext, PureContext} import com.wavesplatform.lang.v1.evaluator.{ContractEvaluator, EvaluatorV2} @@ -30,14 +31,14 @@ import com.wavesplatform.lang.v1.serialization.SerdeV1 import com.wavesplatform.lang.v1.traits.Environment import com.wavesplatform.lang.v1.traits.domain.Recipient import com.wavesplatform.lang.v1.{ContractLimits, FunctionHeader} -import com.wavesplatform.lang.{API, CompileResult, Global, ValidationError} +import com.wavesplatform.lang.{API, CommonError, CompileResult, Global, ValidationError} import com.wavesplatform.serialization.ScriptValuesJson import com.wavesplatform.settings.RestAPISettings import com.wavesplatform.state.diffs.FeeValidation import com.wavesplatform.state.diffs.invoke.InvokeScriptTransactionLike import com.wavesplatform.state.{Blockchain, Diff} import com.wavesplatform.transaction.TransactionType.TransactionType -import com.wavesplatform.transaction.TxValidationError.{GenericError, InvokeRejectError} +import com.wavesplatform.transaction.TxValidationError.{FailedTransactionError, GenericError, InvokeRejectError} import com.wavesplatform.transaction.smart.script.ScriptCompiler import com.wavesplatform.transaction.smart.{BlockchainContext, DAppEnvironment, InvokeScriptTransaction} import com.wavesplatform.transaction.{Asset, TransactionType} @@ -50,6 +51,7 @@ import shapeless.Coproduct case class UtilsApiRoute( timeService: Time, settings: RestAPISettings, + maxTxErrorLogSize: Int, estimator: () => ScriptEstimator, limitedScheduler: Scheduler, blockchain: Blockchain @@ -298,7 +300,7 @@ case class UtilsApiRoute( val requestData = obj ++ Json.obj("address" -> address.toString) val responseJson = result .recover { - case e: InvokeRejectError => Json.obj("error" -> ApiError.ScriptExecutionError.Id, "message" -> e.message) + case e: InvokeRejectError => Json.obj("error" -> ApiError.ScriptExecutionError.Id, "message" -> e.toStringWithLog(maxTxErrorLogSize)) case other => ApiError.fromValidationError(other).json } .explicitGet() ++ requestData @@ -397,6 +399,7 @@ object UtilsApiRoute { limitedResult <- EvaluatorV2 .applyLimitedCoeval( call, + LogExtraInfo(), limit, ctx, script.stdLibVersion, @@ -405,7 +408,13 @@ object UtilsApiRoute { checkConstructorArgsTypes = true ) .value() - .leftMap { case (err, _, log) => InvokeRejectError(err.message, log) } + .leftMap { case (err, _, log) => + val msg = err match { + case CommonError(_, Some(fte: FailedTransactionError)) => fte.error.getOrElse(err.message) + case _ => err.message + } + InvokeRejectError(msg, log) + } result <- limitedResult match { case (eval: EVALUATED, unusedComplexity, _) => Right((eval, limit - unusedComplexity)) case (_: EXPR, _, log) => Left(InvokeRejectError(s"Calculation complexity limit exceeded", log)) diff --git a/node/src/main/scala/com/wavesplatform/settings/WavesSettings.scala b/node/src/main/scala/com/wavesplatform/settings/WavesSettings.scala index 2a159a5affc..7197ea67989 100644 --- a/node/src/main/scala/com/wavesplatform/settings/WavesSettings.scala +++ b/node/src/main/scala/com/wavesplatform/settings/WavesSettings.scala @@ -10,6 +10,7 @@ import scala.concurrent.duration.FiniteDuration case class WavesSettings( directory: String, ntpServer: String, + maxTxErrorLogSize: Int, dbSettings: DBSettings, extensions: Seq[String], extensionsShutdownTimeout: FiniteDuration, @@ -32,6 +33,7 @@ object WavesSettings extends CustomValueReaders { val directory = waves.as[String]("directory") val ntpServer = waves.as[String]("ntp-server") + val maxTxErrorLogSize = waves.as[Int]("max-tx-error-log-size") val dbSettings = waves.as[DBSettings]("db") val extensions = waves.as[Seq[String]]("extensions") val extensionsShutdownTimeout = waves.as[FiniteDuration]("extensions-shutdown-timeout") @@ -49,6 +51,7 @@ object WavesSettings extends CustomValueReaders { WavesSettings( directory, ntpServer, + maxTxErrorLogSize, dbSettings, extensions, extensionsShutdownTimeout, diff --git a/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeDiffsCommon.scala b/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeDiffsCommon.scala index b06be2aa705..1e2bacd7f7b 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeDiffsCommon.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeDiffsCommon.scala @@ -167,7 +167,8 @@ object InvokeDiffsCommon { isSyncCall: Boolean, limitedExecution: Boolean, totalComplexityLimit: Int, - otherIssues: Seq[Issue] + otherIssues: Seq[Issue], + log: Log[Id] ): TracedResult[ValidationError, Diff] = { val complexityLimit = if (limitedExecution) ContractLimits.FailFreeInvokeComplexity - storingComplexity @@ -184,19 +185,21 @@ object InvokeDiffsCommon { val dataEntries = actionsByType(classOf[DataOp]).asInstanceOf[List[DataOp]].map(dataItemToEntry) for { - _ <- TracedResult(checkDataEntries(blockchain, tx, dataEntries, version)).leftMap(FailedTransactionError.dAppExecution(_, storingComplexity)) - _ <- TracedResult(checkLeaseCancels(leaseCancelList)).leftMap(FailedTransactionError.dAppExecution(_, storingComplexity)) + _ <- TracedResult(checkDataEntries(blockchain, tx, dataEntries, version)).leftMap( + FailedTransactionError.dAppExecution(_, storingComplexity, log) + ) + _ <- TracedResult(checkLeaseCancels(leaseCancelList)).leftMap(FailedTransactionError.dAppExecution(_, storingComplexity, log)) _ <- TracedResult( checkScriptActionsAmount(version, actions, transferList, leaseList, leaseCancelList, dataEntries) - .leftMap(FailedTransactionError.dAppExecution(_, storingComplexity)) + .leftMap(FailedTransactionError.dAppExecution(_, storingComplexity, log)) ) _ <- TracedResult(checkSelfPayments(dAppAddress, blockchain, tx, version, transferList)) - .leftMap(FailedTransactionError.dAppExecution(_, storingComplexity)) + .leftMap(FailedTransactionError.dAppExecution(_, storingComplexity, log)) _ <- TracedResult( - Either.cond(transferList.map(_.amount).forall(_ >= 0), (), FailedTransactionError.dAppExecution("Negative amount", storingComplexity)) + Either.cond(transferList.map(_.amount).forall(_ >= 0), (), FailedTransactionError.dAppExecution("Negative amount", storingComplexity, log)) ) - _ <- TracedResult(checkOverflow(transferList.map(_.amount))).leftMap(FailedTransactionError.dAppExecution(_, storingComplexity)) + _ <- TracedResult(checkOverflow(transferList.map(_.amount))).leftMap(FailedTransactionError.dAppExecution(_, storingComplexity, log)) actionAssets = transferList.flatMap(_.assetId).map(IssuedAsset(_)) ++ reissueList.map(r => IssuedAsset(r.assetId)) ++ @@ -215,7 +218,7 @@ object InvokeDiffsCommon { val stepLimit = ContractLimits.MaxComplexityByVersion(version) calcAndCheckFee( - FailedTransactionError.feeForActions, + FailedTransactionError.feeForActions(_, _, log), tx.root, blockchain, stepLimit, @@ -230,7 +233,7 @@ object InvokeDiffsCommon { Either.cond( actionComplexities.sum + storingComplexity <= totalComplexityLimit || limitedExecution, // limited execution has own restriction "complexityLimit" (), - FailedTransactionError.feeForActions(s"Invoke complexity limit = $totalComplexityLimit is exceeded", storingComplexity) + FailedTransactionError.feeForActions(s"Invoke complexity limit = $totalComplexityLimit is exceeded", storingComplexity, log) ) ) @@ -247,14 +250,14 @@ object InvokeDiffsCommon { resolveAddress(transfer.address, blockchain) .map(InvokeScriptResult.Payment(_, Asset.fromCompatId(transfer.assetId), transfer.amount)) .leftMap { - case f: FailedTransactionError => f.addComplexity(storingComplexity) + case f: FailedTransactionError => f.addComplexity(storingComplexity).withLog(log) case e => e } } compositeDiff <- foldActions(blockchain, blockTime, tx, dAppAddress, dAppPublicKey)(actions, paymentsAndFeeDiff, complexityLimit) .leftMap { - case failed: FailedTransactionError => failed.addComplexity(storingComplexity) + case failed: FailedTransactionError => failed.addComplexity(storingComplexity).withLog(log) case other => other } diff --git a/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeScriptDiff.scala b/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeScriptDiff.scala index ca1ce4ab41d..d023687afbf 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeScriptDiff.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeScriptDiff.scala @@ -235,6 +235,7 @@ object InvokeScriptDiff { version, blockchain, contract, + dAppAddress, invocation, environment, complexityAfterPayments, @@ -275,7 +276,8 @@ object InvokeScriptDiff { isSyncCall = true, limitedExecution, totalComplexityLimit, - Seq() + Seq(), + log ) ) ) @@ -401,6 +403,7 @@ object InvokeScriptDiff { version: StdLibVersion, blockchain: Blockchain, contract: DApp, + dAppAddress: Address, invocation: ContractEvaluator.Invocation, environment: Environment[Id], limit: Int, @@ -408,14 +411,27 @@ object InvokeScriptDiff { ): Coeval[Either[ValidationError, (ScriptResult, Log[Id])]] = { val evaluationCtx = CachedDAppCTX.get(version, blockchain).completeContext(environment) ContractEvaluator - .applyV2Coeval(evaluationCtx, contract, invocation, version, limit, blockchain.correctFunctionCallScope, blockchain.newEvaluatorMode) + .applyV2Coeval( + evaluationCtx, + contract, + ByteStr(dAppAddress.bytes), + invocation, + version, + limit, + blockchain.correctFunctionCallScope, + blockchain.newEvaluatorMode + ) .map( _.leftMap[ValidationError] { case (reject @ FailOrRejectError(_, true), _, _) => reject.copy(skipInvokeComplexity = false) case (error, unusedComplexity, log) => val usedComplexity = startComplexityLimit - unusedComplexity - FailedTransactionError.dAppExecution(error.message, usedComplexity, log) + val msg = error match { + case CommonError(_, Some(fte: FailedTransactionError)) => fte.error.getOrElse(error.message) + case _ => error.message + } + FailedTransactionError.dAppExecution(msg, usedComplexity, log) }.flatTap { case (r, log) => InvokeDiffsCommon .checkScriptResultFields(blockchain, r) @@ -423,7 +439,11 @@ object InvokeScriptDiff { case reject: FailOrRejectError => reject case error => val usedComplexity = startComplexityLimit - r.unusedComplexity - FailedTransactionError.dAppExecution(error.toString, usedComplexity, log) + val msg = error match { + case fte: FailedTransactionError => fte.error.getOrElse(error.toString) + case _ => error.toString + } + FailedTransactionError.dAppExecution(msg, usedComplexity, log) }) } ) diff --git a/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeScriptTransactionDiff.scala b/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeScriptTransactionDiff.scala index 50f680ae82b..85d210b29f9 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeScriptTransactionDiff.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeScriptTransactionDiff.scala @@ -105,6 +105,7 @@ object InvokeScriptTransactionDiff { (result, log) <- evaluateV2( version, contract, + dAppAddress, invocation, environment, fullLimit, @@ -173,7 +174,8 @@ object InvokeScriptTransactionDiff { isSyncCall = false, limitedExecution, ContractLimits.MaxTotalInvokeComplexity(version), - otherIssues + otherIssues, + log ) process = (actions: List[CallableAction], unusedComplexity: Long) => { @@ -351,6 +353,7 @@ object InvokeScriptTransactionDiff { private def evaluateV2( version: StdLibVersion, contract: DApp, + dAppAddress: Address, invocation: ContractEvaluator.Invocation, environment: Environment[Id], limit: Int, @@ -362,7 +365,16 @@ object InvokeScriptTransactionDiff { val evaluationCtx = CachedDAppCTX.get(version, blockchain).completeContext(environment) val startLimit = limit - paymentsComplexity ContractEvaluator - .applyV2Coeval(evaluationCtx, contract, invocation, version, startLimit, blockchain.correctFunctionCallScope, blockchain.newEvaluatorMode) + .applyV2Coeval( + evaluationCtx, + contract, + ByteStr(dAppAddress.bytes), + invocation, + version, + startLimit, + blockchain.correctFunctionCallScope, + blockchain.newEvaluatorMode + ) .runAttempt() .leftMap(error => (error.getMessage: ExecutionError, 0, Nil: Log[Id])) .flatten @@ -371,11 +383,15 @@ object InvokeScriptTransactionDiff { InvokeRejectError(msg, log) case (error, unusedComplexity, log) => val usedComplexity = startLimit - unusedComplexity.max(0) + val msg = error match { + case CommonError(_, Some(fte: FailedTransactionError)) => fte.error.getOrElse(error.message) + case _ => error.message + } if (usedComplexity > failFreeLimit) { val storingComplexity = if (blockchain.storeEvaluatedComplexity) usedComplexity else estimatedComplexity - FailedTransactionError.dAppExecution(error.message, storingComplexity + paymentsComplexity, log) + FailedTransactionError.dAppExecution(msg, storingComplexity + paymentsComplexity, log) } else - InvokeRejectError(error.message, log) + InvokeRejectError(msg, log) } .flatTap { case (r, log) => InvokeDiffsCommon @@ -385,11 +401,15 @@ object InvokeScriptTransactionDiff { InvokeRejectError(message, log) case error => val usedComplexity = startLimit - r.unusedComplexity + val msg = error match { + case fte: FailedTransactionError => fte.error.getOrElse(error.toString) + case _ => error.toString + } if (usedComplexity > failFreeLimit) { val storingComplexity = if (blockchain.storeEvaluatedComplexity) usedComplexity else estimatedComplexity - FailedTransactionError.dAppExecution(error.toString, storingComplexity + paymentsComplexity, log) + FailedTransactionError.dAppExecution(msg, storingComplexity + paymentsComplexity, log) } else - InvokeRejectError(error.toString, log) + InvokeRejectError(msg, log) } } } diff --git a/node/src/main/scala/com/wavesplatform/transaction/DiffToLogConverter.scala b/node/src/main/scala/com/wavesplatform/transaction/DiffToLogConverter.scala new file mode 100644 index 00000000000..b05e6ea1332 --- /dev/null +++ b/node/src/main/scala/com/wavesplatform/transaction/DiffToLogConverter.scala @@ -0,0 +1,161 @@ +package com.wavesplatform.transaction + +import cats.Id +import cats.syntax.either.* +import com.wavesplatform.account +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.lang.{CommonError, ExecutionError} +import com.wavesplatform.lang.v1.compiler.Terms.{ARR, CONST_BOOLEAN, CONST_BYTESTR, CONST_LONG, CONST_STRING, CaseObj, EVALUATED} +import com.wavesplatform.lang.v1.compiler.Types.{CASETYPEREF, UNIT} +import com.wavesplatform.lang.v1.evaluator.Log +import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.{Bindings, Types} +import com.wavesplatform.lang.v1.evaluator.EvaluatorV2.LogKeys.* +import com.wavesplatform.lang.v1.traits.domain.Recipient +import com.wavesplatform.lang.v1.traits.domain.Recipient.Address +import com.wavesplatform.state.{BinaryDataEntry, BooleanDataEntry, Diff, EmptyDataEntry, IntegerDataEntry, InvokeScriptResult, StringDataEntry} +import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} + +object DiffToLogConverter { + + def convert(diff: Diff, txId: ByteStr, funcName: String, complexityLimit: Int): Log[Id] = { + def strToMap(str: String, fieldName: String) = CONST_STRING(str).map(s => Map(fieldName -> s)).getOrElse(Map.empty) + def byteStrToMap(byteStr: ByteStr, fieldName: String) = CONST_BYTESTR(byteStr).map(bs => Map(fieldName -> bs)).getOrElse(Map.empty) + def assetToMap(asset: Asset, fieldName: String) = (asset match { + case IssuedAsset(id) => CONST_BYTESTR(id) + case Waves => CaseObj(UNIT, Map.empty).asRight[CommonError] + }).map(assetObj => Map(fieldName -> assetObj)).getOrElse(Map.empty) + def arrToMap(items: Seq[EVALUATED], fieldName: String) = + ARR(items.toIndexedSeq, false).map(arr => Map(fieldName -> arr)).getOrElse(Map.empty) + + def scriptResultsToObj(scriptResults: InvokeScriptResult): CaseObj = { + CaseObj( + CASETYPEREF("StateChanges", List.empty), + arrToMap( + scriptResults.data.map { + case BooleanDataEntry(key, value) => + CaseObj( + Types.booleanDataEntry, + strToMap(key, "key") ++ Map("value" -> CONST_BOOLEAN(value)) + ) + case IntegerDataEntry(key, value) => CaseObj(Types.intDataEntry, strToMap(key, "key") ++ Map("value" -> CONST_LONG(value))) + case StringDataEntry(key, value) => CaseObj(Types.stringDataEntry, strToMap(key, "key") ++ strToMap(value, "value")) + case BinaryDataEntry(key, value) => + CaseObj(Types.binaryDataEntry, CONST_BYTESTR(value).map(b => strToMap(key, "key") ++ Map("value" -> b)).getOrElse(Map.empty)) + case EmptyDataEntry(key) => CaseObj(Types.deleteDataEntry, strToMap(key, "key")) + }, + "data" + ) ++ + arrToMap( + scriptResults.transfers.map { payment => + CaseObj( + Types.scriptTransfer, + Map( + "amount" -> CONST_LONG(payment.amount), + "recipient" -> Bindings.senderObject(Recipient.Address(ByteStr(payment.address.bytes))) + ) ++ assetToMap(payment.asset, "asset") + ) + }, + "transfers" + ) ++ + arrToMap( + scriptResults.issues.map { issue => + CaseObj( + Types.issueActionType, + byteStrToMap(issue.id, "id") ++ + strToMap(issue.name, "name") ++ + strToMap(issue.description, "description") ++ + issue.compiledScript.map(byteStrToMap(_, "script")).getOrElse(Map.empty) ++ + Map( + "decimals" -> CONST_LONG(issue.decimals), + "isReissuable" -> CONST_BOOLEAN(issue.isReissuable), + "quantity" -> CONST_LONG(issue.quantity), + "nonce" -> CONST_LONG(issue.nonce) + ) + ) + }, + "issues" + ) ++ + arrToMap( + scriptResults.reissues.map { reissue => + CaseObj( + Types.reissueActionType, + byteStrToMap(reissue.assetId, "id") ++ Map( + "isReissuable" -> CONST_BOOLEAN(reissue.isReissuable), + "quantity" -> CONST_LONG(reissue.quantity) + ) + ) + }, + "reissues" + ) ++ + arrToMap( + scriptResults.burns.map { burn => + CaseObj(Types.burnActionType, byteStrToMap(burn.assetId, "id") ++ Map("quantity" -> CONST_LONG(burn.quantity))) + }, + "burns" + ) ++ + arrToMap( + scriptResults.sponsorFees.map { sponsor => + CaseObj( + Types.sponsorFeeActionType, + byteStrToMap(sponsor.assetId, "id") ++ sponsor.minSponsoredAssetFee + .map(fee => Map("minSponsoredAssetFee" -> CONST_LONG(fee))) + .getOrElse(Map.empty) + ) + }, + "sponsorFees" + ) ++ + arrToMap( + scriptResults.leases.map { lease => + val recipient = lease.recipient match { + case addr: account.Address => Bindings.senderObject(Address(ByteStr(addr.bytes))) + case alias: account.Alias => CaseObj(Types.aliasType, strToMap(alias.name, "alias")) + } + CaseObj( + Types.leaseActionType, + Map("recipient" -> recipient, "amount" -> CONST_LONG(lease.amount), "nonce" -> CONST_LONG(lease.nonce)) ++ byteStrToMap( + lease.id, + "id" + ) + ) + }, + "leases" + ) ++ + arrToMap( + scriptResults.leaseCancels.map { leaseCancel => + CaseObj(Types.leaseCancelActionType, byteStrToMap(leaseCancel.id, "id")) + }, + "leaseCancels" + ) ++ + arrToMap( + scriptResults.invokes.map { invoke => + CaseObj( + CASETYPEREF("Invoke", List.empty), + Map( + "dApp" -> Bindings.senderObject(Address(ByteStr(invoke.dApp.bytes))), + "call" -> CaseObj( + CASETYPEREF("Call", List.empty), + strToMap(invoke.call.function, "function") ++ + arrToMap(invoke.call.args, "args") + ), + "stateChanges" -> scriptResultsToObj(invoke.stateChanges) + ) ++ + arrToMap( + invoke.payments.map { payment => + CaseObj(Types.paymentType, assetToMap(payment.assetId, "assetId") ++ Map("amount" -> CONST_LONG(payment.amount))) + }, + "payments" + ) + ) + }, + "invokes" + ) + ) + } + + List( + s"$funcName.$StateChanges" -> scriptResultsToObj(diff.scriptResults.getOrElse(txId, InvokeScriptResult.empty)).asRight[ExecutionError], + s"$funcName.$Complexity" -> CONST_LONG(diff.scriptsComplexity).asRight[ExecutionError], + ComplexityLimit -> CONST_LONG(complexityLimit - diff.scriptsComplexity).asRight[ExecutionError] + ) + } +} diff --git a/node/src/main/scala/com/wavesplatform/transaction/ErrorWithLogPrinter.scala b/node/src/main/scala/com/wavesplatform/transaction/ErrorWithLogPrinter.scala new file mode 100644 index 00000000000..9008ea4bd45 --- /dev/null +++ b/node/src/main/scala/com/wavesplatform/transaction/ErrorWithLogPrinter.scala @@ -0,0 +1,39 @@ +package com.wavesplatform.transaction + +import cats.Id +import com.wavesplatform.lang.CommonError +import com.wavesplatform.lang.v1.compiler.TermPrinter +import com.wavesplatform.lang.v1.evaluator.Log +import com.wavesplatform.transaction.TxValidationError.FailedTransactionError + +import scala.annotation.tailrec + +object ErrorWithLogPrinter { + + def logToString(log: Log[Id], limit: Int, depth: Int = 1): String = { + @tailrec + def loop(log: Log[Id], limit: Int, sb: StringBuilder): String = { + log match { + case _ if limit < 0 => sb.append("\n...").toString() + case Nil => sb.toString() + case (name, Right(v)) :: tail => + val logStrItem = s"\n${"\t" * depth}$name = ${TermPrinter(true).prettyString(v, depth)}" + val length = logStrItem.length + if (length <= limit) sb.append(logStrItem) + loop(tail, limit - length, sb) + case (name, Left(CommonError(_, Some(fte: FailedTransactionError)))) :: _ => + val logStrItem = s"\n${"\t" * depth}$name = ${fte.errorDetails}" + val length = logStrItem.length + if (length <= limit) sb.append(logStrItem) + val closingBracket = s"${"\t" * depth})" + sb.append(s"${logToString(fte.log, limit - length - closingBracket.length, depth + 1)}$closingBracket").toString() + case (name, Left(err)) :: _ => + val logStrItem = s"\n${"\t" * depth}$name = $err" + if (logStrItem.length <= limit) sb.append(logStrItem) + sb.toString() + } + } + + s"${loop(log, limit - 1, new StringBuilder)}\n" + } +} diff --git a/node/src/main/scala/com/wavesplatform/transaction/TxValidationError.scala b/node/src/main/scala/com/wavesplatform/transaction/TxValidationError.scala index 6c4a7acc18e..8afe3471a1c 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/TxValidationError.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/TxValidationError.scala @@ -51,6 +51,7 @@ object TxValidationError { sealed trait WithLog extends Product with Serializable { def log: Log[Id] + def toStringWithLog(limit: Int): String } /** Errors which can produce failed transaction */ @@ -77,22 +78,29 @@ object TxValidationError { def addComplexity(complexity: Long): FailedTransactionError = copy(spentComplexity = spentComplexity + complexity) + def withLog(log: Log[Id]): FailedTransactionError = copy(log = log) + private def assetScriptError(assetId: ByteStr, error: Option[String]): String = s"Transaction is not allowed by script of the asset $assetId" + error.fold("")(e => s": $e") + def errorDetails: String = s"FailedTransactionError(code = ${cause.code}, error = $message, log = " + override def toString: String = if (message.startsWith("FailedTransactionError")) message else - s"FailedTransactionError(code = ${cause.code}, error = $message, log =${logToString(log)})" + s"FailedTransactionError(code = ${cause.code}, error = $message)" + + override def toStringWithLog(limit: Int): String = + s"$errorDetails${ErrorWithLogPrinter.logToString(log, limit)})" } object FailedTransactionError { def dAppExecution(error: String, spentComplexity: Long, log: Log[Id] = List.empty): FailedTransactionError = FailedTransactionError(Cause.DAppExecution, spentComplexity, log, Some(error), None) - def feeForActions(error: String, spentComplexity: Long): FailedTransactionError = - FailedTransactionError(Cause.FeeForActions, spentComplexity, List.empty, Some(error), None) + def feeForActions(error: String, spentComplexity: Long, log: Log[Id]): FailedTransactionError = + FailedTransactionError(Cause.FeeForActions, spentComplexity, log, Some(error), None) def assetExecutionInAction(error: String, spentComplexity: Long, log: Log[Id], assetId: ByteStr): FailedTransactionError = FailedTransactionError(Cause.AssetScriptInAction, spentComplexity, log, Some(error), Some(assetId)) @@ -139,30 +147,27 @@ object TxValidationError { if (String.valueOf(message).startsWith("ScriptExecutionError")) message else - s"ScriptExecutionError(error = $message, type = $target, log = ${logToString(log)})" + s"ScriptExecutionError(error = $message, type = $target)" + + override def toStringWithLog(limit: Int): String = + s"ScriptExecutionError(error = $message, type = $target, log = ${ErrorWithLogPrinter.logToString(log, limit)})" } case class InvokeRejectError(message: String, log: Log[Id]) extends ValidationError with WithLog { - override def toString: String = s"InvokeRejectError(error = $message, log = ${logToString(log)})" + override def toString: String = s"InvokeRejectError(error = $message)" + + override def toStringWithLog(limit: Int): String = + s"InvokeRejectError(error = $message, log = ${ErrorWithLogPrinter.logToString(log, limit)})" } - case class TransactionNotAllowedByScript(log: Log[Id], assetId: Option[ByteStr]) extends ValidationError { + case class TransactionNotAllowedByScript(log: Log[Id], assetId: Option[ByteStr]) extends ValidationError with WithLog { def isAssetScript: Boolean = assetId.isDefined private val target: String = assetId.fold("Account")(_ => "Asset") - override def toString: String = s"TransactionNotAllowedByScript(type = $target, log =${logToString(log)})" - } - - def logToString(log: Log[Id]): String = - if (log.isEmpty) "" - else { - log - .map { - case (name, Right(v)) => s"$name = ${v.prettyString(1)}" - case (name, l @ Left(_)) => s"$name = $l" - } - .map("\t" + _) - .mkString("\n", "\n", "\n") - } + override def toString: String = s"TransactionNotAllowedByScript(type = $target)" + + override def toStringWithLog(limit: Int): String = + s"TransactionNotAllowedByScript(type = $target, log = ${ErrorWithLogPrinter.logToString(log, limit)})" + } case class MicroBlockAppendError(err: String, microBlock: MicroBlock) extends ValidationError { override def toString: String = s"MicroBlockAppendError($err, ${microBlock.totalResBlockSig} ~> ${microBlock.reference.trim}])" diff --git a/node/src/main/scala/com/wavesplatform/transaction/smart/Verifier.scala b/node/src/main/scala/com/wavesplatform/transaction/smart/Verifier.scala index 5b31da90276..30596d82abc 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/smart/Verifier.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/smart/Verifier.scala @@ -45,7 +45,7 @@ object Verifier extends ScorexLogging { type ValidationResult[T] = Either[ValidationError, T] def apply(blockchain: Blockchain, limitedExecution: Boolean = false)(tx: Transaction): TracedResult[ValidationError, Int] = (tx: @unchecked) match { - case _: GenesisTransaction => Right(0) + case _: GenesisTransaction => Right(0) case _: EthereumTransaction => Right(0) case pt: ProvenTransaction => (pt, blockchain.accountScript(pt.sender.toAddress)) match { @@ -61,7 +61,8 @@ object Verifier extends ScorexLogging { Left(GenericError("Can't process transaction with signature from scripted account")) case (_: InvokeExpressionTransaction, Some(script)) if forbidInvokeExpressionDueToVerifier(script.script) => Left( - GenericError(s"Can't process InvokeExpressionTransaction from RIDE ${script.script.stdLibVersion} verifier, it might be used from $V6")) + GenericError(s"Can't process InvokeExpressionTransaction from RIDE ${script.script.stdLibVersion} verifier, it might be used from $V6") + ) case (_, Some(script)) => stats.accountScriptExecution .measureForType(pt.tpe)(verifyTx(blockchain, script.script, script.verifierComplexity.toInt, pt, None)) @@ -161,8 +162,8 @@ object Verifier extends ScorexLogging { val senderAddress = transaction.asInstanceOf[Authorized].sender.toAddress val resultE = Try { - val containerAddress = assetIdOpt.fold(Coproduct[Environment.Tthis](Recipient.Address(ByteStr(senderAddress.bytes))))( - v => Coproduct[Environment.Tthis](Environment.AssetId(v.arr)) + val containerAddress = assetIdOpt.fold(Coproduct[Environment.Tthis](Recipient.Address(ByteStr(senderAddress.bytes))))(v => + Coproduct[Environment.Tthis](Environment.AssetId(v.arr)) ) val (log, evaluatedComplexity, result) = ScriptRunner(Coproduct[TxOrd](transaction), blockchain, script, isAsset, containerAddress, complexityLimit) @@ -208,18 +209,17 @@ object Verifier extends ScorexLogging { ) ).toEither .leftMap(e => ScriptExecutionError(s"Uncaught execution error: $e", Nil, None)) - .flatMap { - case (log, evaluatedComplexity, evaluationResult) => - val complexity = if (blockchain.storeEvaluatedComplexity) evaluatedComplexity else script.verifierComplexity.toInt - val verifierResult = evaluationResult match { - case Left(execError) => Left(ScriptExecutionError(execError.message, log, None)) - case Right(FALSE) => Left(TransactionNotAllowedByScript(log, None)) - case Right(TRUE) => Right(complexity) - case Right(x) => Left(GenericError(s"Script returned not a boolean result, but $x")) - } - val logId = s"order ${order.idStr()}" - logIfNecessary(verifierResult, logId, log, evaluationResult.leftMap(_.message)) - verifierResult + .flatMap { case (log, evaluatedComplexity, evaluationResult) => + val complexity = if (blockchain.storeEvaluatedComplexity) evaluatedComplexity else script.verifierComplexity.toInt + val verifierResult = evaluationResult match { + case Left(execError) => Left(ScriptExecutionError(execError.message, log, None)) + case Right(FALSE) => Left(TransactionNotAllowedByScript(log, None)) + case Right(TRUE) => Right(complexity) + case Right(x) => Left(GenericError(s"Script returned not a boolean result, but $x")) + } + val logId = s"order ${order.idStr()}" + logIfNecessary(verifierResult, logId, log, evaluationResult.leftMap(_.message)) + verifierResult } private def verifyExchange( @@ -243,7 +243,10 @@ object Verifier extends ScorexLogging { TracedResult(Left(GenericError("Can't process transaction with signature from scripted account"))) } } - .getOrElse(stats.signatureVerification.measureForType(typeId)(verifyAsEllipticCurveSignature(et, blockchain.isFeatureActivated(BlockchainFeatures.RideV6)).as(0))) + .getOrElse( + stats.signatureVerification + .measureForType(typeId)(verifyAsEllipticCurveSignature(et, blockchain.isFeatureActivated(BlockchainFeatures.RideV6)).as(0)) + ) def orderVerification(order: Order): TracedResult[ValidationError, Int] = { val verificationResult = blockchain @@ -255,7 +258,10 @@ object Verifier extends ScorexLogging { Left(GenericError("Can't process order with signature from scripted account")) } } - .getOrElse(stats.signatureVerification.measureForType(typeId)(verifyOrderSignature(order, blockchain.isFeatureActivated(BlockchainFeatures.RideV6)).as(0))) + .getOrElse( + stats.signatureVerification + .measureForType(typeId)(verifyOrderSignature(order, blockchain.isFeatureActivated(BlockchainFeatures.RideV6)).as(0)) + ) TracedResult(verificationResult) } @@ -295,7 +301,7 @@ object Verifier extends ScorexLogging { case (sb, (k, Right(v))) => sb.append(s"\nEvaluated `$k` to ") v match { - case obj: EVALUATED => TermPrinter.print(str => sb.append(str), obj); sb + case obj: EVALUATED => TermPrinter().print(str => sb.append(str), obj); sb case a => sb.append(a.toString) } case (sb, (k, Left(err))) => sb.append(s"\nFailed to evaluate `$k`: $err") diff --git a/node/src/main/scala/com/wavesplatform/transaction/smart/WavesEnvironment.scala b/node/src/main/scala/com/wavesplatform/transaction/smart/WavesEnvironment.scala index 74144d3884a..31882dbc9da 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/smart/WavesEnvironment.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/smart/WavesEnvironment.scala @@ -28,7 +28,7 @@ import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment import com.wavesplatform.transaction.smart.script.trace.CoevalR.traced import com.wavesplatform.transaction.smart.script.trace.InvokeScriptTrace import com.wavesplatform.transaction.transfer.TransferTransaction -import com.wavesplatform.transaction.{Asset, TransactionBase, TransactionType} +import com.wavesplatform.transaction.{Asset, DiffToLogConverter, TransactionBase, TransactionType} import monix.eval.Coeval import shapeless.* @@ -240,7 +240,7 @@ class WavesEnvironment( payments: Seq[(Option[Array[Byte]], Long)], availableComplexity: Int, reentrant: Boolean - ): Coeval[(Either[ValidationError, EVALUATED], Int)] = ??? + ): Coeval[(Either[ValidationError, (EVALUATED, Log[Id])], Int)] = ??? } object DAppEnvironment { @@ -280,7 +280,13 @@ object DAppEnvironment { } def getErrorMessage: Option[InvokeScriptResult.ErrorMessage] = { - def isNestedError(ve: ValidationError) = invocations.exists(_.result.left.map(_.toString) == Left(ve.toString)) + def isNestedError(ve: ValidationError) = invocations.exists { inv => + (inv.result, ve) match { + case (Left(fte1: FailedTransactionError), fte2: FailedTransactionError) => fte1.error == fte2.error + case (Left(ve1), ve2) => ve1 == ve2 + case _ => false + } + } this.result.left.toOption.collect { case ve if !isNestedError(ve) => @@ -337,7 +343,7 @@ class DAppEnvironment( payments: Seq[(Option[Array[Byte]], Long)], availableComplexity: Int, reentrant: Boolean - ): Coeval[(Either[ValidationError, EVALUATED], Int)] = { + ): Coeval[(Either[ValidationError, (EVALUATED, Log[Id])], Int)] = { val r = for { address <- traced( @@ -398,14 +404,14 @@ class DAppEnvironment( availablePayments = remainingPayments availableData = remainingData availableDataSize = remainingDataSize - (evaluated, diff.scriptsComplexity.toInt) + (evaluated, diff.scriptsComplexity.toInt, DiffToLogConverter.convert(diff, tx.id(), func, availableComplexity)) } r.v.map { _.resultE match { - case Left(fte: FailedTransactionError) => (Left(fte), fte.spentComplexity.toInt) - case Left(e) => (Left(e), 0) - case Right((evaluated, complexity)) => (Right(evaluated), complexity) + case Left(fte: FailedTransactionError) => (Left(fte), fte.spentComplexity.toInt) + case Left(e) => (Left(e), 0) + case Right((evaluated, complexity, diffLog)) => (Right((evaluated, diffLog)), complexity) } } } diff --git a/node/src/main/scala/com/wavesplatform/transaction/smart/script/ScriptRunner.scala b/node/src/main/scala/com/wavesplatform/transaction/smart/script/ScriptRunner.scala index 1b97a33da49..97e34bc9a2f 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/smart/script/ScriptRunner.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/smart/script/ScriptRunner.scala @@ -16,6 +16,7 @@ import com.wavesplatform.lang.script.{ContractScript, Script} import com.wavesplatform.lang.v1.ContractLimits import com.wavesplatform.lang.v1.compiler.Terms.{EVALUATED, EXPR, TRUE} import com.wavesplatform.lang.v1.evaluator.* +import com.wavesplatform.lang.v1.evaluator.ContractEvaluator.LogExtraInfo import com.wavesplatform.lang.v1.evaluator.ctx.EvaluationContext import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.Bindings import com.wavesplatform.lang.v1.traits.Environment @@ -97,7 +98,12 @@ object ScriptRunner { ctxE.fold(e => (Nil, 0, Left(e)), { case (ds, ctx) => partialEvaluate(ds, ctx) }) } - def evaluate(ctx: EvaluationContext[Environment, Id], expr: EXPR, version: StdLibVersion): (Log[Id], Int, Either[ExecutionError, EVALUATED]) = { + def evaluate( + ctx: EvaluationContext[Environment, Id], + expr: EXPR, + logExtraInfo: LogExtraInfo, + version: StdLibVersion + ): (Log[Id], Int, Either[ExecutionError, EVALUATED]) = { val correctedLimit = if (isAssetScript) ContractLimits.MaxComplexityByVersion(version) @@ -117,6 +123,7 @@ object ScriptRunner { EvaluatorV2.applyOrDefault( ctx, expr, + logExtraInfo, script.stdLibVersion, limit, correctFunctionCallScope = checkEstimatorSumOverflow, @@ -129,12 +136,12 @@ object ScriptRunner { script match { case s: ExprScript => - evalVerifier(isContract = false, (_, ctx) => evaluate(ctx, s.expr, s.stdLibVersion)) + evalVerifier(isContract = false, (_, ctx) => evaluate(ctx, s.expr, LogExtraInfo(), s.stdLibVersion)) case ContractScript.ContractScriptImpl(v, DApp(_, decls, _, Some(vf))) => val partialEvaluate: (DirectiveSet, EvaluationContext[Environment, Id]) => (Log[Id], Int, Either[ExecutionError, EVALUATED]) = { (directives, ctx) => - val verify = ContractEvaluator.verify(decls, vf, evaluate(ctx, _, v), _) + val verify = ContractEvaluator.verify(decls, vf, evaluate(ctx, _, _, v), _) val bindingsVersion = if (useCorrectScriptVersion) directives.stdLibVersion diff --git a/node/src/main/scala/com/wavesplatform/transaction/smart/script/trace/TraceStep.scala b/node/src/main/scala/com/wavesplatform/transaction/smart/script/trace/TraceStep.scala index 6b28acd6bda..a5b0c02d12a 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/smart/script/trace/TraceStep.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/smart/script/trace/TraceStep.scala @@ -3,20 +3,21 @@ package com.wavesplatform.transaction.smart.script.trace import cats.Id import com.wavesplatform.account.{Address, AddressOrAlias} import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.lang.ValidationError +import com.wavesplatform.lang.{CommonError, ValidationError} import com.wavesplatform.lang.v1.compiler.Terms.FUNCTION_CALL +import com.wavesplatform.lang.v1.evaluator.EvaluatorV2.LogKeys import com.wavesplatform.lang.v1.evaluator.{Log, ScriptResult} import com.wavesplatform.serialization.ScriptValuesJson import com.wavesplatform.state.InvokeScriptResult import com.wavesplatform.transaction.Asset.IssuedAsset import com.wavesplatform.transaction.TransactionBase import com.wavesplatform.transaction.TxValidationError.{FailedTransactionError, ScriptExecutionError, TransactionNotAllowedByScript} -import com.wavesplatform.transaction.assets._ +import com.wavesplatform.transaction.assets.* import com.wavesplatform.transaction.assets.exchange.ExchangeTransaction import com.wavesplatform.transaction.smart.InvokeScriptTransaction import com.wavesplatform.transaction.transfer.{MassTransferTransaction, TransferTransaction} import play.api.libs.json.Json.JsValueWrapper -import play.api.libs.json._ +import play.api.libs.json.* sealed abstract class TraceStep { def json: JsObject // TODO: Is this format necessary? @@ -94,7 +95,7 @@ case class InvokeScriptTrace( ) ++ (resultE match { case Right(value) => TraceStep.maybeErrorJson(None) ++ Json.obj("result" -> TraceStep.scriptResultJson(invokeId, value)) case Left(e) => TraceStep.maybeErrorJson(Some(e)) - }) ++ (if (logged) Json.obj(TraceStep.logJson(log)) else JsObject.empty) + }) ++ (if (logged && resultE.isRight) Json.obj(TraceStep.logJson(log)) else JsObject.empty) } } @@ -112,12 +113,13 @@ object TraceStep { case see: ScriptExecutionError => Json.obj(logJson(see.log), "error" -> see.message) case tne: TransactionNotAllowedByScript => Json.obj(logJson(tne.log), "error" -> JsNull) case fte: FailedTransactionError => Json.obj(logJson(fte.log), "error" -> fte.error.map(JsString)) - case a => Json.obj("error" -> a.toString) + case a => Json.obj("error" -> a.toString) } private[trace] def logJson(l: Log[Id]): (String, JsValueWrapper) = - "vars" -> l.map { - case (k, Right(v)) => Json.obj("name" -> k) ++ ScriptValuesJson.serializeValue(v) - case (k, Left(err)) => Json.obj("name" -> k, "error" -> err.message) + "vars" -> l.collect { + case (k, Right(v)) if !LogKeys.TraceExcluded.exists(k.contains) => Json.obj("name" -> k) ++ ScriptValuesJson.serializeValue(v) + case (k, Left(CommonError(_, Some(fte: FailedTransactionError)))) => Json.obj("name" -> k, "error" -> fte.error) + case (k, Left(err)) => Json.obj("name" -> k, "error" -> err.message) } } diff --git a/node/src/main/scala/com/wavesplatform/utils/generator/BlockchainGeneratorApp.scala b/node/src/main/scala/com/wavesplatform/utils/generator/BlockchainGeneratorApp.scala index 5b0dff3ba04..a928f769839 100644 --- a/node/src/main/scala/com/wavesplatform/utils/generator/BlockchainGeneratorApp.scala +++ b/node/src/main/scala/com/wavesplatform/utils/generator/BlockchainGeneratorApp.scala @@ -148,7 +148,7 @@ object BlockchainGeneratorApp extends ScorexLogging { map.get(account).toRight(GenericError(s"No key for $account")) } - val utx = new UtxPoolImpl(fakeTime, blockchain, wavesSettings.utxSettings, wavesSettings.minerSettings.enable) + val utx = new UtxPoolImpl(fakeTime, blockchain, wavesSettings.utxSettings, wavesSettings.maxTxErrorLogSize, wavesSettings.minerSettings.enable) val posSelector = PoSSelector(blockchain, None) val utxEvents = ConcurrentSubject.publish[UtxEvent](scheduler) val miner = new MinerImpl( diff --git a/node/src/main/scala/com/wavesplatform/utx/UtxPoolImpl.scala b/node/src/main/scala/com/wavesplatform/utx/UtxPoolImpl.scala index 9ec60b7eb3a..76cdd573e02 100644 --- a/node/src/main/scala/com/wavesplatform/utx/UtxPoolImpl.scala +++ b/node/src/main/scala/com/wavesplatform/utx/UtxPoolImpl.scala @@ -20,7 +20,7 @@ import com.wavesplatform.state.diffs.{BlockDiffer, TransactionDiffer} import com.wavesplatform.state.reader.CompositeBlockchain import com.wavesplatform.state.{Blockchain, Diff, Portfolio} import com.wavesplatform.transaction.* -import com.wavesplatform.transaction.TxValidationError.{AlreadyInTheState, GenericError, SenderIsBlacklisted} +import com.wavesplatform.transaction.TxValidationError.{AlreadyInTheState, GenericError, SenderIsBlacklisted, WithLog} import com.wavesplatform.transaction.assets.exchange.ExchangeTransaction import com.wavesplatform.transaction.smart.InvokeScriptTransaction import com.wavesplatform.transaction.smart.script.trace.TracedResult @@ -43,6 +43,7 @@ class UtxPoolImpl( time: Time, blockchain: Blockchain, utxSettings: UtxSettings, + maxTxErrorLogSize: Int, isMiningEnabled: Boolean, onEvent: UtxEvent => Unit = _ => (), nanoTimeSource: () => TxTimestamp = () => System.nanoTime() @@ -155,7 +156,11 @@ class UtxPoolImpl( log.trace(s"putIfNew(${tx.id()}) succeeded, isNew = $isNew") case Left(err) => log.debug(s"putIfNew(${tx.id()}) failed with ${extractErrorMessage(err)}") - traceLogger.trace(err.toString) + val errMsg = err match { + case w: WithLog => w.toStringWithLog(maxTxErrorLogSize) + case err => err.toString + } + traceLogger.trace(errMsg) } tracedIsNew } diff --git a/node/src/test/scala/com/wavesplatform/db/WithState.scala b/node/src/test/scala/com/wavesplatform/db/WithState.scala index ea2adcf6306..4c887aeba55 100644 --- a/node/src/test/scala/com/wavesplatform/db/WithState.scala +++ b/node/src/test/scala/com/wavesplatform/db/WithState.scala @@ -140,8 +140,8 @@ trait WithState extends DBCacheSettings with Matchers with NTPTime { _: Suite => val nextHeight = state.height + 1 val isProto = state.activatedFeatures.get(BlockchainFeatures.BlockV5.id).exists(nextHeight > 1 && nextHeight >= _) val block = TestBlock.create(txs, if (isProto) Block.ProtoBlockVersion else Block.PlainBlockVersion) - differ(state, block).map( - diff => state.append(diff.diff, diff.carry, diff.totalFee, None, block.header.generationSignature.take(Block.HitSourceLength), block) + differ(state, block).map(diff => + state.append(diff.diff, diff.carry, diff.totalFee, None, block.header.generationSignature.take(Block.HitSourceLength), block) ) }) } @@ -165,7 +165,8 @@ trait WithDomain extends WithState { _: Suite => DomainPresets.domainSettingsWithFS(fs) def withDomain[A]( - settings: WavesSettings = DomainPresets.SettingsFromDefaultConfig.addFeatures(BlockchainFeatures.SmartAccounts), // SmartAccounts to allow V2 transfers by default + settings: WavesSettings = + DomainPresets.SettingsFromDefaultConfig.addFeatures(BlockchainFeatures.SmartAccounts), // SmartAccounts to allow V2 transfers by default balances: Seq[AddrWithBalance] = Seq.empty, wrapDB: DB => DB = identity, beforeSetPriorityDiffs: () => Unit = () => () @@ -181,9 +182,8 @@ trait WithDomain extends WithState { _: Suite => loadActiveLeases(db, _, _) ) domain = Domain(wrapDB(db), bcu, blockchain, settings, beforeSetPriorityDiffs) - val genesis = balances.map { - case AddrWithBalance(address, amount) => - TxHelpers.genesis(address, amount) + val genesis = balances.map { case AddrWithBalance(address, amount) => + TxHelpers.genesis(address, amount) } if (genesis.nonEmpty) { domain.appendBlock(genesis*) diff --git a/node/src/test/scala/com/wavesplatform/history/Domain.scala b/node/src/test/scala/com/wavesplatform/history/Domain.scala index d9a93541dc1..65a5f56c2c0 100644 --- a/node/src/test/scala/com/wavesplatform/history/Domain.scala +++ b/node/src/test/scala/com/wavesplatform/history/Domain.scala @@ -58,7 +58,8 @@ case class Domain( def createDiffE(tx: Transaction): Either[ValidationError, Diff] = transactionDiffer(tx).resultE def createDiff(tx: Transaction): Diff = createDiffE(tx).explicitGet() - lazy val utxPool = new TestUtxPool(SystemTime, blockchain, settings.utxSettings, settings.minerSettings.enable, beforeSetPriorityDiffs) + lazy val utxPool = + new TestUtxPool(SystemTime, blockchain, settings.utxSettings, settings.maxTxErrorLogSize, settings.minerSettings.enable, beforeSetPriorityDiffs) lazy val wallet: Wallet = Wallet(settings.walletSettings.copy(file = None)) def blockchainWithDiscardedDiffs(): CompositeBlockchain = { diff --git a/node/src/test/scala/com/wavesplatform/http/DebugApiRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/DebugApiRouteSpec.scala index 4d0166c3a47..981821ea122 100644 --- a/node/src/test/scala/com/wavesplatform/http/DebugApiRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/DebugApiRouteSpec.scala @@ -216,6 +216,7 @@ class DebugApiRouteSpec ) ) ) + blockchain.stub.activateAllFeatures() } val route = routeWithBlockchain(blockchain) @@ -226,7 +227,7 @@ class DebugApiRouteSpec (json \ "validationTime").as[Int] shouldBe 1000 +- 1000 (json \ "error").as[String] should include("not allowed by script of the asset") (json \ "trace").as[JsArray] shouldBe Json.parse( - "[{\"type\":\"asset\",\"context\":\"orderAmount\",\"id\":\"5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx\",\"result\":\"failure\",\"vars\":[],\"error\":\"error\"}]" + "[{\"type\":\"asset\",\"context\":\"orderAmount\",\"id\":\"5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx\",\"result\":\"failure\",\"vars\":[{\"name\":\"throw.@args\",\"type\":\"Array\",\"value\":[{\"type\":\"String\",\"value\":\"error\"}]},{\"name\":\"throw.@complexity\",\"type\":\"Int\",\"value\":1},{\"name\":\"@complexityLimit\",\"type\":\"Int\",\"value\":2147483646}],\"error\":\"error\"}]" ) } } @@ -318,6 +319,7 @@ class DebugApiRouteSpec ) (blockchain.hasAccountScript _).when(*).returns(true) + blockchain.stub.activateAllFeatures() } val route = routeWithBlockchain(blockchain) @@ -336,7 +338,7 @@ class DebugApiRouteSpec } } - def testPayment(result: String) = withClue("payment") { + def testPayment(result: InvokeScriptTransaction => String) = withClue("payment") { val tx = TxHelpers.invoke(TxHelpers.secondAddress, fee = 1300000, payments = Seq(Payment(1L, TestValues.asset))) jsonPost(routePath("/validate"), tx.json()) ~> route ~> check { @@ -347,108 +349,738 @@ class DebugApiRouteSpec else (json \ "transaction").as[JsObject] should matchJson(tx.json()) - (json \ "trace").as[JsArray] should matchJson(Json.parse(result)) + (json \ "trace").as[JsArray] should matchJson(Json.parse(result(tx))) } } - testPayment("""[ { - | "type" : "verifier", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "result" : "success", - | "error" : null - |}, { - | "type" : "dApp", - | "id" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC", - | "function" : "default", - | "args" : [ ], - | "invocations" : [ ], - | "result" : { - | "data" : [ ], - | "transfers" : [ ], - | "issues" : [ ], - | "reissues" : [ ], - | "burns" : [ ], - | "sponsorFees" : [ ], - | "leases" : [ ], - | "leaseCancels" : [ ], - | "invokes" : [ ] - | }, - | "error" : null, - | "vars" : [ ] - |}, { - | "type" : "asset", - | "context" : "payment", - | "id" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", - | "result" : "failure", - | "vars" : [ { - | "name" : "test", - | "type" : "Boolean", - | "value" : true - | } ], - | "error" : "error" - |} ]""".stripMargin) + testPayment(tx => s"""[ { + | "type" : "verifier", + | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "result" : "success", + | "error" : null + |}, { + | "type" : "dApp", + | "id" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC", + | "function" : "default", + | "args" : [ ], + | "invocations" : [ ], + | "result" : { + | "data" : [ ], + | "transfers" : [ ], + | "issues" : [ ], + | "reissues" : [ ], + | "burns" : [ ], + | "sponsorFees" : [ ], + | "leases" : [ ], + | "leaseCancels" : [ ], + | "invokes" : [ ] + | }, + | "error" : null, + | "vars" : [ { + | "name" : "i", + | "type" : "Invocation", + | "value" : { + | "payments" : { + | "type" : "Array", + | "value" : [ { + | "type" : "AttachedPayment", + | "value" : { + | "amount" : { + | "type" : "Int", + | "value" : 1 + | }, + | "assetId" : { + | "type" : "ByteVector", + | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | } + | } + | } ] + | }, + | "callerPublicKey" : { + | "type" : "ByteVector", + | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | }, + | "feeAssetId" : { + | "type" : "Unit", + | "value" : { } + | }, + | "transactionId" : { + | "type" : "ByteVector", + | "value" : "${tx.id()}" + | }, + | "caller" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | } + | } + | }, + | "fee" : { + | "type" : "Int", + | "value" : 1300000 + | } + | } + | }, { + | "name" : "default.@args", + | "type" : "Array", + | "value" : [ ] + | }, { + | "name" : "default.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51994 + | } ] + |}, { + | "type" : "asset", + | "context" : "payment", + | "id" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "result" : "failure", + | "vars" : [ { + | "name" : "test", + | "type" : "Boolean", + | "value" : true + | }, { + | "name" : "throw.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "String", + | "value" : "error" + | } ] + | }, { + | "name" : "throw.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 2147483646 + | } ], + | "error" : "error" + |} ]""".stripMargin) testFunction( "dataAndTransfer", - _ => """[ { - | "type" : "verifier", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "result" : "success", - | "error" : null - |}, { - | "type" : "dApp", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "function" : "dataAndTransfer", - | "args" : [ ], - | "invocations" : [ ], - | "result" : { - | "data" : [ { - | "key" : "key", - | "type" : "integer", - | "value" : 1 - | }, { - | "key" : "key", - | "type" : "boolean", - | "value" : true - | }, { - | "key" : "key", - | "type" : "string", - | "value" : "str" - | }, { - | "key" : "key", - | "type" : "binary", - | "value" : "base64:" - | }, { - | "key" : "key", - | "value" : null - | } ], - | "transfers" : [ { - | "address" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC", - | "asset" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", - | "amount" : 1 - | } ], - | "issues" : [ ], - | "reissues" : [ ], - | "burns" : [ ], - | "sponsorFees" : [ ], - | "leases" : [ ], - | "leaseCancels" : [ ], - | "invokes" : [ ] - | }, - | "error" : null, - | "vars" : [ ] - |}, { - | "type" : "asset", - | "context" : "transfer", - | "id" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", - | "result" : "failure", - | "vars" : [ { - | "name" : "test", - | "type" : "Boolean", - | "value" : true - | } ], - | "error" : "error" - |} ]""".stripMargin + tx => s"""[ { + | "type" : "verifier", + | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "result" : "success", + | "error" : null + |}, { + | "type" : "dApp", + | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "function" : "dataAndTransfer", + | "args" : [ ], + | "invocations" : [ ], + | "result" : { + | "data" : [ { + | "key" : "key", + | "type" : "integer", + | "value" : 1 + | }, { + | "key" : "key", + | "type" : "boolean", + | "value" : true + | }, { + | "key" : "key", + | "type" : "string", + | "value" : "str" + | }, { + | "key" : "key", + | "type" : "binary", + | "value" : "base64:" + | }, { + | "key" : "key", + | "value" : null + | } ], + | "transfers" : [ { + | "address" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC", + | "asset" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "amount" : 1 + | } ], + | "issues" : [ ], + | "reissues" : [ ], + | "burns" : [ ], + | "sponsorFees" : [ ], + | "leases" : [ ], + | "leaseCancels" : [ ], + | "invokes" : [ ] + | }, + | "error" : null, + | "vars" : [ { + | "name" : "i", + | "type" : "Invocation", + | "value" : { + | "payments" : { + | "type" : "Array", + | "value" : [ ] + | }, + | "callerPublicKey" : { + | "type" : "ByteVector", + | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | }, + | "feeAssetId" : { + | "type" : "Unit", + | "value" : { } + | }, + | "transactionId" : { + | "type" : "ByteVector", + | "value" : "${tx.id()}" + | }, + | "caller" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | } + | } + | }, + | "fee" : { + | "type" : "Int", + | "value" : 102500000 + | } + | } + | }, { + | "name" : "dataAndTransfer.@args", + | "type" : "Array", + | "value" : [ ] + | }, { + | "name" : "IntegerEntry.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "String", + | "value" : "key" + | }, { + | "type" : "Int", + | "value" : 1 + | } ] + | }, { + | "name" : "IntegerEntry.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51999 + | }, { + | "name" : "BooleanEntry.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "String", + | "value" : "key" + | }, { + | "type" : "Boolean", + | "value" : true + | } ] + | }, { + | "name" : "BooleanEntry.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51998 + | }, { + | "name" : "StringEntry.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "String", + | "value" : "key" + | }, { + | "type" : "String", + | "value" : "str" + | } ] + | }, { + | "name" : "StringEntry.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51997 + | }, { + | "name" : "BinaryEntry.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "String", + | "value" : "key" + | }, { + | "type" : "ByteVector", + | "value" : "" + | } ] + | }, { + | "name" : "BinaryEntry.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51996 + | }, { + | "name" : "DeleteEntry.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "String", + | "value" : "key" + | } ] + | }, { + | "name" : "DeleteEntry.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51995 + | }, { + | "name" : "Address.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "ByteVector", + | "value" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC" + | } ] + | }, { + | "name" : "Address.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51994 + | }, { + | "name" : "ScriptTransfer.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC" + | } + | } + | }, { + | "type" : "Int", + | "value" : 1 + | }, { + | "type" : "ByteVector", + | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | } ] + | }, { + | "name" : "ScriptTransfer.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51993 + | }, { + | "name" : "cons.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "ScriptTransfer", + | "value" : { + | "recipient" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC" + | } + | } + | }, + | "amount" : { + | "type" : "Int", + | "value" : 1 + | }, + | "asset" : { + | "type" : "ByteVector", + | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | } + | } + | }, { + | "type" : "Array", + | "value" : [ ] + | } ] + | }, { + | "name" : "cons.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51992 + | }, { + | "name" : "cons.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "DeleteEntry", + | "value" : { + | "key" : { + | "type" : "String", + | "value" : "key" + | } + | } + | }, { + | "type" : "Array", + | "value" : [ { + | "type" : "ScriptTransfer", + | "value" : { + | "recipient" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC" + | } + | } + | }, + | "amount" : { + | "type" : "Int", + | "value" : 1 + | }, + | "asset" : { + | "type" : "ByteVector", + | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | } + | } + | } ] + | } ] + | }, { + | "name" : "cons.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51991 + | }, { + | "name" : "cons.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "BinaryEntry", + | "value" : { + | "key" : { + | "type" : "String", + | "value" : "key" + | }, + | "value" : { + | "type" : "ByteVector", + | "value" : "" + | } + | } + | }, { + | "type" : "Array", + | "value" : [ { + | "type" : "DeleteEntry", + | "value" : { + | "key" : { + | "type" : "String", + | "value" : "key" + | } + | } + | }, { + | "type" : "ScriptTransfer", + | "value" : { + | "recipient" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC" + | } + | } + | }, + | "amount" : { + | "type" : "Int", + | "value" : 1 + | }, + | "asset" : { + | "type" : "ByteVector", + | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | } + | } + | } ] + | } ] + | }, { + | "name" : "cons.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51990 + | }, { + | "name" : "cons.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "StringEntry", + | "value" : { + | "key" : { + | "type" : "String", + | "value" : "key" + | }, + | "value" : { + | "type" : "String", + | "value" : "str" + | } + | } + | }, { + | "type" : "Array", + | "value" : [ { + | "type" : "BinaryEntry", + | "value" : { + | "key" : { + | "type" : "String", + | "value" : "key" + | }, + | "value" : { + | "type" : "ByteVector", + | "value" : "" + | } + | } + | }, { + | "type" : "DeleteEntry", + | "value" : { + | "key" : { + | "type" : "String", + | "value" : "key" + | } + | } + | }, { + | "type" : "ScriptTransfer", + | "value" : { + | "recipient" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC" + | } + | } + | }, + | "amount" : { + | "type" : "Int", + | "value" : 1 + | }, + | "asset" : { + | "type" : "ByteVector", + | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | } + | } + | } ] + | } ] + | }, { + | "name" : "cons.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51989 + | }, { + | "name" : "cons.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "BooleanEntry", + | "value" : { + | "key" : { + | "type" : "String", + | "value" : "key" + | }, + | "value" : { + | "type" : "Boolean", + | "value" : true + | } + | } + | }, { + | "type" : "Array", + | "value" : [ { + | "type" : "StringEntry", + | "value" : { + | "key" : { + | "type" : "String", + | "value" : "key" + | }, + | "value" : { + | "type" : "String", + | "value" : "str" + | } + | } + | }, { + | "type" : "BinaryEntry", + | "value" : { + | "key" : { + | "type" : "String", + | "value" : "key" + | }, + | "value" : { + | "type" : "ByteVector", + | "value" : "" + | } + | } + | }, { + | "type" : "DeleteEntry", + | "value" : { + | "key" : { + | "type" : "String", + | "value" : "key" + | } + | } + | }, { + | "type" : "ScriptTransfer", + | "value" : { + | "recipient" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC" + | } + | } + | }, + | "amount" : { + | "type" : "Int", + | "value" : 1 + | }, + | "asset" : { + | "type" : "ByteVector", + | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | } + | } + | } ] + | } ] + | }, { + | "name" : "cons.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51988 + | }, { + | "name" : "cons.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "IntegerEntry", + | "value" : { + | "key" : { + | "type" : "String", + | "value" : "key" + | }, + | "value" : { + | "type" : "Int", + | "value" : 1 + | } + | } + | }, { + | "type" : "Array", + | "value" : [ { + | "type" : "BooleanEntry", + | "value" : { + | "key" : { + | "type" : "String", + | "value" : "key" + | }, + | "value" : { + | "type" : "Boolean", + | "value" : true + | } + | } + | }, { + | "type" : "StringEntry", + | "value" : { + | "key" : { + | "type" : "String", + | "value" : "key" + | }, + | "value" : { + | "type" : "String", + | "value" : "str" + | } + | } + | }, { + | "type" : "BinaryEntry", + | "value" : { + | "key" : { + | "type" : "String", + | "value" : "key" + | }, + | "value" : { + | "type" : "ByteVector", + | "value" : "" + | } + | } + | }, { + | "type" : "DeleteEntry", + | "value" : { + | "key" : { + | "type" : "String", + | "value" : "key" + | } + | } + | }, { + | "type" : "ScriptTransfer", + | "value" : { + | "recipient" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC" + | } + | } + | }, + | "amount" : { + | "type" : "Int", + | "value" : 1 + | }, + | "asset" : { + | "type" : "ByteVector", + | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | } + | } + | } ] + | } ] + | }, { + | "name" : "cons.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51987 + | } ] + |}, { + | "type" : "asset", + | "context" : "transfer", + | "id" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "result" : "failure", + | "vars" : [ { + | "name" : "test", + | "type" : "Boolean", + | "value" : true + | }, { + | "name" : "throw.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "String", + | "value" : "error" + | } ] + | }, { + | "name" : "throw.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 2147483646 + | } ], + | "error" : "error" + |} ]""".stripMargin ) testFunction( @@ -486,98 +1118,419 @@ class DebugApiRouteSpec | }, | "error" : null, | "vars" : [ { + | "name" : "i", + | "type" : "Invocation", + | "value" : { + | "payments" : { + | "type" : "Array", + | "value" : [ ] + | }, + | "callerPublicKey" : { + | "type" : "ByteVector", + | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | }, + | "feeAssetId" : { + | "type" : "Unit", + | "value" : { } + | }, + | "transactionId" : { + | "type" : "ByteVector", + | "value" : "${tx.id()}" + | }, + | "caller" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | } + | } + | }, + | "fee" : { + | "type" : "Int", + | "value" : 102500000 + | } + | } + | }, { + | "name" : "issue.@args", + | "type" : "Array", + | "value" : [ ] + | }, { | "name" : "decimals", | "type" : "Int", | "value" : 4 + | }, { + | "name" : "Issue.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "String", + | "value" : "name" + | }, { + | "type" : "String", + | "value" : "description" + | }, { + | "type" : "Int", + | "value" : 1000 + | }, { + | "type" : "Int", + | "value" : 4 + | }, { + | "type" : "Boolean", + | "value" : true + | }, { + | "type" : "Unit", + | "value" : { } + | }, { + | "type" : "Int", + | "value" : 0 + | } ] + | }, { + | "name" : "Issue.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51999 + | }, { + | "name" : "cons.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Issue", + | "value" : { + | "isReissuable" : { + | "type" : "Boolean", + | "value" : true + | }, + | "nonce" : { + | "type" : "Int", + | "value" : 0 + | }, + | "description" : { + | "type" : "String", + | "value" : "description" + | }, + | "decimals" : { + | "type" : "Int", + | "value" : 4 + | }, + | "compiledScript" : { + | "type" : "Unit", + | "value" : { } + | }, + | "name" : { + | "type" : "String", + | "value" : "name" + | }, + | "quantity" : { + | "type" : "Int", + | "value" : 1000 + | } + | } + | }, { + | "type" : "Array", + | "value" : [ ] + | } ] + | }, { + | "name" : "cons.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51998 | } ] |} ]""".stripMargin ) testFunction( "reissue", - _ => """[ { - | "type" : "verifier", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "result" : "success", - | "error" : null - |}, { - | "type" : "dApp", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "function" : "reissue", - | "args" : [ ], - | "invocations" : [ ], - | "result" : { - | "data" : [ ], - | "transfers" : [ ], - | "issues" : [ ], - | "reissues" : [ { - | "assetId" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", - | "isReissuable" : false, - | "quantity" : 1 - | } ], - | "burns" : [ ], - | "sponsorFees" : [ ], - | "leases" : [ ], - | "leaseCancels" : [ ], - | "invokes" : [ ] - | }, - | "error" : null, - | "vars" : [ ] - |}, { - | "type" : "asset", - | "context" : "reissue", - | "id" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", - | "result" : "failure", - | "vars" : [ { - | "name" : "test", - | "type" : "Boolean", - | "value" : true - | } ], - | "error" : "error" - |} ]""".stripMargin + tx => s"""[ { + | "type" : "verifier", + | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "result" : "success", + | "error" : null + |}, { + | "type" : "dApp", + | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "function" : "reissue", + | "args" : [ ], + | "invocations" : [ ], + | "result" : { + | "data" : [ ], + | "transfers" : [ ], + | "issues" : [ ], + | "reissues" : [ { + | "assetId" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "isReissuable" : false, + | "quantity" : 1 + | } ], + | "burns" : [ ], + | "sponsorFees" : [ ], + | "leases" : [ ], + | "leaseCancels" : [ ], + | "invokes" : [ ] + | }, + | "error" : null, + | "vars" : [ { + | "name" : "i", + | "type" : "Invocation", + | "value" : { + | "payments" : { + | "type" : "Array", + | "value" : [ ] + | }, + | "callerPublicKey" : { + | "type" : "ByteVector", + | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | }, + | "feeAssetId" : { + | "type" : "Unit", + | "value" : { } + | }, + | "transactionId" : { + | "type" : "ByteVector", + | "value" : "${tx.id()}" + | }, + | "caller" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | } + | } + | }, + | "fee" : { + | "type" : "Int", + | "value" : 102500000 + | } + | } + | }, { + | "name" : "reissue.@args", + | "type" : "Array", + | "value" : [ ] + | }, { + | "name" : "Reissue.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "ByteVector", + | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | }, { + | "type" : "Int", + | "value" : 1 + | }, { + | "type" : "Boolean", + | "value" : false + | } ] + | }, { + | "name" : "Reissue.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51999 + | }, { + | "name" : "cons.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Reissue", + | "value" : { + | "assetId" : { + | "type" : "ByteVector", + | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | }, + | "quantity" : { + | "type" : "Int", + | "value" : 1 + | }, + | "isReissuable" : { + | "type" : "Boolean", + | "value" : false + | } + | } + | }, { + | "type" : "Array", + | "value" : [ ] + | } ] + | }, { + | "name" : "cons.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51998 + | } ] + |}, { + | "type" : "asset", + | "context" : "reissue", + | "id" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "result" : "failure", + | "vars" : [ { + | "name" : "test", + | "type" : "Boolean", + | "value" : true + | }, { + | "name" : "throw.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "String", + | "value" : "error" + | } ] + | }, { + | "name" : "throw.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 2147483646 + | } ], + | "error" : "error" + |} ]""".stripMargin ) testFunction( "burn", - _ => """[ { - | "type" : "verifier", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "result" : "success", - | "error" : null - |}, { - | "type" : "dApp", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "function" : "burn", - | "args" : [ ], - | "invocations" : [ ], - | "result" : { - | "data" : [ ], - | "transfers" : [ ], - | "issues" : [ ], - | "reissues" : [ ], - | "burns" : [ { - | "assetId" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", - | "quantity" : 1 - | } ], - | "sponsorFees" : [ ], - | "leases" : [ ], - | "leaseCancels" : [ ], - | "invokes" : [ ] - | }, - | "error" : null, - | "vars" : [ ] - |}, { - | "type" : "asset", - | "context" : "burn", - | "id" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", - | "result" : "failure", - | "vars" : [ { - | "name" : "test", - | "type" : "Boolean", - | "value" : true - | } ], - | "error" : "error" - |} ]""".stripMargin + tx => s"""[ { + | "type" : "verifier", + | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "result" : "success", + | "error" : null + |}, { + | "type" : "dApp", + | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "function" : "burn", + | "args" : [ ], + | "invocations" : [ ], + | "result" : { + | "data" : [ ], + | "transfers" : [ ], + | "issues" : [ ], + | "reissues" : [ ], + | "burns" : [ { + | "assetId" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "quantity" : 1 + | } ], + | "sponsorFees" : [ ], + | "leases" : [ ], + | "leaseCancels" : [ ], + | "invokes" : [ ] + | }, + | "error" : null, + | "vars" : [ { + | "name" : "i", + | "type" : "Invocation", + | "value" : { + | "payments" : { + | "type" : "Array", + | "value" : [ ] + | }, + | "callerPublicKey" : { + | "type" : "ByteVector", + | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | }, + | "feeAssetId" : { + | "type" : "Unit", + | "value" : { } + | }, + | "transactionId" : { + | "type" : "ByteVector", + | "value" : "${tx.id()}" + | }, + | "caller" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | } + | } + | }, + | "fee" : { + | "type" : "Int", + | "value" : 102500000 + | } + | } + | }, { + | "name" : "burn.@args", + | "type" : "Array", + | "value" : [ ] + | }, { + | "name" : "Burn.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "ByteVector", + | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | }, { + | "type" : "Int", + | "value" : 1 + | } ] + | }, { + | "name" : "Burn.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51999 + | }, { + | "name" : "cons.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Burn", + | "value" : { + | "assetId" : { + | "type" : "ByteVector", + | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | }, + | "quantity" : { + | "type" : "Int", + | "value" : 1 + | } + | } + | }, { + | "type" : "Array", + | "value" : [ ] + | } ] + | }, { + | "name" : "cons.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51998 + | } ] + |}, { + | "type" : "asset", + | "context" : "burn", + | "id" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "result" : "failure", + | "vars" : [ { + | "name" : "test", + | "type" : "Boolean", + | "value" : true + | }, { + | "name" : "throw.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "String", + | "value" : "error" + | } ] + | }, { + | "name" : "throw.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 2147483646 + | } ], + | "error" : "error" + |} ]""".stripMargin ) } @@ -603,6 +1556,8 @@ class DebugApiRouteSpec (blockchain.resolveAlias _).when(Alias.create(recipient2.name).explicitGet()).returning(Right(TxHelpers.secondAddress)) + blockchain.stub.activateAllFeatures() + val (dAppScript, _) = ScriptCompiler .compile( s""" @@ -770,13 +1725,344 @@ class DebugApiRouteSpec | }, | "error" : null, | "vars" : [ { + | "name" : "i", + | "type" : "Invocation", + | "value" : { + | "originCaller" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | } + | } + | }, + | "payments" : { + | "type" : "Array", + | "value" : [ ] + | }, + | "callerPublicKey" : { + | "type" : "ByteVector", + | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | }, + | "feeAssetId" : { + | "type" : "Unit", + | "value" : { } + | }, + | "originCallerPublicKey" : { + | "type" : "ByteVector", + | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | }, + | "transactionId" : { + | "type" : "ByteVector", + | "value" : "${invoke.id()}" + | }, + | "caller" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | } + | } + | }, + | "fee" : { + | "type" : "Int", + | "value" : 500000 + | } + | } + | }, { + | "name" : "default.@args", + | "type" : "Array", + | "value" : [ ] + | }, { + | "name" : "parseBigIntValue.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "String", + | "value" : "6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042047" + | } ] + | }, { + | "name" : "parseBigIntValue.@complexity", + | "type" : "Int", + | "value" : 65 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25935 + | }, { | "name" : "a", | "type" : "BigInt", | "value" : 6.703903964971298549787012499102923E+153 | }, { + | "name" : "==.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "BigInt", + | "value" : 6.703903964971298549787012499102923E+153 + | }, { + | "type" : "BigInt", + | "value" : 6.703903964971298549787012499102923E+153 + | } ] + | }, { + | "name" : "==.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25934 + | }, { | "name" : "test", | "type" : "Int", | "value" : 1 + | }, { + | "name" : "==.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Int", + | "value" : 1 + | }, { + | "type" : "Int", + | "value" : 1 + | } ] + | }, { + | "name" : "==.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25933 + | }, { + | "name" : "Address.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "ByteVector", + | "value" : "3NAgxLPGnw3RGv9JT6NTDaG5D1iLUehg2xd" + | } ] + | }, { + | "name" : "Address.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25932 + | }, { + | "name" : "Lease.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3NAgxLPGnw3RGv9JT6NTDaG5D1iLUehg2xd" + | } + | } + | }, { + | "type" : "Int", + | "value" : 100 + | }, { + | "type" : "Int", + | "value" : 0 + | } ] + | }, { + | "name" : "Lease.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25931 + | }, { + | "name" : "Alias.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "String", + | "value" : "some_alias" + | } ] + | }, { + | "name" : "Alias.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25930 + | }, { + | "name" : "Lease.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Alias", + | "value" : { + | "alias" : { + | "type" : "String", + | "value" : "some_alias" + | } + | } + | }, { + | "type" : "Int", + | "value" : 20 + | }, { + | "type" : "Int", + | "value" : 2 + | } ] + | }, { + | "name" : "Lease.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25929 + | }, { + | "name" : "LeaseCancel.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "ByteVector", + | "value" : "$leaseCancelId" + | } ] + | }, { + | "name" : "LeaseCancel.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25928 + | }, { + | "name" : "cons.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "LeaseCancel", + | "value" : { + | "leaseId" : { + | "type" : "ByteVector", + | "value" : "$leaseCancelId" + | } + | } + | }, { + | "type" : "Array", + | "value" : [ ] + | } ] + | }, { + | "name" : "cons.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25927 + | }, { + | "name" : "cons.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Lease", + | "value" : { + | "recipient" : { + | "type" : "Alias", + | "value" : { + | "alias" : { + | "type" : "String", + | "value" : "some_alias" + | } + | } + | }, + | "amount" : { + | "type" : "Int", + | "value" : 20 + | }, + | "nonce" : { + | "type" : "Int", + | "value" : 2 + | } + | } + | }, { + | "type" : "Array", + | "value" : [ { + | "type" : "LeaseCancel", + | "value" : { + | "leaseId" : { + | "type" : "ByteVector", + | "value" : "$leaseCancelId" + | } + | } + | } ] + | } ] + | }, { + | "name" : "cons.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25926 + | }, { + | "name" : "cons.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Lease", + | "value" : { + | "recipient" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3NAgxLPGnw3RGv9JT6NTDaG5D1iLUehg2xd" + | } + | } + | }, + | "amount" : { + | "type" : "Int", + | "value" : 100 + | }, + | "nonce" : { + | "type" : "Int", + | "value" : 0 + | } + | } + | }, { + | "type" : "Array", + | "value" : [ { + | "type" : "Lease", + | "value" : { + | "recipient" : { + | "type" : "Alias", + | "value" : { + | "alias" : { + | "type" : "String", + | "value" : "some_alias" + | } + | } + | }, + | "amount" : { + | "type" : "Int", + | "value" : 20 + | }, + | "nonce" : { + | "type" : "Int", + | "value" : 2 + | } + | } + | }, { + | "type" : "LeaseCancel", + | "value" : { + | "leaseId" : { + | "type" : "ByteVector", + | "value" : "$leaseCancelId" + | } + | } + | } ] + | } ] + | }, { + | "name" : "cons.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25925 | } ] |} ] | @@ -793,6 +2079,7 @@ class DebugApiRouteSpec val blockchain = createBlockchainStub { blockchain => (blockchain.balance _).when(*, *).returns(Long.MaxValue) + blockchain.stub.activateAllFeatures() val (dAppScript, _) = ScriptCompiler .compile( @@ -914,13 +2201,160 @@ class DebugApiRouteSpec | }, | "error" : null, | "vars" : [ { + | "name" : "i", + | "type" : "Invocation", + | "value" : { + | "originCaller" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | } + | } + | }, + | "payments" : { + | "type" : "Array", + | "value" : [ ] + | }, + | "callerPublicKey" : { + | "type" : "ByteVector", + | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | }, + | "feeAssetId" : { + | "type" : "Unit", + | "value" : { } + | }, + | "originCallerPublicKey" : { + | "type" : "ByteVector", + | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | }, + | "transactionId" : { + | "type" : "ByteVector", + | "value" : "${invoke.id()}" + | }, + | "caller" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | } + | } + | }, + | "fee" : { + | "type" : "Int", + | "value" : 500000 + | } + | } + | }, { + | "name" : "test.@args", + | "type" : "Array", + | "value" : [ ] + | }, { + | "name" : "parseBigIntValue.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "String", + | "value" : "6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042047" + | } ] + | }, { + | "name" : "parseBigIntValue.@complexity", + | "type" : "Int", + | "value" : 65 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25860 + | }, { | "name" : "a", | "type" : "BigInt", | "value" : 6.703903964971298549787012499102923E+153 | }, { + | "name" : "==.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "BigInt", + | "value" : 6.703903964971298549787012499102923E+153 + | }, { + | "type" : "BigInt", + | "value" : 6.703903964971298549787012499102923E+153 + | } ] + | }, { + | "name" : "==.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25859 + | }, { | "name" : "test", | "type" : "Int", | "value" : 1 + | }, { + | "name" : "==.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Int", + | "value" : 1 + | }, { + | "type" : "Int", + | "value" : 1 + | } ] + | }, { + | "name" : "==.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25858 + | }, { + | "name" : "IntegerEntry.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "String", + | "value" : "key" + | }, { + | "type" : "Int", + | "value" : 1 + | } ] + | }, { + | "name" : "IntegerEntry.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25857 + | }, { + | "name" : "cons.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "IntegerEntry", + | "value" : { + | "key" : { + | "type" : "String", + | "value" : "key" + | }, + | "value" : { + | "type" : "Int", + | "value" : 1 + | } + | } + | }, { + | "type" : "Array", + | "value" : [ ] + | } ] + | }, { + | "name" : "cons.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25856 | } ] | } ], | "result" : { @@ -936,9 +2370,133 @@ class DebugApiRouteSpec | }, | "error" : null, | "vars" : [ { + | "name" : "i", + | "type" : "Invocation", + | "value" : { + | "originCaller" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | } + | } + | }, + | "payments" : { + | "type" : "Array", + | "value" : [ ] + | }, + | "callerPublicKey" : { + | "type" : "ByteVector", + | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | }, + | "feeAssetId" : { + | "type" : "Unit", + | "value" : { } + | }, + | "originCallerPublicKey" : { + | "type" : "ByteVector", + | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | }, + | "transactionId" : { + | "type" : "ByteVector", + | "value" : "${invoke.id()}" + | }, + | "caller" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | } + | } + | }, + | "fee" : { + | "type" : "Int", + | "value" : 500000 + | } + | } + | }, { + | "name" : "test1.@args", + | "type" : "Array", + | "value" : [ ] + | }, { + | "name" : "reentrantInvoke.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | } + | } + | }, { + | "type" : "String", + | "value" : "test" + | }, { + | "type" : "Array", + | "value" : [ ] + | }, { + | "type" : "Array", + | "value" : [ ] + | } ] + | }, { + | "name" : "reentrantInvoke.@complexity", + | "type" : "Int", + | "value" : 75 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25925 + | }, { + | "name" : "test.@complexity", + | "type" : "Int", + | "value" : 69 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25856 + | }, { | "name" : "result", | "type" : "Unit", | "value" : { } + | }, { + | "name" : "==.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Unit", + | "value" : { } + | }, { + | "type" : "Unit", + | "value" : { } + | } ] + | }, { + | "name" : "==.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25855 + | }, { + | "name" : "==.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Unit", + | "value" : { } + | }, { + | "type" : "Unit", + | "value" : { } + | } ] + | }, { + | "name" : "==.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25854 | } ] |} ] """.stripMargin @@ -1068,41 +2626,139 @@ class DebugApiRouteSpec (json \ "expression").as[String] shouldBe expression.bytes.value().base64 (json \ "valid").as[Boolean] shouldBe true (json \ "trace").as[JsArray] should matchJson( - """ - | [ - | { - | "type": "dApp", - | "id": "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "function": "default", - | "args": [], - | "invocations": [], - | "result": { - | "data": [], - | "transfers": [], - | "issues": [], - | "reissues": [ - | { - | "assetId": "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", - | "isReissuable": true, - | "quantity": 1 - | } - | ], - | "burns": [], - | "sponsorFees": [], - | "leases": [], - | "leaseCancels": [], - | "invokes": [] - | }, - | "error": null, - | "vars": [ - | { - | "name": "assetId", - | "type": "ByteVector", - | "value": "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" - | } - | ] - | } - | ] + s""" + |[ { + | "type" : "dApp", + | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "function" : "default", + | "args" : [ ], + | "invocations" : [ ], + | "result" : { + | "data" : [ ], + | "transfers" : [ ], + | "issues" : [ ], + | "reissues" : [ { + | "assetId" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "isReissuable" : true, + | "quantity" : 1 + | } ], + | "burns" : [ ], + | "sponsorFees" : [ ], + | "leases" : [ ], + | "leaseCancels" : [ ], + | "invokes" : [ ] + | }, + | "error" : null, + | "vars" : [ { + | "name" : "i", + | "type" : "Invocation", + | "value" : { + | "originCaller" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | } + | } + | }, + | "payments" : { + | "type" : "Array", + | "value" : [ ] + | }, + | "callerPublicKey" : { + | "type" : "ByteVector", + | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | }, + | "feeAssetId" : { + | "type" : "Unit", + | "value" : { } + | }, + | "originCallerPublicKey" : { + | "type" : "ByteVector", + | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | }, + | "transactionId" : { + | "type" : "ByteVector", + | "value" : "${invokeExpression.id()}" + | }, + | "caller" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | } + | } + | }, + | "fee" : { + | "type" : "Int", + | "value" : 1000000 + | } + | } + | }, { + | "name" : "default.@args", + | "type" : "Array", + | "value" : [ ] + | }, { + | "name" : "assetId", + | "type" : "ByteVector", + | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | }, { + | "name" : "Reissue.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "ByteVector", + | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | }, { + | "type" : "Int", + | "value" : 1 + | }, { + | "type" : "Boolean", + | "value" : true + | } ] + | }, { + | "name" : "Reissue.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51999 + | }, { + | "name" : "cons.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Reissue", + | "value" : { + | "assetId" : { + | "type" : "ByteVector", + | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | }, + | "quantity" : { + | "type" : "Int", + | "value" : 1 + | }, + | "isReissuable" : { + | "type" : "Boolean", + | "value" : true + | } + | } + | }, { + | "type" : "Array", + | "value" : [ ] + | } ] + | }, { + | "name" : "cons.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51998 + | } ] + |} ] + | """.stripMargin ) } diff --git a/node/src/test/scala/com/wavesplatform/http/TransactionBroadcastSpec.scala b/node/src/test/scala/com/wavesplatform/http/TransactionBroadcastSpec.scala index a7dbfcfdcb8..b3c6b9f3c5a 100644 --- a/node/src/test/scala/com/wavesplatform/http/TransactionBroadcastSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/TransactionBroadcastSpec.scala @@ -223,6 +223,8 @@ class TransactionBroadcastSpec val leaseId2 = Lease.calculateId(Lease(recipient2, amount2, nonce2), invoke.id()) val blockchain = createBlockchainStub { blockchain => + blockchain.stub.activateAllFeatures() + val (dAppScript, _) = ScriptCompiler .compile( s""" @@ -276,20 +278,24 @@ class TransactionBroadcastSpec Post(routePath("/broadcast?trace=true"), invoke.json()) ~> route ~> check { responseAs[JsObject] should matchJson( s"""{ - | "type" : 16, - | "id" : "${invoke.id()}", - | "sender" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "senderPublicKey" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ", - | "fee" : 500000, - | "feeAssetId" : null, - | "timestamp" : ${invoke.timestamp}, - | "proofs" : [ "${invoke.signature}" ], - | "version" : 1, - | "dApp" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "payment" : [ ], - | "call" : { - | "function" : "test", - | "args" : [ ] + | "error" : 306, + | "message" : "Error while executing dApp: Lease with id=$leaseCancelId not found", + | "transaction" : { + | "type" : 16, + | "id" : "${invoke.id()}", + | "fee" : 500000, + | "feeAssetId" : null, + | "timestamp" : ${invoke.timestamp}, + | "version" : 1, + | "sender" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "senderPublicKey" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ", + | "proofs" : [ "${invoke.signature}" ], + | "dApp" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "payment" : [ ], + | "call" : { + | "function" : "test", + | "args" : [ ] + | } | }, | "trace" : [ { | "type" : "verifier", @@ -301,7 +307,7 @@ class TransactionBroadcastSpec | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", | "function" : "test", | "args" : [ ], - | "invocations": [], + | "invocations" : [ ], | "result" : { | "data" : [ ], | "transfers" : [ ], @@ -309,35 +315,329 @@ class TransactionBroadcastSpec | "reissues" : [ ], | "burns" : [ ], | "sponsorFees" : [ ], - | "leases" : [ - | { - | "recipient" : "${recipient1.bytes}", - | "amount" : $amount1, - | "nonce" : $nonce1, - | "id" : "$leaseId1" - | }, - | { - | "recipient" : "alias:T:${recipient2.name}", - | "amount" : $amount2, - | "nonce" : $nonce2, - | "id" : "$leaseId2" - | } - | ], - | "leaseCancels" : [ - | { - | "id":"$leaseCancelId" - | } - | ], + | "leases" : [ { + | "recipient" : "${recipient1.bytes}", + | "amount" : $amount1, + | "nonce" : $nonce1, + | "id" : "$leaseId1" + | }, { + | "recipient" : "alias:T:${recipient2.name}", + | "amount" : $amount2, + | "nonce" : $nonce2, + | "id" : "$leaseId2" + | } ], + | "leaseCancels" : [ { + | "id" : "$leaseCancelId" + | } ], | "invokes" : [ ] | }, | "error" : null, | "vars" : [ { + | "name" : "i", + | "type" : "Invocation", + | "value" : { + | "originCaller" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | } + | } + | }, + | "payments" : { + | "type" : "Array", + | "value" : [ ] + | }, + | "callerPublicKey" : { + | "type" : "ByteVector", + | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | }, + | "feeAssetId" : { + | "type" : "Unit", + | "value" : { } + | }, + | "originCallerPublicKey" : { + | "type" : "ByteVector", + | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | }, + | "transactionId" : { + | "type" : "ByteVector", + | "value" : "${invoke.id()}" + | }, + | "caller" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | } + | } + | }, + | "fee" : { + | "type" : "Int", + | "value" : 500000 + | } + | } + | }, { + | "name" : "test.@args", + | "type" : "Array", + | "value" : [ ] + | }, { | "name" : "test", | "type" : "Int", | "value" : 1 + | }, { + | "name" : "==.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Int", + | "value" : 1 + | }, { + | "type" : "Int", + | "value" : 1 + | } ] + | }, { + | "name" : "==.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25999 + | }, { + | "name" : "Address.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "ByteVector", + | "value" : "3NAgxLPGnw3RGv9JT6NTDaG5D1iLUehg2xd" + | } ] + | }, { + | "name" : "Address.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25998 + | }, { + | "name" : "Lease.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3NAgxLPGnw3RGv9JT6NTDaG5D1iLUehg2xd" + | } + | } + | }, { + | "type" : "Int", + | "value" : 100 + | }, { + | "type" : "Int", + | "value" : 0 + | } ] + | }, { + | "name" : "Lease.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25997 + | }, { + | "name" : "Alias.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "String", + | "value" : "some_alias" + | } ] + | }, { + | "name" : "Alias.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25996 + | }, { + | "name" : "Lease.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Alias", + | "value" : { + | "alias" : { + | "type" : "String", + | "value" : "some_alias" + | } + | } + | }, { + | "type" : "Int", + | "value" : 20 + | }, { + | "type" : "Int", + | "value" : 2 + | } ] + | }, { + | "name" : "Lease.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25995 + | }, { + | "name" : "LeaseCancel.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "ByteVector", + | "value" : "$leaseCancelId" + | } ] + | }, { + | "name" : "LeaseCancel.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25994 + | }, { + | "name" : "cons.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "LeaseCancel", + | "value" : { + | "leaseId" : { + | "type" : "ByteVector", + | "value" : "$leaseCancelId" + | } + | } + | }, { + | "type" : "Array", + | "value" : [ ] + | } ] + | }, { + | "name" : "cons.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25993 + | }, { + | "name" : "cons.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Lease", + | "value" : { + | "recipient" : { + | "type" : "Alias", + | "value" : { + | "alias" : { + | "type" : "String", + | "value" : "some_alias" + | } + | } + | }, + | "amount" : { + | "type" : "Int", + | "value" : 20 + | }, + | "nonce" : { + | "type" : "Int", + | "value" : 2 + | } + | } + | }, { + | "type" : "Array", + | "value" : [ { + | "type" : "LeaseCancel", + | "value" : { + | "leaseId" : { + | "type" : "ByteVector", + | "value" : "$leaseCancelId" + | } + | } + | } ] + | } ] + | }, { + | "name" : "cons.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25992 + | }, { + | "name" : "cons.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Lease", + | "value" : { + | "recipient" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3NAgxLPGnw3RGv9JT6NTDaG5D1iLUehg2xd" + | } + | } + | }, + | "amount" : { + | "type" : "Int", + | "value" : 100 + | }, + | "nonce" : { + | "type" : "Int", + | "value" : 0 + | } + | } + | }, { + | "type" : "Array", + | "value" : [ { + | "type" : "Lease", + | "value" : { + | "recipient" : { + | "type" : "Alias", + | "value" : { + | "alias" : { + | "type" : "String", + | "value" : "some_alias" + | } + | } + | }, + | "amount" : { + | "type" : "Int", + | "value" : 20 + | }, + | "nonce" : { + | "type" : "Int", + | "value" : 2 + | } + | } + | }, { + | "type" : "LeaseCancel", + | "value" : { + | "leaseId" : { + | "type" : "ByteVector", + | "value" : "$leaseCancelId" + | } + | } + | } ] + | } ] + | }, { + | "name" : "cons.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 25991 | } ] | } ] - |}""".stripMargin + |} + |""".stripMargin ) } } diff --git a/node/src/test/scala/com/wavesplatform/http/TransactionsRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/TransactionsRouteSpec.scala index 234bab66308..cb66c2afce2 100644 --- a/node/src/test/scala/com/wavesplatform/http/TransactionsRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/TransactionsRouteSpec.scala @@ -1090,7 +1090,37 @@ class TransactionsRouteSpec val dappTrace = (responseAs[JsObject] \ "trace").as[Seq[JsObject]].find(jsObject => (jsObject \ "type").as[String] == "dApp").get (dappTrace \ "error").get shouldEqual JsNull - (dappTrace \ "vars" \\ "name").map(_.as[String]) should contain theSameElementsAs Seq("leaseToAddress", "leaseToAlias", "leaseId") + (dappTrace \ "vars" \\ "name").map(_.as[String]) should contain theSameElementsAs Seq( + "i", + "default.@args", + "Address.@args", + "Lease.@args", + "Lease.@complexity", + "@complexityLimit", + "leaseToAddress", + "calculateLeaseId.@args", + "calculateLeaseId.@complexity", + "@complexityLimit", + "leaseId", + "==.@args", + "==.@complexity", + "@complexityLimit", + "Alias.@args", + "Lease.@args", + "Lease.@complexity", + "@complexityLimit", + "leaseToAlias", + "LeaseCancel.@args", + "cons.@args", + "cons.@complexity", + "@complexityLimit", + "cons.@args", + "cons.@complexity", + "@complexityLimit", + "cons.@args", + "cons.@complexity", + "@complexityLimit" + ) } } } diff --git a/node/src/test/scala/com/wavesplatform/http/UtilsRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/UtilsRouteSpec.scala index 07a248ac8bc..388c00d3d71 100644 --- a/node/src/test/scala/com/wavesplatform/http/UtilsRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/UtilsRouteSpec.scala @@ -54,6 +54,7 @@ class UtilsRouteSpec extends RouteSpec("/utils") with RestAPISettingsHelper with def getTimestamp(): Long = System.currentTimeMillis() }, restAPISettings, + Int.MaxValue, () => estimator, Schedulers.timeBoundedFixedPool( new HashedWheelTimer(), @@ -827,7 +828,7 @@ class UtilsRouteSpec extends RouteSpec("/utils") with RestAPISettingsHelper with } } - routePath("/script/evaluate/{address}") in withDomain(DomainPresets.RideV5) { d => + routePath("/script/evaluate/{address}") in withDomain(DomainPresets.RideV6) { d => val blockchain = d.blockchain val api = utilsApi.copy(blockchain = blockchain) val route = seal(api.route) @@ -906,7 +907,7 @@ class UtilsRouteSpec extends RouteSpec("/utils") with RestAPISettingsHelper with evalScript("testCallable()") ~> route ~> check { responseAs[JsValue] should matchJson( - """{"result":{"type":"Array","value":[{"type":"BinaryEntry","value":{"key":{"type":"String","value":"test"},"value":{"type":"ByteVector","value":"11111111111111111111111111"}}}]},"complexity":5,"expr":"testCallable()","address":"3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9"}""" + """{"result":{"type":"Array","value":[{"type":"BinaryEntry","value":{"key":{"type":"String","value":"test"},"value":{"type":"ByteVector","value":"11111111111111111111111111"}}}]},"complexity":2,"expr":"testCallable()","address":"3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9"}""" ) } @@ -918,15 +919,24 @@ class UtilsRouteSpec extends RouteSpec("/utils") with RestAPISettingsHelper with } evalScript("testNone()") ~> route ~> check { - responseJson shouldBe Json.obj("error" -> 306, "message" -> "Function or type 'testNone' not found") + responseJson shouldBe Json.obj( + "error" -> 306, + "message" -> "InvokeRejectError(error = Function or type 'testNone' not found, log = \n\ttestNone.@args = []\n)" + ) } evalScript("testCompl()") ~> route ~> check { - responseJson shouldBe Json.obj("error" -> 306, "message" -> "Calculation complexity limit exceeded") + responseJson shouldBe Json.obj( + "error" -> 306, + "message" -> "InvokeRejectError(error = Calculation complexity limit exceeded, log = \n\ttestCompl.@args = []\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 25800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 25600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 25400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 25200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 25000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 24800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 24600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 24400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 24200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 24000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 23800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 23600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 23400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 23200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 23000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 22800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 22600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 22400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 22200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 22000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 21800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 21600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 21400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 21200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 21000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 20800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 20600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 20400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 20200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 20000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 19800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 19600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 19400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 19200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 19000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 18800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 18600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 18400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 18200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 18000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 17800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 17600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 17400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 17200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 17000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 16800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 16600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 16400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 16200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 16000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 15800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 15600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 15400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 15200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 15000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 14800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 14600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 14400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 14200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 14000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 13800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 13600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 13400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 13200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 13000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 12800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 12600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 12400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 12200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 12000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 11800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 11600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 11400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 11200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 11000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 10800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 10600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 10400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 10200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 10000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 9800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 9600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 9400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 9200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 9000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 8800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 8600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 8400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 8200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 8000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 7800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 7600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 7400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 7200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 7000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 6800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 6600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 6400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 6200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 6000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 5800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 5600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 5400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 5200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 5000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 4800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 4600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 4400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 4200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 4000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 3800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 3600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 3400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 3200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 3000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 2800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 2600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 2400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 2200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 2000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 1800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 1600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 1400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 1200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 1000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 0\n)" + ) } evalScript("testF()") ~> route ~> check { - responseJson shouldBe Json.obj("error" -> 306, "message" -> "Test") + responseJson shouldBe Json.obj( + "error" -> 306, + "message" -> "InvokeRejectError(error = Test, log = \n\ttestF.@args = []\n\tthrow.@args = [\n\t\t\"Test\"\n\t]\n\tthrow.@complexity = 1\n\t@complexityLimit = 25999\n)" + ) } evalScript("test(123)") ~> route ~> check { @@ -1000,28 +1010,28 @@ class UtilsRouteSpec extends RouteSpec("/utils") with RestAPISettingsHelper with | } | } ] | }, - | "complexity" : 92, + | "complexity" : 80, | "expr" : " testSyncinvoke() ", | "address" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" |}""".stripMargin) } - val complexityLimit = 1234 + val complexityLimit = 200 val customApi = api.copy(settings = restAPISettings.copy(evaluateScriptComplexityLimit = complexityLimit)) evalScript(""" testSyncCallComplexityExcess() """) ~> customApi.route ~> check { responseAs[JsValue] should matchJson( - s"""{"error":306,"message":"FailedTransactionError(code = 1, error = Invoke complexity limit = $complexityLimit is exceeded, log =)","expr":" testSyncCallComplexityExcess() ","address":"3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9"}""" + s"""{"error":306,"message":"InvokeRejectError(error = Invoke complexity limit = 200 is exceeded, log = \\n\\ttestSyncCallComplexityExcess.@args = []\\n\\tinvoke.@args = [\\n\\t\\tAddress(\\n\\t\\t\\tbytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9'\\n\\t\\t),\\n\\t\\t\\"testSyncCallComplexityExcess\\",\\n\\t\\t[],\\n\\t\\t[]\\n\\t]\\n\\tinvoke.@complexity = 75\\n\\t@complexityLimit = 125\\n\\tr = FailedTransactionError(code = 1, error = Invoke complexity limit = 200 is exceeded, log = \\n\\t\\t@invokedDApp = Address(\\n\\t\\t\\tbytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9'\\n\\t\\t)\\n\\t\\t@invokedFuncName = \\"testSyncCallComplexityExcess\\"\\n\\t\\ti = Invocation(\\n\\t\\t\\toriginCaller = Address(\\n\\t\\t\\t\\tbytes = base58'3MuPKL2kQz1Gp9t7QwrDZN5F8m3u5Uzzo3e'\\n\\t\\t\\t)\\n\\t\\t\\tpayments = []\\n\\t\\t\\tcallerPublicKey = base58'9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ'\\n\\t\\t\\tfeeAssetId = Unit\\n\\t\\t\\toriginCallerPublicKey = base58'11111111111111111111111111111111'\\n\\t\\t\\ttransactionId = base58''\\n\\t\\t\\tcaller = Address(\\n\\t\\t\\t\\tbytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9'\\n\\t\\t\\t)\\n\\t\\t\\tfee = 0\\n\\t\\t)\\n\\t\\ttestSyncCallComplexityExcess.@args = []\\n\\t\\tinvoke.@args = [\\n\\t\\t\\tAddress(\\n\\t\\t\\t\\tbytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9'\\n\\t\\t\\t),\\n\\t\\t\\t\\"testSyncCallComplexityExcess\\",\\n\\t\\t\\t[],\\n\\t\\t\\t[]\\n\\t\\t]\\n\\t\\tinvoke.@complexity = 75\\n\\t\\t@complexityLimit = 50\\n\\t\\tr = FailedTransactionError(code = 1, error = Invoke complexity limit = 200 is exceeded, log = \\n\\t\\t\\t@invokedDApp = Address(\\n\\t\\t\\t\\tbytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9'\\n\\t\\t\\t)\\n\\t\\t\\t@invokedFuncName = \\"testSyncCallComplexityExcess\\"\\n\\t\\t\\ti = Invocation(\\n\\t\\t\\t\\toriginCaller = Address(\\n\\t\\t\\t\\t\\tbytes = base58'3MuPKL2kQz1Gp9t7QwrDZN5F8m3u5Uzzo3e'\\n\\t\\t\\t\\t)\\n\\t\\t\\t\\tpayments = []\\n\\t\\t\\t\\tcallerPublicKey = base58'9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ'\\n\\t\\t\\t\\tfeeAssetId = Unit\\n\\t\\t\\t\\toriginCallerPublicKey = base58'11111111111111111111111111111111'\\n\\t\\t\\t\\ttransactionId = base58''\\n\\t\\t\\t\\tcaller = Address(\\n\\t\\t\\t\\t\\tbytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9'\\n\\t\\t\\t\\t)\\n\\t\\t\\t\\tfee = 0\\n\\t\\t\\t)\\n\\t\\t\\ttestSyncCallComplexityExcess.@args = []\\n\\t\\t)\\n\\t)\\n)","expr":" testSyncCallComplexityExcess() ","address":"3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9"}""".stripMargin ) } evalScript(""" testWriteEntryType("abc") """) ~> route ~> check { responseAs[JsValue] should matchJson( - """{"error":306,"message":"Passed args (bytes, abc) are unsuitable for constructor BinaryEntry(String, ByteVector)","expr":" testWriteEntryType(\"abc\") ","address":"3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9"}""" + """{"error":306,"message":"InvokeRejectError(error = Passed args (bytes, abc) are unsuitable for constructor BinaryEntry(String, ByteVector), log = \n\ttestWriteEntryType.@args = [\n\t\t\"abc\"\n\t]\n\tb = \"abc\"\n\tBinaryEntry.@args = [\n\t\t\"bytes\",\n\t\t\"abc\"\n\t]\n)","expr":" testWriteEntryType(\"abc\") ","address":"3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9"}""" ) } evalScript(""" testWriteEntryType(base58'aaaa') """) ~> route ~> check { responseAs[JsValue] should matchJson( - """{"result":{"type":"Array","value":[{"type":"BinaryEntry","value":{"key":{"type":"String","value":"bytes"},"value":{"type":"ByteVector","value":"aaaa"}}}]},"complexity":3,"expr":" testWriteEntryType(base58'aaaa') ","address":"3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9"}""" + """{"result":{"type":"Array","value":[{"type":"BinaryEntry","value":{"key":{"type":"String","value":"bytes"},"value":{"type":"ByteVector","value":"aaaa"}}}]},"complexity":2,"expr":" testWriteEntryType(base58'aaaa') ","address":"3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9"}""" ) } @@ -1044,7 +1054,7 @@ class UtilsRouteSpec extends RouteSpec("/utils") with RestAPISettingsHelper with evalScript(""" callable() """, dAppAddress2) ~> route ~> check { responseAs[JsValue] should matchJson( - """{"result":{"type":"Array","value":[{"type":"BinaryEntry","value":{"key":{"type":"String","value":"testSyncInvoke"},"value":{"type":"ByteVector","value":"11111111111111111111111111"}}}]},"complexity":297,"expr":" callable() ","address":"3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC"}""" + """{"result":{"type":"Array","value":[{"type":"BinaryEntry","value":{"key":{"type":"String","value":"testSyncInvoke"},"value":{"type":"ByteVector","value":"11111111111111111111111111"}}}]},"complexity":284,"expr":" callable() ","address":"3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC"}""" ) } @@ -1071,13 +1081,13 @@ class UtilsRouteSpec extends RouteSpec("/utils") with RestAPISettingsHelper with compact = true ) d.helpers.setScript(dAppAccount, compactedDApp) - evalScript("user1()") ~> route ~> check { + evalScript("user1()") ~> route ~> check { (responseAs[JsValue] \ "result" \ "value").as[Int] shouldBe 1 } - evalScript("user2()") ~> route ~> check { + evalScript("user2()") ~> route ~> check { (responseAs[JsValue] \ "result" \ "value").as[Int] shouldBe 2 } - evalScript("call()") ~> route ~> check { + evalScript("call()") ~> route ~> check { (responseAs[JsValue] \ "result" \ "value" \ "_2" \ "value").as[Int] shouldBe 3 } } diff --git a/node/src/test/scala/com/wavesplatform/mining/BlockV5Test.scala b/node/src/test/scala/com/wavesplatform/mining/BlockV5Test.scala index 10091c5f94e..9cbb75e1f7c 100644 --- a/node/src/test/scala/com/wavesplatform/mining/BlockV5Test.scala +++ b/node/src/test/scala/com/wavesplatform/mining/BlockV5Test.scala @@ -476,7 +476,7 @@ class BlockV5Test extends FlatSpec with WithDomain with OptionValues with Either val pos = PoSSelector(blockchain, settings.synchronizationSettings.maxBaseTarget) val allChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE) val wallet = Wallet(WalletSettings(None, Some("123"), None)) - val utxPool = new UtxPoolImpl(time, blockchain, settings.utxSettings, settings.minerSettings.enable) + val utxPool = new UtxPoolImpl(time, blockchain, settings.utxSettings, settings.maxTxErrorLogSize, settings.minerSettings.enable) val minerScheduler = Scheduler.singleThread("miner") val appenderScheduler = Scheduler.singleThread("appender") val miner = new MinerImpl(allChannels, blockchain, settings, time, utxPool, wallet, pos, minerScheduler, appenderScheduler, Observable.empty) diff --git a/node/src/test/scala/com/wavesplatform/mining/BlockWithMaxBaseTargetTest.scala b/node/src/test/scala/com/wavesplatform/mining/BlockWithMaxBaseTargetTest.scala index 16ff4e0f0e7..ab7971bd2e0 100644 --- a/node/src/test/scala/com/wavesplatform/mining/BlockWithMaxBaseTargetTest.scala +++ b/node/src/test/scala/com/wavesplatform/mining/BlockWithMaxBaseTargetTest.scala @@ -38,78 +38,76 @@ class BlockWithMaxBaseTargetTest extends FreeSpec with WithDB with DBCacheSettin "base target limit" - { "node should stop if base target greater than maximum in block creation " in { - withEnv { - case Env(settings, pos, bcu, utxPoolStub, scheduler, account, lastBlock) => - var stopReasonCode = 0 - - val allChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE) - val wallet = Wallet(WalletSettings(None, Some("123"), None)) - val miner = - new MinerImpl(allChannels, bcu, settings, ntpTime, utxPoolStub, wallet, pos, scheduler, scheduler, Observable.empty) - - val signal = new Semaphore(1) - signal.acquire() - - System.setSecurityManager(new SecurityManager { - override def checkPermission(perm: Permission): Unit = {} - - override def checkPermission(perm: Permission, context: Object): Unit = {} - - override def checkExit(status: Int): Unit = signal.synchronized { - super.checkExit(status) - stopReasonCode = status - if (status == BaseTargetReachedMaximum.code) - signal.release() - throw new SecurityException("System exit is not allowed") - } - }) - - try { - miner.forgeBlock(account) - } catch { - case _: SecurityException => // NOP + withEnv { case Env(settings, pos, bcu, utxPoolStub, scheduler, account, lastBlock) => + var stopReasonCode = 0 + + val allChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE) + val wallet = Wallet(WalletSettings(None, Some("123"), None)) + val miner = + new MinerImpl(allChannels, bcu, settings, ntpTime, utxPoolStub, wallet, pos, scheduler, scheduler, Observable.empty) + + val signal = new Semaphore(1) + signal.acquire() + + System.setSecurityManager(new SecurityManager { + override def checkPermission(perm: Permission): Unit = {} + + override def checkPermission(perm: Permission, context: Object): Unit = {} + + override def checkExit(status: Int): Unit = signal.synchronized { + super.checkExit(status) + stopReasonCode = status + if (status == BaseTargetReachedMaximum.code) + signal.release() + throw new SecurityException("System exit is not allowed") } + }) + + try { + miner.forgeBlock(account) + } catch { + case _: SecurityException => // NOP + } - signal.tryAcquire(10, TimeUnit.SECONDS) + signal.tryAcquire(10, TimeUnit.SECONDS) - stopReasonCode shouldBe BaseTargetReachedMaximum.code + stopReasonCode shouldBe BaseTargetReachedMaximum.code - System.setSecurityManager(null) + System.setSecurityManager(null) } } "node should stop if base target greater than maximum in block append" in { - withEnv { - case Env(settings, pos, bcu, utxPoolStub, scheduler, _, lastBlock) => - var stopReasonCode = 0 + withEnv { case Env(settings, pos, bcu, utxPoolStub, scheduler, _, lastBlock) => + var stopReasonCode = 0 - val signal = new Semaphore(1) - signal.acquire() + val signal = new Semaphore(1) + signal.acquire() - System.setSecurityManager(new SecurityManager { - override def checkPermission(perm: Permission): Unit = {} + System.setSecurityManager(new SecurityManager { + override def checkPermission(perm: Permission): Unit = {} - override def checkPermission(perm: Permission, context: Object): Unit = {} + override def checkPermission(perm: Permission, context: Object): Unit = {} - override def checkExit(status: Int): Unit = signal.synchronized { - super.checkExit(status) - stopReasonCode = status - if (status == BaseTargetReachedMaximum.code) - signal.release() - throw new SecurityException("System exit is not allowed") - } - }) - - val blockAppendTask = BlockAppender(bcu, ntpTime, utxPoolStub, pos, scheduler)(lastBlock).onErrorRecoverWith { - case _: SecurityException => Task.unit + override def checkExit(status: Int): Unit = signal.synchronized { + super.checkExit(status) + stopReasonCode = status + if (status == BaseTargetReachedMaximum.code) + signal.release() + throw new SecurityException("System exit is not allowed") } - Await.result(blockAppendTask.runToFuture(scheduler), Duration.Inf) + }) + + val blockAppendTask = BlockAppender(bcu, ntpTime, utxPoolStub, pos, scheduler)(lastBlock).onErrorRecoverWith { case _: SecurityException => + Task.unit + } + Await.result(blockAppendTask.runToFuture(scheduler), Duration.Inf) - signal.tryAcquire(10, TimeUnit.SECONDS) + signal.tryAcquire(10, TimeUnit.SECONDS) - stopReasonCode shouldBe BaseTargetReachedMaximum.code + stopReasonCode shouldBe BaseTargetReachedMaximum.code - System.setSecurityManager(null) + System.setSecurityManager(null) } } } @@ -134,7 +132,7 @@ class BlockWithMaxBaseTargetTest extends FreeSpec with WithDB with DBCacheSettin new BlockchainUpdaterImpl(defaultWriter, ignoreSpendableBalanceChanged, settings, ntpTime, ignoreBlockchainUpdateTriggers, (_, _) => Seq.empty) val pos = PoSSelector(bcu, settings.synchronizationSettings.maxBaseTarget) - val utxPoolStub = new UtxPoolImpl(ntpTime, bcu, settings0.utxSettings, settings0.minerSettings.enable) + val utxPoolStub = new UtxPoolImpl(ntpTime, bcu, settings0.utxSettings, settings.maxTxErrorLogSize, settings0.minerSettings.enable) val schedulerService: SchedulerService = Scheduler.singleThread("appender") try { diff --git a/node/src/test/scala/com/wavesplatform/mining/MicroBlockMinerSpec.scala b/node/src/test/scala/com/wavesplatform/mining/MicroBlockMinerSpec.scala index 278ff5e9fd7..02cfefda052 100644 --- a/node/src/test/scala/com/wavesplatform/mining/MicroBlockMinerSpec.scala +++ b/node/src/test/scala/com/wavesplatform/mining/MicroBlockMinerSpec.scala @@ -25,7 +25,7 @@ class MicroBlockMinerSpec extends FlatSpec with PathMockFactory with WithDomain val acc = TestValues.keyPair val settings = domainSettingsWithFS(TestFunctionalitySettings.withFeatures(BlockchainFeatures.NG)) withDomain(settings, Seq(AddrWithBalance(acc.toAddress, TestValues.bigMoney))) { d => - val utxPool = new UtxPoolImpl(ntpTime, d.blockchainUpdater, settings.utxSettings, settings.minerSettings.enable) + val utxPool = new UtxPoolImpl(ntpTime, d.blockchainUpdater, settings.utxSettings, settings.maxTxErrorLogSize, settings.minerSettings.enable) val microBlockMiner = new MicroBlockMinerImpl( _ => (), null, diff --git a/node/src/test/scala/com/wavesplatform/mining/MinerAccountScriptRestrictionsTest.scala b/node/src/test/scala/com/wavesplatform/mining/MinerAccountScriptRestrictionsTest.scala index da91c5b0fd8..f932af78da2 100644 --- a/node/src/test/scala/com/wavesplatform/mining/MinerAccountScriptRestrictionsTest.scala +++ b/node/src/test/scala/com/wavesplatform/mining/MinerAccountScriptRestrictionsTest.scala @@ -92,7 +92,13 @@ class MinerAccountScriptRestrictionsTest extends PropSpec with WithDomain { val defaultSettings = WavesSettings.default() val wavesSettings = defaultSettings.copy(minerSettings = defaultSettings.minerSettings.copy(quorum = 0)) - val utx = new UtxPoolImpl(time, d.blockchainUpdater, wavesSettings.utxSettings, isMiningEnabled = wavesSettings.minerSettings.enable) + val utx = new UtxPoolImpl( + time, + d.blockchainUpdater, + wavesSettings.utxSettings, + wavesSettings.maxTxErrorLogSize, + isMiningEnabled = wavesSettings.minerSettings.enable + ) val appenderScheduler = Scheduler.singleThread("appender") val miner = new MinerImpl( diff --git a/node/src/test/scala/com/wavesplatform/mining/MiningFailuresSuite.scala b/node/src/test/scala/com/wavesplatform/mining/MiningFailuresSuite.scala index 61777d0b70c..660e60578c9 100644 --- a/node/src/test/scala/com/wavesplatform/mining/MiningFailuresSuite.scala +++ b/node/src/test/scala/com/wavesplatform/mining/MiningFailuresSuite.scala @@ -32,14 +32,16 @@ class MiningFailuresSuite extends FlatSpec with PathMockFactory with WithDB { val blockchainUpdater = stub[BlockchainUpdaterNG] val wavesSettings = { - val config = ConfigFactory.parseString(""" - |waves.miner { - | quorum = 0 - | interval-after-last-block-then-generation-is-allowed = 0 - |} - | - |waves.features.supported=[2] - |""".stripMargin).withFallback(ConfigFactory.load()) + val config = ConfigFactory + .parseString(""" + |waves.miner { + | quorum = 0 + | interval-after-last-block-then-generation-is-allowed = 0 + |} + | + |waves.features.supported=[2] + |""".stripMargin) + .withFallback(ConfigFactory.load()) WavesSettings.fromRootConfig(loadConfig(config)) } @@ -54,8 +56,9 @@ class MiningFailuresSuite extends FlatSpec with PathMockFactory with WithDB { val scheduler = Scheduler.singleThread("appender") val allChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE) val wallet = Wallet(WalletSettings(None, Some("123"), None)) - val utxPool = new UtxPoolImpl(ntpTime, blockchainUpdater, wavesSettings.utxSettings, wavesSettings.minerSettings.enable) - val pos = PoSSelector(blockchainUpdater, wavesSettings.synchronizationSettings.maxBaseTarget) + val utxPool = + new UtxPoolImpl(ntpTime, blockchainUpdater, wavesSettings.utxSettings, wavesSettings.maxTxErrorLogSize, wavesSettings.minerSettings.enable) + val pos = PoSSelector(blockchainUpdater, wavesSettings.synchronizationSettings.maxBaseTarget) new MinerImpl( allChannels, blockchainUpdater, diff --git a/node/src/test/scala/com/wavesplatform/mining/MiningWithRewardSuite.scala b/node/src/test/scala/com/wavesplatform/mining/MiningWithRewardSuite.scala index a41f998f47e..e183a739bb0 100644 --- a/node/src/test/scala/com/wavesplatform/mining/MiningWithRewardSuite.scala +++ b/node/src/test/scala/com/wavesplatform/mining/MiningWithRewardSuite.scala @@ -40,20 +40,19 @@ class MiningWithRewardSuite extends AsyncFlatSpec with Matchers with WithDB with behavior of "Miner with activated reward feature" it should "generate valid empty blocks of version 4" in { - withEnv(Seq.empty) { - case Env(_, account, miner, blockchain) => - val generateBlock = generateBlockTask(miner)(account) - val oldBalance = blockchain.balance(account.toAddress) - val newBalance = oldBalance + 2 * settings.blockchainSettings.rewardsSettings.initial - for { - _ <- generateBlock - _ <- generateBlock - } yield { - blockchain.balance(account.toAddress) should be(newBalance) - blockchain.height should be(3) - blockchain.blockHeader(2).get.header.version should be(Block.RewardBlockVersion) - blockchain.blockHeader(3).get.header.version should be(Block.RewardBlockVersion) - } + withEnv(Seq.empty) { case Env(_, account, miner, blockchain) => + val generateBlock = generateBlockTask(miner)(account) + val oldBalance = blockchain.balance(account.toAddress) + val newBalance = oldBalance + 2 * settings.blockchainSettings.rewardsSettings.initial + for { + _ <- generateBlock + _ <- generateBlock + } yield { + blockchain.balance(account.toAddress) should be(newBalance) + blockchain.height should be(3) + blockchain.blockHeader(2).get.header.version should be(Block.RewardBlockVersion) + blockchain.blockHeader(3).get.header.version should be(Block.RewardBlockVersion) + } } } @@ -72,78 +71,70 @@ class MiningWithRewardSuite extends AsyncFlatSpec with Matchers with WithDB with } it should "generate valid blocks with transactions of version 4" in { - val bps: Seq[BlockProducer] = Seq( - (ts, reference, account) => { - val recipient1 = createAccount.toAddress - val recipient2 = createAccount.toAddress - val tx1 = TransferTransaction - .selfSigned(2.toByte, account, recipient1, Waves, 10 * Constants.UnitsInWave, Waves, 400000, ByteStr.empty, ts) - .explicitGet() - val tx2 = TransferTransaction - .selfSigned(2.toByte, account, recipient2, Waves, 5 * Constants.UnitsInWave, Waves, 400000, ByteStr.empty, ts) - .explicitGet() - TestBlock.create(time = ts, ref = reference, txs = Seq(tx1, tx2), version = Block.NgBlockVersion) + val bps: Seq[BlockProducer] = Seq((ts, reference, account) => { + val recipient1 = createAccount.toAddress + val recipient2 = createAccount.toAddress + val tx1 = TransferTransaction + .selfSigned(2.toByte, account, recipient1, Waves, 10 * Constants.UnitsInWave, Waves, 400000, ByteStr.empty, ts) + .explicitGet() + val tx2 = TransferTransaction + .selfSigned(2.toByte, account, recipient2, Waves, 5 * Constants.UnitsInWave, Waves, 400000, ByteStr.empty, ts) + .explicitGet() + TestBlock.create(time = ts, ref = reference, txs = Seq(tx1, tx2), version = Block.NgBlockVersion) + }) + + val txs: Seq[TransactionProducer] = Seq((ts, account) => { + val recipient1 = createAccount.toAddress + TransferTransaction + .selfSigned(2.toByte, account, recipient1, Waves, 10 * Constants.UnitsInWave, Waves, 400000, ByteStr.empty, ts) + .explicitGet() + }) + + withEnv(bps, txs) { case Env(_, account, miner, blockchain) => + val generateBlock = generateBlockTask(miner)(account) + val oldBalance = blockchain.balance(account.toAddress) + val newBalance = oldBalance + settings.blockchainSettings.rewardsSettings.initial - 10 * Constants.UnitsInWave + + generateBlock.map { _ => + blockchain.balance(account.toAddress) should be(newBalance) + blockchain.height should be(3) } - ) - - val txs: Seq[TransactionProducer] = Seq( - (ts, account) => { - val recipient1 = createAccount.toAddress - TransferTransaction - .selfSigned(2.toByte, account, recipient1, Waves, 10 * Constants.UnitsInWave, Waves, 400000, ByteStr.empty, ts) - .explicitGet() - } - ) - - withEnv(bps, txs) { - case Env(_, account, miner, blockchain) => - val generateBlock = generateBlockTask(miner)(account) - val oldBalance = blockchain.balance(account.toAddress) - val newBalance = oldBalance + settings.blockchainSettings.rewardsSettings.initial - 10 * Constants.UnitsInWave - - generateBlock.map { _ => - blockchain.balance(account.toAddress) should be(newBalance) - blockchain.height should be(3) - } } // Test for empty key block with NG - withEnv(bps, txs, settingsWithFeatures(BlockchainFeatures.NG, BlockchainFeatures.SmartAccounts)) { - case Env(_, account, miner, _) => - val (block, _) = forgeBlock(miner)(account).explicitGet() - Task(block.transactionData shouldBe empty) + withEnv(bps, txs, settingsWithFeatures(BlockchainFeatures.NG, BlockchainFeatures.SmartAccounts)) { case Env(_, account, miner, _) => + val (block, _) = forgeBlock(miner)(account).explicitGet() + Task(block.transactionData shouldBe empty) } } private def withEnv(bps: Seq[BlockProducer], txs: Seq[TransactionProducer] = Seq(), settings: WavesSettings = MiningWithRewardSuite.settings)( f: Env => Task[Assertion] ): Task[Assertion] = - resources(settings).use { - case (blockchainUpdater, _) => - for { - _ <- Task.unit - pos = PoSSelector(blockchainUpdater, settings.synchronizationSettings.maxBaseTarget) - utxPool = new UtxPoolImpl(ntpTime, blockchainUpdater, settings.utxSettings, settings.minerSettings.enable) - scheduler = Scheduler.singleThread("appender") - allChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE) - wallet = Wallet(WalletSettings(None, Some("123"), None)) - miner = new MinerImpl(allChannels, blockchainUpdater, settings, ntpTime, utxPool, wallet, pos, scheduler, scheduler, Observable.empty) - account = createAccount - ts = ntpTime.correctedTime() - 60000 - genesisBlock = TestBlock.create(ts + 2, List(GenesisTransaction.create(account.toAddress, ENOUGH_AMT, ts + 1).explicitGet())) - _ <- Task(blockchainUpdater.processBlock(genesisBlock, genesisBlock.header.generationSignature)) - blocks = bps.foldLeft { - (ts + 1, Seq[Block](genesisBlock)) - } { - case ((ts, chain), bp) => - (ts + 3, bp(ts + 3, chain.head.id(), account) +: chain) - }._2 - added <- Task.traverse(blocks.reverse)(b => Task(blockchainUpdater.processBlock(b, b.header.generationSignature))) - _ = added.foreach(_.explicitGet()) - _ = txs.foreach(tx => utxPool.putIfNew(tx(ts + 6, account)).resultE.explicitGet()) - env = Env(blocks, account, miner, blockchainUpdater) - r <- f(env) - } yield r + resources(settings).use { case (blockchainUpdater, _) => + for { + _ <- Task.unit + pos = PoSSelector(blockchainUpdater, settings.synchronizationSettings.maxBaseTarget) + utxPool = new UtxPoolImpl(ntpTime, blockchainUpdater, settings.utxSettings, settings.maxTxErrorLogSize, settings.minerSettings.enable) + scheduler = Scheduler.singleThread("appender") + allChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE) + wallet = Wallet(WalletSettings(None, Some("123"), None)) + miner = new MinerImpl(allChannels, blockchainUpdater, settings, ntpTime, utxPool, wallet, pos, scheduler, scheduler, Observable.empty) + account = createAccount + ts = ntpTime.correctedTime() - 60000 + genesisBlock = TestBlock.create(ts + 2, List(GenesisTransaction.create(account.toAddress, ENOUGH_AMT, ts + 1).explicitGet())) + _ <- Task(blockchainUpdater.processBlock(genesisBlock, genesisBlock.header.generationSignature)) + blocks = bps.foldLeft { + (ts + 1, Seq[Block](genesisBlock)) + } { case ((ts, chain), bp) => + (ts + 3, bp(ts + 3, chain.head.id(), account) +: chain) + }._2 + added <- Task.traverse(blocks.reverse)(b => Task(blockchainUpdater.processBlock(b, b.header.generationSignature))) + _ = added.foreach(_.explicitGet()) + _ = txs.foreach(tx => utxPool.putIfNew(tx(ts + 6, account)).resultE.explicitGet()) + env = Env(blocks, account, miner, blockchainUpdater) + r <- f(env) + } yield r } private def generateBlockTask(miner: MinerImpl)(account: KeyPair): Task[Unit] = miner.generateBlockTask(account, None) @@ -156,11 +147,10 @@ class MiningWithRewardSuite extends AsyncFlatSpec with Matchers with WithDB with import com.wavesplatform.database.DBExt db.readWrite(_.put(Keys.blockReward(0), Some(settings.blockchainSettings.rewardsSettings.initial))) Task.now((bcu, db)) - } { - case (blockchainUpdater, db) => - Task { - blockchainUpdater.shutdown() - } + } { case (blockchainUpdater, db) => + Task { + blockchainUpdater.shutdown() + } } } diff --git a/node/src/test/scala/com/wavesplatform/state/appender/ExtensionAppenderSpec.scala b/node/src/test/scala/com/wavesplatform/state/appender/ExtensionAppenderSpec.scala index d27c90e0405..5e764dcb63d 100644 --- a/node/src/test/scala/com/wavesplatform/state/appender/ExtensionAppenderSpec.scala +++ b/node/src/test/scala/com/wavesplatform/state/appender/ExtensionAppenderSpec.scala @@ -14,8 +14,8 @@ import monix.execution.Scheduler.Implicits.global class ExtensionAppenderSpec extends FlatSpec with WithDomain { "Extension appender" should "drop duplicate transactions from UTX" in withDomain(balances = AddrWithBalance.enoughBalances(TxHelpers.defaultSigner)) { d => - val utx = new UtxPoolImpl(SystemTime, d.blockchain, d.settings.utxSettings, d.settings.minerSettings.enable) - val time = TestTime() + val utx = new UtxPoolImpl(SystemTime, d.blockchain, d.settings.utxSettings, d.settings.maxTxErrorLogSize, d.settings.minerSettings.enable) + val time = TestTime() val extensionAppender = ExtensionAppender(d.blockchain, utx, d.posSelector, time, InvalidBlockStorage.NoOp, PeerDatabase.NoOp, global)(null, _) val tx = TxHelpers.transfer() diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/TransactionValidationErrorPrintTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/TransactionValidationErrorPrintTest.scala index 166646581cb..28e43411bd5 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/TransactionValidationErrorPrintTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/TransactionValidationErrorPrintTest.scala @@ -4,59 +4,60 @@ import com.wavesplatform.account.{Address, KeyPair} import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.db.WithState import com.wavesplatform.lagonaki.mocks.TestBlock -import com.wavesplatform.lang.directives.values.{Expression, V1} +import com.wavesplatform.lang.directives.values.{Expression, V6} import com.wavesplatform.lang.script.v1.ExprScript import com.wavesplatform.lang.utils.compilerContext import com.wavesplatform.lang.v1.compiler.ExpressionCompiler import com.wavesplatform.lang.v1.parser.Parser import com.wavesplatform.state.diffs.TransactionDiffer.TransactionValidationError import com.wavesplatform.test.* +import com.wavesplatform.test.DomainPresets.RideV6 import com.wavesplatform.transaction.TxValidationError.ScriptExecutionError -import com.wavesplatform.transaction.{TxHelpers, TxValidationError} +import com.wavesplatform.transaction.{ErrorWithLogPrinter, TxHelpers} import org.scalatest.Inside class TransactionValidationErrorPrintTest extends PropSpec with Inside with WithState { property("output transaction error should be easy to read") { val assetScript = s""" - | let NETWORKBYTE = takeRight(toBytes(87), 1) - | - | match (tx) { - | # Only allow transfer transactions - | case t:TransferTransaction => { - | let txWithoutAttachment = dropRight(t.bodyBytes, 97) + takeRight(toBytes(0), 1) - | - | let recipientPublicKeyAndSignature = t.attachment - | let recipientPublicKey = take(recipientPublicKeyAndSignature, 32) - | let recipientSignature = takeRight(recipientPublicKeyAndSignature, 64) - | - | let recipientPublicKeyHash = take(keccak256(blake2b256(recipientPublicKey)), 20) - | let rpkWithVersionAndByte = takeRight(toBytes(1), 1) + NETWORKBYTE + recipientPublicKeyHash - | let checksum = take(keccak256(blake2b256(rpkWithVersionAndByte)), 4) - | let recipientAddressFromPublicKey = rpkWithVersionAndByte + checksum - | let recipientAddressFromTx = addressFromRecipient(t.recipient).bytes - | let recipientAddressStr = toBase58String(recipientAddressFromPublicKey) - | let big = base64'${"a" * 2048}' - | - | if (big == big && recipientAddressFromPublicKey != recipientAddressFromTx) then throw( - | "Recipient address error:" + recipientAddressStr - | ) else { - | if (!sigVerify(txWithoutAttachment, recipientSignature, recipientPublicKey)) - | then true - | else false - | } - | } - | case _ => throw("unexpected") - | } + | let NETWORKBYTE = takeRight(toBytes(87), 1) + | + | match (tx) { + | # Only allow transfer transactions + | case t:TransferTransaction => { + | let txWithoutAttachment = dropRight(t.bodyBytes, 97) + takeRight(toBytes(0), 1) + | + | let recipientPublicKeyAndSignature = t.attachment + | let recipientPublicKey = take(recipientPublicKeyAndSignature, 32) + | let recipientSignature = takeRight(recipientPublicKeyAndSignature, 64) + | + | let recipientPublicKeyHash = take(keccak256(blake2b256(recipientPublicKey)), 20) + | let rpkWithVersionAndByte = takeRight(toBytes(1), 1) + NETWORKBYTE + recipientPublicKeyHash + | let checksum = take(keccak256(blake2b256(rpkWithVersionAndByte)), 4) + | let recipientAddressFromPublicKey = rpkWithVersionAndByte + checksum + | let recipientAddressFromTx = addressFromRecipient(t.recipient).bytes + | let recipientAddressStr = toBase58String(recipientAddressFromPublicKey) + | let big = base64'${"a" * 2048}' + | + | if (big == big && recipientAddressFromPublicKey != recipientAddressFromTx) then throw( + | "Recipient address error:" + recipientAddressStr + | ) else { + | if (!sigVerify(txWithoutAttachment, recipientSignature, recipientPublicKey)) + | then true + | else false + | } + | } + | case _ => throw("unexpected") + | } """.stripMargin val untypedScript = Parser.parseExpr(assetScript).get.value - val typedScript = ExprScript(ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), untypedScript).explicitGet()._1) + val typedScript = ExprScript(V6, ExpressionCompiler(compilerContext(V6, Expression, isAssetScript = false), untypedScript).explicitGet()._1) .explicitGet() val preTypedScript = - ExprScript(ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), Parser.parseExpr("true").get.value).explicitGet()._1) + ExprScript(V6, ExpressionCompiler(compilerContext(V6, Expression, isAssetScript = false), Parser.parseExpr("true").get.value).explicitGet()._1) .explicitGet() val seed = Address.fromString("3MydsP4UeQdGwBq7yDbMvf9MzfB2pxFoUKU").explicitGet() @@ -70,7 +71,7 @@ class TransactionValidationErrorPrintTest extends PropSpec with Inside with With name = "name", reissuable = false, script = Some(preTypedScript), - fee = 10000000, + fee = 100000000, timestamp = 0 ) @@ -79,7 +80,7 @@ class TransactionValidationErrorPrintTest extends PropSpec with Inside with With to = KeyPair(master.bytes).toAddress, amount = 1, asset = issueTransaction.asset, - fee = 10000000, + fee = 10400000, timestamp = 0 ) @@ -87,7 +88,7 @@ class TransactionValidationErrorPrintTest extends PropSpec with Inside with With acc = KeyPair(seed.bytes), asset = issueTransaction.asset, script = typedScript, - fee = 10000000, + fee = 100000000, timestamp = 0 ) @@ -95,67 +96,210 @@ class TransactionValidationErrorPrintTest extends PropSpec with Inside with With assertDiffEi( Seq(TestBlock.create(Seq(genesis1, genesis2, issueTransaction, preTransferTransaction, preSetAssetScriptTransaction))), - TestBlock.create(Seq(transferTransaction)) + TestBlock.create(Seq(transferTransaction)), + RideV6.blockchainSettings.functionalitySettings ) { error => - inside(error) { - case Left(TransactionValidationError(see: ScriptExecutionError, _)) => - val expected = //regex because of changeable proof - """ - | \$match0 = TransferTransaction\( - | recipient = Address\( + inside(error) { case Left(TransactionValidationError(see: ScriptExecutionError, _)) => + val expected = + f""" + | $$match0 = TransferTransaction( + | recipient = Address( | bytes = base58'3N1w8y9Udv3k9NCSv9EE3QvMTRnGFTDQSzu' - | \) + | ) | timestamp = 0 - | bodyBytes = base58'ZFDBCm7WGpX1zYwdAbbbk2XHyDz2urZGfPHjeiPWuGuemeYUAswXmdLfPhXamrydNQwFDR9QKFELsMaZDwneo16LGifGX71dUtdqfRtzzr3KvjVYD1uysyghj3KfWNDSriC3E1vKR6SWa91rqdzXhynrNZXHu9EJpud' - | assetId = base58'6Jro1D97trbypmc4HDckkiy2qYtU2JoU7ZUjtwEPi32o' + | bodyBytes = base58'ZFDBCm7WGpX1zYwdAbbbk2XHyDz2urZGfPHjeiPWuGuemeYsL5YvfH7Nf87ebWwX4AhbnuXaNDARaLnSTc42SZbKPXkcbs3ZHNsoF9bRQK5Aw7KjHg7P7Sinbq4wfQWhjbnQNJTQjkfZjX7BNZQ4LnquL9LVyPmXJBh' + | assetId = base58'BG6TEE8VmtvkiVLwc4XmmW7yjiFWezGChTM2tFCNa69B' | feeAssetId = Unit | amount = 1 | version = 2 - | id = base58'FsqB36ighMWLbGS1te7gh9DRFbrCRVjvumgSKwdYAwxi' + | id = base58'H7eZ7bbbga3rhD6LaUiAiaDZrHGU9ibggsqC1HpZCQjj' | senderPublicKey = base58'EbxDdqXBhj3TEd1UFoi1UE1vm1k7gM9EMYAuLr62iaZF' | attachment = base58'' - | sender = Address\( + | sender = Address( | bytes = base58'3Mrt6Y1QweDrKRRNuhhHGdHpu2kXLXq2QK5' - | \) + | ) | fee = 10000000 - | \) + | ) + | $$isInstanceOf.@args = [ + | TransferTransaction( + | recipient = Address( + | bytes = base58'3N1w8y9Udv3k9NCSv9EE3QvMTRnGFTDQSzu' + | ) + | timestamp = 0 + | bodyBytes = base58'ZFDBCm7WGpX1zYwdAbbbk2XHyDz2urZGfPHjeiPWuGuemeYsL5YvfH7Nf87ebWwX4AhbnuXaNDARaLnSTc42SZbKPXkcbs3ZHNsoF9bRQK5Aw7KjHg7P7Sinbq4wfQWhjbnQNJTQjkfZjX7BNZQ4LnquL9LVyPmXJBh' + | assetId = base58'BG6TEE8VmtvkiVLwc4XmmW7yjiFWezGChTM2tFCNa69B' + | feeAssetId = Unit + | amount = 1 + | version = 2 + | id = base58'H7eZ7bbbga3rhD6LaUiAiaDZrHGU9ibggsqC1HpZCQjj' + | senderPublicKey = base58'EbxDdqXBhj3TEd1UFoi1UE1vm1k7gM9EMYAuLr62iaZF' + | attachment = base58'' + | sender = Address( + | bytes = base58'3Mrt6Y1QweDrKRRNuhhHGdHpu2kXLXq2QK5' + | ) + | fee = 10000000 + | ), + | "TransferTransaction" + | ] + | $$isInstanceOf.@complexity = 1 + | @complexityLimit = 2147483646 | big = base64'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' - | @xs = base58'11111112' - | @number = 1 - | @xs = base58'11111112W' - | @number = 1 + | ==.@args = [ + | base64'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + | base64'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + | ] + | ==.@complexity = 1 + | @complexityLimit = 2147483645 + | toBytes.@args = [ + | 1 + | ] + | toBytes.@complexity = 1 + | @complexityLimit = 2147483644 + | takeRight.@args = [ + | base58'11111112', + | 1 + | ] + | takeRight.@complexity = 6 + | @complexityLimit = 2147483638 + | toBytes.@args = [ + | 87 + | ] + | toBytes.@complexity = 1 + | @complexityLimit = 2147483637 + | takeRight.@args = [ + | base58'11111112W', + | 1 + | ] + | takeRight.@complexity = 6 + | @complexityLimit = 2147483631 | NETWORKBYTE = base58'2W' - | t = TransferTransaction\( - | recipient = Address\( + | +.@args = [ + | base58'2', + | base58'2W' + | ] + | +.@complexity = 2 + | @complexityLimit = 2147483629 + | t = TransferTransaction( + | recipient = Address( | bytes = base58'3N1w8y9Udv3k9NCSv9EE3QvMTRnGFTDQSzu' - | \) + | ) | timestamp = 0 - | bodyBytes = base58'ZFDBCm7WGpX1zYwdAbbbk2XHyDz2urZGfPHjeiPWuGuemeYUAswXmdLfPhXamrydNQwFDR9QKFELsMaZDwneo16LGifGX71dUtdqfRtzzr3KvjVYD1uysyghj3KfWNDSriC3E1vKR6SWa91rqdzXhynrNZXHu9EJpud' - | assetId = base58'6Jro1D97trbypmc4HDckkiy2qYtU2JoU7ZUjtwEPi32o' + | bodyBytes = base58'ZFDBCm7WGpX1zYwdAbbbk2XHyDz2urZGfPHjeiPWuGuemeYsL5YvfH7Nf87ebWwX4AhbnuXaNDARaLnSTc42SZbKPXkcbs3ZHNsoF9bRQK5Aw7KjHg7P7Sinbq4wfQWhjbnQNJTQjkfZjX7BNZQ4LnquL9LVyPmXJBh' + | assetId = base58'BG6TEE8VmtvkiVLwc4XmmW7yjiFWezGChTM2tFCNa69B' | feeAssetId = Unit | amount = 1 | version = 2 - | id = base58'FsqB36ighMWLbGS1te7gh9DRFbrCRVjvumgSKwdYAwxi' + | id = base58'H7eZ7bbbga3rhD6LaUiAiaDZrHGU9ibggsqC1HpZCQjj' | senderPublicKey = base58'EbxDdqXBhj3TEd1UFoi1UE1vm1k7gM9EMYAuLr62iaZF' | attachment = base58'' - | sender = Address\( + | sender = Address( | bytes = base58'3Mrt6Y1QweDrKRRNuhhHGdHpu2kXLXq2QK5' - | \) + | ) | fee = 10000000 - | \) + | ) | recipientPublicKeyAndSignature = base58'' + | take.@args = [ + | base58'', + | 32 + | ] + | take.@complexity = 6 + | @complexityLimit = 2147483623 | recipientPublicKey = base58'' + | blake2b256.@args = [ + | base58'' + | ] + | blake2b256.@complexity = 136 + | @complexityLimit = 2147483487 + | keccak256.@args = [ + | base58'xyw95Bsby3s4mt6f4FmFDnFVpQBAeJxBFNGzu2cX4dM' + | ] + | keccak256.@complexity = 195 + | @complexityLimit = 2147483292 + | take.@args = [ + | base58'DRtdYbxMg7YHw4acvDP6xQrvmsRAz3K7gSkH3xBJ5CTL', + | 20 + | ] + | take.@complexity = 6 + | @complexityLimit = 2147483286 | recipientPublicKeyHash = base58'3aDy5kHaDeXWfQwMrBCRvd6r7gzg' + | +.@args = [ + | base58'6v', + | base58'3aDy5kHaDeXWfQwMrBCRvd6r7gzg' + | ] + | +.@complexity = 2 + | @complexityLimit = 2147483284 | rpkWithVersionAndByte = base58'N8tNz9vAHAwFpa4A8Rgk45q8tNjeC' + | blake2b256.@args = [ + | base58'N8tNz9vAHAwFpa4A8Rgk45q8tNjeC' + | ] + | blake2b256.@complexity = 136 + | @complexityLimit = 2147483148 + | keccak256.@args = [ + | base58'CSJhGcnZPNCcHG5gCZuHKArEg8MUy9ridbKZsryV8FEw' + | ] + | keccak256.@complexity = 195 + | @complexityLimit = 2147482953 + | take.@args = [ + | base58'4sAbTTxFgWFkHC5EutjwtRYgM3Q8V6aBD9EjDKVJ7byk', + | 4 + | ] + | take.@complexity = 6 + | @complexityLimit = 2147482947 | checksum = base58'2U8tZq' + | +.@args = [ + | base58'N8tNz9vAHAwFpa4A8Rgk45q8tNjeC', + | base58'2U8tZq' + | ] + | +.@complexity = 2 + | @complexityLimit = 2147482945 | recipientAddressFromPublicKey = base58'3PJmMnHHVTTkzvF67HYFjrm5Vj96mM3UtLs' + | addressFromRecipient.@args = [ + | Address( + | bytes = base58'3N1w8y9Udv3k9NCSv9EE3QvMTRnGFTDQSzu' + | ) + | ] + | addressFromRecipient.@complexity = 5 + | @complexityLimit = 2147482940 | recipientAddressFromTx = base58'3N1w8y9Udv3k9NCSv9EE3QvMTRnGFTDQSzu' + | !=.@args = [ + | base58'3PJmMnHHVTTkzvF67HYFjrm5Vj96mM3UtLs', + | base58'3N1w8y9Udv3k9NCSv9EE3QvMTRnGFTDQSzu' + | ] + | !=.@complexity = 1 + | @complexityLimit = 2147482939 | @a = base58'3PJmMnHHVTTkzvF67HYFjrm5Vj96mM3UtLs' | @b = base58'3N1w8y9Udv3k9NCSv9EE3QvMTRnGFTDQSzu' + | ==.@args = [ + | base58'3PJmMnHHVTTkzvF67HYFjrm5Vj96mM3UtLs', + | base58'3N1w8y9Udv3k9NCSv9EE3QvMTRnGFTDQSzu' + | ] + | ==.@complexity = 1 + | @complexityLimit = 2147482938 + | !.@args = [ + | false + | ] + | !.@complexity = 1 + | @complexityLimit = 2147482937 | @p = false + | toBase58String.@args = [ + | base58'3PJmMnHHVTTkzvF67HYFjrm5Vj96mM3UtLs' + | ] + | toBase58String.@complexity = 3 + | @complexityLimit = 2147482936 | recipientAddressStr = "3PJmMnHHVTTkzvF67HYFjrm5Vj96mM3UtLs" - |""".stripMargin.r - TxValidationError.logToString(see.log) should fullyMatch regex expected + | +.@args = [ + | "Recipient address error:", + | "3PJmMnHHVTTkzvF67HYFjrm5Vj96mM3UtLs" + | ] + | +.@complexity = 1 + | @complexityLimit = 2147482935 + | throw.@args = [ + | "Recipient address error:3PJmMnHHVTTkzvF67HYFjrm5Vj96mM3UtLs" + | ] + | throw.@complexity = 1 + | @complexityLimit = 2147482934 + |""".stripMargin + ErrorWithLogPrinter.logToString(see.log, Int.MaxValue) shouldBe expected } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppErrorLogTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppErrorLogTest.scala new file mode 100644 index 00000000000..078d01b47f2 --- /dev/null +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppErrorLogTest.scala @@ -0,0 +1,1072 @@ +package com.wavesplatform.state.diffs.ci.sync + +import com.wavesplatform.account.{Address, KeyPair} +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.db.WithDomain +import com.wavesplatform.db.WithState.AddrWithBalance +import com.wavesplatform.lagonaki.mocks.TestBlock +import com.wavesplatform.lang.directives.values.V6 +import com.wavesplatform.lang.script.Script +import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BOOLEAN, EXPR} +import com.wavesplatform.lang.v1.compiler.TestCompiler +import com.wavesplatform.lang.v1.traits.domain.{Issue, Lease, Recipient} +import com.wavesplatform.settings.WavesSettings +import com.wavesplatform.test.* +import com.wavesplatform.transaction.TxValidationError.WithLog +import com.wavesplatform.transaction.{TxHelpers, TxVersion} +import com.wavesplatform.transaction.smart.InvokeScriptTransaction +import com.wavesplatform.transaction.smart.script.trace.InvokeScriptTrace +import org.scalatest.OptionValues + +class SyncDAppErrorLogTest extends PropSpec with WithDomain with OptionValues { + + val invoker: KeyPair = TxHelpers.signer(1) + val dApp1: KeyPair = TxHelpers.signer(2) + val dApp2: KeyPair = TxHelpers.signer(3) + val dApp3: KeyPair = TxHelpers.signer(4) + + val balances: Seq[AddrWithBalance] = + Seq(invoker, dApp1, dApp2, dApp3).map(acc => AddrWithBalance(acc.toAddress, 10.waves)) + + val settings: WavesSettings = DomainPresets.RideV6 + + val errorLogLimit = 1024 + + property( + "correct error log for FailedTransactionError" + ) { + createTestCase( + "testCase", + Seq(CONST_BOOLEAN(true)) + )((tx, leaseId, assetId) => + s"""FailedTransactionError(code = 1, error = AccountBalanceError(Map(3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM -> negative waves balance: 3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM, old: 999000010, new: -98900999990)), log = + | @invokedDApp = Address( + | bytes = base58'3MsY23LPQnvPZnBKpvs6YcnCvGjLVD42pSy' + | ) + | @invokedFuncName = "testCase" + | i = Invocation( + | originCaller = Address( + | bytes = base58'3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC' + | ) + | payments = [] + | callerPublicKey = base58'8h47fXqSctZ6sb3q6Sst9qH1UNzR5fjez2eEP6BvEfcr' + | feeAssetId = Unit + | originCallerPublicKey = base58'8h47fXqSctZ6sb3q6Sst9qH1UNzR5fjez2eEP6BvEfcr' + | transactionId = base58'${tx.id()}' + | caller = Address( + | bytes = base58'3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC' + | ) + | fee = 500000 + | ) + | testCase.@args = [ + | true + | ] + | invoke.@args = [ + | Address( + | bytes = base58'3MsY23LPQnvPZnBKpvs6YcnCvGjLVD42pSy' + | ), + | "nested11", + | [], + | [] + | ] + | invoke.@complexity = 75 + | @complexityLimit = 51925 + | nested11.@stateChanges = StateChanges( + | leases = [ + | Lease( + | recipient = Address( + | bytes = base58'3N4DiVEiZHzcjEhoBx2kmoKKCH7GBZMim3L' + | ) + | amount = 2 + | nonce = 0 + | id = base58'$leaseId' + | ) + | ] + | reissues = [ + | Reissue( + | id = base58'$assetId' + | isReissuable = true + | quantity = 10 + | ) + | ] + | data = [ + | DeleteEntry( + | key = "nested12_key_str" + | ) + | ] + | issues = [ + | Issue( + | isReissuable = true + | nonce = 0 + | description = "desc" + | id = base58'$assetId' + | decimals = 0 + | name = "testAsset" + | quantity = 20 + | ) + | ] + | sponsorFees = [ + | SponsorFee( + | id = base58'$assetId' + | minSponsoredAssetFee = 1 + | ) + | ] + | leaseCancels = [ + | LeaseCancel( + | id = base58'$leaseId' + | ) + | ] + | transfers = [ + | ScriptTransfer( + | amount = 3 + | recipient = Address( + | bytes = base58'3N4DiVEiZHzcjEhoBx2kmoKKCH7GBZMim3L' + | ) + | asset = base58'$assetId' + | ) + | ] + | burns = [ + | Burn( + | id = base58'$assetId' + | quantity = 1 + | ) + | ] + | invokes = [ + | Invoke( + | dApp = Address( + | bytes = base58'3MsY23LPQnvPZnBKpvs6YcnCvGjLVD42pSy' + | ) + | call = Call( + | function = "nested12" + | args = [ + | "test_str", + | 123 + | ] + | ) + | stateChanges = StateChanges( + | leases = [] + | reissues = [] + | data = [ + | StringEntry( + | key = "nested12_key_str" + | value = "test_str" + | ), + | IntegerEntry( + | key = "nested12_key_int" + | value = 123 + | ) + | ] + | issues = [] + | sponsorFees = [] + | leaseCancels = [] + | transfers = [] + | burns = [] + | invokes = [] + | ) + | payments = [] + | ) + | ] + | ) + | nested11.@complexity = 111 + | @complexityLimit = 51814 + | nested11 = 5 + | ==.@args = [ + | 5, + | 5 + | ] + | ==.@complexity = 1 + | @complexityLimit = 51813 + | Address.@args = [ + | base58'3N4DiVEiZHzcjEhoBx2kmoKKCH7GBZMim3L' + | ] + | dapp2 = Address( + | bytes = base58'3N4DiVEiZHzcjEhoBx2kmoKKCH7GBZMim3L' + | ) + | Address.@complexity = 1 + | @complexityLimit = 51812 + | shouldFail = true + | message = base58'emsY' + | sig = base58'4uXfw7162zaopAkTNa7eo6YK2mJsTiHGJL3dCtRRH63z1nrdoHBHyhbvrfZovkxf2jKsi2vPsaP2XykfZmUiwPeg' + | pub = base58'HnU9jfhpMcQNaG5yQ46eR43RnkWKGxerw2zVrbpnbGof' + | sigVerify.@args = [ + | base58'emsY', + | base58'4uXfw7162zaopAkTNa7eo6YK2mJsTiHGJL3dCtRRH63z1nrdoHBHyhbvrfZovkxf2jKsi2vPsaP2XykfZmUiwPeg', + | base58'HnU9jfhpMcQNaG5yQ46eR43RnkWKGxerw2zVrbpnbGof' + | ] + | sigVerify.@complexity = 180 + | @complexityLimit = 51632 + | sigVerify.@args = [ + | base58'emsY', + | base58'4uXfw7162zaopAkTNa7eo6YK2mJsTiHGJL3dCtRRH63z1nrdoHBHyhbvrfZovkxf2jKsi2vPsaP2XykfZmUiwPeg', + | base58'HnU9jfhpMcQNaG5yQ46eR43RnkWKGxerw2zVrbpnbGof' + | ] + | sigVerify.@complexity = 180 + | @complexityLimit = 51452 + | sigVerify.@args = [ + | base58'emsY', + | base58'4uXfw7162zaopAkTNa7eo6YK2mJsTiHGJL3dCtRRH63z1nrdoHBHyhbvrfZovkxf2jKsi2vPsaP2XykfZmUiwPeg', + | base58'HnU9jfhpMcQNaG5yQ46eR43RnkWKGxerw2zVrbpnbGof' + | ] + | sigVerify.@complexity = 180 + | @complexityLimit = 51272 + | sigVerify_64Kb.@args = [ + | base58'emsY', + | base58'4uXfw7162zaopAkTNa7eo6YK2mJsTiHGJL3dCtRRH63z1nrdoHBHyhbvrfZovkxf2jKsi2vPsaP2XykfZmUiwPeg', + | base58'HnU9jfhpMcQNaG5yQ46eR43RnkWKGxerw2zVrbpnbGof' + | ] + | sigVerify_64Kb.@complexity = 93 + | @complexityLimit = 51179 + | complex = true + | identityBool.@args = [ + | true + | ] + | t = true + | identityBool.@complexity = 1 + | @complexityLimit = 51178 + | cons.@args = [ + | true, + | [] + | ] + | cons.@complexity = 1 + | @complexityLimit = 51177 + | invoke.@args = [ + | Address( + | bytes = base58'3N4DiVEiZHzcjEhoBx2kmoKKCH7GBZMim3L' + | ), + | "testCase", + | [ + | true + | ], + | [] + | ] + | invoke.@complexity = 75 + | @complexityLimit = 51102 + | inv = FailedTransactionError(code = 1, error = AccountBalanceError(Map(3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM -> negative waves balance: 3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM, old: 999000010, new: -98900999990)), log = + | @invokedDApp = Address( + | bytes = base58'3N4DiVEiZHzcjEhoBx2kmoKKCH7GBZMim3L' + | ) + | @invokedFuncName = "testCase" + | i = Invocation( + | originCaller = Address( + | bytes = base58'3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC' + | ) + | payments = [] + | callerPublicKey = base58'5UMMCMELXL4yZKaUiuATw6H2xoeY2k93NwzCxiMoTbrK' + | feeAssetId = Unit + | originCallerPublicKey = base58'8h47fXqSctZ6sb3q6Sst9qH1UNzR5fjez2eEP6BvEfcr' + | transactionId = base58'${tx.id()}' + | caller = Address( + | bytes = base58'3MsY23LPQnvPZnBKpvs6YcnCvGjLVD42pSy' + | ) + | fee = 500000 + | ) + | testCase.@args = [ + | true + | ] + | Address.@args = [ + | base58'3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM' + | ] + | dapp3 = Address( + | bytes = base58'3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM' + | ) + | Address.@complexity = 1 + | @complexityLimit = 51101 + | cons.@args = [ + | true, + | [] + | ] + | cons.@complexity = 1 + | @complexityLimit = 51100 + | AttachedPayment.@args = [ + | Unit, + | 10 + | ] + | AttachedPayment.@complexity = 1 + | @complexityLimit = 51099 + | cons.@args = [ + | AttachedPayment( + | assetId = Unit + | amount = 10 + | ), + | [] + | ] + | cons.@complexity = 1 + | @complexityLimit = 51098 + | invoke.@args = [ + | Address( + | bytes = base58'3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM' + | ), + | "nested31", + | [ + | true + | ], + | [ + | AttachedPayment( + | assetId = Unit + | amount = 10 + | ) + | ] + | ] + | invoke.@complexity = 75 + | @complexityLimit = 51023 + | nested31.@stateChanges = StateChanges( + | leases = [] + | reissues = [] + | data = [ + | BooleanEntry( + | key = "nested31_key_bool" + | value = true + | ) + | ] + | issues = [] + | sponsorFees = [] + | leaseCancels = [] + | transfers = [] + | burns = [] + | invokes = [] + | ) + | nested31.@complexity = 2 + | @complexityLimit = 51021 + | nested31 = Unit + | ==.@args = [ + | Unit, + | Unit + | ] + | ==.@complexity = 1 + | @complexityLimit = 51020 + | shouldFail = true + | message = base58'emsY' + | sig = base58'4uXfw7162zaopAkTNa7eo6YK2mJsTiHGJL3dCtRRH63z1nrdoHBHyhbvrfZovkxf2jKsi2vPsaP2XykfZmUiwPeg' + | pub = base58'HnU9jfhpMcQNaG5yQ46eR43RnkWKGxerw2zVrbpnbGof' + | sigVerify_8Kb.@args = [ + | base58'emsY', + | base58'4uXfw7162zaopAkTNa7eo6YK2mJsTiHGJL3dCtRRH63z1nrdoHBHyhbvrfZovkxf2jKsi2vPsaP2XykfZmUiwPeg', + | base58'HnU9jfhpMcQNaG5yQ46eR43RnkWKGxerw2zVrbpnbGof' + | ] + | sigVerify_8Kb.@complexity = 43 + | @complexityLimit = 50977 + | complexCase1 = true + | cons.@args = [ + | true, + | [] + | ] + | cons.@complexity = 1 + | @complexityLimit = 50976 + | invoke.@args = [ + | Address( + | bytes = base58'3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM' + | ), + | "testCase", + | [ + | true + | ], + | [] + | ] + | invoke.@complexity = 75 + | @complexityLimit = 50901 + | inv = FailedTransactionError(code = 1, error = AccountBalanceError(Map(3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM -> negative waves balance: 3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM, old: 999000010, new: -98900999990)), log = + | @invokedDApp = Address( + | bytes = base58'3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM' + | ) + | @invokedFuncName = "testCase" + | i = Invocation( + | originCaller = Address( + | bytes = base58'3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC' + | ) + | payments = [] + | callerPublicKey = base58'5h6zeBqbczVqDG82kzFhgyeuWTv1pgKbMvGrhPq6WLwz' + | feeAssetId = Unit + | originCallerPublicKey = base58'8h47fXqSctZ6sb3q6Sst9qH1UNzR5fjez2eEP6BvEfcr' + | transactionId = base58'${tx.id()}' + | caller = Address( + | bytes = base58'3N4DiVEiZHzcjEhoBx2kmoKKCH7GBZMim3L' + | ) + | fee = 500000 + | ) + | testCase.@args = [ + | true + | ] + | cons.@args = [ + | base58'aaa', + | [] + | ] + | cons.@complexity = 1 + | @complexityLimit = 50900 + | invoke.@args = [ + | Address( + | bytes = base58'3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM' + | ), + | "nested32", + | [ + | base58'aaa' + | ], + | [] + | ] + | invoke.@complexity = 75 + | @complexityLimit = 50825 + | nested32.@stateChanges = StateChanges( + | leases = [] + | reissues = [] + | data = [ + | BinaryEntry( + | key = "nested32_key_bin" + | value = base58'aaa' + | ) + | ] + | issues = [] + | sponsorFees = [] + | leaseCancels = [] + | transfers = [] + | burns = [] + | invokes = [] + | ) + | nested32.@complexity = 2 + | @complexityLimit = 50823 + | nested32 = Unit + | ==.@args = [ + | Unit, + | Unit + | ] + | ==.@complexity = 1 + | @complexityLimit = 50822 + | message = base58'emsY' + | sig = base58'4uXfw7162zaopAkTNa7eo6YK2mJsTiHGJL3dCtRRH63z1nrdoHBHyhbvrfZovkxf2jKsi2vPsaP2XykfZmUiwPeg' + | pub = base58'HnU9jfhpMcQNaG5yQ46eR43RnkWKGxerw2zVrbpnbGof' + | sigVerify.@args = [ + | base58'emsY', + | base58'4uXfw7162zaopAkTNa7eo6YK2mJsTiHGJL3dCtRRH63z1nrdoHBHyhbvrfZovkxf2jKsi2vPsaP2XykfZmUiwPeg', + | base58'HnU9jfhpMcQNaG5yQ46eR43RnkWKGxerw2zVrbpnbGof' + | ] + | sigVerify.@complexity = 180 + | @complexityLimit = 50642 + | ScriptTransfer.@args = [ + | Address( + | bytes = base58'3N4DiVEiZHzcjEhoBx2kmoKKCH7GBZMim3L' + | ), + | 99900000000, + | Unit + | ] + | ScriptTransfer.@complexity = 1 + | @complexityLimit = 50641 + | cons.@args = [ + | ScriptTransfer( + | recipient = Address( + | bytes = base58'3N4DiVEiZHzcjEhoBx2kmoKKCH7GBZMim3L' + | ) + | amount = 99900000000 + | asset = Unit + | ), + | [] + | ] + | cons.@complexity = 1 + | @complexityLimit = 50640 + | ) + | ) + |)""".stripMargin + ) + } + + property( + "correct error log for ScriptExecutionError" + ) { + createTestCase( + "testCase", + Seq(CONST_BOOLEAN(false)) + )((tx, leaseId, assetId) => + s"""InvokeRejectError(error = AccountBalanceError(Map(3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM -> negative waves balance: 3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM, old: 999000010, new: -98900999990)), log = + | @invokedDApp = Address( + | bytes = base58'3MsY23LPQnvPZnBKpvs6YcnCvGjLVD42pSy' + | ) + | @invokedFuncName = "testCase" + | i = Invocation( + | originCaller = Address( + | bytes = base58'3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC' + | ) + | payments = [] + | callerPublicKey = base58'8h47fXqSctZ6sb3q6Sst9qH1UNzR5fjez2eEP6BvEfcr' + | feeAssetId = Unit + | originCallerPublicKey = base58'8h47fXqSctZ6sb3q6Sst9qH1UNzR5fjez2eEP6BvEfcr' + | transactionId = base58'${tx.id()}' + | caller = Address( + | bytes = base58'3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC' + | ) + | fee = 500000 + | ) + | testCase.@args = [ + | false + | ] + | invoke.@args = [ + | Address( + | bytes = base58'3MsY23LPQnvPZnBKpvs6YcnCvGjLVD42pSy' + | ), + | "nested11", + | [], + | [] + | ] + | invoke.@complexity = 75 + | @complexityLimit = 51925 + | nested11.@stateChanges = StateChanges( + | leases = [ + | Lease( + | recipient = Address( + | bytes = base58'3N4DiVEiZHzcjEhoBx2kmoKKCH7GBZMim3L' + | ) + | amount = 2 + | nonce = 0 + | id = base58'$leaseId' + | ) + | ] + | reissues = [ + | Reissue( + | id = base58'$assetId' + | isReissuable = true + | quantity = 10 + | ) + | ] + | data = [ + | DeleteEntry( + | key = "nested12_key_str" + | ) + | ] + | issues = [ + | Issue( + | isReissuable = true + | nonce = 0 + | description = "desc" + | id = base58'$assetId' + | decimals = 0 + | name = "testAsset" + | quantity = 20 + | ) + | ] + | sponsorFees = [ + | SponsorFee( + | id = base58'$assetId' + | minSponsoredAssetFee = 1 + | ) + | ] + | leaseCancels = [ + | LeaseCancel( + | id = base58'$leaseId' + | ) + | ] + | transfers = [ + | ScriptTransfer( + | amount = 3 + | recipient = Address( + | bytes = base58'3N4DiVEiZHzcjEhoBx2kmoKKCH7GBZMim3L' + | ) + | asset = base58'$assetId' + | ) + | ] + | burns = [ + | Burn( + | id = base58'$assetId' + | quantity = 1 + | ) + | ] + | invokes = [ + | Invoke( + | dApp = Address( + | bytes = base58'3MsY23LPQnvPZnBKpvs6YcnCvGjLVD42pSy' + | ) + | call = Call( + | function = "nested12" + | args = [ + | "test_str", + | 123 + | ] + | ) + | stateChanges = StateChanges( + | leases = [] + | reissues = [] + | data = [ + | StringEntry( + | key = "nested12_key_str" + | value = "test_str" + | ), + | IntegerEntry( + | key = "nested12_key_int" + | value = 123 + | ) + | ] + | issues = [] + | sponsorFees = [] + | leaseCancels = [] + | transfers = [] + | burns = [] + | invokes = [] + | ) + | payments = [] + | ) + | ] + | ) + | nested11.@complexity = 111 + | @complexityLimit = 51814 + | nested11 = 5 + | ==.@args = [ + | 5, + | 5 + | ] + | ==.@complexity = 1 + | @complexityLimit = 51813 + | Address.@args = [ + | base58'3N4DiVEiZHzcjEhoBx2kmoKKCH7GBZMim3L' + | ] + | dapp2 = Address( + | bytes = base58'3N4DiVEiZHzcjEhoBx2kmoKKCH7GBZMim3L' + | ) + | Address.@complexity = 1 + | @complexityLimit = 51812 + | shouldFail = false + | cons.@args = [ + | false, + | [] + | ] + | cons.@complexity = 1 + | @complexityLimit = 51811 + | invoke.@args = [ + | Address( + | bytes = base58'3N4DiVEiZHzcjEhoBx2kmoKKCH7GBZMim3L' + | ), + | "testCase", + | [ + | false + | ], + | [] + | ] + | invoke.@complexity = 75 + | @complexityLimit = 51736 + | inv = FailedTransactionError(code = 1, error = AccountBalanceError(Map(3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM -> negative waves balance: 3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM, old: 999000010, new: -98900999990)), log = + | @invokedDApp = Address( + | bytes = base58'3N4DiVEiZHzcjEhoBx2kmoKKCH7GBZMim3L' + | ) + | @invokedFuncName = "testCase" + | i = Invocation( + | originCaller = Address( + | bytes = base58'3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC' + | ) + | payments = [] + | callerPublicKey = base58'5UMMCMELXL4yZKaUiuATw6H2xoeY2k93NwzCxiMoTbrK' + | feeAssetId = Unit + | originCallerPublicKey = base58'8h47fXqSctZ6sb3q6Sst9qH1UNzR5fjez2eEP6BvEfcr' + | transactionId = base58'${tx.id()}' + | caller = Address( + | bytes = base58'3MsY23LPQnvPZnBKpvs6YcnCvGjLVD42pSy' + | ) + | fee = 500000 + | ) + | testCase.@args = [ + | false + | ] + | Address.@args = [ + | base58'3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM' + | ] + | dapp3 = Address( + | bytes = base58'3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM' + | ) + | Address.@complexity = 1 + | @complexityLimit = 51735 + | cons.@args = [ + | true, + | [] + | ] + | cons.@complexity = 1 + | @complexityLimit = 51734 + | AttachedPayment.@args = [ + | Unit, + | 10 + | ] + | AttachedPayment.@complexity = 1 + | @complexityLimit = 51733 + | cons.@args = [ + | AttachedPayment( + | assetId = Unit + | amount = 10 + | ), + | [] + | ] + | cons.@complexity = 1 + | @complexityLimit = 51732 + | invoke.@args = [ + | Address( + | bytes = base58'3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM' + | ), + | "nested31", + | [ + | true + | ], + | [ + | AttachedPayment( + | assetId = Unit + | amount = 10 + | ) + | ] + | ] + | invoke.@complexity = 75 + | @complexityLimit = 51657 + | nested31.@stateChanges = StateChanges( + | leases = [] + | reissues = [] + | data = [ + | BooleanEntry( + | key = "nested31_key_bool" + | value = true + | ) + | ] + | issues = [] + | sponsorFees = [] + | leaseCancels = [] + | transfers = [] + | burns = [] + | invokes = [] + | ) + | nested31.@complexity = 2 + | @complexityLimit = 51655 + | nested31 = Unit + | ==.@args = [ + | Unit, + | Unit + | ] + | ==.@complexity = 1 + | @complexityLimit = 51654 + | shouldFail = false + | cons.@args = [ + | false, + | [] + | ] + | cons.@complexity = 1 + | @complexityLimit = 51653 + | invoke.@args = [ + | Address( + | bytes = base58'3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM' + | ), + | "testCase", + | [ + | false + | ], + | [] + | ] + | invoke.@complexity = 75 + | @complexityLimit = 51578 + | inv = FailedTransactionError(code = 1, error = AccountBalanceError(Map(3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM -> negative waves balance: 3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM, old: 999000010, new: -98900999990)), log = + | @invokedDApp = Address( + | bytes = base58'3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM' + | ) + | @invokedFuncName = "testCase" + | i = Invocation( + | originCaller = Address( + | bytes = base58'3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC' + | ) + | payments = [] + | callerPublicKey = base58'5h6zeBqbczVqDG82kzFhgyeuWTv1pgKbMvGrhPq6WLwz' + | feeAssetId = Unit + | originCallerPublicKey = base58'8h47fXqSctZ6sb3q6Sst9qH1UNzR5fjez2eEP6BvEfcr' + | transactionId = base58'${tx.id()}' + | caller = Address( + | bytes = base58'3N4DiVEiZHzcjEhoBx2kmoKKCH7GBZMim3L' + | ) + | fee = 500000 + | ) + | testCase.@args = [ + | false + | ] + | cons.@args = [ + | base58'aaa', + | [] + | ] + | cons.@complexity = 1 + | @complexityLimit = 51577 + | invoke.@args = [ + | Address( + | bytes = base58'3N87Qja7rNj8z6H7nG9EYtjCXQtZLawaxyM' + | ), + | "nested32", + | [ + | base58'aaa' + | ], + | [] + | ] + | invoke.@complexity = 75 + | @complexityLimit = 51502 + | nested32.@stateChanges = StateChanges( + | leases = [] + | reissues = [] + | data = [ + | BinaryEntry( + | key = "nested32_key_bin" + | value = base58'aaa' + | ) + | ] + | issues = [] + | sponsorFees = [] + | leaseCancels = [] + | transfers = [] + | burns = [] + | invokes = [] + | ) + | nested32.@complexity = 2 + | @complexityLimit = 51500 + | nested32 = Unit + | ==.@args = [ + | Unit, + | Unit + | ] + | ==.@complexity = 1 + | @complexityLimit = 51499 + | message = base58'emsY' + | sig = base58'4uXfw7162zaopAkTNa7eo6YK2mJsTiHGJL3dCtRRH63z1nrdoHBHyhbvrfZovkxf2jKsi2vPsaP2XykfZmUiwPeg' + | pub = base58'HnU9jfhpMcQNaG5yQ46eR43RnkWKGxerw2zVrbpnbGof' + | sigVerify.@args = [ + | base58'emsY', + | base58'4uXfw7162zaopAkTNa7eo6YK2mJsTiHGJL3dCtRRH63z1nrdoHBHyhbvrfZovkxf2jKsi2vPsaP2XykfZmUiwPeg', + | base58'HnU9jfhpMcQNaG5yQ46eR43RnkWKGxerw2zVrbpnbGof' + | ] + | sigVerify.@complexity = 180 + | @complexityLimit = 51319 + | ScriptTransfer.@args = [ + | Address( + | bytes = base58'3N4DiVEiZHzcjEhoBx2kmoKKCH7GBZMim3L' + | ), + | 99900000000, + | Unit + | ] + | ScriptTransfer.@complexity = 1 + | @complexityLimit = 51318 + | cons.@args = [ + | ScriptTransfer( + | recipient = Address( + | bytes = base58'3N4DiVEiZHzcjEhoBx2kmoKKCH7GBZMim3L' + | ) + | amount = 99900000000 + | asset = Unit + | ), + | [] + | ] + | cons.@complexity = 1 + | @complexityLimit = 51317 + | ) + | ) + |)""".stripMargin + ) + } + + property(s"very big error logs are shortened correctly to $errorLogLimit}") { + val dApp = TxHelpers.signer(0) + + val setScript = TxHelpers.setScript( + dApp, + TestCompiler(V6).compileContract( + s""" + |@Callable(inv) + |func foo() = { + | let a = "1".size() + | strict res = invoke(this, "foo", [], []) + | [] + |} + | """.stripMargin + ), + fee = 1.waves, + version = TxVersion.V2, + timestamp = 0 + ) + + val invoke = TxHelpers.invoke(dApp.toAddress, Some("foo"), Seq.empty, invoker = dApp, timestamp = 0) + + assertDiffEiTraced( + Seq( + TestBlock.create(Seq(TxHelpers.genesis(dApp.toAddress, timestamp = 0))), + TestBlock.create(Seq(setScript)) + ), + TestBlock.create(Seq(invoke)), + settings.blockchainSettings.functionalitySettings + ) { result => + result.trace + .collectFirst { case invokeTrace: InvokeScriptTrace => + invokeTrace.resultE match { + case Left(w: WithLog) => w.toStringWithLog(errorLogLimit) + case _ => fail("Result should be error with log") + } + } + .foreach { error => + val expectedError = + s"""FailedTransactionError(code = 1, error = ScriptRunsLimitError(DApp calls limit = 100 is exceeded), log = + | @invokedDApp = Address( + | bytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9' + | ) + | @invokedFuncName = "foo" + | inv = Invocation( + | originCaller = Address( + | bytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9' + | ) + | payments = [] + | callerPublicKey = base58'9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ' + | feeAssetId = Unit + | originCallerPublicKey = base58'9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ' + | transactionId = base58'CgfvaFiiQXPqvFytezf8iAAastJu5qdbr1ysPXtvpPgz' + | caller = Address( + | bytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9' + | ) + | fee = 500000 + | ) + | foo.@args = [] + | invoke.@args = [ + | Address( + | bytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9' + | ), + | "foo", + | [], + | [] + | ] + | invoke.@complexity = 75 + | @complexityLimit = 51925 + | res = FailedTransactionError(code = 1, error = ScriptRunsLimitError(DApp calls limit = 100 is exceeded), log = + | @invokedDApp = Address( + | bytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9' + | ) + | @invokedFuncName = "foo" + |... + | ) + |)""".stripMargin + + expectedError.dropWhile(_ != '\n').tail.length shouldBe <(errorLogLimit) + error shouldBe expectedError + } + } + } + + private def createTestCase( + funcName: String, + args: Seq[EXPR] + )(expectedResult: (InvokeScriptTransaction, ByteStr, ByteStr) => String): Unit = { + withDomain(settings, balances) { d => + val invoke = TxHelpers.invoke(dApp1.toAddress, func = Some(funcName), args = args, invoker = invoker) + d.appendBlock( + TxHelpers.setScript(dApp1, dAppContract1(dApp2.toAddress)), + TxHelpers.setScript(dApp2, dAppContract2(dApp3.toAddress)), + TxHelpers.setScript(dApp3, dAppContract3) + ) + d.transactionDiffer(invoke) + .trace + .collectFirst { case invokeTrace: InvokeScriptTrace => + invokeTrace.resultE match { + case Left(w: WithLog) => w.toStringWithLog(Int.MaxValue) + case _ => fail("Result should be error with log") + } + } + .foreach { error => + val leaseId = Lease.calculateId(Lease(Recipient.Address(ByteStr(dApp2.toAddress.bytes)), 2, 0), invoke.id()) + val assetId = Issue.calculateId(0, "desc", isReissuable = true, "testAsset", 20, 0, invoke.id()) + + error shouldBe expectedResult(invoke, leaseId, assetId) + } + } + } + + private def dAppContract1(dApp2: Address): Script = + TestCompiler(V6).compileContract( + s""" + |{-# STDLIB_VERSION 6 #-} + |{-# CONTENT_TYPE DAPP #-} + |{-# SCRIPT_TYPE ACCOUNT #-} + | + |let dapp2 = Address(base58'${dApp2.toString}') + | + |let message = base58'emsY' + |let pub = base58'HnU9jfhpMcQNaG5yQ46eR43RnkWKGxerw2zVrbpnbGof' + |let sig = base58'4uXfw7162zaopAkTNa7eo6YK2mJsTiHGJL3dCtRRH63z1nrdoHBHyhbvrfZovkxf2jKsi2vPsaP2XykfZmUiwPeg' + | + |let complex = sigVerify(message, sig, pub) && sigVerify(message, sig, pub) && sigVerify(message, sig, pub) && sigVerify_64Kb(message, sig, pub) + | + |func identityBool(t: Boolean) = t + | + |@Callable(i) + |func nested12(str: String, int: Int) = { + | [StringEntry("nested12_key_str", str), IntegerEntry("nested12_key_int", int)] + |} + | + |@Callable(i) + |func nested11() = { + | strict nested12 = invoke(this, "nested12", ["test_str", 123], []) + | let issue = Issue("testAsset", "desc", 20, 0, true) + | let assetId = calculateAssetId(issue) + | let lease = Lease(dapp2, 2) + | ( + | [ + | DeleteEntry("nested12_key_str"), + | issue, + | Burn(assetId, 1), + | Reissue(assetId, 10, true), + | lease, + | LeaseCancel(calculateLeaseId(lease)), + | ScriptTransfer(dapp2, 3, assetId), + | SponsorFee(assetId, 1) + | ], + | 5 + | ) + |} + | + |@Callable(i) + |func testCase(shouldFail: Boolean) = { + | strict nested11 = invoke(this, "nested11", [], []) + | strict inv = invoke(dapp2, "testCase", [shouldFail && complex && identityBool(true)], []) + | [] + |} + |""".stripMargin + ) + + private def dAppContract2(dApp3: Address): Script = + TestCompiler(V6).compileContract( + s""" + |{-# STDLIB_VERSION 6 #-} + |{-# CONTENT_TYPE DAPP #-} + |{-# SCRIPT_TYPE ACCOUNT #-} + | + |let dapp3 = Address(base58'${dApp3.toString}') + | + |let message = base58'emsY' + |let pub = base58'HnU9jfhpMcQNaG5yQ46eR43RnkWKGxerw2zVrbpnbGof' + |let sig = base58'4uXfw7162zaopAkTNa7eo6YK2mJsTiHGJL3dCtRRH63z1nrdoHBHyhbvrfZovkxf2jKsi2vPsaP2XykfZmUiwPeg' + | + |let complexCase1 = sigVerify_8Kb(message, sig, pub) + | + |@Callable(i) + |func testCase(shouldFail: Boolean) = { + | strict nested31 = invoke(dapp3, "nested31", [true], [AttachedPayment(unit, 10)]) + | strict inv = invoke(dapp3, "testCase", [shouldFail && complexCase1], []) + | [] + |} + |""".stripMargin + ) + + private def dAppContract3: Script = + TestCompiler(V6).compileContract( + s""" + |{-# STDLIB_VERSION 6 #-} + |{-# CONTENT_TYPE DAPP #-} + |{-# SCRIPT_TYPE ACCOUNT #-} + | + |let message = base58'emsY' + |let pub = base58'HnU9jfhpMcQNaG5yQ46eR43RnkWKGxerw2zVrbpnbGof' + |let sig = base58'4uXfw7162zaopAkTNa7eo6YK2mJsTiHGJL3dCtRRH63z1nrdoHBHyhbvrfZovkxf2jKsi2vPsaP2XykfZmUiwPeg' + | + |@Callable(i) + |func nested31(bool: Boolean) = { + | [BooleanEntry("nested31_key_bool", bool)] + |} + | + |@Callable(i) + |func nested32(bs: ByteVector) = { + | [BinaryEntry("nested32_key_bin", bs)] + |} + | + |@Callable(i) + |func testCase(bool:Boolean) = { + | strict nested32 = invoke(this, "nested32", [base58'aaa'], []) + | if (sigVerify(message, sig, pub)) then + | [ScriptTransfer(i.caller, 99900000000, unit)] + | else [] + |} + |""".stripMargin + ) +} diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/eth/EthereumTransferSmartTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/eth/EthereumTransferSmartTest.scala index a745c9d5929..d37b27edb34 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/eth/EthereumTransferSmartTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/eth/EthereumTransferSmartTest.scala @@ -108,7 +108,7 @@ class EthereumTransferSmartTest extends PropSpec with WithDomain with EthHelpers ) } else (the[Exception] thrownBy d.appendBlock(setVerifier())).getMessage should include( - s"t = Left(CommonError(extract() called on unit value))" + s"extract() called on unit value" ) } } diff --git a/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala b/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala index 7807cc84fb1..dcd0e1db6fd 100644 --- a/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala +++ b/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala @@ -344,7 +344,8 @@ object TxHelpers { script: Script, fee: Long = FeeConstants(TransactionType.SetScript) * FeeUnit, version: TxVersion = TxVersion.V1, - chainId: Byte = AddressScheme.current.chainId + chainId: Byte = AddressScheme.current.chainId, + timestamp: TxTimestamp = timestamp ): SetScriptTransaction = { SetScriptTransaction.selfSigned(version, acc, Some(script), fee, timestamp, chainId).explicitGet() } @@ -369,7 +370,8 @@ object TxHelpers { invoker: KeyPair = defaultSigner, fee: Long = FeeConstants(TransactionType.InvokeScript) * FeeUnit, feeAssetId: Asset = Waves, - version: TxVersion = TxVersion.V2 + version: TxVersion = TxVersion.V2, + timestamp: TxTimestamp = timestamp ): InvokeScriptTransaction = { val fc = func.map(name => functionCall(name, args*)) Signed.invokeScript(version, invoker, dApp, fc, payments, fee, feeAssetId, timestamp) diff --git a/node/src/test/scala/com/wavesplatform/transaction/smart/EthereumTransactionStateChangesSpec.scala b/node/src/test/scala/com/wavesplatform/transaction/smart/EthereumTransactionStateChangesSpec.scala index 6fa644963b5..e9e0e44c716 100644 --- a/node/src/test/scala/com/wavesplatform/transaction/smart/EthereumTransactionStateChangesSpec.scala +++ b/node/src/test/scala/com/wavesplatform/transaction/smart/EthereumTransactionStateChangesSpec.scala @@ -60,10 +60,10 @@ class EthereumTransactionStateChangesSpec extends FlatSpec with WithDomain with d.helpers.setScript( dApp, TxHelpers.scriptV5(s"""@Callable(i) - |func deposit() = { - | if ((${(1 to 15).map(_ => "sigVerify(base58'', base58'', base58'')").mkString(" || ")}) || true) then throw("err") - | else [StringEntry("test", "foo")] - |}""".stripMargin) + |func deposit() = { + | if ((${(1 to 15).map(_ => "sigVerify(base58'', base58'', base58'')").mkString(" || ")}) || true) then throw("err") + | else [StringEntry("test", "foo")] + |}""".stripMargin) ) val invoke = EthTxGenerator.generateEthInvoke( @@ -232,7 +232,7 @@ class EthereumTransactionStateChangesSpec extends FlatSpec with WithDomain with | } ], | "error" : { | "code" : 1, - | "text" : "FailedTransactionError(code = 1, error = err, log =)" + | "text" : "err" | } |} |""".stripMargin) diff --git a/node/src/test/scala/com/wavesplatform/transaction/smart/SubInvokeStateChangesSpec.scala b/node/src/test/scala/com/wavesplatform/transaction/smart/SubInvokeStateChangesSpec.scala index b02f4f7047a..d58d0fab064 100644 --- a/node/src/test/scala/com/wavesplatform/transaction/smart/SubInvokeStateChangesSpec.scala +++ b/node/src/test/scala/com/wavesplatform/transaction/smart/SubInvokeStateChangesSpec.scala @@ -30,29 +30,30 @@ class SubInvokeStateChangesSpec extends FlatSpec with WithDomain with JsonMatche val balances = Seq(dAppAddress, addr2f, addr3f, addr2s, addr3s).map(acc => AddrWithBalance(acc.toAddress, 1.waves)) :+ AddrWithBalance(TxHelpers.defaultAddress) - withDomain(DomainPresets.RideV5, balances) { d => { // Prerequisites - val script1 = compileV5(genScript(Seq(addr2s.toAddress, addr2f.toAddress))) - val script2 = compileV5(genScript(Some(addr3f.toAddress))) - val script3 = compileV5(genScript(None, fail = true)) - val script2alt = compileV5(genScript(Some(addr3s.toAddress))) - val script3alt = compileV5(genScript(None)) + withDomain(DomainPresets.RideV5, balances) { d => + { // Prerequisites + val script1 = compileV5(genScript(Seq(addr2s.toAddress, addr2f.toAddress))) + val script2 = compileV5(genScript(Some(addr3f.toAddress))) + val script3 = compileV5(genScript(None, fail = true)) + val script2alt = compileV5(genScript(Some(addr3s.toAddress))) + val script3alt = compileV5(genScript(None)) - val setScripts = Seq( - TxHelpers.setScript(dAppAddress, script1), - TxHelpers.setScript(addr2f, script2), - TxHelpers.setScript(addr3f, script3), - TxHelpers.setScript(addr2s, script2alt), - TxHelpers.setScript(addr3s, script3alt) - ) - d.appendBlock(setScripts *) - } + val setScripts = Seq( + TxHelpers.setScript(dAppAddress, script1), + TxHelpers.setScript(addr2f, script2), + TxHelpers.setScript(addr3f, script3), + TxHelpers.setScript(addr2s, script2alt), + TxHelpers.setScript(addr3s, script3alt) + ) + d.appendBlock(setScripts*) + } // Actual test val invoke = TxHelpers.invoke(dAppAddress.toAddress, Some(ContractFunction)) d.appendBlock(invoke) val stateChanges = d.commonApi.invokeScriptResult(invoke.id()) - val json = Json.toJson(stateChanges) + val json = Json.toJson(stateChanges) json should matchJson( """{ | "data" : [ ], @@ -70,8 +71,8 @@ class SubInvokeStateChangesSpec extends FlatSpec with WithDomain with JsonMatche | "args" : [ ] | }, | "payment" : [ { - | "assetId" : null, - | "amount" : 17 + | "assetId" : null, + | "amount" : 17 | } ], | "stateChanges" : { | "data" : [ ], @@ -112,8 +113,8 @@ class SubInvokeStateChangesSpec extends FlatSpec with WithDomain with JsonMatche | "args" : [ ] | }, | "payment" : [ { - | "assetId" : null, - | "amount" : 17 + | "assetId" : null, + | "amount" : 17 | } ], | "stateChanges" : { | "data" : [ ], @@ -131,8 +132,8 @@ class SubInvokeStateChangesSpec extends FlatSpec with WithDomain with JsonMatche | "args" : [ ] | }, | "payment" : [ { - | "assetId" : null, - | "amount" : 17 + | "assetId" : null, + | "amount" : 17 | } ], | "stateChanges" : { | "data" : [ ], @@ -154,9 +155,10 @@ class SubInvokeStateChangesSpec extends FlatSpec with WithDomain with JsonMatche | } ], | "error" : { | "code" : 1, - | "text" : "FailedTransactionError(code = 1, error = boom, log =\n\t@p = false\n)" + | "text" : "boom" | } - |}""".stripMargin + |} + |""".stripMargin ) val allAddresses = Seq(dAppAddress, addr2s, addr3s, addr2f, addr3f).map(_.toAddress) @@ -174,8 +176,8 @@ class SubInvokeStateChangesSpec extends FlatSpec with WithDomain with JsonMatche |@Callable(i) |func $ContractFunction() = { | ${calls.zipWithIndex - .map { case (address, i) => s"""strict r$i = invoke(Address(base58'$address'), "$ContractFunction", [], [AttachedPayment(unit, 17)])""" } - .mkString("\n")} + .map { case (address, i) => s"""strict r$i = invoke(Address(base58'$address'), "$ContractFunction", [], [AttachedPayment(unit, 17)])""" } + .mkString("\n")} | if ($fail && !(${(1 to 10).map(_ => "sigVerify(base58'', base58'', base58'')").mkString(" || ")})) then throw("boom") else [] |}""".stripMargin } diff --git a/node/src/test/scala/com/wavesplatform/utx/UtxFailedTxsSpec.scala b/node/src/test/scala/com/wavesplatform/utx/UtxFailedTxsSpec.scala index 5f7668d8850..7ecdf94a829 100644 --- a/node/src/test/scala/com/wavesplatform/utx/UtxFailedTxsSpec.scala +++ b/node/src/test/scala/com/wavesplatform/utx/UtxFailedTxsSpec.scala @@ -196,23 +196,23 @@ class UtxFailedTxsSpec extends FlatSpec with WithDomain with Eventually { val (script, _) = ScriptCompiler .compile( """ - |{-# STDLIB_VERSION 4 #-} - |{-# CONTENT_TYPE DAPP #-} - |{-# SCRIPT_TYPE ACCOUNT #-} - | - |@Callable(i) - |func test1000() = { - | if (height % 2 == 0) then - | [] - | else - | if (!sigVerify(base58'', base58'', base58'') - | && !sigVerify(base58'', base58'', base58'') - | && !sigVerify(base58'', base58'', base58'') - | && !sigVerify(base58'', base58'', base58'')) then - | throw("height is odd") - | else [IntegerEntry("h", height)] - | } - | """.stripMargin, + |{-# STDLIB_VERSION 4 #-} + |{-# CONTENT_TYPE DAPP #-} + |{-# SCRIPT_TYPE ACCOUNT #-} + | + |@Callable(i) + |func test1000() = { + | if (height % 2 == 0) then + | [] + | else + | if (!sigVerify(base58'', base58'', base58'') + | && !sigVerify(base58'', base58'', base58'') + | && !sigVerify(base58'', base58'', base58'') + | && !sigVerify(base58'', base58'', base58'')) then + | throw("height is odd") + | else [IntegerEntry("h", height)] + | } + | """.stripMargin, ScriptEstimatorV3(fixOverflow = true, overhead = true) ) .explicitGet() @@ -236,25 +236,25 @@ class UtxFailedTxsSpec extends FlatSpec with WithDomain with Eventually { private[this] def genExpr(targetComplexity: Int, result: Boolean): String = { s""" - |if ($result) then - | ${"sigVerify(base58'', base58'', base58'') ||" * ((targetComplexity / 200) - 1)} true - |else - | ${"sigVerify(base58'', base58'', base58'') ||" * ((targetComplexity / 200) - 1)} false""".stripMargin + |if ($result) then + | ${"sigVerify(base58'', base58'', base58'') ||" * ((targetComplexity / 200) - 1)} true + |else + | ${"sigVerify(base58'', base58'', base58'') ||" * ((targetComplexity / 200) - 1)} false""".stripMargin } private[this] def genScript(targetComplexity: Int, result: Boolean = false): Script = { val expr = genExpr(targetComplexity, result) // ((1 to (targetComplexity / 2) - 2).map(_ => "true") :+ result.toString).mkString("&&") val scriptText = s""" - |{-#STDLIB_VERSION 4#-} - |{-#SCRIPT_TYPE ACCOUNT#-} - |{-#CONTENT_TYPE DAPP#-} - | - |@Callable(i) - |func default() = { - | if ($expr) then [] else throw("reached err") - |} - |""".stripMargin + |{-#STDLIB_VERSION 4#-} + |{-#SCRIPT_TYPE ACCOUNT#-} + |{-#CONTENT_TYPE DAPP#-} + | + |@Callable(i) + |func default() = { + | if ($expr) then [] else throw("reached err") + |} + |""".stripMargin TxHelpers.script(scriptText.stripMargin) } @@ -295,7 +295,7 @@ class UtxFailedTxsSpec extends FlatSpec with WithDomain with Eventually { val balances = AddrWithBalance.enoughBalances(TxHelpers.defaultSigner, dApp) withDomain(settings, balances) { d => - val utx = new UtxPoolImpl(ntpTime, d.blockchainUpdater, settings.utxSettings, settings.minerSettings.enable) + val utx = new UtxPoolImpl(ntpTime, d.blockchainUpdater, settings.utxSettings, settings.maxTxErrorLogSize, settings.minerSettings.enable) f(d, utx) utx.close() } diff --git a/node/src/test/scala/com/wavesplatform/utx/UtxPoolSpecification.scala b/node/src/test/scala/com/wavesplatform/utx/UtxPoolSpecification.scala index 4992b7c3af0..02860a36736 100644 --- a/node/src/test/scala/com/wavesplatform/utx/UtxPoolSpecification.scala +++ b/node/src/test/scala/com/wavesplatform/utx/UtxPoolSpecification.scala @@ -200,6 +200,7 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact allowSkipChecks = false, forceValidateInCleanup = false ), + Int.MaxValue, isMiningEnabled = true ) val amountPart = (senderBalance - fee) / 2 - fee @@ -225,7 +226,7 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact allowSkipChecks = false, forceValidateInCleanup = false ) - val utxPool = new UtxPoolImpl(time, bcu, settings, isMiningEnabled = true) + val utxPool = new UtxPoolImpl(time, bcu, settings, Int.MaxValue, isMiningEnabled = true) (sender, utxPool, txs) }).label("withBlacklisted") @@ -247,7 +248,7 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact allowSkipChecks = false, forceValidateInCleanup = false ) - val utxPool = new UtxPoolImpl(time, bcu, settings, isMiningEnabled = true) + val utxPool = new UtxPoolImpl(time, bcu, settings, Int.MaxValue, isMiningEnabled = true) (sender, utxPool, txs) }).label("withBlacklistedAndAllowedByRule") @@ -269,7 +270,7 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact allowSkipChecks = false, forceValidateInCleanup = false ) - val utxPool = new UtxPoolImpl(time, bcu, settings, isMiningEnabled = true) + val utxPool = new UtxPoolImpl(time, bcu, settings, Int.MaxValue, isMiningEnabled = true) (sender, utxPool, txs) }).label("withBlacklistedAndWhitelisted") @@ -295,7 +296,7 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact allowSkipChecks = false, forceValidateInCleanup = false ) - val utxPool = new UtxPoolImpl(time, bcu, settings, isMiningEnabled = true) + val utxPool = new UtxPoolImpl(time, bcu, settings, Int.MaxValue, isMiningEnabled = true) (sender, utxPool, txs) }).label("massTransferWithBlacklisted") @@ -304,7 +305,7 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact val time = TestTime() forAll(listOfN(count, transfer(sender, senderBalance / 2, time))) { txs => - val utx = new UtxPoolImpl(time, bcu, utxSettings, isMiningEnabled = true) + val utx = new UtxPoolImpl(time, bcu, utxSettings, Int.MaxValue, isMiningEnabled = true) f(txs, utx, time) } } @@ -332,6 +333,7 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact allowSkipChecks = false, forceValidateInCleanup = false ), + Int.MaxValue, isMiningEnabled = true ) (utx, time, tx1, tx2) @@ -372,6 +374,7 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact allowSkipChecks = false, forceValidateInCleanup = false ), + Int.MaxValue, isMiningEnabled = true ) @@ -442,8 +445,9 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact Set.empty, allowTransactionsFromSmartAccounts = true, allowSkipChecks = allowSkipChecks == 1, - forceValidateInCleanup = false) - val utx = new UtxPoolImpl(time, bcu, utxSettings, isMiningEnabled = true) + forceValidateInCleanup = false + ) + val utx = new UtxPoolImpl(time, bcu, utxSettings, Int.MaxValue, isMiningEnabled = true) utx.putIfNew(headTransaction).resultE should beRight utx.putIfNew(vipTransaction).resultE should matchPattern { @@ -487,8 +491,9 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact Set(sender2.toAddress.toString, sender3.toAddress.toString), allowTransactionsFromSmartAccounts = true, allowSkipChecks = allowSkipChecks, - forceValidateInCleanup = false) - val utx = new UtxPoolImpl(time, bcu, utxSettings, isMiningEnabled = true) + forceValidateInCleanup = false + ) + val utx = new UtxPoolImpl(time, bcu, utxSettings, Int.MaxValue, isMiningEnabled = true) utx.putIfNew(headTransaction).resultE.explicitGet() utx.putIfNew(vipTransaction).resultE.explicitGet() @@ -558,8 +563,9 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact Set(sender2.toAddress.toString, sender3.toAddress.toString), allowTransactionsFromSmartAccounts = true, allowSkipChecks = allowSkipChecks, - forceValidateInCleanup = false) - val utx = new UtxPoolImpl(time, bcu, utxSettings, isMiningEnabled = true) + forceValidateInCleanup = false + ) + val utx = new UtxPoolImpl(time, bcu, utxSettings, Int.MaxValue, isMiningEnabled = true) Random.shuffle(whitelistedTxs ++ txs).foreach(tx => utx.putIfNew(tx)) @@ -640,6 +646,7 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact allowSkipChecks = false, forceValidateInCleanup = false ), + Int.MaxValue, isMiningEnabled = true ) (scripted ++ unscripted).foreach(tx => utx.putIfNew(tx).resultE.explicitGet()) @@ -656,12 +663,11 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact } } - "processes transaction fees" in { - val blockMiner = TxHelpers.signer(1200) - val recipient = TxHelpers.signer(1201) + val blockMiner = TxHelpers.signer(1200) + val recipient = TxHelpers.signer(1201) val initialAmount = 10000.waves - val minerBalance = initialAmount + 0.001.waves * 2 + val minerBalance = initialAmount + 0.001.waves * 2 withDomain(DomainPresets.NG, balances = Seq(AddrWithBalance(blockMiner.toAddress, minerBalance))) { d => val transfer1 = TxHelpers.transfer(blockMiner, recipient.toAddress, version = 1.toByte, amount = initialAmount, fee = 0.001.waves) @@ -764,8 +770,9 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact Set.empty, allowTransactionsFromSmartAccounts = true, allowSkipChecks = false, - forceValidateInCleanup = false) - val utxPool = new UtxPoolImpl(time, bcu, settings, isMiningEnabled = true, nanoTimeSource = () => nanoTimeSource()) + forceValidateInCleanup = false + ) + val utxPool = new UtxPoolImpl(time, bcu, settings, Int.MaxValue, isMiningEnabled = true, nanoTimeSource = () => nanoTimeSource()) utxPool.putIfNew(transfer).resultE should beRight val (tx, _) = utxPool.packUnconfirmed(MultiDimensionalMiningConstraint.unlimited, PackStrategy.Limit(100 nanos)) @@ -785,7 +792,7 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact fastLaneAddresses = Set.empty, forceValidateInCleanup = false ) - val utxPool = new UtxPoolImpl(ntpTime, d.blockchainUpdater, settings, isMiningEnabled = true) + val utxPool = new UtxPoolImpl(ntpTime, d.blockchainUpdater, settings, Int.MaxValue, isMiningEnabled = true) val startTime = System.nanoTime() val (result, _) = utxPool.packUnconfirmed(MultiDimensionalMiningConstraint.unlimited, PackStrategy.Estimate(3 seconds)) result shouldBe None @@ -835,7 +842,13 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact d.appendBlock(setScripts*) val invoke = TxHelpers.invoke(genesisTxs.head.recipient, Some("default")) - val utx = new UtxPoolImpl(ntpTime, d.blockchainUpdater, DefaultWavesSettings.utxSettings, isMiningEnabled = true) + val utx = new UtxPoolImpl( + ntpTime, + d.blockchainUpdater, + DefaultWavesSettings.utxSettings, + DefaultWavesSettings.maxTxErrorLogSize, + isMiningEnabled = true + ) utx.putIfNew(invoke, forceValidate = true).resultE.explicitGet() shouldBe true utx.removeAll(Seq(invoke)) utx.putIfNew(invoke, forceValidate = false).resultE.explicitGet() shouldBe true @@ -855,7 +868,13 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact | } """.stripMargin ) - val utx = new UtxPoolImpl(ntpTime, d.blockchainUpdater, DefaultWavesSettings.utxSettings, isMiningEnabled = false) + val utx = new UtxPoolImpl( + ntpTime, + d.blockchainUpdater, + DefaultWavesSettings.utxSettings, + DefaultWavesSettings.maxTxErrorLogSize, + isMiningEnabled = false + ) d.appendBlock(setScript(secondSigner, dApp(5))) utx.putIfNew(invoke()).resultE shouldBe Right(true) @@ -888,7 +907,13 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact | } """.stripMargin ) - val utx = new UtxPoolImpl(ntpTime, d.blockchainUpdater, DefaultWavesSettings.utxSettings, isMiningEnabled = false) + val utx = new UtxPoolImpl( + ntpTime, + d.blockchainUpdater, + DefaultWavesSettings.utxSettings, + DefaultWavesSettings.maxTxErrorLogSize, + isMiningEnabled = false + ) d.appendBlock(setScript(signer(2), innerDApp)) d.appendBlock(setScript(secondSigner, dApp(5))) @@ -913,7 +938,7 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact val expr = TestCompiler(V6).compileFreeCall(""" [ BooleanEntry("check", true) ] """) val invoke = TxHelpers.invokeExpression(expr) - val utx = new UtxPoolImpl(ntpTime, d.blockchainUpdater, DefaultWavesSettings.utxSettings, true) + val utx = new UtxPoolImpl(ntpTime, d.blockchainUpdater, DefaultWavesSettings.utxSettings, DefaultWavesSettings.maxTxErrorLogSize, true) utx.putIfNew(invoke).resultE.explicitGet() shouldBe true utx.all shouldBe Seq(invoke) @@ -960,17 +985,22 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact (() => blockchain.activatedFeatures).when().returning(Map.empty) val utx = - new UtxPoolImpl(ntpTime, blockchain, WavesSettings.default().utxSettings, isMiningEnabled = true) + new UtxPoolImpl( + ntpTime, + blockchain, + WavesSettings.default().utxSettings, + WavesSettings.default().maxTxErrorLogSize, + isMiningEnabled = true + ) (blockchain.balance _).when(*, *).returning(ENOUGH_AMT).repeat((rest.length + 1) * 2) - (blockchain.balance _).when(*, *).returning(ENOUGH_AMT) (blockchain.leaseBalance _).when(*).returning(LeaseBalance(0, 0)) (blockchain.accountScript _).when(*).onCall { _: Address => - utx.removeAll(rest) - None - } + utx.removeAll(rest) + None + } val tb = TestBlock.create(Nil) (blockchain.blockHeader _).when(*).returning(Some(SignedBlockHeader(tb.header, tb.signature))) @@ -1019,7 +1049,14 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact val time = TestTime() val events = new ListBuffer[UtxEvent] val utxPool = - new UtxPoolImpl(time, d.blockchainUpdater, WavesSettings.default().utxSettings, isMiningEnabled = true, events += _) + new UtxPoolImpl( + time, + d.blockchainUpdater, + WavesSettings.default().utxSettings, + WavesSettings.default().maxTxErrorLogSize, + isMiningEnabled = true, + events += _ + ) def assertEvents(f: PartialFunction[Seq[UtxEvent], Unit]): Unit = { val currentEvents = events.toList @@ -1117,26 +1154,25 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact Seq( (simpleContract, Seq.empty), (selfInvokeContract, Seq(CONST_LONG(9))) - ).foreach { - case (contract, args) => - withDomain(settings, balances = AddrWithBalance.enoughBalances(dApp, invoker)) { d => - val setScript = TxHelpers.setScript(dApp, contract) - val invoke = TxHelpers.invoke(dApp.toAddress, func = Some("test"), args = args, invoker = invoker) + ).foreach { case (contract, args) => + withDomain(settings, balances = AddrWithBalance.enoughBalances(dApp, invoker)) { d => + val setScript = TxHelpers.setScript(dApp, contract) + val invoke = TxHelpers.invoke(dApp.toAddress, func = Some("test"), args = args, invoker = invoker) - d.appendBlock(setScript) - d.utxPool.addTransaction(invoke, verify = true) + d.appendBlock(setScript) + d.utxPool.addTransaction(invoke, verify = true) - d.utxPool.size shouldBe 1 + d.utxPool.size shouldBe 1 - d.utxPool.cleanUnconfirmed() + d.utxPool.cleanUnconfirmed() - val expectedResult = if (!isMiningEnabled && forceValidateInCleanup) { - None - } else { - Some(invoke) - } - d.utxPool.transactionById(invoke.id()) shouldBe expectedResult + val expectedResult = if (!isMiningEnabled && forceValidateInCleanup) { + None + } else { + Some(invoke) } + d.utxPool.transactionById(invoke.id()) shouldBe expectedResult + } } } } diff --git a/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/ReplEngine.scala b/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/ReplEngine.scala index 816aedc4852..f7ce2ba03b8 100644 --- a/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/ReplEngine.scala +++ b/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/ReplEngine.scala @@ -2,55 +2,58 @@ package com.wavesplatform.lang.v1.repl import cats.Monad import cats.data.EitherT -import cats.implicits._ +import cats.implicits.* import com.wavesplatform.lang.v1.compiler.CompilerContext.VariableInfo import com.wavesplatform.lang.v1.compiler.Terms.EVALUATED import com.wavesplatform.lang.v1.compiler.Types.{FINAL, UNIT} -import com.wavesplatform.lang.v1.compiler.{CompilerContext, ExpressionCompiler} +import com.wavesplatform.lang.v1.compiler.{CompilerContext, ExpressionCompiler, TermPrinter} import com.wavesplatform.lang.v1.evaluator.EvaluatorV1 import com.wavesplatform.lang.v1.evaluator.ctx.{EvaluationContext, FunctionTypeSignature, LazyVal} import com.wavesplatform.lang.v1.parser.Expressions.EXPR import com.wavesplatform.lang.v1.parser.Expressions.Pos.AnyPos import com.wavesplatform.lang.v1.parser.Parser -import com.wavesplatform.lang.v1.repl.Implicits._ +import com.wavesplatform.lang.v1.repl.Implicits.* import com.wavesplatform.lang.v1.traits.Environment -class ReplEngine[F[_] : Monad] { +class ReplEngine[F[_]: Monad] { val evaluator = new EvaluatorV1[F, Environment] def eval( - expr: String, - compileCtx: CompilerContext, - evalCtx: EvaluationContext[Environment, F] + expr: String, + compileCtx: CompilerContext, + evalCtx: EvaluationContext[Environment, F] ): F[Either[String, (String, (CompilerContext, EvaluationContext[Environment, F]))]] = { val r = for { parsed <- EitherT.fromEither[F](parse(expr)) (newCompileCtx, compiled, exprType) <- EitherT.fromEither[F](ExpressionCompiler.applyWithCtx(compileCtx, parsed)) - evaluated <- EitherT(evaluator.applyWithCtx(evalCtx, compiled)).leftMap(error => if (error.message.isEmpty) "Evaluation error" else error.message) + evaluated <- EitherT(evaluator.applyWithCtx(evalCtx, compiled)).leftMap(error => + if (error.message.isEmpty) "Evaluation error" else error.message + ) } yield resultWithCtx(evaluated, compileCtx, newCompileCtx, exprType) r.value } private def parse(expr: String): Either[String, EXPR] = - Parser.parseExprOrDecl(expr) + Parser + .parseExprOrDecl(expr) .fold( - { case _ => Left(s"Can't parse '$expr'") }, + { case _ => Left(s"Can't parse '$expr'") }, { case (result, _) => Right(result) } ) private def resultWithCtx( - evaluated: (EvaluationContext[Environment, F], EVALUATED), - compileCtx: CompilerContext, - newCompileCtx: CompilerContext, - exprType: FINAL + evaluated: (EvaluationContext[Environment, F], EVALUATED), + compileCtx: CompilerContext, + newCompileCtx: CompilerContext, + exprType: FINAL ) = { val (newEvalCtx, result) = evaluated - val filteredCompileCtx = excludeInternalDecls(newCompileCtx) - val resultO = assignedResult(exprType, result, filteredCompileCtx) - val output = mkOutput(resultO, compileCtx, filteredCompileCtx) - val newCtx = resultO.fold((filteredCompileCtx, newEvalCtx))(addResultToCtx(_, filteredCompileCtx, newEvalCtx)) + val filteredCompileCtx = excludeInternalDecls(newCompileCtx) + val resultO = assignedResult(exprType, result, filteredCompileCtx) + val output = mkOutput(resultO, compileCtx, filteredCompileCtx) + val newCtx = resultO.fold((filteredCompileCtx, newEvalCtx))(addResultToCtx(_, filteredCompileCtx, newEvalCtx)) (output, newCtx) } @@ -58,12 +61,12 @@ class ReplEngine[F[_] : Monad] { compileCtx.copy(varDefs = compileCtx.varDefs.filterNot(v => internalVarPrefixes.contains(v._1.head))) private val assignPrefix = "res" - private val assignedR = s"^$assignPrefix([1-9][0-9]*)".r + private val assignedR = s"^$assignPrefix([1-9][0-9]*)".r private def assignedResult( - exprType: FINAL, - value: EVALUATED, - compileCtx: CompilerContext + exprType: FINAL, + value: EVALUATED, + compileCtx: CompilerContext ): Option[(String, FINAL, EVALUATED)] = if (exprType == UNIT) None else { @@ -80,9 +83,9 @@ class ReplEngine[F[_] : Monad] { } private def mkOutput( - resultO: Option[(String, FINAL, EVALUATED)], - compileCtx: CompilerContext, - newCompileCtx: CompilerContext + resultO: Option[(String, FINAL, EVALUATED)], + compileCtx: CompilerContext, + newCompileCtx: CompilerContext ): String = { val (lets, funcs) = declsDiff(compileCtx, newCompileCtx) @@ -96,7 +99,7 @@ class ReplEngine[F[_] : Monad] { lets.toSeq.sortBy(_._1).map { case (name, t) => DeclPrinter.declaredLetStr(name, t) } val evalStr = - resultO.fold("") { case (name, t, result) => s"$name: $t = ${result.prettyString(0)}" } + resultO.fold("") { case (name, t, result) => s"$name: $t = ${TermPrinter().prettyString(result, 0)}" } val delim1 = if (mappedLets.nonEmpty && mappedFuncs.nonEmpty) "\n" else "" val delim2 = if ((mappedLets.nonEmpty || mappedFuncs.nonEmpty) && resultO.isDefined) "\n" else "" @@ -105,8 +108,8 @@ class ReplEngine[F[_] : Monad] { } private def declsDiff( - compileCtx: CompilerContext, - newCompileCtx: CompilerContext + compileCtx: CompilerContext, + newCompileCtx: CompilerContext ): (Set[(String, FINAL)], Set[(String, List[FunctionTypeSignature])]) = { val newLets = (newCompileCtx.varDefs.keySet diff compileCtx.varDefs.keySet) @@ -120,9 +123,9 @@ class ReplEngine[F[_] : Monad] { } private def addResultToCtx( - result: (String, FINAL, EVALUATED), - compileCtx: CompilerContext, - evalCtx: EvaluationContext[Environment, F] + result: (String, FINAL, EVALUATED), + compileCtx: CompilerContext, + evalCtx: EvaluationContext[Environment, F] ): (CompilerContext, EvaluationContext[Environment, F]) = { val (name, t, value) = result ( diff --git a/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/node/ErrorMessageEnvironment.scala b/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/node/ErrorMessageEnvironment.scala index cc6e3f54ef8..1ff8f0a0789 100644 --- a/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/node/ErrorMessageEnvironment.scala +++ b/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/node/ErrorMessageEnvironment.scala @@ -4,6 +4,7 @@ import com.wavesplatform.common.state.ByteStr import com.wavesplatform.lang.ValidationError import com.wavesplatform.lang.script.Script import com.wavesplatform.lang.v1.compiler.Terms.EVALUATED +import com.wavesplatform.lang.v1.evaluator.Log import com.wavesplatform.lang.v1.traits.Environment.InputEntity import com.wavesplatform.lang.v1.traits.domain.Recipient.Address import com.wavesplatform.lang.v1.traits.domain.{BlockInfo, Recipient, ScriptAssetInfo, Tx} @@ -11,20 +12,20 @@ import com.wavesplatform.lang.v1.traits.{DataType, Environment} import monix.eval.Coeval case class ErrorMessageEnvironment[F[_]](message: String) extends Environment[F] { - lazy val unavailable = throw BlockchainUnavailableException(message) - override def chainId: Byte = 0 - override def height: F[Long] = unavailable - override def inputEntity: InputEntity = unavailable - override def tthis: Environment.Tthis = unavailable - override def transactionById(id: Array[Byte]): F[Option[Tx]] = unavailable - override def transferTransactionById(id: Array[Byte]): F[Option[Tx.Transfer]] = unavailable - override def transactionHeightById(id: Array[Byte]): F[Option[Long]] = unavailable - override def assetInfoById(d: Array[Byte]): F[Option[ScriptAssetInfo]] = unavailable - override def lastBlockOpt(): F[Option[BlockInfo]] = unavailable - override def blockInfoByHeight(height: Int): F[Option[BlockInfo]] = unavailable - override def data(addressOrAlias: Recipient, key: String, dataType: DataType): F[Option[Any]] = unavailable - override def hasData(addressOrAlias: Recipient): F[Boolean] = unavailable - override def resolveAlias(name: String): F[Either[String, Recipient.Address]] = unavailable + lazy val unavailable = throw BlockchainUnavailableException(message) + override def chainId: Byte = 0 + override def height: F[Long] = unavailable + override def inputEntity: InputEntity = unavailable + override def tthis: Environment.Tthis = unavailable + override def transactionById(id: Array[Byte]): F[Option[Tx]] = unavailable + override def transferTransactionById(id: Array[Byte]): F[Option[Tx.Transfer]] = unavailable + override def transactionHeightById(id: Array[Byte]): F[Option[Long]] = unavailable + override def assetInfoById(d: Array[Byte]): F[Option[ScriptAssetInfo]] = unavailable + override def lastBlockOpt(): F[Option[BlockInfo]] = unavailable + override def blockInfoByHeight(height: Int): F[Option[BlockInfo]] = unavailable + override def data(addressOrAlias: Recipient, key: String, dataType: DataType): F[Option[Any]] = unavailable + override def hasData(addressOrAlias: Recipient): F[Boolean] = unavailable + override def resolveAlias(name: String): F[Either[String, Recipient.Address]] = unavailable override def accountBalanceOf(addressOrAlias: Recipient, assetId: Option[Array[Byte]]): F[Either[String, Long]] = unavailable override def accountWavesBalanceOf(addressOrAlias: Recipient): F[Either[String, Environment.BalanceDetails]] = unavailable override def multiPaymentAllowed: Boolean = unavailable @@ -38,8 +39,9 @@ case class ErrorMessageEnvironment[F[_]](message: String) extends Environment[F] func: String, args: List[EVALUATED], payments: Seq[(Option[Array[Byte]], Long)], - availableComplexity: Int - , reentrant: Boolean): Coeval[F[(Either[ValidationError, EVALUATED], Int)]] = unavailable + availableComplexity: Int, + reentrant: Boolean + ): Coeval[F[(Either[ValidationError, (EVALUATED, Log[F])], Int)]] = unavailable } case class BlockchainUnavailableException(message: String) extends RuntimeException { diff --git a/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/node/http/WebEnvironment.scala b/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/node/http/WebEnvironment.scala index 3b2352bedbb..702c7a1c596 100644 --- a/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/node/http/WebEnvironment.scala +++ b/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/node/http/WebEnvironment.scala @@ -7,6 +7,7 @@ import com.wavesplatform.common.utils.{Base58, EitherExt2} import com.wavesplatform.lang.ValidationError import com.wavesplatform.lang.script.Script import com.wavesplatform.lang.v1.compiler.Terms.EVALUATED +import com.wavesplatform.lang.v1.evaluator.Log import com.wavesplatform.lang.v1.repl.node.http.NodeClient.* import com.wavesplatform.lang.v1.repl.node.http.response.ImplicitMappings import com.wavesplatform.lang.v1.repl.node.http.response.model.* @@ -152,7 +153,7 @@ private[repl] case class WebEnvironment(settings: NodeConnectionSettings, client payments: Seq[(Option[Array[Byte]], Long)], availableComplexity: Int, reentrant: Boolean - ): Coeval[Future[(Either[ValidationError, EVALUATED], Int)]] = ??? + ): Coeval[Future[(Either[ValidationError, (EVALUATED, Log[Future])], Int)]] = ??? } object WebEnvironment {