Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate code for Ink! contracts to use with seal_call_runtime. #1160

Open
Neopallium opened this issue Sep 12, 2023 · 0 comments
Open

Generate code for Ink! contracts to use with seal_call_runtime. #1160

Neopallium opened this issue Sep 12, 2023 · 0 comments

Comments

@Neopallium
Copy link
Contributor

Continuing discussion from PR #1155 : #1155 (comment)

I hope that the details below is helpful to subxt.

The codegen (crate polymesh-api-codegen) that is used for polymesh-api has support for generating code for both RPC clients (std native and no_std web) & Ink!.

The generated code uses feature flag ink to support ink and not-ink clients. The type_info feature flag
is used to derive scale-info::TypeInfo for the chain types.

Ink! also requires some other derives (SpreadLayout and PackedLayout) to be able to the chain types in contract storage.
I haven't come up with away to auto-detect which ink derives should be used for the generated types so for now the generator has a list of custom types which should have extra #[derive(...)] code:
https://github.com/PolymeshAssociation/polymesh-api/blob/c444e452b0401d40694ba326042f56a5617d642f/crates/polymesh-api-codegen/src/generate.rs#L287

Here are some snips from generated code (used the Substrate node-template).
I used the dump_codegen.rs example from polymesh-api-codegen to generator the code.
https://github.com/PolymeshAssociation/polymesh-api/blob/main/crates/polymesh-api-codegen/dump_codegen.sh

I added some notes prefixed with ----

/// ----- Moved these top-level types here:
#[derive(Clone)]
pub struct Api {
    #[cfg(not(feature = "ink"))]
    client: ::polymesh_api_client::Client, /// <----- `polymesh_api_client` is the backend to submit signed tx for RPC clients.
}

impl Api {
    pub fn call(&self) -> CallApi {
        CallApi { api: self }
    }
}

#[cfg(feature = "ink")]
impl Api {
    pub fn new() -> Self {
        Self {}
    }

    /// --------------------- For Ink! support Polymesh has a `ChainExtension` to provide a custom  `call_runtime`
    /// ----- and  `read_storage` support.
    pub fn runtime(&self) -> ::polymesh_api_ink::extension::PolymeshRuntimeInstance {
        ::polymesh_api_ink::extension::new_instance()
    }

    /// ------------- uses the `read_storage` method of the `ChainExtension` for storage reads.
    /// ----- this is the low-level method used by the `QueryApi` below.
    pub fn read_storage<T: ::codec::Decode>(
        &self,
        key: ::alloc::vec::Vec<u8>,
    ) -> ::polymesh_api_ink::error::Result<Option<T>> {
        let runtime = self.runtime();
        let value = runtime
            .read_storage(key.into())?
            .map(|data| T::decode(&mut data.as_slice()))
            .transpose()?;
        Ok(value)
    }

    pub fn query(&self) -> QueryApi {
        QueryApi { api: self }
    }

    pub fn wrap_call(&self, call: ::alloc::vec::Vec<u8>) -> WrappedCall {
        WrappedCall::new(call)
    }
}

#[cfg(not(feature = "ink"))]
impl Api {
    pub async fn new(url: &str) -> ::polymesh_api_client::error::Result<Self> {
        Ok(Self {
            client: ::polymesh_api_client::Client::new(url).await?,
        })
    }

    pub fn query(&self) -> QueryApi {
        QueryApi {
            api: self,
            at: None,
        }
    }

    pub fn query_at(&self, block: ::polymesh_api_client::BlockHash) -> QueryApi {
        QueryApi {
            api: self,
            at: Some(block),
        }
    }

    pub fn wrap_call(
        &self,
        call: types::runtime::RuntimeCall,
    ) -> ::polymesh_api_client::Result<WrappedCall> {
        Ok(WrappedCall::new(self, call))
    }
}

