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

Fixes #474 #535

Merged
merged 23 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 Down Expand Up @@ -41,7 +41,9 @@
class AmountType extends SerializedType<AmountType> {

public static final BigDecimal MAX_DROPS = new BigDecimal("1e17");
public static final BigDecimal MIN_DROPS = new BigDecimal("-1e17");
public static final BigDecimal MIN_XRP = new BigDecimal("1e-6");
public static final BigDecimal MAX_NEGATIVE_XRP = new BigDecimal("-1e-6");

public static final String DEFAULT_AMOUNT_HEX = "4000000000000000";
public static final String ZERO_CURRENCY_AMOUNT_HEX = "8000000000000000";
Expand All @@ -50,18 +52,18 @@ class AmountType extends SerializedType<AmountType> {
private static final int MAX_IOU_PRECISION = 16;

/**
* According to <a href=https://xrpl.org/currency-formats.html#currency-formats>xrpl.org</a>,
* the minimum token value exponent is -96. However, because the value field is converted from a {@link String}
* to a {@link BigDecimal} when encoding/decoding, and because {@link BigDecimal} defaults to using single
* digit number, the minimum exponent in this context is -96 + 15, as XRPL amounts have a precision of 15 digits.
* According to <a href=https://xrpl.org/currency-formats.html#currency-formats>xrpl.org</a>, the minimum token value
* exponent is -96. However, because the value field is converted from a {@link String} to a {@link BigDecimal} when
* encoding/decoding, and because {@link BigDecimal} defaults to using single digit number, the minimum exponent in
* this context is -96 + 15, as XRPL amounts have a precision of 15 digits.
*/
private static final int MIN_IOU_EXPONENT = -81;

/**
* According to <a href=https://xrpl.org/currency-formats.html#currency-formats>xrpl.org</a>,
* the maximum token value exponent is 80. However, because the value field is converted from a {@link String}
* to a {@link BigDecimal} when encoding/decoding, and because {@link BigDecimal} defaults to using single
* digit number, the maximum exponent in this context is 80 + 15, as XRPL amounts have a precision of 15 digits.
* According to <a href=https://xrpl.org/currency-formats.html#currency-formats>xrpl.org</a>, the maximum token value
* exponent is 80. However, because the value field is converted from a {@link String} to a {@link BigDecimal} when
* encoding/decoding, and because {@link BigDecimal} defaults to using single digit number, the maximum exponent in
* this context is 80 + 15, as XRPL amounts have a precision of 15 digits.
*/
private static final int MAX_IOU_EXPONENT = 95;

Expand All @@ -88,8 +90,14 @@ private static void assertXrpIsValid(String amount) {
}
BigDecimal value = new BigDecimal(amount);
if (!value.equals(BigDecimal.ZERO)) {
if (value.compareTo(MIN_XRP) < 0 || value.compareTo(MAX_DROPS) > 0) {
throw new IllegalArgumentException(amount + " is an illegal amount");
if (value.signum() < 0) { // `value` is negative
if (value.compareTo(MAX_NEGATIVE_XRP) > 0 || value.compareTo(MIN_DROPS) < 0) {
sappenin marked this conversation as resolved.
Show resolved Hide resolved
throw new IllegalArgumentException(String.format("%s is an illegal amount", amount));
}
} else { // `value` is positive
if (value.compareTo(MIN_XRP) < 0 || value.compareTo(MAX_DROPS) > 0) {
throw new IllegalArgumentException(String.format("%s is an illegal amount", amount));
}
}
}
}
Expand Down Expand Up @@ -142,14 +150,21 @@ public AmountType fromJson(JsonNode value) throws JsonProcessingException {
if (value.isValueNode()) {
assertXrpIsValid(value.asText());

UnsignedByteArray number = UnsignedByteArray.fromHex(
final boolean isValueNegative = value.asText().startsWith("-");
sappenin marked this conversation as resolved.
Show resolved Hide resolved
final UnsignedByteArray number = UnsignedByteArray.fromHex(
ByteUtils.padded(
UnsignedLong.valueOf(value.asText()).toString(16),
64 / 4
UnsignedLong
.valueOf(isValueNegative ? value.asText().substring(1) : value.asText())
.toString(16),
16 // <-- 64 / 16
sappenin marked this conversation as resolved.
Show resolved Hide resolved
)
);
byte[] rawBytes = number.toByteArray();
rawBytes[0] |= 0x40;
final byte[] rawBytes = number.toByteArray();
sappenin marked this conversation as resolved.
Show resolved Hide resolved
if (isValueNegative) {
rawBytes[0] |= 0x00;
sappenin marked this conversation as resolved.
Show resolved Hide resolved
} else {
rawBytes[0] |= 0x40;
}
return new AmountType(UnsignedByteArray.of(rawBytes));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
* =========================LICENSE_END==================================
*/

import java.math.BigDecimal;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
Expand All @@ -34,9 +35,17 @@ public interface CurrencyAmount {
long ONE_XRP_IN_DROPS = 1_000_000L;
long MAX_XRP = 100_000_000_000L; // <-- per https://xrpl.org/rippleapi-reference.html#value
long MAX_XRP_IN_DROPS = MAX_XRP * ONE_XRP_IN_DROPS;
BigDecimal MAX_XRP_BD = BigDecimal.valueOf(MAX_XRP);

/**
* Handle this {@link CurrencyAmount} depending on its actual polymorphic sub-type.
* Indicates whether this amount is positive or negative.
*
* @return {@code true} if this amount is negative; {@code false} otherwise (i.e., if the value is 0 or positive).
*/
boolean isNegative();

/**
* Handle this {@link CurrencyAmount} depending on its actual polymorphic subtype.
*
* @param xrpCurrencyAmountHandler A {@link Consumer} that is called if this instance is of type
* {@link XrpCurrencyAmount}.
Expand All @@ -60,7 +69,7 @@ default void handle(
}

/**
* Map this {@link CurrencyAmount} to an instance of {@link R}, depending on its actualy polymorphic sub-type.
* Map this {@link CurrencyAmount} to an instance of {@link R}, depending on its actual polymorphic subtype.
*
* @param xrpCurrencyAmountMapper A {@link Function} that is called if this instance is of type
* {@link XrpCurrencyAmount}.
Expand Down
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,9 +20,13 @@
* =========================LICENSE_END==================================
*/

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.immutables.value.Value;
import org.immutables.value.Value.Auxiliary;
import org.immutables.value.Value.Default;
import org.immutables.value.Value.Derived;

/**
* A {@link CurrencyAmount} for Issued Currencies on the XRP Ledger.
Expand All @@ -45,14 +49,14 @@ public interface IssuedCurrencyAmount extends CurrencyAmount {
String MIN_VALUE = "-9999999999999999e80";

/**
* The smallest possible positive value that an {@link IssuedCurrencyAmount} can have. Put another way,
* this value is the closest an {@link IssuedCurrencyAmount}'s {@link #value()} can be to zero if it is positive.
* The smallest possible positive value that an {@link IssuedCurrencyAmount} can have. Put another way, this value is
* the closest an {@link IssuedCurrencyAmount}'s {@link #value()} can be to zero if it is positive.
*/
String MIN_POSITIVE_VALUE = "1000000000000000e-96";

/**
* The largest possible negative value that an {@link IssuedCurrencyAmount} can have. Put another way,
* this value is the closest an {@link IssuedCurrencyAmount}'s {@link #value()} can be to zero if it is negative.
* The largest possible negative value that an {@link IssuedCurrencyAmount} can have. Put another way, this value is
* the closest an {@link IssuedCurrencyAmount}'s {@link #value()} can be to zero if it is negative.
*/
String MAX_NEGATIVE_VALUE = "-1000000000000000e-96";

Expand All @@ -67,10 +71,10 @@ static ImmutableIssuedCurrencyAmount.Builder builder() {

/**
* Quoted decimal representation of the amount of currency. This can include scientific notation, such as 1.23e11
* meaning 123,000,000,000. Both e and E may be used. Note that while this implementation merely holds a {@link
* String} with no value restrictions, the XRP Ledger does not tolerate unlimited precision values. Instead, non-XRP
* values (i.e., values held in this object) can have up to 16 decimal digits of precision, with a maximum value of
* 9999999999999999e80. The smallest positive non-XRP value is 1e-81.
* meaning 123,000,000,000. Both e and E may be used. Note that while this implementation merely holds a
* {@link String} with no value restrictions, the XRP Ledger does not tolerate unlimited precision values. Instead,
* non-XRP values (i.e., values held in this object) can have up to 16 decimal digits of precision, with a maximum
* value of 9999999999999999e80. The smallest positive non-XRP value is 1e-81.
*
* @return A {@link String} containing the amount of this issued currency.
*/
Expand All @@ -91,4 +95,16 @@ static ImmutableIssuedCurrencyAmount.Builder builder() {
*/
Address issuer();

/**
* Indicates whether this amount is positive or negative.
*
* @return {@code true} if this amount is negative; {@code false} otherwise (i.e., if the value is 0 or positive).
*/
@Derived
@JsonIgnore // <-- This is not actually part of the binary serialization format, so exclude from JSON
@Auxiliary
default boolean isNegative() {
return value().startsWith("-");
}

}
Loading
Loading