Giter Site home page Giter Site logo

shawntabrizi / substrate-collectables-workshop Goto Github PK

View Code? Open in Web Editor NEW
230.0 13.0 98.0 698.42 MB

A guided tutorial for building an NFT marketplace with the Polkadot SDK

Home Page: https://www.shawntabrizi.com/substrate-collectables-workshop/

License: MIT License

Rust 98.53% Shell 1.47%
marketplace nft polkadot rust substrate tutorial

substrate-collectables-workshop's Introduction

Substrate Collectables Workshop

This repository is the basis for a tutorial teaching how to develop a simple NFT marketplace using the polkadot-sdk.

If you are looking for the previous version of this tutorial from 2020, you can find all the original source code on the docsify-old branch. However, this content is way out of date, hence this new tutorial! :)

Goal

The goal of this tutorial is to teach by experience various entry level concepts around Polkadot Pallet development.

The tutorial is designed to be completed by anyone with basic familiarity with Rust, and little to no familiarity with the Polkadot SDK.

If you do not feel comfortable with the level of Rust used in this tutorial, we recommend you first check out the rust-state-machine tutorial.

How To Use

This repository is not meant to be used directly, but as the source for generating an interactive tutorial using the source code and readme files included at each commit.

This repository manages 3 branches, each with its own history and purpose:

  • master: This is an expanded version of each step and file of the tutorial. Each step has its own full source code and README which is used for that step in the tutorial.
  • gitorial: This branch repackages all the steps of the tutorial into a single Git history. You can actually take a look at the history of the gitorial branch, and see how each steps evolves using the Git diff.
  • mdbook: This is a special repackaging of the tutorial for generating an mdBook that can be directly used by students.

If you have small changes that need to be made to a single step, feel free to open an issue or make a PR against the master branch. However, for more complex changes which may affect multiple steps, consider learning more about the gitorial format.

More about Gitorial

The heart of this tutorial is the Gitorial format.

If you browse the commit history of the gitorial branch, you will see that each commit is designed to be a single step in the tutorial.

All commits are prefixed with one of:

  • section: This denotes the beginning of a new set of steps which will have a specific goal. These commits will only have changes to the README.md file which can be used to introduce the new section of the tutorial.
  • template: This is the commit has a README.md that teaches the reader any information needed to complete the step. It will also include files with TODO comments, telling the user what specifically needs to be done. A template will always be followed by a solution.
  • solution: These commits will always come after a template commit. These commits will have the final state of all files in the project at the end of a step. Commits prefixed with solution should always compile, run and test successfully (compiler warning are okay). The template and solution commits should be presented together so reads can compare their work to a working solution. These commits can also be used to generate a diff of the step. The README.md file in this commit does not need to be presented to the user.
  • action: This denotes a step in the tutorial where the user needs to complete some action, not necessarily write any code. For example, the user might need to import a new crate. In this case, it does not make sense to have a template and solution, but just the final outcome after the action was taken. The previous commit can be used for generating a diff. The README.md file should contain any information the user needs to complete the action successfully.
  • readme: This is only applied to the last commit in this repo, and denotes that this commit was specifically for make a README.md for users that browse this repository on github. This step should not be used in the tutorial generation.

You can use Git to make changes to the history of the repo, and then use git merge to propagate those changes cleanly into the rest of your repo.

Maintenance

Maintaining the repo means keeping all three of the main branches in sync.

For this, you can use the gitorial-cli.

Once you have made changes to the appropriate branch, you can use these commands to get all branches in order:

  • Convert master to an up to date gitorial branch:

    gitorial-cli repack -p /path/to/substrate-collectables-workshop -i master -s steps -o gitorial2

    Then check your work, and git reset --hard the gitorial branch with gitorial2.

  • Convert gitorial to an up to date mdbook branch:

     gitorial-cli mdbook -p /path/to/substrate-collectables-workshop -i gitorial -o mdbook
  • Convert gitorial to an up to date master branch:

     gitorial-cli unpack -p /path/to/substrate-collectables-workshop -i gitorial -o master -s steps

substrate-collectables-workshop's People

Contributors

mittal-parth avatar shawntabrizi avatar zzz6519003 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

substrate-collectables-workshop's Issues

Explain what the macros do