/// ------------------ The `ChainApi` trait from `polymesh_api_client` is used to provide some
/// -- important chain types and to provide some helpers `get_nonce`, `block_events`
#[async_trait::async_trait]
#[cfg(not(feature = "ink"))]
impl ::polymesh_api_client::ChainApi for Api {
    type RuntimeCall = types::runtime::RuntimeCall;
    type RuntimeEvent = types::runtime::RuntimeEvent;
    type DispatchInfo = types::frame_support::dispatch::DispatchInfo;
    type DispatchError = types::sp_runtime::DispatchError;
    async fn get_nonce(
        &self,
        account: ::polymesh_api_client::AccountId,
    ) -> ::polymesh_api_client::Result<u32> {
        let info = self.query().system().account(account).await?;
        Ok(info.nonce)
    }
    async fn block_events(
        &self,
        block: Option<::polymesh_api_client::BlockHash>,
    ) -> ::polymesh_api_client::Result<
        ::alloc::vec::Vec<::polymesh_api_client::EventRecord<Self::RuntimeEvent>>,
    > {
        let system = match block {
            Some(block) => self.query_at(block).system(),
            None => self.query().system(),
        };
        Ok(system.events().await?)
    }

    /// ------------------- This makes it much easier for the clients to check if their tx was successful or failed.
    fn event_to_extrinsic_result(
        event: &::polymesh_api_client::EventRecord<Self::RuntimeEvent>,
    ) -> Option<::polymesh_api_client::ExtrinsicResult<Self>> {
        match &event.event {
            types::runtime::RuntimeEvent::System(
                types::frame_system::pallet::SystemEvent::ExtrinsicSuccess { dispatch_info },
            ) => Some(::polymesh_api_client::ExtrinsicResult::Success(
                dispatch_info.clone(),
            )),
            types::runtime::RuntimeEvent::System(
                types::frame_system::pallet::SystemEvent::ExtrinsicFailed {
                    dispatch_info,
                    dispatch_error,
                },
            ) => Some(::polymesh_api_client::ExtrinsicResult::Failed(
                dispatch_info.clone(),
                dispatch_error.clone(),
            )),
            _ => None,
        }
    }
    fn client(&self) -> &::polymesh_api_client::Client {
        &self.client
    }
}

/// -------------------- `CallApi` is the top-level type used to construct an extrinsic call.
#[derive(Clone)]
pub struct CallApi<'api> {
    api: &'api Api,
}
impl<'api> CallApi<'api> {
    pub fn system(&self) -> api::system::SystemCallApi<'api> {
        api::system::SystemCallApi::from(self.api)
    }
    //// ---------------- <snipped other pallets>
    pub fn sudo(&self) -> api::sudo::SudoCallApi<'api> {
        api::sudo::SudoCallApi::from(self.api)
    }
}

/// ---------------------- The `WrappedCall` type is returned from `api.call().pallet().extrinsic(<params)`
/// ------------ it holds the encoded call and is provided by the backend (RPC client or Ink!)
/// ------------ to provide methods for signing and/or submitting the call to the chain.
#[cfg(not(feature = "ink"))]
pub type WrappedCall = ::polymesh_api_client::Call<Api>;

#[cfg(feature = "ink")]
pub type WrappedCall = ::polymesh_api_ink::Call;

/// -------- RPC clients get `TransactionResults` after submitting a tx to await on it's execution results.
/// ------- Ink! contracts get the results (Success/failed) when they submit the call.
#[cfg(not(feature = "ink"))]
pub type TransactionResults = ::polymesh_api_client::TransactionResults<Api>;

/// ------------- The `From` impl is used for batching, multisig or other extrinsics that take a `RuntimeCall`.
/// ------------- Ink! doesn't have support for this right now.
#[cfg(not(feature = "ink"))]
impl From<WrappedCall> for types::runtime::RuntimeCall {
    fn from(wrapped: WrappedCall) -> Self {
        wrapped.into_runtime_call()
    }
}

