All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wavesplatform.TransactionGen.scala Maven / Gradle / Ivy

package com.wavesplatform

import com.wavesplatform.account.*
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.EitherExt2
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.lang.directives.values.V3
import com.wavesplatform.lang.script.v1.ExprScript
import com.wavesplatform.lang.script.{ContractScript, Script}
import com.wavesplatform.lang.v1.compiler.Terms.*
import com.wavesplatform.lang.v1.testing.{ScriptGen, TypedScriptGen}
import com.wavesplatform.lang.v1.{ContractLimits, FunctionHeader}
import com.wavesplatform.settings.Constants
import com.wavesplatform.state.*
import com.wavesplatform.transaction.*
import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves}
import com.wavesplatform.transaction.assets.*
import com.wavesplatform.transaction.assets.exchange.*
import com.wavesplatform.transaction.lease.*
import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment
import com.wavesplatform.transaction.smart.{InvokeExpressionTransaction, InvokeScriptTransaction, SetScriptTransaction}
import com.wavesplatform.transaction.transfer.*
import com.wavesplatform.transaction.utils.EthConverters.*
import com.wavesplatform.transaction.utils.Signed
import org.scalacheck.Gen.{alphaLowerChar, alphaUpperChar, frequency, numChar}
import org.scalacheck.{Arbitrary, Gen}
import org.scalatest.Suite
import org.web3j.crypto.ECKeyPair

import scala.concurrent.duration.*
import scala.util.Random

trait TransactionGenBase extends ScriptGen with TypedScriptGen with NTPTime { _: Suite =>

  val ScriptExtraFee                  = 400000L
  protected def waves(n: Float): Long = (n * 100000000L).toLong

  def byteArrayGen(length: Int): Gen[Array[Byte]] = Gen.containerOfN[Array, Byte](length, Arbitrary.arbitrary[Byte])

  val bytes32gen: Gen[Array[Byte]] = byteArrayGen(32)
  val bytes64gen: Gen[Array[Byte]] = byteArrayGen(64)
  val attachmentGen: Gen[ByteStr]  = bytes32gen.map(ByteStr(_))

  def genBoundedBytes(minSize: Int, maxSize: Int): Gen[Array[Byte]] =
    for {
      length <- Gen.chooseNum(minSize, maxSize)
      bytes  <- byteArrayGen(length)
    } yield bytes

  def genBoundedString(minSize: Int, maxSize: Int): Gen[String] = {
    genBoundedStringBytes(minSize, maxSize).map(new String(_))
  }

  def genBoundedStringBytes(minSize: Int, maxSize: Int): Gen[Array[Byte]] = {
    Gen.choose(minSize, maxSize) flatMap { sz =>
      Gen.listOfN(sz, Gen.choose(0, 0x7f).map(_.toByte)).map(_.toArray)
    }
  }

  val accountGen: Gen[KeyPair] = bytes32gen.map(seed => KeyPair(seed))

  val ethAccountGen: Gen[ECKeyPair] = accountGen.map(_.toEthKeyPair)

  val aliasSymbolChar: Gen[Char] = Gen.oneOf('.', '@', '_', '-')

  val invalidAliasSymbolChar: Gen[Char] = Gen.oneOf('~', '`', '!', '#', '$', '%', '^', '&', '*', '=', '+')

  val aliasAlphabetGen: Gen[Char] = frequency((1, numChar), (1, aliasSymbolChar), (9, alphaLowerChar))

  val invalidAliasAlphabetGen: Gen[Char] = frequency((1, numChar), (3, invalidAliasSymbolChar), (9, alphaUpperChar))

  val validAliasStringGen: Gen[String] = for {
    length     <- Gen.chooseNum(Alias.MinLength, Alias.MaxLength)
    aliasChars <- Gen.listOfN(length, aliasAlphabetGen)
  } yield aliasChars.mkString

  val aliasGen: Gen[Alias] = for {
    str <- validAliasStringGen
  } yield Alias.create(str.mkString).explicitGet()

  val invalidAliasStringGen: Gen[String] = for {
    length     <- Gen.chooseNum(Alias.MinLength, Alias.MaxLength)
    aliasChars <- Gen.listOfN(length, invalidAliasAlphabetGen)
  } yield aliasChars.mkString

  val accountOrAliasGen: Gen[AddressOrAlias] = Gen.oneOf(aliasGen, accountGen.map(_.toAddress))

  val positiveLongGen: Gen[Long] = Gen.choose(1, 100000000L * 100000000L / 100)
  val positiveIntGen: Gen[Int]   = Gen.choose(1, Int.MaxValue / 100)
  val smallFeeGen: Gen[Long]     = Gen.choose(400000L, 100000000L)

