Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support XRPFees amendment #508

Merged
merged 14 commits into from
Oct 18, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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.XrpCurrencyAmount;

import java.io.IOException;

/**
* Custom Jackson deserializer for {@link XrpCurrencyAmount}s.
*/
public class BaseFeeDropsDeserializer extends StdDeserializer<XrpCurrencyAmount> {

/**
* 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());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -20,14 +20,18 @@
* =========================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;
import javax.annotation.Nullable;

/**
* A {@link SetFee} pseudo-transaction marks a change in transaction cost or reserve requirements as a result of Fee
Expand All @@ -53,34 +57,107 @@ 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.
* @return A hex {@link String} baseFee value.
*
* @deprecated Prefer {@link #baseFeeDrops()} over this field.
*/
@JsonProperty("BaseFee")
String baseFee();
@Value.Default
nkramer44 marked this conversation as resolved.
Show resolved Hide resolved
@Deprecated
nkramer44 marked this conversation as resolved.
Show resolved Hide resolved
@JsonIgnore
default String baseFee() {
return baseFeeDrops().value().toString(16);
}

/**
* The cost, in fee units, of the reference transaction.
* The charge, in drops of XRP, for the reference transaction (This is the transaction cost before scaling for load).
*
* <p>This field will also be populated with the {@code BaseFee} value from any {@link SetFee} transactions
* that occurred before the XRPFees amendment took effect.</p>
*
* @return An {@link XrpCurrencyAmount}.
*/
@JsonProperty("BaseFeeDrops")
@JsonAlias("BaseFee")
@JsonDeserialize(using = BaseFeeDropsDeserializer.class)
XrpCurrencyAmount baseFeeDrops();

/**
* The cost, in fee units, of the reference transaction. This field can be {@code null} if this {@link SetFee}
* transaction was included in a ledger after the XRPFees amendment was enabled because this amendment removes the
* {@code ReferenceFeeUnits} field from the {@link SetFee} transaction.
*
* @return An {@link UnsignedInteger} cost of ref transaction.
*
* @deprecated Prefer {@link #maybeReferenceFeeUnits()} over this field.
*/
@Deprecated
@Nullable
@Value.Default
@JsonIgnore
default UnsignedInteger referenceFeeUnits() {
nkramer44 marked this conversation as resolved.
Show resolved Hide resolved
return maybeReferenceFeeUnits().orElse(null);
nkramer44 marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* The cost, in fee units, of the reference transaction, or empty if this {@link SetFee} transaction occurred after
* the XRPFees amendment was enabled.
*
* @return An optionally-present {@link UnsignedInteger}.
*/
@JsonProperty("ReferenceFeeUnits")
UnsignedInteger referenceFeeUnits();
Optional<UnsignedInteger> maybeReferenceFeeUnits();

/**
* The base reserve, in drops.
*
* @return An {@link UnsignedInteger} base reverse value in {@link org.xrpl.xrpl4j.model.client.fees.FeeDrops}.
* @return An {@link UnsignedInteger} base reserve value in {@link org.xrpl.xrpl4j.model.client.fees.FeeDrops}.
*
* @deprecated Prefer {@link #reserveBaseDrops()} over this field.
*/
@Value.Default
nkramer44 marked this conversation as resolved.
Show resolved Hide resolved
@Deprecated
nkramer44 marked this conversation as resolved.
Show resolved Hide resolved
@JsonIgnore
default UnsignedInteger reserveBase() {
return UnsignedInteger.valueOf(reserveBaseDrops().value().longValue());
}

/**
* The base reserve, as an {@link XrpCurrencyAmount}.
*
* <p>This field will also be populated with the {@code ReserveBase} value from any {@link SetFee} transactions
* that occurred before the XRPFees amendment took effect.</p>
*
* @return An {@link XrpCurrencyAmount}.
*/
@JsonProperty("ReserveBase")
UnsignedInteger reserveBase();
@JsonProperty("ReserveBaseDrops")
@JsonAlias("ReserveBase")
XrpCurrencyAmount reserveBaseDrops();

/**
* The incremental reserve, in drops.
*
* @return An {@link UnsignedInteger} incremental reserve in {@link org.xrpl.xrpl4j.model.client.fees.FeeDrops}.
*
* @deprecated Prefer {@link #reserveIncrementDrops()} over this field.
*/
@Deprecated
@JsonIgnore
@Value.Default
nkramer44 marked this conversation as resolved.
Show resolved Hide resolved
default UnsignedInteger reserveIncrement() {
return UnsignedInteger.valueOf(reserveIncrementDrops().value().longValue());
}

/**
* The incremental reserve, as an {@link XrpCurrencyAmount}.
*
* <p>This field will also be populated with the {@code ReserveIncrement} value from any {@link SetFee} transactions
* that occurred before the XRPFees amendment took effect.</p>
*
* @return An {@link XrpCurrencyAmount}.
*/
@JsonProperty("ReserveIncrement")
UnsignedInteger reserveIncrement();
@JsonProperty("ReserveIncrementDrops")
@JsonAlias("ReserveIncrement")
XrpCurrencyAmount reserveIncrementDrops();

/**
* The index of the ledger version where this pseudo-transaction appears. This distinguishes the pseudo-transaction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,27 @@

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 {
public class SetFeeTest extends AbstractJsonTest {

@Test
public void testBuilder() {
public void testConstructWithNoFeeUnits() {
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))
.baseFeeDrops(XrpCurrencyAmount.ofDrops(10))
.reserveBaseDrops(XrpCurrencyAmount.ofDrops(20000000))
.reserveIncrementDrops(XrpCurrencyAmount.ofDrops(5000000))
.ledgerSequence(Optional.of(LedgerIndex.of(UnsignedInteger.valueOf(67850752))))
.build();

Expand All @@ -49,8 +51,107 @@ 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()).isNull();
assertThat(setFee.maybeReferenceFeeUnits()).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 testConstructWithFeeUnits() {
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))
.maybeReferenceFeeUnits(UnsignedInteger.valueOf(10))
.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()).isNotNull().isEqualTo(UnsignedInteger.valueOf(10));
assertThat(setFee.maybeReferenceFeeUnits()).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))
.maybeReferenceFeeUnits(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);
}
}
Loading
Loading