com.wavesplatform.state.InvokeScriptResult.scala Maven / Gradle / Ivy
The newest version!
package com.wavesplatform.state
import cats.kernel.Monoid
import com.google.protobuf.ByteString
import com.wavesplatform.account.{Address, AddressOrAlias, AddressScheme, Alias}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.*
import com.wavesplatform.lang.v1.compiler.Terms.*
import com.wavesplatform.lang.v1.compiler.{Terms, Types}
import com.wavesplatform.lang.v1.evaluator.{IncompleteResult, ScriptResult, ScriptResultV3, ScriptResultV4}
import com.wavesplatform.lang.v1.serialization.SerdeV1
import com.wavesplatform.lang.v1.traits.domain.*
import com.wavesplatform.protobuf.transaction.InvokeScriptResult.Call.Argument
import com.wavesplatform.protobuf.transaction.InvokeScriptResult.Call.Argument.Value
import com.wavesplatform.protobuf.transaction.{PBAmounts, PBRecipients, PBTransactions, InvokeScriptResult as PBInvokeScriptResult}
import com.wavesplatform.protobuf.utils.PBUtils
import com.wavesplatform.protobuf.{Amount, *}
import com.wavesplatform.state.InvokeScriptResult as R
import com.wavesplatform.transaction.Asset
import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves}
import com.wavesplatform.transaction.smart.InvokeScriptTransaction
import com.wavesplatform.utils.*
import play.api.libs.json.*
final case class InvokeScriptResult(
data: Seq[R.DataEntry] = Nil,
transfers: Seq[R.Payment] = Nil,
issues: Seq[R.Issue] = Nil,
reissues: Seq[R.Reissue] = Nil,
burns: Seq[R.Burn] = Nil,
sponsorFees: Seq[R.SponsorFee] = Nil,
leases: Seq[R.Lease] = Nil,
leaseCancels: Seq[R.LeaseCancel] = Nil,
invokes: Seq[R.Invocation] = Nil,
error: Option[R.ErrorMessage] = None
)
//noinspection TypeAnnotation
object InvokeScriptResult {
type LeaseCancel = com.wavesplatform.lang.v1.traits.domain.LeaseCancel
type SponsorFee = com.wavesplatform.lang.v1.traits.domain.SponsorFee
type Issue = com.wavesplatform.lang.v1.traits.domain.Issue
type Reissue = com.wavesplatform.lang.v1.traits.domain.Reissue
type Burn = com.wavesplatform.lang.v1.traits.domain.Burn
type DataEntry = com.wavesplatform.state.DataEntry[?]
val empty = InvokeScriptResult()
final case class AttachedPayment(assetId: Asset, amount: Long)
object AttachedPayment {
implicit val attachedPaymentWrites: OWrites[AttachedPayment] = Json.writes[AttachedPayment]
def fromInvokePaymentList(ps: Seq[InvokeScriptTransaction.Payment]): Seq[AttachedPayment] =
ps.map(p => AttachedPayment(p.assetId, p.amount))
}
final case class Call(function: String, args: Seq[EVALUATED])
object Call {
implicit val callWrites: OWrites[Call] = Json.writes[Call]
def fromFunctionCall(fc: FUNCTION_CALL): Call = Call(fc.function.funcName, fc.args.collect { case e: EVALUATED => e })
}
final case class Invocation(dApp: Address, call: Call, payments: Seq[AttachedPayment], stateChanges: InvokeScriptResult)
object Invocation {
def calledAddresses(inv: InvokeScriptResult.Invocation): LazyList[Address] =
LazyList(inv.dApp) #::: inv.stateChanges.invokes.to(LazyList).flatMap(calledAddresses)
def calledAddresses(invs: Iterable[InvokeScriptResult.Invocation]): LazyList[Address] =
invs.to(LazyList).flatMap(calledAddresses)
}
final case class Payment(address: Address, asset: Asset, amount: Long)
object Payment {
implicit val jsonWrites: OWrites[Payment] = Json.writes[Payment]
}
case class Lease(recipient: AddressOrAlias, amount: Long, nonce: Long, id: ByteStr)
object Lease {
implicit val recipientWrites: Writes[AddressOrAlias] = Writes[AddressOrAlias] {
case address: Address => implicitly[Writes[Address]].writes(address)
case alias: Alias => JsString(alias.toString)
case _ => JsNull
}
implicit val jsonWrites: OWrites[Lease] = Json.writes[Lease]
}
def paymentsFromPortfolio(addr: Address, portfolio: Portfolio): Seq[Payment] = {
val waves = InvokeScriptResult.Payment(addr, Waves, portfolio.balance)
val assets = portfolio.assets.map { case (assetId, amount) => InvokeScriptResult.Payment(addr, assetId, amount) }
(assets.toVector ++ Some(waves)).filter(_.amount != 0)
}
implicit val issueFormat: Writes[Issue] = Writes[Issue] { iss =>
Json.obj(
"assetId" -> iss.id,
"name" -> iss.name,
"description" -> iss.description,
"quantity" -> iss.quantity,
"decimals" -> iss.decimals,
"isReissuable" -> iss.isReissuable,
"compiledScript" -> iss.compiledScript,
"nonce" -> iss.nonce
)
}
implicit val reissueFormat: OWrites[Reissue] = Json.writes[Reissue]
implicit val burnFormat: OWrites[Burn] = Json.writes[Burn]
implicit val sponsorFeeFormat: OWrites[SponsorFee] = Json.writes[SponsorFee]
implicit val leaseCancelFormat: OWrites[LeaseCancel] = Json.writes[LeaseCancel]
implicit val errorMessageFormat: OWrites[ErrorMessage] = Json.writes[ErrorMessage]
implicit val invocationFormat: Writes[Invocation] = (i: Invocation) =>
Json.obj(
"dApp" -> i.dApp.toString,
"call" -> i.call,
"payment" -> i.payments,
"stateChanges" -> jsonFormat.writes(i.stateChanges)
)
implicit val jsonFormat: OWrites[InvokeScriptResult] = Json.writes[InvokeScriptResult]
implicit val monoid: Monoid[InvokeScriptResult] = new Monoid[InvokeScriptResult] {
override val empty: InvokeScriptResult =
InvokeScriptResult.this.empty
override def combine(x: InvokeScriptResult, y: InvokeScriptResult): InvokeScriptResult = {
InvokeScriptResult(
data = x.data ++ y.data,
transfers = x.transfers ++ y.transfers,
issues = x.issues ++ y.issues,
reissues = x.reissues ++ y.reissues,
burns = x.burns ++ y.burns,
sponsorFees = x.sponsorFees ++ y.sponsorFees,
invokes = x.invokes ++ y.invokes,
leases = x.leases ++ y.leases,
leaseCancels = x.leaseCancels ++ y.leaseCancels,
error = x.error.orElse(y.error)
)
}
}
def toBytes(isr: InvokeScriptResult): Array[Byte] = {
val pbValue = this.toPB(isr, addressForTransfer = false)
PBUtils.encodeDeterministic(pbValue)
}
def fromBytes(bs: Array[Byte]): InvokeScriptResult = {
val pbValue = PBInvokeScriptResult.parseFrom(bs)
fromPB(pbValue)
}
def toPB(isr: InvokeScriptResult, addressForTransfer: Boolean): PBInvokeScriptResult = {
PBInvokeScriptResult(
isr.data.map(PBTransactions.toPBDataEntry),
isr.transfers.map { payment =>
val sender = if (addressForTransfer) payment.address.bytes else PBRecipients.publicKeyHash(payment.address)
PBInvokeScriptResult.Payment(
ByteString.copyFrom(sender),
Some(PBAmounts.fromAssetAndAmount(payment.asset, payment.amount))
)
},
isr.issues.map(toPbIssue),
isr.reissues.map(toPbReissue),
isr.burns.map(toPbBurn),
isr.error.map(toPbErrorMessage),
isr.sponsorFees.map(toPbSponsorFee),
isr.leases.map(toPbLease),
isr.leaseCancels.map(toPbLeaseCancel),
isr.invokes.map(toPbInvocation(_, addressForTransfer))
)
}
def fromLangResult(invokeId: ByteStr, result: ScriptResult): InvokeScriptResult = {
import com.wavesplatform.lang.v1.traits.domain as lang
def langAddressToAddress(a: lang.Recipient.Address): Address =
Address.fromBytes(a.bytes.arr).explicitGet()
def langTransferToPayment(t: lang.AssetTransfer): Payment =
Payment(langAddressToAddress(t.recipientAddressBytes), Asset.fromCompatId(t.assetId), t.amount)
def langLeaseToLease(l: lang.Lease): Lease =
Lease(AddressOrAlias.fromRide(l.recipient).explicitGet(), l.amount, l.nonce, lang.Lease.calculateId(l, invokeId))
result match {
case ScriptResultV3(ds, ts, _) =>
InvokeScriptResult(data = ds.map(DataEntry.fromLangDataOp), transfers = ts.map(langTransferToPayment))
case ScriptResultV4(actions, _, _) =>
// XXX need return value processing
val issues = actions.collect { case i: lang.Issue => i }
val reissues = actions.collect { case ri: lang.Reissue => ri }
val burns = actions.collect { case b: lang.Burn => b }
val sponsorFees = actions.collect { case sf: lang.SponsorFee => sf }
val dataOps = actions.collect { case d: lang.DataOp => DataEntry.fromLangDataOp(d) }
val transfers = actions.collect { case t: lang.AssetTransfer => langTransferToPayment(t) }
val leases = actions.collect { case l: lang.Lease => langLeaseToLease(l) }
val leaseCancels = actions.collect { case l: lang.LeaseCancel => l }
val invokes = result.invokes.map { case (dApp, fname, args, payments, r) =>
Invocation(
langAddressToAddress(dApp),
Call(fname, args),
payments.map { case CaseObj(_, fields) =>
((fields("assetId"), fields("amount")): @unchecked) match {
case (CONST_BYTESTR(b), CONST_LONG(a)) => InvokeScriptResult.AttachedPayment(IssuedAsset(b), a)
case (_, CONST_LONG(a)) => InvokeScriptResult.AttachedPayment(Waves, a)
}
},
fromLangResult(invokeId, r)
)
}
InvokeScriptResult(dataOps, transfers, issues, reissues, burns, sponsorFees, leases, leaseCancels, invokes)
case i: IncompleteResult => throw new IllegalArgumentException(s"Cannot cast incomplete result: $i")
}
}
import com.wavesplatform.protobuf.transaction.InvokeScriptResult as PBISR
def rideExprToPB(arg: Terms.EXPR): PBISR.Call.Argument.Value = {
import PBISR.Call.Argument.Value
arg match {
case Terms.CONST_LONG(t) => Value.IntegerValue(t)
case bs: Terms.CONST_BYTESTR => Value.BinaryValue(bs.bs.toByteString)
case str: Terms.CONST_STRING => Value.StringValue(str.s)
case Terms.CONST_BOOLEAN(b) => Value.BooleanValue(b)
case Terms.ARR(xs) => Value.List(Argument.List(xs.map(x => Argument(rideExprToPB(x)))))
case caseObj: Terms.CaseObj => Value.CaseObj(ByteString.copyFrom(SerdeV1.serialize(caseObj, allowObjects = true)))
case _ => Value.Empty
}
}
private def toPbCall(c: Call): PBInvokeScriptResult.Call = {
// argsBytes = c.args.map(b => ByteString.copyFrom(Serde.serialize(b, true)))
PBInvokeScriptResult.Call(c.function, args = c.args.map(a => PBISR.Call.Argument(rideExprToPB(a))))
}
private def toPbInvocation(i: Invocation, addressForTransfer: Boolean) = {
PBInvokeScriptResult.Invocation(
ByteString.copyFrom(i.dApp.bytes),
Some(toPbCall(i.call)),
i.payments.map(p => Amount(PBAmounts.toPBAssetId(p.assetId), p.amount)),
Some(toPB(i.stateChanges, addressForTransfer))
)
}
private def toPbIssue(r: Issue) = {
assert(r.compiledScript.isEmpty)
PBInvokeScriptResult.Issue(
ByteString.copyFrom(r.id.arr),
r.name,
r.description,
r.quantity,
r.decimals,
r.isReissuable,
ByteString.EMPTY,
r.nonce
)
}
private def toPbReissue(r: Reissue) =
PBInvokeScriptResult.Reissue(ByteString.copyFrom(r.assetId.arr), r.quantity, r.isReissuable)
private def toPbBurn(b: Burn) =
PBInvokeScriptResult.Burn(ByteString.copyFrom(b.assetId.arr), b.quantity)
private def toPbSponsorFee(sf: SponsorFee) =
PBInvokeScriptResult.SponsorFee(Some(Amount(sf.assetId.toByteString, sf.minSponsoredAssetFee.getOrElse(0))))
private def toPbLease(l: Lease) =
PBInvokeScriptResult.Lease(Some(PBRecipients.create(l.recipient)), l.amount, l.nonce, l.id.toByteString)
private def toPbLeaseCancel(l: LeaseCancel) =
PBInvokeScriptResult.LeaseCancel(ByteString.copyFrom(l.id.arr))
private def toPbErrorMessage(em: ErrorMessage) =
PBInvokeScriptResult.ErrorMessage(em.code, em.text)
private def toVanillaCall(i: PBInvokeScriptResult.Call): Call = {
import com.wavesplatform.lang.v1.compiler.Terms
def toVanillaTerm(v: Argument.Value): Terms.EVALUATED =
v match {
case Value.IntegerValue(value) => Terms.CONST_LONG(value)
case Value.BinaryValue(value) => Terms.CONST_BYTESTR(value.toByteStr).explicitGet()
case Value.StringValue(value) => Terms.CONST_STRING(value).explicitGet()
case Value.BooleanValue(value) => Terms.CONST_BOOLEAN(value)
case Value.List(value) => Terms.ARR(value.items.map(a => toVanillaTerm(a.value)).toVector, limited = true).explicitGet()
case Value.CaseObj(bytes) =>
SerdeV1
.deserialize(bytes.toByteArray, allowObjects = true)
.toOption
.collect { case (obj: CaseObj, _) => obj }
.getOrElse(Terms.CaseObj(Types.UNIT, Map.empty))
case _ => Terms.CaseObj(Types.UNIT, Map.empty)
}
val args = if (i.argsBytes.nonEmpty) i.argsBytes.map { bytes =>
val (value, _) = SerdeV1.deserialize(bytes.toByteArray, allowObjects = true).explicitGet()
value.asInstanceOf[EVALUATED]
}
else i.args.map(a => toVanillaTerm(a.value))
Call(i.function, args)
}
private def toVanillaInvocation(i: PBInvokeScriptResult.Invocation): Invocation = {
Invocation(
PBRecipients.toAddress(i.dApp.toByteArray, AddressScheme.current.chainId).explicitGet(),
toVanillaCall(i.call.get),
i.payments.map { p =>
val (asset, amount) = PBAmounts.toAssetAndAmount(p)
InvokeScriptResult.AttachedPayment(asset, amount)
},
fromPB(i.stateChanges.get)
)
}
private def toVanillaIssue(r: PBInvokeScriptResult.Issue): Issue = {
assert(r.script.isEmpty)
Issue(r.assetId.toByteStr, None, r.decimals, r.description, r.reissuable, r.name, r.amount, r.nonce)
}
private def toVanillaReissue(r: PBInvokeScriptResult.Reissue) =
Reissue(r.assetId.toByteStr, r.isReissuable, r.amount)
private def toVanillaBurn(b: PBInvokeScriptResult.Burn) =
Burn(b.assetId.toByteStr, b.amount)
private def toVanillaSponsorFee(sf: PBInvokeScriptResult.SponsorFee) = {
val amount = sf.minFee.get
SponsorFee(amount.assetId.toByteStr, Some(amount.amount).filter(_ > 0))
}
private def toVanillaLease(l: PBInvokeScriptResult.Lease) = {
val recipient = PBRecipients.toAddressOrAlias(l.getRecipient, AddressScheme.current.chainId).explicitGet()
Lease(recipient, l.amount, l.nonce, l.leaseId.toByteStr)
}
private def toVanillaLeaseCancel(sf: PBInvokeScriptResult.LeaseCancel) =
LeaseCancel(sf.leaseId.toByteStr)
private def toVanillaErrorMessage(b: PBInvokeScriptResult.ErrorMessage) =
ErrorMessage(b.code, b.text)
def fromPB(pbValue: PBInvokeScriptResult): InvokeScriptResult = {
InvokeScriptResult(
pbValue.data.map(PBTransactions.toVanillaDataEntry),
pbValue.transfers.map { p =>
val (asset, amount) = PBAmounts.toAssetAndAmount(p.getAmount)
InvokeScriptResult.Payment(PBRecipients.toAddress(p.address.toByteArray, AddressScheme.current.chainId).explicitGet(), asset, amount)
},
pbValue.issues.map(toVanillaIssue),
pbValue.reissues.map(toVanillaReissue),
pbValue.burns.map(toVanillaBurn),
pbValue.sponsorFees.map(toVanillaSponsorFee),
pbValue.leases.map(toVanillaLease),
pbValue.leaseCancels.map(toVanillaLeaseCancel),
pbValue.invokes.map(toVanillaInvocation),
pbValue.errorMessage.map(toVanillaErrorMessage)
)
}
case class ErrorMessage(code: Int, text: String)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy