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

com.wavesplatform.protobuf.transaction.PBTransactions.scala Maven / Gradle / Ivy

The newest version!
package com.wavesplatform.protobuf.transaction

import cats.syntax.traverse.*
import com.google.protobuf.ByteString
import com.wavesplatform.account.{AddressOrAlias, PublicKey}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.EitherExt2
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.lang.script.ScriptReader
import com.wavesplatform.lang.script.v1.ExprScript
import com.wavesplatform.lang.v1.compiler.Terms
import com.wavesplatform.lang.v1.compiler.Terms.EXPR
import com.wavesplatform.lang.v1.serialization.SerdeV1
import com.wavesplatform.protobuf.*
import com.wavesplatform.protobuf.transaction.Transaction.Data
import com.wavesplatform.protobuf.utils.PBImplicitConversions.*
import com.wavesplatform.serialization.Deser
import com.wavesplatform.state.{BinaryDataEntry, BooleanDataEntry, EmptyDataEntry, IntegerDataEntry, StringDataEntry}
import com.wavesplatform.transaction as vt
import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves}
import com.wavesplatform.transaction.TxValidationError.{GenericError, NegativeAmount}
import com.wavesplatform.transaction.assets.UpdateAssetInfoTransaction
import com.wavesplatform.transaction.serialization.impl.PBTransactionSerializer
import com.wavesplatform.transaction.smart.InvokeExpressionTransaction
import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment
import com.wavesplatform.transaction.transfer.MassTransferTransaction
import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer
import com.wavesplatform.transaction.{
  EthereumTransaction,
  Proofs,
  TxDecimals,
  TxExchangeAmount,
  TxExchangePrice,
  TxNonNegativeAmount,
  TxPositiveAmount,
  TxValidationError
}
import com.wavesplatform.utils.StringBytes
import scalapb.UnknownFieldSet.empty

import scala.util.Try

object PBTransactions {

  def createGenesis(chainId: Byte, timestamp: Long, signature: ByteStr, data: GenesisTransactionData): SignedTransaction =
    new SignedTransaction(
      SignedTransaction.Transaction.WavesTransaction(Transaction(chainId, timestamp = timestamp, data = Data.Genesis(data))),
      Seq(ByteString.copyFrom(signature.arr))
    )

  def create(
      sender: com.wavesplatform.account.PublicKey,
      chainId: Byte = 0,
      fee: Long = 0L,
      feeAssetId: VanillaAssetId = Waves,
      timestamp: Long = 0L,
      version: Int = 0,
      proofsArray: Seq[com.wavesplatform.common.state.ByteStr] = Nil,
      data: com.wavesplatform.protobuf.transaction.Transaction.Data = com.wavesplatform.protobuf.transaction.Transaction.Data.Empty
  ): SignedTransaction =
    new SignedTransaction(
      SignedTransaction.Transaction
        .WavesTransaction(Transaction(chainId, sender.toByteString, Some((feeAssetId, fee): Amount), timestamp, version, data)),
      proofsArray.map(bs => ByteString.copyFrom(bs.arr))
    )

  def vanillaUnsafe(signedTx: PBSignedTransaction): VanillaTransaction = {
    import com.wavesplatform.common.utils.*
    vanilla(signedTx, unsafe = true).explicitGet()
  }

  def tryToVanilla(signedTx: PBSignedTransaction): Try[VanillaTransaction] =
    Try(vanilla(signedTx, unsafe = false).explicitGet())

  def vanilla(signedTx: PBSignedTransaction, unsafe: Boolean): Either[ValidationError, VanillaTransaction] =
    signedTx.transaction match {
      case SignedTransaction.Transaction.Empty                      => Left(GenericError("Transaction must be specified"))
      case SignedTransaction.Transaction.EthereumTransaction(value) => EthereumTransaction(value.toByteArray)
      case SignedTransaction.Transaction.WavesTransaction(parsedTx) =>
        val (feeAsset, feeAmount) = PBAmounts.toAssetAndAmount(parsedTx.fee.getOrElse(Amount.defaultInstance))
        val sender = Option(parsedTx.senderPublicKey)
          .filterNot(_.isEmpty)
          .map(pk => PublicKey(pk.toByteArray))
          .orNull
        for {
          tx <-
            if (unsafe)
              Right(
                createVanillaUnsafe(
                  parsedTx.version,
                  parsedTx.chainId.toByte,
                  sender,
                  feeAmount,
                  feeAsset,
                  parsedTx.timestamp,
                  Proofs(signedTx.proofs.map(_.toByteStr)),
                  parsedTx.data
                )
              )
            else
              for {
                proofs <- Proofs.create(signedTx.proofs.map(_.toByteStr))
                tx <- createVanilla(
                  parsedTx.version,
                  parsedTx.chainId.toByte,
                  sender,
                  feeAmount,
                  feeAsset,
                  parsedTx.timestamp,
                  proofs,
                  parsedTx.data
                )
              } yield tx
        } yield tx
    }

