forked from paritytech/substrate
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from gleb-urvanov/feature/asset-info
Feature/asset info
- Loading branch information
Showing
8 changed files
with
1,337 additions
and
673 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
[package] | ||
authors = ['Substrate DevHub <https://github.com/substrate-developer-hub>'] | ||
description = 'FRAME pallet template for defining custom runtime logic.' | ||
edition = '2018' | ||
homepage = 'https://substrate.dev' | ||
license = 'Unlicense' | ||
name = 'pallet-assets-info' | ||
repository = 'https://github.com/substrate-developer-hub/substrate-node-template/' | ||
version = '2.0.0' | ||
|
||
[package.metadata.docs.rs] | ||
targets = ['x86_64-unknown-linux-gnu'] | ||
|
||
# alias "parity-scale-code" to "codec" | ||
[dependencies.codec] | ||
default-features = false | ||
features = ['derive'] | ||
package = 'parity-scale-codec' | ||
version = '1.3.4' | ||
|
||
[dependencies] | ||
frame-support = { default-features = false, version = '2.0.0' } | ||
frame-system = { default-features = false, version = '2.0.0' } | ||
pallet-assets = { path = '../assets', default-features = false, version = '2.0.0' } | ||
|
||
[dev-dependencies] | ||
sp-core = { default-features = false, version = '2.0.0' } | ||
sp-io = { default-features = false, version = '2.0.0' } | ||
sp-runtime = { default-features = false, version = '2.0.0' } | ||
|
||
[features] | ||
default = ['std'] | ||
std = [ | ||
'codec/std', | ||
'frame-support/std', | ||
'frame-system/std', | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
#![cfg_attr(not(feature = "std"), no_std)] | ||
|
||
/// Edit this file to define custom logic or remove it if it is not needed. | ||
/// Learn more about FRAME and the core library of Substrate FRAME pallets: | ||
/// https://substrate.dev/docs/en/knowledgebase/runtime/frame | ||
use frame_support::{ | ||
codec::{Decode, Encode}, | ||
decl_error, decl_event, decl_module, decl_storage, dispatch, ensure, | ||
sp_runtime::RuntimeDebug, | ||
traits::{Get, Vec}, | ||
}; | ||
use frame_system::ensure_root; | ||
|
||
use pallet_assets as assets; | ||
|
||
#[cfg(test)] | ||
mod mock; | ||
|
||
#[cfg(test)] | ||
mod tests; | ||
|
||
/// Configure the pallet by specifying the parameters and types on which it depends. | ||
pub trait Trait: assets::Trait { | ||
/// Because this pallet emits events, it depends on the runtime's definition of an event. | ||
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>; | ||
|
||
/// The minimum length a name may be. | ||
type MinLengthName: Get<usize>; | ||
|
||
/// The maximum length a name may be. | ||
type MaxLengthName: Get<usize>; | ||
|
||
/// The minimum length a symbol may be. | ||
type MinLengthSymbol: Get<usize>; | ||
|
||
/// The maximum length a symbol may be. | ||
type MaxLengthSymbol: Get<usize>; | ||
|
||
/// The minimum length a description may be. | ||
type MinLengthDescription: Get<usize>; | ||
|
||
/// The maximum length a description may be. | ||
type MaxLengthDescription: Get<usize>; | ||
|
||
/// The maximum decimal points an asset may be. | ||
type MaxDecimals: Get<u32>; | ||
} | ||
|
||
#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq, Eq)] | ||
pub struct AssetInfo { | ||
name: Option<Vec<u8>>, | ||
symbol: Option<Vec<u8>>, | ||
description: Option<Vec<u8>>, | ||
decimals: Option<u32>, | ||
} | ||
|
||
decl_storage! { | ||
trait Store for Module<T: Trait> as AssetsInfoModule { | ||
/// TWOX-NOTE: `AssetId` is trusted, so this is safe. | ||
AssetsInfo get(fn get_info): map hasher(twox_64_concat) T::AssetId => AssetInfo; | ||
} | ||
} | ||
|
||
// Pallets use events to inform users when important changes are made. | ||
// https://substrate.dev/docs/en/knowledgebase/runtime/events | ||
decl_event!( | ||
pub enum Event<T> | ||
where | ||
AssetId = <T as assets::Trait>::AssetId, | ||
{ | ||
/// Asset info stored. [assetId, info] | ||
InfoStored(AssetId, AssetInfo), | ||
} | ||
); | ||
|
||
// Errors inform users that something went wrong. | ||
decl_error! { | ||
pub enum Error for Module<T: Trait> { | ||
/// A name is too short. | ||
TooShortName, | ||
/// A name is too long. | ||
TooLongName, | ||
/// A symbol is too short. | ||
TooShortSymbol, | ||
/// A symbol is too long. | ||
TooLongSymbol, | ||
/// A description is too short. | ||
TooShortDescription, | ||
/// A description is too long. | ||
TooLongDescription, | ||
/// A decimals point value is out of range | ||
DecimalsOutOfRange, | ||
/// Asset does not exist | ||
AssetNotExist, | ||
} | ||
} | ||
|
||
decl_module! { | ||
pub struct Module<T: Trait> for enum Call where origin: T::Origin { | ||
|
||
type Error = Error<T>; | ||
|
||
fn deposit_event() = default; | ||
|
||
#[weight = 10_000 + T::DbWeight::get().writes(1) + T::DbWeight::get().reads(1)] | ||
pub fn set_info(origin, asset: T::AssetId, name: Option<Vec<u8>>, symbol: Option<Vec<u8>>, description: Option<Vec<u8>>, decimals: Option<u32>) -> dispatch::DispatchResult { | ||
ensure_root(origin)?; | ||
|
||
let info = Self::set_asset_info(asset, name, symbol, description, decimals)?; | ||
|
||
Self::deposit_event(RawEvent::InfoStored(asset, info)); | ||
|
||
Ok(()) | ||
} | ||
} | ||
} | ||
|
||
impl<T: Trait> Module<T> { | ||
pub fn set_asset_info( | ||
asset: T::AssetId, | ||
name: Option<Vec<u8>>, | ||
symbol: Option<Vec<u8>>, | ||
description: Option<Vec<u8>>, | ||
decimals: Option<u32>, | ||
) -> Result<AssetInfo, Error<T>> { | ||
// is this the correct approach, could be a separate fn at least ? | ||
#[cfg(not(test))] | ||
{ | ||
let id = <assets::Module<T>>::next_asset_id(); | ||
ensure!(asset < id, Error::<T>::AssetNotExist); | ||
} | ||
|
||
let current: AssetInfo = Self::get_info(asset); | ||
|
||
let info = AssetInfo { | ||
name: name.or(current.name), | ||
symbol: symbol.or(current.symbol), | ||
description: description.or(current.description), | ||
decimals: decimals.or(current.decimals), | ||
}; | ||
let to_check = info.clone(); | ||
|
||
if to_check.name.is_some() { | ||
let name = to_check.name.unwrap(); | ||
ensure!( | ||
name.len() >= T::MinLengthName::get(), | ||
Error::<T>::TooShortName | ||
); | ||
ensure!( | ||
name.len() <= T::MaxLengthName::get(), | ||
Error::<T>::TooLongName | ||
); | ||
} | ||
if to_check.symbol.is_some() { | ||
let sym = to_check.symbol.unwrap(); | ||
ensure!( | ||
sym.len() >= T::MinLengthSymbol::get(), | ||
Error::<T>::TooShortSymbol | ||
); | ||
ensure!( | ||
sym.len() <= T::MaxLengthSymbol::get(), | ||
Error::<T>::TooLongSymbol | ||
); | ||
} | ||
if to_check.description.is_some() { | ||
let desc = to_check.description.unwrap(); | ||
ensure!( | ||
desc.len() >= T::MinLengthDescription::get(), | ||
Error::<T>::TooShortDescription | ||
); | ||
ensure!( | ||
desc.len() <= T::MaxLengthDescription::get(), | ||
Error::<T>::TooLongDescription | ||
); | ||
} | ||
if to_check.decimals.is_some() { | ||
let decimals = to_check.decimals.unwrap(); | ||
ensure!( | ||
decimals <= T::MaxDecimals::get() as u32, | ||
Error::<T>::DecimalsOutOfRange | ||
); | ||
} | ||
|
||
<AssetsInfo<T>>::insert(asset, info.clone()); | ||
|
||
Ok(info) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
use crate::{Module, Trait}; | ||
use sp_core::H256; | ||
use frame_support::{impl_outer_origin, parameter_types, weights::Weight}; | ||
use sp_runtime::{ | ||
traits::{BlakeTwo256, IdentityLookup}, testing::Header, Perbill, | ||
}; | ||
use frame_system as system; | ||
use pallet_assets as assets; | ||
|
||
impl_outer_origin! { | ||
pub enum Origin for Test {} | ||
} | ||
|
||
// Configure a mock runtime to test the pallet. | ||
|
||
#[derive(Clone, Eq, PartialEq)] | ||
pub struct Test; | ||
parameter_types! { | ||
pub const BlockHashCount: u64 = 250; | ||
pub const MaximumBlockWeight: Weight = 1024; | ||
pub const MaximumBlockLength: u32 = 2 * 1024; | ||
pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); | ||
pub const MinLengthName: usize = 0; | ||
pub const MaxLengthName: usize = 32; | ||
pub const MinLengthSymbol: usize = 3; | ||
pub const MaxLengthSymbol: usize = 8; | ||
pub const MinLengthDescription: usize = 0; | ||
pub const MaxLengthDescription: usize = 255; | ||
pub const MaxDecimals: u32 = 10; | ||
} | ||
|
||
impl system::Trait for Test { | ||
type BaseCallFilter = (); | ||
type Origin = Origin; | ||
type Call = (); | ||
type Index = u64; | ||
type BlockNumber = u64; | ||
type Hash = H256; | ||
type Hashing = BlakeTwo256; | ||
type AccountId = u64; | ||
type Lookup = IdentityLookup<Self::AccountId>; | ||
type Header = Header; | ||
type Event = (); | ||
type BlockHashCount = BlockHashCount; | ||
type MaximumBlockWeight = MaximumBlockWeight; | ||
type DbWeight = (); | ||
type BlockExecutionWeight = (); | ||
type ExtrinsicBaseWeight = (); | ||
type MaximumExtrinsicWeight = MaximumBlockWeight; | ||
type MaximumBlockLength = MaximumBlockLength; | ||
type AvailableBlockRatio = AvailableBlockRatio; | ||
type Version = (); | ||
type PalletInfo = (); | ||
type AccountData = (); | ||
type OnNewAccount = (); | ||
type OnKilledAccount = (); | ||
type SystemWeightInfo = (); | ||
} | ||
|
||
impl assets::Trait for Test { | ||
type Event = (); | ||
type Balance = u64; | ||
type AssetId = u32; | ||
} | ||
|
||
impl Trait for Test { | ||
type Event = (); | ||
type MinLengthName = MinLengthName; | ||
type MaxLengthName = MaxLengthName; | ||
type MinLengthSymbol = MinLengthSymbol; | ||
type MaxLengthSymbol = MaxLengthSymbol; | ||
type MinLengthDescription = MinLengthDescription; | ||
type MaxLengthDescription = MaxLengthDescription; | ||
type MaxDecimals = MaxDecimals; | ||
} | ||
|
||
pub type AssetsInfoModule = Module<Test>; | ||
|
||
// Build genesis storage according to the mock runtime. | ||
pub fn new_test_ext() -> sp_io::TestExternalities { | ||
system::GenesisConfig::default().build_storage::<Test>().unwrap().into() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
use crate::{mock::*, AssetInfo, Error}; | ||
use frame_support::{assert_noop, assert_ok}; | ||
|
||
#[test] | ||
fn set_info_and_retrieve_works_ok() { | ||
new_test_ext().execute_with(|| { | ||
const ASSET_ID: u32 = 0; | ||
let info = AssetInfo { | ||
name: Some(b"name".to_vec()), | ||
symbol: Some(b"SYM".to_vec()), | ||
description: Some(b"desc".to_vec()), | ||
decimals: Some(8), | ||
}; | ||
// Dispatch a signed extrinsic. | ||
assert_ok!(AssetsInfoModule::set_info( | ||
Origin::root(), | ||
ASSET_ID, | ||
info.name.clone(), | ||
info.symbol.clone(), | ||
info.description.clone(), | ||
info.decimals.clone(), | ||
)); | ||
// Read pallet storage and assert an expected result. | ||
assert_eq!(AssetsInfoModule::get_info(ASSET_ID), info); | ||
}); | ||
} | ||
|
||
#[test] | ||
fn set_info_optional_and_retrieve_works_ok() { | ||
new_test_ext().execute_with(|| { | ||
const ASSET_ID: u32 = 0; | ||
let info = AssetInfo { | ||
name: None, | ||
symbol: Some(b"SYM".to_vec()), | ||
description: None, | ||
decimals: Some(8), | ||
}; | ||
// Dispatch a signed extrinsic. | ||
assert_ok!(AssetsInfoModule::set_info( | ||
Origin::root(), | ||
ASSET_ID, | ||
None, | ||
// None, | ||
info.symbol.clone(), | ||
None, | ||
info.decimals, | ||
)); | ||
// Read pallet storage and assert an expected result. | ||
assert_eq!(AssetsInfoModule::get_info(ASSET_ID), info); | ||
}); | ||
} | ||
|
||
#[test] | ||
fn correct_error_for_invalid_symbol_value() { | ||
new_test_ext().execute_with(|| { | ||
// Ensure the expected error is thrown when no value is present. | ||
assert_noop!( | ||
AssetsInfoModule::set_info( | ||
Origin::root(), | ||
0, | ||
None, | ||
Some(vec![]), | ||
None, | ||
None, | ||
), | ||
Error::<Test>::TooShortSymbol | ||
); | ||
}); | ||
} |
Oops, something went wrong.