From eaf95719982ea4746872c79e65780a6491036aa9 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    |  7 +-
 end2end-tests/tests/cucumber_tests.rs         | 63 ++++++++++++----
 runtime/common/src/fees.rs                    |  6 +-
 5 files changed, 97 insertions(+), 55 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..00077ebd6 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
+  Scenario: If alice sends all her ÄžDs to Dave
     When alice 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.
     """
     Then alice should have 2 ÄžD
-    Then dave should have 8 ÄžD
+    """
+    10 ÄžD (initial Alice 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