From 96650dd256774aabe485a3e73457510dcb868f3e Mon Sep 17 00:00:00 2001 From: nkramer44 Date: Mon, 14 Aug 2023 17:07:34 -0400 Subject: [PATCH] Allow for No-Op AccountSet SetFlag and ClearFlag values (#479) * fix AccountSet desrialization for ClearFlags or SetFlags with large numbers * use if-else * fix typos * add comment --- .../xrpl4j/model/transactions/AccountSet.java | 187 ++++++++++++++- .../model/transactions/AccountSetTests.java | 213 +++++++++++++++++- .../json/AccountSetJsonTests.java | 31 +++ 3 files changed, 418 insertions(+), 13 deletions(-) diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AccountSet.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AccountSet.java index ace5b6994..d558d1989 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AccountSet.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/AccountSet.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. @@ -21,6 +21,7 @@ */ import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -29,7 +30,6 @@ import com.google.common.primitives.UnsignedInteger; import org.immutables.value.Value; import org.xrpl.xrpl4j.model.flags.AccountSetTransactionFlags; -import org.xrpl.xrpl4j.model.flags.TransactionFlags; import java.util.Optional; @@ -65,25 +65,189 @@ default AccountSetTransactionFlags flags() { /** * Unique identifier of a flag to disable for this account. * + *

If this field is empty, developers should check if {@link #clearFlagRawValue()} is also empty. If + * {@link #clearFlagRawValue()} is present, it means that the {@code ClearFlag} field of the transaction was not a + * valid {@link AccountSetFlag} but was still present in a validated transaction on ledger.

+ * *

Because the preferred way of setting account flags is with {@link AccountSetFlag}s, this field should * not be set in conjunction with the {@link AccountSet#flags()} field. * * @return An {@link Optional} of type {@link AccountSetFlag} representing the flag to disable on this account. */ - @JsonProperty("ClearFlag") + @JsonIgnore Optional clearFlag(); + /** + * A flag to disable for this account, as an {@link UnsignedInteger}. + * + *

Developers should prefer setting {@link #clearFlag()} and leaving this field empty when constructing + * a new {@link AccountSet}. This field is used to serialize and deserialize the {@code "ClearFlag"} field in JSON, + * as some {@link AccountSet} transactions on the XRPL set the "ClearFlag" field to a number that is not recognized as + * an asf flag by rippled. Without this field, xrpl4j would fail to deserialize those transactions, as + * {@link AccountSetFlag} does not support arbitrary integer values.

+ * + *

Additionally, using this field as the source of truth for JSON serialization/deserialization rather than + * {@link #clearFlag()} allows developers to recompute the hash of a transaction that was deserialized from a rippled + * RPC/WS result accurately. An alternative to this field would be to add an enum variant to {@link AccountSetFlag} + * for unknown values, but binary serializing an {@link AccountSet} that was constructed by deserializing JSON would + * result in a different binary blob than what exists on ledger.

+ * + * @return An {@link Optional} {@link UnsignedInteger}. + */ + @JsonProperty("ClearFlag") + Optional clearFlagRawValue(); + + /** + * Normalization method to try to get {@link #clearFlag()}and {@link #clearFlagRawValue()} to match. + * + *

If neither field is present, there is nothing to do.

+ *

If both fields are present, there is nothing to do, but we will check that {@link #clearFlag()}'s + * underlying value equals {@link #clearFlagRawValue()}.

+ *

If {@link #clearFlag()} is present but {@link #clearFlagRawValue()} is empty, we set + * {@link #clearFlagRawValue()} to the underlying value of {@link #clearFlag()}.

+ *

If {@link #clearFlag()} is empty and {@link #clearFlagRawValue()} is present, we will set + * {@link #clearFlag()} to the {@link AccountSetFlag} variant associated with {@link #clearFlagRawValue()}, or leave + * {@link #clearFlag()} empty if {@link #clearFlagRawValue()} does not map to an {@link AccountSetFlag}.

+ * + * @return A normalized {@link AccountSet}. + */ + @Value.Check + default AccountSet normalizeClearFlag() { + if (!clearFlag().isPresent() && !clearFlagRawValue().isPresent()) { + // If both are empty, nothing to do. + return this; + } else if (clearFlag().isPresent() && clearFlagRawValue().isPresent()) { + // Both will be present if: + // 1. A developer set them both manually (in the builder) + // 2. This normalize method has already been called. + + // We should still check that the clearFlagRawValue matches the inner value of AccountSetFlag. + Preconditions.checkState( + clearFlag().get().getValue() == clearFlagRawValue().get().longValue(), + String.format("clearFlag and clearFlagRawValue should be equivalent, but clearFlag's underlying " + + "value was %s and clearFlagRawValue was %s", + clearFlag().get().getValue(), + clearFlagRawValue().get().longValue() + ) + ); + return this; + } else if (clearFlag().isPresent() && !clearFlagRawValue().isPresent()) { + // This can only happen if the developer only set clearFlag(). In this case, we need to set clearFlagRawValue to + // match clearFlag. + return AccountSet.builder().from(this) + .clearFlagRawValue(UnsignedInteger.valueOf(clearFlag().get().getValue())) + .build(); + } else { // clearFlag not present and clearFlagRawValue is present + // This can happen if: + // 1. A developer sets clearFlagRawValue manually in the builder + // 2. JSON has ClearFlag and jackson sets clearFlagRawValue. + // This value will never be negative due to XRPL representing this kind of flag as an unsigned number, + // so no lower bound check is required. + if (clearFlagRawValue().get().longValue() <= 15) { + // Set clearFlag to clearFlagRawValue if clearFlagRawValue matches a valid AccountSetFlag variant. + return AccountSet.builder().from(this) + .clearFlag(AccountSetFlag.forValue(clearFlagRawValue().get().intValue())) + .build(); + } else { + // Otherwise, leave clearFlag empty. + return this; + } + } + } + /** * Unique identifier of a flag to enable for this account. * - *

Because the preferred way of setting account flags is with {@link AccountSetFlag}s, this field should not be set - * in conjunction with the {@link AccountSet#flags()} field. + *

If this field is empty, developers should check if {@link #setFlagRawValue()} is also empty. If + * {@link #setFlagRawValue()} is present, it means that the {@code ClearFlag} field of the transaction was not a + * valid {@link AccountSetFlag} but was still present in a validated transaction on ledger.

+ * + *

Because the preferred way of setting account flags is with {@link AccountSetFlag}s, this field should not be + * set in conjunction with the {@link AccountSet#flags()} field. * * @return An {@link Optional} of type {@link AccountSetFlag} representing the flag to enable on this account. */ - @JsonProperty("SetFlag") + @JsonIgnore Optional setFlag(); + /** + * A flag to disable for this account, as an {@link UnsignedInteger}. + * + *

Developers should prefer setting {@link #setFlag()} and leaving this field empty when constructing + * a new {@link AccountSet}. This field is used to serialize and deserialize the {@code "ClearFlag"} field in JSON, + * as some {@link AccountSet} transactions on the XRPL set the "ClearFlag" field to a number that is not recognized as + * an asf flag by rippled. Without this field, xrpl4j would fail to deserialize those transactions, as + * {@link AccountSetFlag} does not support arbitrary integer values.

+ * + *

Additionally, using this field as the source of truth for JSON serialization/deserialization rather than + * {@link #setFlag()} allows developers to recompute the hash of a transaction that was deserialized from a rippled + * RPC/WS result accurately. An alternative to this field would be to add an enum variant to {@link AccountSetFlag} + * for unknown values, but binary serializing an {@link AccountSet} that was constructed by deserializing JSON would + * result in a different binary blob than what exists on ledger.

+ * + * @return An {@link Optional} {@link UnsignedInteger} + */ + @JsonProperty("SetFlag") + Optional setFlagRawValue(); + + /** + * Normalization method to try to get {@link #setFlag()}and {@link #setFlagRawValue()} to match. + * + *

If neither field is present, there is nothing to do.

+ *

If both fields are present, there is nothing to do, but we will check that {@link #setFlag()}'s + * underlying value equals {@link #setFlagRawValue()}.

+ *

If {@link #setFlag()} is present but {@link #setFlagRawValue()} is empty, we set + * {@link #setFlagRawValue()} to the underlying value of {@link #setFlag()}.

+ *

If {@link #setFlag()} is empty and {@link #setFlagRawValue()} is present, we will set + * {@link #setFlag()} to the {@link AccountSetFlag} variant associated with {@link #setFlagRawValue()}, or leave + * {@link #setFlag()} empty if {@link #setFlagRawValue()} does not map to an {@link AccountSetFlag}.

+ * + * @return A normalized {@link AccountSet}. + */ + @Value.Check + default AccountSet normalizeSetFlag() { + if (!setFlag().isPresent() && !setFlagRawValue().isPresent()) { + // If both are empty, nothing to do. + return this; + } else if (setFlag().isPresent() && setFlagRawValue().isPresent()) { + // Both will be present if: + // 1. A developer set them both manually (in the builder) + // 2. This normalize method has already been called. + + // We should still check that the setFlagRawValue matches the inner value of AccountSetFlag. + Preconditions.checkState( + setFlag().get().getValue() == setFlagRawValue().get().longValue(), + String.format("setFlag and setFlagRawValue should be equivalent, but setFlag's underlying " + + "value was %s and setFlagRawValue was %s", + setFlag().get().getValue(), + setFlagRawValue().get().longValue() + ) + ); + return this; + } else if (setFlag().isPresent() && !setFlagRawValue().isPresent()) { + // This can only happen if the developer only set setFlag(). In this case, we need to set setFlagRawValue to + // match setFlag. + return AccountSet.builder().from(this) + .setFlagRawValue(UnsignedInteger.valueOf(setFlag().get().getValue())) + .build(); + } else { // setFlag is empty and setFlagRawValue is present + // This can happen if: + // 1. A developer sets setFlagRawValue manually in the builder + // 2. JSON has ClearFlag and jackson sets setFlagRawValue. + // This value will never be negative due to XRPL representing this kind of flag as an unsigned number, + // so no lower bound check is required. + if (setFlagRawValue().get().longValue() <= 15) { + // Set setFlag to setFlagRawValue if setFlagRawValue matches a valid AccountSetFlag variant. + return AccountSet.builder().from(this) + .setFlag(AccountSetFlag.forValue(setFlagRawValue().get().intValue())) + .build(); + } else { + // Otherwise, leave setFlag empty. + return this; + } + } + } + /** * The hex string of the lowercase ASCII of the domain for the account. For example, the domain example.com would be * represented as "6578616D706C652E636F6D". @@ -132,8 +296,8 @@ default AccountSetTransactionFlags flags() { Optional tickSize(); /** - * Sets an alternate account that is allowed to mint NFTokens on this - * account's behalf using NFTokenMint's `Issuer` field. + * Sets an alternate account that is allowed to mint NFTokens on this account's behalf using NFTokenMint's `Issuer` + * field. * * @return An {@link Optional} field of type {@link Address}. */ @@ -206,8 +370,8 @@ default void checkTickSize() { */ enum AccountSetFlag { /** - * This flag will do nothing but exists to accurately deserialize AccountSet transactions whose {@code SetFlag} - * or {@code ClearFlag} fields are zero. + * This flag will do nothing but exists to accurately deserialize AccountSet transactions whose {@code SetFlag} or + * {@code ClearFlag} fields are zero. */ NONE(0), /** @@ -291,6 +455,7 @@ enum AccountSetFlag { * @param value The int value of the flag. * * @return The {@link AccountSetFlag} for the given integer value. + * * @see "https://github.com/FasterXML/jackson-databind/issues/1850" */ @JsonCreator diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AccountSetTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AccountSetTests.java index cdf4cb094..4d1991dfa 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AccountSetTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/AccountSetTests.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. @@ -21,15 +21,27 @@ */ import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertThrows; import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedLong; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.xrpl.xrpl4j.model.flags.AccountSetTransactionFlags; +import org.xrpl.xrpl4j.model.transactions.AccountSet.AccountSetFlag; + +import java.util.Arrays; +import java.util.stream.Stream; public class AccountSetTests { + public static Stream accountSetFlags() { + return Arrays.stream(AccountSetFlag.values()).map(Arguments::of); + } + @Test public void simpleAccountSet() { AccountSet accountSet = AccountSet.builder() @@ -49,12 +61,209 @@ public void simpleAccountSet() { assertThat(accountSet.sequence()).isEqualTo(UnsignedInteger.valueOf(5)); assertThat(accountSet.domain()).isNotEmpty().get().isEqualTo("6578616D706C652E636F6D"); assertThat(accountSet.setFlag()).isNotEmpty().get().isEqualTo(AccountSet.AccountSetFlag.ACCOUNT_TXN_ID); + assertThat(accountSet.setFlagRawValue()).isNotEmpty().get() + .isEqualTo(UnsignedInteger.valueOf(AccountSetFlag.ACCOUNT_TXN_ID.getValue())); assertThat(accountSet.messageKey()).isNotEmpty().get() .isEqualTo("03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB"); assertThat(accountSet.transferRate()).isNotEmpty().get().isEqualTo(UnsignedInteger.valueOf(1000000001)); assertThat(accountSet.flags().isEmpty()).isTrue(); } + @Test + void testWithEmptyClearFlagAndEmptyRawValue() { + AccountSet accountSet = AccountSet.builder() + .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.valueOf(5)) + .build(); + + assertThat(accountSet.clearFlag()).isEmpty(); + assertThat(accountSet.clearFlagRawValue()).isEmpty(); + } + + @ParameterizedTest + @MethodSource("accountSetFlags") + void testWithPresentClearFlagAndPresentRawValue(AccountSetFlag accountSetFlag) { + AccountSet accountSet = AccountSet.builder() + .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.valueOf(5)) + .clearFlag(accountSetFlag) + .clearFlagRawValue(UnsignedInteger.valueOf(accountSetFlag.getValue())) + .build(); + + assertThat(accountSet.clearFlag()).isNotEmpty().get().isEqualTo(accountSetFlag); + assertThat(accountSet.clearFlagRawValue()).isNotEmpty().get() + .isEqualTo(UnsignedInteger.valueOf(accountSetFlag.getValue())); + } + + @ParameterizedTest + @MethodSource("accountSetFlags") + void testWithPresentClearFlagAndPresentRawValueThrowsForMismatchedValues(AccountSetFlag accountSetFlag) { + assertThatThrownBy( + () -> AccountSet.builder() + .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.valueOf(5)) + .clearFlag(accountSetFlag) + .clearFlagRawValue(UnsignedInteger.valueOf(accountSetFlag.getValue()).plus(UnsignedInteger.ONE)) + .build() + ).isInstanceOf(IllegalStateException.class) + .hasMessage(String.format("clearFlag and clearFlagRawValue should be equivalent, but clearFlag's underlying " + + "value was %s and clearFlagRawValue was %s", + accountSetFlag.getValue(), + accountSetFlag.getValue() + 1 + )); + } + + @ParameterizedTest + @MethodSource("accountSetFlags") + void testWithPresentClearFlagAndEmptyRawValue(AccountSetFlag accountSetFlag) { + AccountSet accountSet = AccountSet.builder() + .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.valueOf(5)) + .clearFlag(accountSetFlag) + .build(); + + assertThat(accountSet.clearFlag()).isNotEmpty().get().isEqualTo(accountSetFlag); + assertThat(accountSet.clearFlagRawValue()).isNotEmpty().get() + .isEqualTo(UnsignedInteger.valueOf(accountSetFlag.getValue())); + } + + @ParameterizedTest + @MethodSource("accountSetFlags") + void testWithEmptyClearFlagAndPresentRawValue(AccountSetFlag accountSetFlag) { + AccountSet accountSet = AccountSet.builder() + .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.valueOf(5)) + .clearFlagRawValue(UnsignedInteger.valueOf(accountSetFlag.getValue())) + .build(); + + assertThat(accountSet.clearFlag()).isNotEmpty().get().isEqualTo(accountSetFlag); + assertThat(accountSet.clearFlagRawValue()).isNotEmpty().get() + .isEqualTo(UnsignedInteger.valueOf(accountSetFlag.getValue())); + } + + @Test + void testWithEmptyClearFlagAndPresentInvalidRawValue() { + AccountSet accountSet = AccountSet.builder() + .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.valueOf(5)) + .clearFlagRawValue(UnsignedInteger.valueOf(16)) + .build(); + + assertThat(accountSet.clearFlag()).isEmpty(); + assertThat(accountSet.clearFlagRawValue()).isNotEmpty().get() + .isEqualTo(UnsignedInteger.valueOf(16)); + } + + ////////////////////////////////////// + + @Test + void testWithEmptySetFlagAndEmptyRawValue() { + AccountSet accountSet = AccountSet.builder() + .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.valueOf(5)) + .build(); + + assertThat(accountSet.setFlag()).isEmpty(); + assertThat(accountSet.setFlagRawValue()).isEmpty(); + } + + @ParameterizedTest + @MethodSource("accountSetFlags") + void testWithPresentSetFlagAndPresentRawValue(AccountSetFlag accountSetFlag) { + AccountSet accountSet = AccountSet.builder() + .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.valueOf(5)) + .setFlag(accountSetFlag) + .setFlagRawValue(UnsignedInteger.valueOf(accountSetFlag.getValue())) + .build(); + + assertThat(accountSet.setFlag()).isNotEmpty().get().isEqualTo(accountSetFlag); + assertThat(accountSet.setFlagRawValue()).isNotEmpty().get() + .isEqualTo(UnsignedInteger.valueOf(accountSetFlag.getValue())); + } + + @ParameterizedTest + @MethodSource("accountSetFlags") + void testWithPresentSetFlagAndPresentRawValueThrowsForMismatchedValues(AccountSetFlag accountSetFlag) { + assertThatThrownBy( + () -> AccountSet.builder() + .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.valueOf(5)) + .setFlag(accountSetFlag) + .setFlagRawValue(UnsignedInteger.valueOf(accountSetFlag.getValue()).plus(UnsignedInteger.ONE)) + .build() + ).isInstanceOf(IllegalStateException.class) + .hasMessage(String.format("setFlag and setFlagRawValue should be equivalent, but setFlag's underlying " + + "value was %s and setFlagRawValue was %s", + accountSetFlag.getValue(), + accountSetFlag.getValue() + 1 + )); + } + + @ParameterizedTest + @MethodSource("accountSetFlags") + void testWithPresentSetFlagAndEmptyRawValue(AccountSetFlag accountSetFlag) { + AccountSet accountSet = AccountSet.builder() + .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.valueOf(5)) + .setFlag(accountSetFlag) + .build(); + + assertThat(accountSet.setFlag()).isNotEmpty().get().isEqualTo(accountSetFlag); + assertThat(accountSet.setFlagRawValue()).isNotEmpty().get() + .isEqualTo(UnsignedInteger.valueOf(accountSetFlag.getValue())); + } + + @ParameterizedTest + @MethodSource("accountSetFlags") + void testWithEmptySetFlagAndPresentRawValue(AccountSetFlag accountSetFlag) { + AccountSet accountSet = AccountSet.builder() + .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.valueOf(5)) + .setFlagRawValue(UnsignedInteger.valueOf(accountSetFlag.getValue())) + .build(); + + assertThat(accountSet.setFlag()).isNotEmpty().get().isEqualTo(accountSetFlag); + assertThat(accountSet.setFlagRawValue()).isNotEmpty().get() + .isEqualTo(UnsignedInteger.valueOf(accountSetFlag.getValue())); + } + + @Test + void testWithEmptySetFlagAndPresentInvalidRawValue() { + AccountSet accountSet = AccountSet.builder() + .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.valueOf(5)) + .setFlagRawValue(UnsignedInteger.valueOf(16)) + .build(); + + assertThat(accountSet.setFlag()).isEmpty(); + assertThat(accountSet.setFlagRawValue()).isNotEmpty().get() + .isEqualTo(UnsignedInteger.valueOf(16)); + + accountSet = AccountSet.builder() + .account(Address.of("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn")) + .fee(XrpCurrencyAmount.ofDrops(12)) + .sequence(UnsignedInteger.valueOf(5)) + .setFlagRawValue(UnsignedInteger.MAX_VALUE.minus(UnsignedInteger.ONE)) + .build(); + + assertThat(accountSet.setFlag()).isEmpty(); + assertThat(accountSet.setFlagRawValue()).isNotEmpty().get() + .isEqualTo(UnsignedInteger.MAX_VALUE.minus(UnsignedInteger.ONE)); + } + @Test void accountSetWithSetFlagAndTransactionFlags() { AccountSetTransactionFlags flags = AccountSetTransactionFlags.builder() diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/AccountSetJsonTests.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/AccountSetJsonTests.java index 764e91430..6f6447209 100644 --- a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/AccountSetJsonTests.java +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/json/AccountSetJsonTests.java @@ -25,6 +25,7 @@ import org.json.JSONException; import org.junit.jupiter.api.Test; import org.xrpl.xrpl4j.crypto.keys.PublicKey; +import org.xrpl.xrpl4j.crypto.signing.Signature; import org.xrpl.xrpl4j.model.AbstractJsonTest; import org.xrpl.xrpl4j.model.flags.AccountSetTransactionFlags; import org.xrpl.xrpl4j.model.flags.TransactionFlags; @@ -194,4 +195,34 @@ void testJsonWithZeroClearFlagAndSetFlag() throws JSONException, JsonProcessingE assertCanSerializeAndDeserialize(accountSet, json); } + + @Test + void testJsonWithUnrecognizedClearAndSetFlag() throws JSONException, JsonProcessingException { + AccountSet accountSet = AccountSet.builder() + .account(Address.of("rhyg7sn3ZQj9aja9CBuLETFKdkG9Fte7ck")) + .fee(XrpCurrencyAmount.ofDrops(15)) + .sequence(UnsignedInteger.valueOf(40232131)) + .setFlagRawValue(UnsignedInteger.valueOf(4294967295L)) + .clearFlagRawValue(UnsignedInteger.valueOf(4294967295L)) + .signingPublicKey(PublicKey.fromBase16EncodedPublicKey( + "ED03FCED79BB3699089BC3F0F57BBD9F9ABA4772C20EC14BFAE908B4299344194A" + )) + .transactionSignature(Signature.fromBase16("8D0915449FB617234DD54E1BA79136B50C4439696BB4287F" + + "CA25717F3FD4875D5A1FEE6269B91D71D9306B48DECAAE1F1C91CAD1AAD0E0D683DC11E68212740E")) + .build(); + + String json = "{\n" + + " \"Account\": \"rhyg7sn3ZQj9aja9CBuLETFKdkG9Fte7ck\",\n" + + " \"Fee\": \"15\",\n" + + " \"Sequence\": 40232131,\n" + + " \"SetFlag\": 4294967295,\n" + + " \"ClearFlag\": 4294967295,\n" + + " \"SigningPubKey\": \"ED03FCED79BB3699089BC3F0F57BBD9F9ABA4772C20EC14BFAE908B4299344194A\",\n" + + " \"TransactionType\": \"AccountSet\",\n" + + " \"TxnSignature\": \"8D0915449FB617234DD54E1BA79136B50C4439696BB4287FCA25717F3FD4875D5A1FEE6269B" + + "91D71D9306B48DECAAE1F1C91CAD1AAD0E0D683DC11E68212740E\"" + + "}"; + + assertCanSerializeAndDeserialize(accountSet, json); + } }