After reading through the tutorial I understand how to build the functions and storage for a module with the decl_storage! and decl_module! macros, but I don't understand what is actually being built by the macros. It would be great to have a section that explains some of the inner workings.

For the storage macro, what are the "for" and "as" words expressing?
trait Store for Module<T: Trait> as KittyStorage {

What is the structure of a Module and Storage? We really only see a glimpse of their existence in the construct_runtime! macro with Substratekitties: substratekitties::{Module, Call, Storage}

Similarly for decl_module!, what is enum Call?
pub struct Module<T: Trait> for enum Call where origin: T::Origin {

Minor UX improvements for the layout

  1. Make sure "Next" button is always available, so users won't have to look in the sidebar on the left to continue reading at the end of each chapter.
  2. When clicking "Next" button or navigating to the next chapter, the content area scroll should reset, so I don't end peeking the end of the next subchapter after scrolling down the previous one. (Bonus points for actually remembering the scroll position of the last/each content page, so after accidental click on Next I won't reset my progress in the subchapter).

Expand description of `Result`

Many people got the import wrong dispatch::Result.
We should communicate this more clearly. Not sure how actually.
Also people had questions about Rust's ? operator and the oddly looking Ok(()) at the end.

Explanation on creating custom event trait

I think it would be better to have more explanation to build custom type event on creating an event

For example, if I want to make a Transfer event Transfer(AccountId, Price, data):

Specify what name will be used in the event,
in cryptokitties.rs,

decl_event!(
    pub enum Event<T>
    where
        <T as system::Trait>::AccountId,
    {
       Transfer(AccountId, u128, String)
    }
);

substrate-node-new creates accounts with 0 balance

Hi,

i was trying to go through the substratekitties workshop. Unfortunately when i set up the dev chain with substrate-node-new substratekitties someName and then start the node with cd substratekitties ./target/release/substratekitties --dev all dummy accounts are created with a balance of 0. Therefore i can't make any transactions / balance transfers.

Is there anything i need to change in some kind of config fil? I am clueless how how to fix this issue. Thanks in advance. :)

btw. I'm working on a Mac.

Unable to compile at the very beginning at "Creating a Module"

Hi,

I got an compiler error below when I execute ./build.sh following the tutorial.

st00d25-no-MacBook-Pro:substratekitties st00d25$ ./build.sh
Building webassembly binary in runtime/wasm...
   Compiling node-template-runtime v0.9.0 (/Users/st00d25/Qsync/documents/toudai_kifukouza/application_task/substratekitties/runtime)
error: cannot find macro `decl_storage!` in this scope
 --> /Users/st00d25/Qsync/documents/toudai_kifukouza/application_task/substratekitties/runtime/src/substratekitties.rs:5:1
  |
5 | decl_storage! {
  | ^^^^^^^^^^^^

error: cannot find macro `decl_module!` in this scope
  --> /Users/st00d25/Qsync/documents/toudai_kifukouza/application_task/substratekitties/runtime/src/substratekitties.rs:11:1
   |
11 | decl_module! {
   | ^^^^^^^^^^^

error[E0433]: failed to resolve: could not find `Module` in `substratekitties`
   --> /Users/st00d25/Qsync/documents/toudai_kifukouza/application_task/substratekitties/runtime/src/lib.rs:180:1
    |
180 | / construct_runtime!(
181 | |     pub enum Runtime with Log(InternalLog: DigestItem<Hash, Ed25519AuthorityId>) where
182 | |         Block = Block,
183 | |         NodeBlock = opaque::Block,
...   |
194 | |     }
195 | | );
    | |__^ could not find `Module` in `substratekitties`
    |
    = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

error[E0412]: cannot find type `Module` in module `substratekitties`
   --> /Users/st00d25/Qsync/documents/toudai_kifukouza/application_task/substratekitties/runtime/src/lib.rs:180:1
    |
180 | / construct_runtime!(
181 | |     pub enum Runtime with Log(InternalLog: DigestItem<Hash, Ed25519AuthorityId>) where
182 | |         Block = Block,
183 | |         NodeBlock = opaque::Block,
...   |
194 | |     }
195 | | );
    | |__^ not found in `substratekitties`
    |
    = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
help: possible candidates are found in other modules, you can import them into scope
    |
