Skip to content

Commit

Permalink
negative MPT amounts, other formatting feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
nkramer44 committed Oct 19, 2024
1 parent 8828d84 commit 73e282e
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.base.Preconditions;
import com.google.common.io.BaseEncoding;
import com.google.common.primitives.UnsignedLong;
import org.xrpl.xrpl4j.codec.addresses.ByteUtils;
import org.xrpl.xrpl4j.codec.addresses.UnsignedByte;
Expand Down Expand Up @@ -199,7 +197,7 @@ public AmountType fromJson(JsonNode value) throws JsonProcessingException {
MptAmount amount = objectMapper.treeToValue(value, MptAmount.class);

if (FluentCompareTo.is(amount.unsignedLongValue()).greaterThan(UnsignedLong.valueOf(Long.MAX_VALUE))) {
throw new IllegalArgumentException("Invalid MPT amount: given value requires 64 bits, only 63 allowed.");
throw new IllegalArgumentException("Invalid MPT amount. Maximum MPT value is (2^63 - 1)");
}

UnsignedByteArray amountBytes = UnsignedByteArray.fromHex(
Expand All @@ -211,7 +209,8 @@ public AmountType fromJson(JsonNode value) throws JsonProcessingException {
UnsignedByteArray issuanceIdBytes = new Hash192Type().fromJson(new TextNode(amount.mptIssuanceId())).value();

// MPT Amounts always have 0110000 as its first byte.
UnsignedByteArray result = UnsignedByteArray.of(UnsignedByte.of(0x60));
int leadingByte = amount.isNegative() ? 0x20 : 0x60;
UnsignedByteArray result = UnsignedByteArray.of(UnsignedByte.of(leadingByte));
result.append(amountBytes);
result.append(issuanceIdBytes);

Expand Down Expand Up @@ -251,12 +250,14 @@ public JsonNode toJson() {
} else if (this.isMpt()) {
BinaryParser parser = new BinaryParser(this.toHex());
// We know the first byte already based on this.isMpt()
parser.skip(1);
UnsignedByte leadingByte = parser.read(1).get(0);
boolean isNegative = !leadingByte.isNthBitSet(2);
UnsignedLong amount = parser.readUInt64();
UnsignedByteArray issuanceId = new Hash192Type().fromParser(parser).value();

String amountBase10 = amount.toString(10);
MptAmount mptAmount = MptAmount.builder()
.value(amount.toString(10))
.value(isNegative ? "-" + amountBase10 : amountBase10)
.mptIssuanceId(issuanceId.hexValue())
.build();

Expand Down Expand Up @@ -301,13 +302,14 @@ public JsonNode toJson() {
*/
private boolean isNative() {
// 1st bit in 1st byte is set to 0 for native XRP, 3rd bit is also 0.
// 0xA0 is 1010 0000
return (toBytes()[0] & 0xA0) == 0;
byte leadingByte = toBytes()[0];
return (leadingByte & 0x80) == 0 && (leadingByte & 0x20) == 0;
}

private boolean isMpt() {
// 1st bit in 1st byte is 0, 2nd bit is 1, and 3rd bit is 1
return (toBytes()[0] & 0xA0) == 0x20;
// 1st bit in 1st byte is 0, and 3rd bit is 1
byte leadingByte = toBytes()[0];
return (leadingByte & 0x80) == 0 && (leadingByte & 0x20) != 0;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,20 @@ static ImmutableMptAmount.Builder builder() {

String value();

@JsonProperty("mpt_issuance_id")
String mptIssuanceId();

@Value.Derived
@JsonIgnore
default boolean isNegative() {
return value().startsWith("-");
}

@Value.Derived
@JsonIgnore
default UnsignedLong unsignedLongValue() {
return UnsignedLong.valueOf(value());
return isNegative() ?
UnsignedLong.valueOf(value().substring(1)) :
UnsignedLong.valueOf(value());
}

@JsonProperty("mpt_issuance_id")
String mptIssuanceId();

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@

import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;

/**
Expand All @@ -41,7 +40,6 @@
*/
public abstract class SerializedType<T extends SerializedType<T>> {


@SuppressWarnings("all")
private static final Map<String, Supplier<SerializedType<?>>> typeMap =
new ImmutableMap.Builder<String, Supplier<SerializedType<?>>>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,15 @@ void encodeDecodeMptAmount() {
assertThat(fromJson.toJson().toString()).isEqualTo(json);
}

@Test
void encodeDecodeMptAmountNegative() {
String json = "{\"value\":\"-100\",\"mpt_issuance_id\":\"00002403C84A0A28E0190E208E982C352BBD5006600555CF\"}";
AmountType fromJson = codec.fromJson(json);
assertThat(fromJson.toHex())
.isEqualTo("20000000000000006400002403C84A0A28E0190E208E982C352BBD5006600555CF");
assertThat(fromJson.toJson().toString()).isEqualTo(json);
}

@Test
void encodeDecodeLargestAmount() {
String json = "{\"value\":\"9223372036854775807\"," +
Expand All @@ -239,12 +248,31 @@ void encodeDecodeLargestAmount() {
assertThat(fromJson.toJson().toString()).isEqualTo(json);
}

@Test
void encodeDecodeLargestAmountNegative() {
String json = "{\"value\":\"-9223372036854775807\"," +
"\"mpt_issuance_id\":\"00002403C84A0A28E0190E208E982C352BBD5006600555CF\"}";
AmountType fromJson = codec.fromJson(json);
assertThat(fromJson.toHex())
.isEqualTo("207FFFFFFFFFFFFFFF00002403C84A0A28E0190E208E982C352BBD5006600555CF");
assertThat(fromJson.toJson().toString()).isEqualTo(json);
}

@Test
void encodeMptAmountWithMoreThan63BitAmountThrows() {
UnsignedLong maxLongPlusOne = UnsignedLong.valueOf(Long.MAX_VALUE).plus(UnsignedLong.ONE);
String json = "{\"value\":\"" + maxLongPlusOne + "\"," +
"\"mpt_issuance_id\":\"00002403C84A0A28E0190E208E982C352BBD5006600555CF\"}";
assertThatThrownBy(() -> codec.fromJson(json)).isInstanceOf(IllegalArgumentException.class)
.hasMessage("Invalid MPT amount: given value requires 64 bits, only 63 allowed.");
.hasMessage("Invalid MPT amount. Maximum MPT value is (2^63 - 1)");
}

@Test
void encodeMptAmountWithMoreThan63BitAmountThrowsNegative() {
UnsignedLong maxLongPlusOne = UnsignedLong.valueOf(Long.MAX_VALUE).plus(UnsignedLong.ONE);
String json = "{\"value\":\"-" + maxLongPlusOne + "\"," +
"\"mpt_issuance_id\":\"00002403C84A0A28E0190E208E982C352BBD5006600555CF\"}";
assertThatThrownBy(() -> codec.fromJson(json)).isInstanceOf(IllegalArgumentException.class)
.hasMessage("Invalid MPT amount. Maximum MPT value is (2^63 - 1)");
}
}
15 changes: 14 additions & 1 deletion xrpl4j-core/src/test/resources/data-driven-tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -3701,8 +3701,10 @@
"mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF",
"value": "-1"
},
"is_native": false,
"type": "Amount",
"error": "Value is negative"
"expected_hex": "20000000000000000100002403C84A0A28E0190E208E982C352BBD5006600555CF",
"is_negative": false
},
{
"test_json": {
Expand Down Expand Up @@ -3800,6 +3802,17 @@
"expected_hex": "60000000000000000000002403C84A0A28E0190E208E982C352BBD5006600555CF",
"is_negative": false
},
{
"test_json": {
"mpt_issuance_id":"00002403C84A0A28E0190E208E982C352BBD5006600555CF",
"value": "-0"
},
"type_id": 6,
"is_native": false,
"type": "Amount",
"expected_hex": "20000000000000000000002403C84A0A28E0190E208E982C352BBD5006600555CF",
"is_negative": false
},
{
"test_json": {
"mpt_issuance_id":"00002403C84A0A28E0190E208E982C352BBD5006600555CF",
Expand Down

0 comments on commit 73e282e

Please sign in to comment.