  val maxOrderTimeGen: Gen[Long] = Gen.choose(10000L, Order.MaxLiveTime).map(_ + ntpTime.correctedTime())
  val timestampGen: Gen[Long]    = Gen.choose(1L, Long.MaxValue - 100)
  val ntpTimestampGen: Gen[Long] = Gen.choose(1, 1000).map(ntpTime.correctedTime() - _)

  def validTimestampGen(blockTimestamp: Long, back: FiniteDuration = 120.minutes, forward: FiniteDuration = 90.minutes): Gen[Long] =
    Gen.choose(blockTimestamp - back.toMillis, blockTimestamp + forward.toMillis)

  val wavesAssetGen: Gen[Option[ByteStr]] = Gen.const(None)
  val assetIdGen: Gen[Option[ByteStr]]    = Gen.frequency((1, wavesAssetGen), (10, Gen.option(bytes32gen.map(ByteStr(_)))))

  val assetPairGen: Gen[AssetPair] = assetIdGen.flatMap {
    case None => bytes32gen.map(b => AssetPair(Waves, IssuedAsset(ByteStr(b))))
    case Some(a1bytes) =>
      val a2bytesGen: Gen[Option[Array[Byte]]] = byteArrayGen(31).map(a2bytes => Option(a1bytes.arr(0) +: a2bytes))

      Gen.oneOf(Gen.const(None), a2bytesGen).map { a2 =>
        val asset1 = IssuedAsset(a1bytes)
        val asset2 = a2.fold[Asset](Waves)(arr => IssuedAsset(ByteStr(arr)))
        AssetPair(asset1, asset2)
      }
  }

  val MinIssueFee = 100000000L

  val issueParamGen: Gen[(KeyPair, Array[Byte], Array[Byte], Long, Byte, Boolean, Long, TxTimestamp)] = for {
    sender      <- accountGen
    assetName   <- genBoundedStringBytes(IssueTransaction.MinAssetNameLength, IssueTransaction.MaxAssetNameLength)
    description <- genBoundedStringBytes(0, IssueTransaction.MaxAssetDescriptionLength)
    quantity    <- Gen.choose(Long.MaxValue / 200, Long.MaxValue / 100)
    decimals    <- Gen.choose(0: Byte, 8: Byte)
    reissuable  <- Arbitrary.arbitrary[Boolean]
    fee         <- Gen.choose(MinIssueFee, 2 * MinIssueFee)
    timestamp = System.currentTimeMillis()
  } yield (sender, assetName, description, quantity, decimals, reissuable, fee, timestamp)

  val proofsGen: Gen[Proofs] = for {
    proofsAmount <- Gen.choose(1, 8)
    proofs       <- Gen.listOfN(proofsAmount, genBoundedBytes(0, 50))
  } yield Proofs.create(proofs.map(ByteStr(_))).explicitGet()

  val scriptGen: Gen[Script]         = exprGen.map(e => ExprScript(e).explicitGet())
  val contractScriptGen: Gen[Script] = contractGen.map(e => ContractScript(V3, e).explicitGet())
  val contractOrExpr: Gen[Script]    = Gen.oneOf(scriptGen, contractScriptGen)

  def issueAndSetAssetScriptGen(sender: KeyPair): Gen[(IssueTransaction, SetAssetScriptTransaction)] =
    for {
      (_, assetName, description, quantity, decimals, _, iFee, _) <- issueParamGen
      timestamp                                                   <- timestampGen
      script1                                                     <- scriptGen
      script2                                                     <- scriptGen
      issue = IssueTransaction
        .selfSigned(
          TxVersion.V2,
          sender,
          new String(assetName),
          new String(description),
          quantity,
          decimals,
          reissuable = true,
          Some(script1),
          iFee,
          timestamp
        )
        .explicitGet()
      setAssetScript = SetAssetScriptTransaction
        .selfSigned(1.toByte, sender, IssuedAsset(issue.id()), Some(script2), 1 * Constants.UnitsInWave + ScriptExtraFee, timestamp)
        .explicitGet()
    } yield (issue, setAssetScript)

  val setAssetScriptTransactionGen: Gen[(Seq[Transaction], SetAssetScriptTransaction)] =
    for {
      sender                  <- accountGen
      (issue, setAssetScript) <- issueAndSetAssetScriptGen(sender)
    } yield (Seq(issue), setAssetScript)

  val setScriptTransactionGen: Gen[SetScriptTransaction] = for {
    sender    <- accountGen
    fee       <- smallFeeGen
    timestamp <- timestampGen
    script    <- Gen.option(contractOrExpr)
  } yield SetScriptTransaction.selfSigned(1.toByte, sender, script, fee, timestamp).explicitGet()

  private val leaseParamGen = for {
    sender    <- accountGen
    amount    <- positiveLongGen
    fee       <- smallFeeGen
    timestamp <- timestampGen
    recipient <- accountGen
  } yield (sender, amount, fee, timestamp, recipient)

