Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions Base/Xrpl.BinaryCodec/Enums/Field.Uint8.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public partial class Field
public static readonly Uint8Field Method = new Uint8Field(nameof(Method), 2);
public static readonly Uint8Field Scale = new Uint8Field(nameof(Scale), 4);
public static readonly Uint8Field AssetScale = new Uint8Field(nameof(AssetScale), 5);
public static readonly Uint8Field LEVersion = new Uint8Field(nameof(LEVersion), 6);
public static readonly Uint8Field TickSize = new Uint8Field(nameof(TickSize), 16);
public static readonly Uint8Field UNLModifyDisabling = new Uint8Field(nameof(UNLModifyDisabling), 17);
public static readonly Uint8Field HookResult = new Uint8Field(nameof(HookResult), 18);
Expand Down
10 changes: 10 additions & 0 deletions Base/Xrpl.BinaryCodec/Enums/definitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -3150,6 +3150,16 @@
"type": "UInt8"
}
],
[
"LEVersion",
{
"isSerialized": true,
"isSigningField": true,
"isVLEncoded": false,
"nth": 6,
"type": "UInt8"
}
],
[
"TickSize",
{
Expand Down
2 changes: 1 addition & 1 deletion Base/Xrpl.BinaryCodec/Xrpl.BinaryCodec.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/StaticBit-io/XrplCSharp</PackageProjectUrl>
<Title>XrplCSharp</Title>
<PackageVersion>10.9.0.0</PackageVersion>
<PackageVersion>10.11.0.0</PackageVersion>
</PropertyGroup>

<PropertyGroup>
Expand Down
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Changes

## 10.11.0.0 08/03/2026
* **`sfLEVersion` — the Vault ledger entry's schema version** ([rippled #7817](https://github.com/XRPLF/rippled/pull/7817), merged into `develop` 07/30/2026, reported by protocol-watch). `UInt8` nth 6, `SoeDefault` on `ltVAULT`: it marks which accounting scheme a vault follows. Vaults created before cash-basis accounting was activated carry no `LEVersion` at all, and rippled resolves that absence as version 0 rather than an error — so an absent value is meaningful, not missing data:
* `definitions.json` + the generated `Field.Uint8` entry. **Both are required**: `definitions.json` is not read at runtime, it is the input to `Tools/GenerateEnums`, so a field added there alone travels nowhere. `TestULEVersion_BinaryRoundTrip` is what proves the round trip actually works rather than that the JSON was edited
* `LOVault.LEVersion` (`uint?`, matching the other UInt8 fields of that object) plus a `VaultVersion` enum naming the two values the protocol defines so far (`Legacy` = 0, `CashBasis` = 1)
* `TestULOVault_LEVersion_Deserialize` covers both shapes — the field present, and a legacy vault without it deserializing to `null`
* `Xrpl.BinaryCodec` bumped to **10.11.0.0**, aligned with `Xrpl` rather than to its own next minor (10.10.0.0): the codec ships the field, so the two move together and a consumer can read one version number off both. 10.10.x is simply skipped — the codec's last published version is 10.9.0, so no number is being reused. `Xrpl.AddressCodec` and `Xrpl.Keypairs` are untouched and keep 10.9.0.0

* **Ledger-object flags the protocol declares but the models never named** — an unnamed bit still arrives in the model as a number, so reading the object kept working and only the consumer's ability to test it by name was lost. That is why these went unnoticed; a field-by-field diff of rippled `LedgerFormats.h` (tag `3.3.0-rc1`) against every flag enum found four gaps:
* `MPTokenIssuanceFlags` + **`MPTCanHoldConfidentialBalance`** (0x80) — introduced by ConfidentialTransfer. The rest of the amendment was already complete (transactions 85–89, `IssuerEncryptionKey`/`AuditorEncryptionKey`, `ConfidentialOutstandingAmount`); only the flag had no name. Value confirmed against a live node: `MPTokenIssuanceSet` with `MutableFlags = tmfMPTSetCanHoldConfidentialBalance` moves the issuance from `Flags = 0` to `Flags = 128`
* `MPTokenFlags` + **`lsfMPTAMM`** (0x4) — a much older gap: the flag is present as far back as 3.2.1. `AMMCreate` sets it together with `lsfMPTAuthorized` to implicitly authorize an MPT asset for the AMM pseudo-account
Expand Down
42 changes: 42 additions & 0 deletions Tests/Xrpl.Tests/Models/TestUProtocolCompleteness.cs
Original file line number Diff line number Diff line change
Expand Up @@ -322,5 +322,47 @@ public void TestULORippleState_SponsorFields_Deserialize()
Assert.AreEqual(Account1, state.HighSponsor);
Assert.AreEqual(Account2, state.LowSponsor);
}

[TestMethod]
public void TestULOVault_LEVersion_Deserialize()
{
string json = JsonSerializer.Serialize(new Dictionary<string, object>
{
["LedgerEntryType"] = "Vault",
["Account"] = Account1,
["Owner"] = Account2,
["ShareMPTID"] = "00000001A407AF5856CCF3C42619DAA925813FC955C72983",
["WithdrawalPolicy"] = 1,
["Scale"] = 6,
["LEVersion"] = (uint)VaultVersion.CashBasis,
});
LOVault vault = JsonSerializer.Deserialize<LOVault>(json, XrplJsonOptions.Default);
Assert.AreEqual((uint)VaultVersion.CashBasis, vault.LEVersion);

// A vault created before cash-basis accounting carries no LEVersion at all;
// rippled resolves that absence as VaultVersion.Legacy rather than an error
string legacy = JsonSerializer.Serialize(new Dictionary<string, object>
{
["LedgerEntryType"] = "Vault",
["Account"] = Account1,
["Owner"] = Account2,
});
Assert.IsNull(JsonSerializer.Deserialize<LOVault>(legacy, XrplJsonOptions.Default).LEVersion);
}

[TestMethod]
public void TestULEVersion_BinaryRoundTrip()
{
// The field only travels if definitions.json knows it — this fails with an
// encoding error, not an assertion, when the entry is missing.
// Parsed from text rather than built from int literals: that is the shape a
// node response arrives in, and Uint8.FromJson takes a byte, not an Int32
JsonObject json = JsonNode.Parse("""{"LEVersion":1,"Scale":6}""")!.AsObject();
string blob = XrplBinaryCodec.Encode(json);
JsonObject decoded = XrplBinaryCodec.Decode(blob).AsObject();

Assert.AreEqual(1u, decoded["LEVersion"]!.GetValue<uint>());
Assert.AreEqual(6u, decoded["Scale"]!.GetValue<uint>());
}
}
}
29 changes: 29 additions & 0 deletions Xrpl/Models/Ledger/LOVault.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,27 @@ public enum VaultLedgerFlags : uint
lsfVaultPrivate = 0x00010000,
}

