diff --git a/pom.xml b/pom.xml index 698d326f5..3b93cc50d 100644 --- a/pom.xml +++ b/pom.xml @@ -293,7 +293,7 @@ 2.14.2 12.3 2.0.7 - 5.10.0 + 5.10.1 32.1.1-jre @@ -375,7 +375,7 @@ maven-source-plugin - 3.1.0 + 3.3.0 attach-sources diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/LedgerEntryResult.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/LedgerEntryResult.java index 49d71c663..247a1b888 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/LedgerEntryResult.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/client/ledger/LedgerEntryResult.java @@ -26,6 +26,8 @@ public interface LedgerEntryResult extends XrplResult { /** * Construct a {@code LedgerEntryResult} builder. * + * @param The type of {@link LedgerObject}. + * * @return An {@link ImmutableLedgerEntryResult.Builder}. */ static ImmutableLedgerEntryResult.Builder builder() { diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/BaseFeeDropsDeserializer.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/BaseFeeDropsDeserializer.java new file mode 100644 index 000000000..e7d282fe0 --- /dev/null +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/jackson/modules/BaseFeeDropsDeserializer.java @@ -0,0 +1,61 @@ +package org.xrpl.xrpl4j.model.jackson.modules; + +/*- + * ========================LICENSE_START================================= + * xrpl4j :: model + * %% + * Copyright (C) 2020 - 2022 XRPL Foundation and its contributors + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * =========================LICENSE_END================================== + */ + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.google.common.primitives.UnsignedLong; +import org.xrpl.xrpl4j.model.transactions.SetFee; +import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; + +import java.io.IOException; + +/** + * Custom Jackson deserializer for {@link XrpCurrencyAmount} instances found in {@link SetFee}. + * + *

Before the XRPFees amendment, a {@link SetFee} + * transaction serializes its `BaseFee` to a hex string. After the + * XRPFees amendment, a {@link SetFee} transaction + * serializes its `BaseFee` to a decimal string. + * + * @see "https://xrpl.org/resources/known-amendments/#xrpfees" + */ +public class BaseFeeDropsDeserializer extends StdDeserializer { + + /** + * No-args constructor. + */ + public BaseFeeDropsDeserializer() { + super(XrpCurrencyAmount.class); + } + + @Override + public XrpCurrencyAmount deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException { + // Pre-XRPFees, SetFee transactions serialize `BaseFee` to a hex string. Post XRPFees SetFee transactions + // have a `BaseFeeDrops` field which is a decimal string. + if (jsonParser.currentName().equals("BaseFee")) { + return XrpCurrencyAmount.of(UnsignedLong.valueOf(jsonParser.getText(), 16)); + } else { + return XrpCurrencyAmount.ofDrops(jsonParser.getValueAsLong()); + } + } +} diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/SetFee.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/SetFee.java index 65178f352..8b7ee2412 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/SetFee.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/SetFee.java @@ -9,9 +9,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,12 +20,15 @@ * =========================LICENSE_END================================== */ +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.primitives.UnsignedInteger; import org.immutables.value.Value; import org.xrpl.xrpl4j.model.client.common.LedgerIndex; +import org.xrpl.xrpl4j.model.jackson.modules.BaseFeeDropsDeserializer; import java.util.Optional; @@ -53,34 +56,92 @@ static ImmutableSetFee.Builder builder() { * The charge, in drops of XRP, for the reference transaction, as hex. (This is the transaction cost before scaling * for load.) * - * @return A hex {@link String} basefee value. + *

This method only exists for historical purposes. When deserialized from a {@link SetFee} transaction from + * ledgers prior to the {@code XRPFees} amendment, this field will still be set based on {@link #baseFeeDrops()}. + * + * @return A hex {@link String} baseFee value. + */ + @Value.Derived + @JsonIgnore + default String baseFee() { + return baseFeeDrops().value().toString(16); + } + + /** + * The charge, in drops of XRP, for the reference transaction (This is the transaction cost before scaling for load). + * + *

This field will also be populated with the {@code BaseFee} value from any {@link SetFee} transactions + * that occurred before the XRPFees amendment took effect.

+ * + * @return An {@link XrpCurrencyAmount}. */ - @JsonProperty("BaseFee") - String baseFee(); + @JsonProperty("BaseFeeDrops") + @JsonAlias("BaseFee") + @JsonDeserialize(using = BaseFeeDropsDeserializer.class) + XrpCurrencyAmount baseFeeDrops(); /** - * The cost, in fee units, of the reference transaction. + * The cost, in fee units, of the reference transaction. {@link SetFee} transactions deserialized from ledgers prior + * to the {@code XRPFees} amendment will always have this field, but transactions deserialized from ledgers post + * {@code XRPFees} activation will never have this field. * * @return An {@link UnsignedInteger} cost of ref transaction. */ @JsonProperty("ReferenceFeeUnits") - UnsignedInteger referenceFeeUnits(); + Optional referenceFeeUnits(); /** * The base reserve, in drops. * - * @return An {@link UnsignedInteger} base reverse value in {@link org.xrpl.xrpl4j.model.client.fees.FeeDrops}. + *

This method only exists for historical purposes. When deserialized from a {@link SetFee} transaction from + * ledgers prior to the {@code XRPFees} amendment, this field will still be set based on {@link #reserveBaseDrops()}}. + * + * @return An {@link UnsignedInteger} base reserve value in {@link org.xrpl.xrpl4j.model.client.fees.FeeDrops}. */ - @JsonProperty("ReserveBase") - UnsignedInteger reserveBase(); + @Value.Derived + @JsonIgnore + default UnsignedInteger reserveBase() { + return UnsignedInteger.valueOf(reserveBaseDrops().value().longValue()); + } + + /** + * The base reserve, as an {@link XrpCurrencyAmount}. + * + *

This field will also be populated with the {@code ReserveBase} value from any {@link SetFee} transactions + * that occurred before the XRPFees amendment took effect.

+ * + * @return An {@link XrpCurrencyAmount}. + */ + @JsonProperty("ReserveBaseDrops") + @JsonAlias("ReserveBase") + XrpCurrencyAmount reserveBaseDrops(); /** * The incremental reserve, in drops. * + *

This method only exists for historical purposes. When deserialized from a {@link SetFee} transaction from + * ledgers prior to the {@code XRPFees} amendment, this field will still be set based on + * {@link #reserveIncrementDrops()}. + * * @return An {@link UnsignedInteger} incremental reserve in {@link org.xrpl.xrpl4j.model.client.fees.FeeDrops}. */ - @JsonProperty("ReserveIncrement") - UnsignedInteger reserveIncrement(); + @Value.Derived + @JsonIgnore + default UnsignedInteger reserveIncrement() { + return UnsignedInteger.valueOf(reserveIncrementDrops().value().longValue()); + } + + /** + * The incremental reserve, as an {@link XrpCurrencyAmount}. + * + *

This field will also be populated with the {@code ReserveIncrement} value from any {@link SetFee} transactions + * that occurred before the XRPFees amendment took effect.

+ * + * @return An {@link XrpCurrencyAmount}. + */ + @JsonProperty("ReserveIncrementDrops") + @JsonAlias("ReserveIncrement") + XrpCurrencyAmount reserveIncrementDrops(); /** * The index of the ledger version where this pseudo-transaction appears. This distinguishes the pseudo-transaction diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/SetFeeTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/SetFeeTest.java index dbf5c3118..42018706f 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/SetFeeTest.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/SetFeeTest.java @@ -22,25 +22,57 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedLong; +import org.json.JSONException; import org.junit.jupiter.api.Test; +import org.xrpl.xrpl4j.model.AbstractJsonTest; import org.xrpl.xrpl4j.model.client.common.LedgerIndex; import java.util.Optional; -public class SetFeeTest { +/** + * Unit tests for {@link SetFee}. + */ +public class SetFeeTest extends AbstractJsonTest { + + @Test + public void testConstructWithNoFeeUnits() { + SetFee setFee = SetFee.builder() + .account(Address.of("rrrrrrrrrrrrrrrrrrrrrhoLvTp")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.valueOf(2470665)) + .baseFeeDrops(XrpCurrencyAmount.ofDrops(10)) + .reserveBaseDrops(XrpCurrencyAmount.ofDrops(20000000)) + .reserveIncrementDrops(XrpCurrencyAmount.ofDrops(5000000)) + .ledgerSequence(Optional.of(LedgerIndex.of(UnsignedInteger.valueOf(67850752)))) + .build(); + + assertThat(setFee.transactionType()).isEqualTo(TransactionType.SET_FEE); + assertThat(setFee.account()).isEqualTo(Address.of("rrrrrrrrrrrrrrrrrrrrrhoLvTp")); + assertThat(setFee.fee().value()).isEqualTo(UnsignedLong.valueOf(12)); + assertThat(setFee.sequence()).isEqualTo(UnsignedInteger.valueOf(2470665)); + assertThat(setFee.ledgerSequence()).isNotEmpty().get().isEqualTo(LedgerIndex.of(UnsignedInteger.valueOf(67850752))); + assertThat(setFee.baseFee()).isEqualTo("a"); + assertThat(setFee.baseFeeDrops()).isEqualTo(XrpCurrencyAmount.ofDrops(10)); + assertThat(setFee.referenceFeeUnits()).isEmpty(); + assertThat(setFee.reserveIncrement()).isEqualTo(UnsignedInteger.valueOf(5000000)); + assertThat(setFee.reserveIncrementDrops()).isEqualTo(XrpCurrencyAmount.ofDrops(5000000)); + assertThat(setFee.reserveBase()).isEqualTo(UnsignedInteger.valueOf(20000000)); + assertThat(setFee.reserveBaseDrops()).isEqualTo(XrpCurrencyAmount.ofDrops(20000000)); + } @Test - public void testBuilder() { + public void testConstructWithFeeUnits() { SetFee setFee = SetFee.builder() .account(Address.of("rrrrrrrrrrrrrrrrrrrrrhoLvTp")) .fee(XrpCurrencyAmount.ofDrops(12)) .sequence(UnsignedInteger.valueOf(2470665)) - .baseFee("000000000000000A") + .baseFeeDrops(XrpCurrencyAmount.ofDrops(10)) + .reserveBaseDrops(XrpCurrencyAmount.ofDrops(20000000)) + .reserveIncrementDrops(XrpCurrencyAmount.ofDrops(5000000)) .referenceFeeUnits(UnsignedInteger.valueOf(10)) - .reserveBase(UnsignedInteger.valueOf(20000000)) - .reserveIncrement(UnsignedInteger.valueOf(5000000)) .ledgerSequence(Optional.of(LedgerIndex.of(UnsignedInteger.valueOf(67850752)))) .build(); @@ -49,8 +81,78 @@ public void testBuilder() { assertThat(setFee.fee().value()).isEqualTo(UnsignedLong.valueOf(12)); assertThat(setFee.sequence()).isEqualTo(UnsignedInteger.valueOf(2470665)); assertThat(setFee.ledgerSequence()).isNotEmpty().get().isEqualTo(LedgerIndex.of(UnsignedInteger.valueOf(67850752))); - assertThat(setFee.referenceFeeUnits()).isEqualTo(UnsignedInteger.valueOf(10)); + assertThat(setFee.baseFee()).isEqualTo("a"); + assertThat(setFee.baseFeeDrops()).isEqualTo(XrpCurrencyAmount.ofDrops(10)); + assertThat(setFee.referenceFeeUnits()).isNotEmpty().get().isEqualTo(UnsignedInteger.valueOf(10)); assertThat(setFee.reserveIncrement()).isEqualTo(UnsignedInteger.valueOf(5000000)); + assertThat(setFee.reserveIncrementDrops()).isEqualTo(XrpCurrencyAmount.ofDrops(5000000)); assertThat(setFee.reserveBase()).isEqualTo(UnsignedInteger.valueOf(20000000)); + assertThat(setFee.reserveBaseDrops()).isEqualTo(XrpCurrencyAmount.ofDrops(20000000)); + } + + @Test + public void testDeserializePreXrpFeesTransaction() throws JsonProcessingException { + SetFee expected = SetFee.builder() + .account(Address.of("rrrrrrrrrrrrrrrrrrrrrhoLvTp")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.valueOf(2470665)) + .baseFeeDrops(XrpCurrencyAmount.ofDrops(10)) + .referenceFeeUnits(UnsignedInteger.valueOf(10)) + .reserveBaseDrops(XrpCurrencyAmount.ofDrops(20000000)) + .reserveIncrementDrops(XrpCurrencyAmount.ofDrops(5000000)) + .ledgerSequence(Optional.of(LedgerIndex.of(UnsignedInteger.valueOf(67850752)))) + .build(); + + String json = "{" + + "\"Account\":\"rrrrrrrrrrrrrrrrrrrrrhoLvTp\"," + + "\"Fee\":\"12\"," + + "\"LedgerSequence\":67850752," + + "\"Sequence\":2470665," + + "\"SigningPubKey\":\"\"," + + "\"TransactionType\":\"SetFee\"," + + "\"ReserveIncrement\":5000000," + + "\"ReserveBase\":20000000," + + "\"ReferenceFeeUnits\":10," + + "\"BaseFee\":\"a\"}"; + + Transaction actual = objectMapper.readValue(json, Transaction.class); + assertThat(actual).isEqualTo(expected); + + String reserialized = objectMapper.writeValueAsString(actual); + Transaction redeserialized = objectMapper.readValue(reserialized, Transaction.class); + + assertThat(redeserialized).isEqualTo(expected); + } + + @Test + public void testDeserializePostXrpFeesTransaction() throws JsonProcessingException { + SetFee expected = SetFee.builder() + .account(Address.of("rrrrrrrrrrrrrrrrrrrrrhoLvTp")) + .fee(XrpCurrencyAmount.ofDrops(0)) + .sequence(UnsignedInteger.valueOf(0)) + .baseFeeDrops(XrpCurrencyAmount.ofDrops(10)) + .reserveBaseDrops(XrpCurrencyAmount.ofDrops(10000000)) + .reserveIncrementDrops(XrpCurrencyAmount.ofDrops(2000000)) + .ledgerSequence(Optional.of(LedgerIndex.of(UnsignedInteger.valueOf(66462465)))) + .build(); + + String json = "{\n" + + " \"Account\": \"rrrrrrrrrrrrrrrrrrrrrhoLvTp\",\n" + + " \"BaseFeeDrops\": \"10\",\n" + + " \"Fee\": \"0\",\n" + + " \"LedgerSequence\": 66462465,\n" + + " \"ReserveBaseDrops\": \"10000000\",\n" + + " \"ReserveIncrementDrops\": \"2000000\",\n" + + " \"Sequence\": 0,\n" + + " \"SigningPubKey\": \"\",\n" + + " \"TransactionType\": \"SetFee\"}"; + + Transaction actual = objectMapper.readValue(json, Transaction.class); + assertThat(actual).isEqualTo(expected); + + String reserialized = objectMapper.writeValueAsString(actual); + Transaction redeserialized = objectMapper.readValue(reserialized, Transaction.class); + + assertThat(redeserialized).isEqualTo(expected); } } diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/SetFeeJsonTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/SetFeeJsonTests.java deleted file mode 100644 index 05e0fa594..000000000 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/SetFeeJsonTests.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.xrpl.xrpl4j.model.transactions.json; - -/*- - * ========================LICENSE_START================================= - * xrpl4j :: core - * %% - * Copyright (C) 2020 - 2023 XRPL Foundation and its contributors - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * =========================LICENSE_END================================== - */ - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.google.common.primitives.UnsignedInteger; -import org.json.JSONException; -import org.junit.jupiter.api.Test; -import org.xrpl.xrpl4j.model.AbstractJsonTest; -import org.xrpl.xrpl4j.model.client.common.LedgerIndex; -import org.xrpl.xrpl4j.model.transactions.Address; -import org.xrpl.xrpl4j.model.transactions.SetFee; -import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; - -import java.util.Optional; - -public class SetFeeJsonTests extends AbstractJsonTest { - - @Test - public void testJson() throws JsonProcessingException, JSONException { - SetFee setFee = SetFee.builder() - .account(Address.of("rrrrrrrrrrrrrrrrrrrrrhoLvTp")) - .fee(XrpCurrencyAmount.ofDrops(12)) - .sequence(UnsignedInteger.valueOf(2470665)) - .baseFee("000000000000000A") - .referenceFeeUnits(UnsignedInteger.valueOf(10)) - .reserveBase(UnsignedInteger.valueOf(20000000)) - .reserveIncrement(UnsignedInteger.valueOf(5000000)) - .ledgerSequence(Optional.of(LedgerIndex.of(UnsignedInteger.valueOf(67850752)))) - .build(); - - String json = "{" + - "\"Account\":\"rrrrrrrrrrrrrrrrrrrrrhoLvTp\"," + - "\"Fee\":\"12\"," + - "\"LedgerSequence\":67850752," + - "\"Sequence\":2470665," + - "\"SigningPubKey\":\"\"," + - "\"TransactionType\":\"SetFee\"," + - "\"ReserveIncrement\":5000000," + - "\"ReserveBase\":20000000," + - "\"ReferenceFeeUnits\":10," + - "\"BaseFee\":\"000000000000000A\"}"; - - assertCanSerializeAndDeserialize(setFee, json); - } -}