From 8dbc217c0ca9cd106ccea942e9e57ec594d4c153 Mon Sep 17 00:00:00 2001 From: "nieznany.sprawiciel" Date: Mon, 22 Jul 2024 19:30:39 +0200 Subject: [PATCH 1/5] Tests reproducing problems with payments --- GolemLib.Tests/TypesTests.cs | 104 +++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/GolemLib.Tests/TypesTests.cs b/GolemLib.Tests/TypesTests.cs index 238be0d..8f18c5b 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.21836097762m, 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.00384796996m, 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.00384289108m, 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.147286712660000001m, 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.016642399650000003m, 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.0128149103m, 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.00228319825m, 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.022225778174999998m, reward); + } } From cf660660c6f818093b4174805fb17a0eb226063c Mon Sep 17 00:00:00 2001 From: "nieznany.sprawiciel" Date: Tue, 23 Jul 2024 19:12:56 +0200 Subject: [PATCH 2/5] Conversion to double through string --- GolemLib.Tests/TypesTests.cs | 16 ++++++++-------- GolemLib/types.cs | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/GolemLib.Tests/TypesTests.cs b/GolemLib.Tests/TypesTests.cs index 8f18c5b..5d084d8 100644 --- a/GolemLib.Tests/TypesTests.cs +++ b/GolemLib.Tests/TypesTests.cs @@ -184,7 +184,7 @@ public void GolemUsage_Reward_Case1() var price = PriceFromAgreement(new decimal[] { 0.0m, 0.0004m, 0.0003m, 0.0002m }); var reward = usage.Reward(price); - Assert.Equal(0.21836097762m, reward); + Assert.Equal(0.2183609776200000003m, reward); } [Fact] @@ -194,7 +194,7 @@ public void GolemUsage_Reward_Case2() var price = PriceFromAgreement(new decimal[] { 0.0m, 0.0004m, 0.0003m, 0.0002m }); var reward = usage.Reward(price); - Assert.Equal(0.00384796996m, reward); + Assert.Equal(0.0038479699600000004m, reward); } [Fact] @@ -204,7 +204,7 @@ public void GolemUsage_Reward_Case3() var price = PriceFromAgreement(new decimal[] { 0.0m, 0.0004m, 0.0003m, 0.0002m }); var reward = usage.Reward(price); - Assert.Equal(0.00384289108m, reward); + Assert.Equal(0.0038428910799999996m, reward); } [Fact] @@ -214,7 +214,7 @@ public void GolemUsage_Reward_Case4() var price = PriceFromAgreement(new decimal[] { 0.0m, 0.0003m, 0.0005m, 0.0m }); var reward = usage.Reward(price); - Assert.Equal(0.147286712660000001m, reward); + Assert.Equal(0.1472867126600000005m, reward); } [Fact] @@ -224,7 +224,7 @@ public void GolemUsage_Reward_Case5() var price = PriceFromAgreement(new decimal[] { 0.0m, 0.00025m, 0.00025m, 0.0m }); var reward = usage.Reward(price); - Assert.Equal(0.016642399650000003m, reward); + Assert.Equal(0.0166423996500000025m, reward); } [Fact] @@ -234,7 +234,7 @@ public void GolemUsage_Reward_Case6() var price = PriceFromAgreement(new decimal[] { 0.0m, 0.00025m, 0.00025m, 0.0m }); var reward = usage.Reward(price); - Assert.Equal(0.0128149103m, reward); + Assert.Equal(0.01281491029999999975m, reward); } [Fact] @@ -244,7 +244,7 @@ public void GolemUsage_Reward_Case7() var price = PriceFromAgreement(new decimal[] { 0.0m, 0.00025m, 0.00025m, 0.0m }); var reward = usage.Reward(price); - Assert.Equal(0.00228319825m, reward); + Assert.Equal(0.00228319824999999975m, reward); } [Fact] @@ -254,6 +254,6 @@ public void GolemUsage_Reward_Case8() var price = PriceFromAgreement(new decimal[] { 0.0m, 0.00025m, 0.00025m, 0.0m }); var reward = usage.Reward(price); - Assert.Equal(0.022225778174999998m, reward); + Assert.Equal(0.0222257781749999975m, reward); } } diff --git a/GolemLib/types.cs b/GolemLib/types.cs index ed467ad..0d6a155 100644 --- a/GolemLib/types.cs +++ b/GolemLib/types.cs @@ -26,7 +26,8 @@ private static decimal RustCompatibilityRound(decimal i) // To receive a more accurate result (needed to match Rust's behavior), we need to use the `string`-based construction method. // 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); + var formattedDecimal = double.Parse(i.ToString("E15", CultureInfo.InvariantCulture)); + var formattedDouble = formattedDecimal.ToString("E15", CultureInfo.InvariantCulture); return decimal.Parse(formattedDouble, NumberStyles.Float); } From 68992401ef7e4579187888119e5965f5740350dc Mon Sep 17 00:00:00 2001 From: "nieznany.sprawiciel" Date: Wed, 24 Jul 2024 11:58:51 +0200 Subject: [PATCH 3/5] Decimal -> double conversion through string --- GolemLib/types.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/GolemLib/types.cs b/GolemLib/types.cs index 0d6a155..3ca5eba 100644 --- a/GolemLib/types.cs +++ b/GolemLib/types.cs @@ -25,8 +25,11 @@ 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. - // Print to string with exponential notation including 16 significant digits (1 integer digit and 15 fractional digits). + // Decimal -> double default conversion rounds decimal in a way that after converting back to decimal we get different number. + // FOr this reason we need to use string as an intermediate step. var formattedDecimal = double.Parse(i.ToString("E15", CultureInfo.InvariantCulture)); + + // Print to string with exponential notation including 16 significant digits (1 integer digit and 15 fractional digits). var formattedDouble = formattedDecimal.ToString("E15", CultureInfo.InvariantCulture); return decimal.Parse(formattedDouble, NumberStyles.Float); } From b3aba7d3bb6b13ed45781fd035273d29383b6acc Mon Sep 17 00:00:00 2001 From: "nieznany.sprawiciel" Date: Wed, 24 Jul 2024 14:23:46 +0200 Subject: [PATCH 4/5] Colllect ci Provider configuration files --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bb0c902..293689f 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 From c514e75972e64ff6ace6c0c34644fda343a8e473 Mon Sep 17 00:00:00 2001 From: Adam Czajkowski <48181325+prawilny@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:28:48 +0200 Subject: [PATCH 5/5] Better comments --- GolemLib/types.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/GolemLib/types.cs b/GolemLib/types.cs index 3ca5eba..7e9ef28 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,15 +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 -> double default conversion rounds decimal in a way that after converting back to decimal we get different number. - // FOr this reason we need to use string as an intermediate step. - var formattedDecimal = double.Parse(i.ToString("E15", CultureInfo.InvariantCulture)); + // `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 = formattedDecimal.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); }