/// -------------------- `QueryApi` is the top-level type used to query chain storage.
#[derive(Clone)]
pub struct QueryApi<'api> {
    api: &'api Api,
    #[cfg(not(feature = "ink"))]
    at: Option<::polymesh_api_client::BlockHash>, /// ----- Ink! contracts are only allowed to query the current block storage.
}
impl<'api> QueryApi<'api> {
    pub fn system(&self) -> api::system::SystemQueryApi<'api> {
        api::system::SystemQueryApi {
            api: self.api,
            #[cfg(not(feature = "ink"))]
            at: self.at,
        }
    }
    /// --------------------- <Snipped other pallets>
    pub fn sudo(&self) -> api::sudo::SudoQueryApi<'api> {
        api::sudo::SudoQueryApi {
            api: self.api,
            #[cfg(not(feature = "ink"))]
            at: self.at,
        }
    }
}

/// ------------------- the `api` sub-module is used for per-pallet `<Pallet>CallApi` and `<Pallet>QueryApi` types.
/// ------ Only including the `pallet_sudo` here to keep this short.

#[allow(dead_code, unused_imports, non_camel_case_types)]
pub mod api {
    use super::types;
    use super::types::*;
    use super::WrappedCall;

    pub mod sudo {
        use super::*;

        /// ---------------- The per-pallet `<pallet>CallApi` provides one method for each extrinsic
        /// ------ and has both ink and non-ink impls.
        #[derive(Clone)]
        pub struct SudoCallApi<'api> {
            api: &'api super::super::Api,
        }
        impl<'api> SudoCallApi<'api> {
            #[doc = "See [`Pallet::sudo`]."]
            #[cfg(not(feature = "ink"))]
            pub fn sudo(
                &self,
                call: runtime::RuntimeCall,
            ) -> ::polymesh_api_client::error::Result<super::super::WrappedCall> {
                self.api.wrap_call(runtime::RuntimeCall::Sudo(
                    types::pallet_sudo::pallet::SudoCall::sudo {
                        call: ::alloc::boxed::Box::new(call),
                    },
                ))
            }
            #[doc = "See [`Pallet::sudo`]."]
            #[cfg(feature = "ink")]
            pub fn sudo(&self, call: runtime::RuntimeCall) -> super::super::WrappedCall {
                use ::codec::Encode;
                let mut buf = ::alloc::vec![6u8, 0u8];
                call.encode_to(&mut buf);
                self.api.wrap_call(buf)
            }
            /// ----------------------------------------------- <Snipped other sudo extrinsics>.
        }
        impl<'api> From<&'api super::super::Api> for SudoCallApi<'api> {
            fn from(api: &'api super::super::Api) -> Self {
                Self { api }
            }
        }

        /// ---------------- The per-pallet `<pallet>QueryApi` provides one method for each storage item.
        /// ------ The storage hash prefix is calculated by the generator and hard-coded.
        /// ------ The ink impl uses the `read_storage` method from the ChainExtension.
        #[derive(Clone)]
        pub struct SudoQueryApi<'api> {
            pub(crate) api: &'api super::super::Api,
            #[cfg(not(feature = "ink"))]
            pub(crate) at: Option<::polymesh_api_client::BlockHash>,
        }
        impl<'api> SudoQueryApi<'api> {
            #[doc = " The `AccountId` of the sudo key."]
            #[cfg(not(feature = "ink"))]
            pub async fn key(
                &self,
            ) -> ::polymesh_api_client::error::Result<Option<::polymesh_api_client::AccountId>>
            {
                let key = ::polymesh_api_client::StorageKey(::alloc::vec![
                    92u8, 13u8, 17u8, 118u8, 165u8, 104u8, 193u8, 249u8, 41u8, 68u8, 52u8, 13u8,
                    191u8, 237u8, 158u8, 156u8, 83u8, 14u8, 188u8, 167u8, 3u8, 200u8, 89u8, 16u8,
                    231u8, 22u8, 76u8, 183u8, 209u8, 201u8, 228u8, 123u8,
                ]);
                let value = self.api.client.get_storage_by_key(key, self.at).await?;
                Ok(value)
            }
            #[doc = " The `AccountId` of the sudo key."]
            #[cfg(feature = "ink")]
            pub fn key(
                &self,
            ) -> ::polymesh_api_ink::error::Result<Option<::polymesh_api_client::AccountId>>
            {
                let value = self.api.read_storage(::alloc::vec![
                    92u8, 13u8, 17u8, 118u8, 165u8, 104u8, 193u8, 249u8, 41u8, 68u8, 52u8, 13u8,
                    191u8, 237u8, 158u8, 156u8, 83u8, 14u8, 188u8, 167u8, 3u8, 200u8, 89u8, 16u8,
                    231u8, 22u8, 76u8, 183u8, 209u8, 201u8, 228u8, 123u8,
                ])?;
                Ok(value)
            }
        }
    }
}