  private[this] def createVanilla(
      version: Int,
      chainId: Byte,
      sender: PublicKey,
      feeAmount: Long,
      feeAssetId: VanillaAssetId,
      timestamp: Long,
      proofs: Proofs,
      data: PBTransaction.Data
  ): Either[ValidationError, VanillaTransaction] = {

    val signature = proofs.toSignature
    val result: Either[ValidationError, VanillaTransaction] = data match {
      case Data.Genesis(GenesisTransactionData(recipient, amount, `empty`)) =>
        for {
          addr <- PBRecipients.toAddress(recipient.toByteArray, chainId)
          tx   <- vt.GenesisTransaction.create(addr, amount, timestamp)
        } yield tx

      case Data.Payment(PaymentTransactionData(recipient, amount, `empty`)) =>
        for {
          addr <- PBRecipients.toAddress(recipient.toByteArray, chainId)
          tx   <- vt.PaymentTransaction.create(sender, addr, amount, feeAmount, timestamp, signature)
        } yield tx

      case Data.Transfer(TransferTransactionData(Some(recipient), Some(amount), attachment, `empty`)) =>
        for {
          address <- recipient.toAddressOrAlias(chainId)
          tx <- vt.transfer.TransferTransaction.create(
            version.toByte,
            sender,
            address,
            amount.vanillaAssetId,
            amount.longAmount,
            feeAssetId,
            feeAmount,
            attachment.toByteStr,
            timestamp,
            proofs
          )
        } yield tx

      case Data.CreateAlias(CreateAliasTransactionData(alias, _)) =>
        vt.CreateAliasTransaction.create(version.toByte, sender, alias, feeAmount, timestamp, proofs, chainId)

      case Data.Issue(IssueTransactionData(name, description, quantity, decimals, reissuable, script, `empty`)) =>
        vt.assets.IssueTransaction.create(
          version.toByte,
          sender,
          name,
          description,
          quantity,
          decimals.toByte,
          reissuable,
          toVanillaScript(script),
          feeAmount,
          timestamp,
          proofs,
          chainId
        )

      case Data.Reissue(ReissueTransactionData(Some(Amount(assetId, amount, `empty`)), reissuable, `empty`)) =>
        vt.assets.ReissueTransaction.create(
          version.toByte,
          sender,
          IssuedAsset(assetId.toByteStr),
          amount,
          reissuable,
          feeAmount,
          timestamp,
          proofs,
          chainId
        )

      case Data.Burn(BurnTransactionData(Some(Amount(assetId, amount, `empty`)), `empty`)) =>
        vt.assets.BurnTransaction.create(version.toByte, sender, IssuedAsset(assetId.toByteStr), amount, feeAmount, timestamp, proofs, chainId)

      case Data.SetAssetScript(SetAssetScriptTransactionData(assetId, script, `empty`)) =>
        vt.assets.SetAssetScriptTransaction.create(
          version.toByte,
          sender,
          IssuedAsset(assetId.toByteStr),
          toVanillaScript(script),
          feeAmount,
          timestamp,
          proofs,
          chainId
        )

      case Data.SetScript(SetScriptTransactionData(script, `empty`)) =>
        vt.smart.SetScriptTransaction.create(
          version.toByte,
          sender,
          toVanillaScript(script),
          feeAmount,
          timestamp,
          proofs,
          chainId
        )

      case Data.Lease(LeaseTransactionData(Some(recipient), amount, `empty`)) =>
        for {
          address <- recipient.toAddressOrAlias(chainId)
          tx      <- vt.lease.LeaseTransaction.create(version.toByte, sender, address, amount, feeAmount, timestamp, proofs)
        } yield tx

      case Data.LeaseCancel(LeaseCancelTransactionData(leaseId, `empty`)) =>
        vt.lease.LeaseCancelTransaction.create(version.toByte, sender, leaseId.toByteStr, feeAmount, timestamp, proofs, chainId)

      case Data.Exchange(ExchangeTransactionData(amount, price, buyMatcherFee, sellMatcherFee, Seq(order1, order2), `empty`)) =>
        for {
          order1 <- PBOrders.vanilla(order1)
          order2 <- PBOrders.vanilla(order2)
          tx <- vt.assets.exchange.ExchangeTransaction.create(
            version.toByte,
            order1,
            order2,
            amount,
            price,
            buyMatcherFee,
            sellMatcherFee,
            feeAmount,
            timestamp,
            proofs,
            chainId
          )
        } yield tx

      case Data.DataTransaction(dt) =>
        vt.DataTransaction.create(version.toByte, sender, dt.data.toList.map(toVanillaDataEntry), feeAmount, timestamp, proofs, chainId)

      case Data.MassTransfer(mt) =>
        for {
          parsedTransfers <- mt.transfers.traverse { t =>
            t.getRecipient.toAddressOrAlias(chainId).flatMap { addressOrAlias =>
              TxNonNegativeAmount(t.amount)(NegativeAmount(t.amount, "asset"))
                .map(ParsedTransfer(addressOrAlias, _))
            }
          }
          tx <- vt.transfer.MassTransferTransaction.create(
            version.toByte,
            sender,
            PBAmounts.toVanillaAssetId(mt.assetId),
            parsedTransfers,
            feeAmount,
            timestamp,
            mt.attachment.toByteStr,
            proofs,
            chainId
          )
        } yield tx

      case Data.SponsorFee(SponsorFeeTransactionData(Some(Amount(assetId, minFee, `empty`)), `empty`)) =>
        vt.assets.SponsorFeeTransaction.create(
          version.toByte,
          sender,
          IssuedAsset(assetId.toByteStr),
          Option(minFee).filter(_ > 0),
          feeAmount,
          timestamp,
          proofs,
          chainId
        )

      case Data.InvokeScript(InvokeScriptTransactionData(Some(dappAddress), functionCall, payments, `empty`)) =>
        import cats.instances.either.*
        import cats.instances.option.*
        import cats.syntax.traverse.*

        for {
          dApp <- PBRecipients.toAddressOrAlias(dappAddress, chainId)

          fcOpt <- Deser
            .parseOption(functionCall.asReadOnlyByteBuffer())(SerdeV1.deserializeFunctionCall)
            .sequence
            .left
            .map(e => GenericError(s"Invalid InvokeScript function call: $e"))

          _ <- fcOpt match {
            case None | Some(Terms.FUNCTION_CALL(_, _)) => Right(())
            case Some(expr)                             => Left(GenericError(s"Not a function call: $expr"))
          }

          tx <- vt.smart.InvokeScriptTransaction.create(
            version.toByte,
            sender,
            dApp,
            fcOpt,
            payments.map(p => vt.smart.InvokeScriptTransaction.Payment(p.longAmount, PBAmounts.toVanillaAssetId(p.assetId))),
            feeAmount,
            feeAssetId,
            timestamp,
            proofs,
            chainId
          )
        } yield tx

      case Data.UpdateAssetInfo(UpdateAssetInfoTransactionData(assetId, name, description, `empty`)) =>
        UpdateAssetInfoTransaction.create(
          version.toByte,
          sender,
          assetId.toByteStr,
          name,
          description,
          timestamp,
          feeAmount,
          feeAssetId,
          proofs,
          chainId
        )

      case Data.InvokeExpression(InvokeExpressionTransactionData(expressionBytes, `empty`)) =>
        for {
          expression <- toVanillaScript(expressionBytes) match {
            case Some(e: ExprScript) => Right(e)
            case Some(_)             => Left(GenericError("Unexpected expression type for InvokeExpression"))
            case None                => Left(GenericError(s"Unexpected empty expression bytes for InvokeExpression"))
          }
          tx <- InvokeExpressionTransaction.create(
            version.toByte,
            sender,
            expression,
            feeAmount,
            feeAssetId,
            timestamp,
            proofs,
            chainId
          )
        } yield tx

      case _ =>
        Left(TxValidationError.UnsupportedTransactionType)
    }

    result
  }