15  | use aura::Module;
    |
15  | use balances::Module;
    |
15  | use consensus::Module;
    |
15  | use indices::Module;
    |
and 3 other candidates

error: duplicate lang item in crate `std`: `panic_impl`.
  |
  = note: first defined in crate `sr_io`.

error: duplicate lang item in crate `std`: `oom`.
  |
  = note: first defined in crate `sr_io`.

error: aborting due to 6 previous errors

Some errors occurred: E0412, E0433.
For more information about an error, try `rustc --explain E0412`.
error: Could not compile `node-template-runtime`.

I have added "use" in substratekitties.rs to avoid this error like below.

//add this
use support::{decl_module, decl_storage};

pub trait Trait: system::Trait {}

decl_storage! {
    trait Store for Module<T: Trait> as KittyStorage {
        // Declare storage and getter functions here
    }
}

decl_module! {
    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
        // Declare public functions here
    }
}

I would like to share this tips for others.
Thank you

Will there ever be a time when `./scripts/build.sh` will pass and `cargo build --release` will fail?

Wondering if both the build script and cargo build are needed simply to verify that the module is correct. If the wasm build is successful than the llvm build should be successful too. The reason this matters is that the build script has close-enough-to-instant results that it doesn't delay the tutorial. However building with cargo takes considerably longer since it requires a good deal more packages, and thus ruins the flow of working through this tutorial.

So long as the next step isn't using Polkadot, users should be able to just use the build script to verify that the module is compiling properly and than move onto the next section. cargo should only be needed if the next step is to actually run substrate.

Fix imports from parity_codec_derive -> parity_codec

The derive features moved from a seperate crate to being a feature of the parity_codec crate.
paritytech/substrate@85b1c78.

The code examples are broken at the moment and need to be changed to match the new syntax.

error[E0432]: unresolved import `parity_codec_derive`
 --> /Users/stefaniedoll/Parity/substratekitties/runtime/src/substratekitties.rs:4:5
  |
4 | use parity_codec_derive::{Encode, Decode};
  |     ^^^^^^^^^^^^^^^^^^^ did you mean `parity_codec::parity_codec_derive`?

error: cannot determine resolution for the derive macro `Decode`
 --> /Users/stefaniedoll/Parity/substratekitties/runtime/src/substratekitties.rs:6:18
  |
6 | #[derive(Encode, Decode, Default, Clone, PartialEq)]
  |                  ^^^^^^
  |
  = note: import resolution is stuck, try simplifying macro imports

Link to docs about entropy source

Substrate uses a safe mixing algorithm that uses the entropy of previous blocks to generate new random data for each subsequent block.

Explaining entropy and where to get it is not in scope for this tutorial, but a link would be nice. In particular I'd like to know under what assumptions the mixing algorithm is safe, and what kinds of validator collusion or other attacks might compromise it.

Rewrite the final Substratekittie Runtime

The final Substratekitties runtime was written a long while ago, and really is not the best example of module code.

Things that could be improved:

  • Using Enumerable Storage Map
  • Good tests
  • Generic Types
  • More private functions to remove redundant code
  • Smarter ensure checks
  • Reduce the number of "game" functions to simplify the tutorial
  • More emphasis with interacting with other modules
  • Kitty attributes as their own storage item rather than in a struct so that upgrades are easier

@xlc had written his own version of the kitties tutorial which I think goes in the right direction, but may need to be modified so it is easier to teach in a step by step process:

use support::{
	decl_module, decl_storage, decl_event, ensure, StorageValue, StorageMap,
	Parameter, traits::Currency
};
use runtime_primitives::traits::{SimpleArithmetic, Bounded, One, Member};
use parity_codec::{Encode, Decode};
use runtime_io::blake2_128;
use system::ensure_signed;
use rstd::result;
use crate::linked_item::{LinkedList, LinkedItem};

pub trait Trait: system::Trait {
	type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
	type KittyIndex: Parameter + Member + SimpleArithmetic + Bounded + Default + Copy;
	type Currency: Currency<Self::AccountId>;
}

type BalanceOf<T> = <<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance;

#[derive(Encode, Decode)]
pub struct Kitty(pub [u8; 16]);

