diff --git a/CHANGELOG.md b/CHANGELOG.md index f855f81c1..7092561c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ Release built: _not released yet_ - Brand-new `two_way_linked_*` properties on the `details` property of the Resources, Accounts, Packages and other global entities. - See https://docs.radixdlt.com/docs/metadata-for-verification#metadata-standards-for-verification-of-onledger-entities for detailed specification. - Added support for the Native Resource Details in the `/state/entity/details` endpoint, returned when the `native_resource_details` opt-in is enabled. - - Brand-new `native_resource_details` property on the `details` property. + - Introduced a new `native_resource_details` property on the `details` object when looking up fungible or non-fungible resource entities with the entity details endpoint. This property is present when the resource has a special meaning to native blueprints, and gives extra information about the resource. For example, it identifies pool units with their linked pool, and gives the redemption value for a single unit. - Includes **unit** redemption value for the Validator LSU token and the unit tokens of various Pools. ### Database changes diff --git a/src/RadixDlt.NetworkGateway.Abstractions/Numerics/TokenAmount.cs b/src/RadixDlt.NetworkGateway.Abstractions/Numerics/TokenAmount.cs index c9f6aa90d..a42e5c687 100644 --- a/src/RadixDlt.NetworkGateway.Abstractions/Numerics/TokenAmount.cs +++ b/src/RadixDlt.NetworkGateway.Abstractions/Numerics/TokenAmount.cs @@ -152,9 +152,9 @@ public static TokenAmount FromDecimalString(string decimalString) public static TokenAmount operator -(TokenAmount a, TokenAmount b) => (a.IsNaN() || b.IsNaN()) ? NaN : new TokenAmount(a._subUnits - b._subUnits); - public static TokenAmount operator *(TokenAmount a, TokenAmount b) => (a.IsNaN() || b.IsNaN()) ? NaN : new TokenAmount(a._subUnits * b._subUnits); + public static TokenAmount operator *(TokenAmount a, TokenAmount b) => (a.IsNaN() || b.IsNaN()) ? NaN : new TokenAmount((a._subUnits * b._subUnits) / _divisor); - public static TokenAmount operator /(TokenAmount a, TokenAmount b) => (a.IsNaN() || b.IsNaN()) ? NaN : Divide(a, b); + public static TokenAmount operator /(TokenAmount a, TokenAmount b) => (a.IsNaN() || b.IsNaN() || b == Zero) ? NaN : new TokenAmount((a._subUnits * _divisor) / b._subUnits); // ReSharper disable SimplifyConditionalTernaryExpression - As it's clearer as written #pragma warning disable IDE0075 @@ -267,17 +267,4 @@ public int CompareTo(TokenAmount other) var isNaNComparison = _isNaN.CompareTo(other._isNaN); return isNaNComparison != 0 ? isNaNComparison : _subUnits.CompareTo(other._subUnits); } - - private static TokenAmount Divide(TokenAmount dividend, TokenAmount divisor) - { - if (divisor == Zero) - { - return NaN; - } - - var doublePrecisionDividendSubUnits = dividend._subUnits * _divisor; - var divisorSubUnits = divisor._subUnits; - - return FromSubUnits(doublePrecisionDividendSubUnits / divisorSubUnits); - } } diff --git a/src/RadixDlt.NetworkGateway.PostgresIntegration/Services/StandardMetadataResolver.cs b/src/RadixDlt.NetworkGateway.PostgresIntegration/Services/StandardMetadataResolver.cs index 357639373..66b228672 100644 --- a/src/RadixDlt.NetworkGateway.PostgresIntegration/Services/StandardMetadataResolver.cs +++ b/src/RadixDlt.NetworkGateway.PostgresIntegration/Services/StandardMetadataResolver.cs @@ -82,7 +82,7 @@ internal class StandardMetadataResolver { private record struct PartiallyValidatedTwoWayLink(long FromStateVersion, StandardMetadataKey Discriminator, bool IsLocked, string EntityAddress, string TargetValue, string ValidationResult); - private record struct ValidatedTwoWayLink(PartiallyValidatedTwoWayLink Link, ResolvedTwoWayLink? ResolvedTwoWayLink, string? ValidationError) + private record struct TwoWayLinkValidationResult(PartiallyValidatedTwoWayLink Link, ResolvedTwoWayLink? ResolvedTwoWayLink, string? ValidationError) { public bool IsSuccessfullyResolved([NotNullWhen(true)] out ResolvedTwoWayLink? resolved) { @@ -323,7 +323,7 @@ await Parallel.ForEachAsync(partiallyValidatedEntries, options, async (pv, inner return result.ToDictionary(e => e.Key, e => (ICollection)e.Value.ToList()); } - private async ValueTask ResolveTwoWayLink(PartiallyValidatedTwoWayLink link, bool validateOnLedgerOnly, ICollection allEntries, CancellationToken token) + private async ValueTask ResolveTwoWayLink(PartiallyValidatedTwoWayLink link, bool validateOnLedgerOnly, ICollection allEntries, CancellationToken token) { if (link.ValidationResult == StandardMetadataConstants.ValidationUnknown) { @@ -337,7 +337,7 @@ private async ValueTask ResolveTwoWayLink(PartiallyValidate return await ResolveDappClaimedWebsite(link, validateOnLedgerOnly, token); } - return new ValidatedTwoWayLink(link, null, "expected off-ledger app-check validation result, got: " + link.ValidationResult); + return new TwoWayLinkValidationResult(link, null, "expected off-ledger app-check validation result, got: " + link.ValidationResult); } if (link.Discriminator == StandardMetadataKey.DappAccountLocker) @@ -347,17 +347,17 @@ private async ValueTask ResolveTwoWayLink(PartiallyValidate return ResolveDappAccountLocker(link, allEntries); } - return new ValidatedTwoWayLink(link, null, "expected on-ledger app-check validation result, got: " + link.ValidationResult); + return new TwoWayLinkValidationResult(link, null, "expected on-ledger app-check validation result, got: " + link.ValidationResult); } if (link.ValidationResult != StandardMetadataConstants.ValidationOnLedgerSucceeded) { - return new ValidatedTwoWayLink(link, null, link.ValidationResult); + return new TwoWayLinkValidationResult(link, null, link.ValidationResult); } if (link.Discriminator == StandardMetadataKey.DappAccountType) { - return new ValidatedTwoWayLink(link, null, null); + return new TwoWayLinkValidationResult(link, null, null); } var target = (EntityAddress)link.TargetValue; @@ -370,10 +370,10 @@ private async ValueTask ResolveTwoWayLink(PartiallyValidate _ => throw CreateException(link, "unsupported entry discriminator"), }; - return new ValidatedTwoWayLink(link, resolved, null); + return new TwoWayLinkValidationResult(link, resolved, null); } - private async ValueTask ResolveDappClaimedWebsite(PartiallyValidatedTwoWayLink link, bool validateOnLedgerOnly, CancellationToken token) + private async ValueTask ResolveDappClaimedWebsite(PartiallyValidatedTwoWayLink link, bool validateOnLedgerOnly, CancellationToken token) { var claimedWebsite = link.TargetValue; @@ -407,19 +407,19 @@ private async ValueTask ResolveDappClaimedWebsite(Partially var invalidReason = await Validate(); return invalidReason == null - ? new ValidatedTwoWayLink(link, new DappClaimedWebsiteResolvedTwoWayLink(new Uri(claimedWebsite)), null) - : new ValidatedTwoWayLink(link, null, invalidReason); + ? new TwoWayLinkValidationResult(link, new DappClaimedWebsiteResolvedTwoWayLink(new Uri(claimedWebsite)), null) + : new TwoWayLinkValidationResult(link, null, invalidReason); } - private ValidatedTwoWayLink ResolveDappAccountLocker(PartiallyValidatedTwoWayLink link, ICollection allEntries) + private TwoWayLinkValidationResult ResolveDappAccountLocker(PartiallyValidatedTwoWayLink link, ICollection allEntries) { var entityAddress = (EntityAddress)link.EntityAddress; var lockerAddress = (EntityAddress)link.TargetValue; var isValid = allEntries.Any(x => x.EntityAddress == entityAddress && x.Discriminator == StandardMetadataKey.DappClaimedEntities && x.TargetValue == lockerAddress); return isValid - ? new ValidatedTwoWayLink(link, new DappAccountLockerResolvedTwoWayLink(lockerAddress), null) - : new ValidatedTwoWayLink(link, null, "claimed_entities entry with the locker address missing"); + ? new TwoWayLinkValidationResult(link, new DappAccountLockerResolvedTwoWayLink(lockerAddress), null) + : new TwoWayLinkValidationResult(link, null, "claimed_entities entry with the locker address missing"); } private Exception CreateException(PartiallyValidatedTwoWayLink entry, string details) diff --git a/tests/RadixDlt.NetworkGateway.UnitTests/Abstractions/Numerics/TokenAmountTests.cs b/tests/RadixDlt.NetworkGateway.UnitTests/Abstractions/Numerics/TokenAmountTests.cs index e41e87977..4a804d905 100644 --- a/tests/RadixDlt.NetworkGateway.UnitTests/Abstractions/Numerics/TokenAmountTests.cs +++ b/tests/RadixDlt.NetworkGateway.UnitTests/Abstractions/Numerics/TokenAmountTests.cs @@ -207,6 +207,7 @@ public void GivenNaN_SubunitsIsZero() new object[] { TokenAmount.FromStringParts(true, "1", "234"), false }, new object[] { TokenAmount.FromStringParts(false, "1", "-234"), true }, // Invalid call new object[] { TokenAmount.FromStringParts(true, "1", "-234"), true }, // Invalid call + new object[] { TokenAmount.FromDecimalString("5") / TokenAmount.Zero, true }, }; [Theory] @@ -242,4 +243,18 @@ public void Divide_ApproximateValue(string dividend, string divisor, string expe resultAsNumber.Should().BeApproximately(expectedAsNumber, 100); } + + [Theory] + [InlineData("12.5", "2", "25")] + [InlineData("2", "5", "10")] + [InlineData("2.1", "2", "4.2")] + [InlineData("50000000000", "0.123456789", "6172839450")] + public void Multiply_ExactValue(string op1, string op2, string expected) + { + var result = TokenAmount.FromDecimalString(op1) * TokenAmount.FromDecimalString(op2); + var resultAsNumber = decimal.Parse(result.ToString(), NumberFormatInfo.InvariantInfo); + var expectedAsNumber = decimal.Parse(expected, NumberFormatInfo.InvariantInfo); + + resultAsNumber.Should().Be(expectedAsNumber); + } }