  private[this] def createVanillaUnsafe(
      version: Int,
      chainId: Byte,
      sender: PublicKey,
      feeAmount: Long,
      feeAssetId: VanillaAssetId,
      timestamp: Long,
      proofs: Proofs,
      data: PBTransaction.Data
  ): VanillaTransaction = {
    import com.wavesplatform.common.utils.*

    val signature = proofs.toSignature
    data match {
      case Data.Genesis(GenesisTransactionData(recipient, amount, `empty`)) =>
        vt.GenesisTransaction(
          PBRecipients.toAddress(recipient.toByteArray, chainId).explicitGet(),
          TxNonNegativeAmount.unsafeFrom(amount),
          timestamp,
          signature,
          chainId
        )

      case Data.Payment(PaymentTransactionData(recipient, amount, `empty`)) =>
        vt.PaymentTransaction(
          sender,
          PBRecipients.toAddress(recipient.toByteArray, chainId).explicitGet(),
          TxPositiveAmount.unsafeFrom(amount),
          TxPositiveAmount.unsafeFrom(feeAmount),
          timestamp,
          signature,
          chainId
        )

      case Data.Transfer(TransferTransactionData(Some(recipient), Some(amount), attachment, `empty`)) =>
        vt.transfer.TransferTransaction(
          version.toByte,
          sender,
          recipient.toAddressOrAlias(chainId).explicitGet(),
          amount.vanillaAssetId,
          TxPositiveAmount.unsafeFrom(amount.longAmount),
          feeAssetId,
          TxPositiveAmount.unsafeFrom(feeAmount),
          attachment.toByteStr,
          timestamp,
          proofs,
          chainId
        )

      case Data.CreateAlias(CreateAliasTransactionData(alias, `empty`)) =>
        vt.CreateAliasTransaction(
          version.toByte,
          sender,
          alias,
          TxPositiveAmount.unsafeFrom(feeAmount),
          timestamp,
          Proofs(signature),
          chainId
        )

      case Data.Issue(IssueTransactionData(name, description, quantity, decimals, reissuable, script, `empty`)) =>
        vt.assets.IssueTransaction(
          version.toByte,
          sender,
          name.toByteString,
          description.toByteString,
          TxPositiveAmount.unsafeFrom(quantity),
          TxDecimals.unsafeFrom(decimals.toByte),
          reissuable,
          toVanillaScript(script),
          TxPositiveAmount.unsafeFrom(feeAmount),
          timestamp,
          proofs,
          chainId
        )

      case Data.Reissue(ReissueTransactionData(Some(Amount(assetId, amount, `empty`)), reissuable, `empty`)) =>
        vt.assets.ReissueTransaction(
          version.toByte,
          sender,
          IssuedAsset(assetId.toByteStr),
          TxPositiveAmount.unsafeFrom(amount),
          reissuable,
          TxPositiveAmount.unsafeFrom(feeAmount),
          timestamp,
          proofs,
          chainId
        )

      case Data.Burn(BurnTransactionData(Some(Amount(assetId, amount, `empty`)), `empty`)) =>
        vt.assets.BurnTransaction(
          version.toByte,
          sender,
          IssuedAsset(assetId.toByteStr),
          TxNonNegativeAmount.unsafeFrom(amount),
          TxPositiveAmount.unsafeFrom(feeAmount),
          timestamp,
          proofs,
          chainId
        )

      case Data.SetAssetScript(SetAssetScriptTransactionData(assetId, script, `empty`)) =>
        vt.assets.SetAssetScriptTransaction(
          version.toByte,
          sender,
          IssuedAsset(assetId.toByteStr),
          toVanillaScript(script),
          TxPositiveAmount.unsafeFrom(feeAmount),
          timestamp,
          proofs,
          chainId
        )

      case Data.SetScript(SetScriptTransactionData(script, `empty`)) =>
        vt.smart.SetScriptTransaction(
          version.toByte,
          sender,
          toVanillaScript(script),
          TxPositiveAmount.unsafeFrom(feeAmount),
          timestamp,
          proofs,
          chainId
        )

      case Data.Lease(LeaseTransactionData(Some(recipient), amount, `empty`)) =>
        vt.lease.LeaseTransaction(
          version.toByte,
          sender,
          recipient.toAddressOrAlias(chainId).explicitGet(),
          TxPositiveAmount.unsafeFrom(amount),
          TxPositiveAmount.unsafeFrom(feeAmount),
          timestamp,
          proofs,
          chainId
        )

      case Data.LeaseCancel(LeaseCancelTransactionData(leaseId, `empty`)) =>
        vt.lease.LeaseCancelTransaction(version.toByte, sender, leaseId.toByteStr, TxPositiveAmount.unsafeFrom(feeAmount), timestamp, proofs, chainId)

      case Data.Exchange(ExchangeTransactionData(amount, price, buyMatcherFee, sellMatcherFee, Seq(buyOrder, sellOrder), `empty`)) =>
        vt.assets.exchange.ExchangeTransaction(
          version.toByte,
          PBOrders.vanilla(buyOrder).explicitGet(),
          PBOrders.vanilla(sellOrder).explicitGet(),
          TxExchangeAmount.unsafeFrom(amount),
          TxExchangePrice.unsafeFrom(price),
          buyMatcherFee,
          sellMatcherFee,
          TxPositiveAmount.unsafeFrom(feeAmount),
          timestamp,
          proofs,
          chainId
        )

      case Data.DataTransaction(dt) =>
        vt.DataTransaction(
          version.toByte,
          sender,
          dt.data.toList.map(toVanillaDataEntry),
          TxPositiveAmount.unsafeFrom(feeAmount),
          timestamp,
          proofs,
          chainId
        )

      case Data.MassTransfer(mt) =>
        vt.transfer.MassTransferTransaction(
          version.toByte,
          sender,
          PBAmounts.toVanillaAssetId(mt.assetId),
          mt.transfers
            .flatMap(t => t.getRecipient.toAddressOrAlias(chainId).toOption.map(ParsedTransfer(_, TxNonNegativeAmount.unsafeFrom(t.amount))))
            .toList,
          TxPositiveAmount.unsafeFrom(feeAmount),
          timestamp,
          mt.attachment.toByteStr,
          proofs,
          chainId
        )

      case Data.SponsorFee(SponsorFeeTransactionData(Some(Amount(assetId, minFee, `empty`)), `empty`)) =>
        vt.assets.SponsorFeeTransaction(
          version.toByte,
          sender,
          IssuedAsset(assetId.toByteStr),
          Some(minFee).filter(_ > 0).map(TxPositiveAmount.unsafeFrom),
          TxPositiveAmount.unsafeFrom(feeAmount),
          timestamp,
          proofs,
          chainId
        )

      case Data.InvokeScript(InvokeScriptTransactionData(Some(dappAddress), functionCall, payments, `empty`)) =>
        vt.smart.InvokeScriptTransaction(
          version.toByte,
          sender,
          PBRecipients.toAddressOrAlias(dappAddress, chainId).explicitGet(),
          Deser
            .parseOption(functionCall.asReadOnlyByteBuffer())(SerdeV1.deserializeFunctionCall)
            .map(_.explicitGet()),
          payments.map(p => vt.smart.InvokeScriptTransaction.Payment(p.longAmount, PBAmounts.toVanillaAssetId(p.assetId))),
          TxPositiveAmount.unsafeFrom(feeAmount),
          feeAssetId,
          timestamp,
          proofs,
          chainId
        )

      case Data.UpdateAssetInfo(UpdateAssetInfoTransactionData(assetId, name, description, `empty`)) =>
        vt.assets.UpdateAssetInfoTransaction(
          version.toByte,
          sender,
          IssuedAsset(assetId.toByteStr),
          name,
          description,
          timestamp,
          TxPositiveAmount.unsafeFrom(feeAmount),
          feeAssetId,
          proofs,
          chainId
        )

      case Data.InvokeExpression(InvokeExpressionTransactionData(expressionBytes, `empty`)) =>
        InvokeExpressionTransaction(
          version.toByte,
          sender,
          toVanillaScript(expressionBytes).get.asInstanceOf[ExprScript],
          TxPositiveAmount.unsafeFrom(feeAmount),
          feeAssetId,
          timestamp,
          proofs,
          chainId
        )

      case other =>
        throw new IllegalArgumentException(s"Unsupported transaction data: $other")
    }
  }

  def protobuf(tx: VanillaTransaction): PBSignedTransaction = {
    tx match {
      case vt.GenesisTransaction(recipient, amount, timestamp, signature, chainId) =>
        PBTransactions.createGenesis(
          chainId,
          timestamp,
          signature,
          GenesisTransactionData(PBRecipients.create(recipient).getPublicKeyHash, amount.value)
        )

      case vt.PaymentTransaction(sender, recipient, amount, fee, timestamp, signature, chainId) =>
        val data = PaymentTransactionData(PBRecipients.create(recipient).getPublicKeyHash, amount.value)
        PBTransactions.create(sender, chainId, fee.value, Waves, timestamp, 1, Seq(signature), Data.Payment(data))

      case tx: vt.transfer.TransferTransaction =>
        import tx.*
        val data = TransferTransactionData(Some(recipient.toPB), Some((assetId, amount.value)), attachment.toByteString)
        PBTransactions.create(sender, chainId, fee.value, feeAssetId, timestamp, version, proofs, Data.Transfer(data))

      case tx: vt.CreateAliasTransaction =>
        import tx.*
        val data = CreateAliasTransactionData(alias.name)
        PBTransactions.create(sender, chainId, fee.value, tx.feeAssetId, timestamp, version, proofs, Data.CreateAlias(data))

      case tx: vt.assets.exchange.ExchangeTransaction =>
        import tx.*
        val data = ExchangeTransactionData(
          amount.value,
          price.value,
          buyMatcherFee,
          sellMatcherFee,
          Seq(PBOrders.protobuf(order1), PBOrders.protobuf(order2))
        )
        PBTransactions.create(tx.sender, chainId, fee.value, tx.feeAssetId, timestamp, version, proofs, Data.Exchange(data))

      case tx: vt.assets.IssueTransaction =>
        import tx.*
        val data = IssueTransactionData(name.toStringUtf8, description.toStringUtf8, quantity.value, decimals.value, reissuable, toPBScript(script))
        PBTransactions.create(sender, chainId, fee.value, tx.feeAssetId, timestamp, version, proofs, Data.Issue(data))

      case tx: vt.assets.ReissueTransaction =>
        import tx.*
        val data = ReissueTransactionData(Some(Amount(asset.id.toByteString, quantity.value)), reissuable)
        PBTransactions.create(sender, chainId, fee.value, tx.feeAssetId, timestamp, version, proofs, Data.Reissue(data))

      case tx: vt.assets.BurnTransaction =>
        import tx.*
        val data = BurnTransactionData(Some(Amount(asset.id.toByteString, quantity.value)))
        PBTransactions.create(sender, chainId, fee.value, tx.feeAssetId, timestamp, version, proofs, Data.Burn(data))

      case tx @ vt.assets.SetAssetScriptTransaction(_, sender, assetId, script, fee, timestamp, proofs, chainId) =>
        val data = SetAssetScriptTransactionData(assetId.id.toByteString, toPBScript(script))
        PBTransactions.create(sender, chainId, fee.value, tx.feeAssetId, timestamp, tx.version, proofs, Data.SetAssetScript(data))

      case tx @ vt.smart.SetScriptTransaction(_, sender, script, fee, timestamp, proofs, chainId) =>
        val data = SetScriptTransactionData(toPBScript(script))
        PBTransactions.create(sender, chainId, fee.value, tx.feeAssetId, timestamp, tx.version, proofs, Data.SetScript(data))

      case tx: vt.lease.LeaseTransaction =>
        import tx.*
        val data = LeaseTransactionData(Some(recipient.toPB), amount.value)
        PBTransactions.create(sender, chainId, fee.value, tx.feeAssetId, timestamp, version, proofs, Data.Lease(data))

      case tx: vt.lease.LeaseCancelTransaction =>
        import tx.*
        val data = LeaseCancelTransactionData(leaseId.toByteString)
        PBTransactions.create(sender, chainId, fee.value, tx.feeAssetId, timestamp, version, proofs, Data.LeaseCancel(data))

      case tx @ MassTransferTransaction(version, sender, assetId, transfers, fee, timestamp, attachment, proofs, chainId) =>
        val data = MassTransferTransactionData(
          PBAmounts.toPBAssetId(assetId),
          transfers.map(pt => MassTransferTransactionData.Transfer(Some(pt.address.toPB), pt.amount.value)),
          attachment.toByteString
        )
        PBTransactions.create(sender, chainId, fee.value, tx.feeAssetId, timestamp, version, proofs, Data.MassTransfer(data))

      case tx @ vt.DataTransaction(version, sender, data, fee, timestamp, proofs, chainId) =>
        val txData = DataTransactionData(data.map(toPBDataEntry))
        PBTransactions.create(sender, chainId, fee.value, tx.feeAssetId, timestamp, version, proofs, Data.DataTransaction(txData))

      case tx @ vt.assets.SponsorFeeTransaction(version, sender, assetId, minSponsoredAssetFee, fee, timestamp, proofs, chainId) =>
        val data = SponsorFeeTransactionData(Some(Amount(assetId.id.toByteString, minSponsoredAssetFee.map(_.value).getOrElse(0L))))
        PBTransactions.create(sender, chainId, fee.value, tx.feeAssetId, timestamp, version, proofs, Data.SponsorFee(data))

      case vt.smart.InvokeScriptTransaction(version, sender, dappAddress, fcOpt, payment, fee, feeAssetId, timestamp, proofs, chainId) =>
        val data = Data.InvokeScript(toPBInvokeScriptData(dappAddress, fcOpt, payment))
        PBTransactions.create(sender, chainId, fee.value, feeAssetId, timestamp, version, proofs, data)

      case vt.assets.UpdateAssetInfoTransaction(version, sender, assetId, name, description, timestamp, fee, feeAssetId, proofs, chainId) =>
        val data = UpdateAssetInfoTransactionData()
          .withAssetId(assetId.id.toByteString)
          .withName(name)
          .withDescription(description)

        PBTransactions.create(sender, chainId, fee.value, feeAssetId, timestamp, version, proofs, Data.UpdateAssetInfo(data))

      case tx @ InvokeExpressionTransaction(version, sender, _, fee, feeAssetId, timestamp, proofs, chainId) =>
        val data = Data.InvokeExpression(InvokeExpressionTransactionData(tx.expressionBytes.toByteString))
        PBTransactions.create(sender, chainId, fee.value, feeAssetId, timestamp, version, proofs, data)

      case et: EthereumTransaction =>
        PBSignedTransaction(PBSignedTransaction.Transaction.EthereumTransaction(ByteString.copyFrom(et.bytes())))

      case _ =>
        throw new IllegalArgumentException(s"Unsupported transaction: $tx")
    }
  }