type KittyLinkedItem<T> = LinkedItem<<T as Trait>::KittyIndex>;
type OwnedKittiesList<T> = LinkedList<OwnedKitties<T>, <T as system::Trait>::AccountId, <T as Trait>::KittyIndex>;

decl_storage! {
	trait Store for Module<T: Trait> as Kitties {
		/// Stores all the kitties, key is the kitty id / index
		pub Kitties get(kitty): map T::KittyIndex => Option<Kitty>;
		/// Stores the total number of kitties. i.e. the next kitty index
		pub KittiesCount get(kitties_count): T::KittyIndex;

		/// Get kitty ownership. Stored in a linked map.
		pub OwnedKitties get(owned_kitties): map (T::AccountId, Option<T::KittyIndex>) => Option<KittyLinkedItem<T>>;

		/// Get kitty owner
		pub KittyOwners get(kitty_owner): map T::KittyIndex => Option<T::AccountId>;

		/// Get kitty price. None means not for sale.
		pub KittyPrices get(kitty_price): map T::KittyIndex => Option<BalanceOf<T>>
	}
}

decl_event!(
	pub enum Event<T> where
		<T as system::Trait>::AccountId,
		<T as Trait>::KittyIndex,
		Balance = BalanceOf<T>,
	{
		/// A kitty is created. (owner, kitty_id)
		Created(AccountId, KittyIndex),
		/// A kitty is transferred. (from, to, kitty_id)
		Transferred(AccountId, AccountId, KittyIndex),
		/// A kitty is available for sale. (owner, kitty_id, price)
		Ask(AccountId, KittyIndex, Option<Balance>),
		/// A kitty is sold. (from, to, kitty_id, price)
		Sold(AccountId, AccountId, KittyIndex, Balance),
	}
);

decl_module! {
	pub struct Module<T: Trait> for enum Call where origin: T::Origin {
		fn deposit_event<T>() = default;

		/// Create a new kitty
		pub fn create(origin) {
			let sender = ensure_signed(origin)?;
			let kitty_id = Self::next_kitty_id()?;

			// Generate a random 128bit value
			let dna = Self::random_value(&sender);

			// Create and store kitty
			let kitty = Kitty(dna);
			Self::insert_kitty(&sender, kitty_id, kitty);

			Self::deposit_event(RawEvent::Created(sender, kitty_id));
		}

		/// Breed kitties
		pub fn breed(origin, kitty_id_1: T::KittyIndex, kitty_id_2: T::KittyIndex) {
			let sender = ensure_signed(origin)?;

			let new_kitty_id = Self::do_breed(&sender, kitty_id_1, kitty_id_2)?;

			Self::deposit_event(RawEvent::Created(sender, new_kitty_id));
		}

		/// Transfer a kitty to new owner
		pub fn transfer(origin, to: T::AccountId, kitty_id: T::KittyIndex) {
			let sender = ensure_signed(origin)?;

			ensure!(<OwnedKitties<T>>::exists(&(sender.clone(), Some(kitty_id))), "Only owner can transfer kitty");
			
			Self::do_transfer(&sender, &to, kitty_id);

			Self::deposit_event(RawEvent::Transferred(sender, to, kitty_id));
		}

		/// Set a price for a kitty for sale
		/// None to delist the kitty
		pub fn ask(origin, kitty_id: T::KittyIndex, price: Option<BalanceOf<T>>) {
			let sender = ensure_signed(origin)?;

			ensure!(<OwnedKitties<T>>::exists(&(sender.clone(), Some(kitty_id))), "Only owner can set price for kitty");

			if let Some(ref price) = price {
				<KittyPrices<T>>::insert(kitty_id, price);
			} else {
				<KittyPrices<T>>::remove(kitty_id);
			}
			
			Self::deposit_event(RawEvent::Ask(sender, kitty_id, price));
		}

		/// Buy a kitty with max price willing to pay
		pub fn buy(origin, kitty_id: T::KittyIndex, price: BalanceOf<T>) {
			let sender = ensure_signed(origin)?;

			let owner = Self::kitty_owner(kitty_id);
			ensure!(owner.is_some(), "Kitty does not exist");
			let owner = owner.unwrap();

			let kitty_price = Self::kitty_price(kitty_id);
			ensure!(kitty_price.is_some(), "Kitty not for sale");

			let kitty_price = kitty_price.unwrap();
			ensure!(price >= kitty_price, "Price is too low");

			T::Currency::transfer(&sender, &owner, kitty_price)?;

			<KittyPrices<T>>::remove(kitty_id);

			Self::do_transfer(&owner, &sender, kitty_id);

			Self::deposit_event(RawEvent::Sold(owner, sender, kitty_id, price));
		}
	}
}

