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

Commit

Permalink
jem/20210502 core17 (#435)
Browse files Browse the repository at this point in the history
* Upgrade to core v17

* Add parser for EffectClaimableBalanceClawedBack

* Introduce ClawBackOperation

* Add ClawBackClaimableBalanceOperation

* Allow setting and clearing of trustline flags
  • Loading branch information
Synesso committed May 2, 2021
1 parent 92a5a21 commit a24a2d1
Show file tree
Hide file tree
Showing 11 changed files with 292 additions and 27 deletions.
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@

As this project is pre 1.0, breaking changes may happen for minor version bumps. A breaking change will get clearly notified in this log.

## Next version
## Next Version

## 0.20.0

- Updated for core protocol v17/CAP35:
- Added EffectTrustLineFlagsUpdated
- Added EffectClaimableBalanceClawedBack
- Added ClawBackOperation
- Added ClawBackClaimableBalanceOperation
- Added SetTrustLineFlagsOperation
- Auth challenge transactions can now specify separate web auth & home domains. The home domain is the FQDN that hosts
the relevant `stellar.toml` file for the authenticating service. The web auth domain is the FQDN of the
authenticating service itself. Implements SEP-0010 v3.1.
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# [Stellar SDK for Scala](https://synesso.github.io/scala-stellar-sdk/)

![Build](https://github.com/Synesso/scala-stellar-sdk/workflows/Build/badge.svg?branch=master)
[![Build](https://github.com/Synesso/scala-stellar-sdk/workflows/Build/badge.svg?branch=master)](https://github.com/Synesso/scala-stellar-sdk/actions/workflows/scala.yml)
[![Coverage](https://img.shields.io/codecov/c/gh/Synesso/scala-stellar-sdk.svg)](https://codecov.io/gh/Synesso/scala-stellar-sdk)
[![Issues](https://img.shields.io/github/issues/Synesso/scala-stellar-sdk.svg)](https://github.com/Synesso/scala-stellar-sdk/issues)
![Supports Stellar Horizon v1.14.0](https://img.shields.io/badge/Horizon-v1.14.0-blue.svg)
![Supports Stellar Core v15](https://img.shields.io/badge/Core-v15-blue.svg)
![Supports Stellar Horizon v2.2.0](https://img.shields.io/badge/Horizon-v2.2.0-blue.svg)
![Supports Stellar Core v17](https://img.shields.io/badge/Core-v17-blue.svg)
[![Scala Steward badge](https://img.shields.io/badge/Scala_Steward-helping-brightgreen.svg?style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAVFBMVEUAAACHjojlOy5NWlrKzcYRKjGFjIbp293YycuLa3pYY2LSqql4f3pCUFTgSjNodYRmcXUsPD/NTTbjRS+2jomhgnzNc223cGvZS0HaSD0XLjbaSjElhIr+AAAAAXRSTlMAQObYZgAAAHlJREFUCNdNyosOwyAIhWHAQS1Vt7a77/3fcxxdmv0xwmckutAR1nkm4ggbyEcg/wWmlGLDAA3oL50xi6fk5ffZ3E2E3QfZDCcCN2YtbEWZt+Drc6u6rlqv7Uk0LdKqqr5rk2UCRXOk0vmQKGfc94nOJyQjouF9H/wCc9gECEYfONoAAAAASUVORK5CYII=)](https://scala-steward.org)

With the Stellar SDK for Scala, you can perform [Stellar](https://stellar.org/) operations from your Scala application. It provides the ability to access Stellar networks via any Horizon instance to build and submit transactions, query the state of the network and stream updates. You'll like this SDK, because it provides a more natural API for Scala developers than the official Java SDK.
Expand All @@ -18,7 +18,7 @@ Add the JitPack & jcenter resolvers and the [latest dependency](https://jitpack.
```scala
resolvers += "jitpack" at "https://jitpack.io"
resolvers += Resolver.jcenterRepo
libraryDependencies += "com.github.synesso" %% "scala-stellar-sdk" % "0.19.2"
libraryDependencies += "com.github.synesso" %% "scala-stellar-sdk" % "0.20.0"
```

From there, it is a simple affair to create and fund a new account on the test network.
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ lazy val root = project
),
libraryDependencies ++= List(
"ch.qos.logback" % "logback-classic" % "1.2.3",
"com.github.synesso" % "stellar-xdr-jre" % "15.1.0.3",
"com.github.synesso" % "stellar-xdr-jre" % "17.0.0",
"com.softwaremill.retry" %% "retry" % "0.3.3",
"com.squareup.okhttp3" % "okhttp" % okhttp,
"com.squareup.okhttp3" % "logging-interceptor" % okhttp,
Expand Down
2 changes: 1 addition & 1 deletion src/it/bin/stellar_standalone.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/bash

CONTAINER=stellar/quickstart:latest
PROTOCOL_VERSION=15
PROTOCOL_VERSION=17

function container_started {
local state
Expand Down
131 changes: 124 additions & 7 deletions src/it/scala/stellar/sdk/LocalNetworkIntegrationSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ package stellar.sdk

import java.io.EOFException
import java.time.{Instant, Period}

import com.typesafe.scalalogging.LazyLogging
import okhttp3.HttpUrl
import okio.ByteString
import org.json4s.JsonDSL._
import org.specs2.concurrent.ExecutionEnv
import org.specs2.mutable.Specification
import org.stellar.xdr.TrustLineFlags
import stellar.sdk.inet.HorizonEntityNotFound
import stellar.sdk.model.Amount.lumens
import stellar.sdk.model.ClaimPredicate.{AbsolutelyBefore, Or, Unconditional}
Expand Down Expand Up @@ -40,12 +40,14 @@ class LocalNetworkIntegrationSpec(implicit ee: ExecutionEnv) extends Specificati
val accnC = KeyPair.fromPassphrase("account c")
val accnD = KeyPair.fromPassphrase("account d")
val accnE = KeyPair.fromPassphrase("account e")
val accnF = KeyPair.fromPassphrase("account f")

logger.debug(s"Account A = ${accnA.accountId}")
logger.debug(s"Account B = ${accnB.accountId}")
logger.debug(s"Account C = ${accnC.accountId}")
logger.debug(s"Account D = ${accnD.accountId}")
logger.debug(s"Account E = ${accnE.accountId}")
logger.debug(s"Account F = ${accnF.accountId}")

val accounts = Set(accnA, accnB, accnC, accnD)

Expand Down Expand Up @@ -101,6 +103,7 @@ class LocalNetworkIntegrationSpec(implicit ee: ExecutionEnv) extends Specificati
private val chinchillaA = Asset("Chinchilla", accnA)
private val chinchillaMaster = Asset("Chinchilla", masterAccountKey)
private val dachshundB = Asset("Dachshund", accnB)
private val clawbackAsset = Asset("XYZ", accnF)

// Transaction hashes. These will changed when setup operations change.
private val txnHash2 = "e13447898b27dbf278d4411022e2e6d0aae78ef70670c7af7834a1f2a6d191d8"
Expand All @@ -116,6 +119,7 @@ class LocalNetworkIntegrationSpec(implicit ee: ExecutionEnv) extends Specificati
CreateAccountOperation(accnB.toAccountId, lumens(1000)),
CreateAccountOperation(accnC.toAccountId, lumens(1000)),
CreateAccountOperation(accnD.toAccountId, lumens(1000)),
CreateAccountOperation(accnF.toAccountId, lumens(1000)),
WriteDataOperation("life_universe_everything", "42", Some(accnB)),
WriteDataOperation("brain the size of a planet", "and they ask me to open a door", Some(accnB)),
WriteDataOperation("fenton", "FENTON!", Some(accnC)),
Expand Down Expand Up @@ -272,6 +276,8 @@ class LocalNetworkIntegrationSpec(implicit ee: ExecutionEnv) extends Specificati
"list all assets" >> {
val eventualResps = network.assets().map(_.toSeq)
eventualResps must containTheSameElementsAs(Seq(
AssetResponse(aardvarkA, 0, 0, authRequired = true, authRevocable = true),
AssetResponse(beaverA, 0, 0, authRequired = true, authRevocable = true),
AssetResponse(chinchillaA, 101, 1, authRequired = true, authRevocable = true),
AssetResponse(chinchillaMaster, 101, 1, authRequired = false, authRevocable = false),
)).awaitFor(10 seconds)
Expand All @@ -285,7 +291,7 @@ class LocalNetworkIntegrationSpec(implicit ee: ExecutionEnv) extends Specificati

"filter assets by issuer" >> {
val byIssuer = network.assets(issuer = Some(accnA)).map(_.take(10).toList)
byIssuer.map(_.size) must beEqualTo(1).awaitFor(10 seconds)
byIssuer.map(_.size) must beEqualTo(3).awaitFor(10 seconds)
byIssuer.map(_.map(_.asset.issuer.accountId).distinct) must beEqualTo(Seq(accnA.accountId)).awaitFor(10 seconds)
}

Expand All @@ -300,7 +306,7 @@ class LocalNetworkIntegrationSpec(implicit ee: ExecutionEnv) extends Specificati
"effect endpoint" should {
"parse all effects" >> {
val effects = network.effects()
effects.map(_.size) must beEqualTo(240).awaitFor(10 seconds)
effects.map(_.size) must beEqualTo(246).awaitFor(10 seconds)
}

"filter effects by account" >> {
Expand Down Expand Up @@ -329,13 +335,16 @@ class LocalNetworkIntegrationSpec(implicit ee: ExecutionEnv) extends Specificati
EffectAccountDebited(_, _, accn1, amount1),
EffectAccountCredited(_, _, accn2, amount2),
EffectAccountRemoved(_, _, accn3),
EffectTrustLineDeauthorized(_, created, accn4, IssuedAsset12(code, accn5))
EffectTrustLineDeauthorized(_, _, accn4, IssuedAsset12(code, accn5)),
EffectTrustLineFlagsUpdated(_, _, accn6, asset5, false, false, false)
) =>
accn1 must beEquivalentTo(accnC)
accn2 must beEquivalentTo(accnB)
accn3 must beEquivalentTo(accnC)
accn4 must beEquivalentTo(accnB)
accn5 must beEquivalentTo(accnA)
accn6 must beEquivalentTo(accnB)
asset5 mustEqual aardvarkA
amount1 mustEqual lumens(1000)
amount2 mustEqual lumens(1000)
code mustEqual "Aardvark"
Expand Down Expand Up @@ -458,7 +467,7 @@ class LocalNetworkIntegrationSpec(implicit ee: ExecutionEnv) extends Specificati

"operation endpoint" should {
"list all operations" >> {
network.operations().map(_.size) must beEqualTo(131).awaitFor(10.seconds)
network.operations().map(_.size) must beEqualTo(132).awaitFor(10.seconds)
}

"list operations by account" >> {
Expand Down Expand Up @@ -595,8 +604,8 @@ class LocalNetworkIntegrationSpec(implicit ee: ExecutionEnv) extends Specificati
byAccount.map(_.head) must beLike[TransactionHistory] {
case t =>
t.account must beEquivalentTo(masterAccountKey)
t.feeCharged mustEqual NativeAmount(1700)
t.operationCount mustEqual 17
t.feeCharged mustEqual NativeAmount(1800)
t.operationCount mustEqual 18
t.memo mustEqual NoMemo
t.ledgerEntries must not(throwAn[EOFException])
t.feeLedgerEntries must not(throwAn[EOFException])
Expand Down Expand Up @@ -877,5 +886,113 @@ class LocalNetworkIntegrationSpec(implicit ee: ExecutionEnv) extends Specificati
}.awaitFor(5.seconds)
}
}

"Clawbacks" should {
"be used to revoke funds from an account" >> {
val setClawbackEnabled = for {
account <- network.account(masterAccountKey)
txn = Transaction(
source = account,
operations = List(
SetOptionsOperation(
setFlags = Some(Set(AuthorizationRevocableFlag, AuthorizationClawbackEnabledFlag)),
sourceAccount = Some(accnF)
),
ChangeTrustOperation(IssuedAmount(99_999L, clawbackAsset), Some(accnA)),
PaymentOperation(accnA.toAccountId, IssuedAmount(50_000L, clawbackAsset), Some(accnF))
),
timeBounds = TimeBounds.Unbounded,
maxFee = NativeAmount(1000)
).sign(masterAccountKey, accnF, accnA)
txnResult <- txn.submit()
} yield txnResult
setClawbackEnabled must beLike[TransactionPostResponse] { case r: TransactionApproved =>
r.isSuccess must beTrue
}.awaitFor(60.seconds)
network.account(accnA).map(_.balances) must beLike[List[Balance]] { balances =>
balances must contain(Balance(IssuedAmount(50_000L, clawbackAsset), Some(99_999L), authorized = true, authorizedToMaintainLiabilities = true))
}.awaitFor(3.seconds)

val clawbackTheFunds = for {
account <- network.account(accnF)
txn = Transaction(
source = account,
operations = List(
ClawBackOperation(accnA.toAccountId, IssuedAmount(40_000L, clawbackAsset), Some(accnF))
),
timeBounds = TimeBounds.Unbounded,
maxFee = NativeAmount(1000)
).sign(accnF)
txnResult <- txn.submit()
} yield txnResult
clawbackTheFunds must beLike[TransactionPostResponse] { case r: TransactionApproved =>
r.isSuccess must beTrue
}.awaitFor(60.seconds)

network.account(accnA).map(_.balances) must beLike[List[Balance]] { balances =>
balances must contain(Balance(IssuedAmount(10_000L, clawbackAsset), Some(99_999L), authorized = true, authorizedToMaintainLiabilities = true))
}.awaitFor(3.seconds)
}

"be able to claw back claimable balances" >> {
Await.result(for {
account <- network.account(accnF)
txn = Transaction(
source = account,
operations = List(
CreateClaimableBalanceOperation(
amount = Amount(1000, clawbackAsset),
claimants = List(AccountIdClaimant(accnA, Unconditional))
)
),
timeBounds = TimeBounds.Unbounded,
maxFee = NativeAmount(200)
).sign(accnF)
txnResult <- txn.submit()
} yield txnResult must beLike[TransactionPostResponse] { case r: TransactionApproved =>
r.isSuccess must beTrue
}, 60.seconds)

val clawbackTheFunds = for {
balanceId <- network.claimsByClaimant(accnA).map(_.head.id)
account <- network.account(accnF)
txn = Transaction(
source = account,
operations = List(ClawBackClaimableBalanceOperation(balanceId)),
timeBounds = TimeBounds.Unbounded,
maxFee = NativeAmount(1000)
).sign(accnF)
txnResult <- txn.submit()
} yield txnResult
clawbackTheFunds must beLike[TransactionPostResponse] { case r: TransactionApproved =>
r.isSuccess must beTrue
}.awaitFor(60.seconds)

network.claimsByAsset(dachshundB) must beEmpty[Seq[ClaimableBalance]].awaitFor(10.seconds)
}
}

"Trustline flags" should {
"be settable" >> {
(for {
account <- network.account(accnF)
txn = Transaction(
source = account,
operations = List(SetTrustLineFlagsOperation(
asset = clawbackAsset,
trustor = accnA,
setFlags = Set(TrustLineFlags.AUTHORIZED_FLAG),
clearFlags = Set.empty[TrustLineFlags]
)),
timeBounds = TimeBounds.Unbounded,
maxFee = NativeAmount(1000)
).sign(accnF)
txnResult <- txn.submit()
} yield txnResult) must beLike[TransactionPostResponse] { case r: TransactionApproved =>
r.isSuccess must beTrue
}.awaitFor(60.seconds)

}
}
}

4 changes: 2 additions & 2 deletions src/main/scala/stellar/sdk/model/Asset.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import okio.ByteString
import org.json4s.JsonAST.JValue
import org.json4s.{DefaultFormats, Formats}
import org.stellar.xdr.Asset.{AssetAlphaNum12, AssetAlphaNum4}
import org.stellar.xdr.{AllowTrustOp, AssetCode12, AssetCode4, AssetType, Asset => XAsset}
import org.stellar.xdr.{AllowTrustOp, AssetCode, AssetCode12, AssetCode4, AssetType, Asset => XAsset}
import stellar.sdk.util.ByteArrays._
import stellar.sdk.{KeyPair, PublicKeyOps}

Expand All @@ -16,7 +16,7 @@ sealed trait Asset {
}

object Asset {
def decodeCode(asset: AllowTrustOp.AllowTrustOpAsset): String = {
def decodeCode(asset: AssetCode): String = {
asset.getDiscriminant match {
case AssetType.ASSET_TYPE_CREDIT_ALPHANUM4 =>
paddedByteArrayToString(asset.getAssetCode4.getAssetCode4)
Expand Down
3 changes: 2 additions & 1 deletion src/main/scala/stellar/sdk/model/StrKey.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package stellar.sdk.model

import java.nio.ByteBuffer

import org.apache.commons.codec.binary.Base32
import org.stellar.xdr.{AccountID, CryptoKeyType, MuxedAccount, PublicKeyType, SignerKey, SignerKeyType, Uint256, Uint64, PublicKey => XPublicKey}
import stellar.sdk.model.StrKey.codec
import stellar.sdk.util.ByteArrays
import stellar.sdk.{KeyPair, PublicKey}

import scala.util.Try


/**
* A StrKey (Stellar Key) is a typed, encoded byte array.
Expand Down
5 changes: 4 additions & 1 deletion src/main/scala/stellar/sdk/model/ledger/LedgerEntry.scala
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,10 @@ object AccountEntry {
lx,
ext2.getV2.getNumSponsored.getUint32.toInt,
ext2.getV2.getNumSponsoring.getUint32.toInt,
ext2.getV2.getSignerSponsoringIDs.map(_.getSponsorshipDescriptor).map(AccountId.decodeXdr).toList
ext2.getV2.getSignerSponsoringIDs
.map(_.getSponsorshipDescriptor)
.filter(_ != null)
.map(AccountId.decodeXdr).toList
)
}

Expand Down
Loading

0 comments on commit a24a2d1

Please sign in to comment.