diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bb0c9024..293689f8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -37,6 +37,7 @@ jobs: Golem.Tests/tests/*.log Golem.Tests/tests/*/modules/golem-data/yagna/*.log Golem.Tests/tests/*/modules/golem-data/provider/*.log + Golem.Tests/tests/*/modules/golem-data/provider/*.json Golem.Tests/tests/*/modules/golem-data/provider/exe-unit/work/logs/*.log Golem.Tests/tests/*/modules/golem-data/provider/exe-unit/work/*/agreement.json Golem.Tests/tests/*/modules/golem-data/provider/exe-unit/work/*/*/*.log diff --git a/GolemLib.Tests/TypesTests.cs b/GolemLib.Tests/TypesTests.cs index 238be0d3..5d084d8b 100644 --- a/GolemLib.Tests/TypesTests.cs +++ b/GolemLib.Tests/TypesTests.cs @@ -5,6 +5,7 @@ namespace GolemLib.Tests; // The following tests ensure that C# code's behavior matches Rust's one. public class TypesTests { + [Fact] public void GolemUsage_Reward_HappyPath() { @@ -152,4 +153,107 @@ public void GolemUsage_Reward_Overflow() var reward = usage.Reward(price); Assert.Equal(0.088189239176m, reward); } + + public static GolemUsage UsageFromAgreement(decimal[] array) + { + return new GolemUsage(new GolemPrice + { + StartPrice = 1.0m, + EnvPerSec = array[1], + GpuPerSec = array[2], + NumRequests = array[0] + }); + } + + public static GolemPrice PriceFromAgreement(decimal[] array) + { + return new GolemPrice + { + StartPrice = array[3], + EnvPerSec = array[1], + GpuPerSec = array[2], + NumRequests = array[0] + }; + } + + + [Fact] + public void GolemUsage_Reward_Case1() + { + var usage = UsageFromAgreement(new decimal[] { 27.0m, 538.8576082m, 8.7264478m }); + var price = PriceFromAgreement(new decimal[] { 0.0m, 0.0004m, 0.0003m, 0.0002m }); + + var reward = usage.Reward(price); + Assert.Equal(0.2183609776200000003m, reward); + } + + [Fact] + public void GolemUsage_Reward_Case2() + { + var usage = UsageFromAgreement(new decimal[] { 0.0m, 9.119924900000001m, 0.0m }); + var price = PriceFromAgreement(new decimal[] { 0.0m, 0.0004m, 0.0003m, 0.0002m }); + + var reward = usage.Reward(price); + Assert.Equal(0.0038479699600000004m, reward); + } + + [Fact] + public void GolemUsage_Reward_Case3() + { + var usage = UsageFromAgreement(new decimal[] { 0.0m, 9.1072277m, 0.0m }); + var price = PriceFromAgreement(new decimal[] { 0.0m, 0.0004m, 0.0003m, 0.0002m }); + + var reward = usage.Reward(price); + Assert.Equal(0.0038428910799999996m, reward); + } + + [Fact] + public void GolemUsage_Reward_Case4() + { + var usage = UsageFromAgreement(new decimal[] { 31.0m, 474.6873272m, 9.761029m }); + var price = PriceFromAgreement(new decimal[] { 0.0m, 0.0003m, 0.0005m, 0.0m }); + + var reward = usage.Reward(price); + Assert.Equal(0.1472867126600000005m, reward); + } + + [Fact] + public void GolemUsage_Reward_Case5() + { + var usage = UsageFromAgreement(new decimal[] { 0.0m, 66.5695986m, 0.0m }); + var price = PriceFromAgreement(new decimal[] { 0.0m, 0.00025m, 0.00025m, 0.0m }); + + var reward = usage.Reward(price); + Assert.Equal(0.0166423996500000025m, reward); + } + + [Fact] + public void GolemUsage_Reward_Case6() + { + var usage = UsageFromAgreement(new decimal[] { 1.0m, 41.4281323m, 9.8315089m }); + var price = PriceFromAgreement(new decimal[] { 0.0m, 0.00025m, 0.00025m, 0.0m }); + + var reward = usage.Reward(price); + Assert.Equal(0.01281491029999999975m, reward); + } + + [Fact] + public void GolemUsage_Reward_Case7() + { + var usage = UsageFromAgreement(new decimal[] { 0.0m, 9.132793m, 0.0m }); + var price = PriceFromAgreement(new decimal[] { 0.0m, 0.00025m, 0.00025m, 0.0m }); + + var reward = usage.Reward(price); + Assert.Equal(0.00228319824999999975m, reward); + } + + [Fact] + public void GolemUsage_Reward_Case8() + { + var usage = UsageFromAgreement(new decimal[] { 0.0m, 88.9031127m, 0.0m }); + var price = PriceFromAgreement(new decimal[] { 0.0m, 0.00025m, 0.00025m, 0.0m }); + + var reward = usage.Reward(price); + Assert.Equal(0.0222257781749999975m, reward); + } } diff --git a/GolemLib/types.cs b/GolemLib/types.cs index ed467ad4..7e9ef288 100644 --- a/GolemLib/types.cs +++ b/GolemLib/types.cs @@ -14,6 +14,9 @@ public class GolemUsage : GolemPrice { public static decimal Round(decimal v) => GolemUsage.RustCompatibilityRound(v); + // Print to string with exponential notation including 16 significant digits (1 integer digit and 15 fractional digits). + private const string print16SignificantDigitsFormat = "E15"; + // I couldn't find definite answer if C#'s `double` is IEEE-754 compliant floating number: // https://csharpindepth.com/Articles/FloatingPoint claims that it is, stackoverflow claimed that it isn't. // Still, both `double` and IEEE-754 64-bit floating numbers use 52 bits for binary significant digits, @@ -22,11 +25,14 @@ public class GolemUsage : GolemPrice // digits when parsing floats. In order to get the same results in Rust and C#, we need to match its behavior. private static decimal RustCompatibilityRound(decimal i) { - // `decimal` constructor using `double` argument truncates the value to 15 significant digits. - // To receive a more accurate result (needed to match Rust's behavior), we need to use the `string`-based construction method. + // `decimal` constructor using `double` argument truncates the value to 15 decimal significant digits. + // `decimal` to double cast also truncates the argument to 15 decimal significant digits. + // To receive a more accurate result (needed to match Rust's behavior), we need to use the `string`-based construction methods. - // Print to string with exponential notation including 16 significant digits (1 integer digit and 15 fractional digits). - var formattedDouble = ((double)i).ToString("E15", CultureInfo.InvariantCulture); + string formattedDecimal = i.ToString(print16SignificantDigitsFormat, CultureInfo.InvariantCulture); + // We need to go through a `double` to introduce float inaccuracies to match Rust's behavior. + double doubleIntroducingInaccuracy = double.Parse(formattedDecimal); + string formattedDouble = doubleIntroducingInaccuracy.ToString(print16SignificantDigitsFormat, CultureInfo.InvariantCulture); return decimal.Parse(formattedDouble, NumberStyles.Float); }