From 753418054e740e3d8ec381f749896190495c061d Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Wed, 10 Nov 2021 10:16:16 -0800 Subject: [PATCH] #374: fix npe on liquidity pool trustline in changetrustresponse --- src/main/java/org/stellar/sdk/Asset.java | 36 ++++++++-- .../org/stellar/sdk/AssetTypePoolShare.java | 70 +++++++++++++++++++ .../sdk/responses/AssetDeserializer.java | 24 ++++--- .../ChangeTrustOperationResponse.java | 11 ++- .../responses/OperationDeserializerTest.java | 41 +++++++++++ 5 files changed, 161 insertions(+), 21 deletions(-) create mode 100644 src/main/java/org/stellar/sdk/AssetTypePoolShare.java diff --git a/src/main/java/org/stellar/sdk/Asset.java b/src/main/java/org/stellar/sdk/Asset.java index 4f8624d53..bb5f91061 100644 --- a/src/main/java/org/stellar/sdk/Asset.java +++ b/src/main/java/org/stellar/sdk/Asset.java @@ -9,9 +9,11 @@ public abstract class Asset implements Comparable { /** * Parses an asset string and returns the equivalent Asset instance. - * The asset string is expected to either be "native" or a string of the form "CODE:ISSUER" + * The asset string is expected to either be "native" or a string of Alpha4 or Alpha12 + * asset code as "CODE:ISSUER" * - * @param canonicalForm Canonical string representation of an asset + * @param canonicalForm Canonical string representation of an Alpha4 or Alpha12 asset + * @return Asset or throws IllegalArgumentException if not Alpha4 or Alpha12 asset code */ public static Asset create(String canonicalForm) { if (canonicalForm.equals("native")) { @@ -24,12 +26,36 @@ public static Asset create(String canonicalForm) { return Asset.createNonNativeAsset(parts[0], parts[1]); } + /** + * Creates Asset for Alpha4/Alpha5/Native + * + * @param type the type of asset can be 'native', 'alpha4', 'alpha12' + * @param code the asset code that conforms to type or null + * @param issuer the asset issuer the conforms to type or null + * @return + */ public static Asset create(String type, String code, String issuer) { + return create(type, code, issuer, null); + } + + /** + * Creates Asset for Alpha4/Alpha5/Native/LiquidityPool + * + * @param type the type of asset can be 'native', 'alpha4', 'alpha12' or 'liquidity_pool_shares' + * @param code the asset code that conforms to type or null + * @param issuer the asset issuer the conforms to type or null + * @param liquidityPoolID provided only if type is 'liquidity_pool_shares' + * @return Asset + */ + public static Asset create(String type, String code, String issuer, String liquidityPoolID) { if (type.equals("native")) { return new AssetTypeNative(); - } else { - return Asset.createNonNativeAsset(code, issuer); } + if (type.equals("liquidity_pool_shares")) { + return new AssetTypePoolShare(liquidityPoolID); + } + + return Asset.createNonNativeAsset(code, issuer); } public static Asset create(ChangeTrustAsset.Wrapper wrapped) { @@ -82,7 +108,7 @@ public static Asset fromXdr(org.stellar.sdk.xdr.Asset xdr) { *
  • native
  • *
  • credit_alphanum4
  • *
  • credit_alphanum12
  • - *
  • pool_share
  • + *
  • liquidity_pool_shares
  • * */ public abstract String getType(); diff --git a/src/main/java/org/stellar/sdk/AssetTypePoolShare.java b/src/main/java/org/stellar/sdk/AssetTypePoolShare.java new file mode 100644 index 000000000..05279b5b0 --- /dev/null +++ b/src/main/java/org/stellar/sdk/AssetTypePoolShare.java @@ -0,0 +1,70 @@ +package org.stellar.sdk; + +import com.google.common.base.Objects; + +/** + * Represents Stellar liquidity pool share asset - lumens (XLM) + * @see Assets + */ +public final class AssetTypePoolShare extends Asset { + + private final String poolId; + + public AssetTypePoolShare(String poolId) { + this.poolId = poolId; + } + + @Override + public String toString() { + return "liquidity_pool_shares"; + } + + @Override + public String getType() { + return "liquidity_pool_shares"; + } + + @Override + public boolean equals(Object object) { + if (object == null || !this.getClass().equals(object.getClass())) { + return false; + } + + return (Objects.equal(((AssetTypePoolShare)object).getPoolId(), poolId)); + } + + @Override + public int hashCode() { + return Objects.hashCode(poolId); + } + + @Override + public org.stellar.sdk.xdr.Asset toXdr() { + throw new UnsupportedOperationException("liquidity_pool_shares are not defined as Asset in XDR"); + } + + @Override + public int compareTo(Asset other) { + if (other == null || !this.getClass().equals(other.getClass())) { + return -1; + } + + AssetTypePoolShare otherPoolShare = (AssetTypePoolShare)other; + + if (poolId == null && otherPoolShare.getPoolId() == null) { + return 0; + } + + if (poolId == null) { + return -1; + } + + if (otherPoolShare.getPoolId() == null) { + return 1; + } + + return poolId.compareTo(otherPoolShare.getPoolId()); + } + + public String getPoolId() { return poolId; } +} diff --git a/src/main/java/org/stellar/sdk/responses/AssetDeserializer.java b/src/main/java/org/stellar/sdk/responses/AssetDeserializer.java index 4cd05f66a..635905f5c 100644 --- a/src/main/java/org/stellar/sdk/responses/AssetDeserializer.java +++ b/src/main/java/org/stellar/sdk/responses/AssetDeserializer.java @@ -1,15 +1,16 @@ package org.stellar.sdk.responses; +import com.google.common.base.Function; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonParseException; - import org.stellar.sdk.Asset; -import org.stellar.sdk.AssetTypeNative; import java.lang.reflect.Type; +import static com.google.common.base.Optional.fromNullable; + class AssetDeserializer implements JsonDeserializer { @Override public Asset deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { @@ -17,13 +18,18 @@ public Asset deserialize(JsonElement json, Type typeOfT, JsonDeserializationCont // Probably a canonical string return Asset.create(json.getAsString()); } - String type = json.getAsJsonObject().get("asset_type").getAsString(); - if (type.equals("native")) { - return new AssetTypeNative(); - } else { - String code = json.getAsJsonObject().get("asset_code").getAsString(); - String issuer = json.getAsJsonObject().get("asset_issuer").getAsString(); - return Asset.createNonNativeAsset(code, issuer); + + return Asset.create(json.getAsJsonObject().get("asset_type").getAsString(), + fromNullable(json.getAsJsonObject().get("asset_code")).transform(ToString.FUNCTION).orNull(), + fromNullable(json.getAsJsonObject().get("asset_issuer")).transform(ToString.FUNCTION).orNull(), + fromNullable(json.getAsJsonObject().get("liquidity_pool_id")).transform(ToString.FUNCTION).orNull()); + } + + enum ToString implements Function { + FUNCTION; + @Override + public String apply(JsonElement input) { + return input.getAsString(); } } } diff --git a/src/main/java/org/stellar/sdk/responses/operations/ChangeTrustOperationResponse.java b/src/main/java/org/stellar/sdk/responses/operations/ChangeTrustOperationResponse.java index 2cb33acef..a099acbbf 100644 --- a/src/main/java/org/stellar/sdk/responses/operations/ChangeTrustOperationResponse.java +++ b/src/main/java/org/stellar/sdk/responses/operations/ChangeTrustOperationResponse.java @@ -2,9 +2,7 @@ import com.google.common.base.Optional; import com.google.gson.annotations.SerializedName; - import org.stellar.sdk.Asset; -import org.stellar.sdk.AssetTypeNative; import org.stellar.sdk.responses.MuxedAccount; /** @@ -30,6 +28,8 @@ public class ChangeTrustOperationResponse extends OperationResponse { private String assetIssuer; @SerializedName("limit") private String limit; + @SerializedName("liquidity_pool_id") + private String liquidityPoolId; public Optional getTrustorMuxed() { if (this.trustorMuxed == null || this.trustorMuxed.isEmpty()) { @@ -51,10 +51,7 @@ public String getLimit() { } public Asset getAsset() { - if (assetType.equals("native")) { - return new AssetTypeNative(); - } else { - return Asset.createNonNativeAsset(assetCode, assetIssuer); - } + return Asset.create(assetType, assetCode, assetIssuer, liquidityPoolId); } + } diff --git a/src/test/java/org/stellar/sdk/responses/OperationDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/OperationDeserializerTest.java index 0d7fda458..e40e20b07 100644 --- a/src/test/java/org/stellar/sdk/responses/OperationDeserializerTest.java +++ b/src/test/java/org/stellar/sdk/responses/OperationDeserializerTest.java @@ -366,6 +366,7 @@ public void testDeserializeChangeTrustOperation() { ChangeTrustOperationResponse operation = (ChangeTrustOperationResponse) GsonSingleton.getInstance().fromJson(json, OperationResponse.class); + operation.getAsset(); assertEquals(operation.getTrustee(), "GDIROJW2YHMSFZJJ4R5XWWNUVND5I45YEWS5DSFKXCHMADZ5V374U2LM"); assertEquals(operation.getTrustor(), "GDZ55LVXECRTW4G36EZPTHI4XIYS5JUC33TUS22UOETVFVOQ77JXWY4F"); assertEquals(operation.getLimit(), "922337203685.4775807"); @@ -373,6 +374,46 @@ public void testDeserializeChangeTrustOperation() { assertFalse(operation.getTrustorMuxed().isPresent()); } + @Test + public void testDeserializeChangeTrustOperationLiquidityPoolShares() { + String json = "{\n" + + " \"_links\": {\n" + + " \"self\": {\n" + + " \"href\": \"//horizon-testnet.stellar.org/operations/3602970755207169\"\n" + + " },\n" + + " \"transaction\": {\n" + + " \"href\": \"//horizon-testnet.stellar.org/transactions/8d409a788543895843d269c3f97a2d6a2ebca6e9f8f9a7ae593457b5c0ba6644\"\n" + + " },\n" + + " \"effects\": {\n" + + " \"href\": \"//horizon-testnet.stellar.org/operations/3602970755207169/effects\"\n" + + " },\n" + + " \"succeeds\": {\n" + + " \"href\": \"//horizon-testnet.stellar.org/effects?order=desc\\u0026cursor=3602970755207169\"\n" + + " },\n" + + " \"precedes\": {\n" + + " \"href\": \"//horizon-testnet.stellar.org/effects?order=asc\\u0026cursor=3602970755207169\"\n" + + " }\n" + + " },\n" + + " \"id\": \"3602970755207169\",\n" + + " \"paging_token\": \"3602970755207169\",\n" + + " \"source_account\": \"GDZ55LVXECRTW4G36EZPTHI4XIYS5JUC33TUS22UOETVFVOQ77JXWY4F\",\n" + + " \"type\": \"change_trust\",\n" + + " \"type_i\": 6,\n" + + " \"asset_type\": \"liquidity_pool_shares\",\n" + + " \"liquidity_pool_id\": \"02449937ed825805b7a945bb6c027b53dfaf140983c1a1a64c42a81edd89b5e0\",\n" + + " \"limit\": \"5.0000000\",\n" + + " \"trustee\": \"GDIROJW2YHMSFZJJ4R5XWWNUVND5I45YEWS5DSFKXCHMADZ5V374U2LM\",\n" + + " \"trustor\": \"GDZ55LVXECRTW4G36EZPTHI4XIYS5JUC33TUS22UOETVFVOQ77JXWY4F\"\n" + + " }"; + + ChangeTrustOperationResponse operation = (ChangeTrustOperationResponse) GsonSingleton.getInstance().fromJson(json, OperationResponse.class); + + assertEquals(operation.getTrustee(), "GDIROJW2YHMSFZJJ4R5XWWNUVND5I45YEWS5DSFKXCHMADZ5V374U2LM"); + assertEquals(operation.getTrustor(), "GDZ55LVXECRTW4G36EZPTHI4XIYS5JUC33TUS22UOETVFVOQ77JXWY4F"); + assertEquals(operation.getLimit(), "5.0000000"); + assertEquals(((AssetTypePoolShare)operation.getAsset()).getPoolId(), "02449937ed825805b7a945bb6c027b53dfaf140983c1a1a64c42a81edd89b5e0"); + } + @Test public void testDeserializeMuxedChangeTrustOperation() { String json = "{\n" +