Skip to content

Commit

Permalink
SC-560: Recalculate limits (#3032)
Browse files Browse the repository at this point in the history
Co-authored-by: Mikhail Potanin <[email protected]>
  • Loading branch information
Sergey Nazarov and Mikhail Potanin authored Apr 1, 2020
1 parent 825e480 commit c0e0a4e
Show file tree
Hide file tree
Showing 11 changed files with 167 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ object Testing {
case s: ByteStr => CONST_BYTESTR(s)
case s: CaseObj => Right(s)
case s: Boolean => Right(CONST_BOOLEAN(s))
case a: Seq[_] => Right(ARR(a.map(x => evaluated(x).explicitGet()).toIndexedSeq))
case a: Seq[_] => ARR(a.map(x => evaluated(x).explicitGet()).toIndexedSeq)
case _ => Left("Bad Assert: unexprected type")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,22 @@ object ContractLimits {
val MaxInvokeScriptSizeInBytes = 5 * 1024
val MaxWriteSetSizeInBytes = 5 * 1024
val MaxWriteSetSize = 100
val MaxKeySizeInBytes = 100
val MaxKeySizeInBytes = 100

// Mass Transfer 0.001 + 0.0005*N, rounded up to 0.001, fee for CI is 0.005
val MaxPaymentAmount = 10

// Data weight related constants
val OBJ_WEIGHT = 40l
val FIELD_WEIGHT = 30l
val EMPTYARR_WEIGHT = 20l
val ELEM_WEIGHT = 20l
val MaxWeight =
150l * 1024l * 2l // MaxBytes dublicate in bodyBytes and data
+ 32l + 8l + 8l + 8l // header
+ OBJ_WEIGHT + FIELD_WEIGHT + 32l // address object
+ EMPTYARR_WEIGHT + (ELEM_WEIGHT + 64l) * 8l // proofs
+ EMPTYARR_WEIGHT + (ELEM_WEIGHT + OBJ_WEIGHT + FIELD_WEIGHT * 2l) * 100l // Data entries

val MaxCmpWeight = 13000l
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import monix.eval.Coeval
import cats.implicits._
import com.wavesplatform.common.utils._
import com.wavesplatform.lang.ExecutionError
import com.wavesplatform.lang.v1.ContractLimits._

object Terms {
private val DATA_TX_BYTES_MAX = 150 * 1024 // should be the same as DataTransaction.MAX_BYTES
Expand Down Expand Up @@ -68,8 +69,13 @@ object Terms {
sealed trait EVALUATED extends EXPR {
def prettyString(level: Int) : String = toString
def toStr: Coeval[String] = Coeval.now(toString)
def weight: Long
}

case class CONST_LONG(t: Long) extends EVALUATED {
override def toString: String = t.toString
override val weight: Long = 8l
}
case class CONST_LONG(t: Long) extends EVALUATED { override def toString: String = t.toString }

case class CONST_BYTESTR private (bs: ByteStr) extends EVALUATED {
override def toString: String = bs.toString
Expand All @@ -80,7 +86,7 @@ object Terms {
"base58'" ++ Base58.encode(bs) ++ "'"
}
}

override val weight: Long = bs.size
}
object CONST_BYTESTR {
def apply(bs: ByteStr): Either[ExecutionError, CONST_BYTESTR] =
Expand Down Expand Up @@ -111,6 +117,7 @@ object Terms {
case class CONST_STRING private (s: String) extends EVALUATED {
override def toString: String = s
override def prettyString(level: Int) : String = "\"" ++ escape(s) ++ "\""
override val weight: Long = s.getBytes.size
}
object CONST_STRING {
def apply(s: String): Either[ExecutionError, CONST_STRING] =
Expand All @@ -121,18 +128,48 @@ object Terms {
)
}

case class CONST_BOOLEAN(b: Boolean) extends EVALUATED { override def toString: String = b.toString }
case class CONST_BOOLEAN(b: Boolean) extends EVALUATED {
override def toString: String = b.toString
override val weight: Long = 1l
}

lazy val TRUE = CONST_BOOLEAN(true)
lazy val FALSE = CONST_BOOLEAN(false)

case class CaseObj(caseType: CASETYPEREF, fields: Map[String, EVALUATED]) extends EVALUATED {
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)

override val weight: Long = OBJ_WEIGHT + FIELD_WEIGHT * fields.size + fields.map(_._2.weight).sum
}

case class ARR(xs: IndexedSeq[EVALUATED]) extends EVALUATED {
object CaseObj {
def apply(caseType: CASETYPEREF, fields: Map[String, EVALUATED]): CaseObj = {
val obj = new CaseObj(caseType, fields)
if (obj.weight > MaxWeight) {
throw new Exception(s"the object ${caseType.name} is too heavy. Actual weight: ${obj.weight}, limit: ${MaxWeight}")
} else {
obj
}
}
}

abstract case class ARR(xs: IndexedSeq[EVALUATED]) extends EVALUATED {
override def toString: String = TermPrinter.string(this)
}

object ARR {
def apply(xs: IndexedSeq[EVALUATED], mweight: Long): Either[ExecutionError, ARR] =
if (mweight > MaxWeight) {
Left(s"the list is too heavy. Actual weight: ${mweight}, limit: ${MaxWeight}")
} else {
Right(new ARR(xs) { override val weight: Long = mweight })
}

def apply(xs: IndexedSeq[EVALUATED]): Either[ExecutionError, ARR] = {
val weight = EMPTYARR_WEIGHT + ELEM_WEIGHT * xs.size + xs.map(_.weight).sum
ARR(xs, weight)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.wavesplatform.lang.v1.evaluator.ctx._
import com.wavesplatform.lang.v1.parser.BinaryOperation
import com.wavesplatform.lang.v1.parser.BinaryOperation._
import com.wavesplatform.lang.v1.{BaseGlobal, CTX}
import com.wavesplatform.lang.v1.ContractLimits._

import scala.annotation.tailrec
import scala.util.{Success, Try}
Expand Down Expand Up @@ -60,7 +61,7 @@ object PureContext {

lazy val eq: BaseFunction[NoContext] =
NativeFunction(EQ_OP.func, 1, EQ, BOOLEAN, ("a", TYPEPARAM('T')), ("b", TYPEPARAM('T'))) {
case a :: b :: Nil => Right(CONST_BOOLEAN(a == b))
case a :: b :: Nil => Either.cond(b.weight <= MaxCmpWeight || a.weight <= MaxCmpWeight, CONST_BOOLEAN(a == b), "Comparable value too heavy.")
case xs => notImplemented[Id](s"${EQ_OP.func}(a: T, b: T)", xs)
}

Expand Down Expand Up @@ -271,7 +272,7 @@ object PureContext {
("head", TYPEPARAM('A')),
("tail", PARAMETERIZEDLIST(TYPEPARAM('B')))
) {
case h :: ARR(t) :: Nil => Right(ARR(h +: t))
case h :: (a @ ARR(t)) :: Nil => ARR(h +: t, h.weight + a.weight + ELEM_WEIGHT)
case xs => notImplemented[Id]("cons(head: T, tail: LIST[T]", xs)
}

Expand Down Expand Up @@ -433,7 +434,7 @@ object PureContext {
case CONST_STRING(str) :: CONST_STRING(sep) :: Nil =>
split(str, sep)
.traverse(CONST_STRING(_))
.map(s => ARR(s.toIndexedSeq))
.flatMap(s => ARR(s.toIndexedSeq))
case xs => notImplemented[Id]("split(str: String, separator: String)", xs)
}

Expand Down Expand Up @@ -699,7 +700,7 @@ object PureContext {
ctx,
CTX[NoContext](
Seq.empty,
Map(("nil", (LIST(NOTHING), ContextfulVal.pure[NoContext](ARR(IndexedSeq.empty[EVALUATED]))))),
Map(("nil", (LIST(NOTHING), ContextfulVal.pure[NoContext](ARR(IndexedSeq.empty[EVALUATED]).explicitGet())))),
Array(
value,
valueOrErrorMessage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ package object converters {
implicit def c(b: String): EVALUATED = CONST_STRING(b).explicitGet()
implicit def c(b: Long): EVALUATED = CONST_LONG(b)
implicit def c(b: Boolean): EVALUATED = CONST_BOOLEAN(b)
implicit def c(is: IndexedSeq[EVALUATED]): EVALUATED = ARR(is)
implicit def c(is: Seq[EVALUATED]): EVALUATED = ARR(is.toIndexedSeq)
implicit def c(is: IndexedSeq[EVALUATED]): EVALUATED = ARR(is).explicitGet()
implicit def c(is: Seq[EVALUATED]): EVALUATED = ARR(is.toIndexedSeq).explicitGet()

implicit def fromOptionBV[T](v: Option[ByteStr]): EVALUATED = v.flatMap(CONST_BYTESTR(_).toOption).getOrElse(unit)
implicit def fromOptionL[T](v: Option[Long]): EVALUATED = v.map(CONST_LONG).getOrElse(unit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ object Bindings {
)

private def proofsPart(existingProofs: IndexedSeq[ByteStr]) =
"proofs" -> ARR((existingProofs ++ Seq.fill(8 - existingProofs.size)(ByteStr.empty)).map(b => CONST_BYTESTR(b).explicitGet()))
"proofs" -> ARR((existingProofs ++ IndexedSeq.fill(8 - existingProofs.size)(ByteStr.empty)).map(b => CONST_BYTESTR(b).explicitGet())).explicitGet()

private def provenTxPart(tx: Proven, proofsEnabled: Boolean): Map[String, EVALUATED] = {
val commonPart = combine(Map(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,94 @@ class EvaluatorV1Test extends PropSpec with PropertyChecks with Matchers with Sc
}
}

private def recCmp(cnt: Int)(f: ((String => String) => String) = (gen => gen("x") ++ gen("y") ++ s"x${cnt+1} == y${cnt+1}")): (Log[Id], Either[ExecutionError, Boolean]) = {
val context: CTX[NoContext] = Monoid.combineAll(
Seq(
pureContext,
defaultCryptoContext,
CTX[NoContext](Seq(), Map(), Array.empty[BaseFunction[NoContext]])
))

def gen(a: String) = (0 to cnt).foldLeft(s"""let ${a}0="qqqq";""") { (c, n) => c ++ s"""let $a${n+1}=[$a$n,$a$n,$a$n];""" }
val script = f(gen)

val r = noContextEvaluator.applyWithLogging[EVALUATED](
context.evaluationContext[Id],
ExpressionCompiler
.compile(script, context.compilerContext)
.explicitGet()
)
(r._1, r._2.map {
case CONST_BOOLEAN(b) => b
case _ => ???
})
}

property("recCmp") {
val (log, result) = recCmp(4)()

result shouldBe Right(true)

//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")))
}

property("recCmp fail by cmp") {
val (log, result) = recCmp(5)()

result shouldBe 'Left
}

property("recData fail by ARR") {
val cnt = 8
val (log, result) = recCmp(cnt)(gen => gen("x") ++ s"x${cnt+1}.size() == 3")

result shouldBe 'Left
}

property("recData use uncomparable data") {
val cnt = 7
val (log, result) = recCmp(cnt)(gen => gen("x") ++ s"x${cnt+1}[1].size() == 3")

result shouldBe Right(true)
}

private def genRCO(cnt: Int) = {
(0 to cnt).foldLeft[EXPR](CONST_STRING("qqqq").explicitGet()) { (acc, i) =>
val n = s"x$i"
val r = REF(n)
LET_BLOCK(LET(n, acc), FUNCTION_CALL(FunctionHeader.User("ScriptTransfer"), List(r, r, r)))
}
}

property("recursive caseobject") {
val environment = emptyBlockchainEnvironment()
val term = genRCO(3)

//println(term)

defaultEvaluator.apply[CONST_BOOLEAN](defaultFullContext.evaluationContext(environment), FUNCTION_CALL(FunctionHeader.Native(EQ), List(term, term))) shouldBe evaluated(true)
}

property("recursive caseobject fail by compare") {
val environment = emptyBlockchainEnvironment()
val term = genRCO(4)

//println(term)

defaultEvaluator.apply[CONST_BOOLEAN](defaultFullContext.evaluationContext(environment), FUNCTION_CALL(FunctionHeader.Native(EQ), List(term, term))) shouldBe 'Left
}

property("recursive caseobject compare with unit") {
val environment = emptyBlockchainEnvironment()
val term = genRCO(4)

//println(term)

defaultEvaluator.apply[CONST_BOOLEAN](defaultFullContext.evaluationContext(environment), FUNCTION_CALL(FunctionHeader.Native(EQ), List(term, REF("unit")))) shouldBe evaluated(false)
}

private def multiSig(bodyBytes: Array[Byte],
senderPK: PublicKey,
alicePK: PublicKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ class IntegrationTest extends PropSpec with PropertyChecks with ScriptGen with M
"value" -> CONST_STRING("2").explicitGet()
)
)
)))
)).explicitGet())
}

property("allow 'throw' in '==' arguments") {
Expand Down Expand Up @@ -762,7 +762,7 @@ class IntegrationTest extends PropSpec with PropertyChecks with ScriptGen with M
eval[EVALUATED](src) shouldBe Right(ARR(IndexedSeq(
CONST_STRING("q:we").explicitGet(),
CONST_STRING("q;we:x;q.we").explicitGet()
)))
)).explicitGet())
}

property("split separate correctly") {
Expand All @@ -773,7 +773,7 @@ class IntegrationTest extends PropSpec with PropertyChecks with ScriptGen with M
CONST_STRING("str2").explicitGet(),
CONST_STRING("str3").explicitGet(),
CONST_STRING("str4").explicitGet()
)))
)).explicitGet())
}

