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
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>11.0.0.0</PackageVersion>
<PackageVersion>11.0.1.0</PackageVersion>
</PropertyGroup>

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

## Unreleased

* **The signing path builds its `JsonSerializerOptions` once** (#147). `XrplBinaryCodec.ObjectToJsonNode` constructed a fresh instance on every call, and every signing operation goes through it - `Encode`, `EncodeForSigning`, `EncodeForSigningClaim` and `EncodeForMultiSigning` all route there. Measured end to end on `EncodeForSigning`, 50 000 calls: **1075.8 ms and 14458 B/op before, 621.8 ms and 13601 B/op after** - 1.73x, and 857 fewer bytes each call. The encoded blob is unchanged, hashing identically either way.
* not the catastrophe this bug is usually described as: since .NET 7 System.Text.Json shares a caching context between structurally equal options instances, so type metadata was not being rebuilt per call - had it been, the gap would be orders of magnitude rather than 1.7x. What was paid is an allocation and a structural-equality lookup in a pool capped at 64 contexts
* `LOVault.ToHex` had the same pattern on a colder path
* `Xrpl.BinaryCodec` moves to **11.0.1.0** for it. The other base packages are untouched and stay where they are - they are consumed by `ProjectReference`, so a package built at a newer version keeps depending on the published ones

* **An amount the ledger allows but `decimal` cannot hold is refused, not guessed at** (#148). `Currency.ValueAsNumber` answered such values three different ways: a positive one clamped to `decimal.MaxValue`, a negative one threw `FormatException`, and a very small one quietly became zero. XRPL issued currency runs from `1e-81` to roughly `1e96` - a 16-digit mantissa with an exponent in `[-96, 80]`, per rippled's `STAmount` - while `decimal` stops near `7.9e28`, so this cannot be parsed away; the only choice is how to fail.
* the clamp is gone. An amount above the range now throws `AmountOutOfRangeException`, which carries the value as the node sent it. Returning `7.9e28` for `1e96` is wrong by 67 orders of magnitude, and it did not stay contained: `GetBalanceChanges` subtracts two balances, so the clamped value went on to throw `OverflowException` from arithmetic instead
* the negative case was a parse bug. The fallback's `NumberStyles` expression came to `AllowExponent | AllowDecimalPoint` - `AllowLeadingSign` was missing, so no negative value could reach the branch meant to handle it. The primary parse was correct all along, despite six `&` terms that all evaluate to zero
* **an amount below `1e-28` still returns zero, and the asymmetry is deliberate.** A balance of `1e-81` rounded to zero is zero at any scale a caller can act on; failing over it would cost more than it protects. An amount of `1e96` reported as `7.9e28` is not in that category
* the threshold is nowhere near the protocol's ceiling: `1e29` is barely above `decimal.MaxValue` and was already unreachable. A token with a large supply meets this without going anywhere near the ledger's limits
* `Offer.AmountEach` reads the same property on both sides of an order and divides them. Anyone may place an offer in their own token at any value the protocol allows, so it fails the same way - and used to return a plausible-looking exchange rate that was wrong by 67 orders of magnitude, without throwing. Both it and `GetBalanceChanges` now say so in their own documentation rather than leaving it to be discovered
* `Currency.ToString()` falls back to the raw value rather than letting the getter throw through it. By convention `ToString` does not throw, and logging, string interpolation and a debugger's watch window are exactly where someone would be while working out why an amount is unusual - failing there hides the value at the moment it is most wanted
* the setter no longer writes a string it cannot read back. `G16` keeps the ledger's sixteen significant digits and rounds to nearest - which is what rippled does, so it stays - but at the top of `decimal`'s own range rounding to nearest rounds **up**, past what the type holds. Only there is the sixteenth digit truncated instead, which cannot overflow because dropping digits only moves a number toward zero
* **dust survives.** Balances like `0.000000000000000001` do arrive from the network and the SDK must be able to send them back; they are safe because the ledger's limit is sixteen *significant* digits and dust carries one. Pinned by a test, because the obvious way to bound precision - truncating to sixteen decimal *places* - turns `1e-18` into zero and would make a remainder disappear silently
* a test that claimed a round trip can never increase a value was asserting something the protocol does not promise, on a value where it could not fail. rippled's `Number` defaults to `ToNearest`, so an amount beyond sixteen digits can legitimately come back larger. Replaced with the property that does hold - an amount already at ledger precision goes out unchanged - and with one stating the rounding, so the next reader does not reach for truncation and move the SDK away from rippled
* why the setter rounds while the binary codec refuses more than sixteen digits outright is now written down. The two see different inputs: seventeen digits cannot arrive from the network, so the codec only ever meets a hand-written string, while the setter meets computed `decimal`s that routinely carry 28 - `AmmMath` returns them
* `Console.WriteLine(exception)` is out of the parse path. A library does not write to the console
* **breaking in effect, if not in signature**: code that read an out-of-range amount used to get a number and now gets an exception. Representing the full range instead of refusing it is #150

## 11.0.0.0 08/26/2026

### Migration at a glance
Expand Down
Loading
Loading