0

I have a struct declaration in Substrate Pallet as follows

Method implementations are inside Uniques pallet

The flow

  • an NFT gets minted
  • Sets Attributes for the minted NFT
  • attribute value passed as a JSON
  • Inside the split method, requires to get the attribute for the NFT
  • modify current_fraction to another value
  • Save the attribute

Uniques pallet's set_attribute function

pub fn set_attribute(
            origin: OriginFor<T>,
            collection: T::CollectionId,
            maybe_item: Option<T::ItemId>,
            key: BoundedVec<u8, T::KeyLimit>,
            value: BoundedVec<u8, T::ValueLimit>,
        )

So if I pass a JSON like { current_fraction: 1 } from polkadot App, the value of value: BoundedVec<u8, T::ValueLimit>, is [123, 34, 99, 117, 114, 114, 101, 110, 116, 95, 102, 114, 97, 99, 116, 105, 111, 110, 34, 58, 49, 125] ,

So this is the value I need to retrieve , reconstruct the object, modify its value and saves it

#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)]
pub struct RealEstateValues {
    pub current_fraction: u32,
}

I have a function to split an Asset written in the same substrate pallet as follows

#[pallet::call_index(26)]
        #[pallet::weight(T::WeightInfo::split())]
        pub fn split(
            sender_origin: OriginFor<T>,
            collection_id: T::CollectionId,
            item_id: T::ItemId,
            key: BoundedVec<u8, T::KeyLimit>,
            num_of_splits: u32,
        ) -> DispatchResult {

            // TODO: Move this to config or storage
            let property_key = "realEstateData";
            let maximum_splits: u32 = 52u32;

            // ensure!(num_of_splits.le(&maximum_splits), Error::<T, I>::ExceedsMaxSplits);

            let property_key: BoundedVec<u8, ConstU32<32>>  =
                BoundedVec::try_from(property_key.as_bytes().to_vec()).map_err(|_| Error::<T, I>::ErrorConvertingKey)?;

            ensure!(key == property_key, Error::<T, I>::InvalidKey);

            let origin = ensure_signed(sender_origin.clone())?;

            let item_metadata = ItemMetadataOf::<T, I>::get(collection_id, item_id).ok_or(Error::<T, I>::MetadataMissingForItem)?;
            let item_attribute = Attribute::<T, I>::get((collection_id, Some(item_id), &key)).ok_or(Error::<T, I>::AttributesMissingForItem)?;



            let real_estate_value = item_attribute.0.clone();

            let item_attribute_vec = real_estate_value.to_vec();
            let real_estate_value_json: RealEstateValues = Decode::decode(&mut &item_attribute_vec[..]).map_err(|_| Error::<T, I>::InvalidPropertyAttributes)?;

            //  ***********************************************************************************

            // ***********************************************************************************

            ensure!(num_of_splits.saturating_mul(real_estate_value_json.current_fraction).le(&maximum_splits), Error::<T, I>::ExceedsMaxSplits);

            let new_real_estate_values = RealEstateValues {
                current_fraction: num_of_splits,

            };

            let new_real_estate_values_bounded_vec: BoundedVec<u8, T::ValueLimit> =  Encode::encode(&new_real_estate_values).try_into().unwrap();//new_real_estate_values.encode().try_into().unwrap();
            //   let p = serde_json::to_string(&new_real_estate_values).unwrap();
            // // let new_real_estate_values_bounded_vec: BoundedVec<u8, T::ValueLimit> = p.as_bytes();
            let  mut current_item_id = CurrentItemItByCollection::<T, I>::get(collection_id).ok_or(Error::<T, I>::UnknownItem)?;

            let parent_item_id = current_item_id;

            let mut new_item_ids = Vec::with_capacity(num_of_splits as usize);

            for _ in 0..num_of_splits  {

                let  next_nft_id = <T as Config<I>>::ItemId::from(current_item_id.into() + 1);

                new_item_ids.push(next_nft_id);

                Self::do_mint(collection_id, next_nft_id, origin.clone(), |collection_details| {
                    if collection_details.issuer != origin {
                        return Err(Error::<T, I>::NoPermission.into());
                    }
                    Ok(())
                })?;

                Self::deposit_event(Event::NewItemMinted { collection_id, item_id: next_nft_id, owner:origin.clone() });

                Self::set_metadata(
                    sender_origin.clone(),
                    collection_id,
                    next_nft_id,
                    item_metadata.data.clone(),
                    false,
                )?;


                Self::set_attribute(
                    sender_origin.clone(),
                    collection_id,
                    Some(next_nft_id),
                    key.clone(),
                    new_real_estate_values_bounded_vec.clone(),
                )?;

                current_item_id = next_nft_id;
            }
            CurrentItemItByCollection::<T, I>::set(collection_id, Some(current_item_id));

            Self::deposit_event(Event::ItemSplit {
                parent_collection_id: collection_id,
                parent_item_id: item_id,
                num_of_splits,
                new_item_ids,
                owner: origin.clone(),
            });

            // Burn parent NFT on success

            Self::do_burn(collection_id, parent_item_id, |collection_details, details| {
                let is_permitted = collection_details.admin == origin || details.owner == origin;
                ensure!(is_permitted, Error::<T, I>::NoPermission);
                Ok(())
            })?;

            Self::deposit_event(Event::Burned { collection: collection_id, item: parent_item_id, owner: origin });

            Ok(())

        }

