Generate code for Ink! contracts to use with `seal_call_runtime`.
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 489
- Forks
- 293
- Avg merge
- 18h 35m
- Merged PRs (30d)
- 3
Description
Continuing discussion from PR #1155 : https://github.com/paritytech/subxt/pull/1155#issuecomment-1714050564
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()
}
}
}
}
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the discussion in PR #1155 and compare the requested generated API with the referenced polymesh-api-codegen examples, especially generate.rs and dump_codegen.rs. Determine how Ink! support should coexist with RPC clients and verify that generated bindings can construct calls for seal_call_runtime and query storage without breaking existing output.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- blockchain
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 20/100