Skip to main content

sails_rs/client/
mod.rs

1use crate::meta::SailsMessageHeader;
2use crate::prelude::*;
3use core::{
4    any::TypeId,
5    error::Error,
6    marker::PhantomData,
7    pin::Pin,
8    task::{Context, Poll},
9};
10use futures::Stream;
11pub use sails_idl_meta::{Identifiable, InterfaceId, MethodMeta};
12
13#[cfg(all(feature = "gtest", not(target_arch = "wasm32")))]
14mod gtest_env;
15#[cfg(all(feature = "gtest", not(target_arch = "wasm32")))]
16pub use gtest_env::*;
17
18#[cfg(all(feature = "gsdk", not(target_arch = "wasm32")))]
19mod gsdk_env;
20#[cfg(all(feature = "gsdk", not(target_arch = "wasm32")))]
21pub use gsdk_env::{GsdkEnv, GsdkError, GsdkParams};
22
23mod gstd_env;
24pub use gstd_env::{GstdEnv, GstdParams};
25
26pub(crate) const PENDING_CALL_INVALID_STATE: &str =
27    "PendingCall polled after completion or invalid state";
28pub(crate) const PENDING_CTOR_INVALID_STATE: &str =
29    "PendingCtor polled after completion or invalid state";
30
31// --- Route header typestate ---
32
33/// Alias for v1 service route strings (OLD IDL protocol).
34pub type Route = &'static str;
35
36/// Typestate marker for route-header encoding.
37pub trait RouteHeader: Clone + core::fmt::Debug {}
38
39/// v2 (NEW IDL) binary-header route: numeric route index (default).
40#[derive(Debug, Clone, Copy, Default)]
41pub struct RouteIdx(pub u8);
42impl RouteHeader for RouteIdx {}
43
44/// v1 (OLD IDL) SCALE-string route: carries the service route string at runtime.
45/// Ctor services use `RouteName("")` (no service prefix).
46#[derive(Debug, Clone, Copy, Default)]
47pub struct RouteName(pub Route);
48impl RouteHeader for RouteName {}
49
50// --- GearEnv and related ---
51
52pub trait GearEnv: Clone {
53    type Params: Default;
54    type Error: Error;
55    type MessageState;
56
57    fn deploy<P: Program>(&self, code_id: CodeId, salt: Vec<u8>) -> Deployment<P, Self> {
58        Deployment::new(self.clone(), code_id, salt)
59    }
60}
61
62/// Marker for environments that can create programs from within a program.
63///
64/// Implemented for every built-in environment except [`GstdEnv`] under the
65/// `ethexe` feature, where programs are deployed via the L1 rather than created
66/// on-chain. Generated client constructors require this bound, so attempting to
67/// deploy through an environment that lacks it is a compile-time error.
68#[diagnostic::on_unimplemented(
69    message = "`{Self}` cannot create programs from within a program",
70    note = "on `ethexe`, programs are deployed via the L1, so `GstdEnv` does not implement `EnvWithCtor`"
71)]
72pub trait EnvWithCtor: GearEnv {}
73
74pub trait Program: Sized {
75    fn deploy(code_id: CodeId, salt: Vec<u8>) -> Deployment<Self, GstdEnv> {
76        Deployment::new(GstdEnv, code_id, salt)
77    }
78
79    fn client(program_id: ActorId) -> Actor<Self, GstdEnv> {
80        Actor::new(GstdEnv, program_id)
81    }
82}
83
84#[derive(Debug, Clone)]
85pub struct Deployment<A, E: GearEnv = GstdEnv> {
86    env: E,
87    code_id: CodeId,
88    salt: Vec<u8>,
89    _phantom: PhantomData<A>,
90}
91
92impl<A, E: GearEnv> Deployment<A, E> {
93    pub fn new(env: E, code_id: CodeId, salt: Vec<u8>) -> Self {
94        Deployment {
95            env,
96            code_id,
97            salt,
98            _phantom: PhantomData,
99        }
100    }
101
102    pub fn with_env<N: GearEnv>(self, env: &N) -> Deployment<A, N> {
103        let Self {
104            env: _,
105            code_id,
106            salt,
107            _phantom: _,
108        } = self;
109        Deployment {
110            env: env.clone(),
111            code_id,
112            salt,
113            _phantom: PhantomData,
114        }
115    }
116
117    /// v2 (NEW IDL, default) ctor.
118    pub fn pending_ctor<T>(self, args: T::Params) -> PendingCtor<A, T, E>
119    where
120        T: ServiceCall<Route = RouteIdx>,
121    {
122        PendingCtor::new(self.env, self.code_id, self.salt, RouteIdx(0), args)
123    }
124
125    /// v1 (OLD IDL) ctor. Ctors have no service prefix — uses `RouteName("")`.
126    pub fn pending_ctor_v1<T>(self, args: T::Params) -> PendingCtor<A, T, E>
127    where
128        T: ServiceCall<Route = RouteName>,
129    {
130        PendingCtor::new(self.env, self.code_id, self.salt, RouteName(""), args)
131    }
132}
133
134#[derive(Debug, Clone)]
135pub struct Actor<A, E: GearEnv = GstdEnv> {
136    env: E,
137    id: ActorId,
138    _phantom: PhantomData<A>,
139}
140
141impl<A, E: GearEnv> Actor<A, E> {
142    pub fn new(env: E, id: ActorId) -> Self {
143        Actor {
144            env,
145            id,
146            _phantom: PhantomData,
147        }
148    }
149
150    pub fn id(&self) -> ActorId {
151        self.id
152    }
153
154    pub fn with_env<N: GearEnv>(self, env: &N) -> Actor<A, N> {
155        let Self {
156            env: _,
157            id,
158            _phantom: _,
159        } = self;
160        Actor {
161            env: env.clone(),
162            id,
163            _phantom: PhantomData,
164        }
165    }
166
167    pub fn with_actor_id(mut self, actor_id: ActorId) -> Self {
168        self.id = actor_id;
169        self
170    }
171
172    /// v2 (NEW IDL, default): service identified by numeric route index.
173    pub fn service<S>(&self, route_idx: u8) -> Service<S, E> {
174        Service::new(self.env.clone(), self.id, RouteIdx(route_idx))
175    }
176
177    /// v1 (OLD IDL): service identified by its IDL name (empty string for anonymous).
178    pub fn service_v1<S>(&self, name: Route) -> Service<S, E, RouteName> {
179        Service::new(self.env.clone(), self.id, RouteName(name))
180    }
181}
182
183#[derive(Debug, Clone)]
184pub struct Service<S, E: GearEnv = GstdEnv, R: RouteHeader = RouteIdx> {
185    env: E,
186    actor_id: ActorId,
187    route: R,
188    _phantom: PhantomData<S>,
189}
190
191impl<S, E: GearEnv, R: RouteHeader> Service<S, E, R> {
192    pub fn new(env: E, actor_id: ActorId, route: R) -> Self {
193        Service {
194            env,
195            actor_id,
196            route,
197            _phantom: PhantomData,
198        }
199    }
200
201    pub fn actor_id(&self) -> ActorId {
202        self.actor_id
203    }
204
205    pub fn with_actor_id(mut self, actor_id: ActorId) -> Self {
206        self.actor_id = actor_id;
207        self
208    }
209
210    pub fn pending_call<T: ServiceCall<Route = R>>(&self, args: T::Params) -> PendingCall<T, E> {
211        PendingCall::new(self.env.clone(), self.actor_id, self.route.clone(), args)
212    }
213
214    pub fn base_service<B>(&self) -> Service<B, E, R> {
215        Service::new(self.env.clone(), self.actor_id, self.route.clone())
216    }
217
218    #[cfg(not(target_arch = "wasm32"))]
219    pub fn decode_event<Ev: Event<R>>(
220        &self,
221        payload: impl AsRef<[u8]>,
222    ) -> Result<Ev, parity_scale_codec::Error> {
223        Ev::decode_event(&self.route, payload)
224    }
225
226    #[cfg(not(target_arch = "wasm32"))]
227    pub async fn listen(
228        &self,
229    ) -> Result<impl Stream<Item = (ActorId, S::Event)> + Unpin + use<S, E, R>, <E as GearEnv>::Error>
230    where
231        S: ServiceWithEvents<R>,
232        E: Listener<Error = <E as GearEnv>::Error>,
233    {
234        let self_id = self.actor_id;
235        let route = self.route.clone();
236        self.env
237            .listen(move |(actor_id, payload)| {
238                if actor_id != self_id {
239                    return None;
240                }
241                S::Event::decode_event(&route, payload)
242                    .ok()
243                    .map(|e| (actor_id, e))
244            })
245            .await
246    }
247}
248
249// v2-specific id accessors only on RouteIdx
250impl<S, E: GearEnv> Service<S, E, RouteIdx> {
251    pub fn interface_id(&self) -> InterfaceId
252    where
253        S: Identifiable,
254    {
255        S::INTERFACE_ID
256    }
257
258    pub fn route_idx(&self) -> u8 {
259        self.route.0
260    }
261}
262
263#[cfg(not(target_arch = "wasm32"))]
264pub trait ServiceWithEvents<R: RouteHeader = RouteIdx> {
265    type Event: Event<R>;
266}
267
268pin_project_lite::pin_project! {
269    pub struct PendingCall<T: ServiceCall, E: GearEnv = GstdEnv> {
270        env: E,
271        destination: ActorId,
272        route: T::Route,
273        params: Option<E::Params>,
274        args: Option<T::Params>,
275        #[pin]
276        state: Option<E::MessageState>
277    }
278}
279
280impl<T: ServiceCall, E: GearEnv> PendingCall<T, E> {
281    pub fn new(env: E, destination: ActorId, route: T::Route, args: T::Params) -> Self {
282        PendingCall {
283            env,
284            destination,
285            route,
286            params: None,
287            args: Some(args),
288            state: None,
289        }
290    }
291
292    pub fn with_destination(mut self, actor_id: ActorId) -> Self {
293        self.destination = actor_id;
294        self
295    }
296
297    pub fn with_params(mut self, f: impl FnOnce(E::Params) -> E::Params) -> Self {
298        self.params = Some(f(self.params.unwrap_or_default()));
299        self
300    }
301
302    pub fn encode_call(mut self) -> Vec<u8> {
303        let (payload, _) = self.take_encoded_args_and_params();
304        payload
305    }
306
307    #[inline]
308    pub(crate) fn take_encoded_args_and_params(&mut self) -> (Vec<u8>, E::Params) {
309        let args = self
310            .args
311            .take()
312            .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}"));
313        let payload = T::encode_call(&self.route, &args);
314        let params = self.params.take().unwrap_or_default();
315        (payload, params)
316    }
317}
318
319pub trait PendingCtorOutput<A, E: GearEnv> {
320    type Output;
321
322    fn map_result(self, env: E, id: ActorId) -> Self::Output;
323
324    fn actor(output: Self::Output) -> Actor<A, E>;
325}
326
327impl<A, E: GearEnv> PendingCtorOutput<A, E> for () {
328    type Output = Actor<A, E>;
329
330    fn map_result(self, env: E, id: ActorId) -> Self::Output {
331        Actor::new(env, id)
332    }
333
334    fn actor(output: Self::Output) -> Actor<A, E> {
335        output
336    }
337}
338
339impl<A, E: GearEnv, Err: core::fmt::Debug> PendingCtorOutput<A, E> for Result<(), Err> {
340    type Output = Result<Actor<A, E>, Err>;
341
342    fn map_result(self, env: E, id: ActorId) -> Self::Output {
343        self.map(|_| Actor::new(env, id))
344    }
345
346    fn actor(output: Self::Output) -> Actor<A, E> {
347        output.expect("PendingCtor output is not Ok")
348    }
349}
350
351pin_project_lite::pin_project! {
352    pub struct PendingCtor<A, T: ServiceCall, E: GearEnv = GstdEnv> {
353        env: E,
354        code_id: CodeId,
355        route: T::Route,
356        params: Option<E::Params>,
357        salt: Option<Vec<u8>>,
358        args: Option<T::Params>,
359        _actor: PhantomData<A>,
360        #[pin]
361        state: Option<E::MessageState>,
362        program_id: Option<ActorId>,
363    }
364}
365
366impl<A, T: ServiceCall, E: GearEnv> PendingCtor<A, T, E> {
367    pub fn new(env: E, code_id: CodeId, salt: Vec<u8>, route: T::Route, args: T::Params) -> Self {
368        PendingCtor {
369            env,
370            code_id,
371            route,
372            params: None,
373            salt: Some(salt),
374            args: Some(args),
375            state: None,
376            program_id: None,
377            _actor: PhantomData,
378        }
379    }
380
381    pub fn with_params(mut self, f: impl FnOnce(E::Params) -> E::Params) -> Self {
382        self.params = Some(f(self.params.unwrap_or_default()));
383        self
384    }
385}
386
387/// Unified service-call codec parameterized by route header.
388///
389pub trait ServiceCall {
390    type Route: RouteHeader;
391    type Params;
392    type Reply;
393    /// Application-level error type (IDL `throws`). Always `()` for v1.
394    type Throws;
395    type Output;
396
397    fn encode_call(route: &Self::Route, value: &Self::Params) -> Vec<u8>;
398
399    fn decode_reply(
400        route: &Self::Route,
401        payload: impl AsRef<[u8]>,
402    ) -> Result<Self::Output, parity_scale_codec::Error>;
403
404    fn decode_error(
405        route: &Self::Route,
406        payload: impl AsRef<[u8]>,
407    ) -> Result<Self::Output, parity_scale_codec::Error>;
408}
409
410// --- Standalone helpers (previously on the ServiceCall trait) ---
411
412/// v2-specific: decode a reply payload that has a SailsMessageHeader prefix.
413/// Used internally by `io_struct_impl!`.
414pub fn decode_with_header<T, M>(
415    route_idx: u8,
416    payload: impl AsRef<[u8]>,
417) -> Result<T, parity_scale_codec::Error>
418where
419    T: Decode + 'static,
420    M: MethodMeta + Identifiable,
421{
422    let mut value = payload.as_ref();
423    if is_empty_tuple::<T>() {
424        return Decode::decode(&mut value);
425    }
426    let header = SailsMessageHeader::decode(&mut value)?;
427    if header.interface_id() != M::INTERFACE_ID {
428        return Err("Invalid reply interface_id".into());
429    }
430    if header.route_id() != route_idx {
431        return Err("Invalid reply route_idx".into());
432    }
433    if header.entry_id() != M::ENTRY_ID {
434        return Err("Invalid reply entry_id".into());
435    }
436    Decode::decode(&mut value)
437}
438
439/// Returns true if `T` is the unit type `()`.
440pub fn is_empty_tuple<T>() -> bool
441where
442    T: 'static,
443{
444    TypeId::of::<T>() == TypeId::of::<()>()
445}
446
447/// Env error type that [`decode_reply_or_throw`] can inspect to recover an
448/// application-level `throws` value from a userspace-panic reply.
449pub trait ReplyError: Sized {
450    /// Wrap a SCALE decode failure of a reply payload.
451    fn from_codec_error(err: parity_scale_codec::Error) -> Self;
452
453    /// If this error is a userspace panic carrying a reply payload, return that payload.
454    fn userspace_panic_payload(&self) -> Option<&[u8]>;
455}
456
457/// Decodes a raw reply against `route`:
458/// - `Ok(payload)` is decoded via [`ServiceCall::decode_reply`];
459/// - a userspace-panic error is decoded via [`ServiceCall::decode_error`] to recover a
460///   `throws` value, falling back to the original error when that fails;
461/// - any other error is propagated unchanged.
462pub fn decode_reply_or_throw<T: ServiceCall, E: ReplyError>(
463    route: &T::Route,
464    reply: Result<Vec<u8>, E>,
465) -> Result<T::Output, E> {
466    match reply {
467        Ok(payload) => T::decode_reply(route, payload).map_err(E::from_codec_error),
468        Err(err) => {
469            if let Some(payload) = err.userspace_panic_payload()
470                && let Ok(reply) = T::decode_error(route, payload)
471            {
472                return Ok(reply);
473            }
474            Err(err)
475        }
476    }
477}
478
479/// v2-specific: encode a call using a stack buffer (zero-copy, requires `gcore`).
480/// Used in wasm32 on-chain dispatch code via the `io_struct_impl!` inherent methods.
481pub fn encode_call_optimized<T, R>(
482    route_idx: u8,
483    value: &T::Params,
484    f: impl FnOnce(&[u8]) -> R,
485) -> R
486where
487    T: ServiceCall<Route = RouteIdx> + MethodMeta + Identifiable,
488    T::Params: Encode,
489{
490    encode_call_optimized_with_id::<T, R>(T::INTERFACE_ID, T::ENTRY_ID, route_idx, value, f)
491}
492
493pub fn encode_call_optimized_with_id<T, R>(
494    interface_id: InterfaceId,
495    entry_id: u16,
496    route_idx: u8,
497    value: &T::Params,
498    f: impl FnOnce(&[u8]) -> R,
499) -> R
500where
501    T: ServiceCall<Route = RouteIdx>,
502    T::Params: Encode,
503{
504    let header = SailsMessageHeader::new(
505        crate::meta::Version::v1(),
506        crate::meta::HeaderLength::new(crate::meta::MINIMAL_HLEN).unwrap(),
507        interface_id,
508        route_idx,
509        entry_id,
510    );
511    let size = (crate::meta::MINIMAL_HLEN as usize) + Encode::encoded_size(value);
512    gcore::stack_buffer::with_byte_buffer(size, |buffer| {
513        let mut buffer_writer = crate::utils::MaybeUninitBufferWriter::new(buffer);
514        header.encode_to(&mut buffer_writer);
515        Encode::encode_to(value, &mut buffer_writer);
516        buffer_writer.with_buffer(f)
517    })
518}
519
520#[macro_export]
521macro_rules! params_struct_impl {
522    (
523        $env:ident,
524        $name:ident { $( $(#[$attr:meta])* $vis:vis $field:ident: $ty:ty ),* $(,)?  }
525    ) => {
526        #[derive(Debug, Default)]
527        pub struct $name {
528            $(
529                $(#[$attr])* $vis $field : Option< $ty >,
530            )*
531        }
532
533        impl $name {
534            $(
535                paste::paste! {
536                    $(#[$attr])*
537                    pub fn [<with_ $field>](mut self, $field: $ty) -> Self {
538                        self.$field = Some($field);
539                        self
540                    }
541                }
542            )*
543        }
544
545        impl<A, T: $crate::client::ServiceCall> $crate::client::PendingCtor<A, T, $env> {
546            $(
547                paste::paste! {
548                    $(#[$attr])*
549                    pub fn [<with_ $field>](self, $field: $ty) -> Self {
550                        self.with_params(|params| params.[<with_ $field>]($field))
551                    }
552                }
553            )*
554        }
555
556        impl<T: $crate::client::ServiceCall> $crate::client::PendingCall<T, $env> {
557            $(
558                paste::paste! {
559                    $(#[$attr])*
560                    pub fn [<with_ $field>](self, $field: $ty) -> Self {
561                        self.with_params(|params| params.[<with_ $field>]($field))
562                    }
563                }
564            )*
565        }
566    };
567}
568
569#[macro_export]
570macro_rules! params_for_pending_impl {
571    (
572        $env:ident,
573        $name:ident { $( $(#[$attr:meta])* $vis:vis $field:ident: $ty:ty ),* $(,)?  }
574    ) => {
575        impl $name {
576            $(
577                paste::paste! {
578                    $(#[$attr])*
579                    pub fn [<with_ $field>](mut self, $field: $ty) -> Self {
580                        self.$field = Some($field);
581                        self
582                    }
583                }
584            )*
585        }
586
587        impl<A, T: $crate::client::ServiceCall> $crate::client::PendingCtor<A, T, $env> {
588            $(
589                paste::paste! {
590                    $(#[$attr])*
591                    pub fn [<with_ $field>](self, $field: $ty) -> Self {
592                        self.with_params(|params| params.[<with_ $field>]($field))
593                    }
594                }
595            )*
596        }
597
598        impl<T: $crate::client::ServiceCall> $crate::client::PendingCall<T, $env> {
599            $(
600                paste::paste! {
601                    $(#[$attr])*
602                    pub fn [<with_ $field>](self, $field: $ty) -> Self {
603                        self.with_params(|params| params.[<with_ $field>]($field))
604                    }
605                }
606            )*
607        }
608    };
609}
610
611#[macro_export]
612macro_rules! io_struct_impl {
613    // 3-arg form: service method with entry_id + interface_id (no throws)
614    (
615        $name:ident ( $( $param:ident : $ty:ty ),* ) -> $reply:ty, $entry_id:expr, $interface_id:expr
616    ) => {
617        $crate::io_struct_impl!(@impl_base $name ( $( $param : $ty ),* ) -> $reply, $entry_id, $interface_id);
618
619        impl $crate::client::ServiceCall for $name {
620            type Route = $crate::client::RouteIdx;
621            type Params = ( $( $ty, )* );
622            type Reply = $reply;
623            type Throws = ();
624            type Output = $reply;
625
626            fn encode_call(route: &$crate::client::RouteIdx, value: &Self::Params) -> Vec<u8> {
627                let header = $crate::meta::SailsMessageHeader::new(
628                    $crate::meta::Version::v1(),
629                    $crate::meta::HeaderLength::new($crate::meta::MINIMAL_HLEN).unwrap(),
630                    <$name as $crate::client::Identifiable>::INTERFACE_ID,
631                    route.0,
632                    <$name as $crate::client::MethodMeta>::ENTRY_ID,
633                );
634                let mut result = header.to_bytes();
635                $crate::prelude::Encode::encode_to(value, &mut result);
636                result
637            }
638
639            fn decode_reply(
640                route: &$crate::client::RouteIdx,
641                payload: impl AsRef<[u8]>,
642            ) -> Result<Self::Output, $crate::scale_codec::Error> {
643                $crate::client::decode_with_header::<Self::Reply, $name>(route.0, payload)
644            }
645
646            fn decode_error(
647                _route: &$crate::client::RouteIdx,
648                _payload: impl AsRef<[u8]>,
649            ) -> Result<Self::Output, $crate::scale_codec::Error> {
650                Err("Throws type is `()`".into())
651            }
652        }
653    };
654    // 3-arg form with throws
655    (
656        $name:ident ( $( $param:ident : $ty:ty ),* ) -> $reply:ty | $throws:ty, $entry_id:expr, $interface_id:expr
657    ) => {
658        $crate::io_struct_impl!(@impl_base $name ( $( $param : $ty ),* ) -> $reply, $entry_id, $interface_id);
659
660        impl $crate::client::ServiceCall for $name {
661            type Route = $crate::client::RouteIdx;
662            type Params = ( $( $ty, )* );
663            type Reply = $reply;
664            type Throws = $throws;
665            type Output = Result<$reply, $throws>;
666
667            fn encode_call(route: &$crate::client::RouteIdx, value: &Self::Params) -> Vec<u8> {
668                let header = $crate::meta::SailsMessageHeader::new(
669                    $crate::meta::Version::v1(),
670                    $crate::meta::HeaderLength::new($crate::meta::MINIMAL_HLEN).unwrap(),
671                    <$name as $crate::client::Identifiable>::INTERFACE_ID,
672                    route.0,
673                    <$name as $crate::client::MethodMeta>::ENTRY_ID,
674                );
675                let mut result = header.to_bytes();
676                $crate::prelude::Encode::encode_to(value, &mut result);
677                result
678            }
679
680            fn decode_reply(
681                route: &$crate::client::RouteIdx,
682                payload: impl AsRef<[u8]>,
683            ) -> Result<Self::Output, $crate::scale_codec::Error> {
684                Ok(Ok($crate::client::decode_with_header::<Self::Reply, $name>(route.0, payload)?))
685            }
686
687            fn decode_error(
688                route: &$crate::client::RouteIdx,
689                payload: impl AsRef<[u8]>,
690            ) -> Result<Self::Output, $crate::scale_codec::Error> {
691                Ok(Err($crate::client::decode_with_header::<Self::Throws, $name>(route.0, payload)?))
692            }
693        }
694    };
695    // 2-arg form: ctor method (InterfaceId::zero)
696    (
697        $name:ident ( $( $param:ident : $ty:ty ),* ) -> $reply:ty $( | $throws:ty )?, $entry_id:expr
698    ) => {
699        $crate::io_struct_impl!($name ( $( $param : $ty ),* ) -> $reply $( | $throws )?, $entry_id, $crate::meta::InterfaceId::zero());
700    };
701    // @impl_base: struct + Identifiable + MethodMeta + convenience inherent methods
702    (
703        @impl_base $name:ident ( $( $param:ident : $ty:ty ),* ) -> $reply:ty, $entry_id:expr, $interface_id:expr
704    ) => {
705        pub struct $name(());
706        impl $name {
707            /// Encodes the full call with the correct Sails header.
708            pub fn encode_call(route_idx: u8, $( $param: $ty, )* ) -> Vec<u8> {
709                <$name as $crate::client::ServiceCall>::encode_call(
710                    &$crate::client::RouteIdx(route_idx), &( $( $param, )* )
711                )
712            }
713            /// Decodes the reply checking against the correct Sails header.
714            pub fn decode_reply(route_idx: u8, payload: impl AsRef<[u8]>) -> Result<$reply, $crate::scale_codec::Error> {
715                $crate::client::decode_with_header::<$reply, $name>(route_idx, payload)
716            }
717        }
718        impl $crate::client::Identifiable for $name {
719            const INTERFACE_ID: $crate::meta::InterfaceId = $interface_id;
720        }
721        impl $crate::client::MethodMeta for $name {
722            const ENTRY_ID: u16 = $entry_id;
723        }
724    };
725}
726
727/// v1 (OLD IDL, SCALE-string encoded) service/ctor IO struct.
728///
729/// `ServiceCall<RouteName>` uses the route string from the `RouteName` instance
730/// passed at call time (set by `Actor::service_v1(name)` / `pending_ctor_v1`).
731///
732/// v1 has NO `throws` concept — `Throws` is always `()` and `Output = Reply`.
733///
734/// Encoding: `SCALE(route.0) + SCALE(stringify!($name)) + SCALE(params)`
735/// If `route.0` is empty (ctor / anonymous service), the service prefix is omitted.
736#[macro_export]
737macro_rules! io_struct_impl_v1 {
738    (
739        $name:ident ( $( $param:ident : $ty:ty ),* ) -> $reply:ty
740    ) => {
741        pub struct $name(());
742
743        impl $name {
744            pub fn encode_call(route: $crate::client::Route, $( $param: $ty, )* ) -> Vec<u8> {
745                <$name as $crate::client::ServiceCall>::encode_call(
746                    &$crate::client::RouteName(route), &( $( $param, )* )
747                )
748            }
749
750            pub fn decode_reply(route: $crate::client::Route, payload: impl AsRef<[u8]>) -> Result<$reply, $crate::scale_codec::Error> {
751                <$name as $crate::client::ServiceCall>::decode_reply(
752                    &$crate::client::RouteName(route),
753                    payload,
754                )
755            }
756        }
757
758        impl $crate::client::ServiceCall for $name {
759            type Route = $crate::client::RouteName;
760            type Params = ( $( $ty, )* );
761            type Reply = $reply;
762            // v1 has no `throws` concept — always unit, Output == Reply.
763            type Throws = ();
764            type Output = $reply;
765
766            fn encode_call(
767                route: &$crate::client::RouteName,
768                value: &Self::Params,
769            ) -> Vec<u8> {
770                use $crate::prelude::Encode as _;
771                let mut result = Vec::new();
772                if !route.0.is_empty() {
773                    route.0.encode_to(&mut result);
774                }
775                stringify!($name).encode_to(&mut result);
776                value.encode_to(&mut result);
777                result
778            }
779
780            fn decode_reply(
781                route: &$crate::client::RouteName,
782                payload: impl AsRef<[u8]>,
783            ) -> Result<Self::Output, $crate::scale_codec::Error> {
784                use $crate::prelude::Decode as _;
785                let mut value = payload.as_ref();
786                if $crate::client::is_empty_tuple::<$reply>() {
787                    return $crate::prelude::Decode::decode(&mut value);
788                }
789                if !route.0.is_empty() {
790                    let svc = String::decode(&mut value)?;
791                    if svc != route.0 {
792                        return Err("Invalid reply service route".into());
793                    }
794                }
795                let method = String::decode(&mut value)?;
796                if method != stringify!($name) {
797                    return Err("Invalid reply method route".into());
798                }
799                $crate::prelude::Decode::decode(&mut value)
800            }
801
802            fn decode_error(
803                _route: &$crate::client::RouteName,
804                _payload: impl AsRef<[u8]>,
805            ) -> Result<Self::Output, $crate::scale_codec::Error> {
806                Err("Throws type is `()` for v1".into())
807            }
808        }
809    };
810}
811
812#[allow(unused_macros)]
813macro_rules! str_scale_encode {
814    ($s:ident) => {{
815        const S: &str = stringify!($s);
816        assert!(S.len() <= 63, "Ident too long for encoding");
817        const LEN: u8 = S.len() as u8;
818        const BYTES: [u8; LEN as usize + 1] = {
819            const fn to_array(s: &str) -> [u8; LEN as usize + 1] {
820                let bytes = s.as_bytes();
821                let mut out = [0u8; LEN as usize + 1];
822                out[0] = LEN << 2;
823                let mut i = 0;
824                while i < LEN as usize {
825                    out[i + 1] = bytes[i];
826                    i += 1;
827                }
828                out
829            }
830            to_array(S)
831        };
832        BYTES.as_slice()
833    }};
834}
835
836#[allow(async_fn_in_trait)]
837pub trait Listener {
838    type Error: Error;
839
840    async fn listen<E, F>(
841        &self,
842        f: F,
843    ) -> Result<impl Stream<Item = (ActorId, E)> + Unpin + use<Self, E, F>, Self::Error>
844    where
845        F: FnMut((ActorId, Vec<u8>)) -> Option<(ActorId, E)>;
846}
847
848#[cfg(not(target_arch = "wasm32"))]
849struct EventInput<'a> {
850    idx: u8,
851    payload: &'a [u8],
852    first: bool,
853}
854
855#[cfg(not(target_arch = "wasm32"))]
856impl<'a> parity_scale_codec::Input for EventInput<'a> {
857    fn remaining_len(&mut self) -> Result<Option<usize>, parity_scale_codec::Error> {
858        Ok(Some(1 + self.payload.len()))
859    }
860
861    fn read(&mut self, into: &mut [u8]) -> Result<(), parity_scale_codec::Error> {
862        if into.is_empty() {
863            return Ok(());
864        }
865        let (head, tail) = into.split_at_mut(if self.first { 1 } else { 0 });
866        if self.first {
867            head[0] = self.idx;
868            self.first = false;
869        }
870        if tail.is_empty() {
871            return Ok(());
872        }
873        if tail.len() > self.payload.len() {
874            return Err("Not enough data to fill buffer".into());
875        }
876        tail.copy_from_slice(&self.payload[..tail.len()]);
877        self.payload = &self.payload[tail.len()..];
878        Ok(())
879    }
880
881    fn read_byte(&mut self) -> Result<u8, parity_scale_codec::Error> {
882        if self.first {
883            self.first = false;
884            Ok(self.idx)
885        } else {
886            let b = *self.payload.first().ok_or("Not enough data to read byte")?;
887            self.payload = &self.payload[1..];
888            Ok(b)
889        }
890    }
891}
892
893// --- Event traits ---
894
895/// v1 (OLD IDL): compile-time list of event variant names.
896/// Implemented by `client-gen-v1`-generated event enums.
897#[cfg(not(target_arch = "wasm32"))]
898pub trait EventNames {
899    const EVENT_NAMES: &'static [Route];
900}
901
902/// v2-specific: decode an event payload using SailsMessageHeader.
903/// Used by v2-generated `impl Event<RouteIdx>` blocks.
904#[cfg(not(target_arch = "wasm32"))]
905pub fn decode_event_v2<E>(
906    route_idx: u8,
907    payload: impl AsRef<[u8]>,
908) -> Result<E, parity_scale_codec::Error>
909where
910    E: Decode + Identifiable,
911{
912    let mut payload = payload.as_ref();
913
914    let header = SailsMessageHeader::decode(&mut payload)?;
915    if header.interface_id() != E::INTERFACE_ID {
916        return Err("Invalid event interface_id".into());
917    }
918    if header.route_id() != route_idx {
919        return Err("Invalid event route_idx".into());
920    }
921
922    let entry_id = header.entry_id();
923    if entry_id > 255 {
924        return Err("Entry ID exceeds u8 limit for SCALE enum".into());
925    }
926
927    let variant_index = entry_id as u8;
928    let mut input = EventInput {
929        idx: variant_index,
930        payload,
931        first: true,
932    };
933    Decode::decode(&mut input)
934}
935
936/// v1-specific: decode an event payload encoded as `SCALE(variant_name) + SCALE(data)`.
937#[cfg(not(target_arch = "wasm32"))]
938pub fn decode_event_v1<E>(
939    route: Route,
940    payload: impl AsRef<[u8]>,
941) -> Result<E, parity_scale_codec::Error>
942where
943    E: Decode + EventNames,
944{
945    let mut payload = payload.as_ref();
946    let payload_route = String::decode(&mut payload)?;
947    if payload_route != route {
948        return Err("Invalid v1 event route".into());
949    }
950    let name = String::decode(&mut payload)?;
951    let idx = <E as EventNames>::EVENT_NAMES
952        .iter()
953        .position(|candidate| *candidate == name)
954        .ok_or("Unknown v1 event name")? as u8;
955    let mut input = EventInput {
956        idx,
957        payload,
958        first: true,
959    };
960    Decode::decode(&mut input)
961}
962
963/// Event codec parameterized by route header.
964/// `R = RouteIdx` → v2 SailsHeader-based decoding.
965/// `R = RouteName` → v1 SCALE-string variant-name decoding.
966#[cfg(not(target_arch = "wasm32"))]
967pub trait Event<R = RouteIdx>: Sized
968where
969    R: RouteHeader,
970{
971    fn decode_event(
972        route: &R,
973        payload: impl AsRef<[u8]>,
974    ) -> Result<Self, parity_scale_codec::Error>;
975}
976
977#[cfg(test)]
978mod tests {
979    use super::*;
980    // Define Add with InterfaceId to test 3-arg macro (Service mode)
981    io_struct_impl!(Add (value: u32) -> u32, 0, InterfaceId::from_bytes_8([1, 2, 3, 4, 5, 6, 7, 8]));
982    // Define Value with 2-arg macro (Ctor/Legacy mode)
983    io_struct_impl!(Value () -> u32, 1);
984    // Define Sub with `throws` type
985    io_struct_impl!(Sub (value: u32) -> u32 | String, 0, InterfaceId::from_bytes_8([1, 2, 3, 4, 5, 6, 7, 8]));
986
987    #[test]
988    fn test_io_struct_impl() {
989        // Add is a "Service" method, encode takes RouteIdx
990        let route_idx = RouteIdx(5u8);
991        let add_specific = Add::encode_call(route_idx.0, 42);
992
993        let expected_header_add = [
994            0x47, 0x4D, // magic ("GM")
995            1,    // version
996            16,   // hlen
997            1, 2, 3, 4, 5, 6, 7, 8, // interface_id
998            0, 0, // entry_id (0 for Add)
999            5, // route_id
1000            0, // reserved
1001        ];
1002        let expected_add_payload = [42, 0, 0, 0]; // 42u32 LE
1003
1004        let mut expected_add_specific = Vec::new();
1005        expected_add_specific.extend_from_slice(&expected_header_add);
1006        expected_add_specific.extend_from_slice(&expected_add_payload);
1007
1008        assert_eq!(add_specific, expected_add_specific);
1009
1010        let reply_payload = [42, 0, 0, 0];
1011        let mut reply_with_header = expected_add_specific.clone();
1012        reply_with_header.truncate(16);
1013        reply_with_header.extend_from_slice(&reply_payload);
1014
1015        let decoded = Add::decode_reply(route_idx.0, &reply_with_header).unwrap();
1016        assert_eq!(decoded, 42);
1017
1018        // Value is "Ctor" mode, it uses InterfaceId::zero()
1019        let value_encoded = Value::encode_call(0);
1020        let expected_header_value = [
1021            0x47, 0x4D, 1, 16, // magic, version, hlen
1022            0, 0, 0, 0, 0, 0, 0, 0, // interface_id (zero)
1023            1, 0, // entry_id (1 for Value)
1024            0, 0, // route_id 0 and reserved 0
1025        ];
1026        assert_eq!(value_encoded, expected_header_value);
1027
1028        // Decode reply for Value
1029        let mut value_reply = expected_header_value.to_vec();
1030        value_reply.extend_from_slice(&[123, 0, 0, 0]); // payload 123u32
1031        let decoded_value = Value::decode_reply(0, &value_reply).unwrap();
1032        assert_eq!(decoded_value, 123);
1033    }
1034
1035    #[test]
1036    fn test_io_struct_impl_throws() {
1037        let route_idx = RouteIdx(5u8);
1038        let sub_specific = Sub::encode_call(route_idx.0, 42);
1039
1040        let expected_header_sub = [
1041            0x47, 0x4D, // magic ("GM")
1042            1,    // version
1043            16,   // hlen
1044            1, 2, 3, 4, 5, 6, 7, 8, // interface_id
1045            0, 0, // entry_id (0 for Sub)
1046            5, // route_id
1047            0, // reserved
1048        ];
1049        let expected_sub_payload = [42, 0, 0, 0]; // 42u32 LE
1050
1051        let mut expected_sub_specific = Vec::new();
1052        expected_sub_specific.extend_from_slice(&expected_header_sub);
1053        expected_sub_specific.extend_from_slice(&expected_sub_payload);
1054
1055        assert_eq!(sub_specific, expected_sub_specific);
1056
1057        let mut success_reply = expected_header_sub.to_vec();
1058        success_reply.extend_from_slice(&expected_sub_payload);
1059
1060        let decoded_success =
1061            <Sub as ServiceCall>::decode_reply(&route_idx, &success_reply).unwrap();
1062        assert_eq!(decoded_success, Ok(42));
1063        assert_eq!(Sub::decode_reply(route_idx.0, &success_reply).unwrap(), 42);
1064
1065        let error_message = String::from("boom");
1066        let mut error_reply = expected_header_sub.to_vec();
1067        error_reply.extend_from_slice(&error_message.encode());
1068
1069        let decoded_error = <Sub as ServiceCall>::decode_error(&route_idx, &error_reply).unwrap();
1070        assert_eq!(decoded_error, Err(error_message));
1071    }
1072
1073    #[test]
1074    fn test_io_struct_impl_v1() {
1075        io_struct_impl_v1!(DoThis (value: u32) -> u32);
1076
1077        // Encoding: SCALE("MyService") + SCALE("DoThis") + SCALE(42u32)
1078        let encoded = DoThis::encode_call("MyService", 42u32);
1079        let mut expected = Vec::new();
1080        "MyService".encode_to(&mut expected);
1081        "DoThis".encode_to(&mut expected);
1082        42u32.encode_to(&mut expected);
1083        assert_eq!(encoded, expected);
1084
1085        // Decoding
1086        let decoded = DoThis::decode_reply("MyService", &encoded).unwrap();
1087        assert_eq!(decoded, 42u32);
1088    }
1089}