This is the corresponding test


#[test]
fn split_should_work() {
    new_test_ext().execute_with(|| {
        Balances::make_free_balance_be(&1, 100_000);
        let key_str = "realEstateData";
        let key_bounded_vec: BoundedVec<u8, ConstU32<50>> =key_str.as_bytes().to_vec().try_into().unwrap();

        let mut item_id = 1u32;
        let collection_id = 1u32;

        println!("real_estate_values: {:?}", real_estate_values);

        let json_data = serde_json::json!({
            "current_fraction": 1,
        });

        // Serialize the JSON data to bytes
        let serialized_json = serde_json::to_vec(&json_data).unwrap();


        
        let real_estate_values_bounded_vec: BoundedVec<u8, ConstU32<50>> = serialized_json.try_into().unwrap();

        // let real_estate_values_bounded_vec: BoundedVec<u8, ConstU32<50>> = serde_json::to_string(&real_estate_values).unwrap().as_bytes().to_vec().try_into().unwrap();
        // let real_estate_values_bounded_vec: BoundedVec<u8, ConstU32<50>> = Encode::encode(&real_estate_values).try_into().unwrap();
        println!("real_estate_values_bounded_vec: {:?}", real_estate_values_bounded_vec);
        let real_estate_value_json: RealEstateValues = Decode::decode(&mut &real_estate_values_bounded_vec[..]).unwrap();
        println!("real_estate_value_json: {:?}", real_estate_value_json);

        assert_ok!(Uniques::create(RuntimeOrigin::signed(1), collection_id, 1));

        assert_ok!(Uniques::mint(RuntimeOrigin::signed(1), collection_id, item_id, 1));

        // Sets Metadata
        assert_ok!(Uniques::set_metadata(
            RuntimeOrigin::signed(1),
            collection_id,
            item_id,
            bvec![0, 0],
            false
        ));

        // Sets Attributes
        assert_ok!(Uniques::set_attribute(
            RuntimeOrigin::signed(1),
            collection_id,
            Some(item_id),
            key_bounded_vec.clone(),
            real_estate_values_bounded_vec
        ));

        assert_ok!(Uniques::set_price(RuntimeOrigin::signed(1), collection_id, item_id, Some(400), None));
        assert_ok!(Uniques::set_metadata(RuntimeOrigin::signed(1), collection_id, item_id, bvec![42, 42], false));


        assert_ok!(Uniques::split(RuntimeOrigin::signed(1), collection_id, item_id, key_bounded_vec.clone(), 4));

        item_id += 1;

        assert_ok!(Uniques::set_metadata(
            RuntimeOrigin::signed(1),
            collection_id,
            item_id,
            bvec![0, 0],
            false
        ));

        assert_noop!(
            Uniques::split(RuntimeOrigin::signed(1), collection_id, item_id, key_bounded_vec, 14),
            Error::<Test>::ExceedsMaxSplits
        );
    });
}

So when I do

let real_estate_value_json: RealEstateValues = Decode::decode(&mut &item_attribute_vec[..]).map_err(|_| Error::<T, I>::InvalidPropertyAttributes)?;

when I try to use serde or serde_json dependency it throws an error when Im compiling substrate

it gives this error

  error[E0152]: duplicate lang item in crate `std` (which `serde_json` depends on): `panic_impl`.
    |
    = note: the lang item is first defined in crate `sp_io` (which `sp_application_crypto` depends on)
    = note: first definition in `sp_io` 

If I use this method in serde

let real_estate_value_json: RealEstateValues = from_slice(&item_attribute_vec)
                .map_err(|_| Error::<T, I>::InvalidPropertyAttributes)?;
    ```

It incorrectly sets current_fraction value 
How to fix this issue?
1

1 Answer 1

1

I dont quite understand what you are doing, but it looks like a mixing of the Serialize and Deserialize traits from serde with the Encode and Decode traits from SCALE.
There should be no JSON or serde needed in your runtime, just SCALE decode. It is simpler and has deterministic encoding for values.

Some example:

#[test]
fn encode_decode() {
    use codec::{Encode, Decode};
    use sp_runtime::Perbill;

    #[derive(Encode, Decode)]
    struct Value {
        current_fraction: Perbill,
    }
    let value = Value { current_fraction: Perbill::from_percent(10) };

    // encoding is infallible
    let encoded_value = Value::encode(&value);

    // Decode the value again:
    match Value::decode(&mut &encoded_value[..]) {
        Ok(decoded_value) => {
            assert_eq!(decoded_value.current_fraction, Perbill::from_percent(10))
        },
        Err(e) => /* handle error ... */,
    }
}

Also note that there exist types like Perbill or FixedU64 to represent fractions for your convenience.

2
  • Value submitted as a HexEncoded json. and stored in a bounded vec, if you try to decode this it gives incorrect results for values
    – Kalanamith
    Commented Mar 30 at 18:45
  • I have updated question
    – Kalanamith
    Commented Mar 30 at 20:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.