property("split separator at the end") {
Expand All @@ -783,7 +783,7 @@ class IntegrationTest extends PropSpec with PropertyChecks with ScriptGen with M
CONST_STRING("str1").explicitGet(),
CONST_STRING("str2").explicitGet(),
CONST_STRING("").explicitGet()
)))
)).explicitGet())
}

property("split double separator") {
Expand All @@ -794,7 +794,7 @@ class IntegrationTest extends PropSpec with PropertyChecks with ScriptGen with M
CONST_STRING("").explicitGet(),
CONST_STRING("str2").explicitGet(),
CONST_STRING("str3").explicitGet()
)))
)).explicitGet())
}

property("parseInt") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.wavesplatform.lang

import cats.kernel.Monoid
import com.wavesplatform.common.utils._
import com.wavesplatform.lang.Common.multiplierFunction
import com.wavesplatform.lang.directives.values._
import com.wavesplatform.lang.v1.CTX
Expand Down Expand Up @@ -31,7 +32,8 @@ package object compiler {
("p1", TYPEPARAM('T')),
("p2", TYPEPARAM('T'))) { case l => Right(l.head) }

private val arr = ARR(IndexedSeq[EVALUATED](null, null))
private val arr = ARR(IndexedSeq[EVALUATED](Common.pointAInstance, Common.pointAInstance)).explicitGet()