fn combine_dna(dna1: u8, dna2: u8, selector: u8) -> u8 {
	((selector & dna1) | (!selector & dna2))
}

impl<T: Trait> Module<T> {
	fn random_value(sender: &T::AccountId) -> [u8; 16] {
		let payload = (<system::Module<T>>::random_seed(), sender, <system::Module<T>>::extrinsic_index(), <system::Module<T>>::block_number());
		payload.using_encoded(blake2_128)
	}

	fn next_kitty_id() -> result::Result<T::KittyIndex, &'static str> {
		let kitty_id = Self::kitties_count();
		if kitty_id == <T::KittyIndex as Bounded>::max_value() {
			return Err("Kitties count overflow");
		}
		Ok(kitty_id)
	}

	fn insert_owned_kitty(owner: &T::AccountId, kitty_id: T::KittyIndex) {
		<OwnedKittiesList<T>>::append(owner, kitty_id);
	}

	fn insert_kitty(owner: &T::AccountId, kitty_id: T::KittyIndex, kitty: Kitty) {
		// Create and store kitty
		<Kitties<T>>::insert(kitty_id, kitty);
		<KittiesCount<T>>::put(kitty_id + One::one());
		<KittyOwners<T>>::insert(kitty_id, owner.clone());

		Self::insert_owned_kitty(owner, kitty_id);
	}

	fn do_breed(sender: &T::AccountId, kitty_id_1: T::KittyIndex, kitty_id_2: T::KittyIndex) -> result::Result<T::KittyIndex, &'static str> {
		let kitty1 = Self::kitty(kitty_id_1);
		let kitty2 = Self::kitty(kitty_id_2);

		ensure!(kitty1.is_some(), "Invalid kitty_id_1");
		ensure!(kitty2.is_some(), "Invalid kitty_id_2");
		ensure!(kitty_id_1 != kitty_id_2, "Needs different parent");
		ensure!(Self::kitty_owner(&kitty_id_1).map(|owner| owner == *sender).unwrap_or(false), "Not onwer of kitty1");
		ensure!(Self::kitty_owner(&kitty_id_2).map(|owner| owner == *sender).unwrap_or(false), "Not owner of kitty2");

		let kitty_id = Self::next_kitty_id()?;

		let kitty1_dna = kitty1.unwrap().0;
		let kitty2_dna = kitty2.unwrap().0;

		// Generate a random 128bit value
		let selector = Self::random_value(&sender);
		let mut new_dna = [0u8; 16];

		// Combine parents and selector to create new kitty
		for i in 0..kitty1_dna.len() {
			new_dna[i] = combine_dna(kitty1_dna[i], kitty2_dna[i], selector[i]);
		}

		Self::insert_kitty(sender, kitty_id, Kitty(new_dna));

		Ok(kitty_id)
	}

	fn do_transfer(from: &T::AccountId, to: &T::AccountId, kitty_id: T::KittyIndex)  {
		<OwnedKittiesList<T>>::remove(&from, kitty_id);
		<OwnedKittiesList<T>>::append(&to, kitty_id);
		<KittyOwners<T>>::insert(kitty_id, to);
	}
}

/// Tests for Kitties module
#[cfg(test)]
mod tests {
	use super::*;

	use runtime_io::with_externalities;
	use primitives::{H256, Blake2Hasher};
	use support::{impl_outer_origin};
	use runtime_primitives::{
		BuildStorage,
		traits::{BlakeTwo256, IdentityLookup},
		testing::{Digest, DigestItem, Header}
	};

	impl_outer_origin! {
		pub enum Origin for Test {}
	}

