From 2d2799e339db6d29762142167e7613d61a9a08f7 Mon Sep 17 00:00:00 2001
From: librelois <c@elo.tf>
Date: Sun, 5 Jun 2022 17:14:49 +0200
Subject: [PATCH] fix(gdev): add constant tx fees (2 cents by tx) to prevent
 #62

Fix #62
---
 end2end-tests/README.md                       | 75 ++++++++++---------
 .../account_creation.feature                  |  1 +
 .../cucumber-features/transfer_all.feature    | 15 ++--
 end2end-tests/tests/cucumber_tests.rs         | 63 ++++++++++++----
 runtime/common/src/fees.rs                    |  6 +-
 5 files changed, 101 insertions(+), 59 deletions(-)

diff --git a/end2end-tests/README.md b/end2end-tests/README.md
index 8dfd383e4..c15fc019d 100644
--- a/end2end-tests/README.md
+++ b/end2end-tests/README.md
@@ -1,4 +1,4 @@
-# Duniter-v2s integration tests
+# Duniter-v2s end2end tests
 
 ## cucumber functionnal tests
 
@@ -47,37 +47,9 @@ Feature: My awesome feature
     Then We should observe that
 ```
 
-### Test users
-
-6 test users are provided:
-
-- alice
-- bob
-- charlie
-- dave
-- eve
-- ferdie
-
-### genesis state
-
-Each scenario bootstraps its own blockchain with its own genesis state.
-
-By default, all scenarios use the same configuration for the genesis, which is located in the file
-`/cucumber-genesis/default.json`.
+### Steps
 
-You can define a custom genesis state for each scenario with the tag `@genesis.confName`.
-
-The genesis configuration must then be defined in a json file located at
-`/cucumber-genesis/confName.json`.
-
-You can also define a custom genesis at the feature level, all the scenarios of this feature will
-then inherit the genesis configuration.
-
-### Currency amounts
-
-Amounts must be expressed as an integer of `ÄžD` or `UD`, decimal numbers are not supported.
-If you need more precision, you can express amounts in cents of ÄžD (write `cÄžD`), or in thousandths
-of UD (write `mUD`).
+Each scenario is a list of steps. In our context (blockchain), only the `When` and `Then` steps make sense, `Given` being the genesis.
 
 #### When
 
@@ -95,23 +67,54 @@ List of possible actions:
 
     Example: `alice should have 10 ÄžD`
 
-### Universal dividend creation
-
-#### Then
-
 -  Check the current UD amount
 
     Usage: `Current UD amount should be {amount}.{cents} ÄžD`
 
     Example: `Current UD amount should be 10.00 ÄžD`
 
-
 -  Check the monetary mass
 
     Usage: `Monetary mass should be {amount}.{cents} ÄžD`
 
     Example: `Monetary mass should be 30.00 ÄžD`
 
+### Test users
+
+6 test users are provided:
+
+- alice
+- bob
+- charlie
+- dave
+- eve
+- ferdie
+
+### Currency amounts
+
+Amounts must be expressed as an integer of `ÄžD` or `UD`, decimal numbers are not supported.
+If you need more precision, you can express amounts in cents of ÄžD (write `cÄžD`), or in thousandths
+of UD (write `mUD`).
+
+### genesis state
+
+Each scenario bootstraps its own blockchain with its own genesis state.
+
+By default, all scenarios use the same configuration for the genesis, which is located in the file
+`/cucumber-genesis/default.json`.
+
+You can define a custom genesis state for each scenario with the tag `@genesis.confName`.
+
+The genesis configuration must then be defined in a json file located at
+`/cucumber-genesis/confName.json`.
+
+You can also define a custom genesis at the feature level, all the scenarios of this feature will
+then inherit the genesis configuration.
+
+### ignoreErrors
+
+For some scenarios, you may need to perform an action (When) that fails voluntarily, in this case you must add the tag @ignoreErrors to your scenario, otherwise it will be considered as failed
+
 ### Run cucumber functional tests
 
 To run the cucumber tests, you will need to have the rust toolchain installed locally.
diff --git a/end2end-tests/cucumber-features/account_creation.feature b/end2end-tests/cucumber-features/account_creation.feature
index bc2fc4e78..e3df0d149 100644
--- a/end2end-tests/cucumber-features/account_creation.feature
+++ b/end2end-tests/cucumber-features/account_creation.feature
@@ -26,6 +26,7 @@ Feature: Balance transfer
     """
     Then eve should have 2 ÄžD
 
+  @ignoreErrors
   Scenario: Create a new account without any founds
     Then eve should have 0 ÄžD
     When eve send 0 ÄžD to alice
diff --git a/end2end-tests/cucumber-features/transfer_all.feature b/end2end-tests/cucumber-features/transfer_all.feature
index b111bb518..23b516ee4 100644
--- a/end2end-tests/cucumber-features/transfer_all.feature
+++ b/end2end-tests/cucumber-features/transfer_all.feature
@@ -1,11 +1,14 @@
 @genesis.default
 Feature: Balance transfer all
 
-  Scenario: If alice sends all her ÄžDs to Dave, Dave will get 8 ÄžD
-    When alice sends all her ÄžDs to dave
+  Scenario: If bob sends all his ÄžDs to Dave
+    When bob sends all her ÄžDs to dave
     """
-    Alice is a smith member, as such she is not allowed to empty her account completely,
-    if she tries to do so, the existence deposit (2 ÄžD) must remain.
+    Bob is a member, as such he is not allowed to empty his account completely,
+    if he tries to do so, the existence deposit (2 ÄžD) must remain.
     """
-    Then alice should have 2 ÄžD
-    Then dave should have 8 ÄžD
+    Then bob should have 2 ÄžD
+    """
+    10 ÄžD (initial Bob balance) - 2 ÄžD (Existential deposit) - 0.02 ÄžD (transaction fees)
+    """
+    Then dave should have 798 cÄžD
diff --git a/end2end-tests/tests/cucumber_tests.rs b/end2end-tests/tests/cucumber_tests.rs
index ffe54030a..a039bc2b7 100644
--- a/end2end-tests/tests/cucumber_tests.rs
+++ b/end2end-tests/tests/cucumber_tests.rs
@@ -29,33 +29,44 @@ use std::sync::{
 };
 
 #[derive(WorldInit)]
-pub struct DuniterWorld(Option<DuniterWorldInner>);
+pub struct DuniterWorld {
+    ignore_errors: bool,
+    inner: Option<DuniterWorldInner>,
+}
 
 impl DuniterWorld {
+    // Write methods
     async fn init(&mut self, maybe_genesis_conf_file: Option<PathBuf>) {
-        if let Some(ref mut inner) = self.0 {
+        if let Some(ref mut inner) = self.inner {
+            inner.kill();
+        }
+        self.inner = Some(DuniterWorldInner::new(maybe_genesis_conf_file).await);
+    }
+    fn kill(&mut self) {
+        if let Some(ref mut inner) = self.inner {
             inner.kill();
         }
-        self.0 = Some(DuniterWorldInner::new(maybe_genesis_conf_file).await);
     }
+    fn set_ignore_errors(&mut self, ignore_errors: bool) {
+        self.ignore_errors = ignore_errors;
+    }
+    // Read methods
     fn api(&self) -> &Api {
-        if let Some(ref inner) = self.0 {
+        if let Some(ref inner) = self.inner {
             &inner.api
         } else {
             panic!("uninit")
         }
     }
     fn client(&self) -> &Client {
-        if let Some(ref inner) = self.0 {
+        if let Some(ref inner) = self.inner {
             &inner.client
         } else {
             panic!("uninit")
         }
     }
-    fn kill(&mut self) {
-        if let Some(ref mut inner) = self.0 {
-            inner.kill();
-        }
+    fn ignore_errors(&self) -> bool {
+        self.ignore_errors
     }
 }
 
@@ -71,7 +82,10 @@ impl World for DuniterWorld {
     type Error = Infallible;
 
     async fn new() -> std::result::Result<Self, Infallible> {
-        Ok(DuniterWorld(None))
+        Ok(Self {
+            ignore_errors: false,
+            inner: None,
+        })
     }
 }
 
@@ -148,10 +162,16 @@ async fn transfer(
     let to = AccountKeyring::from_str(&to).expect("unknown to");
     let (amount, is_ud) = parse_amount(amount, &unit);
 
-    if is_ud {
+    let res = if is_ud {
         common::balances::transfer_ud(world.api(), world.client(), from, amount, to).await
     } else {
         common::balances::transfer(world.api(), world.client(), from, amount, to).await
+    };
+
+    if world.ignore_errors() {
+        Ok(())
+    } else {
+        res
     }
 }
 
@@ -164,13 +184,18 @@ async fn send_all_to(world: &mut DuniterWorld, from: String, to: String) -> Resu
     common::balances::transfer_all(world.api(), world.client(), from, to).await
 }
 
-#[then(regex = r"([a-zA-Z]+) should have (\d+) ÄžD")]
-async fn should_have(world: &mut DuniterWorld, who: String, amount: u64) -> Result<()> {
+#[then(regex = r"([a-zA-Z]+) should have (\d+) (ÄžD|cÄžD)")]
+async fn should_have(
+    world: &mut DuniterWorld,
+    who: String,
+    amount: u64,
+    unit: String,
+) -> Result<()> {
     // Parse inputs
     let who = AccountKeyring::from_str(&who)
         .expect("unknown to")
         .to_account_id();
-    let amount = amount * 100;
+    let (amount, _is_ud) = parse_amount(amount, &unit);
 
     let who_account = world.api().storage().system().account(who, None).await?;
     assert_eq!(who_account.data.free, amount);
@@ -250,6 +275,7 @@ async fn main() {
                 "{}.json",
                 genesis_conf_name(&feature.tags, &scenario.tags)
             ));
+            world.set_ignore_errors(ignore_errors(&scenario.tags));
             Box::pin(world.init(Some(genesis_conf_file_path)))
         })
         .after(move |_feature, _rule, _scenario, maybe_world| {
@@ -280,3 +306,12 @@ fn genesis_conf_name(feature_tags: &[String], scenario_tags: &[String]) -> Strin
     }
     "default".to_owned()
 }
+
+fn ignore_errors(scenario_tags: &[String]) -> bool {
+    for tag in scenario_tags {
+        if tag == "ignoreErrors" {
+            return true;
+        }
+    }
+    false
+}
diff --git a/runtime/common/src/fees.rs b/runtime/common/src/fees.rs
index aec304d1d..cac38836f 100644
--- a/runtime/common/src/fees.rs
+++ b/runtime/common/src/fees.rs
@@ -17,7 +17,7 @@
 pub use frame_support::weights::{
     Weight, WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial,
 };
-use sp_arithmetic::traits::{BaseArithmetic, Unsigned, Zero};
+use sp_arithmetic::traits::{BaseArithmetic, One, Unsigned};
 
 pub struct WeightToFeeImpl<T>(sp_std::marker::PhantomData<T>);
 
@@ -35,8 +35,8 @@ where
             degree: 1,
         })
     }
-    // Force disable fees
+    // Force constant fees
     fn calc(_weight: &Weight) -> Self::Balance {
-        Zero::zero()
+        One::one()
     }
 }
-- 
GitLab