diff --git a/client/tests/integration/transfer_asset.rs b/client/tests/integration/transfer_asset.rs index be37310c5cd..bc9be056ae9 100644 --- a/client/tests/integration/transfer_asset.rs +++ b/client/tests/integration/transfer_asset.rs @@ -1,8 +1,17 @@ +use std::str::FromStr; + use iroha_client::{ client::{self, QueryResult}, crypto::KeyPair, data_model::{isi::Instruction, prelude::*, Registered}, }; +use iroha_data_model::{ + account::{Account, AccountId}, + asset::{Asset, AssetDefinition}, + isi::InstructionBox, + metadata::Metadata, + name::Name, +}; use iroha_primitives::fixed::Fixed; use test_network::*; @@ -55,6 +64,10 @@ fn simulate_insufficient_funds() { 10_800, ) } +#[test] +fn simulate_transfer_store_asset() { + simulate_transfer_store(Transfer::asset_store) +} fn simulate_transfer( starting_amount: T, @@ -120,3 +133,69 @@ fn simulate_transfer( ) .expect("Test case failure."); } + +fn simulate_transfer_store( + transfer_ctr: impl FnOnce(AssetId, AccountId) -> Transfer, +) where + Transfer: Instruction, +{ + let (_rt, _peer, iroha_client) = ::new().with_port(10_805).start_with_runtime(); + wait_for_genesis_committed(&[iroha_client.clone()], 0); + let (alice_id, mouse_id) = generate_two_ids(); + let create_mouse = create_mouse(mouse_id.clone()); + let asset_definition_id: AssetDefinitionId = "camomile#wonderland".parse().expect("Valid"); + let create_asset = + Register::asset_definition(AssetDefinition::store(asset_definition_id.clone())); + let set_key_value = SetKeyValue::asset( + AssetId::new(asset_definition_id.clone(), alice_id.clone()), + Name::from_str("alicek").unwrap(), + true, + ); + + let instructions: [InstructionBox; 3] = [ + // create_alice.into(), We don't need to register Alice, because she is created in genesis + create_mouse.into(), + create_asset.into(), + set_key_value.into(), + ]; + + iroha_client + .submit_all_blocking(instructions) + .expect("Failed to prepare state."); + + let transfer_asset = transfer_ctr( + AssetId::new(asset_definition_id.clone(), alice_id.clone()), + mouse_id.clone(), + ); + let transfer_box: InstructionBox = transfer_asset.into(); + + iroha_client + .submit_till( + transfer_box, + client::asset::by_account_id(mouse_id.clone()), + |result| { + let assets = result.collect::>>().expect("Valid"); + for asset in assets.iter() { + println!("Asset: {}", asset.id().definition_id); + } + assets.iter().any(|asset| { + asset.id().definition_id == asset_definition_id + && asset.id().account_id == mouse_id + }) + }, + ) + .expect("Test case failure."); +} + +fn generate_two_ids() -> (AccountId, AccountId) { + let alice_id: AccountId = "alice@wonderland".parse().expect("Valid"); + let mouse_id: AccountId = "mouse@wonderland".parse().expect("Valid"); + (alice_id, mouse_id) +} + +fn create_mouse(mouse_id: AccountId) -> Register { + let (bob_public_key, _) = KeyPair::generate() + .expect("Failed to generate KeyPair") + .into(); + Register::account(Account::new(mouse_id, [bob_public_key])) +} diff --git a/core/src/smartcontracts/isi/asset.rs b/core/src/smartcontracts/isi/asset.rs index 4aaf3e19168..450ee355578 100644 --- a/core/src/smartcontracts/isi/asset.rs +++ b/core/src/smartcontracts/isi/asset.rs @@ -33,7 +33,7 @@ impl Registrable for NewAssetDefinition { /// - update metadata /// - transfer, etc. pub mod isi { - use iroha_data_model::isi::error::MintabilityError; + use iroha_data_model::{isi::error::MintabilityError, metadata::Metadata}; use super::*; use crate::smartcontracts::account::isi::forbid_minting; @@ -103,6 +103,45 @@ pub mod isi { } } + impl Execute for Transfer { + #[metrics(+"transfer_store")] + fn execute(self, _authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { + let asset_id = self.source_id; + assert_asset_type(&asset_id.definition_id, wsv, AssetValueType::Store)?; + + let (original_owner_metadata, destination_id) = { + let original_store_owner: &Account = wsv.account(&asset_id.account_id)?; + let metadata: Metadata = original_store_owner.metadata().clone(); + let destination_id = + AssetId::new(asset_id.definition_id.clone(), self.destination_id.clone()); + (metadata, destination_id) + }; + + let asset_metadata_limits = wsv.config.asset_metadata_limits; + let destination_store_asset = + wsv.asset_or_insert(destination_id.clone(), Metadata::new())?; + let store: &mut Metadata = destination_store_asset + .try_as_mut() + .map_err(eyre::Error::from) + .map_err(|e| Error::Conversion(e.to_string()))?; + + for pair in original_owner_metadata.iter() { + let key: &Name = pair.0; + let value: &Value = pair.1; + store.insert_with_limits(key.clone(), value.clone(), asset_metadata_limits)?; + } + + { + let original_store_owner = wsv.account_mut(&asset_id.account_id)?; + assert!(original_store_owner.remove_asset(&asset_id).is_some()); + } + + wsv.emit_events([AssetEvent::Deleted(asset_id)]); + + Ok(()) + } + } + macro_rules! impl_mint { ($ty:ty, $metrics:literal) => { impl InnerMint for $ty {} diff --git a/core/src/smartcontracts/isi/mod.rs b/core/src/smartcontracts/isi/mod.rs index a0bf424f1ef..6e12ab0649c 100644 --- a/core/src/smartcontracts/isi/mod.rs +++ b/core/src/smartcontracts/isi/mod.rs @@ -167,6 +167,7 @@ impl Execute for AssetTransferBox { Self::Quantity(isi) => isi.execute(authority, wsv), Self::BigQuantity(isi) => isi.execute(authority, wsv), Self::Fixed(isi) => isi.execute(authority, wsv), + Self::Store(isi) => isi.execute(authority, wsv), } } } diff --git a/data_model/src/isi.rs b/data_model/src/isi.rs index 4bbfa0de0d7..f86f57956a2 100644 --- a/data_model/src/isi.rs +++ b/data_model/src/isi.rs @@ -149,6 +149,7 @@ pub mod model { impl Instruction for Transfer {} impl Instruction for Transfer {} impl Instruction for Transfer {} + impl Instruction for Transfer {} impl Instruction for Grant {} impl Instruction for Grant {} @@ -166,7 +167,7 @@ pub mod model { mod transparent { use super::*; - use crate::{account::NewAccount, domain::NewDomain}; + use crate::{account::NewAccount, domain::NewDomain, metadata::Metadata}; macro_rules! isi { ($($meta:meta)* $item:item) => { @@ -847,6 +848,17 @@ mod transparent { } } + impl Transfer { + /// Constructs a new [`Transfer`] for an [`Asset`] of [`Store`] type. + pub fn asset_store(asset_id: AssetId, to: AccountId) -> Self { + Self { + source_id: asset_id, + object: Metadata::new(), + destination_id: to, + } + } + } + impl_display! { Transfer where @@ -865,6 +877,7 @@ mod transparent { impl_into_box! { Transfer | Transfer | + Transfer | Transfer => AssetTransferBox ==> TransferBox::Asset } @@ -873,6 +886,7 @@ mod transparent { Transfer | Transfer | Transfer | + Transfer | Transfer => TransferBox ==> InstructionBox::Transfer } @@ -1194,6 +1208,8 @@ isi_box! { BigQuantity(Transfer), /// Transfer [`Asset`] of [`Fixed`] type. Fixed(Transfer), + /// Transfer [`Asset`] of [`Store`] type. + Store(Transfer), } } diff --git a/data_model/src/lib.rs b/data_model/src/lib.rs index b6b232b9184..2f3f93627a0 100644 --- a/data_model/src/lib.rs +++ b/data_model/src/lib.rs @@ -131,6 +131,8 @@ mod seal { Transfer, Transfer, Transfer, + Transfer, + Grant, Grant, diff --git a/data_model/src/visit.rs b/data_model/src/visit.rs index f20931a59ea..cef6593d64d 100644 --- a/data_model/src/visit.rs +++ b/data_model/src/visit.rs @@ -125,6 +125,7 @@ pub trait Visit { visit_transfer_asset_quantity(&Transfer), visit_transfer_asset_big_quantity(&Transfer), visit_transfer_asset_fixed(&Transfer), + visit_transfer_asset_store(&Transfer), visit_transfer_domain(&Transfer), // Visit SetKeyValueBox @@ -349,6 +350,7 @@ pub fn visit_transfer( visitor.visit_transfer_asset_big_quantity(authority, obj) } AssetTransferBox::Fixed(obj) => visitor.visit_transfer_asset_fixed(authority, obj), + AssetTransferBox::Store(obj) => visitor.visit_transfer_asset_store(authority, obj), }, } } @@ -425,6 +427,7 @@ leaf_visitors! { visit_transfer_asset_quantity(&Transfer), visit_transfer_asset_big_quantity(&Transfer), visit_transfer_asset_fixed(&Transfer), + visit_transfer_asset_store(&Transfer), visit_set_asset_key_value(&SetKeyValue), visit_remove_asset_key_value(&RemoveKeyValue), visit_register_asset_definition(&Register), diff --git a/smart_contract/executor/derive/src/default.rs b/smart_contract/executor/derive/src/default.rs index fa12601e750..03576df9ab0 100644 --- a/smart_contract/executor/derive/src/default.rs +++ b/smart_contract/executor/derive/src/default.rs @@ -141,6 +141,7 @@ pub fn impl_derive_visit(emitter: &mut Emitter, input: &syn2::DeriveInput) -> To "fn visit_transfer_asset_quantity(operation: &Transfer)", "fn visit_transfer_asset_big_quantity(operation: &Transfer)", "fn visit_transfer_asset_fixed(operation: &Transfer)", + "fn visit_transfer_asset_store(operation: &Transfer)", "fn visit_set_asset_key_value(operation: &SetKeyValue)", "fn visit_remove_asset_key_value(operation: &RemoveKeyValue)", "fn visit_register_asset_definition(operation: &Register)", diff --git a/smart_contract/executor/src/default.rs b/smart_contract/executor/src/default.rs index 0c7eafa30f2..53192eb6021 100644 --- a/smart_contract/executor/src/default.rs +++ b/smart_contract/executor/src/default.rs @@ -15,7 +15,7 @@ pub use asset::{ visit_mint_asset_big_quantity, visit_mint_asset_fixed, visit_mint_asset_quantity, visit_register_asset, visit_remove_asset_key_value, visit_set_asset_key_value, visit_transfer_asset_big_quantity, visit_transfer_asset_fixed, visit_transfer_asset_quantity, - visit_unregister_asset, + visit_transfer_asset_store, visit_unregister_asset, }; pub use asset_definition::{ visit_register_asset_definition, visit_remove_asset_definition_key_value, @@ -581,7 +581,7 @@ pub mod asset_definition { } pub mod asset { - use iroha_smart_contract::data_model::isi::Instruction; + use iroha_smart_contract::data_model::{isi::Instruction, metadata::Metadata}; use iroha_smart_contract_utils::Encode; use permission::{asset::is_asset_owner, asset_definition::is_asset_definition_owner}; @@ -829,6 +829,14 @@ pub mod asset { validate_transfer_asset(executor, authority, isi); } + pub fn visit_transfer_asset_store( + executor: &mut V, + authority: &AccountId, + isi: &Transfer, + ) { + validate_transfer_asset(executor, authority, isi); + } + pub fn visit_set_asset_key_value( executor: &mut V, authority: &AccountId,