val testContext = Monoid
.combine(
PureContext.build(Global, V3),
Expand All @@ -40,7 +42,7 @@ package object compiler {
Map(
("p", (Common.AorB, null)),
("tv", (Common.AorBorC, null)),
("l", (LIST(LONG), ContextfulVal.pure[NoContext](ARR(IndexedSeq(CONST_LONG(1L), CONST_LONG(2L)))))),
("l", (LIST(LONG), ContextfulVal.pure[NoContext](ARR(IndexedSeq(CONST_LONG(1L), CONST_LONG(2L))).explicitGet()))),
("lpa", (LIST(Common.pointTypeA), ContextfulVal.pure[NoContext](arr))),
("lpabc", (LIST(Common.AorBorC), ContextfulVal.pure[NoContext](arr)))
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.wavesplatform.lang.v1.evaluator

import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.EitherExt2
import com.wavesplatform.common.utils._
import com.wavesplatform.common.state.diffs.ProduceError._
import com.wavesplatform.lang.Common.NoShrink
import com.wavesplatform.lang.v1.compiler.Terms._
Expand All @@ -28,7 +28,7 @@ class ScriptResultTest extends PropSpec with PropertyChecks with Matchers with N
"key" -> CONST_STRING("xxx").explicitGet(),
"value" -> CONST_LONG(42)
)
))))
))).explicitGet())
)

val transferSetObj = CaseObj(
Expand All @@ -51,7 +51,7 @@ class ScriptResultTest extends PropSpec with PropertyChecks with Matchers with N
"asset" -> noAsset
)
)
)))
)).explicitGet())
)

val scriptResultObj = CaseObj(CASETYPEREF("ScriptResult", el), Map(FieldNames.ScriptWriteSet -> writeSetObj, FieldNames.ScriptTransferSet -> transferSetObj))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ class InvokeScriptTransactionSpecification extends PropSpec with PropertyChecks
Some(
Terms.FUNCTION_CALL(
FunctionHeader.User("foo"),
List(ARR(IndexedSeq(CONST_LONG(1L), CONST_LONG(2L))))
List(ARR(IndexedSeq(CONST_LONG(1L), CONST_LONG(2L))).explicitGet())
)
),
Seq(),
Expand Down

0 comments on commit c0e0a4e

Please sign in to comment.