  def toPBInvokeScriptData(dAppAddress: AddressOrAlias, exprOpt: Option[EXPR], payment: Seq[Payment]): InvokeScriptTransactionData = {
    InvokeScriptTransactionData(
      Some(PBRecipients.create(dAppAddress)),
      ByteString.copyFrom(Deser.serializeOption(exprOpt)(SerdeV1.serialize(_))),
      payment.map(p => (p.assetId, p.amount): Amount)
    )
  }

  def toVanillaDataEntry(de: DataEntry): com.wavesplatform.state.DataEntry[?] = {
    import DataEntry.Value as DEV

    de.value match {
      case DEV.IntValue(num)      => IntegerDataEntry(de.key, num)
      case DEV.BoolValue(bool)    => BooleanDataEntry(de.key, bool)
      case DEV.BinaryValue(bytes) => BinaryDataEntry(de.key, bytes.toByteStr)
      case DEV.StringValue(str)   => StringDataEntry(de.key, str)
      case DEV.Empty              => EmptyDataEntry(de.key)
    }
  }

  def toPBDataEntry(de: com.wavesplatform.state.DataEntry[?]): DataEntry = {
    DataEntry(
      de.key,
      de match {
        case IntegerDataEntry(_, value) => DataEntry.Value.IntValue(value)
        case BooleanDataEntry(_, value) => DataEntry.Value.BoolValue(value)
        case BinaryDataEntry(_, value)  => DataEntry.Value.BinaryValue(value.toByteString)
        case StringDataEntry(_, value)  => DataEntry.Value.StringValue(value)
        case EmptyDataEntry(_)          => DataEntry.Value.Empty
      }
    )
  }

  def toVanillaScript(script: ByteString): Option[com.wavesplatform.lang.script.Script] = {
    import com.wavesplatform.common.utils.*
    if (script.isEmpty) None else Some(ScriptReader.fromBytes(script.toByteArray).explicitGet())
  }

  def toPBScript(script: Option[com.wavesplatform.lang.script.Script]): ByteString = script match {
    case Some(sc) => ByteString.copyFrom(sc.bytes().arr)
    case None     => ByteString.EMPTY
  }

  // Stores Ethereum txs as-is
  def toByteArrayMerkle(tx: VanillaTransaction): Array[Byte] = tx match {
    case et: EthereumTransaction => et.bytes()
    case vt                      => toByteArray(vt)
  }

  def toByteArray(tx: VanillaTransaction): Array[Byte] =
    PBTransactionSerializer.bytes(tx)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy