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
Expand Up @@ -26,6 +26,8 @@ public interface LedgerEntryResult<T extends LedgerObject> extends XrplResult {
/**
* Construct a {@code LedgerEntryResult} builder.
*
* @param <T> The type of {@link LedgerObject}.
*
* @return An {@link ImmutableLedgerEntryResult.Builder}.
*/
static <T extends LedgerObject> ImmutableLedgerEntryResult.Builder<T> builder() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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}.
*
* <p>Before the <a href="https://xrpl.org/resources/known-amendments/#xrpfees">XRPFees amendment</a>, a {@link SetFee}
* transaction serializes its `BaseFee` to a hex string. After the
* <a href="https://xrpl.org/resources/known-amendments/#xrpfees">XRPFees amendment</a>, a {@link SetFee} transaction
* serializes its `BaseFee` to a decimal string.
*
* @see "https://xrpl.org/resources/known-amendments/#xrpfees"
*/
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,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;

Expand Down Expand Up @@ -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.
* <p>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).
*
* <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("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<UnsignedInteger> referenceFeeUnits();

/**
* The base reserve, in drops.
*
* @return An {@link UnsignedInteger} base reverse value in {@link org.xrpl.xrpl4j.model.client.fees.FeeDrops}.
* <p>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}.
*
* <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("ReserveBaseDrops")
@JsonAlias("ReserveBase")
XrpCurrencyAmount reserveBaseDrops();

/**
* The incremental reserve, in drops.
*
* <p>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}.
*
* <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("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,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();

Expand All @@ -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);
}
}
Loading
Loading