/// ------------ `mod types` holds all types generated from the chain metadata.
pub mod types {
    use super::WrappedCall;
    pub mod sp_arithmetic {
        use super::*;
        #[derive(Clone, Debug, PartialEq, Eq, :: codec :: Encode, :: codec :: Decode)]
        #[cfg_attr(
            all(feature = "std", feature = "type_info"),
            derive(::scale_info::TypeInfo)
        )]
        #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
        pub enum ArithmeticError {
            #[codec(index = 0u8)]
            Underflow,
            #[codec(index = 1u8)]
            Overflow,
            #[codec(index = 2u8)]
            DivisionByZero,
        }
    }
    /// ---------------------------------- Only including the types from `pallet_sudo`, all pallets in the metadata will have a sub-module
    /// ------- here with their types.
    pub mod pallet_sudo {
        use super::*;
        pub mod pallet {
            use super::*;
            #[doc = "The `Event` enum of this pallet"]
            #[derive(Clone, Debug, PartialEq, Eq, :: codec :: Encode, :: codec :: Decode)]
            #[cfg_attr(
                all(feature = "std", feature = "type_info"),
                derive(::scale_info::TypeInfo)
            )]
            #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
            pub enum SudoEvent {
                #[doc = "A sudo just took place. \\[result\\]"]
                #[codec(index = 0u8)]
                Sudid {
                    sudo_result: Result<(), sp_runtime::DispatchError>,
                },
                #[doc = "The \\[sudoer\\] just switched identity; the old key is supplied if one existed."]
                #[codec(index = 1u8)]
                KeyChanged {
                    old_sudoer: Option<::polymesh_api_client::AccountId>,
                },
                #[doc = "A sudo just took place. \\[result\\]"]
                #[codec(index = 2u8)]
                SudoAsDone {
                    sudo_result: Result<(), sp_runtime::DispatchError>,
                },
            }
            impl SudoEvent {
                pub fn as_static_str(&self) -> &'static str {
                    #[allow(unreachable_patterns)]
                    match self {
                        Self::Sudid { .. } => "Sudo.Sudid",
                        Self::KeyChanged { .. } => "Sudo.KeyChanged",
                        Self::SudoAsDone { .. } => "Sudo.SudoAsDone",
                        _ => "Unknown",
                    }
                }
            }
            #[cfg(not(feature = "ink"))]
            impl ::polymesh_api_client::EnumInfo for SudoEvent {
                fn as_name(&self) -> &'static str {
                    self.as_static_str()
                }
                fn as_docs(&self) -> &'static [&'static str] {
                    # [allow (unreachable_patterns)] match self { Self :: Sudid { .. } => { & ["A sudo just took place. \\[result\\]" ,] } , Self :: KeyChanged { .. } => { & ["The \\[sudoer\\] just switched identity; the old key is supplied if one existed." ,] } , Self :: SudoAsDone { .. } => { & ["A sudo just took place. \\[result\\]" ,] } , _ => & [""] , }
                }
            }
            impl From<SudoEvent> for &'static str {
                fn from(v: SudoEvent) -> Self {
                    v.as_static_str()
                }
            }
            impl From<&SudoEvent> for &'static str {
                fn from(v: &SudoEvent) -> Self {
                    v.as_static_str()
                }
            }
            #[doc = "Contains a variant per dispatchable extrinsic that this pallet has."]
            #[derive(Clone, Debug, PartialEq, Eq, :: codec :: Encode, :: codec :: Decode)]
            #[cfg_attr(
                all(feature = "std", feature = "type_info"),
                derive(::scale_info::TypeInfo)
            )]
            #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
            pub enum SudoCall {
                #[doc = "See [`Pallet::sudo`]."]
                #[codec(index = 0u8)]
                sudo {
                    call: ::alloc::boxed::Box<runtime::RuntimeCall>,
                },
                #[doc = "See [`Pallet::sudo_unchecked_weight`]."]
                #[codec(index = 1u8)]
                sudo_unchecked_weight {
                    call: ::alloc::boxed::Box<runtime::RuntimeCall>,
                    weight: ::polymesh_api_client::sp_weights::Weight,
                },
                #[doc = "See [`Pallet::set_key`]."]
                #[codec(index = 2u8)]
                set_key {
                    new: ::polymesh_api_client::MultiAddress<::polymesh_api_client::AccountId, u32>,
                },
                #[doc = "See [`Pallet::sudo_as`]."]
                #[codec(index = 3u8)]
                sudo_as {
                    who: ::polymesh_api_client::MultiAddress<::polymesh_api_client::AccountId, u32>,
                    call: ::alloc::boxed::Box<runtime::RuntimeCall>,
                },
            }
            impl SudoCall {
                pub fn as_static_str(&self) -> &'static str {
                    #[allow(unreachable_patterns)]
                    match self {
                        Self::sudo { .. } => "Sudo.sudo",
                        Self::sudo_unchecked_weight { .. } => "Sudo.sudo_unchecked_weight",
                        Self::set_key { .. } => "Sudo.set_key",
                        Self::sudo_as { .. } => "Sudo.sudo_as",
                        _ => "Unknown",
                    }
                }
            }
            #[cfg(not(feature = "ink"))]
            impl ::polymesh_api_client::EnumInfo for SudoCall {
                fn as_name(&self) -> &'static str {
                    self.as_static_str()
                }
                fn as_docs(&self) -> &'static [&'static str] {
                    #[allow(unreachable_patterns)]
                    match self {
                        Self::sudo { .. } => &["See [`Pallet::sudo`]."],
                        Self::sudo_unchecked_weight { .. } => {
                            &["See [`Pallet::sudo_unchecked_weight`]."]
                        }
                        Self::set_key { .. } => &["See [`Pallet::set_key`]."],
                        Self::sudo_as { .. } => &["See [`Pallet::sudo_as`]."],
                        _ => &[""],
                    }
                }
            }
            impl From<SudoCall> for &'static str {
                fn from(v: SudoCall) -> Self {
                    v.as_static_str()
                }
            }
            impl From<&SudoCall> for &'static str {
                fn from(v: &SudoCall) -> Self {
                    v.as_static_str()
                }
            }
            #[doc = "Error for the Sudo pallet"]
            #[derive(Clone, Debug, PartialEq, Eq, :: codec :: Encode, :: codec :: Decode)]
            #[cfg_attr(
                all(feature = "std", feature = "type_info"),
                derive(::scale_info::TypeInfo)
            )]
            #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
            pub enum SudoError {
                #[doc = "Sender must be the Sudo account"]
                #[codec(index = 0u8)]
                RequireSudo,
            }
            impl SudoError {
                pub fn as_static_str(&self) -> &'static str {
                    #[allow(unreachable_patterns)]
                    match self {
                        Self::RequireSudo => "Sudo.RequireSudo",
                        _ => "Unknown",
                    }
                }
            }
            #[cfg(not(feature = "ink"))]
            impl ::polymesh_api_client::EnumInfo for SudoError {
                fn as_name(&self) -> &'static str {
                    self.as_static_str()
                }
                fn as_docs(&self) -> &'static [&'static str] {
                    #[allow(unreachable_patterns)]
                    match self {
                        Self::RequireSudo => &["Sender must be the Sudo account"],
                        _ => &[""],
                    }
                }
            }
            impl From<SudoError> for &'static str {
                fn from(v: SudoError) -> Self {
                    v.as_static_str()
                }
            }
            impl From<&SudoError> for &'static str {
                fn from(v: &SudoError) -> Self {
                    v.as_static_str()
                }
            }
        }
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant