substrate icon indicating copy to clipboard operation
substrate copied to clipboard

Feature/frame interface

Open mustermeiszer opened this issue 2 years ago • 4 comments

Closes paritytech/polkadot-sdk#215

Idea

The idea is to have a single entry point for interface like calls. This entry point will be the pallet-interface. The pallet itself is just forwarding calls to an associated type Interface that implements trait UnfilteredDispatchable.

In order to make interfaces re-usable across chains:

  • all chains MUST locate the pallet-interface at the same index
  • all chains MUST locate the actual interfaces at the same index in the enum InterfaceCall that is aggregating the interfaces
    • e.g. Pip20 is agreed on to live at index 20pip20::Call should be located at index 20 in the enum InterfaceCall
  • interfaces MUST use the struct Select<From, To>
    • the struct allows to derive a chain native type from a different type. Like trait Convert is doing for a lot of things in XCM
    • From MUST be the same type for all chains
    • the struct decodes to From (i.e. it is a transparent wrapper around From)

→ this allows to

  • decode interface calls for all chains in the same manner
  • encode interface calls for all chains in the same manner

Open Questions

  • Do you people agree with the approach?
  • Do you think it is worth adding?

The interface macro and expansions

Definition of an Interface

The following example definition showcases how an interface would be defined.

A detailed one

	#[frame_support::interface]
	pub mod pip20 {
		use frame_support::{
			dispatch::DispatchResult,
			interface::{CallResult, Select, Selector, SelectorResult, ViewResult},
			Parameter,
		};
		use sp_core::H256;
		use sp_runtime::traits::Member;

		pub type CurrencySelectable = H256;
		pub type AccountIdSelectable = [u8; 32];
		pub type BalanceSelectable = u128;

		#[interface::definition]
		pub trait Pip20: frame_system::Config {
			/// A means for converting between from a [u8; 32] to the native chains account id.
			type SelectAccount: Selector<
				Selectable = AccountIdSelectable,
				Selected = Self::AccountId,
			>;

			/// The chains native currency type.
			type Currency: Parameter + Member;

			/// A means for converting between from a `H256` to the chains native currency.
			type SelectCurrency: Selector<
				Selectable = CurrencySelectable,
				Selected = Self::Currency,
			>;

			/// The chains native balance type.
			type Balance: Parameter + Member;

			/// A means for converting between from a u128 to the chains native balance.
			type SelectBalance: Selector<Selectable = BalanceSelectable, Selected = Self::Balance>;

			#[interface::view]
			#[interface::view_index(0)]
			fn free_balance(
				currency: Select<Self::SelectCurrency>,
				who: Select<Self::SelectAccount>,
			) -> ViewResult<BalanceSelectable>;

			#[interface::view]
			#[interface::view_index(1)]
			fn balances(
				who: Select<Self::SelectAccount>,
			) -> ViewResult<Vec<(CurrencySelectable, BalanceSelectable)>>;

			#[interface::call]
			#[interface::call_index(0)]
			#[interface::weight(0)]
			fn transfer(
				origin: Self::RuntimeOrigin,
				currency: Select<Self::SelectCurrency>,
				recv: Select<Self::SelectAccount>,
				amount: Select<Self::SelectBalance>,
			) -> CallResult;

			#[interface::call]
			#[interface::call_index(3)]
			#[interface::weight(0)]
			fn burn(
				origin: Self::RuntimeOrigin,
				currency: Select<Self::SelectCurrency>,
				from: Select<Self::SelectAccount>,
				amount: Select<Self::SelectBalance>,
			) -> CallResult;

			#[interface::call]
			#[interface::call_index(1)]
			#[interface::weight(0)]
			fn approve(
				origin: Self::RuntimeOrigin,
				currency: Select<Self::SelectCurrency>,
				recv: Select<Self::SelectAccount>,
				amount: Select<Self::SelectBalance>,
			) -> CallResult;
		}
	}

A smaller one

	#[frame_support::interface]
	pub mod pip42 {
		use frame_support::interface;
		use frame_support::interface::CallResult;
		use sp_core::Get;
		use sp_runtime::BoundedVec;

		#[interface::definition]
		pub trait Pip42: frame_system::Config {
			type MaxRemark: Get<u32>;

			#[interface::call]
			#[interface::call_index(0)]
			#[interface::weight(0)]
			fn remark(
				origin: Self::RuntimeOrigin,
				bytes: BoundedVec<u8, Self::MaxRemark>,
			) -> CallResult;
		}
	}

The macro expands the module into

  • the trait itself without altering it
  • an enum Call - similar to the Call of pallets used for dispatching
  • an enum View - the entry points for look-ups that can be called via a runtime API

enum Call

Is mostly identical with the enum Call of a normal pallet. With the most notable difference, that methods are based on the trait of the interface and implemented directly by the Runtime.

enum View

Is mostly identical with the enum Call of a normal pallet. As for the enum Call that methods are based on the trait of the interface and implemented directly by the Runtime. The biggest difference is that these methods do not change some state, as they should always be executed outside of block construction without persisting state changes.

Runtime API

The runtime api defined in frame-support::interface.

sp_api::decl_runtime_apis! {
	pub trait Interface<View>
		where View: sp_api::Encode + frame_support::interface::View
	{
		fn view(view: View) -> ViewResult<Vec<u8>>;
	}
}

Runtime Level

Given the above interface at the runtime level one would define two additional enums that represent the interfaces a runtime exposes. It is of course required for the Runtime to implement the trait Pip20 to make this compile.

enum InterfaceCall

The enum aggregating all call interfaces for a runtime. Passed to the pallet-interface as the associated type Interface.

E.g.

#[frame_support::call_entry(Runtime)]
pub enum InterfaceCall {
    #[call_entry::index(20)]
    Pip20(pip20::Call<Runtime>),
}

enum InterfaceView

The enum aggregating all view interfaces for a runtime. Will be used in the runtime api.

E.g.

#[frame_support::view_entry]
pub enum InterfaceView {
    #[view_entry::index(20)]
    Pip20(pip20::View<Runtime>),
}

Runtime API

impl_runtime_apis! {
    impl frame_supporti::interface::Interface<InterfaceView> for Runtime {
        fn view(view: InterfaceView) -> ViewResult<Vec<u8>> {
            view.view() 
        }
    }
}

Tasks

  • [x] Parse interface definition - the trait
  • [x] Expand interface enum Call
  • [x] Expand interface enum View
  • [x] Parse and expand enum CallEntry
  • [x] Parse and expand enum ViewEntry
  • [ ] (Maybe) make runtime-api accept default imlementation
  • [ ] (Maybe) define default index for an interface and trigger a compilation warning if it is positioned at a different index in the enum InterfaceCall
  • [ ] Clean up
  • [ ] Tests
  • [ ] Docs

mustermeiszer avatar Jul 03 '23 09:07 mustermeiszer

@bkchr @kianenigma Here is the WIP state. Took me some time, but this was my bedtime project and I forgot about it from time to time so sorry for the delay.

I wanted to create this WIP PR already to get feedback on the direction.

mustermeiszer avatar Jul 03 '23 10:07 mustermeiszer

The CI pipeline was cancelled due to failure one of the required jobs. Job name: cargo-check-benches Logs: https://gitlab.parity.io/parity/mirrors/substrate/-/jobs/3121406

paritytech-cicd-pr avatar Jul 04 '23 13:07 paritytech-cicd-pr

Ping @bkchr and @kianenigma ^^

mustermeiszer avatar Jul 13 '23 19:07 mustermeiszer

Hey, is anyone still working on this? Due to the inactivity this issue has been automatically marked as stale. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Aug 12 '23 22:08 stale[bot]