/// <summary>
/// Values of the Vault ledger entry's <c>LEVersion</c> field (rippled <c>VaultVersion</c>).
/// </summary>
/// <remarks>
/// <see cref="LOVault.LEVersion"/> stays a plain <c>uint?</c>, matching the other UInt8
/// fields of this object; these constants name the values the protocol defines so far.
/// </remarks>
public enum VaultVersion : uint
{
/// <summary>
/// Accrual-basis accounting. Vaults created before cash-basis accounting was activated
/// carry no LEVersion at all and are treated as this version implicitly.
/// </summary>
Legacy = 0,

/// <summary>
/// Cash-basis accounting (rippled #7817).
/// </summary>
CashBasis = 1,
}

/// <summary>
/// Recommended structure for the Vault Data field.
/// The JSON is whitespace-removed and hex-encoded (max 256 bytes).
Expand Down Expand Up @@ -144,6 +165,14 @@ public LOVault()
[JsonPropertyName("Scale")]
public uint? Scale { get; init; }

/// <summary>
/// Schema version of this ledger entry (UInt8), see <see cref="VaultVersion"/>.
/// Absent on vaults created before cash-basis accounting was activated, which
/// rippled resolves as <see cref="VaultVersion.Legacy"/> (0) rather than an error.
/// </summary>
[JsonPropertyName("LEVersion")]
public uint? LEVersion { get; init; }

/// <summary>
/// Arbitrary hex-encoded data associated with the vault, limited to 256 bytes.
/// Use <see cref="DataParsed"/> for a human-readable representation.
Expand Down
Loading