diff --git a/client/tests/integration/transfer_asset.rs b/client/tests/integration/transfer_asset.rs index be37310c5cd..63953cf6b4a 100644 --- a/client/tests/integration/transfer_asset.rs +++ b/client/tests/integration/transfer_asset.rs @@ -1,8 +1,16 @@ +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, + name::Name, +}; use iroha_primitives::fixed::Fixed; use test_network::*; @@ -33,8 +41,8 @@ fn simulate_transfer_big_quantity() { #[test] fn simulate_transfer_fixed() { simulate_transfer( - Fixed::try_from(200_f64).expect("Valid"), - &Fixed::try_from(20_f64).expect("Valid"), + Fixed::try_from(200_f64).unwrap(), + &Fixed::try_from(20_f64).unwrap(), AssetDefinition::fixed, Mint::asset_fixed, Transfer::asset_fixed, @@ -47,8 +55,8 @@ fn simulate_transfer_fixed() { #[should_panic(expected = "insufficient funds")] fn simulate_insufficient_funds() { simulate_transfer( - Fixed::try_from(20_f64).expect("Valid"), - &Fixed::try_from(200_f64).expect("Valid"), + Fixed::try_from(20_f64).unwrap(), + &Fixed::try_from(200_f64).unwrap(), AssetDefinition::fixed, Mint::asset_fixed, Transfer::asset_fixed, @@ -56,6 +64,53 @@ fn simulate_insufficient_funds() { ) } +#[test] +fn simulate_transfer_store_asset() { + 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().unwrap(); + 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::asset_store( + 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::>>().unwrap(); + assets.iter().any(|asset| { + asset.id().definition_id == asset_definition_id + && asset.id().account_id == mouse_id + }) + }, + ) + .expect("Test case failure."); +} + fn simulate_transfer( starting_amount: T, amount_to_transfer: &T, @@ -74,13 +129,9 @@ fn simulate_transfer( .start_with_runtime(); wait_for_genesis_committed(&[iroha_client.clone()], 0); - let alice_id: AccountId = "alice@wonderland".parse().expect("Valid"); - let mouse_id: AccountId = "mouse@wonderland".parse().expect("Valid"); - let (bob_public_key, _) = KeyPair::generate() - .expect("Failed to generate KeyPair") - .into(); - let create_mouse = Register::account(Account::new(mouse_id.clone(), [bob_public_key])); - let asset_definition_id: AssetDefinitionId = "camomile#wonderland".parse().expect("Valid"); + let (alice_id, mouse_id) = generate_two_ids(); + let create_mouse = create_mouse(mouse_id.clone()); + let asset_definition_id: AssetDefinitionId = "camomile#wonderland".parse().unwrap(); let create_asset = Register::asset_definition(asset_definition_ctr(asset_definition_id.clone())); let mint_asset = mint_ctr( @@ -109,7 +160,7 @@ fn simulate_transfer( transfer_asset, client::asset::by_account_id(mouse_id.clone()), |result| { - let assets = result.collect::>>().expect("Valid"); + let assets = result.collect::>>().unwrap(); assets.iter().any(|asset| { asset.id().definition_id == asset_definition_id @@ -120,3 +171,16 @@ fn simulate_transfer( ) .expect("Test case failure."); } + +fn generate_two_ids() -> (AccountId, AccountId) { + let alice_id: AccountId = "alice@wonderland".parse().unwrap(); + let mouse_id: AccountId = "mouse@wonderland".parse().unwrap(); + (alice_id, mouse_id) +} + +fn create_mouse(mouse_id: AccountId) -> Register { + let (mouse_public_key, _) = KeyPair::generate() + .expect("Failed to generate KeyPair") + .into(); + Register::account(Account::new(mouse_id, [mouse_public_key])) +} diff --git a/configs/peer/executor.wasm b/configs/peer/executor.wasm index e326dcecc29..b8b37182ae4 100644 Binary files a/configs/peer/executor.wasm and b/configs/peer/executor.wasm differ diff --git a/core/src/smartcontracts/isi/asset.rs b/core/src/smartcontracts/isi/asset.rs index 4aaf3e19168..27c65f96245 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::{asset::AssetValue, isi::error::MintabilityError, metadata::Metadata}; use super::*; use crate::smartcontracts::account::isi::forbid_minting; @@ -103,6 +103,37 @@ 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 account_id = asset_id.account_id.clone(); + + let asset = wsv.account_mut(&account_id).and_then(|account| { + account + .remove_asset(&asset_id) + .ok_or_else(|| FindError::Asset(asset_id.clone())) + })?; + + let destination_store = { + let destination_id = + AssetId::new(asset_id.definition_id.clone(), self.destination_id.clone()); + let destination_store_asset = + wsv.asset_or_insert(destination_id.clone(), asset.value)?; + + destination_store_asset.clone() + }; + + wsv.emit_events([ + AssetEvent::Deleted(asset_id), + AssetEvent::Created(destination_store), + ]); + + 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 22460378365..ff966821b45 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 0ba15aefaca..3c7c6fcee4f 100644 --- a/data_model/src/lib.rs +++ b/data_model/src/lib.rs @@ -131,6 +131,7 @@ mod seal { Transfer, Transfer, Transfer, + Transfer, Grant, Grant, diff --git a/data_model/src/visit.rs b/data_model/src/visit.rs index 74183d4d04a..31a1478bd1f 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/docs/source/references/schema.json b/docs/source/references/schema.json index 60225b3c14a..7de02949e9e 100644 --- a/docs/source/references/schema.json +++ b/docs/source/references/schema.json @@ -578,6 +578,11 @@ "tag": "Fixed", "discriminant": 2, "type": "Transfer" + }, + { + "tag": "Store", + "discriminant": 3, + "type": "Transfer" } ] }, @@ -4253,6 +4258,22 @@ } ] }, + "Transfer": { + "Struct": [ + { + "name": "source_id", + "type": "AssetId" + }, + { + "name": "object", + "type": "Metadata" + }, + { + "name": "destination_id", + "type": "AccountId" + } + ] + }, "Transfer": { "Struct": [ { 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 cb6d1617354..85639593b4c 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, @@ -919,7 +919,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}; @@ -1173,6 +1173,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,