Skip to content

Commit

Permalink
Add numeric asset example
Browse files Browse the repository at this point in the history
Signed-off-by: Nurzhan Sakén <nurzhan.sakenov@gmail.com>
  • Loading branch information
nxsaken committed Jun 25, 2024
1 parent 17b310a commit 2b03df1
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 51 deletions.
10 changes: 5 additions & 5 deletions Rust/examples/account_register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,23 @@ fn main() -> iroha_examples::Result<()> {
// By default, only the owner of the domain can register accounts in it.
let as_alice_in_wonderland = AliceInWonderland::client();
// The same signatory can have an account in different domains.
register(&as_alice_in_wonderland, AliceInChess::account_id())?;
register(&as_alice_in_wonderland, AliceInChess::id())?;

// The domain owner can also grant a permission to register accounts in the domain.
let can_register_accounts_in_chess = Permission::new(
"CanRegisterAccountInDomain".parse::<PermissionId>()?,
serde_json::json!({ "domain": Chess::domain_id() }),
serde_json::json!({ "domain": Chess::id() }),
);
// Grant the permission to Bob from Wonderland.
let bob_in_wonderland = BobInWonderland::account_id();
let bob_in_wonderland = BobInWonderland::id();
as_alice_in_wonderland.submit_blocking(Grant::permission(
can_register_accounts_in_chess.clone(),
bob_in_wonderland.clone(),
))?;
// Bob in Wonderland can now register accounts in Chess.
let as_bob_in_wonderland = BobInWonderland::client();
register(&as_bob_in_wonderland, BobInChess::account_id())?;
register(&as_bob_in_wonderland, MagnusInChess::account_id())?;
register(&as_bob_in_wonderland, BobInChess::id())?;
register(&as_bob_in_wonderland, MagnusInChess::id())?;
// Revoke the permission from Bob in Wonderland.
as_alice_in_wonderland.submit_blocking(Revoke::permission(
can_register_accounts_in_chess,
Expand Down
6 changes: 3 additions & 3 deletions Rust/examples/account_unregister.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ use iroha_examples::{AliceInChess, AliceInWonderland, BobInChess, MagnusInChess}
fn main() -> iroha_examples::Result<()> {
// An account's owner can unregister that account.
let as_bob_in_chess = BobInChess::client();
unregister(&as_bob_in_chess, BobInChess::account_id())?;
unregister(&as_bob_in_chess, BobInChess::id())?;
// A domain owner can unregister any account in that domain.
let as_alice_in_wonderland = AliceInWonderland::client();
unregister(&as_alice_in_wonderland, AliceInChess::account_id())?;
unregister(&as_alice_in_wonderland, MagnusInChess::account_id())?;
unregister(&as_alice_in_wonderland, AliceInChess::id())?;
unregister(&as_alice_in_wonderland, MagnusInChess::id())?;
Ok(())
}

Expand Down
57 changes: 38 additions & 19 deletions Rust/examples/asset_definition_register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,53 +5,72 @@
use iroha::client::{asset, Client};
use iroha::data_model::asset::{AssetDefinition, AssetValueType};
use iroha::data_model::ipfs::IpfsPath;
use iroha::data_model::prelude::{Metadata, NewAssetDefinition, NumericSpec, Register};
use iroha::data_model::isi::Grant;
use iroha::data_model::permission::{Permission, PermissionId};
use iroha::data_model::prelude::{Metadata, NewAssetDefinition, NumericSpec, Register, Revoke};
use iroha_examples::{
AliceInWonderland, BobInChess, ChessBook, ChessPawns, WonderlandMoney, WonderlandRoses,
AliceInWonderland, BobInChess, BobInWonderland, Chess, ChessBook, ChessPawns, ExampleDomain,
WonderlandMoney, WonderlandRoses,
};

fn main() -> iroha_examples::Result<()> {
let as_alice_in_wonderland = AliceInWonderland::client();
// Roses in Wonderland are defined in the default genesis block.
// `rose#wonderland` are defined in the default genesis block.
println!(
"Wonderland Roses:\n{:#?}",
as_alice_in_wonderland.request(asset::definition_by_id(
WonderlandRoses::asset_definition_id(),
))?
as_alice_in_wonderland.request(asset::definition_by_id(WonderlandRoses::id(),))?
);
// Assets can be defined as either numeric or store.
// Numeric assets can be minted (increased) or burned (decreased).
// Wonderland Money is a numeric asset with fractional values up to 2 decimal places.
// `money#wonderland` is a numeric asset with fractional values up to 2 decimal places.
register(
&as_alice_in_wonderland,
AssetDefinition::new(
WonderlandMoney::asset_definition_id(),
WonderlandMoney::id(),
AssetValueType::Numeric(NumericSpec::fractional(2)),
),
)?;
// Chess Pawns is a numeric asset with integer values that can only be minted once.
// It means that a certain amount of an asset can be given
// to an account one time, and from that point it can only be burnt.
// Mintability is covered in detail in the TODO(`asset_numeric`) example.
// Since `bob@chess` is not the owner of `chess`, `alice@wonderland`
// has to grant `bob@chess` permission to define assets in `chess`.
let can_define_assets_in_chess = Permission::new(
"CanRegisterAssetDefinitionInDomain".parse::<PermissionId>()?,
serde_json::json!({ "domain": Chess::id() }),
);
// Grant the permission to `bob@chess`.
as_alice_in_wonderland.submit_blocking(Grant::permission(
can_define_assets_in_chess.clone(),
BobInChess::id(),
))?;
// `pawn#chess` is a numeric asset with integer values that can only be minted once.
// It means that a certain amount of an asset can be given to an account one time,
// and from that point it can only be burnt.
//
// Mintability is covered in detail in the `asset_numeric` example.
//
// Bob in Chess will be the owner of the definition of Chess Pawns,
// meaning he will have the default right to mint/burn Chess Pawns.
// Since Alice in Wonderland owns Chess, she will also have that right.
// `bob@chess` will be the owner of the definition of `pawn#chess`,
// meaning he will have the default right to mint/burn `pawn#chess`.
// Since `alice@wonderland` owns `chess`, she will also have that right.
register(
&BobInChess::client(),
AssetDefinition::new(
ChessPawns::asset_definition_id(),
ChessPawns::id(),
AssetValueType::Numeric(NumericSpec::integer()),
),
)
.mintable_once(),
)?;
// Chess Book is a store asset. Store assets are not minted or burned.
// Revoke the permission.
as_alice_in_wonderland.submit_blocking(Revoke::permission(
can_define_assets_in_chess.clone(),
BobInChess::id(),
))?;
// `book#chess` is a store asset. Store assets are not minted or burned.
// Instead, key-value pairs are set or removed for them.
//
// Here we also provide an optional IPFS path to the asset logo,
// and some metadata. Metadata is covered in detail in TODO(`metadata`)
register(
&as_alice_in_wonderland,
AssetDefinition::store(ChessBook::asset_definition_id())
AssetDefinition::store(ChessBook::id())
.with_logo("QmQqzMTavQgT4f4T5v6PWBp7XNKtoPmC9jvn12WPT3gkSE".parse::<IpfsPath>()?)
.with_metadata(Metadata::default()),
)?;
Expand Down
116 changes: 116 additions & 0 deletions Rust/examples/asset_numeric.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//! Shows how to mint, burn and transfer numeric assets.
//!
//! Depends on `asset_definition_register`

use iroha::client::asset;
use iroha::data_model::prelude::{
numeric, Asset, AssetValue, Burn, Mint, Numeric, Register, Transfer,
};

use iroha_examples::{
AliceInWonderland, BobInWonderland, MagnusInChess, MoneyOfAliceInWonderland,
MoneyOfBobInWonderland, WonderlandMoneyOfMagnusInChess,
};

fn main() -> iroha_examples::Result<()> {
let as_alice_in_wland = AliceInWonderland::client();
let as_bob_in_wland = BobInWonderland::client();
// When specific `money#wonderland` belongs to `alice@wonderland`,
// we call that her asset, `money##alice@wonderland`.
// Thus, an asset is an instance of an asset definition owned by an account.
let money_of_alice_in_wland = MoneyOfAliceInWonderland::id();
let money_of_bob_in_wland = MoneyOfBobInWonderland::id();
// `money#wonderland` can be held by accounts outside `wonderland`.
let wland_money_of_magnus_in_chess = WonderlandMoneyOfMagnusInChess::id();
// TODO: this section is not true, but I'd like it to be;
// see https://github.com/hyperledger/iroha/issues/4087#issuecomment-2188067574
as_alice_in_wland.submit_all_blocking([
// For `alice@wonderland` to be able to hold `money#wonderland`, we need
// to register `money##alice@wonderland`. This is sort of like
// giving her a wallet before she can carry `money#wonderland`.
Register::asset(Asset::new(money_of_alice_in_wland.clone(), 0_u32)),
// Register `money#wonderland` for `bob@wonderland` for `alice@wonderland`s later transfer.
// Since `alice@wonderland` owns the definition of `money#wonderland`, she has to do it.
Register::asset(Asset::new(money_of_bob_in_wland.clone(), 0_u32)),
// Register `money#wonderland` for `magnus@chess` for `bob@wonderland`s later transfer.
Register::asset(Asset::new(wland_money_of_magnus_in_chess.clone(), 0_u32)),
])?;
// FIXME: currently, minting will register the asset if it does not exist.
// Now `alice@wonderland` can mint `money#wonderland` for herself, since
// she was the one who defined it. Someone holding a relevant permission
// can also mint an asset.
//
// Minting increases the asset's amount.
as_alice_in_wland.submit_all_blocking([
Mint::asset_numeric(numeric!(1.25), money_of_alice_in_wland.clone()),
// `money#wonderland` is defined to be mintable repeatedly,
// therefore we can repeat the Mint instruction as much as we want.
Mint::asset_numeric(numeric!(1.25), money_of_alice_in_wland.clone()),
Mint::asset_numeric(numeric!(1.25), money_of_alice_in_wland.clone()),
Mint::asset_numeric(numeric!(1.25), money_of_alice_in_wland.clone()),
])?;
// Observe that `alice@wonderland` has 5 of `money#wonderland`.
as_alice_in_wland
.request(asset::by_id(money_of_alice_in_wland.clone()))?
.assert_eq(numeric!(5));
// Now that `alice@wonderland` has some of `money#wonderland`,
// she can burn it. An asset can be burned by its owner,
// the owner of its definition, and a holder of a relevant permission.
//
// Burning decreases the asset's amount.
// You cannot burn more of the asset that is actually owned.
as_alice_in_wland.submit_all_blocking([
Burn::asset_numeric(numeric!(0.01), money_of_alice_in_wland.clone()),
Burn::asset_numeric(numeric!(1.01), money_of_alice_in_wland.clone()),
Burn::asset_numeric(numeric!(2.01), money_of_alice_in_wland.clone()),
])?;
// Observe that `alice@wonderland` has 1.97 of `money#wonderland` left.
as_alice_in_wland
.request(asset::by_id(money_of_alice_in_wland.clone()))?
.assert_eq(numeric!(1.98));
as_alice_in_wland.submit_blocking(
// `alice@wonderland` can transfer some of her `money#wonderland` to another account.
// Like with minting, an asset can be transferred by its owner, the owner of
// its definition, or someone with a relevant permission.
Transfer::asset_numeric(
money_of_alice_in_wland.clone(),
numeric!(1.4),
BobInWonderland::id(),
),
)?;
// `alice@wonderland` observes that she has 0.57 of `money#wonderland` left.
as_alice_in_wland
.request(asset::by_id(money_of_alice_in_wland))?
.assert_eq(numeric!(0.57));
// `bob@wonderland` observes that he has 1.4 of `money#wonderland` now.
// He can do that because he owns `money##bob@wonderland`.
as_bob_in_wland
.request(asset::by_id(money_of_bob_in_wland.clone()))?
.assert_eq(numeric!(1.4));
as_bob_in_wland.submit_blocking(
// `bob@wonderland` can transfer some of his `money#wonderland` to `magnus@chess`.
// Note how `money#wonderland` can be held by accounts in different domains.
Transfer::asset_numeric(money_of_bob_in_wland, numeric!(0.7), MagnusInChess::id()),
)?;
// `alice@wonderland` observes that `magnus@chess` has 0.7 of `money#wonderland`.
// She can do that because she owns the definition of `money#wonderland`.
as_alice_in_wland
.request(asset::by_id(wland_money_of_magnus_in_chess))?
.assert_eq(numeric!(0.7));
Ok(())
}

trait NumericAssetExt {
fn assert_eq(&self, expected: Numeric);
}

impl NumericAssetExt for Asset {
fn assert_eq(&self, expected: Numeric) {
let AssetValue::Numeric(actual) = self.value else {
// FIXME: this API inconvenience should be resolved
// when numeric assets are separated from store assets.
panic!("should be a numeric asset");
};
assert_eq!(actual, expected);
}
}
2 changes: 1 addition & 1 deletion Rust/examples/domain_register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use iroha_examples::{AliceInWonderland, Chess, ExampleDomain};

fn main() -> iroha_examples::Result<()> {
let as_alice_in_wonderland = AliceInWonderland::client();
let chess = Chess::domain_id();
let chess = Chess::id();
let register_chess = Register::domain(Domain::new(chess.clone()));
as_alice_in_wonderland.submit_blocking(register_chess)?;
let chess = as_alice_in_wonderland.request(domain::by_id(chess))?;
Expand Down
6 changes: 3 additions & 3 deletions Rust/examples/domain_transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ use iroha::data_model::prelude::{AccountId, DomainId, Transfer};
use iroha_examples::{AliceInWonderland, BobInWonderland, ExampleDomain, Wonderland};

fn main() -> iroha_examples::Result<()> {
let chess = Wonderland::domain_id();
let alice_in_wonderland = AliceInWonderland::account_id();
let bob_in_wonderland = BobInWonderland::account_id();
let chess = Wonderland::id();
let alice_in_wonderland = AliceInWonderland::id();
let bob_in_wonderland = BobInWonderland::id();
// Transfer Chess from Alice in Wonderland to Bob in Wonderland.
transfer(
&AliceInWonderland::client(),
Expand Down
2 changes: 1 addition & 1 deletion Rust/examples/domain_unregister.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use iroha_examples::{AliceInWonderland, Chess, ExampleDomain};

fn main() -> iroha_examples::Result<()> {
let as_alice_in_wonderland = AliceInWonderland::client();
let chess = Chess::domain_id();
let chess = Chess::id();
let unregister_chess = Unregister::domain(chess.clone());
as_alice_in_wonderland.submit_blocking(unregister_chess)?;
as_alice_in_wonderland
Expand Down
42 changes: 23 additions & 19 deletions Rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub trait ExampleDomain {
///
/// A [`Name`] cannot be empty, cannot contain whitespace or characters `@` and `#`,
/// which are reserved for accounts and assets.
fn domain_id() -> DomainId {
fn id() -> DomainId {
// You can also parse into a `Name`, then use `DomainId::new`.
Self::NAME.parse::<DomainId>().unwrap()
}
Expand Down Expand Up @@ -69,10 +69,10 @@ where
/// composed of a [`PublicKey`] and a [`DomainId`].
///
/// An [`AccountId`] can be parsed from a string of the form `public_key@domain`.
pub fn account_id() -> AccountId {
pub fn id() -> AccountId {
let signatory = Signatory::public_key();
let domain = Domain::domain_id();
// return format!("{signatory}@{domain}").parse::<AccountId>().unwrap();
let domain = Domain::id();
// "signatory@domain".parse::<AccountId>().unwrap();
AccountId::new(domain, signatory)
}

Expand All @@ -93,10 +93,10 @@ where
))
.expect("config is loaded and valid");
let client = Client::new(config);
let expected_account = ExampleAccount::<Signatory, Domain>::account_id();
let expected_account = ExampleAccount::<Signatory, Domain>::id();
assert_eq!(
client.account,
ExampleAccount::<Signatory, Domain>::account_id(),
ExampleAccount::<Signatory, Domain>::id(),
"Client was requested for `{}`, but the actual authority does not match.\n\
Check the corresponding client configuration file.\n\
Expected: {}\n\
Expand All @@ -107,7 +107,7 @@ where
);
println!(
"Client for `{}` in `{}` created.\n\
Account: {}",
Authority: {}",
Signatory::ALIAS,
Domain::NAME,
client.account,
Expand All @@ -133,7 +133,7 @@ pub trait ExampleAssetName {
///
/// [asset definition]: ExampleAssetDefinition
/// [asset]: ExampleAsset
fn asset_name() -> Name {
fn name() -> Name {
Self::NAME.parse::<Name>().unwrap()
}
}
Expand All @@ -154,10 +154,10 @@ where
/// composed of a [`Name`] and a [`DomainId`].
///
/// An [`AssetDefinitionId`] can be parsed from a string of the form `asset_name#domain`.
pub fn asset_definition_id() -> AssetDefinitionId {
let asset_name = AssetName::asset_name();
let domain = Domain::domain_id();
// return format!("{asset_name}#{domain}").parse::<AssetDefinitionId>().unwrap();
pub fn id() -> AssetDefinitionId {
let asset_name = AssetName::name();
let domain = Domain::id();
// "asset_name#asset_domain".parse::<AssetDefinitionId>().unwrap();
AssetDefinitionId::new(domain, asset_name)
}
}
Expand Down Expand Up @@ -188,11 +188,11 @@ where
/// when the asset and its owner belong to different domains
/// - `asset_name##asset_owner@common_domain`:
/// when the asset and its owner share the domain
pub fn asset_id() -> AssetId {
let asset_definition =
ExampleAssetDefinition::<AssetName, AssetDomain>::asset_definition_id();
let owner = ExampleAccount::<AssetOwner, OwnerDomain>::account_id();
// return format!("{asset_definition}#{owner}").parse::<AssetId>().unwrap();
pub fn id() -> AssetId {
let asset_definition = ExampleAssetDefinition::<AssetName, AssetDomain>::id();
let owner = ExampleAccount::<AssetOwner, OwnerDomain>::id();
// "asset_name#asset_domain#asset_owner@owner_domain".parse::<AssetId>().unwrap();
// "asset_name##asset_owner@common_domain".parse::<AssetId>().unwrap();
AssetId::new(asset_definition, owner)
}
}
Expand Down Expand Up @@ -311,8 +311,12 @@ pub type ChessPawns = ExampleAssetDefinition<Pawns, Chess>;
pub type ChessBook = ExampleAssetDefinition<Book, Chess>;

/// `roses##alice@wonderland` is defined in the default genesis block.
pub type WonderlandRosesOfAliceInWonderland = ExampleAsset<WonderlandRoses, AliceInWonderland>;
pub type RosesOfAliceInWonderland = ExampleAsset<WonderlandRoses, AliceInWonderland>;
/// `money##alice@wonderland` is defined in the TODO(`asset_register`) example.
pub type WonderlandMoneyOfAliceInWonderland = ExampleAsset<WonderlandRoses, AliceInWonderland>;
pub type MoneyOfAliceInWonderland = ExampleAsset<WonderlandMoney, AliceInWonderland>;
/// `book#chess#alice@wonderland` is defined in the TODO(`asset_register`) example.
pub type ChessBookOfAliceInWonderland = ExampleAsset<ChessBook, AliceInWonderland>;
/// `money##bob@wonderland` is defined in the TODO(`asset_register`) example.
pub type MoneyOfBobInWonderland = ExampleAsset<WonderlandMoney, BobInWonderland>;
/// `money#wonderland#magnus@chess` is defined in the TODO(`asset_register`) example.
pub type WonderlandMoneyOfMagnusInChess = ExampleAsset<WonderlandMoney, MagnusInChess>;

0 comments on commit 2b03df1

Please sign in to comment.