├── .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 | --------------------------------------------------------------------------------