Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

Commit

Permalink
Parse assets from compact code (#408)
Browse files Browse the repository at this point in the history
* Parse assets from compact code

* parse trustline_sponsorship_created
  • Loading branch information
Synesso committed Feb 21, 2021
1 parent d9d82b8 commit a0ad729
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 14 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ As this project is pre 1.0, breaking changes may happen for minor version bumps.

## Next version

## 0.19.2

- Adds support for missing snapshot events, capturing those that are unrecognised into a catch all type so that streams can continue.
Match on `UnrecognisedEffect` in order to extract unknown values.

## 0.19.1

- [#389](https://github.com/Synesso/scala-stellar-sdk/issues/389) Adds support for Signer Sponsorship state snapshot events.
Expand Down
12 changes: 11 additions & 1 deletion src/main/scala/stellar/sdk/model/Asset.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ object Asset {
if (code.length <= 4) IssuedAsset4.of(code, issuer) else IssuedAsset12.of(code, issuer)
}

private val IssuedAssetRegex = "([a-zA-Z0-9]{1,12}):(G[A-Z0-9]{55})".r
def parseIssuedAsset(code: String): NonNativeAsset =
code match {
case IssuedAssetRegex(code, issuer) => apply(code, KeyPair.fromAccountId(issuer))
case _ => throw AssetException(s"Cannot parse issued asset [code=$code]")
}

def parseAsset(prefix: String = "", obj: JValue): Asset = {

def assetCode = (obj \ s"${prefix}asset_code").extract[String]
Expand All @@ -64,7 +71,7 @@ object Asset {
case "native" => NativeAsset
case "credit_alphanum4" => IssuedAsset4(assetCode, assetIssuer)
case "credit_alphanum12" => IssuedAsset12(assetCode, assetIssuer)
case t => throw new RuntimeException(s"Unrecognised asset type '$t'")
case t => throw AssetException(s"Unrecognised asset type '$t'")
}
}
}
Expand Down Expand Up @@ -132,3 +139,6 @@ case class IssuedAsset12 private (code: String, issuer: PublicKeyOps) extends No
object IssuedAsset12 {
def of(code: String, keyPair: PublicKeyOps): IssuedAsset12 = IssuedAsset12(code, keyPair.asPublicKey)
}

case class AssetException(msg: String) extends RuntimeException(msg)

3 changes: 0 additions & 3 deletions src/main/scala/stellar/sdk/model/FormatException.scala

This file was deleted.

44 changes: 34 additions & 10 deletions src/main/scala/stellar/sdk/model/response/EffectResponse.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import java.time.ZonedDateTime

import org.json4s.DefaultFormats
import org.json4s.JsonAST.JObject
import org.json4s.native.JsonMethods
import org.json4s.native.JsonMethods._
import stellar.sdk._
import stellar.sdk.model._
Expand Down Expand Up @@ -67,14 +68,18 @@ case class EffectTrustLineAuthorizedToMaintainLiabilities(id: String, createdAt:

case class EffectTrustLineDeauthorized(id: String, createdAt: ZonedDateTime, trustor: PublicKeyOps, asset: NonNativeAsset) extends EffectResponse

case class EffectTrustLineSponsorshipCreated(id: String, createdAt: ZonedDateTime, account: PublicKeyOps, asset: NonNativeAsset, sponsor: PublicKeyOps) extends EffectResponse

case class EffectTrade(id: String, createdAt: ZonedDateTime, offerId: Long, buyer: PublicKeyOps, bought: Amount, seller: PublicKeyOps, sold: Amount) extends EffectResponse

case class UnrecognisedEffect(id: String, createdAt: ZonedDateTime, json: String) extends EffectResponse

object EffectResponseDeserializer extends ResponseParser[EffectResponse]({ o: JObject =>
implicit val formats = DefaultFormats

def account(accountKey: String = "account") = KeyPair.fromAccountId((o \ accountKey).extract[String])

def asset(prefix: String = "", issuerKey: String = "asset_issuer") = {
def asset(prefix: String = "", issuerKey: String = "asset_issuer"): Asset = {
def assetCode = (o \ s"${prefix}asset_code").extract[String]

def assetIssuer = KeyPair.fromAccountId((o \ s"$prefix$issuerKey").extract[String])
Expand All @@ -87,6 +92,8 @@ object EffectResponseDeserializer extends ResponseParser[EffectResponse]({ o: JO
}
}

def issuedAsset(code: String = "asset"): NonNativeAsset = Asset.parseIssuedAsset((o \ code).extract[String])

def bigDecimal(key: String) = BigDecimal((o \ key).extract[String])

def amount(prefix: String = "", key: String = "amount") = {
Expand All @@ -107,24 +114,37 @@ object EffectResponseDeserializer extends ResponseParser[EffectResponse]({ o: JO
EffectAccountCreated(id, createdAt, account(), startingBalance)
case "account_credited" => EffectAccountCredited(id, createdAt, account(), amount())
case "account_debited" => EffectAccountDebited(id, createdAt, account(), amount())
case "account_flags_updated" => EffectAccountFlagsUpdated(id, createdAt, account())
case "account_home_domain_updated" => EffectAccountHomeDomainUpdated(id, createdAt, account(), (o \ "home_domain").extract[String])
case "account_inflation_destination_updated" => EffectAccountInflationDestinationUpdated(id, createdAt, account())
case "account_removed" => EffectAccountRemoved(id, createdAt, account())
case "account_sponsorship_created" => EffectAccountSponsorshipCreated(id, createdAt, account())
case "account_sponsorship_removed" => UnrecognisedEffect(id, createdAt, JsonMethods.compact(JsonMethods.render(o)))
case "account_sponsorship_updated" => UnrecognisedEffect(id, createdAt, JsonMethods.compact(JsonMethods.render(o)))
case "account_thresholds_updated" =>
val thresholds = Thresholds(
(o \ "low_threshold").extract[Int],
(o \ "med_threshold").extract[Int],
(o \ "high_threshold").extract[Int]
)
EffectAccountThresholdsUpdated(id, createdAt, account(), thresholds)
case "account_home_domain_updated" => EffectAccountHomeDomainUpdated(id, createdAt, account(), (o \ "home_domain").extract[String])
case "account_flags_updated" => EffectAccountFlagsUpdated(id, createdAt, account())
case "account_sponsorship_created" => EffectAccountSponsorshipCreated(id, createdAt, account())
case "claimable_balance_claimant_created" => UnrecognisedEffect(id, createdAt, JsonMethods.compact(JsonMethods.render(o)))
case "claimable_balance_claimed" => UnrecognisedEffect(id, createdAt, JsonMethods.compact(JsonMethods.render(o)))
case "claimable_balance_created" => UnrecognisedEffect(id, createdAt, JsonMethods.compact(JsonMethods.render(o)))
case "claimable_balance_sponsorship_created" => UnrecognisedEffect(id, createdAt, JsonMethods.compact(JsonMethods.render(o)))
case "claimable_balance_sponsorship_removed" => UnrecognisedEffect(id, createdAt, JsonMethods.compact(JsonMethods.render(o)))
case "claimable_balance_sponsorship_updated" => UnrecognisedEffect(id, createdAt, JsonMethods.compact(JsonMethods.render(o)))
case "data_created" => EffectDataCreated(id, createdAt, account())
case "data_removed" => EffectDataRemoved(id, createdAt, account())
case "data_sponsorship_created" => UnrecognisedEffect(id, createdAt, JsonMethods.compact(JsonMethods.render(o)))
case "data_sponsorship_removed" => UnrecognisedEffect(id, createdAt, JsonMethods.compact(JsonMethods.render(o)))
case "data_sponsorship_updated" => UnrecognisedEffect(id, createdAt, JsonMethods.compact(JsonMethods.render(o)))
case "data_updated" => EffectDataUpdated(id, createdAt, account())
case "offer_created" => UnrecognisedEffect(id, createdAt, JsonMethods.compact(JsonMethods.render(o)))
case "offer_removed" => UnrecognisedEffect(id, createdAt, JsonMethods.compact(JsonMethods.render(o)))
case "offer_updated" => UnrecognisedEffect(id, createdAt, JsonMethods.compact(JsonMethods.render(o)))
case "sequence_bumped" => EffectSequenceBumped(id, createdAt, account(), (o \ "new_seq").extract[String].toLong)
case "signer_created" => EffectSignerCreated(id, createdAt, account(), weight, (o \ "public_key").extract[String])
case "signer_updated" => EffectSignerUpdated(id, createdAt, account(), weight, (o \ "public_key").extract[String])
case "signer_removed" => EffectSignerRemoved(id, createdAt, account(), (o \ "public_key").extract[String])
case "signer_sponsorship_created" => EffectSignerSponsorshipCreated(id, createdAt, account(),
signer = account("signer"),
Expand All @@ -139,13 +159,17 @@ object EffectResponseDeserializer extends ResponseParser[EffectResponse]({ o: JO
formerSponsor = account("former_sponsor"),
newSponsor = account("new_sponsor")
)
case "trustline_created" => EffectTrustLineCreated(id, createdAt, account(), amount(key = "limit").asInstanceOf[IssuedAmount])
case "trustline_updated" => EffectTrustLineUpdated(id, createdAt, account(), amount(key = "limit").asInstanceOf[IssuedAmount])
case "trustline_removed" => EffectTrustLineRemoved(id, createdAt, account(), asset().asInstanceOf[NonNativeAsset])
case "signer_updated" => EffectSignerUpdated(id, createdAt, account(), weight, (o \ "public_key").extract[String])
case "trade" => EffectTrade(id, createdAt, (o \ "offer_id").extract[String].toLong, account(), amount("bought_"), account("seller"), amount("sold_"))
case "trustline_authorized" => EffectTrustLineAuthorized(id, createdAt, account("trustor"), asset(issuerKey = "account").asInstanceOf[NonNativeAsset])
case "trustline_authorized_to_maintain_liabilities" => EffectTrustLineAuthorizedToMaintainLiabilities(id, createdAt, account("trustor"), asset(issuerKey = "account").asInstanceOf[NonNativeAsset])
case "trustline_created" => EffectTrustLineCreated(id, createdAt, account(), amount(key = "limit").asInstanceOf[IssuedAmount])
case "trustline_deauthorized" => EffectTrustLineDeauthorized(id, createdAt, account("trustor"), asset(issuerKey = "account").asInstanceOf[NonNativeAsset])
case "trade" => EffectTrade(id, createdAt, (o \ "offer_id").extract[String].toLong, account(), amount("bought_"), account("seller"), amount("sold_"))
case t => throw new RuntimeException(s"Unrecognised effect type '$t': ${compact(render(o))}")
case "trustline_removed" => EffectTrustLineRemoved(id, createdAt, account(), asset().asInstanceOf[NonNativeAsset])
case "trustline_sponsorship_created" => EffectTrustLineSponsorshipCreated(id, createdAt, account(), issuedAsset(), account("sponsor"))
case "trustline_sponsorship_removed" => UnrecognisedEffect(id, createdAt, JsonMethods.compact(JsonMethods.render(o)))
case "trustline_sponsorship_updated" => UnrecognisedEffect(id, createdAt, JsonMethods.compact(JsonMethods.render(o)))
case "trustline_updated" => EffectTrustLineUpdated(id, createdAt, account(), amount(key = "limit").asInstanceOf[IssuedAmount])
case _ => UnrecognisedEffect(id, createdAt, JsonMethods.compact(JsonMethods.render(o)))
}
})
26 changes: 26 additions & 0 deletions src/test/scala/stellar/sdk/model/AssetSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,30 @@ class AssetSpec extends Specification with ArbitraryInput {
Asset.decodeXdr(asset.xdr) mustEqual asset
}
}

"parsing issued asset from compact code" should {
"correctly parse valid asset" >> prop { asset: NonNativeAsset =>
Asset.parseIssuedAsset(s"${asset.code}:${asset.issuer.accountId}") mustEqual asset
}

"fail to parse stand alone issuer" >> {
Try(Asset.parseIssuedAsset(KeyPair.random.accountId)) must beAFailedTry[NonNativeAsset]
}

"fail to parse when asset code is 0 chars" >> {
Try(Asset.parseIssuedAsset(s":${KeyPair.random.accountId}")) must beAFailedTry[NonNativeAsset]
}

"fail to parse when asset code is 13 chars" >> {
Try(Asset.parseIssuedAsset(s"abc123abc1230:${KeyPair.random.accountId}")) must beAFailedTry[NonNativeAsset]
}

"fail to parse when issuer is too long" >> {
Try(Asset.parseIssuedAsset(s"doge:${KeyPair.random.accountId}F")) must beAFailedTry[NonNativeAsset]
}

"fail to parse when issuer is invalid" >> {
Try(Asset.parseIssuedAsset(s"doge:${KeyPair.random.secretSeed.mkString}")) must beAFailedTry[NonNativeAsset]
}
}
}

0 comments on commit a0ad729

Please sign in to comment.