  def createLease(sender: KeyPair, amount: Long, fee: Long, timestamp: Long, recipient: AddressOrAlias): Gen[LeaseTransaction] = {
    val v1 = LeaseTransaction.selfSigned(1.toByte, sender, recipient, amount, fee, timestamp).explicitGet()
    val v2 = LeaseTransaction.selfSigned(2.toByte, sender, recipient, amount, fee, timestamp).explicitGet()
    Gen.oneOf(v1, v2)
  }

  def createLeaseCancel(sender: KeyPair, leaseId: ByteStr, cancelFee: Long, timestamp: Long): Gen[LeaseCancelTransaction] = {
    val v1 = LeaseCancelTransaction.selfSigned(1.toByte, sender, leaseId, cancelFee, timestamp + 1).explicitGet()
    val v2 = LeaseCancelTransaction.selfSigned(2.toByte, sender, leaseId, cancelFee, timestamp + 1).explicitGet()
    Gen.oneOf(v1, v2)
  }
  val leaseAndCancelGen: Gen[(LeaseTransaction, LeaseCancelTransaction)] = for {
    (sender, amount, fee, timestamp, recipient) <- leaseParamGen
    lease                                       <- createLease(sender, amount, fee, timestamp, recipient.toAddress)
    cancelFee                                   <- smallFeeGen
    leaseCancel                                 <- createLeaseCancel(sender, lease.id(), cancelFee, timestamp + 1)
  } yield (lease, leaseCancel)

  def leaseAndCancelGeneratorP(leaseSender: KeyPair, recipient: AddressOrAlias, timestamp: Long): Gen[(LeaseTransaction, LeaseCancelTransaction)] =
    for {
      (_, amount, fee, _, _) <- leaseParamGen
      lease                  <- createLease(leaseSender, amount, fee, timestamp, recipient)
      fee2                   <- smallFeeGen
      unlease                <- createLeaseCancel(leaseSender, lease.id(), fee2, timestamp + 1)
    } yield (lease, unlease)

  val leaseGen: Gen[LeaseTransaction]             = leaseAndCancelGen.map(_._1)
  val leaseCancelGen: Gen[LeaseCancelTransaction] = leaseAndCancelGen.map(_._2)

  val transferParamGen: Gen[(Asset, KeyPair, AddressOrAlias, Long, TxTimestamp, Asset, TxTimestamp, ByteStr)] = for {
    amount     <- positiveLongGen
    feeAmount  <- smallFeeGen
    assetId    <- Gen.option(bytes32gen)
    feeAssetId <- Gen.option(bytes32gen)
    timestamp  <- timestampGen
    sender     <- accountGen
    attachment <- genBoundedBytes(0, TransferTransaction.MaxAttachmentSize)
    recipient  <- accountOrAliasGen
  } yield (
    Asset.fromCompatId(assetId.map(ByteStr(_))),
    sender,
    recipient,
    amount,
    timestamp,
    Asset.fromCompatId(feeAssetId.map(ByteStr(_))),
    feeAmount,
    ByteStr(attachment)
  )

  def transferGeneratorP(sender: KeyPair, recipient: AddressOrAlias, assetId: Asset, feeAssetId: Asset): Gen[TransferTransaction] =
    for {
      (_, _, _, amount, timestamp, _, feeAmount, attachment) <- transferParamGen
    } yield TransferTransaction.selfSigned(1.toByte, sender, recipient, assetId, amount, feeAssetId, feeAmount, attachment, timestamp).explicitGet()

  def versionedTransferGeneratorP(sender: KeyPair, recipient: AddressOrAlias, assetId: Asset, feeAssetId: Asset): Gen[TransferTransaction] =
    for {
      (_, _, _, amount, timestamp, _, feeAmount, attachment) <- transferParamGen
    } yield TransferTransaction.selfSigned(2.toByte, sender, recipient, assetId, amount, feeAssetId, feeAmount, attachment, timestamp).explicitGet()

  def transferGeneratorP(timestamp: Long, sender: KeyPair, recipient: AddressOrAlias, maxAmount: Long): Gen[TransferTransaction] =
    for {
      amount                                    <- Gen.choose(1, maxAmount)
      (_, _, _, _, _, _, feeAmount, attachment) <- transferParamGen
    } yield TransferTransaction.selfSigned(1.toByte, sender, recipient, Waves, amount, Waves, feeAmount, attachment, timestamp).explicitGet()

  def transferGeneratorP(timestamp: Long, sender: KeyPair, recipient: AddressOrAlias, assetId: Asset, feeAssetId: Asset): Gen[TransferTransaction] =
    for {
      (_, _, _, amount, _, _, feeAmount, attachment) <- transferParamGen
    } yield TransferTransaction.selfSigned(1.toByte, sender, recipient, assetId, amount, feeAssetId, feeAmount, attachment, timestamp).explicitGet()

  def wavesTransferGeneratorP(sender: KeyPair, recipient: AddressOrAlias): Gen[TransferTransaction] =
    transferGeneratorP(sender, recipient, Waves, Waves)

  def wavesTransferGeneratorP(timestamp: Long, sender: KeyPair, recipient: AddressOrAlias): Gen[TransferTransaction] =
    transferGeneratorP(timestamp, sender, recipient, Waves, Waves)

  def createWavesTransfer(
      sender: KeyPair,
      recipient: Address,
      amount: Long,
      fee: Long,
      timestamp: Long
  ): Either[ValidationError, TransferTransaction] =
    TransferTransaction.selfSigned(1.toByte, sender, recipient, Waves, amount, Waves, fee, ByteStr.empty, timestamp)

  val transferV1Gen: Gen[TransferTransaction] = (for {
    (assetId, sender, recipient, amount, timestamp, feeAssetId, feeAmount, attachment) <- transferParamGen
  } yield TransferTransaction.selfSigned(1.toByte, sender, recipient, assetId, amount, feeAssetId, feeAmount, attachment, timestamp).explicitGet())
    .label("transferTransaction")

  val transferV2Gen: Gen[TransferTransaction] = (for {
    (assetId, sender, recipient, amount, timestamp, feeAssetId, feeAmount, attachment) <- transferParamGen
    proofs                                                                             <- proofsGen
  } yield TransferTransaction
    .selfSigned(2.toByte, sender, recipient, assetId, amount, feeAssetId, feeAmount, attachment, timestamp)
    .explicitGet(
    ))
    .label("VersionedTransferTransaction")

  val createAliasGen: Gen[CreateAliasTransaction] =
    for {
      timestamp: Long <- positiveLongGen
      sender          <- accountGen
      alias: Alias    <- aliasGen
      tx <- Gen.oneOf(
        CreateAliasTransaction.selfSigned(Transaction.V1, sender, alias.name, MinIssueFee, timestamp).explicitGet(),
        CreateAliasTransaction.selfSigned(Transaction.V2, sender, alias.name, MinIssueFee, timestamp).explicitGet()
      )
    } yield tx

  def createAliasGen(sender: KeyPair, alias: Alias, fee: Long, timestamp: Long): Gen[CreateAliasTransaction] =
    for {
      tx <- Gen.oneOf(
        CreateAliasTransaction.selfSigned(Transaction.V1, sender, alias.name, fee, timestamp).explicitGet(),
        CreateAliasTransaction.selfSigned(Transaction.V2, sender, alias.name, fee, timestamp).explicitGet()
      )
    } yield tx

  val issueReissueBurnGen: Gen[(IssueTransaction, ReissueTransaction, BurnTransaction)] = for {
    amount <- positiveLongGen
    sender <- accountGen
    r      <- issueReissueBurnGeneratorP(amount, amount, amount, sender)
  } yield r

  def createLegacyIssue(
      issuer: KeyPair,
      nameBytes: Array[Byte],
      descriptionBytes: Array[Byte],
      quantity: Long,
      decimals: Byte,
      reissuable: Boolean,
      fee: Long,
      timestamp: Long
  ): Gen[IssueTransaction] =
    IssueTransaction
      .selfSigned(TxVersion.V1, issuer, new String(nameBytes), new String(descriptionBytes), quantity, decimals, reissuable, None, fee, timestamp)
      .explicitGet()

  def createReissue(
      reissuer: KeyPair,
      assetId: IssuedAsset,
      quantity: Long,
      reissuable: Boolean,
      fee: Long,
      timestamp: Long
  ): Gen[ReissueTransaction] = {
    for {
      tx <- Gen.oneOf(
        ReissueTransaction.selfSigned(1.toByte, reissuer, assetId, quantity, reissuable, fee, timestamp).explicitGet(),
        ReissueTransaction.selfSigned(2.toByte, reissuer, assetId, quantity, reissuable, fee, timestamp).explicitGet()
      )
    } yield tx
  }

  def createBurn(burner: KeyPair, assetId: IssuedAsset, amount: Long, fee: Long, timestamp: Long): Gen[BurnTransaction] = {
    for {
      tx <- Gen.oneOf(
        BurnTransaction.selfSigned(1.toByte, burner, assetId, amount, fee, timestamp).explicitGet(),
        BurnTransaction.selfSigned(2.toByte, burner, assetId, amount, fee, timestamp).explicitGet()
      )
    } yield tx
  }

  /** @param issueQuantity
    *   must be positive
    * @param reissueQuantity
    *   must be positive
    * @param burnQuantity
    *   must be positive
    */
  def issueReissueBurnGeneratorP(
      issueQuantity: Long,
      reissueQuantity: Long,
      burnQuantity: Long,
      sender: KeyPair
  ): Gen[(IssueTransaction, ReissueTransaction, BurnTransaction)] = {
    require(issueQuantity > 0 && reissueQuantity > 0 && burnQuantity > 0, "Asset amounts must be positive")
    for {
      (_, assetName, description, _, decimals, reissuable, iFee, timestamp) <- issueParamGen
      reissuable2                                                           <- Arbitrary.arbitrary[Boolean]
      fee                                                                   <- smallFeeGen
      issue   <- createLegacyIssue(sender, assetName, description, issueQuantity, decimals, reissuable, iFee, timestamp)
      reissue <- createReissue(sender, issue.asset, reissueQuantity, reissuable2, fee, timestamp)
      burn    <- createBurn(sender, issue.asset, burnQuantity, fee, timestamp)
    } yield (issue, reissue, burn)
  }

  val issueGen: Gen[IssueTransaction]     = issueReissueBurnGen.map(_._1)
  val reissueGen: Gen[ReissueTransaction] = issueReissueBurnGen.map(_._2)
  val burnGen: Gen[BurnTransaction]       = issueReissueBurnGen.map(_._3)

  def sponsorFeeCancelSponsorFeeGen(
      sender: KeyPair,
      reducedFee: Boolean = true
  ): Gen[(IssueTransaction, SponsorFeeTransaction, SponsorFeeTransaction, SponsorFeeTransaction)] =
    for {
      (_, assetName, description, quantity, decimals, reissuable, iFee, timestamp) <- issueParamGen
      issue = IssueTransaction
        .selfSigned(
          TxVersion.V1,
          sender,
          new String(assetName),
          new String(description),
          quantity,
          decimals,
          reissuable = reissuable,
          script = None,
          iFee,
          timestamp
        )
        .explicitGet()
      minFee  <- smallFeeGen
      minFee1 <- smallFeeGen
      assetId = issue.asset
      fee     = (if (reducedFee) 0.001 * Constants.UnitsInWave else 1 * Constants.UnitsInWave.toDouble).toLong
    } yield (
      issue,
      SponsorFeeTransaction.selfSigned(1.toByte, sender, assetId, Some(minFee), fee, timestamp).explicitGet(),
      SponsorFeeTransaction.selfSigned(1.toByte, sender, assetId, Some(minFee1), fee, timestamp).explicitGet(),
      SponsorFeeTransaction.selfSigned(1.toByte, sender, assetId, None, fee, timestamp).explicitGet()
    )

  val sponsorFeeGen: Gen[SponsorFeeTransaction] = for {
    sender        <- accountGen
    (_, tx, _, _) <- sponsorFeeCancelSponsorFeeGen(sender)
  } yield {
    tx
  }

  val argGen: Gen[EXPR] = Gen.const(CONST_LONG(11))

  val funcCallGen: Gen[FUNCTION_CALL] = for {
    functionName <- genBoundedString(1, 32).map(_.filter(_.isLetter))
    amt          <- Gen.choose(0, ContractLimits.MaxInvokeScriptArgs)
    args         <- Gen.listOfN(amt, argGen)

  } yield FUNCTION_CALL(FunctionHeader.User(functionName), args)

  def invokeScriptGen(paymentsGen: Gen[Seq[Payment]]): Gen[InvokeScriptTransaction] =
    for {
      payments    <- paymentsGen
      sender      <- accountGen
      dappAddress <- accountGen
      fc          <- funcCallGen
      fee         <- smallFeeGen
      timestamp   <- timestampGen
    } yield Signed.invokeScript(1.toByte, sender, dappAddress.toAddress, Some(fc), payments, fee, Waves, timestamp)

  val paymentListGen: Gen[Seq[Payment]] =
    for {
      wavesPayment <- Gen.option(positiveLongGen.map(Payment(_, Waves)))
      assetPayment = for {
        asset <- bytes32gen.map(ByteStr(_)).map(IssuedAsset(_))
        amt   <- positiveLongGen
      } yield Payment(amt, asset)
      assetPayments <- Gen.listOfN[Payment](ContractLimits.MaxAttachedPaymentAmount - 1, assetPayment)
    } yield assetPayments ++ wavesPayment

  val priceGen: Gen[Long]            = Gen.choose(1, 3 * 100000L * 100000000L)
  val matcherAmountGen: Gen[Long]    = Gen.choose(1, 3 * 100000L * 100000000L)
  val matcherFeeAmountGen: Gen[Long] = Gen.choose(1, 3 * 100000L * 100000000L)

  val orderTypeGen: Gen[OrderType] = Gen.oneOf(OrderType.BUY, OrderType.SELL)

