├── .gitattributes
├── .gitignore
├── scripts
└── init.sh
├── src
├── main.rs
├── cli.rs
├── chain_spec.rs
└── service.rs
├── LICENSE
├── runtime
├── build.rs
├── Cargo.toml
└── src
│ ├── lib.rs
│ └── fungible.rs
├── README.md
└── Cargo.toml
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | **/target/
4 | # These are backup files generated by rustfmt
5 | **/*.rs.bk
--------------------------------------------------------------------------------
/scripts/init.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | echo "*** Initializing WASM build environment"
6 |
7 | if [ -z $CI_PROJECT_NAME ] ; then
8 | rustup update nightly
9 | rustup update stable
10 | fi
11 |
12 | rustup target add wasm32-unknown-unknown --toolchain nightly
13 |
14 | # Install wasm-gc. It's useful for stripping slimming down wasm binaries.
15 | command -v wasm-gc || \
16 | cargo +nightly install --git https://github.com/alexcrichton/wasm-gc --force
17 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | //! Substrate Node Template CLI library.
2 |
3 | #![warn(missing_docs)]
4 | #![warn(unused_extern_crates)]
5 |
6 | mod chain_spec;
7 | mod service;
8 | mod cli;
9 |
10 | pub use substrate_cli::{VersionInfo, IntoExit, error};
11 |
12 | fn main() {
13 | let version = VersionInfo {
14 | name: "Substrate Node",
15 | commit: env!("VERGEN_SHA_SHORT"),
16 | version: env!("CARGO_PKG_VERSION"),
17 | executable_name: "node-template",
18 | author: "Anonymous",
19 | description: "Template Node",
20 | support_url: "support.anonymous.an",
21 | };
22 |
23 | if let Err(e) = cli::run(::std::env::args(), cli::Exit, version) {
24 | eprintln!("Fatal error: {}\n\n{:?}", e, e);
25 | std::process::exit(1)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Shawn Tabrizi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/runtime/build.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Parity Technologies (UK) Ltd.
2 | // This file is part of Substrate.
3 |
4 | // Substrate is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 |
9 | // Substrate is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU General Public License for more details.
13 |
14 | // You should have received a copy of the GNU General Public License
15 | // along with Substrate. If not, see .
16 |
17 | use wasm_builder_runner::{build_current_project_with_rustflags, WasmBuilderSource};
18 |
19 | fn main() {
20 | build_current_project_with_rustflags(
21 | "wasm_binary.rs",
22 | WasmBuilderSource::Crates("1.0.4"),
23 | // This instructs LLD to export __heap_base as a global variable, which is used by the
24 | // external memory allocator.
25 | "-Clink-arg=--export=__heap_base",
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # substrate-feeless-token-factory
2 |
3 | The goal of this project is to investigate alternative fee mechanics for managing token assets.
4 |
5 | ## Background
6 |
7 | Ethereum has shown itself to be the ultimate platform for building token economies. Thousands of contracts have been created which support standards like ERC20 and ERC721.
8 |
9 | However, businesses using Ethereum have struggled adopting new users into the ecosystem due to the upfront costs of ETH (the native blockchain currency) to interact with these tokens. Many newcomers do not understand why they need ETH to be able to interact with other tokens they actually are interested in.
10 |
11 | Businesses have shown that they would be more than happy to fund the usage of their users. Some have done this by providing a faucet or ETH drop to their users, while others may have implemented L2 solutions or have made compromises building centralized solutions.
12 |
13 | ## What is it?
14 |
15 | In simple terms, this project provides a runtime module which provides the following features:
16 |
17 | * A token factory where any user is able to create an ERC20 compliant token on top of the Substrate runtime
18 | * An additional API for transfer of these tokens without the end user paying any fees in the native currency
19 |
20 | Ideas for alternative payment methods for transfers:
21 |
22 | - [x] Token Fee Fund: A fund for a particular token where token transfers are paid from the fund.
23 | - [x] Pay with Token: The ability to "pay" the block producer not with the native currency, but with the token you are trying to transfer.
24 | - [ ] Proof of Work: Complete some small proof of work along with your transfer to allow the transaction to be included.
25 | - [ ] ?
26 |
27 | At the time of writing this, the "pay with token" method is not quite supported with our front end libraries.
28 |
29 | ## User Story
30 |
31 | For example, the "Better Energy Foundation" issue a new token to be used as electricity credits.
32 |
33 | When they do this, they fund the token with an **initial fund** of the underlying blockchain currency: **10,000 units**. They specify that the users of their token have **10 free transactions every 1,000 blocks**.
34 |
35 | They can sell their tokens and transfer them to the buyers just like a normal ICO.
36 |
37 | These buyers can then call the `try_free_transfer` function when trying to trade the token among their peers, and the fees are paid for using the fund.
38 |
39 | Anyone in the community can continue to add more funds, and allow the free transfers to continue.
40 |
41 | If a user does not have any more "free" transactions left for the current period, they can always make a transaction using the normal `transfer` function which will charge them a normal fee.
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [[bin]]
2 | name = 'node-template'
3 | path = 'src/main.rs'
4 |
5 | [dependencies]
6 | derive_more = '0.14.0'
7 | exit-future = '0.1'
8 | futures = '0.1'
9 | log = '0.4'
10 | parking_lot = '0.9.0'
11 | tokio = '0.1'
12 | trie-root = '0.15.2'
13 |
14 | [dependencies.babe]
15 | git = 'https://github.com/paritytech/substrate.git'
16 | package = 'substrate-consensus-babe'
17 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
18 |
19 | [dependencies.babe-primitives]
20 | git = 'https://github.com/paritytech/substrate.git'
21 | package = 'substrate-consensus-babe-primitives'
22 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
23 |
24 | [dependencies.basic-authorship]
25 | git = 'https://github.com/paritytech/substrate.git'
26 | package = 'substrate-basic-authorship'
27 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
28 |
29 | [dependencies.codec]
30 | package = 'parity-scale-codec'
31 | version = '1.0.0'
32 |
33 | [dependencies.ctrlc]
34 | features = ['termination']
35 | version = '3.0'
36 |
37 | [dependencies.grandpa]
38 | git = 'https://github.com/paritytech/substrate.git'
39 | package = 'substrate-finality-grandpa'
40 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
41 |
42 | [dependencies.grandpa-primitives]
43 | git = 'https://github.com/paritytech/substrate.git'
44 | package = 'substrate-finality-grandpa-primitives'
45 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
46 |
47 | [dependencies.inherents]
48 | git = 'https://github.com/paritytech/substrate.git'
49 | package = 'substrate-inherents'
50 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
51 |
52 | [dependencies.network]
53 | git = 'https://github.com/paritytech/substrate.git'
54 | package = 'substrate-network'
55 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
56 |
57 | [dependencies.node-template-runtime]
58 | path = 'runtime'
59 |
60 | [dependencies.primitives]
61 | git = 'https://github.com/paritytech/substrate.git'
62 | package = 'substrate-primitives'
63 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
64 |
65 | [dependencies.sr-io]
66 | git = 'https://github.com/paritytech/substrate.git'
67 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
68 |
69 | [dependencies.substrate-cli]
70 | git = 'https://github.com/paritytech/substrate.git'
71 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
72 |
73 | [dependencies.substrate-client]
74 | git = 'https://github.com/paritytech/substrate.git'
75 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
76 |
77 | [dependencies.substrate-executor]
78 | git = 'https://github.com/paritytech/substrate.git'
79 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
80 |
81 | [dependencies.substrate-service]
82 | git = 'https://github.com/paritytech/substrate.git'
83 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
84 |
85 | [dependencies.transaction-pool]
86 | git = 'https://github.com/paritytech/substrate.git'
87 | package = 'substrate-transaction-pool'
88 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
89 |
90 | [package]
91 | authors = ['Anonymous']
92 | build = 'build.rs'
93 | edition = '2018'
94 | name = 'node-template'
95 | version = '2.0.0'
96 | [profile.release]
97 | panic = 'unwind'
98 |
99 | [build-dependencies]
100 | vergen = '3'
101 |
--------------------------------------------------------------------------------
/src/cli.rs:
--------------------------------------------------------------------------------
1 | use crate::service;
2 | use futures::{future, Future, sync::oneshot};
3 | use std::cell::RefCell;
4 | use tokio::runtime::Runtime;
5 | pub use substrate_cli::{VersionInfo, IntoExit, error};
6 | use substrate_cli::{informant, parse_and_prepare, ParseAndPrepare, NoCustom};
7 | use substrate_service::{ServiceFactory, Roles as ServiceRoles};
8 | use crate::chain_spec;
9 | use std::ops::Deref;
10 | use log::info;
11 |
12 | /// Parse command line arguments into service configuration.
13 | pub fn run(args: I, exit: E, version: VersionInfo) -> error::Result<()> where
14 | I: IntoIterator- ,
15 | T: Into
+ Clone,
16 | E: IntoExit,
17 | {
18 | match parse_and_prepare::(&version, "substrate-node", args) {
19 | ParseAndPrepare::Run(cmd) => cmd.run(load_spec, exit, |exit, _cli_args, _custom_args, config| {
20 | info!("{}", version.name);
21 | info!(" version {}", config.full_version());
22 | info!(" by {}, 2017, 2018", version.author);
23 | info!("Chain specification: {}", config.chain_spec.name());
24 | info!("Node name: {}", config.name);
25 | info!("Roles: {:?}", config.roles);
26 | let runtime = Runtime::new().map_err(|e| format!("{:?}", e))?;
27 | match config.roles {
28 | ServiceRoles::LIGHT => run_until_exit(
29 | runtime,
30 | service::Factory::new_light(config).map_err(|e| format!("{:?}", e))?,
31 | exit
32 | ),
33 | _ => run_until_exit(
34 | runtime,
35 | service::Factory::new_full(config).map_err(|e| format!("{:?}", e))?,
36 | exit
37 | ),
38 | }.map_err(|e| format!("{:?}", e))
39 | }),
40 | ParseAndPrepare::BuildSpec(cmd) => cmd.run(load_spec),
41 | ParseAndPrepare::ExportBlocks(cmd) => cmd.run::(load_spec, exit),
42 | ParseAndPrepare::ImportBlocks(cmd) => cmd.run::(load_spec, exit),
43 | ParseAndPrepare::PurgeChain(cmd) => cmd.run(load_spec),
44 | ParseAndPrepare::RevertChain(cmd) => cmd.run::(load_spec),
45 | ParseAndPrepare::CustomCommand(_) => Ok(())
46 | }?;
47 |
48 | Ok(())
49 | }
50 |
51 | fn load_spec(id: &str) -> Result, String> {
52 | Ok(match chain_spec::Alternative::from(id) {
53 | Some(spec) => Some(spec.load()?),
54 | None => None,
55 | })
56 | }
57 |
58 | fn run_until_exit(
59 | mut runtime: Runtime,
60 | service: T,
61 | e: E,
62 | ) -> error::Result<()> where
63 | T: Deref>,
64 | T: Future- + Send + 'static,
65 | C: substrate_service::Components,
66 | E: IntoExit,
67 | {
68 | let (exit_send, exit) = exit_future::signal();
69 |
70 | let informant = informant::build(&service);
71 | runtime.executor().spawn(exit.until(informant).map(|_| ()));
72 |
73 | // we eagerly drop the service so that the internal exit future is fired,
74 | // but we need to keep holding a reference to the global telemetry guard
75 | let _telemetry = service.telemetry();
76 |
77 | let service_res = {
78 | let exit = e.into_exit().map_err(|_| error::Error::Other("Exit future failed.".into()));
79 | let service = service.map_err(|err| error::Error::Service(err));
80 | let select = service.select(exit).map(|_| ()).map_err(|(err, _)| err);
81 | runtime.block_on(select)
82 | };
83 |
84 | exit_send.fire();
85 |
86 | // TODO [andre]: timeout this future #1318
87 | let _ = runtime.shutdown_on_idle().wait();
88 |
89 | service_res
90 | }
91 |
92 | // handles ctrl-c
93 | pub struct Exit;
94 | impl IntoExit for Exit {
95 | type Exit = future::MapErr
, fn(oneshot::Canceled) -> ()>;
96 | fn into_exit(self) -> Self::Exit {
97 | // can't use signal directly here because CtrlC takes only `Fn`.
98 | let (exit_send, exit) = oneshot::channel();
99 |
100 | let exit_send_cell = RefCell::new(Some(exit_send));
101 | ctrlc::set_handler(move || {
102 | if let Some(exit_send) = exit_send_cell.try_borrow_mut().expect("signal handler not reentrant; qed").take() {
103 | exit_send.send(()).expect("Error sending exit notification");
104 | }
105 | }).expect("Error setting Ctrl-C handler");
106 |
107 | exit.map_err(drop)
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/chain_spec.rs:
--------------------------------------------------------------------------------
1 | use primitives::{Pair, Public};
2 | use node_template_runtime::{
3 | AccountId, BabeConfig, BalancesConfig, GenesisConfig, GrandpaConfig,
4 | SudoConfig, IndicesConfig, SystemConfig, WASM_BINARY,
5 | };
6 | use babe_primitives::{AuthorityId as BabeId};
7 | use grandpa_primitives::{AuthorityId as GrandpaId};
8 | use substrate_service;
9 |
10 | // Note this is the URL for the telemetry server
11 | //const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/";
12 |
13 | /// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type.
14 | pub type ChainSpec = substrate_service::ChainSpec;
15 |
16 | /// The chain specification option. This is expected to come in from the CLI and
17 | /// is little more than one of a number of alternatives which can easily be converted
18 | /// from a string (`--chain=...`) into a `ChainSpec`.
19 | #[derive(Clone, Debug)]
20 | pub enum Alternative {
21 | /// Whatever the current runtime is, with just Alice as an auth.
22 | Development,
23 | /// Whatever the current runtime is, with simple Alice/Bob auths.
24 | LocalTestnet,
25 | }
26 |
27 | /// Helper function to generate a crypto pair from seed
28 | pub fn get_from_seed(seed: &str) -> ::Public {
29 | TPublic::Pair::from_string(&format!("//{}", seed), None)
30 | .expect("static values are valid; qed")
31 | .public()
32 | }
33 |
34 | /// Helper function to generate stash, controller and session key from seed
35 | pub fn get_authority_keys_from_seed(seed: &str) -> (AccountId, AccountId, GrandpaId, BabeId) {
36 | (
37 | get_from_seed::(&format!("{}//stash", seed)),
38 | get_from_seed::(seed),
39 | get_from_seed::(seed),
40 | get_from_seed::(seed),
41 | )
42 | }
43 |
44 | impl Alternative {
45 | /// Get an actual chain config from one of the alternatives.
46 | pub(crate) fn load(self) -> Result {
47 | Ok(match self {
48 | Alternative::Development => ChainSpec::from_genesis(
49 | "Development",
50 | "dev",
51 | || testnet_genesis(vec![
52 | get_authority_keys_from_seed("Alice"),
53 | ],
54 | get_from_seed::("Alice"),
55 | vec![
56 | get_from_seed::("Alice"),
57 | get_from_seed::("Bob"),
58 | get_from_seed::("Alice//stash"),
59 | get_from_seed::("Bob//stash"),
60 | ],
61 | true),
62 | vec![],
63 | None,
64 | None,
65 | None,
66 | None
67 | ),
68 | Alternative::LocalTestnet => ChainSpec::from_genesis(
69 | "Local Testnet",
70 | "local_testnet",
71 | || testnet_genesis(vec![
72 | get_authority_keys_from_seed("Alice"),
73 | get_authority_keys_from_seed("Bob"),
74 | ],
75 | get_from_seed::("Alice"),
76 | vec![
77 | get_from_seed::("Alice"),
78 | get_from_seed::("Bob"),
79 | get_from_seed::("Charlie"),
80 | get_from_seed::("Dave"),
81 | get_from_seed::("Eve"),
82 | get_from_seed::("Ferdie"),
83 | get_from_seed::("Alice//stash"),
84 | get_from_seed::("Bob//stash"),
85 | get_from_seed::("Charlie//stash"),
86 | get_from_seed::("Dave//stash"),
87 | get_from_seed::("Eve//stash"),
88 | get_from_seed::("Ferdie//stash"),
89 | ],
90 | true),
91 | vec![],
92 | None,
93 | None,
94 | None,
95 | None
96 | ),
97 | })
98 | }
99 |
100 | pub(crate) fn from(s: &str) -> Option {
101 | match s {
102 | "dev" => Some(Alternative::Development),
103 | "" | "local" => Some(Alternative::LocalTestnet),
104 | _ => None,
105 | }
106 | }
107 | }
108 |
109 | fn testnet_genesis(initial_authorities: Vec<(AccountId, AccountId, GrandpaId, BabeId)>,
110 | root_key: AccountId,
111 | endowed_accounts: Vec,
112 | _enable_println: bool) -> GenesisConfig {
113 | GenesisConfig {
114 | system: Some(SystemConfig {
115 | code: WASM_BINARY.to_vec(),
116 | changes_trie_config: Default::default(),
117 | }),
118 | indices: Some(IndicesConfig {
119 | ids: endowed_accounts.clone(),
120 | }),
121 | balances: Some(BalancesConfig {
122 | balances: endowed_accounts.iter().cloned().map(|k|(k, 1 << 70)).collect(),
123 | vesting: vec![],
124 | }),
125 | sudo: Some(SudoConfig {
126 | key: root_key,
127 | }),
128 | babe: Some(BabeConfig {
129 | authorities: initial_authorities.iter().map(|x| (x.3.clone(), 1)).collect(),
130 | }),
131 | grandpa: Some(GrandpaConfig {
132 | authorities: initial_authorities.iter().map(|x| (x.2.clone(), 1)).collect(),
133 | }),
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/runtime/Cargo.toml:
--------------------------------------------------------------------------------
1 | [dependencies.babe]
2 | default-features = false
3 | git = 'https://github.com/paritytech/substrate.git'
4 | package = 'srml-babe'
5 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
6 |
7 | [dependencies.babe-primitives]
8 | default-features = false
9 | git = 'https://github.com/paritytech/substrate.git'
10 | package = 'substrate-consensus-babe-primitives'
11 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
12 |
13 | [dependencies.balances]
14 | default_features = false
15 | git = 'https://github.com/paritytech/substrate.git'
16 | package = 'srml-balances'
17 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
18 |
19 | [dependencies.client]
20 | default_features = false
21 | git = 'https://github.com/paritytech/substrate.git'
22 | package = 'substrate-client'
23 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
24 |
25 | [dependencies.codec]
26 | default-features = false
27 | features = ['derive']
28 | package = 'parity-scale-codec'
29 | version = '1.0.0'
30 |
31 | [dependencies.executive]
32 | default_features = false
33 | git = 'https://github.com/paritytech/substrate.git'
34 | package = 'srml-executive'
35 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
36 |
37 | [dependencies.grandpa]
38 | default-features = false
39 | git = 'https://github.com/paritytech/substrate.git'
40 | package = 'srml-grandpa'
41 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
42 |
43 | [dependencies.indices]
44 | default_features = false
45 | git = 'https://github.com/paritytech/substrate.git'
46 | package = 'srml-indices'
47 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
48 |
49 | [dependencies.offchain-primitives]
50 | default-features = false
51 | git = 'https://github.com/paritytech/substrate.git'
52 | package = 'substrate-offchain-primitives'
53 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
54 |
55 | [dependencies.primitives]
56 | default_features = false
57 | git = 'https://github.com/paritytech/substrate.git'
58 | package = 'substrate-primitives'
59 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
60 |
61 | [dependencies.rstd]
62 | default_features = false
63 | git = 'https://github.com/paritytech/substrate.git'
64 | package = 'sr-std'
65 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
66 |
67 | [dependencies.runtime-io]
68 | default_features = false
69 | git = 'https://github.com/paritytech/substrate.git'
70 | package = 'sr-io'
71 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
72 |
73 | [dependencies.safe-mix]
74 | default-features = false
75 | version = '1.0'
76 |
77 | [dependencies.serde]
78 | features = ['derive']
79 | optional = true
80 | version = '1.0'
81 |
82 | [dependencies.sr-primitives]
83 | default_features = false
84 | git = 'https://github.com/paritytech/substrate.git'
85 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
86 |
87 | [dependencies.substrate-session]
88 | default-features = false
89 | git = 'https://github.com/paritytech/substrate.git'
90 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
91 |
92 | [dependencies.sudo]
93 | default_features = false
94 | git = 'https://github.com/paritytech/substrate.git'
95 | package = 'srml-sudo'
96 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
97 |
98 | [dependencies.support]
99 | default_features = false
100 | git = 'https://github.com/paritytech/substrate.git'
101 | package = 'srml-support'
102 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
103 |
104 | [dependencies.system]
105 | default_features = false
106 | git = 'https://github.com/paritytech/substrate.git'
107 | package = 'srml-system'
108 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
109 |
110 | [dependencies.timestamp]
111 | default_features = false
112 | git = 'https://github.com/paritytech/substrate.git'
113 | package = 'srml-timestamp'
114 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
115 |
116 | [dependencies.version]
117 | default_features = false
118 | git = 'https://github.com/paritytech/substrate.git'
119 | package = 'sr-version'
120 | rev = 'bc9d55f738db72d35b6dfdf9dad7bf2d82204665'
121 |
122 | [package]
123 | authors = ['Anonymous']
124 | edition = '2018'
125 | name = 'node-template-runtime'
126 | version = '2.0.0'
127 | [build-dependencies.wasm-builder-runner]
128 | package = 'substrate-wasm-builder-runner'
129 | version = '1.0.2'
130 |
131 | [features]
132 | default = ['std']
133 | no_std = []
134 | std = [
135 | 'codec/std',
136 | 'client/std',
137 | 'rstd/std',
138 | 'runtime-io/std',
139 | 'support/std',
140 | 'balances/std',
141 | 'babe/std',
142 | 'babe-primitives/std',
143 | 'executive/std',
144 | 'indices/std',
145 | 'grandpa/std',
146 | 'primitives/std',
147 | 'sr-primitives/std',
148 | 'system/std',
149 | 'timestamp/std',
150 | 'sudo/std',
151 | 'version/std',
152 | 'serde',
153 | 'safe-mix/std',
154 | 'offchain-primitives/std',
155 | 'substrate-session/std',
156 | ]
157 |
--------------------------------------------------------------------------------
/src/service.rs:
--------------------------------------------------------------------------------
1 | #![warn(unused_extern_crates)]
2 |
3 | //! Service and ServiceFactory implementation. Specialized wrapper over substrate service.
4 |
5 | use std::sync::Arc;
6 | use std::time::Duration;
7 | use substrate_client::{self as client, LongestChain};
8 | use babe::{import_queue, start_babe, BabeImportQueue, Config};
9 | use grandpa::{self, FinalityProofProvider as GrandpaFinalityProofProvider};
10 | use futures::prelude::*;
11 | use node_template_runtime::{self, GenesisConfig, opaque::Block, RuntimeApi, WASM_BINARY};
12 | use substrate_service::{
13 | FactoryFullConfiguration, LightComponents, FullComponents, FullBackend,
14 | FullClient, LightClient, LightBackend, FullExecutor, LightExecutor,
15 | error::{Error as ServiceError},
16 | };
17 | use transaction_pool::{self, txpool::{Pool as TransactionPool}};
18 | use inherents::InherentDataProviders;
19 | use network::construct_simple_protocol;
20 | use substrate_executor::native_executor_instance;
21 | use substrate_service::{ServiceFactory, construct_service_factory, TelemetryOnConnect};
22 | pub use substrate_executor::NativeExecutor;
23 |
24 | // Our native executor instance.
25 | native_executor_instance!(
26 | pub Executor,
27 | node_template_runtime::api::dispatch,
28 | node_template_runtime::native_version,
29 | WASM_BINARY
30 | );
31 |
32 | construct_simple_protocol! {
33 | /// Demo protocol attachment for substrate.
34 | pub struct NodeProtocol where Block = Block { }
35 | }
36 |
37 | type BabeBlockImportForService = babe::BabeBlockImport<
38 | FullBackend,
39 | FullExecutor,
40 | ::Block,
41 | grandpa::BlockImportForService,
42 | ::RuntimeApi,
43 | client::Client<
44 | FullBackend,
45 | FullExecutor,
46 | ::Block,
47 | ::RuntimeApi
48 | >,
49 | >;
50 |
51 | pub struct NodeConfig {
52 | /// GRANDPA and BABE connection to import block.
53 | // FIXME #1134 rather than putting this on the config, let's have an actual intermediate setup state
54 | pub import_setup: Option<(
55 | BabeBlockImportForService,
56 | grandpa::LinkHalfForService,
57 | babe::BabeLink,
58 | )>,
59 | /// Tasks that were created by previous setup steps and should be spawned.
60 | pub tasks_to_spawn: Option + Send>>>,
61 | inherent_data_providers: InherentDataProviders,
62 | }
63 |
64 | impl Default for NodeConfig where F: ServiceFactory {
65 | fn default() -> NodeConfig {
66 | NodeConfig {
67 | import_setup: None,
68 | inherent_data_providers: InherentDataProviders::new(),
69 | tasks_to_spawn: None,
70 | }
71 | }
72 | }
73 |
74 | construct_service_factory! {
75 | struct Factory {
76 | Block = Block,
77 | RuntimeApi = RuntimeApi,
78 | NetworkProtocol = NodeProtocol { |config| Ok(NodeProtocol::new()) },
79 | RuntimeDispatch = Executor,
80 | FullTransactionPoolApi =
81 | transaction_pool::ChainApi<
82 | client::Client, FullExecutor, Block, RuntimeApi>,
83 | Block
84 | > {
85 | |config, client|
86 | Ok(TransactionPool::new(config, transaction_pool::ChainApi::new(client)))
87 | },
88 | LightTransactionPoolApi =
89 | transaction_pool::ChainApi<
90 | client::Client, LightExecutor, Block, RuntimeApi>,
91 | Block
92 | > {
93 | |config, client|
94 | Ok(TransactionPool::new(config, transaction_pool::ChainApi::new(client)))
95 | },
96 | Genesis = GenesisConfig,
97 | Configuration = NodeConfig,
98 | FullService = FullComponents {
99 | |config: FactoryFullConfiguration| FullComponents::::new(config)
100 | },
101 | AuthoritySetup = {
102 | |mut service: Self::FullService| {
103 | let (block_import, link_half, babe_link) =
104 | service.config_mut().custom.import_setup.take()
105 | .expect("Link Half and Block Import are present for Full Services or setup failed before. qed");
106 |
107 | // spawn any futures that were created in the previous setup steps
108 | if let Some(tasks) = service.config_mut().custom.tasks_to_spawn.take() {
109 | for task in tasks {
110 | service.spawn_task(
111 | task.select(service.on_exit())
112 | .map(|_| ())
113 | .map_err(|_| ())
114 | );
115 | }
116 | }
117 |
118 | if service.config().roles.is_authority() {
119 | let proposer = basic_authorship::ProposerFactory {
120 | client: service.client(),
121 | transaction_pool: service.transaction_pool(),
122 | };
123 |
124 | let client = service.client();
125 | let select_chain = service.select_chain()
126 | .ok_or(ServiceError::SelectChainRequired)?;
127 |
128 | let babe_config = babe::BabeParams {
129 | config: Config::get_or_compute(&*client)?,
130 | keystore: service.keystore(),
131 | client,
132 | select_chain,
133 | block_import,
134 | env: proposer,
135 | sync_oracle: service.network(),
136 | inherent_data_providers: service.config()
137 | .custom.inherent_data_providers.clone(),
138 | force_authoring: service.config().force_authoring,
139 | time_source: babe_link,
140 | };
141 |
142 | let babe = start_babe(babe_config)?;
143 | let select = babe.select(service.on_exit()).then(|_| Ok(()));
144 |
145 | // the BABE authoring task is considered infallible, i.e. if it
146 | // fails we take down the service with it.
147 | service.spawn_essential_task(select);
148 | }
149 |
150 | let config = grandpa::Config {
151 | // FIXME #1578 make this available through chainspec
152 | gossip_duration: Duration::from_millis(333),
153 | justification_period: 4096,
154 | name: Some(service.config().name.clone()),
155 | keystore: Some(service.keystore()),
156 | };
157 |
158 | match (service.config().roles.is_authority(), service.config().disable_grandpa) {
159 | (false, false) => {
160 | // start the lightweight GRANDPA observer
161 | service.spawn_task(Box::new(grandpa::run_grandpa_observer(
162 | config,
163 | link_half,
164 | service.network(),
165 | service.on_exit(),
166 | )?));
167 | },
168 | (true, false) => {
169 | // start the full GRANDPA voter
170 | let telemetry_on_connect = TelemetryOnConnect {
171 | telemetry_connection_sinks: service.telemetry_on_connect_stream(),
172 | };
173 | let grandpa_config = grandpa::GrandpaParams {
174 | config: config,
175 | link: link_half,
176 | network: service.network(),
177 | inherent_data_providers:
178 | service.config().custom.inherent_data_providers.clone(),
179 | on_exit: service.on_exit(),
180 | telemetry_on_connect: Some(telemetry_on_connect),
181 | };
182 |
183 | // the GRANDPA voter task is considered infallible, i.e.
184 | // if it fails we take down the service with it.
185 | service.spawn_essential_task(grandpa::run_grandpa_voter(grandpa_config)?);
186 | },
187 | (_, true) => {
188 | grandpa::setup_disabled_grandpa(
189 | service.client(),
190 | &service.config().custom.inherent_data_providers,
191 | service.network(),
192 | )?;
193 | },
194 | }
195 |
196 | Ok(service)
197 | }
198 | },
199 | LightService = LightComponents
200 | { |config| >::new(config) },
201 | FullImportQueue = BabeImportQueue {
202 | |
203 | config: &mut FactoryFullConfiguration,
204 | client: Arc>,
205 | select_chain: Self::SelectChain,
206 | transaction_pool: Option>>,
207 | | {
208 | let (block_import, link_half) =
209 | grandpa::block_import::<_, _, _, RuntimeApi, FullClient, _>(
210 | client.clone(), client.clone(), select_chain
211 | )?;
212 | let justification_import = block_import.clone();
213 | let (import_queue, babe_link, babe_block_import, pruning_task) = import_queue(
214 | Config::get_or_compute(&*client)?,
215 | block_import,
216 | Some(Box::new(justification_import)),
217 | None,
218 | client.clone(),
219 | client,
220 | config.custom.inherent_data_providers.clone(),
221 | transaction_pool,
222 | )?;
223 | config.custom.import_setup = Some((babe_block_import.clone(), link_half, babe_link));
224 | config.custom.tasks_to_spawn = Some(vec![Box::new(pruning_task)]);
225 | Ok(import_queue)
226 | }
227 | },
228 | LightImportQueue = BabeImportQueue
229 | { |config: &FactoryFullConfiguration, client: Arc>| {
230 | #[allow(deprecated)]
231 | let fetch_checker = client.backend().blockchain().fetcher()
232 | .upgrade()
233 | .map(|fetcher| fetcher.checker().clone())
234 | .ok_or_else(|| "Trying to start light import queue without active fetch checker")?;
235 | let block_import = grandpa::light_block_import::<_, _, _, RuntimeApi, LightClient>(
236 | client.clone(), Arc::new(fetch_checker), client.clone()
237 | )?;
238 |
239 | let finality_proof_import = block_import.clone();
240 | let finality_proof_request_builder =
241 | finality_proof_import.create_finality_proof_request_builder();
242 |
243 | // FIXME: pruning task isn't started since light client doesn't do `AuthoritySetup`.
244 | let (import_queue, ..) = import_queue::<_, _, _, _, _, _, TransactionPool>(
245 | Config::get_or_compute(&*client)?,
246 | block_import,
247 | None,
248 | Some(Box::new(finality_proof_import)),
249 | client.clone(),
250 | client,
251 | config.custom.inherent_data_providers.clone(),
252 | None,
253 | )?;
254 |
255 | Ok((import_queue, finality_proof_request_builder))
256 | }},
257 | SelectChain = LongestChain, Self::Block>
258 | { |config: &FactoryFullConfiguration, client: Arc>| {
259 | #[allow(deprecated)]
260 | Ok(LongestChain::new(client.backend().clone()))
261 | }
262 | },
263 | FinalityProofProvider = { |client: Arc>| {
264 | Ok(Some(Arc::new(GrandpaFinalityProofProvider::new(client.clone(), client)) as _))
265 | }},
266 | RpcExtensions = (),
267 | }
268 | }
269 |
--------------------------------------------------------------------------------
/runtime/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! The Substrate Node Template runtime. This can be compiled with `#[no_std]`, ready for Wasm.
2 |
3 | #![cfg_attr(not(feature = "std"), no_std)]
4 | // `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256.
5 | #![recursion_limit="256"]
6 |
7 | // Make the WASM binary available.
8 | #[cfg(feature = "std")]
9 | include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
10 |
11 | use rstd::prelude::*;
12 | use primitives::{OpaqueMetadata, crypto::key_types};
13 | use sr_primitives::{
14 | ApplyResult, transaction_validity::TransactionValidity, generic, create_runtime_str,
15 | impl_opaque_keys, AnySignature
16 | };
17 | use sr_primitives::traits::{NumberFor, BlakeTwo256, Block as BlockT, DigestFor, StaticLookup, Verify, ConvertInto};
18 | use sr_primitives::weights::Weight;
19 | use babe::{AuthorityId as BabeId};
20 | use grandpa::{AuthorityId as GrandpaId, AuthorityWeight as GrandpaWeight};
21 | use grandpa::fg_primitives::{self, ScheduledChange};
22 | use client::{
23 | block_builder::api::{CheckInherentsResult, InherentData, self as block_builder_api},
24 | runtime_api as client_api, impl_runtime_apis
25 | };
26 | use version::RuntimeVersion;
27 | #[cfg(feature = "std")]
28 | use version::NativeVersion;
29 |
30 | // A few exports that help ease life for downstream crates.
31 | #[cfg(any(feature = "std", test))]
32 | pub use sr_primitives::BuildStorage;
33 | pub use timestamp::Call as TimestampCall;
34 | pub use balances::Call as BalancesCall;
35 | pub use sr_primitives::{Permill, Perbill};
36 | pub use support::{StorageValue, construct_runtime, parameter_types};
37 |
38 | /// An index to a block.
39 | pub type BlockNumber = u32;
40 |
41 | /// Alias to 512-bit hash when used in the context of a transaction signature on the chain.
42 | pub type Signature = AnySignature;
43 |
44 | /// Some way of identifying an account on the chain. We intentionally make it equivalent
45 | /// to the public key of our transaction signing scheme.
46 | pub type AccountId = ::Signer;
47 |
48 | /// The type for looking up accounts. We don't expect more than 4 billion of them, but you
49 | /// never know...
50 | pub type AccountIndex = u32;
51 |
52 | /// Balance of an account.
53 | pub type Balance = u128;
54 |
55 | /// Index of a transaction in the chain.
56 | pub type Index = u32;
57 |
58 | /// A hash of some data used by the chain.
59 | pub type Hash = primitives::H256;
60 |
61 | /// Digest item type.
62 | pub type DigestItem = generic::DigestItem;
63 |
64 | /// Used for the module template in `./template.rs`
65 | mod fungible;
66 |
67 | /// Opaque types. These are used by the CLI to instantiate machinery that don't need to know
68 | /// the specifics of the runtime. They can then be made to be agnostic over specific formats
69 | /// of data like extrinsics, allowing for them to continue syncing the network through upgrades
70 | /// to even the core datastructures.
71 | pub mod opaque {
72 | use super::*;
73 |
74 | pub use sr_primitives::OpaqueExtrinsic as UncheckedExtrinsic;
75 |
76 | /// Opaque block header type.
77 | pub type Header = generic::Header;
78 | /// Opaque block type.
79 | pub type Block = generic::Block;
80 | /// Opaque block identifier type.
81 | pub type BlockId = generic::BlockId;
82 |
83 | pub type SessionHandlers = (Grandpa, Babe);
84 |
85 | impl_opaque_keys! {
86 | pub struct SessionKeys {
87 | #[id(key_types::GRANDPA)]
88 | pub grandpa: GrandpaId,
89 | #[id(key_types::BABE)]
90 | pub babe: BabeId,
91 | }
92 | }
93 | }
94 |
95 | /// This runtime version.
96 | pub const VERSION: RuntimeVersion = RuntimeVersion {
97 | spec_name: create_runtime_str!("node-template"),
98 | impl_name: create_runtime_str!("node-template"),
99 | authoring_version: 3,
100 | spec_version: 4,
101 | impl_version: 4,
102 | apis: RUNTIME_API_VERSIONS,
103 | };
104 |
105 | /// Constants for Babe.
106 |
107 | /// Since BABE is probabilistic this is the average expected block time that
108 | /// we are targetting. Blocks will be produced at a minimum duration defined
109 | /// by `SLOT_DURATION`, but some slots will not be allocated to any
110 | /// authority and hence no block will be produced. We expect to have this
111 | /// block time on average following the defined slot duration and the value
112 | /// of `c` configured for BABE (where `1 - c` represents the probability of
113 | /// a slot being empty).
114 | /// This value is only used indirectly to define the unit constants below
115 | /// that are expressed in blocks. The rest of the code should use
116 | /// `SLOT_DURATION` instead (like the timestamp module for calculating the
117 | /// minimum period).
118 | ///
119 | pub const MILLISECS_PER_BLOCK: u64 = 6000;
120 |
121 | pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK;
122 |
123 | pub const EPOCH_DURATION_IN_BLOCKS: u32 = 10 * MINUTES;
124 |
125 | // These time units are defined in number of blocks.
126 | pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber);
127 | pub const HOURS: BlockNumber = MINUTES * 60;
128 | pub const DAYS: BlockNumber = HOURS * 24;
129 |
130 | // 1 in 4 blocks (on average, not counting collisions) will be primary babe blocks.
131 | pub const PRIMARY_PROBABILITY: (u64, u64) = (1, 4);
132 |
133 | /// The version infromation used to identify this runtime when compiled natively.
134 | #[cfg(feature = "std")]
135 | pub fn native_version() -> NativeVersion {
136 | NativeVersion {
137 | runtime_version: VERSION,
138 | can_author_with: Default::default(),
139 | }
140 | }
141 |
142 | parameter_types! {
143 | pub const BlockHashCount: BlockNumber = 250;
144 | pub const MaximumBlockWeight: Weight = 1_000_000;
145 | pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75);
146 | pub const MaximumBlockLength: u32 = 5 * 1024 * 1024;
147 | pub const Version: RuntimeVersion = VERSION;
148 | }
149 |
150 | impl system::Trait for Runtime {
151 | /// The identifier used to distinguish between accounts.
152 | type AccountId = AccountId;
153 | /// The aggregated dispatch type that is available for extrinsics.
154 | type Call = Call;
155 | /// The lookup mechanism to get account ID from whatever is passed in dispatchers.
156 | type Lookup = Indices;
157 | /// The index type for storing how many extrinsics an account has signed.
158 | type Index = Index;
159 | /// The index type for blocks.
160 | type BlockNumber = BlockNumber;
161 | /// The type for hashing blocks and tries.
162 | type Hash = Hash;
163 | /// The hashing algorithm used.
164 | type Hashing = BlakeTwo256;
165 | /// The header type.
166 | type Header = generic::Header;
167 | /// The ubiquitous event type.
168 | type Event = Event;
169 | /// Update weight (to fee) multiplier per-block.
170 | type WeightMultiplierUpdate = ();
171 | /// The ubiquitous origin type.
172 | type Origin = Origin;
173 | /// Maximum number of block number to block hash mappings to keep (oldest pruned first).
174 | type BlockHashCount = BlockHashCount;
175 | /// Maximum weight of each block. With a default weight system of 1byte == 1weight, 4mb is ok.
176 | type MaximumBlockWeight = MaximumBlockWeight;
177 | /// Maximum size of all encoded transactions (in bytes) that are allowed in one block.
178 | type MaximumBlockLength = MaximumBlockLength;
179 | /// Portion of the block weight that is available to all normal transactions.
180 | type AvailableBlockRatio = AvailableBlockRatio;
181 | type Version = Version;
182 | }
183 |
184 | parameter_types! {
185 | pub const EpochDuration: u64 = EPOCH_DURATION_IN_BLOCKS as u64;
186 | pub const ExpectedBlockTime: u64 = MILLISECS_PER_BLOCK;
187 | }
188 |
189 | impl babe::Trait for Runtime {
190 | type EpochDuration = EpochDuration;
191 | type ExpectedBlockTime = ExpectedBlockTime;
192 | }
193 |
194 | impl grandpa::Trait for Runtime {
195 | type Event = Event;
196 | }
197 |
198 | impl indices::Trait for Runtime {
199 | /// The type for recording indexing into the account enumeration. If this ever overflows, there
200 | /// will be problems!
201 | type AccountIndex = u32;
202 | /// Use the standard means of resolving an index hint from an id.
203 | type ResolveHint = indices::SimpleResolveHint;
204 | /// Determine whether an account is dead.
205 | type IsDeadAccount = Balances;
206 | /// The ubiquitous event type.
207 | type Event = Event;
208 | }
209 |
210 | parameter_types! {
211 | pub const MinimumPeriod: u64 = 5000;
212 | }
213 |
214 | impl timestamp::Trait for Runtime {
215 | /// A timestamp: milliseconds since the unix epoch.
216 | type Moment = u64;
217 | type OnTimestampSet = Babe;
218 | type MinimumPeriod = MinimumPeriod;
219 | }
220 |
221 | parameter_types! {
222 | pub const ExistentialDeposit: u128 = 500;
223 | pub const TransferFee: u128 = 0;
224 | pub const CreationFee: u128 = 0;
225 | pub const TransactionBaseFee: u128 = 0;
226 | pub const TransactionByteFee: u128 = 0;
227 | }
228 |
229 | impl balances::Trait for Runtime {
230 | /// The type for recording an account's balance.
231 | type Balance = Balance;
232 | /// What to do if an account's free balance gets zeroed.
233 | type OnFreeBalanceZero = ();
234 | /// What to do if a new account is created.
235 | type OnNewAccount = Indices;
236 | /// The ubiquitous event type.
237 | type Event = Event;
238 |
239 | type TransactionPayment = ();
240 | type DustRemoval = ();
241 | type TransferPayment = ();
242 | type ExistentialDeposit = ExistentialDeposit;
243 | type TransferFee = TransferFee;
244 | type CreationFee = CreationFee;
245 | type TransactionBaseFee = TransactionBaseFee;
246 | type TransactionByteFee = TransactionByteFee;
247 | type WeightToFee = ConvertInto;
248 | }
249 |
250 | impl sudo::Trait for Runtime {
251 | type Event = Event;
252 | type Proposal = Call;
253 | }
254 |
255 | parameter_types! {
256 | pub const FreeTransferPeriod: u32 = 500;
257 | pub const FundTransferFee: u128 = 10_000;
258 | }
259 |
260 | /// Used for the module template in `./template.rs`
261 | impl fungible::Trait for Runtime {
262 | type Event = Event;
263 | type TokenBalance = u64;
264 | type TokenId = u32;
265 | type Currency = Balances;
266 | type TokenFreeTransfers = u32;
267 | type FindAuthor = ();
268 | type FreeTransferPeriod = FreeTransferPeriod;
269 | type FundTransferFee = FundTransferFee;
270 | }
271 |
272 | construct_runtime!(
273 | pub enum Runtime where
274 | Block = Block,
275 | NodeBlock = opaque::Block,
276 | UncheckedExtrinsic = UncheckedExtrinsic
277 | {
278 | System: system::{Module, Call, Storage, Config, Event},
279 | Timestamp: timestamp::{Module, Call, Storage, Inherent},
280 | Babe: babe::{Module, Call, Storage, Config, Inherent(Timestamp)},
281 | Grandpa: grandpa::{Module, Call, Storage, Config, Event},
282 | Indices: indices::{default, Config},
283 | Balances: balances,
284 | Sudo: sudo,
285 | Fungible: fungible::{Module, Call, Storage, Event},
286 | }
287 | );
288 |
289 | /// The address format for describing accounts.
290 | pub type Address = ::Source;
291 | /// Block header type as expected by this runtime.
292 | pub type Header = generic::Header;
293 | /// Block type as expected by this runtime.
294 | pub type Block = generic::Block;
295 | /// A Block signed with a Justification
296 | pub type SignedBlock = generic::SignedBlock;
297 | /// BlockId type as expected by this runtime.
298 | pub type BlockId = generic::BlockId;
299 | /// The SignedExtension to the basic transaction logic.
300 | pub type SignedExtra = (
301 | system::CheckVersion,
302 | system::CheckGenesis,
303 | system::CheckEra,
304 | system::CheckNonce,
305 | system::CheckWeight,
306 | balances::TakeFees,
307 | // TODO: UI does not quite support this yet
308 | //fungible::TakeTokenFees,
309 | );
310 | /// Unchecked extrinsic type as expected by this runtime.
311 | pub type UncheckedExtrinsic = generic::UncheckedExtrinsic;
312 | /// Extrinsic type that has already been checked.
313 | pub type CheckedExtrinsic = generic::CheckedExtrinsic;
314 | /// Executive: handles dispatch to the various modules.
315 | pub type Executive = executive::Executive, Runtime, AllModules>;
316 |
317 | impl_runtime_apis! {
318 | impl client_api::Core for Runtime {
319 | fn version() -> RuntimeVersion {
320 | VERSION
321 | }
322 |
323 | fn execute_block(block: Block) {
324 | Executive::execute_block(block)
325 | }
326 |
327 | fn initialize_block(header: &::Header) {
328 | Executive::initialize_block(header)
329 | }
330 | }
331 |
332 | impl client_api::Metadata for Runtime {
333 | fn metadata() -> OpaqueMetadata {
334 | Runtime::metadata().into()
335 | }
336 | }
337 |
338 | impl block_builder_api::BlockBuilder for Runtime {
339 | fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyResult {
340 | Executive::apply_extrinsic(extrinsic)
341 | }
342 |
343 | fn finalize_block() -> ::Header {
344 | Executive::finalize_block()
345 | }
346 |
347 | fn inherent_extrinsics(data: InherentData) -> Vec<::Extrinsic> {
348 | data.create_extrinsics()
349 | }
350 |
351 | fn check_inherents(block: Block, data: InherentData) -> CheckInherentsResult {
352 | data.check_extrinsics(&block)
353 | }
354 |
355 | fn random_seed() -> ::Hash {
356 | System::random_seed()
357 | }
358 | }
359 |
360 | impl client_api::TaggedTransactionQueue for Runtime {
361 | fn validate_transaction(tx: ::Extrinsic) -> TransactionValidity {
362 | Executive::validate_transaction(tx)
363 | }
364 | }
365 |
366 | impl offchain_primitives::OffchainWorkerApi for Runtime {
367 | fn offchain_worker(number: NumberFor) {
368 | Executive::offchain_worker(number)
369 | }
370 | }
371 |
372 | impl fg_primitives::GrandpaApi for Runtime {
373 | fn grandpa_pending_change(digest: &DigestFor)
374 | -> Option>>
375 | {
376 | Grandpa::pending_change(digest)
377 | }
378 |
379 | fn grandpa_forced_change(digest: &DigestFor)
380 | -> Option<(NumberFor, ScheduledChange>)>
381 | {
382 | Grandpa::forced_change(digest)
383 | }
384 |
385 | fn grandpa_authorities() -> Vec<(GrandpaId, GrandpaWeight)> {
386 | Grandpa::grandpa_authorities()
387 | }
388 | }
389 |
390 | impl babe_primitives::BabeApi for Runtime {
391 | fn startup_data() -> babe_primitives::BabeConfiguration {
392 | // The choice of `c` parameter (where `1 - c` represents the
393 | // probability of a slot being empty), is done in accordance to the
394 | // slot duration and expected target block time, for safely
395 | // resisting network delays of maximum two seconds.
396 | //
397 | babe_primitives::BabeConfiguration {
398 | median_required_blocks: 1000,
399 | slot_duration: Babe::slot_duration(),
400 | c: PRIMARY_PROBABILITY,
401 | }
402 | }
403 |
404 | fn epoch() -> babe_primitives::Epoch {
405 | babe_primitives::Epoch {
406 | start_slot: Babe::epoch_start_slot(),
407 | authorities: Babe::authorities(),
408 | epoch_index: Babe::epoch_index(),
409 | randomness: Babe::randomness(),
410 | duration: EpochDuration::get(),
411 | secondary_slots: Babe::secondary_slots().0,
412 | }
413 | }
414 | }
415 |
416 | impl substrate_session::SessionKeys for Runtime {
417 | fn generate_session_keys(seed: Option>) -> Vec {
418 | let seed = seed.as_ref().map(|s| rstd::str::from_utf8(&s).expect("Seed is an utf8 string"));
419 | opaque::SessionKeys::generate(seed)
420 | }
421 | }
422 | }
423 |
--------------------------------------------------------------------------------
/runtime/src/fungible.rs:
--------------------------------------------------------------------------------
1 | /// A runtime module template with necessary imports
2 |
3 | /// Feel free to remove or edit this file as needed.
4 | /// If you change the name of this file, make sure to update its references in runtime/src/lib.rs
5 | /// If you remove this file, you can remove those references
6 |
7 |
8 | /// For more guidance on Substrate modules, see the example module
9 | /// https://github.com/paritytech/substrate/blob/master/srml/example/src/lib.rs
10 |
11 | use support::{traits::{Currency, WithdrawReason, ExistenceRequirement}, decl_module, decl_storage, decl_event, ensure,
12 | Parameter, StorageValue, StorageMap, StorageDoubleMap, dispatch::Result
13 | };
14 | use support::traits::{FindAuthor, Get};
15 | use sr_primitives::ModuleId;
16 | use sr_primitives::traits::{Member, SimpleArithmetic, Zero, StaticLookup, One,
17 | CheckedAdd, CheckedSub, SignedExtension, DispatchError, MaybeSerializeDebug,
18 | SaturatedConversion, AccountIdConversion,
19 | };
20 | use sr_primitives::weights::{DispatchInfo, SimpleDispatchInfo};
21 | use sr_primitives::transaction_validity::{TransactionPriority, ValidTransaction};
22 | use system::ensure_signed;
23 | use codec::{Encode, Decode, Codec};
24 |
25 | pub trait Trait: system::Trait {
26 |
27 | type Currency: Currency;
28 |
29 | /// The overarching event type.
30 | type Event: From> + Into<::Event>;
31 |
32 | /// The units in which we record balances.
33 | type TokenBalance: Parameter + Member + SimpleArithmetic + Codec + Default + Copy + MaybeSerializeDebug;
34 |
35 | /// The arithmetic type of asset identifier.
36 | type TokenId: Parameter + Member + SimpleArithmetic + Codec + Default + Copy + MaybeSerializeDebug;
37 |
38 | type FindAuthor: FindAuthor;
39 |
40 | type TokenFreeTransfers: Parameter + SimpleArithmetic + Default + Copy;
41 |
42 | type FreeTransferPeriod: Get;
43 |
44 | type FundTransferFee: Get>;
45 | }
46 |
47 | type BalanceOf = <::Currency as Currency<::AccountId>>::Balance;
48 |
49 | const MODULE_ID: ModuleId =ModuleId(*b"coinfund");
50 |
51 | decl_event!(
52 | pub enum Event where
53 | AccountId = ::AccountId,
54 | TokenId = ::TokenId,
55 | TokenBalance = ::TokenBalance,
56 | Balance = BalanceOf,
57 | {
58 | NewToken(TokenId, AccountId, TokenBalance),
59 | Transfer(TokenId, AccountId, AccountId, TokenBalance),
60 | Approval(TokenId, AccountId, AccountId, TokenBalance),
61 | Deposit(TokenId, AccountId, Balance),
62 | }
63 | );
64 |
65 | // This module's storage items.
66 | decl_storage! {
67 | trait Store for Module as Fungible {
68 | Count get(count): T::TokenId;
69 |
70 | // ERC 20
71 | TotalSupply get(total_supply): map T::TokenId => T::TokenBalance;
72 | Balances get(balance_of): map (T::TokenId, T::AccountId) => T::TokenBalance;
73 | Allowance get(allowance_of): map (T::TokenId, T::AccountId, T::AccountId) => T::TokenBalance;
74 |
75 | // Free Transfers
76 | FreeTransfers get(free_transfers): map T::TokenId => T::TokenFreeTransfers;
77 | FreeTransferCount get(free_transfer_count): double_map (), blake2_128((T::TokenId, T::AccountId)) => T::TokenFreeTransfers;
78 | }
79 | }
80 |
81 | // The module's dispatchable functions.
82 | decl_module! {
83 | pub struct Module for enum Call where origin: T::Origin {
84 | fn deposit_event() = default;
85 |
86 | /// The time before free transfers are reset
87 | const FreeTransferPeriod: T::BlockNumber = T::FreeTransferPeriod::get();
88 | const FundTransferFee: BalanceOf = T::FundTransferFee::get();
89 |
90 |
91 | fn on_initialize(n: T::BlockNumber) {
92 | if n % T::FreeTransferPeriod::get() == Zero::zero() {
93 | // Reset everyone's transfer count
94 | >::remove_prefix(&());
95 | }
96 | }
97 |
98 | #[weight = SimpleDispatchInfo::FixedNormal(1_000_000)]
99 | fn create_token(origin, #[compact] total_supply: T::TokenBalance, free_transfers: T::TokenFreeTransfers, deposit: BalanceOf) {
100 | let sender = ensure_signed(origin)?;
101 |
102 | let id = Self::count();
103 | let next_id = id.checked_add(&One::one()).ok_or("overflow when adding new token")?;
104 | let imbalance = T::Currency::withdraw(&sender, deposit, WithdrawReason::Transfer, ExistenceRequirement::KeepAlive)?;
105 |
106 | >::insert((id, sender.clone()), total_supply);
107 | >::insert(id, total_supply);
108 | >::insert(id, free_transfers);
109 | >::put(next_id);
110 |
111 | T::Currency::resolve_creating(&Self::fund_account_id(id), imbalance);
112 |
113 | Self::deposit_event(RawEvent::NewToken(id, sender.clone(), total_supply));
114 |
115 | Self::deposit_event(RawEvent::Deposit(id, sender, deposit));
116 | }
117 |
118 | #[weight = SimpleDispatchInfo::FixedNormal(0)]
119 | fn try_free_transfer(origin,
120 | #[compact] id: T::TokenId,
121 | to: ::Source,
122 | #[compact] amount: T::TokenBalance
123 | ) {
124 | let sender = ensure_signed(origin)?;
125 | let to = T::Lookup::lookup(to)?;
126 |
127 | let free_transfer_limit = Self::free_transfers(id);
128 | let free_transfer_count = Self::free_transfer_count(&(), &(id, sender.clone()));
129 | let new_free_transfer_count = free_transfer_count
130 | .checked_add(&One::one()).ok_or("overflow when counting new transfer")?;
131 |
132 | ensure!(free_transfer_count < free_transfer_limit, "no more free transfers available");
133 | ensure!(!amount.is_zero(), "transfer amount should be non-zero");
134 | ensure!(amount <= Self::balance_of((id, sender.clone())), "user does not have enough tokens");
135 |
136 | // Burn fees from funds
137 | let fund_account = Self::fund_account_id(id);
138 | let fund_fee = T::FundTransferFee::get();
139 | let _ = T::Currency::withdraw(&fund_account, fund_fee, WithdrawReason::Transfer, ExistenceRequirement::AllowDeath)?;
140 |
141 | Self::make_transfer(id, sender.clone(), to, amount).expect("user has been checked to have enough funds to transfer. qed");
142 |
143 | >::insert(&(), &(id, sender), &new_free_transfer_count);
144 | }
145 |
146 | fn transfer(origin,
147 | #[compact] id: T::TokenId,
148 | to: ::Source,
149 | #[compact] amount: T::TokenBalance
150 | ) {
151 | let sender = ensure_signed(origin)?;
152 | let to = T::Lookup::lookup(to)?;
153 | ensure!(!amount.is_zero(), "transfer amount should be non-zero");
154 |
155 | Self::make_transfer(id, sender, to, amount)?;
156 | }
157 |
158 | fn approve(origin,
159 | #[compact] id: T::TokenId,
160 | spender: ::Source,
161 | #[compact] value: T::TokenBalance
162 | ) {
163 | let sender = ensure_signed(origin)?;
164 | let spender = T::Lookup::lookup(spender)?;
165 |
166 | >::insert((id, sender.clone(), spender.clone()), value);
167 |
168 | Self::deposit_event(RawEvent::Approval(id, sender, spender, value));
169 | }
170 |
171 | fn transfer_from(origin,
172 | #[compact] id: T::TokenId,
173 | from: T::AccountId,
174 | to: T::AccountId,
175 | #[compact] value: T::TokenBalance
176 | ) {
177 | let sender = ensure_signed(origin)?;
178 | let allowance = Self::allowance_of((id, from.clone(), sender.clone()));
179 |
180 | let updated_allowance = allowance.checked_sub(&value).ok_or("underflow in calculating allowance")?;
181 |
182 | Self::make_transfer(id, from.clone(), to.clone(), value)?;
183 |
184 | >::insert((id, from, sender), updated_allowance);
185 | }
186 |
187 | fn deposit(origin, #[compact] token_id: T::TokenId, #[compact] value: BalanceOf) {
188 | let who = ensure_signed(origin)?;
189 | ensure!(Self::count() > token_id, "Non-existent token");
190 | T::Currency::transfer(&who, &Self::fund_account_id(token_id), value)?;
191 |
192 | Self::deposit_event(RawEvent::Deposit(token_id, who, value));
193 | }
194 | }
195 | }
196 |
197 | impl Module {
198 | pub fn fund_account_id(index: T::TokenId) -> T::AccountId {
199 | MODULE_ID.into_sub_account(index)
200 | }
201 |
202 | fn make_transfer(id: T::TokenId, from: T::AccountId, to: T::AccountId, amount: T::TokenBalance) -> Result {
203 |
204 | let from_balance = Self::balance_of((id, from.clone()));
205 | ensure!(from_balance >= amount.clone(), "user does not have enough tokens");
206 |
207 | >::insert((id, from.clone()), from_balance - amount.clone());
208 | >::mutate((id, to.clone()), |balance| *balance += amount.clone());
209 |
210 | Self::deposit_event(RawEvent::Transfer(id, from, to, amount));
211 |
212 | Ok(())
213 | }
214 | }
215 |
216 | /// Allow payment of fees using the token being transferred.
217 | #[derive(Encode, Decode, Clone, Eq, PartialEq)]
218 | pub struct TakeTokenFees {
219 | id: T::TokenId,
220 | value: T::TokenBalance,
221 | }
222 |
223 | #[cfg(feature = "std")]
224 | impl rstd::fmt::Debug for TakeTokenFees
225 | {
226 | fn fmt(&self, f: &mut rstd::fmt::Formatter) -> rstd::fmt::Result {
227 | // TODO: Fix this to actually show value
228 | write!(f, "TokenFee ( id: ?, fee: ? )")
229 | }
230 | }
231 |
232 | impl SignedExtension for TakeTokenFees {
233 | type AccountId = T::AccountId;
234 | type Call = T::Call;
235 | type AdditionalSigned = ();
236 | type Pre = ();
237 | fn additional_signed(&self) -> rstd::result::Result<(), &'static str> { Ok(()) }
238 |
239 | fn validate(
240 | &self,
241 | who: &Self::AccountId,
242 | _call: &Self::Call,
243 | _info: DispatchInfo,
244 | _len: usize,
245 | ) -> rstd::result::Result {
246 |
247 | let id = self.id;
248 | let fee = self.value;
249 |
250 | // TODO: Actually look up the block author and transfer to them
251 | // let digest = >::digest();
252 | // let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime());
253 | // // TODO: FIX
254 | // if let Some(author) = T::FindAuthor::find_author(pre_runtime_digests) {
255 | // >::make_transfer(id, who, author, fee);
256 | // } else {
257 | // Default::default()
258 | // }
259 |
260 | let result = >::make_transfer(id, who.clone(), Default::default(), fee);
261 |
262 |
263 | let mut r = ValidTransaction::default();
264 | if result.is_ok() {
265 | // TODO: Calculate priority based on percentage of total supply
266 | r.priority = fee.saturated_into::();
267 | }
268 | Ok(r)
269 | }
270 | }
271 |
272 | /// tests for this module
273 | #[cfg(test)]
274 | mod tests {
275 | use super::*;
276 |
277 | use runtime_io::with_externalities;
278 | use primitives::{H256, Blake2Hasher};
279 | use support::{impl_outer_origin, assert_ok, assert_err, parameter_types};
280 | use sr_primitives::{
281 | traits::{BlakeTwo256, IdentityLookup, ConvertInto, OnFinalize, OnInitialize},
282 | testing::Header};
283 | use sr_primitives::weights::Weight;
284 | use sr_primitives::Perbill;
285 |
286 | impl_outer_origin! {
287 | pub enum Origin for Test {}
288 | }
289 |
290 | // For testing the module, we construct most of a mock runtime. This means
291 | // first constructing a configuration type (`Test`) which `impl`s each of the
292 | // configuration traits of modules we want to use.
293 | #[derive(Clone, Eq, PartialEq)]
294 | pub struct Test;
295 | parameter_types! {
296 | pub const BlockHashCount: u64 = 250;
297 | pub const MaximumBlockWeight: Weight = 1024;
298 | pub const MaximumBlockLength: u32 = 2 * 1024;
299 | pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75);
300 | }
301 | impl balances::Trait for Test {
302 | type Balance = u64;
303 | type OnFreeBalanceZero = ();
304 | type OnNewAccount = ();
305 | type TransactionPayment = ();
306 | type TransferPayment = ();
307 | type DustRemoval = ();
308 | type Event = ();
309 | type ExistentialDeposit = ();
310 | type TransferFee = ();
311 | type CreationFee = ();
312 | type TransactionBaseFee = ();
313 | type TransactionByteFee = ();
314 | type WeightToFee = ConvertInto;
315 | }
316 |
317 | impl system::Trait for Test {
318 | type Origin = Origin;
319 | type Call = ();
320 | type Index = u64;
321 | type BlockNumber = u64;
322 | type Hash = H256;
323 | type Hashing = BlakeTwo256;
324 | type AccountId = u128;
325 | type Lookup = IdentityLookup;
326 | type Header = Header;
327 | type WeightMultiplierUpdate = ();
328 | type Event = ();
329 | type BlockHashCount = BlockHashCount;
330 | type MaximumBlockWeight = MaximumBlockWeight;
331 | type MaximumBlockLength = MaximumBlockLength;
332 | type AvailableBlockRatio = AvailableBlockRatio;
333 | type Version = ();
334 | }
335 | parameter_types! {
336 | pub const FreeTransferPeriod: u32 = 10;
337 | pub const FundTransferFee: u32 = 10;
338 | }
339 | impl Trait for Test {
340 | type Event = ();
341 | type TokenBalance = u64;
342 | type TokenId = u32;
343 | type Currency = Balances;
344 | type TokenFreeTransfers = u32;
345 | type FindAuthor = ();
346 | type FreeTransferPeriod = FreeTransferPeriod;
347 | type FundTransferFee = FundTransferFee;
348 | }
349 | type FungibleModule = Module;
350 | type Balances = balances::Module;
351 | type System = system::Module;
352 |
353 | fn run_to_block(n: u64) {
354 | while System::block_number() < n {
355 | FungibleModule::on_finalize(System::block_number());
356 | Balances::on_finalize(System::block_number());
357 | System::on_finalize(System::block_number());
358 | System::set_block_number(System::block_number() + 1);
359 | System::on_initialize(System::block_number());
360 | Balances::on_initialize(System::block_number());
361 | FungibleModule::on_initialize(System::block_number());
362 | }
363 | }
364 |
365 | // This function basically just builds a genesis storage key/value store according to
366 | // our desired mockup.
367 | fn new_test_ext() -> runtime_io::TestExternalities {
368 | let mut t = system::GenesisConfig::default().build_storage::().unwrap();
369 |
370 | let _ = balances::GenesisConfig:: {
371 | balances: vec![
372 | (1, 100),
373 | (2, 500),
374 | ],
375 | vesting: vec![],
376 | }.assimilate_storage(&mut t).unwrap();
377 |
378 | t.into()
379 | }
380 |
381 | #[test]
382 | fn basic_setup_works() {
383 | with_externalities(&mut new_test_ext(), || {
384 | // Users are initially funded
385 | assert_eq!(Balances::free_balance(1), 100);
386 | assert_eq!(Balances::free_balance(2), 500);
387 |
388 | // Nothing in storage
389 | assert_eq!(FungibleModule::count(), 0);
390 | assert_eq!(FungibleModule::total_supply(0), 0);
391 | assert_eq!(FungibleModule::balance_of((0,1)), 0);
392 | assert_eq!(FungibleModule::balance_of((0,2)), 0);
393 | assert_eq!(FungibleModule::allowance_of((0, 1, 2)), 0);
394 | assert_eq!(FungibleModule::free_transfers(0), 0);
395 | assert_eq!(FungibleModule::free_transfer_count(&(), &(0, 1)), 0);
396 |
397 | // No funds deposited yet
398 | assert_eq!(Balances::free_balance(FungibleModule::fund_account_id(0)), 0);
399 | });
400 | }
401 |
402 | #[test]
403 | fn user_can_create_tokens() {
404 | with_externalities(&mut new_test_ext(), || {
405 | // User can create a token
406 | assert_ok!(FungibleModule::create_token(Origin::signed(1), 10_000, 5, 50));
407 |
408 | // Their free balance is reduced due to deposit
409 | assert_eq!(Balances::free_balance(1), 50);
410 | // Deposit is now held for the fund
411 | assert_eq!(Balances::free_balance(FungibleModule::fund_account_id(0)), 50);
412 |
413 | // There is now a token
414 | assert_eq!(FungibleModule::count(), 1);
415 | assert_eq!(FungibleModule::total_supply(0), 10_000);
416 | assert_eq!(FungibleModule::balance_of((0,1)), 10_000);
417 | assert_eq!(FungibleModule::free_transfers(0), 5);
418 |
419 | });
420 | }
421 |
422 | #[test]
423 | fn user_can_transfer_tokens() {
424 | with_externalities(&mut new_test_ext(), || {
425 | // Create a token
426 | assert_ok!(FungibleModule::create_token(Origin::signed(1), 10_000, 5, 50));
427 | // Transfer the token
428 | assert_ok!(FungibleModule::transfer(Origin::signed(1), 0, 2, 500));
429 | // Balances are updated
430 | assert_eq!(FungibleModule::balance_of((0,1)), 9_500);
431 | assert_eq!(FungibleModule::balance_of((0,2)), 500);
432 | // Can't transfer more than they have
433 | assert_err!(FungibleModule::transfer(Origin::signed(1), 0, 2, 9_501), "user does not have enough tokens");
434 | // But can transfer everything
435 | assert_ok!(FungibleModule::transfer(Origin::signed(1), 0, 2, 9_500));
436 | assert_eq!(FungibleModule::balance_of((0,1)), 0);
437 | assert_eq!(FungibleModule::balance_of((0,2)), 10_000);
438 | });
439 | }
440 |
441 | #[test]
442 | fn user_can_free_transfer_tokens() {
443 | with_externalities(&mut new_test_ext(), || {
444 | // Create a token
445 | assert_ok!(FungibleModule::create_token(Origin::signed(1), 10_000, 5, 50));
446 | // Transfer the token
447 | assert_ok!(FungibleModule::try_free_transfer(Origin::signed(1), 0, 2, 500));
448 | // Balances are updated
449 | assert_eq!(FungibleModule::balance_of((0,1)), 9_500);
450 | assert_eq!(FungibleModule::balance_of((0,2)), 500);
451 | // Fee is taken from fund
452 | assert_eq!(Balances::free_balance(FungibleModule::fund_account_id(0)), 40);
453 | assert_eq!(FungibleModule::free_transfer_count(&(), &(0, 1)), 1);
454 | // Can't transfer more than they have
455 | assert_err!(FungibleModule::try_free_transfer(Origin::signed(1), 0, 2, 9_501), "user does not have enough tokens");
456 | // But can transfer everything
457 | assert_ok!(FungibleModule::try_free_transfer(Origin::signed(1), 0, 2, 9_500));
458 | assert_eq!(FungibleModule::balance_of((0,1)), 0);
459 | assert_eq!(FungibleModule::balance_of((0,2)), 10_000);
460 | assert_eq!(FungibleModule::free_transfer_count(&(), &(0, 1)), 2);
461 | assert_eq!(Balances::free_balance(FungibleModule::fund_account_id(0)), 30);
462 | });
463 | }
464 |
465 | #[test]
466 | fn free_transfer_limit_works() {
467 | with_externalities(&mut new_test_ext(), || {
468 | // Create a token
469 | assert_ok!(FungibleModule::create_token(Origin::signed(1), 10_000, 4, 50));
470 | for _ in 0..4 {
471 | assert_ok!(FungibleModule::try_free_transfer(Origin::signed(1), 0, 2, 500));
472 | }
473 | assert_eq!(Balances::free_balance(FungibleModule::fund_account_id(0)), 10);
474 |
475 | // 5th free transfer fails
476 | assert_err!(FungibleModule::try_free_transfer(Origin::signed(1), 0, 2, 500), "no more free transfers available");
477 | // No funds taken
478 | assert_eq!(Balances::free_balance(FungibleModule::fund_account_id(0)), 10);
479 | assert_eq!(FungibleModule::free_transfer_count(&(), &(0, 1)), 4);
480 |
481 | run_to_block(10);
482 | // Free transfer count reset
483 | assert_eq!(FungibleModule::free_transfer_count(&(), &(0, 1)), 0);
484 | // Transfer works now
485 | assert_ok!(FungibleModule::try_free_transfer(Origin::signed(1), 0, 2, 500));
486 | });
487 | }
488 |
489 | #[test]
490 | fn cannot_free_transfer_without_deposit() {
491 | with_externalities(&mut new_test_ext(), || {
492 | // Create a token
493 | assert_ok!(FungibleModule::create_token(Origin::signed(1), 10_000, 5, 10));
494 |
495 | // First free transfer should work
496 | assert_ok!(FungibleModule::try_free_transfer(Origin::signed(1), 0, 2, 500));
497 | // Funds go to zero
498 | assert_eq!(Balances::free_balance(FungibleModule::fund_account_id(0)), 0);
499 |
500 | // Next free transfer fails
501 | assert_err!(FungibleModule::try_free_transfer(Origin::signed(1), 0, 2, 500), "too few free funds in account");
502 | });
503 | }
504 |
505 |
506 | #[test]
507 | fn it_should_create_token() {
508 | with_externalities(&mut new_test_ext(), || {
509 | // asserting that the stored value is equal to what we stored
510 | let origin = Origin::signed(1);
511 | >::put(1);
512 | let total_supply = 10000000000;
513 | let free_moves = 40;
514 | let deposit = 10;
515 |
516 | assert_ok!(FungibleModule::create_token(origin, total_supply, free_moves, deposit));
517 |
518 | });
519 | }
520 |
521 | #[test]
522 | fn it_deposits_more_currency_to_token() {
523 | with_externalities(&mut new_test_ext(), || {
524 | // asserting that the stored value is equal to what we stored
525 | let origin = Origin::signed(1);
526 | >::put(1);
527 | let token_id = 0;
528 | let value = 40;
529 |
530 | assert_ok!(FungibleModule::deposit(origin, token_id, value));
531 |
532 | assert_eq!(Balances::free_balance(&1), 60);
533 | });
534 | }
535 |
536 | #[test]
537 | fn it_does_not_deposit_if_not_enough_balance() {
538 | with_externalities(&mut new_test_ext(), || {
539 | // asserting that the stored value is equal to what we stored
540 | let origin = Origin::signed(1);
541 | >::put(1);
542 | let token_id = 0;
543 | let value = 101;
544 |
545 | assert_err!(FungibleModule::deposit(origin, token_id, value), "balance too low to send value");
546 |
547 | assert_eq!(Balances::free_balance(&1), 100);
548 | });
549 | }
550 | }
551 |
--------------------------------------------------------------------------------