	// For testing the module, we construct most of a mock runtime. This means
	// first constructing a configuration type (`Test`) which `impl`s each of the
	// configuration traits of modules we want to use.
	#[derive(Clone, Eq, PartialEq, Debug)]
	pub struct Test;
	impl system::Trait for Test {
		type Origin = Origin;
		type Index = u64;
		type BlockNumber = u64;
		type Hash = H256;
		type Hashing = BlakeTwo256;
		type Digest = Digest;
		type AccountId = u64;
		type Lookup = IdentityLookup<Self::AccountId>;
		type Header = Header;
		type Event = ();
		type Log = DigestItem;
	}
	impl balances::Trait for Test {
		type Balance = u32;
		type OnFreeBalanceZero = ();
		type OnNewAccount = ();
		type Event = ();

		type TransactionPayment = ();
		type DustRemoval = ();
		type TransferPayment = ();
	}
	impl Trait for Test {
		type KittyIndex = u32;
		type Currency = balances::Module<Test>;
		type Event = ();
	}
	type KittyModule = Module<Test>;
	type OwnedKittiesTest = OwnedKitties<Test>;

	// This function basically just builds a genesis storage key/value store according to
	// our desired mockup.
	fn new_test_ext() -> runtime_io::TestExternalities<Blake2Hasher> {
		system::GenesisConfig::<Test>::default().build_storage().unwrap().0.into()
	}

	#[test]
	fn owned_kitties_can_append_values() {
		with_externalities(&mut new_test_ext(), || {
			OwnedKittiesList::<Test>::append(&0, 1);

			assert_eq!(OwnedKittiesTest::get(&(0, None)), Some(KittyLinkedItem::<Test> {
				prev: Some(1),
				next: Some(1),
			}));

			assert_eq!(OwnedKittiesTest::get(&(0, Some(1))), Some(KittyLinkedItem::<Test> {
				prev: None,
				next: None,
			}));

			OwnedKittiesList::<Test>::append(&0, 2);

			assert_eq!(OwnedKittiesTest::get(&(0, None)), Some(KittyLinkedItem::<Test> {
				prev: Some(2),
				next: Some(1),
			}));

			assert_eq!(OwnedKittiesTest::get(&(0, Some(1))), Some(KittyLinkedItem::<Test> {
				prev: None,
				next: Some(2),
			}));

			assert_eq!(OwnedKittiesTest::get(&(0, Some(2))), Some(KittyLinkedItem::<Test> {
				prev: Some(1),
				next: None,
			}));

			OwnedKittiesList::<Test>::append(&0, 3);

			assert_eq!(OwnedKittiesTest::get(&(0, None)), Some(KittyLinkedItem::<Test> {
				prev: Some(3),
				next: Some(1),
			}));

			assert_eq!(OwnedKittiesTest::get(&(0, Some(1))), Some(KittyLinkedItem::<Test> {
				prev: None,
				next: Some(2),
			}));

			assert_eq!(OwnedKittiesTest::get(&(0, Some(2))), Some(KittyLinkedItem::<Test> {
				prev: Some(1),
				next: Some(3),
			}));

			assert_eq!(OwnedKittiesTest::get(&(0, Some(3))), Some(KittyLinkedItem::<Test> {
				prev: Some(2),
				next: None,
			}));
		});
	}

	#[test]
	fn owned_kitties_can_remove_values() {
		with_externalities(&mut new_test_ext(), || {
			OwnedKittiesList::<Test>::append(&0, 1);
			OwnedKittiesList::<Test>::append(&0, 2);
			OwnedKittiesList::<Test>::append(&0, 3);

			OwnedKittiesList::<Test>::remove(&0, 2);

			assert_eq!(OwnedKittiesTest::get(&(0, None)), Some(KittyLinkedItem::<Test> {
				prev: Some(3),
				next: Some(1),
			}));

			assert_eq!(OwnedKittiesTest::get(&(0, Some(1))), Some(KittyLinkedItem::<Test> {
				prev: None,
				next: Some(3),
			}));

			assert_eq!(OwnedKittiesTest::get(&(0, Some(2))), None);

			assert_eq!(OwnedKittiesTest::get(&(0, Some(3))), Some(KittyLinkedItem::<Test> {
				prev: Some(1),
				next: None,
			}));

			OwnedKittiesList::<Test>::remove(&0, 1);

			assert_eq!(OwnedKittiesTest::get(&(0, None)), Some(KittyLinkedItem::<Test> {
				prev: Some(3),
				next: Some(3),
			}));

			assert_eq!(OwnedKittiesTest::get(&(0, Some(1))), None);

			assert_eq!(OwnedKittiesTest::get(&(0, Some(2))), None);

			assert_eq!(OwnedKittiesTest::get(&(0, Some(3))), Some(KittyLinkedItem::<Test> {
				prev: None,
				next: None,
			}));

			OwnedKittiesList::<Test>::remove(&0, 3);

			assert_eq!(OwnedKittiesTest::get(&(0, None)), Some(KittyLinkedItem::<Test> {
				prev: None,
				next: None,
			}));

			assert_eq!(OwnedKittiesTest::get(&(0, Some(1))), None);

			assert_eq!(OwnedKittiesTest::get(&(0, Some(2))), None);

			assert_eq!(OwnedKittiesTest::get(&(0, Some(2))), None);
		});
	}
}

linked_item.rs

use support::{StorageMap, Parameter};
use runtime_primitives::traits::Member;
use parity_codec::{Encode, Decode};

#[cfg_attr(feature = "std", derive(Debug, PartialEq, Eq))]
#[derive(Encode, Decode)]
pub struct LinkedItem<Item> {
	pub prev: Option<Item>,
	pub next: Option<Item>,
}

pub struct LinkedList<Storage, Key, Item>(rstd::marker::PhantomData<(Storage, Key, Item)>);

impl<Storage, Key, Value> LinkedList<Storage, Key, Value> where
  Value: Parameter + Member + Copy,
  Key: Parameter,
  Storage: StorageMap<(Key, Option<Value>), LinkedItem<Value>, Query = Option<LinkedItem<Value>>>,
{
	fn read_head(key: &Key) -> LinkedItem<Value> {
		Self::read(key, None)
	}

	fn write_head(account: &Key, item: LinkedItem<Value>) {
		Self::write(account, None, item);
	}

	fn read(key: &Key, value: Option<Value>) -> LinkedItem<Value> {
		Storage::get(&(key.clone(), value)).unwrap_or_else(|| LinkedItem {
			prev: None,
			next: None,
		})
	}

	fn write(key: &Key, value: Option<Value>, item: LinkedItem<Value>) {
		Storage::insert(&(key.clone(), value), item);
	}

	pub fn append(key: &Key, value: Value) {
		let head = Self::read_head(key);
		let new_head = LinkedItem {
			prev: Some(value),
			next: head.next,
		};

		Self::write_head(key, new_head);

		let prev = Self::read(key, head.prev);
		let new_prev = LinkedItem {
			prev: prev.prev,
			next: Some(value),
		};
		Self::write(key, head.prev, new_prev);

		let item = LinkedItem {
			prev: head.prev,
			next: None,
		};
		Self::write(key, Some(value), item);
	}

	pub fn remove(key: &Key, value: Value) {
		if let Some(item) = Storage::take(&(key.clone(), Some(value))) {
			let prev = Self::read(key, item.prev);
			let new_prev = LinkedItem {
				prev: prev.prev,
				next: item.next,
			};

			Self::write(key, item.prev, new_prev);

			let next = Self::read(key, item.next);
			let new_next = LinkedItem {
				prev: item.prev,
				next: next.next,
			};

			Self::write(key, item.next, new_next);
		}
	}
}

Chapter 1: creating a module error

//substratekitties.rs

use support::{decl_storage, decl_module};

pub trait Trait: system::Trait {}

decl_storage! {
    trait Store for Module<T: Trait> as KittyStorage {
        // Declare storage and getter functions here
    }
}

decl_module! {
    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
        // Declare public functions here
    }
}

//lib.rs

construct_runtime!(
    pub enum Runtime with Log(InternalLog: DigestItem<Hash, Ed25519AuthorityId>) where
        Block = Block,
        NodeBlock = opaque::Block,
        UncheckedExtrinsic = UncheckedExtrinsic
    {
        System: system::{default, Log(ChangesTrieRoot)},
        Timestamp: timestamp::{Module, Call, Storage, Config<T>, Inherent},
        Consensus: consensus::{Module, Call, Storage, Config<T>, Log(AuthoritiesChange), Inherent},
        Aura: aura::{Module},
        Indices: indices,
        Balances: balances,
        Sudo: sudo,
        // Add this line
        Substratekitties: substratekitties::{Module, Call, Storage},
    }
);

//results in: "expected 3 found 2 type arguments" -> error on lib.rs ^ code

Unknown type to decode

Hi @shawntabrizi thank you for the tutorial, it's been very helpful. I had one issue at the very last section. I pass in the type into addCodecTransform in the react component constructor, but I get the Unknown type to decode error below.

I created kitties and sent them to accounts using the polkadot-js app. I created kitties for different accounts in the oo7 app, however, I got the error below while trying to decode and show the kitty cards.

I have oo7-substrate ^0.4.11

// in the constructor
addCodecTransform('Kitty<Hash,Balance>', {
    id: 'Hash',
    dna: 'Hash',
    price: 'Balance',
    gen: 'u64'
});

// in the console
runtime.substratekitties.allKittiesCount.then(console.log)
8
runtime.substratekitties.allKittiesArray(0).then((result) => { hash = result })
runtime.substratekitties.kitties(hash).then(console.log)

Uncaught Unknown type to decode: Kitty<Hash,Balance>
    decode @ codec.js:363
    StorageBond.r @ storageBond.js:16
    callback @ subscriptionBond.js:20
    ws.onmessage @ nodeService.js:86

Substrate UI does not compile on Windows

When doing yarn install build fails with the message

Building the projects in this solution one at a time. To enable parallel build, please add the "/m" switch.
  blake2s.c
  blake2sp.c
  blake2b.c
  blake2bp.c
c:\dev\substrate\project\substratekitties-ui\node_modules\blake2js\lib\blake2\sse\blake2-config.h(70): fatal error C1189: #error:  "This code requires at least SSE2." (compiling source file ..\lib\BLAKE2\sse\blake2s.c) [C:\Dev\Substrate\project\substratekitties-ui\node_modules\blake2js\build\addon.vcxproj]
c:\dev\substrate\project\substratekitties-ui\node_modules\blake2js\lib\blake2\sse\blake2-config.h(70): fatal error C1189: #error:  "This code requires at least SSE2." (compiling source file ..\lib\BLAKE2\sse\blake2b.c) [C:\Dev\Substrate\project\substratekitties-ui\node_modules\blake2js\build\addon.vcxproj]

Didn't find a way to enable SSE2, although my machine is x64 so it should be enabled by default.

Unused method:make_transfer is used in Kitty Tutorial

Hi I'm following substrate kitty tutorial.

In below tutorial, make_transfer method is used to transfer coin when buying kitty.
https://shawntabrizi.github.io/substrate-collectables-workshop/#/3/buying-a-kitty
<balances::Module>::make_transfer(&from, &to, value)?;

However when I use make_transfer, I face following error.
error[E0599]: no function or associated item named make_transfer found for type srml_balances::Module<T> in the current scope --> /usr/src/app/ohinerichain/runtime/src/template.rs:72:36 | 72 | <balances::Module<T>>::make_transfer(origin, &owner, price)?; | ^^^^^^^^^^^^^ function or associated item not found in srml_balances::Module<T>

I found out make_transfer method is no longer available after this commit.
paritytech/substrate@cc8f93c

Can you please suggest how we can do this kind of simple transfer instead of make_transfer and fix tutorial?

A Chinese translation of the tutorial

I am translating this tutorial into Chinese version because of my interest.
Now I have completed README and Section 0 in branch/zh-cn-dev of my fork.
Hope it could help some Chinese developer who are interested in the Substrate.

Explanation on why handling lists in runtimes is dangerous

There's a section that says "Substrate does not natively support a list type since it may encourage dangerous habits.". Then in the auctions code a Vec is used, which is a kind of list and there is/was also a StorageList in substrate. I think it could be useful to expand on that section and maybe add a link to some material that explains better what exactly is dangerous and why and what can be done to avoid it.

Japanese translation of the tutorial

I am translating this tutorial into Japanese so that more developers in Japan find it less daunting and join substrate/polkadot community.
I've already created the repo branch/ja-JP, and am expecting one~two weeks are needed for completion of translation.
If anyone interested in peer review, please leave a comment on this issue.

Feeback from Oslo

  • Make it clear which tab you are on in the editor
  • Add a page to show "how to follow the tutorial"
  • (maybe) Find a way to make the next links better.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.