  val orderParamGen: Gen[(KeyPair, KeyPair, AssetPair, OrderType, TxTimestamp, TxTimestamp, TxTimestamp, TxTimestamp, TxTimestamp)] = for {
    sender     <- accountGen
    matcher    <- accountGen
    pair       <- assetPairGen
    orderType  <- orderTypeGen
    amount     <- matcherAmountGen
    price      <- priceGen
    timestamp  <- timestampGen
    expiration <- maxOrderTimeGen
    matcherFee <- matcherFeeAmountGen
  } yield (sender, matcher, pair, orderType, amount, price, timestamp, expiration, matcherFee)

  val orderV1Gen: Gen[Order] = for {
    (sender, matcher, pair, orderType, price, amount, timestamp, expiration, matcherFee) <- orderParamGen
  } yield Order.selfSigned(1.toByte, sender, matcher.publicKey, pair, orderType, price, amount, timestamp, expiration, matcherFee).explicitGet()

  val orderV2Gen: Gen[Order] = for {
    (sender, matcher, pair, orderType, amount, price, timestamp, expiration, matcherFee) <- orderParamGen
  } yield Order.selfSigned(2.toByte, sender, matcher.publicKey, pair, orderType, amount, price, timestamp, expiration, matcherFee).explicitGet()

  val orderV3Gen: Gen[Order] = for {
    (sender, matcher, pair, orderType, price, amount, timestamp, expiration, matcherFee) <- orderParamGen
    matcherFeeAssetId                                                                    <- assetIdGen
  } yield Order
    .selfSigned(
      3.toByte,
      sender,
      matcher.publicKey,
      pair,
      orderType,
      amount,
      price,
      timestamp,
      expiration,
      matcherFee,
      Asset.fromCompatId(matcherFeeAssetId)
    )
    .explicitGet()

  val orderGen: Gen[Order] = Gen.oneOf(orderV1Gen, orderV2Gen, orderV3Gen)

  val arbitraryOrderGen: Gen[Order] = for {
    (sender, matcher, pair, orderType, _, _, _, _, _) <- orderParamGen
    amount                                            <- Arbitrary.arbitrary[Long]
    price                                             <- Arbitrary.arbitrary[Long]
    timestamp                                         <- Arbitrary.arbitrary[Long]
    expiration                                        <- Arbitrary.arbitrary[Long]
    matcherFee                                        <- Arbitrary.arbitrary[Long]
  } yield Order.selfSigned(1: Byte, sender, matcher.publicKey, pair, orderType, amount, price, timestamp, expiration, matcherFee).explicitGet()

  val exchangeTransactionGen: Gen[ExchangeTransaction] = for {
    sender1                 <- accountGen
    sender2                 <- accountGen
    assetPair               <- assetPairGen
    buyerAnotherAsset       <- assetIdGen.map(Asset.fromCompatId)
    sellerAnotherAsset      <- assetIdGen.map(Asset.fromCompatId)
    buyerMatcherFeeAssetId  <- Gen.oneOf(assetPair.amountAsset, assetPair.priceAsset, buyerAnotherAsset, Waves)
    sellerMatcherFeeAssetId <- Gen.oneOf(assetPair.amountAsset, assetPair.priceAsset, sellerAnotherAsset, Waves)
    r <- Gen.oneOf(
      exchangeV1GeneratorP(sender1, sender2, assetPair.amountAsset, assetPair.priceAsset),
      exchangeV2GeneratorP(
        buyer = sender1,
        seller = sender2,
        amountAssetId = assetPair.amountAsset,
        priceAssetId = assetPair.priceAsset,
        buyMatcherFeeAssetId = buyerMatcherFeeAssetId,
        sellMatcherFeeAssetId = sellerMatcherFeeAssetId
      )
    )
  } yield r

  def exchangeV1GeneratorP(
      buyer: KeyPair,
      seller: KeyPair,
      amountAssetId: Asset,
      priceAssetId: Asset,
      fixedMatcherFee: Option[Long] = None,
      chainId: Byte = AddressScheme.current.chainId,
      fixedMatcher: Option[KeyPair] = None
  ): Gen[ExchangeTransaction] =
    for {
      (_, genMatcher, _, _, amount1, price, timestamp, expiration, genMatcherFee) <- orderParamGen
      amount2: Long                                                               <- matcherAmountGen
      matchedAmount: Long <- Gen.choose(Math.min(amount1, amount2) / 2000, Math.min(amount1, amount2) / 1000)
      assetPair = AssetPair(amountAssetId, priceAssetId)
    } yield {
      val matcherFee = fixedMatcherFee.getOrElse(genMatcherFee)
      val matcher    = fixedMatcher.getOrElse(genMatcher)
      val o1 =
        Order
          .buy(1: Byte, buyer, matcher.publicKey, assetPair, amount1, price, timestamp, expiration, matcherFee, priceMode = OrderPriceMode.Default)
          .explicitGet()
      val o2 = Order
        .sell(
          1: Byte,
          seller,
          matcher.publicKey,
          assetPair,
          amount2,
          price,
          timestamp,
          expiration,
          matcherFee,
          priceMode = OrderPriceMode.Default
        )
        .explicitGet()
      val buyFee  = (BigInt(matcherFee) * BigInt(matchedAmount) / BigInt(amount1)).longValue
      val sellFee = (BigInt(matcherFee) * BigInt(matchedAmount) / BigInt(amount2)).longValue
      val trans =
        ExchangeTransaction
          .signed(1.toByte, matcher.privateKey, o1, o2, matchedAmount, price, buyFee, sellFee, (buyFee + sellFee) / 2, expiration - 100)
          .explicitGet()

      trans
    }

  private type OrderConstructor = (KeyPair, PublicKey, AssetPair, Long, Long, Long, Long, Long) => Order

  def exchangeV2GeneratorP(
      buyer: KeyPair,
      seller: KeyPair,
      amountAssetId: Asset,
      priceAssetId: Asset,
      fixedMatcherFee: Option[Long] = None,
      orderVersions: Set[Byte] = Set(1, 2, 3),
      buyMatcherFeeAssetId: Asset = Waves,
      sellMatcherFeeAssetId: Asset = Waves,
      fixedMatcher: Option[KeyPair] = None
  ): Gen[ExchangeTransaction] = {
    def mkBuyOrder(version: TxVersion): OrderConstructor = (version: @unchecked) match {
      case Order.V1 => Order.buy(Order.V1, _, _, _, _, _, _, _, _).explicitGet()
      case Order.V2 => Order.buy(Order.V2, _, _, _, _, _, _, _, _).explicitGet()
      case Order.V3 => Order.buy(Order.V3, _, _, _, _, _, _, _, _, buyMatcherFeeAssetId).explicitGet()
    }

    def mkSellOrder(version: TxVersion): OrderConstructor = (version: @unchecked) match {
      case Order.V1 => Order.sell(Order.V1, _, _, _, _, _, _, _, _).explicitGet()
      case Order.V2 => Order.sell(Order.V2, _, _, _, _, _, _, _, _).explicitGet()
      case Order.V3 => Order.sell(Order.V3, _, _, _, _, _, _, _, _, sellMatcherFeeAssetId).explicitGet()
    }

    for {
      (_, generatedMatcher, _, _, amount1, price, timestamp, expiration, generatedMatcherFee) <- orderParamGen
      amount2: Long                                                                           <- matcherAmountGen
      matcher    = fixedMatcher.getOrElse(generatedMatcher)
      matcherFee = fixedMatcherFee.getOrElse(generatedMatcherFee)
      matchedAmount: Long <- Gen.choose(Math.min(amount1, amount2) / 2000, Math.min(amount1, amount2) / 1000)
      assetPair = AssetPair(amountAssetId, priceAssetId)
      mkO1 <- Gen.oneOf(orderVersions.map(mkBuyOrder).toSeq)
      mkO2 <- Gen.oneOf(orderVersions.map(mkSellOrder).toSeq)
    } yield {

      val buyFee  = (BigInt(matcherFee) * BigInt(matchedAmount) / BigInt(amount1)).longValue
      val sellFee = (BigInt(matcherFee) * BigInt(matchedAmount) / BigInt(amount2)).longValue

      val o1 = mkO1(buyer, matcher.publicKey, assetPair, amount1, price, timestamp, expiration, matcherFee)
      val o2 = mkO2(seller, matcher.publicKey, assetPair, amount2, price, timestamp, expiration, matcherFee)

      ExchangeTransaction
        .signed(2.toByte, matcher.privateKey, o1, o2, matchedAmount, price, buyFee, sellFee, (buyFee + sellFee) / 2, expiration - 100)
        .explicitGet()
    }
  }

  val randomTransactionGen: Gen[Transaction & ProvenTransaction] = (for {
    tr <- transferV1Gen
    (is, ri, bu) <- issueReissueBurnGen.retryUntil { case (i, r, b) =>
      i.version == 1 && r.version == 1 && b.version == 1
    }
    ca <- createAliasGen.retryUntil(_.version == 1)
    xt <- exchangeTransactionGen
    tx <- Gen.oneOf(tr, is, ri, ca, bu, xt)
  } yield tx).label("random transaction")

  def randomTransactionsGen(count: Int): Gen[Seq[Transaction]] =
    for {
      transactions <- Gen.listOfN(count, randomTransactionGen)
    } yield transactions

  def genesisGeneratorP(recipient: Address): Gen[GenesisTransaction] =
    for {
      amt <- Gen.choose(1, 100000000L * 100000000L)
      ts  <- positiveIntGen
    } yield GenesisTransaction.create(recipient, amt, ts).explicitGet()

  import DataEntry.MaxKeySize

  val dataKeyGen: Gen[String] = for {
    size <- Gen.choose(1, MaxKeySize)
  } yield Random.nextString(size)

  val dataScriptsKeyGen: Gen[String] = for {
    size <- Gen.choose(1, 10)
  } yield Random.nextString(size)

  def longEntryGen(keyGen: Gen[String] = dataKeyGen): Gen[IntegerDataEntry] =
    for {
      key   <- keyGen
      value <- Gen.choose[Long](Long.MinValue, Long.MaxValue)
    } yield IntegerDataEntry(key, value)

  def booleanEntryGen(keyGen: Gen[String] = dataKeyGen): Gen[BooleanDataEntry] =
    for {
      key   <- keyGen
      value <- Gen.oneOf(true, false)
    } yield BooleanDataEntry(key, value)

  def binaryEntryGen(maxSize: Int, keyGen: Gen[String] = dataKeyGen): Gen[BinaryDataEntry] =
    for {
      key   <- keyGen
      size  <- Gen.choose(0, maxSize)
      value <- byteArrayGen(size)
    } yield BinaryDataEntry(key, ByteStr(value))

  def stringEntryGen(maxSize: Int, keyGen: Gen[String] = dataKeyGen): Gen[StringDataEntry] =
    for {
      key   <- keyGen
      size  <- Gen.choose(0, maxSize)
      value <- Gen.listOfN(size, aliasAlphabetGen)
    } yield StringDataEntry(key, value.mkString)

  def emptyEntryGen(keyGen: Gen[String] = dataKeyGen): Gen[EmptyDataEntry] =
    for (key <- keyGen) yield EmptyDataEntry(key)

  def dataEntryGen(maxSize: Int, keyGen: Gen[String] = dataKeyGen, withDeleteEntry: Boolean = false): Gen[DataEntry[?]] =
    Gen.oneOf(
      longEntryGen(keyGen),
      booleanEntryGen(keyGen),
      (Seq(
        binaryEntryGen(maxSize, keyGen),
        stringEntryGen(maxSize, keyGen)
      ) ++ (if (withDeleteEntry) Seq(emptyEntryGen(keyGen)) else Seq()))*
    )

  val dataTransactionGen: Gen[DataTransaction] = dataTransactionGen(DataTransaction.MaxEntryCount)

  def dataTransactionGen(
      maxEntryCount: Int,
      useForScript: Boolean = false,
      withDeleteEntry: Boolean = false,
      sender: Option[SeedKeyPair] = None
  ): Gen[DataTransaction] =
    (for {
      timestamp <- timestampGen
      sender    <- sender.fold(accountGen)(Gen.const)
      size      <- Gen.choose(0, maxEntryCount)
      maxEntrySize = if (useForScript) 200 else (DataTransaction.MaxBytes - 122) / (size max 1) min DataEntry.MaxValueSize
      data <-
        if (useForScript) Gen.listOfN(size, dataEntryGen(maxEntrySize, dataScriptsKeyGen, withDeleteEntry))
        else Gen.listOfN(size, dataEntryGen(maxEntrySize))
      uniq = data.foldRight(List.empty[DataEntry[?]]) { (e, es) =>
        if (es.exists(_.key == e.key)) es else e :: es
      }
    } yield DataTransaction.selfSigned(if (withDeleteEntry) 2.toByte else 1.toByte, sender, uniq, 15000000, timestamp).explicitGet())
      .label("DataTransaction")

  def issueV2TransactionGen(
      senderGen: Gen[KeyPair] = accountGen,
      _scriptGen: Gen[Option[Script]] = Gen.option(scriptGen),
      reissuableParam: Option[Boolean] = None,
      quantityParam: Option[Long] = None,
      feeParam: Option[Long] = None
  ): Gen[IssueTransaction] =
    for {
      script                                                                                                 <- _scriptGen
      sender                                                                                                 <- senderGen
      (_, assetName, description, generatedQuantity, decimals, generatedReissuable, generatedFee, timestamp) <- issueParamGen
      reissuable = reissuableParam.getOrElse(generatedReissuable)
      quantity   = quantityParam.getOrElse(generatedQuantity)
      fee        = feeParam.getOrElse(generatedFee)
    } yield IssueTransaction
      .selfSigned(TxVersion.V2, sender, new String(assetName), new String(description), quantity, decimals, reissuable, script, fee, timestamp)
      .explicitGet()

  def invokeExpressionTransactionGen(sender: KeyPair, script: ExprScript, feeAmount: Long): Gen[InvokeExpressionTransaction] =
    InvokeExpressionTransaction.selfSigned(1, sender, script, feeAmount, Waves, ntpTime.getTimestamp()).explicitGet()
}

trait TransactionGen extends TransactionGenBase { _: Suite => }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy