From 14282b7533ba64116a1f3932aa08f8cb5cd6aa04 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 6 Aug 2025 18:07:09 +0200 Subject: [PATCH 01/66] wip: Gear Env Client --- Cargo.lock | 1 + examples/demo/app/tests/env_gtest.rs | 41 + examples/demo/client/src/env_client.rs | 88 ++ examples/demo/client/src/lib.rs | 2 + rs/Cargo.toml | 4 + rs/src/client.rs | 1020 ++++++++++++++++++++++++ rs/src/lib.rs | 2 + 7 files changed, 1158 insertions(+) create mode 100644 examples/demo/app/tests/env_gtest.rs create mode 100644 examples/demo/client/src/env_client.rs create mode 100644 rs/src/client.rs diff --git a/Cargo.lock b/Cargo.lock index 7a3e3cb90..daa0c2b22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7308,6 +7308,7 @@ dependencies = [ "scale-info", "spin", "thiserror 2.0.12", + "tokio", ] [[package]] diff --git a/examples/demo/app/tests/env_gtest.rs b/examples/demo/app/tests/env_gtest.rs new file mode 100644 index 000000000..5a40f3ef8 --- /dev/null +++ b/examples/demo/app/tests/env_gtest.rs @@ -0,0 +1,41 @@ +use demo_client::env_client::{Counter as _, Demo as _, DemoCtors as _, DemoProgram}; +use sails_rs::{ + client::gtest::{BlockRunMode, GtestEnv}, + prelude::*, +}; + +const ACTOR_ID: u64 = 42; +#[cfg(debug_assertions)] +pub(crate) const DEMO_WASM_PATH: &str = "../../../target/wasm32-gear/debug/demo.opt.wasm"; +#[cfg(not(debug_assertions))] +pub(crate) const DEMO_WASM_PATH: &str = "../../../target/wasm32-gear/release/demo.opt.wasm"; + +fn create_env() -> (GtestEnv, CodeId, GasUnit) { + use sails_rs::gtest::{MAX_USER_GAS_LIMIT, System}; + + let system = System::new(); + system.init_logger_with_default_filter("gwasm=debug,gtest=info,sails_rs=debug,redirect=debug"); + system.mint_to(ACTOR_ID, 100_000_000_000_000); + // Submit program code into the system + let code_id = system.submit_code_file(DEMO_WASM_PATH); + + // Create a remoting instance for the system + // and set the block run mode to Next, + // cause we don't receive any reply on `Exit` call + let env = GtestEnv::new(system, ACTOR_ID.into()).with_block_run_mode(BlockRunMode::Next); + (env, code_id, MAX_USER_GAS_LIMIT) +} + +#[tokio::test] +async fn env_counter_add_works_via_next_mode() { + let (env, code_id, _gas_limit) = create_env(); + + let demo_program = DemoProgram::deploy(env, code_id, vec![]) + .new(Some(42), None) + .await + .unwrap(); + + let mut counter_client = demo_program.counter(); + + assert_eq!(Ok(52), counter_client.add(10).await); +} diff --git a/examples/demo/client/src/env_client.rs b/examples/demo/client/src/env_client.rs new file mode 100644 index 000000000..8826697f4 --- /dev/null +++ b/examples/demo/client/src/env_client.rs @@ -0,0 +1,88 @@ +use sails_rs::{ + client::{Actor, Deployment, GearEnv, PendingCall, PendingCtor, Service}, + prelude::*, +}; + +pub trait DemoCtors { + type Env: GearEnv; + + fn default(self) -> PendingCtor; + fn new( + self, + counter: Option, + dog_position: Option<(i32, i32)>, + ) -> PendingCtor; +} + +pub trait Demo { + type Env: GearEnv; + + fn counter(&self) -> Service; +} + +pub struct DemoProgram; + +impl DemoProgram { + pub fn deploy( + env: E, + code_id: CodeId, + salt: Vec, + ) -> Deployment { + Deployment::new(env, code_id, salt) + } + + pub fn client(env: E, program_id: ActorId) -> Actor { + Actor::new(program_id, env) + } +} + +impl DemoCtors for Deployment { + type Env = E; + + fn default(self) -> PendingCtor { + self.pending_ctor("Default", ()) + } + + fn new( + self, + counter: Option, + dog_position: Option<(i32, i32)>, + ) -> PendingCtor { + self.pending_ctor("New", (counter, dog_position)) + } +} + +impl Demo for Actor { + type Env = E; + + fn counter(&self) -> Service { + self.service("Counter") + } +} + +/// Counter Service +pub trait Counter { + type Env: GearEnv; + + fn add(&mut self, value: u32) -> PendingCall; + fn sub(&mut self, value: u32) -> PendingCall; + fn value(&self) -> PendingCall; +} + +pub struct CounterImpl; + +impl Counter for Service { + type Env = E; + + fn add(&mut self, value: u32) -> PendingCall { + self.pending_call("Add", (value,)) + } + + fn sub(&mut self, value: u32) -> PendingCall { + self.pending_call("Sub", (value,)) + } + + fn value(&self) -> PendingCall { + self.pending_call("Value", ()) + } +} diff --git a/examples/demo/client/src/lib.rs b/examples/demo/client/src/lib.rs index 3eee8fdf2..340ada065 100644 --- a/examples/demo/client/src/lib.rs +++ b/examples/demo/client/src/lib.rs @@ -3,6 +3,8 @@ // Incorporate code generated based on the [IDL](/examples/demo/wasm/demo.idl) file include!("demo_client.rs"); +pub mod env_client; + #[cfg(test)] mod tests { use super::*; diff --git a/rs/Cargo.toml b/rs/Cargo.toml index ad6f9bbf6..03aa5b78f 100644 --- a/rs/Cargo.toml +++ b/rs/Cargo.toml @@ -43,6 +43,9 @@ gclient = { workspace = true, optional = true } gtest = { workspace = true, optional = true } log = { workspace = true, optional = true } +[dev-dependencies] +tokio = { workspace = true, features = ["rt", "macros"] } + [features] default = ["gstd"] build = ["client-builder", "wasm-builder"] @@ -62,3 +65,4 @@ client-builder = ["std", "idl-gen", "dep:sails-client-gen", "dep:convert_case"] mockall = ["std", "dep:mockall"] std = ["futures/std"] wasm-builder = ["dep:gwasm-builder"] + diff --git a/rs/src/client.rs b/rs/src/client.rs new file mode 100644 index 000000000..16680249d --- /dev/null +++ b/rs/src/client.rs @@ -0,0 +1,1020 @@ +use crate::prelude::*; +use core::{ + error::Error, + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, +}; + +pub trait GearEnv: Clone { + type Params: Default; + type Error: Error; + type MessageState; +} + +pin_project_lite::pin_project! { + pub struct PendingCall { + env: E, + destination: ActorId, + params: Option, + payload: Option>, + _output: PhantomData, + #[pin] + state: Option + } +} + +impl PendingCall { + pub fn new(destination: ActorId, env: E, payload: Vec) -> Self { + PendingCall { + env, + destination, + params: None, + payload: Some(payload), + _output: PhantomData, + state: None, + } + } + + pub fn with_params(mut self, f: impl FnOnce(E::Params) -> E::Params) -> Self { + self.params = Some(f(self.params.unwrap_or_default())); + self + } +} + +pin_project_lite::pin_project! { + pub struct PendingCtor { + env: E, + code_id: CodeId, + params: Option, + salt: Option>, + payload: Option>, + _actor: PhantomData, + #[pin] + state: Option, + program_id: Option, + } +} + +impl PendingCtor { + pub fn new(env: E, code_id: CodeId, salt: Vec, payload: Vec) -> Self { + PendingCtor { + env, + code_id, + params: None, + salt: Some(salt), + payload: Some(payload), + _actor: PhantomData, + state: None, + program_id: None, + } + } + + pub fn with_params(mut self, f: impl FnOnce(E::Params) -> E::Params) -> Self { + self.params = Some(f(self.params.unwrap_or_default())); + self + } +} + +#[cfg(feature = "gtest")] +#[cfg(not(target_arch = "wasm32"))] +mod mock { + use super::gtest::GtestError; + use super::*; + + #[derive(Default, Clone)] + pub struct MockEnv; + + #[derive(Default)] + pub struct MockParams; + + impl GearEnv for MockEnv { + type Error = GtestError; + type Params = MockParams; + type MessageState = core::future::Ready, Self::Error>>; + } + + impl PendingCall { + pub fn from_output(output: O) -> Self { + Self::from_result(Ok(output)) + } + + pub fn from_error(err: ::Error) -> Self { + Self::from_result(Err(err)) + } + + pub fn from_result(res: Result::Error>) -> Self { + PendingCall { + env: mock::MockEnv, + destination: ActorId::zero(), + params: None, + payload: None, + _output: PhantomData, + state: Some(future::ready(res.map(|v| v.encode()))), + } + } + } + + impl From for PendingCall { + fn from(value: O) -> Self { + PendingCall::from_output(value) + } + } + + impl Future for PendingCall { + type Output = Result::Error>; + + fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + match self.state.take() { + Some(ready) => { + let res = ready.into_inner(); + Poll::Ready(res.map(|v| O::decode(&mut v.as_ref()).unwrap())) + } + None => panic!("PendingCall polled after completion or invalid state"), + } + } + } +} + +#[cfg(feature = "gtest")] +#[cfg(not(target_arch = "wasm32"))] +pub mod gtest { + use super::*; + use ::gtest::{BlockRunResult, System}; + use core::cell::RefCell; + use futures::channel::{self}; + use hashbrown::HashMap; + use std::rc::Rc; + + #[derive(Debug, PartialEq)] + pub enum GtestError { + ProgramCreationFailed, + MessageSendingFailed, + ReplyTimeout, + } + + impl core::error::Error for GtestError {} + + impl core::fmt::Display for GtestError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + GtestError::ProgramCreationFailed => write!(f, "Program creation failed"), + GtestError::MessageSendingFailed => write!(f, "Message sending failed"), + GtestError::ReplyTimeout => write!(f, "Reply timeout"), + } + } + } + + #[derive(Debug, Default)] + pub struct GtestParams { + actor_id: Option, + gas_limit: Option, + value: Option, + } + + impl GtestParams { + // pub fn new(actor_id: ActorId) -> Self { + // Self { + // actor_id: Some(actor_id), + + // } + // } + + pub fn with_actor_id(self, actor_id: ActorId) -> Self { + Self { + actor_id: Some(actor_id), + ..self + } + } + + pub fn actor_id(&self) -> Option { + self.actor_id + } + } + + const GAS_LIMIT_DEFAULT: ::gtest::constants::Gas = ::gtest::constants::MAX_USER_GAS_LIMIT; + type EventSender = channel::mpsc::UnboundedSender<(ActorId, Vec)>; + type ReplySender = channel::oneshot::Sender, GtestError>>; + type ReplyReceiver = channel::oneshot::Receiver, GtestError>>; + + #[derive(Clone, Debug, PartialEq, Eq)] + pub enum BlockRunMode { + /// Run blocks automatically until all pending replies are received. + Auto, + /// Run only next block and exract events and replies from it. + /// If there is no reply in this block then `RtlError::ReplyIsMissing` error will be returned. + Next, + /// Sending messages does not cause blocks to run. + /// Use `GTestRemoting::run_next_block` method to run the next block and extract events and replies. + Manual, + } + + #[derive(Clone)] + pub struct GtestEnv { + system: Rc, + actor_id: ActorId, + event_senders: Rc>>, + block_run_mode: BlockRunMode, + block_reply_senders: Rc>>, + } + + impl GtestEnv { + /// Create new `GTestRemoting` instance from `gtest::System` with specified `actor_id` + /// and `Auto` block run mode + pub fn new(system: System, actor_id: ActorId) -> Self { + let system = Rc::new(system); + Self { + system, + actor_id, + event_senders: Default::default(), + block_run_mode: BlockRunMode::Auto, + block_reply_senders: Default::default(), + } + } + + // Avoid calling methods of `System` related to block execution. + // Use `GTestRemoting::run_next_block` instead. This method can be used + // for obtaining reference data like balance, timestamp, etc. + pub fn system(&self) -> &System { + &self.system + } + + pub fn with_block_run_mode(self, block_run_mode: BlockRunMode) -> Self { + Self { + block_run_mode, + ..self + } + } + + pub fn with_actor_id(self, actor_id: ActorId) -> Self { + Self { actor_id, ..self } + } + + pub fn actor_id(&self) -> ActorId { + self.actor_id + } + + pub fn run_next_block(&self) { + _ = self.run_next_block_and_extract(); + } + } + + impl GtestEnv { + fn extract_events_and_replies(&self, run_result: &BlockRunResult) { + log::debug!( + "Process block #{} run result, mode {:?}", + run_result.block_info.height, + &self.block_run_mode + ); + let mut event_senders = self.event_senders.borrow_mut(); + let mut reply_senders = self.block_reply_senders.borrow_mut(); + // remove closed event senders + event_senders.retain(|c| !c.is_closed()); + // iterate over log + for entry in run_result.log().iter() { + if entry.destination() == ActorId::zero() { + log::debug!("Extract event from entry {entry:?}"); + for sender in event_senders.iter() { + _ = sender.unbounded_send((entry.source(), entry.payload().to_vec())); + } + continue; + } + #[cfg(feature = "ethexe")] + if entry.destination() == crate::solidity::ETH_EVENT_ADDR { + log::debug!("Extract event from entry {:?}", entry); + for sender in event_senders.iter() { + _ = sender.unbounded_send((entry.source(), entry.payload().to_vec())); + } + continue; + } + if let Some(message_id) = entry.reply_to() { + if let Some(sender) = reply_senders.remove(&message_id) { + log::debug!("Extract reply from entry {entry:?}"); + let reply: result::Result, _> = match entry.reply_code() { + None => Err(GtestError::ReplyTimeout), + // TODO handle error reply + Some(ReplyCode::Error(reason)) => { + panic!("Unexpected error reply: {reason:?}") + } + Some(ReplyCode::Success(_)) => Ok(entry.payload().to_vec()), + _ => Err(GtestError::ReplyTimeout), + }; + _ = sender.send(reply); + } + } + } + } + + fn activate( + &self, + code_id: CodeId, + salt: impl AsRef<[u8]>, + payload: impl AsRef<[u8]>, + params: GtestParams, + ) -> Result<(ActorId, MessageId), GtestError> { + let value = params.value.unwrap_or(0); + #[cfg(not(feature = "ethexe"))] + let gas_limit = params.gas_limit.unwrap_or(GAS_LIMIT_DEFAULT); + #[cfg(feature = "ethexe")] + let gas_limit = GAS_LIMIT_DEFAULT; + let code = self + .system + .submitted_code(code_id) + .ok_or(GtestError::ProgramCreationFailed)?; + let program_id = ::gtest::calculate_program_id(code_id, salt.as_ref(), None); + let program = ::gtest::Program::from_binary_with_id(&self.system, program_id, code); + let actor_id = params.actor_id.unwrap_or(self.actor_id); + let message_id = + program.send_bytes_with_gas(actor_id, payload.as_ref(), gas_limit, value); + log::debug!("Send activation id: {message_id}, to program: {program_id}"); + Ok((program_id, message_id)) + } + + fn send_message( + &self, + target: ActorId, + payload: impl AsRef<[u8]>, + params: GtestParams, + ) -> Result { + let value = params.value.unwrap_or(0); + #[cfg(not(feature = "ethexe"))] + let gas_limit = params.gas_limit.unwrap_or(GAS_LIMIT_DEFAULT); + #[cfg(feature = "ethexe")] + let gas_limit = GAS_LIMIT_DEFAULT; + let program = self + .system + .get_program(target) + .ok_or(GtestError::ProgramCreationFailed)?; + let actor_id = params.actor_id.unwrap_or(self.actor_id); + let message_id = + program.send_bytes_with_gas(actor_id, payload.as_ref(), gas_limit, value); + log::debug!( + "Send message id: {message_id}, to: {target}, payload: {}", + hex::encode(payload.as_ref()) + ); + Ok(message_id) + } + + fn message_reply_from_next_blocks(&self, message_id: MessageId) -> ReplyReceiver { + let (tx, rx) = channel::oneshot::channel::, GtestError>>(); + self.block_reply_senders.borrow_mut().insert(message_id, tx); + + match self.block_run_mode { + BlockRunMode::Auto => { + self.run_until_extract_replies(); + } + BlockRunMode::Next => { + self.run_next_block_and_extract(); + self.drain_reply_senders(); + } + BlockRunMode::Manual => (), + }; + rx + } + + fn run_next_block_and_extract(&self) -> BlockRunResult { + let run_result = self.system.run_next_block(); + self.extract_events_and_replies(&run_result); + run_result + } + + fn run_until_extract_replies(&self) { + while !self.block_reply_senders.borrow().is_empty() { + self.run_next_block_and_extract(); + } + } + + fn drain_reply_senders(&self) { + let mut reply_senders = self.block_reply_senders.borrow_mut(); + // drain reply senders that not founded in block + for (message_id, sender) in reply_senders.drain() { + log::debug!("Reply is missing in block for message {message_id}"); + _ = sender.send(Err(GtestError::ReplyTimeout)); + } + } + } + + impl GearEnv for GtestEnv { + type Params = GtestParams; + type Error = GtestError; + type MessageState = ReplyReceiver; + } + + impl Future for PendingCall { + type Output = Result::Error>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.state.is_none() { + // Send message + let params = self.params.take().unwrap_or_default(); + let payload = self.payload.take().unwrap_or_default(); + let send_res = self.env.send_message(self.destination, payload, params); + match send_res { + Ok(message_id) => { + log::debug!("PendingCall: send message {message_id:?}"); + self.state = Some(self.env.message_reply_from_next_blocks(message_id)); + } + Err(err) => { + log::error!("PendingCall: failed to send message: {err}"); + return Poll::Ready(Err(err)); + } + } + } + if let Some(reply_receiver) = self.project().state.as_pin_mut() { + // Poll reply receiver + match reply_receiver.poll(cx) { + Poll::Ready(Ok(res)) => match res { + // TODO handle reply prefix + Ok(bytes) => match <(String, String, O)>::decode(&mut bytes.as_slice()) { + Ok((_, _, value)) => Poll::Ready(Ok(value)), + Err(err) => Poll::Ready(Err(GtestError::ReplyTimeout)), + }, + Err(err) => Poll::Ready(Err(err)), + }, + Poll::Ready(Err(_err)) => Poll::Ready(Err(GtestError::ReplyTimeout)), + Poll::Pending => Poll::Pending, + } + } else { + panic!("PendingCall polled after completion or invalid state"); + } + } + } + + impl Future for PendingCtor { + type Output = Result, ::Error>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.state.is_none() { + // Send message + let params = self.params.take().unwrap_or_default(); + let salt = self.salt.take().unwrap_or_default(); + let payload = self.payload.take().unwrap_or_default(); + let send_res = self.env.activate(self.code_id, salt, payload, params); + match send_res { + Ok((program_id, message_id)) => { + log::debug!("PendingCall: send message {message_id:?}"); + self.state = Some(self.env.message_reply_from_next_blocks(message_id)); + self.program_id = Some(program_id); + } + Err(err) => { + log::error!("PendingCall: failed to send message: {err}"); + return Poll::Ready(Err(err)); + } + } + } + let this = self.project(); + if let Some(reply_receiver) = this.state.as_pin_mut() { + // Poll reply receiver + match reply_receiver.poll(cx) { + Poll::Ready(Ok(res)) => match res { + // TODO handle reply prefix + Ok(_) => { + let program_id = this.program_id.unwrap(); + let env = this.env.clone(); + Poll::Ready(Ok(Actor::new(program_id, env))) + } + Err(err) => Poll::Ready(Err(err)), + }, + Poll::Ready(Err(_err)) => Poll::Ready(Err(GtestError::ReplyTimeout)), + Poll::Pending => Poll::Pending, + } + } else { + panic!("PendingCtor polled after completion or invalid state"); + } + } + } +} + +#[cfg(feature = "gclient")] +#[cfg(not(target_arch = "wasm32"))] +pub mod gclient { + use super::*; + use ::gclient::{Error, EventListener, EventProcessor as _, GearApi}; + + #[derive(Debug, Default)] + pub struct GclientParams { + gas_limit: Option, + value: Option, + at_block: Option, + } + + #[derive(Clone)] + pub struct GclientEnv { + api: GearApi, + } + + impl GclientEnv { + pub fn new(api: GearApi) -> Self { + Self { api } + } + + pub fn with_suri(self, suri: impl AsRef) -> Self { + let api = self.api.with(suri).unwrap(); + Self { api } + } + + async fn query_calculate_reply( + self, + target: ActorId, + payload: impl AsRef<[u8]>, + params: GclientParams, + ) -> Result, Error> { + let api = self.api; + + // Get Max gas amount if it is not explicitly set + #[cfg(not(feature = "ethexe"))] + let gas_limit = if let Some(gas_limit) = params.gas_limit { + gas_limit + } else { + api.block_gas_limit()? + }; + #[cfg(feature = "ethexe")] + let gas_limit = 0; + let value = params.value.unwrap_or(0); + let origin = H256::from_slice(api.account_id().as_ref()); + let payload = payload.as_ref().to_vec(); + + let reply_info = api + .calculate_reply_for_handle_at( + Some(origin), + target, + payload, + gas_limit, + value, + params.at_block, + ) + .await?; + + match reply_info.code { + ReplyCode::Success(_) => Ok(reply_info.payload), + // TODO + ReplyCode::Error(_reason) => Err(Error::EventNotFound), + ReplyCode::Unsupported => Err(Error::EventNotFound), + } + } + } + + impl GearEnv for GclientEnv { + type Params = GclientParams; + type Error = Error; + type MessageState = Pin, Error>>>>; + } + + async fn send_message( + api: GearApi, + target: ActorId, + payload: Vec, + params: GclientParams, + ) -> Result, Error> { + let value = params.value.unwrap_or(0); + #[cfg(not(feature = "ethexe"))] + let gas_limit = if let Some(gas_limit) = params.gas_limit { + gas_limit + } else { + // Calculate gas amount needed for handling the message + let gas_info = api + .calculate_handle_gas(None, target, payload.clone(), value, true) + .await?; + gas_info.min_limit + }; + #[cfg(feature = "ethexe")] + let gas_limit = 0; + + let mut listener = api.subscribe().await?; + let (message_id, ..) = api + .send_message_bytes(target, payload, gas_limit, value) + .await?; + let (_, reply_code, payload, _) = wait_for_reply(&mut listener, message_id).await?; + // TODO handle errors + match reply_code { + ReplyCode::Success(_) => Ok(payload), + ReplyCode::Error(error_reply_reason) => todo!(), + ReplyCode::Unsupported => todo!(), + } + } + + async fn wait_for_reply( + listener: &mut EventListener, + message_id: MessageId, + ) -> Result<(MessageId, ReplyCode, Vec, ValueUnit), Error> { + let message_id: ::gclient::metadata::runtime_types::gprimitives::MessageId = + message_id.into(); + listener.proc(|e| { + if let ::gclient::Event::Gear(::gclient::GearEvent::UserMessageSent { + message: + ::gclient::metadata::runtime_types::gear_core::message::user::UserMessage { + id, + payload, + value, + details: Some(::gclient::metadata::runtime_types::gear_core::message::common::ReplyDetails { to, code }), + .. + }, + .. + }) = e + { + to.eq(&message_id).then(|| { + let reply_code = ReplyCode::from(code); + + (id.into(), reply_code, payload.0.clone(), value) + }) + } else { + None + } + }) + .await + } + impl PendingCall { + async fn send(self) -> Result { + let api = &self.env.api; + let params = self.params.unwrap_or_default(); + let payload = self.payload.unwrap_or_default(); + let value = params.value.unwrap_or(0); + #[cfg(not(feature = "ethexe"))] + let gas_limit = if let Some(gas_limit) = params.gas_limit { + gas_limit + } else { + // Calculate gas amount needed for handling the message + let gas_info = api + .calculate_handle_gas(None, self.destination, payload.clone(), value, true) + .await?; + gas_info.min_limit + }; + #[cfg(feature = "ethexe")] + let gas_limit = 0; + + let (message_id, ..) = api + .send_message_bytes(self.destination, payload, gas_limit, value) + .await?; + Ok(message_id) + } + + async fn query(self) -> Result { + let params = self.params.unwrap_or_default(); + let payload = self.payload.unwrap_or_default(); + + // Calculate reply + let reply_bytes = self + .env + .query_calculate_reply(self.destination, payload, params) + .await?; + + // Decode reply + match O::decode(&mut reply_bytes.as_slice()) { + Ok(decoded) => Ok(decoded), + Err(err) => Err(Error::Codec(err)), + } + } + } + + impl Future for PendingCall { + type Output = Result::Error>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.state.is_none() { + // Send message + let params = self.params.take().unwrap_or_default(); + let payload = self.payload.take().unwrap_or_default(); + let send_future = + send_message(self.env.api.clone(), self.destination, payload, params); + self.state = Some(Box::pin(send_future)); + } + if let Some(message_future) = self.project().state.as_pin_mut() { + // Poll message future + match message_future.poll(cx) { + Poll::Ready(Ok(bytes)) => match O::decode(&mut bytes.as_slice()) { + Ok(decoded) => Poll::Ready(Ok(decoded)), + Err(err) => Poll::Ready(Err(Error::Codec(err))), + }, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Pending => Poll::Pending, + } + } else { + panic!("PendingCall polled after completion or invalid state"); + } + } + } +} + +mod gstd { + use super::*; + use ::gstd::errors::Error; + use ::gstd::msg; + use ::gstd::msg::MessageFuture; + + #[derive(Default)] + pub struct GstdParams { + #[cfg(not(feature = "ethexe"))] + gas_limit: Option, + value: Option, + wait_up_to: Option, + #[cfg(not(feature = "ethexe"))] + reply_deposit: Option, + #[cfg(not(feature = "ethexe"))] + reply_hook: Option>, + redirect_on_exit: bool, + } + + impl GstdParams { + pub fn with_wait_up_to(self, wait_up_to: Option) -> Self { + Self { wait_up_to, ..self } + } + + pub fn with_redirect_on_exit(self, redirect_on_exit: bool) -> Self { + Self { + redirect_on_exit, + ..self + } + } + + pub fn wait_up_to(&self) -> Option { + self.wait_up_to + } + + pub fn redirect_on_exit(&self) -> bool { + self.redirect_on_exit + } + } + + #[cfg(not(feature = "ethexe"))] + impl GstdParams { + pub fn with_reply_deposit(self, reply_deposit: Option) -> Self { + Self { + reply_deposit, + ..self + } + } + + pub fn with_reply_hook(self, f: F) -> Self { + Self { + reply_hook: Some(Box::new(f)), + ..self + } + } + + pub fn reply_deposit(&self) -> Option { + self.reply_deposit + } + } + + #[derive(Debug, Default, Clone)] + pub struct GstdEnv; + + impl GearEnv for GstdEnv { + type Params = GstdParams; + type Error = Error; + type MessageState = MessageFuture; + } + + #[cfg(not(feature = "ethexe"))] + pub(crate) fn send_for_reply_future( + target: ActorId, + payload: &[u8], + params: GstdParams, + ) -> Result { + let value = params.value.unwrap_or(0); + // here can be a redirect target + let mut message_future = if let Some(gas_limit) = params.gas_limit { + msg::send_bytes_with_gas_for_reply( + target, + payload, + gas_limit, + value, + params.reply_deposit.unwrap_or_default(), + )? + } else { + msg::send_bytes_for_reply( + target, + payload, + value, + params.reply_deposit.unwrap_or_default(), + )? + }; + + message_future = message_future.up_to(params.wait_up_to)?; + + if let Some(reply_hook) = params.reply_hook { + message_future = message_future.handle_reply(reply_hook)?; + } + Ok(message_future) + } + + #[cfg(feature = "ethexe")] + pub(crate) fn send_for_reply_future( + target: ActorId, + payload: &[u8], + args: GstdParams, + ) -> Result { + let value = params.value.unwrap_or(0); + // here can be a redirect target + let mut message_future = msg::send_bytes_for_reply(target, payload, value)?; + + message_future = message_future.up_to(params.wait_up_to)?; + + Ok(message_future) + } + + impl PendingCall { + pub fn send(self) -> Result { + let params = self.params.unwrap_or_default(); + let payload = self.payload.unwrap_or_default(); + let value = params.value.unwrap_or(0); + if let Some(gas_limit) = params.gas_limit { + ::gcore::msg::send_with_gas(self.destination, payload.as_slice(), gas_limit, value) + .map_err(|err| Error::Core(err)) + } else { + ::gcore::msg::send(self.destination, payload.as_slice(), value) + .map_err(|err| Error::Core(err)) + } + } + } + + impl Future for PendingCall { + type Output = Result::Error>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.state.is_none() { + // Send message + let params = self.params.take().unwrap_or_default(); + let payload = self.payload.take().unwrap_or_default(); + let send_res = send_for_reply_future(self.destination, payload.as_slice(), params); + match send_res { + Ok(message_fut) => { + self.state = Some(message_fut); + } + Err(err) => { + return Poll::Ready(Err(err)); + } + } + } + if let Some(message_fut) = self.project().state.as_pin_mut() { + // Poll message future + match message_fut.poll(cx) { + Poll::Ready(Ok(bytes)) => match O::decode(&mut bytes.as_slice()) { + Ok(decoded) => Poll::Ready(Ok(decoded)), + Err(err) => Poll::Ready(Err(Error::Decode(err))), + }, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Pending => Poll::Pending, + } + } else { + panic!("PendingCall polled after completion or invalid state"); + } + } + } +} + +mod service { + use super::*; + type Route = &'static str; + + pub struct Deployment { + env: E, + code_id: CodeId, + salt: Vec, + _phantom: PhantomData, + } + + impl Deployment { + pub fn new(env: E, code_id: CodeId, salt: Vec) -> Self { + Deployment { + env, + code_id, + salt, + _phantom: PhantomData, + } + } + + pub fn with_env(self, env: N) -> Deployment { + let Self { + env: _, + code_id, + salt, + _phantom: _, + } = self; + Deployment { + env, + code_id, + salt, + _phantom: PhantomData, + } + } + + pub fn pending_ctor(self, route: Route, args: T) -> PendingCtor { + let payload = (route, args).encode(); + + PendingCtor::new(self.env, self.code_id, self.salt, payload) + } + } + + pub struct Actor { + env: E, + id: ActorId, + _phantom: PhantomData, + } + + impl Actor { + pub fn new(id: ActorId, env: E) -> Self { + Actor { + env, + id, + _phantom: PhantomData, + } + } + + pub fn service(&self, route: Route) -> Service { + Service::new(self.id, route, self.env.clone()) + } + } + + pub struct Service { + env: E, + actor_id: ActorId, + route: Route, + _phantom: PhantomData, + } + + impl Service { + pub fn new(actor_id: ActorId, route: Route, env: E) -> Self { + Service { + env, + actor_id, + route, + _phantom: PhantomData, + } + } + + pub fn pending_call( + &self, + route: Route, + args: T, + ) -> PendingCall { + let payload = (self.route, route, args).encode(); + + PendingCall::new(self.actor_id, self.env.clone(), payload) + } + } +} +pub use service::{Actor, Deployment, Service}; + +mod client { + + use super::service::Service; + use super::*; + + pub struct MyServiceImpl; + + pub trait MyService { + fn mint(&mut self, to: ActorId, amount: u128) -> PendingCall; + fn burn(&mut self, from: ActorId) -> PendingCall; + fn total(&self) -> PendingCall; + } + + impl MyService for Service { + fn mint(&mut self, to: ActorId, amount: u128) -> PendingCall { + self.pending_call("Mint", (to, amount)) + } + + fn burn(&mut self, from: ActorId) -> PendingCall { + self.pending_call("Burn", (from,)) + } + + fn total(&self) -> PendingCall { + self.pending_call("Total", ()) + } + } + + #[cfg(feature = "mockall")] + #[cfg(not(target_arch = "wasm32"))] + mockall::mock! { + pub MyService {} + + impl MyService for MyService { + fn mint(&mut self, to: ActorId, amount: u128) -> PendingCall; + fn burn(&mut self, from: ActorId) -> PendingCall; + fn total(&self) -> PendingCall; + } + } +} + +#[cfg(feature = "mockall")] +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn sample() -> Result<(), Box> { + use client::*; + + let mut my_service = MockMyService::new(); + my_service.expect_total().returning(move || 137.into()); + my_service.expect_mint().returning(move |_, _| true.into()); + + assert_eq!(my_service.total().await?, 137); + + let mut my_service = my_service; + + assert!(my_service.mint(ActorId::from(137), 1_000).await?); + + Ok(()) + } +} diff --git a/rs/src/lib.rs b/rs/src/lib.rs index b04eb33cc..69355fc77 100644 --- a/rs/src/lib.rs +++ b/rs/src/lib.rs @@ -38,4 +38,6 @@ pub mod prelude; #[cfg(feature = "ethexe")] pub mod solidity; mod types; +#[cfg(feature = "gstd")] +pub mod client; mod utils; From a2c5015a53dff733dc25cf7e2aee5173540618e3 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Thu, 7 Aug 2025 20:02:52 +0200 Subject: [PATCH 02/66] wip: env impl --- examples/demo/app/tests/env_gtest.rs | 2 +- examples/demo/client/src/env_client.rs | 45 +- rs/src/client.rs | 1020 ------------------------ rs/src/client/gtest_env.rs | 364 +++++++++ rs/src/client/mock_env.rs | 54 ++ rs/src/client/mod.rs | 621 +++++++++++++++ rs/src/lib.rs | 1 - 7 files changed, 1077 insertions(+), 1030 deletions(-) delete mode 100644 rs/src/client.rs create mode 100644 rs/src/client/gtest_env.rs create mode 100644 rs/src/client/mock_env.rs create mode 100644 rs/src/client/mod.rs diff --git a/examples/demo/app/tests/env_gtest.rs b/examples/demo/app/tests/env_gtest.rs index 5a40f3ef8..1443f3908 100644 --- a/examples/demo/app/tests/env_gtest.rs +++ b/examples/demo/app/tests/env_gtest.rs @@ -1,6 +1,6 @@ use demo_client::env_client::{Counter as _, Demo as _, DemoCtors as _, DemoProgram}; use sails_rs::{ - client::gtest::{BlockRunMode, GtestEnv}, + client::gtest_env::{BlockRunMode, GtestEnv}, prelude::*, }; diff --git a/examples/demo/client/src/env_client.rs b/examples/demo/client/src/env_client.rs index 8826697f4..da92af7c8 100644 --- a/examples/demo/client/src/env_client.rs +++ b/examples/demo/client/src/env_client.rs @@ -6,12 +6,12 @@ use sails_rs::{ pub trait DemoCtors { type Env: GearEnv; - fn default(self) -> PendingCtor; + fn default(self) -> PendingCtor; fn new( self, counter: Option, dog_position: Option<(i32, i32)>, - ) -> PendingCtor; + ) -> PendingCtor; } pub trait Demo { @@ -27,7 +27,7 @@ impl DemoProgram { env: E, code_id: CodeId, salt: Vec, - ) -> Deployment { + ) -> Deployment { Deployment::new(env, code_id, salt) } @@ -36,19 +36,19 @@ impl DemoProgram { } } -impl DemoCtors for Deployment { +impl DemoCtors for Deployment { type Env = E; - fn default(self) -> PendingCtor { - self.pending_ctor("Default", ()) + fn default(self) -> PendingCtor { + self.pending_ctor(()) } fn new( self, counter: Option, dog_position: Option<(i32, i32)>, - ) -> PendingCtor { - self.pending_ctor("New", (counter, dog_position)) + ) -> PendingCtor { + self.pending_ctor((counter, dog_position)) } } @@ -60,6 +60,35 @@ impl Demo for Actor { } } +pub mod io { + use super::*; + use sails_rs::calls::ActionIo; + pub struct Default(()); + impl Default { + #[allow(dead_code)] + pub fn encode_call() -> Vec { + ::encode_call(&()) + } + } + impl ActionIo for Default { + const ROUTE: &'static [u8] = &[28, 68, 101, 102, 97, 117, 108, 116]; + type Params = (); + type Reply = (); + } + pub struct New(()); + impl New { + #[allow(dead_code)] + pub fn encode_call(counter: Option, dog_position: Option<(i32, i32)>) -> Vec { + ::encode_call(&(counter, dog_position)) + } + } + impl ActionIo for New { + const ROUTE: &'static [u8] = &[12, 78, 101, 119]; + type Params = (Option, Option<(i32, i32)>); + type Reply = (); + } +} + /// Counter Service pub trait Counter { type Env: GearEnv; diff --git a/rs/src/client.rs b/rs/src/client.rs deleted file mode 100644 index 16680249d..000000000 --- a/rs/src/client.rs +++ /dev/null @@ -1,1020 +0,0 @@ -use crate::prelude::*; -use core::{ - error::Error, - marker::PhantomData, - pin::Pin, - task::{Context, Poll}, -}; - -pub trait GearEnv: Clone { - type Params: Default; - type Error: Error; - type MessageState; -} - -pin_project_lite::pin_project! { - pub struct PendingCall { - env: E, - destination: ActorId, - params: Option, - payload: Option>, - _output: PhantomData, - #[pin] - state: Option - } -} - -impl PendingCall { - pub fn new(destination: ActorId, env: E, payload: Vec) -> Self { - PendingCall { - env, - destination, - params: None, - payload: Some(payload), - _output: PhantomData, - state: None, - } - } - - pub fn with_params(mut self, f: impl FnOnce(E::Params) -> E::Params) -> Self { - self.params = Some(f(self.params.unwrap_or_default())); - self - } -} - -pin_project_lite::pin_project! { - pub struct PendingCtor { - env: E, - code_id: CodeId, - params: Option, - salt: Option>, - payload: Option>, - _actor: PhantomData, - #[pin] - state: Option, - program_id: Option, - } -} - -impl PendingCtor { - pub fn new(env: E, code_id: CodeId, salt: Vec, payload: Vec) -> Self { - PendingCtor { - env, - code_id, - params: None, - salt: Some(salt), - payload: Some(payload), - _actor: PhantomData, - state: None, - program_id: None, - } - } - - pub fn with_params(mut self, f: impl FnOnce(E::Params) -> E::Params) -> Self { - self.params = Some(f(self.params.unwrap_or_default())); - self - } -} - -#[cfg(feature = "gtest")] -#[cfg(not(target_arch = "wasm32"))] -mod mock { - use super::gtest::GtestError; - use super::*; - - #[derive(Default, Clone)] - pub struct MockEnv; - - #[derive(Default)] - pub struct MockParams; - - impl GearEnv for MockEnv { - type Error = GtestError; - type Params = MockParams; - type MessageState = core::future::Ready, Self::Error>>; - } - - impl PendingCall { - pub fn from_output(output: O) -> Self { - Self::from_result(Ok(output)) - } - - pub fn from_error(err: ::Error) -> Self { - Self::from_result(Err(err)) - } - - pub fn from_result(res: Result::Error>) -> Self { - PendingCall { - env: mock::MockEnv, - destination: ActorId::zero(), - params: None, - payload: None, - _output: PhantomData, - state: Some(future::ready(res.map(|v| v.encode()))), - } - } - } - - impl From for PendingCall { - fn from(value: O) -> Self { - PendingCall::from_output(value) - } - } - - impl Future for PendingCall { - type Output = Result::Error>; - - fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { - match self.state.take() { - Some(ready) => { - let res = ready.into_inner(); - Poll::Ready(res.map(|v| O::decode(&mut v.as_ref()).unwrap())) - } - None => panic!("PendingCall polled after completion or invalid state"), - } - } - } -} - -#[cfg(feature = "gtest")] -#[cfg(not(target_arch = "wasm32"))] -pub mod gtest { - use super::*; - use ::gtest::{BlockRunResult, System}; - use core::cell::RefCell; - use futures::channel::{self}; - use hashbrown::HashMap; - use std::rc::Rc; - - #[derive(Debug, PartialEq)] - pub enum GtestError { - ProgramCreationFailed, - MessageSendingFailed, - ReplyTimeout, - } - - impl core::error::Error for GtestError {} - - impl core::fmt::Display for GtestError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - GtestError::ProgramCreationFailed => write!(f, "Program creation failed"), - GtestError::MessageSendingFailed => write!(f, "Message sending failed"), - GtestError::ReplyTimeout => write!(f, "Reply timeout"), - } - } - } - - #[derive(Debug, Default)] - pub struct GtestParams { - actor_id: Option, - gas_limit: Option, - value: Option, - } - - impl GtestParams { - // pub fn new(actor_id: ActorId) -> Self { - // Self { - // actor_id: Some(actor_id), - - // } - // } - - pub fn with_actor_id(self, actor_id: ActorId) -> Self { - Self { - actor_id: Some(actor_id), - ..self - } - } - - pub fn actor_id(&self) -> Option { - self.actor_id - } - } - - const GAS_LIMIT_DEFAULT: ::gtest::constants::Gas = ::gtest::constants::MAX_USER_GAS_LIMIT; - type EventSender = channel::mpsc::UnboundedSender<(ActorId, Vec)>; - type ReplySender = channel::oneshot::Sender, GtestError>>; - type ReplyReceiver = channel::oneshot::Receiver, GtestError>>; - - #[derive(Clone, Debug, PartialEq, Eq)] - pub enum BlockRunMode { - /// Run blocks automatically until all pending replies are received. - Auto, - /// Run only next block and exract events and replies from it. - /// If there is no reply in this block then `RtlError::ReplyIsMissing` error will be returned. - Next, - /// Sending messages does not cause blocks to run. - /// Use `GTestRemoting::run_next_block` method to run the next block and extract events and replies. - Manual, - } - - #[derive(Clone)] - pub struct GtestEnv { - system: Rc, - actor_id: ActorId, - event_senders: Rc>>, - block_run_mode: BlockRunMode, - block_reply_senders: Rc>>, - } - - impl GtestEnv { - /// Create new `GTestRemoting` instance from `gtest::System` with specified `actor_id` - /// and `Auto` block run mode - pub fn new(system: System, actor_id: ActorId) -> Self { - let system = Rc::new(system); - Self { - system, - actor_id, - event_senders: Default::default(), - block_run_mode: BlockRunMode::Auto, - block_reply_senders: Default::default(), - } - } - - // Avoid calling methods of `System` related to block execution. - // Use `GTestRemoting::run_next_block` instead. This method can be used - // for obtaining reference data like balance, timestamp, etc. - pub fn system(&self) -> &System { - &self.system - } - - pub fn with_block_run_mode(self, block_run_mode: BlockRunMode) -> Self { - Self { - block_run_mode, - ..self - } - } - - pub fn with_actor_id(self, actor_id: ActorId) -> Self { - Self { actor_id, ..self } - } - - pub fn actor_id(&self) -> ActorId { - self.actor_id - } - - pub fn run_next_block(&self) { - _ = self.run_next_block_and_extract(); - } - } - - impl GtestEnv { - fn extract_events_and_replies(&self, run_result: &BlockRunResult) { - log::debug!( - "Process block #{} run result, mode {:?}", - run_result.block_info.height, - &self.block_run_mode - ); - let mut event_senders = self.event_senders.borrow_mut(); - let mut reply_senders = self.block_reply_senders.borrow_mut(); - // remove closed event senders - event_senders.retain(|c| !c.is_closed()); - // iterate over log - for entry in run_result.log().iter() { - if entry.destination() == ActorId::zero() { - log::debug!("Extract event from entry {entry:?}"); - for sender in event_senders.iter() { - _ = sender.unbounded_send((entry.source(), entry.payload().to_vec())); - } - continue; - } - #[cfg(feature = "ethexe")] - if entry.destination() == crate::solidity::ETH_EVENT_ADDR { - log::debug!("Extract event from entry {:?}", entry); - for sender in event_senders.iter() { - _ = sender.unbounded_send((entry.source(), entry.payload().to_vec())); - } - continue; - } - if let Some(message_id) = entry.reply_to() { - if let Some(sender) = reply_senders.remove(&message_id) { - log::debug!("Extract reply from entry {entry:?}"); - let reply: result::Result, _> = match entry.reply_code() { - None => Err(GtestError::ReplyTimeout), - // TODO handle error reply - Some(ReplyCode::Error(reason)) => { - panic!("Unexpected error reply: {reason:?}") - } - Some(ReplyCode::Success(_)) => Ok(entry.payload().to_vec()), - _ => Err(GtestError::ReplyTimeout), - }; - _ = sender.send(reply); - } - } - } - } - - fn activate( - &self, - code_id: CodeId, - salt: impl AsRef<[u8]>, - payload: impl AsRef<[u8]>, - params: GtestParams, - ) -> Result<(ActorId, MessageId), GtestError> { - let value = params.value.unwrap_or(0); - #[cfg(not(feature = "ethexe"))] - let gas_limit = params.gas_limit.unwrap_or(GAS_LIMIT_DEFAULT); - #[cfg(feature = "ethexe")] - let gas_limit = GAS_LIMIT_DEFAULT; - let code = self - .system - .submitted_code(code_id) - .ok_or(GtestError::ProgramCreationFailed)?; - let program_id = ::gtest::calculate_program_id(code_id, salt.as_ref(), None); - let program = ::gtest::Program::from_binary_with_id(&self.system, program_id, code); - let actor_id = params.actor_id.unwrap_or(self.actor_id); - let message_id = - program.send_bytes_with_gas(actor_id, payload.as_ref(), gas_limit, value); - log::debug!("Send activation id: {message_id}, to program: {program_id}"); - Ok((program_id, message_id)) - } - - fn send_message( - &self, - target: ActorId, - payload: impl AsRef<[u8]>, - params: GtestParams, - ) -> Result { - let value = params.value.unwrap_or(0); - #[cfg(not(feature = "ethexe"))] - let gas_limit = params.gas_limit.unwrap_or(GAS_LIMIT_DEFAULT); - #[cfg(feature = "ethexe")] - let gas_limit = GAS_LIMIT_DEFAULT; - let program = self - .system - .get_program(target) - .ok_or(GtestError::ProgramCreationFailed)?; - let actor_id = params.actor_id.unwrap_or(self.actor_id); - let message_id = - program.send_bytes_with_gas(actor_id, payload.as_ref(), gas_limit, value); - log::debug!( - "Send message id: {message_id}, to: {target}, payload: {}", - hex::encode(payload.as_ref()) - ); - Ok(message_id) - } - - fn message_reply_from_next_blocks(&self, message_id: MessageId) -> ReplyReceiver { - let (tx, rx) = channel::oneshot::channel::, GtestError>>(); - self.block_reply_senders.borrow_mut().insert(message_id, tx); - - match self.block_run_mode { - BlockRunMode::Auto => { - self.run_until_extract_replies(); - } - BlockRunMode::Next => { - self.run_next_block_and_extract(); - self.drain_reply_senders(); - } - BlockRunMode::Manual => (), - }; - rx - } - - fn run_next_block_and_extract(&self) -> BlockRunResult { - let run_result = self.system.run_next_block(); - self.extract_events_and_replies(&run_result); - run_result - } - - fn run_until_extract_replies(&self) { - while !self.block_reply_senders.borrow().is_empty() { - self.run_next_block_and_extract(); - } - } - - fn drain_reply_senders(&self) { - let mut reply_senders = self.block_reply_senders.borrow_mut(); - // drain reply senders that not founded in block - for (message_id, sender) in reply_senders.drain() { - log::debug!("Reply is missing in block for message {message_id}"); - _ = sender.send(Err(GtestError::ReplyTimeout)); - } - } - } - - impl GearEnv for GtestEnv { - type Params = GtestParams; - type Error = GtestError; - type MessageState = ReplyReceiver; - } - - impl Future for PendingCall { - type Output = Result::Error>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if self.state.is_none() { - // Send message - let params = self.params.take().unwrap_or_default(); - let payload = self.payload.take().unwrap_or_default(); - let send_res = self.env.send_message(self.destination, payload, params); - match send_res { - Ok(message_id) => { - log::debug!("PendingCall: send message {message_id:?}"); - self.state = Some(self.env.message_reply_from_next_blocks(message_id)); - } - Err(err) => { - log::error!("PendingCall: failed to send message: {err}"); - return Poll::Ready(Err(err)); - } - } - } - if let Some(reply_receiver) = self.project().state.as_pin_mut() { - // Poll reply receiver - match reply_receiver.poll(cx) { - Poll::Ready(Ok(res)) => match res { - // TODO handle reply prefix - Ok(bytes) => match <(String, String, O)>::decode(&mut bytes.as_slice()) { - Ok((_, _, value)) => Poll::Ready(Ok(value)), - Err(err) => Poll::Ready(Err(GtestError::ReplyTimeout)), - }, - Err(err) => Poll::Ready(Err(err)), - }, - Poll::Ready(Err(_err)) => Poll::Ready(Err(GtestError::ReplyTimeout)), - Poll::Pending => Poll::Pending, - } - } else { - panic!("PendingCall polled after completion or invalid state"); - } - } - } - - impl Future for PendingCtor { - type Output = Result, ::Error>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if self.state.is_none() { - // Send message - let params = self.params.take().unwrap_or_default(); - let salt = self.salt.take().unwrap_or_default(); - let payload = self.payload.take().unwrap_or_default(); - let send_res = self.env.activate(self.code_id, salt, payload, params); - match send_res { - Ok((program_id, message_id)) => { - log::debug!("PendingCall: send message {message_id:?}"); - self.state = Some(self.env.message_reply_from_next_blocks(message_id)); - self.program_id = Some(program_id); - } - Err(err) => { - log::error!("PendingCall: failed to send message: {err}"); - return Poll::Ready(Err(err)); - } - } - } - let this = self.project(); - if let Some(reply_receiver) = this.state.as_pin_mut() { - // Poll reply receiver - match reply_receiver.poll(cx) { - Poll::Ready(Ok(res)) => match res { - // TODO handle reply prefix - Ok(_) => { - let program_id = this.program_id.unwrap(); - let env = this.env.clone(); - Poll::Ready(Ok(Actor::new(program_id, env))) - } - Err(err) => Poll::Ready(Err(err)), - }, - Poll::Ready(Err(_err)) => Poll::Ready(Err(GtestError::ReplyTimeout)), - Poll::Pending => Poll::Pending, - } - } else { - panic!("PendingCtor polled after completion or invalid state"); - } - } - } -} - -#[cfg(feature = "gclient")] -#[cfg(not(target_arch = "wasm32"))] -pub mod gclient { - use super::*; - use ::gclient::{Error, EventListener, EventProcessor as _, GearApi}; - - #[derive(Debug, Default)] - pub struct GclientParams { - gas_limit: Option, - value: Option, - at_block: Option, - } - - #[derive(Clone)] - pub struct GclientEnv { - api: GearApi, - } - - impl GclientEnv { - pub fn new(api: GearApi) -> Self { - Self { api } - } - - pub fn with_suri(self, suri: impl AsRef) -> Self { - let api = self.api.with(suri).unwrap(); - Self { api } - } - - async fn query_calculate_reply( - self, - target: ActorId, - payload: impl AsRef<[u8]>, - params: GclientParams, - ) -> Result, Error> { - let api = self.api; - - // Get Max gas amount if it is not explicitly set - #[cfg(not(feature = "ethexe"))] - let gas_limit = if let Some(gas_limit) = params.gas_limit { - gas_limit - } else { - api.block_gas_limit()? - }; - #[cfg(feature = "ethexe")] - let gas_limit = 0; - let value = params.value.unwrap_or(0); - let origin = H256::from_slice(api.account_id().as_ref()); - let payload = payload.as_ref().to_vec(); - - let reply_info = api - .calculate_reply_for_handle_at( - Some(origin), - target, - payload, - gas_limit, - value, - params.at_block, - ) - .await?; - - match reply_info.code { - ReplyCode::Success(_) => Ok(reply_info.payload), - // TODO - ReplyCode::Error(_reason) => Err(Error::EventNotFound), - ReplyCode::Unsupported => Err(Error::EventNotFound), - } - } - } - - impl GearEnv for GclientEnv { - type Params = GclientParams; - type Error = Error; - type MessageState = Pin, Error>>>>; - } - - async fn send_message( - api: GearApi, - target: ActorId, - payload: Vec, - params: GclientParams, - ) -> Result, Error> { - let value = params.value.unwrap_or(0); - #[cfg(not(feature = "ethexe"))] - let gas_limit = if let Some(gas_limit) = params.gas_limit { - gas_limit - } else { - // Calculate gas amount needed for handling the message - let gas_info = api - .calculate_handle_gas(None, target, payload.clone(), value, true) - .await?; - gas_info.min_limit - }; - #[cfg(feature = "ethexe")] - let gas_limit = 0; - - let mut listener = api.subscribe().await?; - let (message_id, ..) = api - .send_message_bytes(target, payload, gas_limit, value) - .await?; - let (_, reply_code, payload, _) = wait_for_reply(&mut listener, message_id).await?; - // TODO handle errors - match reply_code { - ReplyCode::Success(_) => Ok(payload), - ReplyCode::Error(error_reply_reason) => todo!(), - ReplyCode::Unsupported => todo!(), - } - } - - async fn wait_for_reply( - listener: &mut EventListener, - message_id: MessageId, - ) -> Result<(MessageId, ReplyCode, Vec, ValueUnit), Error> { - let message_id: ::gclient::metadata::runtime_types::gprimitives::MessageId = - message_id.into(); - listener.proc(|e| { - if let ::gclient::Event::Gear(::gclient::GearEvent::UserMessageSent { - message: - ::gclient::metadata::runtime_types::gear_core::message::user::UserMessage { - id, - payload, - value, - details: Some(::gclient::metadata::runtime_types::gear_core::message::common::ReplyDetails { to, code }), - .. - }, - .. - }) = e - { - to.eq(&message_id).then(|| { - let reply_code = ReplyCode::from(code); - - (id.into(), reply_code, payload.0.clone(), value) - }) - } else { - None - } - }) - .await - } - impl PendingCall { - async fn send(self) -> Result { - let api = &self.env.api; - let params = self.params.unwrap_or_default(); - let payload = self.payload.unwrap_or_default(); - let value = params.value.unwrap_or(0); - #[cfg(not(feature = "ethexe"))] - let gas_limit = if let Some(gas_limit) = params.gas_limit { - gas_limit - } else { - // Calculate gas amount needed for handling the message - let gas_info = api - .calculate_handle_gas(None, self.destination, payload.clone(), value, true) - .await?; - gas_info.min_limit - }; - #[cfg(feature = "ethexe")] - let gas_limit = 0; - - let (message_id, ..) = api - .send_message_bytes(self.destination, payload, gas_limit, value) - .await?; - Ok(message_id) - } - - async fn query(self) -> Result { - let params = self.params.unwrap_or_default(); - let payload = self.payload.unwrap_or_default(); - - // Calculate reply - let reply_bytes = self - .env - .query_calculate_reply(self.destination, payload, params) - .await?; - - // Decode reply - match O::decode(&mut reply_bytes.as_slice()) { - Ok(decoded) => Ok(decoded), - Err(err) => Err(Error::Codec(err)), - } - } - } - - impl Future for PendingCall { - type Output = Result::Error>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if self.state.is_none() { - // Send message - let params = self.params.take().unwrap_or_default(); - let payload = self.payload.take().unwrap_or_default(); - let send_future = - send_message(self.env.api.clone(), self.destination, payload, params); - self.state = Some(Box::pin(send_future)); - } - if let Some(message_future) = self.project().state.as_pin_mut() { - // Poll message future - match message_future.poll(cx) { - Poll::Ready(Ok(bytes)) => match O::decode(&mut bytes.as_slice()) { - Ok(decoded) => Poll::Ready(Ok(decoded)), - Err(err) => Poll::Ready(Err(Error::Codec(err))), - }, - Poll::Ready(Err(err)) => Poll::Ready(Err(err)), - Poll::Pending => Poll::Pending, - } - } else { - panic!("PendingCall polled after completion or invalid state"); - } - } - } -} - -mod gstd { - use super::*; - use ::gstd::errors::Error; - use ::gstd::msg; - use ::gstd::msg::MessageFuture; - - #[derive(Default)] - pub struct GstdParams { - #[cfg(not(feature = "ethexe"))] - gas_limit: Option, - value: Option, - wait_up_to: Option, - #[cfg(not(feature = "ethexe"))] - reply_deposit: Option, - #[cfg(not(feature = "ethexe"))] - reply_hook: Option>, - redirect_on_exit: bool, - } - - impl GstdParams { - pub fn with_wait_up_to(self, wait_up_to: Option) -> Self { - Self { wait_up_to, ..self } - } - - pub fn with_redirect_on_exit(self, redirect_on_exit: bool) -> Self { - Self { - redirect_on_exit, - ..self - } - } - - pub fn wait_up_to(&self) -> Option { - self.wait_up_to - } - - pub fn redirect_on_exit(&self) -> bool { - self.redirect_on_exit - } - } - - #[cfg(not(feature = "ethexe"))] - impl GstdParams { - pub fn with_reply_deposit(self, reply_deposit: Option) -> Self { - Self { - reply_deposit, - ..self - } - } - - pub fn with_reply_hook(self, f: F) -> Self { - Self { - reply_hook: Some(Box::new(f)), - ..self - } - } - - pub fn reply_deposit(&self) -> Option { - self.reply_deposit - } - } - - #[derive(Debug, Default, Clone)] - pub struct GstdEnv; - - impl GearEnv for GstdEnv { - type Params = GstdParams; - type Error = Error; - type MessageState = MessageFuture; - } - - #[cfg(not(feature = "ethexe"))] - pub(crate) fn send_for_reply_future( - target: ActorId, - payload: &[u8], - params: GstdParams, - ) -> Result { - let value = params.value.unwrap_or(0); - // here can be a redirect target - let mut message_future = if let Some(gas_limit) = params.gas_limit { - msg::send_bytes_with_gas_for_reply( - target, - payload, - gas_limit, - value, - params.reply_deposit.unwrap_or_default(), - )? - } else { - msg::send_bytes_for_reply( - target, - payload, - value, - params.reply_deposit.unwrap_or_default(), - )? - }; - - message_future = message_future.up_to(params.wait_up_to)?; - - if let Some(reply_hook) = params.reply_hook { - message_future = message_future.handle_reply(reply_hook)?; - } - Ok(message_future) - } - - #[cfg(feature = "ethexe")] - pub(crate) fn send_for_reply_future( - target: ActorId, - payload: &[u8], - args: GstdParams, - ) -> Result { - let value = params.value.unwrap_or(0); - // here can be a redirect target - let mut message_future = msg::send_bytes_for_reply(target, payload, value)?; - - message_future = message_future.up_to(params.wait_up_to)?; - - Ok(message_future) - } - - impl PendingCall { - pub fn send(self) -> Result { - let params = self.params.unwrap_or_default(); - let payload = self.payload.unwrap_or_default(); - let value = params.value.unwrap_or(0); - if let Some(gas_limit) = params.gas_limit { - ::gcore::msg::send_with_gas(self.destination, payload.as_slice(), gas_limit, value) - .map_err(|err| Error::Core(err)) - } else { - ::gcore::msg::send(self.destination, payload.as_slice(), value) - .map_err(|err| Error::Core(err)) - } - } - } - - impl Future for PendingCall { - type Output = Result::Error>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if self.state.is_none() { - // Send message - let params = self.params.take().unwrap_or_default(); - let payload = self.payload.take().unwrap_or_default(); - let send_res = send_for_reply_future(self.destination, payload.as_slice(), params); - match send_res { - Ok(message_fut) => { - self.state = Some(message_fut); - } - Err(err) => { - return Poll::Ready(Err(err)); - } - } - } - if let Some(message_fut) = self.project().state.as_pin_mut() { - // Poll message future - match message_fut.poll(cx) { - Poll::Ready(Ok(bytes)) => match O::decode(&mut bytes.as_slice()) { - Ok(decoded) => Poll::Ready(Ok(decoded)), - Err(err) => Poll::Ready(Err(Error::Decode(err))), - }, - Poll::Ready(Err(err)) => Poll::Ready(Err(err)), - Poll::Pending => Poll::Pending, - } - } else { - panic!("PendingCall polled after completion or invalid state"); - } - } - } -} - -mod service { - use super::*; - type Route = &'static str; - - pub struct Deployment { - env: E, - code_id: CodeId, - salt: Vec, - _phantom: PhantomData, - } - - impl Deployment { - pub fn new(env: E, code_id: CodeId, salt: Vec) -> Self { - Deployment { - env, - code_id, - salt, - _phantom: PhantomData, - } - } - - pub fn with_env(self, env: N) -> Deployment { - let Self { - env: _, - code_id, - salt, - _phantom: _, - } = self; - Deployment { - env, - code_id, - salt, - _phantom: PhantomData, - } - } - - pub fn pending_ctor(self, route: Route, args: T) -> PendingCtor { - let payload = (route, args).encode(); - - PendingCtor::new(self.env, self.code_id, self.salt, payload) - } - } - - pub struct Actor { - env: E, - id: ActorId, - _phantom: PhantomData, - } - - impl Actor { - pub fn new(id: ActorId, env: E) -> Self { - Actor { - env, - id, - _phantom: PhantomData, - } - } - - pub fn service(&self, route: Route) -> Service { - Service::new(self.id, route, self.env.clone()) - } - } - - pub struct Service { - env: E, - actor_id: ActorId, - route: Route, - _phantom: PhantomData, - } - - impl Service { - pub fn new(actor_id: ActorId, route: Route, env: E) -> Self { - Service { - env, - actor_id, - route, - _phantom: PhantomData, - } - } - - pub fn pending_call( - &self, - route: Route, - args: T, - ) -> PendingCall { - let payload = (self.route, route, args).encode(); - - PendingCall::new(self.actor_id, self.env.clone(), payload) - } - } -} -pub use service::{Actor, Deployment, Service}; - -mod client { - - use super::service::Service; - use super::*; - - pub struct MyServiceImpl; - - pub trait MyService { - fn mint(&mut self, to: ActorId, amount: u128) -> PendingCall; - fn burn(&mut self, from: ActorId) -> PendingCall; - fn total(&self) -> PendingCall; - } - - impl MyService for Service { - fn mint(&mut self, to: ActorId, amount: u128) -> PendingCall { - self.pending_call("Mint", (to, amount)) - } - - fn burn(&mut self, from: ActorId) -> PendingCall { - self.pending_call("Burn", (from,)) - } - - fn total(&self) -> PendingCall { - self.pending_call("Total", ()) - } - } - - #[cfg(feature = "mockall")] - #[cfg(not(target_arch = "wasm32"))] - mockall::mock! { - pub MyService {} - - impl MyService for MyService { - fn mint(&mut self, to: ActorId, amount: u128) -> PendingCall; - fn burn(&mut self, from: ActorId) -> PendingCall; - fn total(&self) -> PendingCall; - } - } -} - -#[cfg(feature = "mockall")] -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn sample() -> Result<(), Box> { - use client::*; - - let mut my_service = MockMyService::new(); - my_service.expect_total().returning(move || 137.into()); - my_service.expect_mint().returning(move |_, _| true.into()); - - assert_eq!(my_service.total().await?, 137); - - let mut my_service = my_service; - - assert!(my_service.mint(ActorId::from(137), 1_000).await?); - - Ok(()) - } -} diff --git a/rs/src/client/gtest_env.rs b/rs/src/client/gtest_env.rs new file mode 100644 index 000000000..daff1ac19 --- /dev/null +++ b/rs/src/client/gtest_env.rs @@ -0,0 +1,364 @@ +use super::*; +use ::gtest::{BlockRunResult, System, TestError}; +use core::cell::RefCell; +use futures::channel::{mpsc, oneshot}; +use hashbrown::HashMap; +use std::rc::Rc; + +const GAS_LIMIT_DEFAULT: ::gtest::constants::Gas = ::gtest::constants::MAX_USER_GAS_LIMIT; +type Error = TestError; +type EventSender = mpsc::UnboundedSender<(ActorId, Vec)>; +type ReplySender = oneshot::Sender, Error>>; +type ReplyReceiver = oneshot::Receiver, Error>>; + +#[derive(Debug, Default)] +pub struct GtestParams { + actor_id: Option, + #[cfg(not(feature = "ethexe"))] + gas_limit: Option, + value: ValueUnit, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum BlockRunMode { + /// Run blocks automatically until all pending replies are received. + Auto, + /// Run only next block and exract events and replies from it. + /// If there is no reply in this block then `RtlError::ReplyIsMissing` error will be returned. + Next, + /// Sending messages does not cause blocks to run. + /// Use `GTestRemoting::run_next_block` method to run the next block and extract events and replies. + Manual, +} + +#[derive(Clone)] +pub struct GtestEnv { + system: Rc, + actor_id: ActorId, + event_senders: Rc>>, + block_run_mode: BlockRunMode, + block_reply_senders: Rc>>, +} + +impl GtestEnv { + /// Create new `GTestRemoting` instance from `gtest::System` with specified `actor_id` + /// and `Auto` block run mode + pub fn new(system: System, actor_id: ActorId) -> Self { + let system = Rc::new(system); + Self { + system, + actor_id, + event_senders: Default::default(), + block_run_mode: BlockRunMode::Auto, + block_reply_senders: Default::default(), + } + } + + // Avoid calling methods of `System` related to block execution. + // Use `GTestRemoting::run_next_block` instead. This method can be used + // for obtaining reference data like balance, timestamp, etc. + pub fn system(&self) -> &System { + &self.system + } + + pub fn with_block_run_mode(self, block_run_mode: BlockRunMode) -> Self { + Self { + block_run_mode, + ..self + } + } + + pub fn with_actor_id(self, actor_id: ActorId) -> Self { + Self { actor_id, ..self } + } + + pub fn actor_id(&self) -> ActorId { + self.actor_id + } + + pub fn run_next_block(&self) { + _ = self.run_next_block_and_extract(); + } +} + +impl GtestEnv { + fn extract_events_and_replies(&self, run_result: &BlockRunResult) { + log::debug!( + "Process block #{} run result, mode {:?}", + run_result.block_info.height, + &self.block_run_mode + ); + let mut event_senders = self.event_senders.borrow_mut(); + let mut reply_senders = self.block_reply_senders.borrow_mut(); + // remove closed event senders + event_senders.retain(|c| !c.is_closed()); + // iterate over log + for entry in run_result.log().iter() { + if entry.destination() == ActorId::zero() { + log::debug!("Extract event from entry {entry:?}"); + for sender in event_senders.iter() { + _ = sender.unbounded_send((entry.source(), entry.payload().to_vec())); + } + continue; + } + #[cfg(feature = "ethexe")] + if entry.destination() == crate::solidity::ETH_EVENT_ADDR { + log::debug!("Extract event from entry {:?}", entry); + for sender in event_senders.iter() { + _ = sender.unbounded_send((entry.source(), entry.payload().to_vec())); + } + continue; + } + if let Some(message_id) = entry.reply_to() { + if let Some(sender) = reply_senders.remove(&message_id) { + log::debug!("Extract reply from entry {entry:?}"); + let reply: result::Result, _> = match entry.reply_code() { + None => Err(Error::InvalidReturnType), + // TODO handle error reply + Some(ReplyCode::Error(reason)) => { + panic!("Unexpected error reply: {reason:?}") + } + Some(ReplyCode::Success(_)) => Ok(entry.payload().to_vec()), + _ => Err(Error::InvalidReturnType), + }; + _ = sender.send(reply); + } + } + } + } + + fn activate( + &self, + code_id: CodeId, + salt: impl AsRef<[u8]>, + payload: impl AsRef<[u8]>, + params: GtestParams, + ) -> Result<(ActorId, MessageId), Error> { + #[cfg(not(feature = "ethexe"))] + let gas_limit = params.gas_limit.unwrap_or(GAS_LIMIT_DEFAULT); + #[cfg(feature = "ethexe")] + let gas_limit = GAS_LIMIT_DEFAULT; + let code = self + .system + .submitted_code(code_id) + // TODO Errors + .ok_or(Error::Instrumentation)?; + let program_id = ::gtest::calculate_program_id(code_id, salt.as_ref(), None); + let program = ::gtest::Program::from_binary_with_id(&self.system, program_id, code); + let actor_id = params.actor_id.unwrap_or(self.actor_id); + let message_id = + program.send_bytes_with_gas(actor_id, payload.as_ref(), gas_limit, params.value); + log::debug!("Send activation id: {message_id}, to program: {program_id}"); + Ok((program_id, message_id)) + } + + fn send_message( + &self, + target: ActorId, + payload: impl AsRef<[u8]>, + params: GtestParams, + ) -> Result { + #[cfg(not(feature = "ethexe"))] + let gas_limit = params.gas_limit.unwrap_or(GAS_LIMIT_DEFAULT); + #[cfg(feature = "ethexe")] + let gas_limit = GAS_LIMIT_DEFAULT; + let program = self + .system + .get_program(target) + // TODO Errors + .ok_or(Error::Instrumentation)?; + let actor_id = params.actor_id.unwrap_or(self.actor_id); + let message_id = + program.send_bytes_with_gas(actor_id, payload.as_ref(), gas_limit, params.value); + log::debug!( + "Send message id: {message_id}, to: {target}, payload: {}", + hex::encode(payload.as_ref()) + ); + Ok(message_id) + } + + fn message_reply_from_next_blocks(&self, message_id: MessageId) -> ReplyReceiver { + let (tx, rx) = oneshot::channel::, Error>>(); + self.block_reply_senders.borrow_mut().insert(message_id, tx); + + match self.block_run_mode { + BlockRunMode::Auto => { + self.run_until_extract_replies(); + } + BlockRunMode::Next => { + self.run_next_block_and_extract(); + self.drain_reply_senders(); + } + BlockRunMode::Manual => (), + }; + rx + } + + fn run_next_block_and_extract(&self) -> BlockRunResult { + let run_result = self.system.run_next_block(); + self.extract_events_and_replies(&run_result); + run_result + } + + fn run_until_extract_replies(&self) { + while !self.block_reply_senders.borrow().is_empty() { + self.run_next_block_and_extract(); + } + } + + fn drain_reply_senders(&self) { + let mut reply_senders = self.block_reply_senders.borrow_mut(); + // drain reply senders that not founded in block + for (message_id, sender) in reply_senders.drain() { + log::debug!("Reply is missing in block for message {message_id}"); + // TODO handle error + _ = sender.send(Err(Error::UnsupportedFunction( + "Reply is missing in block for message {message_id}".into(), + ))); + } + } +} + +impl GearEnv for GtestEnv { + type Params = GtestParams; + type Error = Error; + type MessageState = ReplyReceiver; +} + +impl Future for PendingCall { + type Output = Result::Error>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.state.is_none() { + // Send message + let params = self.params.take().unwrap_or_default(); + let payload = self.payload.take().unwrap_or_default(); + let send_res = self.env.send_message(self.destination, payload, params); + match send_res { + Ok(message_id) => { + log::debug!("PendingCall: send message {message_id:?}"); + self.state = Some(self.env.message_reply_from_next_blocks(message_id)); + } + Err(err) => { + log::error!("PendingCall: failed to send message: {err}"); + return Poll::Ready(Err(err)); + } + } + } + if let Some(reply_receiver) = self.project().state.as_pin_mut() { + // Poll reply receiver + match reply_receiver.poll(cx) { + Poll::Ready(Ok(res)) => match res { + // TODO handle reply prefix + Ok(bytes) => match <(String, String, O)>::decode(&mut bytes.as_slice()) { + Ok((_, _, decoded)) => Poll::Ready(Ok(decoded)), + Err(err) => Poll::Ready(Err(Error::ScaleCodecError(err))), + }, + Err(err) => Poll::Ready(Err(err)), + }, + // TODO handle error + Poll::Ready(Err(_err)) => Poll::Ready(Err(Error::InvalidReturnType)), + Poll::Pending => Poll::Pending, + } + } else { + panic!("PendingCall polled after completion or invalid state"); + } + } +} + +impl Future for PendingCtor { + type Output = Result, ::Error>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.state.is_none() { + // Send message + let params = self.params.take().unwrap_or_default(); + let salt = self.salt.take().unwrap_or_default(); + let args = self + .args + .take() + .unwrap_or_else(|| panic!("PendingCtor polled after completion or invalid state")); + let payload = T::encode_call(&args); + let send_res = self + .env + .activate(self.code_id, salt, payload.as_slice(), params); + match send_res { + Ok((program_id, message_id)) => { + log::debug!("PendingCall: send message {message_id:?}"); + self.state = Some(self.env.message_reply_from_next_blocks(message_id)); + self.program_id = Some(program_id); + } + Err(err) => { + log::error!("PendingCall: failed to send message: {err}"); + return Poll::Ready(Err(err)); + } + } + } + let this = self.project(); + if let Some(reply_receiver) = this.state.as_pin_mut() { + // Poll reply receiver + match reply_receiver.poll(cx) { + Poll::Ready(Ok(res)) => match res { + // TODO handle reply prefix + Ok(_) => { + let program_id = this.program_id.unwrap(); + let env = this.env.clone(); + Poll::Ready(Ok(Actor::new(program_id, env))) + } + Err(err) => Poll::Ready(Err(err)), + }, + // TODO handle error + Poll::Ready(Err(_err)) => Poll::Ready(Err(Error::InvalidReturnType)), + Poll::Pending => Poll::Pending, + } + } else { + panic!("PendingCtor polled after completion or invalid state"); + } + } +} + +impl PendingCtor { + pub fn with_actor_id(self, actor_id: ActorId) -> Self { + self.with_params(|mut params| { + params.actor_id = Some(actor_id); + params + }) + } + + #[cfg(not(feature = "ethexe"))] + pub fn with_gas_limit(self, gas_limit: GasUnit) -> Self { + self.with_params(|mut params| { + params.gas_limit = Some(gas_limit); + params + }) + } + pub fn with_value(self, value: ValueUnit) -> Self { + self.with_params(|mut params| { + params.value = value; + params + }) + } +} + +impl PendingCall { + pub fn with_actor_id(self, actor_id: ActorId) -> Self { + self.with_params(|mut params| { + params.actor_id = Some(actor_id); + params + }) + } + + #[cfg(not(feature = "ethexe"))] + pub fn with_gas_limit(self, gas_limit: GasUnit) -> Self { + self.with_params(|mut params| { + params.gas_limit = Some(gas_limit); + params + }) + } + pub fn with_value(self, value: ValueUnit) -> Self { + self.with_params(|mut params| { + params.value = value; + params + }) + } +} diff --git a/rs/src/client/mock_env.rs b/rs/src/client/mock_env.rs new file mode 100644 index 000000000..a1f8daca0 --- /dev/null +++ b/rs/src/client/mock_env.rs @@ -0,0 +1,54 @@ +use super::*; + +#[derive(Default, Clone)] +pub struct MockEnv; + +#[derive(Default)] +pub struct MockParams; + +impl GearEnv for MockEnv { + type Error = ::gstd::errors::Error; + type Params = MockParams; + type MessageState = core::future::Ready, Self::Error>>; +} + +impl PendingCall { + pub fn from_output(output: O) -> Self { + Self::from_result(Ok(output)) + } + + pub fn from_error(err: ::Error) -> Self { + Self::from_result(Err(err)) + } + + pub fn from_result(res: Result::Error>) -> Self { + PendingCall { + env: mock_env::MockEnv, + destination: ActorId::zero(), + params: None, + payload: None, + _output: PhantomData, + state: Some(future::ready(res.map(|v| v.encode()))), + } + } +} + +impl From for PendingCall { + fn from(value: O) -> Self { + PendingCall::from_output(value) + } +} + +impl Future for PendingCall { + type Output = Result::Error>; + + fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + match self.state.take() { + Some(ready) => { + let res = ready.into_inner(); + Poll::Ready(res.map(|v| O::decode(&mut v.as_ref()).unwrap())) + } + None => panic!("PendingCall polled after completion or invalid state"), + } + } +} diff --git a/rs/src/client/mod.rs b/rs/src/client/mod.rs new file mode 100644 index 000000000..57912b216 --- /dev/null +++ b/rs/src/client/mod.rs @@ -0,0 +1,621 @@ +use crate::{calls::ActionIo, prelude::*}; +use core::{ + error::Error, + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, +}; + +#[cfg(not(target_arch = "wasm32"))] +mod mock_env; + +#[cfg(feature = "gtest")] +#[cfg(not(target_arch = "wasm32"))] +pub mod gtest_env; + +pub trait GearEnv: Clone { + type Params: Default; + type Error: Error; + type MessageState; +} + +type Route = &'static str; + +pub struct Deployment { + env: E, + code_id: CodeId, + salt: Vec, + _phantom: PhantomData, +} + +impl Deployment { + pub fn new(env: E, code_id: CodeId, salt: Vec) -> Self { + Deployment { + env, + code_id, + salt, + _phantom: PhantomData, + } + } + + pub fn with_env(self, env: N) -> Deployment { + let Self { + env: _, + code_id, + salt, + _phantom: _, + } = self; + Deployment { + env, + code_id, + salt, + _phantom: PhantomData, + } + } + + pub fn pending_ctor(self, args: T::Params) -> PendingCtor { + PendingCtor::new(self.env, self.code_id, self.salt, args) + } +} + +pub struct Actor { + env: E, + id: ActorId, + _phantom: PhantomData, +} + +impl Actor { + pub fn new(id: ActorId, env: E) -> Self { + Actor { + env, + id, + _phantom: PhantomData, + } + } + + pub fn with_env(self, env: N) -> Actor { + let Self { + env: _, + id, + _phantom: _, + } = self; + Actor { + env, + id, + _phantom: PhantomData, + } + } + + pub fn service(&self, route: Route) -> Service { + Service::new(self.id, route, self.env.clone()) + } +} + +pub struct Service { + env: E, + actor_id: ActorId, + route: Route, + _phantom: PhantomData, +} + +impl Service { + pub fn new(actor_id: ActorId, route: Route, env: E) -> Self { + Service { + env, + actor_id, + route, + _phantom: PhantomData, + } + } + + pub fn pending_call(&self, route: Route, args: T) -> PendingCall { + let payload = (self.route, route, args).encode(); + + PendingCall::new(self.actor_id, self.env.clone(), payload) + } +} + +pin_project_lite::pin_project! { + pub struct PendingCall { + env: E, + destination: ActorId, + params: Option, + payload: Option>, + _output: PhantomData, + #[pin] + state: Option + } +} + +impl PendingCall { + pub fn new(destination: ActorId, env: E, payload: Vec) -> Self { + PendingCall { + env, + destination, + params: None, + payload: Some(payload), + _output: PhantomData, + state: None, + } + } + + pub fn with_params(mut self, f: impl FnOnce(E::Params) -> E::Params) -> Self { + self.params = Some(f(self.params.unwrap_or_default())); + self + } +} + +pin_project_lite::pin_project! { + pub struct PendingCtor { + env: E, + code_id: CodeId, + params: Option, + salt: Option>, + args: Option, + _actor: PhantomData, + #[pin] + state: Option, + program_id: Option, + } +} + +impl PendingCtor { + pub fn new(env: E, code_id: CodeId, salt: Vec, args: T::Params) -> Self { + PendingCtor { + env, + code_id, + params: None, + salt: Some(salt), + args: Some(args), + _actor: PhantomData, + state: None, + program_id: None, + } + } + + pub fn with_params(mut self, f: impl FnOnce(E::Params) -> E::Params) -> Self { + self.params = Some(f(self.params.unwrap_or_default())); + self + } +} + +#[cfg(feature = "gclient")] +#[cfg(not(target_arch = "wasm32"))] +pub mod gclient { + use super::*; + use ::gclient::{Error, EventListener, EventProcessor as _, GearApi}; + + #[derive(Debug, Default)] + pub struct GclientParams { + gas_limit: Option, + value: Option, + at_block: Option, + } + + #[derive(Clone)] + pub struct GclientEnv { + api: GearApi, + } + + impl GclientEnv { + pub fn new(api: GearApi) -> Self { + Self { api } + } + + pub fn with_suri(self, suri: impl AsRef) -> Self { + let api = self.api.with(suri).unwrap(); + Self { api } + } + + async fn query_calculate_reply( + self, + target: ActorId, + payload: impl AsRef<[u8]>, + params: GclientParams, + ) -> Result, Error> { + let api = self.api; + + // Get Max gas amount if it is not explicitly set + #[cfg(not(feature = "ethexe"))] + let gas_limit = if let Some(gas_limit) = params.gas_limit { + gas_limit + } else { + api.block_gas_limit()? + }; + #[cfg(feature = "ethexe")] + let gas_limit = 0; + let value = params.value.unwrap_or(0); + let origin = H256::from_slice(api.account_id().as_ref()); + let payload = payload.as_ref().to_vec(); + + let reply_info = api + .calculate_reply_for_handle_at( + Some(origin), + target, + payload, + gas_limit, + value, + params.at_block, + ) + .await?; + + match reply_info.code { + ReplyCode::Success(_) => Ok(reply_info.payload), + // TODO + ReplyCode::Error(_reason) => Err(Error::EventNotFound), + ReplyCode::Unsupported => Err(Error::EventNotFound), + } + } + } + + impl GearEnv for GclientEnv { + type Params = GclientParams; + type Error = Error; + type MessageState = Pin, Error>>>>; + } + + async fn send_message( + api: GearApi, + target: ActorId, + payload: Vec, + params: GclientParams, + ) -> Result, Error> { + let value = params.value.unwrap_or(0); + #[cfg(not(feature = "ethexe"))] + let gas_limit = if let Some(gas_limit) = params.gas_limit { + gas_limit + } else { + // Calculate gas amount needed for handling the message + let gas_info = api + .calculate_handle_gas(None, target, payload.clone(), value, true) + .await?; + gas_info.min_limit + }; + #[cfg(feature = "ethexe")] + let gas_limit = 0; + + let mut listener = api.subscribe().await?; + let (message_id, ..) = api + .send_message_bytes(target, payload, gas_limit, value) + .await?; + let (_, reply_code, payload, _) = wait_for_reply(&mut listener, message_id).await?; + // TODO handle errors + match reply_code { + ReplyCode::Success(_) => Ok(payload), + ReplyCode::Error(error_reply_reason) => todo!(), + ReplyCode::Unsupported => todo!(), + } + } + + async fn wait_for_reply( + listener: &mut EventListener, + message_id: MessageId, + ) -> Result<(MessageId, ReplyCode, Vec, ValueUnit), Error> { + let message_id: ::gclient::metadata::runtime_types::gprimitives::MessageId = + message_id.into(); + listener.proc(|e| { + if let ::gclient::Event::Gear(::gclient::GearEvent::UserMessageSent { + message: + ::gclient::metadata::runtime_types::gear_core::message::user::UserMessage { + id, + payload, + value, + details: Some(::gclient::metadata::runtime_types::gear_core::message::common::ReplyDetails { to, code }), + .. + }, + .. + }) = e + { + to.eq(&message_id).then(|| { + let reply_code = ReplyCode::from(code); + + (id.into(), reply_code, payload.0.clone(), value) + }) + } else { + None + } + }) + .await + } + impl PendingCall { + async fn send(self) -> Result { + let api = &self.env.api; + let params = self.params.unwrap_or_default(); + let payload = self.payload.unwrap_or_default(); + let value = params.value.unwrap_or(0); + #[cfg(not(feature = "ethexe"))] + let gas_limit = if let Some(gas_limit) = params.gas_limit { + gas_limit + } else { + // Calculate gas amount needed for handling the message + let gas_info = api + .calculate_handle_gas(None, self.destination, payload.clone(), value, true) + .await?; + gas_info.min_limit + }; + #[cfg(feature = "ethexe")] + let gas_limit = 0; + + let (message_id, ..) = api + .send_message_bytes(self.destination, payload, gas_limit, value) + .await?; + Ok(message_id) + } + + async fn query(self) -> Result { + let params = self.params.unwrap_or_default(); + let payload = self.payload.unwrap_or_default(); + + // Calculate reply + let reply_bytes = self + .env + .query_calculate_reply(self.destination, payload, params) + .await?; + + // Decode reply + match O::decode(&mut reply_bytes.as_slice()) { + Ok(decoded) => Ok(decoded), + Err(err) => Err(Error::Codec(err)), + } + } + } + + impl Future for PendingCall { + type Output = Result::Error>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.state.is_none() { + // Send message + let params = self.params.take().unwrap_or_default(); + let payload = self.payload.take().unwrap_or_default(); + let send_future = + send_message(self.env.api.clone(), self.destination, payload, params); + self.state = Some(Box::pin(send_future)); + } + if let Some(message_future) = self.project().state.as_pin_mut() { + // Poll message future + match message_future.poll(cx) { + Poll::Ready(Ok(bytes)) => match O::decode(&mut bytes.as_slice()) { + Ok(decoded) => Poll::Ready(Ok(decoded)), + Err(err) => Poll::Ready(Err(Error::Codec(err))), + }, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Pending => Poll::Pending, + } + } else { + panic!("PendingCall polled after completion or invalid state"); + } + } + } +} + +mod gstd { + use super::*; + use ::gstd::errors::Error; + use ::gstd::msg; + use ::gstd::msg::MessageFuture; + + #[derive(Default)] + pub struct GstdParams { + #[cfg(not(feature = "ethexe"))] + gas_limit: Option, + value: Option, + wait_up_to: Option, + #[cfg(not(feature = "ethexe"))] + reply_deposit: Option, + #[cfg(not(feature = "ethexe"))] + reply_hook: Option>, + redirect_on_exit: bool, + } + + impl GstdParams { + pub fn with_wait_up_to(self, wait_up_to: Option) -> Self { + Self { wait_up_to, ..self } + } + + pub fn with_redirect_on_exit(self, redirect_on_exit: bool) -> Self { + Self { + redirect_on_exit, + ..self + } + } + + pub fn wait_up_to(&self) -> Option { + self.wait_up_to + } + + pub fn redirect_on_exit(&self) -> bool { + self.redirect_on_exit + } + } + + #[cfg(not(feature = "ethexe"))] + impl GstdParams { + pub fn with_reply_deposit(self, reply_deposit: Option) -> Self { + Self { + reply_deposit, + ..self + } + } + + pub fn with_reply_hook(self, f: F) -> Self { + Self { + reply_hook: Some(Box::new(f)), + ..self + } + } + + pub fn reply_deposit(&self) -> Option { + self.reply_deposit + } + } + + #[derive(Debug, Default, Clone)] + pub struct GstdEnv; + + impl GearEnv for GstdEnv { + type Params = GstdParams; + type Error = Error; + type MessageState = MessageFuture; + } + + #[cfg(not(feature = "ethexe"))] + pub(crate) fn send_for_reply_future( + target: ActorId, + payload: &[u8], + params: GstdParams, + ) -> Result { + let value = params.value.unwrap_or(0); + // here can be a redirect target + let mut message_future = if let Some(gas_limit) = params.gas_limit { + msg::send_bytes_with_gas_for_reply( + target, + payload, + gas_limit, + value, + params.reply_deposit.unwrap_or_default(), + )? + } else { + msg::send_bytes_for_reply( + target, + payload, + value, + params.reply_deposit.unwrap_or_default(), + )? + }; + + message_future = message_future.up_to(params.wait_up_to)?; + + if let Some(reply_hook) = params.reply_hook { + message_future = message_future.handle_reply(reply_hook)?; + } + Ok(message_future) + } + + #[cfg(feature = "ethexe")] + pub(crate) fn send_for_reply_future( + target: ActorId, + payload: &[u8], + args: GstdParams, + ) -> Result { + let value = params.value.unwrap_or(0); + // here can be a redirect target + let mut message_future = msg::send_bytes_for_reply(target, payload, value)?; + + message_future = message_future.up_to(params.wait_up_to)?; + + Ok(message_future) + } + + impl PendingCall { + pub fn send(self) -> Result { + let params = self.params.unwrap_or_default(); + let payload = self.payload.unwrap_or_default(); + let value = params.value.unwrap_or(0); + if let Some(gas_limit) = params.gas_limit { + ::gcore::msg::send_with_gas(self.destination, payload.as_slice(), gas_limit, value) + .map_err(|err| Error::Core(err)) + } else { + ::gcore::msg::send(self.destination, payload.as_slice(), value) + .map_err(|err| Error::Core(err)) + } + } + } + + impl Future for PendingCall { + type Output = Result::Error>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.state.is_none() { + // Send message + let params = self.params.take().unwrap_or_default(); + let payload = self.payload.take().unwrap_or_default(); + let send_res = send_for_reply_future(self.destination, payload.as_slice(), params); + match send_res { + Ok(message_fut) => { + self.state = Some(message_fut); + } + Err(err) => { + return Poll::Ready(Err(err)); + } + } + } + if let Some(message_fut) = self.project().state.as_pin_mut() { + // Poll message future + match message_fut.poll(cx) { + Poll::Ready(Ok(bytes)) => match O::decode(&mut bytes.as_slice()) { + Ok(decoded) => Poll::Ready(Ok(decoded)), + Err(err) => Poll::Ready(Err(Error::Decode(err))), + }, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Pending => Poll::Pending, + } + } else { + panic!("PendingCall polled after completion or invalid state"); + } + } + } +} + +// mod client { + +// use super::service::Service; +// use super::*; + +// pub struct MyServiceImpl; + +// pub trait MyService { +// fn mint(&mut self, to: ActorId, amount: u128) -> PendingCall; +// fn burn(&mut self, from: ActorId) -> PendingCall; +// fn total(&self) -> PendingCall; +// } + +// impl MyService for Service { +// fn mint(&mut self, to: ActorId, amount: u128) -> PendingCall { +// self.pending_call("Mint", (to, amount)) +// } + +// fn burn(&mut self, from: ActorId) -> PendingCall { +// self.pending_call("Burn", (from,)) +// } + +// fn total(&self) -> PendingCall { +// self.pending_call("Total", ()) +// } +// } + +// #[cfg(feature = "mockall")] +// #[cfg(not(target_arch = "wasm32"))] +// mockall::mock! { +// pub MyService {} + +// impl MyService for MyService { +// fn mint(&mut self, to: ActorId, amount: u128) -> PendingCall; +// fn burn(&mut self, from: ActorId) -> PendingCall; +// fn total(&self) -> PendingCall; +// } +// } +// } + +// #[cfg(feature = "mockall")] +// #[cfg(test)] +// mod tests { +// use super::*; + +// #[tokio::test] +// async fn sample() -> Result<(), Box> { +// use client::*; + +// let mut my_service = MockMyService::new(); +// my_service.expect_total().returning(move || 137.into()); +// my_service.expect_mint().returning(move |_, _| true.into()); + +// assert_eq!(my_service.total().await?, 137); + +// let mut my_service = my_service; + +// assert!(my_service.mint(ActorId::from(137), 1_000).await?); + +// Ok(()) +// } +// } diff --git a/rs/src/lib.rs b/rs/src/lib.rs index 69355fc77..f3e3160f5 100644 --- a/rs/src/lib.rs +++ b/rs/src/lib.rs @@ -38,6 +38,5 @@ pub mod prelude; #[cfg(feature = "ethexe")] pub mod solidity; mod types; -#[cfg(feature = "gstd")] pub mod client; mod utils; From c83671dc1b3c255c75ad8344e5fdc632c95b45ee Mon Sep 17 00:00:00 2001 From: vobradovich Date: Fri, 8 Aug 2025 17:52:03 +0200 Subject: [PATCH 03/66] wip: env impl --- examples/demo/app/tests/env_gtest.rs | 7 +- examples/demo/client/src/env_client.rs | 82 ++-- rs/src/client/gclient_env.rs | 268 ++++++++++++ rs/src/client/gstd_env.rs | 229 ++++++++++ rs/src/client/gtest_env.rs | 112 ++--- rs/src/client/mock_env.rs | 44 +- rs/src/client/mod.rs | 556 +++++++++---------------- rs/src/events.rs | 10 +- rs/src/gclient/calls.rs | 4 +- rs/src/gtest/calls.rs | 4 +- 10 files changed, 810 insertions(+), 506 deletions(-) create mode 100644 rs/src/client/gclient_env.rs create mode 100644 rs/src/client/gstd_env.rs diff --git a/examples/demo/app/tests/env_gtest.rs b/examples/demo/app/tests/env_gtest.rs index 1443f3908..9e85bc8f3 100644 --- a/examples/demo/app/tests/env_gtest.rs +++ b/examples/demo/app/tests/env_gtest.rs @@ -1,8 +1,5 @@ -use demo_client::env_client::{Counter as _, Demo as _, DemoCtors as _, DemoProgram}; -use sails_rs::{ - client::gtest_env::{BlockRunMode, GtestEnv}, - prelude::*, -}; +use demo_client::env_client::{Demo as _, DemoCtors as _, DemoProgram, counter::Counter as _}; +use sails_rs::{client::*, prelude::*}; const ACTOR_ID: u64 = 42; #[cfg(debug_assertions)] diff --git a/examples/demo/client/src/env_client.rs b/examples/demo/client/src/env_client.rs index da92af7c8..973df7b18 100644 --- a/examples/demo/client/src/env_client.rs +++ b/examples/demo/client/src/env_client.rs @@ -1,7 +1,4 @@ -use sails_rs::{ - client::{Actor, Deployment, GearEnv, PendingCall, PendingCtor, Service}, - prelude::*, -}; +use sails_rs::{client::*, prelude::*}; pub trait DemoCtors { type Env: GearEnv; @@ -17,7 +14,7 @@ pub trait DemoCtors { pub trait Demo { type Env: GearEnv; - fn counter(&self) -> Service; + fn counter(&self) -> Service; } pub struct DemoProgram; @@ -32,7 +29,7 @@ impl DemoProgram { } pub fn client(env: E, program_id: ActorId) -> Actor { - Actor::new(program_id, env) + Actor::new(env, program_id) } } @@ -55,63 +52,52 @@ impl DemoCtors for Deployment { impl Demo for Actor { type Env = E; - fn counter(&self) -> Service { + fn counter(&self) -> Service { self.service("Counter") } } pub mod io { use super::*; - use sails_rs::calls::ActionIo; - pub struct Default(()); - impl Default { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } - } - impl ActionIo for Default { - const ROUTE: &'static [u8] = &[28, 68, 101, 102, 97, 117, 108, 116]; - type Params = (); - type Reply = (); - } - pub struct New(()); - impl New { - #[allow(dead_code)] - pub fn encode_call(counter: Option, dog_position: Option<(i32, i32)>) -> Vec { - ::encode_call(&(counter, dog_position)) - } - } - impl ActionIo for New { - const ROUTE: &'static [u8] = &[12, 78, 101, 119]; - type Params = (Option, Option<(i32, i32)>); - type Reply = (); - } + use sails_rs::client::{CallEncodeDecode, Route}; + sails_rs::io_struct_impl!(Default () -> ()); + sails_rs::io_struct_impl!(New (counter: Option, dog_position: Option<(i32, i32)>) -> ()); } /// Counter Service -pub trait Counter { - type Env: GearEnv; +pub mod counter { + use super::*; + pub trait Counter { + type Env: GearEnv; - fn add(&mut self, value: u32) -> PendingCall; - fn sub(&mut self, value: u32) -> PendingCall; - fn value(&self) -> PendingCall; -} + fn add(&mut self, value: u32) -> PendingCall; + fn sub(&mut self, value: u32) -> PendingCall; + fn value(&self) -> PendingCall; + } -pub struct CounterImpl; + pub struct CounterImpl; -impl Counter for Service { - type Env = E; + impl Counter for Service { + type Env = E; - fn add(&mut self, value: u32) -> PendingCall { - self.pending_call("Add", (value,)) - } + fn add(&mut self, value: u32) -> PendingCall { + self.pending_call((value,)) + } + + fn sub(&mut self, value: u32) -> PendingCall { + self.pending_call((value,)) + } - fn sub(&mut self, value: u32) -> PendingCall { - self.pending_call("Sub", (value,)) + fn value(&self) -> PendingCall { + self.pending_call(()) + } } - fn value(&self) -> PendingCall { - self.pending_call("Value", ()) + pub mod io { + use super::*; + use sails_rs::client::{CallEncodeDecode, Route}; + sails_rs::io_struct_impl!(Add (value: u32) -> u32); + sails_rs::io_struct_impl!(Sub (value: u32) -> u32); + sails_rs::io_struct_impl!(Value () -> u32); } } diff --git a/rs/src/client/gclient_env.rs b/rs/src/client/gclient_env.rs new file mode 100644 index 000000000..978b61e01 --- /dev/null +++ b/rs/src/client/gclient_env.rs @@ -0,0 +1,268 @@ +use super::*; +use crate::events::Listener; +use ::gclient::{Error, EventListener, EventProcessor as _, GearApi}; +use futures::{Stream, StreamExt as _, stream}; + +#[derive(Clone)] +pub struct GclientEnv { + api: GearApi, +} + +crate::params_struct_impl!( + GclientEnv, + GclientParams { + #[cfg(not(feature = "ethexe"))] + gas_limit: GasUnit, + value: ValueUnit, + at_block: H256, + } +); + +impl GclientEnv { + pub fn new(api: GearApi) -> Self { + Self { api } + } + + pub fn with_suri(self, suri: impl AsRef) -> Self { + let api = self.api.with(suri).unwrap(); + Self { api } + } + + async fn query_calculate_reply( + self, + target: ActorId, + payload: impl AsRef<[u8]>, + params: GclientParams, + ) -> Result, Error> { + let api = self.api; + + // Get Max gas amount if it is not explicitly set + #[cfg(not(feature = "ethexe"))] + let gas_limit = if let Some(gas_limit) = params.gas_limit { + gas_limit + } else { + api.block_gas_limit()? + }; + #[cfg(feature = "ethexe")] + let gas_limit = 0; + let value = params.value.unwrap_or(0); + let origin = H256::from_slice(api.account_id().as_ref()); + let payload = payload.as_ref().to_vec(); + + let reply_info = api + .calculate_reply_for_handle_at( + Some(origin), + target, + payload, + gas_limit, + value, + params.at_block, + ) + .await?; + + match reply_info.code { + ReplyCode::Success(_) => Ok(reply_info.payload), + // TODO + ReplyCode::Error(_reason) => Err(Error::EventNotFound), + ReplyCode::Unsupported => Err(Error::EventNotFound), + } + } +} + +impl GearEnv for GclientEnv { + type Params = GclientParams; + type Error = Error; + type MessageState = Pin, Error>>>>; +} + +async fn send_message( + api: GearApi, + target: ActorId, + payload: Vec, + params: GclientParams, +) -> Result, Error> { + let value = params.value.unwrap_or(0); + #[cfg(not(feature = "ethexe"))] + let gas_limit = if let Some(gas_limit) = params.gas_limit { + gas_limit + } else { + // Calculate gas amount needed for handling the message + let gas_info = api + .calculate_handle_gas(None, target, payload.clone(), value, true) + .await?; + gas_info.min_limit + }; + #[cfg(feature = "ethexe")] + let gas_limit = 0; + + let mut listener = api.subscribe().await?; + let (message_id, ..) = api + .send_message_bytes(target, payload, gas_limit, value) + .await?; + let (_, reply_code, payload, _) = wait_for_reply(&mut listener, message_id).await?; + // TODO handle errors + match reply_code { + ReplyCode::Success(_) => Ok(payload), + ReplyCode::Error(error_reply_reason) => todo!(), + ReplyCode::Unsupported => todo!(), + } +} + +impl PendingCall { + pub async fn send(mut self) -> Result { + let api = &self.env.api; + let params = self.params.unwrap_or_default(); + let args = self + .args + .take() + .unwrap_or_else(|| panic!("PendingCtor polled after completion or invalid state")); + let payload = T::encode_params_with_prefix(self.route.unwrap(), &args); + let value = params.value.unwrap_or(0); + #[cfg(not(feature = "ethexe"))] + let gas_limit = if let Some(gas_limit) = params.gas_limit { + gas_limit + } else { + // Calculate gas amount needed for handling the message + let gas_info = api + .calculate_handle_gas(None, self.destination, payload.clone(), value, true) + .await?; + gas_info.min_limit + }; + #[cfg(feature = "ethexe")] + let gas_limit = 0; + + let (message_id, ..) = api + .send_message_bytes(self.destination, payload, gas_limit, value) + .await?; + Ok(message_id) + } + + pub async fn query(mut self) -> Result { + let params = self.params.unwrap_or_default(); + let args = self + .args + .take() + .unwrap_or_else(|| panic!("PendingCtor polled after completion or invalid state")); + let payload = T::encode_params(&args); + + // Calculate reply + let reply_bytes = self + .env + .query_calculate_reply(self.destination, payload, params) + .await?; + + // Decode reply + match T::Reply::decode(&mut reply_bytes.as_slice()) { + Ok(decoded) => Ok(decoded), + Err(err) => Err(Error::Codec(err)), + } + } +} + +impl Future for PendingCall { + type Output = Result::Error>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.state.is_none() { + // Send message + let params = self.params.take().unwrap_or_default(); + let args = self + .args + .take() + .unwrap_or_else(|| panic!("PendingCtor polled after completion or invalid state")); + let payload = T::encode_params(&args); + + let send_future = send_message(self.env.api.clone(), self.destination, payload, params); + self.state = Some(Box::pin(send_future)); + } + if let Some(message_future) = self.project().state.as_pin_mut() { + // Poll message future + match message_future.poll(cx) { + Poll::Ready(Ok(bytes)) => match T::Reply::decode(&mut bytes.as_slice()) { + Ok(decoded) => Poll::Ready(Ok(decoded)), + Err(err) => Poll::Ready(Err(Error::Codec(err))), + }, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Pending => Poll::Pending, + } + } else { + panic!("PendingCall polled after completion or invalid state"); + } + } +} + +impl Listener> for GclientEnv { + type Error = Error; + + async fn listen( + &mut self, + ) -> Result)> + Unpin, Self::Error> { + let listener = self.api.subscribe().await?; + let stream = stream::unfold(listener, |mut l| async move { + let vec = get_events_from_block(&mut l).await.ok(); + vec.map(|v| (v, l)) + }) + .flat_map(stream::iter); + Ok(Box::pin(stream)) + } +} + +async fn wait_for_reply( + listener: &mut EventListener, + message_id: MessageId, +) -> Result<(MessageId, ReplyCode, Vec, ValueUnit), Error> { + let message_id: ::gclient::metadata::runtime_types::gprimitives::MessageId = message_id.into(); + listener.proc(|e| { + if let ::gclient::Event::Gear(::gclient::GearEvent::UserMessageSent { + message: + ::gclient::metadata::runtime_types::gear_core::message::user::UserMessage { + id, + payload, + value, + details: Some(::gclient::metadata::runtime_types::gear_core::message::common::ReplyDetails { to, code }), + .. + }, + .. + }) = e + { + to.eq(&message_id).then(|| (id.into(), code.into(), payload.0.clone(), value)) + } else { + None + } + }) + .await +} + +async fn get_events_from_block( + listener: &mut gclient::EventListener, +) -> Result)>, Error> { + let vec = listener + .proc_many( + |e| { + if let ::gclient::Event::Gear(::gclient::GearEvent::UserMessageSent { + message: + ::gclient::metadata::runtime_types::gear_core::message::user::UserMessage { + id: _, + source, + destination, + payload, + .. + }, + .. + }) = e + { + let source = ActorId::from(source); + if ActorId::from(destination) == ActorId::zero() { + Some((source, payload.0)) + } else { + None + } + } else { + None + } + }, + |v| (v, true), + ) + .await?; + Ok(vec) +} diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs new file mode 100644 index 000000000..2f7cbf6a3 --- /dev/null +++ b/rs/src/client/gstd_env.rs @@ -0,0 +1,229 @@ +use super::*; +use ::gstd::{errors::Error, msg, msg::MessageFuture}; + +#[derive(Default)] +pub struct GstdParams { + #[cfg(not(feature = "ethexe"))] + gas_limit: Option, + value: Option, + wait_up_to: Option, + #[cfg(not(feature = "ethexe"))] + reply_deposit: Option, + #[cfg(not(feature = "ethexe"))] + reply_hook: Option>, + redirect_on_exit: bool, +} + +impl GstdParams { + pub fn with_wait_up_to(self, wait_up_to: Option) -> Self { + Self { wait_up_to, ..self } + } + + pub fn with_redirect_on_exit(self, redirect_on_exit: bool) -> Self { + Self { + redirect_on_exit, + ..self + } + } + + pub fn wait_up_to(&self) -> Option { + self.wait_up_to + } + + pub fn redirect_on_exit(&self) -> bool { + self.redirect_on_exit + } +} + +#[cfg(not(feature = "ethexe"))] +impl GstdParams { + pub fn with_reply_deposit(self, reply_deposit: Option) -> Self { + Self { + reply_deposit, + ..self + } + } + + pub fn with_reply_hook(self, f: F) -> Self { + Self { + reply_hook: Some(Box::new(f)), + ..self + } + } + + pub fn reply_deposit(&self) -> Option { + self.reply_deposit + } +} + +#[derive(Debug, Default, Clone)] +pub struct GstdEnv; + +impl GearEnv for GstdEnv { + type Params = GstdParams; + type Error = Error; + type MessageState = MessageFuture; +} + +#[cfg(not(feature = "ethexe"))] +pub(crate) fn send_for_reply_future( + target: ActorId, + payload: &[u8], + params: GstdParams, +) -> Result { + let value = params.value.unwrap_or(0); + // here can be a redirect target + let mut message_future = if let Some(gas_limit) = params.gas_limit { + msg::send_bytes_with_gas_for_reply( + target, + payload, + gas_limit, + value, + params.reply_deposit.unwrap_or_default(), + )? + } else { + msg::send_bytes_for_reply( + target, + payload, + value, + params.reply_deposit.unwrap_or_default(), + )? + }; + + message_future = message_future.up_to(params.wait_up_to)?; + + if let Some(reply_hook) = params.reply_hook { + message_future = message_future.handle_reply(reply_hook)?; + } + Ok(message_future) +} + +#[cfg(feature = "ethexe")] +pub(crate) fn send_for_reply_future( + target: ActorId, + payload: &[u8], + args: GstdParams, +) -> Result { + let value = params.value.unwrap_or(0); + // here can be a redirect target + let mut message_future = msg::send_bytes_for_reply(target, payload, value)?; + + message_future = message_future.up_to(params.wait_up_to)?; + + Ok(message_future) +} + +impl PendingCall { + pub fn send(mut self) -> Result { + let params = self.params.unwrap_or_default(); + let args = self + .args + .take() + .unwrap_or_else(|| panic!("PendingCtor polled after completion or invalid state")); + let payload = T::encode_params(&args); + + let value = params.value.unwrap_or(0); + if let Some(gas_limit) = params.gas_limit { + ::gcore::msg::send_with_gas(self.destination, payload.as_slice(), gas_limit, value) + .map_err(|err| Error::Core(err)) + } else { + ::gcore::msg::send(self.destination, payload.as_slice(), value) + .map_err(|err| Error::Core(err)) + } + } +} + +impl Future for PendingCall { + type Output = Result::Error>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.state.is_none() { + // Send message + let payload = self.encode_call(); + let params = self.params.take().unwrap_or_default(); + + let send_res = send_for_reply_future(self.destination, payload.as_slice(), params); + match send_res { + Ok(message_fut) => { + self.state = Some(message_fut); + } + Err(err) => { + return Poll::Ready(Err(err)); + } + } + } + if let Some(message_fut) = self.project().state.as_pin_mut() { + // Poll message future + match message_fut.poll(cx) { + Poll::Ready(Ok(bytes)) => match T::Reply::decode(&mut bytes.as_slice()) { + Ok(decoded) => Poll::Ready(Ok(decoded)), + Err(err) => Poll::Ready(Err(Error::Decode(err))), + }, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Pending => Poll::Pending, + } + } else { + panic!("PendingCall polled after completion or invalid state"); + } + } +} + +impl Future for PendingCtor { + type Output = Result, ::Error>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.state.is_none() { + // Send message + let payload = self.encode_ctor(); + let params = self.params.take().unwrap_or_default(); + let value = params.value.unwrap_or(0); + let salt = self.salt.take().unwrap(); + + #[cfg(not(feature = "ethexe"))] + let program_future = if let Some(gas_limit) = params.gas_limit { + gstd::prog::create_program_bytes_with_gas_for_reply( + self.code_id, + salt, + payload, + gas_limit, + value, + params.reply_deposit.unwrap_or_default(), + )? + } else { + gstd::prog::create_program_bytes_for_reply( + self.code_id, + salt, + payload, + value, + params.reply_deposit.unwrap_or_default(), + )? + }; + #[cfg(feature = "ethexe")] + let mut program_future = + prog::create_program_bytes_for_reply(code_id, salt, payload, value)?; + + // self.state = Some(program_future); + self.program_id = Some(program_future.program_id); + } + let this = self.project(); + if let Some(message_fut) = this.state.as_pin_mut() { + // Poll message future + match message_fut.poll(cx) { + Poll::Ready(Ok(bytes)) => match T::Reply::decode(&mut bytes.as_slice()) { + Ok(_decoded) => { + let program_id = this.program_id.take().unwrap_or_else(|| { + panic!("PendingCtor polled after completion or invalid state") + }); + let env = this.env.clone(); + Poll::Ready(Ok(Actor::new(env, program_id))) + } + Err(err) => Poll::Ready(Err(Error::Decode(err))), + }, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Pending => Poll::Pending, + } + } else { + panic!("PendingCall polled after completion or invalid state"); + } + } +} diff --git a/rs/src/client/gtest_env.rs b/rs/src/client/gtest_env.rs index daff1ac19..e43ed4af5 100644 --- a/rs/src/client/gtest_env.rs +++ b/rs/src/client/gtest_env.rs @@ -1,7 +1,11 @@ use super::*; +use crate::events::Listener; use ::gtest::{BlockRunResult, System, TestError}; use core::cell::RefCell; -use futures::channel::{mpsc, oneshot}; +use futures::{ + Stream, + channel::{mpsc, oneshot}, +}; use hashbrown::HashMap; use std::rc::Rc; @@ -11,14 +15,6 @@ type EventSender = mpsc::UnboundedSender<(ActorId, Vec)>; type ReplySender = oneshot::Sender, Error>>; type ReplyReceiver = oneshot::Receiver, Error>>; -#[derive(Debug, Default)] -pub struct GtestParams { - actor_id: Option, - #[cfg(not(feature = "ethexe"))] - gas_limit: Option, - value: ValueUnit, -} - #[derive(Clone, Debug, PartialEq, Eq)] pub enum BlockRunMode { /// Run blocks automatically until all pending replies are received. @@ -40,6 +36,16 @@ pub struct GtestEnv { block_reply_senders: Rc>>, } +crate::params_struct_impl!( + GtestEnv, + GtestParams { + actor_id: ActorId, + #[cfg(not(feature = "ethexe"))] + gas_limit: GasUnit, + value: ValueUnit, + } +); + impl GtestEnv { /// Create new `GTestRemoting` instance from `gtest::System` with specified `actor_id` /// and `Auto` block run mode @@ -134,6 +140,7 @@ impl GtestEnv { payload: impl AsRef<[u8]>, params: GtestParams, ) -> Result<(ActorId, MessageId), Error> { + let value = params.value.unwrap_or(0); #[cfg(not(feature = "ethexe"))] let gas_limit = params.gas_limit.unwrap_or(GAS_LIMIT_DEFAULT); #[cfg(feature = "ethexe")] @@ -146,8 +153,7 @@ impl GtestEnv { let program_id = ::gtest::calculate_program_id(code_id, salt.as_ref(), None); let program = ::gtest::Program::from_binary_with_id(&self.system, program_id, code); let actor_id = params.actor_id.unwrap_or(self.actor_id); - let message_id = - program.send_bytes_with_gas(actor_id, payload.as_ref(), gas_limit, params.value); + let message_id = program.send_bytes_with_gas(actor_id, payload.as_ref(), gas_limit, value); log::debug!("Send activation id: {message_id}, to program: {program_id}"); Ok((program_id, message_id)) } @@ -158,6 +164,7 @@ impl GtestEnv { payload: impl AsRef<[u8]>, params: GtestParams, ) -> Result { + let value = params.value.unwrap_or(0); #[cfg(not(feature = "ethexe"))] let gas_limit = params.gas_limit.unwrap_or(GAS_LIMIT_DEFAULT); #[cfg(feature = "ethexe")] @@ -168,8 +175,7 @@ impl GtestEnv { // TODO Errors .ok_or(Error::Instrumentation)?; let actor_id = params.actor_id.unwrap_or(self.actor_id); - let message_id = - program.send_bytes_with_gas(actor_id, payload.as_ref(), gas_limit, params.value); + let message_id = program.send_bytes_with_gas(actor_id, payload.as_ref(), gas_limit, value); log::debug!( "Send message id: {message_id}, to: {target}, payload: {}", hex::encode(payload.as_ref()) @@ -225,14 +231,15 @@ impl GearEnv for GtestEnv { type MessageState = ReplyReceiver; } -impl Future for PendingCall { - type Output = Result::Error>; +impl Future for PendingCall { + type Output = Result::Error>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if self.state.is_none() { // Send message + let payload = self.encode_call(); let params = self.params.take().unwrap_or_default(); - let payload = self.payload.take().unwrap_or_default(); + let send_res = self.env.send_message(self.destination, payload, params); match send_res { Ok(message_id) => { @@ -250,10 +257,12 @@ impl Future for PendingCall { match reply_receiver.poll(cx) { Poll::Ready(Ok(res)) => match res { // TODO handle reply prefix - Ok(bytes) => match <(String, String, O)>::decode(&mut bytes.as_slice()) { - Ok((_, _, decoded)) => Poll::Ready(Ok(decoded)), - Err(err) => Poll::Ready(Err(Error::ScaleCodecError(err))), - }, + Ok(bytes) => { + match <(String, String, T::Reply)>::decode(&mut bytes.as_slice()) { + Ok((_, _, decoded)) => Poll::Ready(Ok(decoded)), + Err(err) => Poll::Ready(Err(Error::ScaleCodecError(err))), + } + } Err(err) => Poll::Ready(Err(err)), }, // TODO handle error @@ -266,19 +275,15 @@ impl Future for PendingCall { } } -impl Future for PendingCtor { +impl Future for PendingCtor { type Output = Result, ::Error>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if self.state.is_none() { // Send message + let payload = self.encode_ctor(); let params = self.params.take().unwrap_or_default(); let salt = self.salt.take().unwrap_or_default(); - let args = self - .args - .take() - .unwrap_or_else(|| panic!("PendingCtor polled after completion or invalid state")); - let payload = T::encode_call(&args); let send_res = self .env .activate(self.code_id, salt, payload.as_slice(), params); @@ -301,9 +306,12 @@ impl Future for PendingCtor { Poll::Ready(Ok(res)) => match res { // TODO handle reply prefix Ok(_) => { - let program_id = this.program_id.unwrap(); + // TODO + let program_id = this.program_id.take().unwrap_or_else(|| { + panic!("PendingCtor polled after completion or invalid state") + }); let env = this.env.clone(); - Poll::Ready(Ok(Actor::new(program_id, env))) + Poll::Ready(Ok(Actor::new(env, program_id))) } Err(err) => Poll::Ready(Err(err)), }, @@ -317,48 +325,12 @@ impl Future for PendingCtor { } } -impl PendingCtor { - pub fn with_actor_id(self, actor_id: ActorId) -> Self { - self.with_params(|mut params| { - params.actor_id = Some(actor_id); - params - }) - } - - #[cfg(not(feature = "ethexe"))] - pub fn with_gas_limit(self, gas_limit: GasUnit) -> Self { - self.with_params(|mut params| { - params.gas_limit = Some(gas_limit); - params - }) - } - pub fn with_value(self, value: ValueUnit) -> Self { - self.with_params(|mut params| { - params.value = value; - params - }) - } -} - -impl PendingCall { - pub fn with_actor_id(self, actor_id: ActorId) -> Self { - self.with_params(|mut params| { - params.actor_id = Some(actor_id); - params - }) - } +impl Listener> for GtestEnv { + type Error = ::Error; - #[cfg(not(feature = "ethexe"))] - pub fn with_gas_limit(self, gas_limit: GasUnit) -> Self { - self.with_params(|mut params| { - params.gas_limit = Some(gas_limit); - params - }) - } - pub fn with_value(self, value: ValueUnit) -> Self { - self.with_params(|mut params| { - params.value = value; - params - }) + async fn listen(&mut self) -> Result)>, Self::Error> { + let (tx, rx) = mpsc::unbounded::<(ActorId, Vec)>(); + self.event_senders.borrow_mut().push(tx); + Ok(rx) } } diff --git a/rs/src/client/mock_env.rs b/rs/src/client/mock_env.rs index a1f8daca0..fbb571e3b 100644 --- a/rs/src/client/mock_env.rs +++ b/rs/src/client/mock_env.rs @@ -3,8 +3,7 @@ use super::*; #[derive(Default, Clone)] pub struct MockEnv; -#[derive(Default)] -pub struct MockParams; +crate::params_struct_impl!(MockEnv, MockParams {}); impl GearEnv for MockEnv { type Error = ::gstd::errors::Error; @@ -12,8 +11,11 @@ impl GearEnv for MockEnv { type MessageState = core::future::Ready, Self::Error>>; } -impl PendingCall { - pub fn from_output(output: O) -> Self { +impl PendingCall +where + T::Reply: Encode + Decode, +{ + pub fn from_output(output: T::Reply) -> Self { Self::from_result(Ok(output)) } @@ -21,32 +23,52 @@ impl PendingCall { Self::from_result(Err(err)) } - pub fn from_result(res: Result::Error>) -> Self { + pub fn from_result(res: Result::Error>) -> Self { PendingCall { env: mock_env::MockEnv, destination: ActorId::zero(), + route: None, params: None, - payload: None, - _output: PhantomData, + args: None, state: Some(future::ready(res.map(|v| v.encode()))), } } } -impl From for PendingCall { +impl, O> From for PendingCall +where + O: Encode + Decode, +{ fn from(value: O) -> Self { PendingCall::from_output(value) } } -impl Future for PendingCall { - type Output = Result::Error>; +impl Future for PendingCall { + type Output = Result::Error>; fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { match self.state.take() { Some(ready) => { let res = ready.into_inner(); - Poll::Ready(res.map(|v| O::decode(&mut v.as_ref()).unwrap())) + Poll::Ready(res.map(|v| T::Reply::decode(&mut v.as_slice()).unwrap())) + } + None => panic!("PendingCall polled after completion or invalid state"), + } + } +} + +impl Future for PendingCtor { + type Output = Result, ::Error>; + + fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + match self.state.take() { + Some(_ready) => { + let program_id = self.program_id.take().unwrap_or_else(|| { + panic!("PendingCtor polled after completion or invalid state") + }); + let env = self.env.clone(); + Poll::Ready(Ok(Actor::new(env, program_id))) } None => panic!("PendingCall polled after completion or invalid state"), } diff --git a/rs/src/client/mod.rs b/rs/src/client/mod.rs index 57912b216..0dc66a03c 100644 --- a/rs/src/client/mod.rs +++ b/rs/src/client/mod.rs @@ -1,5 +1,6 @@ -use crate::{calls::ActionIo, prelude::*}; +use crate::prelude::*; use core::{ + any::TypeId, error::Error, marker::PhantomData, pin::Pin, @@ -8,10 +9,27 @@ use core::{ #[cfg(not(target_arch = "wasm32"))] mod mock_env; +#[cfg(not(target_arch = "wasm32"))] +pub use mock_env::{MockEnv, MockParams}; #[cfg(feature = "gtest")] #[cfg(not(target_arch = "wasm32"))] -pub mod gtest_env; +mod gtest_env; +#[cfg(feature = "gtest")] +#[cfg(not(target_arch = "wasm32"))] +pub use gtest_env::{BlockRunMode, GtestEnv, GtestParams}; + +#[cfg(feature = "gclient")] +#[cfg(not(target_arch = "wasm32"))] +mod gclient_env; +#[cfg(feature = "gclient")] +#[cfg(not(target_arch = "wasm32"))] +pub use gclient_env::{GclientEnv, GclientParams}; + +#[cfg(feature = "gstd")] +mod gstd_env; +#[cfg(feature = "gstd")] +pub use gstd_env::{GstdEnv, GstdParams}; pub trait GearEnv: Clone { type Params: Default; @@ -19,7 +37,10 @@ pub trait GearEnv: Clone { type MessageState; } -type Route = &'static str; +#[cfg(not(target_arch = "wasm32"))] +pub type DefaultEnv = MockEnv; + +pub type Route = &'static str; pub struct Deployment { env: E, @@ -53,7 +74,7 @@ impl Deployment { } } - pub fn pending_ctor(self, args: T::Params) -> PendingCtor { + pub fn pending_ctor(self, args: T::Params) -> PendingCtor { PendingCtor::new(self.env, self.code_id, self.salt, args) } } @@ -65,7 +86,7 @@ pub struct Actor { } impl Actor { - pub fn new(id: ActorId, env: E) -> Self { + pub fn new(env: E, id: ActorId) -> Self { Actor { env, id, @@ -87,7 +108,7 @@ impl Actor { } pub fn service(&self, route: Route) -> Service { - Service::new(self.id, route, self.env.clone()) + Service::new(self.env.clone(), self.id, route) } } @@ -99,7 +120,7 @@ pub struct Service { } impl Service { - pub fn new(actor_id: ActorId, route: Route, env: E) -> Self { + pub fn new(env: E, actor_id: ActorId, route: Route) -> Self { Service { env, actor_id, @@ -108,33 +129,31 @@ impl Service { } } - pub fn pending_call(&self, route: Route, args: T) -> PendingCall { - let payload = (self.route, route, args).encode(); - - PendingCall::new(self.actor_id, self.env.clone(), payload) + pub fn pending_call(&self, args: T::Params) -> PendingCall { + PendingCall::new(self.env.clone(), self.actor_id, self.route, args) } } pin_project_lite::pin_project! { - pub struct PendingCall { + pub struct PendingCall { env: E, destination: ActorId, + route: Option, params: Option, - payload: Option>, - _output: PhantomData, + args: Option, #[pin] state: Option } } -impl PendingCall { - pub fn new(destination: ActorId, env: E, payload: Vec) -> Self { +impl PendingCall { + pub fn new(env: E, destination: ActorId, route: Route, args: T::Params) -> Self { PendingCall { env, destination, + route: Some(route), params: None, - payload: Some(payload), - _output: PhantomData, + args: Some(args), state: None, } } @@ -143,10 +162,20 @@ impl PendingCall { self.params = Some(f(self.params.unwrap_or_default())); self } + + fn encode_call(&self) -> Vec { + if let Some(route) = &self.route + && let Some(args) = &self.args + { + T::encode_params_with_prefix(route, args) + } else { + vec![] + } + } } pin_project_lite::pin_project! { - pub struct PendingCtor { + pub struct PendingCtor { env: E, code_id: CodeId, params: Option, @@ -159,7 +188,7 @@ pin_project_lite::pin_project! { } } -impl PendingCtor { +impl PendingCtor { pub fn new(env: E, code_id: CodeId, salt: Vec, args: T::Params) -> Self { PendingCtor { env, @@ -177,384 +206,148 @@ impl PendingCtor { self.params = Some(f(self.params.unwrap_or_default())); self } -} -#[cfg(feature = "gclient")] -#[cfg(not(target_arch = "wasm32"))] -pub mod gclient { - use super::*; - use ::gclient::{Error, EventListener, EventProcessor as _, GearApi}; - - #[derive(Debug, Default)] - pub struct GclientParams { - gas_limit: Option, - value: Option, - at_block: Option, - } - - #[derive(Clone)] - pub struct GclientEnv { - api: GearApi, - } - - impl GclientEnv { - pub fn new(api: GearApi) -> Self { - Self { api } - } - - pub fn with_suri(self, suri: impl AsRef) -> Self { - let api = self.api.with(suri).unwrap(); - Self { api } - } - - async fn query_calculate_reply( - self, - target: ActorId, - payload: impl AsRef<[u8]>, - params: GclientParams, - ) -> Result, Error> { - let api = self.api; - - // Get Max gas amount if it is not explicitly set - #[cfg(not(feature = "ethexe"))] - let gas_limit = if let Some(gas_limit) = params.gas_limit { - gas_limit - } else { - api.block_gas_limit()? - }; - #[cfg(feature = "ethexe")] - let gas_limit = 0; - let value = params.value.unwrap_or(0); - let origin = H256::from_slice(api.account_id().as_ref()); - let payload = payload.as_ref().to_vec(); - - let reply_info = api - .calculate_reply_for_handle_at( - Some(origin), - target, - payload, - gas_limit, - value, - params.at_block, - ) - .await?; - - match reply_info.code { - ReplyCode::Success(_) => Ok(reply_info.payload), - // TODO - ReplyCode::Error(_reason) => Err(Error::EventNotFound), - ReplyCode::Unsupported => Err(Error::EventNotFound), - } + fn encode_ctor(&self) -> Vec { + if let Some(args) = &self.args { + T::encode_params(args) + } else { + vec![] } } +} - impl GearEnv for GclientEnv { - type Params = GclientParams; - type Error = Error; - type MessageState = Pin, Error>>>>; - } +pub trait CallEncodeDecode { + const ROUTE: Route; + type Params: Encode; + type Reply: Decode + 'static; - async fn send_message( - api: GearApi, - target: ActorId, - payload: Vec, - params: GclientParams, - ) -> Result, Error> { - let value = params.value.unwrap_or(0); - #[cfg(not(feature = "ethexe"))] - let gas_limit = if let Some(gas_limit) = params.gas_limit { - gas_limit - } else { - // Calculate gas amount needed for handling the message - let gas_info = api - .calculate_handle_gas(None, target, payload.clone(), value, true) - .await?; - gas_info.min_limit - }; - #[cfg(feature = "ethexe")] - let gas_limit = 0; - - let mut listener = api.subscribe().await?; - let (message_id, ..) = api - .send_message_bytes(target, payload, gas_limit, value) - .await?; - let (_, reply_code, payload, _) = wait_for_reply(&mut listener, message_id).await?; - // TODO handle errors - match reply_code { - ReplyCode::Success(_) => Ok(payload), - ReplyCode::Error(error_reply_reason) => todo!(), - ReplyCode::Unsupported => todo!(), - } + fn encode_params(value: &Self::Params) -> Vec { + let mut result = Vec::with_capacity(Self::ROUTE.len() + Encode::size_hint(value)); + Encode::encode_to(Self::ROUTE, &mut result); + Encode::encode_to(value, &mut result); + result } - async fn wait_for_reply( - listener: &mut EventListener, - message_id: MessageId, - ) -> Result<(MessageId, ReplyCode, Vec, ValueUnit), Error> { - let message_id: ::gclient::metadata::runtime_types::gprimitives::MessageId = - message_id.into(); - listener.proc(|e| { - if let ::gclient::Event::Gear(::gclient::GearEvent::UserMessageSent { - message: - ::gclient::metadata::runtime_types::gear_core::message::user::UserMessage { - id, - payload, - value, - details: Some(::gclient::metadata::runtime_types::gear_core::message::common::ReplyDetails { to, code }), - .. - }, - .. - }) = e - { - to.eq(&message_id).then(|| { - let reply_code = ReplyCode::from(code); - - (id.into(), reply_code, payload.0.clone(), value) - }) - } else { - None - } - }) - .await + fn encode_params_with_prefix(prefix: Route, value: &Self::Params) -> Vec { + let mut result = Vec::with_capacity(Self::ROUTE.len() + Encode::size_hint(value)); + Encode::encode_to(prefix, &mut result); + Encode::encode_to(Self::ROUTE, &mut result); + Encode::encode_to(value, &mut result); + result } - impl PendingCall { - async fn send(self) -> Result { - let api = &self.env.api; - let params = self.params.unwrap_or_default(); - let payload = self.payload.unwrap_or_default(); - let value = params.value.unwrap_or(0); - #[cfg(not(feature = "ethexe"))] - let gas_limit = if let Some(gas_limit) = params.gas_limit { - gas_limit - } else { - // Calculate gas amount needed for handling the message - let gas_info = api - .calculate_handle_gas(None, self.destination, payload.clone(), value, true) - .await?; - gas_info.min_limit - }; - #[cfg(feature = "ethexe")] - let gas_limit = 0; - - let (message_id, ..) = api - .send_message_bytes(self.destination, payload, gas_limit, value) - .await?; - Ok(message_id) - } - - async fn query(self) -> Result { - let params = self.params.unwrap_or_default(); - let payload = self.payload.unwrap_or_default(); - - // Calculate reply - let reply_bytes = self - .env - .query_calculate_reply(self.destination, payload, params) - .await?; - // Decode reply - match O::decode(&mut reply_bytes.as_slice()) { - Ok(decoded) => Ok(decoded), - Err(err) => Err(Error::Codec(err)), - } + fn decode_reply(payload: impl AsRef<[u8]>) -> Result { + let mut value = payload.as_ref(); + let zero_size_reply = Self::is_empty_tuple::(); + if !zero_size_reply && !value.starts_with(Self::ROUTE.encode().as_slice()) { + return Err("Invalid reply prefix".into()); } + let start_idx = if zero_size_reply { + 0 + } else { + Self::ROUTE.len() + }; + value = &value[start_idx..]; + Decode::decode(&mut value) } - impl Future for PendingCall { - type Output = Result::Error>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if self.state.is_none() { - // Send message - let params = self.params.take().unwrap_or_default(); - let payload = self.payload.take().unwrap_or_default(); - let send_future = - send_message(self.env.api.clone(), self.destination, payload, params); - self.state = Some(Box::pin(send_future)); - } - if let Some(message_future) = self.project().state.as_pin_mut() { - // Poll message future - match message_future.poll(cx) { - Poll::Ready(Ok(bytes)) => match O::decode(&mut bytes.as_slice()) { - Ok(decoded) => Poll::Ready(Ok(decoded)), - Err(err) => Poll::Ready(Err(Error::Codec(err))), - }, - Poll::Ready(Err(err)) => Poll::Ready(Err(err)), - Poll::Pending => Poll::Pending, - } - } else { - panic!("PendingCall polled after completion or invalid state"); - } - } + fn is_empty_tuple() -> bool { + TypeId::of::() == TypeId::of::<()>() } } -mod gstd { - use super::*; - use ::gstd::errors::Error; - use ::gstd::msg; - use ::gstd::msg::MessageFuture; - - #[derive(Default)] - pub struct GstdParams { - #[cfg(not(feature = "ethexe"))] - gas_limit: Option, - value: Option, - wait_up_to: Option, - #[cfg(not(feature = "ethexe"))] - reply_deposit: Option, - #[cfg(not(feature = "ethexe"))] - reply_hook: Option>, - redirect_on_exit: bool, - } - - impl GstdParams { - pub fn with_wait_up_to(self, wait_up_to: Option) -> Self { - Self { wait_up_to, ..self } +#[macro_export] +macro_rules! params_struct_impl { + ( + $env:ident, + $name:ident { $( $(#[$attr:meta])* $vis:vis $field:ident: $ty:ty ),* $(,)? } + ) => { + #[derive(Debug, Default)] + pub struct $name { + $( + $(#[$attr])* $vis $field : Option< $ty >, + )* } - pub fn with_redirect_on_exit(self, redirect_on_exit: bool) -> Self { - Self { - redirect_on_exit, - ..self - } + impl $name { + $( + paste::paste! { + $(#[$attr])* + pub fn [](mut self, $field: $ty) -> Self { + self.$field = Some($field); + self + } + } + )* } - pub fn wait_up_to(&self) -> Option { - self.wait_up_to + impl PendingCtor<$env, A, T> { + $( + paste::paste! { + $(#[$attr])* + pub fn [](self, $field: $ty) -> Self { + self.with_params(|params| params.[]($field)) + } + } + )* } - pub fn redirect_on_exit(&self) -> bool { - self.redirect_on_exit + impl PendingCall<$env, T> { + $( + paste::paste! { + $(#[$attr])* + pub fn [](self, $field: $ty) -> Self { + self.with_params(|params| params.[]($field)) + } + } + )* } - } + }; +} - #[cfg(not(feature = "ethexe"))] - impl GstdParams { - pub fn with_reply_deposit(self, reply_deposit: Option) -> Self { - Self { - reply_deposit, - ..self +#[macro_export] +macro_rules! io_struct_impl { + ( + $name:ident ( $( $param:ident : $ty:ty ),* ) -> $reply:ty + ) => { + pub struct $name(()); + impl $name { + pub fn encode_params($( $param: $ty, )* ) -> Vec { + <$name as CallEncodeDecode>::encode_params(&( $( $param, )* )) } - } - - pub fn with_reply_hook(self, f: F) -> Self { - Self { - reply_hook: Some(Box::new(f)), - ..self + pub fn encode_params_with_prefix(prefix: Route, $( $param: $ty, )* ) -> Vec { + <$name as CallEncodeDecode>::encode_params_with_prefix(prefix, &( $( $param, )* )) } } - - pub fn reply_deposit(&self) -> Option { - self.reply_deposit - } - } - - #[derive(Debug, Default, Clone)] - pub struct GstdEnv; - - impl GearEnv for GstdEnv { - type Params = GstdParams; - type Error = Error; - type MessageState = MessageFuture; - } - - #[cfg(not(feature = "ethexe"))] - pub(crate) fn send_for_reply_future( - target: ActorId, - payload: &[u8], - params: GstdParams, - ) -> Result { - let value = params.value.unwrap_or(0); - // here can be a redirect target - let mut message_future = if let Some(gas_limit) = params.gas_limit { - msg::send_bytes_with_gas_for_reply( - target, - payload, - gas_limit, - value, - params.reply_deposit.unwrap_or_default(), - )? - } else { - msg::send_bytes_for_reply( - target, - payload, - value, - params.reply_deposit.unwrap_or_default(), - )? - }; - - message_future = message_future.up_to(params.wait_up_to)?; - - if let Some(reply_hook) = params.reply_hook { - message_future = message_future.handle_reply(reply_hook)?; - } - Ok(message_future) - } - - #[cfg(feature = "ethexe")] - pub(crate) fn send_for_reply_future( - target: ActorId, - payload: &[u8], - args: GstdParams, - ) -> Result { - let value = params.value.unwrap_or(0); - // here can be a redirect target - let mut message_future = msg::send_bytes_for_reply(target, payload, value)?; - - message_future = message_future.up_to(params.wait_up_to)?; - - Ok(message_future) - } - - impl PendingCall { - pub fn send(self) -> Result { - let params = self.params.unwrap_or_default(); - let payload = self.payload.unwrap_or_default(); - let value = params.value.unwrap_or(0); - if let Some(gas_limit) = params.gas_limit { - ::gcore::msg::send_with_gas(self.destination, payload.as_slice(), gas_limit, value) - .map_err(|err| Error::Core(err)) - } else { - ::gcore::msg::send(self.destination, payload.as_slice(), value) - .map_err(|err| Error::Core(err)) - } + impl CallEncodeDecode for $name { + const ROUTE: &'static str = stringify!($name); + type Params = ( $( $ty, )* ); + type Reply = $reply; } - } + }; +} - impl Future for PendingCall { - type Output = Result::Error>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if self.state.is_none() { - // Send message - let params = self.params.take().unwrap_or_default(); - let payload = self.payload.take().unwrap_or_default(); - let send_res = send_for_reply_future(self.destination, payload.as_slice(), params); - match send_res { - Ok(message_fut) => { - self.state = Some(message_fut); - } - Err(err) => { - return Poll::Ready(Err(err)); - } +macro_rules! str_scale_encode { + ($s:ident) => {{ + const S: &str = stringify!($s); + assert!(S.len() <= 63, "Ident too long for encoding"); + const LEN: u8 = S.len() as u8; + const BYTES: [u8; LEN as usize + 1] = { + const fn to_array(s: &str) -> [u8; LEN as usize + 1] { + let bytes = s.as_bytes(); + let mut out = [0u8; LEN as usize + 1]; + out[0] = LEN << 2; + let mut i = 0; + while i < LEN as usize { + out[i + 1] = bytes[i]; + i += 1; } + out } - if let Some(message_fut) = self.project().state.as_pin_mut() { - // Poll message future - match message_fut.poll(cx) { - Poll::Ready(Ok(bytes)) => match O::decode(&mut bytes.as_slice()) { - Ok(decoded) => Poll::Ready(Ok(decoded)), - Err(err) => Poll::Ready(Err(Error::Decode(err))), - }, - Poll::Ready(Err(err)) => Poll::Ready(Err(err)), - Poll::Pending => Poll::Pending, - } - } else { - panic!("PendingCall polled after completion or invalid state"); - } - } - } + to_array(S) + }; + BYTES.as_slice() + }}; } // mod client { @@ -619,3 +412,30 @@ mod gstd { // Ok(()) // } // } + +#[cfg(test)] +mod tests { + use super::*; + struct Add; + struct Value; + + #[test] + fn test_str_encode() { + const ADD: &'static [u8] = str_scale_encode!(Add); + assert_eq!(ADD, &[12, 65, 100, 100]); + + const VALUE: &'static [u8] = str_scale_encode!(Value); + assert_eq!(VALUE, &[20, 86, 97, 108, 117, 101]); + } + + fn test_io_struct_impl() { + io_struct_impl!(Add (value: u32) -> u32); + io_struct_impl!(Value () -> u32); + + let add = Add::encode_params(42); + assert_eq!(add, &[12, 65, 100, 100, 42]); + + let value = Value::encode_params(); + assert_eq!(value, &[20, 86, 97, 108, 117, 101]); + } +} diff --git a/rs/src/events.rs b/rs/src/events.rs index ecdaa4540..4dfe7d588 100644 --- a/rs/src/events.rs +++ b/rs/src/events.rs @@ -7,7 +7,9 @@ use core::marker::PhantomData; #[allow(async_fn_in_trait)] pub trait Listener { - async fn listen(&mut self) -> Result + Unpin>; + type Error: core::error::Error; + + async fn listen(&mut self) -> Result + Unpin, Self::Error>; } pub struct RemotingListener { @@ -25,7 +27,11 @@ impl>, E> RemotingListener { } impl>, E: EventIo> Listener for RemotingListener { - async fn listen(&mut self) -> Result + Unpin> { + type Error = R::Error; + + async fn listen( + &mut self, + ) -> Result + Unpin, Self::Error> { let stream = self.remoting.listen().await?; let map = stream.filter_map(move |(actor_id, payload)| async move { E::decode_event(payload).ok().map(|e| (actor_id, e)) diff --git a/rs/src/gclient/calls.rs b/rs/src/gclient/calls.rs index b7983d0a2..9d6f0730d 100644 --- a/rs/src/gclient/calls.rs +++ b/rs/src/gclient/calls.rs @@ -1,6 +1,6 @@ use crate::{ calls::{Query, Remoting}, - errors::{Result, RtlError}, + errors::{Error, Result, RtlError}, events::Listener, futures::{Stream, StreamExt, stream}, prelude::*, @@ -199,6 +199,8 @@ impl Remoting for GClientRemoting { } impl Listener> for GClientRemoting { + type Error = Error; + async fn listen(&mut self) -> Result)> + Unpin> { let listener = self.api.subscribe().await?; let stream = stream::unfold(listener, |mut l| async move { diff --git a/rs/src/gtest/calls.rs b/rs/src/gtest/calls.rs index a14ac16d9..fbb137f56 100644 --- a/rs/src/gtest/calls.rs +++ b/rs/src/gtest/calls.rs @@ -1,7 +1,7 @@ use crate::{ calls::{Action, Remoting}, collections::HashMap, - errors::{Result, RtlError}, + errors::{Error, Result, RtlError}, events::Listener, futures::*, gtest::{BlockRunResult, Program, System}, @@ -281,6 +281,8 @@ impl Remoting for GTestRemoting { } impl Listener> for GTestRemoting { + type Error = Error; + async fn listen(&mut self) -> Result)>> { let (tx, rx) = channel::mpsc::unbounded::<(ActorId, Vec)>(); self.event_senders.borrow_mut().push(tx); From e697db36602e6d810d8301e0cd348c1fb3ed3695 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 11 Aug 2025 15:46:06 +0200 Subject: [PATCH 04/66] wip: env --- rs/src/client/gclient_env.rs | 2 +- rs/src/client/gstd_env.rs | 87 +++++++++++++++++++++++------------- rs/src/client/gtest_env.rs | 37 +++++++++------ rs/src/client/mock_env.rs | 11 ++--- rs/src/client/mod.rs | 52 +++++++++++++-------- rs/src/gstd/events.rs | 3 ++ rs/src/lib.rs | 1 + 7 files changed, 122 insertions(+), 71 deletions(-) diff --git a/rs/src/client/gclient_env.rs b/rs/src/client/gclient_env.rs index 978b61e01..36732774c 100644 --- a/rs/src/client/gclient_env.rs +++ b/rs/src/client/gclient_env.rs @@ -103,7 +103,7 @@ async fn send_message( // TODO handle errors match reply_code { ReplyCode::Success(_) => Ok(payload), - ReplyCode::Error(error_reply_reason) => todo!(), + ReplyCode::Error(_error_reply_reason) => todo!(), ReplyCode::Unsupported => todo!(), } } diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index 2f7cbf6a3..764b77546 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -1,5 +1,9 @@ use super::*; -use ::gstd::{errors::Error, msg, msg::MessageFuture}; +use ::gstd::{ + errors::Error, + msg, + msg::{CreateProgramFuture, MessageFuture}, +}; #[derive(Default)] pub struct GstdParams { @@ -62,7 +66,7 @@ pub struct GstdEnv; impl GearEnv for GstdEnv { type Params = GstdParams; type Error = Error; - type MessageState = MessageFuture; + type MessageState = GtsdFuture; } #[cfg(not(feature = "ethexe"))] @@ -115,20 +119,22 @@ pub(crate) fn send_for_reply_future( impl PendingCall { pub fn send(mut self) -> Result { + let route = self + .route + .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); let params = self.params.unwrap_or_default(); let args = self .args .take() - .unwrap_or_else(|| panic!("PendingCtor polled after completion or invalid state")); - let payload = T::encode_params(&args); + .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); + let payload = T::encode_params_with_prefix(route, &args); let value = params.value.unwrap_or(0); if let Some(gas_limit) = params.gas_limit { ::gcore::msg::send_with_gas(self.destination, payload.as_slice(), gas_limit, value) - .map_err(|err| Error::Core(err)) + .map_err(Error::Core) } else { - ::gcore::msg::send(self.destination, payload.as_slice(), value) - .map_err(|err| Error::Core(err)) + ::gcore::msg::send(self.destination, payload.as_slice(), value).map_err(Error::Core) } } } @@ -137,33 +143,43 @@ impl Future for PendingCall { type Output = Result::Error>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let Some(route) = self.route else { + return Poll::Ready(Err(Error::Decode("PendingCall route is not set".into()))); + }; if self.state.is_none() { // Send message - let payload = self.encode_call(); + let args = self + .args + .take() + .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); + let payload = T::encode_params_with_prefix(route, &args); let params = self.params.take().unwrap_or_default(); let send_res = send_for_reply_future(self.destination, payload.as_slice(), params); match send_res { - Ok(message_fut) => { - self.state = Some(message_fut); + Ok(future) => { + self.state = Some(GtsdFuture::Message { future }); } Err(err) => { return Poll::Ready(Err(err)); } } } - if let Some(message_fut) = self.project().state.as_pin_mut() { + let this = self.as_mut().project(); + if let Some(state) = this.state.as_pin_mut() + && let Projection::Message { future } = state.project() + { // Poll message future - match message_fut.poll(cx) { - Poll::Ready(Ok(bytes)) => match T::Reply::decode(&mut bytes.as_slice()) { - Ok(decoded) => Poll::Ready(Ok(decoded)), + match future.poll(cx) { + Poll::Ready(Ok(payload)) => match T::decode_reply_with_prefix(route, payload) { + Ok(reply) => Poll::Ready(Ok(reply)), Err(err) => Poll::Ready(Err(Error::Decode(err))), }, Poll::Ready(Err(err)) => Poll::Ready(Err(err)), Poll::Pending => Poll::Pending, } } else { - panic!("PendingCall polled after completion or invalid state"); + panic!("{PENDING_CALL_INVALID_STATE}"); } } } @@ -202,28 +218,35 @@ impl Future for PendingCtor { let mut program_future = prog::create_program_bytes_for_reply(code_id, salt, payload, value)?; - // self.state = Some(program_future); - self.program_id = Some(program_future.program_id); + // self.program_id = Some(program_future.program_id); + self.state = Some(GtsdFuture::CreateProgram { + future: program_future, + }); } - let this = self.project(); - if let Some(message_fut) = this.state.as_pin_mut() { - // Poll message future - match message_fut.poll(cx) { - Poll::Ready(Ok(bytes)) => match T::Reply::decode(&mut bytes.as_slice()) { - Ok(_decoded) => { - let program_id = this.program_id.take().unwrap_or_else(|| { - panic!("PendingCtor polled after completion or invalid state") - }); - let env = this.env.clone(); - Poll::Ready(Ok(Actor::new(env, program_id))) - } - Err(err) => Poll::Ready(Err(Error::Decode(err))), - }, + let this = self.as_mut().project(); + if let Some(state) = this.state.as_pin_mut() + && let Projection::CreateProgram { future } = state.project() + { + // Poll create program future + match future.poll(cx) { + Poll::Ready(Ok((program_id, _payload))) => { + // Do not decode payload here + let env = this.env.clone(); + Poll::Ready(Ok(Actor::new(env, program_id))) + } Poll::Ready(Err(err)) => Poll::Ready(Err(err)), Poll::Pending => Poll::Pending, } } else { - panic!("PendingCall polled after completion or invalid state"); + panic!("{PENDING_CTOR_INVALID_STATE}"); } } } + +pin_project_lite::pin_project! { + #[project = Projection] + pub enum GtsdFuture { + CreateProgram { #[pin] future: CreateProgramFuture }, + Message { #[pin] future: MessageFuture }, + } +} diff --git a/rs/src/client/gtest_env.rs b/rs/src/client/gtest_env.rs index e43ed4af5..69dfa11d3 100644 --- a/rs/src/client/gtest_env.rs +++ b/rs/src/client/gtest_env.rs @@ -235,9 +235,19 @@ impl Future for PendingCall { type Output = Result::Error>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let Some(route) = self.route else { + return Poll::Ready(Err(Error::ScaleCodecError( + "PendingCall route is not set".into(), + ))); + }; + if self.state.is_none() { // Send message - let payload = self.encode_call(); + let args = self + .args + .take() + .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); + let payload = T::encode_params_with_prefix(route, &args); let params = self.params.take().unwrap_or_default(); let send_res = self.env.send_message(self.destination, payload, params); @@ -252,17 +262,15 @@ impl Future for PendingCall { } } } - if let Some(reply_receiver) = self.project().state.as_pin_mut() { + let this = self.as_mut().project(); + if let Some(reply_receiver) = this.state.as_pin_mut() { // Poll reply receiver match reply_receiver.poll(cx) { Poll::Ready(Ok(res)) => match res { - // TODO handle reply prefix - Ok(bytes) => { - match <(String, String, T::Reply)>::decode(&mut bytes.as_slice()) { - Ok((_, _, decoded)) => Poll::Ready(Ok(decoded)), - Err(err) => Poll::Ready(Err(Error::ScaleCodecError(err))), - } - } + Ok(payload) => match T::decode_reply_with_prefix(route, payload) { + Ok(reply) => Poll::Ready(Ok(reply)), + Err(err) => Poll::Ready(Err(Error::ScaleCodecError(err))), + }, Err(err) => Poll::Ready(Err(err)), }, // TODO handle error @@ -299,7 +307,7 @@ impl Future for PendingCtor { } } } - let this = self.project(); + let this = self.as_mut().project(); if let Some(reply_receiver) = this.state.as_pin_mut() { // Poll reply receiver match reply_receiver.poll(cx) { @@ -307,9 +315,10 @@ impl Future for PendingCtor { // TODO handle reply prefix Ok(_) => { // TODO - let program_id = this.program_id.take().unwrap_or_else(|| { - panic!("PendingCtor polled after completion or invalid state") - }); + let program_id = this + .program_id + .take() + .unwrap_or_else(|| panic!("{PENDING_CTOR_INVALID_STATE}")); let env = this.env.clone(); Poll::Ready(Ok(Actor::new(env, program_id))) } @@ -320,7 +329,7 @@ impl Future for PendingCtor { Poll::Pending => Poll::Pending, } } else { - panic!("PendingCtor polled after completion or invalid state"); + panic!("{PENDING_CTOR_INVALID_STATE}"); } } } diff --git a/rs/src/client/mock_env.rs b/rs/src/client/mock_env.rs index fbb571e3b..b7aca5879 100644 --- a/rs/src/client/mock_env.rs +++ b/rs/src/client/mock_env.rs @@ -53,7 +53,7 @@ impl Future for PendingCall { let res = ready.into_inner(); Poll::Ready(res.map(|v| T::Reply::decode(&mut v.as_slice()).unwrap())) } - None => panic!("PendingCall polled after completion or invalid state"), + None => panic!("{PENDING_CALL_INVALID_STATE}"), } } } @@ -64,13 +64,14 @@ impl Future for PendingCtor { fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { match self.state.take() { Some(_ready) => { - let program_id = self.program_id.take().unwrap_or_else(|| { - panic!("PendingCtor polled after completion or invalid state") - }); + let program_id = self + .program_id + .take() + .unwrap_or_else(|| panic!("{PENDING_CTOR_INVALID_STATE}")); let env = self.env.clone(); Poll::Ready(Ok(Actor::new(env, program_id))) } - None => panic!("PendingCall polled after completion or invalid state"), + None => panic!("{PENDING_CTOR_INVALID_STATE}"), } } } diff --git a/rs/src/client/mod.rs b/rs/src/client/mod.rs index 0dc66a03c..2cdc7db4f 100644 --- a/rs/src/client/mod.rs +++ b/rs/src/client/mod.rs @@ -31,6 +31,11 @@ mod gstd_env; #[cfg(feature = "gstd")] pub use gstd_env::{GstdEnv, GstdParams}; +pub(crate) const PENDING_CALL_INVALID_STATE: &str = + "PendingCall polled after completion or invalid state"; +pub(crate) const PENDING_CTOR_INVALID_STATE: &str = + "PendingCtor polled after completion or invalid state"; + pub trait GearEnv: Clone { type Params: Default; type Error: Error; @@ -162,16 +167,6 @@ impl PendingCall { self.params = Some(f(self.params.unwrap_or_default())); self } - - fn encode_call(&self) -> Vec { - if let Some(route) = &self.route - && let Some(args) = &self.args - { - T::encode_params_with_prefix(route, args) - } else { - vec![] - } - } } pin_project_lite::pin_project! { @@ -238,16 +233,34 @@ pub trait CallEncodeDecode { fn decode_reply(payload: impl AsRef<[u8]>) -> Result { let mut value = payload.as_ref(); - let zero_size_reply = Self::is_empty_tuple::(); - if !zero_size_reply && !value.starts_with(Self::ROUTE.encode().as_slice()) { + if Self::is_empty_tuple::() { + return Decode::decode(&mut value); + } + // Decode payload as `(String, Self::Reply)` + let route = String::decode(&mut value)?; + if route != Self::ROUTE { + return Err("Invalid reply prefix".into()); + } + Decode::decode(&mut value) + } + + fn decode_reply_with_prefix( + prefix: Route, + payload: impl AsRef<[u8]>, + ) -> Result { + let mut value = payload.as_ref(); + if Self::is_empty_tuple::() { + return Decode::decode(&mut value); + } + // Decode payload as `(String, String, Self::Reply)` + let route = String::decode(&mut value)?; + if route != prefix { + return Err("Invalid reply prefix".into()); + } + let route = String::decode(&mut value)?; + if route != Self::ROUTE { return Err("Invalid reply prefix".into()); } - let start_idx = if zero_size_reply { - 0 - } else { - Self::ROUTE.len() - }; - value = &value[start_idx..]; Decode::decode(&mut value) } @@ -428,12 +441,13 @@ mod tests { assert_eq!(VALUE, &[20, 86, 97, 108, 117, 101]); } + #[test] fn test_io_struct_impl() { io_struct_impl!(Add (value: u32) -> u32); io_struct_impl!(Value () -> u32); let add = Add::encode_params(42); - assert_eq!(add, &[12, 65, 100, 100, 42]); + assert_eq!(add, &[12, 65, 100, 100, 42, 0, 0, 0]); let value = Value::encode_params(); assert_eq!(value, &[20, 86, 97, 108, 117, 101]); diff --git a/rs/src/gstd/events.rs b/rs/src/gstd/events.rs index ddba757f9..e5ad0e15d 100644 --- a/rs/src/gstd/events.rs +++ b/rs/src/gstd/events.rs @@ -223,6 +223,8 @@ mod tests { #[test] fn trait_optimized_event_encode() { let event = TestEvents::Event1(42); + assert_eq!(event.encode(), &[0, 42, 0, 0, 0]); + with_optimized_event_encode(&[1, 2, 3], event, |payload| { assert_eq!( payload, @@ -231,6 +233,7 @@ mod tests { }); let event = TestEvents::Event2 { p1: 43 }; + assert_eq!(event.encode(), &[1, 43, 0]); with_optimized_event_encode(&[], event, |payload| { assert_eq!(payload, [24, 69, 118, 101, 110, 116, 50, 43, 00]); }); diff --git a/rs/src/lib.rs b/rs/src/lib.rs index f3e3160f5..86da83ae1 100644 --- a/rs/src/lib.rs +++ b/rs/src/lib.rs @@ -20,6 +20,7 @@ pub use spin::{self}; #[cfg(feature = "client-builder")] mod builder; pub mod calls; +pub mod client; pub mod errors; #[cfg(not(target_arch = "wasm32"))] pub mod events; From 5c63597c3eb3209ade70f52ccae5bc4e104e6e00 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Tue, 12 Aug 2025 14:41:12 +0200 Subject: [PATCH 05/66] wip: envs --- examples/demo/client/src/env_client.rs | 10 +- rs/src/client/gclient_env.rs | 130 ++++++++++++++++++++----- rs/src/client/gstd_env.rs | 5 +- rs/src/client/gtest_env.rs | 9 +- rs/src/client/mock_env.rs | 2 +- rs/src/client/mod.rs | 19 ++-- 6 files changed, 126 insertions(+), 49 deletions(-) diff --git a/examples/demo/client/src/env_client.rs b/examples/demo/client/src/env_client.rs index 973df7b18..ec4b81033 100644 --- a/examples/demo/client/src/env_client.rs +++ b/examples/demo/client/src/env_client.rs @@ -14,7 +14,7 @@ pub trait DemoCtors { pub trait Demo { type Env: GearEnv; - fn counter(&self) -> Service; + fn counter(&self) -> Service; } pub struct DemoProgram; @@ -28,7 +28,7 @@ impl DemoProgram { Deployment::new(env, code_id, salt) } - pub fn client(env: E, program_id: ActorId) -> Actor { + pub fn client(env: E, program_id: ActorId) -> Actor { Actor::new(env, program_id) } } @@ -49,10 +49,10 @@ impl DemoCtors for Deployment { } } -impl Demo for Actor { +impl Demo for Actor { type Env = E; - fn counter(&self) -> Service { + fn counter(&self) -> Service { self.service("Counter") } } @@ -77,7 +77,7 @@ pub mod counter { pub struct CounterImpl; - impl Counter for Service { + impl Counter for Service { type Env = E; fn add(&mut self, value: u32) -> PendingCall { diff --git a/rs/src/client/gclient_env.rs b/rs/src/client/gclient_env.rs index 36732774c..094f06307 100644 --- a/rs/src/client/gclient_env.rs +++ b/rs/src/client/gclient_env.rs @@ -72,15 +72,50 @@ impl GclientEnv { impl GearEnv for GclientEnv { type Params = GclientParams; type Error = Error; - type MessageState = Pin, Error>>>>; + type MessageState = Pin), Error>>>>; +} + +async fn create_program( + api: GearApi, + code_id: CodeId, + salt: impl AsRef<[u8]>, + payload: impl AsRef<[u8]>, + params: GclientParams, +) -> Result<(ActorId, Vec), Error> { + let value = params.value.unwrap_or(0); + // Calculate gas amount if it is not explicitly set + #[cfg(not(feature = "ethexe"))] + let gas_limit = if let Some(gas_limit) = params.gas_limit { + gas_limit + } else { + // Calculate gas amount needed for initialization + let gas_info = api + .calculate_create_gas(None, code_id, Vec::from(payload.as_ref()), value, true) + .await?; + gas_info.min_limit + }; + #[cfg(feature = "ethexe")] + let gas_limit = 0; + + let mut listener = api.subscribe().await?; + let (message_id, program_id, ..) = api + .create_program_bytes(code_id, salt, payload, gas_limit, value) + .await?; + let (_, reply_code, payload, _) = wait_for_reply(&mut listener, message_id).await?; + // TODO handle errors + match reply_code { + ReplyCode::Success(_) => Ok((program_id, payload)), + ReplyCode::Error(_error_reply_reason) => todo!(), + ReplyCode::Unsupported => todo!(), + } } async fn send_message( api: GearApi, - target: ActorId, + program_id: ActorId, payload: Vec, params: GclientParams, -) -> Result, Error> { +) -> Result<(ActorId, Vec), Error> { let value = params.value.unwrap_or(0); #[cfg(not(feature = "ethexe"))] let gas_limit = if let Some(gas_limit) = params.gas_limit { @@ -88,7 +123,7 @@ async fn send_message( } else { // Calculate gas amount needed for handling the message let gas_info = api - .calculate_handle_gas(None, target, payload.clone(), value, true) + .calculate_handle_gas(None, program_id, payload.clone(), value, true) .await?; gas_info.min_limit }; @@ -97,12 +132,12 @@ async fn send_message( let mut listener = api.subscribe().await?; let (message_id, ..) = api - .send_message_bytes(target, payload, gas_limit, value) + .send_message_bytes(program_id, payload, gas_limit, value) .await?; let (_, reply_code, payload, _) = wait_for_reply(&mut listener, message_id).await?; // TODO handle errors match reply_code { - ReplyCode::Success(_) => Ok(payload), + ReplyCode::Success(_) => Ok((program_id, payload)), ReplyCode::Error(_error_reply_reason) => todo!(), ReplyCode::Unsupported => todo!(), } @@ -110,13 +145,16 @@ async fn send_message( impl PendingCall { pub async fn send(mut self) -> Result { + let Some(route) = self.route else { + return Err(Error::Codec("PendingCall route is not set".into())); + }; let api = &self.env.api; let params = self.params.unwrap_or_default(); let args = self .args .take() - .unwrap_or_else(|| panic!("PendingCtor polled after completion or invalid state")); - let payload = T::encode_params_with_prefix(self.route.unwrap(), &args); + .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); + let payload = T::encode_params_with_prefix(route, &args); let value = params.value.unwrap_or(0); #[cfg(not(feature = "ethexe"))] let gas_limit = if let Some(gas_limit) = params.gas_limit { @@ -138,12 +176,15 @@ impl PendingCall { } pub async fn query(mut self) -> Result { + let Some(route) = self.route else { + return Err(Error::Codec("PendingCall route is not set".into())); + }; let params = self.params.unwrap_or_default(); let args = self .args .take() - .unwrap_or_else(|| panic!("PendingCtor polled after completion or invalid state")); - let payload = T::encode_params(&args); + .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); + let payload = T::encode_params_with_prefix(route, &args); // Calculate reply let reply_bytes = self @@ -152,7 +193,7 @@ impl PendingCall { .await?; // Decode reply - match T::Reply::decode(&mut reply_bytes.as_slice()) { + match T::decode_reply_with_prefix(route, reply_bytes) { Ok(decoded) => Ok(decoded), Err(err) => Err(Error::Codec(err)), } @@ -163,30 +204,71 @@ impl Future for PendingCall { type Output = Result::Error>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let Some(route) = self.route else { + return Poll::Ready(Err(Error::Codec("PendingCall route is not set".into()))); + }; if self.state.is_none() { // Send message let params = self.params.take().unwrap_or_default(); let args = self .args .take() - .unwrap_or_else(|| panic!("PendingCtor polled after completion or invalid state")); - let payload = T::encode_params(&args); + .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); + let payload = T::encode_params_with_prefix(route, &args); let send_future = send_message(self.env.api.clone(), self.destination, payload, params); self.state = Some(Box::pin(send_future)); } - if let Some(message_future) = self.project().state.as_pin_mut() { - // Poll message future - match message_future.poll(cx) { - Poll::Ready(Ok(bytes)) => match T::Reply::decode(&mut bytes.as_slice()) { - Ok(decoded) => Poll::Ready(Ok(decoded)), - Err(err) => Poll::Ready(Err(Error::Codec(err))), - }, - Poll::Ready(Err(err)) => Poll::Ready(Err(err)), - Poll::Pending => Poll::Pending, + + let this = self.as_mut().project(); + let message_future = this + .state + .as_pin_mut() + .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); + // Poll message future + match message_future.poll(cx) { + Poll::Ready(Ok((_, payload))) => match T::decode_reply_with_prefix(route, payload) { + Ok(decoded) => Poll::Ready(Ok(decoded)), + Err(err) => Poll::Ready(Err(Error::Codec(err))), + }, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Pending => Poll::Pending, + } + } +} + +impl Future for PendingCtor { + type Output = Result, ::Error>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.state.is_none() { + // Send message + let params = self.params.take().unwrap_or_default(); + let salt = self.salt.take().unwrap(); + let args = self + .args + .take() + .unwrap_or_else(|| panic!("{PENDING_CTOR_INVALID_STATE}")); + let payload = T::encode_params(&args); + + let create_program_future = + create_program(self.env.api.clone(), self.code_id, salt, payload, params); + self.state = Some(Box::pin(create_program_future)); + } + + let this = self.as_mut().project(); + let message_future = this + .state + .as_pin_mut() + .unwrap_or_else(|| panic!("{PENDING_CTOR_INVALID_STATE}")); + // Poll message future + match message_future.poll(cx) { + Poll::Ready(Ok((program_id, _))) => { + // Do not decode payload here + Poll::Ready(Ok(Actor::new(this.env.clone(), program_id))) } - } else { - panic!("PendingCall polled after completion or invalid state"); + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Pending => Poll::Pending, } } } diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index 764b77546..310aef6ff 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -185,7 +185,7 @@ impl Future for PendingCall { } impl Future for PendingCtor { - type Output = Result, ::Error>; + type Output = Result, ::Error>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if self.state.is_none() { @@ -231,8 +231,7 @@ impl Future for PendingCtor { match future.poll(cx) { Poll::Ready(Ok((program_id, _payload))) => { // Do not decode payload here - let env = this.env.clone(); - Poll::Ready(Ok(Actor::new(env, program_id))) + Poll::Ready(Ok(Actor::new(this.env.clone(), program_id))) } Poll::Ready(Err(err)) => Poll::Ready(Err(err)), Poll::Pending => Poll::Pending, diff --git a/rs/src/client/gtest_env.rs b/rs/src/client/gtest_env.rs index 69dfa11d3..dc55e077e 100644 --- a/rs/src/client/gtest_env.rs +++ b/rs/src/client/gtest_env.rs @@ -133,7 +133,7 @@ impl GtestEnv { } } - fn activate( + fn create_program( &self, code_id: CodeId, salt: impl AsRef<[u8]>, @@ -284,7 +284,7 @@ impl Future for PendingCall { } impl Future for PendingCtor { - type Output = Result, ::Error>; + type Output = Result, ::Error>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if self.state.is_none() { @@ -294,7 +294,7 @@ impl Future for PendingCtor { let salt = self.salt.take().unwrap_or_default(); let send_res = self .env - .activate(self.code_id, salt, payload.as_slice(), params); + .create_program(self.code_id, salt, payload.as_slice(), params); match send_res { Ok((program_id, message_id)) => { log::debug!("PendingCall: send message {message_id:?}"); @@ -319,8 +319,7 @@ impl Future for PendingCtor { .program_id .take() .unwrap_or_else(|| panic!("{PENDING_CTOR_INVALID_STATE}")); - let env = this.env.clone(); - Poll::Ready(Ok(Actor::new(env, program_id))) + Poll::Ready(Ok(Actor::new(this.env.clone(), program_id))) } Err(err) => Poll::Ready(Err(err)), }, diff --git a/rs/src/client/mock_env.rs b/rs/src/client/mock_env.rs index b7aca5879..ea23cf0ef 100644 --- a/rs/src/client/mock_env.rs +++ b/rs/src/client/mock_env.rs @@ -59,7 +59,7 @@ impl Future for PendingCall { } impl Future for PendingCtor { - type Output = Result, ::Error>; + type Output = Result, ::Error>; fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { match self.state.take() { diff --git a/rs/src/client/mod.rs b/rs/src/client/mod.rs index 2cdc7db4f..b84e1e23c 100644 --- a/rs/src/client/mod.rs +++ b/rs/src/client/mod.rs @@ -84,13 +84,13 @@ impl Deployment { } } -pub struct Actor { +pub struct Actor { env: E, id: ActorId, _phantom: PhantomData, } -impl Actor { +impl Actor { pub fn new(env: E, id: ActorId) -> Self { Actor { env, @@ -99,7 +99,7 @@ impl Actor { } } - pub fn with_env(self, env: N) -> Actor { + pub fn with_env(self, env: N) -> Actor { let Self { env: _, id, @@ -112,19 +112,19 @@ impl Actor { } } - pub fn service(&self, route: Route) -> Service { + pub fn service(&self, route: Route) -> Service { Service::new(self.env.clone(), self.id, route) } } -pub struct Service { +pub struct Service { env: E, actor_id: ActorId, route: Route, _phantom: PhantomData, } -impl Service { +impl Service { pub fn new(env: E, actor_id: ActorId, route: Route) -> Self { Service { env, @@ -429,8 +429,8 @@ macro_rules! str_scale_encode { #[cfg(test)] mod tests { use super::*; - struct Add; - struct Value; + io_struct_impl!(Add (value: u32) -> u32); + io_struct_impl!(Value () -> u32); #[test] fn test_str_encode() { @@ -443,9 +443,6 @@ mod tests { #[test] fn test_io_struct_impl() { - io_struct_impl!(Add (value: u32) -> u32); - io_struct_impl!(Value () -> u32); - let add = Add::encode_params(42); assert_eq!(add, &[12, 65, 100, 100, 42, 0, 0, 0]); From 62a821ae92936777053377569e173e63fb63a9c1 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Tue, 12 Aug 2025 18:41:28 +0200 Subject: [PATCH 06/66] wip: Client Gen --- .../ping-pong/app/src/ping_pong_client.rs | 13 +- benchmarks/src/alloc_stress_program.rs | 119 ++---- benchmarks/src/compute_stress_program.rs | 122 ++---- benchmarks/src/counter_bench_program.rs | 137 ++----- examples/demo/client/build.rs | 10 +- examples/demo/client/src/env_client.rs | 16 +- .../no-svcs-prog/wasm/src/no_svcs_prog.rs | 61 +-- examples/no-svcs-prog/wasm/tests/tests.rs | 13 +- .../redirect/client/src/redirect_client.rs | 142 ++----- .../proxy-client/src/redirect_proxy_client.rs | 122 ++---- examples/redirect/proxy/src/lib.rs | 10 +- rs/client-gen/src/ctor_generators.rs | 162 ++------ rs/client-gen/src/events_generator.rs | 20 +- rs/client-gen/src/helpers.rs | 21 +- rs/client-gen/src/io_generators.rs | 128 ------ rs/client-gen/src/lib.rs | 1 - rs/client-gen/src/root_generator.rs | 107 +++-- rs/client-gen/src/service_generators.rs | 168 ++++---- rs/client-gen/tests/generator.rs | 20 +- .../snapshots/generator__basic_works.snap | 86 ++-- .../snapshots/generator__events_works.snap | 91 ++--- .../snapshots/generator__external_types.snap | 85 ++-- .../tests/snapshots/generator__full.snap | 247 ++++-------- .../generator__full_with_sails_path.snap | 280 ++++++------- .../generator__multiple_services.snap | 132 ++---- .../snapshots/generator__nonzero_works.snap | 73 +--- .../snapshots/generator__rmrk_works.snap | 375 +++++------------- rs/src/client/mod.rs | 53 +++ 28 files changed, 924 insertions(+), 1890 deletions(-) delete mode 100644 rs/client-gen/src/io_generators.rs diff --git a/benchmarks/ping-pong/app/src/ping_pong_client.rs b/benchmarks/ping-pong/app/src/ping_pong_client.rs index 92222a031..5e7c2681a 100644 --- a/benchmarks/ping-pong/app/src/ping_pong_client.rs +++ b/benchmarks/ping-pong/app/src/ping_pong_client.rs @@ -29,18 +29,7 @@ pub mod ping_pong_factory { pub mod io { use super::*; use sails_rs::calls::ActionIo; - pub struct NewForBench(()); - impl NewForBench { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } - } - impl ActionIo for NewForBench { - const ROUTE: &'static [u8] = &[44, 78, 101, 119, 70, 111, 114, 66, 101, 110, 99, 104]; - type Params = (); - type Reply = (); - } + sails_rs::io_struct_impl!(NewForBench () -> ()); } } pub struct PingPongService { diff --git a/benchmarks/src/alloc_stress_program.rs b/benchmarks/src/alloc_stress_program.rs index 70824b6bd..d8cef865d 100644 --- a/benchmarks/src/alloc_stress_program.rs +++ b/benchmarks/src/alloc_stress_program.rs @@ -1,87 +1,50 @@ // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::collections::BTreeMap; -#[allow(unused_imports)] -use sails_rs::{ - String, - calls::{Activation, Call, Query, Remoting, RemotingAction}, - prelude::*, -}; -pub struct AllocStressProgramFactory { - #[allow(dead_code)] - remoting: R, +use sails_rs::{client::*, prelude::*}; +pub struct AllocStressProgramProgram; +impl Program for AllocStressProgramProgram {} +pub trait AllocStressProgram { + type Env: GearEnv; + fn alloc_stress(&self) -> Service; } -impl AllocStressProgramFactory { - #[allow(unused)] - pub fn new(remoting: R) -> Self { - Self { remoting } +impl AllocStressProgram for Actor { + type Env = E; + fn alloc_stress(&self) -> Service { + self.service(stringify!(alloc_stress::AllocStress)) } } -impl traits::AllocStressProgramFactory for AllocStressProgramFactory { - type Args = R::Args; - fn new_for_bench(&self) -> impl Activation { - RemotingAction::<_, alloc_stress_program_factory::io::NewForBench>::new( - self.remoting.clone(), - (), - ) +pub trait AllocStressProgramCtors { + type Env: GearEnv; + fn new_for_bench(self) -> PendingCtor; +} +impl AllocStressProgramCtors for Deployment { + type Env = E; + fn new_for_bench(self) -> PendingCtor { + self.pending_ctor(()) } } -pub mod alloc_stress_program_factory { +pub mod io { use super::*; - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct NewForBench(()); - impl NewForBench { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } - } - impl ActionIo for NewForBench { - const ROUTE: &'static [u8] = &[44, 78, 101, 119, 70, 111, 114, 66, 101, 110, 99, 104]; - type Params = (); - type Reply = (); - } - } -} -pub struct AllocStress { - remoting: R, -} -impl AllocStress { - pub fn new(remoting: R) -> Self { - Self { remoting } - } -} -impl traits::AllocStress for AllocStress { - type Args = R::Args; - fn alloc_stress(&mut self, n: u32) -> impl Call { - RemotingAction::<_, alloc_stress::io::AllocStress>::new(self.remoting.clone(), n) - } + sails_rs::io_struct_impl!(NewForBench () -> ()); } pub mod alloc_stress { use super::*; + pub trait AllocStress { + type Env: GearEnv; + fn alloc_stress(&mut self, n: u32) -> PendingCall; + } + pub struct AllocStressImpl; + impl AllocStress for Service { + type Env = E; + fn alloc_stress(&mut self, n: u32) -> PendingCall { + self.pending_call((n,)) + } + } pub mod io { use super::*; - use sails_rs::calls::ActionIo; - pub struct AllocStress(()); - impl AllocStress { - #[allow(dead_code)] - pub fn encode_call(n: u32) -> Vec { - ::encode_call(&n) - } - } - impl ActionIo for AllocStress { - const ROUTE: &'static [u8] = &[ - 44, 65, 108, 108, 111, 99, 83, 116, 114, 101, 115, 115, 44, 65, 108, 108, 111, 99, - 83, 116, 114, 101, 115, 115, - ]; - type Params = u32; - type Reply = super::AllocStressResult; - } + sails_rs::io_struct_impl!(AllocStress (n: u32) -> AllocStressResult); } } #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo)] @@ -90,21 +53,3 @@ pub mod alloc_stress { pub struct AllocStressResult { pub inner: Vec, } - -pub mod traits { - use super::*; - #[allow(dead_code)] - pub trait AllocStressProgramFactory { - type Args; - fn new_for_bench(&self) -> impl Activation; - } - - #[allow(clippy::type_complexity)] - pub trait AllocStress { - type Args; - fn alloc_stress( - &mut self, - n: u32, - ) -> impl Call; - } -} diff --git a/benchmarks/src/compute_stress_program.rs b/benchmarks/src/compute_stress_program.rs index 04eb61e63..df5e4a03b 100644 --- a/benchmarks/src/compute_stress_program.rs +++ b/benchmarks/src/compute_stress_program.rs @@ -1,90 +1,50 @@ // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::collections::BTreeMap; -#[allow(unused_imports)] -use sails_rs::{ - String, - calls::{Activation, Call, Query, Remoting, RemotingAction}, - prelude::*, -}; -pub struct ComputeStressProgramFactory { - #[allow(dead_code)] - remoting: R, +use sails_rs::{client::*, prelude::*}; +pub struct ComputeStressProgramProgram; +impl Program for ComputeStressProgramProgram {} +pub trait ComputeStressProgram { + type Env: GearEnv; + fn compute_stress(&self) -> Service; } -impl ComputeStressProgramFactory { - #[allow(unused)] - pub fn new(remoting: R) -> Self { - Self { remoting } +impl ComputeStressProgram for Actor { + type Env = E; + fn compute_stress(&self) -> Service { + self.service(stringify!(compute_stress::ComputeStress)) } } -impl traits::ComputeStressProgramFactory for ComputeStressProgramFactory { - type Args = R::Args; - fn new_for_bench(&self) -> impl Activation { - RemotingAction::<_, compute_stress_program_factory::io::NewForBench>::new( - self.remoting.clone(), - (), - ) +pub trait ComputeStressProgramCtors { + type Env: GearEnv; + fn new_for_bench(self) -> PendingCtor; +} +impl ComputeStressProgramCtors for Deployment { + type Env = E; + fn new_for_bench(self) -> PendingCtor { + self.pending_ctor(()) } } -pub mod compute_stress_program_factory { +pub mod io { use super::*; - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct NewForBench(()); - impl NewForBench { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } - } - impl ActionIo for NewForBench { - const ROUTE: &'static [u8] = &[44, 78, 101, 119, 70, 111, 114, 66, 101, 110, 99, 104]; - type Params = (); - type Reply = (); - } - } -} -pub struct ComputeStress { - remoting: R, -} -impl ComputeStress { - pub fn new(remoting: R) -> Self { - Self { remoting } - } -} -impl traits::ComputeStress for ComputeStress { - type Args = R::Args; - fn compute_stress( - &mut self, - n: u32, - ) -> impl Call { - RemotingAction::<_, compute_stress::io::ComputeStress>::new(self.remoting.clone(), n) - } + sails_rs::io_struct_impl!(NewForBench () -> ()); } pub mod compute_stress { use super::*; + pub trait ComputeStress { + type Env: GearEnv; + fn compute_stress(&mut self, n: u32) -> PendingCall; + } + pub struct ComputeStressImpl; + impl ComputeStress for Service { + type Env = E; + fn compute_stress(&mut self, n: u32) -> PendingCall { + self.pending_call((n,)) + } + } pub mod io { use super::*; - use sails_rs::calls::ActionIo; - pub struct ComputeStress(()); - impl ComputeStress { - #[allow(dead_code)] - pub fn encode_call(n: u32) -> Vec { - ::encode_call(&n) - } - } - impl ActionIo for ComputeStress { - const ROUTE: &'static [u8] = &[ - 52, 67, 111, 109, 112, 117, 116, 101, 83, 116, 114, 101, 115, 115, 52, 67, 111, - 109, 112, 117, 116, 101, 83, 116, 114, 101, 115, 115, - ]; - type Params = u32; - type Reply = super::ComputeStressResult; - } + sails_rs::io_struct_impl!(ComputeStress (n: u32) -> ComputeStressResult); } } #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo)] @@ -93,21 +53,3 @@ pub mod compute_stress { pub struct ComputeStressResult { pub res: u32, } - -pub mod traits { - use super::*; - #[allow(dead_code)] - pub trait ComputeStressProgramFactory { - type Args; - fn new_for_bench(&self) -> impl Activation; - } - - #[allow(clippy::type_complexity)] - pub trait ComputeStress { - type Args; - fn compute_stress( - &mut self, - n: u32, - ) -> impl Call; - } -} diff --git a/benchmarks/src/counter_bench_program.rs b/benchmarks/src/counter_bench_program.rs index 56be0d435..a92f515c7 100644 --- a/benchmarks/src/counter_bench_program.rs +++ b/benchmarks/src/counter_bench_program.rs @@ -1,119 +1,54 @@ // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::collections::BTreeMap; -#[allow(unused_imports)] -use sails_rs::{ - String, - calls::{Activation, Call, Query, Remoting, RemotingAction}, - prelude::*, -}; -pub struct CounterBenchProgramFactory { - #[allow(dead_code)] - remoting: R, +use sails_rs::{client::*, prelude::*}; +pub struct CounterBenchProgramProgram; +impl Program for CounterBenchProgramProgram {} +pub trait CounterBenchProgram { + type Env: GearEnv; + fn counter_bench(&self) -> Service; } -impl CounterBenchProgramFactory { - #[allow(unused)] - pub fn new(remoting: R) -> Self { - Self { remoting } +impl CounterBenchProgram for Actor { + type Env = E; + fn counter_bench(&self) -> Service { + self.service(stringify!(counter_bench::CounterBench)) } } -impl traits::CounterBenchProgramFactory for CounterBenchProgramFactory { - type Args = R::Args; - fn new_for_bench(&self) -> impl Activation { - RemotingAction::<_, counter_bench_program_factory::io::NewForBench>::new( - self.remoting.clone(), - (), - ) +pub trait CounterBenchProgramCtors { + type Env: GearEnv; + fn new_for_bench(self) -> PendingCtor; +} +impl CounterBenchProgramCtors for Deployment { + type Env = E; + fn new_for_bench(self) -> PendingCtor { + self.pending_ctor(()) } } -pub mod counter_bench_program_factory { +pub mod io { use super::*; - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct NewForBench(()); - impl NewForBench { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } - } - impl ActionIo for NewForBench { - const ROUTE: &'static [u8] = &[44, 78, 101, 119, 70, 111, 114, 66, 101, 110, 99, 104]; - type Params = (); - type Reply = (); - } - } -} -pub struct CounterBench { - remoting: R, -} -impl CounterBench { - pub fn new(remoting: R) -> Self { - Self { remoting } - } -} -impl traits::CounterBench for CounterBench { - type Args = R::Args; - fn inc(&mut self) -> impl Call { - RemotingAction::<_, counter_bench::io::Inc>::new(self.remoting.clone(), ()) - } - fn inc_async(&mut self) -> impl Call { - RemotingAction::<_, counter_bench::io::IncAsync>::new(self.remoting.clone(), ()) - } + sails_rs::io_struct_impl!(NewForBench () -> ()); } pub mod counter_bench { use super::*; - - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct Inc(()); - impl Inc { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } - } - impl ActionIo for Inc { - const ROUTE: &'static [u8] = &[ - 48, 67, 111, 117, 110, 116, 101, 114, 66, 101, 110, 99, 104, 12, 73, 110, 99, - ]; - type Params = (); - type Reply = u64; - } - pub struct IncAsync(()); - impl IncAsync { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } + pub trait CounterBench { + type Env: GearEnv; + fn inc(&mut self) -> PendingCall; + fn inc_async(&mut self) -> PendingCall; + } + pub struct CounterBenchImpl; + impl CounterBench for Service { + type Env = E; + fn inc(&mut self) -> PendingCall { + self.pending_call(()) } - impl ActionIo for IncAsync { - const ROUTE: &'static [u8] = &[ - 48, 67, 111, 117, 110, 116, 101, 114, 66, 101, 110, 99, 104, 32, 73, 110, 99, 65, - 115, 121, 110, 99, - ]; - type Params = (); - type Reply = u64; + fn inc_async(&mut self) -> PendingCall { + self.pending_call(()) } } -} - -pub mod traits { - use super::*; - #[allow(dead_code)] - pub trait CounterBenchProgramFactory { - type Args; - fn new_for_bench(&self) -> impl Activation; - } - #[allow(clippy::type_complexity)] - pub trait CounterBench { - type Args; - fn inc(&mut self) -> impl Call; - fn inc_async(&mut self) -> impl Call; + pub mod io { + use super::*; + sails_rs::io_struct_impl!(Inc () -> u64); + sails_rs::io_struct_impl!(IncAsync () -> u64); } } diff --git a/examples/demo/client/build.rs b/examples/demo/client/build.rs index e9c7d1f7f..697f1a730 100644 --- a/examples/demo/client/build.rs +++ b/examples/demo/client/build.rs @@ -1,8 +1,8 @@ fn main() { // Generate IDL file for the `Demo` app and client code from IDL file - sails_rs::ClientBuilder::::from_env() - .build_idl() - .with_mocks("with_mocks") - .generate() - .unwrap(); + // sails_rs::ClientBuilder::::from_env() + // .build_idl() + // .with_mocks("with_mocks") + // .generate() + // .unwrap(); } diff --git a/examples/demo/client/src/env_client.rs b/examples/demo/client/src/env_client.rs index ec4b81033..4b4d6dd3c 100644 --- a/examples/demo/client/src/env_client.rs +++ b/examples/demo/client/src/env_client.rs @@ -19,19 +19,7 @@ pub trait Demo { pub struct DemoProgram; -impl DemoProgram { - pub fn deploy( - env: E, - code_id: CodeId, - salt: Vec, - ) -> Deployment { - Deployment::new(env, code_id, salt) - } - - pub fn client(env: E, program_id: ActorId) -> Actor { - Actor::new(env, program_id) - } -} +impl Program for DemoProgram {} impl DemoCtors for Deployment { type Env = E; @@ -61,7 +49,7 @@ pub mod io { use super::*; use sails_rs::client::{CallEncodeDecode, Route}; sails_rs::io_struct_impl!(Default () -> ()); - sails_rs::io_struct_impl!(New (counter: Option, dog_position: Option<(i32, i32)>) -> ()); + sails_rs::io_struct_impl!(New (counter: Option, dog_position: Option<(i32, i32),>) -> ()); } /// Counter Service diff --git a/examples/no-svcs-prog/wasm/src/no_svcs_prog.rs b/examples/no-svcs-prog/wasm/src/no_svcs_prog.rs index 5de9d3a48..45f8413ac 100644 --- a/examples/no-svcs-prog/wasm/src/no_svcs_prog.rs +++ b/examples/no-svcs-prog/wasm/src/no_svcs_prog.rs @@ -1,54 +1,25 @@ // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::collections::BTreeMap; -#[allow(unused_imports)] -use sails_rs::{ - String, - calls::{Activation, Call, Query, Remoting, RemotingAction}, - prelude::*, -}; -pub struct NoSvcsProgFactory { - #[allow(dead_code)] - remoting: R, +use sails_rs::{client::*, prelude::*}; +pub struct NoSvcsProgProgram; +impl Program for NoSvcsProgProgram {} +pub trait NoSvcsProg { + type Env: GearEnv; } -impl NoSvcsProgFactory { - #[allow(unused)] - pub fn new(remoting: R) -> Self { - Self { remoting } - } +impl NoSvcsProg for Actor { + type Env = E; } -impl traits::NoSvcsProgFactory for NoSvcsProgFactory { - type Args = R::Args; - fn create(&self) -> impl Activation { - RemotingAction::<_, no_svcs_prog_factory::io::Create>::new(self.remoting.clone(), ()) - } +pub trait NoSvcsProgCtors { + type Env: GearEnv; + fn create(self) -> PendingCtor; } - -pub mod no_svcs_prog_factory { - use super::*; - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct Create(()); - impl Create { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } - } - impl ActionIo for Create { - const ROUTE: &'static [u8] = &[24, 67, 114, 101, 97, 116, 101]; - type Params = (); - type Reply = (); - } +impl NoSvcsProgCtors for Deployment { + type Env = E; + fn create(self) -> PendingCtor { + self.pending_ctor(()) } } -pub mod traits { +pub mod io { use super::*; - #[allow(dead_code)] - pub trait NoSvcsProgFactory { - type Args; - fn create(&self) -> impl Activation; - } + sails_rs::io_struct_impl!(Create () -> ()); } diff --git a/examples/no-svcs-prog/wasm/tests/tests.rs b/examples/no-svcs-prog/wasm/tests/tests.rs index 1dd5d22c2..b88994cb9 100644 --- a/examples/no-svcs-prog/wasm/tests/tests.rs +++ b/examples/no-svcs-prog/wasm/tests/tests.rs @@ -1,8 +1,6 @@ -use no_svcs_prog::client::{NoSvcsProgFactory, traits::NoSvcsProgFactory as _}; -use sails_rs::{ - calls::*, - gtest::{System, calls::*}, -}; +use no_svcs_prog::client::*; +use sails_rs::client::*; +use sails_rs::gtest::{System}; const ADMIN_ID: u64 = 10; const WASM_PATH: &str = "../../../target/wasm32-gear/debug/no_svcs_prog.opt.wasm"; @@ -14,11 +12,10 @@ async fn activating_program_succeeds() { system.mint_to(ADMIN_ID, 100_000_000_000_000); let program_code_id = system.submit_code_file(WASM_PATH); - let remoting = GTestRemoting::new(system, ADMIN_ID.into()); + let env = GtestEnv::new(system, ADMIN_ID.into()); - let result = NoSvcsProgFactory::new(remoting.clone()) + let result = NoSvcsProgProgram::deploy(env, program_code_id, vec![]) .create() - .send_recv(program_code_id, "123") .await; assert!(result.is_ok()); diff --git a/examples/redirect/client/src/redirect_client.rs b/examples/redirect/client/src/redirect_client.rs index a4ab7141c..8ae27277c 100644 --- a/examples/redirect/client/src/redirect_client.rs +++ b/examples/redirect/client/src/redirect_client.rs @@ -1,120 +1,58 @@ // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::collections::BTreeMap; -#[allow(unused_imports)] -use sails_rs::{ - String, - calls::{Activation, Call, Query, Remoting, RemotingAction}, - prelude::*, -}; -pub struct RedirectClientFactory { - #[allow(dead_code)] - remoting: R, +use sails_rs::{client::*, prelude::*}; +pub struct RedirectClientProgram; +impl Program for RedirectClientProgram {} +pub trait RedirectClient { + type Env: GearEnv; + fn redirect(&self) -> Service; } -impl RedirectClientFactory { - #[allow(unused)] - pub fn new(remoting: R) -> Self { - Self { remoting } +impl RedirectClient for Actor { + type Env = E; + fn redirect(&self) -> Service { + self.service(stringify!(redirect::Redirect)) } } -impl traits::RedirectClientFactory for RedirectClientFactory { - type Args = R::Args; - fn new(&self) -> impl Activation { - RemotingAction::<_, redirect_client_factory::io::New>::new(self.remoting.clone(), ()) +pub trait RedirectClientCtors { + type Env: GearEnv; + #[allow(clippy::new_ret_no_self)] + #[allow(clippy::wrong_self_convention)] + fn new(self) -> PendingCtor; +} +impl RedirectClientCtors for Deployment { + type Env = E; + fn new(self) -> PendingCtor { + self.pending_ctor(()) } } -pub mod redirect_client_factory { +pub mod io { use super::*; - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct New(()); - impl New { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } - } - impl ActionIo for New { - const ROUTE: &'static [u8] = &[12, 78, 101, 119]; - type Params = (); - type Reply = (); - } - } -} -pub struct Redirect { - remoting: R, -} -impl Redirect { - pub fn new(remoting: R) -> Self { - Self { remoting } - } -} -impl traits::Redirect for Redirect { - type Args = R::Args; - /// Exit from program with inheritor ID - fn exit(&mut self, inheritor_id: ActorId) -> impl Call { - RemotingAction::<_, redirect::io::Exit>::new(self.remoting.clone(), inheritor_id) - } - /// Returns program ID of the current program - fn get_program_id(&self) -> impl Query { - RemotingAction::<_, redirect::io::GetProgramId>::new(self.remoting.clone(), ()) - } + sails_rs::io_struct_impl!(New () -> ()); } pub mod redirect { use super::*; - - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct Exit(()); - impl Exit { - #[allow(dead_code)] - pub fn encode_call(inheritor_id: ActorId) -> Vec { - ::encode_call(&inheritor_id) - } - } - impl ActionIo for Exit { - const ROUTE: &'static [u8] = &[ - 32, 82, 101, 100, 105, 114, 101, 99, 116, 16, 69, 120, 105, 116, - ]; - type Params = ActorId; - type Reply = (); - } - pub struct GetProgramId(()); - impl GetProgramId { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } + pub trait Redirect { + type Env: GearEnv; + /// Exit from program with inheritor ID + fn exit(&mut self, inheritor_id: ActorId) -> PendingCall; + /// Returns program ID of the current program + fn get_program_id(&self) -> PendingCall; + } + pub struct RedirectImpl; + impl Redirect for Service { + type Env = E; + fn exit(&mut self, inheritor_id: ActorId) -> PendingCall { + self.pending_call((inheritor_id,)) } - impl ActionIo for GetProgramId { - const ROUTE: &'static [u8] = &[ - 32, 82, 101, 100, 105, 114, 101, 99, 116, 48, 71, 101, 116, 80, 114, 111, 103, 114, - 97, 109, 73, 100, - ]; - type Params = (); - type Reply = ActorId; + fn get_program_id(&self) -> PendingCall { + self.pending_call(()) } } -} - -pub mod traits { - use super::*; - #[allow(dead_code)] - pub trait RedirectClientFactory { - type Args; - #[allow(clippy::new_ret_no_self)] - #[allow(clippy::wrong_self_convention)] - fn new(&self) -> impl Activation; - } - #[allow(clippy::type_complexity)] - pub trait Redirect { - type Args; - fn exit(&mut self, inheritor_id: ActorId) -> impl Call; - fn get_program_id(&self) -> impl Query; + pub mod io { + use super::*; + sails_rs::io_struct_impl!(Exit (inheritor_id: ActorId) -> ()); + sails_rs::io_struct_impl!(GetProgramId () -> ActorId); } } diff --git a/examples/redirect/proxy-client/src/redirect_proxy_client.rs b/examples/redirect/proxy-client/src/redirect_proxy_client.rs index 0ed62bb81..40f5ed71e 100644 --- a/examples/redirect/proxy-client/src/redirect_proxy_client.rs +++ b/examples/redirect/proxy-client/src/redirect_proxy_client.rs @@ -1,105 +1,51 @@ // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::collections::BTreeMap; -#[allow(unused_imports)] -use sails_rs::{ - String, - calls::{Activation, Call, Query, Remoting, RemotingAction}, - prelude::*, -}; -pub struct RedirectProxyClientFactory { - #[allow(dead_code)] - remoting: R, +use sails_rs::{client::*, prelude::*}; +pub struct RedirectProxyClientProgram; +impl Program for RedirectProxyClientProgram {} +pub trait RedirectProxyClient { + type Env: GearEnv; + fn proxy(&self) -> Service; } -impl RedirectProxyClientFactory { - #[allow(unused)] - pub fn new(remoting: R) -> Self { - Self { remoting } +impl RedirectProxyClient for Actor { + type Env = E; + fn proxy(&self) -> Service { + self.service(stringify!(proxy::Proxy)) } } -impl traits::RedirectProxyClientFactory for RedirectProxyClientFactory { - type Args = R::Args; - /// Proxy Program's constructor - fn new(&self, target: ActorId) -> impl Activation { - RemotingAction::<_, redirect_proxy_client_factory::io::New>::new( - self.remoting.clone(), - target, - ) +pub trait RedirectProxyClientCtors { + type Env: GearEnv; + /// Proxy Program's constructor#[allow(clippy::new_ret_no_self)] #[allow(clippy::wrong_self_convention)] + fn new(self, target: ActorId) -> PendingCtor; +} +impl RedirectProxyClientCtors for Deployment { + type Env = E; + fn new(self, target: ActorId) -> PendingCtor { + self.pending_ctor((target,)) } } -pub mod redirect_proxy_client_factory { +pub mod io { use super::*; - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct New(()); - impl New { - #[allow(dead_code)] - pub fn encode_call(target: ActorId) -> Vec { - ::encode_call(&target) - } - } - impl ActionIo for New { - const ROUTE: &'static [u8] = &[12, 78, 101, 119]; - type Params = ActorId; - type Reply = (); - } - } -} -pub struct Proxy { - remoting: R, -} -impl Proxy { - pub fn new(remoting: R) -> Self { - Self { remoting } - } -} -impl traits::Proxy for Proxy { - type Args = R::Args; - /// Get program ID of the target program via client - fn get_program_id(&self) -> impl Query { - RemotingAction::<_, proxy::io::GetProgramId>::new(self.remoting.clone(), ()) - } + sails_rs::io_struct_impl!(New (target: ActorId) -> ()); } pub mod proxy { use super::*; - - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct GetProgramId(()); - impl GetProgramId { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } - } - impl ActionIo for GetProgramId { - const ROUTE: &'static [u8] = &[ - 20, 80, 114, 111, 120, 121, 48, 71, 101, 116, 80, 114, 111, 103, 114, 97, 109, 73, - 100, - ]; - type Params = (); - type Reply = ActorId; + pub trait Proxy { + type Env: GearEnv; + /// Get program ID of the target program via client + fn get_program_id(&self) -> PendingCall; + } + pub struct ProxyImpl; + impl Proxy for Service { + type Env = E; + fn get_program_id(&self) -> PendingCall { + self.pending_call(()) } } -} -pub mod traits { - use super::*; - #[allow(dead_code)] - pub trait RedirectProxyClientFactory { - type Args; - #[allow(clippy::new_ret_no_self)] - #[allow(clippy::wrong_self_convention)] - fn new(&self, target: ActorId) -> impl Activation; - } - - #[allow(clippy::type_complexity)] - pub trait Proxy { - type Args; - fn get_program_id(&self) -> impl Query; + pub mod io { + use super::*; + sails_rs::io_struct_impl!(GetProgramId () -> ActorId); } } diff --git a/examples/redirect/proxy/src/lib.rs b/examples/redirect/proxy/src/lib.rs index 82d8a46fd..fc24513a5 100644 --- a/examples/redirect/proxy/src/lib.rs +++ b/examples/redirect/proxy/src/lib.rs @@ -1,7 +1,7 @@ #![no_std] -use redirect_client::{Redirect, traits::Redirect as _}; -use sails_rs::{calls::Query as _, gstd::calls::*, prelude::*}; +use redirect_client::{redirect::Redirect as _, *}; +use sails_rs::{client::*, prelude::*}; struct ProxyService(ActorId); @@ -16,12 +16,12 @@ impl ProxyService { /// Get program ID of the target program via client #[sails_rs::export] pub async fn get_program_id(&self) -> ActorId { - let client = Redirect::new(GStdRemoting::new()); + let client = RedirectClientProgram::client(GstdEnv, self.0).redirect(); client .get_program_id() // Set flag to redirect on exit - .with_redirect_on_exit(true) - .recv(self.0) + // .with_redirect_on_exit(true) + // .recv(self.0) .await .unwrap() } diff --git a/rs/client-gen/src/ctor_generators.rs b/rs/client-gen/src/ctor_generators.rs index 4d9a3f9f7..cbfdfa415 100644 --- a/rs/client-gen/src/ctor_generators.rs +++ b/rs/client-gen/src/ctor_generators.rs @@ -1,178 +1,88 @@ -use crate::{ - helpers::*, io_generators::generate_io_struct, type_generators::generate_type_decl_code, -}; +use crate::helpers::*; use convert_case::{Case, Casing}; use genco::prelude::*; use rust::Tokens; use sails_idl_parser::{ast::visitor, ast::visitor::Visitor, ast::*}; -pub(crate) struct CtorFactoryGenerator<'a> { +pub(crate) struct CtorGenerator<'a> { service_name: &'a str, sails_path: &'a str, - tokens: Tokens, + ctor_tokens: Tokens, io_tokens: Tokens, + trait_ctors_tokens: Tokens, } -impl<'a> CtorFactoryGenerator<'a> { +impl<'a> CtorGenerator<'a> { pub(crate) fn new(service_name: &'a str, sails_path: &'a str) -> Self { Self { service_name, sails_path, - tokens: Tokens::new(), + ctor_tokens: Tokens::new(), io_tokens: Tokens::new(), + trait_ctors_tokens: Tokens::new(), } } pub(crate) fn finalize(self) -> Tokens { - let service_name_snake = self.service_name.to_case(Case::Snake); quote! { - $(self.tokens) + pub trait $(self.service_name)Ctors { + type Env: GearEnv; + $(self.trait_ctors_tokens) + } + + impl $(self.service_name)Ctors for Deployment { + type Env = E; + $(self.ctor_tokens) + } + $['\n'] - pub mod $(service_name_snake)_factory { + pub mod io { use super::*; - pub mod io { - use super::*; - use $(self.sails_path)::calls::ActionIo; - $(self.io_tokens) - } + $(self.io_tokens) } } } } -impl<'ast> Visitor<'ast> for CtorFactoryGenerator<'_> { +impl<'ast> Visitor<'ast> for CtorGenerator<'_> { fn visit_ctor(&mut self, ctor: &'ast Ctor) { - quote_in! {self.tokens => - pub struct $(self.service_name)Factory { - #[allow(dead_code)] - remoting: R, - } - - impl $(self.service_name)Factory { - #[allow(unused)] - pub fn new(remoting: R) -> Self { - Self { - remoting, - } - } - } - - impl traits::$(self.service_name)Factory for $(self.service_name)Factory $("{") - type Args = R::Args; - }; - visitor::accept_ctor(ctor, self); - - quote_in! { self.tokens => - $['\r'] $("}") - }; } fn visit_ctor_func(&mut self, func: &'ast CtorFunc) { let fn_name = func.name(); - let fn_name_snake = fn_name.to_case(Case::Snake); - let fn_name_snake = fn_name_snake.as_str(); + let fn_name_snake = &fn_name.to_case(Case::Snake); + + let params_with_types = &fn_args_with_types(func.params()); + let args = &encoded_args(func.params()); for doc in func.docs() { - quote_in! { self.tokens => + quote_in! { self.trait_ctors_tokens => $['\r'] $("///") $doc }; } - quote_in! { self.tokens => - $['\r'] fn $fn_name_snake$("(")&self, - }; - - visitor::accept_ctor_func(func, self); - - let args = encoded_args(func.params()); - - let service_name_snake = self.service_name.to_case(Case::Snake); - let params_type = format!("{service_name_snake}_factory::io::{fn_name}"); - - quote_in! { self.tokens => - $(")") -> impl Activation { - RemotingAction::<_, $params_type>::new(self.remoting.clone(), $args) - } - }; - - let route_bytes = path_bytes(fn_name).0; - let struct_tokens = generate_io_struct(fn_name, func.params(), None, route_bytes.as_str()); - - quote_in! { self.io_tokens => - $struct_tokens - }; - } - - fn visit_func_param(&mut self, func_param: &'ast FuncParam) { - let type_decl_code = generate_type_decl_code(func_param.type_decl()); - quote_in! { self.tokens => - $(func_param.name()): $(type_decl_code), - }; - } -} - -pub(crate) struct CtorTraitGenerator { - service_name: String, - tokens: Tokens, -} - -impl CtorTraitGenerator { - pub(crate) fn new(service_name: String) -> Self { - Self { - service_name, - tokens: Tokens::new(), - } - } - - pub(crate) fn finalize(self) -> Tokens { - self.tokens - } -} - -impl<'ast> Visitor<'ast> for CtorTraitGenerator { - fn visit_ctor(&mut self, ctor: &'ast Ctor) { - quote_in! {self.tokens => - #[allow(dead_code)] - pub trait $(&self.service_name)Factory $("{") - type Args; - }; - - visitor::accept_ctor(ctor, self); - - quote_in! {self.tokens => - $['\r'] $("}") - }; - } - - fn visit_ctor_func(&mut self, func: &'ast CtorFunc) { - let fn_name = func.name(); - let fn_name_snake = fn_name.to_case(Case::Snake); - let fn_name_snake = fn_name_snake.as_str(); - if fn_name_snake == "new" { - quote_in! {self.tokens => + quote_in! {self.trait_ctors_tokens => #[allow(clippy::new_ret_no_self)] #[allow(clippy::wrong_self_convention)] }; } - quote_in! {self.tokens => - $['\r'] fn $fn_name_snake$("(")&self, + quote_in! { self.trait_ctors_tokens => + $['\r'] + fn $fn_name_snake (self, $params_with_types) -> PendingCtor; }; - visitor::accept_ctor_func(func, self); - - quote_in! {self.tokens => - $(")") -> impl Activation; + quote_in! { self.ctor_tokens => + $['\r'] + fn $fn_name_snake (self, $params_with_types) -> PendingCtor { + self.pending_ctor($args) + } }; - } - - fn visit_func_param(&mut self, func_param: &'ast FuncParam) { - let type_decl_code = generate_type_decl_code(func_param.type_decl()); - quote_in! { self.tokens => - $(func_param.name()): $(type_decl_code), + quote_in! { self.io_tokens => + $(self.sails_path)::io_struct_impl!($fn_name ($params_with_types) -> ()); }; } } diff --git a/rs/client-gen/src/events_generator.rs b/rs/client-gen/src/events_generator.rs index 607fecb64..16d37c1b4 100644 --- a/rs/client-gen/src/events_generator.rs +++ b/rs/client-gen/src/events_generator.rs @@ -53,17 +53,17 @@ impl<'ast> Visitor<'ast> for EventsModuleGenerator<'_> { $['\r'] $("}") }; - quote_in! { self.tokens => - impl EventIo for $(&events_name) { - const ROUTE: &'static [u8] = &[$service_path_bytes]; - const EVENT_NAMES: &'static [&'static [u8]] = &[&[$event_names_bytes]]; - type Event = Self; - } + // quote_in! { self.tokens => + // impl EventIo for $(&events_name) { + // const ROUTE: &'static [u8] = &[$service_path_bytes]; + // const EVENT_NAMES: &'static [&'static [u8]] = &[&[$event_names_bytes]]; + // type Event = Self; + // } - pub fn listener>>(remoting: R) -> impl Listener<$(&events_name)> { - RemotingListener::<_, $(&events_name)>::new(remoting) - } - } + // pub fn listener>>(remoting: R) -> impl Listener<$(&events_name)> { + // RemotingListener::<_, $(&events_name)>::new(remoting) + // } + // } quote_in! { self.tokens => $['\r'] $("}") diff --git a/rs/client-gen/src/helpers.rs b/rs/client-gen/src/helpers.rs index b58f8ab3f..77b87cc9f 100644 --- a/rs/client-gen/src/helpers.rs +++ b/rs/client-gen/src/helpers.rs @@ -1,3 +1,4 @@ +use crate::type_generators::generate_type_decl_code; use parity_scale_codec::Encode; use sails_idl_parser::ast::FuncParam; @@ -31,7 +32,7 @@ pub(crate) fn method_bytes(fn_name: &str) -> (String, usize) { (route_bytes, route_encoded_length) } -pub(crate) fn encoded_fn_args(params: &[FuncParam]) -> String { +pub(crate) fn fn_args(params: &[FuncParam]) -> String { params .iter() .map(|a| a.name()) @@ -40,11 +41,19 @@ pub(crate) fn encoded_fn_args(params: &[FuncParam]) -> String { } pub(crate) fn encoded_args(params: &[FuncParam]) -> String { - if params.len() == 1 { - return params[0].name().to_owned(); - } + let sep = if params.len() == 1 { "," } else { "" }; + let arg_names = fn_args(params); - let arg_names = encoded_fn_args(params); + format!("({arg_names}{sep})") +} - format!("({arg_names})") +pub(crate) fn fn_args_with_types(params: &[FuncParam]) -> String { + params + .iter() + .map(|p| { + let ty = generate_type_decl_code(p.type_decl()); + format!("{}: {}", p.name(), ty) + }) + .collect::>() + .join(", ") } diff --git a/rs/client-gen/src/io_generators.rs b/rs/client-gen/src/io_generators.rs deleted file mode 100644 index 3d2a318d3..000000000 --- a/rs/client-gen/src/io_generators.rs +++ /dev/null @@ -1,128 +0,0 @@ -use crate::helpers::*; -use crate::type_generators::generate_type_decl_with_path; -use genco::prelude::*; -use sails_idl_parser::{ast::visitor, ast::visitor::Visitor, ast::*}; - -pub(crate) struct IoModuleGenerator<'a> { - path: &'a str, - sails_path: &'a str, - tokens: rust::Tokens, -} - -impl<'a> IoModuleGenerator<'a> { - pub(crate) fn new(path: &'a str, sails_path: &'a str) -> Self { - Self { - path, - sails_path, - tokens: rust::Tokens::new(), - } - } - - pub(crate) fn finalize(self) -> rust::Tokens { - quote!( - $['\n'] - pub mod io { - use super::*; - use $(self.sails_path)::calls::ActionIo; - $(self.tokens) - } - ) - } -} - -impl<'ast> Visitor<'ast> for IoModuleGenerator<'_> { - fn visit_service(&mut self, service: &'ast Service) { - visitor::accept_service(service, self); - } - - fn visit_service_func(&mut self, func: &'ast ServiceFunc) { - let fn_name = func.name(); - let (service_path_bytes, _) = path_bytes(self.path); - let (route_bytes, _) = method_bytes(fn_name); - - let struct_tokens = generate_io_struct( - fn_name, - func.params(), - Some(func.output()), - format!("{service_path_bytes}{route_bytes}").as_str(), - ); - - quote_in! { self.tokens => - $struct_tokens - }; - } -} - -pub(crate) fn generate_io_struct( - fn_name: &str, - fn_params: &[FuncParam], - fn_output: Option<&TypeDecl>, - route_bytes: &str, -) -> rust::Tokens { - let params_len = fn_params.len(); - let mut struct_param_tokens = rust::Tokens::new(); - let mut encode_call_args = rust::Tokens::new(); - let mut encode_call_names = rust::Tokens::new(); - for func_param in fn_params { - let type_decl_code = - generate_type_decl_with_path(func_param.type_decl(), "super".to_owned()); - quote_in! { struct_param_tokens => - $(&type_decl_code), - }; - quote_in! { encode_call_args => - $(func_param.name()): $(&type_decl_code), - }; - quote_in! { encode_call_names => - $(func_param.name()), - }; - } - - let param_tokens = match params_len { - 0 => quote!(()), - 1 => quote!($(generate_type_decl_with_path(fn_params[0].type_decl(), "super".to_owned()))), - _ => quote!(($struct_param_tokens)), - }; - - let func_output = fn_output.map_or("()".to_owned(), |output| { - generate_type_decl_with_path(output, "super".to_owned()) - }); - - let encode_call_tokens = match params_len { - 0 => quote!( - impl $fn_name { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - <$fn_name as ActionIo>::encode_call(&()) - } - } - ), - 1 => quote!( - impl $fn_name { - #[allow(dead_code)] - pub fn encode_call($encode_call_args) -> Vec { - <$fn_name as ActionIo>::encode_call(&$(fn_params[0].name())) - } - } - ), - _ => quote!( - impl $fn_name { - #[allow(dead_code)] - pub fn encode_call($encode_call_args) -> Vec { - <$fn_name as ActionIo>::encode_call(&($encode_call_names)) - } - } - ), - }; - - quote! { - pub struct $fn_name (()); - - $encode_call_tokens - - impl ActionIo for $fn_name { - const ROUTE: &'static [u8] = &[$route_bytes]; - type Params = $param_tokens; - type Reply = $func_output; - } - } -} diff --git a/rs/client-gen/src/lib.rs b/rs/client-gen/src/lib.rs index 0bee410be..46507f79c 100644 --- a/rs/client-gen/src/lib.rs +++ b/rs/client-gen/src/lib.rs @@ -7,7 +7,6 @@ use std::{collections::HashMap, ffi::OsStr, fs, io::Write, path::Path}; mod ctor_generators; mod events_generator; mod helpers; -mod io_generators; mod mock_generator; mod root_generator; mod service_generators; diff --git a/rs/client-gen/src/root_generator.rs b/rs/client-gen/src/root_generator.rs index de6ab81fd..86c332752 100644 --- a/rs/client-gen/src/root_generator.rs +++ b/rs/client-gen/src/root_generator.rs @@ -1,7 +1,4 @@ -use crate::{ - ctor_generators::*, events_generator::*, io_generators::*, mock_generator::MockGenerator, - service_generators::*, type_generators::*, -}; +use crate::{ctor_generators::*, events_generator::*, service_generators::*, type_generators::*}; use convert_case::{Case, Casing}; use genco::prelude::*; use rust::Tokens; @@ -10,8 +7,9 @@ use std::collections::HashMap; pub(crate) struct RootGenerator<'a> { tokens: Tokens, - traits_tokens: Tokens, mocks_tokens: Tokens, + service_impl_tokens: Tokens, + service_trait_tokens: Tokens, anonymous_service_name: &'a str, mocks_feature_name: Option<&'a str>, sails_path: &'a str, @@ -27,25 +25,12 @@ impl<'a> RootGenerator<'a> { external_types: HashMap<&'a str, &'a str>, no_derive_traits: bool, ) -> Self { - let mut tokens = quote! { - #[allow(unused_imports)] - use $sails_path::{String, calls::{Activation, Call, Query, Remoting, RemotingAction}, prelude::*}; - #[allow(unused_imports)] - use $sails_path::collections::BTreeMap; - }; - - for (&name, &path) in &external_types { - quote_in! { tokens => - #[allow(unused_imports)] - use $path as $name; - }; - } - Self { anonymous_service_name, - tokens, - traits_tokens: Tokens::new(), + tokens: Tokens::new(), mocks_tokens: Tokens::new(), + service_impl_tokens: Tokens::new(), + service_trait_tokens: Tokens::new(), mocks_feature_name, sails_path, external_types, @@ -73,18 +58,39 @@ impl<'a> RootGenerator<'a> { Tokens::new() }; - let result: Tokens = quote! { - $(self.tokens) - $['\n'] - pub mod traits { - use super::*; - $(self.traits_tokens) + let mut tokens = quote! { + use $(self.sails_path)::{client::*, prelude::*}; + }; + + for (&name, &path) in &self.external_types { + quote_in! { tokens => + #[allow(unused_imports)] + use $path as $name; + }; + } + + let program_name = &self.anonymous_service_name.to_case(Case::Pascal); + quote_in! { tokens => + pub struct $(program_name)Program; + + impl Program for $(program_name)Program {} + + pub trait $program_name { + type Env: GearEnv; + $(self.service_trait_tokens) + } + + impl $program_name for Actor { + type Env = E; + $(self.service_impl_tokens) } + $(self.tokens) + $mocks_tokens }; - let mut result = result.to_file_string().unwrap(); + let mut result = tokens.to_file_string().unwrap(); if with_no_std { result.insert_str( @@ -101,12 +107,7 @@ impl<'a> RootGenerator<'a> { impl<'ast> Visitor<'ast> for RootGenerator<'_> { fn visit_ctor(&mut self, ctor: &'ast Ctor) { - let mut ctor_gen = CtorTraitGenerator::new(self.anonymous_service_name.to_owned()); - ctor_gen.visit_ctor(ctor); - - self.traits_tokens.extend(ctor_gen.finalize()); - - let mut ctor_gen = CtorFactoryGenerator::new(self.anonymous_service_name, self.sails_path); + let mut ctor_gen = CtorGenerator::new(self.anonymous_service_name, self.sails_path); ctor_gen.visit_ctor(ctor); self.tokens.extend(ctor_gen.finalize()); } @@ -117,24 +118,22 @@ impl<'ast> Visitor<'ast> for RootGenerator<'_> { } else { service.name() }; - let service_name_snake = service_name.to_case(Case::Snake); + // let service_name_snake = service_name.to_case(Case::Snake); - let mut service_gen = ServiceTraitGenerator::new(service_name.to_owned()); - service_gen.visit_service(service); - self.traits_tokens.extend(service_gen.finalize()); + let mut ctor_gen = ServiceCtorGenerator::new(service_name); + ctor_gen.visit_service(service); + let (trait_tokens, impl_tokens) = ctor_gen.finalize(); + self.service_trait_tokens.extend(trait_tokens); + self.service_impl_tokens.extend(impl_tokens); let path = service.name(); - let mut client_gen = ServiceClientGenerator::new(service_name.to_owned()); + let mut client_gen = ServiceGenerator::new(service_name, self.sails_path); client_gen.visit_service(service); self.tokens.extend(client_gen.finalize()); let mut service_tokens = Tokens::new(); - let mut io_mod_gen = IoModuleGenerator::new(path, self.sails_path); - io_mod_gen.visit_service(service); - service_tokens.extend(io_mod_gen.finalize()); - if !service.events().is_empty() { let mut events_mod_gen = EventsModuleGenerator::new(service_name, path, self.sails_path); @@ -142,17 +141,17 @@ impl<'ast> Visitor<'ast> for RootGenerator<'_> { service_tokens.extend(events_mod_gen.finalize()); } - quote_in! { self.tokens => - $['\n'] - pub mod $(service_name_snake) { - use super::*; - $(service_tokens) - } - } - - let mut mock_gen: MockGenerator = MockGenerator::new(service_name); - mock_gen.visit_service(service); - self.mocks_tokens.extend(mock_gen.finalize()); + // quote_in! { self.tokens => + // $['\n'] + // pub mod $(service_name_snake) { + // use super::*; + // $(service_tokens) + // } + // } + + // let mut mock_gen: MockGenerator = MockGenerator::new(service_name); + // mock_gen.visit_service(service); + // self.mocks_tokens.extend(mock_gen.finalize()); } fn visit_type(&mut self, t: &'ast Type) { diff --git a/rs/client-gen/src/service_generators.rs b/rs/client-gen/src/service_generators.rs index 429ec5502..d110e6a3e 100644 --- a/rs/client-gen/src/service_generators.rs +++ b/rs/client-gen/src/service_generators.rs @@ -3,139 +3,137 @@ use genco::prelude::*; use rust::Tokens; use sails_idl_parser::{ast::visitor, ast::visitor::Visitor, ast::*}; +use crate::events_generator::EventsModuleGenerator; use crate::helpers::*; use crate::type_generators::generate_type_decl_code; /// Generates a trait with service methods -pub(crate) struct ServiceTraitGenerator { - service_name: String, - tokens: Tokens, +pub(crate) struct ServiceCtorGenerator<'a> { + service_name: &'a str, + trait_tokens: Tokens, + impl_tokens: Tokens, } -impl ServiceTraitGenerator { - pub(crate) fn new(service_name: String) -> Self { +impl<'a> ServiceCtorGenerator<'a> { + pub(crate) fn new(service_name: &'a str) -> Self { Self { service_name, - tokens: Tokens::new(), + trait_tokens: Tokens::new(), + impl_tokens: Tokens::new(), } } - pub(crate) fn finalize(self) -> Tokens { - quote! { - $['\n'] - #[allow(clippy::type_complexity)] - pub trait $(&self.service_name) { - type Args; - $(self.tokens) - } - } + pub(crate) fn finalize(self) -> (Tokens, Tokens) { + (self.trait_tokens, self.impl_tokens) } } -impl<'ast> Visitor<'ast> for ServiceTraitGenerator { +impl<'ast> Visitor<'ast> for ServiceCtorGenerator<'_> { fn visit_service(&mut self, service: &'ast Service) { - visitor::accept_service(service, self); - } - - fn visit_service_func(&mut self, func: &'ast ServiceFunc) { - let mutability = if func.is_query() { "" } else { "mut" }; - let fn_name = func.name().to_case(Case::Snake); - - let mut params_tokens = Tokens::new(); - for param in func.params() { - let type_decl_code = generate_type_decl_code(param.type_decl()); - quote_in! {params_tokens => - $(param.name()): $(type_decl_code), - }; - } - - let output_type_decl_code = generate_type_decl_code(func.output()); - let output_trait = if func.is_query() { "Query" } else { "Call" }; - - quote_in! { self.tokens=> - $['\r'] fn $fn_name (&$mutability self, $params_tokens) -> impl $output_trait; - }; + let service_name_snake = &self.service_name.to_case(Case::Snake); + quote_in!(self.trait_tokens => + fn $service_name_snake(&self) -> Service; + ); + quote_in!(self.impl_tokens => + fn $service_name_snake(&self) -> Service { + self.service(stringify!($service_name_snake::$(self.service_name))) + } + ); } } -/// Generates a client that implements service trait -pub(crate) struct ServiceClientGenerator { - service_name: String, - tokens: Tokens, +/// Generates a service module with trait and struct implementation +pub(crate) struct ServiceGenerator<'a> { + service_name: &'a str, + sails_path: &'a str, + trait_tokens: Tokens, + impl_tokens: Tokens, + io_tokens: Tokens, + events_tokens: Tokens, } -impl ServiceClientGenerator { - pub(crate) fn new(service_name: String) -> Self { +impl<'a> ServiceGenerator<'a> { + pub(crate) fn new(service_name: &'a str, sails_path: &'a str) -> Self { Self { service_name, - tokens: Tokens::new(), + sails_path, + trait_tokens: Tokens::new(), + impl_tokens: Tokens::new(), + io_tokens: Tokens::new(), + events_tokens: Tokens::new(), } } pub(crate) fn finalize(self) -> Tokens { - self.tokens - } -} + let service_name_snake = &self.service_name.to_case(Case::Snake); + quote! { + $['\n'] + pub mod $service_name_snake { + use super::*; -// using quote_in instead of tokens.append -impl<'ast> Visitor<'ast> for ServiceClientGenerator { - fn visit_service(&mut self, service: &'ast Service) { - let name = &self.service_name; + pub trait $(self.service_name) { + type Env: GearEnv; + $(self.trait_tokens) + } - quote_in! {self.tokens => - pub struct $name { - remoting: R, - } + pub struct $(self.service_name)Impl; - impl $name { - pub fn new(remoting: R) -> Self { - Self { remoting } + impl $(self.service_name) for Service { + type Env = E; + $(self.impl_tokens) } - } - impl traits::$name for $name - $("{") - type Args = R::Args; - }; + $['\n'] + pub mod io { + use super::*; + $(self.io_tokens) + } + + $(self.events_tokens) + } + } + } +} +// using quote_in instead of tokens.append +impl<'ast> Visitor<'ast> for ServiceGenerator<'_> { + fn visit_service(&mut self, service: &'ast Service) { visitor::accept_service(service, self); - quote_in! {self.tokens => - $("}") - }; + if !service.events().is_empty() { + let mut events_mod_gen = + EventsModuleGenerator::new(self.service_name, self.service_name, self.sails_path); + events_mod_gen.visit_service(service); + self.events_tokens = events_mod_gen.finalize(); + } } fn visit_service_func(&mut self, func: &'ast ServiceFunc) { let mutability = if func.is_query() { "" } else { "mut" }; let fn_name = func.name(); - let fn_name_snake = fn_name.to_case(Case::Snake); - - let mut params_tokens = Tokens::new(); - for param in func.params() { - let type_decl_code = generate_type_decl_code(param.type_decl()); - quote_in! {params_tokens => - $(param.name()): $(type_decl_code), - }; - } - - let output_type_decl_code = generate_type_decl_code(func.output()); - let output_trait = if func.is_query() { "Query" } else { "Call" }; + let fn_name_snake = &fn_name.to_case(Case::Snake); + let params_with_types = &fn_args_with_types(func.params()); let args = encoded_args(func.params()); - - let service_name_snake = self.service_name.to_case(Case::Snake); - let params_type = format!("{service_name_snake}::io::{fn_name}"); + let output_type_decl_code = generate_type_decl_code(func.output()); for doc in func.docs() { - quote_in! { self.tokens => + quote_in! { self.trait_tokens => $['\r'] $("///") $doc }; } + quote_in! { self.trait_tokens => + $['\r'] fn $fn_name_snake (&$mutability self, $params_with_types) -> PendingCall; + }; - quote_in! {self.tokens => - $['\r'] fn $fn_name_snake (&$mutability self, $params_tokens) -> impl $output_trait { - RemotingAction::<_, $params_type>::new(self.remoting.clone(), $args) + quote_in! {self.impl_tokens => + $['\r'] fn $fn_name_snake (&$mutability self, $params_with_types) -> PendingCall { + self.pending_call($args) } }; + + quote_in! { self.io_tokens => + $(self.sails_path)::io_struct_impl!($fn_name ($params_with_types) -> $output_type_decl_code); + }; } } diff --git a/rs/client-gen/tests/generator.rs b/rs/client-gen/tests/generator.rs index 590bf027d..bf84461ee 100644 --- a/rs/client-gen/tests/generator.rs +++ b/rs/client-gen/tests/generator.rs @@ -183,7 +183,10 @@ fn full_with_sails_path() { type T = enum { One }; constructor { + /// New constructor New : (a: u32); + /// CreateWithData constructor + CreateWithData : (a: u32, b: str, c: ThisThatSvcAppManyVariants); }; service { @@ -192,7 +195,22 @@ fn full_with_sails_path() { query This : (v1: vec u16) -> u32; query That : (v1: null) -> result (str, str); }; - "#; + + service Counter { + /// Add a value to the counter + Add : (value: u32) -> u32; + /// Substract a value from the counter + Sub : (value: u32) -> u32; + /// Get the current value + query Value : () -> u32; + + events { + /// Emitted when a new value is added to the counter + Added: u32; + /// Emitted when a value is subtracted from the counter + Subtracted: u32; + } + };"#; let code = ClientGenerator::from_idl(IDL) .with_sails_crate("my_crate::sails") diff --git a/rs/client-gen/tests/snapshots/generator__basic_works.snap b/rs/client-gen/tests/snapshots/generator__basic_works.snap index 7b8ad98be..9a3b5e2d3 100644 --- a/rs/client-gen/tests/snapshots/generator__basic_works.snap +++ b/rs/client-gen/tests/snapshots/generator__basic_works.snap @@ -3,62 +3,42 @@ source: rs/client-gen/tests/generator.rs expression: "gen_client(idl, \"Basic\")" --- // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::collections::BTreeMap; -#[allow(unused_imports)] -use sails_rs::{ - String, - calls::{Activation, Call, Query, Remoting, RemotingAction}, - prelude::*, -}; -pub struct Basic { - remoting: R, +use sails_rs::{client::*, prelude::*}; +pub struct BasicProgram; +impl Program for BasicProgram {} +pub trait Basic { + type Env: GearEnv; + fn basic(&self) -> Service; } -impl Basic { - pub fn new(remoting: R) -> Self { - Self { remoting } - } -} -impl traits::Basic for Basic { - type Args = R::Args; - fn do_this(&mut self, p1: u32, p2: MyParam) -> impl Call { - RemotingAction::<_, basic::io::DoThis>::new(self.remoting.clone(), (p1, p2)) - } - fn do_that(&mut self, p1: (u8, u32)) -> impl Call { - RemotingAction::<_, basic::io::DoThat>::new(self.remoting.clone(), p1) +impl Basic for Actor { + type Env = E; + fn basic(&self) -> Service { + self.service(stringify!(basic::Basic)) } } pub mod basic { use super::*; + pub trait Basic { + type Env: GearEnv; + fn do_this(&mut self, p1: u32, p2: MyParam) -> PendingCall; + fn do_that(&mut self, p1: (u8, u32)) -> PendingCall; + } + pub struct BasicImpl; + impl Basic for Service { + type Env = E; + fn do_this(&mut self, p1: u32, p2: MyParam) -> PendingCall { + self.pending_call((p1, p2)) + } + fn do_that(&mut self, p1: (u8, u32)) -> PendingCall { + self.pending_call((p1,)) + } + } pub mod io { use super::*; - use sails_rs::calls::ActionIo; - pub struct DoThis(()); - impl DoThis { - #[allow(dead_code)] - pub fn encode_call(p1: u32, p2: super::MyParam) -> Vec { - ::encode_call(&(p1, p2)) - } - } - impl ActionIo for DoThis { - const ROUTE: &'static [u8] = &[24, 68, 111, 84, 104, 105, 115]; - type Params = (u32, super::MyParam); - type Reply = u16; - } - pub struct DoThat(()); - impl DoThat { - #[allow(dead_code)] - pub fn encode_call(p1: (u8, u32)) -> Vec { - ::encode_call(&p1) - } - } - impl ActionIo for DoThat { - const ROUTE: &'static [u8] = &[24, 68, 111, 84, 104, 97, 116]; - type Params = (u8, u32); - type Reply = u8; - } + sails_rs::io_struct_impl!(DoThis (p1: u32, p2: MyParam) -> u16); + sails_rs::io_struct_impl!(DoThat (p1: (u8,u32,)) -> u8); } } #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo)] @@ -80,17 +60,6 @@ pub enum MyParam2 { Variant5 { f1: String, f2: Vec }, } -pub mod traits { - use super::*; - - #[allow(clippy::type_complexity)] - pub trait Basic { - type Args; - fn do_this(&mut self, p1: u32, p2: MyParam) -> impl Call; - fn do_that(&mut self, p1: (u8, u32)) -> impl Call; - } -} - #[cfg(feature = "with_mocks")] #[cfg(not(target_arch = "wasm32"))] extern crate std; @@ -100,5 +69,4 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub Basic {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl traits::Basic for Basic { type Args = A; fn do_this (&mut self, p1: u32,p2: MyParam,) -> MockCall;fn do_that (&mut self, p1: (u8,u32,),) -> MockCall; } } } diff --git a/rs/client-gen/tests/snapshots/generator__events_works.snap b/rs/client-gen/tests/snapshots/generator__events_works.snap index dae96f0bc..4c6ddc739 100644 --- a/rs/client-gen/tests/snapshots/generator__events_works.snap +++ b/rs/client-gen/tests/snapshots/generator__events_works.snap @@ -3,51 +3,40 @@ source: rs/client-gen/tests/generator.rs expression: "gen_client(idl, \"ServiceWithEvents\")" --- // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::collections::BTreeMap; -#[allow(unused_imports)] -use sails_rs::{ - String, - calls::{Activation, Call, Query, Remoting, RemotingAction}, - prelude::*, -}; -pub struct ServiceWithEvents { - remoting: R, +use sails_rs::{client::*, prelude::*}; +pub struct ServiceWithEventsProgram; +impl Program for ServiceWithEventsProgram {} +pub trait ServiceWithEvents { + type Env: GearEnv; + fn service_with_events(&self) + -> Service; } -impl ServiceWithEvents { - pub fn new(remoting: R) -> Self { - Self { remoting } - } -} -impl traits::ServiceWithEvents for ServiceWithEvents { - type Args = R::Args; - fn do_this( - &mut self, - p1: NonZeroU256, - p2: MyParam, - ) -> impl Call { - RemotingAction::<_, service_with_events::io::DoThis>::new(self.remoting.clone(), (p1, p2)) +impl ServiceWithEvents for Actor { + type Env = E; + fn service_with_events( + &self, + ) -> Service { + self.service(stringify!(service_with_events::ServiceWithEvents)) } } pub mod service_with_events { use super::*; + pub trait ServiceWithEvents { + type Env: GearEnv; + fn do_this(&mut self, p1: NonZeroU256, p2: MyParam) -> PendingCall; + } + pub struct ServiceWithEventsImpl; + impl ServiceWithEvents for Service { + type Env = E; + fn do_this(&mut self, p1: NonZeroU256, p2: MyParam) -> PendingCall { + self.pending_call((p1, p2)) + } + } pub mod io { use super::*; - use sails_rs::calls::ActionIo; - pub struct DoThis(()); - impl DoThis { - #[allow(dead_code)] - pub fn encode_call(p1: NonZeroU256, p2: super::MyParam) -> Vec { - ::encode_call(&(p1, p2)) - } - } - impl ActionIo for DoThis { - const ROUTE: &'static [u8] = &[24, 68, 111, 84, 104, 105, 115]; - type Params = (NonZeroU256, super::MyParam); - type Reply = NonZeroU64; - } + sails_rs::io_struct_impl!(DoThis (p1: NonZeroU256, p2: MyParam) -> NonZeroU64); } #[allow(dead_code)] @@ -63,21 +52,6 @@ pub mod service_with_events { Three(MyParam), Reset, } - impl EventIo for ServiceWithEventsEvents { - const ROUTE: &'static [u8] = &[]; - const EVENT_NAMES: &'static [&'static [u8]] = &[ - &[12, 79, 110, 101], - &[12, 84, 119, 111], - &[20, 84, 104, 114, 101, 101], - &[20, 82, 101, 115, 101, 116], - ]; - type Event = Self; - } - pub fn listener>>( - remoting: R, - ) -> impl Listener { - RemotingListener::<_, ServiceWithEventsEvents>::new(remoting) - } } } #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo)] @@ -89,20 +63,6 @@ pub struct MyParam { pub f3: Option<(NonZeroU64, NonZeroU256)>, } -pub mod traits { - use super::*; - - #[allow(clippy::type_complexity)] - pub trait ServiceWithEvents { - type Args; - fn do_this( - &mut self, - p1: NonZeroU256, - p2: MyParam, - ) -> impl Call; - } -} - #[cfg(feature = "with_mocks")] #[cfg(not(target_arch = "wasm32"))] extern crate std; @@ -112,5 +72,4 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub ServiceWithEvents {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl traits::ServiceWithEvents for ServiceWithEvents { type Args = A; fn do_this (&mut self, p1: NonZeroU256,p2: MyParam,) -> MockCall; } } } diff --git a/rs/client-gen/tests/snapshots/generator__external_types.snap b/rs/client-gen/tests/snapshots/generator__external_types.snap index 29514bc17..ee23cc3b0 100644 --- a/rs/client-gen/tests/snapshots/generator__external_types.snap +++ b/rs/client-gen/tests/snapshots/generator__external_types.snap @@ -5,62 +5,42 @@ expression: code // Code generated by sails-client-gen. DO NOT EDIT. #[allow(unused_imports)] use my_crate::MyParam; -#[allow(unused_imports)] -use my_crate::sails::collections::BTreeMap; -#[allow(unused_imports)] -use my_crate::sails::{ - String, - calls::{Activation, Call, Query, Remoting, RemotingAction}, - prelude::*, -}; -pub struct Service { - remoting: R, +use my_crate::sails::{client::*, prelude::*}; +pub struct ServiceProgram; +impl Program for ServiceProgram {} +pub trait Service { + type Env: GearEnv; + fn service(&self) -> Service; } -impl Service { - pub fn new(remoting: R) -> Self { - Self { remoting } - } -} -impl traits::Service for Service { - type Args = R::Args; - fn do_this(&mut self, p1: u32, p2: MyParam) -> impl Call { - RemotingAction::<_, service::io::DoThis>::new(self.remoting.clone(), (p1, p2)) - } - fn do_that(&mut self, p1: (u8, u32)) -> impl Call { - RemotingAction::<_, service::io::DoThat>::new(self.remoting.clone(), p1) +impl Service for Actor { + type Env = E; + fn service(&self) -> Service { + self.service(stringify!(service::Service)) } } pub mod service { use super::*; + pub trait Service { + type Env: GearEnv; + fn do_this(&mut self, p1: u32, p2: MyParam) -> PendingCall; + fn do_that(&mut self, p1: (u8, u32)) -> PendingCall; + } + pub struct ServiceImpl; + impl Service for Service { + type Env = E; + fn do_this(&mut self, p1: u32, p2: MyParam) -> PendingCall { + self.pending_call((p1, p2)) + } + fn do_that(&mut self, p1: (u8, u32)) -> PendingCall { + self.pending_call((p1,)) + } + } pub mod io { use super::*; - use my_crate::sails::calls::ActionIo; - pub struct DoThis(()); - impl DoThis { - #[allow(dead_code)] - pub fn encode_call(p1: u32, p2: super::MyParam) -> Vec { - ::encode_call(&(p1, p2)) - } - } - impl ActionIo for DoThis { - const ROUTE: &'static [u8] = &[24, 68, 111, 84, 104, 105, 115]; - type Params = (u32, super::MyParam); - type Reply = u16; - } - pub struct DoThat(()); - impl DoThat { - #[allow(dead_code)] - pub fn encode_call(p1: (u8, u32)) -> Vec { - ::encode_call(&p1) - } - } - impl ActionIo for DoThat { - const ROUTE: &'static [u8] = &[24, 68, 111, 84, 104, 97, 116]; - type Params = (u8, u32); - type Reply = u8; - } + my_crate::sails::io_struct_impl!(DoThis (p1: u32, p2: MyParam) -> u16); + my_crate::sails::io_struct_impl!(DoThat (p1: (u8,u32,)) -> u8); } } #[derive(Encode, Decode, TypeInfo)] @@ -73,14 +53,3 @@ pub enum MyParam2 { Variant4((u8, u32)), Variant5 { f1: String, f2: Vec }, } - -pub mod traits { - use super::*; - - #[allow(clippy::type_complexity)] - pub trait Service { - type Args; - fn do_this(&mut self, p1: u32, p2: MyParam) -> impl Call; - fn do_that(&mut self, p1: (u8, u32)) -> impl Call; - } -} diff --git a/rs/client-gen/tests/snapshots/generator__full.snap b/rs/client-gen/tests/snapshots/generator__full.snap index 9b49bc557..89ecd85f8 100644 --- a/rs/client-gen/tests/snapshots/generator__full.snap +++ b/rs/client-gen/tests/snapshots/generator__full.snap @@ -3,157 +3,95 @@ source: rs/client-gen/tests/generator.rs expression: "gen_client(IDL, \"Service\")" --- // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::collections::BTreeMap; -#[allow(unused_imports)] -use sails_rs::{ - String, - calls::{Activation, Call, Query, Remoting, RemotingAction}, - prelude::*, -}; -pub struct ServiceFactory { - #[allow(dead_code)] - remoting: R, +use sails_rs::{client::*, prelude::*}; +pub struct ServiceProgram; +impl Program for ServiceProgram {} +pub trait Service { + type Env: GearEnv; + fn service(&self) -> Service; } -impl ServiceFactory { - #[allow(unused)] - pub fn new(remoting: R) -> Self { - Self { remoting } +impl Service for Actor { + type Env = E; + fn service(&self) -> Service { + self.service(stringify!(service::Service)) } } -impl traits::ServiceFactory for ServiceFactory { - type Args = R::Args; - /// New constructor - fn new(&self, a: u32) -> impl Activation { - RemotingAction::<_, service_factory::io::New>::new(self.remoting.clone(), a) +pub trait ServiceCtors { + type Env: GearEnv; + /// New constructor#[allow(clippy::new_ret_no_self)] #[allow(clippy::wrong_self_convention)] + fn new(self, a: u32) -> PendingCtor; +} +impl ServiceCtors for Deployment { + type Env = E; + fn new(self, a: u32) -> PendingCtor { + self.pending_ctor((a,)) } } -pub mod service_factory { +pub mod io { use super::*; - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct New(()); - impl New { - #[allow(dead_code)] - pub fn encode_call(a: u32) -> Vec { - ::encode_call(&a) - } - } - impl ActionIo for New { - const ROUTE: &'static [u8] = &[12, 78, 101, 119]; - type Params = u32; - type Reply = (); - } - } -} -pub struct Service { - remoting: R, -} -impl Service { - pub fn new(remoting: R) -> Self { - Self { remoting } - } -} -impl traits::Service for Service { - type Args = R::Args; - /// Some description - fn do_this( - &mut self, - p1: u32, - p2: String, - p3: (Option, u8), - p4: ThisThatSvcAppTupleStruct, - ) -> impl Call { - RemotingAction::<_, service::io::DoThis>::new(self.remoting.clone(), (p1, p2, p3, p4)) - } - /// Some multiline description - /// Second line - /// Third line - fn do_that( - &mut self, - param: ThisThatSvcAppDoThatParam, - ) -> impl Call, Args = R::Args> { - RemotingAction::<_, service::io::DoThat>::new(self.remoting.clone(), param) - } - /// This is a query - fn this(&self, v1: Vec) -> impl Query { - RemotingAction::<_, service::io::This>::new(self.remoting.clone(), v1) - } - /// This is a second query - /// This is a second line - fn that(&self, v1: ()) -> impl Query, Args = R::Args> { - RemotingAction::<_, service::io::That>::new(self.remoting.clone(), v1) - } + sails_rs::io_struct_impl!(New (a: u32) -> ()); } pub mod service { use super::*; - - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct DoThis(()); - impl DoThis { - #[allow(dead_code)] - pub fn encode_call( - p1: u32, - p2: String, - p3: (Option, u8), - p4: super::ThisThatSvcAppTupleStruct, - ) -> Vec { - ::encode_call(&(p1, p2, p3, p4)) - } - } - impl ActionIo for DoThis { - const ROUTE: &'static [u8] = &[24, 68, 111, 84, 104, 105, 115]; - type Params = ( - u32, - String, - (Option, u8), - super::ThisThatSvcAppTupleStruct, - ); - type Reply = (String, u32); - } - pub struct DoThat(()); - impl DoThat { - #[allow(dead_code)] - pub fn encode_call(param: super::ThisThatSvcAppDoThatParam) -> Vec { - ::encode_call(¶m) - } - } - impl ActionIo for DoThat { - const ROUTE: &'static [u8] = &[24, 68, 111, 84, 104, 97, 116]; - type Params = super::ThisThatSvcAppDoThatParam; - type Reply = Result<(String, u32), (String,)>; - } - pub struct This(()); - impl This { - #[allow(dead_code)] - pub fn encode_call(v1: Vec) -> Vec { - ::encode_call(&v1) - } + pub trait Service { + type Env: GearEnv; + /// Some description + fn do_this( + &mut self, + p1: u32, + p2: String, + p3: (Option, u8), + p4: ThisThatSvcAppTupleStruct, + ) -> PendingCall; + /// Some multiline description + /// Second line + /// Third line + fn do_that( + &mut self, + param: ThisThatSvcAppDoThatParam, + ) -> PendingCall; + /// This is a query + fn this(&self, v1: Vec) -> PendingCall; + /// This is a second query + /// This is a second line + fn that(&self, v1: ()) -> PendingCall; + } + pub struct ServiceImpl; + impl Service for Service { + type Env = E; + fn do_this( + &mut self, + p1: u32, + p2: String, + p3: (Option, u8), + p4: ThisThatSvcAppTupleStruct, + ) -> PendingCall { + self.pending_call((p1, p2, p3, p4)) } - impl ActionIo for This { - const ROUTE: &'static [u8] = &[16, 84, 104, 105, 115]; - type Params = Vec; - type Reply = u32; + fn do_that( + &mut self, + param: ThisThatSvcAppDoThatParam, + ) -> PendingCall { + self.pending_call((param,)) } - pub struct That(()); - impl That { - #[allow(dead_code)] - pub fn encode_call(v1: ()) -> Vec { - ::encode_call(&v1) - } + fn this(&self, v1: Vec) -> PendingCall { + self.pending_call((v1,)) } - impl ActionIo for That { - const ROUTE: &'static [u8] = &[16, 84, 104, 97, 116]; - type Params = (); - type Reply = Result; + fn that(&self, v1: ()) -> PendingCall { + self.pending_call((v1,)) } } + pub mod io { + use super::*; + sails_rs::io_struct_impl!(DoThis (p1: u32, p2: String, p3: (Option,u8,), p4: ThisThatSvcAppTupleStruct) -> (String,u32,)); + sails_rs::io_struct_impl!(DoThat (param: ThisThatSvcAppDoThatParam) -> Result<(String,u32,), (String,)>); + sails_rs::io_struct_impl!(This (v1: Vec) -> u32); + sails_rs::io_struct_impl!(That (v1: ()) -> Result); + } + #[allow(dead_code)] #[cfg(not(target_arch = "wasm32"))] pub mod events { @@ -170,17 +108,6 @@ pub mod service { p1: String, }, } - impl EventIo for ServiceEvents { - const ROUTE: &'static [u8] = &[]; - const EVENT_NAMES: &'static [&'static [u8]] = &[ - &[32, 84, 104, 105, 115, 68, 111, 110, 101], - &[32, 84, 104, 97, 116, 68, 111, 110, 101], - ]; - type Event = Self; - } - pub fn listener>>(remoting: R) -> impl Listener { - RemotingListener::<_, ServiceEvents>::new(remoting) - } } } /// ThisThatSvcAppTupleStruct docs @@ -227,35 +154,6 @@ pub enum T { One, } -pub mod traits { - use super::*; - #[allow(dead_code)] - pub trait ServiceFactory { - type Args; - #[allow(clippy::new_ret_no_self)] - #[allow(clippy::wrong_self_convention)] - fn new(&self, a: u32) -> impl Activation; - } - - #[allow(clippy::type_complexity)] - pub trait Service { - type Args; - fn do_this( - &mut self, - p1: u32, - p2: String, - p3: (Option, u8), - p4: ThisThatSvcAppTupleStruct, - ) -> impl Call; - fn do_that( - &mut self, - param: ThisThatSvcAppDoThatParam, - ) -> impl Call, Args = Self::Args>; - fn this(&self, v1: Vec) -> impl Query; - fn that(&self, v1: ()) -> impl Query, Args = Self::Args>; - } -} - #[cfg(feature = "with_mocks")] #[cfg(not(target_arch = "wasm32"))] extern crate std; @@ -265,5 +163,4 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub Service {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl traits::Service for Service { type Args = A; fn do_this (&mut self, p1: u32,p2: String,p3: (Option,u8,),p4: ThisThatSvcAppTupleStruct,) -> MockCall;fn do_that (&mut self, param: ThisThatSvcAppDoThatParam,) -> MockCall>;fn this (& self, v1: Vec,) -> MockQuery;fn that (& self, v1: (),) -> MockQuery>; } } } diff --git a/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap b/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap index bd710300c..4ee13e557 100644 --- a/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap +++ b/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap @@ -3,146 +3,153 @@ source: rs/client-gen/tests/generator.rs expression: code --- // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use my_crate::sails::collections::BTreeMap; -#[allow(unused_imports)] -use my_crate::sails::{ - String, - calls::{Activation, Call, Query, Remoting, RemotingAction}, - prelude::*, -}; -pub struct ServiceFactory { - #[allow(dead_code)] - remoting: R, -} -impl ServiceFactory { - #[allow(unused)] - pub fn new(remoting: R) -> Self { - Self { remoting } - } +use my_crate::sails::{client::*, prelude::*}; +pub struct ServiceProgram; +impl Program for ServiceProgram {} +pub trait Service { + type Env: GearEnv; + fn service(&self) -> Service; + fn counter(&self) -> Service; } -impl traits::ServiceFactory for ServiceFactory { - type Args = R::Args; - fn new(&self, a: u32) -> impl Activation { - RemotingAction::<_, service_factory::io::New>::new(self.remoting.clone(), a) +impl Service for Actor { + type Env = E; + fn service(&self) -> Service { + self.service(stringify!(service::Service)) } -} - -pub mod service_factory { - use super::*; - pub mod io { - use super::*; - use my_crate::sails::calls::ActionIo; - pub struct New(()); - impl New { - #[allow(dead_code)] - pub fn encode_call(a: u32) -> Vec { - ::encode_call(&a) - } - } - impl ActionIo for New { - const ROUTE: &'static [u8] = &[12, 78, 101, 119]; - type Params = u32; - type Reply = (); - } + fn counter(&self) -> Service { + self.service(stringify!(counter::Counter)) } } -pub struct Service { - remoting: R, +pub trait ServiceCtors { + type Env: GearEnv; + /// New constructor#[allow(clippy::new_ret_no_self)] #[allow(clippy::wrong_self_convention)] + fn new(self, a: u32) -> PendingCtor; + /// CreateWithData constructor + fn create_with_data( + self, + a: u32, + b: String, + c: ThisThatSvcAppManyVariants, + ) -> PendingCtor; } -impl Service { - pub fn new(remoting: R) -> Self { - Self { remoting } +impl ServiceCtors for Deployment { + type Env = E; + fn new(self, a: u32) -> PendingCtor { + self.pending_ctor((a,)) } -} -impl traits::Service for Service { - type Args = R::Args; - fn do_this( - &mut self, - p1: u32, - p2: String, - p3: (Option, u8), - p4: ThisThatSvcAppTupleStruct, - ) -> impl Call { - RemotingAction::<_, service::io::DoThis>::new(self.remoting.clone(), (p1, p2, p3, p4)) - } - fn do_that( - &mut self, - param: ThisThatSvcAppDoThatParam, - ) -> impl Call, Args = R::Args> { - RemotingAction::<_, service::io::DoThat>::new(self.remoting.clone(), param) - } - fn this(&self, v1: Vec) -> impl Query { - RemotingAction::<_, service::io::This>::new(self.remoting.clone(), v1) - } - fn that(&self, v1: ()) -> impl Query, Args = R::Args> { - RemotingAction::<_, service::io::That>::new(self.remoting.clone(), v1) + fn create_with_data( + self, + a: u32, + b: String, + c: ThisThatSvcAppManyVariants, + ) -> PendingCtor { + self.pending_ctor((a, b, c)) } } -pub mod service { +pub mod io { use super::*; + my_crate::sails::io_struct_impl!(New (a: u32) -> ()); + my_crate::sails::io_struct_impl!(CreateWithData (a: u32, b: String, c: ThisThatSvcAppManyVariants) -> ()); +} - pub mod io { - use super::*; - use my_crate::sails::calls::ActionIo; - pub struct DoThis(()); - impl DoThis { - #[allow(dead_code)] - pub fn encode_call( - p1: u32, - p2: String, - p3: (Option, u8), - p4: super::ThisThatSvcAppTupleStruct, - ) -> Vec { - ::encode_call(&(p1, p2, p3, p4)) - } +pub mod service { + use super::*; + pub trait Service { + type Env: GearEnv; + fn do_this( + &mut self, + p1: u32, + p2: String, + p3: (Option, u8), + p4: ThisThatSvcAppTupleStruct, + ) -> PendingCall; + fn do_that( + &mut self, + param: ThisThatSvcAppDoThatParam, + ) -> PendingCall; + fn this(&self, v1: Vec) -> PendingCall; + fn that(&self, v1: ()) -> PendingCall; + } + pub struct ServiceImpl; + impl Service for Service { + type Env = E; + fn do_this( + &mut self, + p1: u32, + p2: String, + p3: (Option, u8), + p4: ThisThatSvcAppTupleStruct, + ) -> PendingCall { + self.pending_call((p1, p2, p3, p4)) } - impl ActionIo for DoThis { - const ROUTE: &'static [u8] = &[24, 68, 111, 84, 104, 105, 115]; - type Params = ( - u32, - String, - (Option, u8), - super::ThisThatSvcAppTupleStruct, - ); - type Reply = (String, u32); + fn do_that( + &mut self, + param: ThisThatSvcAppDoThatParam, + ) -> PendingCall { + self.pending_call((param,)) } - pub struct DoThat(()); - impl DoThat { - #[allow(dead_code)] - pub fn encode_call(param: super::ThisThatSvcAppDoThatParam) -> Vec { - ::encode_call(¶m) - } + fn this(&self, v1: Vec) -> PendingCall { + self.pending_call((v1,)) } - impl ActionIo for DoThat { - const ROUTE: &'static [u8] = &[24, 68, 111, 84, 104, 97, 116]; - type Params = super::ThisThatSvcAppDoThatParam; - type Reply = Result<(String, u32), (String,)>; + fn that(&self, v1: ()) -> PendingCall { + self.pending_call((v1,)) } - pub struct This(()); - impl This { - #[allow(dead_code)] - pub fn encode_call(v1: Vec) -> Vec { - ::encode_call(&v1) - } + } + + pub mod io { + use super::*; + my_crate::sails::io_struct_impl!(DoThis (p1: u32, p2: String, p3: (Option,u8,), p4: ThisThatSvcAppTupleStruct) -> (String,u32,)); + my_crate::sails::io_struct_impl!(DoThat (param: ThisThatSvcAppDoThatParam) -> Result<(String,u32,), (String,)>); + my_crate::sails::io_struct_impl!(This (v1: Vec) -> u32); + my_crate::sails::io_struct_impl!(That (v1: ()) -> Result); + } +} + +pub mod counter { + use super::*; + pub trait Counter { + type Env: GearEnv; + /// Add a value to the counter + fn add(&mut self, value: u32) -> PendingCall; + /// Substract a value from the counter + fn sub(&mut self, value: u32) -> PendingCall; + /// Get the current value + fn value(&self) -> PendingCall; + } + pub struct CounterImpl; + impl Counter for Service { + type Env = E; + fn add(&mut self, value: u32) -> PendingCall { + self.pending_call((value,)) } - impl ActionIo for This { - const ROUTE: &'static [u8] = &[16, 84, 104, 105, 115]; - type Params = Vec; - type Reply = u32; + fn sub(&mut self, value: u32) -> PendingCall { + self.pending_call((value,)) } - pub struct That(()); - impl That { - #[allow(dead_code)] - pub fn encode_call(v1: ()) -> Vec { - ::encode_call(&v1) - } + fn value(&self) -> PendingCall { + self.pending_call(()) } - impl ActionIo for That { - const ROUTE: &'static [u8] = &[16, 84, 104, 97, 116]; - type Params = (); - type Reply = Result; + } + + pub mod io { + use super::*; + my_crate::sails::io_struct_impl!(Add (value: u32) -> u32); + my_crate::sails::io_struct_impl!(Sub (value: u32) -> u32); + my_crate::sails::io_struct_impl!(Value () -> u32); + } + + #[allow(dead_code)] + #[cfg(not(target_arch = "wasm32"))] + pub mod events { + use super::*; + use my_crate::sails::events::*; + #[derive(PartialEq, Debug, Encode, Decode)] + #[codec(crate = my_crate::sails::scale_codec)] + pub enum CounterEvents { + /// Emitted when a new value is added to the counter + Added(u32), + /// Emitted when a value is subtracted from the counter + Subtracted(u32), } } } @@ -175,32 +182,3 @@ pub enum ThisThatSvcAppManyVariants { pub enum T { One, } - -pub mod traits { - use super::*; - #[allow(dead_code)] - pub trait ServiceFactory { - type Args; - #[allow(clippy::new_ret_no_self)] - #[allow(clippy::wrong_self_convention)] - fn new(&self, a: u32) -> impl Activation; - } - - #[allow(clippy::type_complexity)] - pub trait Service { - type Args; - fn do_this( - &mut self, - p1: u32, - p2: String, - p3: (Option, u8), - p4: ThisThatSvcAppTupleStruct, - ) -> impl Call; - fn do_that( - &mut self, - param: ThisThatSvcAppDoThatParam, - ) -> impl Call, Args = Self::Args>; - fn this(&self, v1: Vec) -> impl Query; - fn that(&self, v1: ()) -> impl Query, Args = Self::Args>; - } -} diff --git a/rs/client-gen/tests/snapshots/generator__multiple_services.snap b/rs/client-gen/tests/snapshots/generator__multiple_services.snap index 407eea3ea..59cbc2395 100644 --- a/rs/client-gen/tests/snapshots/generator__multiple_services.snap +++ b/rs/client-gen/tests/snapshots/generator__multiple_services.snap @@ -3,114 +3,66 @@ source: rs/client-gen/tests/generator.rs expression: "gen_client(idl, \"Multiple\")" --- // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::collections::BTreeMap; -#[allow(unused_imports)] -use sails_rs::{ - String, - calls::{Activation, Call, Query, Remoting, RemotingAction}, - prelude::*, -}; -pub struct Multiple { - remoting: R, +use sails_rs::{client::*, prelude::*}; +pub struct MultipleProgram; +impl Program for MultipleProgram {} +pub trait Multiple { + type Env: GearEnv; + fn multiple(&self) -> Service; + fn named(&self) -> Service; } -impl Multiple { - pub fn new(remoting: R) -> Self { - Self { remoting } +impl Multiple for Actor { + type Env = E; + fn multiple(&self) -> Service { + self.service(stringify!(multiple::Multiple)) } -} -impl traits::Multiple for Multiple { - type Args = R::Args; - fn do_this(&mut self, p1: u32, p2: MyParam) -> impl Call { - RemotingAction::<_, multiple::io::DoThis>::new(self.remoting.clone(), (p1, p2)) - } - fn do_that(&mut self, p1: (u8, u32)) -> impl Call { - RemotingAction::<_, multiple::io::DoThat>::new(self.remoting.clone(), p1) + fn named(&self) -> Service { + self.service(stringify!(named::Named)) } } pub mod multiple { use super::*; - - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct DoThis(()); - impl DoThis { - #[allow(dead_code)] - pub fn encode_call(p1: u32, p2: super::MyParam) -> Vec { - ::encode_call(&(p1, p2)) - } - } - impl ActionIo for DoThis { - const ROUTE: &'static [u8] = &[24, 68, 111, 84, 104, 105, 115]; - type Params = (u32, super::MyParam); - type Reply = u16; - } - pub struct DoThat(()); - impl DoThat { - #[allow(dead_code)] - pub fn encode_call(p1: (u8, u32)) -> Vec { - ::encode_call(&p1) - } + pub trait Multiple { + type Env: GearEnv; + fn do_this(&mut self, p1: u32, p2: MyParam) -> PendingCall; + fn do_that(&mut self, p1: (u8, u32)) -> PendingCall; + } + pub struct MultipleImpl; + impl Multiple for Service { + type Env = E; + fn do_this(&mut self, p1: u32, p2: MyParam) -> PendingCall { + self.pending_call((p1, p2)) } - impl ActionIo for DoThat { - const ROUTE: &'static [u8] = &[24, 68, 111, 84, 104, 97, 116]; - type Params = (u8, u32); - type Reply = u8; + fn do_that(&mut self, p1: (u8, u32)) -> PendingCall { + self.pending_call((p1,)) } } -} -pub struct Named { - remoting: R, -} -impl Named { - pub fn new(remoting: R) -> Self { - Self { remoting } - } -} -impl traits::Named for Named { - type Args = R::Args; - fn that(&self, p1: u32) -> impl Query { - RemotingAction::<_, named::io::That>::new(self.remoting.clone(), p1) - } -} - -pub mod named { - use super::*; pub mod io { use super::*; - use sails_rs::calls::ActionIo; - pub struct That(()); - impl That { - #[allow(dead_code)] - pub fn encode_call(p1: u32) -> Vec { - ::encode_call(&p1) - } - } - impl ActionIo for That { - const ROUTE: &'static [u8] = &[20, 78, 97, 109, 101, 100, 16, 84, 104, 97, 116]; - type Params = u32; - type Reply = String; - } + sails_rs::io_struct_impl!(DoThis (p1: u32, p2: MyParam) -> u16); + sails_rs::io_struct_impl!(DoThat (p1: (u8,u32,)) -> u8); } } -pub mod traits { +pub mod named { use super::*; - - #[allow(clippy::type_complexity)] - pub trait Multiple { - type Args; - fn do_this(&mut self, p1: u32, p2: MyParam) -> impl Call; - fn do_that(&mut self, p1: (u8, u32)) -> impl Call; + pub trait Named { + type Env: GearEnv; + fn that(&self, p1: u32) -> PendingCall; + } + pub struct NamedImpl; + impl Named for Service { + type Env = E; + fn that(&self, p1: u32) -> PendingCall { + self.pending_call((p1,)) + } } - #[allow(clippy::type_complexity)] - pub trait Named { - type Args; - fn that(&self, p1: u32) -> impl Query; + pub mod io { + use super::*; + sails_rs::io_struct_impl!(That (p1: u32) -> String); } } @@ -123,6 +75,4 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub Multiple {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl traits::Multiple for Multiple { type Args = A; fn do_this (&mut self, p1: u32,p2: MyParam,) -> MockCall;fn do_that (&mut self, p1: (u8,u32,),) -> MockCall; } } - mock! { pub Named {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl traits::Named for Named { type Args = A; fn that (& self, p1: u32,) -> MockQuery; } } } diff --git a/rs/client-gen/tests/snapshots/generator__nonzero_works.snap b/rs/client-gen/tests/snapshots/generator__nonzero_works.snap index 9f11b20a2..7b193e754 100644 --- a/rs/client-gen/tests/snapshots/generator__nonzero_works.snap +++ b/rs/client-gen/tests/snapshots/generator__nonzero_works.snap @@ -3,51 +3,37 @@ source: rs/client-gen/tests/generator.rs expression: "gen_client(idl, \"NonZeroParams\")" --- // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::collections::BTreeMap; -#[allow(unused_imports)] -use sails_rs::{ - String, - calls::{Activation, Call, Query, Remoting, RemotingAction}, - prelude::*, -}; -pub struct NonZeroParams { - remoting: R, +use sails_rs::{client::*, prelude::*}; +pub struct NonZeroParamsProgram; +impl Program for NonZeroParamsProgram {} +pub trait NonZeroParams { + type Env: GearEnv; + fn non_zero_params(&self) -> Service; } -impl NonZeroParams { - pub fn new(remoting: R) -> Self { - Self { remoting } - } -} -impl traits::NonZeroParams for NonZeroParams { - type Args = R::Args; - fn do_this( - &mut self, - p1: NonZeroU256, - p2: MyParam, - ) -> impl Call { - RemotingAction::<_, non_zero_params::io::DoThis>::new(self.remoting.clone(), (p1, p2)) +impl NonZeroParams for Actor { + type Env = E; + fn non_zero_params(&self) -> Service { + self.service(stringify!(non_zero_params::NonZeroParams)) } } pub mod non_zero_params { use super::*; + pub trait NonZeroParams { + type Env: GearEnv; + fn do_this(&mut self, p1: NonZeroU256, p2: MyParam) -> PendingCall; + } + pub struct NonZeroParamsImpl; + impl NonZeroParams for Service { + type Env = E; + fn do_this(&mut self, p1: NonZeroU256, p2: MyParam) -> PendingCall { + self.pending_call((p1, p2)) + } + } pub mod io { use super::*; - use sails_rs::calls::ActionIo; - pub struct DoThis(()); - impl DoThis { - #[allow(dead_code)] - pub fn encode_call(p1: NonZeroU256, p2: super::MyParam) -> Vec { - ::encode_call(&(p1, p2)) - } - } - impl ActionIo for DoThis { - const ROUTE: &'static [u8] = &[24, 68, 111, 84, 104, 105, 115]; - type Params = (NonZeroU256, super::MyParam); - type Reply = NonZeroU64; - } + sails_rs::io_struct_impl!(DoThis (p1: NonZeroU256, p2: MyParam) -> NonZeroU64); } } #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo)] @@ -59,20 +45,6 @@ pub struct MyParam { pub f3: Option<(NonZeroU64, NonZeroU256)>, } -pub mod traits { - use super::*; - - #[allow(clippy::type_complexity)] - pub trait NonZeroParams { - type Args; - fn do_this( - &mut self, - p1: NonZeroU256, - p2: MyParam, - ) -> impl Call; - } -} - #[cfg(feature = "with_mocks")] #[cfg(not(target_arch = "wasm32"))] extern crate std; @@ -82,5 +54,4 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub NonZeroParams {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl traits::NonZeroParams for NonZeroParams { type Args = A; fn do_this (&mut self, p1: NonZeroU256,p2: MyParam,) -> MockCall; } } } diff --git a/rs/client-gen/tests/snapshots/generator__rmrk_works.snap b/rs/client-gen/tests/snapshots/generator__rmrk_works.snap index 3d54b234a..5d3eb6f68 100644 --- a/rs/client-gen/tests/snapshots/generator__rmrk_works.snap +++ b/rs/client-gen/tests/snapshots/generator__rmrk_works.snap @@ -3,248 +3,130 @@ source: rs/client-gen/tests/generator.rs expression: "gen_client(idl, \"RmrkCatalog\")" --- // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::collections::BTreeMap; -#[allow(unused_imports)] -use sails_rs::{ - String, - calls::{Activation, Call, Query, Remoting, RemotingAction}, - prelude::*, -}; -pub struct RmrkCatalogFactory { - #[allow(dead_code)] - remoting: R, +use sails_rs::{client::*, prelude::*}; +pub struct RmrkCatalogProgram; +impl Program for RmrkCatalogProgram {} +pub trait RmrkCatalog { + type Env: GearEnv; + fn rmrk_catalog(&self) -> Service; } -impl RmrkCatalogFactory { - #[allow(unused)] - pub fn new(remoting: R) -> Self { - Self { remoting } +impl RmrkCatalog for Actor { + type Env = E; + fn rmrk_catalog(&self) -> Service { + self.service(stringify!(rmrk_catalog::RmrkCatalog)) } } -impl traits::RmrkCatalogFactory for RmrkCatalogFactory { - type Args = R::Args; - fn new(&self) -> impl Activation { - RemotingAction::<_, rmrk_catalog_factory::io::New>::new(self.remoting.clone(), ()) +pub trait RmrkCatalogCtors { + type Env: GearEnv; + #[allow(clippy::new_ret_no_self)] + #[allow(clippy::wrong_self_convention)] + fn new(self) -> PendingCtor; +} +impl RmrkCatalogCtors for Deployment { + type Env = E; + fn new(self) -> PendingCtor { + self.pending_ctor(()) } } -pub mod rmrk_catalog_factory { +pub mod io { use super::*; - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct New(()); - impl New { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } - } - impl ActionIo for New { - const ROUTE: &'static [u8] = &[12, 78, 101, 119]; - type Params = (); - type Reply = (); - } - } -} -pub struct RmrkCatalog { - remoting: R, -} -impl RmrkCatalog { - pub fn new(remoting: R) -> Self { - Self { remoting } - } -} -impl traits::RmrkCatalog for RmrkCatalog { - type Args = R::Args; - fn add_equippables( - &mut self, - part_id: u32, - collection_ids: Vec, - ) -> impl Call), Error>, Args = R::Args> { - RemotingAction::<_, rmrk_catalog::io::AddEquippables>::new( - self.remoting.clone(), - (part_id, collection_ids), - ) - } - fn add_parts( - &mut self, - parts: BTreeMap, - ) -> impl Call, Error>, Args = R::Args> { - RemotingAction::<_, rmrk_catalog::io::AddParts>::new(self.remoting.clone(), parts) - } - fn remove_equippable( - &mut self, - part_id: u32, - collection_id: ActorId, - ) -> impl Call, Args = R::Args> { - RemotingAction::<_, rmrk_catalog::io::RemoveEquippable>::new( - self.remoting.clone(), - (part_id, collection_id), - ) - } - fn remove_parts( - &mut self, - part_ids: Vec, - ) -> impl Call, Error>, Args = R::Args> { - RemotingAction::<_, rmrk_catalog::io::RemoveParts>::new(self.remoting.clone(), part_ids) - } - fn reset_equippables( - &mut self, - part_id: u32, - ) -> impl Call, Args = R::Args> { - RemotingAction::<_, rmrk_catalog::io::ResetEquippables>::new(self.remoting.clone(), part_id) - } - fn set_equippables_to_all( - &mut self, - part_id: u32, - ) -> impl Call, Args = R::Args> { - RemotingAction::<_, rmrk_catalog::io::SetEquippablesToAll>::new( - self.remoting.clone(), - part_id, - ) - } - fn equippable( - &self, - part_id: u32, - collection_id: ActorId, - ) -> impl Query, Args = R::Args> { - RemotingAction::<_, rmrk_catalog::io::Equippable>::new( - self.remoting.clone(), - (part_id, collection_id), - ) - } - fn part(&self, part_id: u32) -> impl Query, Args = R::Args> { - RemotingAction::<_, rmrk_catalog::io::Part>::new(self.remoting.clone(), part_id) - } + sails_rs::io_struct_impl!(New () -> ()); } pub mod rmrk_catalog { use super::*; - - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct AddEquippables(()); - impl AddEquippables { - #[allow(dead_code)] - pub fn encode_call(part_id: u32, collection_ids: Vec) -> Vec { - ::encode_call(&(part_id, collection_ids)) - } - } - impl ActionIo for AddEquippables { - const ROUTE: &'static [u8] = &[ - 44, 82, 109, 114, 107, 67, 97, 116, 97, 108, 111, 103, 56, 65, 100, 100, 69, 113, - 117, 105, 112, 112, 97, 98, 108, 101, 115, - ]; - type Params = (u32, Vec); - type Reply = Result<(u32, Vec), super::Error>; - } - pub struct AddParts(()); - impl AddParts { - #[allow(dead_code)] - pub fn encode_call(parts: BTreeMap) -> Vec { - ::encode_call(&parts) - } - } - impl ActionIo for AddParts { - const ROUTE: &'static [u8] = &[ - 44, 82, 109, 114, 107, 67, 97, 116, 97, 108, 111, 103, 32, 65, 100, 100, 80, 97, - 114, 116, 115, - ]; - type Params = BTreeMap; - type Reply = Result, super::Error>; - } - pub struct RemoveEquippable(()); - impl RemoveEquippable { - #[allow(dead_code)] - pub fn encode_call(part_id: u32, collection_id: ActorId) -> Vec { - ::encode_call(&(part_id, collection_id)) - } - } - impl ActionIo for RemoveEquippable { - const ROUTE: &'static [u8] = &[ - 44, 82, 109, 114, 107, 67, 97, 116, 97, 108, 111, 103, 64, 82, 101, 109, 111, 118, - 101, 69, 113, 117, 105, 112, 112, 97, 98, 108, 101, - ]; - type Params = (u32, ActorId); - type Reply = Result<(u32, ActorId), super::Error>; - } - pub struct RemoveParts(()); - impl RemoveParts { - #[allow(dead_code)] - pub fn encode_call(part_ids: Vec) -> Vec { - ::encode_call(&part_ids) - } - } - impl ActionIo for RemoveParts { - const ROUTE: &'static [u8] = &[ - 44, 82, 109, 114, 107, 67, 97, 116, 97, 108, 111, 103, 44, 82, 101, 109, 111, 118, - 101, 80, 97, 114, 116, 115, - ]; - type Params = Vec; - type Reply = Result, super::Error>; - } - pub struct ResetEquippables(()); - impl ResetEquippables { - #[allow(dead_code)] - pub fn encode_call(part_id: u32) -> Vec { - ::encode_call(&part_id) - } + pub trait RmrkCatalog { + type Env: GearEnv; + fn add_equippables( + &mut self, + part_id: u32, + collection_ids: Vec, + ) -> PendingCall; + fn add_parts(&mut self, parts: BTreeMap) + -> PendingCall; + fn remove_equippable( + &mut self, + part_id: u32, + collection_id: ActorId, + ) -> PendingCall; + fn remove_parts(&mut self, part_ids: Vec) -> PendingCall; + fn reset_equippables( + &mut self, + part_id: u32, + ) -> PendingCall; + fn set_equippables_to_all( + &mut self, + part_id: u32, + ) -> PendingCall; + fn equippable( + &self, + part_id: u32, + collection_id: ActorId, + ) -> PendingCall; + fn part(&self, part_id: u32) -> PendingCall; + } + pub struct RmrkCatalogImpl; + impl RmrkCatalog for Service { + type Env = E; + fn add_equippables( + &mut self, + part_id: u32, + collection_ids: Vec, + ) -> PendingCall { + self.pending_call((part_id, collection_ids)) } - impl ActionIo for ResetEquippables { - const ROUTE: &'static [u8] = &[ - 44, 82, 109, 114, 107, 67, 97, 116, 97, 108, 111, 103, 64, 82, 101, 115, 101, 116, - 69, 113, 117, 105, 112, 112, 97, 98, 108, 101, 115, - ]; - type Params = u32; - type Reply = Result<(), super::Error>; + fn add_parts( + &mut self, + parts: BTreeMap, + ) -> PendingCall { + self.pending_call((parts,)) } - pub struct SetEquippablesToAll(()); - impl SetEquippablesToAll { - #[allow(dead_code)] - pub fn encode_call(part_id: u32) -> Vec { - ::encode_call(&part_id) - } + fn remove_equippable( + &mut self, + part_id: u32, + collection_id: ActorId, + ) -> PendingCall { + self.pending_call((part_id, collection_id)) } - impl ActionIo for SetEquippablesToAll { - const ROUTE: &'static [u8] = &[ - 44, 82, 109, 114, 107, 67, 97, 116, 97, 108, 111, 103, 76, 83, 101, 116, 69, 113, - 117, 105, 112, 112, 97, 98, 108, 101, 115, 84, 111, 65, 108, 108, - ]; - type Params = u32; - type Reply = Result<(), super::Error>; + fn remove_parts(&mut self, part_ids: Vec) -> PendingCall { + self.pending_call((part_ids,)) } - pub struct Equippable(()); - impl Equippable { - #[allow(dead_code)] - pub fn encode_call(part_id: u32, collection_id: ActorId) -> Vec { - ::encode_call(&(part_id, collection_id)) - } + fn reset_equippables( + &mut self, + part_id: u32, + ) -> PendingCall { + self.pending_call((part_id,)) } - impl ActionIo for Equippable { - const ROUTE: &'static [u8] = &[ - 44, 82, 109, 114, 107, 67, 97, 116, 97, 108, 111, 103, 40, 69, 113, 117, 105, 112, - 112, 97, 98, 108, 101, - ]; - type Params = (u32, ActorId); - type Reply = Result; + fn set_equippables_to_all( + &mut self, + part_id: u32, + ) -> PendingCall { + self.pending_call((part_id,)) } - pub struct Part(()); - impl Part { - #[allow(dead_code)] - pub fn encode_call(part_id: u32) -> Vec { - ::encode_call(&part_id) - } + fn equippable( + &self, + part_id: u32, + collection_id: ActorId, + ) -> PendingCall { + self.pending_call((part_id, collection_id)) } - impl ActionIo for Part { - const ROUTE: &'static [u8] = &[ - 44, 82, 109, 114, 107, 67, 97, 116, 97, 108, 111, 103, 16, 80, 97, 114, 116, - ]; - type Params = u32; - type Reply = Option; + fn part(&self, part_id: u32) -> PendingCall { + self.pending_call((part_id,)) } } + + pub mod io { + use super::*; + sails_rs::io_struct_impl!(AddEquippables (part_id: u32, collection_ids: Vec) -> Result<(u32,Vec,), Error>); + sails_rs::io_struct_impl!(AddParts (parts: BTreeMap) -> Result, Error>); + sails_rs::io_struct_impl!(RemoveEquippable (part_id: u32, collection_id: ActorId) -> Result<(u32,ActorId,), Error>); + sails_rs::io_struct_impl!(RemoveParts (part_ids: Vec) -> Result, Error>); + sails_rs::io_struct_impl!(ResetEquippables (part_id: u32) -> Result<(), Error>); + sails_rs::io_struct_impl!(SetEquippablesToAll (part_id: u32) -> Result<(), Error>); + sails_rs::io_struct_impl!(Equippable (part_id: u32, collection_id: ActorId) -> Result); + sails_rs::io_struct_impl!(Part (part_id: u32) -> Option); + } } #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo)] #[codec(crate = sails_rs::scale_codec)] @@ -290,54 +172,6 @@ pub struct SlotPart { pub metadata_uri: String, } -pub mod traits { - use super::*; - #[allow(dead_code)] - pub trait RmrkCatalogFactory { - type Args; - #[allow(clippy::new_ret_no_self)] - #[allow(clippy::wrong_self_convention)] - fn new(&self) -> impl Activation; - } - - #[allow(clippy::type_complexity)] - pub trait RmrkCatalog { - type Args; - fn add_equippables( - &mut self, - part_id: u32, - collection_ids: Vec, - ) -> impl Call), Error>, Args = Self::Args>; - fn add_parts( - &mut self, - parts: BTreeMap, - ) -> impl Call, Error>, Args = Self::Args>; - fn remove_equippable( - &mut self, - part_id: u32, - collection_id: ActorId, - ) -> impl Call, Args = Self::Args>; - fn remove_parts( - &mut self, - part_ids: Vec, - ) -> impl Call, Error>, Args = Self::Args>; - fn reset_equippables( - &mut self, - part_id: u32, - ) -> impl Call, Args = Self::Args>; - fn set_equippables_to_all( - &mut self, - part_id: u32, - ) -> impl Call, Args = Self::Args>; - fn equippable( - &self, - part_id: u32, - collection_id: ActorId, - ) -> impl Query, Args = Self::Args>; - fn part(&self, part_id: u32) -> impl Query, Args = Self::Args>; - } -} - #[cfg(feature = "with_mocks")] #[cfg(not(target_arch = "wasm32"))] extern crate std; @@ -347,5 +181,4 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub RmrkCatalog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl traits::RmrkCatalog for RmrkCatalog { type Args = A; fn add_equippables (&mut self, part_id: u32,collection_ids: Vec,) -> MockCall,), Error>>;fn add_parts (&mut self, parts: BTreeMap,) -> MockCall, Error>>;fn remove_equippable (&mut self, part_id: u32,collection_id: ActorId,) -> MockCall>;fn remove_parts (&mut self, part_ids: Vec,) -> MockCall, Error>>;fn reset_equippables (&mut self, part_id: u32,) -> MockCall>;fn set_equippables_to_all (&mut self, part_id: u32,) -> MockCall>;fn equippable (& self, part_id: u32,collection_id: ActorId,) -> MockQuery>;fn part (& self, part_id: u32,) -> MockQuery>; } } } diff --git a/rs/src/client/mod.rs b/rs/src/client/mod.rs index b84e1e23c..1aa224e74 100644 --- a/rs/src/client/mod.rs +++ b/rs/src/client/mod.rs @@ -6,6 +6,7 @@ use core::{ pin::Pin, task::{Context, Poll}, }; +use futures::{Stream, StreamExt as _}; #[cfg(not(target_arch = "wasm32"))] mod mock_env; @@ -42,6 +43,16 @@ pub trait GearEnv: Clone { type MessageState; } +pub trait Program: Sized { + fn deploy(env: E, code_id: CodeId, salt: Vec) -> Deployment { + Deployment::new(env, code_id, salt) + } + + fn client(env: E, program_id: ActorId) -> Actor { + Actor::new(env, program_id) + } +} + #[cfg(not(target_arch = "wasm32"))] pub type DefaultEnv = MockEnv; @@ -363,6 +374,48 @@ macro_rules! str_scale_encode { }}; } +// impl> + GearEnv, S, E: EventDecode> Listener for Service { +// type Error = parity_scale_codec::Error; + +// async fn listen( +// &mut self, +// ) -> Result + Unpin, Self::Error> { +// let stream = self.env.listen().await?; +// let map = stream.filter_map(move |(actor_id, payload)| async move { +// E::decode_event(self.route, payload) +// .ok() +// .map(|e| (actor_id, e)) +// }); +// Ok(Box::pin(map)) +// } +// } + +pub trait EventDecode { + const EVENT_NAMES: &'static [&'static [u8]]; + type Event: Decode; + + fn decode_event( + prefix: Route, + payload: impl AsRef<[u8]>, + ) -> Result { + let mut payload = payload.as_ref(); + let route = String::decode(&mut payload)?; + if route != prefix { + return Err("Invalid event prefix".into()); + } + + for (idx, name) in Self::EVENT_NAMES.iter().enumerate() { + if payload.starts_with(name) { + let idx = idx as u8; + let bytes = [&[idx], &payload[name.len()..]].concat(); + let mut event_bytes = &bytes[..]; + return Decode::decode(&mut event_bytes); + } + } + Err("Invalid event name".into()) + } +} + // mod client { // use super::service::Service; From c190cf6cef4b158645f4f7019324a73ea58b489a Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 13 Aug 2025 16:51:47 +0200 Subject: [PATCH 07/66] wip: cleint gen, events, mocks --- benchmarks/src/alloc_stress_program.rs | 2 +- benchmarks/src/compute_stress_program.rs | 2 +- benchmarks/src/counter_bench_program.rs | 2 +- examples/demo/app/tests/env_gtest.rs | 12 +- .../demo/app/tests/{ => fixture}/gclient.rs | 0 examples/demo/app/tests/gtest.rs | 419 ++++----- examples/demo/client/build.rs | 10 +- examples/demo/client/src/demo_client.rs | 859 +++++------------- examples/demo/client/src/env_client.rs | 41 + examples/demo/client/src/lib.rs | 16 +- examples/proxy/Cargo.toml | 3 + examples/proxy/build.rs | 3 + examples/proxy/src/lib.rs | 27 +- examples/proxy/src/this_that/mod.rs | 46 +- .../redirect/client/src/redirect_client.rs | 2 +- .../proxy-client/src/redirect_proxy_client.rs | 2 +- examples/redirect/tests/gtest.rs | 83 +- rs/client-gen/src/events_generator.rs | 31 +- rs/client-gen/src/mock_generator.rs | 37 +- rs/client-gen/src/root_generator.rs | 28 +- rs/client-gen/src/service_generators.rs | 5 +- .../snapshots/generator__basic_works.snap | 3 +- .../snapshots/generator__events_works.snap | 16 +- .../snapshots/generator__external_types.snap | 2 +- .../tests/snapshots/generator__full.snap | 14 +- .../generator__full_with_sails_path.snap | 15 +- .../generator__multiple_services.snap | 6 +- .../snapshots/generator__nonzero_works.snap | 3 +- .../snapshots/generator__rmrk_works.snap | 3 +- rs/src/client/gtest_env.rs | 58 +- rs/src/client/mod.rs | 93 +- 31 files changed, 743 insertions(+), 1100 deletions(-) rename examples/demo/app/tests/{ => fixture}/gclient.rs (100%) create mode 100644 examples/proxy/build.rs diff --git a/benchmarks/src/alloc_stress_program.rs b/benchmarks/src/alloc_stress_program.rs index d8cef865d..d11b2deb1 100644 --- a/benchmarks/src/alloc_stress_program.rs +++ b/benchmarks/src/alloc_stress_program.rs @@ -9,7 +9,7 @@ pub trait AllocStressProgram { impl AllocStressProgram for Actor { type Env = E; fn alloc_stress(&self) -> Service { - self.service(stringify!(alloc_stress::AllocStress)) + self.service(stringify!(AllocStress)) } } pub trait AllocStressProgramCtors { diff --git a/benchmarks/src/compute_stress_program.rs b/benchmarks/src/compute_stress_program.rs index df5e4a03b..14150bcd9 100644 --- a/benchmarks/src/compute_stress_program.rs +++ b/benchmarks/src/compute_stress_program.rs @@ -9,7 +9,7 @@ pub trait ComputeStressProgram { impl ComputeStressProgram for Actor { type Env = E; fn compute_stress(&self) -> Service { - self.service(stringify!(compute_stress::ComputeStress)) + self.service(stringify!(ComputeStress)) } } pub trait ComputeStressProgramCtors { diff --git a/benchmarks/src/counter_bench_program.rs b/benchmarks/src/counter_bench_program.rs index a92f515c7..fafea6bb2 100644 --- a/benchmarks/src/counter_bench_program.rs +++ b/benchmarks/src/counter_bench_program.rs @@ -9,7 +9,7 @@ pub trait CounterBenchProgram { impl CounterBenchProgram for Actor { type Env = E; fn counter_bench(&self) -> Service { - self.service(stringify!(counter_bench::CounterBench)) + self.service(stringify!(CounterBench)) } } pub trait CounterBenchProgramCtors { diff --git a/examples/demo/app/tests/env_gtest.rs b/examples/demo/app/tests/env_gtest.rs index 9e85bc8f3..36d22e4c4 100644 --- a/examples/demo/app/tests/env_gtest.rs +++ b/examples/demo/app/tests/env_gtest.rs @@ -1,4 +1,8 @@ -use demo_client::env_client::{Demo as _, DemoCtors as _, DemoProgram, counter::Counter as _}; +use demo_client::env_client::{ + Demo as _, DemoCtors as _, DemoProgram, + counter::{Counter as _, events::CounterEvents}, +}; +use futures::StreamExt as _; use sails_rs::{client::*, prelude::*}; const ACTOR_ID: u64 = 42; @@ -33,6 +37,12 @@ async fn env_counter_add_works_via_next_mode() { .unwrap(); let mut counter_client = demo_program.counter(); + let mut counter_listener = counter_client.listener(); + let mut counter_events = counter_listener.listen().await.unwrap(); assert_eq!(Ok(52), counter_client.add(10).await); + assert_eq!( + (demo_program.id(), CounterEvents::Added(10)), + counter_events.next().await.unwrap() + ); } diff --git a/examples/demo/app/tests/gclient.rs b/examples/demo/app/tests/fixture/gclient.rs similarity index 100% rename from examples/demo/app/tests/gclient.rs rename to examples/demo/app/tests/fixture/gclient.rs diff --git a/examples/demo/app/tests/gtest.rs b/examples/demo/app/tests/gtest.rs index 1a10f4c47..f7df6e884 100644 --- a/examples/demo/app/tests/gtest.rs +++ b/examples/demo/app/tests/gtest.rs @@ -1,120 +1,118 @@ -use demo_client::{ - counter::events::CounterEvents, demo_client_factory, dog::events::DogEvents, ping_pong, - traits::*, -}; -use fixture::{ADMIN_ID, DEMO_WASM_PATH, Fixture}; -use gstd::errors::{ErrorReplyReason, SimpleExecutionError}; +use demo_client::*; +use gstd::errors::{ErrorReplyReason, ExtError, SimpleExecutionError}; +use gtest::TestError; use sails_rs::{ - calls::*, - errors::RtlError, - events::*, - futures::StreamExt, - gtest::{ - Program, System, - calls::{BlockRunMode, GTestRemoting}, - }, + client::{Program as _, *}, + futures::StreamExt as _, + gtest::{Program, System}, + prelude::*, }; -mod fixture; +const ACTOR_ID: u64 = 42; +#[cfg(debug_assertions)] +pub(crate) const DEMO_WASM_PATH: &str = "../../../target/wasm32-gear/debug/demo.opt.wasm"; +#[cfg(not(debug_assertions))] +pub(crate) const DEMO_WASM_PATH: &str = "../../../target/wasm32-gear/release/demo.opt.wasm"; + +fn create_env() -> (GtestEnv, CodeId, GasUnit) { + use sails_rs::gtest::{MAX_USER_GAS_LIMIT, System}; + + let system = System::new(); + system.init_logger_with_default_filter("gwasm=debug,gtest=info,sails_rs=debug,redirect=debug"); + system.mint_to(ACTOR_ID, 100_000_000_000_000); + // Submit program code into the system + let code_id = system.submit_code_file(DEMO_WASM_PATH); + + // Create a remoting instance for the system + // and set the block run mode to Next, + // cause we don't receive any reply on `Exit` call + let env = GtestEnv::new(system, ACTOR_ID.into()); + (env, code_id, MAX_USER_GAS_LIMIT) +} #[tokio::test] async fn counter_add_works() { + use demo_client::counter::{Counter as _, events::CounterEvents}; // Arrange - - let fixture = Fixture::new(); - - let demo_factory = fixture.demo_factory(); + let (env, code_id, _gas_limit) = create_env(); // Use generated client code for activating Demo program // using the `new` constructor and the `send_recv` method - let demo_program_id = demo_factory + let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) .new(Some(42), None) - .send_recv(fixture.demo_code_id(), "123") .await .unwrap(); - let mut counter_client = fixture.counter_client(); + let mut counter_client = demo_program.counter(); // Listen to Counter events - let mut counter_listener = fixture.counter_listener(); + let mut counter_listener = counter_client.listener(); let mut counter_events = counter_listener.listen().await.unwrap(); // Act // Use generated client code for calling Counter service // using the `send_recv` method - let result = counter_client - .add(10) - .send_recv(demo_program_id) - .await - .unwrap(); + let result = counter_client.add(10).await.unwrap(); // Asert - let event = counter_events.next().await.unwrap(); assert_eq!(result, 52); - assert_eq!((demo_program_id, CounterEvents::Added(10)), event); + assert_eq!((demo_program.id(), CounterEvents::Added(10)), event); } #[tokio::test] async fn counter_sub_works() { + use demo_client::counter::{Counter as _, events::CounterEvents}; // Arrange - - let fixture = Fixture::new(); - - let demo_factory = fixture.demo_factory(); + let (env, code_id, _gas_limit) = create_env(); // Use generated client code for activating Demo program // using the `new` constructor and the `send`/`recv` pair // of methods - let activation = demo_factory + let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) .new(Some(42), None) - .send(fixture.demo_code_id(), "123") .await .unwrap(); - let demo_program_id = activation.recv().await.unwrap(); - let mut counter_client = fixture.counter_client(); + let mut counter_client = demo_program.counter(); // Listen to Counter events - let mut counter_listener = fixture.counter_listener(); + let mut counter_listener = counter_client.listener(); let mut counter_events = counter_listener.listen().await.unwrap(); // Act // Use generated client code for calling Counter service // using the `send`/`recv` pair of methods - let response = counter_client.sub(10).send(demo_program_id).await.unwrap(); - let result = response.recv().await.unwrap(); + let result = counter_client.sub(10).await.unwrap(); // Assert - let event = counter_events.next().await.unwrap(); assert_eq!(result, 32); - assert_eq!((demo_program_id, CounterEvents::Subtracted(10)), event); + assert_eq!((demo_program.id(), CounterEvents::Subtracted(10)), event); } #[tokio::test] async fn counter_query_works() { + use demo_client::counter::Counter as _; // Arrange - let fixture = Fixture::new(); - - let demo_factory = fixture.demo_factory(); + let (env, code_id, _gas_limit) = create_env(); // Use generated client code for activating Demo program - // using the `new` constructor and the `send_recv` method - let demo_program_id = demo_factory + // using the `new` constructor and the `send`/`recv` pair + // of methods + let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) .new(Some(42), None) - .send_recv(fixture.demo_code_id(), "123") .await .unwrap(); - let counter_client = fixture.counter_client(); + let counter_client = demo_program.counter(); // Act // Use generated client code for query Counter service using the `recv` method - let result = counter_client.value().recv(demo_program_id).await.unwrap(); + let result = counter_client.value().await.unwrap(); // Asert assert_eq!(result, 42); @@ -122,20 +120,19 @@ async fn counter_query_works() { #[tokio::test] async fn counter_query_not_enough_gas() { + use demo_client::counter::Counter as _; // Arrange - let fixture = Fixture::new(); - - let demo_factory = fixture.demo_factory(); + let (env, code_id, _gas_limit) = create_env(); // Use generated client code for activating Demo program - // using the `new` constructor and the `send_recv` method - let demo_program_id = demo_factory + // using the `new` constructor and the `send`/`recv` pair + // of methods + let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) .new(Some(42), None) - .send_recv(fixture.demo_code_id(), "123") .await .unwrap(); - let counter_client = fixture.counter_client(); + let counter_client = demo_program.counter(); // Act @@ -143,31 +140,30 @@ async fn counter_query_not_enough_gas() { let result = counter_client .value() .with_gas_limit(0) // Set gas_limit to 0 - .recv(demo_program_id) .await; // Asert assert!(matches!( result, - Err(sails_rs::errors::Error::Rtl(RtlError::ReplyHasError( - ErrorReplyReason::Execution(SimpleExecutionError::RanOutOfGas), - _payload - ))) + Err(TestError::ExecutionError(ExtError::Execution( + gear_core_errors::ExecutionError::NotEnoughGas + )),) )); } /// Low level program test using `gtest::System` and call encoding/decoding with `io` module #[tokio::test] async fn ping_pong_low_level_works() { + use demo_client::{io::Default, ping_pong::io::Ping}; + let system = System::new(); system.init_logger_with_default_filter("gwasm=debug,gtest=info,sails_rs=debug"); - system.mint_to(ADMIN_ID, 1_000_000_000_000_000); + system.mint_to(ACTOR_ID, 1_000_000_000_000_000); let demo_program = Program::from_file(&system, DEMO_WASM_PATH); // Use generated `io` module to create a program - let message_id = - demo_program.send_bytes(ADMIN_ID, demo_client_factory::io::Default::encode_call()); + let message_id = demo_program.send_bytes(ACTOR_ID, Default::encode_params()); let run_result = system.run_next_block(); let gas_burned = *run_result .gas_burned @@ -178,9 +174,9 @@ async fn ping_pong_low_level_works() { // Use generated `io` module for encoding/decoding calls and replies // and send/receive bytes using `gtest` native means - let ping_call_payload = ping_pong::io::Ping::encode_call("ping".into()); + let ping_call_payload = Ping::encode_params_with_prefix("PingPong", "ping".into()); - let message_id = demo_program.send_bytes(ADMIN_ID, ping_call_payload); + let message_id = demo_program.send_bytes(ACTOR_ID, ping_call_payload); let run_result = system.run_next_block(); let reply_log_record = run_result @@ -191,7 +187,7 @@ async fn ping_pong_low_level_works() { let ping_reply_payload = reply_log_record.payload(); - let ping_reply = ping_pong::io::Ping::decode_reply(ping_reply_payload).unwrap(); + let ping_reply = Ping::decode_reply_with_prefix("PingPong", ping_reply_payload).unwrap(); assert_eq!(ping_reply, Ok("pong".to_string())); @@ -204,73 +200,61 @@ async fn ping_pong_low_level_works() { #[tokio::test] async fn dog_barks() { + use demo_client::dog::{Dog as _, events::DogEvents}; // Arrange + let (env, code_id, _gas_limit) = create_env(); - let fixture = Fixture::new(); - - let demo_factory = fixture.demo_factory(); - - let demo_program_id = demo_factory + // Use generated client code for activating Demo program + // using the `new` constructor and the `send`/`recv` pair + // of methods + let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) .new(None, Some((1, -1))) - .send_recv(fixture.demo_code_id(), "123") .await .unwrap(); - let mut dog_client = fixture.dog_client(); - let mut dog_listener = fixture.dog_listener(); + let mut dog_client = demo_program.dog(); + let mut dog_listener = dog_client.listener(); let mut dog_events = dog_listener.listen().await.unwrap(); // Act - - let result = dog_client - .make_sound() - .send_recv(demo_program_id) - .await - .unwrap(); + let result = dog_client.make_sound().await.unwrap(); // Assert - let event = dog_events.next().await.unwrap(); assert_eq!(result, "Woof! Woof!"); - assert_eq!((demo_program_id, DogEvents::Barked), event); + assert_eq!((demo_program.id(), DogEvents::Barked), event); } #[tokio::test] async fn dog_walks() { + use demo_client::dog::{Dog as _, events::DogEvents}; // Arrange + let (env, code_id, _gas_limit) = create_env(); - let fixture = Fixture::new(); - - let demo_factory = fixture.demo_factory(); - - let demo_program_id = demo_factory + // Use generated client code for activating Demo program + // using the `new` constructor and the `send`/`recv` pair + // of methods + let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) .new(None, Some((1, -1))) - .send_recv(fixture.demo_code_id(), "123") .await .unwrap(); - let mut dog_client = fixture.dog_client(); - let mut dog_listener = fixture.dog_listener(); + let mut dog_client = demo_program.dog(); + let mut dog_listener = dog_client.listener(); let mut dog_events = dog_listener.listen().await.unwrap(); // Act - - dog_client - .walk(10, 20) - .send_recv(demo_program_id) - .await - .unwrap(); + dog_client.walk(10, 20).await.unwrap(); // Assert - - let position = dog_client.position().recv(demo_program_id).await.unwrap(); + let position = dog_client.position().await.unwrap(); let event = dog_events.next().await.unwrap(); assert_eq!(position, (11, 19)); assert_eq!( ( - demo_program_id, + demo_program.id(), DogEvents::Walked { from: (1, -1), to: (11, 19) @@ -282,107 +266,82 @@ async fn dog_walks() { #[tokio::test] async fn dog_weights() { - let fixture = Fixture::new(); - - let demo_factory = fixture.demo_factory(); + use demo_client::dog::Dog as _; + // Arrange + let (env, code_id, _gas_limit) = create_env(); - let demo_program_id = demo_factory + // Use generated client code for activating Demo program + // using the `new` constructor and the `send`/`recv` pair + // of methods + let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) .new(None, Some((1, -1))) - .send_recv(fixture.demo_code_id(), "123") .await .unwrap(); - let dog_client = fixture.dog_client(); + let dog_client = demo_program.dog(); - let avg_weight = dog_client.avg_weight().recv(demo_program_id).await.unwrap(); + let avg_weight = dog_client.avg_weight().await.unwrap(); assert_eq!(avg_weight, 42); } #[tokio::test] async fn references_add() { - let fixture = Fixture::new(); - - let demo_factory = fixture.demo_factory(); + use demo_client::references::References as _; + // Arrange + let (env, code_id, _gas_limit) = create_env(); - let demo_program_id = demo_factory + let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) .new(None, Some((1, -1))) - .send_recv(fixture.demo_code_id(), "123") .await .unwrap(); - let mut client = fixture.references_client(); + let mut client = demo_program.references(); - let value = client.add(42).send_recv(demo_program_id).await.unwrap(); + let value = client.add(42).await.unwrap(); assert_eq!(42, value); } #[tokio::test] async fn references_bytes() { - let fixture = Fixture::new(); - - let demo_factory = fixture.demo_factory(); + use demo_client::references::References as _; + // Arrange + let (env, code_id, _gas_limit) = create_env(); - let demo_program_id = demo_factory + let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) .new(None, Some((1, -1))) - .send_recv(fixture.demo_code_id(), "123") .await .unwrap(); - let mut client = fixture.references_client(); + let mut client = demo_program.references(); - _ = client - .add_byte(42) - .send_recv(demo_program_id) - .await - .unwrap(); - _ = client - .add_byte(89) - .send_recv(demo_program_id) - .await - .unwrap(); - _ = client - .add_byte(14) - .send_recv(demo_program_id) - .await - .unwrap(); + _ = client.add_byte(42).await.unwrap(); + _ = client.add_byte(89).await.unwrap(); + _ = client.add_byte(14).await.unwrap(); - let last = client.last_byte().recv(demo_program_id).await.unwrap(); + let last = client.last_byte().await.unwrap(); assert_eq!(Some(14), last); } #[tokio::test] async fn references_guess_num() { - let fixture = Fixture::new(); - - let demo_factory = fixture.demo_factory(); + use demo_client::references::References as _; + // Arrange + let (env, code_id, _gas_limit) = create_env(); - let demo_program_id = demo_factory + let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) .new(None, Some((1, -1))) - .send_recv(fixture.demo_code_id(), "123") .await .unwrap(); - let mut client = fixture.references_client(); + let mut client = demo_program.references(); - let res1 = client - .guess_num(42) - .send_recv(demo_program_id) - .await - .unwrap(); - let res2 = client - .guess_num(89) - .send_recv(demo_program_id) - .await - .unwrap(); - let res3 = client.message().recv(demo_program_id).await.unwrap(); - let res4 = client.set_num(14).send_recv(demo_program_id).await.unwrap(); - let res5 = client - .guess_num(14) - .send_recv(demo_program_id) - .await - .unwrap(); + let res1 = client.guess_num(42).await.unwrap(); + let res2 = client.guess_num(89).await.unwrap(); + let res3 = client.message().await.unwrap(); + let res4 = client.set_num(14).await.unwrap(); + let res5 = client.guess_num(14).await.unwrap(); assert_eq!(Ok("demo".to_owned()), res1); assert_eq!(Err("Number is too large".to_owned()), res2); @@ -393,124 +352,97 @@ async fn references_guess_num() { #[tokio::test] async fn counter_add_works_via_next_mode() { + use demo_client::counter::{Counter as _, events::CounterEvents}; // Arrange - const DEMO_WASM_PATH: &str = "../../../target/wasm32-gear/debug/demo.opt.wasm"; - let system = System::new(); - system.init_logger_with_default_filter("gwasm=debug,gtest=info,sails_rs=debug"); - system.mint_to(fixture::ADMIN_ID, 1_000_000_000_000_000); - let demo_code_id = system.submit_code_file(DEMO_WASM_PATH); + let (env, code_id, _gas_limit) = create_env(); + let env = env.with_block_run_mode(BlockRunMode::Next); - let remoting = GTestRemoting::new(system, fixture::ADMIN_ID.into()) - .with_block_run_mode(BlockRunMode::Next); - - let demo_factory = demo_client::DemoClientFactory::new(remoting.clone()); - - let demo_program_id = demo_factory + let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) .new(Some(42), None) - .send_recv(demo_code_id, "123") .await .unwrap(); - let mut counter_client = demo_client::Counter::new(remoting.clone()); + let mut counter_client = demo_program.counter(); // Listen to Counter events - let mut counter_listener = demo_client::counter::events::listener(remoting.clone()); + let mut counter_listener = counter_client.listener(); let mut counter_events = counter_listener.listen().await.unwrap(); // Act - let result = counter_client - .add(10) - .send_recv(demo_program_id) - .await - .unwrap(); + let result = counter_client.add(10).await.unwrap(); // Asert - let event = counter_events.next().await.unwrap(); - assert_eq!(result, 52); - assert_eq!((demo_program_id, CounterEvents::Added(10)), event); + assert_eq!( + (demo_program.id(), CounterEvents::Added(10)), + counter_events.next().await.unwrap() + ); } #[tokio::test] async fn counter_add_works_via_manual_mode() { + use demo_client::counter::{Counter as _, events::CounterEvents}; // Arrange - const DEMO_WASM_PATH: &str = "../../../target/wasm32-gear/debug/demo.opt.wasm"; - let system = System::new(); - system.init_logger_with_default_filter("gwasm=debug,gtest=info,sails_rs=debug"); - system.mint_to(fixture::ADMIN_ID, 1_000_000_000_000_000); - let demo_code_id = system.submit_code_file(DEMO_WASM_PATH); + let (env, code_id, _gas_limit) = create_env(); + let env = env.with_block_run_mode(BlockRunMode::Next); - let remoting = GTestRemoting::new(system, fixture::ADMIN_ID.into()) - .with_block_run_mode(BlockRunMode::Manual); - - let demo_factory = demo_client::DemoClientFactory::new(remoting.clone()); - - // Use generated client code for activating Demo program - let activation = demo_factory + let pending_ctor = DemoClientProgram::deploy(env.clone(), code_id, vec![]) .new(Some(42), None) - .send(demo_code_id, "123") - .await + .create_program() .unwrap(); // Run next Block - remoting.run_next_block(); + env.run_next_block(); - let demo_program_id = activation.recv().await.unwrap(); + let demo_program = pending_ctor.await.unwrap(); - let mut counter_client_add = demo_client::Counter::new(remoting.clone()); - let mut counter_client_sub = demo_client::Counter::new(remoting.clone()); + let mut counter_client = demo_program.counter(); // Listen to Counter events - let mut counter_listener = demo_client::counter::events::listener(remoting.clone()); + let mut counter_listener = counter_client.listener(); let mut counter_events = counter_listener.listen().await.unwrap(); // Use generated client code for calling Counter service - let call_add = counter_client_add - .add(10) - .send(demo_program_id) - .await - .unwrap(); - let call_sub = counter_client_sub - .sub(20) - .send(demo_program_id) - .await - .unwrap(); + let call_add = counter_client.add(10).send_message().unwrap(); + let call_sub = counter_client.sub(20).send_message().unwrap(); // Run next Block - remoting.run_next_block(); + env.run_next_block(); // Got replies - let result_add = call_add.recv().await.unwrap(); + let result_add = call_add.await.unwrap(); assert_eq!(result_add, 52); - let result_sub = call_sub.recv().await.unwrap(); + let result_sub = call_sub.await.unwrap(); assert_eq!(result_sub, 32); // Got events assert_eq!( - (demo_program_id, CounterEvents::Added(10)), + (demo_program.id(), CounterEvents::Added(10)), counter_events.next().await.unwrap() ); assert_eq!( - (demo_program_id, CounterEvents::Subtracted(20)), + (demo_program.id(), CounterEvents::Subtracted(20)), counter_events.next().await.unwrap() ); } #[test] fn counter_add_low_level_works() { + use demo_client::{counter::io::Add, io::Default}; + let system = System::new(); system.init_logger_with_default_filter("gwasm=debug,gtest=info,sails_rs=debug"); - system.mint_to(ADMIN_ID, 1_000_000_000_000_000); + system.mint_to(ACTOR_ID, 1_000_000_000_000_000); let demo_program = Program::from_file(&system, DEMO_WASM_PATH); let wasm_size = std::fs::metadata(DEMO_WASM_PATH).unwrap().len(); // Use generated `io` module to create a program - demo_program.send_bytes(ADMIN_ID, demo_client_factory::io::Default::encode_call()); + demo_program.send_bytes(ACTOR_ID, Default::encode_params()); // Use generated `io` module for encoding/decoding calls and replies // and send/receive bytes using `gtest` native means - let call_payload = demo_client::counter::io::Add::encode_call(10); + let call_payload = Add::encode_params_with_prefix("Counter", 10); - let message_id = demo_program.send_bytes(ADMIN_ID, call_payload); + let message_id = demo_program.send_bytes(ACTOR_ID, call_payload); let run_result = system.run_next_block(); let reply_log_record = run_result @@ -521,7 +453,7 @@ fn counter_add_low_level_works() { let reply_payload = reply_log_record.payload(); - let reply = demo_client::counter::io::Add::decode_reply(reply_payload).unwrap(); + let reply = Add::decode_reply_with_prefix("Counter", reply_payload).unwrap(); assert_eq!(reply, 10); @@ -534,41 +466,33 @@ fn counter_add_low_level_works() { #[tokio::test] async fn value_fee_works() { + use demo_client::value_fee::ValueFee as _; // Arrange - let fixture = Fixture::new(); + let (env, code_id, _gas_limit) = create_env(); - let demo_factory = fixture.demo_factory(); - // Use generated client code for activating Demo program - // using the `new` constructor and the `send_recv` method - let program_id = demo_factory + let demo_program = DemoClientProgram::deploy(env.clone(), code_id, vec![]) .new(Some(42), None) - .send_recv(fixture.demo_code_id(), "123") .await .unwrap(); - let initial_balance = fixture.balance_of(fixture::ADMIN_ID.into()); - let mut client = fixture.value_fee_client(); + let initial_balance = env.system().balance_of(ActorId::from(ACTOR_ID)); + let mut client = demo_program.value_fee(); // Act // Use generated client code to call `do_something_and_take_fee` method with zero value - let result = client - .do_something_and_take_fee() - .send_recv(program_id) - .await - .unwrap(); + let result = client.do_something_and_take_fee().await.unwrap(); assert!(!result); // Use generated client code to call `do_something_and_take_fee` method with value let result = client .do_something_and_take_fee() .with_value(15_000_000_000_000) - .send_recv(program_id) .await .unwrap(); assert!(result); - let balance = fixture.balance_of(fixture::ADMIN_ID.into()); + let balance = env.system().balance_of(ActorId::from(ACTOR_ID)); // fee is 10_000_000_000_000 + spent gas // initial_balance - balance = 10_329_809_407_200 assert!( @@ -580,27 +504,26 @@ async fn value_fee_works() { #[tokio::test] async fn program_value_transfer_works() { // Arrange - let fixture = Fixture::new(); + let (env, code_id, _gas_limit) = create_env(); - let demo_factory = fixture.demo_factory(); - // Use generated client code for activating Demo program - // using the `new` constructor and the `send_recv` method - let program_id = demo_factory + let demo_program = DemoClientProgram::deploy(env.clone(), code_id, vec![]) .new(Some(42), None) - .send_recv(fixture.demo_code_id(), "123") .await .unwrap(); + let program_id = demo_program.id(); - let initial_balance = fixture.balance_of(program_id); + let initial_balance = env.system().balance_of(program_id); // Act // send empty bytes with value 1_000_000_000_000 to the program - _ = fixture + _ = env + .system() .get_program(program_id) - .map(|prg| prg.send_bytes_with_value(ADMIN_ID, Vec::::new(), 1_000_000_000_000)); - fixture.remoting().run_next_block(); + .map(|prg| prg.send_bytes_with_value(ACTOR_ID, Vec::::new(), 1_000_000_000_000)); + + env.run_next_block(); // Assert - let balance = fixture.balance_of(program_id); + let balance = env.system().balance_of(program_id); assert_eq!(initial_balance + 1_000_000_000_000, balance); } diff --git a/examples/demo/client/build.rs b/examples/demo/client/build.rs index 697f1a730..e9c7d1f7f 100644 --- a/examples/demo/client/build.rs +++ b/examples/demo/client/build.rs @@ -1,8 +1,8 @@ fn main() { // Generate IDL file for the `Demo` app and client code from IDL file - // sails_rs::ClientBuilder::::from_env() - // .build_idl() - // .with_mocks("with_mocks") - // .generate() - // .unwrap(); + sails_rs::ClientBuilder::::from_env() + .build_idl() + .with_mocks("with_mocks") + .generate() + .unwrap(); } diff --git a/examples/demo/client/src/demo_client.rs b/examples/demo/client/src/demo_client.rs index c4f37e574..92990fc97 100644 --- a/examples/demo/client/src/demo_client.rs +++ b/examples/demo/client/src/demo_client.rs @@ -1,187 +1,123 @@ // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::collections::BTreeMap; -#[allow(unused_imports)] -use sails_rs::{ - String, - calls::{Activation, Call, Query, Remoting, RemotingAction}, - prelude::*, -}; -pub struct DemoClientFactory { - #[allow(dead_code)] - remoting: R, +use sails_rs::{client::*, prelude::*}; +pub struct DemoClientProgram; +impl Program for DemoClientProgram {} +pub trait DemoClient { + type Env: GearEnv; + fn ping_pong(&self) -> Service; + fn counter(&self) -> Service; + fn dog(&self) -> Service; + fn references(&self) -> Service; + fn this_that(&self) -> Service; + fn value_fee(&self) -> Service; } -impl DemoClientFactory { - #[allow(unused)] - pub fn new(remoting: R) -> Self { - Self { remoting } +impl DemoClient for Actor { + type Env = E; + fn ping_pong(&self) -> Service { + self.service(stringify!(PingPong)) + } + fn counter(&self) -> Service { + self.service(stringify!(Counter)) + } + fn dog(&self) -> Service { + self.service(stringify!(Dog)) + } + fn references(&self) -> Service { + self.service(stringify!(References)) + } + fn this_that(&self) -> Service { + self.service(stringify!(ThisThat)) + } + fn value_fee(&self) -> Service { + self.service(stringify!(ValueFee)) } } -impl traits::DemoClientFactory for DemoClientFactory { - type Args = R::Args; +pub trait DemoClientCtors { + type Env: GearEnv; /// Program constructor (called once at the very beginning of the program lifetime) - fn default(&self) -> impl Activation { - RemotingAction::<_, demo_client_factory::io::Default>::new(self.remoting.clone(), ()) + fn default(self) -> PendingCtor; + /// Another program constructor (called once at the very beginning of the program lifetime)#[allow(clippy::new_ret_no_self)] #[allow(clippy::wrong_self_convention)] + fn new( + self, + counter: Option, + dog_position: Option<(i32, i32)>, + ) -> PendingCtor; +} +impl DemoClientCtors for Deployment { + type Env = E; + fn default(self) -> PendingCtor { + self.pending_ctor(()) } - /// Another program constructor (called once at the very beginning of the program lifetime) fn new( - &self, + self, counter: Option, dog_position: Option<(i32, i32)>, - ) -> impl Activation { - RemotingAction::<_, demo_client_factory::io::New>::new( - self.remoting.clone(), - (counter, dog_position), - ) + ) -> PendingCtor { + self.pending_ctor((counter, dog_position)) } } -pub mod demo_client_factory { +pub mod io { use super::*; - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct Default(()); - impl Default { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } - } - impl ActionIo for Default { - const ROUTE: &'static [u8] = &[28, 68, 101, 102, 97, 117, 108, 116]; - type Params = (); - type Reply = (); - } - pub struct New(()); - impl New { - #[allow(dead_code)] - pub fn encode_call(counter: Option, dog_position: Option<(i32, i32)>) -> Vec { - ::encode_call(&(counter, dog_position)) - } - } - impl ActionIo for New { - const ROUTE: &'static [u8] = &[12, 78, 101, 119]; - type Params = (Option, Option<(i32, i32)>); - type Reply = (); - } - } -} -pub struct PingPong { - remoting: R, -} -impl PingPong { - pub fn new(remoting: R) -> Self { - Self { remoting } - } -} -impl traits::PingPong for PingPong { - type Args = R::Args; - fn ping( - &mut self, - input: String, - ) -> impl Call, Args = R::Args> { - RemotingAction::<_, ping_pong::io::Ping>::new(self.remoting.clone(), input) - } + sails_rs::io_struct_impl!(Default () -> ()); + sails_rs::io_struct_impl!(New (counter: Option, dog_position: Option<(i32,i32,)>) -> ()); } pub mod ping_pong { use super::*; + pub trait PingPong { + type Env: GearEnv; + fn ping(&mut self, input: String) -> PendingCall; + } + pub struct PingPongImpl; + impl PingPong for Service { + type Env = E; + fn ping(&mut self, input: String) -> PendingCall { + self.pending_call((input,)) + } + } pub mod io { use super::*; - use sails_rs::calls::ActionIo; - pub struct Ping(()); - impl Ping { - #[allow(dead_code)] - pub fn encode_call(input: String) -> Vec { - ::encode_call(&input) - } - } - impl ActionIo for Ping { - const ROUTE: &'static [u8] = &[ - 32, 80, 105, 110, 103, 80, 111, 110, 103, 16, 80, 105, 110, 103, - ]; - type Params = String; - type Reply = Result; - } - } -} -pub struct Counter { - remoting: R, -} -impl Counter { - pub fn new(remoting: R) -> Self { - Self { remoting } - } -} -impl traits::Counter for Counter { - type Args = R::Args; - /// Add a value to the counter - fn add(&mut self, value: u32) -> impl Call { - RemotingAction::<_, counter::io::Add>::new(self.remoting.clone(), value) - } - /// Substract a value from the counter - fn sub(&mut self, value: u32) -> impl Call { - RemotingAction::<_, counter::io::Sub>::new(self.remoting.clone(), value) - } - /// Get the current value - fn value(&self) -> impl Query { - RemotingAction::<_, counter::io::Value>::new(self.remoting.clone(), ()) + sails_rs::io_struct_impl!(Ping (input: String) -> Result); } } pub mod counter { use super::*; - - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct Add(()); - impl Add { - #[allow(dead_code)] - pub fn encode_call(value: u32) -> Vec { - ::encode_call(&value) - } - } - impl ActionIo for Add { - const ROUTE: &'static [u8] = &[28, 67, 111, 117, 110, 116, 101, 114, 12, 65, 100, 100]; - type Params = u32; - type Reply = u32; - } - pub struct Sub(()); - impl Sub { - #[allow(dead_code)] - pub fn encode_call(value: u32) -> Vec { - ::encode_call(&value) - } - } - impl ActionIo for Sub { - const ROUTE: &'static [u8] = &[28, 67, 111, 117, 110, 116, 101, 114, 12, 83, 117, 98]; - type Params = u32; - type Reply = u32; + pub trait Counter { + type Env: GearEnv; + /// Add a value to the counter + fn add(&mut self, value: u32) -> PendingCall; + /// Substract a value from the counter + fn sub(&mut self, value: u32) -> PendingCall; + /// Get the current value + fn value(&self) -> PendingCall; + } + pub struct CounterImpl; + impl Counter for Service { + type Env = E; + fn add(&mut self, value: u32) -> PendingCall { + self.pending_call((value,)) } - pub struct Value(()); - impl Value { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } + fn sub(&mut self, value: u32) -> PendingCall { + self.pending_call((value,)) } - impl ActionIo for Value { - const ROUTE: &'static [u8] = &[ - 28, 67, 111, 117, 110, 116, 101, 114, 20, 86, 97, 108, 117, 101, - ]; - type Params = (); - type Reply = u32; + fn value(&self) -> PendingCall { + self.pending_call(()) } } - #[allow(dead_code)] + pub mod io { + use super::*; + sails_rs::io_struct_impl!(Add (value: u32) -> u32); + sails_rs::io_struct_impl!(Sub (value: u32) -> u32); + sails_rs::io_struct_impl!(Value () -> u32); + } + #[cfg(not(target_arch = "wasm32"))] pub mod events { use super::*; - use sails_rs::events::*; #[derive(PartialEq, Debug, Encode, Decode)] #[codec(crate = sails_rs::scale_codec)] pub enum CounterEvents { @@ -190,469 +126,219 @@ pub mod counter { /// Emitted when a value is subtracted from the counter Subtracted(u32), } - impl EventIo for CounterEvents { - const ROUTE: &'static [u8] = &[28, 67, 111, 117, 110, 116, 101, 114]; + impl EventDecode for CounterEvents { const EVENT_NAMES: &'static [&'static [u8]] = &[ &[20, 65, 100, 100, 101, 100], &[40, 83, 117, 98, 116, 114, 97, 99, 116, 101, 100], ]; - type Event = Self; } - pub fn listener>>(remoting: R) -> impl Listener { - RemotingListener::<_, CounterEvents>::new(remoting) + impl ServiceEvent for CounterImpl { + type Event = CounterEvents; } } } -pub struct Dog { - remoting: R, -} -impl Dog { - pub fn new(remoting: R) -> Self { - Self { remoting } - } -} -impl traits::Dog for Dog { - type Args = R::Args; - fn make_sound(&mut self) -> impl Call { - RemotingAction::<_, dog::io::MakeSound>::new(self.remoting.clone(), ()) - } - fn walk(&mut self, dx: i32, dy: i32) -> impl Call { - RemotingAction::<_, dog::io::Walk>::new(self.remoting.clone(), (dx, dy)) - } - fn avg_weight(&self) -> impl Query { - RemotingAction::<_, dog::io::AvgWeight>::new(self.remoting.clone(), ()) - } - fn position(&self) -> impl Query { - RemotingAction::<_, dog::io::Position>::new(self.remoting.clone(), ()) - } -} pub mod dog { use super::*; - - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct MakeSound(()); - impl MakeSound { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } - } - impl ActionIo for MakeSound { - const ROUTE: &'static [u8] = &[ - 12, 68, 111, 103, 36, 77, 97, 107, 101, 83, 111, 117, 110, 100, - ]; - type Params = (); - type Reply = String; - } - pub struct Walk(()); - impl Walk { - #[allow(dead_code)] - pub fn encode_call(dx: i32, dy: i32) -> Vec { - ::encode_call(&(dx, dy)) - } - } - impl ActionIo for Walk { - const ROUTE: &'static [u8] = &[12, 68, 111, 103, 16, 87, 97, 108, 107]; - type Params = (i32, i32); - type Reply = (); - } - pub struct AvgWeight(()); - impl AvgWeight { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } + pub trait Dog { + type Env: GearEnv; + fn make_sound(&mut self) -> PendingCall; + fn walk(&mut self, dx: i32, dy: i32) -> PendingCall; + fn avg_weight(&self) -> PendingCall; + fn position(&self) -> PendingCall; + } + pub struct DogImpl; + impl Dog for Service { + type Env = E; + fn make_sound(&mut self) -> PendingCall { + self.pending_call(()) } - impl ActionIo for AvgWeight { - const ROUTE: &'static [u8] = &[ - 12, 68, 111, 103, 36, 65, 118, 103, 87, 101, 105, 103, 104, 116, - ]; - type Params = (); - type Reply = u32; + fn walk(&mut self, dx: i32, dy: i32) -> PendingCall { + self.pending_call((dx, dy)) } - pub struct Position(()); - impl Position { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } + fn avg_weight(&self) -> PendingCall { + self.pending_call(()) } - impl ActionIo for Position { - const ROUTE: &'static [u8] = - &[12, 68, 111, 103, 32, 80, 111, 115, 105, 116, 105, 111, 110]; - type Params = (); - type Reply = (i32, i32); + fn position(&self) -> PendingCall { + self.pending_call(()) } } - #[allow(dead_code)] + pub mod io { + use super::*; + sails_rs::io_struct_impl!(MakeSound () -> String); + sails_rs::io_struct_impl!(Walk (dx: i32, dy: i32) -> ()); + sails_rs::io_struct_impl!(AvgWeight () -> u32); + sails_rs::io_struct_impl!(Position () -> (i32,i32,)); + } + #[cfg(not(target_arch = "wasm32"))] pub mod events { use super::*; - use sails_rs::events::*; #[derive(PartialEq, Debug, Encode, Decode)] #[codec(crate = sails_rs::scale_codec)] pub enum DogEvents { Barked, Walked { from: (i32, i32), to: (i32, i32) }, } - impl EventIo for DogEvents { - const ROUTE: &'static [u8] = &[12, 68, 111, 103]; + impl EventDecode for DogEvents { const EVENT_NAMES: &'static [&'static [u8]] = &[ &[24, 66, 97, 114, 107, 101, 100], &[24, 87, 97, 108, 107, 101, 100], ]; - type Event = Self; } - pub fn listener>>(remoting: R) -> impl Listener { - RemotingListener::<_, DogEvents>::new(remoting) + impl ServiceEvent for DogImpl { + type Event = DogEvents; } } } -pub struct References { - remoting: R, -} -impl References { - pub fn new(remoting: R) -> Self { - Self { remoting } - } -} -impl traits::References for References { - type Args = R::Args; - fn add(&mut self, v: u32) -> impl Call { - RemotingAction::<_, references::io::Add>::new(self.remoting.clone(), v) - } - fn add_byte(&mut self, byte: u8) -> impl Call, Args = R::Args> { - RemotingAction::<_, references::io::AddByte>::new(self.remoting.clone(), byte) - } - fn guess_num( - &mut self, - number: u8, - ) -> impl Call, Args = R::Args> { - RemotingAction::<_, references::io::GuessNum>::new(self.remoting.clone(), number) - } - fn incr(&mut self) -> impl Call { - RemotingAction::<_, references::io::Incr>::new(self.remoting.clone(), ()) - } - fn set_num(&mut self, number: u8) -> impl Call, Args = R::Args> { - RemotingAction::<_, references::io::SetNum>::new(self.remoting.clone(), number) - } - fn baked(&self) -> impl Query { - RemotingAction::<_, references::io::Baked>::new(self.remoting.clone(), ()) - } - fn last_byte(&self) -> impl Query, Args = R::Args> { - RemotingAction::<_, references::io::LastByte>::new(self.remoting.clone(), ()) - } - fn message(&self) -> impl Query, Args = R::Args> { - RemotingAction::<_, references::io::Message>::new(self.remoting.clone(), ()) - } -} pub mod references { use super::*; - - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct Add(()); - impl Add { - #[allow(dead_code)] - pub fn encode_call(v: u32) -> Vec { - ::encode_call(&v) - } - } - impl ActionIo for Add { - const ROUTE: &'static [u8] = &[ - 40, 82, 101, 102, 101, 114, 101, 110, 99, 101, 115, 12, 65, 100, 100, - ]; - type Params = u32; - type Reply = u32; - } - pub struct AddByte(()); - impl AddByte { - #[allow(dead_code)] - pub fn encode_call(byte: u8) -> Vec { - ::encode_call(&byte) - } - } - impl ActionIo for AddByte { - const ROUTE: &'static [u8] = &[ - 40, 82, 101, 102, 101, 114, 101, 110, 99, 101, 115, 28, 65, 100, 100, 66, 121, 116, - 101, - ]; - type Params = u8; - type Reply = Vec; - } - pub struct GuessNum(()); - impl GuessNum { - #[allow(dead_code)] - pub fn encode_call(number: u8) -> Vec { - ::encode_call(&number) - } - } - impl ActionIo for GuessNum { - const ROUTE: &'static [u8] = &[ - 40, 82, 101, 102, 101, 114, 101, 110, 99, 101, 115, 32, 71, 117, 101, 115, 115, 78, - 117, 109, - ]; - type Params = u8; - type Reply = Result; - } - pub struct Incr(()); - impl Incr { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } - } - impl ActionIo for Incr { - const ROUTE: &'static [u8] = &[ - 40, 82, 101, 102, 101, 114, 101, 110, 99, 101, 115, 16, 73, 110, 99, 114, - ]; - type Params = (); - type Reply = super::ReferenceCount; - } - pub struct SetNum(()); - impl SetNum { - #[allow(dead_code)] - pub fn encode_call(number: u8) -> Vec { - ::encode_call(&number) - } + pub trait References { + type Env: GearEnv; + fn add(&mut self, v: u32) -> PendingCall; + fn add_byte(&mut self, byte: u8) -> PendingCall; + fn guess_num(&mut self, number: u8) -> PendingCall; + fn incr(&mut self) -> PendingCall; + fn set_num(&mut self, number: u8) -> PendingCall; + fn baked(&self) -> PendingCall; + fn last_byte(&self) -> PendingCall; + fn message(&self) -> PendingCall; + } + pub struct ReferencesImpl; + impl References for Service { + type Env = E; + fn add(&mut self, v: u32) -> PendingCall { + self.pending_call((v,)) } - impl ActionIo for SetNum { - const ROUTE: &'static [u8] = &[ - 40, 82, 101, 102, 101, 114, 101, 110, 99, 101, 115, 24, 83, 101, 116, 78, 117, 109, - ]; - type Params = u8; - type Reply = Result<(), String>; + fn add_byte(&mut self, byte: u8) -> PendingCall { + self.pending_call((byte,)) } - pub struct Baked(()); - impl Baked { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } + fn guess_num(&mut self, number: u8) -> PendingCall { + self.pending_call((number,)) } - impl ActionIo for Baked { - const ROUTE: &'static [u8] = &[ - 40, 82, 101, 102, 101, 114, 101, 110, 99, 101, 115, 20, 66, 97, 107, 101, 100, - ]; - type Params = (); - type Reply = String; + fn incr(&mut self) -> PendingCall { + self.pending_call(()) } - pub struct LastByte(()); - impl LastByte { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } + fn set_num(&mut self, number: u8) -> PendingCall { + self.pending_call((number,)) } - impl ActionIo for LastByte { - const ROUTE: &'static [u8] = &[ - 40, 82, 101, 102, 101, 114, 101, 110, 99, 101, 115, 32, 76, 97, 115, 116, 66, 121, - 116, 101, - ]; - type Params = (); - type Reply = Option; + fn baked(&self) -> PendingCall { + self.pending_call(()) } - pub struct Message(()); - impl Message { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } + fn last_byte(&self) -> PendingCall { + self.pending_call(()) } - impl ActionIo for Message { - const ROUTE: &'static [u8] = &[ - 40, 82, 101, 102, 101, 114, 101, 110, 99, 101, 115, 28, 77, 101, 115, 115, 97, 103, - 101, - ]; - type Params = (); - type Reply = Option; + fn message(&self) -> PendingCall { + self.pending_call(()) } } -} -pub struct ThisThat { - remoting: R, -} -impl ThisThat { - pub fn new(remoting: R) -> Self { - Self { remoting } - } -} -impl traits::ThisThat for ThisThat { - type Args = R::Args; - fn do_that( - &mut self, - param: DoThatParam, - ) -> impl Call, Args = R::Args> - { - RemotingAction::<_, this_that::io::DoThat>::new(self.remoting.clone(), param) - } - fn do_this( - &mut self, - p1: u32, - p2: String, - p3: (Option, NonZeroU8), - p4: TupleStruct, - ) -> impl Call { - RemotingAction::<_, this_that::io::DoThis>::new(self.remoting.clone(), (p1, p2, p3, p4)) - } - fn noop(&mut self) -> impl Call { - RemotingAction::<_, this_that::io::Noop>::new(self.remoting.clone(), ()) - } - fn that(&self) -> impl Query, Args = R::Args> { - RemotingAction::<_, this_that::io::That>::new(self.remoting.clone(), ()) - } - fn this(&self) -> impl Query { - RemotingAction::<_, this_that::io::This>::new(self.remoting.clone(), ()) + + pub mod io { + use super::*; + sails_rs::io_struct_impl!(Add (v: u32) -> u32); + sails_rs::io_struct_impl!(AddByte (byte: u8) -> Vec); + sails_rs::io_struct_impl!(GuessNum (number: u8) -> Result); + sails_rs::io_struct_impl!(Incr () -> ReferenceCount); + sails_rs::io_struct_impl!(SetNum (number: u8) -> Result<(), String>); + sails_rs::io_struct_impl!(Baked () -> String); + sails_rs::io_struct_impl!(LastByte () -> Option); + sails_rs::io_struct_impl!(Message () -> Option); } } pub mod this_that { use super::*; - - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct DoThat(()); - impl DoThat { - #[allow(dead_code)] - pub fn encode_call(param: super::DoThatParam) -> Vec { - ::encode_call(¶m) - } - } - impl ActionIo for DoThat { - const ROUTE: &'static [u8] = &[ - 32, 84, 104, 105, 115, 84, 104, 97, 116, 24, 68, 111, 84, 104, 97, 116, - ]; - type Params = super::DoThatParam; - type Reply = Result<(ActorId, NonZeroU32, super::ManyVariantsReply), (String,)>; - } - pub struct DoThis(()); - impl DoThis { - #[allow(dead_code)] - pub fn encode_call( - p1: u32, - p2: String, - p3: (Option, NonZeroU8), - p4: super::TupleStruct, - ) -> Vec { - ::encode_call(&(p1, p2, p3, p4)) - } - } - impl ActionIo for DoThis { - const ROUTE: &'static [u8] = &[ - 32, 84, 104, 105, 115, 84, 104, 97, 116, 24, 68, 111, 84, 104, 105, 115, - ]; - type Params = (u32, String, (Option, NonZeroU8), super::TupleStruct); - type Reply = (String, u32); - } - pub struct Noop(()); - impl Noop { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } - } - impl ActionIo for Noop { - const ROUTE: &'static [u8] = &[ - 32, 84, 104, 105, 115, 84, 104, 97, 116, 16, 78, 111, 111, 112, - ]; - type Params = (); - type Reply = (); + pub trait ThisThat { + type Env: GearEnv; + fn do_that(&mut self, param: DoThatParam) -> PendingCall; + fn do_this( + &mut self, + p1: u32, + p2: String, + p3: (Option, NonZeroU8), + p4: TupleStruct, + ) -> PendingCall; + fn noop(&mut self) -> PendingCall; + fn that(&self) -> PendingCall; + fn this(&self) -> PendingCall; + } + pub struct ThisThatImpl; + impl ThisThat for Service { + type Env = E; + fn do_that(&mut self, param: DoThatParam) -> PendingCall { + self.pending_call((param,)) } - pub struct That(()); - impl That { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } + fn do_this( + &mut self, + p1: u32, + p2: String, + p3: (Option, NonZeroU8), + p4: TupleStruct, + ) -> PendingCall { + self.pending_call((p1, p2, p3, p4)) } - impl ActionIo for That { - const ROUTE: &'static [u8] = &[ - 32, 84, 104, 105, 115, 84, 104, 97, 116, 16, 84, 104, 97, 116, - ]; - type Params = (); - type Reply = Result; + fn noop(&mut self) -> PendingCall { + self.pending_call(()) } - pub struct This(()); - impl This { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } + fn that(&self) -> PendingCall { + self.pending_call(()) } - impl ActionIo for This { - const ROUTE: &'static [u8] = &[ - 32, 84, 104, 105, 115, 84, 104, 97, 116, 16, 84, 104, 105, 115, - ]; - type Params = (); - type Reply = u32; + fn this(&self) -> PendingCall { + self.pending_call(()) } } -} -pub struct ValueFee { - remoting: R, -} -impl ValueFee { - pub fn new(remoting: R) -> Self { - Self { remoting } - } -} -impl traits::ValueFee for ValueFee { - type Args = R::Args; - /// Return flag if fee taken and remain value, - /// using special type `CommandReply` - fn do_something_and_take_fee(&mut self) -> impl Call { - RemotingAction::<_, value_fee::io::DoSomethingAndTakeFee>::new(self.remoting.clone(), ()) + + pub mod io { + use super::*; + sails_rs::io_struct_impl!(DoThat (param: DoThatParam) -> Result<(ActorId,NonZeroU32,ManyVariantsReply,), (String,)>); + sails_rs::io_struct_impl!(DoThis (p1: u32, p2: String, p3: (Option,NonZeroU8,), p4: TupleStruct) -> (String,u32,)); + sails_rs::io_struct_impl!(Noop () -> ()); + sails_rs::io_struct_impl!(That () -> Result); + sails_rs::io_struct_impl!(This () -> u32); } } pub mod value_fee { use super::*; + pub trait ValueFee { + type Env: GearEnv; + /// Return flag if fee taken and remain value, + /// using special type `CommandReply` + fn do_something_and_take_fee( + &mut self, + ) -> PendingCall; + } + pub struct ValueFeeImpl; + impl ValueFee for Service { + type Env = E; + fn do_something_and_take_fee( + &mut self, + ) -> PendingCall { + self.pending_call(()) + } + } pub mod io { use super::*; - use sails_rs::calls::ActionIo; - pub struct DoSomethingAndTakeFee(()); - impl DoSomethingAndTakeFee { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } - } - impl ActionIo for DoSomethingAndTakeFee { - const ROUTE: &'static [u8] = &[ - 32, 86, 97, 108, 117, 101, 70, 101, 101, 84, 68, 111, 83, 111, 109, 101, 116, 104, - 105, 110, 103, 65, 110, 100, 84, 97, 107, 101, 70, 101, 101, - ]; - type Params = (); - type Reply = bool; - } + sails_rs::io_struct_impl!(DoSomethingAndTakeFee () -> bool); } - #[allow(dead_code)] #[cfg(not(target_arch = "wasm32"))] pub mod events { use super::*; - use sails_rs::events::*; #[derive(PartialEq, Debug, Encode, Decode)] #[codec(crate = sails_rs::scale_codec)] pub enum ValueFeeEvents { Withheld(u128), } - impl EventIo for ValueFeeEvents { - const ROUTE: &'static [u8] = &[32, 86, 97, 108, 117, 101, 70, 101, 101]; + impl EventDecode for ValueFeeEvents { const EVENT_NAMES: &'static [&'static [u8]] = &[&[32, 87, 105, 116, 104, 104, 101, 108, 100]]; - type Event = Self; } - pub fn listener>>(remoting: R) -> impl Listener { - RemotingListener::<_, ValueFeeEvents>::new(remoting) + impl ServiceEvent for ValueFeeImpl { + type Event = ValueFeeEvents; } } } @@ -695,95 +381,6 @@ pub enum ManyVariantsReply { #[scale_info(crate = sails_rs::scale_info)] pub struct TupleStruct(pub bool); -pub mod traits { - use super::*; - #[allow(dead_code)] - pub trait DemoClientFactory { - type Args; - fn default(&self) -> impl Activation; - #[allow(clippy::new_ret_no_self)] - #[allow(clippy::wrong_self_convention)] - fn new( - &self, - counter: Option, - dog_position: Option<(i32, i32)>, - ) -> impl Activation; - } - - #[allow(clippy::type_complexity)] - pub trait PingPong { - type Args; - fn ping( - &mut self, - input: String, - ) -> impl Call, Args = Self::Args>; - } - - #[allow(clippy::type_complexity)] - pub trait Counter { - type Args; - fn add(&mut self, value: u32) -> impl Call; - fn sub(&mut self, value: u32) -> impl Call; - fn value(&self) -> impl Query; - } - - #[allow(clippy::type_complexity)] - pub trait Dog { - type Args; - fn make_sound(&mut self) -> impl Call; - fn walk(&mut self, dx: i32, dy: i32) -> impl Call; - fn avg_weight(&self) -> impl Query; - fn position(&self) -> impl Query; - } - - #[allow(clippy::type_complexity)] - pub trait References { - type Args; - fn add(&mut self, v: u32) -> impl Call; - fn add_byte(&mut self, byte: u8) -> impl Call, Args = Self::Args>; - fn guess_num( - &mut self, - number: u8, - ) -> impl Call, Args = Self::Args>; - fn incr(&mut self) -> impl Call; - fn set_num( - &mut self, - number: u8, - ) -> impl Call, Args = Self::Args>; - fn baked(&self) -> impl Query; - fn last_byte(&self) -> impl Query, Args = Self::Args>; - fn message(&self) -> impl Query, Args = Self::Args>; - } - - #[allow(clippy::type_complexity)] - pub trait ThisThat { - type Args; - fn do_that( - &mut self, - param: DoThatParam, - ) -> impl Call< - Output = Result<(ActorId, NonZeroU32, ManyVariantsReply), (String,)>, - Args = Self::Args, - >; - fn do_this( - &mut self, - p1: u32, - p2: String, - p3: (Option, NonZeroU8), - p4: TupleStruct, - ) -> impl Call; - fn noop(&mut self) -> impl Call; - fn that(&self) -> impl Query, Args = Self::Args>; - fn this(&self) -> impl Query; - } - - #[allow(clippy::type_complexity)] - pub trait ValueFee { - type Args; - fn do_something_and_take_fee(&mut self) -> impl Call; - } -} - #[cfg(feature = "with_mocks")] #[cfg(not(target_arch = "wasm32"))] extern crate std; @@ -793,10 +390,10 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub PingPong {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl traits::PingPong for PingPong { type Args = A; fn ping (&mut self, input: String,) -> MockCall>; } } - mock! { pub Counter {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl traits::Counter for Counter { type Args = A; fn add (&mut self, value: u32,) -> MockCall;fn sub (&mut self, value: u32,) -> MockCall;fn value (& self, ) -> MockQuery; } } - mock! { pub Dog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl traits::Dog for Dog { type Args = A; fn make_sound (&mut self, ) -> MockCall;fn walk (&mut self, dx: i32,dy: i32,) -> MockCall;fn avg_weight (& self, ) -> MockQuery;fn position (& self, ) -> MockQuery; } } - mock! { pub References {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl traits::References for References { type Args = A; fn add (&mut self, v: u32,) -> MockCall;fn add_byte (&mut self, byte: u8,) -> MockCall>;fn guess_num (&mut self, number: u8,) -> MockCall>;fn incr (&mut self, ) -> MockCall;fn set_num (&mut self, number: u8,) -> MockCall>;fn baked (& self, ) -> MockQuery;fn last_byte (& self, ) -> MockQuery>;fn message (& self, ) -> MockQuery>; } } - mock! { pub ThisThat {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl traits::ThisThat for ThisThat { type Args = A; fn do_that (&mut self, param: DoThatParam,) -> MockCall>;fn do_this (&mut self, p1: u32,p2: String,p3: (Option,NonZeroU8,),p4: TupleStruct,) -> MockCall;fn noop (&mut self, ) -> MockCall;fn that (& self, ) -> MockQuery>;fn this (& self, ) -> MockQuery; } } - mock! { pub ValueFee {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl traits::ValueFee for ValueFee { type Args = A; fn do_something_and_take_fee (&mut self, ) -> MockCall; } } + mock! { pub PingPong {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl ping_pong::PingPong for PingPong { type Env = MockEnv; fn ping (&mut self, input: String) -> PendingCall; } } + mock! { pub Counter {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl counter::Counter for Counter { type Env = MockEnv; fn add (&mut self, value: u32) -> PendingCall;fn sub (&mut self, value: u32) -> PendingCall;fn value (& self, ) -> PendingCall; } } + mock! { pub Dog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl dog::Dog for Dog { type Env = MockEnv; fn make_sound (&mut self, ) -> PendingCall;fn walk (&mut self, dx: i32, dy: i32) -> PendingCall;fn avg_weight (& self, ) -> PendingCall;fn position (& self, ) -> PendingCall; } } + mock! { pub References {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl references::References for References { type Env = MockEnv; fn add (&mut self, v: u32) -> PendingCall;fn add_byte (&mut self, byte: u8) -> PendingCall;fn guess_num (&mut self, number: u8) -> PendingCall;fn incr (&mut self, ) -> PendingCall;fn set_num (&mut self, number: u8) -> PendingCall;fn baked (& self, ) -> PendingCall;fn last_byte (& self, ) -> PendingCall;fn message (& self, ) -> PendingCall; } } + mock! { pub ThisThat {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl this_that::ThisThat for ThisThat { type Env = MockEnv; fn do_that (&mut self, param: DoThatParam) -> PendingCall;fn do_this (&mut self, p1: u32, p2: String, p3: (Option,NonZeroU8,), p4: TupleStruct) -> PendingCall;fn noop (&mut self, ) -> PendingCall;fn that (& self, ) -> PendingCall;fn this (& self, ) -> PendingCall; } } + mock! { pub ValueFee {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl value_fee::ValueFee for ValueFee { type Env = MockEnv; fn do_something_and_take_fee (&mut self, ) -> PendingCall; } } } diff --git a/examples/demo/client/src/env_client.rs b/examples/demo/client/src/env_client.rs index 4b4d6dd3c..69ddd9c8e 100644 --- a/examples/demo/client/src/env_client.rs +++ b/examples/demo/client/src/env_client.rs @@ -88,4 +88,45 @@ pub mod counter { sails_rs::io_struct_impl!(Sub (value: u32) -> u32); sails_rs::io_struct_impl!(Value () -> u32); } + + #[cfg(not(target_arch = "wasm32"))] + pub mod events { + use super::*; + #[derive(PartialEq, Debug, Encode, Decode)] + #[codec(crate = sails_rs::scale_codec)] + pub enum CounterEvents { + /// Emitted when a new value is added to the counter + Added(u32), + /// Emitted when a value is subtracted from the counter + Subtracted(u32), + } + impl EventDecode for CounterEvents { + const EVENT_NAMES: &'static [&'static [u8]] = &[ + &[20, 65, 100, 100, 101, 100], + &[40, 83, 117, 98, 116, 114, 97, 99, 116, 101, 100], + ]; + } + + impl ServiceEvent for CounterImpl { + type Event = CounterEvents; + } + } +} + +#[cfg(feature = "with_mocks")] +#[cfg(not(target_arch = "wasm32"))] +pub mod mockall { + use super::*; + use sails_rs::mockall::*; + mock! { + pub Counter {} + #[allow(refining_impl_trait)] + #[allow(clippy::type_complexity)] + impl counter::Counter for Counter { + type Env = MockEnv; + fn add (&mut self, value: u32) -> PendingCall; + fn sub (&mut self, value: u32) -> PendingCall; + fn value (& self, ) -> PendingCall; + } + } } diff --git a/examples/demo/client/src/lib.rs b/examples/demo/client/src/lib.rs index 340ada065..ef3ec097f 100644 --- a/examples/demo/client/src/lib.rs +++ b/examples/demo/client/src/lib.rs @@ -8,15 +8,17 @@ pub mod env_client; #[cfg(test)] mod tests { use super::*; - use sails_rs::{calls::*, prelude::*}; #[test] fn test_io_module_encode() { - let bytes = this_that::io::DoThat::encode_call(DoThatParam { - p1: NonZeroU32::MAX, - p2: 123.into(), - p3: ManyVariants::One, - }); + let bytes = this_that::io::DoThat::encode_params_with_prefix( + "ThisThat", + DoThatParam { + p1: NonZeroU32::MAX, + p2: 123.into(), + p3: ManyVariants::One, + }, + ); assert_eq!( bytes, @@ -44,7 +46,7 @@ mod tests { ]; let reply: Result<(ActorId, NonZeroU32, ManyVariantsReply), (String,)> = - this_that::io::DoThat::decode_reply(bytes).unwrap(); + this_that::io::DoThat::decode_reply_with_prefix("ThisThat", bytes).unwrap(); assert_eq!( reply, diff --git a/examples/proxy/Cargo.toml b/examples/proxy/Cargo.toml index 155e84041..7bb526e16 100644 --- a/examples/proxy/Cargo.toml +++ b/examples/proxy/Cargo.toml @@ -20,5 +20,8 @@ sails-rename.workspace = true demo-client = { path = "../demo/client", features = ["with_mocks"] } tokio = { workspace = true, features = ["rt", "macros"] } +[build-dependencies] +sails-rename = { workspace = true, features = ["build"] } + [features] idl-gen = ["dep:sails-idl-gen"] diff --git a/examples/proxy/build.rs b/examples/proxy/build.rs new file mode 100644 index 000000000..26231559d --- /dev/null +++ b/examples/proxy/build.rs @@ -0,0 +1,3 @@ +fn main() { + sails_rename::build_wasm(); +} diff --git a/examples/proxy/src/lib.rs b/examples/proxy/src/lib.rs index 2ebd33d69..589b52be6 100644 --- a/examples/proxy/src/lib.rs +++ b/examples/proxy/src/lib.rs @@ -1,21 +1,30 @@ #![no_std] -use demo_client::ThisThat; -use sails_rename::gstd::{calls::GStdRemoting, program}; +use demo_client::{this_that::ThisThatImpl, *}; +use sails_rename::{ActorId, client::*}; mod this_that; -#[derive(Default)] -pub struct ProxyProgram(()); +pub struct ProxyProgram { + this_that_addr: ActorId, +} -#[program(crate = sails_rename)] +#[sails_rename::program(crate = sails_rename)] impl ProxyProgram { - pub fn new() -> Self { - Self::default() + pub fn new(this_that_addr: ActorId) -> Self { + Self { this_that_addr } } - pub fn this_that_caller(&self) -> this_that::ThisThatCaller> { - let this_that_client = ThisThat::new(GStdRemoting); + pub fn this_that_caller(&self) -> this_that::ThisThatCaller> { + let this_that_client = DemoClientProgram::client(DefaultEnv::default(), self.this_that_addr).this_that(); this_that::ThisThatCaller::new(this_that_client) } } + +#[cfg(not(target_arch = "wasm32"))] +pub use code::WASM_BINARY_OPT as WASM_BINARY; + +#[cfg(not(target_arch = "wasm32"))] +mod code { + include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +} diff --git a/examples/proxy/src/this_that/mod.rs b/examples/proxy/src/this_that/mod.rs index 7aef35cd3..e1264c53f 100644 --- a/examples/proxy/src/this_that/mod.rs +++ b/examples/proxy/src/this_that/mod.rs @@ -1,12 +1,12 @@ -use demo_client::{TupleStruct, traits::ThisThat}; -use sails_rename::{calls::*, gstd::Syscall, prelude::*}; +use demo_client::{this_that::ThisThat, *}; +use sails_rename::{client::*, gstd::Syscall, prelude::*}; #[derive(Clone)] pub struct ThisThatCaller { this_that: ThisThatClient, } impl ThisThatCaller where - ThisThatClient: ThisThat, + ThisThatClient: ThisThat, { pub const fn new(this_that: ThisThatClient) -> Self { Self { this_that } @@ -16,7 +16,7 @@ where #[service(crate = sails_rename)] impl ThisThatCaller where - ThisThatClient: ThisThat, + ThisThatClient: ThisThat, { #[export] pub async fn call_do_this( @@ -31,11 +31,7 @@ where panic!("ThisThatCaller cannot call itself"); } - self.this_that - .do_this(p1, p2, p3, p4) - .send_recv(this_that_addr) - .await - .unwrap() + self.this_that.do_this(p1, p2, p3, p4).await.unwrap() } #[export] @@ -44,7 +40,7 @@ where panic!("ThisThatCaller cannot call itself"); } - self.this_that.this().recv(this_that_addr).await.unwrap() + self.this_that.this().await.unwrap() } } @@ -52,7 +48,7 @@ where mod tests { use super::*; use demo_client::mockall::MockThisThat; - use sails_rename::{gstd::services::Service, mockall::*}; + use sails_rename::{client::PendingCall, gstd::services::Service, mockall::*}; #[tokio::test] async fn this_that_caller_query_this() { @@ -60,16 +56,10 @@ mod tests { const ACTOR_ID: u64 = 11; Syscall::with_program_id(ActorId::from(1)); - let mut mock_this_that = MockThisThat::<()>::new(); - mock_this_that.expect_this().returning(|| { - let mut mock_query_this = MockQuery::new(); - mock_query_this - .expect_recv() - .with(predicate::eq(ActorId::from(ACTOR_ID))) - .times(1) - .returning(move |_| Ok(42)); - mock_query_this - }); + let mut mock_this_that = MockThisThat::new(); + mock_this_that + .expect_this() + .returning(|| PendingCall::from_output(42)); // act let this_that_caller = ThisThatCaller::new(mock_this_that).expose(&[]); @@ -85,18 +75,10 @@ mod tests { const ACTOR_ID: u64 = 11; Syscall::with_program_id(ActorId::from(1)); - let mut mock_this_that = MockThisThat::<()>::new(); + let mut mock_this_that = MockThisThat::new(); mock_this_that .expect_do_this() - .returning(move |p1, p2, _p3, _p4| { - let mut mock_call_do_this = MockCall::new(); - mock_call_do_this - .expect_send_recv() - .with(predicate::eq(ActorId::from(ACTOR_ID))) - .times(1) - .returning(move |_| Ok((p2.clone(), p1))); - mock_call_do_this - }); + .returning(move |p1, p2, _p3, _p4| PendingCall::from_output((p2.clone(), p1))); // act let mut this_that_caller = ThisThatCaller::new(mock_this_that).expose(&[]); @@ -121,7 +103,7 @@ mod tests { const ACTOR_ID: u64 = 11; Syscall::with_program_id(ActorId::from(ACTOR_ID)); - let mock_this_that = MockThisThat::<()>::new(); + let mock_this_that = MockThisThat::new(); // act let mut this_that_caller = ThisThatCaller::new(mock_this_that).expose(&[]); diff --git a/examples/redirect/client/src/redirect_client.rs b/examples/redirect/client/src/redirect_client.rs index 8ae27277c..f3d75e9fb 100644 --- a/examples/redirect/client/src/redirect_client.rs +++ b/examples/redirect/client/src/redirect_client.rs @@ -9,7 +9,7 @@ pub trait RedirectClient { impl RedirectClient for Actor { type Env = E; fn redirect(&self) -> Service { - self.service(stringify!(redirect::Redirect)) + self.service(stringify!(Redirect)) } } pub trait RedirectClientCtors { diff --git a/examples/redirect/proxy-client/src/redirect_proxy_client.rs b/examples/redirect/proxy-client/src/redirect_proxy_client.rs index 40f5ed71e..b56025258 100644 --- a/examples/redirect/proxy-client/src/redirect_proxy_client.rs +++ b/examples/redirect/proxy-client/src/redirect_proxy_client.rs @@ -9,7 +9,7 @@ pub trait RedirectProxyClient { impl RedirectProxyClient for Actor { type Env = E; fn proxy(&self) -> Service { - self.service(stringify!(proxy::Proxy)) + self.service(stringify!(Proxy)) } } pub trait RedirectProxyClientCtors { diff --git a/examples/redirect/tests/gtest.rs b/examples/redirect/tests/gtest.rs index fedcd4049..34adef154 100644 --- a/examples/redirect/tests/gtest.rs +++ b/examples/redirect/tests/gtest.rs @@ -1,84 +1,56 @@ -use redirect_client::traits::{Redirect as _, RedirectClientFactory as _}; -use redirect_proxy_client::traits::{Proxy as _, RedirectProxyClientFactory as _}; -use sails_rs::{CodeId, GasUnit, calls::*}; +use redirect_client::{redirect::Redirect as _, *}; +use redirect_proxy_client::{proxy::Proxy as _, *}; +use sails_rs::{CodeId, GasUnit, client::*}; const ACTOR_ID: u64 = 42; #[tokio::test] async fn redirect_on_exit_works() { - let (remoting, program_code_id, proxy_code_id, _gas_limit) = create_remoting(); + let (env, program_code_id, proxy_code_id, _gas_limit) = create_remoting(); - let program_factory = redirect_client::RedirectClientFactory::new(remoting.clone()); - let proxy_factory = redirect_proxy_client::RedirectProxyClientFactory::new(remoting.clone()); + let program_factory_1 = RedirectClientProgram::deploy(env.clone(), program_code_id, vec![1]); + let program_factory_2 = RedirectClientProgram::deploy(env.clone(), program_code_id, vec![2]); + let program_factory_3 = RedirectClientProgram::deploy(env.clone(), program_code_id, vec![3]); + let proxy_factory = RedirectProxyClientProgram::deploy(env.clone(), proxy_code_id, vec![]); - let program_id_1 = program_factory + let program_1 = program_factory_1 .new() // Call program's constructor - .send_recv(program_code_id, b"program_1") .await .unwrap(); - let program_id_2 = program_factory + let program_2 = program_factory_2 .new() // Call program's constructor - .send_recv(program_code_id, b"program_2") .await .unwrap(); - let program_id_3 = program_factory + let program_3 = program_factory_3 .new() // Call program's constructor - .send_recv(program_code_id, b"program_3") .await .unwrap(); - let proxy_program_id = proxy_factory - .new(program_id_1) // Call program's constructor - .send_recv(proxy_code_id, b"proxy") + let proxy_program = proxy_factory + .new(program_1.id()) // Call program's constructor .await .unwrap(); - let mut redirect_client = redirect_client::Redirect::new(remoting.clone()); - let proxy_client = redirect_proxy_client::Proxy::new(remoting.clone()); + let mut redirect_client = program_1.redirect(); + let proxy_client = proxy_program.proxy(); - let result = proxy_client - .get_program_id() - .recv(proxy_program_id) - .await - .unwrap(); + let result = proxy_client.get_program_id().await.unwrap(); + assert_eq!(result, program_1.id()); - assert_eq!(result, program_id_1); + let _ = redirect_client.exit(program_2.id()).await.unwrap(); - let _ = redirect_client - .exit(program_id_2) - .send(program_id_1) - .await - .unwrap(); - - let result = proxy_client - .get_program_id() - .recv(proxy_program_id) - .await - .unwrap(); + let result = proxy_client.get_program_id().await.unwrap(); + assert_eq!(result, program_2.id()); - assert_eq!(result, program_id_2); - - let _ = redirect_client - .exit(program_id_3) - .send(program_id_2) - .await - .unwrap(); - - let result = proxy_client - .get_program_id() - .recv(proxy_program_id) - .await - .unwrap(); + let _ = redirect_client.exit(program_3.id()).await.unwrap(); - assert_eq!(result, program_id_3); + let result = proxy_client.get_program_id().await.unwrap(); + assert_eq!(result, program_3.id()); } -fn create_remoting() -> (impl Remoting + Clone, CodeId, CodeId, GasUnit) { - use sails_rs::gtest::{ - MAX_USER_GAS_LIMIT, System, - calls::{BlockRunMode, GTestRemoting}, - }; +fn create_remoting() -> (GtestEnv, CodeId, CodeId, GasUnit) { + use sails_rs::gtest::{MAX_USER_GAS_LIMIT, System}; let system = System::new(); system.init_logger_with_default_filter("gwasm=debug,gtest=info,sails_rs=debug,redirect=debug"); @@ -90,7 +62,6 @@ fn create_remoting() -> (impl Remoting + Clone, CodeId, CodeId, GasUnit) { // Create a remoting instance for the system // and set the block run mode to Next, // cause we don't receive any reply on `Exit` call - let remoting = - GTestRemoting::new(system, ACTOR_ID.into()).with_block_run_mode(BlockRunMode::Next); - (remoting, program_code_id, proxy_code_id, MAX_USER_GAS_LIMIT) + let env = GtestEnv::new(system, ACTOR_ID.into()).with_block_run_mode(BlockRunMode::Next); + (env, program_code_id, proxy_code_id, MAX_USER_GAS_LIMIT) } diff --git a/rs/client-gen/src/events_generator.rs b/rs/client-gen/src/events_generator.rs index 16d37c1b4..6dbd28f05 100644 --- a/rs/client-gen/src/events_generator.rs +++ b/rs/client-gen/src/events_generator.rs @@ -1,19 +1,17 @@ -use crate::helpers::{method_bytes, path_bytes}; +use crate::helpers::method_bytes; use genco::prelude::*; use sails_idl_parser::{ast::visitor, ast::visitor::Visitor, ast::*}; pub(crate) struct EventsModuleGenerator<'a> { service_name: &'a str, - path: &'a str, sails_path: &'a str, tokens: rust::Tokens, } impl<'a> EventsModuleGenerator<'a> { - pub(crate) fn new(service_name: &'a str, path: &'a str, sails_path: &'a str) -> Self { + pub(crate) fn new(service_name: &'a str, sails_path: &'a str) -> Self { Self { service_name, - path, sails_path, tokens: rust::Tokens::new(), } @@ -26,8 +24,7 @@ impl<'a> EventsModuleGenerator<'a> { impl<'ast> Visitor<'ast> for EventsModuleGenerator<'_> { fn visit_service(&mut self, service: &'ast Service) { - let events_name = format!("{}Events", self.service_name); - let (service_path_bytes, _) = path_bytes(self.path); + let events_name = &format!("{}Events", self.service_name); let event_names_bytes = service .events() .iter() @@ -37,14 +34,12 @@ impl<'ast> Visitor<'ast> for EventsModuleGenerator<'_> { quote_in! { self.tokens => $['\n'] - #[allow(dead_code)] #[cfg(not(target_arch = "wasm32"))] pub mod events $("{") use super::*; - use $(self.sails_path)::events::*; #[derive(PartialEq, Debug, Encode, Decode)] #[codec(crate = $(self.sails_path)::scale_codec)] - pub enum $(&events_name) $("{") + pub enum $events_name $("{") }; visitor::accept_service(service, self); @@ -53,17 +48,15 @@ impl<'ast> Visitor<'ast> for EventsModuleGenerator<'_> { $['\r'] $("}") }; - // quote_in! { self.tokens => - // impl EventIo for $(&events_name) { - // const ROUTE: &'static [u8] = &[$service_path_bytes]; - // const EVENT_NAMES: &'static [&'static [u8]] = &[&[$event_names_bytes]]; - // type Event = Self; - // } + quote_in! { self.tokens => + impl EventDecode for $events_name { + const EVENT_NAMES: &'static [&'static [u8]] = &[&[$event_names_bytes]]; + } - // pub fn listener>>(remoting: R) -> impl Listener<$(&events_name)> { - // RemotingListener::<_, $(&events_name)>::new(remoting) - // } - // } + impl ServiceEvent for $(self.service_name)Impl { + type Event = $events_name; + } + } quote_in! { self.tokens => $['\r'] $("}") diff --git a/rs/client-gen/src/mock_generator.rs b/rs/client-gen/src/mock_generator.rs index bd4d62d46..ec9b8e50b 100644 --- a/rs/client-gen/src/mock_generator.rs +++ b/rs/client-gen/src/mock_generator.rs @@ -1,4 +1,4 @@ -use crate::type_generators::generate_type_decl_code; +use crate::helpers::fn_args_with_types; use convert_case::{Case, Casing}; use genco::prelude::*; use rust::Tokens; @@ -13,19 +13,20 @@ impl<'a> MockGenerator<'a> { pub(crate) fn new(service_name: &'a str) -> Self { Self { service_name, - tokens: rust::Tokens::new(), + tokens: Tokens::new(), } } - pub(crate) fn finalize(self) -> rust::Tokens { + pub(crate) fn finalize(self) -> Tokens { + let service_name_snake = &self.service_name.to_case(Case::Snake); quote! { mock! { - pub $(self.service_name) {} + pub $(self.service_name) {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] - impl traits::$(self.service_name) for $(self.service_name) { - type Args = A; + impl $service_name_snake::$(self.service_name) for $(self.service_name) { + type Env = MockEnv; $(self.tokens) } } @@ -39,26 +40,14 @@ impl<'ast> Visitor<'ast> for MockGenerator<'_> { } fn visit_service_func(&mut self, func: &'ast ServiceFunc) { + let service_name_snake = &self.service_name.to_case(Case::Snake); let mutability = if func.is_query() { "" } else { "mut" }; - let fn_name = func.name().to_case(Case::Snake); + let fn_name = func.name(); + let fn_name_snake = func.name().to_case(Case::Snake); + let params_with_types = &fn_args_with_types(func.params()); - let mut params_tokens = Tokens::new(); - for param in func.params() { - let type_decl_code = generate_type_decl_code(param.type_decl()); - quote_in! {params_tokens => - $(param.name()): $(type_decl_code), - }; - } - - let output_type_decl_code = generate_type_decl_code(func.output()); - let output_mock = if func.is_query() { - "MockQuery" - } else { - "MockCall" - }; - - quote_in! { self.tokens=> - fn $fn_name (&$mutability self, $params_tokens) -> $output_mock; + quote_in! { self.tokens => + fn $fn_name_snake (&$mutability self, $params_with_types) -> PendingCall; }; } } diff --git a/rs/client-gen/src/root_generator.rs b/rs/client-gen/src/root_generator.rs index 86c332752..cb981b33b 100644 --- a/rs/client-gen/src/root_generator.rs +++ b/rs/client-gen/src/root_generator.rs @@ -1,4 +1,4 @@ -use crate::{ctor_generators::*, events_generator::*, service_generators::*, type_generators::*}; +use crate::{ctor_generators::*, mock_generator::*, service_generators::*, type_generators::*}; use convert_case::{Case, Casing}; use genco::prelude::*; use rust::Tokens; @@ -118,7 +118,6 @@ impl<'ast> Visitor<'ast> for RootGenerator<'_> { } else { service.name() }; - // let service_name_snake = service_name.to_case(Case::Snake); let mut ctor_gen = ServiceCtorGenerator::new(service_name); ctor_gen.visit_service(service); @@ -126,32 +125,13 @@ impl<'ast> Visitor<'ast> for RootGenerator<'_> { self.service_trait_tokens.extend(trait_tokens); self.service_impl_tokens.extend(impl_tokens); - let path = service.name(); - let mut client_gen = ServiceGenerator::new(service_name, self.sails_path); client_gen.visit_service(service); self.tokens.extend(client_gen.finalize()); - let mut service_tokens = Tokens::new(); - - if !service.events().is_empty() { - let mut events_mod_gen = - EventsModuleGenerator::new(service_name, path, self.sails_path); - events_mod_gen.visit_service(service); - service_tokens.extend(events_mod_gen.finalize()); - } - - // quote_in! { self.tokens => - // $['\n'] - // pub mod $(service_name_snake) { - // use super::*; - // $(service_tokens) - // } - // } - - // let mut mock_gen: MockGenerator = MockGenerator::new(service_name); - // mock_gen.visit_service(service); - // self.mocks_tokens.extend(mock_gen.finalize()); + let mut mock_gen = MockGenerator::new(service_name); + mock_gen.visit_service(service); + self.mocks_tokens.extend(mock_gen.finalize()); } fn visit_type(&mut self, t: &'ast Type) { diff --git a/rs/client-gen/src/service_generators.rs b/rs/client-gen/src/service_generators.rs index d110e6a3e..286450f90 100644 --- a/rs/client-gen/src/service_generators.rs +++ b/rs/client-gen/src/service_generators.rs @@ -36,7 +36,7 @@ impl<'ast> Visitor<'ast> for ServiceCtorGenerator<'_> { ); quote_in!(self.impl_tokens => fn $service_name_snake(&self) -> Service { - self.service(stringify!($service_name_snake::$(self.service_name))) + self.service(stringify!($(self.service_name))) } ); } @@ -101,8 +101,7 @@ impl<'ast> Visitor<'ast> for ServiceGenerator<'_> { visitor::accept_service(service, self); if !service.events().is_empty() { - let mut events_mod_gen = - EventsModuleGenerator::new(self.service_name, self.service_name, self.sails_path); + let mut events_mod_gen = EventsModuleGenerator::new(self.service_name, self.sails_path); events_mod_gen.visit_service(service); self.events_tokens = events_mod_gen.finalize(); } diff --git a/rs/client-gen/tests/snapshots/generator__basic_works.snap b/rs/client-gen/tests/snapshots/generator__basic_works.snap index 9a3b5e2d3..927dc3b3d 100644 --- a/rs/client-gen/tests/snapshots/generator__basic_works.snap +++ b/rs/client-gen/tests/snapshots/generator__basic_works.snap @@ -13,7 +13,7 @@ pub trait Basic { impl Basic for Actor { type Env = E; fn basic(&self) -> Service { - self.service(stringify!(basic::Basic)) + self.service(stringify!(Basic)) } } @@ -69,4 +69,5 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; + mock! { pub Basic {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl basic::Basic for Basic { type Env = MockEnv; fn do_this (&mut self, p1: u32, p2: MyParam) -> PendingCall;fn do_that (&mut self, p1: (u8,u32,)) -> PendingCall; } } } diff --git a/rs/client-gen/tests/snapshots/generator__events_works.snap b/rs/client-gen/tests/snapshots/generator__events_works.snap index 4c6ddc739..761a882b6 100644 --- a/rs/client-gen/tests/snapshots/generator__events_works.snap +++ b/rs/client-gen/tests/snapshots/generator__events_works.snap @@ -16,7 +16,7 @@ impl ServiceWithEvents for Actor { fn service_with_events( &self, ) -> Service { - self.service(stringify!(service_with_events::ServiceWithEvents)) + self.service(stringify!(ServiceWithEvents)) } } @@ -39,11 +39,9 @@ pub mod service_with_events { sails_rs::io_struct_impl!(DoThis (p1: NonZeroU256, p2: MyParam) -> NonZeroU64); } - #[allow(dead_code)] #[cfg(not(target_arch = "wasm32"))] pub mod events { use super::*; - use sails_rs::events::*; #[derive(PartialEq, Debug, Encode, Decode)] #[codec(crate = sails_rs::scale_codec)] pub enum ServiceWithEventsEvents { @@ -52,6 +50,17 @@ pub mod service_with_events { Three(MyParam), Reset, } + impl EventDecode for ServiceWithEventsEvents { + const EVENT_NAMES: &'static [&'static [u8]] = &[ + &[12, 79, 110, 101], + &[12, 84, 119, 111], + &[20, 84, 104, 114, 101, 101], + &[20, 82, 101, 115, 101, 116], + ]; + } + impl ServiceEvent for ServiceWithEventsImpl { + type Event = ServiceWithEventsEvents; + } } } #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo)] @@ -72,4 +81,5 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; + mock! { pub ServiceWithEvents {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl service_with_events::ServiceWithEvents for ServiceWithEvents { type Env = MockEnv; fn do_this (&mut self, p1: NonZeroU256, p2: MyParam) -> PendingCall; } } } diff --git a/rs/client-gen/tests/snapshots/generator__external_types.snap b/rs/client-gen/tests/snapshots/generator__external_types.snap index ee23cc3b0..bc93daeef 100644 --- a/rs/client-gen/tests/snapshots/generator__external_types.snap +++ b/rs/client-gen/tests/snapshots/generator__external_types.snap @@ -15,7 +15,7 @@ pub trait Service { impl Service for Actor { type Env = E; fn service(&self) -> Service { - self.service(stringify!(service::Service)) + self.service(stringify!(Service)) } } diff --git a/rs/client-gen/tests/snapshots/generator__full.snap b/rs/client-gen/tests/snapshots/generator__full.snap index 89ecd85f8..fcb888733 100644 --- a/rs/client-gen/tests/snapshots/generator__full.snap +++ b/rs/client-gen/tests/snapshots/generator__full.snap @@ -13,7 +13,7 @@ pub trait Service { impl Service for Actor { type Env = E; fn service(&self) -> Service { - self.service(stringify!(service::Service)) + self.service(stringify!(Service)) } } pub trait ServiceCtors { @@ -92,11 +92,9 @@ pub mod service { sails_rs::io_struct_impl!(That (v1: ()) -> Result); } - #[allow(dead_code)] #[cfg(not(target_arch = "wasm32"))] pub mod events { use super::*; - use sails_rs::events::*; #[derive(PartialEq, Debug, Encode, Decode)] #[codec(crate = sails_rs::scale_codec)] pub enum ServiceEvents { @@ -108,6 +106,15 @@ pub mod service { p1: String, }, } + impl EventDecode for ServiceEvents { + const EVENT_NAMES: &'static [&'static [u8]] = &[ + &[32, 84, 104, 105, 115, 68, 111, 110, 101], + &[32, 84, 104, 97, 116, 68, 111, 110, 101], + ]; + } + impl ServiceEvent for ServiceImpl { + type Event = ServiceEvents; + } } } /// ThisThatSvcAppTupleStruct docs @@ -163,4 +170,5 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; + mock! { pub Service {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl service::Service for Service { type Env = MockEnv; fn do_this (&mut self, p1: u32, p2: String, p3: (Option,u8,), p4: ThisThatSvcAppTupleStruct) -> PendingCall;fn do_that (&mut self, param: ThisThatSvcAppDoThatParam) -> PendingCall;fn this (& self, v1: Vec) -> PendingCall;fn that (& self, v1: ()) -> PendingCall; } } } diff --git a/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap b/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap index 4ee13e557..dd8d887bd 100644 --- a/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap +++ b/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap @@ -14,10 +14,10 @@ pub trait Service { impl Service for Actor { type Env = E; fn service(&self) -> Service { - self.service(stringify!(service::Service)) + self.service(stringify!(Service)) } fn counter(&self) -> Service { - self.service(stringify!(counter::Counter)) + self.service(stringify!(Counter)) } } pub trait ServiceCtors { @@ -138,11 +138,9 @@ pub mod counter { my_crate::sails::io_struct_impl!(Value () -> u32); } - #[allow(dead_code)] #[cfg(not(target_arch = "wasm32"))] pub mod events { use super::*; - use my_crate::sails::events::*; #[derive(PartialEq, Debug, Encode, Decode)] #[codec(crate = my_crate::sails::scale_codec)] pub enum CounterEvents { @@ -151,6 +149,15 @@ pub mod counter { /// Emitted when a value is subtracted from the counter Subtracted(u32), } + impl EventDecode for CounterEvents { + const EVENT_NAMES: &'static [&'static [u8]] = &[ + &[20, 65, 100, 100, 101, 100], + &[40, 83, 117, 98, 116, 114, 97, 99, 116, 101, 100], + ]; + } + impl ServiceEvent for CounterImpl { + type Event = CounterEvents; + } } } #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo)] diff --git a/rs/client-gen/tests/snapshots/generator__multiple_services.snap b/rs/client-gen/tests/snapshots/generator__multiple_services.snap index 59cbc2395..be671c6aa 100644 --- a/rs/client-gen/tests/snapshots/generator__multiple_services.snap +++ b/rs/client-gen/tests/snapshots/generator__multiple_services.snap @@ -14,10 +14,10 @@ pub trait Multiple { impl Multiple for Actor { type Env = E; fn multiple(&self) -> Service { - self.service(stringify!(multiple::Multiple)) + self.service(stringify!(Multiple)) } fn named(&self) -> Service { - self.service(stringify!(named::Named)) + self.service(stringify!(Named)) } } @@ -75,4 +75,6 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; + mock! { pub Multiple {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl multiple::Multiple for Multiple { type Env = MockEnv; fn do_this (&mut self, p1: u32, p2: MyParam) -> PendingCall;fn do_that (&mut self, p1: (u8,u32,)) -> PendingCall; } } + mock! { pub Named {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl named::Named for Named { type Env = MockEnv; fn that (& self, p1: u32) -> PendingCall; } } } diff --git a/rs/client-gen/tests/snapshots/generator__nonzero_works.snap b/rs/client-gen/tests/snapshots/generator__nonzero_works.snap index 7b193e754..23874990a 100644 --- a/rs/client-gen/tests/snapshots/generator__nonzero_works.snap +++ b/rs/client-gen/tests/snapshots/generator__nonzero_works.snap @@ -13,7 +13,7 @@ pub trait NonZeroParams { impl NonZeroParams for Actor { type Env = E; fn non_zero_params(&self) -> Service { - self.service(stringify!(non_zero_params::NonZeroParams)) + self.service(stringify!(NonZeroParams)) } } @@ -54,4 +54,5 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; + mock! { pub NonZeroParams {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl non_zero_params::NonZeroParams for NonZeroParams { type Env = MockEnv; fn do_this (&mut self, p1: NonZeroU256, p2: MyParam) -> PendingCall; } } } diff --git a/rs/client-gen/tests/snapshots/generator__rmrk_works.snap b/rs/client-gen/tests/snapshots/generator__rmrk_works.snap index 5d3eb6f68..da94bcb7c 100644 --- a/rs/client-gen/tests/snapshots/generator__rmrk_works.snap +++ b/rs/client-gen/tests/snapshots/generator__rmrk_works.snap @@ -13,7 +13,7 @@ pub trait RmrkCatalog { impl RmrkCatalog for Actor { type Env = E; fn rmrk_catalog(&self) -> Service { - self.service(stringify!(rmrk_catalog::RmrkCatalog)) + self.service(stringify!(RmrkCatalog)) } } pub trait RmrkCatalogCtors { @@ -181,4 +181,5 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; + mock! { pub RmrkCatalog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl rmrk_catalog::RmrkCatalog for RmrkCatalog { type Env = MockEnv; fn add_equippables (&mut self, part_id: u32, collection_ids: Vec) -> PendingCall;fn add_parts (&mut self, parts: BTreeMap) -> PendingCall;fn remove_equippable (&mut self, part_id: u32, collection_id: ActorId) -> PendingCall;fn remove_parts (&mut self, part_ids: Vec) -> PendingCall;fn reset_equippables (&mut self, part_id: u32) -> PendingCall;fn set_equippables_to_all (&mut self, part_id: u32) -> PendingCall;fn equippable (& self, part_id: u32, collection_id: ActorId) -> PendingCall;fn part (& self, part_id: u32) -> PendingCall; } } } diff --git a/rs/src/client/gtest_env.rs b/rs/src/client/gtest_env.rs index dc55e077e..d25b427b9 100644 --- a/rs/src/client/gtest_env.rs +++ b/rs/src/client/gtest_env.rs @@ -122,7 +122,10 @@ impl GtestEnv { None => Err(Error::InvalidReturnType), // TODO handle error reply Some(ReplyCode::Error(reason)) => { - panic!("Unexpected error reply: {reason:?}") + panic!( + "Unexpected error reply: {reason:?}, {:?}", + hex::encode(entry.payload()) + ) } Some(ReplyCode::Success(_)) => Ok(entry.payload().to_vec()), _ => Err(Error::InvalidReturnType), @@ -230,6 +233,31 @@ impl GearEnv for GtestEnv { type Error = Error; type MessageState = ReplyReceiver; } +impl PendingCall { + pub fn send_message(mut self) -> Result { + let Some(route) = self.route else { + return Err(Error::ScaleCodecError( + "PendingCall route is not set".into(), + )); + }; + if self.state.is_some() { + // TODO + return Err(Error::Instrumentation); + } + // Send message + let args = self + .args + .take() + .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); + let payload = T::encode_params_with_prefix(route, &args); + let params = self.params.take().unwrap_or_default(); + + let message_id = self.env.send_message(self.destination, payload, params)?; + log::debug!("PendingCall: send message {message_id:?}"); + self.state = Some(self.env.message_reply_from_next_blocks(message_id)); + Ok(self) + } +} impl Future for PendingCall { type Output = Result::Error>; @@ -283,6 +311,34 @@ impl Future for PendingCall { } } +impl PendingCtor { + pub fn create_program(mut self) -> Result { + if self.state.is_some() { + // TODO + return Err(Error::Instrumentation); + } + // Send message + let payload = self.encode_ctor(); + let params = self.params.take().unwrap_or_default(); + let salt = self.salt.take().unwrap_or_default(); + let send_res = self + .env + .create_program(self.code_id, salt, payload.as_slice(), params); + match send_res { + Ok((program_id, message_id)) => { + log::debug!("PendingCtor: send message {message_id:?}"); + self.state = Some(self.env.message_reply_from_next_blocks(message_id)); + self.program_id = Some(program_id); + Ok(self) + } + Err(err) => { + log::error!("PendingCtor: failed to send message: {err}"); + Err(err) + } + } + } +} + impl Future for PendingCtor { type Output = Result, ::Error>; diff --git a/rs/src/client/mod.rs b/rs/src/client/mod.rs index 1aa224e74..aaf76fb7c 100644 --- a/rs/src/client/mod.rs +++ b/rs/src/client/mod.rs @@ -55,9 +55,13 @@ pub trait Program: Sized { #[cfg(not(target_arch = "wasm32"))] pub type DefaultEnv = MockEnv; +#[cfg(target_arch = "wasm32")] +#[cfg(feature = "gstd")] +pub type DefaultEnv = GstdEnv; pub type Route = &'static str; +#[derive(Debug, Clone)] pub struct Deployment { env: E, code_id: CodeId, @@ -95,6 +99,7 @@ impl Deployment { } } +#[derive(Debug, Clone)] pub struct Actor { env: E, id: ActorId, @@ -110,6 +115,10 @@ impl Actor { } } + pub fn id(&self) -> ActorId { + self.id + } + pub fn with_env(self, env: N) -> Actor { let Self { env: _, @@ -123,11 +132,17 @@ impl Actor { } } + pub fn with_actor_id(mut self, actor_id: ActorId) -> Self { + self.id = actor_id; + self + } + pub fn service(&self, route: Route) -> Service { Service::new(self.env.clone(), self.id, route) } } +#[derive(Debug, Clone)] pub struct Service { env: E, actor_id: ActorId, @@ -145,9 +160,65 @@ impl Service { } } + pub fn with_actor_id(mut self, actor_id: ActorId) -> Self { + self.actor_id = actor_id; + self + } + pub fn pending_call(&self, args: T::Params) -> PendingCall { PendingCall::new(self.env.clone(), self.actor_id, self.route, args) } + + #[cfg(not(target_arch = "wasm32"))] + pub fn listener(&self) -> ServiceListener + where + S: ServiceEvent, + { + ServiceListener::new(self.env.clone(), self.actor_id, self.route) + } +} + +#[cfg(not(target_arch = "wasm32"))] +pub trait ServiceEvent { + type Event: EventDecode; +} + +#[cfg(not(target_arch = "wasm32"))] +pub struct ServiceListener { + env: E, + actor_id: ActorId, + route: Route, + _phantom: PhantomData, +} + +#[cfg(not(target_arch = "wasm32"))] +impl ServiceListener { + pub fn new(env: E, actor_id: ActorId, route: Route) -> Self { + ServiceListener { + env, + actor_id, + route, + _phantom: PhantomData, + } + } + + pub async fn listen( + &mut self, + ) -> Result + Unpin, ::Error> + where + E: crate::events::Listener, Error = ::Error>, + { + let self_id = self.actor_id; + let prefix = self.route; + let stream = self.env.listen().await?; + let map = stream.filter_map(move |(actor_id, payload)| async move { + if actor_id != self_id { + return None; + } + D::decode_event(prefix, payload).ok().map(|e| (actor_id, e)) + }); + Ok(Box::pin(map)) + } } pin_project_lite::pin_project! { @@ -374,30 +445,14 @@ macro_rules! str_scale_encode { }}; } -// impl> + GearEnv, S, E: EventDecode> Listener for Service { -// type Error = parity_scale_codec::Error; - -// async fn listen( -// &mut self, -// ) -> Result + Unpin, Self::Error> { -// let stream = self.env.listen().await?; -// let map = stream.filter_map(move |(actor_id, payload)| async move { -// E::decode_event(self.route, payload) -// .ok() -// .map(|e| (actor_id, e)) -// }); -// Ok(Box::pin(map)) -// } -// } - -pub trait EventDecode { +#[cfg(not(target_arch = "wasm32"))] +pub trait EventDecode: Decode { const EVENT_NAMES: &'static [&'static [u8]]; - type Event: Decode; fn decode_event( prefix: Route, payload: impl AsRef<[u8]>, - ) -> Result { + ) -> Result { let mut payload = payload.as_ref(); let route = String::decode(&mut payload)?; if route != prefix { From 32f4da045eb8f2e98a1da929c1d65d2cccfc9e79 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 27 Aug 2025 18:52:30 +0200 Subject: [PATCH 08/66] wip: rmrk example --- benchmarks/src/alloc_stress_program.rs | 2 +- benchmarks/src/compute_stress_program.rs | 2 +- examples/demo/client/src/demo_client.rs | 6 +- examples/no-svcs-prog/wasm/tests/tests.rs | 2 +- examples/proxy/src/lib.rs | 3 +- examples/rmrk/resource/app/build.rs | 6 +- examples/rmrk/resource/app/src/catalogs.rs | 4 +- examples/rmrk/resource/app/src/lib.rs | 16 +- .../rmrk/resource/app/src/rmrk_catalog.rs | 181 ++++++++++++++ .../rmrk/resource/app/src/services/mod.rs | 41 ++-- .../rmrk/resource/wasm/src/rmrk_resource.rs | 220 +++++------------- .../rmrk/resource/wasm/tests/resources.rs | 58 ++--- rs/client-gen/src/ctor_generators.rs | 3 +- rs/client-gen/src/helpers.rs | 8 +- rs/client-gen/src/service_generators.rs | 7 +- .../snapshots/generator__basic_works.snap | 2 +- .../snapshots/generator__events_works.snap | 2 +- .../snapshots/generator__external_types.snap | 2 +- .../tests/snapshots/generator__full.snap | 4 +- .../generator__full_with_sails_path.snap | 6 +- .../generator__multiple_services.snap | 2 +- .../snapshots/generator__nonzero_works.snap | 2 +- .../snapshots/generator__rmrk_works.snap | 16 +- rs/src/client/gtest_env.rs | 6 +- rs/src/client/mod.rs | 5 + rs/src/gtest/mod.rs | 2 +- rs/src/lib.rs | 1 - 27 files changed, 352 insertions(+), 257 deletions(-) create mode 100644 examples/rmrk/resource/app/src/rmrk_catalog.rs diff --git a/benchmarks/src/alloc_stress_program.rs b/benchmarks/src/alloc_stress_program.rs index d11b2deb1..fbab8024b 100644 --- a/benchmarks/src/alloc_stress_program.rs +++ b/benchmarks/src/alloc_stress_program.rs @@ -44,7 +44,7 @@ pub mod alloc_stress { pub mod io { use super::*; - sails_rs::io_struct_impl!(AllocStress (n: u32) -> AllocStressResult); + sails_rs::io_struct_impl!(AllocStress (n: u32) -> super::AllocStressResult); } } #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo)] diff --git a/benchmarks/src/compute_stress_program.rs b/benchmarks/src/compute_stress_program.rs index 14150bcd9..d6d28ce35 100644 --- a/benchmarks/src/compute_stress_program.rs +++ b/benchmarks/src/compute_stress_program.rs @@ -44,7 +44,7 @@ pub mod compute_stress { pub mod io { use super::*; - sails_rs::io_struct_impl!(ComputeStress (n: u32) -> ComputeStressResult); + sails_rs::io_struct_impl!(ComputeStress (n: u32) -> super::ComputeStressResult); } } #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo)] diff --git a/examples/demo/client/src/demo_client.rs b/examples/demo/client/src/demo_client.rs index 92990fc97..bf0557e25 100644 --- a/examples/demo/client/src/demo_client.rs +++ b/examples/demo/client/src/demo_client.rs @@ -240,7 +240,7 @@ pub mod references { sails_rs::io_struct_impl!(Add (v: u32) -> u32); sails_rs::io_struct_impl!(AddByte (byte: u8) -> Vec); sails_rs::io_struct_impl!(GuessNum (number: u8) -> Result); - sails_rs::io_struct_impl!(Incr () -> ReferenceCount); + sails_rs::io_struct_impl!(Incr () -> super::ReferenceCount); sails_rs::io_struct_impl!(SetNum (number: u8) -> Result<(), String>); sails_rs::io_struct_impl!(Baked () -> String); sails_rs::io_struct_impl!(LastByte () -> Option); @@ -292,8 +292,8 @@ pub mod this_that { pub mod io { use super::*; - sails_rs::io_struct_impl!(DoThat (param: DoThatParam) -> Result<(ActorId,NonZeroU32,ManyVariantsReply,), (String,)>); - sails_rs::io_struct_impl!(DoThis (p1: u32, p2: String, p3: (Option,NonZeroU8,), p4: TupleStruct) -> (String,u32,)); + sails_rs::io_struct_impl!(DoThat (param: super::DoThatParam) -> Result<(ActorId,NonZeroU32,super::ManyVariantsReply,), (String,)>); + sails_rs::io_struct_impl!(DoThis (p1: u32, p2: String, p3: (Option,NonZeroU8,), p4: super::TupleStruct) -> (String,u32,)); sails_rs::io_struct_impl!(Noop () -> ()); sails_rs::io_struct_impl!(That () -> Result); sails_rs::io_struct_impl!(This () -> u32); diff --git a/examples/no-svcs-prog/wasm/tests/tests.rs b/examples/no-svcs-prog/wasm/tests/tests.rs index b88994cb9..8d6fe1476 100644 --- a/examples/no-svcs-prog/wasm/tests/tests.rs +++ b/examples/no-svcs-prog/wasm/tests/tests.rs @@ -1,6 +1,6 @@ use no_svcs_prog::client::*; use sails_rs::client::*; -use sails_rs::gtest::{System}; +use sails_rs::gtest::System; const ADMIN_ID: u64 = 10; const WASM_PATH: &str = "../../../target/wasm32-gear/debug/no_svcs_prog.opt.wasm"; diff --git a/examples/proxy/src/lib.rs b/examples/proxy/src/lib.rs index 589b52be6..c6fc34906 100644 --- a/examples/proxy/src/lib.rs +++ b/examples/proxy/src/lib.rs @@ -16,7 +16,8 @@ impl ProxyProgram { } pub fn this_that_caller(&self) -> this_that::ThisThatCaller> { - let this_that_client = DemoClientProgram::client(DefaultEnv::default(), self.this_that_addr).this_that(); + let this_that_client = + DemoClientProgram::client(DefaultEnv::default(), self.this_that_addr).this_that(); this_that::ThisThatCaller::new(this_that_client) } } diff --git a/examples/rmrk/resource/app/build.rs b/examples/rmrk/resource/app/build.rs index e5423fc78..32c3244d4 100644 --- a/examples/rmrk/resource/app/build.rs +++ b/examples/rmrk/resource/app/build.rs @@ -3,8 +3,9 @@ use std::{env, path::PathBuf}; fn main() { let out_dir_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - let client_rs_file_path = out_dir_path.join("rmrk_catalog.rs"); + let client_rs_file_path = manifest_dir.join("src/rmrk_catalog.rs"); #[cfg(not(target_family = "windows"))] let idl_file_path = out_dir_path.join("rmrk-catalog.idl"); @@ -19,8 +20,7 @@ fn main() { .unwrap(); #[cfg(target_family = "windows")] - let idl_file_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) - .join("..\\..\\catalog\\wasm\\rmrk-catalog.idl"); + let idl_file_path = manifest_dir.join("..\\..\\catalog\\wasm\\rmrk-catalog.idl"); ClientGenerator::from_idl_path(&idl_file_path) .with_mocks("mockall") diff --git a/examples/rmrk/resource/app/src/catalogs.rs b/examples/rmrk/resource/app/src/catalogs.rs index 4998b9536..ca16f0489 100644 --- a/examples/rmrk/resource/app/src/catalogs.rs +++ b/examples/rmrk/resource/app/src/catalogs.rs @@ -1 +1,3 @@ -include!(concat!(env!("OUT_DIR"), "/rmrk_catalog.rs")); +use sails_rs::collections::BTreeMap; + +include!("rmrk_catalog.rs"); diff --git a/examples/rmrk/resource/app/src/lib.rs b/examples/rmrk/resource/app/src/lib.rs index 97e85f06a..58b99ae90 100644 --- a/examples/rmrk/resource/app/src/lib.rs +++ b/examples/rmrk/resource/app/src/lib.rs @@ -4,7 +4,11 @@ #[cfg(not(target_arch = "wasm32"))] pub extern crate std; -use sails_rs::gstd::{calls::GStdRemoting, program}; +use crate::catalogs::{RmrkCatalog as _, RmrkCatalogProgram, rmrk_catalog::RmrkCatalogImpl}; +use sails_rs::{ + client::{Program as _, *}, + prelude::*, +}; use services::ResourceStorage; mod catalogs; @@ -12,8 +16,6 @@ mod catalogs; // while there is no generated client pub mod services; -type RmrkCatalog = catalogs::RmrkCatalog; - #[derive(Default)] pub struct Program; @@ -21,13 +23,15 @@ pub struct Program; impl Program { // Initialize program and seed hosted services pub fn new() -> Self { - ResourceStorage::::seed(); + ResourceStorage::>::seed(); Self } // Expose hosted service #[export(route = "RmrkResource")] - pub fn resource_storage(&self) -> ResourceStorage { - ResourceStorage::new(RmrkCatalog::new(GStdRemoting)) + pub fn resource_storage(&self) -> ResourceStorage> { + let rmrk_catalog_client = + RmrkCatalogProgram::client(DefaultEnv::default(), ActorId::zero()).rmrk_catalog(); + ResourceStorage::new(rmrk_catalog_client) } } diff --git a/examples/rmrk/resource/app/src/rmrk_catalog.rs b/examples/rmrk/resource/app/src/rmrk_catalog.rs new file mode 100644 index 000000000..20221e2d3 --- /dev/null +++ b/examples/rmrk/resource/app/src/rmrk_catalog.rs @@ -0,0 +1,181 @@ +// Code generated by sails-client-gen. DO NOT EDIT. +use sails_rs::{client::*, prelude::*}; +pub struct RmrkCatalogProgram; +impl Program for RmrkCatalogProgram {} +pub trait RmrkCatalog { + type Env: GearEnv; + fn rmrk_catalog(&self) -> Service; +} +impl RmrkCatalog for Actor { + type Env = E; + fn rmrk_catalog(&self) -> Service { + self.service(stringify!(RmrkCatalog)) + } +} +pub trait RmrkCatalogCtors { + type Env: GearEnv; + #[allow(clippy::new_ret_no_self)] + #[allow(clippy::wrong_self_convention)] + fn new(self) -> PendingCtor; +} +impl RmrkCatalogCtors for Deployment { + type Env = E; + fn new(self) -> PendingCtor { + self.pending_ctor(()) + } +} + +pub mod io { + use super::*; + sails_rs::io_struct_impl!(New () -> ()); +} + +pub mod rmrk_catalog { + use super::*; + pub trait RmrkCatalog { + type Env: GearEnv; + fn add_equippables( + &mut self, + part_id: u32, + collection_ids: Vec, + ) -> PendingCall; + fn add_parts(&mut self, parts: BTreeMap) + -> PendingCall; + fn remove_equippable( + &mut self, + part_id: u32, + collection_id: ActorId, + ) -> PendingCall; + fn remove_parts(&mut self, part_ids: Vec) -> PendingCall; + fn reset_equippables( + &mut self, + part_id: u32, + ) -> PendingCall; + fn set_equippables_to_all( + &mut self, + part_id: u32, + ) -> PendingCall; + fn equippable( + &self, + part_id: u32, + collection_id: ActorId, + ) -> PendingCall; + fn part(&self, part_id: u32) -> PendingCall; + } + pub struct RmrkCatalogImpl; + impl RmrkCatalog for Service { + type Env = E; + fn add_equippables( + &mut self, + part_id: u32, + collection_ids: Vec, + ) -> PendingCall { + self.pending_call((part_id, collection_ids)) + } + fn add_parts( + &mut self, + parts: BTreeMap, + ) -> PendingCall { + self.pending_call((parts,)) + } + fn remove_equippable( + &mut self, + part_id: u32, + collection_id: ActorId, + ) -> PendingCall { + self.pending_call((part_id, collection_id)) + } + fn remove_parts(&mut self, part_ids: Vec) -> PendingCall { + self.pending_call((part_ids,)) + } + fn reset_equippables( + &mut self, + part_id: u32, + ) -> PendingCall { + self.pending_call((part_id,)) + } + fn set_equippables_to_all( + &mut self, + part_id: u32, + ) -> PendingCall { + self.pending_call((part_id,)) + } + fn equippable( + &self, + part_id: u32, + collection_id: ActorId, + ) -> PendingCall { + self.pending_call((part_id, collection_id)) + } + fn part(&self, part_id: u32) -> PendingCall { + self.pending_call((part_id,)) + } + } + + pub mod io { + use super::*; + sails_rs::io_struct_impl!(AddEquippables (part_id: u32, collection_ids: Vec) -> Result<(u32,Vec,), super::Error>); + sails_rs::io_struct_impl!(AddParts (parts: BTreeMap) -> Result, super::Error>); + sails_rs::io_struct_impl!(RemoveEquippable (part_id: u32, collection_id: ActorId) -> Result<(u32,ActorId,), super::Error>); + sails_rs::io_struct_impl!(RemoveParts (part_ids: Vec) -> Result, super::Error>); + sails_rs::io_struct_impl!(ResetEquippables (part_id: u32) -> Result<(), super::Error>); + sails_rs::io_struct_impl!(SetEquippablesToAll (part_id: u32) -> Result<(), super::Error>); + sails_rs::io_struct_impl!(Equippable (part_id: u32, collection_id: ActorId) -> Result); + sails_rs::io_struct_impl!(Part (part_id: u32) -> Option); + } +} +#[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo)] +#[codec(crate = sails_rs::scale_codec)] +#[scale_info(crate = sails_rs::scale_info)] +pub enum Error { + PartIdCantBeZero, + BadConfig, + PartAlreadyExists, + ZeroLengthPassed, + PartDoesNotExist, + WrongPartFormat, + NotAllowedToCall, +} +#[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo)] +#[codec(crate = sails_rs::scale_codec)] +#[scale_info(crate = sails_rs::scale_info)] +pub enum Part { + Fixed(FixedPart), + Slot(SlotPart), +} +#[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo)] +#[codec(crate = sails_rs::scale_codec)] +#[scale_info(crate = sails_rs::scale_info)] +pub struct FixedPart { + /// An optional zIndex of base part layer. + /// specifies the stack order of an element. + /// An element with greater stack order is always in front of an element with a lower stack order. + pub z: Option, + /// The metadata URI of the part. + pub metadata_uri: String, +} +#[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo)] +#[codec(crate = sails_rs::scale_codec)] +#[scale_info(crate = sails_rs::scale_info)] +pub struct SlotPart { + /// Array of whitelisted collections that can be equipped in the given slot. Used with slot parts only. + pub equippable: Vec, + /// An optional zIndex of base part layer. + /// specifies the stack order of an element. + /// An element with greater stack order is always in front of an element with a lower stack order. + pub z: Option, + /// The metadata URI of the part. + pub metadata_uri: String, +} + +#[cfg(feature = "mockall")] +#[cfg(not(target_arch = "wasm32"))] +extern crate std; + +#[cfg(feature = "mockall")] +#[cfg(not(target_arch = "wasm32"))] +pub mod mockall { + use super::*; + use sails_rs::mockall::*; + mock! { pub RmrkCatalog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl rmrk_catalog::RmrkCatalog for RmrkCatalog { type Env = MockEnv; fn add_equippables (&mut self, part_id: u32, collection_ids: Vec) -> PendingCall;fn add_parts (&mut self, parts: BTreeMap) -> PendingCall;fn remove_equippable (&mut self, part_id: u32, collection_id: ActorId) -> PendingCall;fn remove_parts (&mut self, part_ids: Vec) -> PendingCall;fn reset_equippables (&mut self, part_id: u32) -> PendingCall;fn set_equippables_to_all (&mut self, part_id: u32) -> PendingCall;fn equippable (& self, part_id: u32, collection_id: ActorId) -> PendingCall;fn part (& self, part_id: u32) -> PendingCall; } } +} diff --git a/examples/rmrk/resource/app/src/services/mod.rs b/examples/rmrk/resource/app/src/services/mod.rs index a7a82b556..f3743a2ec 100644 --- a/examples/rmrk/resource/app/src/services/mod.rs +++ b/examples/rmrk/resource/app/src/services/mod.rs @@ -1,8 +1,8 @@ -use crate::catalogs::traits::RmrkCatalog; +use crate::catalogs::rmrk_catalog::RmrkCatalog; use errors::{Error, Result}; use resources::{ComposedResource, PartId, Resource, ResourceId}; use sails_rs::{ - calls::Query, + client::DefaultEnv, collections::HashMap, gstd::{Syscall, service}, prelude::*, @@ -41,7 +41,7 @@ pub struct ResourceStorage { impl ResourceStorage where - TCatalogClient: RmrkCatalog, + TCatalogClient: RmrkCatalog, { // This function needs to be called before any other function pub fn seed() { @@ -60,7 +60,7 @@ where #[service(events = ResourceStorageEvent)] impl ResourceStorage where - TCatalogClient: RmrkCatalog, + TCatalogClient: RmrkCatalog, { #[export] pub fn add_resource_entry( @@ -112,8 +112,13 @@ where // calls of this or another method (e.g. `add_resource_entry`) working with the same // data before this method returns. - // Call `Rmrk Catalog` via the generated client and the `recv` method - let part = self.catalog_client.part(part_id).recv(*base).await.unwrap(); + // Call `Rmrk Catalog` via the generated client + let part = self + .catalog_client + .part(part_id) + .with_destination(*base) + .await + .unwrap(); // Caution: Reading from the `resource` variable here may yield unexpected value. // This can happen because execution after asynchronous calls can resume @@ -176,18 +181,14 @@ mod tests { use super::*; use crate::catalogs::{FixedPart, Part, mockall::MockRmrkCatalog}; use resources::ComposedResource; - use sails_rs::{ - gstd::{calls::GStdArgs, services::Service}, - mockall::MockQuery, - }; + use sails_rs::{client::PendingCall, gstd::services::Service}; #[tokio::test] async fn test_add_resource_entry() { Syscall::with_message_source(ActorId::from(1)); - ResourceStorage::>::seed(); - let mut resource_storage = - ResourceStorage::new(MockRmrkCatalog::::new()).expose(&[]); + ResourceStorage::::seed(); + let mut resource_storage = ResourceStorage::new(MockRmrkCatalog::new()).expose(&[]); let resource = Resource::Composed(ComposedResource { src: "src".to_string(), @@ -203,18 +204,16 @@ mod tests { assert_eq!(actual_resource, resource); // add_part_to_resource - let mut part_query = MockQuery::new(); - part_query.expect_recv().returning(move |_| { - Ok(Some(Part::Fixed(FixedPart { - z: None, - metadata_uri: "metadata_uri".to_string(), - }))) - }); resource_storage .catalog_client .expect_part() .with(mockall::predicate::eq(1)) - .return_once(|_| part_query); + .return_once(|_| { + PendingCall::from_output(Some(Part::Fixed(FixedPart { + z: None, + metadata_uri: "metadata_uri".to_string(), + }))) + }); let actual_part_id = resource_storage .add_part_to_resource(actual_resource_id, 1) diff --git a/examples/rmrk/resource/wasm/src/rmrk_resource.rs b/examples/rmrk/resource/wasm/src/rmrk_resource.rs index 798910dd9..f70d0303e 100644 --- a/examples/rmrk/resource/wasm/src/rmrk_resource.rs +++ b/examples/rmrk/resource/wasm/src/rmrk_resource.rs @@ -1,163 +1,99 @@ // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::collections::BTreeMap; -#[allow(unused_imports)] -use sails_rs::{ - String, - calls::{Activation, Call, Query, Remoting, RemotingAction}, - prelude::*, -}; -pub struct RmrkResourceFactory { - #[allow(dead_code)] - remoting: R, +use sails_rs::{client::*, prelude::*}; +pub struct RmrkResourceProgram; +impl Program for RmrkResourceProgram {} +pub trait RmrkResource { + type Env: GearEnv; + fn rmrk_resource(&self) -> Service; } -impl RmrkResourceFactory { - #[allow(unused)] - pub fn new(remoting: R) -> Self { - Self { remoting } +impl RmrkResource for Actor { + type Env = E; + fn rmrk_resource(&self) -> Service { + self.service(stringify!(RmrkResource)) } } -impl traits::RmrkResourceFactory for RmrkResourceFactory { - type Args = R::Args; - fn new(&self) -> impl Activation { - RemotingAction::<_, rmrk_resource_factory::io::New>::new(self.remoting.clone(), ()) +pub trait RmrkResourceCtors { + type Env: GearEnv; + #[allow(clippy::new_ret_no_self)] + #[allow(clippy::wrong_self_convention)] + fn new(self) -> PendingCtor; +} +impl RmrkResourceCtors for Deployment { + type Env = E; + fn new(self) -> PendingCtor { + self.pending_ctor(()) } } -pub mod rmrk_resource_factory { +pub mod io { use super::*; - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct New(()); - impl New { - #[allow(dead_code)] - pub fn encode_call() -> Vec { - ::encode_call(&()) - } - } - impl ActionIo for New { - const ROUTE: &'static [u8] = &[12, 78, 101, 119]; - type Params = (); - type Reply = (); - } - } -} -pub struct RmrkResource { - remoting: R, -} -impl RmrkResource { - pub fn new(remoting: R) -> Self { - Self { remoting } - } -} -impl traits::RmrkResource for RmrkResource { - type Args = R::Args; - fn add_part_to_resource( - &mut self, - resource_id: u8, - part_id: u32, - ) -> impl Call, Args = R::Args> { - RemotingAction::<_, rmrk_resource::io::AddPartToResource>::new( - self.remoting.clone(), - (resource_id, part_id), - ) - } - fn add_resource_entry( - &mut self, - resource_id: u8, - resource: Resource, - ) -> impl Call, Args = R::Args> { - RemotingAction::<_, rmrk_resource::io::AddResourceEntry>::new( - self.remoting.clone(), - (resource_id, resource), - ) - } - fn resource( - &self, - resource_id: u8, - ) -> impl Query, Args = R::Args> { - RemotingAction::<_, rmrk_resource::io::Resource>::new(self.remoting.clone(), resource_id) - } + sails_rs::io_struct_impl!(New () -> ()); } pub mod rmrk_resource { use super::*; - - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - pub struct AddPartToResource(()); - impl AddPartToResource { - #[allow(dead_code)] - pub fn encode_call(resource_id: u8, part_id: u32) -> Vec { - ::encode_call(&(resource_id, part_id)) - } - } - impl ActionIo for AddPartToResource { - const ROUTE: &'static [u8] = &[ - 48, 82, 109, 114, 107, 82, 101, 115, 111, 117, 114, 99, 101, 68, 65, 100, 100, 80, - 97, 114, 116, 84, 111, 82, 101, 115, 111, 117, 114, 99, 101, - ]; - type Params = (u8, u32); - type Reply = Result; - } - pub struct AddResourceEntry(()); - impl AddResourceEntry { - #[allow(dead_code)] - pub fn encode_call(resource_id: u8, resource: super::Resource) -> Vec { - ::encode_call(&(resource_id, resource)) - } - } - impl ActionIo for AddResourceEntry { - const ROUTE: &'static [u8] = &[ - 48, 82, 109, 114, 107, 82, 101, 115, 111, 117, 114, 99, 101, 64, 65, 100, 100, 82, - 101, 115, 111, 117, 114, 99, 101, 69, 110, 116, 114, 121, - ]; - type Params = (u8, super::Resource); - type Reply = Result<(u8, super::Resource), super::Error>; + pub trait RmrkResource { + type Env: GearEnv; + fn add_part_to_resource( + &mut self, + resource_id: u8, + part_id: u32, + ) -> PendingCall; + fn add_resource_entry( + &mut self, + resource_id: u8, + resource: Resource, + ) -> PendingCall; + fn resource(&self, resource_id: u8) -> PendingCall; + } + pub struct RmrkResourceImpl; + impl RmrkResource for Service { + type Env = E; + fn add_part_to_resource( + &mut self, + resource_id: u8, + part_id: u32, + ) -> PendingCall { + self.pending_call((resource_id, part_id)) } - pub struct Resource(()); - impl Resource { - #[allow(dead_code)] - pub fn encode_call(resource_id: u8) -> Vec { - ::encode_call(&resource_id) - } + fn add_resource_entry( + &mut self, + resource_id: u8, + resource: Resource, + ) -> PendingCall { + self.pending_call((resource_id, resource)) } - impl ActionIo for Resource { - const ROUTE: &'static [u8] = &[ - 48, 82, 109, 114, 107, 82, 101, 115, 111, 117, 114, 99, 101, 32, 82, 101, 115, 111, - 117, 114, 99, 101, - ]; - type Params = u8; - type Reply = Result; + fn resource(&self, resource_id: u8) -> PendingCall { + self.pending_call((resource_id,)) } } - #[allow(dead_code)] + pub mod io { + use super::*; + sails_rs::io_struct_impl!(AddPartToResource (resource_id: u8, part_id: u32) -> Result); + sails_rs::io_struct_impl!(AddResourceEntry (resource_id: u8, resource: super::Resource) -> Result<(u8,super::Resource,), super::Error>); + sails_rs::io_struct_impl!(Resource (resource_id: u8) -> Result); + } + #[cfg(not(target_arch = "wasm32"))] pub mod events { use super::*; - use sails_rs::events::*; #[derive(PartialEq, Debug, Encode, Decode)] #[codec(crate = sails_rs::scale_codec)] pub enum RmrkResourceEvents { ResourceAdded { resource_id: u8 }, PartAdded { resource_id: u8, part_id: u32 }, } - impl EventIo for RmrkResourceEvents { - const ROUTE: &'static [u8] = - &[48, 82, 109, 114, 107, 82, 101, 115, 111, 117, 114, 99, 101]; + impl EventDecode for RmrkResourceEvents { const EVENT_NAMES: &'static [&'static [u8]] = &[ &[ 52, 82, 101, 115, 111, 117, 114, 99, 101, 65, 100, 100, 101, 100, ], &[36, 80, 97, 114, 116, 65, 100, 100, 101, 100], ]; - type Event = Self; } - pub fn listener>>(remoting: R) -> impl Listener { - RemotingListener::<_, RmrkResourceEvents>::new(remoting) + impl ServiceEvent for RmrkResourceImpl { + type Event = RmrkResourceEvents; } } } @@ -221,33 +157,3 @@ pub struct ComposedResource { pub base: ActorId, pub parts: Vec, } - -pub mod traits { - use super::*; - #[allow(dead_code)] - pub trait RmrkResourceFactory { - type Args; - #[allow(clippy::new_ret_no_self)] - #[allow(clippy::wrong_self_convention)] - fn new(&self) -> impl Activation; - } - - #[allow(clippy::type_complexity)] - pub trait RmrkResource { - type Args; - fn add_part_to_resource( - &mut self, - resource_id: u8, - part_id: u32, - ) -> impl Call, Args = Self::Args>; - fn add_resource_entry( - &mut self, - resource_id: u8, - resource: Resource, - ) -> impl Call, Args = Self::Args>; - fn resource( - &self, - resource_id: u8, - ) -> impl Query, Args = Self::Args>; - } -} diff --git a/examples/rmrk/resource/wasm/tests/resources.rs b/examples/rmrk/resource/wasm/tests/resources.rs index 5f72bf8ad..a6ea9380e 100644 --- a/examples/rmrk/resource/wasm/tests/resources.rs +++ b/examples/rmrk/resource/wasm/tests/resources.rs @@ -1,5 +1,8 @@ use rmrk_catalog::services::parts::{FixedPart, Part}; -use rmrk_resource::client::{self, traits::RmrkResource as _}; +use rmrk_resource::client::{ + self, RmrkResource, RmrkResourceProgram, + rmrk_resource::{RmrkResource as _, RmrkResourceImpl}, +}; use rmrk_resource_app::services::{ ResourceStorageEvent, errors::{Error as ResourceStorageError, Result as ResourceStorageResult}, @@ -8,15 +11,13 @@ use rmrk_resource_app::services::{ use sails_rs::{ ActorId, Decode, Encode, calls::{Call, Query, Remoting}, + client::{GtestEnv, GtestParams, Program as _, Service}, collections::BTreeMap, errors::Result, - gtest::{ - BlockRunResult, Program, System, - calls::{GTestArgs, GTestRemoting, WithArgs as _}, - }, + gtest::{BlockRunResult, Program, System}, }; -type RmrkResourceClient = client::RmrkResource; +type RmrkResourceClient = client::rmrk_resource::RmrkResourceImpl; const CATALOG_PROGRAM_WASM_PATH: &str = "../../../../target/wasm32-gear/debug/rmrk_catalog.wasm"; const RESOURCE_PROGRAM_WASM_PATH: &str = "../../../../target/wasm32-gear/debug/rmrk_resource.wasm"; @@ -382,7 +383,7 @@ impl SystemFixture { } struct Fixture { - program_space: GTestRemoting, + env: GtestEnv, catalog_program_id: ActorId, resource_program_id: ActorId, } @@ -397,10 +398,10 @@ impl Fixture { let catalog_program_id = Self::create_catalog_program(&system); let resource_program_id = Self::create_resource_program(&system); - let program_space = GTestRemoting::new(system, ADMIN_ID.into()); + let env = GtestEnv::new(system, ADMIN_ID.into()); Self { - program_space, + env, catalog_program_id, resource_program_id, } @@ -435,12 +436,8 @@ impl Fixture { resource_program.id() } - fn program_space(&self) -> >estRemoting { - &self.program_space - } - - fn resource_client(&self) -> RmrkResourceClient { - RmrkResourceClient::new(self.program_space.clone()) + fn resource_client(&self) -> Service { + RmrkResourceProgram::client(self.env.clone(), self.resource_program_id).rmrk_resource() } async fn add_resource_async( @@ -448,7 +445,7 @@ impl Fixture { actor_id: u64, resource_id: ResourceId, resource: &Resource, - ) -> Result> { + ) -> Result, sails_rs::gtest::TestError> { let encoded_request = [ resources::RESOURCE_SERVICE_NAME.encode(), resources::ADD_RESOURCE_ENTRY_FUNC_NAME.encode(), @@ -456,17 +453,15 @@ impl Fixture { resource.encode(), ] .concat(); - let program_space = self.program_space().clone(); - let reply = program_space - .message( - self.resource_program_id, - encoded_request, - None, - 0, - GTestArgs::new(actor_id.into()), - ) - .await?; - reply.await + let env = self.env.clone(); + let message_id = env.send_message( + self.resource_program_id, + encoded_request, + GtestParams::default().with_actor_id(actor_id.into()), + )?; + env.message_reply_from_next_blocks(message_id) + .await + .unwrap() } async fn add_resource_via_client( @@ -474,12 +469,11 @@ impl Fixture { actor_id: u64, resource_id: u8, resource: client::Resource, - ) -> Result> { + ) -> Result, sails_rs::gtest::TestError> { let mut resource_client = self.resource_client(); resource_client .add_resource_entry(resource_id, resource) .with_actor_id(actor_id.into()) - .send_recv(self.resource_program_id) .await } @@ -488,12 +482,11 @@ impl Fixture { actor_id: u64, resource_id: u8, part_id: u32, - ) -> Result> { + ) -> Result, sails_rs::gtest::TestError> { let mut resource_client = self.resource_client(); resource_client .add_part_to_resource(resource_id, part_id) .with_actor_id(actor_id.into()) - .send_recv(self.resource_program_id) .await } @@ -501,12 +494,11 @@ impl Fixture { &self, actor_id: u64, resource_id: u8, - ) -> Result> { + ) -> Result, sails_rs::gtest::TestError> { let resource_client = self.resource_client(); resource_client .resource(resource_id) .with_actor_id(actor_id.into()) - .recv(self.resource_program_id) .await } } diff --git a/rs/client-gen/src/ctor_generators.rs b/rs/client-gen/src/ctor_generators.rs index cbfdfa415..4a0e3f607 100644 --- a/rs/client-gen/src/ctor_generators.rs +++ b/rs/client-gen/src/ctor_generators.rs @@ -81,8 +81,9 @@ impl<'ast> Visitor<'ast> for CtorGenerator<'_> { } }; + let params_with_types_super = &fn_args_with_types_path(func.params(), "super"); quote_in! { self.io_tokens => - $(self.sails_path)::io_struct_impl!($fn_name ($params_with_types) -> ()); + $(self.sails_path)::io_struct_impl!($fn_name ($params_with_types_super) -> ()); }; } } diff --git a/rs/client-gen/src/helpers.rs b/rs/client-gen/src/helpers.rs index 77b87cc9f..8c115dde2 100644 --- a/rs/client-gen/src/helpers.rs +++ b/rs/client-gen/src/helpers.rs @@ -1,4 +1,4 @@ -use crate::type_generators::generate_type_decl_code; +use crate::type_generators::generate_type_decl_with_path; use parity_scale_codec::Encode; use sails_idl_parser::ast::FuncParam; @@ -48,10 +48,14 @@ pub(crate) fn encoded_args(params: &[FuncParam]) -> String { } pub(crate) fn fn_args_with_types(params: &[FuncParam]) -> String { + fn_args_with_types_path(params, "") +} + +pub(crate) fn fn_args_with_types_path(params: &[FuncParam], path: &str) -> String { params .iter() .map(|p| { - let ty = generate_type_decl_code(p.type_decl()); + let ty = generate_type_decl_with_path(p.type_decl(), path.to_owned()); format!("{}: {}", p.name(), ty) }) .collect::>() diff --git a/rs/client-gen/src/service_generators.rs b/rs/client-gen/src/service_generators.rs index 286450f90..88c6137cf 100644 --- a/rs/client-gen/src/service_generators.rs +++ b/rs/client-gen/src/service_generators.rs @@ -5,7 +5,7 @@ use sails_idl_parser::{ast::visitor, ast::visitor::Visitor, ast::*}; use crate::events_generator::EventsModuleGenerator; use crate::helpers::*; -use crate::type_generators::generate_type_decl_code; +use crate::type_generators::{generate_type_decl_code, generate_type_decl_with_path}; /// Generates a trait with service methods pub(crate) struct ServiceCtorGenerator<'a> { @@ -114,7 +114,6 @@ impl<'ast> Visitor<'ast> for ServiceGenerator<'_> { let params_with_types = &fn_args_with_types(func.params()); let args = encoded_args(func.params()); - let output_type_decl_code = generate_type_decl_code(func.output()); for doc in func.docs() { quote_in! { self.trait_tokens => @@ -131,8 +130,10 @@ impl<'ast> Visitor<'ast> for ServiceGenerator<'_> { } }; + let output_type_decl_code = generate_type_decl_with_path(func.output(), "super".to_owned()); + let params_with_types_super = &fn_args_with_types_path(func.params(), "super"); quote_in! { self.io_tokens => - $(self.sails_path)::io_struct_impl!($fn_name ($params_with_types) -> $output_type_decl_code); + $(self.sails_path)::io_struct_impl!($fn_name ($params_with_types_super) -> $output_type_decl_code); }; } } diff --git a/rs/client-gen/tests/snapshots/generator__basic_works.snap b/rs/client-gen/tests/snapshots/generator__basic_works.snap index 927dc3b3d..f7e9687c6 100644 --- a/rs/client-gen/tests/snapshots/generator__basic_works.snap +++ b/rs/client-gen/tests/snapshots/generator__basic_works.snap @@ -37,7 +37,7 @@ pub mod basic { pub mod io { use super::*; - sails_rs::io_struct_impl!(DoThis (p1: u32, p2: MyParam) -> u16); + sails_rs::io_struct_impl!(DoThis (p1: u32, p2: super::MyParam) -> u16); sails_rs::io_struct_impl!(DoThat (p1: (u8,u32,)) -> u8); } } diff --git a/rs/client-gen/tests/snapshots/generator__events_works.snap b/rs/client-gen/tests/snapshots/generator__events_works.snap index 761a882b6..b7e4b7e33 100644 --- a/rs/client-gen/tests/snapshots/generator__events_works.snap +++ b/rs/client-gen/tests/snapshots/generator__events_works.snap @@ -36,7 +36,7 @@ pub mod service_with_events { pub mod io { use super::*; - sails_rs::io_struct_impl!(DoThis (p1: NonZeroU256, p2: MyParam) -> NonZeroU64); + sails_rs::io_struct_impl!(DoThis (p1: NonZeroU256, p2: super::MyParam) -> NonZeroU64); } #[cfg(not(target_arch = "wasm32"))] diff --git a/rs/client-gen/tests/snapshots/generator__external_types.snap b/rs/client-gen/tests/snapshots/generator__external_types.snap index bc93daeef..b0c549930 100644 --- a/rs/client-gen/tests/snapshots/generator__external_types.snap +++ b/rs/client-gen/tests/snapshots/generator__external_types.snap @@ -39,7 +39,7 @@ pub mod service { pub mod io { use super::*; - my_crate::sails::io_struct_impl!(DoThis (p1: u32, p2: MyParam) -> u16); + my_crate::sails::io_struct_impl!(DoThis (p1: u32, p2: super::MyParam) -> u16); my_crate::sails::io_struct_impl!(DoThat (p1: (u8,u32,)) -> u8); } } diff --git a/rs/client-gen/tests/snapshots/generator__full.snap b/rs/client-gen/tests/snapshots/generator__full.snap index fcb888733..d50994598 100644 --- a/rs/client-gen/tests/snapshots/generator__full.snap +++ b/rs/client-gen/tests/snapshots/generator__full.snap @@ -86,8 +86,8 @@ pub mod service { pub mod io { use super::*; - sails_rs::io_struct_impl!(DoThis (p1: u32, p2: String, p3: (Option,u8,), p4: ThisThatSvcAppTupleStruct) -> (String,u32,)); - sails_rs::io_struct_impl!(DoThat (param: ThisThatSvcAppDoThatParam) -> Result<(String,u32,), (String,)>); + sails_rs::io_struct_impl!(DoThis (p1: u32, p2: String, p3: (Option,u8,), p4: super::ThisThatSvcAppTupleStruct) -> (String,u32,)); + sails_rs::io_struct_impl!(DoThat (param: super::ThisThatSvcAppDoThatParam) -> Result<(String,u32,), (String,)>); sails_rs::io_struct_impl!(This (v1: Vec) -> u32); sails_rs::io_struct_impl!(That (v1: ()) -> Result); } diff --git a/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap b/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap index dd8d887bd..59d89b116 100644 --- a/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap +++ b/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap @@ -50,7 +50,7 @@ impl ServiceCtors for Deployment { pub mod io { use super::*; my_crate::sails::io_struct_impl!(New (a: u32) -> ()); - my_crate::sails::io_struct_impl!(CreateWithData (a: u32, b: String, c: ThisThatSvcAppManyVariants) -> ()); + my_crate::sails::io_struct_impl!(CreateWithData (a: u32, b: String, c: super::ThisThatSvcAppManyVariants) -> ()); } pub mod service { @@ -99,8 +99,8 @@ pub mod service { pub mod io { use super::*; - my_crate::sails::io_struct_impl!(DoThis (p1: u32, p2: String, p3: (Option,u8,), p4: ThisThatSvcAppTupleStruct) -> (String,u32,)); - my_crate::sails::io_struct_impl!(DoThat (param: ThisThatSvcAppDoThatParam) -> Result<(String,u32,), (String,)>); + my_crate::sails::io_struct_impl!(DoThis (p1: u32, p2: String, p3: (Option,u8,), p4: super::ThisThatSvcAppTupleStruct) -> (String,u32,)); + my_crate::sails::io_struct_impl!(DoThat (param: super::ThisThatSvcAppDoThatParam) -> Result<(String,u32,), (String,)>); my_crate::sails::io_struct_impl!(This (v1: Vec) -> u32); my_crate::sails::io_struct_impl!(That (v1: ()) -> Result); } diff --git a/rs/client-gen/tests/snapshots/generator__multiple_services.snap b/rs/client-gen/tests/snapshots/generator__multiple_services.snap index be671c6aa..f53daec1d 100644 --- a/rs/client-gen/tests/snapshots/generator__multiple_services.snap +++ b/rs/client-gen/tests/snapshots/generator__multiple_services.snap @@ -41,7 +41,7 @@ pub mod multiple { pub mod io { use super::*; - sails_rs::io_struct_impl!(DoThis (p1: u32, p2: MyParam) -> u16); + sails_rs::io_struct_impl!(DoThis (p1: u32, p2: super::MyParam) -> u16); sails_rs::io_struct_impl!(DoThat (p1: (u8,u32,)) -> u8); } } diff --git a/rs/client-gen/tests/snapshots/generator__nonzero_works.snap b/rs/client-gen/tests/snapshots/generator__nonzero_works.snap index 23874990a..4e858659d 100644 --- a/rs/client-gen/tests/snapshots/generator__nonzero_works.snap +++ b/rs/client-gen/tests/snapshots/generator__nonzero_works.snap @@ -33,7 +33,7 @@ pub mod non_zero_params { pub mod io { use super::*; - sails_rs::io_struct_impl!(DoThis (p1: NonZeroU256, p2: MyParam) -> NonZeroU64); + sails_rs::io_struct_impl!(DoThis (p1: NonZeroU256, p2: super::MyParam) -> NonZeroU64); } } #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo)] diff --git a/rs/client-gen/tests/snapshots/generator__rmrk_works.snap b/rs/client-gen/tests/snapshots/generator__rmrk_works.snap index da94bcb7c..a2cb51b3a 100644 --- a/rs/client-gen/tests/snapshots/generator__rmrk_works.snap +++ b/rs/client-gen/tests/snapshots/generator__rmrk_works.snap @@ -118,14 +118,14 @@ pub mod rmrk_catalog { pub mod io { use super::*; - sails_rs::io_struct_impl!(AddEquippables (part_id: u32, collection_ids: Vec) -> Result<(u32,Vec,), Error>); - sails_rs::io_struct_impl!(AddParts (parts: BTreeMap) -> Result, Error>); - sails_rs::io_struct_impl!(RemoveEquippable (part_id: u32, collection_id: ActorId) -> Result<(u32,ActorId,), Error>); - sails_rs::io_struct_impl!(RemoveParts (part_ids: Vec) -> Result, Error>); - sails_rs::io_struct_impl!(ResetEquippables (part_id: u32) -> Result<(), Error>); - sails_rs::io_struct_impl!(SetEquippablesToAll (part_id: u32) -> Result<(), Error>); - sails_rs::io_struct_impl!(Equippable (part_id: u32, collection_id: ActorId) -> Result); - sails_rs::io_struct_impl!(Part (part_id: u32) -> Option); + sails_rs::io_struct_impl!(AddEquippables (part_id: u32, collection_ids: Vec) -> Result<(u32,Vec,), super::Error>); + sails_rs::io_struct_impl!(AddParts (parts: BTreeMap) -> Result, super::Error>); + sails_rs::io_struct_impl!(RemoveEquippable (part_id: u32, collection_id: ActorId) -> Result<(u32,ActorId,), super::Error>); + sails_rs::io_struct_impl!(RemoveParts (part_ids: Vec) -> Result, super::Error>); + sails_rs::io_struct_impl!(ResetEquippables (part_id: u32) -> Result<(), super::Error>); + sails_rs::io_struct_impl!(SetEquippablesToAll (part_id: u32) -> Result<(), super::Error>); + sails_rs::io_struct_impl!(Equippable (part_id: u32, collection_id: ActorId) -> Result); + sails_rs::io_struct_impl!(Part (part_id: u32) -> Option); } } #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo)] diff --git a/rs/src/client/gtest_env.rs b/rs/src/client/gtest_env.rs index d25b427b9..2f2f80204 100644 --- a/rs/src/client/gtest_env.rs +++ b/rs/src/client/gtest_env.rs @@ -136,7 +136,7 @@ impl GtestEnv { } } - fn create_program( + pub fn create_program( &self, code_id: CodeId, salt: impl AsRef<[u8]>, @@ -161,7 +161,7 @@ impl GtestEnv { Ok((program_id, message_id)) } - fn send_message( + pub fn send_message( &self, target: ActorId, payload: impl AsRef<[u8]>, @@ -186,7 +186,7 @@ impl GtestEnv { Ok(message_id) } - fn message_reply_from_next_blocks(&self, message_id: MessageId) -> ReplyReceiver { + pub fn message_reply_from_next_blocks(&self, message_id: MessageId) -> ReplyReceiver { let (tx, rx) = oneshot::channel::, Error>>(); self.block_reply_senders.borrow_mut().insert(message_id, tx); diff --git a/rs/src/client/mod.rs b/rs/src/client/mod.rs index aaf76fb7c..993b2fab1 100644 --- a/rs/src/client/mod.rs +++ b/rs/src/client/mod.rs @@ -245,6 +245,11 @@ impl PendingCall { } } + pub fn with_destination(mut self, actor_id: ActorId) -> Self { + self.destination = actor_id; + self + } + pub fn with_params(mut self, f: impl FnOnce(E::Params) -> E::Params) -> Self { self.params = Some(f(self.params.unwrap_or_default())); self diff --git a/rs/src/gtest/mod.rs b/rs/src/gtest/mod.rs index 4187b6b90..662d98703 100644 --- a/rs/src/gtest/mod.rs +++ b/rs/src/gtest/mod.rs @@ -1,3 +1,3 @@ -pub use gtest::{BlockRunResult, Program, System, constants::*}; +pub use gtest::{BlockRunResult, Program, System, TestError, constants::*}; pub mod calls; diff --git a/rs/src/lib.rs b/rs/src/lib.rs index 86da83ae1..202ead2cf 100644 --- a/rs/src/lib.rs +++ b/rs/src/lib.rs @@ -39,5 +39,4 @@ pub mod prelude; #[cfg(feature = "ethexe")] pub mod solidity; mod types; -pub mod client; mod utils; From 6816f14ba98c1c201e48398606ded6f543573e21 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Thu, 28 Aug 2025 10:44:09 +0200 Subject: [PATCH 09/66] wip: ping pong --- benchmarks/ping-pong/app/src/lib.rs | 15 +-- .../ping-pong/app/src/ping_pong_client.rs | 108 ++++++------------ 2 files changed, 38 insertions(+), 85 deletions(-) diff --git a/benchmarks/ping-pong/app/src/lib.rs b/benchmarks/ping-pong/app/src/lib.rs index bfc66b771..04c05582e 100644 --- a/benchmarks/ping-pong/app/src/lib.rs +++ b/benchmarks/ping-pong/app/src/lib.rs @@ -4,11 +4,8 @@ pub mod client { include!("./ping_pong_client.rs"); } -use client::{ - PingPongPayload as PingPongPayloadC, PingPongService as PingPongServiceC, - traits::PingPongService as _, -}; -use sails_rs::{calls::Call, gstd::calls::GStdRemoting, prelude::*}; +use client::{PingPong as _, ping_pong_service::PingPongService as _}; +use sails_rs::{client::*, prelude::*}; #[derive(Debug, Clone, Copy, Decode, Encode, TypeInfo)] #[codec(crate = sails_rs::scale_codec)] @@ -28,16 +25,16 @@ impl PingPongService { pub async fn ping(&mut self, payload: PingPongPayload) -> PingPongPayload { match payload { PingPongPayload::Start(actor_id) => { - let mut api = PingPongServiceC::new(GStdRemoting::new()); + let mut api = client::PingPongProgram::client(DefaultEnv::default(), actor_id) + .ping_pong_service(); let result = api - .ping(PingPongPayloadC::Ping) - .send_recv(actor_id) + .ping(client::PingPongPayload::Ping) .await .unwrap_or_else(|e| { panic!("Failed to receiving successful ping result: {e:?}") }); - if matches!(result, PingPongPayloadC::Pong) { + if matches!(result, client::PingPongPayload::Pong) { PingPongPayload::Finished } else { panic!("Unexpected payload received: {result:?}") diff --git a/benchmarks/ping-pong/app/src/ping_pong_client.rs b/benchmarks/ping-pong/app/src/ping_pong_client.rs index 5e7c2681a..6f26eb360 100644 --- a/benchmarks/ping-pong/app/src/ping_pong_client.rs +++ b/benchmarks/ping-pong/app/src/ping_pong_client.rs @@ -1,76 +1,50 @@ // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::collections::BTreeMap; -#[allow(unused_imports)] -use sails_rs::{ - String, - calls::{Activation, Call, Query, Remoting, RemotingAction}, - prelude::*, -}; -pub struct PingPongFactory { - #[allow(dead_code)] - remoting: R, +use sails_rs::{client::*, prelude::*}; +pub struct PingPongProgram; +impl Program for PingPongProgram {} +pub trait PingPong { + type Env: GearEnv; + fn ping_pong_service(&self) -> Service; } -impl PingPongFactory { - #[allow(unused)] - pub fn new(remoting: R) -> Self { - Self { remoting } +impl PingPong for Actor { + type Env = E; + fn ping_pong_service(&self) -> Service { + self.service(stringify!(PingPongService)) } } -impl traits::PingPongFactory for PingPongFactory { - type Args = R::Args; - fn new_for_bench(&self) -> impl Activation { - RemotingAction::<_, ping_pong_factory::io::NewForBench>::new(self.remoting.clone(), ()) +pub trait PingPongCtors { + type Env: GearEnv; + fn new_for_bench(self) -> PendingCtor; +} +impl PingPongCtors for Deployment { + type Env = E; + fn new_for_bench(self) -> PendingCtor { + self.pending_ctor(()) } } -pub mod ping_pong_factory { +pub mod io { use super::*; - pub mod io { - use super::*; - use sails_rs::calls::ActionIo; - sails_rs::io_struct_impl!(NewForBench () -> ()); - } -} -pub struct PingPongService { - remoting: R, -} -impl PingPongService { - pub fn new(remoting: R) -> Self { - Self { remoting } - } -} -impl traits::PingPongService for PingPongService { - type Args = R::Args; - fn ping( - &mut self, - payload: PingPongPayload, - ) -> impl Call { - RemotingAction::<_, ping_pong_service::io::Ping>::new(self.remoting.clone(), payload) - } + sails_rs::io_struct_impl!(NewForBench () -> ()); } pub mod ping_pong_service { use super::*; + pub trait PingPongService { + type Env: GearEnv; + fn ping(&mut self, payload: PingPongPayload) -> PendingCall; + } + pub struct PingPongServiceImpl; + impl PingPongService for Service { + type Env = E; + fn ping(&mut self, payload: PingPongPayload) -> PendingCall { + self.pending_call((payload,)) + } + } pub mod io { use super::*; - use sails_rs::calls::ActionIo; - pub struct Ping(()); - impl Ping { - #[allow(dead_code)] - pub fn encode_call(payload: super::PingPongPayload) -> Vec { - ::encode_call(&payload) - } - } - impl ActionIo for Ping { - const ROUTE: &'static [u8] = &[ - 60, 80, 105, 110, 103, 80, 111, 110, 103, 83, 101, 114, 118, 105, 99, 101, 16, 80, - 105, 110, 103, - ]; - type Params = super::PingPongPayload; - type Reply = super::PingPongPayload; - } + sails_rs::io_struct_impl!(Ping (payload: super::PingPongPayload) -> super::PingPongPayload); } } #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo)] @@ -82,21 +56,3 @@ pub enum PingPongPayload { Pong, Finished, } - -pub mod traits { - use super::*; - #[allow(dead_code)] - pub trait PingPongFactory { - type Args; - fn new_for_bench(&self) -> impl Activation; - } - - #[allow(clippy::type_complexity)] - pub trait PingPongService { - type Args; - fn ping( - &mut self, - payload: PingPongPayload, - ) -> impl Call; - } -} From 1f01c5b8f05611dbd0844a518e84548b1e895721 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Fri, 29 Aug 2025 11:02:42 +0200 Subject: [PATCH 10/66] wip: env deploy, DefaultEnv --- benchmarks/ping-pong/app/src/lib.rs | 3 +- examples/demo/app/tests/env_gtest.rs | 6 ++- examples/demo/app/tests/gtest.rs | 49 ++++++++++++------- examples/no-svcs-prog/wasm/tests/tests.rs | 4 +- examples/proxy/src/lib.rs | 3 +- examples/proxy/src/this_that/mod.rs | 2 +- examples/redirect/proxy/src/lib.rs | 2 +- examples/redirect/tests/gtest.rs | 12 ++--- examples/rmrk/resource/app/src/lib.rs | 3 +- .../rmrk/resource/wasm/tests/resources.rs | 9 ++-- rs/client-gen/src/service_generators.rs | 2 +- rs/src/client/gclient_env.rs | 2 +- rs/src/client/mod.rs | 32 ++++++------ 13 files changed, 71 insertions(+), 58 deletions(-) diff --git a/benchmarks/ping-pong/app/src/lib.rs b/benchmarks/ping-pong/app/src/lib.rs index 04c05582e..68e0cffce 100644 --- a/benchmarks/ping-pong/app/src/lib.rs +++ b/benchmarks/ping-pong/app/src/lib.rs @@ -25,8 +25,7 @@ impl PingPongService { pub async fn ping(&mut self, payload: PingPongPayload) -> PingPongPayload { match payload { PingPongPayload::Start(actor_id) => { - let mut api = client::PingPongProgram::client(DefaultEnv::default(), actor_id) - .ping_pong_service(); + let mut api = client::PingPongProgram::client(actor_id).ping_pong_service(); let result = api .ping(client::PingPongPayload::Ping) .await diff --git a/examples/demo/app/tests/env_gtest.rs b/examples/demo/app/tests/env_gtest.rs index 36d22e4c4..bea5fa75a 100644 --- a/examples/demo/app/tests/env_gtest.rs +++ b/examples/demo/app/tests/env_gtest.rs @@ -1,5 +1,5 @@ use demo_client::env_client::{ - Demo as _, DemoCtors as _, DemoProgram, + Demo as _, DemoCtors as _, counter::{Counter as _, events::CounterEvents}, }; use futures::StreamExt as _; @@ -31,7 +31,9 @@ fn create_env() -> (GtestEnv, CodeId, GasUnit) { async fn env_counter_add_works_via_next_mode() { let (env, code_id, _gas_limit) = create_env(); - let demo_program = DemoProgram::deploy(env, code_id, vec![]) + // deploy DemoProgram + let demo_program = env + .deploy(code_id, vec![]) .new(Some(42), None) .await .unwrap(); diff --git a/examples/demo/app/tests/gtest.rs b/examples/demo/app/tests/gtest.rs index f7df6e884..da305e51c 100644 --- a/examples/demo/app/tests/gtest.rs +++ b/examples/demo/app/tests/gtest.rs @@ -1,5 +1,5 @@ use demo_client::*; -use gstd::errors::{ErrorReplyReason, ExtError, SimpleExecutionError}; +use gstd::errors::ExtError; use gtest::TestError; use sails_rs::{ client::{Program as _, *}, @@ -37,8 +37,9 @@ async fn counter_add_works() { let (env, code_id, _gas_limit) = create_env(); // Use generated client code for activating Demo program - // using the `new` constructor and the `send_recv` method - let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) + // using the `new` constructor + let demo_program = env + .deploy(code_id, vec![]) .new(Some(42), None) .await .unwrap(); @@ -68,9 +69,9 @@ async fn counter_sub_works() { let (env, code_id, _gas_limit) = create_env(); // Use generated client code for activating Demo program - // using the `new` constructor and the `send`/`recv` pair - // of methods - let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) + // using the `new` constructor + let demo_program = env + .deploy(code_id, vec![]) .new(Some(42), None) .await .unwrap(); @@ -102,7 +103,8 @@ async fn counter_query_works() { // Use generated client code for activating Demo program // using the `new` constructor and the `send`/`recv` pair // of methods - let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) + let demo_program = env + .deploy(code_id, vec![]) .new(Some(42), None) .await .unwrap(); @@ -127,7 +129,8 @@ async fn counter_query_not_enough_gas() { // Use generated client code for activating Demo program // using the `new` constructor and the `send`/`recv` pair // of methods - let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) + let demo_program = env + .deploy(code_id, vec![]) .new(Some(42), None) .await .unwrap(); @@ -207,7 +210,8 @@ async fn dog_barks() { // Use generated client code for activating Demo program // using the `new` constructor and the `send`/`recv` pair // of methods - let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) + let demo_program = env + .deploy(code_id, vec![]) .new(None, Some((1, -1))) .await .unwrap(); @@ -235,7 +239,8 @@ async fn dog_walks() { // Use generated client code for activating Demo program // using the `new` constructor and the `send`/`recv` pair // of methods - let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) + let demo_program = env + .deploy(code_id, vec![]) .new(None, Some((1, -1))) .await .unwrap(); @@ -273,7 +278,8 @@ async fn dog_weights() { // Use generated client code for activating Demo program // using the `new` constructor and the `send`/`recv` pair // of methods - let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) + let demo_program = env + .deploy(code_id, vec![]) .new(None, Some((1, -1))) .await .unwrap(); @@ -291,7 +297,8 @@ async fn references_add() { // Arrange let (env, code_id, _gas_limit) = create_env(); - let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) + let demo_program = env + .deploy(code_id, vec![]) .new(None, Some((1, -1))) .await .unwrap(); @@ -309,7 +316,8 @@ async fn references_bytes() { // Arrange let (env, code_id, _gas_limit) = create_env(); - let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) + let demo_program = env + .deploy(code_id, vec![]) .new(None, Some((1, -1))) .await .unwrap(); @@ -330,7 +338,8 @@ async fn references_guess_num() { // Arrange let (env, code_id, _gas_limit) = create_env(); - let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) + let demo_program = env + .deploy(code_id, vec![]) .new(None, Some((1, -1))) .await .unwrap(); @@ -357,7 +366,8 @@ async fn counter_add_works_via_next_mode() { let (env, code_id, _gas_limit) = create_env(); let env = env.with_block_run_mode(BlockRunMode::Next); - let demo_program = DemoClientProgram::deploy(env, code_id, vec![]) + let demo_program = env + .deploy(code_id, vec![]) .new(Some(42), None) .await .unwrap(); @@ -385,7 +395,8 @@ async fn counter_add_works_via_manual_mode() { let (env, code_id, _gas_limit) = create_env(); let env = env.with_block_run_mode(BlockRunMode::Next); - let pending_ctor = DemoClientProgram::deploy(env.clone(), code_id, vec![]) + let pending_ctor = env + .deploy(code_id, vec![]) .new(Some(42), None) .create_program() .unwrap(); @@ -470,7 +481,8 @@ async fn value_fee_works() { // Arrange let (env, code_id, _gas_limit) = create_env(); - let demo_program = DemoClientProgram::deploy(env.clone(), code_id, vec![]) + let demo_program = env + .deploy(code_id, vec![]) .new(Some(42), None) .await .unwrap(); @@ -506,7 +518,8 @@ async fn program_value_transfer_works() { // Arrange let (env, code_id, _gas_limit) = create_env(); - let demo_program = DemoClientProgram::deploy(env.clone(), code_id, vec![]) + let demo_program = env + .deploy(code_id, vec![]) .new(Some(42), None) .await .unwrap(); diff --git a/examples/no-svcs-prog/wasm/tests/tests.rs b/examples/no-svcs-prog/wasm/tests/tests.rs index 8d6fe1476..60777152b 100644 --- a/examples/no-svcs-prog/wasm/tests/tests.rs +++ b/examples/no-svcs-prog/wasm/tests/tests.rs @@ -14,9 +14,7 @@ async fn activating_program_succeeds() { let env = GtestEnv::new(system, ADMIN_ID.into()); - let result = NoSvcsProgProgram::deploy(env, program_code_id, vec![]) - .create() - .await; + let result = env.deploy(program_code_id, vec![]).create().await; assert!(result.is_ok()); } diff --git a/examples/proxy/src/lib.rs b/examples/proxy/src/lib.rs index c6fc34906..8632f5826 100644 --- a/examples/proxy/src/lib.rs +++ b/examples/proxy/src/lib.rs @@ -16,8 +16,7 @@ impl ProxyProgram { } pub fn this_that_caller(&self) -> this_that::ThisThatCaller> { - let this_that_client = - DemoClientProgram::client(DefaultEnv::default(), self.this_that_addr).this_that(); + let this_that_client = DemoClientProgram::client(self.this_that_addr).this_that(); this_that::ThisThatCaller::new(this_that_client) } } diff --git a/examples/proxy/src/this_that/mod.rs b/examples/proxy/src/this_that/mod.rs index e1264c53f..f5924e0dc 100644 --- a/examples/proxy/src/this_that/mod.rs +++ b/examples/proxy/src/this_that/mod.rs @@ -48,7 +48,7 @@ where mod tests { use super::*; use demo_client::mockall::MockThisThat; - use sails_rename::{client::PendingCall, gstd::services::Service, mockall::*}; + use sails_rename::{client::PendingCall, gstd::services::Service}; #[tokio::test] async fn this_that_caller_query_this() { diff --git a/examples/redirect/proxy/src/lib.rs b/examples/redirect/proxy/src/lib.rs index fc24513a5..fe6cfa9d5 100644 --- a/examples/redirect/proxy/src/lib.rs +++ b/examples/redirect/proxy/src/lib.rs @@ -16,7 +16,7 @@ impl ProxyService { /// Get program ID of the target program via client #[sails_rs::export] pub async fn get_program_id(&self) -> ActorId { - let client = RedirectClientProgram::client(GstdEnv, self.0).redirect(); + let client = RedirectClientProgram::client(self.0).redirect(); client .get_program_id() // Set flag to redirect on exit diff --git a/examples/redirect/tests/gtest.rs b/examples/redirect/tests/gtest.rs index 34adef154..78be677bc 100644 --- a/examples/redirect/tests/gtest.rs +++ b/examples/redirect/tests/gtest.rs @@ -8,10 +8,10 @@ const ACTOR_ID: u64 = 42; async fn redirect_on_exit_works() { let (env, program_code_id, proxy_code_id, _gas_limit) = create_remoting(); - let program_factory_1 = RedirectClientProgram::deploy(env.clone(), program_code_id, vec![1]); - let program_factory_2 = RedirectClientProgram::deploy(env.clone(), program_code_id, vec![2]); - let program_factory_3 = RedirectClientProgram::deploy(env.clone(), program_code_id, vec![3]); - let proxy_factory = RedirectProxyClientProgram::deploy(env.clone(), proxy_code_id, vec![]); + let program_factory_1 = env.deploy::(program_code_id, vec![1]); + let program_factory_2 = env.deploy::(program_code_id, vec![2]); + let program_factory_3 = env.deploy::(program_code_id, vec![3]); + let proxy_factory = env.deploy::(proxy_code_id, vec![]); let program_1 = program_factory_1 .new() // Call program's constructor @@ -38,12 +38,12 @@ async fn redirect_on_exit_works() { let result = proxy_client.get_program_id().await.unwrap(); assert_eq!(result, program_1.id()); - let _ = redirect_client.exit(program_2.id()).await.unwrap(); + redirect_client.exit(program_2.id()).await.unwrap(); let result = proxy_client.get_program_id().await.unwrap(); assert_eq!(result, program_2.id()); - let _ = redirect_client.exit(program_3.id()).await.unwrap(); + redirect_client.exit(program_3.id()).await.unwrap(); let result = proxy_client.get_program_id().await.unwrap(); assert_eq!(result, program_3.id()); diff --git a/examples/rmrk/resource/app/src/lib.rs b/examples/rmrk/resource/app/src/lib.rs index 58b99ae90..9025f8fa4 100644 --- a/examples/rmrk/resource/app/src/lib.rs +++ b/examples/rmrk/resource/app/src/lib.rs @@ -30,8 +30,7 @@ impl Program { // Expose hosted service #[export(route = "RmrkResource")] pub fn resource_storage(&self) -> ResourceStorage> { - let rmrk_catalog_client = - RmrkCatalogProgram::client(DefaultEnv::default(), ActorId::zero()).rmrk_catalog(); + let rmrk_catalog_client = RmrkCatalogProgram::client(ActorId::zero()).rmrk_catalog(); ResourceStorage::new(rmrk_catalog_client) } } diff --git a/examples/rmrk/resource/wasm/tests/resources.rs b/examples/rmrk/resource/wasm/tests/resources.rs index a6ea9380e..7ea8c7fb2 100644 --- a/examples/rmrk/resource/wasm/tests/resources.rs +++ b/examples/rmrk/resource/wasm/tests/resources.rs @@ -10,15 +10,12 @@ use rmrk_resource_app::services::{ }; use sails_rs::{ ActorId, Decode, Encode, - calls::{Call, Query, Remoting}, - client::{GtestEnv, GtestParams, Program as _, Service}, + client::{Program as _, *}, collections::BTreeMap, errors::Result, gtest::{BlockRunResult, Program, System}, }; -type RmrkResourceClient = client::rmrk_resource::RmrkResourceImpl; - const CATALOG_PROGRAM_WASM_PATH: &str = "../../../../target/wasm32-gear/debug/rmrk_catalog.wasm"; const RESOURCE_PROGRAM_WASM_PATH: &str = "../../../../target/wasm32-gear/debug/rmrk_resource.wasm"; @@ -437,7 +434,9 @@ impl Fixture { } fn resource_client(&self) -> Service { - RmrkResourceProgram::client(self.env.clone(), self.resource_program_id).rmrk_resource() + RmrkResourceProgram::client(self.resource_program_id) + .with_env(&self.env) + .rmrk_resource() } async fn add_resource_async( diff --git a/rs/client-gen/src/service_generators.rs b/rs/client-gen/src/service_generators.rs index 88c6137cf..a853257c4 100644 --- a/rs/client-gen/src/service_generators.rs +++ b/rs/client-gen/src/service_generators.rs @@ -5,7 +5,7 @@ use sails_idl_parser::{ast::visitor, ast::visitor::Visitor, ast::*}; use crate::events_generator::EventsModuleGenerator; use crate::helpers::*; -use crate::type_generators::{generate_type_decl_code, generate_type_decl_with_path}; +use crate::type_generators::generate_type_decl_with_path; /// Generates a trait with service methods pub(crate) struct ServiceCtorGenerator<'a> { diff --git a/rs/src/client/gclient_env.rs b/rs/src/client/gclient_env.rs index 094f06307..43a4cd64c 100644 --- a/rs/src/client/gclient_env.rs +++ b/rs/src/client/gclient_env.rs @@ -1,7 +1,7 @@ use super::*; use crate::events::Listener; use ::gclient::{Error, EventListener, EventProcessor as _, GearApi}; -use futures::{Stream, StreamExt as _, stream}; +use futures::{Stream, stream}; #[derive(Clone)] pub struct GclientEnv { diff --git a/rs/src/client/mod.rs b/rs/src/client/mod.rs index 993b2fab1..f24407432 100644 --- a/rs/src/client/mod.rs +++ b/rs/src/client/mod.rs @@ -41,15 +41,9 @@ pub trait GearEnv: Clone { type Params: Default; type Error: Error; type MessageState; -} - -pub trait Program: Sized { - fn deploy(env: E, code_id: CodeId, salt: Vec) -> Deployment { - Deployment::new(env, code_id, salt) - } - fn client(env: E, program_id: ActorId) -> Actor { - Actor::new(env, program_id) + fn deploy(&self, code_id: CodeId, salt: Vec) -> Deployment { + Deployment::new(self.clone(), code_id, salt) } } @@ -59,6 +53,16 @@ pub type DefaultEnv = MockEnv; #[cfg(feature = "gstd")] pub type DefaultEnv = GstdEnv; +pub trait Program: Sized { + fn deploy(code_id: CodeId, salt: Vec) -> Deployment { + Deployment::new(DefaultEnv::default(), code_id, salt) + } + + fn client(program_id: ActorId) -> Actor { + Actor::new(DefaultEnv::default(), program_id) + } +} + pub type Route = &'static str; #[derive(Debug, Clone)] @@ -79,7 +83,7 @@ impl Deployment { } } - pub fn with_env(self, env: N) -> Deployment { + pub fn with_env(self, env: &N) -> Deployment { let Self { env: _, code_id, @@ -87,7 +91,7 @@ impl Deployment { _phantom: _, } = self; Deployment { - env, + env: env.clone(), code_id, salt, _phantom: PhantomData, @@ -119,14 +123,14 @@ impl Actor { self.id } - pub fn with_env(self, env: N) -> Actor { + pub fn with_env(self, env: &N) -> Actor { let Self { env: _, id, _phantom: _, } = self; Actor { - env, + env: env.clone(), id, _phantom: PhantomData, } @@ -547,10 +551,10 @@ mod tests { #[test] fn test_str_encode() { - const ADD: &'static [u8] = str_scale_encode!(Add); + const ADD: &[u8] = str_scale_encode!(Add); assert_eq!(ADD, &[12, 65, 100, 100]); - const VALUE: &'static [u8] = str_scale_encode!(Value); + const VALUE: &[u8] = str_scale_encode!(Value); assert_eq!(VALUE, &[20, 86, 97, 108, 117, 101]); } From cb1f40931f9f7c3bda9a6f4a09d9dee6b6e715d1 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Tue, 2 Sep 2025 10:59:41 +0200 Subject: [PATCH 11/66] wip: remove MockEnv --- examples/demo/app/tests/gtest.rs | 2 +- examples/demo/client/src/demo_client.rs | 12 +- examples/demo/client/src/env_client.rs | 8 +- examples/proxy/src/lib.rs | 2 +- examples/proxy/src/this_that/mod.rs | 4 +- examples/rmrk/resource/app/src/lib.rs | 4 +- .../rmrk/resource/app/src/rmrk_catalog.rs | 2 +- .../rmrk/resource/app/src/services/mod.rs | 6 +- rs/client-gen/src/mock_generator.rs | 4 +- .../snapshots/generator__basic_works.snap | 2 +- .../snapshots/generator__events_works.snap | 2 +- .../tests/snapshots/generator__full.snap | 2 +- .../generator__multiple_services.snap | 4 +- .../snapshots/generator__nonzero_works.snap | 2 +- .../snapshots/generator__rmrk_works.snap | 2 +- rs/src/client/gstd_env.rs | 288 +++++++++++------- rs/src/client/mock_env.rs | 77 ----- rs/src/client/mod.rs | 21 +- 18 files changed, 214 insertions(+), 230 deletions(-) delete mode 100644 rs/src/client/mock_env.rs diff --git a/examples/demo/app/tests/gtest.rs b/examples/demo/app/tests/gtest.rs index da305e51c..72c158fee 100644 --- a/examples/demo/app/tests/gtest.rs +++ b/examples/demo/app/tests/gtest.rs @@ -2,7 +2,7 @@ use demo_client::*; use gstd::errors::ExtError; use gtest::TestError; use sails_rs::{ - client::{Program as _, *}, + client::*, futures::StreamExt as _, gtest::{Program, System}, prelude::*, diff --git a/examples/demo/client/src/demo_client.rs b/examples/demo/client/src/demo_client.rs index bf0557e25..fe3bdba6b 100644 --- a/examples/demo/client/src/demo_client.rs +++ b/examples/demo/client/src/demo_client.rs @@ -390,10 +390,10 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub PingPong {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl ping_pong::PingPong for PingPong { type Env = MockEnv; fn ping (&mut self, input: String) -> PendingCall; } } - mock! { pub Counter {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl counter::Counter for Counter { type Env = MockEnv; fn add (&mut self, value: u32) -> PendingCall;fn sub (&mut self, value: u32) -> PendingCall;fn value (& self, ) -> PendingCall; } } - mock! { pub Dog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl dog::Dog for Dog { type Env = MockEnv; fn make_sound (&mut self, ) -> PendingCall;fn walk (&mut self, dx: i32, dy: i32) -> PendingCall;fn avg_weight (& self, ) -> PendingCall;fn position (& self, ) -> PendingCall; } } - mock! { pub References {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl references::References for References { type Env = MockEnv; fn add (&mut self, v: u32) -> PendingCall;fn add_byte (&mut self, byte: u8) -> PendingCall;fn guess_num (&mut self, number: u8) -> PendingCall;fn incr (&mut self, ) -> PendingCall;fn set_num (&mut self, number: u8) -> PendingCall;fn baked (& self, ) -> PendingCall;fn last_byte (& self, ) -> PendingCall;fn message (& self, ) -> PendingCall; } } - mock! { pub ThisThat {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl this_that::ThisThat for ThisThat { type Env = MockEnv; fn do_that (&mut self, param: DoThatParam) -> PendingCall;fn do_this (&mut self, p1: u32, p2: String, p3: (Option,NonZeroU8,), p4: TupleStruct) -> PendingCall;fn noop (&mut self, ) -> PendingCall;fn that (& self, ) -> PendingCall;fn this (& self, ) -> PendingCall; } } - mock! { pub ValueFee {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl value_fee::ValueFee for ValueFee { type Env = MockEnv; fn do_something_and_take_fee (&mut self, ) -> PendingCall; } } + mock! { pub PingPong {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl ping_pong::PingPong for PingPong { type Env = GstdEnv; fn ping (&mut self, input: String) -> PendingCall; } } + mock! { pub Counter {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl counter::Counter for Counter { type Env = GstdEnv; fn add (&mut self, value: u32) -> PendingCall;fn sub (&mut self, value: u32) -> PendingCall;fn value (& self, ) -> PendingCall; } } + mock! { pub Dog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl dog::Dog for Dog { type Env = GstdEnv; fn make_sound (&mut self, ) -> PendingCall;fn walk (&mut self, dx: i32, dy: i32) -> PendingCall;fn avg_weight (& self, ) -> PendingCall;fn position (& self, ) -> PendingCall; } } + mock! { pub References {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl references::References for References { type Env = GstdEnv; fn add (&mut self, v: u32) -> PendingCall;fn add_byte (&mut self, byte: u8) -> PendingCall;fn guess_num (&mut self, number: u8) -> PendingCall;fn incr (&mut self, ) -> PendingCall;fn set_num (&mut self, number: u8) -> PendingCall;fn baked (& self, ) -> PendingCall;fn last_byte (& self, ) -> PendingCall;fn message (& self, ) -> PendingCall; } } + mock! { pub ThisThat {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl this_that::ThisThat for ThisThat { type Env = GstdEnv; fn do_that (&mut self, param: DoThatParam) -> PendingCall;fn do_this (&mut self, p1: u32, p2: String, p3: (Option,NonZeroU8,), p4: TupleStruct) -> PendingCall;fn noop (&mut self, ) -> PendingCall;fn that (& self, ) -> PendingCall;fn this (& self, ) -> PendingCall; } } + mock! { pub ValueFee {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl value_fee::ValueFee for ValueFee { type Env = GstdEnv; fn do_something_and_take_fee (&mut self, ) -> PendingCall; } } } diff --git a/examples/demo/client/src/env_client.rs b/examples/demo/client/src/env_client.rs index 69ddd9c8e..bbb19240c 100644 --- a/examples/demo/client/src/env_client.rs +++ b/examples/demo/client/src/env_client.rs @@ -123,10 +123,10 @@ pub mod mockall { #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl counter::Counter for Counter { - type Env = MockEnv; - fn add (&mut self, value: u32) -> PendingCall; - fn sub (&mut self, value: u32) -> PendingCall; - fn value (& self, ) -> PendingCall; + type Env = GstdEnv; + fn add (&mut self, value: u32) -> PendingCall; + fn sub (&mut self, value: u32) -> PendingCall; + fn value (& self, ) -> PendingCall; } } } diff --git a/examples/proxy/src/lib.rs b/examples/proxy/src/lib.rs index 8632f5826..771eef765 100644 --- a/examples/proxy/src/lib.rs +++ b/examples/proxy/src/lib.rs @@ -15,7 +15,7 @@ impl ProxyProgram { Self { this_that_addr } } - pub fn this_that_caller(&self) -> this_that::ThisThatCaller> { + pub fn this_that_caller(&self) -> this_that::ThisThatCaller> { let this_that_client = DemoClientProgram::client(self.this_that_addr).this_that(); this_that::ThisThatCaller::new(this_that_client) } diff --git a/examples/proxy/src/this_that/mod.rs b/examples/proxy/src/this_that/mod.rs index f5924e0dc..423ff4fd0 100644 --- a/examples/proxy/src/this_that/mod.rs +++ b/examples/proxy/src/this_that/mod.rs @@ -6,7 +6,7 @@ pub struct ThisThatCaller { } impl ThisThatCaller where - ThisThatClient: ThisThat, + ThisThatClient: ThisThat, { pub const fn new(this_that: ThisThatClient) -> Self { Self { this_that } @@ -16,7 +16,7 @@ where #[service(crate = sails_rename)] impl ThisThatCaller where - ThisThatClient: ThisThat, + ThisThatClient: ThisThat, { #[export] pub async fn call_do_this( diff --git a/examples/rmrk/resource/app/src/lib.rs b/examples/rmrk/resource/app/src/lib.rs index 9025f8fa4..5c681cac9 100644 --- a/examples/rmrk/resource/app/src/lib.rs +++ b/examples/rmrk/resource/app/src/lib.rs @@ -23,13 +23,13 @@ pub struct Program; impl Program { // Initialize program and seed hosted services pub fn new() -> Self { - ResourceStorage::>::seed(); + ResourceStorage::>::seed(); Self } // Expose hosted service #[export(route = "RmrkResource")] - pub fn resource_storage(&self) -> ResourceStorage> { + pub fn resource_storage(&self) -> ResourceStorage> { let rmrk_catalog_client = RmrkCatalogProgram::client(ActorId::zero()).rmrk_catalog(); ResourceStorage::new(rmrk_catalog_client) } diff --git a/examples/rmrk/resource/app/src/rmrk_catalog.rs b/examples/rmrk/resource/app/src/rmrk_catalog.rs index 20221e2d3..276ca2bf7 100644 --- a/examples/rmrk/resource/app/src/rmrk_catalog.rs +++ b/examples/rmrk/resource/app/src/rmrk_catalog.rs @@ -177,5 +177,5 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub RmrkCatalog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl rmrk_catalog::RmrkCatalog for RmrkCatalog { type Env = MockEnv; fn add_equippables (&mut self, part_id: u32, collection_ids: Vec) -> PendingCall;fn add_parts (&mut self, parts: BTreeMap) -> PendingCall;fn remove_equippable (&mut self, part_id: u32, collection_id: ActorId) -> PendingCall;fn remove_parts (&mut self, part_ids: Vec) -> PendingCall;fn reset_equippables (&mut self, part_id: u32) -> PendingCall;fn set_equippables_to_all (&mut self, part_id: u32) -> PendingCall;fn equippable (& self, part_id: u32, collection_id: ActorId) -> PendingCall;fn part (& self, part_id: u32) -> PendingCall; } } + mock! { pub RmrkCatalog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl rmrk_catalog::RmrkCatalog for RmrkCatalog { type Env = GstdEnv; fn add_equippables (&mut self, part_id: u32, collection_ids: Vec) -> PendingCall;fn add_parts (&mut self, parts: BTreeMap) -> PendingCall;fn remove_equippable (&mut self, part_id: u32, collection_id: ActorId) -> PendingCall;fn remove_parts (&mut self, part_ids: Vec) -> PendingCall;fn reset_equippables (&mut self, part_id: u32) -> PendingCall;fn set_equippables_to_all (&mut self, part_id: u32) -> PendingCall;fn equippable (& self, part_id: u32, collection_id: ActorId) -> PendingCall;fn part (& self, part_id: u32) -> PendingCall; } } } diff --git a/examples/rmrk/resource/app/src/services/mod.rs b/examples/rmrk/resource/app/src/services/mod.rs index f3743a2ec..46f76537b 100644 --- a/examples/rmrk/resource/app/src/services/mod.rs +++ b/examples/rmrk/resource/app/src/services/mod.rs @@ -2,7 +2,7 @@ use crate::catalogs::rmrk_catalog::RmrkCatalog; use errors::{Error, Result}; use resources::{ComposedResource, PartId, Resource, ResourceId}; use sails_rs::{ - client::DefaultEnv, + client::*, collections::HashMap, gstd::{Syscall, service}, prelude::*, @@ -41,7 +41,7 @@ pub struct ResourceStorage { impl ResourceStorage where - TCatalogClient: RmrkCatalog, + TCatalogClient: RmrkCatalog, { // This function needs to be called before any other function pub fn seed() { @@ -60,7 +60,7 @@ where #[service(events = ResourceStorageEvent)] impl ResourceStorage where - TCatalogClient: RmrkCatalog, + TCatalogClient: RmrkCatalog, { #[export] pub fn add_resource_entry( diff --git a/rs/client-gen/src/mock_generator.rs b/rs/client-gen/src/mock_generator.rs index ec9b8e50b..e37883707 100644 --- a/rs/client-gen/src/mock_generator.rs +++ b/rs/client-gen/src/mock_generator.rs @@ -26,7 +26,7 @@ impl<'a> MockGenerator<'a> { #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl $service_name_snake::$(self.service_name) for $(self.service_name) { - type Env = MockEnv; + type Env = GstdEnv; $(self.tokens) } } @@ -47,7 +47,7 @@ impl<'ast> Visitor<'ast> for MockGenerator<'_> { let params_with_types = &fn_args_with_types(func.params()); quote_in! { self.tokens => - fn $fn_name_snake (&$mutability self, $params_with_types) -> PendingCall; + fn $fn_name_snake (&$mutability self, $params_with_types) -> PendingCall; }; } } diff --git a/rs/client-gen/tests/snapshots/generator__basic_works.snap b/rs/client-gen/tests/snapshots/generator__basic_works.snap index f7e9687c6..64712dd34 100644 --- a/rs/client-gen/tests/snapshots/generator__basic_works.snap +++ b/rs/client-gen/tests/snapshots/generator__basic_works.snap @@ -69,5 +69,5 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub Basic {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl basic::Basic for Basic { type Env = MockEnv; fn do_this (&mut self, p1: u32, p2: MyParam) -> PendingCall;fn do_that (&mut self, p1: (u8,u32,)) -> PendingCall; } } + mock! { pub Basic {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl basic::Basic for Basic { type Env = GstdEnv; fn do_this (&mut self, p1: u32, p2: MyParam) -> PendingCall;fn do_that (&mut self, p1: (u8,u32,)) -> PendingCall; } } } diff --git a/rs/client-gen/tests/snapshots/generator__events_works.snap b/rs/client-gen/tests/snapshots/generator__events_works.snap index b7e4b7e33..8418a7f71 100644 --- a/rs/client-gen/tests/snapshots/generator__events_works.snap +++ b/rs/client-gen/tests/snapshots/generator__events_works.snap @@ -81,5 +81,5 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub ServiceWithEvents {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl service_with_events::ServiceWithEvents for ServiceWithEvents { type Env = MockEnv; fn do_this (&mut self, p1: NonZeroU256, p2: MyParam) -> PendingCall; } } + mock! { pub ServiceWithEvents {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl service_with_events::ServiceWithEvents for ServiceWithEvents { type Env = GstdEnv; fn do_this (&mut self, p1: NonZeroU256, p2: MyParam) -> PendingCall; } } } diff --git a/rs/client-gen/tests/snapshots/generator__full.snap b/rs/client-gen/tests/snapshots/generator__full.snap index d50994598..160ea05f3 100644 --- a/rs/client-gen/tests/snapshots/generator__full.snap +++ b/rs/client-gen/tests/snapshots/generator__full.snap @@ -170,5 +170,5 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub Service {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl service::Service for Service { type Env = MockEnv; fn do_this (&mut self, p1: u32, p2: String, p3: (Option,u8,), p4: ThisThatSvcAppTupleStruct) -> PendingCall;fn do_that (&mut self, param: ThisThatSvcAppDoThatParam) -> PendingCall;fn this (& self, v1: Vec) -> PendingCall;fn that (& self, v1: ()) -> PendingCall; } } + mock! { pub Service {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl service::Service for Service { type Env = GstdEnv; fn do_this (&mut self, p1: u32, p2: String, p3: (Option,u8,), p4: ThisThatSvcAppTupleStruct) -> PendingCall;fn do_that (&mut self, param: ThisThatSvcAppDoThatParam) -> PendingCall;fn this (& self, v1: Vec) -> PendingCall;fn that (& self, v1: ()) -> PendingCall; } } } diff --git a/rs/client-gen/tests/snapshots/generator__multiple_services.snap b/rs/client-gen/tests/snapshots/generator__multiple_services.snap index f53daec1d..4506456d6 100644 --- a/rs/client-gen/tests/snapshots/generator__multiple_services.snap +++ b/rs/client-gen/tests/snapshots/generator__multiple_services.snap @@ -75,6 +75,6 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub Multiple {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl multiple::Multiple for Multiple { type Env = MockEnv; fn do_this (&mut self, p1: u32, p2: MyParam) -> PendingCall;fn do_that (&mut self, p1: (u8,u32,)) -> PendingCall; } } - mock! { pub Named {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl named::Named for Named { type Env = MockEnv; fn that (& self, p1: u32) -> PendingCall; } } + mock! { pub Multiple {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl multiple::Multiple for Multiple { type Env = GstdEnv; fn do_this (&mut self, p1: u32, p2: MyParam) -> PendingCall;fn do_that (&mut self, p1: (u8,u32,)) -> PendingCall; } } + mock! { pub Named {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl named::Named for Named { type Env = GstdEnv; fn that (& self, p1: u32) -> PendingCall; } } } diff --git a/rs/client-gen/tests/snapshots/generator__nonzero_works.snap b/rs/client-gen/tests/snapshots/generator__nonzero_works.snap index 4e858659d..53f24e2a7 100644 --- a/rs/client-gen/tests/snapshots/generator__nonzero_works.snap +++ b/rs/client-gen/tests/snapshots/generator__nonzero_works.snap @@ -54,5 +54,5 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub NonZeroParams {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl non_zero_params::NonZeroParams for NonZeroParams { type Env = MockEnv; fn do_this (&mut self, p1: NonZeroU256, p2: MyParam) -> PendingCall; } } + mock! { pub NonZeroParams {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl non_zero_params::NonZeroParams for NonZeroParams { type Env = GstdEnv; fn do_this (&mut self, p1: NonZeroU256, p2: MyParam) -> PendingCall; } } } diff --git a/rs/client-gen/tests/snapshots/generator__rmrk_works.snap b/rs/client-gen/tests/snapshots/generator__rmrk_works.snap index a2cb51b3a..4c3c64003 100644 --- a/rs/client-gen/tests/snapshots/generator__rmrk_works.snap +++ b/rs/client-gen/tests/snapshots/generator__rmrk_works.snap @@ -181,5 +181,5 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub RmrkCatalog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl rmrk_catalog::RmrkCatalog for RmrkCatalog { type Env = MockEnv; fn add_equippables (&mut self, part_id: u32, collection_ids: Vec) -> PendingCall;fn add_parts (&mut self, parts: BTreeMap) -> PendingCall;fn remove_equippable (&mut self, part_id: u32, collection_id: ActorId) -> PendingCall;fn remove_parts (&mut self, part_ids: Vec) -> PendingCall;fn reset_equippables (&mut self, part_id: u32) -> PendingCall;fn set_equippables_to_all (&mut self, part_id: u32) -> PendingCall;fn equippable (& self, part_id: u32, collection_id: ActorId) -> PendingCall;fn part (& self, part_id: u32) -> PendingCall; } } + mock! { pub RmrkCatalog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl rmrk_catalog::RmrkCatalog for RmrkCatalog { type Env = GstdEnv; fn add_equippables (&mut self, part_id: u32, collection_ids: Vec) -> PendingCall;fn add_parts (&mut self, parts: BTreeMap) -> PendingCall;fn remove_equippable (&mut self, part_id: u32, collection_id: ActorId) -> PendingCall;fn remove_parts (&mut self, part_ids: Vec) -> PendingCall;fn reset_equippables (&mut self, part_id: u32) -> PendingCall;fn set_equippables_to_all (&mut self, part_id: u32) -> PendingCall;fn equippable (& self, part_id: u32, collection_id: ActorId) -> PendingCall;fn part (& self, part_id: u32) -> PendingCall; } } } diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index 310aef6ff..b459b3334 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -66,7 +66,10 @@ pub struct GstdEnv; impl GearEnv for GstdEnv { type Params = GstdParams; type Error = Error; + #[cfg(target_arch = "wasm32")] type MessageState = GtsdFuture; + #[cfg(not(target_arch = "wasm32"))] + type MessageState = core::future::Ready, Self::Error>>; } #[cfg(not(feature = "ethexe"))] @@ -117,130 +120,133 @@ pub(crate) fn send_for_reply_future( Ok(message_future) } -impl PendingCall { - pub fn send(mut self) -> Result { - let route = self - .route - .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let params = self.params.unwrap_or_default(); - let args = self - .args - .take() - .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(route, &args); - - let value = params.value.unwrap_or(0); - if let Some(gas_limit) = params.gas_limit { - ::gcore::msg::send_with_gas(self.destination, payload.as_slice(), gas_limit, value) - .map_err(Error::Core) - } else { - ::gcore::msg::send(self.destination, payload.as_slice(), value).map_err(Error::Core) - } - } -} - -impl Future for PendingCall { - type Output = Result::Error>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let Some(route) = self.route else { - return Poll::Ready(Err(Error::Decode("PendingCall route is not set".into()))); - }; - if self.state.is_none() { - // Send message +#[cfg(target_arch = "wasm32")] +const _: () = { + impl PendingCall { + pub fn send(mut self) -> Result { + let route = self + .route + .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); + let params = self.params.unwrap_or_default(); let args = self .args .take() .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); let payload = T::encode_params_with_prefix(route, &args); - let params = self.params.take().unwrap_or_default(); - let send_res = send_for_reply_future(self.destination, payload.as_slice(), params); - match send_res { - Ok(future) => { - self.state = Some(GtsdFuture::Message { future }); - } - Err(err) => { - return Poll::Ready(Err(err)); - } - } - } - let this = self.as_mut().project(); - if let Some(state) = this.state.as_pin_mut() - && let Projection::Message { future } = state.project() - { - // Poll message future - match future.poll(cx) { - Poll::Ready(Ok(payload)) => match T::decode_reply_with_prefix(route, payload) { - Ok(reply) => Poll::Ready(Ok(reply)), - Err(err) => Poll::Ready(Err(Error::Decode(err))), - }, - Poll::Ready(Err(err)) => Poll::Ready(Err(err)), - Poll::Pending => Poll::Pending, + let value = params.value.unwrap_or(0); + if let Some(gas_limit) = params.gas_limit { + ::gcore::msg::send_with_gas(self.destination, payload.as_slice(), gas_limit, value) + .map_err(Error::Core) + } else { + ::gcore::msg::send(self.destination, payload.as_slice(), value).map_err(Error::Core) } - } else { - panic!("{PENDING_CALL_INVALID_STATE}"); } } -} -impl Future for PendingCtor { - type Output = Result, ::Error>; + impl Future for PendingCall { + type Output = Result::Error>; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if self.state.is_none() { - // Send message - let payload = self.encode_ctor(); - let params = self.params.take().unwrap_or_default(); - let value = params.value.unwrap_or(0); - let salt = self.salt.take().unwrap(); - - #[cfg(not(feature = "ethexe"))] - let program_future = if let Some(gas_limit) = params.gas_limit { - gstd::prog::create_program_bytes_with_gas_for_reply( - self.code_id, - salt, - payload, - gas_limit, - value, - params.reply_deposit.unwrap_or_default(), - )? - } else { - gstd::prog::create_program_bytes_for_reply( - self.code_id, - salt, - payload, - value, - params.reply_deposit.unwrap_or_default(), - )? + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let Some(route) = self.route else { + return Poll::Ready(Err(Error::Decode("PendingCall route is not set".into()))); }; - #[cfg(feature = "ethexe")] - let mut program_future = - prog::create_program_bytes_for_reply(code_id, salt, payload, value)?; - - // self.program_id = Some(program_future.program_id); - self.state = Some(GtsdFuture::CreateProgram { - future: program_future, - }); + if self.state.is_none() { + // Send message + let args = self + .args + .take() + .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); + let payload = T::encode_params_with_prefix(route, &args); + let params = self.params.take().unwrap_or_default(); + + let send_res = send_for_reply_future(self.destination, payload.as_slice(), params); + match send_res { + Ok(future) => { + self.state = Some(GtsdFuture::Message { future }); + } + Err(err) => { + return Poll::Ready(Err(err)); + } + } + } + let this = self.as_mut().project(); + if let Some(state) = this.state.as_pin_mut() + && let Projection::Message { future } = state.project() + { + // Poll message future + match future.poll(cx) { + Poll::Ready(Ok(payload)) => match T::decode_reply_with_prefix(route, payload) { + Ok(reply) => Poll::Ready(Ok(reply)), + Err(err) => Poll::Ready(Err(Error::Decode(err))), + }, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Pending => Poll::Pending, + } + } else { + panic!("{PENDING_CALL_INVALID_STATE}"); + } } - let this = self.as_mut().project(); - if let Some(state) = this.state.as_pin_mut() - && let Projection::CreateProgram { future } = state.project() - { - // Poll create program future - match future.poll(cx) { - Poll::Ready(Ok((program_id, _payload))) => { - // Do not decode payload here - Poll::Ready(Ok(Actor::new(this.env.clone(), program_id))) + } + + impl Future for PendingCtor { + type Output = Result, ::Error>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.state.is_none() { + // Send message + let payload = self.encode_ctor(); + let params = self.params.take().unwrap_or_default(); + let value = params.value.unwrap_or(0); + let salt = self.salt.take().unwrap(); + + #[cfg(not(feature = "ethexe"))] + let program_future = if let Some(gas_limit) = params.gas_limit { + gstd::prog::create_program_bytes_with_gas_for_reply( + self.code_id, + salt, + payload, + gas_limit, + value, + params.reply_deposit.unwrap_or_default(), + )? + } else { + gstd::prog::create_program_bytes_for_reply( + self.code_id, + salt, + payload, + value, + params.reply_deposit.unwrap_or_default(), + )? + }; + #[cfg(feature = "ethexe")] + let mut program_future = + prog::create_program_bytes_for_reply(code_id, salt, payload, value)?; + + // self.program_id = Some(program_future.program_id); + self.state = Some(GtsdFuture::CreateProgram { + future: program_future, + }); + } + let this = self.as_mut().project(); + if let Some(state) = this.state.as_pin_mut() + && let Projection::CreateProgram { future } = state.project() + { + // Poll create program future + match future.poll(cx) { + Poll::Ready(Ok((program_id, _payload))) => { + // Do not decode payload here + Poll::Ready(Ok(Actor::new(this.env.clone(), program_id))) + } + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Pending => Poll::Pending, } - Poll::Ready(Err(err)) => Poll::Ready(Err(err)), - Poll::Pending => Poll::Pending, + } else { + panic!("{PENDING_CTOR_INVALID_STATE}"); } - } else { - panic!("{PENDING_CTOR_INVALID_STATE}"); } } -} +}; pin_project_lite::pin_project! { #[project = Projection] @@ -249,3 +255,71 @@ pin_project_lite::pin_project! { Message { #[pin] future: MessageFuture }, } } + +#[cfg(not(target_arch = "wasm32"))] +const _: () = { + impl PendingCall + where + T::Reply: Encode + Decode, + { + pub fn from_output(output: T::Reply) -> Self { + Self::from_result(Ok(output)) + } + + pub fn from_error(err: ::Error) -> Self { + Self::from_result(Err(err)) + } + + pub fn from_result(res: Result::Error>) -> Self { + PendingCall { + env: GstdEnv, + destination: ActorId::zero(), + route: None, + params: None, + args: None, + state: Some(future::ready(res.map(|v| v.encode()))), + } + } + } + + impl, O> From for PendingCall + where + O: Encode + Decode, + { + fn from(value: O) -> Self { + PendingCall::from_output(value) + } + } + + impl Future for PendingCall { + type Output = Result::Error>; + + fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + match self.state.take() { + Some(ready) => { + let res = ready.into_inner(); + Poll::Ready(res.map(|v| T::Reply::decode(&mut v.as_slice()).unwrap())) + } + None => panic!("{PENDING_CALL_INVALID_STATE}"), + } + } + } + + impl Future for PendingCtor { + type Output = Result, ::Error>; + + fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + match self.state.take() { + Some(_ready) => { + let program_id = self + .program_id + .take() + .unwrap_or_else(|| panic!("{PENDING_CTOR_INVALID_STATE}")); + let env = self.env.clone(); + Poll::Ready(Ok(Actor::new(env, program_id))) + } + None => panic!("{PENDING_CTOR_INVALID_STATE}"), + } + } + } +}; diff --git a/rs/src/client/mock_env.rs b/rs/src/client/mock_env.rs deleted file mode 100644 index ea23cf0ef..000000000 --- a/rs/src/client/mock_env.rs +++ /dev/null @@ -1,77 +0,0 @@ -use super::*; - -#[derive(Default, Clone)] -pub struct MockEnv; - -crate::params_struct_impl!(MockEnv, MockParams {}); - -impl GearEnv for MockEnv { - type Error = ::gstd::errors::Error; - type Params = MockParams; - type MessageState = core::future::Ready, Self::Error>>; -} - -impl PendingCall -where - T::Reply: Encode + Decode, -{ - pub fn from_output(output: T::Reply) -> Self { - Self::from_result(Ok(output)) - } - - pub fn from_error(err: ::Error) -> Self { - Self::from_result(Err(err)) - } - - pub fn from_result(res: Result::Error>) -> Self { - PendingCall { - env: mock_env::MockEnv, - destination: ActorId::zero(), - route: None, - params: None, - args: None, - state: Some(future::ready(res.map(|v| v.encode()))), - } - } -} - -impl, O> From for PendingCall -where - O: Encode + Decode, -{ - fn from(value: O) -> Self { - PendingCall::from_output(value) - } -} - -impl Future for PendingCall { - type Output = Result::Error>; - - fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { - match self.state.take() { - Some(ready) => { - let res = ready.into_inner(); - Poll::Ready(res.map(|v| T::Reply::decode(&mut v.as_slice()).unwrap())) - } - None => panic!("{PENDING_CALL_INVALID_STATE}"), - } - } -} - -impl Future for PendingCtor { - type Output = Result, ::Error>; - - fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { - match self.state.take() { - Some(_ready) => { - let program_id = self - .program_id - .take() - .unwrap_or_else(|| panic!("{PENDING_CTOR_INVALID_STATE}")); - let env = self.env.clone(); - Poll::Ready(Ok(Actor::new(env, program_id))) - } - None => panic!("{PENDING_CTOR_INVALID_STATE}"), - } - } -} diff --git a/rs/src/client/mod.rs b/rs/src/client/mod.rs index f24407432..16bce6db9 100644 --- a/rs/src/client/mod.rs +++ b/rs/src/client/mod.rs @@ -8,11 +8,6 @@ use core::{ }; use futures::{Stream, StreamExt as _}; -#[cfg(not(target_arch = "wasm32"))] -mod mock_env; -#[cfg(not(target_arch = "wasm32"))] -pub use mock_env::{MockEnv, MockParams}; - #[cfg(feature = "gtest")] #[cfg(not(target_arch = "wasm32"))] mod gtest_env; @@ -27,9 +22,7 @@ mod gclient_env; #[cfg(not(target_arch = "wasm32"))] pub use gclient_env::{GclientEnv, GclientParams}; -#[cfg(feature = "gstd")] mod gstd_env; -#[cfg(feature = "gstd")] pub use gstd_env::{GstdEnv, GstdParams}; pub(crate) const PENDING_CALL_INVALID_STATE: &str = @@ -47,19 +40,13 @@ pub trait GearEnv: Clone { } } -#[cfg(not(target_arch = "wasm32"))] -pub type DefaultEnv = MockEnv; -#[cfg(target_arch = "wasm32")] -#[cfg(feature = "gstd")] -pub type DefaultEnv = GstdEnv; - pub trait Program: Sized { - fn deploy(code_id: CodeId, salt: Vec) -> Deployment { - Deployment::new(DefaultEnv::default(), code_id, salt) + fn deploy(code_id: CodeId, salt: Vec) -> Deployment { + Deployment::new(GstdEnv, code_id, salt) } - fn client(program_id: ActorId) -> Actor { - Actor::new(DefaultEnv::default(), program_id) + fn client(program_id: ActorId) -> Actor { + Actor::new(GstdEnv, program_id) } } From 07eb92d924f30ee669b6bb577de37cfe7084cb8c Mon Sep 17 00:00:00 2001 From: vobradovich Date: Tue, 2 Sep 2025 14:05:38 +0200 Subject: [PATCH 12/66] wip: gclient tests --- .../demo/app/tests/{fixture => }/gclient.rs | 170 +++++++----------- examples/demo/app/tests/gtest.rs | 10 +- .../rmrk/resource/wasm/tests/resources.rs | 8 +- rs/src/client/gclient_env.rs | 151 ++++++++++------ rs/src/client/gtest_env.rs | 71 ++++---- rs/src/client/mod.rs | 4 +- 6 files changed, 208 insertions(+), 206 deletions(-) rename examples/demo/app/tests/{fixture => }/gclient.rs (55%) diff --git a/examples/demo/app/tests/fixture/gclient.rs b/examples/demo/app/tests/gclient.rs similarity index 55% rename from examples/demo/app/tests/fixture/gclient.rs rename to examples/demo/app/tests/gclient.rs index fb8733b6d..9c55faa05 100644 --- a/examples/demo/app/tests/fixture/gclient.rs +++ b/examples/demo/app/tests/gclient.rs @@ -1,9 +1,7 @@ -use demo_client::{counter::events::*, ping_pong, traits::*}; +use demo_client::{counter::events::*, counter::*, ping_pong::*, value_fee::*, *}; use gclient::GearApi; use gstd::errors::{ErrorReplyReason, SimpleExecutionError}; -use sails_rs::{ - calls::*, errors::RtlError, events::*, futures::StreamExt, gclient::calls::*, prelude::*, -}; +use sails_rs::{client::*, futures::StreamExt, prelude::*}; use std::panic; #[cfg(debug_assertions)] @@ -16,36 +14,32 @@ pub(crate) const DEMO_WASM_PATH: &str = "../../../target/wasm32-gear/release/dem async fn counter_add_works() { // Arrange - let (remoting, demo_code_id, gas_limit, gear_api) = spin_up_node_with_demo_code().await; + let (env, demo_code_id, gas_limit, gear_api) = spin_up_node_with_demo_code().await; let admin_id = ActorId::try_from(gear_api.account_id().encode().as_ref()) .expect("failed to create actor id"); - let demo_factory = demo_client::DemoClientFactory::new(remoting.clone()); - // Use generated client code for activating Demo program - // using the `new` constructor and the `send_recv` method - let demo_program_id = demo_factory + // using the `new` constructor + let demo_program = env + .deploy::(demo_code_id, vec![]) .new(Some(42), None) .with_gas_limit(gas_limit) - .send_recv(demo_code_id, "123") .await .unwrap(); let initial_balance = gear_api.free_balance(admin_id).await.unwrap(); - let mut counter_client = demo_client::Counter::new(remoting.clone()); + let mut counter_client = demo_program.counter(); // Listen to Counter events - let mut counter_listener = demo_client::counter::events::listener(remoting.clone()); + let mut counter_listener = counter_client.listener(); let mut counter_events = counter_listener.listen().await.unwrap(); // Act // Use generated client code for calling Counter service - // using the `send_recv` method let result = counter_client .add(10) .with_gas_limit(gas_limit) - .send_recv(demo_program_id) .await .unwrap(); @@ -57,7 +51,7 @@ async fn counter_add_works() { let event = counter_events.next().await.unwrap(); assert_eq!(result, 52); - assert_eq!((demo_program_id, CounterEvents::Added(10)), event); + assert_eq!((demo_program.id(), CounterEvents::Added(10)), event); } #[tokio::test] @@ -65,44 +59,38 @@ async fn counter_add_works() { async fn counter_sub_works() { // Arrange - let (remoting, demo_code_id, gas_limit, ..) = spin_up_node_with_demo_code().await; - - let demo_factory = demo_client::DemoClientFactory::new(remoting.clone()); + let (env, demo_code_id, gas_limit, ..) = spin_up_node_with_demo_code().await; // Use generated client code for activating Demo program // using the `new` constructor and the `send`/`recv` pair // of methods - let activation = demo_factory + let demo_program = env + .deploy::(demo_code_id, vec![]) .new(Some(42), None) .with_gas_limit(gas_limit) - .send(demo_code_id, "123") .await .unwrap(); - let demo_program_id = activation.recv().await.unwrap(); - let mut counter_client = demo_client::Counter::new(remoting.clone()); + let mut counter_client = demo_program.counter(); // Listen to Counter events - let mut counter_listener = demo_client::counter::events::listener(remoting.clone()); + let mut counter_listener = counter_client.listener(); let mut counter_events = counter_listener.listen().await.unwrap(); // Act // Use generated client code for calling Counter service // using the `send`/`recv` pair of methods - let response = counter_client + let result = counter_client .sub(10) .with_gas_limit(gas_limit) - .send(demo_program_id) .await .unwrap(); - let result = response.recv().await.unwrap(); // Assert - let event = counter_events.next().await.unwrap(); assert_eq!(result, 32); - assert_eq!((demo_program_id, CounterEvents::Subtracted(10)), event); + assert_eq!((demo_program.id(), CounterEvents::Subtracted(10)), event); } #[tokio::test] @@ -110,39 +98,34 @@ async fn counter_sub_works() { async fn ping_pong_works() { // Arrange - let (remoting, demo_code_id, gas_limit, ..) = spin_up_node_with_demo_code().await; - - let demo_factory = demo_client::DemoClientFactory::new(remoting.clone()); + let (env, demo_code_id, gas_limit, ..) = spin_up_node_with_demo_code().await; // Use generated client code for activating Demo program - // using the `default` constructor and the `send_recv` method - let demo_program_id = demo_factory + // using the `default` constructor + let demo_program = env + .deploy::(demo_code_id, vec![]) .default() .with_gas_limit(gas_limit) - .send_recv(demo_code_id, "123") .await .unwrap(); // Use generated `io` module for encoding/decoding calls and replies - // and send/receive bytes using `gclient` native means (remoting is just a wrapper) - let ping_call_payload = ping_pong::io::Ping::encode_call("ping".into()); + // and send/receive bytes using `gclient` native means (env is just a wrapper) + let ping_call_payload = + ping_pong::io::Ping::encode_params_with_prefix("PingPong", "ping".into()); // Act - - let ping_reply_payload = remoting - .message( - demo_program_id, + let ping_reply_payload = env + .send_message( + demo_program.id(), ping_call_payload, - Some(gas_limit), - 0, - GClientArgs::default(), + GclientParams::default().with_gas_limit(gas_limit), ) .await - .unwrap() - .await .unwrap(); - let ping_reply = ping_pong::io::Ping::decode_reply(ping_reply_payload).unwrap(); + let ping_reply = + ping_pong::io::Ping::decode_reply_with_prefix("PingPong", ping_reply_payload).unwrap(); // Assert @@ -153,26 +136,22 @@ async fn ping_pong_works() { #[ignore = "requires run gear node on GEAR_PATH"] async fn demo_returns_not_enough_gas_on_activation() { // Arrange - - let (remoting, demo_code_id, ..) = spin_up_node_with_demo_code().await; - - let demo_factory = demo_client::DemoClientFactory::new(remoting.clone()); + let (env, demo_code_id, ..) = spin_up_node_with_demo_code().await; // Act - - let activation_result = demo_factory + let demo_program = env + .deploy::(demo_code_id, vec![]) .default() .with_gas_limit(0) - .send_recv(demo_code_id, "123") .await; // Assert - assert!(matches!( - activation_result, - Err(sails_rs::errors::Error::Rtl(RtlError::ReplyHasErrorString( - _message - ))) + demo_program, + Err(GclientError::ReplyHasError( + ErrorReplyReason::Execution(SimpleExecutionError::RanOutOfGas), + _ + )) )); } @@ -181,25 +160,23 @@ async fn demo_returns_not_enough_gas_on_activation() { async fn counter_query_works() { // Arrange - let (remoting, demo_code_id, gas_limit, ..) = spin_up_node_with_demo_code().await; - - let demo_factory = demo_client::DemoClientFactory::new(remoting.clone()); + let (env, demo_code_id, gas_limit, ..) = spin_up_node_with_demo_code().await; // Use generated client code for activating Demo program - // using the `new` constructor and the `send_recv` method - let demo_program_id = demo_factory + // using the `new` constructor + let demo_program = env + .deploy::(demo_code_id, vec![]) .new(Some(42), None) .with_gas_limit(gas_limit) - .send_recv(demo_code_id, "123") .await .unwrap(); - let counter_client = demo_client::Counter::new(remoting.clone()); + let counter_client = demo_program.counter(); // Act - // Use generated client code for query Counter service using the `recv` method - let result = counter_client.value().recv(demo_program_id).await.unwrap(); + // Use generated client code for query Counter service using the `query` method + let result = counter_client.value().query().await.unwrap(); // Asert assert_eq!(result, 42); @@ -210,31 +187,23 @@ async fn counter_query_works() { async fn counter_query_with_message_works() { // Arrange - let (remoting, demo_code_id, gas_limit, ..) = spin_up_node_with_demo_code().await; - - let demo_factory = demo_client::DemoClientFactory::new(remoting.clone()); + let (env, demo_code_id, gas_limit, ..) = spin_up_node_with_demo_code().await; // Use generated client code for activating Demo program - // using the `new` constructor and the `send_recv` method - let demo_program_id = demo_factory + // using the `new` constructor + let demo_program = env + .deploy::(demo_code_id, vec![]) .new(Some(42), None) .with_gas_limit(gas_limit) - .send_recv(demo_code_id, "123") .await .unwrap(); - let counter_client = demo_client::Counter::new(remoting.clone()); + let counter_client = demo_program.counter(); // Act - // Use generated client code for query Counter service using the `recv` method - // Set `query_with_message` to `true` - let result = counter_client - .value() - .query_with_message(true) - .recv(demo_program_id) - .await - .unwrap(); + // Use generated client code for query Counter service + let result = counter_client.value().await.unwrap(); // Asert assert_eq!(result, 42); @@ -245,20 +214,18 @@ async fn counter_query_with_message_works() { async fn counter_query_not_enough_gas() { // Arrange - let (remoting, demo_code_id, gas_limit, ..) = spin_up_node_with_demo_code().await; - - let demo_factory = demo_client::DemoClientFactory::new(remoting.clone()); + let (env, demo_code_id, gas_limit, ..) = spin_up_node_with_demo_code().await; // Use generated client code for activating Demo program // using the `new` constructor and the `send_recv` method - let demo_program_id = demo_factory + let demo_program = env + .deploy::(demo_code_id, vec![]) .new(Some(42), None) .with_gas_limit(gas_limit) - .send_recv(demo_code_id, "123") .await .unwrap(); - let counter_client = demo_client::Counter::new(remoting.clone()); + let counter_client = demo_program.counter(); // Act @@ -266,16 +233,15 @@ async fn counter_query_not_enough_gas() { let result = counter_client .value() .with_gas_limit(0) // Set gas_limit to 0 - .recv(demo_program_id) .await; // Asert assert!(matches!( result, - Err(sails_rs::errors::Error::Rtl(RtlError::ReplyHasError( + Err(GclientError::ReplyHasError( ErrorReplyReason::Execution(SimpleExecutionError::RanOutOfGas), - _payload - ))) + _ + )) )); } @@ -283,35 +249,29 @@ async fn counter_query_not_enough_gas() { #[ignore = "requires run gear node on GEAR_PATH"] async fn value_fee_works() { // Arrange - let (remoting, demo_code_id, _gas_limit, gear_api) = spin_up_node_with_demo_code().await; + let (env, demo_code_id, _gas_limit, gear_api) = spin_up_node_with_demo_code().await; let admin_id = ActorId::try_from(gear_api.account_id().encode().as_ref()) .expect("failed to create actor id"); - let demo_factory = demo_client::DemoClientFactory::new(remoting.clone()); - let program_id = demo_factory + let demo_program = env + .deploy::(demo_code_id, vec![]) .new(Some(42), None) - .send_recv(demo_code_id, "123") .await .unwrap(); let initial_balance = gear_api.free_balance(admin_id).await.unwrap(); - let mut client = demo_client::ValueFee::new(remoting.clone()); + let mut client = demo_program.value_fee(); // Act // Use generated client code to call `do_something_and_take_fee` method with zero value - let result = client - .do_something_and_take_fee() - .send_recv(program_id) - .await - .unwrap(); + let result = client.do_something_and_take_fee().await.unwrap(); assert!(!result); // Use generated client code to call `do_something_and_take_fee` method with value let result = client .do_something_and_take_fee() .with_value(15_000_000_000_000) - .send_recv(program_id) .await .unwrap(); @@ -327,7 +287,7 @@ async fn value_fee_works() { ); } -async fn spin_up_node_with_demo_code() -> (GClientRemoting, CodeId, GasUnit, GearApi) { +async fn spin_up_node_with_demo_code() -> (GclientEnv, CodeId, GasUnit, GearApi) { let gear_path = option_env!("GEAR_PATH"); if gear_path.is_none() { panic!("the 'GEAR_PATH' environment variable was not set during compile time"); @@ -335,6 +295,6 @@ async fn spin_up_node_with_demo_code() -> (GClientRemoting, CodeId, GasUnit, Gea let api = GearApi::dev_from_path(gear_path.unwrap()).await.unwrap(); let gas_limit = api.block_gas_limit().unwrap(); let (code_id, _) = api.upload_code_by_path(DEMO_WASM_PATH).await.unwrap(); - let remoting = GClientRemoting::new(api.clone()); + let remoting = GclientEnv::new(api.clone()); (remoting, code_id, gas_limit, api) } diff --git a/examples/demo/app/tests/gtest.rs b/examples/demo/app/tests/gtest.rs index 72c158fee..0c497d68b 100644 --- a/examples/demo/app/tests/gtest.rs +++ b/examples/demo/app/tests/gtest.rs @@ -1,6 +1,4 @@ use demo_client::*; -use gstd::errors::ExtError; -use gtest::TestError; use sails_rs::{ client::*, futures::StreamExt as _, @@ -146,11 +144,13 @@ async fn counter_query_not_enough_gas() { .await; // Asert + println!("{:?}", result); assert!(matches!( result, - Err(TestError::ExecutionError(ExtError::Execution( - gear_core_errors::ExecutionError::NotEnoughGas - )),) + Err(GtestError::ReplyHasError( + ErrorReplyReason::Execution(SimpleExecutionError::RanOutOfGas), + _ + )) )); } diff --git a/examples/rmrk/resource/wasm/tests/resources.rs b/examples/rmrk/resource/wasm/tests/resources.rs index 7ea8c7fb2..fed8d8033 100644 --- a/examples/rmrk/resource/wasm/tests/resources.rs +++ b/examples/rmrk/resource/wasm/tests/resources.rs @@ -444,7 +444,7 @@ impl Fixture { actor_id: u64, resource_id: ResourceId, resource: &Resource, - ) -> Result, sails_rs::gtest::TestError> { + ) -> Result, GtestError> { let encoded_request = [ resources::RESOURCE_SERVICE_NAME.encode(), resources::ADD_RESOURCE_ENTRY_FUNC_NAME.encode(), @@ -468,7 +468,7 @@ impl Fixture { actor_id: u64, resource_id: u8, resource: client::Resource, - ) -> Result, sails_rs::gtest::TestError> { + ) -> Result, GtestError> { let mut resource_client = self.resource_client(); resource_client .add_resource_entry(resource_id, resource) @@ -481,7 +481,7 @@ impl Fixture { actor_id: u64, resource_id: u8, part_id: u32, - ) -> Result, sails_rs::gtest::TestError> { + ) -> Result, GtestError> { let mut resource_client = self.resource_client(); resource_client .add_part_to_resource(resource_id, part_id) @@ -493,7 +493,7 @@ impl Fixture { &self, actor_id: u64, resource_id: u8, - ) -> Result, sails_rs::gtest::TestError> { + ) -> Result, GtestError> { let resource_client = self.resource_client(); resource_client .resource(resource_id) diff --git a/rs/src/client/gclient_env.rs b/rs/src/client/gclient_env.rs index 43a4cd64c..aaf8619bb 100644 --- a/rs/src/client/gclient_env.rs +++ b/rs/src/client/gclient_env.rs @@ -1,8 +1,18 @@ use super::*; use crate::events::Listener; -use ::gclient::{Error, EventListener, EventProcessor as _, GearApi}; +use ::gclient::{EventListener, EventProcessor as _, GearApi}; use futures::{Stream, stream}; +#[derive(Debug, thiserror::Error)] +pub enum GclientError { + #[error(transparent)] + Env(#[from] gclient::Error), + #[error("reply error: {0}")] + ReplyHasError(ErrorReplyReason, crate::Vec), + #[error("unsupported")] + Unsupported, +} + #[derive(Clone)] pub struct GclientEnv { api: GearApi, @@ -28,51 +38,81 @@ impl GclientEnv { Self { api } } - async fn query_calculate_reply( - self, + pub async fn create_program( + &self, + code_id: CodeId, + salt: impl AsRef<[u8]>, + payload: impl AsRef<[u8]>, + params: GclientParams, + ) -> Result<(ActorId, Vec), GclientError> { + let api = self.api.clone(); + create_program(api, code_id, salt, payload, params).await + } + pub async fn send_message( + &self, + program_id: ActorId, + payload: Vec, + params: GclientParams, + ) -> Result, GclientError> { + let api = self.api.clone(); + send_message(api, program_id, payload, params) + .await + .map(|(_program_id, payload)| payload) + } + + pub async fn query_calculate_reply( + &self, target: ActorId, payload: impl AsRef<[u8]>, params: GclientParams, - ) -> Result, Error> { - let api = self.api; + ) -> Result, GclientError> { + let api = self.api.clone(); + query_calculate_reply(api, target, payload, params).await + } +} - // Get Max gas amount if it is not explicitly set - #[cfg(not(feature = "ethexe"))] - let gas_limit = if let Some(gas_limit) = params.gas_limit { - gas_limit - } else { - api.block_gas_limit()? - }; - #[cfg(feature = "ethexe")] - let gas_limit = 0; - let value = params.value.unwrap_or(0); - let origin = H256::from_slice(api.account_id().as_ref()); - let payload = payload.as_ref().to_vec(); - - let reply_info = api - .calculate_reply_for_handle_at( - Some(origin), - target, - payload, - gas_limit, - value, - params.at_block, - ) - .await?; +async fn query_calculate_reply( + api: GearApi, + target: ActorId, + payload: impl AsRef<[u8]>, + params: GclientParams, +) -> Result, GclientError> { + // Get Max gas amount if it is not explicitly set + #[cfg(not(feature = "ethexe"))] + let gas_limit = if let Some(gas_limit) = params.gas_limit { + gas_limit + } else { + api.block_gas_limit()? + }; + #[cfg(feature = "ethexe")] + let gas_limit = 0; + let value = params.value.unwrap_or(0); + let origin = H256::from_slice(api.account_id().as_ref()); + let payload = payload.as_ref().to_vec(); - match reply_info.code { - ReplyCode::Success(_) => Ok(reply_info.payload), - // TODO - ReplyCode::Error(_reason) => Err(Error::EventNotFound), - ReplyCode::Unsupported => Err(Error::EventNotFound), - } + let reply_info = api + .calculate_reply_for_handle_at( + Some(origin), + target, + payload, + gas_limit, + value, + params.at_block, + ) + .await?; + + match reply_info.code { + ReplyCode::Success(_) => Ok(reply_info.payload), + ReplyCode::Error(reason) => Err(GclientError::ReplyHasError(reason, reply_info.payload)), + // TODO + ReplyCode::Unsupported => Err(GclientError::Unsupported), } } impl GearEnv for GclientEnv { type Params = GclientParams; - type Error = Error; - type MessageState = Pin), Error>>>>; + type Error = GclientError; + type MessageState = Pin), GclientError>>>>; } async fn create_program( @@ -81,7 +121,7 @@ async fn create_program( salt: impl AsRef<[u8]>, payload: impl AsRef<[u8]>, params: GclientParams, -) -> Result<(ActorId, Vec), Error> { +) -> Result<(ActorId, Vec), GclientError> { let value = params.value.unwrap_or(0); // Calculate gas amount if it is not explicitly set #[cfg(not(feature = "ethexe"))] @@ -105,8 +145,9 @@ async fn create_program( // TODO handle errors match reply_code { ReplyCode::Success(_) => Ok((program_id, payload)), - ReplyCode::Error(_error_reply_reason) => todo!(), - ReplyCode::Unsupported => todo!(), + ReplyCode::Error(reason) => Err(GclientError::ReplyHasError(reason, payload)), + // TODO + ReplyCode::Unsupported => Err(GclientError::Unsupported), } } @@ -115,7 +156,7 @@ async fn send_message( program_id: ActorId, payload: Vec, params: GclientParams, -) -> Result<(ActorId, Vec), Error> { +) -> Result<(ActorId, Vec), GclientError> { let value = params.value.unwrap_or(0); #[cfg(not(feature = "ethexe"))] let gas_limit = if let Some(gas_limit) = params.gas_limit { @@ -138,15 +179,16 @@ async fn send_message( // TODO handle errors match reply_code { ReplyCode::Success(_) => Ok((program_id, payload)), - ReplyCode::Error(_error_reply_reason) => todo!(), - ReplyCode::Unsupported => todo!(), + ReplyCode::Error(reason) => Err(GclientError::ReplyHasError(reason, payload)), + // TODO + ReplyCode::Unsupported => Err(GclientError::Unsupported), } } impl PendingCall { - pub async fn send(mut self) -> Result { + pub async fn send(mut self) -> Result { let Some(route) = self.route else { - return Err(Error::Codec("PendingCall route is not set".into())); + return Err(gclient::Error::Codec("PendingCall route is not set".into()).into()); }; let api = &self.env.api; let params = self.params.unwrap_or_default(); @@ -175,9 +217,9 @@ impl PendingCall { Ok(message_id) } - pub async fn query(mut self) -> Result { + pub async fn query(mut self) -> Result { let Some(route) = self.route else { - return Err(Error::Codec("PendingCall route is not set".into())); + return Err(gclient::Error::Codec("PendingCall route is not set".into()).into()); }; let params = self.params.unwrap_or_default(); let args = self @@ -195,7 +237,7 @@ impl PendingCall { // Decode reply match T::decode_reply_with_prefix(route, reply_bytes) { Ok(decoded) => Ok(decoded), - Err(err) => Err(Error::Codec(err)), + Err(err) => Err(gclient::Error::Codec(err).into()), } } } @@ -205,7 +247,10 @@ impl Future for PendingCall { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let Some(route) = self.route else { - return Poll::Ready(Err(Error::Codec("PendingCall route is not set".into()))); + return Poll::Ready(Err(gclient::Error::Codec( + "PendingCall route is not set".into(), + ) + .into())); }; if self.state.is_none() { // Send message @@ -229,7 +274,7 @@ impl Future for PendingCall { match message_future.poll(cx) { Poll::Ready(Ok((_, payload))) => match T::decode_reply_with_prefix(route, payload) { Ok(decoded) => Poll::Ready(Ok(decoded)), - Err(err) => Poll::Ready(Err(Error::Codec(err))), + Err(err) => Poll::Ready(Err(gclient::Error::Codec(err).into())), }, Poll::Ready(Err(err)) => Poll::Ready(Err(err)), Poll::Pending => Poll::Pending, @@ -274,7 +319,7 @@ impl Future for PendingCtor { } impl Listener> for GclientEnv { - type Error = Error; + type Error = GclientError; async fn listen( &mut self, @@ -292,7 +337,7 @@ impl Listener> for GclientEnv { async fn wait_for_reply( listener: &mut EventListener, message_id: MessageId, -) -> Result<(MessageId, ReplyCode, Vec, ValueUnit), Error> { +) -> Result<(MessageId, ReplyCode, Vec, ValueUnit), GclientError> { let message_id: ::gclient::metadata::runtime_types::gprimitives::MessageId = message_id.into(); listener.proc(|e| { if let ::gclient::Event::Gear(::gclient::GearEvent::UserMessageSent { @@ -312,12 +357,12 @@ async fn wait_for_reply( None } }) - .await + .await.map_err(Into::into) } async fn get_events_from_block( listener: &mut gclient::EventListener, -) -> Result)>, Error> { +) -> Result)>, GclientError> { let vec = listener .proc_many( |e| { diff --git a/rs/src/client/gtest_env.rs b/rs/src/client/gtest_env.rs index 2f2f80204..ef7590eab 100644 --- a/rs/src/client/gtest_env.rs +++ b/rs/src/client/gtest_env.rs @@ -10,10 +10,19 @@ use hashbrown::HashMap; use std::rc::Rc; const GAS_LIMIT_DEFAULT: ::gtest::constants::Gas = ::gtest::constants::MAX_USER_GAS_LIMIT; -type Error = TestError; type EventSender = mpsc::UnboundedSender<(ActorId, Vec)>; -type ReplySender = oneshot::Sender, Error>>; -type ReplyReceiver = oneshot::Receiver, Error>>; +type ReplySender = oneshot::Sender, GtestError>>; +type ReplyReceiver = oneshot::Receiver, GtestError>>; + +#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)] +pub enum GtestError { + #[error(transparent)] + Env(#[from] TestError), + #[error("reply error: {0}")] + ReplyHasError(ErrorReplyReason, crate::Vec), + #[error("reply is missing")] + ReplyIsMissing, +} #[derive(Clone, Debug, PartialEq, Eq)] pub enum BlockRunMode { @@ -119,16 +128,12 @@ impl GtestEnv { if let Some(sender) = reply_senders.remove(&message_id) { log::debug!("Extract reply from entry {entry:?}"); let reply: result::Result, _> = match entry.reply_code() { - None => Err(Error::InvalidReturnType), - // TODO handle error reply + None => Err(GtestError::ReplyIsMissing), Some(ReplyCode::Error(reason)) => { - panic!( - "Unexpected error reply: {reason:?}, {:?}", - hex::encode(entry.payload()) - ) + Err(GtestError::ReplyHasError(reason, entry.payload().to_vec())) } Some(ReplyCode::Success(_)) => Ok(entry.payload().to_vec()), - _ => Err(Error::InvalidReturnType), + _ => Err(GtestError::ReplyIsMissing), }; _ = sender.send(reply); } @@ -142,7 +147,7 @@ impl GtestEnv { salt: impl AsRef<[u8]>, payload: impl AsRef<[u8]>, params: GtestParams, - ) -> Result<(ActorId, MessageId), Error> { + ) -> Result<(ActorId, MessageId), GtestError> { let value = params.value.unwrap_or(0); #[cfg(not(feature = "ethexe"))] let gas_limit = params.gas_limit.unwrap_or(GAS_LIMIT_DEFAULT); @@ -151,8 +156,7 @@ impl GtestEnv { let code = self .system .submitted_code(code_id) - // TODO Errors - .ok_or(Error::Instrumentation)?; + .ok_or(TestError::Instrumentation)?; let program_id = ::gtest::calculate_program_id(code_id, salt.as_ref(), None); let program = ::gtest::Program::from_binary_with_id(&self.system, program_id, code); let actor_id = params.actor_id.unwrap_or(self.actor_id); @@ -166,7 +170,7 @@ impl GtestEnv { target: ActorId, payload: impl AsRef<[u8]>, params: GtestParams, - ) -> Result { + ) -> Result { let value = params.value.unwrap_or(0); #[cfg(not(feature = "ethexe"))] let gas_limit = params.gas_limit.unwrap_or(GAS_LIMIT_DEFAULT); @@ -175,8 +179,7 @@ impl GtestEnv { let program = self .system .get_program(target) - // TODO Errors - .ok_or(Error::Instrumentation)?; + .ok_or(TestError::ActorNotFound(target))?; let actor_id = params.actor_id.unwrap_or(self.actor_id); let message_id = program.send_bytes_with_gas(actor_id, payload.as_ref(), gas_limit, value); log::debug!( @@ -187,7 +190,7 @@ impl GtestEnv { } pub fn message_reply_from_next_blocks(&self, message_id: MessageId) -> ReplyReceiver { - let (tx, rx) = oneshot::channel::, Error>>(); + let (tx, rx) = oneshot::channel::, GtestError>>(); self.block_reply_senders.borrow_mut().insert(message_id, tx); match self.block_run_mode { @@ -220,29 +223,25 @@ impl GtestEnv { // drain reply senders that not founded in block for (message_id, sender) in reply_senders.drain() { log::debug!("Reply is missing in block for message {message_id}"); - // TODO handle error - _ = sender.send(Err(Error::UnsupportedFunction( - "Reply is missing in block for message {message_id}".into(), - ))); + _ = sender.send(Err(GtestError::ReplyIsMissing)); } } } impl GearEnv for GtestEnv { type Params = GtestParams; - type Error = Error; + type Error = GtestError; type MessageState = ReplyReceiver; } impl PendingCall { - pub fn send_message(mut self) -> Result { + pub fn send_message(mut self) -> Result { let Some(route) = self.route else { - return Err(Error::ScaleCodecError( + return Err(TestError::ScaleCodecError( "PendingCall route is not set".into(), - )); + ))?; }; if self.state.is_some() { - // TODO - return Err(Error::Instrumentation); + panic!("{PENDING_CALL_INVALID_STATE}"); } // Send message let args = self @@ -264,9 +263,10 @@ impl Future for PendingCall { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let Some(route) = self.route else { - return Poll::Ready(Err(Error::ScaleCodecError( + return Poll::Ready(Err(TestError::ScaleCodecError( "PendingCall route is not set".into(), - ))); + ) + .into())); }; if self.state.is_none() { @@ -297,12 +297,11 @@ impl Future for PendingCall { Poll::Ready(Ok(res)) => match res { Ok(payload) => match T::decode_reply_with_prefix(route, payload) { Ok(reply) => Poll::Ready(Ok(reply)), - Err(err) => Poll::Ready(Err(Error::ScaleCodecError(err))), + Err(err) => Poll::Ready(Err(TestError::ScaleCodecError(err).into())), }, Err(err) => Poll::Ready(Err(err)), }, - // TODO handle error - Poll::Ready(Err(_err)) => Poll::Ready(Err(Error::InvalidReturnType)), + Poll::Ready(Err(_err)) => Poll::Ready(Err(GtestError::ReplyIsMissing)), Poll::Pending => Poll::Pending, } } else { @@ -312,10 +311,9 @@ impl Future for PendingCall { } impl PendingCtor { - pub fn create_program(mut self) -> Result { + pub fn create_program(mut self) -> Result { if self.state.is_some() { - // TODO - return Err(Error::Instrumentation); + panic!("{PENDING_CTOR_INVALID_STATE}"); } // Send message let payload = self.encode_ctor(); @@ -379,8 +377,7 @@ impl Future for PendingCtor { } Err(err) => Poll::Ready(Err(err)), }, - // TODO handle error - Poll::Ready(Err(_err)) => Poll::Ready(Err(Error::InvalidReturnType)), + Poll::Ready(Err(_err)) => Poll::Ready(Err(GtestError::ReplyIsMissing)), Poll::Pending => Poll::Pending, } } else { diff --git a/rs/src/client/mod.rs b/rs/src/client/mod.rs index 16bce6db9..1de470a4d 100644 --- a/rs/src/client/mod.rs +++ b/rs/src/client/mod.rs @@ -13,14 +13,14 @@ use futures::{Stream, StreamExt as _}; mod gtest_env; #[cfg(feature = "gtest")] #[cfg(not(target_arch = "wasm32"))] -pub use gtest_env::{BlockRunMode, GtestEnv, GtestParams}; +pub use gtest_env::{BlockRunMode, GtestEnv, GtestError, GtestParams}; #[cfg(feature = "gclient")] #[cfg(not(target_arch = "wasm32"))] mod gclient_env; #[cfg(feature = "gclient")] #[cfg(not(target_arch = "wasm32"))] -pub use gclient_env::{GclientEnv, GclientParams}; +pub use gclient_env::{GclientEnv, GclientError, GclientParams}; mod gstd_env; pub use gstd_env::{GstdEnv, GstdParams}; From c40c98e20197833d49a5287e0c4d323f7b26e1c6 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 3 Sep 2025 17:38:30 +0200 Subject: [PATCH 13/66] wip: event listener, names --- Cargo.lock | 1 + Cargo.toml | 5 +- examples/demo/app/tests/gclient.rs | 2 +- examples/demo/client/src/demo_client.rs | 13 +-- examples/demo/client/src/env_client.rs | 5 +- .../rmrk/resource/wasm/src/rmrk_resource.rs | 7 +- rs/Cargo.toml | 6 +- rs/client-gen/src/events_generator.rs | 8 +- .../snapshots/generator__events_works.snap | 7 +- .../tests/snapshots/generator__full.snap | 5 +- .../generator__full_with_sails_path.snap | 5 +- rs/ethexe/Cargo.lock | 12 ++ rs/src/client/gclient_env.rs | 19 ++-- rs/src/client/gstd_env.rs | 13 ++- rs/src/client/gtest_env.rs | 11 +- rs/src/client/mod.rs | 103 +++++------------- 16 files changed, 83 insertions(+), 139 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index daa0c2b22..b118a4d57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7309,6 +7309,7 @@ dependencies = [ "spin", "thiserror 2.0.12", "tokio", + "tokio-stream", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index bd37715d7..6a795f6f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,9 @@ gclient = "=1.9.0" gcore = "=1.9.0" gear-core = { version = "=1.9.0", default-features = false } gear-core-errors = "=1.9.0" -gprimitives = { version = "=1.9.0", default-features = false, features = ["codec"] } +gprimitives = { version = "=1.9.0", default-features = false, features = [ + "codec", +] } gstd = "=1.9.0" gtest = "=1.9.0" gwasm-builder = { version = "=1.9.0", package = "gear-wasm-builder" } @@ -107,4 +109,5 @@ thiserror = { version = "2.0", default-features = false } toml_edit = "0.22" tempfile = "3.19" tokio = "1.45" +tokio-stream = "0.1" trybuild = "1" diff --git a/examples/demo/app/tests/gclient.rs b/examples/demo/app/tests/gclient.rs index 9c55faa05..6d85570f8 100644 --- a/examples/demo/app/tests/gclient.rs +++ b/examples/demo/app/tests/gclient.rs @@ -1,4 +1,4 @@ -use demo_client::{counter::events::*, counter::*, ping_pong::*, value_fee::*, *}; +use demo_client::{counter::events::*, counter::*, value_fee::*, *}; use gclient::GearApi; use gstd::errors::{ErrorReplyReason, SimpleExecutionError}; use sails_rs::{client::*, futures::StreamExt, prelude::*}; diff --git a/examples/demo/client/src/demo_client.rs b/examples/demo/client/src/demo_client.rs index fe3bdba6b..0c2a8f061 100644 --- a/examples/demo/client/src/demo_client.rs +++ b/examples/demo/client/src/demo_client.rs @@ -127,10 +127,7 @@ pub mod counter { Subtracted(u32), } impl EventDecode for CounterEvents { - const EVENT_NAMES: &'static [&'static [u8]] = &[ - &[20, 65, 100, 100, 101, 100], - &[40, 83, 117, 98, 116, 114, 97, 99, 116, 101, 100], - ]; + const EVENT_NAMES: &'static [Route] = &["Added", "Subtracted"]; } impl ServiceEvent for CounterImpl { type Event = CounterEvents; @@ -182,10 +179,7 @@ pub mod dog { Walked { from: (i32, i32), to: (i32, i32) }, } impl EventDecode for DogEvents { - const EVENT_NAMES: &'static [&'static [u8]] = &[ - &[24, 66, 97, 114, 107, 101, 100], - &[24, 87, 97, 108, 107, 101, 100], - ]; + const EVENT_NAMES: &'static [Route] = &["Barked", "Walked"]; } impl ServiceEvent for DogImpl { type Event = DogEvents; @@ -334,8 +328,7 @@ pub mod value_fee { Withheld(u128), } impl EventDecode for ValueFeeEvents { - const EVENT_NAMES: &'static [&'static [u8]] = - &[&[32, 87, 105, 116, 104, 104, 101, 108, 100]]; + const EVENT_NAMES: &'static [Route] = &["Withheld"]; } impl ServiceEvent for ValueFeeImpl { type Event = ValueFeeEvents; diff --git a/examples/demo/client/src/env_client.rs b/examples/demo/client/src/env_client.rs index bbb19240c..b9ce0ec1f 100644 --- a/examples/demo/client/src/env_client.rs +++ b/examples/demo/client/src/env_client.rs @@ -101,10 +101,7 @@ pub mod counter { Subtracted(u32), } impl EventDecode for CounterEvents { - const EVENT_NAMES: &'static [&'static [u8]] = &[ - &[20, 65, 100, 100, 101, 100], - &[40, 83, 117, 98, 116, 114, 97, 99, 116, 101, 100], - ]; + const EVENT_NAMES: &'static [Route] = &["Added", "Subtracted"]; } impl ServiceEvent for CounterImpl { diff --git a/examples/rmrk/resource/wasm/src/rmrk_resource.rs b/examples/rmrk/resource/wasm/src/rmrk_resource.rs index f70d0303e..eef8dd476 100644 --- a/examples/rmrk/resource/wasm/src/rmrk_resource.rs +++ b/examples/rmrk/resource/wasm/src/rmrk_resource.rs @@ -85,12 +85,7 @@ pub mod rmrk_resource { PartAdded { resource_id: u8, part_id: u32 }, } impl EventDecode for RmrkResourceEvents { - const EVENT_NAMES: &'static [&'static [u8]] = &[ - &[ - 52, 82, 101, 115, 111, 117, 114, 99, 101, 65, 100, 100, 101, 100, - ], - &[36, 80, 97, 114, 116, 65, 100, 100, 101, 100], - ]; + const EVENT_NAMES: &'static [Route] = &["ResourceAdded", "PartAdded"]; } impl ServiceEvent for RmrkResourceImpl { type Event = RmrkResourceEvents; diff --git a/rs/Cargo.toml b/rs/Cargo.toml index 03aa5b78f..c7f6891a4 100644 --- a/rs/Cargo.toml +++ b/rs/Cargo.toml @@ -34,6 +34,7 @@ sails-macros = { workspace = true, optional = true } scale-info = { workspace = true, features = ["derive", "docs"] } spin.workspace = true thiserror.workspace = true +tokio-stream = { workspace = true, optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] gear-core = { workspace = true, features = [ @@ -57,12 +58,11 @@ ethexe = [ "dep:keccak-const", "sails-macros?/ethexe", ] -gclient = ["dep:gclient"] +gclient = ["dep:gclient", "dep:tokio-stream"] gstd = ["dep:gstd", "dep:gear-core", "dep:sails-macros"] -gtest = ["std", "dep:gtest", "dep:log"] +gtest = ["std", "dep:gtest", "dep:log", "dep:tokio-stream"] idl-gen = ["dep:sails-idl-gen"] client-builder = ["std", "idl-gen", "dep:sails-client-gen", "dep:convert_case"] mockall = ["std", "dep:mockall"] std = ["futures/std"] wasm-builder = ["dep:gwasm-builder"] - diff --git a/rs/client-gen/src/events_generator.rs b/rs/client-gen/src/events_generator.rs index 6dbd28f05..ab2fd7069 100644 --- a/rs/client-gen/src/events_generator.rs +++ b/rs/client-gen/src/events_generator.rs @@ -25,12 +25,12 @@ impl<'a> EventsModuleGenerator<'a> { impl<'ast> Visitor<'ast> for EventsModuleGenerator<'_> { fn visit_service(&mut self, service: &'ast Service) { let events_name = &format!("{}Events", self.service_name); - let event_names_bytes = service + let event_names = service .events() .iter() - .map(|e| method_bytes(e.name()).0) + .map(|e| format!("\"{}\"", e.name())) .collect::>() - .join("], &["); + .join(", "); quote_in! { self.tokens => $['\n'] @@ -50,7 +50,7 @@ impl<'ast> Visitor<'ast> for EventsModuleGenerator<'_> { quote_in! { self.tokens => impl EventDecode for $events_name { - const EVENT_NAMES: &'static [&'static [u8]] = &[&[$event_names_bytes]]; + const EVENT_NAMES: &'static [Route] = &[$event_names]; } impl ServiceEvent for $(self.service_name)Impl { diff --git a/rs/client-gen/tests/snapshots/generator__events_works.snap b/rs/client-gen/tests/snapshots/generator__events_works.snap index 8418a7f71..99541659c 100644 --- a/rs/client-gen/tests/snapshots/generator__events_works.snap +++ b/rs/client-gen/tests/snapshots/generator__events_works.snap @@ -51,12 +51,7 @@ pub mod service_with_events { Reset, } impl EventDecode for ServiceWithEventsEvents { - const EVENT_NAMES: &'static [&'static [u8]] = &[ - &[12, 79, 110, 101], - &[12, 84, 119, 111], - &[20, 84, 104, 114, 101, 101], - &[20, 82, 101, 115, 101, 116], - ]; + const EVENT_NAMES: &'static [Route] = &["One", "Two", "Three", "Reset"]; } impl ServiceEvent for ServiceWithEventsImpl { type Event = ServiceWithEventsEvents; diff --git a/rs/client-gen/tests/snapshots/generator__full.snap b/rs/client-gen/tests/snapshots/generator__full.snap index 160ea05f3..92151adb9 100644 --- a/rs/client-gen/tests/snapshots/generator__full.snap +++ b/rs/client-gen/tests/snapshots/generator__full.snap @@ -107,10 +107,7 @@ pub mod service { }, } impl EventDecode for ServiceEvents { - const EVENT_NAMES: &'static [&'static [u8]] = &[ - &[32, 84, 104, 105, 115, 68, 111, 110, 101], - &[32, 84, 104, 97, 116, 68, 111, 110, 101], - ]; + const EVENT_NAMES: &'static [Route] = &["ThisDone", "ThatDone"]; } impl ServiceEvent for ServiceImpl { type Event = ServiceEvents; diff --git a/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap b/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap index 59d89b116..275f05e02 100644 --- a/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap +++ b/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap @@ -150,10 +150,7 @@ pub mod counter { Subtracted(u32), } impl EventDecode for CounterEvents { - const EVENT_NAMES: &'static [&'static [u8]] = &[ - &[20, 65, 100, 100, 101, 100], - &[40, 83, 117, 98, 116, 114, 97, 99, 116, 101, 100], - ]; + const EVENT_NAMES: &'static [Route] = &["Added", "Subtracted"]; } impl ServiceEvent for CounterImpl { type Event = CounterEvents; diff --git a/rs/ethexe/Cargo.lock b/rs/ethexe/Cargo.lock index 221b444b4..b74730abd 100644 --- a/rs/ethexe/Cargo.lock +++ b/rs/ethexe/Cargo.lock @@ -4301,6 +4301,7 @@ dependencies = [ "scale-info", "spin", "thiserror 2.0.12", + "tokio-stream", ] [[package]] @@ -5511,6 +5512,17 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.8.20" diff --git a/rs/src/client/gclient_env.rs b/rs/src/client/gclient_env.rs index aaf8619bb..40a5ffa54 100644 --- a/rs/src/client/gclient_env.rs +++ b/rs/src/client/gclient_env.rs @@ -1,7 +1,6 @@ use super::*; -use crate::events::Listener; use ::gclient::{EventListener, EventProcessor as _, GearApi}; -use futures::{Stream, stream}; +use futures::{Stream, StreamExt, stream}; #[derive(Debug, thiserror::Error)] pub enum GclientError { @@ -142,11 +141,9 @@ async fn create_program( .create_program_bytes(code_id, salt, payload, gas_limit, value) .await?; let (_, reply_code, payload, _) = wait_for_reply(&mut listener, message_id).await?; - // TODO handle errors match reply_code { ReplyCode::Success(_) => Ok((program_id, payload)), ReplyCode::Error(reason) => Err(GclientError::ReplyHasError(reason, payload)), - // TODO ReplyCode::Unsupported => Err(GclientError::Unsupported), } } @@ -176,11 +173,9 @@ async fn send_message( .send_message_bytes(program_id, payload, gas_limit, value) .await?; let (_, reply_code, payload, _) = wait_for_reply(&mut listener, message_id).await?; - // TODO handle errors match reply_code { ReplyCode::Success(_) => Ok((program_id, payload)), ReplyCode::Error(reason) => Err(GclientError::ReplyHasError(reason, payload)), - // TODO ReplyCode::Unsupported => Err(GclientError::Unsupported), } } @@ -318,18 +313,20 @@ impl Future for PendingCtor { } } -impl Listener> for GclientEnv { +impl Listener for GclientEnv { type Error = GclientError; - async fn listen( - &mut self, - ) -> Result)> + Unpin, Self::Error> { + async fn listen)) -> Option<(ActorId, E)>>( + &self, + f: F, + ) -> Result + Unpin, Self::Error> { let listener = self.api.subscribe().await?; let stream = stream::unfold(listener, |mut l| async move { let vec = get_events_from_block(&mut l).await.ok(); vec.map(|v| (v, l)) }) .flat_map(stream::iter); + let stream = tokio_stream::StreamExt::filter_map(stream, f); Ok(Box::pin(stream)) } } @@ -361,7 +358,7 @@ async fn wait_for_reply( } async fn get_events_from_block( - listener: &mut gclient::EventListener, + listener: &mut EventListener, ) -> Result)>, GclientError> { let vec = listener .proc_many( diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index b459b3334..fac266e82 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -109,8 +109,8 @@ pub(crate) fn send_for_reply_future( pub(crate) fn send_for_reply_future( target: ActorId, payload: &[u8], - args: GstdParams, -) -> Result { + params: GstdParams, +) -> Result { let value = params.value.unwrap_or(0); // here can be a redirect target let mut message_future = msg::send_bytes_for_reply(target, payload, value)?; @@ -135,6 +135,13 @@ const _: () = { let payload = T::encode_params_with_prefix(route, &args); let value = params.value.unwrap_or(0); + + #[cfg(feature = "ethexe")] + { + ::gcore::msg::send(self.destination, payload.as_slice(), value).map_err(Error::Core) + } + + #[cfg(not(feature = "ethexe"))] if let Some(gas_limit) = params.gas_limit { ::gcore::msg::send_with_gas(self.destination, payload.as_slice(), gas_limit, value) .map_err(Error::Core) @@ -221,7 +228,7 @@ const _: () = { }; #[cfg(feature = "ethexe")] let mut program_future = - prog::create_program_bytes_for_reply(code_id, salt, payload, value)?; + gstd::prog::create_program_bytes_for_reply(self.code_id, salt, payload, value)?; // self.program_id = Some(program_future.program_id); self.state = Some(GtsdFuture::CreateProgram { diff --git a/rs/src/client/gtest_env.rs b/rs/src/client/gtest_env.rs index ef7590eab..838b729bf 100644 --- a/rs/src/client/gtest_env.rs +++ b/rs/src/client/gtest_env.rs @@ -1,5 +1,4 @@ use super::*; -use crate::events::Listener; use ::gtest::{BlockRunResult, System, TestError}; use core::cell::RefCell; use futures::{ @@ -8,6 +7,7 @@ use futures::{ }; use hashbrown::HashMap; use std::rc::Rc; +use tokio_stream::StreamExt; const GAS_LIMIT_DEFAULT: ::gtest::constants::Gas = ::gtest::constants::MAX_USER_GAS_LIMIT; type EventSender = mpsc::UnboundedSender<(ActorId, Vec)>; @@ -386,12 +386,15 @@ impl Future for PendingCtor { } } -impl Listener> for GtestEnv { +impl Listener for GtestEnv { type Error = ::Error; - async fn listen(&mut self) -> Result)>, Self::Error> { + async fn listen)) -> Option<(ActorId, E)>>( + &self, + f: F, + ) -> Result + Unpin, Self::Error> { let (tx, rx) = mpsc::unbounded::<(ActorId, Vec)>(); self.event_senders.borrow_mut().push(tx); - Ok(rx) + Ok(rx.filter_map(f)) } } diff --git a/rs/src/client/mod.rs b/rs/src/client/mod.rs index 1de470a4d..b827ccade 100644 --- a/rs/src/client/mod.rs +++ b/rs/src/client/mod.rs @@ -6,7 +6,7 @@ use core::{ pin::Pin, task::{Context, Poll}, }; -use futures::{Stream, StreamExt as _}; +use futures::Stream; #[cfg(feature = "gtest")] #[cfg(not(target_arch = "wasm32"))] @@ -197,18 +197,18 @@ impl ServiceListener { &mut self, ) -> Result + Unpin, ::Error> where - E: crate::events::Listener, Error = ::Error>, + E: Listener::Error>, { let self_id = self.actor_id; let prefix = self.route; - let stream = self.env.listen().await?; - let map = stream.filter_map(move |(actor_id, payload)| async move { - if actor_id != self_id { - return None; - } - D::decode_event(prefix, payload).ok().map(|e| (actor_id, e)) - }); - Ok(Box::pin(map)) + self.env + .listen(move |(actor_id, payload)| { + if actor_id != self_id { + return None; + } + D::decode_event(prefix, payload).ok().map(|e| (actor_id, e)) + }) + .await } } @@ -441,9 +441,19 @@ macro_rules! str_scale_encode { }}; } +#[allow(async_fn_in_trait)] +pub trait Listener { + type Error: Error; + + async fn listen)) -> Option<(ActorId, E)>>( + &self, + f: F, + ) -> Result + Unpin, Self::Error>; +} + #[cfg(not(target_arch = "wasm32"))] pub trait EventDecode: Decode { - const EVENT_NAMES: &'static [&'static [u8]]; + const EVENT_NAMES: &'static [Route]; fn decode_event( prefix: Route, @@ -454,11 +464,11 @@ pub trait EventDecode: Decode { if route != prefix { return Err("Invalid event prefix".into()); } - - for (idx, name) in Self::EVENT_NAMES.iter().enumerate() { - if payload.starts_with(name) { + let evt_name = String::decode(&mut payload)?; + for (idx, &name) in Self::EVENT_NAMES.iter().enumerate() { + if evt_name == name { let idx = idx as u8; - let bytes = [&[idx], &payload[name.len()..]].concat(); + let bytes = [&[idx], payload].concat(); let mut event_bytes = &bytes[..]; return Decode::decode(&mut event_bytes); } @@ -467,69 +477,6 @@ pub trait EventDecode: Decode { } } -// mod client { - -// use super::service::Service; -// use super::*; - -// pub struct MyServiceImpl; - -// pub trait MyService { -// fn mint(&mut self, to: ActorId, amount: u128) -> PendingCall; -// fn burn(&mut self, from: ActorId) -> PendingCall; -// fn total(&self) -> PendingCall; -// } - -// impl MyService for Service { -// fn mint(&mut self, to: ActorId, amount: u128) -> PendingCall { -// self.pending_call("Mint", (to, amount)) -// } - -// fn burn(&mut self, from: ActorId) -> PendingCall { -// self.pending_call("Burn", (from,)) -// } - -// fn total(&self) -> PendingCall { -// self.pending_call("Total", ()) -// } -// } - -// #[cfg(feature = "mockall")] -// #[cfg(not(target_arch = "wasm32"))] -// mockall::mock! { -// pub MyService {} - -// impl MyService for MyService { -// fn mint(&mut self, to: ActorId, amount: u128) -> PendingCall; -// fn burn(&mut self, from: ActorId) -> PendingCall; -// fn total(&self) -> PendingCall; -// } -// } -// } - -// #[cfg(feature = "mockall")] -// #[cfg(test)] -// mod tests { -// use super::*; - -// #[tokio::test] -// async fn sample() -> Result<(), Box> { -// use client::*; - -// let mut my_service = MockMyService::new(); -// my_service.expect_total().returning(move || 137.into()); -// my_service.expect_mint().returning(move |_, _| true.into()); - -// assert_eq!(my_service.total().await?, 137); - -// let mut my_service = my_service; - -// assert!(my_service.mint(ActorId::from(137), 1_000).await?); - -// Ok(()) -// } -// } - #[cfg(test)] mod tests { use super::*; From 1a393e101a0913a3eee528cd612833e633886e0c Mon Sep 17 00:00:00 2001 From: vobradovich Date: Thu, 4 Sep 2025 12:03:11 +0200 Subject: [PATCH 14/66] wip: fix clippy, cleanup --- .../ping-pong/app/src/ping_pong_client.rs | 3 +- benchmarks/src/alloc_stress_program.rs | 3 +- benchmarks/src/compute_stress_program.rs | 3 +- benchmarks/src/counter_bench_program.rs | 3 +- examples/demo/app/tests/env_gtest.rs | 2 +- examples/demo/app/tests/gclient.rs | 4 +-- examples/demo/app/tests/gtest.rs | 14 ++++----- examples/demo/client/src/demo_client.rs | 3 +- .../no-svcs-prog/wasm/src/no_svcs_prog.rs | 3 +- .../redirect/client/src/redirect_client.rs | 3 +- .../proxy-client/src/redirect_proxy_client.rs | 3 +- examples/rmrk/resource/app/build.rs | 3 +- examples/rmrk/resource/app/src/catalogs.rs | 2 -- examples/rmrk/resource/app/src/lib.rs | 2 +- .../rmrk/resource/app/src/rmrk_catalog.rs | 3 +- .../rmrk/resource/wasm/src/rmrk_resource.rs | 3 +- rs/client-gen/src/events_generator.rs | 1 - rs/client-gen/src/helpers.rs | 31 ------------------- rs/client-gen/src/root_generator.rs | 3 +- rs/client-gen/src/service_generators.rs | 2 +- .../snapshots/generator__basic_works.snap | 3 +- .../snapshots/generator__events_works.snap | 3 +- .../snapshots/generator__external_types.snap | 3 +- .../tests/snapshots/generator__full.snap | 3 +- .../generator__full_with_sails_path.snap | 3 +- .../generator__multiple_services.snap | 3 +- .../snapshots/generator__nonzero_works.snap | 3 +- .../snapshots/generator__rmrk_works.snap | 3 +- rs/src/client/mod.rs | 2 +- 29 files changed, 52 insertions(+), 68 deletions(-) diff --git a/benchmarks/ping-pong/app/src/ping_pong_client.rs b/benchmarks/ping-pong/app/src/ping_pong_client.rs index 6f26eb360..e212b17b4 100644 --- a/benchmarks/ping-pong/app/src/ping_pong_client.rs +++ b/benchmarks/ping-pong/app/src/ping_pong_client.rs @@ -1,5 +1,6 @@ // Code generated by sails-client-gen. DO NOT EDIT. -use sails_rs::{client::*, prelude::*}; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct PingPongProgram; impl Program for PingPongProgram {} pub trait PingPong { diff --git a/benchmarks/src/alloc_stress_program.rs b/benchmarks/src/alloc_stress_program.rs index fbab8024b..99f6b2bbf 100644 --- a/benchmarks/src/alloc_stress_program.rs +++ b/benchmarks/src/alloc_stress_program.rs @@ -1,5 +1,6 @@ // Code generated by sails-client-gen. DO NOT EDIT. -use sails_rs::{client::*, prelude::*}; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct AllocStressProgramProgram; impl Program for AllocStressProgramProgram {} pub trait AllocStressProgram { diff --git a/benchmarks/src/compute_stress_program.rs b/benchmarks/src/compute_stress_program.rs index d6d28ce35..27c20cf5e 100644 --- a/benchmarks/src/compute_stress_program.rs +++ b/benchmarks/src/compute_stress_program.rs @@ -1,5 +1,6 @@ // Code generated by sails-client-gen. DO NOT EDIT. -use sails_rs::{client::*, prelude::*}; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct ComputeStressProgramProgram; impl Program for ComputeStressProgramProgram {} pub trait ComputeStressProgram { diff --git a/benchmarks/src/counter_bench_program.rs b/benchmarks/src/counter_bench_program.rs index fafea6bb2..52c5f6eab 100644 --- a/benchmarks/src/counter_bench_program.rs +++ b/benchmarks/src/counter_bench_program.rs @@ -1,5 +1,6 @@ // Code generated by sails-client-gen. DO NOT EDIT. -use sails_rs::{client::*, prelude::*}; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct CounterBenchProgramProgram; impl Program for CounterBenchProgramProgram {} pub trait CounterBenchProgram { diff --git a/examples/demo/app/tests/env_gtest.rs b/examples/demo/app/tests/env_gtest.rs index bea5fa75a..7cf7bf9bc 100644 --- a/examples/demo/app/tests/env_gtest.rs +++ b/examples/demo/app/tests/env_gtest.rs @@ -39,7 +39,7 @@ async fn env_counter_add_works_via_next_mode() { .unwrap(); let mut counter_client = demo_program.counter(); - let mut counter_listener = counter_client.listener(); + let counter_listener = counter_client.listener(); let mut counter_events = counter_listener.listen().await.unwrap(); assert_eq!(Ok(52), counter_client.add(10).await); diff --git a/examples/demo/app/tests/gclient.rs b/examples/demo/app/tests/gclient.rs index d59742744..597d4a4c7 100644 --- a/examples/demo/app/tests/gclient.rs +++ b/examples/demo/app/tests/gclient.rs @@ -31,7 +31,7 @@ async fn counter_add_works() { let mut counter_client = demo_program.counter(); // Listen to Counter events - let mut counter_listener = counter_client.listener(); + let counter_listener = counter_client.listener(); let mut counter_events = counter_listener.listen().await.unwrap(); // Act @@ -73,7 +73,7 @@ async fn counter_sub_works() { let mut counter_client = demo_program.counter(); // Listen to Counter events - let mut counter_listener = counter_client.listener(); + let counter_listener = counter_client.listener(); let mut counter_events = counter_listener.listen().await.unwrap(); // Act diff --git a/examples/demo/app/tests/gtest.rs b/examples/demo/app/tests/gtest.rs index a48f81c2d..7f87e0145 100644 --- a/examples/demo/app/tests/gtest.rs +++ b/examples/demo/app/tests/gtest.rs @@ -44,7 +44,7 @@ async fn counter_add_works() { let mut counter_client = demo_program.counter(); // Listen to Counter events - let mut counter_listener = counter_client.listener(); + let counter_listener = counter_client.listener(); let mut counter_events = counter_listener.listen().await.unwrap(); // Act @@ -76,7 +76,7 @@ async fn counter_sub_works() { let mut counter_client = demo_program.counter(); // Listen to Counter events - let mut counter_listener = counter_client.listener(); + let counter_listener = counter_client.listener(); let mut counter_events = counter_listener.listen().await.unwrap(); // Act @@ -144,7 +144,7 @@ async fn counter_query_not_enough_gas() { .await; // Assert - println!("{:?}", result); + println!("{result:?}"); assert!(matches!( result, Err(GtestError::ReplyHasError( @@ -217,7 +217,7 @@ async fn dog_barks() { .unwrap(); let mut dog_client = demo_program.dog(); - let mut dog_listener = dog_client.listener(); + let dog_listener = dog_client.listener(); let mut dog_events = dog_listener.listen().await.unwrap(); // Act @@ -246,7 +246,7 @@ async fn dog_walks() { .unwrap(); let mut dog_client = demo_program.dog(); - let mut dog_listener = dog_client.listener(); + let dog_listener = dog_client.listener(); let mut dog_events = dog_listener.listen().await.unwrap(); // Act @@ -374,7 +374,7 @@ async fn counter_add_works_via_next_mode() { let mut counter_client = demo_program.counter(); // Listen to Counter events - let mut counter_listener = counter_client.listener(); + let counter_listener = counter_client.listener(); let mut counter_events = counter_listener.listen().await.unwrap(); // Act @@ -408,7 +408,7 @@ async fn counter_add_works_via_manual_mode() { let mut counter_client = demo_program.counter(); // Listen to Counter events - let mut counter_listener = counter_client.listener(); + let counter_listener = counter_client.listener(); let mut counter_events = counter_listener.listen().await.unwrap(); // Use generated client code for calling Counter service diff --git a/examples/demo/client/src/demo_client.rs b/examples/demo/client/src/demo_client.rs index 0c2a8f061..05c64c12e 100644 --- a/examples/demo/client/src/demo_client.rs +++ b/examples/demo/client/src/demo_client.rs @@ -1,5 +1,6 @@ // Code generated by sails-client-gen. DO NOT EDIT. -use sails_rs::{client::*, prelude::*}; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct DemoClientProgram; impl Program for DemoClientProgram {} pub trait DemoClient { diff --git a/examples/no-svcs-prog/wasm/src/no_svcs_prog.rs b/examples/no-svcs-prog/wasm/src/no_svcs_prog.rs index 45f8413ac..ed4e7670d 100644 --- a/examples/no-svcs-prog/wasm/src/no_svcs_prog.rs +++ b/examples/no-svcs-prog/wasm/src/no_svcs_prog.rs @@ -1,5 +1,6 @@ // Code generated by sails-client-gen. DO NOT EDIT. -use sails_rs::{client::*, prelude::*}; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct NoSvcsProgProgram; impl Program for NoSvcsProgProgram {} pub trait NoSvcsProg { diff --git a/examples/redirect/client/src/redirect_client.rs b/examples/redirect/client/src/redirect_client.rs index f3d75e9fb..ed956df05 100644 --- a/examples/redirect/client/src/redirect_client.rs +++ b/examples/redirect/client/src/redirect_client.rs @@ -1,5 +1,6 @@ // Code generated by sails-client-gen. DO NOT EDIT. -use sails_rs::{client::*, prelude::*}; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct RedirectClientProgram; impl Program for RedirectClientProgram {} pub trait RedirectClient { diff --git a/examples/redirect/proxy-client/src/redirect_proxy_client.rs b/examples/redirect/proxy-client/src/redirect_proxy_client.rs index b56025258..387d57af0 100644 --- a/examples/redirect/proxy-client/src/redirect_proxy_client.rs +++ b/examples/redirect/proxy-client/src/redirect_proxy_client.rs @@ -1,5 +1,6 @@ // Code generated by sails-client-gen. DO NOT EDIT. -use sails_rs::{client::*, prelude::*}; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct RedirectProxyClientProgram; impl Program for RedirectProxyClientProgram {} pub trait RedirectProxyClient { diff --git a/examples/rmrk/resource/app/build.rs b/examples/rmrk/resource/app/build.rs index 32c3244d4..5e7609930 100644 --- a/examples/rmrk/resource/app/build.rs +++ b/examples/rmrk/resource/app/build.rs @@ -2,13 +2,12 @@ use sails_client_gen::ClientGenerator; use std::{env, path::PathBuf}; fn main() { - let out_dir_path = PathBuf::from(env::var("OUT_DIR").unwrap()); let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); let client_rs_file_path = manifest_dir.join("src/rmrk_catalog.rs"); #[cfg(not(target_family = "windows"))] - let idl_file_path = out_dir_path.join("rmrk-catalog.idl"); + let idl_file_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("rmrk-catalog.idl"); #[cfg(not(target_family = "windows"))] git_download::repo("https://github.com/gear-tech/sails") .branch_name("master") diff --git a/examples/rmrk/resource/app/src/catalogs.rs b/examples/rmrk/resource/app/src/catalogs.rs index ca16f0489..7684a9604 100644 --- a/examples/rmrk/resource/app/src/catalogs.rs +++ b/examples/rmrk/resource/app/src/catalogs.rs @@ -1,3 +1 @@ -use sails_rs::collections::BTreeMap; - include!("rmrk_catalog.rs"); diff --git a/examples/rmrk/resource/app/src/lib.rs b/examples/rmrk/resource/app/src/lib.rs index 5c681cac9..f4c524209 100644 --- a/examples/rmrk/resource/app/src/lib.rs +++ b/examples/rmrk/resource/app/src/lib.rs @@ -11,7 +11,7 @@ use sails_rs::{ }; use services::ResourceStorage; -mod catalogs; +pub mod catalogs; // Exposed publicly because of tests which use generated data // while there is no generated client pub mod services; diff --git a/examples/rmrk/resource/app/src/rmrk_catalog.rs b/examples/rmrk/resource/app/src/rmrk_catalog.rs index 276ca2bf7..6c4b53c85 100644 --- a/examples/rmrk/resource/app/src/rmrk_catalog.rs +++ b/examples/rmrk/resource/app/src/rmrk_catalog.rs @@ -1,5 +1,6 @@ // Code generated by sails-client-gen. DO NOT EDIT. -use sails_rs::{client::*, prelude::*}; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct RmrkCatalogProgram; impl Program for RmrkCatalogProgram {} pub trait RmrkCatalog { diff --git a/examples/rmrk/resource/wasm/src/rmrk_resource.rs b/examples/rmrk/resource/wasm/src/rmrk_resource.rs index eef8dd476..612d7cd93 100644 --- a/examples/rmrk/resource/wasm/src/rmrk_resource.rs +++ b/examples/rmrk/resource/wasm/src/rmrk_resource.rs @@ -1,5 +1,6 @@ // Code generated by sails-client-gen. DO NOT EDIT. -use sails_rs::{client::*, prelude::*}; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct RmrkResourceProgram; impl Program for RmrkResourceProgram {} pub trait RmrkResource { diff --git a/rs/client-gen/src/events_generator.rs b/rs/client-gen/src/events_generator.rs index ab2fd7069..b24d0f6b7 100644 --- a/rs/client-gen/src/events_generator.rs +++ b/rs/client-gen/src/events_generator.rs @@ -1,4 +1,3 @@ -use crate::helpers::method_bytes; use genco::prelude::*; use sails_idl_parser::{ast::visitor, ast::visitor::Visitor, ast::*}; diff --git a/rs/client-gen/src/helpers.rs b/rs/client-gen/src/helpers.rs index 8c115dde2..254fcebf2 100644 --- a/rs/client-gen/src/helpers.rs +++ b/rs/client-gen/src/helpers.rs @@ -1,37 +1,6 @@ use crate::type_generators::generate_type_decl_with_path; -use parity_scale_codec::Encode; use sails_idl_parser::ast::FuncParam; -pub(crate) fn path_bytes(path: &str) -> (String, usize) { - if path.is_empty() { - (String::new(), 0) - } else { - let service_path_bytes = path.encode(); - let service_path_encoded_length = service_path_bytes.len(); - let mut service_path_bytes = service_path_bytes - .into_iter() - .map(|x| x.to_string()) - .collect::>() - .join(","); - - service_path_bytes.push(','); - - (service_path_bytes, service_path_encoded_length) - } -} - -pub(crate) fn method_bytes(fn_name: &str) -> (String, usize) { - let route_bytes = fn_name.encode(); - let route_encoded_length = route_bytes.len(); - let route_bytes = route_bytes - .into_iter() - .map(|x| x.to_string()) - .collect::>() - .join(","); - - (route_bytes, route_encoded_length) -} - pub(crate) fn fn_args(params: &[FuncParam]) -> String { params .iter() diff --git a/rs/client-gen/src/root_generator.rs b/rs/client-gen/src/root_generator.rs index cb981b33b..f48aac5da 100644 --- a/rs/client-gen/src/root_generator.rs +++ b/rs/client-gen/src/root_generator.rs @@ -59,7 +59,8 @@ impl<'a> RootGenerator<'a> { }; let mut tokens = quote! { - use $(self.sails_path)::{client::*, prelude::*}; + #[allow(unused_imports)] + use $(self.sails_path)::{client::*, collections::*, prelude::*}; }; for (&name, &path) in &self.external_types { diff --git a/rs/client-gen/src/service_generators.rs b/rs/client-gen/src/service_generators.rs index a853257c4..f07eeabfc 100644 --- a/rs/client-gen/src/service_generators.rs +++ b/rs/client-gen/src/service_generators.rs @@ -29,7 +29,7 @@ impl<'a> ServiceCtorGenerator<'a> { } impl<'ast> Visitor<'ast> for ServiceCtorGenerator<'_> { - fn visit_service(&mut self, service: &'ast Service) { + fn visit_service(&mut self, _service: &'ast Service) { let service_name_snake = &self.service_name.to_case(Case::Snake); quote_in!(self.trait_tokens => fn $service_name_snake(&self) -> Service; diff --git a/rs/client-gen/tests/snapshots/generator__basic_works.snap b/rs/client-gen/tests/snapshots/generator__basic_works.snap index 64712dd34..8040c9b61 100644 --- a/rs/client-gen/tests/snapshots/generator__basic_works.snap +++ b/rs/client-gen/tests/snapshots/generator__basic_works.snap @@ -3,7 +3,8 @@ source: rs/client-gen/tests/generator.rs expression: "gen_client(idl, \"Basic\")" --- // Code generated by sails-client-gen. DO NOT EDIT. -use sails_rs::{client::*, prelude::*}; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct BasicProgram; impl Program for BasicProgram {} pub trait Basic { diff --git a/rs/client-gen/tests/snapshots/generator__events_works.snap b/rs/client-gen/tests/snapshots/generator__events_works.snap index 99541659c..456c0d50b 100644 --- a/rs/client-gen/tests/snapshots/generator__events_works.snap +++ b/rs/client-gen/tests/snapshots/generator__events_works.snap @@ -3,7 +3,8 @@ source: rs/client-gen/tests/generator.rs expression: "gen_client(idl, \"ServiceWithEvents\")" --- // Code generated by sails-client-gen. DO NOT EDIT. -use sails_rs::{client::*, prelude::*}; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct ServiceWithEventsProgram; impl Program for ServiceWithEventsProgram {} pub trait ServiceWithEvents { diff --git a/rs/client-gen/tests/snapshots/generator__external_types.snap b/rs/client-gen/tests/snapshots/generator__external_types.snap index b0c549930..144a7fa1d 100644 --- a/rs/client-gen/tests/snapshots/generator__external_types.snap +++ b/rs/client-gen/tests/snapshots/generator__external_types.snap @@ -5,7 +5,8 @@ expression: code // Code generated by sails-client-gen. DO NOT EDIT. #[allow(unused_imports)] use my_crate::MyParam; -use my_crate::sails::{client::*, prelude::*}; +#[allow(unused_imports)] +use my_crate::sails::{client::*, collections::*, prelude::*}; pub struct ServiceProgram; impl Program for ServiceProgram {} pub trait Service { diff --git a/rs/client-gen/tests/snapshots/generator__full.snap b/rs/client-gen/tests/snapshots/generator__full.snap index 92151adb9..b9fb9762d 100644 --- a/rs/client-gen/tests/snapshots/generator__full.snap +++ b/rs/client-gen/tests/snapshots/generator__full.snap @@ -3,7 +3,8 @@ source: rs/client-gen/tests/generator.rs expression: "gen_client(IDL, \"Service\")" --- // Code generated by sails-client-gen. DO NOT EDIT. -use sails_rs::{client::*, prelude::*}; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct ServiceProgram; impl Program for ServiceProgram {} pub trait Service { diff --git a/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap b/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap index 275f05e02..a17a1386e 100644 --- a/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap +++ b/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap @@ -3,7 +3,8 @@ source: rs/client-gen/tests/generator.rs expression: code --- // Code generated by sails-client-gen. DO NOT EDIT. -use my_crate::sails::{client::*, prelude::*}; +#[allow(unused_imports)] +use my_crate::sails::{client::*, collections::*, prelude::*}; pub struct ServiceProgram; impl Program for ServiceProgram {} pub trait Service { diff --git a/rs/client-gen/tests/snapshots/generator__multiple_services.snap b/rs/client-gen/tests/snapshots/generator__multiple_services.snap index 4506456d6..edc31d3c2 100644 --- a/rs/client-gen/tests/snapshots/generator__multiple_services.snap +++ b/rs/client-gen/tests/snapshots/generator__multiple_services.snap @@ -3,7 +3,8 @@ source: rs/client-gen/tests/generator.rs expression: "gen_client(idl, \"Multiple\")" --- // Code generated by sails-client-gen. DO NOT EDIT. -use sails_rs::{client::*, prelude::*}; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct MultipleProgram; impl Program for MultipleProgram {} pub trait Multiple { diff --git a/rs/client-gen/tests/snapshots/generator__nonzero_works.snap b/rs/client-gen/tests/snapshots/generator__nonzero_works.snap index 53f24e2a7..5948fd6c8 100644 --- a/rs/client-gen/tests/snapshots/generator__nonzero_works.snap +++ b/rs/client-gen/tests/snapshots/generator__nonzero_works.snap @@ -3,7 +3,8 @@ source: rs/client-gen/tests/generator.rs expression: "gen_client(idl, \"NonZeroParams\")" --- // Code generated by sails-client-gen. DO NOT EDIT. -use sails_rs::{client::*, prelude::*}; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct NonZeroParamsProgram; impl Program for NonZeroParamsProgram {} pub trait NonZeroParams { diff --git a/rs/client-gen/tests/snapshots/generator__rmrk_works.snap b/rs/client-gen/tests/snapshots/generator__rmrk_works.snap index 4c3c64003..3fa77a585 100644 --- a/rs/client-gen/tests/snapshots/generator__rmrk_works.snap +++ b/rs/client-gen/tests/snapshots/generator__rmrk_works.snap @@ -3,7 +3,8 @@ source: rs/client-gen/tests/generator.rs expression: "gen_client(idl, \"RmrkCatalog\")" --- // Code generated by sails-client-gen. DO NOT EDIT. -use sails_rs::{client::*, prelude::*}; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct RmrkCatalogProgram; impl Program for RmrkCatalogProgram {} pub trait RmrkCatalog { diff --git a/rs/src/client/mod.rs b/rs/src/client/mod.rs index b827ccade..90c783cca 100644 --- a/rs/src/client/mod.rs +++ b/rs/src/client/mod.rs @@ -194,7 +194,7 @@ impl ServiceListener { } pub async fn listen( - &mut self, + &self, ) -> Result + Unpin, ::Error> where E: Listener::Error>, From 374260fd7ebf239271b8d4b7ed2235df9633cb81 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Thu, 4 Sep 2025 17:29:35 +0200 Subject: [PATCH 15/66] wip: benchmarks refactoring --- benchmarks/bench_data.json | 24 +-- benchmarks/src/benchmarks.rs | 364 ++++++++++++++++------------------- rs/src/client/gtest_env.rs | 22 +++ 3 files changed, 201 insertions(+), 209 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 622dbdb7b..634e3b542 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -1,23 +1,23 @@ { "compute": { - "median": 450513691329 + "median": 450514033034 }, "alloc": { - "0": 563795739, - "12": 567302331, - "143": 726670926, - "986": 827656693, - "10945": 2018421219, - "46367": 6403682702, - "121392": 16858374460, - "317810": 43146693797 + "0": 564372814, + "12": 567860771, + "143": 727294202, + "986": 828286386, + "10945": 2019063746, + "46367": 6404347595, + "121392": 16859080113, + "317810": 43147405867 }, "counter": { - "async_call": 850475467, - "sync_call": 677853594 + "async_call": 851475728, + "sync_call": 678856494 }, "cross_program": { - "median": 2511553138 + "median": 2445884441 }, "redirect": { "median": 3612929773 diff --git a/benchmarks/src/benchmarks.rs b/benchmarks/src/benchmarks.rs index 03028e712..afee29f47 100644 --- a/benchmarks/src/benchmarks.rs +++ b/benchmarks/src/benchmarks.rs @@ -19,141 +19,28 @@ use crate::clients::{ alloc_stress_client::{ - AllocStressProgramFactory, alloc_stress::io::AllocStress, - traits::AllocStressProgramFactory as _, + AllocStressProgram, AllocStressProgramCtors, AllocStressProgramProgram, alloc_stress::*, }, compute_stress_client::{ - ComputeStressProgramFactory, compute_stress::io::ComputeStress, - traits::ComputeStressProgramFactory as _, + ComputeStressProgram, ComputeStressProgramCtors, ComputeStressProgramProgram, + compute_stress::*, }, counter_bench_client::{ - CounterBenchProgramFactory, - counter_bench::io::{Inc, IncAsync}, - traits::CounterBenchProgramFactory as _, + CounterBenchProgram, CounterBenchProgramCtors, CounterBenchProgramProgram, counter_bench::*, }, }; use gtest::{System, constants::DEFAULT_USER_ALICE}; use itertools::{Either, Itertools}; use ping_pong_bench_app::client::{ - PingPongFactory, PingPongPayload, ping_pong_service::io::Ping, traits::PingPongFactory as _, -}; -use redirect_client::{ - RedirectClientFactory, redirect::io::Exit, traits::RedirectClientFactory as _, -}; -use redirect_proxy_client::{ - RedirectProxyClientFactory, proxy::io::GetProgramId, traits::RedirectProxyClientFactory as _, -}; -use sails_rs::{ - calls::{ActionIo, Activation}, - gtest::calls::GTestRemoting, + PingPong, PingPongCtors, PingPongPayload, PingPongProgram, ping_pong_service::*, }; +use redirect_client::{redirect::*, *}; +use redirect_proxy_client::{proxy::*, *}; +use sails_rs::{client::*, prelude::*}; use std::{collections::BTreeMap, sync::atomic::AtomicU64}; static COUNTER_SALT: AtomicU64 = AtomicU64::new(0); -macro_rules! create_program_async { - ($(($factory:ty, $wasm_path:expr)),+) => {{ - create_program_async!($(($factory, $wasm_path, new_for_bench)),+) - }}; - - ($(($factory:ty, $wasm_path:expr, $ctor_name:ident $(, $ctor_params:expr),*)),+) => {{ - let system = System::new(); - system.mint_to(DEFAULT_USER_ALICE, 1_000_000_000_000_000); - - $( - #[allow(unused)] - let code_id = system.submit_local_code_file($wasm_path); - )* - - let remoting = GTestRemoting::new(system, DEFAULT_USER_ALICE.into()); - - ( - remoting.clone(), - $( - { - let salt = COUNTER_SALT.fetch_add(1, std::sync::atomic::Ordering::SeqCst) - .to_le_bytes(); - let factory = <$factory>::new(remoting.clone()); - factory - .$ctor_name($($ctor_params),*) - .send_recv(code_id, &salt) - .await - .expect("failed to initialize the program") - } - ),* - ) - }}; - - ($remoting:expr, $(($factory:ty, $wasm_path:expr)),+) => {{ - create_program_async!($remoting, $(($factory, $wasm_path, new_for_bench)),+) - }}; - - ($remoting:expr, $(($factory:ty, $wasm_path:expr, $ctor_name:ident $(, $ctor_params:expr),*)),+) => {{ - let remoting = $remoting; - - ( - remoting.clone(), - $({ - let code_id = remoting.system().submit_local_code_file($wasm_path); - let salt = COUNTER_SALT.fetch_add(1, std::sync::atomic::Ordering::SeqCst) - .to_le_bytes(); - let factory = <$factory>::new(remoting.clone()); - factory - .$ctor_name($($ctor_params),*) - .send_recv(code_id, &salt) - .await - .expect("failed to initialize the program") - }),* - ) - }}; -} - -macro_rules! call_action { - ($remoting:expr, $pid:expr, $action:ty $(, $action_params:expr),*) => {{ - call_action!($remoting, $pid, $action $(, $action_params),* ; with_reply) - }}; - - ($remoting:expr, $pid:expr, $action:ty $(, $action_params:expr),* ; no_reply_check) => {{ - call_action!($remoting, $pid, $action $(, $action_params),* ; no_reply) - }}; - - ($remoting:expr, $pid:expr, $action:ty $(, $action_params:expr),* ; $mode:ident) => {{ - let system_remoting = $remoting.system(); - let program = system_remoting - .get_program($pid) - .expect("program was created; qed."); - let from = $remoting.actor_id(); - - // Form payload for the program - let payload = <$action>::encode_call($($action_params),*); - let mid = program.send_bytes(from, payload); - let block_res = system_remoting.run_next_block(); - assert!(block_res.succeed.contains(&mid)); - - call_action!(@handle_result $mode, block_res, mid, $action) - }}; - - (@handle_result with_reply, $block_res:expr, $mid:expr, $action:ty) => {{ - let payload = $block_res - .log() - .iter() - .find_map(|log| { - log.reply_to() - .filter(|reply_to| reply_to == &$mid) - .map(|_| log.payload().to_vec()) - }) - .expect("reply found"); - - let resp = <$action>::decode_reply(payload).expect("decode reply"); - let gas = $block_res.gas_burned.get(&$mid).copied().expect("gas recorded"); - (resp, gas) - }}; - - (@handle_result no_reply, $block_res:expr, $mid:expr, $action:ty) => {{ - $block_res.gas_burned.get(&$mid).copied().expect("gas recorded") - }}; -} - #[tokio::test] async fn alloc_stress_bench() { let mut benches: BTreeMap> = Default::default(); @@ -178,16 +65,25 @@ async fn alloc_stress_bench() { #[tokio::test] async fn compute_stress_bench() { let wasm_path = "../target/wasm32-gear/release/compute_stress.opt.wasm"; - - let (remoting, pid) = - create_program_async!((ComputeStressProgramFactory::, wasm_path)); + let env = create_env(); + let program = deploy_for_bench(&env, wasm_path, |d| { + ComputeStressProgramCtors::new_for_bench(d) + }) + .await; + let mut service = program.compute_stress(); let input_value = 30; let expected_sum = compute_stress::sum_of_fib(input_value); let mut gas_benches = (0..100) .map(|_| { - let (stress_resp, gas) = call_action!(remoting, pid, ComputeStress, input_value); + let message_id = service.compute_stress(input_value).send_one_way().unwrap(); + let (payload, gas) = extract_reply_and_gas(env.system(), message_id); + let stress_resp = crate::clients::compute_stress_client::compute_stress::io::ComputeStress::decode_reply_with_prefix( + "ComputeStress", + payload.as_slice(), + ) + .unwrap(); assert_eq!(stress_resp.res, expected_sum); gas }) @@ -203,9 +99,12 @@ async fn compute_stress_bench() { #[tokio::test] async fn counter_bench() { let wasm_path = "../target/wasm32-gear/release/counter_bench.opt.wasm"; - - let (remoting, pid) = - create_program_async!((CounterBenchProgramFactory::, wasm_path)); + let env = create_env(); + let program = deploy_for_bench(&env, wasm_path, |d| { + CounterBenchProgramCtors::new_for_bench(d) + }) + .await; + let mut service = program.counter_bench(); let mut expected_value = 0; let (mut gas_benches_sync, mut gas_benches_async): (Vec<_>, Vec<_>) = (0..100) @@ -213,17 +112,29 @@ async fn counter_bench() { .map(|(i, _)| { let is_sync = i % 2 == 0; let gas = if is_sync { - let (stress_resp, gas_sync_inc) = call_action!(remoting, pid, Inc); + let message_id = service.inc().send_one_way().unwrap(); + let (payload, gas) = extract_reply_and_gas(env.system(), message_id); + let stress_resp = crate::clients::counter_bench_client::counter_bench::io::Inc::decode_reply_with_prefix( + "CounterBench", + payload.as_slice(), + ) + .unwrap(); assert_eq!(stress_resp, expected_value); expected_value += 1; - gas_sync_inc + gas } else { - let (stress_resp, gas_async_inc) = call_action!(remoting, pid, IncAsync); + let message_id = service.inc_async().send_one_way().unwrap(); + let (payload, gas) = extract_reply_and_gas(env.system(), message_id); + let stress_resp = crate::clients::counter_bench_client::counter_bench::io::IncAsync::decode_reply_with_prefix( + "CounterBench", + payload.as_slice(), + ) + .unwrap(); assert_eq!(stress_resp, expected_value); expected_value += 1; - gas_async_inc + gas }; (i, gas) @@ -249,19 +160,25 @@ async fn counter_bench() { #[tokio::test] async fn cross_program_bench() { let wasm_path = "../target/wasm32-gear/release/ping_pong_bench_app.opt.wasm"; - let (remoting, start_ping_pid, pong_pid) = create_program_async!( - (PingPongFactory::, wasm_path), - (PingPongFactory::, wasm_path) - ); + let env = create_env(); + let program_ping = deploy_for_bench(&env, wasm_path, |d| PingPongCtors::new_for_bench(d)).await; + let program_pong = deploy_for_bench(&env, wasm_path, |d| PingPongCtors::new_for_bench(d)).await; + + let mut service = program_ping.ping_pong_service(); let mut gas_benches = (0..100) .map(|_| { - let (stress_resp, gas) = call_action!( - remoting, - start_ping_pid, - Ping, - PingPongPayload::Start(pong_pid) - ); + let message_id = service + .ping(PingPongPayload::Start(program_pong.id())) + .send_one_way() + .unwrap(); + let (payload, gas) = extract_reply_and_gas(env.system(), message_id); + let stress_resp = + ping_pong_bench_app::client::ping_pong_service::io::Ping::decode_reply_with_prefix( + "PingPongService", + payload.as_slice(), + ) + .unwrap(); assert_eq!(stress_resp, PingPongPayload::Finished); gas }) @@ -274,65 +191,75 @@ async fn cross_program_bench() { .unwrap(); } -#[tokio::test] -async fn redirect_bench() { - let redirect_wasm_path = "../target/wasm32-gear/release/redirect_app.opt.wasm"; - let proxy_wasm_path = "../target/wasm32-gear/release/redirect_proxy.opt.wasm"; - - let (remoting, redirect_pid1, redirect_pid2) = create_program_async!( - ( - RedirectClientFactory::, - redirect_wasm_path, - new - ), - ( - RedirectClientFactory::, - redirect_wasm_path, - new - ) - ); - let (remoting, proxy_pid) = create_program_async!( - remoting, - ( - RedirectProxyClientFactory::, - proxy_wasm_path, - new, - redirect_pid1 - ) - ); - - // Warm-up proxy program - (0..100).for_each(|_| { - let (resp, _) = call_action!(remoting, proxy_pid, GetProgramId); - assert_eq!(resp, redirect_pid1); - }); - - // Call exit on a redirect program - call_action!(remoting, redirect_pid1, Exit, redirect_pid2; no_reply_check); - - // Bench proxy program - let gas_benches = (0..100) - .map(|_| { - let (resp, gas_get_program) = call_action!(remoting, proxy_pid, GetProgramId); - assert_eq!(resp, redirect_pid2); - - gas_get_program - }) - .collect::>(); - - crate::store_bench_data(|bench_data| { - bench_data.update_redirect_bench(median(gas_benches)); - }) - .unwrap(); -} +// #[tokio::test] +// async fn redirect_bench() { +// let redirect_wasm_path = "../target/wasm32-gear/release/redirect_app.opt.wasm"; +// let proxy_wasm_path = "../target/wasm32-gear/release/redirect_proxy.opt.wasm"; + +// let (remoting, redirect_pid1, redirect_pid2) = create_program_async!( +// ( +// RedirectClientFactory::, +// redirect_wasm_path, +// new +// ), +// ( +// RedirectClientFactory::, +// redirect_wasm_path, +// new +// ) +// ); +// let (remoting, proxy_pid) = create_program_async!( +// remoting, +// ( +// RedirectProxyClientFactory::, +// proxy_wasm_path, +// new, +// redirect_pid1 +// ) +// ); + +// // Warm-up proxy program +// (0..100).for_each(|_| { +// let (resp, _) = call_action!(remoting, proxy_pid, GetProgramId); +// assert_eq!(resp, redirect_pid1); +// }); + +// // Call exit on a redirect program +// call_action!(remoting, redirect_pid1, Exit, redirect_pid2; no_reply_check); + +// // Bench proxy program +// let gas_benches = (0..100) +// .map(|_| { +// let (resp, gas_get_program) = call_action!(remoting, proxy_pid, GetProgramId); +// assert_eq!(resp, redirect_pid2); + +// gas_get_program +// }) +// .collect::>(); + +// crate::store_bench_data(|bench_data| { +// bench_data.update_redirect_bench(median(gas_benches)); +// }) +// .unwrap(); +// } async fn alloc_stress_test(n: u32) -> (usize, u64) { // Path taken from the .binpath file let wasm_path = "../target/wasm32-gear/release/alloc_stress.opt.wasm"; - - let (remoting, pid) = - create_program_async!((AllocStressProgramFactory::, wasm_path)); - let (stress_resp, gas) = call_action!(remoting, pid, AllocStress, n); + let env = create_env(); + let program = deploy_for_bench(&env, wasm_path, |d| { + AllocStressProgramCtors::new_for_bench(d) + }) + .await; + + let mut service = program.alloc_stress(); + let message_id = service.alloc_stress(n).send_one_way().unwrap(); + let (payload, gas) = extract_reply_and_gas(env.system(), message_id); + let stress_resp = crate::clients::alloc_stress_client::alloc_stress::io::AllocStress::decode_reply_with_prefix( + "AllocStress", + payload.as_slice(), + ) + .unwrap(); let expected_len = alloc_stress::fibonacci_sum(n) as usize; assert_eq!(stress_resp.inner.len(), expected_len); @@ -340,6 +267,49 @@ async fn alloc_stress_test(n: u32) -> (usize, u64) { (expected_len, gas) } +fn create_env() -> GtestEnv { + let system = System::new(); + system.mint_to(DEFAULT_USER_ALICE, 1_000_000_000_000_000); + GtestEnv::new(system, DEFAULT_USER_ALICE.into()) +} + +async fn deploy_for_bench< + P: Program, + IO: CallEncodeDecode, + F: FnOnce(Deployment) -> PendingCtor, +>( + env: &GtestEnv, + wasm_path: &str, + f: F, +) -> Actor { + let code_id = env.system().submit_local_code_file(wasm_path); + let salt = COUNTER_SALT + .fetch_add(1, std::sync::atomic::Ordering::SeqCst) + .to_le_bytes() + .to_vec(); + let deployment = env.deploy::

(code_id, salt); let ctor = f(deployment); - let program = ctor.await.expect("failed to initialize the program"); + let program = ctor + .with_value(100_000_000_000_000) + .await + .expect("failed to initialize the program"); program } diff --git a/examples/ping-pong-stack/ping_pong_stack.idl b/examples/ping-pong-stack/ping_pong_stack.idl index bcb42c970..a742c06c4 100644 --- a/examples/ping-pong-stack/ping_pong_stack.idl +++ b/examples/ping-pong-stack/ping_pong_stack.idl @@ -1,8 +1,9 @@ constructor { - NewForBench : (); + CreatePing : (code_id: code_id); + CreatePong : (); }; service PingPongStack { - Start : (actor_id: actor_id, limit: u32) -> null; + Start : (limit: u32) -> null; Ping : (countdown: u32) -> null; }; diff --git a/examples/ping-pong-stack/src/lib.rs b/examples/ping-pong-stack/src/lib.rs index 474960552..32777cb24 100644 --- a/examples/ping-pong-stack/src/lib.rs +++ b/examples/ping-pong-stack/src/lib.rs @@ -1,26 +1,32 @@ #![no_std] -use client::{PingPongStack as _, ping_pong_stack::PingPongStack as _}; +use client::{ + PingPongStack as _, PingPongStackCtors as _, PingPongStackProgram, + ping_pong_stack::PingPongStack as _, +}; use sails_rs::{client::Program as _, gstd::*, prelude::*}; -struct PingPongStack; +struct PingPongStack(ActorId); impl PingPongStack { - pub fn new() -> Self { - Self + pub fn new(actor_id: ActorId) -> Self { + Self(actor_id) } } #[sails_rs::service] impl PingPongStack { #[export] - pub async fn start(&mut self, actor_id: ActorId, limit: u32) { - self.call(actor_id, limit).await; + pub async fn start(&mut self, limit: u32) { + if self.0 == ActorId::zero() { + panic!("Pong actor not set") + } + self.call(self.0, limit).await; } #[export] pub async fn ping(&mut self, countdown: u32) { - let source = msg::source(); + let source = Syscall::message_source(); self.call(source, countdown - 1).await; } @@ -28,7 +34,7 @@ impl PingPongStack { async fn call(&mut self, actor_id: ActorId, countdown: u32) -> bool { sails_rs::gstd::debug!("Ping: {countdown}, actor_id: {actor_id}"); if countdown > 0 { - let mut api = client::PingPongStackProgram::client(actor_id).ping_pong_stack(); + let mut api = PingPongStackProgram::client(actor_id).ping_pong_stack(); let _res = api.ping(countdown).with_reply_deposit(10_000_000_000).await; sails_rs::gstd::debug!("Result: {_res:?}"); debug_assert!(_res.is_ok()); @@ -40,17 +46,27 @@ impl PingPongStack { } #[derive(Default)] -pub struct Program; +pub struct Program(ActorId); #[sails_rs::program] impl Program { - pub fn new_for_bench() -> Self { - Self + pub async fn create_ping(code_id: CodeId) -> Self { + let msg_id = Syscall::message_id(); + let actor = PingPongStackProgram::deploy(code_id, msg_id.into_bytes().into()) + .create_pong() + .with_reply_deposit(10_000_000_000) + .await + .unwrap(); + Self(actor.id()) + } + + pub fn create_pong() -> Self { + Self(ActorId::zero()) } // Exposed service pub fn ping_pong_stack(&self) -> PingPongStack { - PingPongStack::new() + PingPongStack::new(self.0) } } diff --git a/examples/ping-pong-stack/src/ping_pong_stack.rs b/examples/ping-pong-stack/src/ping_pong_stack.rs index 6d3980fc3..3ab3a21ca 100644 --- a/examples/ping-pong-stack/src/ping_pong_stack.rs +++ b/examples/ping-pong-stack/src/ping_pong_stack.rs @@ -21,35 +21,42 @@ impl PingPongStack } pub trait PingPongStackCtors { type Env: sails_rs::client::GearEnv; - fn new_for_bench( + fn create_ping( self, - ) -> sails_rs::client::PendingCtor; + code_id: CodeId, + ) -> sails_rs::client::PendingCtor; + fn create_pong( + self, + ) -> sails_rs::client::PendingCtor; } impl PingPongStackCtors for sails_rs::client::Deployment { type Env = E; - fn new_for_bench( + fn create_ping( + self, + code_id: CodeId, + ) -> sails_rs::client::PendingCtor { + self.pending_ctor((code_id,)) + } + fn create_pong( self, - ) -> sails_rs::client::PendingCtor { + ) -> sails_rs::client::PendingCtor { self.pending_ctor(()) } } pub mod io { use super::*; - sails_rs::io_struct_impl!(NewForBench () -> ()); + sails_rs::io_struct_impl!(CreatePing (code_id: CodeId) -> ()); + sails_rs::io_struct_impl!(CreatePong () -> ()); } pub mod ping_pong_stack { use super::*; pub trait PingPongStack { type Env: sails_rs::client::GearEnv; - fn start( - &mut self, - actor_id: ActorId, - limit: u32, - ) -> sails_rs::client::PendingCall; + fn start(&mut self, limit: u32) -> sails_rs::client::PendingCall; fn ping(&mut self, countdown: u32) -> sails_rs::client::PendingCall; } pub struct PingPongStackImpl; @@ -57,12 +64,8 @@ pub mod ping_pong_stack { for sails_rs::client::Service { type Env = E; - fn start( - &mut self, - actor_id: ActorId, - limit: u32, - ) -> sails_rs::client::PendingCall { - self.pending_call((actor_id, limit)) + fn start(&mut self, limit: u32) -> sails_rs::client::PendingCall { + self.pending_call((limit,)) } fn ping(&mut self, countdown: u32) -> sails_rs::client::PendingCall { self.pending_call((countdown,)) @@ -71,7 +74,7 @@ pub mod ping_pong_stack { pub mod io { use super::*; - sails_rs::io_struct_impl!(Start (actor_id: ActorId, limit: u32) -> ()); + sails_rs::io_struct_impl!(Start (limit: u32) -> ()); sails_rs::io_struct_impl!(Ping (countdown: u32) -> ()); } } diff --git a/examples/ping-pong-stack/tests/gtest.rs b/examples/ping-pong-stack/tests/gtest.rs index 438c7e55c..4213368ad 100644 --- a/examples/ping-pong-stack/tests/gtest.rs +++ b/examples/ping-pong-stack/tests/gtest.rs @@ -9,26 +9,17 @@ const ACTOR_ID: u64 = 42; async fn ping_pong_stack_works() { let (env, code_id, _gas_limit) = create_env(); - let program_1 = env + let program = env .deploy::(code_id, vec![1]) - .new_for_bench() - .await - .unwrap(); - - let program_2 = env - .deploy::(code_id, vec![2]) - .new_for_bench() + .create_ping(code_id) + .with_value(100_000_000_000_000) .await .unwrap(); let limit = 10; let initial_balance = env.system().balance_of(ACTOR_ID); - program_1 - .ping_pong_stack() - .start(program_2.id(), limit) - .await - .unwrap(); + program.ping_pong_stack().start(limit).await.unwrap(); let balance = env.system().balance_of(ACTOR_ID); From 022543fd9d1fd89054224966a9c5d42cd58797aa Mon Sep 17 00:00:00 2001 From: vobradovich Date: Tue, 23 Sep 2025 16:11:07 +0200 Subject: [PATCH 36/66] wip: asyn runtime pure perf --- benchmarks/bench_data.json | 4 +- rs/src/client/gstd_env.rs | 114 ++++++++++-------- rs/src/gstd/async_runtime.rs | 228 +++++++++++++++++++++++++++++++++++ rs/src/gstd/mod.rs | 8 +- 4 files changed, 298 insertions(+), 56 deletions(-) create mode 100644 rs/src/gstd/async_runtime.rs diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 56186fb3f..efb2d096d 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -13,8 +13,8 @@ "317810": 43147409792 }, "counter": { - "async_call": 851480134, - "sync_call": 678859589 + "async_call": 817057135, + "sync_call": 650947873 }, "cross_program": { "median": 2436719419 diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index 1cfc545a8..8bc2b9c40 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -1,8 +1,7 @@ +use crate::gstd::SimpleMessageFuture; + use super::*; -use ::gstd::{ - errors::Error, - msg::{CreateProgramFuture, MessageFuture}, -}; +use ::gstd::{errors::Error, msg::CreateProgramFuture}; #[derive(Default)] pub struct GstdParams { @@ -114,51 +113,55 @@ impl PendingCall { const _: () = { use core::task::ready; - #[cfg(not(feature = "ethexe"))] - #[inline] - fn send_for_reply_future( - destination: ActorId, - payload: &[u8], - params: &mut GstdParams, - ) -> Result { - let value = params.value.unwrap_or_default(); - let reply_deposit = params.reply_deposit.unwrap_or_default(); - // here can be a redirect target - let mut message_future = if let Some(gas_limit) = params.gas_limit { - ::gstd::msg::send_bytes_with_gas_for_reply( - destination, - payload, - gas_limit, - value, - reply_deposit, - )? - } else { - ::gstd::msg::send_bytes_for_reply(destination, payload, value, reply_deposit)? - }; - - message_future = message_future.up_to(params.wait_up_to)?; - - if let Some(reply_hook) = params.reply_hook.take() { - message_future = message_future.handle_reply(reply_hook)?; - } - Ok(message_future) - } - - #[cfg(feature = "ethexe")] - #[inline] - fn send_for_reply_future( - destination: ActorId, - payload: &[u8], - params: &mut GstdParams, - ) -> Result { - let value = params.value.unwrap_or_default(); - // here can be a redirect target - let mut message_future = ::gstd::msg::send_bytes_for_reply(destination, payload, value)?; - - message_future = message_future.up_to(params.wait_up_to)?; - - Ok(message_future) - } + // #[cfg(not(feature = "ethexe"))] + // #[inline] + // fn send_for_reply_future( + // destination: ActorId, + // payload: &[u8], + // params: &mut GstdParams, + // ) -> Result { + // let value = params.value.unwrap_or(0); + // // here can be a redirect target + // let mut message_future = if let Some(gas_limit) = params.gas_limit { + // ::gstd::msg::send_bytes_with_gas_for_reply( + // destination, + // payload, + // gas_limit, + // value, + // params.reply_deposit.unwrap_or_default(), + // )? + // } else { + // ::gstd::msg::send_bytes_for_reply( + // destination, + // payload, + // value, + // params.reply_deposit.unwrap_or_default(), + // )? + // }; + + // message_future = message_future.up_to(params.wait_up_to)?; + + // if let Some(reply_hook) = params.reply_hook.take() { + // message_future = message_future.handle_reply(reply_hook)?; + // } + // Ok(message_future) + // } + + // #[cfg(feature = "ethexe")] + // #[inline] + // fn send_for_reply_future( + // destination: ActorId, + // payload: &[u8], + // params: &mut GstdParams, + // ) -> Result { + // let value = params.value.unwrap_or(0); + // // here can be a redirect target + // let mut message_future = ::gstd::msg::send_bytes_for_reply(destination, payload, value)?; + + // message_future = message_future.up_to(params.wait_up_to)?; + + // Ok(message_future) + // } #[inline] fn send_for_reply( @@ -167,7 +170,14 @@ const _: () = { params: &mut GstdParams, ) -> Result { // send message - let future = send_for_reply_future(destination, payload.as_ref(), params)?; + // let future = send_for_reply_future(destination, payload, params)?; + let future = crate::gstd::send_bytes_for_reply( + destination, + payload, + params.value.unwrap_or_default(), + params.gas_limit, + params.reply_deposit, + )?; if params.redirect_on_exit { let created_block = params.wait_up_to.map(|_| gstd::exec::block_height()); Ok(GstdFuture::MessageWithRedirect { @@ -335,10 +345,10 @@ pin_project_lite::pin_project! { #[project_replace = Replace] pub enum GstdFuture { CreateProgram { #[pin] future: CreateProgramFuture }, - Message { #[pin] future: MessageFuture }, + Message { #[pin] future: SimpleMessageFuture }, MessageWithRedirect { #[pin] - future: MessageFuture, + future: SimpleMessageFuture, destination: ActorId, created_block: Option, payload: Vec, // reuse encoded payload when redirecting diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs new file mode 100644 index 000000000..b5da00fed --- /dev/null +++ b/rs/src/gstd/async_runtime.rs @@ -0,0 +1,228 @@ +use super::*; +use ::gstd::errors::Error; +use core::{ + pin::Pin, + task::{Context, Poll, ready}, +}; + +pub struct SimpleMessageFuture { + /// A message identifier for an expected reply. + /// + /// This identifier is generated by the corresponding send function (e.g. + /// [`send_bytes`](super::send_bytes)). + pub waiting_reply_to: MessageId, + // /// Reply deposit that was allocated for this message. Checked in + // /// handle_reply. + // #[cfg_attr(feature = "ethexe", allow(unused))] + pub reply_deposit: Option, +} + +impl Unpin for SimpleMessageFuture {} + +impl Future for SimpleMessageFuture { + type Output = Result, Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // let message_id = ::gcore::msg::id(); + + // check if message is timed out. + // if let Some((expected, now)) = async_runtime::locks().is_timeout(msg_id, waiting_reply_to) { + // // Remove lock after timeout. + // async_runtime::locks().remove(msg_id, waiting_reply_to); + + // return Poll::Ready(Err(Error::Timeout(expected, now))); + // } + let (payload, reply_code) = ready!(poll(&self.waiting_reply_to, cx)); + match reply_code { + ReplyCode::Success(_) => Poll::Ready(Ok(payload)), + ReplyCode::Error(reason) => Poll::Ready(Err(Error::ErrorReply(payload.into(), reason))), + ReplyCode::Unsupported => Poll::Ready(Err(Error::UnsupportedReply(payload))), + } + } +} + +pub fn send_bytes_for_reply( + destination: ActorId, + payload: &[u8], + value: ValueUnit, + gas_limit: Option, + reply_deposit: Option, +) -> Result { + let waiting_reply_to = if let Some(gas_limit) = gas_limit { + ::gcore::msg::send_with_gas(destination, payload, gas_limit, value).map_err(Error::Core)? + } else { + ::gcore::msg::send(destination, payload, value).map_err(Error::Core)? + }; + + if let Some(reply_deposit) = reply_deposit { + ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit)?; + } + + let message_id = ::gcore::msg::id(); + signals().insert( + waiting_reply_to, + ReplySignal::Pending { + message_id, + // waker: cx.waker().clone(), + }, + ); + + Ok(SimpleMessageFuture { + waiting_reply_to, + reply_deposit, + }) +} + +enum ReplySignal { + Pending { + message_id: MessageId, + // waker: task::Waker, + }, + Ready { + payload: Vec, + reply_code: ReplyCode, + }, +} + +// fn signals() -> &'static mut crate::collections::BTreeMap { +// static mut SIGNALS: crate::collections::BTreeMap = +// crate::collections::BTreeMap::new(); +// unsafe { &mut *core::ptr::addr_of_mut!(SIGNALS) } +// } + +fn signals() -> &'static mut crate::collections::HashMap { + static mut MAP: Option> = None; + unsafe { &mut *core::ptr::addr_of_mut!(MAP) } + .get_or_insert_with(crate::collections::HashMap::new) +} + +#[inline] +fn poll(reply_to: &MessageId, _cx: &mut Context<'_>) -> Poll<(Vec, ReplyCode)> { + let signals_map = signals(); + match signals_map.get(reply_to) { + Some(ReplySignal::Ready { .. }) => { + let ReplySignal::Ready { + payload, + reply_code, + } = signals_map.remove(reply_to).unwrap() + else { + unreachable!() + }; + Poll::Ready((payload, reply_code)) + } + Some(ReplySignal::Pending { .. }) => { + // waker.clone_from(cx.waker()); + Poll::Pending + } + None => panic!( + "Somebody created a future with the MessageId that never ended in static replies!" + ), + } +} + +// pub fn register_signal(waiting_reply_to: MessageId) { +// let message_id = ::gcore::msg::id(); + +// // crate::async_runtime::locks().lock(message_id, waiting_reply_to, Default::default()); +// } + +fn record_reply() { + let reply_to: MessageId = + ::gcore::msg::reply_to().expect("`gcore::msg::reply_to()` called in wrong context"); + let signals_map = signals(); + if let Some(ReplySignal::Pending { message_id }) = signals_map.remove(&reply_to) { + let payload = ::gstd::msg::load_bytes().expect("Failed to load bytes"); + let reply_code = + ::gcore::msg::reply_code().expect("Shouldn't be called with incorrect context"); + signals_map.insert( + reply_to, + ReplySignal::Ready { + payload, + reply_code, + }, + ); + + // waker.wake_by_ref(); + + ::gcore::exec::wake(message_id).expect("Failed to wake the message"); + } else { + ::gstd::debug!( + "A message has received a reply though it wasn't to receive one, or a processed message has received a reply" + ); + } +} + +type PinnedFuture = Pin + 'static>>; + +fn futures() -> &'static mut crate::collections::HashMap { + static mut MAP: Option> = None; + unsafe { &mut *core::ptr::addr_of_mut!(MAP) } + .get_or_insert_with(crate::collections::HashMap::new) +} + +pub const SYSTEM_RESERVE: u64 = 10_000_000_000; + +/// The main asynchronous message handling loop. +/// +/// Gear allows user and program interaction via +/// messages. This function is the entry point to run the asynchronous message +/// processing. +#[inline] +pub fn message_loop(future: F) +where + F: Future + 'static, +{ + let msg_id = ::gcore::msg::id(); + let futures_map = futures(); + let completed = { + let task = futures_map.entry(msg_id).or_insert_with(|| { + #[cfg(not(feature = "ethexe"))] + { + ::gcore::exec::system_reserve_gas(SYSTEM_RESERVE) + .expect("Failed to reserve gas for system signal"); + } + // Task::new(future) + Box::pin(future) + }); + + // if task.lock_exceeded { + // // Futures and locks for the message will be cleaned up by + // // the async_runtime::handle_signal function + // panic!( + // "Message 0x{} has exceeded lock ownership time", + // hex::encode(msg_id) + // ); + // } + + let mut cx = Context::from_waker(task::Waker::noop()); + + ::gstd::debug!("message_loop: polling future for {msg_id}"); + + task.as_mut().poll(&mut cx).is_ready() + }; + + if completed { + futures_map.remove(&msg_id); + // super::locks().remove_message_entry(msg_id); + // #[cfg(not(feature = "ethexe"))] + // let _ = critical::take_hook(); + } else { + ::gcore::exec::wait_up_to(100); + // super::locks().wait(msg_id); + } +} + +/// Default reply handler. +pub fn handle_reply_with_hook() { + record_reply(); + + // Execute reply hook (if it was registered) + // let replied_to = + // crate::msg::reply_to().expect("`gstd::handle_reply_with_hook()` called in wrong context"); + + // #[cfg(not(feature = "ethexe"))] + // reply_hooks().execute_and_remove(replied_to); + + // #[cfg(feature = "ethexe")] + // let _ = replied_to; +} diff --git a/rs/src/gstd/mod.rs b/rs/src/gstd/mod.rs index 14c2cc64c..fdc34f1aa 100644 --- a/rs/src/gstd/mod.rs +++ b/rs/src/gstd/mod.rs @@ -1,4 +1,9 @@ #[doc(hidden)] +// pub use gstd::{async_init, async_main, handle_reply_with_hook, message_loop}; +pub use async_runtime::{ + SimpleMessageFuture, handle_reply_with_hook, message_loop, send_bytes_for_reply, +}; +#[doc(hidden)] #[cfg(feature = "ethexe")] pub use ethexe::{EthEvent, EthEventExpo}; #[doc(hidden)] @@ -6,8 +11,6 @@ pub use events::{EventEmitter, SailsEvent}; #[cfg(not(feature = "ethexe"))] #[doc(hidden)] pub use gstd::handle_signal; -#[doc(hidden)] -pub use gstd::{async_init, async_main, handle_reply_with_hook, message_loop}; pub use gstd::{debug, exec, msg}; #[doc(hidden)] pub use sails_macros::{event, export, program, service}; @@ -20,6 +23,7 @@ use crate::{ }; use gcore::stack_buffer; +pub(crate) mod async_runtime; #[cfg(feature = "ethexe")] mod ethexe; mod events; From 7d59d6135f6fa0a5e0ee8bcef80329917ac78630 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 24 Sep 2025 15:11:55 +0200 Subject: [PATCH 37/66] wip: async runtime timeouts --- benchmarks/bench_data.json | 4 +- examples/ping-pong-stack/tests/gtest.rs | 2 +- rs/src/client/gstd_env.rs | 26 +++- rs/src/gstd/async_runtime.rs | 183 +++++++++++++++++------- rs/src/gstd/locks.rs | 102 +++++++++++++ rs/src/gstd/mod.rs | 5 +- 6 files changed, 261 insertions(+), 61 deletions(-) create mode 100644 rs/src/gstd/locks.rs diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index efb2d096d..85d176a2d 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -13,8 +13,8 @@ "317810": 43147409792 }, "counter": { - "async_call": 817057135, - "sync_call": 650947873 + "async_call": 833526292, + "sync_call": 665626558 }, "cross_program": { "median": 2436719419 diff --git a/examples/ping-pong-stack/tests/gtest.rs b/examples/ping-pong-stack/tests/gtest.rs index 4213368ad..224b15412 100644 --- a/examples/ping-pong-stack/tests/gtest.rs +++ b/examples/ping-pong-stack/tests/gtest.rs @@ -35,7 +35,7 @@ fn create_env() -> (GtestEnv, CodeId, GasUnit) { let system = System::new(); system.init_logger_with_default_filter( - "gwasm=debug,gtest=info,sails_rs=debug,ping_pong_stack=debug", + "gwasm=debug,gtest=info,sails_rs=debug,ping_pong_stack=debug,gstd=debug", ); system.mint_to(ACTOR_ID, 1_000_000_000_000_000); // Submit program code into the system diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index 8bc2b9c40..e7f12a698 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -1,6 +1,5 @@ -use crate::gstd::SimpleMessageFuture; - use super::*; +use crate::gstd::MessageFuture; use ::gstd::{errors::Error, msg::CreateProgramFuture}; #[derive(Default)] @@ -112,6 +111,7 @@ impl PendingCall { #[cfg(target_arch = "wasm32")] const _: () = { use core::task::ready; + use futures::future::FusedFuture; // #[cfg(not(feature = "ethexe"))] // #[inline] @@ -170,10 +170,10 @@ const _: () = { params: &mut GstdParams, ) -> Result { // send message - // let future = send_for_reply_future(destination, payload, params)?; + // let future = send_for_reply_future(destination, payload.as_ref(), params)?; let future = crate::gstd::send_bytes_for_reply( destination, - payload, + payload.as_ref(), params.value.unwrap_or_default(), params.gas_limit, params.reply_deposit, @@ -274,6 +274,20 @@ const _: () = { } } + impl FusedFuture for PendingCall { + fn is_terminated(&self) -> bool { + self.state + .as_ref() + .map(|future| match future { + GstdFuture::CreateProgram { future } => future.is_terminated(), + GstdFuture::Message { future } => future.is_terminated(), + GstdFuture::MessageWithRedirect { future, .. } => future.is_terminated(), + GstdFuture::Dummy => false, + }) + .unwrap_or_default() + } + } + impl Future for PendingCtor { type Output = Result, ::Error>; @@ -345,10 +359,10 @@ pin_project_lite::pin_project! { #[project_replace = Replace] pub enum GstdFuture { CreateProgram { #[pin] future: CreateProgramFuture }, - Message { #[pin] future: SimpleMessageFuture }, + Message { #[pin] future: MessageFuture }, MessageWithRedirect { #[pin] - future: SimpleMessageFuture, + future: MessageFuture, destination: ActorId, created_block: Option, payload: Vec, // reuse encoded payload when redirecting diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index b5da00fed..f7a38d629 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -1,11 +1,13 @@ use super::*; -use ::gstd::errors::Error; +use crate::collections::HashMap; +use ::gstd::{BlockNumber, errors::Error}; use core::{ pin::Pin, - task::{Context, Poll, ready}, + task::{Context, Poll}, }; +use futures::future::FusedFuture; -pub struct SimpleMessageFuture { +pub struct MessageFuture { /// A message identifier for an expected reply. /// /// This identifier is generated by the corresponding send function (e.g. @@ -17,27 +19,35 @@ pub struct SimpleMessageFuture { pub reply_deposit: Option, } -impl Unpin for SimpleMessageFuture {} +impl Unpin for MessageFuture {} -impl Future for SimpleMessageFuture { +impl Future for MessageFuture { type Output = Result, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - // let message_id = ::gcore::msg::id(); + // let msg_id = ::gcore::msg::id(); // check if message is timed out. - // if let Some((expected, now)) = async_runtime::locks().is_timeout(msg_id, waiting_reply_to) { + // if let Some((expected, now)) = locks().is_timeout(msg_id, self.waiting_reply_to) { // // Remove lock after timeout. - // async_runtime::locks().remove(msg_id, waiting_reply_to); + // async_runtime::locks().remove(msg_id, self.waiting_reply_to); // return Poll::Ready(Err(Error::Timeout(expected, now))); // } - let (payload, reply_code) = ready!(poll(&self.waiting_reply_to, cx)); - match reply_code { - ReplyCode::Success(_) => Poll::Ready(Ok(payload)), - ReplyCode::Error(reason) => Poll::Ready(Err(Error::ErrorReply(payload.into(), reason))), - ReplyCode::Unsupported => Poll::Ready(Err(Error::UnsupportedReply(payload))), - } + + poll(&self.waiting_reply_to, cx) + // locks().remove(msg_id, self.waiting_reply_to); + // match reply_code { + // ReplyCode::Success(_) => Poll::Ready(Ok(payload)), + // ReplyCode::Error(reason) => Poll::Ready(Err(Error::ErrorReply(payload.into(), reason))), + // ReplyCode::Unsupported => Poll::Ready(Err(Error::UnsupportedReply(payload))), + // } + } +} + +impl FusedFuture for MessageFuture { + fn is_terminated(&self) -> bool { + !signals().contains_key(&self.waiting_reply_to) } } @@ -47,7 +57,7 @@ pub fn send_bytes_for_reply( value: ValueUnit, gas_limit: Option, reply_deposit: Option, -) -> Result { +) -> Result { let waiting_reply_to = if let Some(gas_limit) = gas_limit { ::gcore::msg::send_with_gas(destination, payload, gas_limit, value).map_err(Error::Core)? } else { @@ -66,8 +76,18 @@ pub fn send_bytes_for_reply( // waker: cx.waker().clone(), }, ); + ::gstd::debug!( + "send_bytes_for_reply: add lock for reply_to {waiting_reply_to} in message {message_id}" + ); + tasks() + .get_mut(&message_id) + .expect("Message task must exist") + .reply_to_locks + .insert(waiting_reply_to, Default::default()); + + // locks().lock(message_id, waiting_reply_to, Default::default()); - Ok(SimpleMessageFuture { + Ok(MessageFuture { waiting_reply_to, reply_deposit, }) @@ -82,6 +102,10 @@ enum ReplySignal { payload: Vec, reply_code: ReplyCode, }, + Timeout { + expected: BlockNumber, + now: BlockNumber, + }, } // fn signals() -> &'static mut crate::collections::BTreeMap { @@ -97,9 +121,13 @@ fn signals() -> &'static mut crate::collections::HashMap } #[inline] -fn poll(reply_to: &MessageId, _cx: &mut Context<'_>) -> Poll<(Vec, ReplyCode)> { +fn poll(reply_to: &MessageId, _cx: &mut Context<'_>) -> Poll, Error>> { let signals_map = signals(); match signals_map.get(reply_to) { + Some(ReplySignal::Pending { .. }) => { + // waker.clone_from(cx.waker()); + Poll::Pending + } Some(ReplySignal::Ready { .. }) => { let ReplySignal::Ready { payload, @@ -108,11 +136,20 @@ fn poll(reply_to: &MessageId, _cx: &mut Context<'_>) -> Poll<(Vec, ReplyCode else { unreachable!() }; - Poll::Ready((payload, reply_code)) + match reply_code { + ReplyCode::Success(_) => Poll::Ready(Ok(payload)), + ReplyCode::Error(reason) => { + Poll::Ready(Err(Error::ErrorReply(payload.into(), reason))) + } + ReplyCode::Unsupported => Poll::Ready(Err(Error::UnsupportedReply(payload))), + } } - Some(ReplySignal::Pending { .. }) => { - // waker.clone_from(cx.waker()); - Poll::Pending + Some(ReplySignal::Timeout { .. }) => { + let ReplySignal::Timeout { expected, now } = signals_map.remove(reply_to).unwrap() + else { + unreachable!() + }; + Poll::Ready(Err(Error::Timeout(expected, now))) } None => panic!( "Somebody created a future with the MessageId that never ended in static replies!" @@ -141,6 +178,12 @@ fn record_reply() { reply_code, }, ); + ::gstd::debug!("record_reply: remove lock for reply_to {reply_to} in message {message_id}"); + tasks() + .get_mut(&message_id) + .expect("Message task must exist") + .reply_to_locks + .remove(&reply_to); // waker.wake_by_ref(); @@ -154,8 +197,56 @@ fn record_reply() { type PinnedFuture = Pin + 'static>>; -fn futures() -> &'static mut crate::collections::HashMap { - static mut MAP: Option> = None; +/// Matches a task to a some message in order to avoid duplicate execution +/// of code that was running before the program was interrupted by `wait`. +pub struct Task { + future: PinnedFuture, + reply_to_locks: HashMap, +} + +impl Task { + fn new(future: F) -> Self + where + F: Future + 'static, + { + Self { + future: Box::pin(future), + reply_to_locks: HashMap::new(), + } + } + + fn signal_reply_timeout(&mut self, now: BlockNumber) { + let signals_map = signals(); + + self.reply_to_locks + .extract_if(|_, lock| now >= lock.deadline()) + .for_each(|(reply_to, lock)| { + if let Some(ReplySignal::Pending { .. }) = signals_map.remove(&reply_to) { + signals_map.insert( + reply_to, + ReplySignal::Timeout { + expected: lock.deadline(), + now, + }, + ); + } + ::gstd::debug!( + "reply_timeout: remove lock for reply_to {reply_to} in message due to timeout" + ); + }); + } + + fn wait(&self, now: BlockNumber) { + self.reply_to_locks + .values() + .min_by(|lock1, lock2| lock1.cmp(lock2)) + .expect("Cannot find lock to be waited") + .wait(now); + } +} + +fn tasks() -> &'static mut crate::collections::HashMap { + static mut MAP: Option> = None; unsafe { &mut *core::ptr::addr_of_mut!(MAP) } .get_or_insert_with(crate::collections::HashMap::new) } @@ -173,42 +264,34 @@ where F: Future + 'static, { let msg_id = ::gcore::msg::id(); - let futures_map = futures(); - let completed = { - let task = futures_map.entry(msg_id).or_insert_with(|| { - #[cfg(not(feature = "ethexe"))] - { - ::gcore::exec::system_reserve_gas(SYSTEM_RESERVE) - .expect("Failed to reserve gas for system signal"); - } - // Task::new(future) - Box::pin(future) - }); - - // if task.lock_exceeded { - // // Futures and locks for the message will be cleaned up by - // // the async_runtime::handle_signal function - // panic!( - // "Message 0x{} has exceeded lock ownership time", - // hex::encode(msg_id) - // ); - // } + let tasks_map = tasks(); + let task = tasks_map.entry(msg_id).or_insert_with(|| { + #[cfg(not(feature = "ethexe"))] + { + ::gcore::exec::system_reserve_gas(SYSTEM_RESERVE) + .expect("Failed to reserve gas for system signal"); + } + Task::new(future) + }); - let mut cx = Context::from_waker(task::Waker::noop()); + // Check if any reply has timed out before polling them. + let current_block = exec::block_height(); + task.signal_reply_timeout(current_block); + let completed = { + let mut cx = Context::from_waker(task::Waker::noop()); ::gstd::debug!("message_loop: polling future for {msg_id}"); - - task.as_mut().poll(&mut cx).is_ready() + task.future.as_mut().poll(&mut cx).is_ready() }; if completed { - futures_map.remove(&msg_id); - // super::locks().remove_message_entry(msg_id); + tasks_map.remove(&msg_id); + // locks().remove_message_entry(msg_id); // #[cfg(not(feature = "ethexe"))] // let _ = critical::take_hook(); } else { - ::gcore::exec::wait_up_to(100); - // super::locks().wait(msg_id); + // ::gcore::exec::wait_up_to(100); + task.wait(current_block); } } diff --git a/rs/src/gstd/locks.rs b/rs/src/gstd/locks.rs new file mode 100644 index 000000000..49d20ad40 --- /dev/null +++ b/rs/src/gstd/locks.rs @@ -0,0 +1,102 @@ +use crate::prelude::*; +use core::cmp::Ordering; +use gstd::{BlockCount, BlockNumber, Config, exec}; + +/// Type of wait locks. +#[derive(Debug, PartialEq, Eq)] +pub(crate) enum LockType { + WaitFor(BlockCount), + WaitUpTo(BlockCount), +} + +/// Wait lock +#[derive(Debug, PartialEq, Eq)] +pub struct Lock { + /// The start block number of this lock. + pub at: BlockNumber, + /// The type of this lock. + ty: LockType, +} + +impl Lock { + /// Wait for + pub fn exactly(b: BlockCount) -> Self { + // if b == 0 { + // return Err(Error::Gstd(UsageError::EmptyWaitDuration)); + // } + + Self { + at: exec::block_height(), + ty: LockType::WaitFor(b), + } + } + + /// Wait up to + pub fn up_to(b: BlockCount) -> Self { + // if b == 0 { + // return Err(Error::Gstd(UsageError::EmptyWaitDuration)); + // } + + Self { + at: exec::block_height(), + ty: LockType::WaitUpTo(b), + } + } + + /// Call wait functions by the lock type. + pub fn wait(&self, now: BlockNumber) { + if let Some(blocks) = self.deadline().checked_sub(now) { + if blocks == 0 { + unreachable!( + "Checked in `crate::msg::async::poll`, will trigger the timeout error automatically." + ); + } + + match self.ty { + LockType::WaitFor(_) => exec::wait_for(blocks), + LockType::WaitUpTo(_) => exec::wait_up_to(blocks), + } + } else { + unreachable!( + "Checked in `crate::msg::async::poll`, will trigger the timeout error automatically." + ); + } + } + + /// Gets the deadline of the current lock. + pub fn deadline(&self) -> BlockNumber { + match &self.ty { + LockType::WaitFor(d) | LockType::WaitUpTo(d) => self.at.saturating_add(*d), + } + } + + /// Check if this lock is timed out. + pub fn timeout(&self, now: BlockNumber) -> Option<(BlockNumber, BlockNumber)> { + let expected = self.deadline(); + (now >= expected).then(|| (expected, now)) + } +} + +impl PartialOrd for Lock { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Lock { + fn cmp(&self, other: &Self) -> Ordering { + self.deadline().cmp(&other.deadline()) + } +} + +impl Default for Lock { + fn default() -> Self { + Lock::up_to(Config::wait_up_to()) + } +} + +impl Default for LockType { + fn default() -> Self { + LockType::WaitUpTo(Config::wait_up_to()) + } +} diff --git a/rs/src/gstd/mod.rs b/rs/src/gstd/mod.rs index fdc34f1aa..c09331fef 100644 --- a/rs/src/gstd/mod.rs +++ b/rs/src/gstd/mod.rs @@ -1,7 +1,7 @@ #[doc(hidden)] -// pub use gstd::{async_init, async_main, handle_reply_with_hook, message_loop}; +// pub use gstd::{handle_reply_with_hook, message_loop}; pub use async_runtime::{ - SimpleMessageFuture, handle_reply_with_hook, message_loop, send_bytes_for_reply, + MessageFuture, handle_reply_with_hook, message_loop, send_bytes_for_reply, }; #[doc(hidden)] #[cfg(feature = "ethexe")] @@ -27,6 +27,7 @@ pub(crate) mod async_runtime; #[cfg(feature = "ethexe")] mod ethexe; mod events; +pub(crate) mod locks; pub mod services; mod syscalls; From b9010d7630e577484a88d2b8f15f4e147f0561ad Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 24 Sep 2025 19:54:55 +0200 Subject: [PATCH 38/66] wip: refactor, opt --- benchmarks/bench_data.json | 4 +- rs/src/gstd/async_runtime.rs | 405 +++++++++++++++++------------------ rs/src/gstd/locks.rs | 6 - 3 files changed, 196 insertions(+), 219 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 85d176a2d..b7bbd2a9e 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -13,8 +13,8 @@ "317810": 43147409792 }, "counter": { - "async_call": 833526292, - "sync_call": 665626558 + "async_call": 820092342, + "sync_call": 652191920 }, "cross_program": { "median": 2436719419 diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index f7a38d629..512f36b39 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -1,206 +1,27 @@ use super::*; use crate::collections::HashMap; -use ::gstd::{BlockNumber, errors::Error}; use core::{ pin::Pin, task::{Context, Poll}, }; -use futures::future::FusedFuture; +use futures::future::{FusedFuture, FutureExt as _, LocalBoxFuture}; +use gstd::{BlockNumber, errors::Error}; -pub struct MessageFuture { - /// A message identifier for an expected reply. - /// - /// This identifier is generated by the corresponding send function (e.g. - /// [`send_bytes`](super::send_bytes)). - pub waiting_reply_to: MessageId, - // /// Reply deposit that was allocated for this message. Checked in - // /// handle_reply. - // #[cfg_attr(feature = "ethexe", allow(unused))] - pub reply_deposit: Option, -} - -impl Unpin for MessageFuture {} - -impl Future for MessageFuture { - type Output = Result, Error>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - // let msg_id = ::gcore::msg::id(); - - // check if message is timed out. - // if let Some((expected, now)) = locks().is_timeout(msg_id, self.waiting_reply_to) { - // // Remove lock after timeout. - // async_runtime::locks().remove(msg_id, self.waiting_reply_to); - - // return Poll::Ready(Err(Error::Timeout(expected, now))); - // } - - poll(&self.waiting_reply_to, cx) - // locks().remove(msg_id, self.waiting_reply_to); - // match reply_code { - // ReplyCode::Success(_) => Poll::Ready(Ok(payload)), - // ReplyCode::Error(reason) => Poll::Ready(Err(Error::ErrorReply(payload.into(), reason))), - // ReplyCode::Unsupported => Poll::Ready(Err(Error::UnsupportedReply(payload))), - // } - } -} - -impl FusedFuture for MessageFuture { - fn is_terminated(&self) -> bool { - !signals().contains_key(&self.waiting_reply_to) - } -} - -pub fn send_bytes_for_reply( - destination: ActorId, - payload: &[u8], - value: ValueUnit, - gas_limit: Option, - reply_deposit: Option, -) -> Result { - let waiting_reply_to = if let Some(gas_limit) = gas_limit { - ::gcore::msg::send_with_gas(destination, payload, gas_limit, value).map_err(Error::Core)? - } else { - ::gcore::msg::send(destination, payload, value).map_err(Error::Core)? - }; - - if let Some(reply_deposit) = reply_deposit { - ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit)?; - } - - let message_id = ::gcore::msg::id(); - signals().insert( - waiting_reply_to, - ReplySignal::Pending { - message_id, - // waker: cx.waker().clone(), - }, - ); - ::gstd::debug!( - "send_bytes_for_reply: add lock for reply_to {waiting_reply_to} in message {message_id}" - ); - tasks() - .get_mut(&message_id) - .expect("Message task must exist") - .reply_to_locks - .insert(waiting_reply_to, Default::default()); - - // locks().lock(message_id, waiting_reply_to, Default::default()); - - Ok(MessageFuture { - waiting_reply_to, - reply_deposit, - }) -} - -enum ReplySignal { - Pending { - message_id: MessageId, - // waker: task::Waker, - }, - Ready { - payload: Vec, - reply_code: ReplyCode, - }, - Timeout { - expected: BlockNumber, - now: BlockNumber, - }, -} - -// fn signals() -> &'static mut crate::collections::BTreeMap { -// static mut SIGNALS: crate::collections::BTreeMap = -// crate::collections::BTreeMap::new(); -// unsafe { &mut *core::ptr::addr_of_mut!(SIGNALS) } -// } - -fn signals() -> &'static mut crate::collections::HashMap { - static mut MAP: Option> = None; +fn tasks() -> &'static mut crate::collections::HashMap { + static mut MAP: Option> = None; unsafe { &mut *core::ptr::addr_of_mut!(MAP) } .get_or_insert_with(crate::collections::HashMap::new) } -#[inline] -fn poll(reply_to: &MessageId, _cx: &mut Context<'_>) -> Poll, Error>> { - let signals_map = signals(); - match signals_map.get(reply_to) { - Some(ReplySignal::Pending { .. }) => { - // waker.clone_from(cx.waker()); - Poll::Pending - } - Some(ReplySignal::Ready { .. }) => { - let ReplySignal::Ready { - payload, - reply_code, - } = signals_map.remove(reply_to).unwrap() - else { - unreachable!() - }; - match reply_code { - ReplyCode::Success(_) => Poll::Ready(Ok(payload)), - ReplyCode::Error(reason) => { - Poll::Ready(Err(Error::ErrorReply(payload.into(), reason))) - } - ReplyCode::Unsupported => Poll::Ready(Err(Error::UnsupportedReply(payload))), - } - } - Some(ReplySignal::Timeout { .. }) => { - let ReplySignal::Timeout { expected, now } = signals_map.remove(reply_to).unwrap() - else { - unreachable!() - }; - Poll::Ready(Err(Error::Timeout(expected, now))) - } - None => panic!( - "Somebody created a future with the MessageId that never ended in static replies!" - ), - } +fn signals() -> &'static mut WakeSignals { + static mut MAP: Option = None; + unsafe { &mut *core::ptr::addr_of_mut!(MAP) }.get_or_insert_with(WakeSignals::new) } -// pub fn register_signal(waiting_reply_to: MessageId) { -// let message_id = ::gcore::msg::id(); - -// // crate::async_runtime::locks().lock(message_id, waiting_reply_to, Default::default()); -// } - -fn record_reply() { - let reply_to: MessageId = - ::gcore::msg::reply_to().expect("`gcore::msg::reply_to()` called in wrong context"); - let signals_map = signals(); - if let Some(ReplySignal::Pending { message_id }) = signals_map.remove(&reply_to) { - let payload = ::gstd::msg::load_bytes().expect("Failed to load bytes"); - let reply_code = - ::gcore::msg::reply_code().expect("Shouldn't be called with incorrect context"); - signals_map.insert( - reply_to, - ReplySignal::Ready { - payload, - reply_code, - }, - ); - ::gstd::debug!("record_reply: remove lock for reply_to {reply_to} in message {message_id}"); - tasks() - .get_mut(&message_id) - .expect("Message task must exist") - .reply_to_locks - .remove(&reply_to); - - // waker.wake_by_ref(); - - ::gcore::exec::wake(message_id).expect("Failed to wake the message"); - } else { - ::gstd::debug!( - "A message has received a reply though it wasn't to receive one, or a processed message has received a reply" - ); - } -} - -type PinnedFuture = Pin + 'static>>; - /// Matches a task to a some message in order to avoid duplicate execution /// of code that was running before the program was interrupted by `wait`. pub struct Task { - future: PinnedFuture, + future: LocalBoxFuture<'static, ()>, reply_to_locks: HashMap, } @@ -210,7 +31,7 @@ impl Task { F: Future + 'static, { Self { - future: Box::pin(future), + future: future.boxed_local(), reply_to_locks: HashMap::new(), } } @@ -221,17 +42,9 @@ impl Task { self.reply_to_locks .extract_if(|_, lock| now >= lock.deadline()) .for_each(|(reply_to, lock)| { - if let Some(ReplySignal::Pending { .. }) = signals_map.remove(&reply_to) { - signals_map.insert( - reply_to, - ReplySignal::Timeout { - expected: lock.deadline(), - now, - }, - ); - } + signals_map.record_timeout(&reply_to, lock.deadline(), now); ::gstd::debug!( - "reply_timeout: remove lock for reply_to {reply_to} in message due to timeout" + "signal_reply_timeout: remove lock for reply_to {reply_to} in message due to timeout" ); }); } @@ -245,14 +58,6 @@ impl Task { } } -fn tasks() -> &'static mut crate::collections::HashMap { - static mut MAP: Option> = None; - unsafe { &mut *core::ptr::addr_of_mut!(MAP) } - .get_or_insert_with(crate::collections::HashMap::new) -} - -pub const SYSTEM_RESERVE: u64 = 10_000_000_000; - /// The main asynchronous message handling loop. /// /// Gear allows user and program interaction via @@ -268,14 +73,14 @@ where let task = tasks_map.entry(msg_id).or_insert_with(|| { #[cfg(not(feature = "ethexe"))] { - ::gcore::exec::system_reserve_gas(SYSTEM_RESERVE) + ::gcore::exec::system_reserve_gas(gstd::Config::system_reserve()) .expect("Failed to reserve gas for system signal"); } Task::new(future) }); // Check if any reply has timed out before polling them. - let current_block = exec::block_height(); + let current_block = ::gcore::exec::block_height(); task.signal_reply_timeout(current_block); let completed = { @@ -286,18 +91,196 @@ where if completed { tasks_map.remove(&msg_id); - // locks().remove_message_entry(msg_id); // #[cfg(not(feature = "ethexe"))] // let _ = critical::take_hook(); } else { - // ::gcore::exec::wait_up_to(100); task.wait(current_block); } } +pub type Payload = Vec; + +struct WakeSignal { + message_id: MessageId, + reply: Option<(Payload, ReplyCode)>, + timeout: Option<(BlockNumber, BlockNumber)>, +} + +struct WakeSignals { + signals: HashMap, +} + +impl WakeSignals { + pub fn new() -> Self { + Self { + signals: HashMap::new(), + } + } + + pub fn register_signal(&mut self, waiting_reply_to: MessageId) { + let message_id = ::gcore::msg::id(); + + self.signals.insert( + waiting_reply_to, + WakeSignal { + message_id, + reply: None, + timeout: None, + }, + ); + + ::gstd::debug!( + "register_signal: add lock for reply_to {waiting_reply_to} in message {message_id}" + ); + tasks() + .get_mut(&message_id) + .expect("Message task must exist") + .reply_to_locks + .insert(waiting_reply_to, Default::default()); + } + + pub fn record_reply(&mut self) { + let reply_to = + ::gcore::msg::reply_to().expect("Shouldn't be called with incorrect context"); + if let Some(signal) = self.signals.get_mut(&reply_to) { + signal.reply = Some(( + ::gstd::msg::load_bytes().expect("Failed to load bytes"), + ::gcore::msg::reply_code().expect("Shouldn't be called with incorrect context"), + )); + + ::gstd::debug!( + "record_reply: remove lock for reply_to {reply_to} in message {}", + signal.message_id + ); + tasks() + .get_mut(&signal.message_id) + .expect("Message task must exist") + .reply_to_locks + .remove(&reply_to); + + // wake message processign after handle reply + ::gcore::exec::wake(signal.message_id).expect("Failed to wake the message") + } else { + ::gstd::debug!( + "A message has received a reply though it wasn't to receive one, or a processed message has received a reply" + ); + } + } + + pub fn record_timeout( + &mut self, + reply_to: &MessageId, + expected: BlockNumber, + now: BlockNumber, + ) { + if let Some(signal @ WakeSignal { reply: None, .. }) = self.signals.get_mut(reply_to) { + signal.timeout = Some((expected, now)); + } else { + ::gstd::debug!("A message has received a reply before timed out"); + } + } + + pub fn waits_for(&self, reply_to: MessageId) -> bool { + self.signals.contains_key(&reply_to) + } + + pub fn poll( + &mut self, + reply_to: MessageId, + _cx: &mut Context<'_>, + ) -> Poll, Error>> { + match self.signals.remove(&reply_to) { + None => panic!( + "Somebody created a future with the MessageId that never ended in static replies!" + ), + Some( + signal @ WakeSignal { + reply: None, + timeout: None, + .. + }, + ) => { + self.signals.insert(reply_to, signal); + Poll::Pending + } + Some(WakeSignal { + reply: Some((payload, reply_code)), + .. + }) => match reply_code { + ReplyCode::Success(_) => Poll::Ready(Ok(payload)), + ReplyCode::Error(reason) => { + Poll::Ready(Err(Error::ErrorReply(payload.into(), reason))) + } + ReplyCode::Unsupported => Poll::Ready(Err(Error::UnsupportedReply(payload))), + }, + Some(WakeSignal { + timeout: Some((expected, now)), + .. + }) => Poll::Ready(Err(Error::Timeout(expected, now))), + } + } +} + +pub struct MessageFuture { + /// A message identifier for an expected reply. + /// + /// This identifier is generated by the corresponding send function (e.g. + /// [`send_bytes`](super::send_bytes)). + pub waiting_reply_to: MessageId, + /// Reply deposit that was allocated for this message. Checked in + /// handle_reply. + #[cfg_attr(feature = "ethexe", allow(unused))] + pub reply_deposit: Option, +} + +impl Unpin for MessageFuture {} + +impl Future for MessageFuture { + type Output = Result, Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + signals().poll(self.waiting_reply_to, cx) + } +} + +impl FusedFuture for MessageFuture { + fn is_terminated(&self) -> bool { + !signals().waits_for(self.waiting_reply_to) + } +} + +pub fn send_bytes_for_reply( + destination: ActorId, + payload: &[u8], + value: ValueUnit, + gas_limit: Option, + reply_deposit: Option, +) -> Result { + #[cfg(not(feature = "ethexe"))] + let waiting_reply_to = if let Some(gas_limit) = gas_limit { + ::gcore::msg::send_with_gas(destination, payload, gas_limit, value)? + } else { + ::gcore::msg::send(destination, payload, value)? + }; + #[cfg(feature = "ethexe")] + let waiting_reply_to = ::gcore::msg::send(destination, payload, value)?; + + #[cfg(not(feature = "ethexe"))] + if let Some(reply_deposit) = reply_deposit { + ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit)?; + } + + signals().register_signal(waiting_reply_to); + + Ok(MessageFuture { + waiting_reply_to, + reply_deposit, + }) +} + /// Default reply handler. pub fn handle_reply_with_hook() { - record_reply(); + signals().record_reply(); // Execute reply hook (if it was registered) // let replied_to = diff --git a/rs/src/gstd/locks.rs b/rs/src/gstd/locks.rs index 49d20ad40..72c17f55c 100644 --- a/rs/src/gstd/locks.rs +++ b/rs/src/gstd/locks.rs @@ -69,12 +69,6 @@ impl Lock { LockType::WaitFor(d) | LockType::WaitUpTo(d) => self.at.saturating_add(*d), } } - - /// Check if this lock is timed out. - pub fn timeout(&self, now: BlockNumber) -> Option<(BlockNumber, BlockNumber)> { - let expected = self.deadline(); - (now >= expected).then(|| (expected, now)) - } } impl PartialOrd for Lock { From 1cc1d0004cb3e8bff8ffe768163adc2d42e01aa0 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Thu, 25 Sep 2025 14:23:06 +0200 Subject: [PATCH 39/66] wip: refactor, opt --- benchmarks/bench_data.json | 4 +- rs/src/gstd/async_runtime.rs | 87 +++++++++++++++++++----------------- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index b7bbd2a9e..19d4065f7 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -13,8 +13,8 @@ "317810": 43147409792 }, "counter": { - "async_call": 820092342, - "sync_call": 652191920 + "async_call": 820445338, + "sync_call": 652544916 }, "cross_program": { "median": 2436719419 diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index 512f36b39..eb494412c 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -100,10 +100,18 @@ where pub type Payload = Vec; -struct WakeSignal { - message_id: MessageId, - reply: Option<(Payload, ReplyCode)>, - timeout: Option<(BlockNumber, BlockNumber)>, +enum WakeSignal { + Pending { + message_id: MessageId, + }, + Ready { + payload: Payload, + reply_code: ReplyCode, + }, + Timeout { + expected: BlockNumber, + now: BlockNumber, + }, } struct WakeSignals { @@ -120,14 +128,8 @@ impl WakeSignals { pub fn register_signal(&mut self, waiting_reply_to: MessageId) { let message_id = ::gcore::msg::id(); - self.signals.insert( - waiting_reply_to, - WakeSignal { - message_id, - reply: None, - timeout: None, - }, - ); + self.signals + .insert(waiting_reply_to, WakeSignal::Pending { message_id }); ::gstd::debug!( "register_signal: add lock for reply_to {waiting_reply_to} in message {message_id}" @@ -142,24 +144,28 @@ impl WakeSignals { pub fn record_reply(&mut self) { let reply_to = ::gcore::msg::reply_to().expect("Shouldn't be called with incorrect context"); - if let Some(signal) = self.signals.get_mut(&reply_to) { - signal.reply = Some(( - ::gstd::msg::load_bytes().expect("Failed to load bytes"), - ::gcore::msg::reply_code().expect("Shouldn't be called with incorrect context"), - )); + if let Some(signal @ WakeSignal::Pending { .. }) = self.signals.get_mut(&reply_to) { + let message_id = match signal { + WakeSignal::Pending { message_id } => *message_id, + _ => unreachable!(), + }; + *signal = WakeSignal::Ready { + payload: ::gstd::msg::load_bytes().expect("Failed to load bytes"), + reply_code: ::gcore::msg::reply_code() + .expect("Shouldn't be called with incorrect context"), + }; ::gstd::debug!( - "record_reply: remove lock for reply_to {reply_to} in message {}", - signal.message_id + "record_reply: remove lock for reply_to {reply_to} in message {message_id}" ); tasks() - .get_mut(&signal.message_id) + .get_mut(&message_id) .expect("Message task must exist") .reply_to_locks .remove(&reply_to); // wake message processign after handle reply - ::gcore::exec::wake(signal.message_id).expect("Failed to wake the message") + ::gcore::exec::wake(message_id).expect("Failed to wake the message") } else { ::gstd::debug!( "A message has received a reply though it wasn't to receive one, or a processed message has received a reply" @@ -173,10 +179,10 @@ impl WakeSignals { expected: BlockNumber, now: BlockNumber, ) { - if let Some(signal @ WakeSignal { reply: None, .. }) = self.signals.get_mut(reply_to) { - signal.timeout = Some((expected, now)); + if let Some(signal @ WakeSignal::Pending { .. }) = self.signals.get_mut(reply_to) { + *signal = WakeSignal::Timeout { expected, now }; } else { - ::gstd::debug!("A message has received a reply before timed out"); + ::gstd::debug!("A message has timed out after reply"); } } @@ -189,23 +195,20 @@ impl WakeSignals { reply_to: MessageId, _cx: &mut Context<'_>, ) -> Poll, Error>> { - match self.signals.remove(&reply_to) { + match self.signals.get(&reply_to) { None => panic!( "Somebody created a future with the MessageId that never ended in static replies!" ), - Some( - signal @ WakeSignal { - reply: None, - timeout: None, - .. - }, - ) => { - self.signals.insert(reply_to, signal); - Poll::Pending + Some(WakeSignal::Pending { .. }) => { + return Poll::Pending; } - Some(WakeSignal { - reply: Some((payload, reply_code)), - .. + _ => {} + }; + + match self.signals.remove(&reply_to) { + Some(WakeSignal::Ready { + payload, + reply_code, }) => match reply_code { ReplyCode::Success(_) => Poll::Ready(Ok(payload)), ReplyCode::Error(reason) => { @@ -213,10 +216,10 @@ impl WakeSignals { } ReplyCode::Unsupported => Poll::Ready(Err(Error::UnsupportedReply(payload))), }, - Some(WakeSignal { - timeout: Some((expected, now)), - .. - }) => Poll::Ready(Err(Error::Timeout(expected, now))), + Some(WakeSignal::Timeout { expected, now, .. }) => { + Poll::Ready(Err(Error::Timeout(expected, now))) + } + _ => unreachable!(), } } } @@ -249,6 +252,7 @@ impl FusedFuture for MessageFuture { } } +#[inline] pub fn send_bytes_for_reply( destination: ActorId, payload: &[u8], @@ -279,6 +283,7 @@ pub fn send_bytes_for_reply( } /// Default reply handler. +#[inline] pub fn handle_reply_with_hook() { signals().record_reply(); From f1c63fbdfdf4bb9b236ceffaddee400cc7c03600 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Thu, 25 Sep 2025 15:19:27 +0200 Subject: [PATCH 40/66] wip: locks opt wip: gstd_env PendingCall::poll opt --- benchmarks/bench_data.json | 4 +-- rs/src/gstd/async_runtime.rs | 48 ++++++++++++++++++--------- rs/src/gstd/locks.rs | 64 ++++++++---------------------------- 3 files changed, 47 insertions(+), 69 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 19d4065f7..5f83c34a4 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -13,8 +13,8 @@ "317810": 43147409792 }, "counter": { - "async_call": 820445338, - "sync_call": 652544916 + "async_call": 819161950, + "sync_call": 651213116 }, "cross_program": { "median": 2436719419 diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index eb494412c..ab87dd001 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -36,6 +36,17 @@ impl Task { } } + #[inline] + fn insert_lock(&mut self, reply_to: MessageId, lock: locks::Lock) { + self.reply_to_locks.insert(reply_to, lock); + } + + #[inline] + fn remove_lock(&mut self, reply_to: &MessageId) { + self.reply_to_locks.remove(reply_to); + } + + #[inline] fn signal_reply_timeout(&mut self, now: BlockNumber) { let signals_map = signals(); @@ -49,6 +60,7 @@ impl Task { }); } + #[inline] fn wait(&self, now: BlockNumber) { self.reply_to_locks .values() @@ -136,9 +148,8 @@ impl WakeSignals { ); tasks() .get_mut(&message_id) - .expect("Message task must exist") - .reply_to_locks - .insert(waiting_reply_to, Default::default()); + .expect("A message task must exist") + .insert_lock(waiting_reply_to, Default::default()); } pub fn record_reply(&mut self) { @@ -160,9 +171,8 @@ impl WakeSignals { ); tasks() .get_mut(&message_id) - .expect("Message task must exist") - .reply_to_locks - .remove(&reply_to); + .expect("A message task must exist") + .remove_lock(&reply_to); // wake message processign after handle reply ::gcore::exec::wake(message_id).expect("Failed to wake the message") @@ -186,26 +196,24 @@ impl WakeSignals { } } - pub fn waits_for(&self, reply_to: MessageId) -> bool { - self.signals.contains_key(&reply_to) + pub fn waits_for(&self, reply_to: &MessageId) -> bool { + self.signals.contains_key(reply_to) } pub fn poll( &mut self, - reply_to: MessageId, + reply_to: &MessageId, _cx: &mut Context<'_>, ) -> Poll, Error>> { - match self.signals.get(&reply_to) { - None => panic!( - "Somebody created a future with the MessageId that never ended in static replies!" - ), + match self.signals.get(reply_to) { + None => panic!("Poll not registered feature"), Some(WakeSignal::Pending { .. }) => { return Poll::Pending; } _ => {} }; - match self.signals.remove(&reply_to) { + match self.signals.remove(reply_to) { Some(WakeSignal::Ready { payload, reply_code, @@ -242,13 +250,13 @@ impl Future for MessageFuture { type Output = Result, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - signals().poll(self.waiting_reply_to, cx) + signals().poll(&self.waiting_reply_to, cx) } } impl FusedFuture for MessageFuture { fn is_terminated(&self) -> bool { - !signals().waits_for(self.waiting_reply_to) + !signals().waits_for(&self.waiting_reply_to) } } @@ -297,3 +305,11 @@ pub fn handle_reply_with_hook() { // #[cfg(feature = "ethexe")] // let _ = replied_to; } + +pub fn poll(message_id: &MessageId, cx: &mut Context<'_>) -> Poll, Error>> { + signals().poll(message_id, cx) +} + +pub fn is_terminated(message_id: &MessageId) -> bool { + !signals().waits_for(message_id) +} diff --git a/rs/src/gstd/locks.rs b/rs/src/gstd/locks.rs index 72c17f55c..28c4c0217 100644 --- a/rs/src/gstd/locks.rs +++ b/rs/src/gstd/locks.rs @@ -4,69 +4,37 @@ use gstd::{BlockCount, BlockNumber, Config, exec}; /// Type of wait locks. #[derive(Debug, PartialEq, Eq)] -pub(crate) enum LockType { - WaitFor(BlockCount), - WaitUpTo(BlockCount), -} - -/// Wait lock -#[derive(Debug, PartialEq, Eq)] -pub struct Lock { - /// The start block number of this lock. - pub at: BlockNumber, - /// The type of this lock. - ty: LockType, +pub(crate) enum Lock { + WaitFor(BlockNumber), + WaitUpTo(BlockNumber), } impl Lock { /// Wait for pub fn exactly(b: BlockCount) -> Self { - // if b == 0 { - // return Err(Error::Gstd(UsageError::EmptyWaitDuration)); - // } - - Self { - at: exec::block_height(), - ty: LockType::WaitFor(b), - } + let current = exec::block_height(); + Self::WaitFor(current.saturating_add(b)) } /// Wait up to pub fn up_to(b: BlockCount) -> Self { - // if b == 0 { - // return Err(Error::Gstd(UsageError::EmptyWaitDuration)); - // } - - Self { - at: exec::block_height(), - ty: LockType::WaitUpTo(b), - } + let current = exec::block_height(); + Self::WaitUpTo(current.saturating_add(b)) } /// Call wait functions by the lock type. pub fn wait(&self, now: BlockNumber) { - if let Some(blocks) = self.deadline().checked_sub(now) { - if blocks == 0 { - unreachable!( - "Checked in `crate::msg::async::poll`, will trigger the timeout error automatically." - ); - } - - match self.ty { - LockType::WaitFor(_) => exec::wait_for(blocks), - LockType::WaitUpTo(_) => exec::wait_up_to(blocks), - } - } else { - unreachable!( - "Checked in `crate::msg::async::poll`, will trigger the timeout error automatically." - ); + match &self { + Lock::WaitFor(d) => exec::wait_for(d.checked_sub(now).expect("Checked in `crate::gstd::async_runtime::message_loop`")), + Lock::WaitUpTo(d) => exec::wait_up_to(d.checked_sub(now).expect("Checked in `crate::gstd::async_runtime::message_loop`")), } } /// Gets the deadline of the current lock. pub fn deadline(&self) -> BlockNumber { - match &self.ty { - LockType::WaitFor(d) | LockType::WaitUpTo(d) => self.at.saturating_add(*d), + match &self { + Lock::WaitFor(d) => *d, + Lock::WaitUpTo(d) => *d, } } } @@ -88,9 +56,3 @@ impl Default for Lock { Lock::up_to(Config::wait_up_to()) } } - -impl Default for LockType { - fn default() -> Self { - LockType::WaitUpTo(Config::wait_up_to()) - } -} From 1c6df3d552ec579dadcb94b5d7ad42759c6cf21f Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 29 Sep 2025 11:48:49 +0200 Subject: [PATCH 41/66] wip: use Vec for locks --- benchmarks/bench_data.json | 4 ++-- rs/src/gstd/async_runtime.rs | 13 +++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 5f83c34a4..7ec2cf1fb 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -13,8 +13,8 @@ "317810": 43147409792 }, "counter": { - "async_call": 819161950, - "sync_call": 651213116 + "async_call": 817988847, + "sync_call": 650301995 }, "cross_program": { "median": 2436719419 diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index ab87dd001..e7a949b5b 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -22,7 +22,7 @@ fn signals() -> &'static mut WakeSignals { /// of code that was running before the program was interrupted by `wait`. pub struct Task { future: LocalBoxFuture<'static, ()>, - reply_to_locks: HashMap, + reply_to_locks: Vec<(MessageId, locks::Lock)>, } impl Task { @@ -32,18 +32,18 @@ impl Task { { Self { future: future.boxed_local(), - reply_to_locks: HashMap::new(), + reply_to_locks: Vec::new(), } } #[inline] fn insert_lock(&mut self, reply_to: MessageId, lock: locks::Lock) { - self.reply_to_locks.insert(reply_to, lock); + self.reply_to_locks.push((reply_to, lock)); } #[inline] fn remove_lock(&mut self, reply_to: &MessageId) { - self.reply_to_locks.remove(reply_to); + self.reply_to_locks.retain(|(mid, _)| mid != reply_to); } #[inline] @@ -51,7 +51,7 @@ impl Task { let signals_map = signals(); self.reply_to_locks - .extract_if(|_, lock| now >= lock.deadline()) + .extract_if(.., |(_, lock)| now >= lock.deadline()) .for_each(|(reply_to, lock)| { signals_map.record_timeout(&reply_to, lock.deadline(), now); ::gstd::debug!( @@ -63,7 +63,8 @@ impl Task { #[inline] fn wait(&self, now: BlockNumber) { self.reply_to_locks - .values() + .iter() + .map(|(_, lock)| lock) .min_by(|lock1, lock2| lock1.cmp(lock2)) .expect("Cannot find lock to be waited") .wait(now); From c12506a57086b4352ffcf7218ee217bba4679870 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 29 Sep 2025 11:49:24 +0200 Subject: [PATCH 42/66] wip: compare locks, prefer WaitUpTo --- benchmarks/bench_data.json | 4 ++-- rs/src/gstd/locks.rs | 19 ++++++++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 7ec2cf1fb..7d038bb10 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -13,8 +13,8 @@ "317810": 43147409792 }, "counter": { - "async_call": 817988847, - "sync_call": 650301995 + "async_call": 818053683, + "sync_call": 650366831 }, "cross_program": { "median": 2436719419 diff --git a/rs/src/gstd/locks.rs b/rs/src/gstd/locks.rs index 28c4c0217..06ce4aaf6 100644 --- a/rs/src/gstd/locks.rs +++ b/rs/src/gstd/locks.rs @@ -25,8 +25,14 @@ impl Lock { /// Call wait functions by the lock type. pub fn wait(&self, now: BlockNumber) { match &self { - Lock::WaitFor(d) => exec::wait_for(d.checked_sub(now).expect("Checked in `crate::gstd::async_runtime::message_loop`")), - Lock::WaitUpTo(d) => exec::wait_up_to(d.checked_sub(now).expect("Checked in `crate::gstd::async_runtime::message_loop`")), + Lock::WaitFor(d) => exec::wait_for( + d.checked_sub(now) + .expect("Checked in `crate::gstd::async_runtime::message_loop`"), + ), + Lock::WaitUpTo(d) => exec::wait_up_to( + d.checked_sub(now) + .expect("Checked in `crate::gstd::async_runtime::message_loop`"), + ), } } @@ -47,7 +53,14 @@ impl PartialOrd for Lock { impl Ord for Lock { fn cmp(&self, other: &Self) -> Ordering { - self.deadline().cmp(&other.deadline()) + let mut ord = self.deadline().cmp(&other.deadline()); + if ord == Ordering::Equal { + ord = match self { + Lock::WaitFor(_) => Ordering::Greater, + Lock::WaitUpTo(_) => Ordering::Less, + } + } + ord } } From 978250bf483f11b4f245f92ae55b08664a72b527 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 29 Sep 2025 16:35:48 +0200 Subject: [PATCH 43/66] wip: replace `?` with `ok!`, remove double hashing in WakeSignals::poll --- rs/src/client/gstd_env.rs | 4 ++-- rs/src/gstd/async_runtime.rs | 39 ++++++++++++++++++++---------------- rs/src/gstd/mod.rs | 12 +++++++++++ 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index e7f12a698..2b7252287 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -171,13 +171,13 @@ const _: () = { ) -> Result { // send message // let future = send_for_reply_future(destination, payload.as_ref(), params)?; - let future = crate::gstd::send_bytes_for_reply( + let future = crate::ok!(crate::gstd::send_bytes_for_reply( destination, payload.as_ref(), params.value.unwrap_or_default(), params.gas_limit, params.reply_deposit, - )?; + )); if params.redirect_on_exit { let created_block = params.wait_up_to.map(|_| gstd::exec::block_height()); Ok(GstdFuture::MessageWithRedirect { diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index e7a949b5b..e17bccc9f 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -206,26 +206,26 @@ impl WakeSignals { reply_to: &MessageId, _cx: &mut Context<'_>, ) -> Poll, Error>> { - match self.signals.get(reply_to) { - None => panic!("Poll not registered feature"), - Some(WakeSignal::Pending { .. }) => { - return Poll::Pending; - } - _ => {} + let entry = self.signals.entry_ref(reply_to); + let entry = match entry { + hashbrown::hash_map::EntryRef::Occupied(occupied_entry) => occupied_entry, + hashbrown::hash_map::EntryRef::Vacant(_) => panic!("Poll not registered feature"), }; - - match self.signals.remove(reply_to) { - Some(WakeSignal::Ready { + if let WakeSignal::Pending { .. } = entry.get() { + return Poll::Pending; + } + match entry.remove() { + WakeSignal::Ready { payload, reply_code, - }) => match reply_code { + } => match reply_code { ReplyCode::Success(_) => Poll::Ready(Ok(payload)), ReplyCode::Error(reason) => { Poll::Ready(Err(Error::ErrorReply(payload.into(), reason))) } ReplyCode::Unsupported => Poll::Ready(Err(Error::UnsupportedReply(payload))), }, - Some(WakeSignal::Timeout { expected, now, .. }) => { + WakeSignal::Timeout { expected, now, .. } => { Poll::Ready(Err(Error::Timeout(expected, now))) } _ => unreachable!(), @@ -251,13 +251,13 @@ impl Future for MessageFuture { type Output = Result, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - signals().poll(&self.waiting_reply_to, cx) + poll(&self.waiting_reply_to, cx) } } impl FusedFuture for MessageFuture { fn is_terminated(&self) -> bool { - !signals().waits_for(&self.waiting_reply_to) + is_terminated(&self.waiting_reply_to) } } @@ -271,16 +271,21 @@ pub fn send_bytes_for_reply( ) -> Result { #[cfg(not(feature = "ethexe"))] let waiting_reply_to = if let Some(gas_limit) = gas_limit { - ::gcore::msg::send_with_gas(destination, payload, gas_limit, value)? + crate::ok!(::gcore::msg::send_with_gas( + destination, + payload, + gas_limit, + value + )) } else { - ::gcore::msg::send(destination, payload, value)? + crate::ok!(::gcore::msg::send(destination, payload, value)) }; #[cfg(feature = "ethexe")] - let waiting_reply_to = ::gcore::msg::send(destination, payload, value)?; + let waiting_reply_to = ::gcore::msg::send(destination, payload, value); #[cfg(not(feature = "ethexe"))] if let Some(reply_deposit) = reply_deposit { - ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit)?; + _ = ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit); } signals().register_signal(waiting_reply_to); diff --git a/rs/src/gstd/mod.rs b/rs/src/gstd/mod.rs index c09331fef..9d53f834f 100644 --- a/rs/src/gstd/mod.rs +++ b/rs/src/gstd/mod.rs @@ -143,3 +143,15 @@ pub trait InvocationIo { TypeId::of::() == TypeId::of::<()>() } } + +#[macro_export] +macro_rules! ok { + ($e:expr) => { + match $e { + Ok(t) => t, + Err(err) => { + return Err(err.into()); + } + } + }; +} From 7326a074ec5129827c3b2d4565d54b4fb8a77c42 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 29 Sep 2025 16:39:08 +0200 Subject: [PATCH 44/66] wip: replace retain w/ swap_remove --- benchmarks/bench_data.json | 8 ++++---- rs/src/gstd/async_runtime.rs | 8 +++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 7d038bb10..1bdd0fd7a 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -13,14 +13,14 @@ "317810": 43147409792 }, "counter": { - "async_call": 818053683, - "sync_call": 650366831 + "async_call": 817273443, + "sync_call": 649586591 }, "cross_program": { - "median": 2436719419 + "median": 2248884222 }, "redirect": { - "median": 3473474864 + "median": 3194519366 }, "message_stack": { "0": 79911236500, diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index e17bccc9f..2862d33b4 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -43,7 +43,13 @@ impl Task { #[inline] fn remove_lock(&mut self, reply_to: &MessageId) { - self.reply_to_locks.retain(|(mid, _)| mid != reply_to); + if let Some(index) = self + .reply_to_locks + .iter() + .position(|(mid, _)| mid == reply_to) + { + self.reply_to_locks.swap_remove(index); + } } #[inline] From 8793d1fd8501beb5d22abe258bfcdcdb47fc2a42 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 29 Sep 2025 17:30:43 +0200 Subject: [PATCH 45/66] wip: Lock struct, WaitType enum --- benchmarks/bench_data.json | 18 +++++----- rs/src/client/gstd_env.rs | 64 ++++++++++++++++++------------------ rs/src/gstd/async_runtime.rs | 37 +++++++++++++++++++++ rs/src/gstd/locks.rs | 55 +++++++++++++++++++------------ rs/src/gstd/mod.rs | 3 +- 5 files changed, 114 insertions(+), 63 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 1bdd0fd7a..96b8c1201 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -13,20 +13,20 @@ "317810": 43147409792 }, "counter": { - "async_call": 817273443, - "sync_call": 649586591 + "async_call": 817033237, + "sync_call": 649346385 }, "cross_program": { - "median": 2248884222 + "median": 2248325463 }, "redirect": { - "median": 3194519366 + "median": 3193415171 }, "message_stack": { - "0": 79911236500, - "1": 376957078300, - "5": 1569303378600, - "10": 2959197465500, - "20": 6398916489100 + "0": 70684653600, + "1": 338627560200, + "5": 1413458191100, + "10": 2640638971300, + "20": 5232031765600 } } \ No newline at end of file diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index 2b7252287..8deabac53 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -191,6 +191,23 @@ const _: () = { } } + fn create_program( + code_id: CodeId, + salt: impl AsRef<[u8]>, + payload: impl AsRef<[u8]>, + params: &GstdParams, + ) -> Result<(GstdFuture, ActorId), Error> { + let (future, program_id) = crate::ok!(crate::gstd::create_program_for_reply( + code_id, + salt.as_ref(), + payload.as_ref(), + params.value.unwrap_or_default(), + params.gas_limit, + params.reply_deposit, + )); + Ok((GstdFuture::CreateProgram { future }, program_id)) + } + impl Future for PendingCall { type Output = Result::Error>; @@ -204,7 +221,10 @@ const _: () = { let destination = self.destination; let params = self.params.get_or_insert_default(); // Send message - let future = send_for_reply(destination, payload, params)?; + let future = match send_for_reply(destination, payload, params) { + Ok(future) => future, + Err(err) => return Poll::Ready(Err(err)), + }; self.state = Some(future); // No need to poll the future return Poll::Pending; @@ -303,35 +323,14 @@ const _: () = { .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); let payload = T::encode_params(args); // Send message - #[cfg(not(feature = "ethexe"))] - let future = if let Some(gas_limit) = params.gas_limit { - ::gstd::prog::create_program_bytes_with_gas_for_reply( - self.code_id, - salt, - payload, - gas_limit, - value, - params.reply_deposit.unwrap_or_default(), - )? - } else { - ::gstd::prog::create_program_bytes_for_reply( - self.code_id, - salt, - payload, - value, - params.reply_deposit.unwrap_or_default(), - )? - }; - #[cfg(feature = "ethexe")] - let future = ::gstd::prog::create_program_bytes_for_reply( - self.code_id, - salt, - payload, - value, - )?; - - // self.program_id = Some(program_future.program_id); - self.state = Some(GstdFuture::CreateProgram { future }); + let (future, program_id) = + match create_program(self.code_id, salt, payload, ¶ms) { + Ok(res) => res, + Err(err) => return Poll::Ready(Err(err)), + }; + + self.program_id = Some(program_id); + self.state = Some(future); // No need to poll the future return Poll::Pending; } @@ -341,7 +340,8 @@ const _: () = { if let Projection::CreateProgram { future } = state.project() { // Poll create program future match ready!(future.poll(cx)) { - Ok((program_id, _payload)) => { + Ok(_payload) => { + let program_id = unsafe { this.program_id.unwrap_unchecked() }; // Do not decode payload here Poll::Ready(Ok(Actor::new(this.env.clone(), program_id))) } @@ -358,7 +358,7 @@ pin_project_lite::pin_project! { #[project = Projection] #[project_replace = Replace] pub enum GstdFuture { - CreateProgram { #[pin] future: CreateProgramFuture }, + CreateProgram { #[pin] future: MessageFuture }, Message { #[pin] future: MessageFuture }, MessageWithRedirect { #[pin] diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index 2862d33b4..3fc69e470 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -302,6 +302,43 @@ pub fn send_bytes_for_reply( }) } +#[inline] +pub fn create_program_for_reply( + code_id: CodeId, + salt: &[u8], + payload: &[u8], + value: ValueUnit, + gas_limit: Option, + reply_deposit: Option, +) -> Result<(MessageFuture, ActorId), ::gstd::errors::Error> { + #[cfg(not(feature = "ethexe"))] + let (waiting_reply_to, program_id) = if let Some(gas_limit) = gas_limit { + crate::ok!(::gcore::prog::create_program_with_gas( + code_id, salt, payload, gas_limit, value + )) + } else { + crate::ok!(::gcore::prog::create_program(code_id, salt, payload, value)) + }; + #[cfg(feature = "ethexe")] + let (waiting_reply_to, program_id) = + crate::ok!(::gcore::prog::create_program(code_id, salt, payload, value)); + + #[cfg(not(feature = "ethexe"))] + if let Some(reply_deposit) = reply_deposit { + _ = ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit); + } + + signals().register_signal(waiting_reply_to); + + Ok(( + MessageFuture { + waiting_reply_to, + reply_deposit, + }, + program_id, + )) +} + /// Default reply handler. #[inline] pub fn handle_reply_with_hook() { diff --git a/rs/src/gstd/locks.rs b/rs/src/gstd/locks.rs index 06ce4aaf6..5c68c69f1 100644 --- a/rs/src/gstd/locks.rs +++ b/rs/src/gstd/locks.rs @@ -4,44 +4,57 @@ use gstd::{BlockCount, BlockNumber, Config, exec}; /// Type of wait locks. #[derive(Debug, PartialEq, Eq)] -pub(crate) enum Lock { - WaitFor(BlockNumber), - WaitUpTo(BlockNumber), +pub(crate) struct Lock { + deadline: BlockNumber, + ty: WaitType, +} + +/// Wait types. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub(crate) enum WaitType { + Exactly, + #[default] + UpTo, } impl Lock { /// Wait for pub fn exactly(b: BlockCount) -> Self { let current = exec::block_height(); - Self::WaitFor(current.saturating_add(b)) + Self { + deadline: current.saturating_add(b), + ty: WaitType::Exactly, + } } /// Wait up to pub fn up_to(b: BlockCount) -> Self { let current = exec::block_height(); - Self::WaitUpTo(current.saturating_add(b)) + Self { + deadline: current.saturating_add(b), + ty: WaitType::UpTo, + } } /// Call wait functions by the lock type. pub fn wait(&self, now: BlockNumber) { - match &self { - Lock::WaitFor(d) => exec::wait_for( - d.checked_sub(now) - .expect("Checked in `crate::gstd::async_runtime::message_loop`"), - ), - Lock::WaitUpTo(d) => exec::wait_up_to( - d.checked_sub(now) - .expect("Checked in `crate::gstd::async_runtime::message_loop`"), - ), + let duration = self + .deadline + .checked_sub(now) + .expect("Checked in `crate::gstd::async_runtime::message_loop`"); + match self.ty { + WaitType::Exactly => exec::wait_for(duration), + WaitType::UpTo => exec::wait_up_to(duration), } } /// Gets the deadline of the current lock. pub fn deadline(&self) -> BlockNumber { - match &self { - Lock::WaitFor(d) => *d, - Lock::WaitUpTo(d) => *d, - } + self.deadline + } + + pub fn wait_type(&self) -> WaitType { + self.ty } } @@ -55,9 +68,9 @@ impl Ord for Lock { fn cmp(&self, other: &Self) -> Ordering { let mut ord = self.deadline().cmp(&other.deadline()); if ord == Ordering::Equal { - ord = match self { - Lock::WaitFor(_) => Ordering::Greater, - Lock::WaitUpTo(_) => Ordering::Less, + ord = match self.wait_type() { + WaitType::Exactly => Ordering::Greater, + WaitType::UpTo => Ordering::Less, } } ord diff --git a/rs/src/gstd/mod.rs b/rs/src/gstd/mod.rs index 9d53f834f..854ddd005 100644 --- a/rs/src/gstd/mod.rs +++ b/rs/src/gstd/mod.rs @@ -1,7 +1,8 @@ #[doc(hidden)] // pub use gstd::{handle_reply_with_hook, message_loop}; pub use async_runtime::{ - MessageFuture, handle_reply_with_hook, message_loop, send_bytes_for_reply, + MessageFuture, create_program_for_reply, handle_reply_with_hook, message_loop, + send_bytes_for_reply, }; #[doc(hidden)] #[cfg(feature = "ethexe")] From e4b80f00ee5d3455aab1a3ad6736dc315b31e354 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 1 Oct 2025 17:02:15 +0200 Subject: [PATCH 46/66] wip: use Lock in GstdParams. remove deadline recalc --- benchmarks/bench_data.json | 18 +++--- rs/src/client/gstd_env.rs | 113 +++++++---------------------------- rs/src/gstd/async_runtime.rs | 76 +++++++++++++---------- rs/src/gstd/locks.rs | 30 +++++----- rs/src/gstd/mod.rs | 11 +++- rs/src/gstd/reply_hooks.rs | 29 +++++++++ 6 files changed, 128 insertions(+), 149 deletions(-) create mode 100644 rs/src/gstd/reply_hooks.rs diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 96b8c1201..58d8673cb 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -13,20 +13,20 @@ "317810": 43147409792 }, "counter": { - "async_call": 817033237, - "sync_call": 649346385 + "async_call": 797660740, + "sync_call": 629814629 }, "cross_program": { - "median": 2248325463 + "median": 2216723585 }, "redirect": { - "median": 3193415171 + "median": 3143495250 }, "message_stack": { - "0": 70684653600, - "1": 338627560200, - "5": 1413458191100, - "10": 2640638971300, - "20": 5232031765600 + "0": 69107033500, + "1": 332351617900, + "5": 1388337296600, + "10": 2592200283700, + "20": 5072335162800 } } \ No newline at end of file diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index 8deabac53..06c69af38 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -1,13 +1,13 @@ use super::*; -use crate::gstd::MessageFuture; -use ::gstd::{errors::Error, msg::CreateProgramFuture}; +use crate::gstd::{MessageFuture, async_runtime, locks}; +use ::gstd::errors::Error; #[derive(Default)] pub struct GstdParams { #[cfg(not(feature = "ethexe"))] pub gas_limit: Option, pub value: Option, - pub wait_up_to: Option, + pub wait: Option, #[cfg(not(feature = "ethexe"))] pub reply_deposit: Option, #[cfg(not(feature = "ethexe"))] @@ -19,7 +19,7 @@ crate::params_for_pending_impl!(GstdEnv, GstdParams { #[cfg(not(feature = "ethexe"))] pub gas_limit: GasUnit, pub value: ValueUnit, - pub wait_up_to: BlockCount, + pub wait: locks::Lock, #[cfg(not(feature = "ethexe"))] pub reply_deposit: GasUnit, }); @@ -78,26 +78,19 @@ impl GstdEnv { &self, destination: ActorId, payload: impl AsRef<[u8]>, - params: GstdParams, + mut params: GstdParams, ) -> Result { - let value = params.value.unwrap_or_default(); - let payload_bytes = payload.as_ref(); - - #[cfg(not(feature = "ethexe"))] - let waiting_reply_to = if let Some(gas_limit) = params.gas_limit { - ::gcore::msg::send_with_gas(destination, payload_bytes, gas_limit, value)? - } else { - ::gcore::msg::send(destination, payload_bytes, value)? - }; - #[cfg(feature = "ethexe")] - let waiting_reply_to = ::gcore::msg::send(destination, payload_bytes, value)?; - - #[cfg(not(feature = "ethexe"))] - if let Some(reply_deposit) = params.reply_deposit { - ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit)?; - } + let message = crate::ok!(async_runtime::send_bytes_for_reply( + destination, + payload.as_ref(), + params.value.unwrap_or_default(), + params.wait.unwrap_or_default(), + params.gas_limit, + params.reply_deposit, + // params.reply_hook.take(), + )); - Ok(waiting_reply_to) + Ok(message.waiting_reply_to) } } @@ -113,56 +106,6 @@ const _: () = { use core::task::ready; use futures::future::FusedFuture; - // #[cfg(not(feature = "ethexe"))] - // #[inline] - // fn send_for_reply_future( - // destination: ActorId, - // payload: &[u8], - // params: &mut GstdParams, - // ) -> Result { - // let value = params.value.unwrap_or(0); - // // here can be a redirect target - // let mut message_future = if let Some(gas_limit) = params.gas_limit { - // ::gstd::msg::send_bytes_with_gas_for_reply( - // destination, - // payload, - // gas_limit, - // value, - // params.reply_deposit.unwrap_or_default(), - // )? - // } else { - // ::gstd::msg::send_bytes_for_reply( - // destination, - // payload, - // value, - // params.reply_deposit.unwrap_or_default(), - // )? - // }; - - // message_future = message_future.up_to(params.wait_up_to)?; - - // if let Some(reply_hook) = params.reply_hook.take() { - // message_future = message_future.handle_reply(reply_hook)?; - // } - // Ok(message_future) - // } - - // #[cfg(feature = "ethexe")] - // #[inline] - // fn send_for_reply_future( - // destination: ActorId, - // payload: &[u8], - // params: &mut GstdParams, - // ) -> Result { - // let value = params.value.unwrap_or(0); - // // here can be a redirect target - // let mut message_future = ::gstd::msg::send_bytes_for_reply(destination, payload, value)?; - - // message_future = message_future.up_to(params.wait_up_to)?; - - // Ok(message_future) - // } - #[inline] fn send_for_reply( destination: ActorId, @@ -175,13 +118,13 @@ const _: () = { destination, payload.as_ref(), params.value.unwrap_or_default(), + params.wait.unwrap_or_default(), params.gas_limit, params.reply_deposit, + // params.reply_hook.take(), )); if params.redirect_on_exit { - let created_block = params.wait_up_to.map(|_| gstd::exec::block_height()); Ok(GstdFuture::MessageWithRedirect { - created_block, future, destination, payload, @@ -195,15 +138,17 @@ const _: () = { code_id: CodeId, salt: impl AsRef<[u8]>, payload: impl AsRef<[u8]>, - params: &GstdParams, + params: &mut GstdParams, ) -> Result<(GstdFuture, ActorId), Error> { let (future, program_id) = crate::ok!(crate::gstd::create_program_for_reply( code_id, salt.as_ref(), payload.as_ref(), params.value.unwrap_or_default(), + params.wait.unwrap_or_default(), params.gas_limit, params.reply_deposit, + // params.reply_hook.take(), )); Ok((GstdFuture::CreateProgram { future }, program_id)) } @@ -253,7 +198,6 @@ const _: () = { let params = this.params.get_or_insert_default(); if let Replace::MessageWithRedirect { destination: _destination, - created_block, payload, .. } = state.as_mut().project_replace(GstdFuture::Dummy) @@ -262,16 +206,6 @@ const _: () = { { gstd::debug!("Redirecting message from {_destination} to {new_target}"); - // Calculate updated `wait_up_to` if provided - // wait_up_to = wait_up_to - (current_block - created_block) - params.wait_up_to = params.wait_up_to.and_then(|wait_up_to| { - created_block.map(|created_block| { - let current_block = gstd::exec::block_height(); - wait_up_to - .saturating_sub(current_block.saturating_sub(created_block)) - }) - }); - // send message to new target let future = send_for_reply(new_target, payload, params)?; // Replace the future with a new one @@ -313,7 +247,7 @@ const _: () = { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if self.state.is_none() { - let params = self.params.take().unwrap_or_default(); + let mut params = self.params.take().unwrap_or_default(); let value = params.value.unwrap_or_default(); let salt = self.salt.take().unwrap(); @@ -324,7 +258,7 @@ const _: () = { let payload = T::encode_params(args); // Send message let (future, program_id) = - match create_program(self.code_id, salt, payload, ¶ms) { + match create_program(self.code_id, salt, payload, &mut params) { Ok(res) => res, Err(err) => return Poll::Ready(Err(err)), }; @@ -336,7 +270,7 @@ const _: () = { } let this = self.as_mut().project(); // SAFETY: checked in the code above. - let mut state = unsafe { this.state.as_pin_mut().unwrap_unchecked() }; + let state = unsafe { this.state.as_pin_mut().unwrap_unchecked() }; if let Projection::CreateProgram { future } = state.project() { // Poll create program future match ready!(future.poll(cx)) { @@ -364,7 +298,6 @@ pin_project_lite::pin_project! { #[pin] future: MessageFuture, destination: ActorId, - created_block: Option, payload: Vec, // reuse encoded payload when redirecting }, Dummy, diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index 3fc69e470..078352904 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -1,5 +1,5 @@ use super::*; -use crate::collections::HashMap; +use crate::{collections::HashMap, gstd::reply_hooks::HooksMap}; use core::{ pin::Pin, task::{Context, Poll}, @@ -18,6 +18,11 @@ fn signals() -> &'static mut WakeSignals { unsafe { &mut *core::ptr::addr_of_mut!(MAP) }.get_or_insert_with(WakeSignals::new) } +// fn reply_hooks() -> &'static mut HooksMap { +// static mut MAP: Option = None; +// unsafe { &mut *core::ptr::addr_of_mut!(MAP) }.get_or_insert_with(HooksMap::new) +// } + /// Matches a task to a some message in order to avoid duplicate execution /// of code that was running before the program was interrupted by `wait`. pub struct Task { @@ -99,7 +104,7 @@ where }); // Check if any reply has timed out before polling them. - let current_block = ::gcore::exec::block_height(); + let current_block = Syscall::block_height(); task.signal_reply_timeout(current_block); let completed = { @@ -144,7 +149,7 @@ impl WakeSignals { } } - pub fn register_signal(&mut self, waiting_reply_to: MessageId) { + pub fn register_signal(&mut self, waiting_reply_to: MessageId, lock: locks::Lock) { let message_id = ::gcore::msg::id(); self.signals @@ -156,13 +161,11 @@ impl WakeSignals { tasks() .get_mut(&message_id) .expect("A message task must exist") - .insert_lock(waiting_reply_to, Default::default()); + .insert_lock(waiting_reply_to, lock); } - pub fn record_reply(&mut self) { - let reply_to = - ::gcore::msg::reply_to().expect("Shouldn't be called with incorrect context"); - if let Some(signal @ WakeSignal::Pending { .. }) = self.signals.get_mut(&reply_to) { + pub fn record_reply(&mut self, reply_to: &MessageId) { + if let Some(signal @ WakeSignal::Pending { .. }) = self.signals.get_mut(reply_to) { let message_id = match signal { WakeSignal::Pending { message_id } => *message_id, _ => unreachable!(), @@ -245,10 +248,6 @@ pub struct MessageFuture { /// This identifier is generated by the corresponding send function (e.g. /// [`send_bytes`](super::send_bytes)). pub waiting_reply_to: MessageId, - /// Reply deposit that was allocated for this message. Checked in - /// handle_reply. - #[cfg_attr(feature = "ethexe", allow(unused))] - pub reply_deposit: Option, } impl Unpin for MessageFuture {} @@ -272,8 +271,10 @@ pub fn send_bytes_for_reply( destination: ActorId, payload: &[u8], value: ValueUnit, + wait: Lock, gas_limit: Option, reply_deposit: Option, + // reply_hook: Option>, ) -> Result { #[cfg(not(feature = "ethexe"))] let waiting_reply_to = if let Some(gas_limit) = gas_limit { @@ -291,15 +292,15 @@ pub fn send_bytes_for_reply( #[cfg(not(feature = "ethexe"))] if let Some(reply_deposit) = reply_deposit { - _ = ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit); + let deposited = ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit).is_ok(); + // if deposited && let Some(reply_hook) = reply_hook { + // reply_hooks().register(waiting_reply_to, reply_hook); + // } } - signals().register_signal(waiting_reply_to); + signals().register_signal(waiting_reply_to, wait); - Ok(MessageFuture { - waiting_reply_to, - reply_deposit, - }) + Ok(MessageFuture { waiting_reply_to }) } #[inline] @@ -308,8 +309,10 @@ pub fn create_program_for_reply( salt: &[u8], payload: &[u8], value: ValueUnit, + wait: Lock, gas_limit: Option, reply_deposit: Option, + // reply_hook: Option>, ) -> Result<(MessageFuture, ActorId), ::gstd::errors::Error> { #[cfg(not(feature = "ethexe"))] let (waiting_reply_to, program_id) = if let Some(gas_limit) = gas_limit { @@ -325,36 +328,45 @@ pub fn create_program_for_reply( #[cfg(not(feature = "ethexe"))] if let Some(reply_deposit) = reply_deposit { - _ = ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit); + let deposited = ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit).is_ok(); + // if deposited && let Some(reply_hook) = reply_hook { + // reply_hooks().register(waiting_reply_to, reply_hook); + // } } - signals().register_signal(waiting_reply_to); + signals().register_signal(waiting_reply_to, wait); - Ok(( - MessageFuture { - waiting_reply_to, - reply_deposit, - }, - program_id, - )) + Ok((MessageFuture { waiting_reply_to }, program_id)) } /// Default reply handler. #[inline] pub fn handle_reply_with_hook() { - signals().record_reply(); + let reply_to = ::gcore::msg::reply_to().expect("Shouldn't be called with incorrect context"); - // Execute reply hook (if it was registered) - // let replied_to = - // crate::msg::reply_to().expect("`gstd::handle_reply_with_hook()` called in wrong context"); + signals().record_reply(&reply_to); // #[cfg(not(feature = "ethexe"))] - // reply_hooks().execute_and_remove(replied_to); + // reply_hooks().execute_and_remove(&reply_to); // #[cfg(feature = "ethexe")] // let _ = replied_to; } +/// Default signal handler. +#[cfg(not(feature = "ethexe"))] +#[inline] +pub fn handle_signal() { + let msg_id = ::gcore::msg::signal_from().expect( + "`gstd::async_runtime::handle_signal()` must be called only in `handle_signal` entrypoint", + ); + + // critical::take_and_execute(); + + tasks().remove(&msg_id); + // reply_hooks().remove(&msg_id) +} + pub fn poll(message_id: &MessageId, cx: &mut Context<'_>) -> Poll, Error>> { signals().poll(message_id, cx) } diff --git a/rs/src/gstd/locks.rs b/rs/src/gstd/locks.rs index 5c68c69f1..1cc65215d 100644 --- a/rs/src/gstd/locks.rs +++ b/rs/src/gstd/locks.rs @@ -3,15 +3,15 @@ use core::cmp::Ordering; use gstd::{BlockCount, BlockNumber, Config, exec}; /// Type of wait locks. -#[derive(Debug, PartialEq, Eq)] -pub(crate) struct Lock { +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Lock { deadline: BlockNumber, ty: WaitType, } /// Wait types. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -pub(crate) enum WaitType { +pub enum WaitType { Exactly, #[default] UpTo, @@ -20,7 +20,7 @@ pub(crate) enum WaitType { impl Lock { /// Wait for pub fn exactly(b: BlockCount) -> Self { - let current = exec::block_height(); + let current = Syscall::block_height(); Self { deadline: current.saturating_add(b), ty: WaitType::Exactly, @@ -29,15 +29,24 @@ impl Lock { /// Wait up to pub fn up_to(b: BlockCount) -> Self { - let current = exec::block_height(); + let current = Syscall::block_height(); Self { deadline: current.saturating_add(b), ty: WaitType::UpTo, } } + /// Gets the deadline of the current lock. + pub fn deadline(&self) -> BlockNumber { + self.deadline + } + + pub fn wait_type(&self) -> WaitType { + self.ty + } + /// Call wait functions by the lock type. - pub fn wait(&self, now: BlockNumber) { + pub(crate) fn wait(&self, now: BlockNumber) { let duration = self .deadline .checked_sub(now) @@ -47,15 +56,6 @@ impl Lock { WaitType::UpTo => exec::wait_up_to(duration), } } - - /// Gets the deadline of the current lock. - pub fn deadline(&self) -> BlockNumber { - self.deadline - } - - pub fn wait_type(&self) -> WaitType { - self.ty - } } impl PartialOrd for Lock { diff --git a/rs/src/gstd/mod.rs b/rs/src/gstd/mod.rs index 854ddd005..851724dd1 100644 --- a/rs/src/gstd/mod.rs +++ b/rs/src/gstd/mod.rs @@ -1,3 +1,6 @@ +#[cfg(not(feature = "ethexe"))] +#[doc(hidden)] +pub use async_runtime::handle_signal; #[doc(hidden)] // pub use gstd::{handle_reply_with_hook, message_loop}; pub use async_runtime::{ @@ -9,9 +12,10 @@ pub use async_runtime::{ pub use ethexe::{EthEvent, EthEventExpo}; #[doc(hidden)] pub use events::{EventEmitter, SailsEvent}; -#[cfg(not(feature = "ethexe"))] -#[doc(hidden)] -pub use gstd::handle_signal; +pub use locks::{Lock, WaitType}; +// #[cfg(not(feature = "ethexe"))] +// #[doc(hidden)] +// pub use gstd::handle_signal; pub use gstd::{debug, exec, msg}; #[doc(hidden)] pub use sails_macros::{event, export, program, service}; @@ -29,6 +33,7 @@ pub(crate) mod async_runtime; mod ethexe; mod events; pub(crate) mod locks; +mod reply_hooks; pub mod services; mod syscalls; diff --git a/rs/src/gstd/reply_hooks.rs b/rs/src/gstd/reply_hooks.rs new file mode 100644 index 000000000..ef8d23995 --- /dev/null +++ b/rs/src/gstd/reply_hooks.rs @@ -0,0 +1,29 @@ +use crate::{MessageId, boxed::Box, collections::HashMap}; + +pub(crate) struct HooksMap(HashMap>); + +impl HooksMap { + pub fn new() -> Self { + Self(HashMap::new()) + } + + /// Register hook to be executed when a reply for message_id is received. + pub(crate) fn register(&mut self, mid: MessageId, f: F) { + if self.0.contains_key(&mid) { + panic!("handle_reply: reply hook for this message_id is already registered"); + } + self.0.insert(mid, Box::new(f)); + } + + /// Execute hook for message_id (if registered) + pub(crate) fn execute_and_remove(&mut self, message_id: &MessageId) { + if let Some(f) = self.0.remove(message_id) { + f(); + } + } + + /// Clear hook for message_id without executing it. + pub(crate) fn remove(&mut self, message_id: &MessageId) { + self.0.remove(message_id); + } +} From 9d4bdb8597218bb13965cd23201a4b6709811eff Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 1 Oct 2025 17:24:56 +0200 Subject: [PATCH 47/66] wip: reply hooks (old) --- benchmarks/bench_data.json | 18 +++++++++--------- rs/src/client/gstd_env.rs | 6 +++--- rs/src/gstd/async_runtime.rs | 30 +++++++++++++++--------------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 58d8673cb..98186dfe9 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -13,20 +13,20 @@ "317810": 43147409792 }, "counter": { - "async_call": 797660740, - "sync_call": 629814629 + "async_call": 803583790, + "sync_call": 635737679 }, "cross_program": { - "median": 2216723585 + "median": 2258091217 }, "redirect": { - "median": 3143495250 + "median": 3205931947 }, "message_stack": { - "0": 69107033500, - "1": 332351617900, - "5": 1388337296600, - "10": 2592200283700, - "20": 5072335162800 + "0": 71355372600, + "1": 341501614300, + "5": 1424999236700, + "10": 2663327704100, + "20": 5212346387000 } } \ No newline at end of file diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index 06c69af38..a6a5d8863 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -87,7 +87,7 @@ impl GstdEnv { params.wait.unwrap_or_default(), params.gas_limit, params.reply_deposit, - // params.reply_hook.take(), + params.reply_hook.take(), )); Ok(message.waiting_reply_to) @@ -121,7 +121,7 @@ const _: () = { params.wait.unwrap_or_default(), params.gas_limit, params.reply_deposit, - // params.reply_hook.take(), + params.reply_hook.take(), )); if params.redirect_on_exit { Ok(GstdFuture::MessageWithRedirect { @@ -148,7 +148,7 @@ const _: () = { params.wait.unwrap_or_default(), params.gas_limit, params.reply_deposit, - // params.reply_hook.take(), + params.reply_hook.take(), )); Ok((GstdFuture::CreateProgram { future }, program_id)) } diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index 078352904..bea7670a1 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -18,10 +18,10 @@ fn signals() -> &'static mut WakeSignals { unsafe { &mut *core::ptr::addr_of_mut!(MAP) }.get_or_insert_with(WakeSignals::new) } -// fn reply_hooks() -> &'static mut HooksMap { -// static mut MAP: Option = None; -// unsafe { &mut *core::ptr::addr_of_mut!(MAP) }.get_or_insert_with(HooksMap::new) -// } +fn reply_hooks() -> &'static mut HooksMap { + static mut MAP: Option = None; + unsafe { &mut *core::ptr::addr_of_mut!(MAP) }.get_or_insert_with(HooksMap::new) +} /// Matches a task to a some message in order to avoid duplicate execution /// of code that was running before the program was interrupted by `wait`. @@ -274,7 +274,7 @@ pub fn send_bytes_for_reply( wait: Lock, gas_limit: Option, reply_deposit: Option, - // reply_hook: Option>, + reply_hook: Option>, ) -> Result { #[cfg(not(feature = "ethexe"))] let waiting_reply_to = if let Some(gas_limit) = gas_limit { @@ -293,9 +293,9 @@ pub fn send_bytes_for_reply( #[cfg(not(feature = "ethexe"))] if let Some(reply_deposit) = reply_deposit { let deposited = ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit).is_ok(); - // if deposited && let Some(reply_hook) = reply_hook { - // reply_hooks().register(waiting_reply_to, reply_hook); - // } + if deposited && let Some(reply_hook) = reply_hook { + reply_hooks().register(waiting_reply_to, reply_hook); + } } signals().register_signal(waiting_reply_to, wait); @@ -312,7 +312,7 @@ pub fn create_program_for_reply( wait: Lock, gas_limit: Option, reply_deposit: Option, - // reply_hook: Option>, + reply_hook: Option>, ) -> Result<(MessageFuture, ActorId), ::gstd::errors::Error> { #[cfg(not(feature = "ethexe"))] let (waiting_reply_to, program_id) = if let Some(gas_limit) = gas_limit { @@ -329,9 +329,9 @@ pub fn create_program_for_reply( #[cfg(not(feature = "ethexe"))] if let Some(reply_deposit) = reply_deposit { let deposited = ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit).is_ok(); - // if deposited && let Some(reply_hook) = reply_hook { - // reply_hooks().register(waiting_reply_to, reply_hook); - // } + if deposited && let Some(reply_hook) = reply_hook { + reply_hooks().register(waiting_reply_to, reply_hook); + } } signals().register_signal(waiting_reply_to, wait); @@ -346,8 +346,8 @@ pub fn handle_reply_with_hook() { signals().record_reply(&reply_to); - // #[cfg(not(feature = "ethexe"))] - // reply_hooks().execute_and_remove(&reply_to); + #[cfg(not(feature = "ethexe"))] + reply_hooks().execute_and_remove(&reply_to); // #[cfg(feature = "ethexe")] // let _ = replied_to; @@ -364,7 +364,7 @@ pub fn handle_signal() { // critical::take_and_execute(); tasks().remove(&msg_id); - // reply_hooks().remove(&msg_id) + reply_hooks().remove(&msg_id) } pub fn poll(message_id: &MessageId, cx: &mut Context<'_>) -> Poll, Error>> { From 4215d8c1037b898d00dc2d5cbe10ddb313f320fb Mon Sep 17 00:00:00 2001 From: vobradovich Date: Thu, 2 Oct 2025 14:33:52 +0200 Subject: [PATCH 48/66] wip: move reply_hook to WakeSignal, remove HashMap --- benchmarks/bench_data.json | 18 ++-- rs/src/client/gstd_env.rs | 8 +- rs/src/gstd/async_runtime.rs | 180 ++++++++++++++++++++--------------- rs/src/gstd/mod.rs | 3 +- rs/src/gstd/reply_hooks.rs | 29 ------ 5 files changed, 115 insertions(+), 123 deletions(-) delete mode 100644 rs/src/gstd/reply_hooks.rs diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 98186dfe9..247263fc0 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -13,20 +13,20 @@ "317810": 43147409792 }, "counter": { - "async_call": 803583790, - "sync_call": 635737679 + "async_call": 800185741, + "sync_call": 632498203 }, "cross_program": { - "median": 2258091217 + "median": 2222368778 }, "redirect": { - "median": 3205931947 + "median": 3152889038 }, "message_stack": { - "0": 71355372600, - "1": 341501614300, - "5": 1424999236700, - "10": 2663327704100, - "20": 5212346387000 + "0": 69409573300, + "1": 333751504600, + "5": 1394230291400, + "10": 2603606913500, + "20": 5095008691300 } } \ No newline at end of file diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index a6a5d8863..4d36011b8 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -1,5 +1,5 @@ use super::*; -use crate::gstd::{MessageFuture, async_runtime, locks}; +use crate::gstd::{Lock, MessageFuture, async_runtime}; use ::gstd::errors::Error; #[derive(Default)] @@ -7,11 +7,11 @@ pub struct GstdParams { #[cfg(not(feature = "ethexe"))] pub gas_limit: Option, pub value: Option, - pub wait: Option, + pub wait: Option, #[cfg(not(feature = "ethexe"))] pub reply_deposit: Option, #[cfg(not(feature = "ethexe"))] - pub reply_hook: Option>, + pub reply_hook: Option>, pub redirect_on_exit: bool, } @@ -19,7 +19,7 @@ crate::params_for_pending_impl!(GstdEnv, GstdParams { #[cfg(not(feature = "ethexe"))] pub gas_limit: GasUnit, pub value: ValueUnit, - pub wait: locks::Lock, + pub wait: Lock, #[cfg(not(feature = "ethexe"))] pub reply_deposit: GasUnit, }); diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index bea7670a1..51250e517 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -1,5 +1,5 @@ use super::*; -use crate::{collections::HashMap, gstd::reply_hooks::HooksMap}; +use crate::collections::HashMap; use core::{ pin::Pin, task::{Context, Poll}, @@ -18,11 +18,6 @@ fn signals() -> &'static mut WakeSignals { unsafe { &mut *core::ptr::addr_of_mut!(MAP) }.get_or_insert_with(WakeSignals::new) } -fn reply_hooks() -> &'static mut HooksMap { - static mut MAP: Option = None; - unsafe { &mut *core::ptr::addr_of_mut!(MAP) }.get_or_insert_with(HooksMap::new) -} - /// Matches a task to a some message in order to avoid duplicate execution /// of code that was running before the program was interrupted by `wait`. pub struct Task { @@ -64,7 +59,7 @@ impl Task { self.reply_to_locks .extract_if(.., |(_, lock)| now >= lock.deadline()) .for_each(|(reply_to, lock)| { - signals_map.record_timeout(&reply_to, lock.deadline(), now); + signals_map.record_timeout(reply_to, lock.deadline(), now); ::gstd::debug!( "signal_reply_timeout: remove lock for reply_to {reply_to} in message due to timeout" ); @@ -127,6 +122,7 @@ pub type Payload = Vec; enum WakeSignal { Pending { message_id: MessageId, + reply_hook: Option>, }, Ready { payload: Payload, @@ -135,6 +131,7 @@ enum WakeSignal { Timeout { expected: BlockNumber, now: BlockNumber, + reply_hook: Option>, }, } @@ -149,11 +146,21 @@ impl WakeSignals { } } - pub fn register_signal(&mut self, waiting_reply_to: MessageId, lock: locks::Lock) { + pub fn register_signal( + &mut self, + waiting_reply_to: MessageId, + lock: locks::Lock, + reply_hook: Option>, + ) { let message_id = ::gcore::msg::id(); - self.signals - .insert(waiting_reply_to, WakeSignal::Pending { message_id }); + self.signals.insert( + waiting_reply_to, + WakeSignal::Pending { + message_id, + reply_hook, + }, + ); ::gstd::debug!( "register_signal: add lock for reply_to {waiting_reply_to} in message {message_id}" @@ -165,27 +172,45 @@ impl WakeSignals { } pub fn record_reply(&mut self, reply_to: &MessageId) { - if let Some(signal @ WakeSignal::Pending { .. }) = self.signals.get_mut(reply_to) { - let message_id = match signal { - WakeSignal::Pending { message_id } => *message_id, - _ => unreachable!(), - }; - *signal = WakeSignal::Ready { - payload: ::gstd::msg::load_bytes().expect("Failed to load bytes"), - reply_code: ::gcore::msg::reply_code() - .expect("Shouldn't be called with incorrect context"), + if let hashbrown::hash_map::EntryRef::Occupied(mut entry) = self.signals.entry_ref(reply_to) + { + match entry.get_mut() { + WakeSignal::Pending { + message_id, + reply_hook, + } => { + let message_id = *message_id; + let reply_hook = reply_hook.take(); + // replase entry with `WakeSignal::Ready` + _ = entry.insert(WakeSignal::Ready { + payload: ::gstd::msg::load_bytes().expect("Failed to load bytes"), + reply_code: ::gcore::msg::reply_code() + .expect("Shouldn't be called with incorrect context"), + }); + ::gstd::debug!( + "record_reply: remove lock for reply_to {reply_to} in message {message_id}" + ); + tasks() + .get_mut(&message_id) + .expect("A message task must exist") + .remove_lock(reply_to); + // wake message loop after receiving reply + ::gcore::exec::wake(message_id).expect("Failed to wake the message"); + + // execute reply hook + if let Some(f) = reply_hook { + f(); + } + } + WakeSignal::Timeout { reply_hook, .. } => { + // execute reply hook and remove entry + if let Some(f) = reply_hook.take() { + f(); + } + _ = entry.remove(); + } + WakeSignal::Ready { .. } => panic!("A reply has already received"), }; - - ::gstd::debug!( - "record_reply: remove lock for reply_to {reply_to} in message {message_id}" - ); - tasks() - .get_mut(&message_id) - .expect("A message task must exist") - .remove_lock(&reply_to); - - // wake message processign after handle reply - ::gcore::exec::wake(message_id).expect("Failed to wake the message") } else { ::gstd::debug!( "A message has received a reply though it wasn't to receive one, or a processed message has received a reply" @@ -193,14 +218,17 @@ impl WakeSignals { } } - pub fn record_timeout( - &mut self, - reply_to: &MessageId, - expected: BlockNumber, - now: BlockNumber, - ) { - if let Some(signal @ WakeSignal::Pending { .. }) = self.signals.get_mut(reply_to) { - *signal = WakeSignal::Timeout { expected, now }; + pub fn record_timeout(&mut self, reply_to: MessageId, expected: BlockNumber, now: BlockNumber) { + if let hashbrown::hash_map::Entry::Occupied(mut entry) = self.signals.entry(reply_to) + && let WakeSignal::Pending { reply_hook, .. } = entry.get_mut() + { + // move `reply_hook` to `WakeSignal::Timeout` state + let reply_hook = reply_hook.take(); + entry.insert(WakeSignal::Timeout { + expected, + now, + reply_hook, + }); } else { ::gstd::debug!("A message has timed out after reply"); } @@ -215,29 +243,35 @@ impl WakeSignals { reply_to: &MessageId, _cx: &mut Context<'_>, ) -> Poll, Error>> { - let entry = self.signals.entry_ref(reply_to); - let entry = match entry { - hashbrown::hash_map::EntryRef::Occupied(occupied_entry) => occupied_entry, - hashbrown::hash_map::EntryRef::Vacant(_) => panic!("Poll not registered feature"), + let hashbrown::hash_map::EntryRef::Occupied(entry) = self.signals.entry_ref(reply_to) + else { + panic!("Poll not registered feature") }; - if let WakeSignal::Pending { .. } = entry.get() { - return Poll::Pending; - } - match entry.remove() { - WakeSignal::Ready { - payload, - reply_code, - } => match reply_code { - ReplyCode::Success(_) => Poll::Ready(Ok(payload)), - ReplyCode::Error(reason) => { - Poll::Ready(Err(Error::ErrorReply(payload.into(), reason))) - } - ReplyCode::Unsupported => Poll::Ready(Err(Error::UnsupportedReply(payload))), - }, + match entry.get() { + WakeSignal::Pending { .. } => Poll::Pending, WakeSignal::Timeout { expected, now, .. } => { - Poll::Ready(Err(Error::Timeout(expected, now))) + // DO NOT remove entry if `WakeSignal::Timeout` + // will be removed in `record_reply` + Poll::Ready(Err(Error::Timeout(*expected, *now))) + } + WakeSignal::Ready { .. } => { + // remove entry if `WakeSignal::Ready` + let WakeSignal::Ready { + payload, + reply_code, + } = entry.remove() + else { + // SAFETY: checked in the code above. + unsafe { hint::unreachable_unchecked() } + }; + match reply_code { + ReplyCode::Success(_) => Poll::Ready(Ok(payload)), + ReplyCode::Error(reason) => { + Poll::Ready(Err(Error::ErrorReply(payload.into(), reason))) + } + ReplyCode::Unsupported => Poll::Ready(Err(Error::UnsupportedReply(payload))), + } } - _ => unreachable!(), } } } @@ -274,7 +308,7 @@ pub fn send_bytes_for_reply( wait: Lock, gas_limit: Option, reply_deposit: Option, - reply_hook: Option>, + reply_hook: Option>, ) -> Result { #[cfg(not(feature = "ethexe"))] let waiting_reply_to = if let Some(gas_limit) = gas_limit { @@ -292,13 +326,10 @@ pub fn send_bytes_for_reply( #[cfg(not(feature = "ethexe"))] if let Some(reply_deposit) = reply_deposit { - let deposited = ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit).is_ok(); - if deposited && let Some(reply_hook) = reply_hook { - reply_hooks().register(waiting_reply_to, reply_hook); - } + _ = ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit); } - signals().register_signal(waiting_reply_to, wait); + signals().register_signal(waiting_reply_to, wait, reply_hook); Ok(MessageFuture { waiting_reply_to }) } @@ -312,7 +343,7 @@ pub fn create_program_for_reply( wait: Lock, gas_limit: Option, reply_deposit: Option, - reply_hook: Option>, + reply_hook: Option>, ) -> Result<(MessageFuture, ActorId), ::gstd::errors::Error> { #[cfg(not(feature = "ethexe"))] let (waiting_reply_to, program_id) = if let Some(gas_limit) = gas_limit { @@ -328,13 +359,10 @@ pub fn create_program_for_reply( #[cfg(not(feature = "ethexe"))] if let Some(reply_deposit) = reply_deposit { - let deposited = ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit).is_ok(); - if deposited && let Some(reply_hook) = reply_hook { - reply_hooks().register(waiting_reply_to, reply_hook); - } + _ = ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit); } - signals().register_signal(waiting_reply_to, wait); + signals().register_signal(waiting_reply_to, wait, reply_hook); Ok((MessageFuture { waiting_reply_to }, program_id)) } @@ -345,12 +373,6 @@ pub fn handle_reply_with_hook() { let reply_to = ::gcore::msg::reply_to().expect("Shouldn't be called with incorrect context"); signals().record_reply(&reply_to); - - #[cfg(not(feature = "ethexe"))] - reply_hooks().execute_and_remove(&reply_to); - - // #[cfg(feature = "ethexe")] - // let _ = replied_to; } /// Default signal handler. @@ -360,11 +382,11 @@ pub fn handle_signal() { let msg_id = ::gcore::msg::signal_from().expect( "`gstd::async_runtime::handle_signal()` must be called only in `handle_signal` entrypoint", ); - + // TODO: find out what `msg_id` here - from `message_loop` or `waiting_reply_to` // critical::take_and_execute(); - tasks().remove(&msg_id); - reply_hooks().remove(&msg_id) + // tasks().remove(&msg_id); + // reply_hooks().remove(&msg_id) } pub fn poll(message_id: &MessageId, cx: &mut Context<'_>) -> Poll, Error>> { diff --git a/rs/src/gstd/mod.rs b/rs/src/gstd/mod.rs index 851724dd1..54fe10199 100644 --- a/rs/src/gstd/mod.rs +++ b/rs/src/gstd/mod.rs @@ -32,8 +32,7 @@ pub(crate) mod async_runtime; #[cfg(feature = "ethexe")] mod ethexe; mod events; -pub(crate) mod locks; -mod reply_hooks; +mod locks; pub mod services; mod syscalls; diff --git a/rs/src/gstd/reply_hooks.rs b/rs/src/gstd/reply_hooks.rs deleted file mode 100644 index ef8d23995..000000000 --- a/rs/src/gstd/reply_hooks.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::{MessageId, boxed::Box, collections::HashMap}; - -pub(crate) struct HooksMap(HashMap>); - -impl HooksMap { - pub fn new() -> Self { - Self(HashMap::new()) - } - - /// Register hook to be executed when a reply for message_id is received. - pub(crate) fn register(&mut self, mid: MessageId, f: F) { - if self.0.contains_key(&mid) { - panic!("handle_reply: reply hook for this message_id is already registered"); - } - self.0.insert(mid, Box::new(f)); - } - - /// Execute hook for message_id (if registered) - pub(crate) fn execute_and_remove(&mut self, message_id: &MessageId) { - if let Some(f) = self.0.remove(message_id) { - f(); - } - } - - /// Clear hook for message_id without executing it. - pub(crate) fn remove(&mut self, message_id: &MessageId) { - self.0.remove(message_id); - } -} From ee0c1ad41e7ad1153096bac756ddd058380a2f32 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Fri, 3 Oct 2025 11:46:16 +0200 Subject: [PATCH 49/66] wip: micro perf --- benchmarks/bench_data.json | 14 +++++++------- rs/src/client/gstd_env.rs | 6 ++---- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 247263fc0..a58199d03 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -17,16 +17,16 @@ "sync_call": 632498203 }, "cross_program": { - "median": 2222368778 + "median": 2221277112 }, "redirect": { - "median": 3152889038 + "median": 3151597611 }, "message_stack": { - "0": 69409573300, - "1": 333751504600, - "5": 1394230291400, - "10": 2603606913500, - "20": 5095008691300 + "0": 69398212500, + "1": 333663474600, + "5": 1393835584600, + "10": 2602828860700, + "20": 5093463946500 } } \ No newline at end of file diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index 4d36011b8..16251647c 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -156,6 +156,7 @@ const _: () = { impl Future for PendingCall { type Output = Result::Error>; + #[inline(always)] fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if self.state.is_none() { let args = self @@ -166,10 +167,7 @@ const _: () = { let destination = self.destination; let params = self.params.get_or_insert_default(); // Send message - let future = match send_for_reply(destination, payload, params) { - Ok(future) => future, - Err(err) => return Poll::Ready(Err(err)), - }; + let future = send_for_reply(destination, payload, params)?; self.state = Some(future); // No need to poll the future return Poll::Pending; From 68b5d8fcac2f9569a9e2b266dbc78a76db218494 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Fri, 3 Oct 2025 14:21:50 +0200 Subject: [PATCH 50/66] wip: add comments --- rs/src/gstd/async_runtime.rs | 80 +++++++++++++++++++++++++++++++----- rs/src/gstd/syscalls.rs | 52 ++++++++++++++--------- 2 files changed, 103 insertions(+), 29 deletions(-) diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index 51250e517..319546ce5 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -77,17 +77,25 @@ impl Task { } } -/// The main asynchronous message handling loop. +/// Drives asynchronous handling for the currently executing inbound message. /// -/// Gear allows user and program interaction via -/// messages. This function is the entry point to run the asynchronous message -/// processing. +/// - locates or creates the `Task` holding the user future for the current message id; +/// - polls the future once and, if it completes, tears down the bookkeeping; +/// - when the future stays pending, arms the shortest wait lock so the runtime suspends until a wake. +/// +/// # Context +/// Called from the contract's `handle` entry point while [message_loop] runs single-threaded inside the +/// actor. It must be invoked exactly once per incoming message to advance the async state machine. +/// +/// # Panics +/// Panics propagated from the user future bubble up, and the function will panic if no wait lock is +/// registered when a pending future requests suspension (see `Task::wait`), signalling a contract logic bug. #[inline] pub fn message_loop(future: F) where F: Future + 'static, { - let msg_id = ::gcore::msg::id(); + let msg_id = Syscall::message_id(); let tasks_map = tasks(); let task = tasks_map.entry(msg_id).or_insert_with(|| { #[cfg(not(feature = "ethexe"))] @@ -146,13 +154,26 @@ impl WakeSignals { } } + /// Registers a pending reply for `waiting_reply_to` while the current message is being processed. + /// + /// - stores pending state (and an optional reply hook) so `poll`/`record_reply` can resolve it later; + /// - attaches the provided `lock` to the owning `Task` so wait/timeout bookkeeping stays consistent. + /// + /// # Context + /// Called from helpers such as `send_bytes_for_reply` / `create_program_for_reply` while the message + /// handler executes inside [message_loop] in `handle()` entry point, see [Gear Protocol](https://wiki.vara.network/docs/build/introduction). + /// The current `message_id` is read from the runtime and used to fetch the associated `Task` entry. + /// + /// # Panics + /// Panics if the `Task` for the current `message_id` cannot be found, which indicates the function + /// was invoked outside the [message_loop] context (programmer error). pub fn register_signal( &mut self, waiting_reply_to: MessageId, lock: locks::Lock, reply_hook: Option>, ) { - let message_id = ::gcore::msg::id(); + let message_id = Syscall::message_id(); self.signals.insert( waiting_reply_to, @@ -171,6 +192,19 @@ impl WakeSignals { .insert_lock(waiting_reply_to, lock); } + /// Processes an incoming reply for `reply_to` and transitions the stored wake state. + /// + /// - upgrades the pending entry to `Ready`, capturing payload and reply code; + /// - detaches the wait lock from the owning `Task`, then wakes the suspended message loop; + /// - executes the optional reply hook once the reply becomes available. + /// + /// # Context + /// Invoked by `handle_reply_with_hook` when a reply arrives during `handle_reply()` execution. The + /// runtime supplies `reply_to`, and this method synchronises bookkeeping before waking the task. + /// + /// # Panics + /// Panics if it encounters an already finalised entry (`WakeSignal::Ready`) or the associated task is + /// missing. Both scenarios indicate logic bugs or duplicate delivery. pub fn record_reply(&mut self, reply_to: &MessageId) { if let hashbrown::hash_map::EntryRef::Occupied(mut entry) = self.signals.entry_ref(reply_to) { @@ -184,7 +218,7 @@ impl WakeSignals { // replase entry with `WakeSignal::Ready` _ = entry.insert(WakeSignal::Ready { payload: ::gstd::msg::load_bytes().expect("Failed to load bytes"), - reply_code: ::gcore::msg::reply_code() + reply_code: Syscall::reply_code() .expect("Shouldn't be called with incorrect context"), }); ::gstd::debug!( @@ -218,6 +252,16 @@ impl WakeSignals { } } + /// Marks a pending reply as timed out and preserves context for later handling. + /// + /// - upgrades a `WakeSignal::Pending` entry to `WakeSignal::Timeout`, capturing when the reply was expected + /// and when the timeout was detected; + /// - retains the optional reply hook so it can still be executed if a late reply arrives and reuses the + /// stored state when `record_reply` is called afterwards. + /// + /// # Context + /// Triggered from `Task::signal_reply_timeout` whenever the runtime observes that a waiting reply exceeded + /// its deadline while executing inside [message_loop]. pub fn record_timeout(&mut self, reply_to: MessageId, expected: BlockNumber, now: BlockNumber) { if let hashbrown::hash_map::Entry::Occupied(mut entry) = self.signals.entry(reply_to) && let WakeSignal::Pending { reply_hook, .. } = entry.get_mut() @@ -238,6 +282,19 @@ impl WakeSignals { self.signals.contains_key(reply_to) } + /// Polls the stored wake signal for `reply_to`, returning the appropriate future state. + /// + /// # What it does + /// - inspects the current `WakeSignal` variant and returns `Pending`, a `Ready` payload, or propagates + /// a timeout error; when `Ready`, the entry is removed so subsequent polls observe completion. + /// + /// # Context + /// Called by [MessageFuture::poll] (and any wrappers) while a consumer awaits a reply produced by + /// [message_loop]. It runs on the same execution thread and must be non-blocking. + /// + /// # Panics + /// Panics if the signal was never registered for `reply_to`, which indicates misuse of the async API + /// (polling without having called one of the [send_bytes_for_reply]/[create_program_for_reply] methods first). pub fn poll( &mut self, reply_to: &MessageId, @@ -280,7 +337,7 @@ pub struct MessageFuture { /// A message identifier for an expected reply. /// /// This identifier is generated by the corresponding send function (e.g. - /// [`send_bytes`](super::send_bytes)). + /// [`gcore::msg::send`](::gcore::msg::send)). pub waiting_reply_to: MessageId, } @@ -370,7 +427,7 @@ pub fn create_program_for_reply( /// Default reply handler. #[inline] pub fn handle_reply_with_hook() { - let reply_to = ::gcore::msg::reply_to().expect("Shouldn't be called with incorrect context"); + let reply_to = Syscall::reply_to().expect("Shouldn't be called with incorrect context"); signals().record_reply(&reply_to); } @@ -379,10 +436,13 @@ pub fn handle_reply_with_hook() { #[cfg(not(feature = "ethexe"))] #[inline] pub fn handle_signal() { - let msg_id = ::gcore::msg::signal_from().expect( + let msg_id = Syscall::signal_from().expect( "`gstd::async_runtime::handle_signal()` must be called only in `handle_signal` entrypoint", ); // TODO: find out what `msg_id` here - from `message_loop` or `waiting_reply_to` + // [Docs](https://wiki.vara.network/docs/build/gstd/system-signals) refers to `msg::id()` in `handle()` + // but it is probably `waiting_reply_to` + // critical::take_and_execute(); // tasks().remove(&msg_id); diff --git a/rs/src/gstd/syscalls.rs b/rs/src/gstd/syscalls.rs index 642a8aa14..0d818c2ee 100644 --- a/rs/src/gstd/syscalls.rs +++ b/rs/src/gstd/syscalls.rs @@ -9,7 +9,7 @@ use crate::prelude::*; /// These methods are essential for enabling on-chain applications to interact with the Gear runtime /// in a consistent manner. Depending on the target environment, different implementations are provided: /// -/// - For the WASM target, direct calls are made to `gstd::msg` and `gstd::exec` to fetch runtime data. +/// - For the WASM target, direct calls are made to `gcore::msg` and `gcore::exec` to fetch runtime data. /// - In standard (`std`) environments, a mock implementation uses thread-local state for testing purposes. /// - In `no_std` configurations without the `std` feature and not WASM target, the functions are marked as unimplemented. /// @@ -19,62 +19,76 @@ pub struct Syscall; #[cfg(target_arch = "wasm32")] impl Syscall { + #[inline(always)] pub fn message_id() -> MessageId { - gstd::msg::id() + ::gcore::msg::id() } + #[inline(always)] pub fn message_size() -> usize { - gstd::msg::size() + ::gcore::msg::size() } + #[inline(always)] pub fn message_source() -> ActorId { - gstd::msg::source() + ::gcore::msg::source() } + #[inline(always)] pub fn message_value() -> u128 { - gstd::msg::value() + ::gcore::msg::value() } + #[inline(always)] pub fn reply_to() -> Result { - gstd::msg::reply_to() + ::gcore::msg::reply_to() } + #[inline(always)] pub fn reply_code() -> Result { - gstd::msg::reply_code() + ::gcore::msg::reply_code() } #[cfg(not(feature = "ethexe"))] + #[inline(always)] pub fn signal_from() -> Result { - gstd::msg::signal_from() + ::gcore::msg::signal_from() } #[cfg(not(feature = "ethexe"))] + #[inline(always)] pub fn signal_code() -> Result, gcore::errors::Error> { - gstd::msg::signal_code() + ::gcore::msg::signal_code() } + #[inline(always)] pub fn program_id() -> ActorId { - gstd::exec::program_id() + ::gcore::exec::program_id() } + #[inline(always)] pub fn block_height() -> u32 { - gstd::exec::block_height() + ::gcore::exec::block_height() } + #[inline(always)] pub fn block_timestamp() -> u64 { - gstd::exec::block_timestamp() + ::gcore::exec::block_timestamp() } + #[inline(always)] pub fn value_available() -> u128 { - gstd::exec::value_available() + ::gcore::exec::value_available() } - pub fn env_vars() -> gstd::EnvVars { - gstd::exec::env_vars() + #[inline(always)] + pub fn env_vars() -> ::gcore::EnvVars { + ::gcore::exec::env_vars() } + #[inline(always)] pub fn exit(inheritor_id: ActorId) -> ! { - gstd::exec::exit(inheritor_id) + ::gcore::exec::exit(inheritor_id) } } @@ -111,7 +125,7 @@ syscall_unimplemented!( block_height() -> u32, block_timestamp() -> u64, value_available() -> u128, - env_vars() -> gstd::EnvVars, + env_vars() -> ::gcore::EnvVars, ); #[cfg(not(target_arch = "wasm32"))] @@ -199,8 +213,8 @@ const _: () = { } impl Syscall { - pub fn env_vars() -> gstd::EnvVars { - gstd::EnvVars { + pub fn env_vars() -> ::gcore::EnvVars { + ::gcore::EnvVars { performance_multiplier: gstd::Percent::new(100), existential_deposit: 1_000_000_000_000, mailbox_threshold: 3000, From 7968115ce638796140a8582ed3d90cca111f686a Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 6 Oct 2025 17:10:41 +0200 Subject: [PATCH 51/66] wip: add Task::clear(), call in handle_signal() --- benchmarks/bench_data.json | 20 ++++++++++---------- rs/src/gstd/async_runtime.rs | 35 ++++++++++++++++++----------------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index a58199d03..b08d1e543 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -1,6 +1,6 @@ { "compute": { - "median": 450514040279 + "median": 450514036959 }, "alloc": { "0": 564376739, @@ -13,20 +13,20 @@ "317810": 43147409792 }, "counter": { - "async_call": 800185741, - "sync_call": 632498203 + "async_call": 803570343, + "sync_call": 635718923 }, "cross_program": { - "median": 2221277112 + "median": 2230577866 }, "redirect": { - "median": 3151597611 + "median": 3167814760 }, "message_stack": { - "0": 69398212500, - "1": 333663474600, - "5": 1393835584600, - "10": 2602828860700, - "20": 5093463946500 + "0": 69700784900, + "1": 335100538800, + "5": 1399810616000, + "10": 2614476351100, + "20": 5116456354900 } } \ No newline at end of file diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index 319546ce5..53255196a 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -43,13 +43,10 @@ impl Task { #[inline] fn remove_lock(&mut self, reply_to: &MessageId) { - if let Some(index) = self - .reply_to_locks + self.reply_to_locks .iter() .position(|(mid, _)| mid == reply_to) - { - self.reply_to_locks.swap_remove(index); - } + .map(|index| self.reply_to_locks.swap_remove(index)); } #[inline] @@ -75,6 +72,14 @@ impl Task { .expect("Cannot find lock to be waited") .wait(now); } + + #[inline] + fn clear(&self) { + let signals_map = signals(); + self.reply_to_locks.iter().for_each(|(reply_to, _)| { + signals_map.remove(reply_to); + }); + } } /// Drives asynchronous handling for the currently executing inbound message. @@ -232,15 +237,11 @@ impl WakeSignals { ::gcore::exec::wake(message_id).expect("Failed to wake the message"); // execute reply hook - if let Some(f) = reply_hook { - f(); - } + if let Some(f) = reply_hook { f() } } WakeSignal::Timeout { reply_hook, .. } => { // execute reply hook and remove entry - if let Some(f) = reply_hook.take() { - f(); - } + if let Some(f) = reply_hook.take() { f() } _ = entry.remove(); } WakeSignal::Ready { .. } => panic!("A reply has already received"), @@ -331,6 +332,10 @@ impl WakeSignals { } } } + + fn remove(&mut self, reply_to: &MessageId) -> Option { + self.signals.remove(reply_to) + } } pub struct MessageFuture { @@ -439,14 +444,10 @@ pub fn handle_signal() { let msg_id = Syscall::signal_from().expect( "`gstd::async_runtime::handle_signal()` must be called only in `handle_signal` entrypoint", ); - // TODO: find out what `msg_id` here - from `message_loop` or `waiting_reply_to` - // [Docs](https://wiki.vara.network/docs/build/gstd/system-signals) refers to `msg::id()` in `handle()` - // but it is probably `waiting_reply_to` - // critical::take_and_execute(); - // tasks().remove(&msg_id); - // reply_hooks().remove(&msg_id) + // Remove Task and all associated signals + if let Some(task) = tasks().remove(&msg_id) { task.clear() } } pub fn poll(message_id: &MessageId, cx: &mut Context<'_>) -> Poll, Error>> { From 23fad29f7fa74924305673c401d1196fb38f93db Mon Sep 17 00:00:00 2001 From: vobradovich Date: Tue, 7 Oct 2025 16:26:51 +0200 Subject: [PATCH 52/66] wip: ethexe, docs, tests --- .codex/config.toml | 4 + benchmarks/bench_data.json | 18 +-- examples/event-routes/app/src/lib.rs | 4 +- rs/src/client/gstd_env.rs | 16 +- rs/src/gstd/async_runtime.rs | 216 ++++++++++++++++++++++++--- rs/src/gstd/mod.rs | 2 +- 6 files changed, 226 insertions(+), 34 deletions(-) create mode 100644 .codex/config.toml diff --git a/.codex/config.toml b/.codex/config.toml new file mode 100644 index 000000000..d80506fe4 --- /dev/null +++ b/.codex/config.toml @@ -0,0 +1,4 @@ +# The model decides when to escalate +approval_policy = "on-request" +model_reasoning_effort = "high" +model_reasoning_summary = "detailed" diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index b08d1e543..c55e29b3a 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -13,20 +13,20 @@ "317810": 43147409792 }, "counter": { - "async_call": 803570343, - "sync_call": 635718923 + "async_call": 803980558, + "sync_call": 636091307 }, "cross_program": { - "median": 2230577866 + "median": 2231370119 }, "redirect": { - "median": 3167814760 + "median": 3168973023 }, "message_stack": { - "0": 69700784900, - "1": 335100538800, - "5": 1399810616000, - "10": 2614476351100, - "20": 5116456354900 + "0": 69720235700, + "1": 335178342000, + "5": 1400121828800, + "10": 2615079325900, + "20": 5117642853700 } } \ No newline at end of file diff --git a/examples/event-routes/app/src/lib.rs b/examples/event-routes/app/src/lib.rs index e0fb59186..21d1e170e 100644 --- a/examples/event-routes/app/src/lib.rs +++ b/examples/event-routes/app/src/lib.rs @@ -29,9 +29,7 @@ impl Service { pub async fn foo(&mut self) { let source = Syscall::message_source(); self.emit_event(Events::Start).unwrap(); - let _res = gstd::msg::send_for_reply(source, self.0, 0, 0) - .unwrap() - .await; + let _res = gstd::send_for_reply(source, self.0, 0).unwrap().await; self.emit_event(Events::End).unwrap(); } } diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index 16251647c..24fcc8951 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -4,15 +4,15 @@ use ::gstd::errors::Error; #[derive(Default)] pub struct GstdParams { - #[cfg(not(feature = "ethexe"))] - pub gas_limit: Option, pub value: Option, pub wait: Option, + pub redirect_on_exit: bool, + #[cfg(not(feature = "ethexe"))] + pub gas_limit: Option, #[cfg(not(feature = "ethexe"))] pub reply_deposit: Option, #[cfg(not(feature = "ethexe"))] pub reply_hook: Option>, - pub redirect_on_exit: bool, } crate::params_for_pending_impl!(GstdEnv, GstdParams { @@ -74,6 +74,7 @@ impl GearEnv for GstdEnv { } impl GstdEnv { + #[cfg_attr(feature = "ethexe", allow(unused_mut))] pub fn send_one_way( &self, destination: ActorId, @@ -85,8 +86,11 @@ impl GstdEnv { payload.as_ref(), params.value.unwrap_or_default(), params.wait.unwrap_or_default(), + #[cfg(not(feature = "ethexe"))] params.gas_limit, + #[cfg(not(feature = "ethexe"))] params.reply_deposit, + #[cfg(not(feature = "ethexe"))] params.reply_hook.take(), )); @@ -119,8 +123,11 @@ const _: () = { payload.as_ref(), params.value.unwrap_or_default(), params.wait.unwrap_or_default(), + #[cfg(not(feature = "ethexe"))] params.gas_limit, + #[cfg(not(feature = "ethexe"))] params.reply_deposit, + #[cfg(not(feature = "ethexe"))] params.reply_hook.take(), )); if params.redirect_on_exit { @@ -146,8 +153,11 @@ const _: () = { payload.as_ref(), params.value.unwrap_or_default(), params.wait.unwrap_or_default(), + #[cfg(not(feature = "ethexe"))] params.gas_limit, + #[cfg(not(feature = "ethexe"))] params.reply_deposit, + #[cfg(not(feature = "ethexe"))] params.reply_hook.take(), )); Ok((GstdFuture::CreateProgram { future }, program_id)) diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index 53255196a..4f00c5a3d 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -36,11 +36,25 @@ impl Task { } } + /// Registers the wait/timeout lock associated with an outgoing message reply. + /// + /// - stores the `(reply_to, lock)` pair so the task can later detect timeouts; + /// + /// # Context + /// Called exclusively from [`WakeSignals::register_signal`] while the outer `message_loop` + /// prepares to await a reply for the current inbound message. #[inline] fn insert_lock(&mut self, reply_to: MessageId, lock: locks::Lock) { self.reply_to_locks.push((reply_to, lock)); } + /// Removes the stored lock for the given reply identifier, if present. + /// + /// - searches for the `(reply_to, lock)` pair and removes it. + /// + /// # Context + /// Called from [`WakeSignals::record_reply`] once a response is received, as well + /// as during cleanup when a task finishes. #[inline] fn remove_lock(&mut self, reply_to: &MessageId) { self.reply_to_locks @@ -49,6 +63,15 @@ impl Task { .map(|index| self.reply_to_locks.swap_remove(index)); } + /// Notifies the signal registry about replies that have exceeded their deadlines. + /// + /// - scans all tracked locks, extracting those whose deadlines are at or before `now`; + /// - informs [`WakeSignals`] about the timeout so it can update the wake state and + /// potentially execute a deferred reply hook. + /// + /// # Context + /// Invoked from [`message_loop`] before polling the user future to ensure that timeouts + /// are processed promptly for the current message. #[inline] fn signal_reply_timeout(&mut self, now: BlockNumber) { let signals_map = signals(); @@ -57,12 +80,23 @@ impl Task { .extract_if(.., |(_, lock)| now >= lock.deadline()) .for_each(|(reply_to, lock)| { signals_map.record_timeout(reply_to, lock.deadline(), now); - ::gstd::debug!( - "signal_reply_timeout: remove lock for reply_to {reply_to} in message due to timeout" - ); + // ::gstd::debug!( + // "signal_reply_timeout: remove lock for reply_to {reply_to} in message due to timeout" + // ); }); } + /// Arms the most urgent wait lock so the executor suspends until a wake signal. + /// + /// - finds the lock with the smallest deadline and delegates to `Lock::wait` to set + /// the runtime suspension point. + /// + /// # Context + /// Called from [`message_loop`] whenever the user future remains pending after polling. + /// + /// # Panics + /// Panics if no locks are registered for the current task, which indicates a logic error + /// (e.g. awaiting a reply without having registered one). #[inline] fn wait(&self, now: BlockNumber) { self.reply_to_locks @@ -73,6 +107,15 @@ impl Task { .wait(now); } + #[cfg(not(feature = "ethexe"))] + /// Removes all outstanding reply locks from the signal registry without waiting on them. + /// + /// # What it does + /// - iterates every stored `(reply_to, _)` pair and asks [`WakeSignals`] to drop the wake entry; + /// - used as part of task teardown to avoid keeping stale replies alive. + /// + /// # Context + /// Called from [`handle_signal`]. #[inline] fn clear(&self) { let signals_map = signals(); @@ -188,9 +231,9 @@ impl WakeSignals { }, ); - ::gstd::debug!( - "register_signal: add lock for reply_to {waiting_reply_to} in message {message_id}" - ); + // ::gstd::debug!( + // "register_signal: add lock for reply_to {waiting_reply_to} in message {message_id}" + // ); tasks() .get_mut(&message_id) .expect("A message task must exist") @@ -237,11 +280,15 @@ impl WakeSignals { ::gcore::exec::wake(message_id).expect("Failed to wake the message"); // execute reply hook - if let Some(f) = reply_hook { f() } + if let Some(f) = reply_hook { + f() + } } WakeSignal::Timeout { reply_hook, .. } => { // execute reply hook and remove entry - if let Some(f) = reply_hook.take() { f() } + if let Some(f) = reply_hook.take() { + f() + } _ = entry.remove(); } WakeSignal::Ready { .. } => panic!("A reply has already received"), @@ -333,6 +380,7 @@ impl WakeSignals { } } + #[cfg(not(feature = "ethexe"))] fn remove(&mut self, reply_to: &MessageId) -> Option { self.signals.remove(reply_to) } @@ -362,6 +410,34 @@ impl FusedFuture for MessageFuture { } } +#[inline] +pub fn send_for_reply( + destination: ActorId, + payload: E, + value: ValueUnit, +) -> Result { + let size = Encode::encoded_size(&payload); + stack_buffer::with_byte_buffer(size, |buffer: &mut [mem::MaybeUninit]| { + let mut buffer_writer = MaybeUninitBufferWriter::new(buffer); + Encode::encode_to(&payload, &mut buffer_writer); + buffer_writer.with_buffer(|buffer| { + send_bytes_for_reply( + destination, + buffer, + value, + Default::default(), + #[cfg(not(feature = "ethexe"))] + None, + #[cfg(not(feature = "ethexe"))] + None, + #[cfg(not(feature = "ethexe"))] + None, + ) + }) + }) +} + +#[cfg(not(feature = "ethexe"))] #[inline] pub fn send_bytes_for_reply( destination: ActorId, @@ -372,7 +448,6 @@ pub fn send_bytes_for_reply( reply_deposit: Option, reply_hook: Option>, ) -> Result { - #[cfg(not(feature = "ethexe"))] let waiting_reply_to = if let Some(gas_limit) = gas_limit { crate::ok!(::gcore::msg::send_with_gas( destination, @@ -383,10 +458,7 @@ pub fn send_bytes_for_reply( } else { crate::ok!(::gcore::msg::send(destination, payload, value)) }; - #[cfg(feature = "ethexe")] - let waiting_reply_to = ::gcore::msg::send(destination, payload, value); - #[cfg(not(feature = "ethexe"))] if let Some(reply_deposit) = reply_deposit { _ = ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit); } @@ -396,6 +468,23 @@ pub fn send_bytes_for_reply( Ok(MessageFuture { waiting_reply_to }) } +#[cfg(feature = "ethexe")] +#[inline] +pub fn send_bytes_for_reply( + destination: ActorId, + payload: &[u8], + value: ValueUnit, + wait: Lock, +) -> Result { + let waiting_reply_to = crate::ok!(::gcore::msg::send(destination, payload, value)); + + signals().register_signal(waiting_reply_to, wait, None); + + Ok(MessageFuture { waiting_reply_to }) +} + +#[cfg(not(feature = "ethexe"))] +#[allow(clippy::too_many_arguments)] #[inline] pub fn create_program_for_reply( code_id: CodeId, @@ -407,7 +496,6 @@ pub fn create_program_for_reply( reply_deposit: Option, reply_hook: Option>, ) -> Result<(MessageFuture, ActorId), ::gstd::errors::Error> { - #[cfg(not(feature = "ethexe"))] let (waiting_reply_to, program_id) = if let Some(gas_limit) = gas_limit { crate::ok!(::gcore::prog::create_program_with_gas( code_id, salt, payload, gas_limit, value @@ -415,11 +503,7 @@ pub fn create_program_for_reply( } else { crate::ok!(::gcore::prog::create_program(code_id, salt, payload, value)) }; - #[cfg(feature = "ethexe")] - let (waiting_reply_to, program_id) = - crate::ok!(::gcore::prog::create_program(code_id, salt, payload, value)); - #[cfg(not(feature = "ethexe"))] if let Some(reply_deposit) = reply_deposit { _ = ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit); } @@ -429,6 +513,23 @@ pub fn create_program_for_reply( Ok((MessageFuture { waiting_reply_to }, program_id)) } +#[cfg(feature = "ethexe")] +#[inline] +pub fn create_program_for_reply( + code_id: CodeId, + salt: &[u8], + payload: &[u8], + value: ValueUnit, + wait: Lock, +) -> Result<(MessageFuture, ActorId), ::gstd::errors::Error> { + let (waiting_reply_to, program_id) = + crate::ok!(::gcore::prog::create_program(code_id, salt, payload, value)); + + signals().register_signal(waiting_reply_to, wait, None); + + Ok((MessageFuture { waiting_reply_to }, program_id)) +} + /// Default reply handler. #[inline] pub fn handle_reply_with_hook() { @@ -447,7 +548,9 @@ pub fn handle_signal() { // critical::take_and_execute(); // Remove Task and all associated signals - if let Some(task) = tasks().remove(&msg_id) { task.clear() } + if let Some(task) = tasks().remove(&msg_id) { + task.clear() + } } pub fn poll(message_id: &MessageId, cx: &mut Context<'_>) -> Poll, Error>> { @@ -457,3 +560,80 @@ pub fn poll(message_id: &MessageId, cx: &mut Context<'_>) -> Poll pub fn is_terminated(message_id: &MessageId) -> bool { !signals().waits_for(message_id) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::gstd::locks; + use crate::gstd::syscalls::Syscall; + + fn set_context(message_id: MessageId, block_height: u32) { + Syscall::with_message_id(message_id); + Syscall::with_block_height(block_height); + } + + #[test] + fn insert_lock_adds_entry() { + set_context(MessageId::from(1), 10); + + let mut task = Task::new(async {}); + let reply_to = MessageId::from(2); + let lock = locks::Lock::up_to(3); + + task.insert_lock(reply_to, lock); + + assert_eq!(task.reply_to_locks.len(), 1); + assert_eq!(task.reply_to_locks[0].0, reply_to); + assert_eq!(task.reply_to_locks[0].1.deadline(), lock.deadline()); + } + + #[test] + fn remove_lock_drops_matching_entry() { + set_context(MessageId::from(3), 5); + + let mut task = Task::new(async {}); + let reply_to = MessageId::from(4); + let lock = locks::Lock::up_to(1); + + task.insert_lock(reply_to, lock); + task.remove_lock(&reply_to); + + assert!(task.reply_to_locks.is_empty()); + } + + #[test] + fn signal_reply_timeout_promotes_expired_locks() { + // arrange + let message_id = MessageId::from(5); + set_context(message_id, 20); + let reply_to = MessageId::from(6); + let lock = locks::Lock::up_to(5); + let signals_map = signals(); + let task = Task::new(async {}); + + tasks().insert(message_id, task); + let task = tasks().get_mut(&message_id).unwrap(); + signals_map.register_signal(reply_to, lock, None); + + // act + task.signal_reply_timeout(30); + + // assert + match signals_map.signals.get(&reply_to) { + Some(WakeSignal::Timeout { expected, now, .. }) => { + assert_eq!(*expected, lock.deadline()); + assert_eq!(*now, 30); + } + _ => unreachable!(), + } + + assert!(task.reply_to_locks.is_empty()); + } + + #[test] + #[should_panic(expected = "Cannot find lock to be waited")] + fn wait_without_locks_panics() { + let task = Task::new(async {}); + task.wait(0); + } +} diff --git a/rs/src/gstd/mod.rs b/rs/src/gstd/mod.rs index 54fe10199..2f8b3d064 100644 --- a/rs/src/gstd/mod.rs +++ b/rs/src/gstd/mod.rs @@ -5,7 +5,7 @@ pub use async_runtime::handle_signal; // pub use gstd::{handle_reply_with_hook, message_loop}; pub use async_runtime::{ MessageFuture, create_program_for_reply, handle_reply_with_hook, message_loop, - send_bytes_for_reply, + send_bytes_for_reply, send_for_reply, }; #[doc(hidden)] #[cfg(feature = "ethexe")] From 7bd9c1b658b334a17b49392ea82bde83554f358a Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 8 Oct 2025 13:33:30 +0200 Subject: [PATCH 53/66] wip: add critical_hook --- benchmarks/bench_data.json | 18 ++++++------- rs/src/gstd/async_runtime.rs | 51 +++++++++++++++++++++++++++++------- rs/src/gstd/mod.rs | 6 ++--- 3 files changed, 53 insertions(+), 22 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index c55e29b3a..d53d88ffd 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -13,20 +13,20 @@ "317810": 43147409792 }, "counter": { - "async_call": 803980558, - "sync_call": 636091307 + "async_call": 806218275, + "sync_call": 638208719 }, "cross_program": { - "median": 2231370119 + "median": 2235789108 }, "redirect": { - "median": 3168973023 + "median": 3181954800 }, "message_stack": { - "0": 69720235700, - "1": 335178342000, - "5": 1400121828800, - "10": 2615079325900, - "20": 5117642853700 + "0": 69937198800, + "1": 336008525800, + "5": 1403420919400, + "10": 2621615755100, + "20": 5130733943600 } } \ No newline at end of file diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index 4f00c5a3d..f99ee1890 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -23,6 +23,8 @@ fn signals() -> &'static mut WakeSignals { pub struct Task { future: LocalBoxFuture<'static, ()>, reply_to_locks: Vec<(MessageId, locks::Lock)>, + #[cfg(not(feature = "ethexe"))] + critical_hook: Option>, } impl Task { @@ -33,6 +35,8 @@ impl Task { Self { future: future.boxed_local(), reply_to_locks: Vec::new(), + #[cfg(not(feature = "ethexe"))] + critical_hook: None, } } @@ -41,7 +45,7 @@ impl Task { /// - stores the `(reply_to, lock)` pair so the task can later detect timeouts; /// /// # Context - /// Called exclusively from [`WakeSignals::register_signal`] while the outer `message_loop` + /// Called exclusively from [WakeSignals::register_signal] while the outer `message_loop` /// prepares to await a reply for the current inbound message. #[inline] fn insert_lock(&mut self, reply_to: MessageId, lock: locks::Lock) { @@ -53,7 +57,7 @@ impl Task { /// - searches for the `(reply_to, lock)` pair and removes it. /// /// # Context - /// Called from [`WakeSignals::record_reply`] once a response is received, as well + /// Called from [WakeSignals::record_reply] once a response is received, as well /// as during cleanup when a task finishes. #[inline] fn remove_lock(&mut self, reply_to: &MessageId) { @@ -66,11 +70,11 @@ impl Task { /// Notifies the signal registry about replies that have exceeded their deadlines. /// /// - scans all tracked locks, extracting those whose deadlines are at or before `now`; - /// - informs [`WakeSignals`] about the timeout so it can update the wake state and + /// - informs [WakeSignals] about the timeout so it can update the wake state and /// potentially execute a deferred reply hook. /// /// # Context - /// Invoked from [`message_loop`] before polling the user future to ensure that timeouts + /// Invoked from [message_loop] before polling the user future to ensure that timeouts /// are processed promptly for the current message. #[inline] fn signal_reply_timeout(&mut self, now: BlockNumber) { @@ -92,7 +96,7 @@ impl Task { /// the runtime suspension point. /// /// # Context - /// Called from [`message_loop`] whenever the user future remains pending after polling. + /// Called from [message_loop] whenever the user future remains pending after polling. /// /// # Panics /// Panics if no locks are registered for the current task, which indicates a logic error @@ -107,7 +111,6 @@ impl Task { .wait(now); } - #[cfg(not(feature = "ethexe"))] /// Removes all outstanding reply locks from the signal registry without waiting on them. /// /// # What it does @@ -116,6 +119,7 @@ impl Task { /// /// # Context /// Called from [`handle_signal`]. + #[cfg(not(feature = "ethexe"))] #[inline] fn clear(&self) { let signals_map = signals(); @@ -125,6 +129,32 @@ impl Task { } } +/// Sets a critical hook. +/// +/// # Panics +/// If called in the `handle_reply` or `handle_signal` entrypoints. +/// +/// # SAFETY +/// Ensure that sufficient `gstd::Config::SYSTEM_RESERVE` is set in your +/// program, as this gas is locked during each async call to provide resources +/// for hook execution in case it is triggered. +#[cfg(not(feature = "ethexe"))] +pub fn set_critical_hook(f: F) { + if msg::reply_code().is_ok() { + panic!("`gstd::critical::set_hook()` must not be called in `handle_reply` entrypoint") + } + + if msg::signal_code().is_ok() { + panic!("`gstd::critical::set_hook()` must not be called in `handle_signal` entrypoint") + } + let message_id = Syscall::message_id(); + + tasks() + .get_mut(&message_id) + .expect("A message task must exist") + .critical_hook = Some(Box::new(f)); +} + /// Drives asynchronous handling for the currently executing inbound message. /// /// - locates or creates the `Task` holding the user future for the current message id; @@ -545,10 +575,11 @@ pub fn handle_signal() { let msg_id = Syscall::signal_from().expect( "`gstd::async_runtime::handle_signal()` must be called only in `handle_signal` entrypoint", ); - // critical::take_and_execute(); - - // Remove Task and all associated signals - if let Some(task) = tasks().remove(&msg_id) { + // Remove Task and all associated signals, execute critical hook + if let Some(mut task) = tasks().remove(&msg_id) { + if let Some(critical_hook) = task.critical_hook.take() { + critical_hook(msg_id); + } task.clear() } } diff --git a/rs/src/gstd/mod.rs b/rs/src/gstd/mod.rs index 2f8b3d064..d6feab9ab 100644 --- a/rs/src/gstd/mod.rs +++ b/rs/src/gstd/mod.rs @@ -1,12 +1,12 @@ -#[cfg(not(feature = "ethexe"))] -#[doc(hidden)] -pub use async_runtime::handle_signal; #[doc(hidden)] // pub use gstd::{handle_reply_with_hook, message_loop}; pub use async_runtime::{ MessageFuture, create_program_for_reply, handle_reply_with_hook, message_loop, send_bytes_for_reply, send_for_reply, }; +#[cfg(not(feature = "ethexe"))] +#[doc(hidden)] +pub use async_runtime::{handle_signal, set_critical_hook}; #[doc(hidden)] #[cfg(feature = "ethexe")] pub use ethexe::{EthEvent, EthEventExpo}; From 863024974ca2efd4365a0c6f42076d02a7d36e1c Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 8 Oct 2025 17:11:16 +0200 Subject: [PATCH 54/66] wip: refactor --- benchmarks/bench_data.json | 10 +++++----- rs/src/gstd/async_runtime.rs | 18 +++++------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index d53d88ffd..fce233808 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -23,10 +23,10 @@ "median": 3181954800 }, "message_stack": { - "0": 69937198800, - "1": 336008525800, - "5": 1403420919400, - "10": 2621615755100, - "20": 5130733943600 + "0": 70010817400, + "1": 336314680800, + "5": 1404553499500, + "10": 2623979976800, + "20": 5135356914100 } } \ No newline at end of file diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index f99ee1890..2d8d3111a 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -102,13 +102,11 @@ impl Task { /// Panics if no locks are registered for the current task, which indicates a logic error /// (e.g. awaiting a reply without having registered one). #[inline] - fn wait(&self, now: BlockNumber) { + fn next_lock(&self) -> Option<&Lock> { self.reply_to_locks .iter() .map(|(_, lock)| lock) .min_by(|lock1, lock2| lock1.cmp(lock2)) - .expect("Cannot find lock to be waited") - .wait(now); } /// Removes all outstanding reply locks from the signal registry without waiting on them. @@ -196,10 +194,10 @@ where if completed { tasks_map.remove(&msg_id); - // #[cfg(not(feature = "ethexe"))] - // let _ = critical::take_hook(); } else { - task.wait(current_block); + task.next_lock() + .expect("Cannot find lock to be waited") + .wait(current_block); } } @@ -592,6 +590,7 @@ pub fn is_terminated(message_id: &MessageId) -> bool { !signals().waits_for(message_id) } +#[cfg(feature = "std")] #[cfg(test)] mod tests { use super::*; @@ -660,11 +659,4 @@ mod tests { assert!(task.reply_to_locks.is_empty()); } - - #[test] - #[should_panic(expected = "Cannot find lock to be waited")] - fn wait_without_locks_panics() { - let task = Task::new(async {}); - task.wait(0); - } } From fdc4a5e5c6019de0013a15883abdac5f8714f1f4 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 8 Oct 2025 18:27:50 +0200 Subject: [PATCH 55/66] wip: use BinaryHeap for locks, lazy remove locks --- benchmarks/bench_data.json | 18 +++---- rs/src/gstd/async_runtime.rs | 100 +++++++++++------------------------ 2 files changed, 40 insertions(+), 78 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index fce233808..ed8cdb857 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -13,20 +13,20 @@ "317810": 43147409792 }, "counter": { - "async_call": 806218275, - "sync_call": 638208719 + "async_call": 804712020, + "sync_call": 636739663 }, "cross_program": { - "median": 2235789108 + "median": 2239903283 }, "redirect": { - "median": 3181954800 + "median": 3189446474 }, "message_stack": { - "0": 70010817400, - "1": 336314680800, - "5": 1404553499500, - "10": 2623979976800, - "20": 5135356914100 + "0": 70068695000, + "1": 336070883800, + "5": 1403103004100, + "10": 2620987152900, + "20": 5129383020200 } } \ No newline at end of file diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index 2d8d3111a..1903cbb9e 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -1,6 +1,7 @@ use super::*; -use crate::collections::HashMap; +use crate::collections::{BinaryHeap, HashMap}; use core::{ + cmp::Reverse, pin::Pin, task::{Context, Poll}, }; @@ -22,7 +23,7 @@ fn signals() -> &'static mut WakeSignals { /// of code that was running before the program was interrupted by `wait`. pub struct Task { future: LocalBoxFuture<'static, ()>, - reply_to_locks: Vec<(MessageId, locks::Lock)>, + reply_to_locks: BinaryHeap<(Reverse, MessageId)>, #[cfg(not(feature = "ethexe"))] critical_hook: Option>, } @@ -34,7 +35,7 @@ impl Task { { Self { future: future.boxed_local(), - reply_to_locks: Vec::new(), + reply_to_locks: Default::default(), #[cfg(not(feature = "ethexe"))] critical_hook: None, } @@ -42,29 +43,16 @@ impl Task { /// Registers the wait/timeout lock associated with an outgoing message reply. /// - /// - stores the `(reply_to, lock)` pair so the task can later detect timeouts; + /// - stores the `(lock, reply_to)` pair so the task can later detect timeouts; /// /// # Context - /// Called exclusively from [WakeSignals::register_signal] while the outer `message_loop` + /// Called exclusively from [WakeSignals::register_signal] while the outer [message_loop] /// prepares to await a reply for the current inbound message. - #[inline] - fn insert_lock(&mut self, reply_to: MessageId, lock: locks::Lock) { - self.reply_to_locks.push((reply_to, lock)); - } - - /// Removes the stored lock for the given reply identifier, if present. - /// - /// - searches for the `(reply_to, lock)` pair and removes it. /// - /// # Context - /// Called from [WakeSignals::record_reply] once a response is received, as well - /// as during cleanup when a task finishes. + /// Lock removed exclusively from [Task::signal_reply_timeout]. #[inline] - fn remove_lock(&mut self, reply_to: &MessageId) { - self.reply_to_locks - .iter() - .position(|(mid, _)| mid == reply_to) - .map(|index| self.reply_to_locks.swap_remove(index)); + fn insert_lock(&mut self, reply_to: MessageId, lock: locks::Lock) { + self.reply_to_locks.push((Reverse(lock), reply_to)); } /// Notifies the signal registry about replies that have exceeded their deadlines. @@ -80,48 +68,36 @@ impl Task { fn signal_reply_timeout(&mut self, now: BlockNumber) { let signals_map = signals(); - self.reply_to_locks - .extract_if(.., |(_, lock)| now >= lock.deadline()) - .for_each(|(reply_to, lock)| { - signals_map.record_timeout(reply_to, lock.deadline(), now); - // ::gstd::debug!( - // "signal_reply_timeout: remove lock for reply_to {reply_to} in message due to timeout" - // ); - }); + while let Some((Reverse(lock), reply_to)) = self.reply_to_locks.peek() + && now >= lock.deadline() + { + signals_map.record_timeout(*reply_to, lock.deadline(), now); + self.reply_to_locks.pop(); + } } - /// Arms the most urgent wait lock so the executor suspends until a wake signal. - /// - /// - finds the lock with the smallest deadline and delegates to `Lock::wait` to set - /// the runtime suspension point. + /// Finds the lock with the smallest deadline /// /// # Context - /// Called from [message_loop] whenever the user future remains pending after polling. - /// - /// # Panics - /// Panics if no locks are registered for the current task, which indicates a logic error - /// (e.g. awaiting a reply without having registered one). + /// Called from [message_loop] whenever the user future remains pending after [Task::signal_reply_timeout] and polling future. #[inline] fn next_lock(&self) -> Option<&Lock> { - self.reply_to_locks - .iter() - .map(|(_, lock)| lock) - .min_by(|lock1, lock2| lock1.cmp(lock2)) + self.reply_to_locks.peek().map(|(lock, _)| &lock.0) } /// Removes all outstanding reply locks from the signal registry without waiting on them. /// /// # What it does - /// - iterates every stored `(reply_to, _)` pair and asks [`WakeSignals`] to drop the wake entry; + /// - iterates every stored `(_, reply_to)` pair and asks [`WakeSignals`] to drop the wake entry; /// - used as part of task teardown to avoid keeping stale replies alive. /// /// # Context /// Called from [`handle_signal`]. #[cfg(not(feature = "ethexe"))] #[inline] - fn clear(&self) { + fn clear_signals(&self) { let signals_map = signals(); - self.reply_to_locks.iter().for_each(|(reply_to, _)| { + self.reply_to_locks.iter().for_each(|(_, reply_to)| { signals_map.remove(reply_to); }); } @@ -300,10 +276,6 @@ impl WakeSignals { ::gstd::debug!( "record_reply: remove lock for reply_to {reply_to} in message {message_id}" ); - tasks() - .get_mut(&message_id) - .expect("A message task must exist") - .remove_lock(reply_to); // wake message loop after receiving reply ::gcore::exec::wake(message_id).expect("Failed to wake the message"); @@ -578,7 +550,7 @@ pub fn handle_signal() { if let Some(critical_hook) = task.critical_hook.take() { critical_hook(msg_id); } - task.clear() + task.clear_signals() } } @@ -594,7 +566,6 @@ pub fn is_terminated(message_id: &MessageId) -> bool { #[cfg(test)] mod tests { use super::*; - use crate::gstd::locks; use crate::gstd::syscalls::Syscall; fn set_context(message_id: MessageId, block_height: u32) { @@ -608,27 +579,18 @@ mod tests { let mut task = Task::new(async {}); let reply_to = MessageId::from(2); - let lock = locks::Lock::up_to(3); + let lock = Lock::up_to(3); task.insert_lock(reply_to, lock); + task.insert_lock(MessageId::from(3), Lock::exactly(5)); - assert_eq!(task.reply_to_locks.len(), 1); - assert_eq!(task.reply_to_locks[0].0, reply_to); - assert_eq!(task.reply_to_locks[0].1.deadline(), lock.deadline()); - } - - #[test] - fn remove_lock_drops_matching_entry() { - set_context(MessageId::from(3), 5); - - let mut task = Task::new(async {}); - let reply_to = MessageId::from(4); - let lock = locks::Lock::up_to(1); - - task.insert_lock(reply_to, lock); - task.remove_lock(&reply_to); + let Some((Reverse(next_lock), next_reply_to)) = task.reply_to_locks.peek() else { + unreachable!() + }; - assert!(task.reply_to_locks.is_empty()); + assert_eq!(task.reply_to_locks.len(), 2); + assert_eq!(next_reply_to, &reply_to); + assert_eq!(next_lock, &lock); } #[test] @@ -637,7 +599,7 @@ mod tests { let message_id = MessageId::from(5); set_context(message_id, 20); let reply_to = MessageId::from(6); - let lock = locks::Lock::up_to(5); + let lock = Lock::up_to(5); let signals_map = signals(); let task = Task::new(async {}); From 673daae1073d86fce8ee7c2f658ec505fcfb20c4 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Thu, 9 Oct 2025 13:53:59 +0200 Subject: [PATCH 56/66] wip: lazy removal lock in `next_lock` call --- benchmarks/bench_data.json | 18 +++++++++--------- rs/src/gstd/async_runtime.rs | 15 ++++++++++++--- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index ed8cdb857..97fade3b5 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -13,20 +13,20 @@ "317810": 43147409792 }, "counter": { - "async_call": 804712020, - "sync_call": 636739663 + "async_call": 805883547, + "sync_call": 637959757 }, "cross_program": { - "median": 2239903283 + "median": 2245987051 }, "redirect": { - "median": 3189446474 + "median": 3207309273 }, "message_stack": { - "0": 70068695000, - "1": 336070883800, - "5": 1403103004100, - "10": 2620987152900, - "20": 5129383020200 + "0": 70267597200, + "1": 337257355000, + "5": 1408240556700, + "10": 2631061745100, + "20": 5149438642300 } } \ No newline at end of file diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index 1903cbb9e..9049b8f46 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -49,7 +49,7 @@ impl Task { /// Called exclusively from [WakeSignals::register_signal] while the outer [message_loop] /// prepares to await a reply for the current inbound message. /// - /// Lock removed exclusively from [Task::signal_reply_timeout]. + /// Lock removed from [Task::signal_reply_timeout] or [Task::next_lock]. #[inline] fn insert_lock(&mut self, reply_to: MessageId, lock: locks::Lock) { self.reply_to_locks.push((Reverse(lock), reply_to)); @@ -81,8 +81,17 @@ impl Task { /// # Context /// Called from [message_loop] whenever the user future remains pending after [Task::signal_reply_timeout] and polling future. #[inline] - fn next_lock(&self) -> Option<&Lock> { - self.reply_to_locks.peek().map(|(lock, _)| &lock.0) + fn next_lock(&mut self) -> Option { + let signals_map = signals(); + while let Some((Reverse(lock), reply_to)) = self.reply_to_locks.peek() { + // skip if not waits for reply_to + if !signals_map.waits_for(reply_to) { + self.reply_to_locks.pop(); + continue; + } + return Some(*lock); + } + None } /// Removes all outstanding reply locks from the signal registry without waiting on them. From b806243fca7de5cfc6d821125756b64cba8280f3 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Thu, 9 Oct 2025 16:42:54 +0200 Subject: [PATCH 57/66] wip: add `sleep_for` --- benchmarks/bench_data.json | 18 ++-- rs/src/gstd/async_runtime.rs | 184 +++++++++++++++++++++++++++-------- rs/src/gstd/mod.rs | 2 +- 3 files changed, 151 insertions(+), 53 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 97fade3b5..d203ed1ce 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -13,20 +13,20 @@ "317810": 43147409792 }, "counter": { - "async_call": 805883547, - "sync_call": 637959757 + "async_call": 806601610, + "sync_call": 638669351 }, "cross_program": { - "median": 2245987051 + "median": 2246345652 }, "redirect": { - "median": 3207309273 + "median": 3207871648 }, "message_stack": { - "0": 70267597200, - "1": 337257355000, - "5": 1408240556700, - "10": 2631061745100, - "20": 5149438642300 + "0": 70340123900, + "1": 337456091700, + "5": 1408944133400, + "10": 2632344758800, + "20": 5151756313700 } } \ No newline at end of file diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index 9049b8f46..852f4e491 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -23,7 +23,7 @@ fn signals() -> &'static mut WakeSignals { /// of code that was running before the program was interrupted by `wait`. pub struct Task { future: LocalBoxFuture<'static, ()>, - reply_to_locks: BinaryHeap<(Reverse, MessageId)>, + locks: BinaryHeap<(Reverse, Option)>, #[cfg(not(feature = "ethexe"))] critical_hook: Option>, } @@ -35,29 +35,36 @@ impl Task { { Self { future: future.boxed_local(), - reply_to_locks: Default::default(), + locks: Default::default(), #[cfg(not(feature = "ethexe"))] critical_hook: None, } } - /// Registers the wait/timeout lock associated with an outgoing message reply. + /// Stores the lock associated with an outbound reply, keeping it ordered by deadline. /// - /// - stores the `(lock, reply_to)` pair so the task can later detect timeouts; + /// - pushes `(lock, Some(reply_to))` into the binary heap so that timeouts and wake-ups can be + /// handled efficiently. /// /// # Context - /// Called exclusively from [WakeSignals::register_signal] while the outer [message_loop] - /// prepares to await a reply for the current inbound message. - /// - /// Lock removed from [Task::signal_reply_timeout] or [Task::next_lock]. + /// Called from [WakeSignals::register_signal] when the [message_loop] schedules a reply wait. #[inline] - fn insert_lock(&mut self, reply_to: MessageId, lock: locks::Lock) { - self.reply_to_locks.push((Reverse(lock), reply_to)); + fn insert_lock(&mut self, reply_to: MessageId, lock: Lock) { + self.locks.push((Reverse(lock), Some(reply_to))); + } + + /// Tracks a sleep-specific lock (without a reply identifier) for the inbound message. + /// + /// # Context + /// Used when the task needs to suspend itself via `exec::wait_*` without tying the wait to a + /// particular reply id. + fn insert_sleep(&mut self, lock: Lock) { + self.locks.push((Reverse(lock), None)); } /// Notifies the signal registry about replies that have exceeded their deadlines. /// - /// - scans all tracked locks, extracting those whose deadlines are at or before `now`; + /// - peeks locks, extracting those whose deadlines are at or before `now`; /// - informs [WakeSignals] about the timeout so it can update the wake state and /// potentially execute a deferred reply hook. /// @@ -68,25 +75,29 @@ impl Task { fn signal_reply_timeout(&mut self, now: BlockNumber) { let signals_map = signals(); - while let Some((Reverse(lock), reply_to)) = self.reply_to_locks.peek() + while let Some((Reverse(lock), reply_to)) = self.locks.peek() && now >= lock.deadline() { - signals_map.record_timeout(*reply_to, lock.deadline(), now); - self.reply_to_locks.pop(); + if let Some(reply_to) = reply_to { + signals_map.record_timeout(*reply_to, lock.deadline(), now); + } + self.locks.pop(); } } - /// Finds the lock with the smallest deadline + /// Returns the earliest lock still awaiting completion, skipping stale entries. /// /// # Context /// Called from [message_loop] whenever the user future remains pending after [Task::signal_reply_timeout] and polling future. #[inline] fn next_lock(&mut self) -> Option { let signals_map = signals(); - while let Some((Reverse(lock), reply_to)) = self.reply_to_locks.peek() { + while let Some((Reverse(lock), reply_to)) = self.locks.peek() { // skip if not waits for reply_to - if !signals_map.waits_for(reply_to) { - self.reply_to_locks.pop(); + if let Some(reply_to) = reply_to + && !signals_map.waits_for(reply_to) + { + self.locks.pop(); continue; } return Some(*lock); @@ -96,7 +107,6 @@ impl Task { /// Removes all outstanding reply locks from the signal registry without waiting on them. /// - /// # What it does /// - iterates every stored `(_, reply_to)` pair and asks [`WakeSignals`] to drop the wake entry; /// - used as part of task teardown to avoid keeping stale replies alive. /// @@ -106,15 +116,17 @@ impl Task { #[inline] fn clear_signals(&self) { let signals_map = signals(); - self.reply_to_locks.iter().for_each(|(_, reply_to)| { - signals_map.remove(reply_to); + self.locks.iter().for_each(|(_, reply_to)| { + if let Some(reply_to) = reply_to { + signals_map.remove(reply_to); + } }); } } /// Sets a critical hook. /// -/// # Panics +/// # Context /// If called in the `handle_reply` or `handle_signal` entrypoints. /// /// # SAFETY @@ -150,7 +162,7 @@ pub fn set_critical_hook(f: F) { /// /// # Panics /// Panics propagated from the user future bubble up, and the function will panic if no wait lock is -/// registered when a pending future requests suspension (see `Task::wait`), signalling a contract logic bug. +/// registered when a pending future requests suspension, signalling a contract logic bug. #[inline] pub fn message_loop(future: F) where @@ -255,16 +267,15 @@ impl WakeSignals { /// Processes an incoming reply for `reply_to` and transitions the stored wake state. /// - /// - upgrades the pending entry to `Ready`, capturing payload and reply code; - /// - detaches the wait lock from the owning `Task`, then wakes the suspended message loop; + /// - upgrades the pending entry to [WakeSignal::Ready], capturing payload and reply code; /// - executes the optional reply hook once the reply becomes available. /// /// # Context - /// Invoked by `handle_reply_with_hook` when a reply arrives during `handle_reply()` execution. The - /// runtime supplies `reply_to`, and this method synchronises bookkeeping before waking the task. + /// Invoked by [handle_reply_with_hook] when a reply arrives during `handle_reply()` execution. The + /// runtime supplies `reply_to`. /// /// # Panics - /// Panics if it encounters an already finalised entry (`WakeSignal::Ready`) or the associated task is + /// Panics if it encounters an already finalised entry [WakeSignal::Ready] or the associated task is /// missing. Both scenarios indicate logic bugs or duplicate delivery. pub fn record_reply(&mut self, reply_to: &MessageId) { if let hashbrown::hash_map::EntryRef::Occupied(mut entry) = self.signals.entry_ref(reply_to) @@ -311,13 +322,13 @@ impl WakeSignals { /// Marks a pending reply as timed out and preserves context for later handling. /// - /// - upgrades a `WakeSignal::Pending` entry to `WakeSignal::Timeout`, capturing when the reply was expected + /// - upgrades a [WakeSignal::Pending] entry to [WakeSignal::Timeout], capturing when the reply was expected /// and when the timeout was detected; /// - retains the optional reply hook so it can still be executed if a late reply arrives and reuses the /// stored state when `record_reply` is called afterwards. /// /// # Context - /// Triggered from `Task::signal_reply_timeout` whenever the runtime observes that a waiting reply exceeded + /// Triggered from [Task::signal_reply_timeout] whenever the runtime observes that a waiting reply exceeded /// its deadline while executing inside [message_loop]. pub fn record_timeout(&mut self, reply_to: MessageId, expected: BlockNumber, now: BlockNumber) { if let hashbrown::hash_map::Entry::Occupied(mut entry) = self.signals.entry(reply_to) @@ -571,11 +582,60 @@ pub fn is_terminated(message_id: &MessageId) -> bool { !signals().waits_for(message_id) } +struct MessageSleepFuture { + deadline: BlockNumber, +} + +impl MessageSleepFuture { + fn new(deadline: BlockNumber) -> Self { + Self { deadline } + } +} + +impl Unpin for MessageSleepFuture {} + +impl Future for MessageSleepFuture { + type Output = (); + + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + let now = Syscall::block_height(); + + if now >= self.deadline { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} + +/// Delays message execution in asynchronous way for the specified number of blocks. +/// +/// It works pretty much like the [exec::wait_for] function, but +/// allows to continue execution after the delay in the same handler. It is +/// worth mentioning that the program state gets persisted inside the call, and +/// the execution resumes with potentially different state. +pub fn sleep_for(block_count: BlockCount) -> impl Future { + let message_id = Syscall::message_id(); + let lock = Lock::exactly(block_count); + tasks() + .get_mut(&message_id) + .expect("A message task must exist") + .insert_sleep(lock); + MessageSleepFuture::new(lock.deadline()) +} + #[cfg(feature = "std")] #[cfg(test)] mod tests { use super::*; use crate::gstd::syscalls::Syscall; + use core::sync::atomic::AtomicU64; + + static MSG_ID: AtomicU64 = AtomicU64::new(1); + + fn msg_id() -> MessageId { + MessageId::from(MSG_ID.fetch_add(1, core::sync::atomic::Ordering::SeqCst)) + } fn set_context(message_id: MessageId, block_height: u32) { Syscall::with_message_id(message_id); @@ -583,31 +643,31 @@ mod tests { } #[test] - fn insert_lock_adds_entry() { - set_context(MessageId::from(1), 10); + fn task_insert_lock_adds_entry() { + set_context(msg_id(), 10); let mut task = Task::new(async {}); - let reply_to = MessageId::from(2); + let reply_to = msg_id(); let lock = Lock::up_to(3); task.insert_lock(reply_to, lock); - task.insert_lock(MessageId::from(3), Lock::exactly(5)); + task.insert_lock(msg_id(), Lock::exactly(5)); - let Some((Reverse(next_lock), next_reply_to)) = task.reply_to_locks.peek() else { + let Some((Reverse(next_lock), next_reply_to)) = task.locks.peek() else { unreachable!() }; - assert_eq!(task.reply_to_locks.len(), 2); - assert_eq!(next_reply_to, &reply_to); + assert_eq!(task.locks.len(), 2); + assert_eq!(Some(&reply_to), next_reply_to.as_ref()); assert_eq!(next_lock, &lock); } #[test] - fn signal_reply_timeout_promotes_expired_locks() { + fn task_signal_reply_timeout_promotes_expired_locks() { // arrange - let message_id = MessageId::from(5); + let message_id = msg_id(); set_context(message_id, 20); - let reply_to = MessageId::from(6); + let reply_to = msg_id(); let lock = Lock::up_to(5); let signals_map = signals(); let task = Task::new(async {}); @@ -617,17 +677,55 @@ mod tests { signals_map.register_signal(reply_to, lock, None); // act - task.signal_reply_timeout(30); + task.signal_reply_timeout(25); // assert match signals_map.signals.get(&reply_to) { Some(WakeSignal::Timeout { expected, now, .. }) => { assert_eq!(*expected, lock.deadline()); - assert_eq!(*now, 30); + assert_eq!(*now, 25); } _ => unreachable!(), } - assert!(task.reply_to_locks.is_empty()); + assert!(task.locks.is_empty()); + } + + #[test] + fn task_remove_signal_skip_not_waited_lock() { + // arrange + let message_id = msg_id(); + set_context(message_id, 30); + let reply_to = msg_id(); + let lock = Lock::up_to(5); + let signals_map = signals(); + let task = Task::new(async {}); + + tasks().insert(message_id, task); + let task = tasks().get_mut(&message_id).unwrap(); + signals_map.register_signal(reply_to, lock, None); + + assert_eq!(1, task.locks.len()); + + task.signal_reply_timeout(31); + // TODO: record reply + poll + signals_map.remove(&reply_to); + + assert_eq!(1, task.locks.len()); + assert_eq!(None, task.next_lock()); + } + + #[test] + fn task_insert_sleep_adds_entry_without_reply() { + set_context(msg_id(), 40); + + let mut task = Task::new(async {}); + let lock = Lock::exactly(4); + + task.insert_sleep(lock); + assert_eq!(Some(lock), task.next_lock()); + + task.signal_reply_timeout(44); + assert_eq!(None, task.next_lock()); } } diff --git a/rs/src/gstd/mod.rs b/rs/src/gstd/mod.rs index d6feab9ab..4d16baa53 100644 --- a/rs/src/gstd/mod.rs +++ b/rs/src/gstd/mod.rs @@ -2,7 +2,7 @@ // pub use gstd::{handle_reply_with_hook, message_loop}; pub use async_runtime::{ MessageFuture, create_program_for_reply, handle_reply_with_hook, message_loop, - send_bytes_for_reply, send_for_reply, + send_bytes_for_reply, send_for_reply, sleep_for }; #[cfg(not(feature = "ethexe"))] #[doc(hidden)] From 69dbe7851028d99d645912b4d4372c40d827923f Mon Sep 17 00:00:00 2001 From: vobradovich Date: Fri, 10 Oct 2025 14:45:45 +0200 Subject: [PATCH 58/66] wip: move Timeout processing to `poll` fn, store deadline in WakeSignal::Pending --- benchmarks/bench_data.json | 20 ++-- rs/src/gstd/async_runtime.rs | 207 +++++++++++++++++++---------------- rs/src/gstd/mod.rs | 2 +- 3 files changed, 121 insertions(+), 108 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index d203ed1ce..c881ff518 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -1,6 +1,6 @@ { "compute": { - "median": 450514036959 + "median": 450514040279 }, "alloc": { "0": 564376739, @@ -13,20 +13,20 @@ "317810": 43147409792 }, "counter": { - "async_call": 806601610, - "sync_call": 638669351 + "async_call": 803535700, + "sync_call": 636925721 }, "cross_program": { - "median": 2246345652 + "median": 2241047058 }, "redirect": { - "median": 3207871648 + "median": 3198301159 }, "message_stack": { - "0": 70340123900, - "1": 337456091700, - "5": 1408944133400, - "10": 2632344758800, - "20": 5151756313700 + "0": 70031559900, + "1": 336252014700, + "5": 1404246926900, + "10": 2623289459400, + "20": 5153430863700 } } \ No newline at end of file diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index 852f4e491..1edbe35a6 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -21,6 +21,9 @@ fn signals() -> &'static mut WakeSignals { /// Matches a task to a some message in order to avoid duplicate execution /// of code that was running before the program was interrupted by `wait`. +/// +/// The [`Task`] lifecycle matches to the single message processing in the `handle()` entry-point +/// and ends when all internal futures are resolved or `handle_signal()` received for this `message_id`. pub struct Task { future: LocalBoxFuture<'static, ()>, locks: BinaryHeap<(Reverse, Option)>, @@ -43,11 +46,11 @@ impl Task { /// Stores the lock associated with an outbound reply, keeping it ordered by deadline. /// - /// - pushes `(lock, Some(reply_to))` into the binary heap so that timeouts and wake-ups can be - /// handled efficiently. + /// - pushes `(lock, Some(reply_to))` into the binary heap so the task can efficiently retrieve the + /// earliest lock when deciding how long to sleep. /// /// # Context - /// Called from [WakeSignals::register_signal] when the [message_loop] schedules a reply wait. + /// Called from [`WakeSignals::register_signal`] when the [`message_loop`] schedules a reply wait. #[inline] fn insert_lock(&mut self, reply_to: MessageId, lock: Lock) { self.locks.push((Reverse(lock), Some(reply_to))); @@ -62,44 +65,27 @@ impl Task { self.locks.push((Reverse(lock), None)); } - /// Notifies the signal registry about replies that have exceeded their deadlines. - /// - /// - peeks locks, extracting those whose deadlines are at or before `now`; - /// - informs [WakeSignals] about the timeout so it can update the wake state and - /// potentially execute a deferred reply hook. - /// - /// # Context - /// Invoked from [message_loop] before polling the user future to ensure that timeouts - /// are processed promptly for the current message. - #[inline] - fn signal_reply_timeout(&mut self, now: BlockNumber) { - let signals_map = signals(); - - while let Some((Reverse(lock), reply_to)) = self.locks.peek() - && now >= lock.deadline() - { - if let Some(reply_to) = reply_to { - signals_map.record_timeout(*reply_to, lock.deadline(), now); - } - self.locks.pop(); - } - } - - /// Returns the earliest lock still awaiting completion, skipping stale entries. + /// Returns the earliest lock still awaiting completion, removing stale or cleared entries. /// /// # Context - /// Called from [message_loop] whenever the user future remains pending after [Task::signal_reply_timeout] and polling future. + /// Called from [`message_loop`] whenever the user future remains pending after polling. #[inline] - fn next_lock(&mut self) -> Option { + fn next_lock(&mut self, now: BlockNumber) -> Option { let signals_map = signals(); while let Some((Reverse(lock), reply_to)) = self.locks.peek() { - // skip if not waits for reply_to + // 1. skip and remove expired + if now >= lock.deadline() { + self.locks.pop(); + continue; + } + // 2. skip and remove if not waits for reply_to if let Some(reply_to) = reply_to && !signals_map.waits_for(reply_to) { self.locks.pop(); continue; } + // 3. keep lock in `self.locks` for `WakeSignal::Pending` in case of `clear_signals` return Some(*lock); } None @@ -115,10 +101,12 @@ impl Task { #[cfg(not(feature = "ethexe"))] #[inline] fn clear_signals(&self) { + let now = Syscall::block_height(); let signals_map = signals(); self.locks.iter().for_each(|(_, reply_to)| { if let Some(reply_to) = reply_to { - signals_map.remove(reply_to); + // set the `WakeSignal::Timeout` for further processing in `handle_reply` + signals_map.record_timeout(*reply_to, now); } }); } @@ -157,7 +145,7 @@ pub fn set_critical_hook(f: F) { /// - when the future stays pending, arms the shortest wait lock so the runtime suspends until a wake. /// /// # Context -/// Called from the contract's `handle` entry point while [message_loop] runs single-threaded inside the +/// Called from the contract's `handle` entry point while [`message_loop`] runs single-threaded inside the /// actor. It must be invoked exactly once per incoming message to advance the async state machine. /// /// # Panics @@ -179,10 +167,6 @@ where Task::new(future) }); - // Check if any reply has timed out before polling them. - let current_block = Syscall::block_height(); - task.signal_reply_timeout(current_block); - let completed = { let mut cx = Context::from_waker(task::Waker::noop()); ::gstd::debug!("message_loop: polling future for {msg_id}"); @@ -192,17 +176,23 @@ where if completed { tasks_map.remove(&msg_id); } else { - task.next_lock() + let now = Syscall::block_height(); + task.next_lock(now) .expect("Cannot find lock to be waited") - .wait(current_block); + .wait(now); } } pub type Payload = Vec; +/// The [`WakeSignal`] lifecycle corresponds to waiting for a reply to a sent message +/// and ends when `handle_reply()` is received. +/// +/// May outlive parent [`Task`] in state [`WakeSignal::Timeout`]. enum WakeSignal { Pending { message_id: MessageId, + deadline: BlockNumber, reply_hook: Option>, }, Ready { @@ -230,16 +220,17 @@ impl WakeSignals { /// Registers a pending reply for `waiting_reply_to` while the current message is being processed. /// /// - stores pending state (and an optional reply hook) so `poll`/`record_reply` can resolve it later; - /// - attaches the provided `lock` to the owning `Task` so wait/timeout bookkeeping stays consistent. + /// - records the lock deadline for timeout detection and attaches the lock to the owning [`Task`] so + /// wait bookkeeping stays consistent. /// /// # Context /// Called from helpers such as `send_bytes_for_reply` / `create_program_for_reply` while the message - /// handler executes inside [message_loop] in `handle()` entry point, see [Gear Protocol](https://wiki.vara.network/docs/build/introduction). + /// handler executes inside [`message_loop`] in `handle()` entry point, see [Gear Protocol](https://wiki.vara.network/docs/build/introduction). /// The current `message_id` is read from the runtime and used to fetch the associated `Task` entry. /// /// # Panics /// Panics if the `Task` for the current `message_id` cannot be found, which indicates the function - /// was invoked outside the [message_loop] context (programmer error). + /// was invoked outside the [`message_loop`] context (programmer error). pub fn register_signal( &mut self, waiting_reply_to: MessageId, @@ -247,11 +238,13 @@ impl WakeSignals { reply_hook: Option>, ) { let message_id = Syscall::message_id(); + let deadline = lock.deadline(); self.signals.insert( waiting_reply_to, WakeSignal::Pending { message_id, + deadline, reply_hook, }, ); @@ -267,15 +260,16 @@ impl WakeSignals { /// Processes an incoming reply for `reply_to` and transitions the stored wake state. /// - /// - upgrades the pending entry to [WakeSignal::Ready], capturing payload and reply code; + /// - upgrades the [`WakeSignal::Pending`] entry to [`WakeSignal::Ready`], capturing payload and reply code; /// - executes the optional reply hook once the reply becomes available. + /// - for the [`WakeSignal::Timeout`] entry executes the optional reply hook and remove entry; /// /// # Context - /// Invoked by [handle_reply_with_hook] when a reply arrives during `handle_reply()` execution. The + /// Invoked by [`handle_reply_with_hook`] when a reply arrives during `handle_reply()` execution. The /// runtime supplies `reply_to`. /// /// # Panics - /// Panics if it encounters an already finalised entry [WakeSignal::Ready] or the associated task is + /// Panics if it encounters an already finalised entry [`WakeSignal::Ready`] or the associated task is /// missing. Both scenarios indicate logic bugs or duplicate delivery. pub fn record_reply(&mut self, reply_to: &MessageId) { if let hashbrown::hash_map::EntryRef::Occupied(mut entry) = self.signals.entry_ref(reply_to) @@ -283,6 +277,7 @@ impl WakeSignals { match entry.get_mut() { WakeSignal::Pending { message_id, + deadline: _, reply_hook, } => { let message_id = *message_id; @@ -322,18 +317,22 @@ impl WakeSignals { /// Marks a pending reply as timed out and preserves context for later handling. /// - /// - upgrades a [WakeSignal::Pending] entry to [WakeSignal::Timeout], capturing when the reply was expected + /// - upgrades a [`WakeSignal::Pending`] entry to [`WakeSignal::Timeout`], capturing when the reply was expected /// and when the timeout was detected; /// - retains the optional reply hook so it can still be executed if a late reply arrives and reuses the /// stored state when `record_reply` is called afterwards. /// /// # Context - /// Triggered from [Task::signal_reply_timeout] whenever the runtime observes that a waiting reply exceeded - /// its deadline while executing inside [message_loop]. - pub fn record_timeout(&mut self, reply_to: MessageId, expected: BlockNumber, now: BlockNumber) { + /// Triggered from [`Task::clear_signals`]. + pub fn record_timeout(&mut self, reply_to: MessageId, now: BlockNumber) { if let hashbrown::hash_map::Entry::Occupied(mut entry) = self.signals.entry(reply_to) - && let WakeSignal::Pending { reply_hook, .. } = entry.get_mut() + && let WakeSignal::Pending { + reply_hook, + deadline, + .. + } = entry.get_mut() { + let expected = *deadline; // move `reply_hook` to `WakeSignal::Timeout` state let reply_hook = reply_hook.take(); entry.insert(WakeSignal::Timeout { @@ -347,33 +346,56 @@ impl WakeSignals { } pub fn waits_for(&self, reply_to: &MessageId) -> bool { - self.signals.contains_key(reply_to) + self.signals + .get(reply_to) + .map(|signal| !matches!(signal, WakeSignal::Timeout { .. })) + .unwrap_or_default() } /// Polls the stored wake signal for `reply_to`, returning the appropriate future state. /// - /// # What it does - /// - inspects the current `WakeSignal` variant and returns `Pending`, a `Ready` payload, or propagates - /// a timeout error; when `Ready`, the entry is removed so subsequent polls observe completion. + /// - inspects the current `WakeSignal` variant, promoting pending entries whose deadline has passed to + /// [`WakeSignal::Timeout`]; + /// - returns `Pending`, a `Ready` payload, or propagates a timeout error; when `Ready`, the entry is + /// removed so subsequent polls observe completion. /// /// # Context - /// Called by [MessageFuture::poll] (and any wrappers) while a consumer awaits a reply produced by - /// [message_loop]. It runs on the same execution thread and must be non-blocking. + /// Called by [`MessageFuture::poll`] (and any wrappers) while a consumer awaits a reply produced by + /// [`message_loop`]. It runs on the same execution thread and must be non-blocking. /// /// # Panics /// Panics if the signal was never registered for `reply_to`, which indicates misuse of the async API - /// (polling without having called one of the [send_bytes_for_reply]/[create_program_for_reply] methods first). + /// (polling without having called one of the [`send_bytes_for_reply`]/[`create_program_for_reply`] methods first). pub fn poll( &mut self, reply_to: &MessageId, _cx: &mut Context<'_>, ) -> Poll, Error>> { - let hashbrown::hash_map::EntryRef::Occupied(entry) = self.signals.entry_ref(reply_to) + let hashbrown::hash_map::EntryRef::Occupied(mut entry) = self.signals.entry_ref(reply_to) else { panic!("Poll not registered feature") }; - match entry.get() { - WakeSignal::Pending { .. } => Poll::Pending, + + match entry.get_mut() { + WakeSignal::Pending { + deadline, + reply_hook, + .. + } => { + let now = Syscall::block_height(); + let expected = *deadline; + if now >= expected { + let reply_hook = reply_hook.take(); + _ = entry.insert(WakeSignal::Timeout { + expected, + now, + reply_hook, + }); + Poll::Ready(Err(Error::Timeout(expected, now))) + } else { + Poll::Pending + } + } WakeSignal::Timeout { expected, now, .. } => { // DO NOT remove entry if `WakeSignal::Timeout` // will be removed in `record_reply` @@ -399,11 +421,6 @@ impl WakeSignals { } } } - - #[cfg(not(feature = "ethexe"))] - fn remove(&mut self, reply_to: &MessageId) -> Option { - self.signals.remove(reply_to) - } } pub struct MessageFuture { @@ -570,7 +587,7 @@ pub fn handle_signal() { if let Some(critical_hook) = task.critical_hook.take() { critical_hook(msg_id); } - task.clear_signals() + task.clear_signals(); } } @@ -610,7 +627,7 @@ impl Future for MessageSleepFuture { /// Delays message execution in asynchronous way for the specified number of blocks. /// -/// It works pretty much like the [exec::wait_for] function, but +/// It works pretty much like the [`gcore::exec::wait_for`] function, but /// allows to continue execution after the delay in the same handler. It is /// worth mentioning that the program state gets persisted inside the call, and /// the execution resumes with potentially different state. @@ -629,7 +646,7 @@ pub fn sleep_for(block_count: BlockCount) -> impl Future { mod tests { use super::*; use crate::gstd::syscalls::Syscall; - use core::sync::atomic::AtomicU64; + use core::{sync::atomic::AtomicU64, task, task::Context}; static MSG_ID: AtomicU64 = AtomicU64::new(1); @@ -663,69 +680,65 @@ mod tests { } #[test] - fn task_signal_reply_timeout_promotes_expired_locks() { - // arrange + fn signals_poll_converts_pending_into_timeout() { let message_id = msg_id(); set_context(message_id, 20); + tasks().insert(message_id, Task::new(async {})); + let reply_to = msg_id(); let lock = Lock::up_to(5); - let signals_map = signals(); - let task = Task::new(async {}); + let deadline = lock.deadline(); - tasks().insert(message_id, task); - let task = tasks().get_mut(&message_id).unwrap(); - signals_map.register_signal(reply_to, lock, None); + signals().register_signal(reply_to, lock, None); - // act - task.signal_reply_timeout(25); + Syscall::with_block_height(deadline - 1); + let mut cx = Context::from_waker(task::Waker::noop()); + assert!(matches!(signals().poll(&reply_to, &mut cx), Poll::Pending)); - // assert - match signals_map.signals.get(&reply_to) { - Some(WakeSignal::Timeout { expected, now, .. }) => { - assert_eq!(*expected, lock.deadline()); - assert_eq!(*now, 25); + Syscall::with_block_height(deadline); + let mut cx = Context::from_waker(task::Waker::noop()); + match signals().poll(&reply_to, &mut cx) { + Poll::Ready(Err(Error::Timeout(expected, now))) => { + assert_eq!(expected, deadline); + assert_eq!(now, deadline); } - _ => unreachable!(), + other => panic!("expected timeout, got {other:?}"), } - assert!(task.locks.is_empty()); + signals().signals.remove(&reply_to); + tasks().remove(&message_id); } #[test] fn task_remove_signal_skip_not_waited_lock() { - // arrange let message_id = msg_id(); set_context(message_id, 30); let reply_to = msg_id(); let lock = Lock::up_to(5); - let signals_map = signals(); - let task = Task::new(async {}); - tasks().insert(message_id, task); + tasks().insert(message_id, Task::new(async {})); let task = tasks().get_mut(&message_id).unwrap(); - signals_map.register_signal(reply_to, lock, None); + signals().register_signal(reply_to, lock, None); assert_eq!(1, task.locks.len()); - task.signal_reply_timeout(31); - // TODO: record reply + poll - signals_map.remove(&reply_to); + signals().signals.remove(&reply_to); assert_eq!(1, task.locks.len()); - assert_eq!(None, task.next_lock()); + assert_eq!(None, task.next_lock(31)); + tasks().remove(&message_id); } #[test] fn task_insert_sleep_adds_entry_without_reply() { - set_context(msg_id(), 40); + let message_id = msg_id(); + set_context(message_id, 40); let mut task = Task::new(async {}); let lock = Lock::exactly(4); task.insert_sleep(lock); - assert_eq!(Some(lock), task.next_lock()); - - task.signal_reply_timeout(44); - assert_eq!(None, task.next_lock()); + assert_eq!(Some(lock), task.next_lock(42)); + assert_eq!(None, task.next_lock(lock.deadline())); } } diff --git a/rs/src/gstd/mod.rs b/rs/src/gstd/mod.rs index 4d16baa53..f5b84e233 100644 --- a/rs/src/gstd/mod.rs +++ b/rs/src/gstd/mod.rs @@ -2,7 +2,7 @@ // pub use gstd::{handle_reply_with_hook, message_loop}; pub use async_runtime::{ MessageFuture, create_program_for_reply, handle_reply_with_hook, message_loop, - send_bytes_for_reply, send_for_reply, sleep_for + send_bytes_for_reply, send_for_reply, sleep_for, }; #[cfg(not(feature = "ethexe"))] #[doc(hidden)] From 24d6127f2c1b2e4f570709fb3b359ec880c29c94 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Fri, 10 Oct 2025 18:55:29 +0200 Subject: [PATCH 59/66] feat: add feature `async_runtime` --- benchmarks/bench_data.json | 16 ++--- rs/Cargo.toml | 3 +- rs/src/client/gstd_env.rs | 6 +- rs/src/gstd/async_runtime.rs | 1 + rs/src/gstd/locks.rs | 8 ++- rs/src/gstd/mod.rs | 132 +++++++++++++++++++++++++++++++++-- 6 files changed, 146 insertions(+), 20 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index c881ff518..ffd5467ad 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -1,6 +1,6 @@ { "compute": { - "median": 450514040279 + "median": 450514036959 }, "alloc": { "0": 564376739, @@ -13,8 +13,8 @@ "317810": 43147409792 }, "counter": { - "async_call": 803535700, - "sync_call": 636925721 + "async_call": 803534870, + "sync_call": 636924891 }, "cross_program": { "median": 2241047058 @@ -23,10 +23,10 @@ "median": 3198301159 }, "message_stack": { - "0": 70031559900, - "1": 336252014700, - "5": 1404246926900, - "10": 2623289459400, - "20": 5153430863700 + "0": 70030895900, + "1": 336249358700, + "5": 1404235497500, + "10": 2623268070000, + "20": 5153458042800 } } \ No newline at end of file diff --git a/rs/Cargo.toml b/rs/Cargo.toml index c7f6891a4..62f75c737 100644 --- a/rs/Cargo.toml +++ b/rs/Cargo.toml @@ -48,7 +48,7 @@ log = { workspace = true, optional = true } tokio = { workspace = true, features = ["rt", "macros"] } [features] -default = ["gstd"] +default = ["gstd", "async-runtime"] build = ["client-builder", "wasm-builder"] debug = ["gstd?/debug"] ethexe = [ @@ -66,3 +66,4 @@ client-builder = ["std", "idl-gen", "dep:sails-client-gen", "dep:convert_case"] mockall = ["std", "dep:mockall"] std = ["futures/std"] wasm-builder = ["dep:gwasm-builder"] +async-runtime = ["gstd"] \ No newline at end of file diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index 24fcc8951..f8fbc1005 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -1,5 +1,5 @@ use super::*; -use crate::gstd::{Lock, MessageFuture, async_runtime}; +use crate::gstd::{CreateProgramFuture, Lock, MessageFuture}; use ::gstd::errors::Error; #[derive(Default)] @@ -81,7 +81,7 @@ impl GstdEnv { payload: impl AsRef<[u8]>, mut params: GstdParams, ) -> Result { - let message = crate::ok!(async_runtime::send_bytes_for_reply( + let message = crate::ok!(crate::gstd::send_bytes_for_reply( destination, payload.as_ref(), params.value.unwrap_or_default(), @@ -300,7 +300,7 @@ pin_project_lite::pin_project! { #[project = Projection] #[project_replace = Replace] pub enum GstdFuture { - CreateProgram { #[pin] future: MessageFuture }, + CreateProgram { #[pin] future: CreateProgramFuture }, Message { #[pin] future: MessageFuture }, MessageWithRedirect { #[pin] diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index 1edbe35a6..7dd145f5e 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -324,6 +324,7 @@ impl WakeSignals { /// /// # Context /// Triggered from [`Task::clear_signals`]. + #[cfg(not(feature = "ethexe"))] pub fn record_timeout(&mut self, reply_to: MessageId, now: BlockNumber) { if let hashbrown::hash_map::Entry::Occupied(mut entry) = self.signals.entry(reply_to) && let WakeSignal::Pending { diff --git a/rs/src/gstd/locks.rs b/rs/src/gstd/locks.rs index 1cc65215d..56bddd164 100644 --- a/rs/src/gstd/locks.rs +++ b/rs/src/gstd/locks.rs @@ -41,12 +41,18 @@ impl Lock { self.deadline } + /// Gets the duration from current [`Syscall::block_height()`]. + pub fn duration(&self) -> Option { + let current = Syscall::block_height(); + self.deadline.checked_sub(current) + } + pub fn wait_type(&self) -> WaitType { self.ty } /// Call wait functions by the lock type. - pub(crate) fn wait(&self, now: BlockNumber) { + pub fn wait(&self, now: BlockNumber) { let duration = self .deadline .checked_sub(now) diff --git a/rs/src/gstd/mod.rs b/rs/src/gstd/mod.rs index f5b84e233..842daca57 100644 --- a/rs/src/gstd/mod.rs +++ b/rs/src/gstd/mod.rs @@ -1,9 +1,11 @@ -#[doc(hidden)] -// pub use gstd::{handle_reply_with_hook, message_loop}; +#[cfg(feature = "async-runtime")] pub use async_runtime::{ MessageFuture, create_program_for_reply, handle_reply_with_hook, message_loop, send_bytes_for_reply, send_for_reply, sleep_for, }; +#[cfg(feature = "async-runtime")] +pub type CreateProgramFuture = MessageFuture; +#[cfg(feature = "async-runtime")] #[cfg(not(feature = "ethexe"))] #[doc(hidden)] pub use async_runtime::{handle_signal, set_critical_hook}; @@ -12,12 +14,20 @@ pub use async_runtime::{handle_signal, set_critical_hook}; pub use ethexe::{EthEvent, EthEventExpo}; #[doc(hidden)] pub use events::{EventEmitter, SailsEvent}; -pub use locks::{Lock, WaitType}; -// #[cfg(not(feature = "ethexe"))] -// #[doc(hidden)] -// pub use gstd::handle_signal; +#[cfg(not(feature = "async-runtime"))] +#[cfg(not(feature = "ethexe"))] +#[doc(hidden)] +pub use gstd::handle_signal; +#[cfg(not(feature = "async-runtime"))] +#[cfg(not(feature = "ethexe"))] +#[doc(hidden)] +pub use gstd::msg::{CreateProgramFuture, MessageFuture}; pub use gstd::{debug, exec, msg}; #[doc(hidden)] +#[cfg(not(feature = "async-runtime"))] +pub use gstd::{handle_reply_with_hook, message_loop}; +pub use locks::{Lock, WaitType}; +#[doc(hidden)] pub use sails_macros::{event, export, program, service}; pub use syscalls::Syscall; @@ -28,7 +38,8 @@ use crate::{ }; use gcore::stack_buffer; -pub(crate) mod async_runtime; +#[cfg(feature = "async-runtime")] +mod async_runtime; #[cfg(feature = "ethexe")] mod ethexe; mod events; @@ -160,3 +171,110 @@ macro_rules! ok { } }; } + +#[cfg(not(feature = "async-runtime"))] +#[cfg(not(feature = "ethexe"))] +#[inline] +pub fn send_bytes_for_reply( + destination: ActorId, + payload: &[u8], + value: ValueUnit, + wait: Lock, + gas_limit: Option, + reply_deposit: Option, + reply_hook: Option>, +) -> Result { + let reply_deposit = reply_deposit.unwrap_or_default(); + // here can be a redirect target + let mut message_future = if let Some(gas_limit) = gas_limit { + ::gstd::msg::send_bytes_with_gas_for_reply( + destination, + payload, + gas_limit, + value, + reply_deposit, + )? + } else { + ::gstd::msg::send_bytes_for_reply(destination, payload, value, reply_deposit)? + }; + + message_future = match wait.wait_type() { + WaitType::Exactly => message_future.exactly(wait.duration())?, + WaitType::UpTo => message_future.up_to(wait.duration())?, + }; + + if let Some(reply_hook) = reply_hook { + message_future = message_future.handle_reply(reply_hook)?; + } + Ok(message_future) +} + +#[cfg(not(feature = "async-runtime"))] +#[cfg(feature = "ethexe")] +#[inline] +pub fn send_bytes_for_reply( + destination: ActorId, + payload: &[u8], + value: ValueUnit, + wait: Lock, +) -> Result { + let value = params.value.unwrap_or(0); + // here can be a redirect target + let mut message_future = ::gstd::msg::send_bytes_for_reply(destination, payload, value)?; + + message_future = match wait.wait_type() { + WaitType::Exactly => message_future.exactly(wait.duration())?, + WaitType::UpTo => message_future.up_to(wait.duration())?, + }; + + Ok(message_future) +} + +#[cfg(not(feature = "async-runtime"))] +#[allow(clippy::too_many_arguments)] +#[inline] +pub fn create_program_for_reply( + code_id: CodeId, + salt: &[u8], + payload: &[u8], + value: ValueUnit, + wait: Lock, + gas_limit: Option, + reply_deposit: Option, + reply_hook: Option>, +) -> Result<(CreateProgramFuture, ActorId), ::gstd::errors::Error> { + #[cfg(not(feature = "ethexe"))] + let mut future = if let Some(gas_limit) = gas_limit { + ::gstd::prog::create_program_bytes_with_gas_for_reply( + code_id, + salt, + payload, + gas_limit, + value, + reply_deposit.unwrap_or_default(), + )? + } else { + ::gstd::prog::create_program_bytes_for_reply( + code_id, + salt, + payload, + value, + reply_deposit.unwrap_or_default(), + )? + }; + #[cfg(feature = "ethexe")] + let future = ::gstd::prog::create_program_bytes_for_reply(self.code_id, salt, payload, value)?; + let program_id = future.program_id; + + future = match wait.wait_type() { + WaitType::Exactly => future.exactly(wait.duration())?, + WaitType::UpTo => future.up_to(wait.duration())?, + }; + + #[cfg(not(feature = "ethexe"))] + if let Some(reply_hook) = reply_hook { + future = future.handle_reply(reply_hook)?; + } + + Ok((future, program_id)) +} From 5acecf7347b0720032eaef2752df209c08465ec6 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 13 Oct 2025 14:02:46 +0200 Subject: [PATCH 60/66] feat: add Syscall::read_bytes, Syscall::system_reserve_gas --- benchmarks/bench_data.json | 18 +++++++++--------- rs/src/gstd/async_runtime.rs | 7 +++---- rs/src/gstd/syscalls.rs | 35 +++++++++++++++++++++++++---------- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index ffd5467ad..997c945a1 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -13,20 +13,20 @@ "317810": 43147409792 }, "counter": { - "async_call": 803534870, - "sync_call": 636924891 + "async_call": 804843419, + "sync_call": 638233440 }, "cross_program": { - "median": 2241047058 + "median": 2242793866 }, "redirect": { - "median": 3198301159 + "median": 3200910565 }, "message_stack": { - "0": 70030895900, - "1": 336249358700, - "5": 1404235497500, - "10": 2623268070000, - "20": 5153458042800 + "0": 70118596500, + "1": 336583030200, + "5": 1405553052600, + "10": 2625819310200, + "20": 5176338428500 } } \ No newline at end of file diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index 7dd145f5e..46c06641e 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -161,7 +161,7 @@ where let task = tasks_map.entry(msg_id).or_insert_with(|| { #[cfg(not(feature = "ethexe"))] { - ::gcore::exec::system_reserve_gas(gstd::Config::system_reserve()) + Syscall::system_reserve_gas(gstd::Config::system_reserve()) .expect("Failed to reserve gas for system signal"); } Task::new(future) @@ -284,7 +284,7 @@ impl WakeSignals { let reply_hook = reply_hook.take(); // replase entry with `WakeSignal::Ready` _ = entry.insert(WakeSignal::Ready { - payload: ::gstd::msg::load_bytes().expect("Failed to load bytes"), + payload: Syscall::read_bytes().expect("Failed to read bytes"), reply_code: Syscall::reply_code() .expect("Shouldn't be called with incorrect context"), }); @@ -349,8 +349,7 @@ impl WakeSignals { pub fn waits_for(&self, reply_to: &MessageId) -> bool { self.signals .get(reply_to) - .map(|signal| !matches!(signal, WakeSignal::Timeout { .. })) - .unwrap_or_default() + .is_some_and(|signal| !matches!(signal, WakeSignal::Timeout { .. })) } /// Polls the stored wake signal for `reply_to`, returning the appropriate future state. diff --git a/rs/src/gstd/syscalls.rs b/rs/src/gstd/syscalls.rs index 0d818c2ee..626ad0dff 100644 --- a/rs/src/gstd/syscalls.rs +++ b/rs/src/gstd/syscalls.rs @@ -90,15 +90,28 @@ impl Syscall { pub fn exit(inheritor_id: ActorId) -> ! { ::gcore::exec::exit(inheritor_id) } + + #[inline(always)] + pub fn read_bytes() -> Result, ::gcore::errors::Error> { + let mut result = vec![0u8; ::gcore::msg::size()]; + ::gcore::msg::read(result.as_mut())?; + Ok(result) + } + + #[cfg(not(feature = "ethexe"))] + #[inline(always)] + pub fn system_reserve_gas(amount: GasUnit) -> Result<(), ::gcore::errors::Error> { + ::gcore::exec::system_reserve_gas(amount) + } } #[cfg(not(target_arch = "wasm32"))] #[cfg(not(feature = "std"))] macro_rules! syscall_unimplemented { - ($($name:ident() -> $type:ty),* $(,)?) => { + ($($name:ident( $( $param:ident : $ty:ty ),* ) -> $type:ty),* $(,)?) => { impl Syscall { $( - pub fn $name() -> $type { + pub fn $name($( $param: $ty ),* ) -> $type { unimplemented!("{ERROR}") } )* @@ -126,16 +139,11 @@ syscall_unimplemented!( block_timestamp() -> u64, value_available() -> u128, env_vars() -> ::gcore::EnvVars, + exit(_inheritor_id: ActorId) -> !, + read_bytes() -> Result, gcore::errors::Error>, + system_reserve_gas(_amount: GasUnit) -> Result<(), ::gcore::errors::Error>, ); -#[cfg(not(target_arch = "wasm32"))] -#[cfg(not(feature = "std"))] -impl Syscall { - pub fn exit(_inheritor_id: ActorId) -> ! { - unimplemented!("{ERROR}") - } -} - #[cfg(not(target_arch = "wasm32"))] #[cfg(feature = "std")] const _: () = { @@ -189,6 +197,7 @@ const _: () = { block_height() -> u32, block_timestamp() -> u64, value_available() -> u128, + read_bytes() -> Result, gcore::errors::Error>, ); impl Default for SyscallState { @@ -208,6 +217,7 @@ const _: () = { block_height: 0, block_timestamp: 0, value_available: 0, + read_bytes: Err(::gcore::errors::Error::SyscallUsage), } } } @@ -225,5 +235,10 @@ const _: () = { pub fn exit(inheritor_id: ActorId) -> ! { panic!("Program exited with inheritor id: {}", inheritor_id); } + + #[cfg(not(feature = "ethexe"))] + pub fn system_reserve_gas(_amount: GasUnit) -> Result<(), ::gcore::errors::Error> { + Ok(()) + } } }; From da2dd395d06dde366a6ed9746895c8139cf53954 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Fri, 17 Oct 2025 11:59:22 +0200 Subject: [PATCH 61/66] bench: fix message_stack units --- benchmarks/bench_data.json | 16 ++++++++-------- benchmarks/src/benchmarks.rs | 15 ++++++++++----- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 35ab6d625..d22478fbe 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -13,20 +13,20 @@ "317810": 43147409792 }, "counter": { - "async_call": 851480964, - "sync_call": 678860419 + "async_call": 851480134, + "sync_call": 678859589 }, "cross_program": { "median": 2436719419 }, "redirect": { - "median": 3473474864 + "median": 3474613683 }, "message_stack": { - "0": 79911236500, - "1": 376957078300, - "5": 1569303378600, - "10": 2959197465500, - "20": 6398916489100 + "0": 799115685, + "1": 3769584063, + "5": 15693086906, + "10": 29592077575, + "20": 63989367411 } } \ No newline at end of file diff --git a/benchmarks/src/benchmarks.rs b/benchmarks/src/benchmarks.rs index aa413c898..0e95e4014 100644 --- a/benchmarks/src/benchmarks.rs +++ b/benchmarks/src/benchmarks.rs @@ -301,12 +301,17 @@ async fn message_stack_test(limit: u32) -> u64 { }) .await; - let initial_balance = env.system().balance_of(DEFAULT_USER_ALICE); - - program.ping_pong_stack().start(limit).await.unwrap(); + let message_id = program + .ping_pong_stack() + .start(limit) + .send_one_way() + .unwrap(); + let block_res = env.system().run_next_block(); + assert!(block_res.succeed.contains(&message_id)); + assert_eq!(block_res.gas_burned.len(), (limit * 2 + 1) as usize); - let balance = env.system().balance_of(DEFAULT_USER_ALICE); - (initial_balance - balance).try_into().unwrap() + let gas = block_res.gas_burned.values().sum(); + gas } fn create_env() -> GtestEnv { From 9052f9b28185d55cf75c73aa4bed7b50739418a0 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Fri, 17 Oct 2025 17:56:35 +0200 Subject: [PATCH 62/66] wip: rename WakeSignal::Timeout to Expired, fix `send_one_way` --- benchmarks/bench_data.json | 14 ++-- rs/src/client/gstd_env.rs | 5 +- rs/src/gstd/async_runtime.rs | 139 +++++++++++++++++++++++++++-------- rs/src/gstd/mod.rs | 2 +- 4 files changed, 120 insertions(+), 40 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index a49149622..78c68121c 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -17,16 +17,16 @@ "sync_call": 638234270 }, "cross_program": { - "median": 2241985467 + "median": 2241384285 }, "redirect": { - "median": 3200910565 + "median": 3199953137 }, "message_stack": { - "0": 701192605, - "1": 3365856862, - "5": 14055636766, - "10": 26258398942, - "20": 51763789325 + "0": 701617641, + "1": 3367769473, + "5": 14063499677, + "10": 26273699728, + "20": 51793965861 } } \ No newline at end of file diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index 49a04fe23..2931d9246 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -81,11 +81,10 @@ impl GstdEnv { payload: impl AsRef<[u8]>, mut params: GstdParams, ) -> Result { - let message = crate::ok!(crate::gstd::send_bytes_for_reply( + let reply_to = crate::ok!(crate::gstd::send_one_way( destination, payload.as_ref(), params.value.unwrap_or_default(), - params.wait.unwrap_or_default(), #[cfg(not(feature = "ethexe"))] params.gas_limit, #[cfg(not(feature = "ethexe"))] @@ -94,7 +93,7 @@ impl GstdEnv { params.reply_hook.take(), )); - Ok(message.waiting_reply_to) + Ok(reply_to) } } diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index 46c06641e..d891adde4 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -105,7 +105,7 @@ impl Task { let signals_map = signals(); self.locks.iter().for_each(|(_, reply_to)| { if let Some(reply_to) = reply_to { - // set the `WakeSignal::Timeout` for further processing in `handle_reply` + // set the `WakeSignal::Expired` for further processing in `handle_reply` signals_map.record_timeout(*reply_to, now); } }); @@ -188,18 +188,23 @@ pub type Payload = Vec; /// The [`WakeSignal`] lifecycle corresponds to waiting for a reply to a sent message /// and ends when `handle_reply()` is received. /// -/// May outlive parent [`Task`] in state [`WakeSignal::Timeout`]. +/// May outlive parent [`Task`] in [`WakeSignal::Expired`] state. +/// +/// Can be created in [`WakeSignal::Expired`] state if there is no [`Task`] to await. enum WakeSignal { + /// Reply is still pending; tracks origin message, deadline, and optional hook to run on completion or timeout. Pending { message_id: MessageId, deadline: BlockNumber, reply_hook: Option>, }, + /// Reply handled; captures payload and reply code so the waiting future can resolve. Ready { payload: Payload, reply_code: ReplyCode, }, - Timeout { + /// Reply missed its deadline; retains timing data and hook so late arrivals can still be acknowledged. + Expired { expected: BlockNumber, now: BlockNumber, reply_hook: Option>, @@ -217,11 +222,11 @@ impl WakeSignals { } } - /// Registers a pending reply for `waiting_reply_to` while the current message is being processed. + /// Registers a reply wait for `waiting_reply_to` while the current message is being processed. /// - /// - stores pending state (and an optional reply hook) so `poll`/`record_reply` can resolve it later; - /// - records the lock deadline for timeout detection and attaches the lock to the owning [`Task`] so - /// wait bookkeeping stays consistent. + /// - stores [`WakeSignal::Pending`] together with an optional hook so `poll`/`record_reply` can resolve it later; + /// - records the lock deadline for timeout detection and attaches the lock to the owning [`Task`] for + /// consistent wake bookkeeping. /// /// # Context /// Called from helpers such as `send_bytes_for_reply` / `create_program_for_reply` while the message @@ -258,11 +263,38 @@ impl WakeSignals { .insert_lock(waiting_reply_to, lock); } + /// Registers a reply hook for `waiting_reply_to` without creating a tracked wait. + /// + /// - stores a [`WakeSignal::Expired`] entry so `record_reply` will still execute the hook if a reply + /// arrives later; + /// - intended for one-way sends that want to observe replies from outside [`message_loop`]. + /// + /// # Context + /// Called from [`send_one_way`] and other synchronous helpers; may be invoked outside [`message_loop`]. + #[inline] + pub fn register_hook( + &mut self, + waiting_reply_to: MessageId, + reply_hook: Option>, + ) { + if let Some(reply_hook) = reply_hook { + let now = Syscall::block_height(); + self.signals.insert( + waiting_reply_to, + WakeSignal::Expired { + expected: now, + now, + reply_hook: Some(reply_hook), + }, + ); + } + } + /// Processes an incoming reply for `reply_to` and transitions the stored wake state. /// /// - upgrades the [`WakeSignal::Pending`] entry to [`WakeSignal::Ready`], capturing payload and reply code; /// - executes the optional reply hook once the reply becomes available. - /// - for the [`WakeSignal::Timeout`] entry executes the optional reply hook and remove entry; + /// - for the [`WakeSignal::Expired`] entry executes the optional reply hook and remove entry; /// /// # Context /// Invoked by [`handle_reply_with_hook`] when a reply arrives during `handle_reply()` execution. The @@ -299,7 +331,7 @@ impl WakeSignals { f() } } - WakeSignal::Timeout { reply_hook, .. } => { + WakeSignal::Expired { reply_hook, .. } => { // execute reply hook and remove entry if let Some(f) = reply_hook.take() { f() @@ -317,7 +349,7 @@ impl WakeSignals { /// Marks a pending reply as timed out and preserves context for later handling. /// - /// - upgrades a [`WakeSignal::Pending`] entry to [`WakeSignal::Timeout`], capturing when the reply was expected + /// - upgrades a [`WakeSignal::Pending`] entry to [`WakeSignal::Expired`], capturing when the reply was expected /// and when the timeout was detected; /// - retains the optional reply hook so it can still be executed if a late reply arrives and reuses the /// stored state when `record_reply` is called afterwards. @@ -334,9 +366,9 @@ impl WakeSignals { } = entry.get_mut() { let expected = *deadline; - // move `reply_hook` to `WakeSignal::Timeout` state + // move `reply_hook` to `WakeSignal::Expired` state let reply_hook = reply_hook.take(); - entry.insert(WakeSignal::Timeout { + entry.insert(WakeSignal::Expired { expected, now, reply_hook, @@ -349,13 +381,13 @@ impl WakeSignals { pub fn waits_for(&self, reply_to: &MessageId) -> bool { self.signals .get(reply_to) - .is_some_and(|signal| !matches!(signal, WakeSignal::Timeout { .. })) + .is_some_and(|signal| !matches!(signal, WakeSignal::Expired { .. })) } /// Polls the stored wake signal for `reply_to`, returning the appropriate future state. /// /// - inspects the current `WakeSignal` variant, promoting pending entries whose deadline has passed to - /// [`WakeSignal::Timeout`]; + /// [`WakeSignal::Expired`]; /// - returns `Pending`, a `Ready` payload, or propagates a timeout error; when `Ready`, the entry is /// removed so subsequent polls observe completion. /// @@ -386,7 +418,7 @@ impl WakeSignals { let expected = *deadline; if now >= expected { let reply_hook = reply_hook.take(); - _ = entry.insert(WakeSignal::Timeout { + _ = entry.insert(WakeSignal::Expired { expected, now, reply_hook, @@ -396,8 +428,8 @@ impl WakeSignals { Poll::Pending } } - WakeSignal::Timeout { expected, now, .. } => { - // DO NOT remove entry if `WakeSignal::Timeout` + WakeSignal::Expired { expected, now, .. } => { + // DO NOT remove entry if `WakeSignal::Expired` // will be removed in `record_reply` Poll::Ready(Err(Error::Timeout(*expected, *now))) } @@ -476,29 +508,78 @@ pub fn send_for_reply( #[cfg(not(feature = "ethexe"))] #[inline] -pub fn send_bytes_for_reply( +fn send_bytes( destination: ActorId, payload: &[u8], value: ValueUnit, - wait: Lock, gas_limit: Option, reply_deposit: Option, - reply_hook: Option>, -) -> Result { +) -> Result { let waiting_reply_to = if let Some(gas_limit) = gas_limit { - crate::ok!(::gcore::msg::send_with_gas( - destination, - payload, - gas_limit, - value - )) + ::gcore::msg::send_with_gas(destination, payload, gas_limit, value)? } else { - crate::ok!(::gcore::msg::send(destination, payload, value)) + ::gcore::msg::send(destination, payload, value)? }; if let Some(reply_deposit) = reply_deposit { _ = ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit); } + Ok(waiting_reply_to) +} + +#[cfg(feature = "ethexe")] +#[inline] +fn send_bytes( + destination: ActorId, + payload: &[u8], + value: ValueUnit, +) -> Result { + ::gcore::msg::send(destination, payload, value).map_err(::gstd::errors::Error::Core) +} + +#[inline] +pub fn send_one_way( + destination: ActorId, + payload: &[u8], + value: ValueUnit, + #[cfg(not(feature = "ethexe"))] gas_limit: Option, + #[cfg(not(feature = "ethexe"))] reply_deposit: Option, + #[cfg(not(feature = "ethexe"))] reply_hook: Option>, +) -> Result { + let waiting_reply_to = crate::ok!(send_bytes( + destination, + payload, + value, + #[cfg(not(feature = "ethexe"))] + gas_limit, + #[cfg(not(feature = "ethexe"))] + reply_deposit + )); + + #[cfg(not(feature = "ethexe"))] + signals().register_hook(waiting_reply_to, reply_hook); + + Ok(waiting_reply_to) +} + +#[cfg(not(feature = "ethexe"))] +#[inline] +pub fn send_bytes_for_reply( + destination: ActorId, + payload: &[u8], + value: ValueUnit, + wait: Lock, + gas_limit: Option, + reply_deposit: Option, + reply_hook: Option>, +) -> Result { + let waiting_reply_to = crate::ok!(send_bytes( + destination, + payload, + value, + gas_limit, + reply_deposit + )); signals().register_signal(waiting_reply_to, wait, reply_hook); @@ -513,7 +594,7 @@ pub fn send_bytes_for_reply( value: ValueUnit, wait: Lock, ) -> Result { - let waiting_reply_to = crate::ok!(::gcore::msg::send(destination, payload, value)); + let waiting_reply_to = crate::ok!(send_bytes(destination, payload, value)); signals().register_signal(waiting_reply_to, wait, None); diff --git a/rs/src/gstd/mod.rs b/rs/src/gstd/mod.rs index 842daca57..c013cace8 100644 --- a/rs/src/gstd/mod.rs +++ b/rs/src/gstd/mod.rs @@ -1,7 +1,7 @@ #[cfg(feature = "async-runtime")] pub use async_runtime::{ MessageFuture, create_program_for_reply, handle_reply_with_hook, message_loop, - send_bytes_for_reply, send_for_reply, sleep_for, + send_bytes_for_reply, send_for_reply, send_one_way, sleep_for, }; #[cfg(feature = "async-runtime")] pub type CreateProgramFuture = MessageFuture; From a093c868c60ce328f514645c0f4ba20086758df8 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 20 Oct 2025 12:07:40 +0200 Subject: [PATCH 63/66] fix: clippy --- rs/src/gstd/async_runtime.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index d891adde4..98f391c2e 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -271,6 +271,7 @@ impl WakeSignals { /// /// # Context /// Called from [`send_one_way`] and other synchronous helpers; may be invoked outside [`message_loop`]. + #[cfg(not(feature = "ethexe"))] #[inline] pub fn register_hook( &mut self, From 804460aaee407563c60e47d7bdfcacb33aefb4b0 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 20 Oct 2025 13:38:54 +0200 Subject: [PATCH 64/66] fix: tests --- examples/demo/app/src/chaos/mod.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/examples/demo/app/src/chaos/mod.rs b/examples/demo/app/src/chaos/mod.rs index d95303c50..bea2c29b0 100644 --- a/examples/demo/app/src/chaos/mod.rs +++ b/examples/demo/app/src/chaos/mod.rs @@ -1,4 +1,4 @@ -use sails_rs::gstd::debug; +use sails_rs::gstd::{Lock, debug}; use sails_rs::{gstd, prelude::*}; static mut REPLY_HOOK_COUNTER: u32 = 0; @@ -10,9 +10,7 @@ impl ChaosService { #[export] pub async fn panic_after_wait(&self) { let source = Syscall::message_source(); - let _ = gstd::msg::send_for_reply::<()>(source, (), 0, 0) - .unwrap() - .await; + let _ = gstd::send_for_reply::<()>(source, (), 0).unwrap().await; debug!("Message received, now panicking!"); panic!("Simulated panic after wait"); } @@ -22,16 +20,19 @@ impl ChaosService { let source = Syscall::message_source(); debug!("before handle_reply"); - let fut = gstd::msg::send_for_reply::<()>(source, (), 0, 10_000_000_000).unwrap(); - let fut = fut - .handle_reply(|| { + let fut = gstd::send_bytes_for_reply( + source, + &[], + 0, + Lock::up_to(1), + None, + Some(10_000_000_000), + Some(Box::new(|| { unsafe { REPLY_HOOK_COUNTER += 1 }; debug!("handle_reply triggered"); - }) - .unwrap() - .up_to(Some(1)) - .unwrap(); - + })), + ) + .unwrap(); let _ = fut.await; debug!("after handle_reply"); } From cf08be86551f155e3c0dd55644c1ba493c267cfa Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 22 Oct 2025 18:40:10 +0200 Subject: [PATCH 65/66] fix: cfg features --- rs/src/gstd/async_runtime.rs | 31 ----------------- rs/src/gstd/mod.rs | 64 ++++++++++++++++++++++++++++++++---- 2 files changed, 58 insertions(+), 37 deletions(-) diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index 98f391c2e..7425d09b7 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -507,37 +507,6 @@ pub fn send_for_reply( }) } -#[cfg(not(feature = "ethexe"))] -#[inline] -fn send_bytes( - destination: ActorId, - payload: &[u8], - value: ValueUnit, - gas_limit: Option, - reply_deposit: Option, -) -> Result { - let waiting_reply_to = if let Some(gas_limit) = gas_limit { - ::gcore::msg::send_with_gas(destination, payload, gas_limit, value)? - } else { - ::gcore::msg::send(destination, payload, value)? - }; - - if let Some(reply_deposit) = reply_deposit { - _ = ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit); - } - Ok(waiting_reply_to) -} - -#[cfg(feature = "ethexe")] -#[inline] -fn send_bytes( - destination: ActorId, - payload: &[u8], - value: ValueUnit, -) -> Result { - ::gcore::msg::send(destination, payload, value).map_err(::gstd::errors::Error::Core) -} - #[inline] pub fn send_one_way( destination: ActorId, diff --git a/rs/src/gstd/mod.rs b/rs/src/gstd/mod.rs index c013cace8..894ca3aef 100644 --- a/rs/src/gstd/mod.rs +++ b/rs/src/gstd/mod.rs @@ -19,7 +19,6 @@ pub use events::{EventEmitter, SailsEvent}; #[doc(hidden)] pub use gstd::handle_signal; #[cfg(not(feature = "async-runtime"))] -#[cfg(not(feature = "ethexe"))] #[doc(hidden)] pub use gstd::msg::{CreateProgramFuture, MessageFuture}; pub use gstd::{debug, exec, msg}; @@ -172,6 +171,60 @@ macro_rules! ok { }; } +#[cfg(not(feature = "ethexe"))] +#[inline] +fn send_bytes( + destination: ActorId, + payload: &[u8], + value: ValueUnit, + gas_limit: Option, + reply_deposit: Option, +) -> Result { + let waiting_reply_to = if let Some(gas_limit) = gas_limit { + ::gcore::msg::send_with_gas(destination, payload, gas_limit, value)? + } else { + ::gcore::msg::send(destination, payload, value)? + }; + + if let Some(reply_deposit) = reply_deposit { + _ = ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit); + } + Ok(waiting_reply_to) +} + +#[cfg(feature = "ethexe")] +#[inline] +fn send_bytes( + destination: ActorId, + payload: &[u8], + value: ValueUnit, +) -> Result { + ::gcore::msg::send(destination, payload, value).map_err(::gstd::errors::Error::Core) +} + +#[cfg(not(feature = "async-runtime"))] +#[inline] +pub fn send_one_way( + destination: ActorId, + payload: &[u8], + value: ValueUnit, + #[cfg(not(feature = "ethexe"))] gas_limit: Option, + #[cfg(not(feature = "ethexe"))] reply_deposit: Option, + #[cfg(not(feature = "ethexe"))] _reply_hook: Option>, +) -> Result { + let waiting_reply_to = crate::ok!(send_bytes( + destination, + payload, + value, + #[cfg(not(feature = "ethexe"))] + gas_limit, + #[cfg(not(feature = "ethexe"))] + reply_deposit + )); + + Ok(waiting_reply_to) +} + #[cfg(not(feature = "async-runtime"))] #[cfg(not(feature = "ethexe"))] #[inline] @@ -218,7 +271,6 @@ pub fn send_bytes_for_reply( value: ValueUnit, wait: Lock, ) -> Result { - let value = params.value.unwrap_or(0); // here can be a redirect target let mut message_future = ::gstd::msg::send_bytes_for_reply(destination, payload, value)?; @@ -239,9 +291,9 @@ pub fn create_program_for_reply( payload: &[u8], value: ValueUnit, wait: Lock, - gas_limit: Option, - reply_deposit: Option, - reply_hook: Option>, + #[cfg(not(feature = "ethexe"))] gas_limit: Option, + #[cfg(not(feature = "ethexe"))] reply_deposit: Option, + #[cfg(not(feature = "ethexe"))] reply_hook: Option>, ) -> Result<(CreateProgramFuture, ActorId), ::gstd::errors::Error> { #[cfg(not(feature = "ethexe"))] let mut future = if let Some(gas_limit) = gas_limit { @@ -263,7 +315,7 @@ pub fn create_program_for_reply( )? }; #[cfg(feature = "ethexe")] - let future = ::gstd::prog::create_program_bytes_for_reply(self.code_id, salt, payload, value)?; + let mut future = ::gstd::prog::create_program_bytes_for_reply(code_id, salt, payload, value)?; let program_id = future.program_id; future = match wait.wait_type() { From 427c8cc2cbb982848629bc42077b2a15d9ae5927 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Thu, 23 Oct 2025 13:26:21 +0200 Subject: [PATCH 66/66] fix: remove entrye before hook exec on WakeSignal::Expired --- benchmarks/bench_data.json | 12 ++++++------ rs/src/gstd/async_runtime.rs | 5 +++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 78c68121c..acefaa1a7 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -17,16 +17,16 @@ "sync_call": 638234270 }, "cross_program": { - "median": 2241384285 + "median": 2242192684 }, "redirect": { "median": 3199953137 }, "message_stack": { - "0": 701617641, - "1": 3367769473, - "5": 14063499677, - "10": 26273699728, - "20": 51793965861 + "0": 701611001, + "1": 3367742913, + "5": 14063393437, + "10": 26273493888, + "20": 51793560821 } } \ No newline at end of file diff --git a/rs/src/gstd/async_runtime.rs b/rs/src/gstd/async_runtime.rs index 7425d09b7..35b6ed8b8 100644 --- a/rs/src/gstd/async_runtime.rs +++ b/rs/src/gstd/async_runtime.rs @@ -333,11 +333,12 @@ impl WakeSignals { } } WakeSignal::Expired { reply_hook, .. } => { + let reply_hook = reply_hook.take(); + _ = entry.remove(); // execute reply hook and remove entry - if let Some(f) = reply_hook.take() { + if let Some(f) = reply_hook { f() } - _ = entry.remove(); } WakeSignal::Ready { .. } => panic!("A reply has already received"), };

(code_id, salt); + let ctor = f(deployment); + let program = ctor.await.expect("failed to initialize the program"); + program +} + +fn extract_reply_and_gas(system: &System, message_id: MessageId) -> (Vec, u64) { + let block_res = system.run_next_block(); + assert!(block_res.succeed.contains(&message_id)); + let payload = block_res + .log() + .iter() + .find_map(|log| { + log.reply_to() + .filter(|reply_to| *reply_to == message_id) + .map(|_| log.payload().to_vec()) + }) + .expect("reply found"); + + let gas = *block_res.gas_burned.get(&message_id).expect("gas recorded"); + (payload, gas) +} + fn median(mut values: Vec) -> u64 { values.sort_unstable(); diff --git a/rs/src/client/gtest_env.rs b/rs/src/client/gtest_env.rs index 838b729bf..689409ae5 100644 --- a/rs/src/client/gtest_env.rs +++ b/rs/src/client/gtest_env.rs @@ -234,6 +234,28 @@ impl GearEnv for GtestEnv { type MessageState = ReplyReceiver; } impl PendingCall { + pub fn send_one_way(mut self) -> Result { + let Some(route) = self.route else { + return Err(TestError::ScaleCodecError( + "PendingCall route is not set".into(), + ))?; + }; + if self.state.is_some() { + panic!("{PENDING_CALL_INVALID_STATE}"); + } + // Send message + let args = self + .args + .take() + .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); + let payload = T::encode_params_with_prefix(route, &args); + let params = self.params.take().unwrap_or_default(); + + let message_id = self.env.send_message(self.destination, payload, params)?; + log::debug!("PendingCall: send message {message_id:?}"); + Ok(message_id) + } + pub fn send_message(mut self) -> Result { let Some(route) = self.route else { return Err(TestError::ScaleCodecError( From f2803c5da17ad633a62a8c4916b8ef527a987ce2 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Fri, 5 Sep 2025 13:19:14 +0200 Subject: [PATCH 16/66] wip: gstd future with redirect --- examples/redirect/proxy/src/lib.rs | 3 +- examples/redirect/tests/gtest.rs | 17 ++-- rs/src/client/gstd_env.rs | 140 +++++++++++++++++++++++++++-- 3 files changed, 139 insertions(+), 21 deletions(-) diff --git a/examples/redirect/proxy/src/lib.rs b/examples/redirect/proxy/src/lib.rs index fe6cfa9d5..8af2963c8 100644 --- a/examples/redirect/proxy/src/lib.rs +++ b/examples/redirect/proxy/src/lib.rs @@ -20,8 +20,7 @@ impl ProxyService { client .get_program_id() // Set flag to redirect on exit - // .with_redirect_on_exit(true) - // .recv(self.0) + .with_redirect_on_exit(true) .await .unwrap() } diff --git a/examples/redirect/tests/gtest.rs b/examples/redirect/tests/gtest.rs index 78be677bc..7c04a6d1b 100644 --- a/examples/redirect/tests/gtest.rs +++ b/examples/redirect/tests/gtest.rs @@ -6,7 +6,7 @@ const ACTOR_ID: u64 = 42; #[tokio::test] async fn redirect_on_exit_works() { - let (env, program_code_id, proxy_code_id, _gas_limit) = create_remoting(); + let (env, program_code_id, proxy_code_id, _gas_limit) = create_env(); let program_factory_1 = env.deploy::(program_code_id, vec![1]); let program_factory_2 = env.deploy::(program_code_id, vec![2]); @@ -32,24 +32,21 @@ async fn redirect_on_exit_works() { .await .unwrap(); - let mut redirect_client = program_1.redirect(); - let proxy_client = proxy_program.proxy(); - - let result = proxy_client.get_program_id().await.unwrap(); + let result = proxy_program.proxy().get_program_id().await.unwrap(); assert_eq!(result, program_1.id()); - redirect_client.exit(program_2.id()).await.unwrap(); + program_1.redirect().exit(program_2.id()).await.unwrap(); - let result = proxy_client.get_program_id().await.unwrap(); + let result = proxy_program.proxy().get_program_id().await.unwrap(); assert_eq!(result, program_2.id()); - redirect_client.exit(program_3.id()).await.unwrap(); + program_2.redirect().exit(program_3.id()).await.unwrap(); - let result = proxy_client.get_program_id().await.unwrap(); + let result = proxy_program.proxy().get_program_id().await.unwrap(); assert_eq!(result, program_3.id()); } -fn create_remoting() -> (GtestEnv, CodeId, CodeId, GasUnit) { +fn create_env() -> (GtestEnv, CodeId, CodeId, GasUnit) { use sails_rs::gtest::{MAX_USER_GAS_LIMIT, System}; let system = System::new(); diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index fac266e82..2d956fe71 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -4,6 +4,7 @@ use ::gstd::{ msg, msg::{CreateProgramFuture, MessageFuture}, }; +use core::task::ready; #[derive(Default)] pub struct GstdParams { @@ -39,6 +40,19 @@ impl GstdParams { } } +impl Clone for GstdParams { + fn clone(&self) -> Self { + Self { + gas_limit: self.gas_limit.clone(), + value: self.value.clone(), + wait_up_to: self.wait_up_to.clone(), + reply_deposit: self.reply_deposit.clone(), + reply_hook: None, + redirect_on_exit: self.redirect_on_exit.clone(), + } + } +} + #[cfg(not(feature = "ethexe"))] impl GstdParams { pub fn with_reply_deposit(self, reply_deposit: Option) -> Self { @@ -60,6 +74,25 @@ impl GstdParams { } } +impl PendingCall { + pub fn with_wait_up_to(self, wait_up_to: Option) -> Self { + self.with_params(|params| params.with_wait_up_to(wait_up_to)) + } + + /// Set `redirect_on_exit` flag to `true`` + /// + /// This flag is used to redirect a message to a new program when the target program exits + /// with an inheritor. + /// + /// WARNING: When this flag is set, the message future captures the payload and other arguments, + /// potentially resulting in multiple messages being sent. This can lead to increased gas consumption. + /// + /// This flag is set to `false`` by default. + pub fn with_redirect_on_exit(self, redirect_on_exit: bool) -> Self { + self.with_params(|params| params.with_redirect_on_exit(redirect_on_exit)) + } +} + #[derive(Debug, Default, Clone)] pub struct GstdEnv; @@ -120,6 +153,30 @@ pub(crate) fn send_for_reply_future( Ok(message_future) } +pub(crate) fn send_for_reply>( + target: ActorId, + payload: T, + params: GstdParams, +) -> Result { + let redirect_on_exit = params.redirect_on_exit; + // clone params w/o reply_hook + let params_cloned = params.clone(); + // send message + let future = send_for_reply_future(target, payload.as_ref(), params)?; + if redirect_on_exit { + let created_block = params_cloned.wait_up_to.map(|_| gstd::exec::block_height()); + Ok(GtsdFuture::MessageWithRedirect { + created_block, + future, + params: params_cloned, + payload: payload.as_ref().to_vec(), + target: target, + }) + } else { + Ok(GtsdFuture::Message { future }) + } +} + #[cfg(target_arch = "wasm32")] const _: () = { impl PendingCall { @@ -167,10 +224,10 @@ const _: () = { let payload = T::encode_params_with_prefix(route, &args); let params = self.params.take().unwrap_or_default(); - let send_res = send_for_reply_future(self.destination, payload.as_slice(), params); + let send_res = send_for_reply(self.destination, payload, params); match send_res { Ok(future) => { - self.state = Some(GtsdFuture::Message { future }); + self.state = Some(future); } Err(err) => { return Poll::Ready(Err(err)); @@ -178,17 +235,72 @@ const _: () = { } } let this = self.as_mut().project(); - if let Some(state) = this.state.as_pin_mut() - && let Projection::Message { future } = state.project() - { + if let Some(mut state) = this.state.as_pin_mut() { // Poll message future - match future.poll(cx) { - Poll::Ready(Ok(payload)) => match T::decode_reply_with_prefix(route, payload) { + let output = match state.as_mut().project() { + Projection::CreateProgram { .. } => panic!("{PENDING_CALL_INVALID_STATE}"), + Projection::Message { future } => ready!(future.poll(cx)), + Projection::MessageWithRedirect { future, .. } => ready!(future.poll(cx)), + Projection::Dummy => panic!("{PENDING_CALL_INVALID_STATE}"), + }; + match output { + // ok reply + Ok(payload) => match T::decode_reply_with_prefix(route, payload) { Ok(reply) => Poll::Ready(Ok(reply)), Err(err) => Poll::Ready(Err(Error::Decode(err))), }, - Poll::Ready(Err(err)) => Poll::Ready(Err(err)), - Poll::Pending => Poll::Pending, + // reply with ProgramExited + Err(gstd::errors::Error::ErrorReply( + error_payload, + ErrorReplyReason::UnavailableActor( + SimpleUnavailableActorError::ProgramExited, + ), + )) => { + if let Replace::MessageWithRedirect { + target: _target, + payload, + mut params, + created_block, + .. + } = state.as_mut().project_replace(GtsdFuture::Dummy) + && params.redirect_on_exit + && let Ok(new_target) = ActorId::try_from(error_payload.0.as_ref()) + { + gstd::debug!("Redirecting message from {_target} to {new_target}"); + + // Calculate updated `wait_up_to` if provided + // wait_up_to = wait_up_to - (current_block - created_block) + params.wait_up_to = params.wait_up_to.and_then(|wait_up_to| { + created_block.map(|created_block| { + let current_block = gstd::exec::block_height(); + wait_up_to + .saturating_sub(current_block.saturating_sub(created_block)) + }) + }); + + // send message to new target + let future_res = send_for_reply(new_target, payload, params); + match future_res { + Ok(future) => { + // Replace the future with a new one + _ = state.as_mut().project_replace(future); + // Return Pending to allow the new future to be polled + Poll::Pending + } + Err(err) => Poll::Ready(Err(err)), + } + } else { + Poll::Ready(Err(gstd::errors::Error::ErrorReply( + error_payload, + ErrorReplyReason::UnavailableActor( + SimpleUnavailableActorError::ProgramExited, + ), + ) + .into())) + } + } + // error reply + Err(err) => Poll::Ready(Err(err)), } } else { panic!("{PENDING_CALL_INVALID_STATE}"); @@ -257,9 +369,19 @@ const _: () = { pin_project_lite::pin_project! { #[project = Projection] + #[project_replace = Replace] pub enum GtsdFuture { CreateProgram { #[pin] future: CreateProgramFuture }, Message { #[pin] future: MessageFuture }, + MessageWithRedirect { + #[pin] + future: MessageFuture, + target: ActorId, + payload: Vec, + params: GstdParams, + created_block: Option, + }, + Dummy, } } From e0fb024b444642dc1e26e41e81b75cc224e74fef Mon Sep 17 00:00:00 2001 From: vobradovich Date: Fri, 5 Sep 2025 17:27:50 +0200 Subject: [PATCH 17/66] wip: bench redirect --- benchmarks/bench_data.json | 4 +- benchmarks/src/benchmarks.rs | 119 +++++++++++++++++++---------------- 2 files changed, 68 insertions(+), 55 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 634e3b542..226243ff5 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -17,9 +17,9 @@ "sync_call": 678856494 }, "cross_program": { - "median": 2445884441 + "median": 2475722344 }, "redirect": { - "median": 3612929773 + "median": 3686731410 } } \ No newline at end of file diff --git a/benchmarks/src/benchmarks.rs b/benchmarks/src/benchmarks.rs index afee29f47..c5afc5ec4 100644 --- a/benchmarks/src/benchmarks.rs +++ b/benchmarks/src/benchmarks.rs @@ -34,8 +34,10 @@ use itertools::{Either, Itertools}; use ping_pong_bench_app::client::{ PingPong, PingPongCtors, PingPongPayload, PingPongProgram, ping_pong_service::*, }; -use redirect_client::{redirect::*, *}; -use redirect_proxy_client::{proxy::*, *}; +use redirect_client::{RedirectClient, RedirectClientCtors, RedirectClientProgram, redirect::*}; +use redirect_proxy_client::{ + RedirectProxyClient, RedirectProxyClientCtors, RedirectProxyClientProgram, proxy::*, +}; use sails_rs::{client::*, prelude::*}; use std::{collections::BTreeMap, sync::atomic::AtomicU64}; @@ -191,57 +193,68 @@ async fn cross_program_bench() { .unwrap(); } -// #[tokio::test] -// async fn redirect_bench() { -// let redirect_wasm_path = "../target/wasm32-gear/release/redirect_app.opt.wasm"; -// let proxy_wasm_path = "../target/wasm32-gear/release/redirect_proxy.opt.wasm"; - -// let (remoting, redirect_pid1, redirect_pid2) = create_program_async!( -// ( -// RedirectClientFactory::, -// redirect_wasm_path, -// new -// ), -// ( -// RedirectClientFactory::, -// redirect_wasm_path, -// new -// ) -// ); -// let (remoting, proxy_pid) = create_program_async!( -// remoting, -// ( -// RedirectProxyClientFactory::, -// proxy_wasm_path, -// new, -// redirect_pid1 -// ) -// ); - -// // Warm-up proxy program -// (0..100).for_each(|_| { -// let (resp, _) = call_action!(remoting, proxy_pid, GetProgramId); -// assert_eq!(resp, redirect_pid1); -// }); - -// // Call exit on a redirect program -// call_action!(remoting, redirect_pid1, Exit, redirect_pid2; no_reply_check); - -// // Bench proxy program -// let gas_benches = (0..100) -// .map(|_| { -// let (resp, gas_get_program) = call_action!(remoting, proxy_pid, GetProgramId); -// assert_eq!(resp, redirect_pid2); - -// gas_get_program -// }) -// .collect::>(); - -// crate::store_bench_data(|bench_data| { -// bench_data.update_redirect_bench(median(gas_benches)); -// }) -// .unwrap(); -// } +#[tokio::test] +async fn redirect_bench() { + let redirect_wasm_path = "../target/wasm32-gear/release/redirect_app.opt.wasm"; + let proxy_wasm_path = "../target/wasm32-gear/release/redirect_proxy.opt.wasm"; + + let env = create_env(); + let program_redirect_1 = + deploy_for_bench(&env, redirect_wasm_path, |d| RedirectClientCtors::new(d)).await; + let program_redirect_2 = + deploy_for_bench(&env, redirect_wasm_path, |d| RedirectClientCtors::new(d)).await; + let program_proxy = deploy_for_bench(&env, proxy_wasm_path, |d| { + RedirectProxyClientCtors::new(d, program_redirect_1.id()) + }) + .await; + + // Warm-up proxy program + (0..100).for_each(|_| { + let message_id = program_proxy + .proxy() + .get_program_id() + .send_one_way() + .unwrap(); + let (payload, gas) = extract_reply_and_gas(env.system(), message_id); + let resp = redirect_proxy_client::proxy::io::GetProgramId::decode_reply_with_prefix( + "Proxy", + payload.as_slice(), + ) + .unwrap(); + assert_eq!(resp, program_redirect_1.id()); + }); + + // Call exit on a redirect program + program_redirect_1 + .redirect() + .exit(program_redirect_2.id()) + .send_one_way() + .unwrap(); + + // Bench proxy program + let gas_benches = (0..100) + .map(|_| { + let message_id = program_proxy + .proxy() + .get_program_id() + .send_one_way() + .unwrap(); + let (payload, gas) = extract_reply_and_gas(env.system(), message_id); + let resp = redirect_proxy_client::proxy::io::GetProgramId::decode_reply_with_prefix( + "Proxy", + payload.as_slice(), + ) + .unwrap(); + assert_eq!(resp, program_redirect_2.id()); + gas + }) + .collect::>(); + + crate::store_bench_data(|bench_data| { + bench_data.update_redirect_bench(median(gas_benches)); + }) + .unwrap(); +} async fn alloc_stress_test(n: u32) -> (usize, u64) { // Path taken from the .binpath file From 57fbccdcb3921c5556624ebe7d2020534219b884 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 8 Sep 2025 15:52:01 +0200 Subject: [PATCH 18/66] wip: cleanup, fix clippy --- benchmarks/bench_data.json | 26 +- benchmarks/src/benchmarks.rs | 25 +- examples/demo/app/tests/env_gtest.rs | 50 ---- examples/demo/client/src/demo_client.rs | 4 +- examples/demo/client/src/env_client.rs | 129 --------- examples/demo/client/src/lib.rs | 2 - .../proxy-client/src/redirect_proxy_client.rs | 4 +- rs/client-gen/src/ctor_generators.rs | 4 +- rs/src/client/gstd_env.rs | 274 ++++++++---------- rs/src/client/gtest_env.rs | 33 +-- rs/src/client/mod.rs | 67 ++++- 11 files changed, 225 insertions(+), 393 deletions(-) delete mode 100644 examples/demo/app/tests/env_gtest.rs delete mode 100644 examples/demo/client/src/env_client.rs diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 226243ff5..53436b6f0 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -1,25 +1,25 @@ { "compute": { - "median": 450514033034 + "median": 450514036959 }, "alloc": { - "0": 564372814, - "12": 567860771, - "143": 727294202, - "986": 828286386, - "10945": 2019063746, - "46367": 6404347595, - "121392": 16859080113, - "317810": 43147405867 + "0": 564376739, + "12": 567864696, + "143": 727298127, + "986": 828290311, + "10945": 2019067671, + "46367": 6404351520, + "121392": 16859084038, + "317810": 43147409792 }, "counter": { - "async_call": 851475728, - "sync_call": 678856494 + "async_call": 851480964, + "sync_call": 678860419 }, "cross_program": { - "median": 2475722344 + "median": 2475019485 }, "redirect": { - "median": 3686731410 + "median": 3688139273 } } \ No newline at end of file diff --git a/benchmarks/src/benchmarks.rs b/benchmarks/src/benchmarks.rs index c5afc5ec4..76b59a321 100644 --- a/benchmarks/src/benchmarks.rs +++ b/benchmarks/src/benchmarks.rs @@ -18,26 +18,15 @@ //! ``` use crate::clients::{ - alloc_stress_client::{ - AllocStressProgram, AllocStressProgramCtors, AllocStressProgramProgram, alloc_stress::*, - }, - compute_stress_client::{ - ComputeStressProgram, ComputeStressProgramCtors, ComputeStressProgramProgram, - compute_stress::*, - }, - counter_bench_client::{ - CounterBenchProgram, CounterBenchProgramCtors, CounterBenchProgramProgram, counter_bench::*, - }, + alloc_stress_client::{AllocStressProgram, AllocStressProgramCtors, alloc_stress::*}, + compute_stress_client::{ComputeStressProgram, ComputeStressProgramCtors, compute_stress::*}, + counter_bench_client::{CounterBenchProgram, CounterBenchProgramCtors, counter_bench::*}, }; use gtest::{System, constants::DEFAULT_USER_ALICE}; use itertools::{Either, Itertools}; -use ping_pong_bench_app::client::{ - PingPong, PingPongCtors, PingPongPayload, PingPongProgram, ping_pong_service::*, -}; -use redirect_client::{RedirectClient, RedirectClientCtors, RedirectClientProgram, redirect::*}; -use redirect_proxy_client::{ - RedirectProxyClient, RedirectProxyClientCtors, RedirectProxyClientProgram, proxy::*, -}; +use ping_pong_bench_app::client::{PingPong, PingPongCtors, PingPongPayload, ping_pong_service::*}; +use redirect_client::{RedirectClient, RedirectClientCtors, redirect::*}; +use redirect_proxy_client::{RedirectProxyClient, RedirectProxyClientCtors, proxy::*}; use sails_rs::{client::*, prelude::*}; use std::{collections::BTreeMap, sync::atomic::AtomicU64}; @@ -215,7 +204,7 @@ async fn redirect_bench() { .get_program_id() .send_one_way() .unwrap(); - let (payload, gas) = extract_reply_and_gas(env.system(), message_id); + let (payload, _gas) = extract_reply_and_gas(env.system(), message_id); let resp = redirect_proxy_client::proxy::io::GetProgramId::decode_reply_with_prefix( "Proxy", payload.as_slice(), diff --git a/examples/demo/app/tests/env_gtest.rs b/examples/demo/app/tests/env_gtest.rs deleted file mode 100644 index 7cf7bf9bc..000000000 --- a/examples/demo/app/tests/env_gtest.rs +++ /dev/null @@ -1,50 +0,0 @@ -use demo_client::env_client::{ - Demo as _, DemoCtors as _, - counter::{Counter as _, events::CounterEvents}, -}; -use futures::StreamExt as _; -use sails_rs::{client::*, prelude::*}; - -const ACTOR_ID: u64 = 42; -#[cfg(debug_assertions)] -pub(crate) const DEMO_WASM_PATH: &str = "../../../target/wasm32-gear/debug/demo.opt.wasm"; -#[cfg(not(debug_assertions))] -pub(crate) const DEMO_WASM_PATH: &str = "../../../target/wasm32-gear/release/demo.opt.wasm"; - -fn create_env() -> (GtestEnv, CodeId, GasUnit) { - use sails_rs::gtest::{MAX_USER_GAS_LIMIT, System}; - - let system = System::new(); - system.init_logger_with_default_filter("gwasm=debug,gtest=info,sails_rs=debug,redirect=debug"); - system.mint_to(ACTOR_ID, 100_000_000_000_000); - // Submit program code into the system - let code_id = system.submit_code_file(DEMO_WASM_PATH); - - // Create a remoting instance for the system - // and set the block run mode to Next, - // cause we don't receive any reply on `Exit` call - let env = GtestEnv::new(system, ACTOR_ID.into()).with_block_run_mode(BlockRunMode::Next); - (env, code_id, MAX_USER_GAS_LIMIT) -} - -#[tokio::test] -async fn env_counter_add_works_via_next_mode() { - let (env, code_id, _gas_limit) = create_env(); - - // deploy DemoProgram - let demo_program = env - .deploy(code_id, vec![]) - .new(Some(42), None) - .await - .unwrap(); - - let mut counter_client = demo_program.counter(); - let counter_listener = counter_client.listener(); - let mut counter_events = counter_listener.listen().await.unwrap(); - - assert_eq!(Ok(52), counter_client.add(10).await); - assert_eq!( - (demo_program.id(), CounterEvents::Added(10)), - counter_events.next().await.unwrap() - ); -} diff --git a/examples/demo/client/src/demo_client.rs b/examples/demo/client/src/demo_client.rs index 05c64c12e..cc3b11f95 100644 --- a/examples/demo/client/src/demo_client.rs +++ b/examples/demo/client/src/demo_client.rs @@ -37,7 +37,9 @@ pub trait DemoClientCtors { type Env: GearEnv; /// Program constructor (called once at the very beginning of the program lifetime) fn default(self) -> PendingCtor; - /// Another program constructor (called once at the very beginning of the program lifetime)#[allow(clippy::new_ret_no_self)] #[allow(clippy::wrong_self_convention)] + /// Another program constructor (called once at the very beginning of the program lifetime) + #[allow(clippy::new_ret_no_self)] + #[allow(clippy::wrong_self_convention)] fn new( self, counter: Option, diff --git a/examples/demo/client/src/env_client.rs b/examples/demo/client/src/env_client.rs deleted file mode 100644 index b9ce0ec1f..000000000 --- a/examples/demo/client/src/env_client.rs +++ /dev/null @@ -1,129 +0,0 @@ -use sails_rs::{client::*, prelude::*}; - -pub trait DemoCtors { - type Env: GearEnv; - - fn default(self) -> PendingCtor; - fn new( - self, - counter: Option, - dog_position: Option<(i32, i32)>, - ) -> PendingCtor; -} - -pub trait Demo { - type Env: GearEnv; - - fn counter(&self) -> Service; -} - -pub struct DemoProgram; - -impl Program for DemoProgram {} - -impl DemoCtors for Deployment { - type Env = E; - - fn default(self) -> PendingCtor { - self.pending_ctor(()) - } - - fn new( - self, - counter: Option, - dog_position: Option<(i32, i32)>, - ) -> PendingCtor { - self.pending_ctor((counter, dog_position)) - } -} - -impl Demo for Actor { - type Env = E; - - fn counter(&self) -> Service { - self.service("Counter") - } -} - -pub mod io { - use super::*; - use sails_rs::client::{CallEncodeDecode, Route}; - sails_rs::io_struct_impl!(Default () -> ()); - sails_rs::io_struct_impl!(New (counter: Option, dog_position: Option<(i32, i32),>) -> ()); -} - -/// Counter Service -pub mod counter { - use super::*; - pub trait Counter { - type Env: GearEnv; - - fn add(&mut self, value: u32) -> PendingCall; - fn sub(&mut self, value: u32) -> PendingCall; - fn value(&self) -> PendingCall; - } - - pub struct CounterImpl; - - impl Counter for Service { - type Env = E; - - fn add(&mut self, value: u32) -> PendingCall { - self.pending_call((value,)) - } - - fn sub(&mut self, value: u32) -> PendingCall { - self.pending_call((value,)) - } - - fn value(&self) -> PendingCall { - self.pending_call(()) - } - } - - pub mod io { - use super::*; - use sails_rs::client::{CallEncodeDecode, Route}; - sails_rs::io_struct_impl!(Add (value: u32) -> u32); - sails_rs::io_struct_impl!(Sub (value: u32) -> u32); - sails_rs::io_struct_impl!(Value () -> u32); - } - - #[cfg(not(target_arch = "wasm32"))] - pub mod events { - use super::*; - #[derive(PartialEq, Debug, Encode, Decode)] - #[codec(crate = sails_rs::scale_codec)] - pub enum CounterEvents { - /// Emitted when a new value is added to the counter - Added(u32), - /// Emitted when a value is subtracted from the counter - Subtracted(u32), - } - impl EventDecode for CounterEvents { - const EVENT_NAMES: &'static [Route] = &["Added", "Subtracted"]; - } - - impl ServiceEvent for CounterImpl { - type Event = CounterEvents; - } - } -} - -#[cfg(feature = "with_mocks")] -#[cfg(not(target_arch = "wasm32"))] -pub mod mockall { - use super::*; - use sails_rs::mockall::*; - mock! { - pub Counter {} - #[allow(refining_impl_trait)] - #[allow(clippy::type_complexity)] - impl counter::Counter for Counter { - type Env = GstdEnv; - fn add (&mut self, value: u32) -> PendingCall; - fn sub (&mut self, value: u32) -> PendingCall; - fn value (& self, ) -> PendingCall; - } - } -} diff --git a/examples/demo/client/src/lib.rs b/examples/demo/client/src/lib.rs index ef3ec097f..4626a0625 100644 --- a/examples/demo/client/src/lib.rs +++ b/examples/demo/client/src/lib.rs @@ -3,8 +3,6 @@ // Incorporate code generated based on the [IDL](/examples/demo/wasm/demo.idl) file include!("demo_client.rs"); -pub mod env_client; - #[cfg(test)] mod tests { use super::*; diff --git a/examples/redirect/proxy-client/src/redirect_proxy_client.rs b/examples/redirect/proxy-client/src/redirect_proxy_client.rs index 387d57af0..4869942cc 100644 --- a/examples/redirect/proxy-client/src/redirect_proxy_client.rs +++ b/examples/redirect/proxy-client/src/redirect_proxy_client.rs @@ -15,7 +15,9 @@ impl RedirectProxyClient for Actor { } pub trait RedirectProxyClientCtors { type Env: GearEnv; - /// Proxy Program's constructor#[allow(clippy::new_ret_no_self)] #[allow(clippy::wrong_self_convention)] + /// Proxy Program's constructor + #[allow(clippy::new_ret_no_self)] + #[allow(clippy::wrong_self_convention)] fn new(self, target: ActorId) -> PendingCtor; } impl RedirectProxyClientCtors for Deployment { diff --git a/rs/client-gen/src/ctor_generators.rs b/rs/client-gen/src/ctor_generators.rs index 4a0e3f607..1dc871bbd 100644 --- a/rs/client-gen/src/ctor_generators.rs +++ b/rs/client-gen/src/ctor_generators.rs @@ -64,8 +64,8 @@ impl<'ast> Visitor<'ast> for CtorGenerator<'_> { if fn_name_snake == "new" { quote_in! {self.trait_ctors_tokens => - #[allow(clippy::new_ret_no_self)] - #[allow(clippy::wrong_self_convention)] + $['\r'] #[allow(clippy::new_ret_no_self)] + $['\r'] #[allow(clippy::wrong_self_convention)] }; } diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index 2d956fe71..fb9be004c 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -1,29 +1,32 @@ use super::*; use ::gstd::{ errors::Error, - msg, msg::{CreateProgramFuture, MessageFuture}, }; -use core::task::ready; #[derive(Default)] pub struct GstdParams { #[cfg(not(feature = "ethexe"))] - gas_limit: Option, - value: Option, - wait_up_to: Option, + pub gas_limit: Option, + pub value: Option, + pub wait_up_to: Option, #[cfg(not(feature = "ethexe"))] - reply_deposit: Option, + pub reply_deposit: Option, #[cfg(not(feature = "ethexe"))] - reply_hook: Option>, - redirect_on_exit: bool, + pub reply_hook: Option>, + pub redirect_on_exit: bool, } -impl GstdParams { - pub fn with_wait_up_to(self, wait_up_to: Option) -> Self { - Self { wait_up_to, ..self } - } +crate::params_for_pending_impl!(GstdEnv, GstdParams { + #[cfg(not(feature = "ethexe"))] + pub gas_limit: GasUnit, + pub value: ValueUnit, + pub wait_up_to: BlockCount, + #[cfg(not(feature = "ethexe"))] + pub reply_deposit: GasUnit, +}); +impl GstdParams { pub fn with_redirect_on_exit(self, redirect_on_exit: bool) -> Self { Self { redirect_on_exit, @@ -31,54 +34,16 @@ impl GstdParams { } } - pub fn wait_up_to(&self) -> Option { - self.wait_up_to - } - - pub fn redirect_on_exit(&self) -> bool { - self.redirect_on_exit - } -} - -impl Clone for GstdParams { - fn clone(&self) -> Self { - Self { - gas_limit: self.gas_limit.clone(), - value: self.value.clone(), - wait_up_to: self.wait_up_to.clone(), - reply_deposit: self.reply_deposit.clone(), - reply_hook: None, - redirect_on_exit: self.redirect_on_exit.clone(), - } - } -} - -#[cfg(not(feature = "ethexe"))] -impl GstdParams { - pub fn with_reply_deposit(self, reply_deposit: Option) -> Self { - Self { - reply_deposit, - ..self - } - } - + #[cfg(not(feature = "ethexe"))] pub fn with_reply_hook(self, f: F) -> Self { Self { reply_hook: Some(Box::new(f)), ..self } } - - pub fn reply_deposit(&self) -> Option { - self.reply_deposit - } } impl PendingCall { - pub fn with_wait_up_to(self, wait_up_to: Option) -> Self { - self.with_params(|params| params.with_wait_up_to(wait_up_to)) - } - /// Set `redirect_on_exit` flag to `true`` /// /// This flag is used to redirect a message to a new program when the target program exits @@ -91,6 +56,11 @@ impl PendingCall { pub fn with_redirect_on_exit(self, redirect_on_exit: bool) -> Self { self.with_params(|params| params.with_redirect_on_exit(redirect_on_exit)) } + + #[cfg(not(feature = "ethexe"))] + pub fn with_reply_hook(self, f: F) -> Self { + self.with_params(|params| params.with_reply_hook(f)) + } } #[derive(Debug, Default, Clone)] @@ -105,106 +75,106 @@ impl GearEnv for GstdEnv { type MessageState = core::future::Ready, Self::Error>>; } -#[cfg(not(feature = "ethexe"))] -pub(crate) fn send_for_reply_future( - target: ActorId, - payload: &[u8], - params: GstdParams, -) -> Result { - let value = params.value.unwrap_or(0); - // here can be a redirect target - let mut message_future = if let Some(gas_limit) = params.gas_limit { - msg::send_bytes_with_gas_for_reply( - target, - payload, - gas_limit, - value, - params.reply_deposit.unwrap_or_default(), - )? - } else { - msg::send_bytes_for_reply( - target, - payload, - value, - params.reply_deposit.unwrap_or_default(), - )? - }; - - message_future = message_future.up_to(params.wait_up_to)?; - - if let Some(reply_hook) = params.reply_hook { - message_future = message_future.handle_reply(reply_hook)?; +impl PendingCall { + pub fn send_one_way(&mut self) -> Result { + let Some(route) = self.route else { + return Err(Error::Decode("PendingCall route is not set".into())); + }; + + let args = self + .args + .take() + .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); + let payload = T::encode_params_with_prefix(route, &args); + let params = self.params.take().unwrap_or_default(); + + let value = params.value.unwrap_or(0); + + #[cfg(not(feature = "ethexe"))] + if let Some(gas_limit) = params.gas_limit { + return ::gcore::msg::send_with_gas( + self.destination, + payload.as_slice(), + gas_limit, + value, + ) + .map_err(Error::Core); + } + + ::gcore::msg::send(self.destination, payload.as_slice(), value).map_err(Error::Core) } - Ok(message_future) } -#[cfg(feature = "ethexe")] -pub(crate) fn send_for_reply_future( - target: ActorId, - payload: &[u8], - params: GstdParams, -) -> Result { - let value = params.value.unwrap_or(0); - // here can be a redirect target - let mut message_future = msg::send_bytes_for_reply(target, payload, value)?; +#[cfg(target_arch = "wasm32")] +const _: () = { + use core::task::ready; - message_future = message_future.up_to(params.wait_up_to)?; + #[cfg(not(feature = "ethexe"))] + pub(crate) fn send_for_reply_future( + target: ActorId, + payload: &[u8], + params: &mut GstdParams, + ) -> Result { + let value = params.value.unwrap_or(0); + // here can be a redirect target + let mut message_future = if let Some(gas_limit) = params.gas_limit { + ::gstd::msg::send_bytes_with_gas_for_reply( + target, + payload, + gas_limit, + value, + params.reply_deposit.unwrap_or_default(), + )? + } else { + ::gstd::msg::send_bytes_for_reply( + target, + payload, + value, + params.reply_deposit.unwrap_or_default(), + )? + }; + + message_future = message_future.up_to(params.wait_up_to)?; + + if let Some(reply_hook) = params.reply_hook.take() { + message_future = message_future.handle_reply(reply_hook)?; + } + Ok(message_future) + } - Ok(message_future) -} + #[cfg(feature = "ethexe")] + fn send_for_reply_future( + target: ActorId, + payload: &[u8], + params: &mut GstdParams, + ) -> Result { + let value = params.value.unwrap_or(0); + // here can be a redirect target + let mut message_future = ::gstd::msg::send_bytes_for_reply(target, payload, value)?; -pub(crate) fn send_for_reply>( - target: ActorId, - payload: T, - params: GstdParams, -) -> Result { - let redirect_on_exit = params.redirect_on_exit; - // clone params w/o reply_hook - let params_cloned = params.clone(); - // send message - let future = send_for_reply_future(target, payload.as_ref(), params)?; - if redirect_on_exit { - let created_block = params_cloned.wait_up_to.map(|_| gstd::exec::block_height()); - Ok(GtsdFuture::MessageWithRedirect { - created_block, - future, - params: params_cloned, - payload: payload.as_ref().to_vec(), - target: target, - }) - } else { - Ok(GtsdFuture::Message { future }) - } -} + message_future = message_future.up_to(params.wait_up_to)?; -#[cfg(target_arch = "wasm32")] -const _: () = { - impl PendingCall { - pub fn send(mut self) -> Result { - let route = self - .route - .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let params = self.params.unwrap_or_default(); - let args = self - .args - .take() - .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(route, &args); - - let value = params.value.unwrap_or(0); - - #[cfg(feature = "ethexe")] - { - ::gcore::msg::send(self.destination, payload.as_slice(), value).map_err(Error::Core) - } + Ok(message_future) + } - #[cfg(not(feature = "ethexe"))] - if let Some(gas_limit) = params.gas_limit { - ::gcore::msg::send_with_gas(self.destination, payload.as_slice(), gas_limit, value) - .map_err(Error::Core) - } else { - ::gcore::msg::send(self.destination, payload.as_slice(), value).map_err(Error::Core) - } + pub(crate) fn send_for_reply( + target: ActorId, + payload: Vec, + mut params: GstdParams, + ) -> Result { + // send message + let future = send_for_reply_future(target, payload.as_ref(), &mut params)?; + if params.redirect_on_exit { + let created_block = params.wait_up_to.map(|_| gstd::exec::block_height()); + Ok(GtsdFuture::MessageWithRedirect { + created_block, + future, + params, + payload, + target, + }) + } else { + Ok(GtsdFuture::Message { future }) } } @@ -314,14 +284,18 @@ const _: () = { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if self.state.is_none() { // Send message - let payload = self.encode_ctor(); + let args = self + .args + .take() + .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); + let payload = T::encode_params(&args); let params = self.params.take().unwrap_or_default(); let value = params.value.unwrap_or(0); let salt = self.salt.take().unwrap(); #[cfg(not(feature = "ethexe"))] let program_future = if let Some(gas_limit) = params.gas_limit { - gstd::prog::create_program_bytes_with_gas_for_reply( + ::gstd::prog::create_program_bytes_with_gas_for_reply( self.code_id, salt, payload, @@ -330,7 +304,7 @@ const _: () = { params.reply_deposit.unwrap_or_default(), )? } else { - gstd::prog::create_program_bytes_for_reply( + ::gstd::prog::create_program_bytes_for_reply( self.code_id, salt, payload, @@ -339,8 +313,12 @@ const _: () = { )? }; #[cfg(feature = "ethexe")] - let mut program_future = - gstd::prog::create_program_bytes_for_reply(self.code_id, salt, payload, value)?; + let program_future = ::gstd::prog::create_program_bytes_for_reply( + self.code_id, + salt, + payload, + value, + )?; // self.program_id = Some(program_future.program_id); self.state = Some(GtsdFuture::CreateProgram { diff --git a/rs/src/client/gtest_env.rs b/rs/src/client/gtest_env.rs index 689409ae5..9d2823d54 100644 --- a/rs/src/client/gtest_env.rs +++ b/rs/src/client/gtest_env.rs @@ -234,7 +234,7 @@ impl GearEnv for GtestEnv { type MessageState = ReplyReceiver; } impl PendingCall { - pub fn send_one_way(mut self) -> Result { + pub fn send_one_way(&mut self) -> Result { let Some(route) = self.route else { return Err(TestError::ScaleCodecError( "PendingCall route is not set".into(), @@ -257,24 +257,7 @@ impl PendingCall { } pub fn send_message(mut self) -> Result { - let Some(route) = self.route else { - return Err(TestError::ScaleCodecError( - "PendingCall route is not set".into(), - ))?; - }; - if self.state.is_some() { - panic!("{PENDING_CALL_INVALID_STATE}"); - } - // Send message - let args = self - .args - .take() - .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(route, &args); - let params = self.params.take().unwrap_or_default(); - - let message_id = self.env.send_message(self.destination, payload, params)?; - log::debug!("PendingCall: send message {message_id:?}"); + let message_id = self.send_one_way()?; self.state = Some(self.env.message_reply_from_next_blocks(message_id)); Ok(self) } @@ -338,7 +321,11 @@ impl PendingCtor { panic!("{PENDING_CTOR_INVALID_STATE}"); } // Send message - let payload = self.encode_ctor(); + let args = self + .args + .take() + .unwrap_or_else(|| panic!("{PENDING_CTOR_INVALID_STATE}")); + let payload = T::encode_params(&args); let params = self.params.take().unwrap_or_default(); let salt = self.salt.take().unwrap_or_default(); let send_res = self @@ -365,7 +352,11 @@ impl Future for PendingCtor { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if self.state.is_none() { // Send message - let payload = self.encode_ctor(); + let args = self + .args + .take() + .unwrap_or_else(|| panic!("{PENDING_CTOR_INVALID_STATE}")); + let payload = T::encode_params(&args); let params = self.params.take().unwrap_or_default(); let salt = self.salt.take().unwrap_or_default(); let send_res = self diff --git a/rs/src/client/mod.rs b/rs/src/client/mod.rs index 90c783cca..c7751b8be 100644 --- a/rs/src/client/mod.rs +++ b/rs/src/client/mod.rs @@ -279,14 +279,6 @@ impl PendingCtor { self.params = Some(f(self.params.unwrap_or_default())); self } - - fn encode_ctor(&self) -> Vec { - if let Some(args) = &self.args { - T::encode_params(args) - } else { - vec![] - } - } } pub trait CallEncodeDecode { @@ -396,6 +388,48 @@ macro_rules! params_struct_impl { }; } +#[macro_export] +macro_rules! params_for_pending_impl { + ( + $env:ident, + $name:ident { $( $(#[$attr:meta])* $vis:vis $field:ident: $ty:ty ),* $(,)? } + ) => { + impl $name { + $( + paste::paste! { + $(#[$attr])* + pub fn [](mut self, $field: $ty) -> Self { + self.$field = Some($field); + self + } + } + )* + } + + impl PendingCtor<$env, A, T> { + $( + paste::paste! { + $(#[$attr])* + pub fn [](self, $field: $ty) -> Self { + self.with_params(|params| params.[]($field)) + } + } + )* + } + + impl PendingCall<$env, T> { + $( + paste::paste! { + $(#[$attr])* + pub fn [](self, $field: $ty) -> Self { + self.with_params(|params| params.[]($field)) + } + } + )* + } + }; +} + #[macro_export] macro_rules! io_struct_impl { ( @@ -418,6 +452,7 @@ macro_rules! io_struct_impl { }; } +#[allow(unused_macros)] macro_rules! str_scale_encode { ($s:ident) => {{ const S: &str = stringify!($s); @@ -497,7 +532,23 @@ mod tests { let add = Add::encode_params(42); assert_eq!(add, &[12, 65, 100, 100, 42, 0, 0, 0]); + let value = Add::encode_params_with_prefix("Counter", 42); + assert_eq!( + value, + &[ + 28, 67, 111, 117, 110, 116, 101, 114, 12, 65, 100, 100, 42, 0, 0, 0 + ] + ); + let value = Value::encode_params(); assert_eq!(value, &[20, 86, 97, 108, 117, 101]); + + let value = Value::encode_params_with_prefix("Counter"); + assert_eq!( + value, + &[ + 28, 67, 111, 117, 110, 116, 101, 114, 20, 86, 97, 108, 117, 101 + ] + ); } } From 501708eed0971ea4f86ca47bb3b0a8198325c867 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 8 Sep 2025 16:31:25 +0200 Subject: [PATCH 19/66] fix: client gen full path --- .../ping-pong/app/src/ping_pong_client.rs | 44 +++-- benchmarks/src/alloc_stress_program.rs | 40 ++-- benchmarks/src/compute_stress_program.rs | 47 +++-- benchmarks/src/counter_bench_program.rs | 42 ++-- examples/demo/client/src/demo_client.rs | 186 ++++++++++-------- .../no-svcs-prog/wasm/src/no_svcs_prog.rs | 16 +- .../redirect/client/src/redirect_client.rs | 40 ++-- .../proxy-client/src/redirect_proxy_client.rs | 36 ++-- .../rmrk/resource/app/src/rmrk_catalog.rs | 68 ++++--- .../rmrk/resource/wasm/src/rmrk_resource.rs | 50 +++-- rs/client-gen/src/ctor_generators.rs | 8 +- rs/client-gen/src/events_generator.rs | 2 +- rs/client-gen/src/mock_generator.rs | 8 +- rs/client-gen/src/root_generator.rs | 10 +- rs/client-gen/src/service_generators.rs | 16 +- .../snapshots/generator__basic_works.snap | 38 ++-- .../snapshots/generator__events_works.snap | 37 ++-- .../snapshots/generator__external_types.snap | 40 ++-- .../tests/snapshots/generator__full.snap | 48 ++--- .../generator__full_with_sails_path.snap | 82 ++++---- .../generator__multiple_services.snap | 52 +++-- .../snapshots/generator__nonzero_works.snap | 36 +++- .../snapshots/generator__rmrk_works.snap | 68 ++++--- 23 files changed, 624 insertions(+), 390 deletions(-) diff --git a/benchmarks/ping-pong/app/src/ping_pong_client.rs b/benchmarks/ping-pong/app/src/ping_pong_client.rs index e212b17b4..5afb4db10 100644 --- a/benchmarks/ping-pong/app/src/ping_pong_client.rs +++ b/benchmarks/ping-pong/app/src/ping_pong_client.rs @@ -2,24 +2,34 @@ #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; pub struct PingPongProgram; -impl Program for PingPongProgram {} +impl sails_rs::client::Program for PingPongProgram {} pub trait PingPong { - type Env: GearEnv; - fn ping_pong_service(&self) -> Service; + type Env: sails_rs::client::GearEnv; + fn ping_pong_service( + &self, + ) -> sails_rs::client::Service; } -impl PingPong for Actor { +impl PingPong for sails_rs::client::Actor { type Env = E; - fn ping_pong_service(&self) -> Service { + fn ping_pong_service( + &self, + ) -> sails_rs::client::Service { self.service(stringify!(PingPongService)) } } pub trait PingPongCtors { - type Env: GearEnv; - fn new_for_bench(self) -> PendingCtor; + type Env: sails_rs::client::GearEnv; + fn new_for_bench( + self, + ) -> sails_rs::client::PendingCtor; } -impl PingPongCtors for Deployment { +impl PingPongCtors + for sails_rs::client::Deployment +{ type Env = E; - fn new_for_bench(self) -> PendingCtor { + fn new_for_bench( + self, + ) -> sails_rs::client::PendingCtor { self.pending_ctor(()) } } @@ -32,13 +42,21 @@ pub mod io { pub mod ping_pong_service { use super::*; pub trait PingPongService { - type Env: GearEnv; - fn ping(&mut self, payload: PingPongPayload) -> PendingCall; + type Env: sails_rs::client::GearEnv; + fn ping( + &mut self, + payload: PingPongPayload, + ) -> sails_rs::client::PendingCall; } pub struct PingPongServiceImpl; - impl PingPongService for Service { + impl PingPongService + for sails_rs::client::Service + { type Env = E; - fn ping(&mut self, payload: PingPongPayload) -> PendingCall { + fn ping( + &mut self, + payload: PingPongPayload, + ) -> sails_rs::client::PendingCall { self.pending_call((payload,)) } } diff --git a/benchmarks/src/alloc_stress_program.rs b/benchmarks/src/alloc_stress_program.rs index 99f6b2bbf..8e97c3988 100644 --- a/benchmarks/src/alloc_stress_program.rs +++ b/benchmarks/src/alloc_stress_program.rs @@ -2,24 +2,32 @@ #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; pub struct AllocStressProgramProgram; -impl Program for AllocStressProgramProgram {} +impl sails_rs::client::Program for AllocStressProgramProgram {} pub trait AllocStressProgram { - type Env: GearEnv; - fn alloc_stress(&self) -> Service; + type Env: sails_rs::client::GearEnv; + fn alloc_stress(&self) -> sails_rs::client::Service; } -impl AllocStressProgram for Actor { +impl AllocStressProgram + for sails_rs::client::Actor +{ type Env = E; - fn alloc_stress(&self) -> Service { + fn alloc_stress(&self) -> sails_rs::client::Service { self.service(stringify!(AllocStress)) } } pub trait AllocStressProgramCtors { - type Env: GearEnv; - fn new_for_bench(self) -> PendingCtor; + type Env: sails_rs::client::GearEnv; + fn new_for_bench( + self, + ) -> sails_rs::client::PendingCtor; } -impl AllocStressProgramCtors for Deployment { +impl AllocStressProgramCtors + for sails_rs::client::Deployment +{ type Env = E; - fn new_for_bench(self) -> PendingCtor { + fn new_for_bench( + self, + ) -> sails_rs::client::PendingCtor { self.pending_ctor(()) } } @@ -32,13 +40,19 @@ pub mod io { pub mod alloc_stress { use super::*; pub trait AllocStress { - type Env: GearEnv; - fn alloc_stress(&mut self, n: u32) -> PendingCall; + type Env: sails_rs::client::GearEnv; + fn alloc_stress( + &mut self, + n: u32, + ) -> sails_rs::client::PendingCall; } pub struct AllocStressImpl; - impl AllocStress for Service { + impl AllocStress for sails_rs::client::Service { type Env = E; - fn alloc_stress(&mut self, n: u32) -> PendingCall { + fn alloc_stress( + &mut self, + n: u32, + ) -> sails_rs::client::PendingCall { self.pending_call((n,)) } } diff --git a/benchmarks/src/compute_stress_program.rs b/benchmarks/src/compute_stress_program.rs index 27c20cf5e..6fbfb5380 100644 --- a/benchmarks/src/compute_stress_program.rs +++ b/benchmarks/src/compute_stress_program.rs @@ -2,24 +2,37 @@ #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; pub struct ComputeStressProgramProgram; -impl Program for ComputeStressProgramProgram {} +impl sails_rs::client::Program for ComputeStressProgramProgram {} pub trait ComputeStressProgram { - type Env: GearEnv; - fn compute_stress(&self) -> Service; + type Env: sails_rs::client::GearEnv; + fn compute_stress( + &self, + ) -> sails_rs::client::Service; } -impl ComputeStressProgram for Actor { +impl ComputeStressProgram + for sails_rs::client::Actor +{ type Env = E; - fn compute_stress(&self) -> Service { + fn compute_stress( + &self, + ) -> sails_rs::client::Service { self.service(stringify!(ComputeStress)) } } pub trait ComputeStressProgramCtors { - type Env: GearEnv; - fn new_for_bench(self) -> PendingCtor; + type Env: sails_rs::client::GearEnv; + fn new_for_bench( + self, + ) -> sails_rs::client::PendingCtor; } -impl ComputeStressProgramCtors for Deployment { +impl ComputeStressProgramCtors + for sails_rs::client::Deployment +{ type Env = E; - fn new_for_bench(self) -> PendingCtor { + fn new_for_bench( + self, + ) -> sails_rs::client::PendingCtor + { self.pending_ctor(()) } } @@ -32,13 +45,21 @@ pub mod io { pub mod compute_stress { use super::*; pub trait ComputeStress { - type Env: GearEnv; - fn compute_stress(&mut self, n: u32) -> PendingCall; + type Env: sails_rs::client::GearEnv; + fn compute_stress( + &mut self, + n: u32, + ) -> sails_rs::client::PendingCall; } pub struct ComputeStressImpl; - impl ComputeStress for Service { + impl ComputeStress + for sails_rs::client::Service + { type Env = E; - fn compute_stress(&mut self, n: u32) -> PendingCall { + fn compute_stress( + &mut self, + n: u32, + ) -> sails_rs::client::PendingCall { self.pending_call((n,)) } } diff --git a/benchmarks/src/counter_bench_program.rs b/benchmarks/src/counter_bench_program.rs index 52c5f6eab..94e430949 100644 --- a/benchmarks/src/counter_bench_program.rs +++ b/benchmarks/src/counter_bench_program.rs @@ -2,24 +2,36 @@ #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; pub struct CounterBenchProgramProgram; -impl Program for CounterBenchProgramProgram {} +impl sails_rs::client::Program for CounterBenchProgramProgram {} pub trait CounterBenchProgram { - type Env: GearEnv; - fn counter_bench(&self) -> Service; + type Env: sails_rs::client::GearEnv; + fn counter_bench( + &self, + ) -> sails_rs::client::Service; } -impl CounterBenchProgram for Actor { +impl CounterBenchProgram + for sails_rs::client::Actor +{ type Env = E; - fn counter_bench(&self) -> Service { + fn counter_bench( + &self, + ) -> sails_rs::client::Service { self.service(stringify!(CounterBench)) } } pub trait CounterBenchProgramCtors { - type Env: GearEnv; - fn new_for_bench(self) -> PendingCtor; + type Env: sails_rs::client::GearEnv; + fn new_for_bench( + self, + ) -> sails_rs::client::PendingCtor; } -impl CounterBenchProgramCtors for Deployment { +impl CounterBenchProgramCtors + for sails_rs::client::Deployment +{ type Env = E; - fn new_for_bench(self) -> PendingCtor { + fn new_for_bench( + self, + ) -> sails_rs::client::PendingCtor { self.pending_ctor(()) } } @@ -32,17 +44,17 @@ pub mod io { pub mod counter_bench { use super::*; pub trait CounterBench { - type Env: GearEnv; - fn inc(&mut self) -> PendingCall; - fn inc_async(&mut self) -> PendingCall; + type Env: sails_rs::client::GearEnv; + fn inc(&mut self) -> sails_rs::client::PendingCall; + fn inc_async(&mut self) -> sails_rs::client::PendingCall; } pub struct CounterBenchImpl; - impl CounterBench for Service { + impl CounterBench for sails_rs::client::Service { type Env = E; - fn inc(&mut self) -> PendingCall { + fn inc(&mut self) -> sails_rs::client::PendingCall { self.pending_call(()) } - fn inc_async(&mut self) -> PendingCall { + fn inc_async(&mut self) -> sails_rs::client::PendingCall { self.pending_call(()) } } diff --git a/examples/demo/client/src/demo_client.rs b/examples/demo/client/src/demo_client.rs index cc3b11f95..b27bb48d7 100644 --- a/examples/demo/client/src/demo_client.rs +++ b/examples/demo/client/src/demo_client.rs @@ -2,41 +2,41 @@ #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; pub struct DemoClientProgram; -impl Program for DemoClientProgram {} +impl sails_rs::client::Program for DemoClientProgram {} pub trait DemoClient { - type Env: GearEnv; - fn ping_pong(&self) -> Service; - fn counter(&self) -> Service; - fn dog(&self) -> Service; - fn references(&self) -> Service; - fn this_that(&self) -> Service; - fn value_fee(&self) -> Service; + type Env: sails_rs::client::GearEnv; + fn ping_pong(&self) -> sails_rs::client::Service; + fn counter(&self) -> sails_rs::client::Service; + fn dog(&self) -> sails_rs::client::Service; + fn references(&self) -> sails_rs::client::Service; + fn this_that(&self) -> sails_rs::client::Service; + fn value_fee(&self) -> sails_rs::client::Service; } -impl DemoClient for Actor { +impl DemoClient for sails_rs::client::Actor { type Env = E; - fn ping_pong(&self) -> Service { + fn ping_pong(&self) -> sails_rs::client::Service { self.service(stringify!(PingPong)) } - fn counter(&self) -> Service { + fn counter(&self) -> sails_rs::client::Service { self.service(stringify!(Counter)) } - fn dog(&self) -> Service { + fn dog(&self) -> sails_rs::client::Service { self.service(stringify!(Dog)) } - fn references(&self) -> Service { + fn references(&self) -> sails_rs::client::Service { self.service(stringify!(References)) } - fn this_that(&self) -> Service { + fn this_that(&self) -> sails_rs::client::Service { self.service(stringify!(ThisThat)) } - fn value_fee(&self) -> Service { + fn value_fee(&self) -> sails_rs::client::Service { self.service(stringify!(ValueFee)) } } pub trait DemoClientCtors { - type Env: GearEnv; + type Env: sails_rs::client::GearEnv; /// Program constructor (called once at the very beginning of the program lifetime) - fn default(self) -> PendingCtor; + fn default(self) -> sails_rs::client::PendingCtor; /// Another program constructor (called once at the very beginning of the program lifetime) #[allow(clippy::new_ret_no_self)] #[allow(clippy::wrong_self_convention)] @@ -44,18 +44,20 @@ pub trait DemoClientCtors { self, counter: Option, dog_position: Option<(i32, i32)>, - ) -> PendingCtor; + ) -> sails_rs::client::PendingCtor; } -impl DemoClientCtors for Deployment { +impl DemoClientCtors + for sails_rs::client::Deployment +{ type Env = E; - fn default(self) -> PendingCtor { + fn default(self) -> sails_rs::client::PendingCtor { self.pending_ctor(()) } fn new( self, counter: Option, dog_position: Option<(i32, i32)>, - ) -> PendingCtor { + ) -> sails_rs::client::PendingCtor { self.pending_ctor((counter, dog_position)) } } @@ -69,13 +71,13 @@ pub mod io { pub mod ping_pong { use super::*; pub trait PingPong { - type Env: GearEnv; - fn ping(&mut self, input: String) -> PendingCall; + type Env: sails_rs::client::GearEnv; + fn ping(&mut self, input: String) -> sails_rs::client::PendingCall; } pub struct PingPongImpl; - impl PingPong for Service { + impl PingPong for sails_rs::client::Service { type Env = E; - fn ping(&mut self, input: String) -> PendingCall { + fn ping(&mut self, input: String) -> sails_rs::client::PendingCall { self.pending_call((input,)) } } @@ -89,24 +91,24 @@ pub mod ping_pong { pub mod counter { use super::*; pub trait Counter { - type Env: GearEnv; + type Env: sails_rs::client::GearEnv; /// Add a value to the counter - fn add(&mut self, value: u32) -> PendingCall; + fn add(&mut self, value: u32) -> sails_rs::client::PendingCall; /// Substract a value from the counter - fn sub(&mut self, value: u32) -> PendingCall; + fn sub(&mut self, value: u32) -> sails_rs::client::PendingCall; /// Get the current value - fn value(&self) -> PendingCall; + fn value(&self) -> sails_rs::client::PendingCall; } pub struct CounterImpl; - impl Counter for Service { + impl Counter for sails_rs::client::Service { type Env = E; - fn add(&mut self, value: u32) -> PendingCall { + fn add(&mut self, value: u32) -> sails_rs::client::PendingCall { self.pending_call((value,)) } - fn sub(&mut self, value: u32) -> PendingCall { + fn sub(&mut self, value: u32) -> sails_rs::client::PendingCall { self.pending_call((value,)) } - fn value(&self) -> PendingCall { + fn value(&self) -> sails_rs::client::PendingCall { self.pending_call(()) } } @@ -132,7 +134,7 @@ pub mod counter { impl EventDecode for CounterEvents { const EVENT_NAMES: &'static [Route] = &["Added", "Subtracted"]; } - impl ServiceEvent for CounterImpl { + impl sails_rs::client::ServiceEvent for CounterImpl { type Event = CounterEvents; } } @@ -141,25 +143,25 @@ pub mod counter { pub mod dog { use super::*; pub trait Dog { - type Env: GearEnv; - fn make_sound(&mut self) -> PendingCall; - fn walk(&mut self, dx: i32, dy: i32) -> PendingCall; - fn avg_weight(&self) -> PendingCall; - fn position(&self) -> PendingCall; + type Env: sails_rs::client::GearEnv; + fn make_sound(&mut self) -> sails_rs::client::PendingCall; + fn walk(&mut self, dx: i32, dy: i32) -> sails_rs::client::PendingCall; + fn avg_weight(&self) -> sails_rs::client::PendingCall; + fn position(&self) -> sails_rs::client::PendingCall; } pub struct DogImpl; - impl Dog for Service { + impl Dog for sails_rs::client::Service { type Env = E; - fn make_sound(&mut self) -> PendingCall { + fn make_sound(&mut self) -> sails_rs::client::PendingCall { self.pending_call(()) } - fn walk(&mut self, dx: i32, dy: i32) -> PendingCall { + fn walk(&mut self, dx: i32, dy: i32) -> sails_rs::client::PendingCall { self.pending_call((dx, dy)) } - fn avg_weight(&self) -> PendingCall { + fn avg_weight(&self) -> sails_rs::client::PendingCall { self.pending_call(()) } - fn position(&self) -> PendingCall { + fn position(&self) -> sails_rs::client::PendingCall { self.pending_call(()) } } @@ -184,7 +186,7 @@ pub mod dog { impl EventDecode for DogEvents { const EVENT_NAMES: &'static [Route] = &["Barked", "Walked"]; } - impl ServiceEvent for DogImpl { + impl sails_rs::client::ServiceEvent for DogImpl { type Event = DogEvents; } } @@ -193,41 +195,47 @@ pub mod dog { pub mod references { use super::*; pub trait References { - type Env: GearEnv; - fn add(&mut self, v: u32) -> PendingCall; - fn add_byte(&mut self, byte: u8) -> PendingCall; - fn guess_num(&mut self, number: u8) -> PendingCall; - fn incr(&mut self) -> PendingCall; - fn set_num(&mut self, number: u8) -> PendingCall; - fn baked(&self) -> PendingCall; - fn last_byte(&self) -> PendingCall; - fn message(&self) -> PendingCall; + type Env: sails_rs::client::GearEnv; + fn add(&mut self, v: u32) -> sails_rs::client::PendingCall; + fn add_byte(&mut self, byte: u8) -> sails_rs::client::PendingCall; + fn guess_num( + &mut self, + number: u8, + ) -> sails_rs::client::PendingCall; + fn incr(&mut self) -> sails_rs::client::PendingCall; + fn set_num(&mut self, number: u8) -> sails_rs::client::PendingCall; + fn baked(&self) -> sails_rs::client::PendingCall; + fn last_byte(&self) -> sails_rs::client::PendingCall; + fn message(&self) -> sails_rs::client::PendingCall; } pub struct ReferencesImpl; - impl References for Service { + impl References for sails_rs::client::Service { type Env = E; - fn add(&mut self, v: u32) -> PendingCall { + fn add(&mut self, v: u32) -> sails_rs::client::PendingCall { self.pending_call((v,)) } - fn add_byte(&mut self, byte: u8) -> PendingCall { + fn add_byte(&mut self, byte: u8) -> sails_rs::client::PendingCall { self.pending_call((byte,)) } - fn guess_num(&mut self, number: u8) -> PendingCall { + fn guess_num( + &mut self, + number: u8, + ) -> sails_rs::client::PendingCall { self.pending_call((number,)) } - fn incr(&mut self) -> PendingCall { + fn incr(&mut self) -> sails_rs::client::PendingCall { self.pending_call(()) } - fn set_num(&mut self, number: u8) -> PendingCall { + fn set_num(&mut self, number: u8) -> sails_rs::client::PendingCall { self.pending_call((number,)) } - fn baked(&self) -> PendingCall { + fn baked(&self) -> sails_rs::client::PendingCall { self.pending_call(()) } - fn last_byte(&self) -> PendingCall { + fn last_byte(&self) -> sails_rs::client::PendingCall { self.pending_call(()) } - fn message(&self) -> PendingCall { + fn message(&self) -> sails_rs::client::PendingCall { self.pending_call(()) } } @@ -248,23 +256,29 @@ pub mod references { pub mod this_that { use super::*; pub trait ThisThat { - type Env: GearEnv; - fn do_that(&mut self, param: DoThatParam) -> PendingCall; + type Env: sails_rs::client::GearEnv; + fn do_that( + &mut self, + param: DoThatParam, + ) -> sails_rs::client::PendingCall; fn do_this( &mut self, p1: u32, p2: String, p3: (Option, NonZeroU8), p4: TupleStruct, - ) -> PendingCall; - fn noop(&mut self) -> PendingCall; - fn that(&self) -> PendingCall; - fn this(&self) -> PendingCall; + ) -> sails_rs::client::PendingCall; + fn noop(&mut self) -> sails_rs::client::PendingCall; + fn that(&self) -> sails_rs::client::PendingCall; + fn this(&self) -> sails_rs::client::PendingCall; } pub struct ThisThatImpl; - impl ThisThat for Service { + impl ThisThat for sails_rs::client::Service { type Env = E; - fn do_that(&mut self, param: DoThatParam) -> PendingCall { + fn do_that( + &mut self, + param: DoThatParam, + ) -> sails_rs::client::PendingCall { self.pending_call((param,)) } fn do_this( @@ -273,16 +287,16 @@ pub mod this_that { p2: String, p3: (Option, NonZeroU8), p4: TupleStruct, - ) -> PendingCall { + ) -> sails_rs::client::PendingCall { self.pending_call((p1, p2, p3, p4)) } - fn noop(&mut self) -> PendingCall { + fn noop(&mut self) -> sails_rs::client::PendingCall { self.pending_call(()) } - fn that(&self) -> PendingCall { + fn that(&self) -> sails_rs::client::PendingCall { self.pending_call(()) } - fn this(&self) -> PendingCall { + fn this(&self) -> sails_rs::client::PendingCall { self.pending_call(()) } } @@ -300,19 +314,19 @@ pub mod this_that { pub mod value_fee { use super::*; pub trait ValueFee { - type Env: GearEnv; + type Env: sails_rs::client::GearEnv; /// Return flag if fee taken and remain value, /// using special type `CommandReply` fn do_something_and_take_fee( &mut self, - ) -> PendingCall; + ) -> sails_rs::client::PendingCall; } pub struct ValueFeeImpl; - impl ValueFee for Service { + impl ValueFee for sails_rs::client::Service { type Env = E; fn do_something_and_take_fee( &mut self, - ) -> PendingCall { + ) -> sails_rs::client::PendingCall { self.pending_call(()) } } @@ -333,7 +347,7 @@ pub mod value_fee { impl EventDecode for ValueFeeEvents { const EVENT_NAMES: &'static [Route] = &["Withheld"]; } - impl ServiceEvent for ValueFeeImpl { + impl sails_rs::client::ServiceEvent for ValueFeeImpl { type Event = ValueFeeEvents; } } @@ -386,10 +400,10 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub PingPong {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl ping_pong::PingPong for PingPong { type Env = GstdEnv; fn ping (&mut self, input: String) -> PendingCall; } } - mock! { pub Counter {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl counter::Counter for Counter { type Env = GstdEnv; fn add (&mut self, value: u32) -> PendingCall;fn sub (&mut self, value: u32) -> PendingCall;fn value (& self, ) -> PendingCall; } } - mock! { pub Dog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl dog::Dog for Dog { type Env = GstdEnv; fn make_sound (&mut self, ) -> PendingCall;fn walk (&mut self, dx: i32, dy: i32) -> PendingCall;fn avg_weight (& self, ) -> PendingCall;fn position (& self, ) -> PendingCall; } } - mock! { pub References {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl references::References for References { type Env = GstdEnv; fn add (&mut self, v: u32) -> PendingCall;fn add_byte (&mut self, byte: u8) -> PendingCall;fn guess_num (&mut self, number: u8) -> PendingCall;fn incr (&mut self, ) -> PendingCall;fn set_num (&mut self, number: u8) -> PendingCall;fn baked (& self, ) -> PendingCall;fn last_byte (& self, ) -> PendingCall;fn message (& self, ) -> PendingCall; } } - mock! { pub ThisThat {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl this_that::ThisThat for ThisThat { type Env = GstdEnv; fn do_that (&mut self, param: DoThatParam) -> PendingCall;fn do_this (&mut self, p1: u32, p2: String, p3: (Option,NonZeroU8,), p4: TupleStruct) -> PendingCall;fn noop (&mut self, ) -> PendingCall;fn that (& self, ) -> PendingCall;fn this (& self, ) -> PendingCall; } } - mock! { pub ValueFee {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl value_fee::ValueFee for ValueFee { type Env = GstdEnv; fn do_something_and_take_fee (&mut self, ) -> PendingCall; } } + mock! { pub PingPong {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl ping_pong::PingPong for PingPong { type Env = sails_rs::client::GstdEnv; fn ping (&mut self, input: String) -> sails_rs::client::PendingCall; } } + mock! { pub Counter {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl counter::Counter for Counter { type Env = sails_rs::client::GstdEnv; fn add (&mut self, value: u32) -> sails_rs::client::PendingCall;fn sub (&mut self, value: u32) -> sails_rs::client::PendingCall;fn value (& self, ) -> sails_rs::client::PendingCall; } } + mock! { pub Dog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl dog::Dog for Dog { type Env = sails_rs::client::GstdEnv; fn make_sound (&mut self, ) -> sails_rs::client::PendingCall;fn walk (&mut self, dx: i32, dy: i32) -> sails_rs::client::PendingCall;fn avg_weight (& self, ) -> sails_rs::client::PendingCall;fn position (& self, ) -> sails_rs::client::PendingCall; } } + mock! { pub References {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl references::References for References { type Env = sails_rs::client::GstdEnv; fn add (&mut self, v: u32) -> sails_rs::client::PendingCall;fn add_byte (&mut self, byte: u8) -> sails_rs::client::PendingCall;fn guess_num (&mut self, number: u8) -> sails_rs::client::PendingCall;fn incr (&mut self, ) -> sails_rs::client::PendingCall;fn set_num (&mut self, number: u8) -> sails_rs::client::PendingCall;fn baked (& self, ) -> sails_rs::client::PendingCall;fn last_byte (& self, ) -> sails_rs::client::PendingCall;fn message (& self, ) -> sails_rs::client::PendingCall; } } + mock! { pub ThisThat {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl this_that::ThisThat for ThisThat { type Env = sails_rs::client::GstdEnv; fn do_that (&mut self, param: DoThatParam) -> sails_rs::client::PendingCall;fn do_this (&mut self, p1: u32, p2: String, p3: (Option,NonZeroU8,), p4: TupleStruct) -> sails_rs::client::PendingCall;fn noop (&mut self, ) -> sails_rs::client::PendingCall;fn that (& self, ) -> sails_rs::client::PendingCall;fn this (& self, ) -> sails_rs::client::PendingCall; } } + mock! { pub ValueFee {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl value_fee::ValueFee for ValueFee { type Env = sails_rs::client::GstdEnv; fn do_something_and_take_fee (&mut self, ) -> sails_rs::client::PendingCall; } } } diff --git a/examples/no-svcs-prog/wasm/src/no_svcs_prog.rs b/examples/no-svcs-prog/wasm/src/no_svcs_prog.rs index ed4e7670d..78041c57b 100644 --- a/examples/no-svcs-prog/wasm/src/no_svcs_prog.rs +++ b/examples/no-svcs-prog/wasm/src/no_svcs_prog.rs @@ -2,20 +2,22 @@ #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; pub struct NoSvcsProgProgram; -impl Program for NoSvcsProgProgram {} +impl sails_rs::client::Program for NoSvcsProgProgram {} pub trait NoSvcsProg { - type Env: GearEnv; + type Env: sails_rs::client::GearEnv; } -impl NoSvcsProg for Actor { +impl NoSvcsProg for sails_rs::client::Actor { type Env = E; } pub trait NoSvcsProgCtors { - type Env: GearEnv; - fn create(self) -> PendingCtor; + type Env: sails_rs::client::GearEnv; + fn create(self) -> sails_rs::client::PendingCtor; } -impl NoSvcsProgCtors for Deployment { +impl NoSvcsProgCtors + for sails_rs::client::Deployment +{ type Env = E; - fn create(self) -> PendingCtor { + fn create(self) -> sails_rs::client::PendingCtor { self.pending_ctor(()) } } diff --git a/examples/redirect/client/src/redirect_client.rs b/examples/redirect/client/src/redirect_client.rs index ed956df05..ece2fbd54 100644 --- a/examples/redirect/client/src/redirect_client.rs +++ b/examples/redirect/client/src/redirect_client.rs @@ -2,26 +2,30 @@ #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; pub struct RedirectClientProgram; -impl Program for RedirectClientProgram {} +impl sails_rs::client::Program for RedirectClientProgram {} pub trait RedirectClient { - type Env: GearEnv; - fn redirect(&self) -> Service; + type Env: sails_rs::client::GearEnv; + fn redirect(&self) -> sails_rs::client::Service; } -impl RedirectClient for Actor { +impl RedirectClient + for sails_rs::client::Actor +{ type Env = E; - fn redirect(&self) -> Service { + fn redirect(&self) -> sails_rs::client::Service { self.service(stringify!(Redirect)) } } pub trait RedirectClientCtors { - type Env: GearEnv; + type Env: sails_rs::client::GearEnv; #[allow(clippy::new_ret_no_self)] #[allow(clippy::wrong_self_convention)] - fn new(self) -> PendingCtor; + fn new(self) -> sails_rs::client::PendingCtor; } -impl RedirectClientCtors for Deployment { +impl RedirectClientCtors + for sails_rs::client::Deployment +{ type Env = E; - fn new(self) -> PendingCtor { + fn new(self) -> sails_rs::client::PendingCtor { self.pending_ctor(()) } } @@ -34,19 +38,25 @@ pub mod io { pub mod redirect { use super::*; pub trait Redirect { - type Env: GearEnv; + type Env: sails_rs::client::GearEnv; /// Exit from program with inheritor ID - fn exit(&mut self, inheritor_id: ActorId) -> PendingCall; + fn exit( + &mut self, + inheritor_id: ActorId, + ) -> sails_rs::client::PendingCall; /// Returns program ID of the current program - fn get_program_id(&self) -> PendingCall; + fn get_program_id(&self) -> sails_rs::client::PendingCall; } pub struct RedirectImpl; - impl Redirect for Service { + impl Redirect for sails_rs::client::Service { type Env = E; - fn exit(&mut self, inheritor_id: ActorId) -> PendingCall { + fn exit( + &mut self, + inheritor_id: ActorId, + ) -> sails_rs::client::PendingCall { self.pending_call((inheritor_id,)) } - fn get_program_id(&self) -> PendingCall { + fn get_program_id(&self) -> sails_rs::client::PendingCall { self.pending_call(()) } } diff --git a/examples/redirect/proxy-client/src/redirect_proxy_client.rs b/examples/redirect/proxy-client/src/redirect_proxy_client.rs index 4869942cc..c7bedb290 100644 --- a/examples/redirect/proxy-client/src/redirect_proxy_client.rs +++ b/examples/redirect/proxy-client/src/redirect_proxy_client.rs @@ -2,27 +2,37 @@ #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; pub struct RedirectProxyClientProgram; -impl Program for RedirectProxyClientProgram {} +impl sails_rs::client::Program for RedirectProxyClientProgram {} pub trait RedirectProxyClient { - type Env: GearEnv; - fn proxy(&self) -> Service; + type Env: sails_rs::client::GearEnv; + fn proxy(&self) -> sails_rs::client::Service; } -impl RedirectProxyClient for Actor { +impl RedirectProxyClient + for sails_rs::client::Actor +{ type Env = E; - fn proxy(&self) -> Service { + fn proxy(&self) -> sails_rs::client::Service { self.service(stringify!(Proxy)) } } pub trait RedirectProxyClientCtors { - type Env: GearEnv; + type Env: sails_rs::client::GearEnv; /// Proxy Program's constructor #[allow(clippy::new_ret_no_self)] #[allow(clippy::wrong_self_convention)] - fn new(self, target: ActorId) -> PendingCtor; + fn new( + self, + target: ActorId, + ) -> sails_rs::client::PendingCtor; } -impl RedirectProxyClientCtors for Deployment { +impl RedirectProxyClientCtors + for sails_rs::client::Deployment +{ type Env = E; - fn new(self, target: ActorId) -> PendingCtor { + fn new( + self, + target: ActorId, + ) -> sails_rs::client::PendingCtor { self.pending_ctor((target,)) } } @@ -35,14 +45,14 @@ pub mod io { pub mod proxy { use super::*; pub trait Proxy { - type Env: GearEnv; + type Env: sails_rs::client::GearEnv; /// Get program ID of the target program via client - fn get_program_id(&self) -> PendingCall; + fn get_program_id(&self) -> sails_rs::client::PendingCall; } pub struct ProxyImpl; - impl Proxy for Service { + impl Proxy for sails_rs::client::Service { type Env = E; - fn get_program_id(&self) -> PendingCall { + fn get_program_id(&self) -> sails_rs::client::PendingCall { self.pending_call(()) } } diff --git a/examples/rmrk/resource/app/src/rmrk_catalog.rs b/examples/rmrk/resource/app/src/rmrk_catalog.rs index 6c4b53c85..eeec15202 100644 --- a/examples/rmrk/resource/app/src/rmrk_catalog.rs +++ b/examples/rmrk/resource/app/src/rmrk_catalog.rs @@ -2,26 +2,28 @@ #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; pub struct RmrkCatalogProgram; -impl Program for RmrkCatalogProgram {} +impl sails_rs::client::Program for RmrkCatalogProgram {} pub trait RmrkCatalog { - type Env: GearEnv; - fn rmrk_catalog(&self) -> Service; + type Env: sails_rs::client::GearEnv; + fn rmrk_catalog(&self) -> sails_rs::client::Service; } -impl RmrkCatalog for Actor { +impl RmrkCatalog for sails_rs::client::Actor { type Env = E; - fn rmrk_catalog(&self) -> Service { + fn rmrk_catalog(&self) -> sails_rs::client::Service { self.service(stringify!(RmrkCatalog)) } } pub trait RmrkCatalogCtors { - type Env: GearEnv; + type Env: sails_rs::client::GearEnv; #[allow(clippy::new_ret_no_self)] #[allow(clippy::wrong_self_convention)] - fn new(self) -> PendingCtor; + fn new(self) -> sails_rs::client::PendingCtor; } -impl RmrkCatalogCtors for Deployment { +impl RmrkCatalogCtors + for sails_rs::client::Deployment +{ type Env = E; - fn new(self) -> PendingCtor { + fn new(self) -> sails_rs::client::PendingCtor { self.pending_ctor(()) } } @@ -34,81 +36,89 @@ pub mod io { pub mod rmrk_catalog { use super::*; pub trait RmrkCatalog { - type Env: GearEnv; + type Env: sails_rs::client::GearEnv; fn add_equippables( &mut self, part_id: u32, collection_ids: Vec, - ) -> PendingCall; - fn add_parts(&mut self, parts: BTreeMap) - -> PendingCall; + ) -> sails_rs::client::PendingCall; + fn add_parts( + &mut self, + parts: BTreeMap, + ) -> sails_rs::client::PendingCall; fn remove_equippable( &mut self, part_id: u32, collection_id: ActorId, - ) -> PendingCall; - fn remove_parts(&mut self, part_ids: Vec) -> PendingCall; + ) -> sails_rs::client::PendingCall; + fn remove_parts( + &mut self, + part_ids: Vec, + ) -> sails_rs::client::PendingCall; fn reset_equippables( &mut self, part_id: u32, - ) -> PendingCall; + ) -> sails_rs::client::PendingCall; fn set_equippables_to_all( &mut self, part_id: u32, - ) -> PendingCall; + ) -> sails_rs::client::PendingCall; fn equippable( &self, part_id: u32, collection_id: ActorId, - ) -> PendingCall; - fn part(&self, part_id: u32) -> PendingCall; + ) -> sails_rs::client::PendingCall; + fn part(&self, part_id: u32) -> sails_rs::client::PendingCall; } pub struct RmrkCatalogImpl; - impl RmrkCatalog for Service { + impl RmrkCatalog for sails_rs::client::Service { type Env = E; fn add_equippables( &mut self, part_id: u32, collection_ids: Vec, - ) -> PendingCall { + ) -> sails_rs::client::PendingCall { self.pending_call((part_id, collection_ids)) } fn add_parts( &mut self, parts: BTreeMap, - ) -> PendingCall { + ) -> sails_rs::client::PendingCall { self.pending_call((parts,)) } fn remove_equippable( &mut self, part_id: u32, collection_id: ActorId, - ) -> PendingCall { + ) -> sails_rs::client::PendingCall { self.pending_call((part_id, collection_id)) } - fn remove_parts(&mut self, part_ids: Vec) -> PendingCall { + fn remove_parts( + &mut self, + part_ids: Vec, + ) -> sails_rs::client::PendingCall { self.pending_call((part_ids,)) } fn reset_equippables( &mut self, part_id: u32, - ) -> PendingCall { + ) -> sails_rs::client::PendingCall { self.pending_call((part_id,)) } fn set_equippables_to_all( &mut self, part_id: u32, - ) -> PendingCall { + ) -> sails_rs::client::PendingCall { self.pending_call((part_id,)) } fn equippable( &self, part_id: u32, collection_id: ActorId, - ) -> PendingCall { + ) -> sails_rs::client::PendingCall { self.pending_call((part_id, collection_id)) } - fn part(&self, part_id: u32) -> PendingCall { + fn part(&self, part_id: u32) -> sails_rs::client::PendingCall { self.pending_call((part_id,)) } } @@ -178,5 +188,5 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub RmrkCatalog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl rmrk_catalog::RmrkCatalog for RmrkCatalog { type Env = GstdEnv; fn add_equippables (&mut self, part_id: u32, collection_ids: Vec) -> PendingCall;fn add_parts (&mut self, parts: BTreeMap) -> PendingCall;fn remove_equippable (&mut self, part_id: u32, collection_id: ActorId) -> PendingCall;fn remove_parts (&mut self, part_ids: Vec) -> PendingCall;fn reset_equippables (&mut self, part_id: u32) -> PendingCall;fn set_equippables_to_all (&mut self, part_id: u32) -> PendingCall;fn equippable (& self, part_id: u32, collection_id: ActorId) -> PendingCall;fn part (& self, part_id: u32) -> PendingCall; } } + mock! { pub RmrkCatalog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl rmrk_catalog::RmrkCatalog for RmrkCatalog { type Env = sails_rs::client::GstdEnv; fn add_equippables (&mut self, part_id: u32, collection_ids: Vec) -> sails_rs::client::PendingCall;fn add_parts (&mut self, parts: BTreeMap) -> sails_rs::client::PendingCall;fn remove_equippable (&mut self, part_id: u32, collection_id: ActorId) -> sails_rs::client::PendingCall;fn remove_parts (&mut self, part_ids: Vec) -> sails_rs::client::PendingCall;fn reset_equippables (&mut self, part_id: u32) -> sails_rs::client::PendingCall;fn set_equippables_to_all (&mut self, part_id: u32) -> sails_rs::client::PendingCall;fn equippable (& self, part_id: u32, collection_id: ActorId) -> sails_rs::client::PendingCall;fn part (& self, part_id: u32) -> sails_rs::client::PendingCall; } } } diff --git a/examples/rmrk/resource/wasm/src/rmrk_resource.rs b/examples/rmrk/resource/wasm/src/rmrk_resource.rs index 612d7cd93..d8d60d127 100644 --- a/examples/rmrk/resource/wasm/src/rmrk_resource.rs +++ b/examples/rmrk/resource/wasm/src/rmrk_resource.rs @@ -2,26 +2,34 @@ #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; pub struct RmrkResourceProgram; -impl Program for RmrkResourceProgram {} +impl sails_rs::client::Program for RmrkResourceProgram {} pub trait RmrkResource { - type Env: GearEnv; - fn rmrk_resource(&self) -> Service; + type Env: sails_rs::client::GearEnv; + fn rmrk_resource( + &self, + ) -> sails_rs::client::Service; } -impl RmrkResource for Actor { +impl RmrkResource + for sails_rs::client::Actor +{ type Env = E; - fn rmrk_resource(&self) -> Service { + fn rmrk_resource( + &self, + ) -> sails_rs::client::Service { self.service(stringify!(RmrkResource)) } } pub trait RmrkResourceCtors { - type Env: GearEnv; + type Env: sails_rs::client::GearEnv; #[allow(clippy::new_ret_no_self)] #[allow(clippy::wrong_self_convention)] - fn new(self) -> PendingCtor; + fn new(self) -> sails_rs::client::PendingCtor; } -impl RmrkResourceCtors for Deployment { +impl RmrkResourceCtors + for sails_rs::client::Deployment +{ type Env = E; - fn new(self) -> PendingCtor { + fn new(self) -> sails_rs::client::PendingCtor { self.pending_ctor(()) } } @@ -34,37 +42,43 @@ pub mod io { pub mod rmrk_resource { use super::*; pub trait RmrkResource { - type Env: GearEnv; + type Env: sails_rs::client::GearEnv; fn add_part_to_resource( &mut self, resource_id: u8, part_id: u32, - ) -> PendingCall; + ) -> sails_rs::client::PendingCall; fn add_resource_entry( &mut self, resource_id: u8, resource: Resource, - ) -> PendingCall; - fn resource(&self, resource_id: u8) -> PendingCall; + ) -> sails_rs::client::PendingCall; + fn resource( + &self, + resource_id: u8, + ) -> sails_rs::client::PendingCall; } pub struct RmrkResourceImpl; - impl RmrkResource for Service { + impl RmrkResource for sails_rs::client::Service { type Env = E; fn add_part_to_resource( &mut self, resource_id: u8, part_id: u32, - ) -> PendingCall { + ) -> sails_rs::client::PendingCall { self.pending_call((resource_id, part_id)) } fn add_resource_entry( &mut self, resource_id: u8, resource: Resource, - ) -> PendingCall { + ) -> sails_rs::client::PendingCall { self.pending_call((resource_id, resource)) } - fn resource(&self, resource_id: u8) -> PendingCall { + fn resource( + &self, + resource_id: u8, + ) -> sails_rs::client::PendingCall { self.pending_call((resource_id,)) } } @@ -88,7 +102,7 @@ pub mod rmrk_resource { impl EventDecode for RmrkResourceEvents { const EVENT_NAMES: &'static [Route] = &["ResourceAdded", "PartAdded"]; } - impl ServiceEvent for RmrkResourceImpl { + impl sails_rs::client::ServiceEvent for RmrkResourceImpl { type Event = RmrkResourceEvents; } } diff --git a/rs/client-gen/src/ctor_generators.rs b/rs/client-gen/src/ctor_generators.rs index 1dc871bbd..00461499f 100644 --- a/rs/client-gen/src/ctor_generators.rs +++ b/rs/client-gen/src/ctor_generators.rs @@ -26,11 +26,11 @@ impl<'a> CtorGenerator<'a> { pub(crate) fn finalize(self) -> Tokens { quote! { pub trait $(self.service_name)Ctors { - type Env: GearEnv; + type Env: $(self.sails_path)::client::GearEnv; $(self.trait_ctors_tokens) } - impl $(self.service_name)Ctors for Deployment { + impl $(self.service_name)Ctors for $(self.sails_path)::client::Deployment { type Env = E; $(self.ctor_tokens) } @@ -71,12 +71,12 @@ impl<'ast> Visitor<'ast> for CtorGenerator<'_> { quote_in! { self.trait_ctors_tokens => $['\r'] - fn $fn_name_snake (self, $params_with_types) -> PendingCtor; + fn $fn_name_snake (self, $params_with_types) -> $(self.sails_path)::client::PendingCtor; }; quote_in! { self.ctor_tokens => $['\r'] - fn $fn_name_snake (self, $params_with_types) -> PendingCtor { + fn $fn_name_snake (self, $params_with_types) -> $(self.sails_path)::client::PendingCtor { self.pending_ctor($args) } }; diff --git a/rs/client-gen/src/events_generator.rs b/rs/client-gen/src/events_generator.rs index b24d0f6b7..e7899f359 100644 --- a/rs/client-gen/src/events_generator.rs +++ b/rs/client-gen/src/events_generator.rs @@ -52,7 +52,7 @@ impl<'ast> Visitor<'ast> for EventsModuleGenerator<'_> { const EVENT_NAMES: &'static [Route] = &[$event_names]; } - impl ServiceEvent for $(self.service_name)Impl { + impl $(self.sails_path)::client::ServiceEvent for $(self.service_name)Impl { type Event = $events_name; } } diff --git a/rs/client-gen/src/mock_generator.rs b/rs/client-gen/src/mock_generator.rs index e37883707..f6669557d 100644 --- a/rs/client-gen/src/mock_generator.rs +++ b/rs/client-gen/src/mock_generator.rs @@ -6,13 +6,15 @@ use sails_idl_parser::{ast::visitor, ast::visitor::Visitor, ast::*}; pub(crate) struct MockGenerator<'a> { service_name: &'a str, + sails_path: &'a str, tokens: rust::Tokens, } impl<'a> MockGenerator<'a> { - pub(crate) fn new(service_name: &'a str) -> Self { + pub(crate) fn new(service_name: &'a str, sails_path: &'a str) -> Self { Self { service_name, + sails_path, tokens: Tokens::new(), } } @@ -26,7 +28,7 @@ impl<'a> MockGenerator<'a> { #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl $service_name_snake::$(self.service_name) for $(self.service_name) { - type Env = GstdEnv; + type Env = $(self.sails_path)::client::GstdEnv; $(self.tokens) } } @@ -47,7 +49,7 @@ impl<'ast> Visitor<'ast> for MockGenerator<'_> { let params_with_types = &fn_args_with_types(func.params()); quote_in! { self.tokens => - fn $fn_name_snake (&$mutability self, $params_with_types) -> PendingCall; + fn $fn_name_snake (&$mutability self, $params_with_types) -> $(self.sails_path)::client::PendingCall<$(self.sails_path)::client::GstdEnv, $service_name_snake::io::$fn_name>; }; } } diff --git a/rs/client-gen/src/root_generator.rs b/rs/client-gen/src/root_generator.rs index f48aac5da..d972dfd7f 100644 --- a/rs/client-gen/src/root_generator.rs +++ b/rs/client-gen/src/root_generator.rs @@ -74,14 +74,14 @@ impl<'a> RootGenerator<'a> { quote_in! { tokens => pub struct $(program_name)Program; - impl Program for $(program_name)Program {} + impl $(self.sails_path)::client::Program for $(program_name)Program {} pub trait $program_name { - type Env: GearEnv; + type Env: $(self.sails_path)::client::GearEnv; $(self.service_trait_tokens) } - impl $program_name for Actor { + impl $program_name for $(self.sails_path)::client::Actor { type Env = E; $(self.service_impl_tokens) } @@ -120,7 +120,7 @@ impl<'ast> Visitor<'ast> for RootGenerator<'_> { service.name() }; - let mut ctor_gen = ServiceCtorGenerator::new(service_name); + let mut ctor_gen = ServiceCtorGenerator::new(service_name, self.sails_path); ctor_gen.visit_service(service); let (trait_tokens, impl_tokens) = ctor_gen.finalize(); self.service_trait_tokens.extend(trait_tokens); @@ -130,7 +130,7 @@ impl<'ast> Visitor<'ast> for RootGenerator<'_> { client_gen.visit_service(service); self.tokens.extend(client_gen.finalize()); - let mut mock_gen = MockGenerator::new(service_name); + let mut mock_gen = MockGenerator::new(service_name, self.sails_path); mock_gen.visit_service(service); self.mocks_tokens.extend(mock_gen.finalize()); } diff --git a/rs/client-gen/src/service_generators.rs b/rs/client-gen/src/service_generators.rs index f07eeabfc..0ebcff3bd 100644 --- a/rs/client-gen/src/service_generators.rs +++ b/rs/client-gen/src/service_generators.rs @@ -10,14 +10,16 @@ use crate::type_generators::generate_type_decl_with_path; /// Generates a trait with service methods pub(crate) struct ServiceCtorGenerator<'a> { service_name: &'a str, + sails_path: &'a str, trait_tokens: Tokens, impl_tokens: Tokens, } impl<'a> ServiceCtorGenerator<'a> { - pub(crate) fn new(service_name: &'a str) -> Self { + pub(crate) fn new(service_name: &'a str, sails_path: &'a str) -> Self { Self { service_name, + sails_path, trait_tokens: Tokens::new(), impl_tokens: Tokens::new(), } @@ -32,10 +34,10 @@ impl<'ast> Visitor<'ast> for ServiceCtorGenerator<'_> { fn visit_service(&mut self, _service: &'ast Service) { let service_name_snake = &self.service_name.to_case(Case::Snake); quote_in!(self.trait_tokens => - fn $service_name_snake(&self) -> Service; + fn $service_name_snake(&self) -> $(self.sails_path)::client::Service; ); quote_in!(self.impl_tokens => - fn $service_name_snake(&self) -> Service { + fn $service_name_snake(&self) -> $(self.sails_path)::client::Service { self.service(stringify!($(self.service_name))) } ); @@ -72,13 +74,13 @@ impl<'a> ServiceGenerator<'a> { use super::*; pub trait $(self.service_name) { - type Env: GearEnv; + type Env: $(self.sails_path)::client::GearEnv; $(self.trait_tokens) } pub struct $(self.service_name)Impl; - impl $(self.service_name) for Service { + impl $(self.service_name) for $(self.sails_path)::client::Service { type Env = E; $(self.impl_tokens) } @@ -121,11 +123,11 @@ impl<'ast> Visitor<'ast> for ServiceGenerator<'_> { }; } quote_in! { self.trait_tokens => - $['\r'] fn $fn_name_snake (&$mutability self, $params_with_types) -> PendingCall; + $['\r'] fn $fn_name_snake (&$mutability self, $params_with_types) -> $(self.sails_path)::client::PendingCall; }; quote_in! {self.impl_tokens => - $['\r'] fn $fn_name_snake (&$mutability self, $params_with_types) -> PendingCall { + $['\r'] fn $fn_name_snake (&$mutability self, $params_with_types) -> $(self.sails_path)::client::PendingCall { self.pending_call($args) } }; diff --git a/rs/client-gen/tests/snapshots/generator__basic_works.snap b/rs/client-gen/tests/snapshots/generator__basic_works.snap index 8040c9b61..9abe30135 100644 --- a/rs/client-gen/tests/snapshots/generator__basic_works.snap +++ b/rs/client-gen/tests/snapshots/generator__basic_works.snap @@ -6,14 +6,14 @@ expression: "gen_client(idl, \"Basic\")" #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; pub struct BasicProgram; -impl Program for BasicProgram {} +impl sails_rs::client::Program for BasicProgram {} pub trait Basic { - type Env: GearEnv; - fn basic(&self) -> Service; + type Env: sails_rs::client::GearEnv; + fn basic(&self) -> sails_rs::client::Service; } -impl Basic for Actor { +impl Basic for sails_rs::client::Actor { type Env = E; - fn basic(&self) -> Service { + fn basic(&self) -> sails_rs::client::Service { self.service(stringify!(Basic)) } } @@ -21,17 +21,31 @@ impl Basic for Actor { pub mod basic { use super::*; pub trait Basic { - type Env: GearEnv; - fn do_this(&mut self, p1: u32, p2: MyParam) -> PendingCall; - fn do_that(&mut self, p1: (u8, u32)) -> PendingCall; + type Env: sails_rs::client::GearEnv; + fn do_this( + &mut self, + p1: u32, + p2: MyParam, + ) -> sails_rs::client::PendingCall; + fn do_that( + &mut self, + p1: (u8, u32), + ) -> sails_rs::client::PendingCall; } pub struct BasicImpl; - impl Basic for Service { + impl Basic for sails_rs::client::Service { type Env = E; - fn do_this(&mut self, p1: u32, p2: MyParam) -> PendingCall { + fn do_this( + &mut self, + p1: u32, + p2: MyParam, + ) -> sails_rs::client::PendingCall { self.pending_call((p1, p2)) } - fn do_that(&mut self, p1: (u8, u32)) -> PendingCall { + fn do_that( + &mut self, + p1: (u8, u32), + ) -> sails_rs::client::PendingCall { self.pending_call((p1,)) } } @@ -70,5 +84,5 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub Basic {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl basic::Basic for Basic { type Env = GstdEnv; fn do_this (&mut self, p1: u32, p2: MyParam) -> PendingCall;fn do_that (&mut self, p1: (u8,u32,)) -> PendingCall; } } + mock! { pub Basic {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl basic::Basic for Basic { type Env = sails_rs::client::GstdEnv; fn do_this (&mut self, p1: u32, p2: MyParam) -> sails_rs::client::PendingCall;fn do_that (&mut self, p1: (u8,u32,)) -> sails_rs::client::PendingCall; } } } diff --git a/rs/client-gen/tests/snapshots/generator__events_works.snap b/rs/client-gen/tests/snapshots/generator__events_works.snap index 456c0d50b..613bbb27b 100644 --- a/rs/client-gen/tests/snapshots/generator__events_works.snap +++ b/rs/client-gen/tests/snapshots/generator__events_works.snap @@ -6,17 +6,20 @@ expression: "gen_client(idl, \"ServiceWithEvents\")" #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; pub struct ServiceWithEventsProgram; -impl Program for ServiceWithEventsProgram {} +impl sails_rs::client::Program for ServiceWithEventsProgram {} pub trait ServiceWithEvents { - type Env: GearEnv; - fn service_with_events(&self) - -> Service; + type Env: sails_rs::client::GearEnv; + fn service_with_events( + &self, + ) -> sails_rs::client::Service; } -impl ServiceWithEvents for Actor { +impl ServiceWithEvents + for sails_rs::client::Actor +{ type Env = E; fn service_with_events( &self, - ) -> Service { + ) -> sails_rs::client::Service { self.service(stringify!(ServiceWithEvents)) } } @@ -24,13 +27,23 @@ impl ServiceWithEvents for Actor { pub mod service_with_events { use super::*; pub trait ServiceWithEvents { - type Env: GearEnv; - fn do_this(&mut self, p1: NonZeroU256, p2: MyParam) -> PendingCall; + type Env: sails_rs::client::GearEnv; + fn do_this( + &mut self, + p1: NonZeroU256, + p2: MyParam, + ) -> sails_rs::client::PendingCall; } pub struct ServiceWithEventsImpl; - impl ServiceWithEvents for Service { + impl ServiceWithEvents + for sails_rs::client::Service + { type Env = E; - fn do_this(&mut self, p1: NonZeroU256, p2: MyParam) -> PendingCall { + fn do_this( + &mut self, + p1: NonZeroU256, + p2: MyParam, + ) -> sails_rs::client::PendingCall { self.pending_call((p1, p2)) } } @@ -54,7 +67,7 @@ pub mod service_with_events { impl EventDecode for ServiceWithEventsEvents { const EVENT_NAMES: &'static [Route] = &["One", "Two", "Three", "Reset"]; } - impl ServiceEvent for ServiceWithEventsImpl { + impl sails_rs::client::ServiceEvent for ServiceWithEventsImpl { type Event = ServiceWithEventsEvents; } } @@ -77,5 +90,5 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub ServiceWithEvents {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl service_with_events::ServiceWithEvents for ServiceWithEvents { type Env = GstdEnv; fn do_this (&mut self, p1: NonZeroU256, p2: MyParam) -> PendingCall; } } + mock! { pub ServiceWithEvents {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl service_with_events::ServiceWithEvents for ServiceWithEvents { type Env = sails_rs::client::GstdEnv; fn do_this (&mut self, p1: NonZeroU256, p2: MyParam) -> sails_rs::client::PendingCall; } } } diff --git a/rs/client-gen/tests/snapshots/generator__external_types.snap b/rs/client-gen/tests/snapshots/generator__external_types.snap index 144a7fa1d..ad4424f7f 100644 --- a/rs/client-gen/tests/snapshots/generator__external_types.snap +++ b/rs/client-gen/tests/snapshots/generator__external_types.snap @@ -8,14 +8,16 @@ use my_crate::MyParam; #[allow(unused_imports)] use my_crate::sails::{client::*, collections::*, prelude::*}; pub struct ServiceProgram; -impl Program for ServiceProgram {} +impl my_crate::sails::client::Program for ServiceProgram {} pub trait Service { - type Env: GearEnv; - fn service(&self) -> Service; + type Env: my_crate::sails::client::GearEnv; + fn service(&self) -> my_crate::sails::client::Service; } -impl Service for Actor { +impl Service + for my_crate::sails::client::Actor +{ type Env = E; - fn service(&self) -> Service { + fn service(&self) -> my_crate::sails::client::Service { self.service(stringify!(Service)) } } @@ -23,17 +25,33 @@ impl Service for Actor { pub mod service { use super::*; pub trait Service { - type Env: GearEnv; - fn do_this(&mut self, p1: u32, p2: MyParam) -> PendingCall; - fn do_that(&mut self, p1: (u8, u32)) -> PendingCall; + type Env: my_crate::sails::client::GearEnv; + fn do_this( + &mut self, + p1: u32, + p2: MyParam, + ) -> my_crate::sails::client::PendingCall; + fn do_that( + &mut self, + p1: (u8, u32), + ) -> my_crate::sails::client::PendingCall; } pub struct ServiceImpl; - impl Service for Service { + impl Service + for my_crate::sails::client::Service + { type Env = E; - fn do_this(&mut self, p1: u32, p2: MyParam) -> PendingCall { + fn do_this( + &mut self, + p1: u32, + p2: MyParam, + ) -> my_crate::sails::client::PendingCall { self.pending_call((p1, p2)) } - fn do_that(&mut self, p1: (u8, u32)) -> PendingCall { + fn do_that( + &mut self, + p1: (u8, u32), + ) -> my_crate::sails::client::PendingCall { self.pending_call((p1,)) } } diff --git a/rs/client-gen/tests/snapshots/generator__full.snap b/rs/client-gen/tests/snapshots/generator__full.snap index b9fb9762d..6eee83883 100644 --- a/rs/client-gen/tests/snapshots/generator__full.snap +++ b/rs/client-gen/tests/snapshots/generator__full.snap @@ -6,25 +6,29 @@ expression: "gen_client(IDL, \"Service\")" #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; pub struct ServiceProgram; -impl Program for ServiceProgram {} +impl sails_rs::client::Program for ServiceProgram {} pub trait Service { - type Env: GearEnv; - fn service(&self) -> Service; + type Env: sails_rs::client::GearEnv; + fn service(&self) -> sails_rs::client::Service; } -impl Service for Actor { +impl Service for sails_rs::client::Actor { type Env = E; - fn service(&self) -> Service { + fn service(&self) -> sails_rs::client::Service { self.service(stringify!(Service)) } } pub trait ServiceCtors { - type Env: GearEnv; - /// New constructor#[allow(clippy::new_ret_no_self)] #[allow(clippy::wrong_self_convention)] - fn new(self, a: u32) -> PendingCtor; + type Env: sails_rs::client::GearEnv; + /// New constructor + #[allow(clippy::new_ret_no_self)] + #[allow(clippy::wrong_self_convention)] + fn new(self, a: u32) -> sails_rs::client::PendingCtor; } -impl ServiceCtors for Deployment { +impl ServiceCtors + for sails_rs::client::Deployment +{ type Env = E; - fn new(self, a: u32) -> PendingCtor { + fn new(self, a: u32) -> sails_rs::client::PendingCtor { self.pending_ctor((a,)) } } @@ -37,7 +41,7 @@ pub mod io { pub mod service { use super::*; pub trait Service { - type Env: GearEnv; + type Env: sails_rs::client::GearEnv; /// Some description fn do_this( &mut self, @@ -45,22 +49,22 @@ pub mod service { p2: String, p3: (Option, u8), p4: ThisThatSvcAppTupleStruct, - ) -> PendingCall; + ) -> sails_rs::client::PendingCall; /// Some multiline description /// Second line /// Third line fn do_that( &mut self, param: ThisThatSvcAppDoThatParam, - ) -> PendingCall; + ) -> sails_rs::client::PendingCall; /// This is a query - fn this(&self, v1: Vec) -> PendingCall; + fn this(&self, v1: Vec) -> sails_rs::client::PendingCall; /// This is a second query /// This is a second line - fn that(&self, v1: ()) -> PendingCall; + fn that(&self, v1: ()) -> sails_rs::client::PendingCall; } pub struct ServiceImpl; - impl Service for Service { + impl Service for sails_rs::client::Service { type Env = E; fn do_this( &mut self, @@ -68,19 +72,19 @@ pub mod service { p2: String, p3: (Option, u8), p4: ThisThatSvcAppTupleStruct, - ) -> PendingCall { + ) -> sails_rs::client::PendingCall { self.pending_call((p1, p2, p3, p4)) } fn do_that( &mut self, param: ThisThatSvcAppDoThatParam, - ) -> PendingCall { + ) -> sails_rs::client::PendingCall { self.pending_call((param,)) } - fn this(&self, v1: Vec) -> PendingCall { + fn this(&self, v1: Vec) -> sails_rs::client::PendingCall { self.pending_call((v1,)) } - fn that(&self, v1: ()) -> PendingCall { + fn that(&self, v1: ()) -> sails_rs::client::PendingCall { self.pending_call((v1,)) } } @@ -110,7 +114,7 @@ pub mod service { impl EventDecode for ServiceEvents { const EVENT_NAMES: &'static [Route] = &["ThisDone", "ThatDone"]; } - impl ServiceEvent for ServiceImpl { + impl sails_rs::client::ServiceEvent for ServiceImpl { type Event = ServiceEvents; } } @@ -168,5 +172,5 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub Service {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl service::Service for Service { type Env = GstdEnv; fn do_this (&mut self, p1: u32, p2: String, p3: (Option,u8,), p4: ThisThatSvcAppTupleStruct) -> PendingCall;fn do_that (&mut self, param: ThisThatSvcAppDoThatParam) -> PendingCall;fn this (& self, v1: Vec) -> PendingCall;fn that (& self, v1: ()) -> PendingCall; } } + mock! { pub Service {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl service::Service for Service { type Env = sails_rs::client::GstdEnv; fn do_this (&mut self, p1: u32, p2: String, p3: (Option,u8,), p4: ThisThatSvcAppTupleStruct) -> sails_rs::client::PendingCall;fn do_that (&mut self, param: ThisThatSvcAppDoThatParam) -> sails_rs::client::PendingCall;fn this (& self, v1: Vec) -> sails_rs::client::PendingCall;fn that (& self, v1: ()) -> sails_rs::client::PendingCall; } } } diff --git a/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap b/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap index a17a1386e..7ebbdce8b 100644 --- a/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap +++ b/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap @@ -6,36 +6,48 @@ expression: code #[allow(unused_imports)] use my_crate::sails::{client::*, collections::*, prelude::*}; pub struct ServiceProgram; -impl Program for ServiceProgram {} +impl my_crate::sails::client::Program for ServiceProgram {} pub trait Service { - type Env: GearEnv; - fn service(&self) -> Service; - fn counter(&self) -> Service; + type Env: my_crate::sails::client::GearEnv; + fn service(&self) -> my_crate::sails::client::Service; + fn counter(&self) -> my_crate::sails::client::Service; } -impl Service for Actor { +impl Service + for my_crate::sails::client::Actor +{ type Env = E; - fn service(&self) -> Service { + fn service(&self) -> my_crate::sails::client::Service { self.service(stringify!(Service)) } - fn counter(&self) -> Service { + fn counter(&self) -> my_crate::sails::client::Service { self.service(stringify!(Counter)) } } pub trait ServiceCtors { - type Env: GearEnv; - /// New constructor#[allow(clippy::new_ret_no_self)] #[allow(clippy::wrong_self_convention)] - fn new(self, a: u32) -> PendingCtor; + type Env: my_crate::sails::client::GearEnv; + /// New constructor + #[allow(clippy::new_ret_no_self)] + #[allow(clippy::wrong_self_convention)] + fn new( + self, + a: u32, + ) -> my_crate::sails::client::PendingCtor; /// CreateWithData constructor fn create_with_data( self, a: u32, b: String, c: ThisThatSvcAppManyVariants, - ) -> PendingCtor; + ) -> my_crate::sails::client::PendingCtor; } -impl ServiceCtors for Deployment { +impl ServiceCtors + for my_crate::sails::client::Deployment +{ type Env = E; - fn new(self, a: u32) -> PendingCtor { + fn new( + self, + a: u32, + ) -> my_crate::sails::client::PendingCtor { self.pending_ctor((a,)) } fn create_with_data( @@ -43,7 +55,7 @@ impl ServiceCtors for Deployment { a: u32, b: String, c: ThisThatSvcAppManyVariants, - ) -> PendingCtor { + ) -> my_crate::sails::client::PendingCtor { self.pending_ctor((a, b, c)) } } @@ -57,23 +69,25 @@ pub mod io { pub mod service { use super::*; pub trait Service { - type Env: GearEnv; + type Env: my_crate::sails::client::GearEnv; fn do_this( &mut self, p1: u32, p2: String, p3: (Option, u8), p4: ThisThatSvcAppTupleStruct, - ) -> PendingCall; + ) -> my_crate::sails::client::PendingCall; fn do_that( &mut self, param: ThisThatSvcAppDoThatParam, - ) -> PendingCall; - fn this(&self, v1: Vec) -> PendingCall; - fn that(&self, v1: ()) -> PendingCall; + ) -> my_crate::sails::client::PendingCall; + fn this(&self, v1: Vec) -> my_crate::sails::client::PendingCall; + fn that(&self, v1: ()) -> my_crate::sails::client::PendingCall; } pub struct ServiceImpl; - impl Service for Service { + impl Service + for my_crate::sails::client::Service + { type Env = E; fn do_this( &mut self, @@ -81,19 +95,19 @@ pub mod service { p2: String, p3: (Option, u8), p4: ThisThatSvcAppTupleStruct, - ) -> PendingCall { + ) -> my_crate::sails::client::PendingCall { self.pending_call((p1, p2, p3, p4)) } fn do_that( &mut self, param: ThisThatSvcAppDoThatParam, - ) -> PendingCall { + ) -> my_crate::sails::client::PendingCall { self.pending_call((param,)) } - fn this(&self, v1: Vec) -> PendingCall { + fn this(&self, v1: Vec) -> my_crate::sails::client::PendingCall { self.pending_call((v1,)) } - fn that(&self, v1: ()) -> PendingCall { + fn that(&self, v1: ()) -> my_crate::sails::client::PendingCall { self.pending_call((v1,)) } } @@ -110,24 +124,26 @@ pub mod service { pub mod counter { use super::*; pub trait Counter { - type Env: GearEnv; + type Env: my_crate::sails::client::GearEnv; /// Add a value to the counter - fn add(&mut self, value: u32) -> PendingCall; + fn add(&mut self, value: u32) -> my_crate::sails::client::PendingCall; /// Substract a value from the counter - fn sub(&mut self, value: u32) -> PendingCall; + fn sub(&mut self, value: u32) -> my_crate::sails::client::PendingCall; /// Get the current value - fn value(&self) -> PendingCall; + fn value(&self) -> my_crate::sails::client::PendingCall; } pub struct CounterImpl; - impl Counter for Service { + impl Counter + for my_crate::sails::client::Service + { type Env = E; - fn add(&mut self, value: u32) -> PendingCall { + fn add(&mut self, value: u32) -> my_crate::sails::client::PendingCall { self.pending_call((value,)) } - fn sub(&mut self, value: u32) -> PendingCall { + fn sub(&mut self, value: u32) -> my_crate::sails::client::PendingCall { self.pending_call((value,)) } - fn value(&self) -> PendingCall { + fn value(&self) -> my_crate::sails::client::PendingCall { self.pending_call(()) } } @@ -153,7 +169,7 @@ pub mod counter { impl EventDecode for CounterEvents { const EVENT_NAMES: &'static [Route] = &["Added", "Subtracted"]; } - impl ServiceEvent for CounterImpl { + impl my_crate::sails::client::ServiceEvent for CounterImpl { type Event = CounterEvents; } } diff --git a/rs/client-gen/tests/snapshots/generator__multiple_services.snap b/rs/client-gen/tests/snapshots/generator__multiple_services.snap index edc31d3c2..17065ef0f 100644 --- a/rs/client-gen/tests/snapshots/generator__multiple_services.snap +++ b/rs/client-gen/tests/snapshots/generator__multiple_services.snap @@ -6,18 +6,18 @@ expression: "gen_client(idl, \"Multiple\")" #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; pub struct MultipleProgram; -impl Program for MultipleProgram {} +impl sails_rs::client::Program for MultipleProgram {} pub trait Multiple { - type Env: GearEnv; - fn multiple(&self) -> Service; - fn named(&self) -> Service; + type Env: sails_rs::client::GearEnv; + fn multiple(&self) -> sails_rs::client::Service; + fn named(&self) -> sails_rs::client::Service; } -impl Multiple for Actor { +impl Multiple for sails_rs::client::Actor { type Env = E; - fn multiple(&self) -> Service { + fn multiple(&self) -> sails_rs::client::Service { self.service(stringify!(Multiple)) } - fn named(&self) -> Service { + fn named(&self) -> sails_rs::client::Service { self.service(stringify!(Named)) } } @@ -25,17 +25,31 @@ impl Multiple for Actor { pub mod multiple { use super::*; pub trait Multiple { - type Env: GearEnv; - fn do_this(&mut self, p1: u32, p2: MyParam) -> PendingCall; - fn do_that(&mut self, p1: (u8, u32)) -> PendingCall; + type Env: sails_rs::client::GearEnv; + fn do_this( + &mut self, + p1: u32, + p2: MyParam, + ) -> sails_rs::client::PendingCall; + fn do_that( + &mut self, + p1: (u8, u32), + ) -> sails_rs::client::PendingCall; } pub struct MultipleImpl; - impl Multiple for Service { + impl Multiple for sails_rs::client::Service { type Env = E; - fn do_this(&mut self, p1: u32, p2: MyParam) -> PendingCall { + fn do_this( + &mut self, + p1: u32, + p2: MyParam, + ) -> sails_rs::client::PendingCall { self.pending_call((p1, p2)) } - fn do_that(&mut self, p1: (u8, u32)) -> PendingCall { + fn do_that( + &mut self, + p1: (u8, u32), + ) -> sails_rs::client::PendingCall { self.pending_call((p1,)) } } @@ -50,13 +64,13 @@ pub mod multiple { pub mod named { use super::*; pub trait Named { - type Env: GearEnv; - fn that(&self, p1: u32) -> PendingCall; + type Env: sails_rs::client::GearEnv; + fn that(&self, p1: u32) -> sails_rs::client::PendingCall; } pub struct NamedImpl; - impl Named for Service { + impl Named for sails_rs::client::Service { type Env = E; - fn that(&self, p1: u32) -> PendingCall { + fn that(&self, p1: u32) -> sails_rs::client::PendingCall { self.pending_call((p1,)) } } @@ -76,6 +90,6 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub Multiple {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl multiple::Multiple for Multiple { type Env = GstdEnv; fn do_this (&mut self, p1: u32, p2: MyParam) -> PendingCall;fn do_that (&mut self, p1: (u8,u32,)) -> PendingCall; } } - mock! { pub Named {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl named::Named for Named { type Env = GstdEnv; fn that (& self, p1: u32) -> PendingCall; } } + mock! { pub Multiple {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl multiple::Multiple for Multiple { type Env = sails_rs::client::GstdEnv; fn do_this (&mut self, p1: u32, p2: MyParam) -> sails_rs::client::PendingCall;fn do_that (&mut self, p1: (u8,u32,)) -> sails_rs::client::PendingCall; } } + mock! { pub Named {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl named::Named for Named { type Env = sails_rs::client::GstdEnv; fn that (& self, p1: u32) -> sails_rs::client::PendingCall; } } } diff --git a/rs/client-gen/tests/snapshots/generator__nonzero_works.snap b/rs/client-gen/tests/snapshots/generator__nonzero_works.snap index 5948fd6c8..0fa2261b6 100644 --- a/rs/client-gen/tests/snapshots/generator__nonzero_works.snap +++ b/rs/client-gen/tests/snapshots/generator__nonzero_works.snap @@ -6,14 +6,20 @@ expression: "gen_client(idl, \"NonZeroParams\")" #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; pub struct NonZeroParamsProgram; -impl Program for NonZeroParamsProgram {} +impl sails_rs::client::Program for NonZeroParamsProgram {} pub trait NonZeroParams { - type Env: GearEnv; - fn non_zero_params(&self) -> Service; + type Env: sails_rs::client::GearEnv; + fn non_zero_params( + &self, + ) -> sails_rs::client::Service; } -impl NonZeroParams for Actor { +impl NonZeroParams + for sails_rs::client::Actor +{ type Env = E; - fn non_zero_params(&self) -> Service { + fn non_zero_params( + &self, + ) -> sails_rs::client::Service { self.service(stringify!(NonZeroParams)) } } @@ -21,13 +27,23 @@ impl NonZeroParams for Actor { pub mod non_zero_params { use super::*; pub trait NonZeroParams { - type Env: GearEnv; - fn do_this(&mut self, p1: NonZeroU256, p2: MyParam) -> PendingCall; + type Env: sails_rs::client::GearEnv; + fn do_this( + &mut self, + p1: NonZeroU256, + p2: MyParam, + ) -> sails_rs::client::PendingCall; } pub struct NonZeroParamsImpl; - impl NonZeroParams for Service { + impl NonZeroParams + for sails_rs::client::Service + { type Env = E; - fn do_this(&mut self, p1: NonZeroU256, p2: MyParam) -> PendingCall { + fn do_this( + &mut self, + p1: NonZeroU256, + p2: MyParam, + ) -> sails_rs::client::PendingCall { self.pending_call((p1, p2)) } } @@ -55,5 +71,5 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub NonZeroParams {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl non_zero_params::NonZeroParams for NonZeroParams { type Env = GstdEnv; fn do_this (&mut self, p1: NonZeroU256, p2: MyParam) -> PendingCall; } } + mock! { pub NonZeroParams {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl non_zero_params::NonZeroParams for NonZeroParams { type Env = sails_rs::client::GstdEnv; fn do_this (&mut self, p1: NonZeroU256, p2: MyParam) -> sails_rs::client::PendingCall; } } } diff --git a/rs/client-gen/tests/snapshots/generator__rmrk_works.snap b/rs/client-gen/tests/snapshots/generator__rmrk_works.snap index 3fa77a585..941ceb477 100644 --- a/rs/client-gen/tests/snapshots/generator__rmrk_works.snap +++ b/rs/client-gen/tests/snapshots/generator__rmrk_works.snap @@ -6,26 +6,28 @@ expression: "gen_client(idl, \"RmrkCatalog\")" #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; pub struct RmrkCatalogProgram; -impl Program for RmrkCatalogProgram {} +impl sails_rs::client::Program for RmrkCatalogProgram {} pub trait RmrkCatalog { - type Env: GearEnv; - fn rmrk_catalog(&self) -> Service; + type Env: sails_rs::client::GearEnv; + fn rmrk_catalog(&self) -> sails_rs::client::Service; } -impl RmrkCatalog for Actor { +impl RmrkCatalog for sails_rs::client::Actor { type Env = E; - fn rmrk_catalog(&self) -> Service { + fn rmrk_catalog(&self) -> sails_rs::client::Service { self.service(stringify!(RmrkCatalog)) } } pub trait RmrkCatalogCtors { - type Env: GearEnv; + type Env: sails_rs::client::GearEnv; #[allow(clippy::new_ret_no_self)] #[allow(clippy::wrong_self_convention)] - fn new(self) -> PendingCtor; + fn new(self) -> sails_rs::client::PendingCtor; } -impl RmrkCatalogCtors for Deployment { +impl RmrkCatalogCtors + for sails_rs::client::Deployment +{ type Env = E; - fn new(self) -> PendingCtor { + fn new(self) -> sails_rs::client::PendingCtor { self.pending_ctor(()) } } @@ -38,81 +40,89 @@ pub mod io { pub mod rmrk_catalog { use super::*; pub trait RmrkCatalog { - type Env: GearEnv; + type Env: sails_rs::client::GearEnv; fn add_equippables( &mut self, part_id: u32, collection_ids: Vec, - ) -> PendingCall; - fn add_parts(&mut self, parts: BTreeMap) - -> PendingCall; + ) -> sails_rs::client::PendingCall; + fn add_parts( + &mut self, + parts: BTreeMap, + ) -> sails_rs::client::PendingCall; fn remove_equippable( &mut self, part_id: u32, collection_id: ActorId, - ) -> PendingCall; - fn remove_parts(&mut self, part_ids: Vec) -> PendingCall; + ) -> sails_rs::client::PendingCall; + fn remove_parts( + &mut self, + part_ids: Vec, + ) -> sails_rs::client::PendingCall; fn reset_equippables( &mut self, part_id: u32, - ) -> PendingCall; + ) -> sails_rs::client::PendingCall; fn set_equippables_to_all( &mut self, part_id: u32, - ) -> PendingCall; + ) -> sails_rs::client::PendingCall; fn equippable( &self, part_id: u32, collection_id: ActorId, - ) -> PendingCall; - fn part(&self, part_id: u32) -> PendingCall; + ) -> sails_rs::client::PendingCall; + fn part(&self, part_id: u32) -> sails_rs::client::PendingCall; } pub struct RmrkCatalogImpl; - impl RmrkCatalog for Service { + impl RmrkCatalog for sails_rs::client::Service { type Env = E; fn add_equippables( &mut self, part_id: u32, collection_ids: Vec, - ) -> PendingCall { + ) -> sails_rs::client::PendingCall { self.pending_call((part_id, collection_ids)) } fn add_parts( &mut self, parts: BTreeMap, - ) -> PendingCall { + ) -> sails_rs::client::PendingCall { self.pending_call((parts,)) } fn remove_equippable( &mut self, part_id: u32, collection_id: ActorId, - ) -> PendingCall { + ) -> sails_rs::client::PendingCall { self.pending_call((part_id, collection_id)) } - fn remove_parts(&mut self, part_ids: Vec) -> PendingCall { + fn remove_parts( + &mut self, + part_ids: Vec, + ) -> sails_rs::client::PendingCall { self.pending_call((part_ids,)) } fn reset_equippables( &mut self, part_id: u32, - ) -> PendingCall { + ) -> sails_rs::client::PendingCall { self.pending_call((part_id,)) } fn set_equippables_to_all( &mut self, part_id: u32, - ) -> PendingCall { + ) -> sails_rs::client::PendingCall { self.pending_call((part_id,)) } fn equippable( &self, part_id: u32, collection_id: ActorId, - ) -> PendingCall { + ) -> sails_rs::client::PendingCall { self.pending_call((part_id, collection_id)) } - fn part(&self, part_id: u32) -> PendingCall { + fn part(&self, part_id: u32) -> sails_rs::client::PendingCall { self.pending_call((part_id,)) } } @@ -182,5 +192,5 @@ extern crate std; pub mod mockall { use super::*; use sails_rs::mockall::*; - mock! { pub RmrkCatalog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl rmrk_catalog::RmrkCatalog for RmrkCatalog { type Env = GstdEnv; fn add_equippables (&mut self, part_id: u32, collection_ids: Vec) -> PendingCall;fn add_parts (&mut self, parts: BTreeMap) -> PendingCall;fn remove_equippable (&mut self, part_id: u32, collection_id: ActorId) -> PendingCall;fn remove_parts (&mut self, part_ids: Vec) -> PendingCall;fn reset_equippables (&mut self, part_id: u32) -> PendingCall;fn set_equippables_to_all (&mut self, part_id: u32) -> PendingCall;fn equippable (& self, part_id: u32, collection_id: ActorId) -> PendingCall;fn part (& self, part_id: u32) -> PendingCall; } } + mock! { pub RmrkCatalog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl rmrk_catalog::RmrkCatalog for RmrkCatalog { type Env = sails_rs::client::GstdEnv; fn add_equippables (&mut self, part_id: u32, collection_ids: Vec) -> sails_rs::client::PendingCall;fn add_parts (&mut self, parts: BTreeMap) -> sails_rs::client::PendingCall;fn remove_equippable (&mut self, part_id: u32, collection_id: ActorId) -> sails_rs::client::PendingCall;fn remove_parts (&mut self, part_ids: Vec) -> sails_rs::client::PendingCall;fn reset_equippables (&mut self, part_id: u32) -> sails_rs::client::PendingCall;fn set_equippables_to_all (&mut self, part_id: u32) -> sails_rs::client::PendingCall;fn equippable (& self, part_id: u32, collection_id: ActorId) -> sails_rs::client::PendingCall;fn part (& self, part_id: u32) -> sails_rs::client::PendingCall; } } } From 5e50a9b7b8dc0ff55aedb56584cad6b4a3b1d89b Mon Sep 17 00:00:00 2001 From: vobradovich Date: Tue, 9 Sep 2025 09:58:23 +0200 Subject: [PATCH 20/66] fix: cli program template --- rs/cli/src/program_new.rs | 21 ++++++++++++++------- rs/cli/templates/tests/gtest.askama | 26 +++++++++++++------------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/rs/cli/src/program_new.rs b/rs/cli/src/program_new.rs index f891d747e..c011f35c6 100644 --- a/rs/cli/src/program_new.rs +++ b/rs/cli/src/program_new.rs @@ -63,6 +63,7 @@ struct TestsGtest { client_crate_name: String, client_program_name: String, service_name: String, + service_name_snake: String, } pub struct ProgramGenerator { @@ -71,6 +72,8 @@ pub struct ProgramGenerator { sails_path: Option, app: bool, offline: bool, + service_name: String, + program_struct_name: String, } impl ProgramGenerator { @@ -91,19 +94,22 @@ impl ProgramGenerator { }, |name| name.to_case(Case::Kebab), ); + let service_name = package_name.to_case(Case::Pascal); Self { path, package_name, sails_path, app, offline, + service_name, + program_struct_name: "Program".to_string(), } } fn root_build(&self) -> RootBuild { RootBuild { app_crate_name: self.app_name().to_case(Case::Snake), - program_struct_name: "Program".to_string(), + program_struct_name: self.program_struct_name.clone(), } } @@ -118,23 +124,23 @@ impl ProgramGenerator { program_crate_name: self.package_name.to_owned(), app_crate_name: self.app_name(), client_crate_name: self.client_name(), - service_name: "Service".to_string(), + service_name: self.service_name.clone(), app: self.app, } } fn app_lib(&self) -> AppLib { AppLib { - service_name: "Service".to_string(), - service_name_snake: "service".to_string(), - program_struct_name: "Program".to_string(), + service_name: self.service_name.clone(), + service_name_snake: self.service_name.to_case(Case::Snake), + program_struct_name: self.program_struct_name.clone(), } } fn client_build(&self) -> ClientBuild { ClientBuild { app_crate_name: self.app_name().to_case(Case::Snake), - program_struct_name: "Program".to_string(), + program_struct_name: self.program_struct_name.clone(), } } @@ -149,7 +155,8 @@ impl ProgramGenerator { program_crate_name: self.package_name.to_case(Case::Snake), client_crate_name: self.client_name().to_case(Case::Snake), client_program_name: self.client_name().to_case(Case::Pascal), - service_name: "Service".to_string(), + service_name: self.service_name.clone(), + service_name_snake: self.service_name.to_case(Case::Snake), } } diff --git a/rs/cli/templates/tests/gtest.askama b/rs/cli/templates/tests/gtest.askama index 873023d23..a24ec6a1b 100644 --- a/rs/cli/templates/tests/gtest.askama +++ b/rs/cli/templates/tests/gtest.askama @@ -1,9 +1,12 @@ use sails_rs::{ - calls::*, - gtest::{System, calls::*}, + client::*, + gtest::*, +}; +use {{ client_crate_name }}::{ + {{ client_program_name }}, + {{ client_program_name }}Ctors, + {{ service_name_snake }}::*, }; - -use {{ client_crate_name }}::traits::*; const ACTOR_ID: u64 = 42; @@ -12,24 +15,21 @@ async fn do_something_works() { let system = System::new(); system.init_logger_with_default_filter("gwasm=debug,gtest=info,sails_rs=debug"); system.mint_to(ACTOR_ID, 100_000_000_000_000); - let remoting = GTestRemoting::new(system, ACTOR_ID.into()); - // Submit program code into the system - let program_code_id = remoting.system().submit_code({{ program_crate_name }}::WASM_BINARY); - - let program_factory = {{ client_crate_name }}::{{ client_program_name }}Factory::new(remoting.clone()); + let program_code_id = system.submit_code({{ program_crate_name }}::WASM_BINARY); + + // Create Sails Env + let env = GtestEnv::new(system, ACTOR_ID.into()); - let program_id = program_factory + let program = env.deploy::<{{ client_crate_name }}::{{ client_program_name }}Program>(program_code_id, b"salt".to_vec()) .new() // Call program's constructor - .send_recv(program_code_id, b"salt") .await .unwrap(); - let mut service_client = {{ client_crate_name }}::{{ service_name }}::new(remoting.clone()); + let mut service_client = program.{{ service_name_snake }}(); let result = service_client .do_something() // Call service's method - .send_recv(program_id) .await .unwrap(); From 02b778a62e6b10e642217ab7dfab961cb4527b13 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 10 Sep 2025 11:36:37 +0200 Subject: [PATCH 21/66] chore: optimizations --- benchmarks/bench_data.json | 4 +- rs/src/client/gclient_env.rs | 35 +++------ rs/src/client/gstd_env.rs | 146 ++++++++++++++++------------------- rs/src/client/gtest_env.rs | 83 ++++++++------------ rs/src/client/mod.rs | 9 ++- 5 files changed, 119 insertions(+), 158 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 53436b6f0..27ed37de0 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -17,9 +17,9 @@ "sync_call": 678860419 }, "cross_program": { - "median": 2475019485 + "median": 2471705780 }, "redirect": { - "median": 3688139273 + "median": 3686262587 } } \ No newline at end of file diff --git a/rs/src/client/gclient_env.rs b/rs/src/client/gclient_env.rs index 40a5ffa54..99160f936 100644 --- a/rs/src/client/gclient_env.rs +++ b/rs/src/client/gclient_env.rs @@ -1,5 +1,6 @@ use super::*; use ::gclient::{EventListener, EventProcessor as _, GearApi}; +use core::task::ready; use futures::{Stream, StreamExt, stream}; #[derive(Debug, thiserror::Error)] @@ -182,16 +183,13 @@ async fn send_message( impl PendingCall { pub async fn send(mut self) -> Result { - let Some(route) = self.route else { - return Err(gclient::Error::Codec("PendingCall route is not set".into()).into()); - }; let api = &self.env.api; let params = self.params.unwrap_or_default(); let args = self .args .take() .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(route, &args); + let payload = T::encode_params_with_prefix(self.route, &args); let value = params.value.unwrap_or(0); #[cfg(not(feature = "ethexe"))] let gas_limit = if let Some(gas_limit) = params.gas_limit { @@ -213,15 +211,12 @@ impl PendingCall { } pub async fn query(mut self) -> Result { - let Some(route) = self.route else { - return Err(gclient::Error::Codec("PendingCall route is not set".into()).into()); - }; let params = self.params.unwrap_or_default(); let args = self .args .take() .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(route, &args); + let payload = T::encode_params_with_prefix(self.route, &args); // Calculate reply let reply_bytes = self @@ -230,7 +225,7 @@ impl PendingCall { .await?; // Decode reply - match T::decode_reply_with_prefix(route, reply_bytes) { + match T::decode_reply_with_prefix(self.route, reply_bytes) { Ok(decoded) => Ok(decoded), Err(err) => Err(gclient::Error::Codec(err).into()), } @@ -241,12 +236,6 @@ impl Future for PendingCall { type Output = Result::Error>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let Some(route) = self.route else { - return Poll::Ready(Err(gclient::Error::Codec( - "PendingCall route is not set".into(), - ) - .into())); - }; if self.state.is_none() { // Send message let params = self.params.take().unwrap_or_default(); @@ -254,7 +243,7 @@ impl Future for PendingCall { .args .take() .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(route, &args); + let payload = T::encode_params_with_prefix(self.route, &args); let send_future = send_message(self.env.api.clone(), self.destination, payload, params); self.state = Some(Box::pin(send_future)); @@ -266,13 +255,12 @@ impl Future for PendingCall { .as_pin_mut() .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); // Poll message future - match message_future.poll(cx) { - Poll::Ready(Ok((_, payload))) => match T::decode_reply_with_prefix(route, payload) { + match ready!(message_future.poll(cx)) { + Ok((_, payload)) => match T::decode_reply_with_prefix(self.route, payload) { Ok(decoded) => Poll::Ready(Ok(decoded)), Err(err) => Poll::Ready(Err(gclient::Error::Codec(err).into())), }, - Poll::Ready(Err(err)) => Poll::Ready(Err(err)), - Poll::Pending => Poll::Pending, + Err(err) => Poll::Ready(Err(err)), } } } @@ -302,13 +290,12 @@ impl Future for PendingCtor { .as_pin_mut() .unwrap_or_else(|| panic!("{PENDING_CTOR_INVALID_STATE}")); // Poll message future - match message_future.poll(cx) { - Poll::Ready(Ok((program_id, _))) => { + match ready!(message_future.poll(cx)) { + Ok((program_id, _)) => { // Do not decode payload here Poll::Ready(Ok(Actor::new(this.env.clone(), program_id))) } - Poll::Ready(Err(err)) => Poll::Ready(Err(err)), - Poll::Pending => Poll::Pending, + Err(err) => Poll::Ready(Err(err)), } } } diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index fb9be004c..69f09a26c 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -77,15 +77,11 @@ impl GearEnv for GstdEnv { impl PendingCall { pub fn send_one_way(&mut self) -> Result { - let Some(route) = self.route else { - return Err(Error::Decode("PendingCall route is not set".into())); - }; - let args = self .args .take() .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(route, &args); + let payload = T::encode_params_with_prefix(self.route, &args); let params = self.params.take().unwrap_or_default(); let value = params.value.unwrap_or(0); @@ -182,16 +178,13 @@ const _: () = { type Output = Result::Error>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let Some(route) = self.route else { - return Poll::Ready(Err(Error::Decode("PendingCall route is not set".into()))); - }; if self.state.is_none() { // Send message let args = self .args .take() .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(route, &args); + let payload = T::encode_params_with_prefix(self.route, &args); let params = self.params.take().unwrap_or_default(); let send_res = send_for_reply(self.destination, payload, params); @@ -205,75 +198,71 @@ const _: () = { } } let this = self.as_mut().project(); - if let Some(mut state) = this.state.as_pin_mut() { - // Poll message future - let output = match state.as_mut().project() { - Projection::CreateProgram { .. } => panic!("{PENDING_CALL_INVALID_STATE}"), - Projection::Message { future } => ready!(future.poll(cx)), - Projection::MessageWithRedirect { future, .. } => ready!(future.poll(cx)), - Projection::Dummy => panic!("{PENDING_CALL_INVALID_STATE}"), - }; - match output { - // ok reply - Ok(payload) => match T::decode_reply_with_prefix(route, payload) { - Ok(reply) => Poll::Ready(Ok(reply)), - Err(err) => Poll::Ready(Err(Error::Decode(err))), - }, - // reply with ProgramExited - Err(gstd::errors::Error::ErrorReply( - error_payload, - ErrorReplyReason::UnavailableActor( - SimpleUnavailableActorError::ProgramExited, - ), - )) => { - if let Replace::MessageWithRedirect { - target: _target, - payload, - mut params, - created_block, - .. - } = state.as_mut().project_replace(GtsdFuture::Dummy) - && params.redirect_on_exit - && let Ok(new_target) = ActorId::try_from(error_payload.0.as_ref()) - { - gstd::debug!("Redirecting message from {_target} to {new_target}"); - - // Calculate updated `wait_up_to` if provided - // wait_up_to = wait_up_to - (current_block - created_block) - params.wait_up_to = params.wait_up_to.and_then(|wait_up_to| { - created_block.map(|created_block| { - let current_block = gstd::exec::block_height(); - wait_up_to - .saturating_sub(current_block.saturating_sub(created_block)) - }) - }); - - // send message to new target - let future_res = send_for_reply(new_target, payload, params); - match future_res { - Ok(future) => { - // Replace the future with a new one - _ = state.as_mut().project_replace(future); - // Return Pending to allow the new future to be polled - Poll::Pending - } - Err(err) => Poll::Ready(Err(err)), + let Some(mut state) = this.state.as_pin_mut() else { + panic!("{PENDING_CALL_INVALID_STATE}"); + }; + // Poll message future + let output = match state.as_mut().project() { + Projection::Message { future } => ready!(future.poll(cx)), + Projection::MessageWithRedirect { future, .. } => ready!(future.poll(cx)), + _ => panic!("{PENDING_CALL_INVALID_STATE}"), + }; + match output { + // ok reply + Ok(payload) => match T::decode_reply_with_prefix(self.route, payload) { + Ok(reply) => Poll::Ready(Ok(reply)), + Err(err) => Poll::Ready(Err(Error::Decode(err))), + }, + // reply with ProgramExited + Err(gstd::errors::Error::ErrorReply( + error_payload, + ErrorReplyReason::UnavailableActor(SimpleUnavailableActorError::ProgramExited), + )) => { + if let Replace::MessageWithRedirect { + target: _target, + payload, + mut params, + created_block, + .. + } = state.as_mut().project_replace(GtsdFuture::Dummy) + && params.redirect_on_exit + && let Ok(new_target) = ActorId::try_from(error_payload.0.as_ref()) + { + gstd::debug!("Redirecting message from {_target} to {new_target}"); + + // Calculate updated `wait_up_to` if provided + // wait_up_to = wait_up_to - (current_block - created_block) + params.wait_up_to = params.wait_up_to.and_then(|wait_up_to| { + created_block.map(|created_block| { + let current_block = gstd::exec::block_height(); + wait_up_to + .saturating_sub(current_block.saturating_sub(created_block)) + }) + }); + + // send message to new target + let future_res = send_for_reply(new_target, payload, params); + match future_res { + Ok(future) => { + // Replace the future with a new one + _ = state.as_mut().project_replace(future); + // Return Pending to allow the new future to be polled + Poll::Pending } - } else { - Poll::Ready(Err(gstd::errors::Error::ErrorReply( - error_payload, - ErrorReplyReason::UnavailableActor( - SimpleUnavailableActorError::ProgramExited, - ), - ) - .into())) + Err(err) => Poll::Ready(Err(err)), } + } else { + Poll::Ready(Err(gstd::errors::Error::ErrorReply( + error_payload, + ErrorReplyReason::UnavailableActor( + SimpleUnavailableActorError::ProgramExited, + ), + ) + .into())) } - // error reply - Err(err) => Poll::Ready(Err(err)), } - } else { - panic!("{PENDING_CALL_INVALID_STATE}"); + // error reply + Err(err) => Poll::Ready(Err(err)), } } } @@ -330,13 +319,12 @@ const _: () = { && let Projection::CreateProgram { future } = state.project() { // Poll create program future - match future.poll(cx) { - Poll::Ready(Ok((program_id, _payload))) => { + match ready!(future.poll(cx)) { + Ok((program_id, _payload)) => { // Do not decode payload here Poll::Ready(Ok(Actor::new(this.env.clone(), program_id))) } - Poll::Ready(Err(err)) => Poll::Ready(Err(err)), - Poll::Pending => Poll::Pending, + Err(err) => Poll::Ready(Err(err)), } } else { panic!("{PENDING_CTOR_INVALID_STATE}"); @@ -381,7 +369,7 @@ const _: () = { PendingCall { env: GstdEnv, destination: ActorId::zero(), - route: None, + route: "", params: None, args: None, state: Some(future::ready(res.map(|v| v.encode()))), diff --git a/rs/src/client/gtest_env.rs b/rs/src/client/gtest_env.rs index 9d2823d54..3ea0c75c6 100644 --- a/rs/src/client/gtest_env.rs +++ b/rs/src/client/gtest_env.rs @@ -1,6 +1,6 @@ use super::*; use ::gtest::{BlockRunResult, System, TestError}; -use core::cell::RefCell; +use core::{cell::RefCell, task::ready}; use futures::{ Stream, channel::{mpsc, oneshot}, @@ -235,11 +235,6 @@ impl GearEnv for GtestEnv { } impl PendingCall { pub fn send_one_way(&mut self) -> Result { - let Some(route) = self.route else { - return Err(TestError::ScaleCodecError( - "PendingCall route is not set".into(), - ))?; - }; if self.state.is_some() { panic!("{PENDING_CALL_INVALID_STATE}"); } @@ -248,7 +243,7 @@ impl PendingCall { .args .take() .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(route, &args); + let payload = T::encode_params_with_prefix(self.route, &args); let params = self.params.take().unwrap_or_default(); let message_id = self.env.send_message(self.destination, payload, params)?; @@ -267,20 +262,13 @@ impl Future for PendingCall { type Output = Result::Error>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let Some(route) = self.route else { - return Poll::Ready(Err(TestError::ScaleCodecError( - "PendingCall route is not set".into(), - ) - .into())); - }; - if self.state.is_none() { // Send message let args = self .args .take() .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(route, &args); + let payload = T::encode_params_with_prefix(self.route, &args); let params = self.params.take().unwrap_or_default(); let send_res = self.env.send_message(self.destination, payload, params); @@ -296,21 +284,20 @@ impl Future for PendingCall { } } let this = self.as_mut().project(); - if let Some(reply_receiver) = this.state.as_pin_mut() { - // Poll reply receiver - match reply_receiver.poll(cx) { - Poll::Ready(Ok(res)) => match res { - Ok(payload) => match T::decode_reply_with_prefix(route, payload) { - Ok(reply) => Poll::Ready(Ok(reply)), - Err(err) => Poll::Ready(Err(TestError::ScaleCodecError(err).into())), - }, - Err(err) => Poll::Ready(Err(err)), + let reply_receiver = this + .state + .as_pin_mut() + .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); + // Poll reply receiver + match ready!(reply_receiver.poll(cx)) { + Ok(res) => match res { + Ok(payload) => match T::decode_reply_with_prefix(self.route, payload) { + Ok(reply) => Poll::Ready(Ok(reply)), + Err(err) => Poll::Ready(Err(TestError::ScaleCodecError(err).into())), }, - Poll::Ready(Err(_err)) => Poll::Ready(Err(GtestError::ReplyIsMissing)), - Poll::Pending => Poll::Pending, - } - } else { - panic!("PendingCall polled after completion or invalid state"); + Err(err) => Poll::Ready(Err(err)), + }, + Err(_err) => Poll::Ready(Err(GtestError::ReplyIsMissing)), } } } @@ -375,26 +362,24 @@ impl Future for PendingCtor { } } let this = self.as_mut().project(); - if let Some(reply_receiver) = this.state.as_pin_mut() { - // Poll reply receiver - match reply_receiver.poll(cx) { - Poll::Ready(Ok(res)) => match res { - // TODO handle reply prefix - Ok(_) => { - // TODO - let program_id = this - .program_id - .take() - .unwrap_or_else(|| panic!("{PENDING_CTOR_INVALID_STATE}")); - Poll::Ready(Ok(Actor::new(this.env.clone(), program_id))) - } - Err(err) => Poll::Ready(Err(err)), - }, - Poll::Ready(Err(_err)) => Poll::Ready(Err(GtestError::ReplyIsMissing)), - Poll::Pending => Poll::Pending, - } - } else { - panic!("{PENDING_CTOR_INVALID_STATE}"); + let reply_receiver = this + .state + .as_pin_mut() + .unwrap_or_else(|| panic!("{PENDING_CTOR_INVALID_STATE}")); + // Poll reply receiver + match ready!(reply_receiver.poll(cx)) { + Ok(res) => match res { + Ok(_) => { + // Do not decode payload here + let program_id = this + .program_id + .take() + .unwrap_or_else(|| panic!("{PENDING_CTOR_INVALID_STATE}")); + Poll::Ready(Ok(Actor::new(this.env.clone(), program_id))) + } + Err(err) => Poll::Ready(Err(err)), + }, + Err(_err) => Poll::Ready(Err(GtestError::ReplyIsMissing)), } } } diff --git a/rs/src/client/mod.rs b/rs/src/client/mod.rs index c7751b8be..7ab37b2a5 100644 --- a/rs/src/client/mod.rs +++ b/rs/src/client/mod.rs @@ -216,7 +216,7 @@ pin_project_lite::pin_project! { pub struct PendingCall { env: E, destination: ActorId, - route: Option, + route: Route, params: Option, args: Option, #[pin] @@ -229,7 +229,7 @@ impl PendingCall { PendingCall { env, destination, - route: Some(route), + route, params: None, args: Some(args), state: None, @@ -287,14 +287,15 @@ pub trait CallEncodeDecode { type Reply: Decode + 'static; fn encode_params(value: &Self::Params) -> Vec { - let mut result = Vec::with_capacity(Self::ROUTE.len() + Encode::size_hint(value)); + let mut result = Vec::with_capacity(1 + Self::ROUTE.len() + Encode::size_hint(value)); Encode::encode_to(Self::ROUTE, &mut result); Encode::encode_to(value, &mut result); result } fn encode_params_with_prefix(prefix: Route, value: &Self::Params) -> Vec { - let mut result = Vec::with_capacity(Self::ROUTE.len() + Encode::size_hint(value)); + let mut result = + Vec::with_capacity(2 + prefix.len() + Self::ROUTE.len() + Encode::size_hint(value)); Encode::encode_to(prefix, &mut result); Encode::encode_to(Self::ROUTE, &mut result); Encode::encode_to(value, &mut result); From 88ba3d1ea990664871dc855b76fad18c9c968ac3 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 10 Sep 2025 14:45:51 +0200 Subject: [PATCH 22/66] wip: opt, code remove --- benchmarks/bench_data.json | 4 ++-- examples/demo/app/tests/gtest.rs | 2 +- examples/redirect/tests/gtest.rs | 2 +- rs/src/client/gstd_env.rs | 5 ++++- rs/src/gclient/mod.rs | 2 +- rs/src/gstd/mod.rs | 4 ++-- rs/src/gtest/mod.rs | 2 +- rs/src/lib.rs | 10 +++++----- 8 files changed, 17 insertions(+), 14 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 27ed37de0..ae8fec4c5 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -17,9 +17,9 @@ "sync_call": 678860419 }, "cross_program": { - "median": 2471705780 + "median": 2461550210 }, "redirect": { - "median": 3686262587 + "median": 3672751012 } } \ No newline at end of file diff --git a/examples/demo/app/tests/gtest.rs b/examples/demo/app/tests/gtest.rs index 7f87e0145..13ba3b3e4 100644 --- a/examples/demo/app/tests/gtest.rs +++ b/examples/demo/app/tests/gtest.rs @@ -13,7 +13,7 @@ pub(crate) const DEMO_WASM_PATH: &str = "../../../target/wasm32-gear/debug/demo. pub(crate) const DEMO_WASM_PATH: &str = "../../../target/wasm32-gear/release/demo.opt.wasm"; fn create_env() -> (GtestEnv, CodeId, GasUnit) { - use sails_rs::gtest::{MAX_USER_GAS_LIMIT, System}; + use sails_rs::gtest::{constants::MAX_USER_GAS_LIMIT, System}; let system = System::new(); system.init_logger_with_default_filter("gwasm=debug,gtest=info,sails_rs=debug,redirect=debug"); diff --git a/examples/redirect/tests/gtest.rs b/examples/redirect/tests/gtest.rs index 7c04a6d1b..cb6419fd1 100644 --- a/examples/redirect/tests/gtest.rs +++ b/examples/redirect/tests/gtest.rs @@ -47,7 +47,7 @@ async fn redirect_on_exit_works() { } fn create_env() -> (GtestEnv, CodeId, CodeId, GasUnit) { - use sails_rs::gtest::{MAX_USER_GAS_LIMIT, System}; + use sails_rs::gtest::{constants::MAX_USER_GAS_LIMIT, System}; let system = System::new(); system.init_logger_with_default_filter("gwasm=debug,gtest=info,sails_rs=debug,redirect=debug"); diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index 69f09a26c..2626d9764 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -106,6 +106,7 @@ const _: () = { use core::task::ready; #[cfg(not(feature = "ethexe"))] + #[inline] pub(crate) fn send_for_reply_future( target: ActorId, payload: &[u8], @@ -139,6 +140,7 @@ const _: () = { } #[cfg(feature = "ethexe")] + #[inline] fn send_for_reply_future( target: ActorId, payload: &[u8], @@ -153,7 +155,8 @@ const _: () = { Ok(message_future) } - pub(crate) fn send_for_reply( + #[inline] + fn send_for_reply( target: ActorId, payload: Vec, mut params: GstdParams, diff --git a/rs/src/gclient/mod.rs b/rs/src/gclient/mod.rs index 09075e5a5..9c5113d56 100644 --- a/rs/src/gclient/mod.rs +++ b/rs/src/gclient/mod.rs @@ -1 +1 @@ -pub mod calls; +// pub mod calls; diff --git a/rs/src/gstd/mod.rs b/rs/src/gstd/mod.rs index 0ccb8a656..4a2ea488a 100644 --- a/rs/src/gstd/mod.rs +++ b/rs/src/gstd/mod.rs @@ -20,11 +20,11 @@ use crate::{ }; use gcore::stack_buffer; -pub mod calls; +// pub mod calls; #[cfg(feature = "ethexe")] mod ethexe; mod events; -mod message_future; +// mod message_future; pub mod services; mod syscalls; diff --git a/rs/src/gtest/mod.rs b/rs/src/gtest/mod.rs index 662d98703..9c5a46586 100644 --- a/rs/src/gtest/mod.rs +++ b/rs/src/gtest/mod.rs @@ -1,3 +1,3 @@ pub use gtest::{BlockRunResult, Program, System, TestError, constants::*}; -pub mod calls; +// pub mod calls; diff --git a/rs/src/lib.rs b/rs/src/lib.rs index 202ead2cf..b33856c82 100644 --- a/rs/src/lib.rs +++ b/rs/src/lib.rs @@ -19,11 +19,11 @@ pub use spin::{self}; #[cfg(feature = "client-builder")] mod builder; -pub mod calls; +// pub mod calls; pub mod client; pub mod errors; -#[cfg(not(target_arch = "wasm32"))] -pub mod events; +// #[cfg(not(target_arch = "wasm32"))] +// pub mod events; #[cfg(feature = "gclient")] #[cfg(not(target_arch = "wasm32"))] pub mod gclient; @@ -31,10 +31,10 @@ pub mod gclient; pub mod gstd; #[cfg(feature = "gtest")] #[cfg(not(target_arch = "wasm32"))] -pub mod gtest; +pub use gtest::{self}; #[cfg(feature = "mockall")] #[cfg(not(target_arch = "wasm32"))] -pub mod mockall; +pub use mockall::{self}; pub mod prelude; #[cfg(feature = "ethexe")] pub mod solidity; From 4044c93037c1d83ca1e0e5939d213bb7b134fab8 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 10 Sep 2025 15:13:25 +0200 Subject: [PATCH 23/66] wip: redirect opt --- benchmarks/bench_data.json | 2 +- examples/demo/app/tests/gtest.rs | 2 +- examples/redirect/proxy/src/lib.rs | 11 ++++++----- examples/redirect/tests/gtest.rs | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index ae8fec4c5..e25a730ac 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -20,6 +20,6 @@ "median": 2461550210 }, "redirect": { - "median": 3672751012 + "median": 3514376024 } } \ No newline at end of file diff --git a/examples/demo/app/tests/gtest.rs b/examples/demo/app/tests/gtest.rs index 13ba3b3e4..f1f349265 100644 --- a/examples/demo/app/tests/gtest.rs +++ b/examples/demo/app/tests/gtest.rs @@ -13,7 +13,7 @@ pub(crate) const DEMO_WASM_PATH: &str = "../../../target/wasm32-gear/debug/demo. pub(crate) const DEMO_WASM_PATH: &str = "../../../target/wasm32-gear/release/demo.opt.wasm"; fn create_env() -> (GtestEnv, CodeId, GasUnit) { - use sails_rs::gtest::{constants::MAX_USER_GAS_LIMIT, System}; + use sails_rs::gtest::{System, constants::MAX_USER_GAS_LIMIT}; let system = System::new(); system.init_logger_with_default_filter("gwasm=debug,gtest=info,sails_rs=debug,redirect=debug"); diff --git a/examples/redirect/proxy/src/lib.rs b/examples/redirect/proxy/src/lib.rs index 8af2963c8..e375d3a09 100644 --- a/examples/redirect/proxy/src/lib.rs +++ b/examples/redirect/proxy/src/lib.rs @@ -3,11 +3,13 @@ use redirect_client::{redirect::Redirect as _, *}; use sails_rs::{client::*, prelude::*}; -struct ProxyService(ActorId); +struct ProxyService( + sails_rs::client::Service, +); impl ProxyService { - pub const fn new(target: ActorId) -> Self { - Self(target) + pub fn new(target: ActorId) -> Self { + Self(RedirectClientProgram::client(target).redirect()) } } @@ -16,8 +18,7 @@ impl ProxyService { /// Get program ID of the target program via client #[sails_rs::export] pub async fn get_program_id(&self) -> ActorId { - let client = RedirectClientProgram::client(self.0).redirect(); - client + self.0 .get_program_id() // Set flag to redirect on exit .with_redirect_on_exit(true) diff --git a/examples/redirect/tests/gtest.rs b/examples/redirect/tests/gtest.rs index cb6419fd1..32bf54409 100644 --- a/examples/redirect/tests/gtest.rs +++ b/examples/redirect/tests/gtest.rs @@ -47,7 +47,7 @@ async fn redirect_on_exit_works() { } fn create_env() -> (GtestEnv, CodeId, CodeId, GasUnit) { - use sails_rs::gtest::{constants::MAX_USER_GAS_LIMIT, System}; + use sails_rs::gtest::{System, constants::MAX_USER_GAS_LIMIT}; let system = System::new(); system.init_logger_with_default_filter("gwasm=debug,gtest=info,sails_rs=debug,redirect=debug"); From 4ad6e984132f6194ce3c2d1d9707f05b2135e212 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Thu, 11 Sep 2025 12:04:42 +0200 Subject: [PATCH 24/66] fix: logs --- rs/src/client/gtest_env.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rs/src/client/gtest_env.rs b/rs/src/client/gtest_env.rs index 3ea0c75c6..b5015b19c 100644 --- a/rs/src/client/gtest_env.rs +++ b/rs/src/client/gtest_env.rs @@ -351,12 +351,12 @@ impl Future for PendingCtor { .create_program(self.code_id, salt, payload.as_slice(), params); match send_res { Ok((program_id, message_id)) => { - log::debug!("PendingCall: send message {message_id:?}"); + log::debug!("PendingCtor: send message {message_id:?}"); self.state = Some(self.env.message_reply_from_next_blocks(message_id)); self.program_id = Some(program_id); } Err(err) => { - log::error!("PendingCall: failed to send message: {err}"); + log::error!("PendingCtor: failed to send message: {err}"); return Poll::Ready(Err(err)); } } From 227504e6bfc948bc1ccb6ec0b799f47ad9fd7d3d Mon Sep 17 00:00:00 2001 From: vobradovich Date: Thu, 11 Sep 2025 13:56:42 +0200 Subject: [PATCH 25/66] wip: gtest env query --- rs/ethexe/ethapp/tests/gtest.rs | 52 ++- rs/ethexe/ethapp_with_events/tests/gtest.rs | 60 ++-- rs/src/calls.rs | 315 ----------------- rs/src/client/gclient_env.rs | 6 +- rs/src/client/gtest_env.rs | 45 +++ rs/src/client/mod.rs | 12 +- rs/src/events.rs | 64 ---- rs/src/gtest/calls.rs | 364 -------------------- rs/src/gtest/mod.rs | 3 - rs/src/lib.rs | 3 - rs/src/mockall.rs | 95 ----- 11 files changed, 106 insertions(+), 913 deletions(-) delete mode 100644 rs/src/calls.rs delete mode 100644 rs/src/events.rs delete mode 100644 rs/src/gtest/calls.rs delete mode 100644 rs/src/gtest/mod.rs delete mode 100644 rs/src/mockall.rs diff --git a/rs/ethexe/ethapp/tests/gtest.rs b/rs/ethexe/ethapp/tests/gtest.rs index 12ce0ce0a..0215adb92 100644 --- a/rs/ethexe/ethapp/tests/gtest.rs +++ b/rs/ethexe/ethapp/tests/gtest.rs @@ -1,11 +1,8 @@ use sails_rs::{ alloy_primitives::B256, alloy_sol_types::SolValue, - calls::Remoting, - gtest::{ - Program, System, - calls::{GTestArgs, GTestRemoting}, - }, + client::*, + gtest::{Program, System}, }; #[cfg(debug_assertions)] @@ -19,7 +16,7 @@ pub(crate) const ADMIN_ID: u64 = 10; async fn ethapp_sol_works() { let system = System::new(); system.init_logger_with_default_filter("gwasm=debug,gtest=debug,sails_rs=debug"); - system.mint_to(ADMIN_ID, 100_000_000_000_000); + system.mint_to(ADMIN_ID, 1_000_000_000_000_000); let program = Program::from_file(&system, WASM_PATH); @@ -75,32 +72,29 @@ async fn ethapp_sol_works() { async fn ethapp_remoting_works() { let system = System::new(); system.init_logger_with_default_filter("gwasm=debug,gtest=debug,sails_rs=debug"); - system.mint_to(ADMIN_ID, 100_000_000_000_000); + system.mint_to(ADMIN_ID, 1_000_000_000_000_000); let code_id = system.submit_code_file(WASM_PATH); - let remoting = GTestRemoting::new(system, ADMIN_ID.into()); + let env = GtestEnv::new(system, ADMIN_ID.into()); let ctor = sails_rs::solidity::selector("createPrg(uint128,bool)"); let input = (0u128, false).abi_encode_sequence(); let payload = [ctor.as_slice(), input.as_slice()].concat(); - let (program_id, _) = remoting - .clone() - .activate(code_id, vec![], payload.as_slice(), 0, GTestArgs::default()) - .await - .unwrap() - .await + let (program_id, _) = env + .create_program(code_id, vec![], payload.as_slice(), Default::default()) .unwrap(); let do_this_sig = sails_rs::solidity::selector("svc1DoThis(uint128,bool,uint32,string)"); let do_this_params = (0u128, false, 42, "hello").abi_encode_sequence(); let payload = [do_this_sig.as_slice(), do_this_params.as_slice()].concat(); - let reply_payload = remoting - .clone() - .message(program_id, payload, 0, GTestArgs::default()) + let message_id = env + .send_message(program_id, payload, Default::default()) + .unwrap(); + let reply_payload = env + .message_reply_from_next_blocks(message_id) .await .unwrap() - .await .unwrap(); let reply = u32::abi_decode(reply_payload.as_slice(), true); @@ -111,21 +105,22 @@ async fn ethapp_remoting_works() { async fn ethapp_remoting_encode_reply_works() { let system = System::new(); system.init_logger_with_default_filter("gwasm=debug,gtest=debug,sails_rs=debug"); - system.mint_to(ADMIN_ID, 100_000_000_000_000); + system.mint_to(ADMIN_ID, 1_000_000_000_000_000); let code_id = system.submit_code_file(WASM_PATH); - let remoting = GTestRemoting::new(system, ADMIN_ID.into()); + let env = GtestEnv::new(system, ADMIN_ID.into()); let ctor = sails_rs::solidity::selector("createPrg(uint128,bool)"); let input = (0u128, true).abi_encode_sequence(); let payload = [ctor.as_slice(), input.as_slice()].concat(); // act - let (program_id, reply_payload) = remoting - .clone() - .activate(code_id, vec![], payload.as_slice(), 0, GTestArgs::default()) + let (program_id, message_id) = env + .create_program(code_id, vec![], payload.as_slice(), Default::default()) + .unwrap(); + let reply_payload = env + .message_reply_from_next_blocks(message_id) .await .unwrap() - .await .unwrap(); // assert @@ -139,12 +134,13 @@ async fn ethapp_remoting_encode_reply_works() { let payload = [do_this_sig.as_slice(), do_this_params.as_slice()].concat(); // act - let reply_payload = remoting - .clone() - .message(program_id, payload, 0, GTestArgs::default()) + let message_id = env + .send_message(program_id, payload, Default::default()) + .unwrap(); + let reply_payload = env + .message_reply_from_next_blocks(message_id) .await .unwrap() - .await .unwrap(); // assert diff --git a/rs/ethexe/ethapp_with_events/tests/gtest.rs b/rs/ethexe/ethapp_with_events/tests/gtest.rs index 37356e546..e002ff917 100644 --- a/rs/ethexe/ethapp_with_events/tests/gtest.rs +++ b/rs/ethexe/ethapp_with_events/tests/gtest.rs @@ -1,13 +1,9 @@ use ethapp_with_events::Events; use sails_rs::{ alloy_sol_types::SolValue, - calls::Remoting, - events::Listener, + client::*, futures::StreamExt, - gtest::{ - Program, System, - calls::{GTestArgs, GTestRemoting}, - }, + gtest::{Program, System}, prelude::*, }; @@ -23,7 +19,7 @@ async fn ethapp_with_events_low_level_works() { // arrange let system = System::new(); system.init_logger_with_default_filter("gwasm=debug,gtest=debug,sails_rs=debug"); - system.mint_to(ADMIN_ID, 100_000_000_000_000); + system.mint_to(ADMIN_ID, 1_000_000_000_000_000); let program = Program::from_file(&system, WASM_PATH); @@ -110,35 +106,32 @@ async fn ethapp_with_events_low_level_works() { async fn ethapp_with_events_remoting_works() { let system = System::new(); system.init_logger_with_default_filter("gwasm=debug,gtest=debug,sails_rs=debug"); - system.mint_to(ADMIN_ID, 100_000_000_000_000); + system.mint_to(ADMIN_ID, 1_000_000_000_000_000); let code_id = system.submit_code_file(WASM_PATH); - let remoting = GTestRemoting::new(system, ADMIN_ID.into()); - let mut binding = remoting.clone(); - let mut listener = binding.listen().await.unwrap(); + let env = GtestEnv::new(system, ADMIN_ID.into()); + let binding = env.clone(); + let mut listener = binding.listen(Some).await.unwrap(); let ctor = sails_rs::solidity::selector("create(uint128,bool)"); let input = (0u128, false).abi_encode_sequence(); let payload = [ctor.as_slice(), input.as_slice()].concat(); - let (program_id, _) = remoting - .clone() - .activate(code_id, vec![], payload.as_slice(), 0, GTestArgs::default()) - .await - .unwrap() - .await + let (program_id, _) = env + .create_program(code_id, vec![], payload.as_slice(), Default::default()) .unwrap(); let do_this_sig = sails_rs::solidity::selector("svc1DoThis(uint128,bool,uint32,string)"); let do_this_params = (0u128, false, 42, "hello").abi_encode_sequence(); let payload = [do_this_sig.as_slice(), do_this_params.as_slice()].concat(); - let reply_payload = remoting - .clone() - .message(program_id, payload, 0, GTestArgs::default()) + let message_id = env + .send_message(program_id, payload, Default::default()) + .unwrap(); + let reply_payload = env + .message_reply_from_next_blocks(message_id) .await .unwrap() - .await .unwrap(); let reply = u32::abi_decode(reply_payload.as_slice(), true); @@ -165,35 +158,32 @@ async fn ethapp_with_events_remoting_works() { async fn ethapp_with_events_exposure_emit_works() { let system = System::new(); system.init_logger_with_default_filter("gwasm=debug,gtest=debug,sails_rs=debug"); - system.mint_to(ADMIN_ID, 100_000_000_000_000); + system.mint_to(ADMIN_ID, 1_000_000_000_000_000); let code_id = system.submit_code_file(WASM_PATH); - let remoting = GTestRemoting::new(system, ADMIN_ID.into()); - let mut binding = remoting.clone(); - let mut listener = binding.listen().await.unwrap(); + let env = GtestEnv::new(system, ADMIN_ID.into()); + let binding = env.clone(); + let mut listener = binding.listen(Some).await.unwrap(); let ctor = sails_rs::solidity::selector("create(uint128,bool)"); let input = (0u128, false).abi_encode_sequence(); let payload = [ctor.as_slice(), input.as_slice()].concat(); - let (program_id, _) = remoting - .clone() - .activate(code_id, vec![], payload.as_slice(), 0, GTestArgs::default()) - .await - .unwrap() - .await + let (program_id, _) = env + .create_program(code_id, vec![], payload.as_slice(), Default::default()) .unwrap(); let do_this_sig = sails_rs::solidity::selector("svc2DoThis(uint128,bool,uint32,string)"); let do_this_params = (0u128, false, 42, "hello").abi_encode_sequence(); let payload = [do_this_sig.as_slice(), do_this_params.as_slice()].concat(); - let reply_payload = remoting - .clone() - .message(program_id, payload, 0, GTestArgs::default()) + let message_id = env + .send_message(program_id, payload, Default::default()) + .unwrap(); + let reply_payload = env + .message_reply_from_next_blocks(message_id) .await .unwrap() - .await .unwrap(); let reply = u32::abi_decode(reply_payload.as_slice(), true); diff --git a/rs/src/calls.rs b/rs/src/calls.rs deleted file mode 100644 index 379842820..000000000 --- a/rs/src/calls.rs +++ /dev/null @@ -1,315 +0,0 @@ -use crate::{ - errors::{Error, Result, RtlError}, - prelude::{any::TypeId, *}, -}; -use core::{future::Future, marker::PhantomData}; - -pub trait Action { - type Args; - - #[cfg(not(feature = "ethexe"))] - fn with_gas_limit(self, gas_limit: GasUnit) -> Self; - fn with_value(self, value: ValueUnit) -> Self; - fn with_args Self::Args>(self, args_fn: F) -> Self; - - #[cfg(not(feature = "ethexe"))] - fn gas_limit(&self) -> Option; - fn value(&self) -> ValueUnit; - fn args(&self) -> &Self::Args; -} - -#[allow(async_fn_in_trait)] -pub trait Call: Action { - type Output; - - async fn send(self, target: ActorId) -> Result>; - - async fn send_recv(self, target: ActorId) -> Result - where - Self: Sized, - { - self.send(target).await?.recv().await - } -} - -#[allow(async_fn_in_trait)] -pub trait Activation: Action { - async fn send>( - self, - code_id: CodeId, - salt: S, - ) -> Result>; - - async fn send_recv>(self, code_id: CodeId, salt: S) -> Result - where - Self: Sized, - { - self.send(code_id, salt).await?.recv().await - } -} - -#[allow(async_fn_in_trait)] -pub trait Query: Action { - type Output; - - async fn recv(self, target: ActorId) -> Result; -} - -#[allow(async_fn_in_trait)] -pub trait Reply { - type Output; - - async fn recv(self) -> Result; -} - -struct CallTicket { - reply_future: TReplyFuture, - _io: PhantomData, -} - -impl CallTicket { - pub(crate) fn new(reply_future: TReplyFuture) -> Self { - Self { - reply_future, - _io: PhantomData, - } - } -} - -impl Reply for CallTicket -where - TReplyFuture: Future>>, - TActionIo: ActionIo, -{ - type Output = TActionIo::Reply; - - async fn recv(self) -> Result { - let reply_bytes = self.reply_future.await?; - TActionIo::decode_reply(reply_bytes) - } -} - -struct ActivationTicket { - reply_future: TReplyFuture, - _io: PhantomData, -} - -impl ActivationTicket { - pub(crate) fn new(reply_future: TReplyFuture) -> Self { - Self { - reply_future, - _io: PhantomData, - } - } -} - -impl Reply for ActivationTicket -where - TReplyFuture: Future)>>, - TActionIo: ActionIo, -{ - type Output = ActorId; - - async fn recv(self) -> Result { - let (actor_id, payload) = self.reply_future.await?; - TActionIo::decode_reply(payload)?; - Ok(actor_id) - } -} - -#[allow(async_fn_in_trait)] -pub trait Remoting { - type Args: Default; - - async fn activate( - self, - code_id: CodeId, - salt: impl AsRef<[u8]>, - payload: impl AsRef<[u8]>, - #[cfg(not(feature = "ethexe"))] gas_limit: Option, - value: ValueUnit, - args: Self::Args, - ) -> Result)>>>; - - async fn message( - self, - target: ActorId, - payload: impl AsRef<[u8]>, - #[cfg(not(feature = "ethexe"))] gas_limit: Option, - value: ValueUnit, - args: Self::Args, - ) -> Result>>>; - - async fn query( - self, - target: ActorId, - payload: impl AsRef<[u8]>, - #[cfg(not(feature = "ethexe"))] gas_limit: Option, - value: ValueUnit, - args: Self::Args, - ) -> Result>; -} - -pub struct RemotingAction { - remoting: TRemoting, - params: TActionIo::Params, - #[cfg(not(feature = "ethexe"))] - gas_limit: Option, - value: ValueUnit, - args: TRemoting::Args, -} - -impl RemotingAction { - pub fn new(remoting: TRemoting, params: TActionIo::Params) -> Self { - Self { - remoting, - params, - #[cfg(not(feature = "ethexe"))] - gas_limit: Default::default(), - value: Default::default(), - args: Default::default(), - } - } -} - -impl Action for RemotingAction { - type Args = TRemoting::Args; - - #[cfg(not(feature = "ethexe"))] - fn with_gas_limit(self, gas_limit: GasUnit) -> Self { - Self { - gas_limit: Some(gas_limit), - ..self - } - } - - fn with_value(self, value: ValueUnit) -> Self { - Self { value, ..self } - } - - fn with_args Self::Args>(self, args_fn: F) -> Self { - let RemotingAction { args, .. } = self; - let args = args_fn(args); - Self { args, ..self } - } - - #[cfg(not(feature = "ethexe"))] - fn gas_limit(&self) -> Option { - self.gas_limit - } - - fn value(&self) -> ValueUnit { - self.value - } - - fn args(&self) -> &Self::Args { - &self.args - } -} - -impl Call for RemotingAction -where - TRemoting: Remoting, - TActionIo: ActionIo, -{ - type Output = TActionIo::Reply; - - async fn send(self, target: ActorId) -> Result> { - let payload = TActionIo::encode_call(&self.params); - let reply_future = self - .remoting - .message( - target, - payload, - #[cfg(not(feature = "ethexe"))] - self.gas_limit, - self.value, - self.args, - ) - .await?; - Ok(CallTicket::<_, TActionIo>::new(reply_future)) - } -} - -impl Activation for RemotingAction -where - TRemoting: Remoting, - TActionIo: ActionIo, -{ - async fn send>( - self, - code_id: CodeId, - salt: S, - ) -> Result> { - let payload = TActionIo::encode_call(&self.params); - let reply_future = self - .remoting - .activate( - code_id, - salt, - payload, - #[cfg(not(feature = "ethexe"))] - self.gas_limit, - self.value, - self.args, - ) - .await?; - Ok(ActivationTicket::<_, TActionIo>::new(reply_future)) - } -} - -impl Query for RemotingAction -where - TRemoting: Remoting, - TActionIo: ActionIo, -{ - type Output = TActionIo::Reply; - - async fn recv(self, target: ActorId) -> Result { - let payload = TActionIo::encode_call(&self.params); - let reply_bytes = self - .remoting - .query( - target, - payload, - #[cfg(not(feature = "ethexe"))] - self.gas_limit, - self.value, - self.args, - ) - .await?; - TActionIo::decode_reply(reply_bytes) - } -} - -pub trait ActionIo { - const ROUTE: &'static [u8]; - type Params: Encode; - type Reply: Decode + 'static; - - fn encode_call(value: &Self::Params) -> Vec { - let mut result = Vec::with_capacity(Self::ROUTE.len() + Encode::size_hint(value)); - result.extend_from_slice(Self::ROUTE); - Encode::encode_to(value, &mut result); - result - } - - fn decode_reply(payload: impl AsRef<[u8]>) -> Result { - let mut value = payload.as_ref(); - let zero_size_reply = Self::is_empty_tuple::(); - if !zero_size_reply && !value.starts_with(Self::ROUTE) { - return Err(Error::Rtl(RtlError::ReplyPrefixMismatches)); - } - let start_idx = if zero_size_reply { - 0 - } else { - Self::ROUTE.len() - }; - value = &value[start_idx..]; - Decode::decode(&mut value).map_err(Error::Codec) - } - - fn is_empty_tuple() -> bool { - TypeId::of::() == TypeId::of::<()>() - } -} diff --git a/rs/src/client/gclient_env.rs b/rs/src/client/gclient_env.rs index 99160f936..a69558c9b 100644 --- a/rs/src/client/gclient_env.rs +++ b/rs/src/client/gclient_env.rs @@ -225,10 +225,8 @@ impl PendingCall { .await?; // Decode reply - match T::decode_reply_with_prefix(self.route, reply_bytes) { - Ok(decoded) => Ok(decoded), - Err(err) => Err(gclient::Error::Codec(err).into()), - } + T::decode_reply_with_prefix(self.route, reply_bytes) + .map_err(|err| gclient::Error::Codec(err).into()) } } diff --git a/rs/src/client/gtest_env.rs b/rs/src/client/gtest_env.rs index b5015b19c..f9a2c1abe 100644 --- a/rs/src/client/gtest_env.rs +++ b/rs/src/client/gtest_env.rs @@ -206,6 +206,34 @@ impl GtestEnv { rx } + pub fn query( + &self, + target: ActorId, + payload: impl AsRef<[u8]>, + params: GtestParams, + ) -> Result, GtestError> { + let value = params.value.unwrap_or(0); + #[cfg(not(feature = "ethexe"))] + let gas_limit = params.gas_limit.unwrap_or(GAS_LIMIT_DEFAULT); + #[cfg(feature = "ethexe")] + let gas_limit = GAS_LIMIT_DEFAULT; + + let actor_id = params.actor_id.unwrap_or(self.actor_id); + let reply_info = self + .system + .calculate_reply_for_handle(actor_id, target, payload.as_ref(), gas_limit, value) + .map_err(|_s| GtestError::ReplyIsMissing)?; + + match reply_info.code { + ReplyCode::Success(_) => Ok(reply_info.payload), + ReplyCode::Error(err) => Err(GtestError::ReplyHasError(err, reply_info.payload)), + _ => { + log::debug!("Unexpected reply code: {:?}", reply_info.code); + Err(GtestError::ReplyIsMissing) + } + } + } + fn run_next_block_and_extract(&self) -> BlockRunResult { let run_result = self.system.run_next_block(); self.extract_events_and_replies(&run_result); @@ -233,6 +261,7 @@ impl GearEnv for GtestEnv { type Error = GtestError; type MessageState = ReplyReceiver; } + impl PendingCall { pub fn send_one_way(&mut self) -> Result { if self.state.is_some() { @@ -256,6 +285,22 @@ impl PendingCall { self.state = Some(self.env.message_reply_from_next_blocks(message_id)); Ok(self) } + + pub fn query(mut self) -> Result { + let params = self.params.unwrap_or_default(); + let args = self + .args + .take() + .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); + let payload = T::encode_params_with_prefix(self.route, &args); + + // Calculate reply + let reply_bytes = self.env.query(self.destination, payload, params)?; + + // Decode reply + T::decode_reply_with_prefix(self.route, reply_bytes) + .map_err(|err| TestError::ScaleCodecError(err).into()) + } } impl Future for PendingCall { diff --git a/rs/src/client/mod.rs b/rs/src/client/mod.rs index 7ab37b2a5..7ca740524 100644 --- a/rs/src/client/mod.rs +++ b/rs/src/client/mod.rs @@ -151,6 +151,14 @@ impl Service { } } + pub fn actor_id(&self) -> ActorId { + self.actor_id + } + + pub fn route(&self) -> Route { + self.route + } + pub fn with_actor_id(mut self, actor_id: ActorId) -> Self { self.actor_id = actor_id; self @@ -287,7 +295,7 @@ pub trait CallEncodeDecode { type Reply: Decode + 'static; fn encode_params(value: &Self::Params) -> Vec { - let mut result = Vec::with_capacity(1 + Self::ROUTE.len() + Encode::size_hint(value)); + let mut result = Vec::with_capacity(1 + Self::ROUTE.len() + Encode::encoded_size(value)); Encode::encode_to(Self::ROUTE, &mut result); Encode::encode_to(value, &mut result); result @@ -295,7 +303,7 @@ pub trait CallEncodeDecode { fn encode_params_with_prefix(prefix: Route, value: &Self::Params) -> Vec { let mut result = - Vec::with_capacity(2 + prefix.len() + Self::ROUTE.len() + Encode::size_hint(value)); + Vec::with_capacity(2 + prefix.len() + Self::ROUTE.len() + Encode::encoded_size(value)); Encode::encode_to(prefix, &mut result); Encode::encode_to(Self::ROUTE, &mut result); Encode::encode_to(value, &mut result); diff --git a/rs/src/events.rs b/rs/src/events.rs deleted file mode 100644 index 4dfe7d588..000000000 --- a/rs/src/events.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::{ - errors::{Error, Result, RtlError}, - futures::{Stream, StreamExt}, - prelude::*, -}; -use core::marker::PhantomData; - -#[allow(async_fn_in_trait)] -pub trait Listener { - type Error: core::error::Error; - - async fn listen(&mut self) -> Result + Unpin, Self::Error>; -} - -pub struct RemotingListener { - remoting: R, - _io: PhantomData, -} - -impl>, E> RemotingListener { - pub fn new(remoting: R) -> Self { - Self { - remoting, - _io: PhantomData, - } - } -} - -impl>, E: EventIo> Listener for RemotingListener { - type Error = R::Error; - - async fn listen( - &mut self, - ) -> Result + Unpin, Self::Error> { - let stream = self.remoting.listen().await?; - let map = stream.filter_map(move |(actor_id, payload)| async move { - E::decode_event(payload).ok().map(|e| (actor_id, e)) - }); - Ok(Box::pin(map)) - } -} - -pub trait EventIo { - const ROUTE: &'static [u8]; - const EVENT_NAMES: &'static [&'static [u8]]; - type Event: Decode; - - fn decode_event(payload: impl AsRef<[u8]>) -> Result { - let payload = payload.as_ref(); - if !payload.starts_with(Self::ROUTE) { - Err(RtlError::EventPrefixMismatches)?; - } - let event_bytes = &payload[Self::ROUTE.len()..]; - for (idx, name) in Self::EVENT_NAMES.iter().enumerate() { - if event_bytes.starts_with(name) { - let idx = idx as u8; - let bytes = [&[idx], &event_bytes[name.len()..]].concat(); - let mut event_bytes = &bytes[..]; - return Decode::decode(&mut event_bytes).map_err(Error::Codec); - } - } - Err(RtlError::EventNameIsNotFound)? - } -} diff --git a/rs/src/gtest/calls.rs b/rs/src/gtest/calls.rs deleted file mode 100644 index 382ed9c4d..000000000 --- a/rs/src/gtest/calls.rs +++ /dev/null @@ -1,364 +0,0 @@ -use crate::{ - calls::{Action, Query, Remoting}, - collections::HashMap, - errors::{Error, Result, RtlError}, - events::Listener, - futures::*, - gtest::{BlockRunResult, Program, System}, - prelude::*, - rc::Rc, -}; -use core::{cell::RefCell, future::Future}; -use gear_core_errors::ReplyCode; - -type EventSender = channel::mpsc::UnboundedSender<(ActorId, Vec)>; -type ReplySender = channel::oneshot::Sender>>; - -const GAS_LIMIT_DEFAULT: gtest::constants::Gas = gtest::constants::MAX_USER_GAS_LIMIT; - -#[derive(Debug, Default)] -pub struct GTestArgs { - actor_id: Option, - query_with_message: bool, -} - -impl GTestArgs { - pub fn new(actor_id: ActorId) -> Self { - Self { - actor_id: Some(actor_id), - query_with_message: false, - } - } - - pub fn with_actor_id(self, actor_id: ActorId) -> Self { - Self { - actor_id: Some(actor_id), - ..self - } - } - - pub fn with_query_with_message(self, flag: bool) -> Self { - Self { - query_with_message: flag, - ..self - } - } - - pub fn actor_id(&self) -> Option { - self.actor_id - } - - pub fn query_with_message(&self) -> bool { - self.query_with_message - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum BlockRunMode { - /// Run blocks automatically until all pending replies are received. - Auto, - /// Run only next block and exract events and replies from it. - /// If there is no reply in this block then `RtlError::ReplyIsMissing` error will be returned. - Next, - /// Sending messages does not cause blocks to run. - /// Use `GTestRemoting::run_next_block` method to run the next block and extract events and replies. - Manual, -} - -#[derive(Clone)] -pub struct GTestRemoting { - system: Rc, - actor_id: ActorId, - event_senders: Rc>>, - block_run_mode: BlockRunMode, - block_reply_senders: Rc>>, -} - -impl GTestRemoting { - /// Create new `GTestRemoting` instance from `gtest::System` with specified `actor_id` - /// and `Auto` block run mode - pub fn new(system: System, actor_id: ActorId) -> Self { - let system = Rc::new(system); - Self { - system, - actor_id, - event_senders: Default::default(), - block_run_mode: BlockRunMode::Auto, - block_reply_senders: Default::default(), - } - } - - // Avoid calling methods of `System` related to block execution. - // Use `GTestRemoting::run_next_block` instead. This method can be used - // for obtaining reference data like balance, timestamp, etc. - pub fn system(&self) -> &System { - &self.system - } - - pub fn with_block_run_mode(self, block_run_mode: BlockRunMode) -> Self { - Self { - block_run_mode, - ..self - } - } - - pub fn with_actor_id(self, actor_id: ActorId) -> Self { - Self { actor_id, ..self } - } - - pub fn actor_id(&self) -> ActorId { - self.actor_id - } - - pub fn run_next_block(&self) { - _ = self.run_next_block_and_extract(); - } -} - -impl GTestRemoting { - fn extract_events_and_replies(&self, run_result: &BlockRunResult) { - log::debug!( - "Process block #{} run result, mode {:?}", - run_result.block_info.height, - &self.block_run_mode - ); - let mut event_senders = self.event_senders.borrow_mut(); - let mut reply_senders = self.block_reply_senders.borrow_mut(); - // remove closed event senders - event_senders.retain(|c| !c.is_closed()); - // iterate over log - for entry in run_result.log().iter() { - if entry.destination() == ActorId::zero() { - log::debug!("Extract event from entry {entry:?}"); - for sender in event_senders.iter() { - _ = sender.unbounded_send((entry.source(), entry.payload().to_vec())); - } - continue; - } - #[cfg(feature = "ethexe")] - if entry.destination() == crate::solidity::ETH_EVENT_ADDR { - log::debug!("Extract event from entry {:?}", entry); - for sender in event_senders.iter() { - _ = sender.unbounded_send((entry.source(), entry.payload().to_vec())); - } - continue; - } - if let Some(message_id) = entry.reply_to() - && let Some(sender) = reply_senders.remove(&message_id) - { - log::debug!("Extract reply from entry {entry:?}"); - let reply: result::Result, _> = match entry.reply_code() { - None => Err(RtlError::ReplyCodeIsMissing.into()), - Some(ReplyCode::Error(reason)) => { - Err(RtlError::ReplyHasError(reason, entry.payload().to_vec()).into()) - } - Some(ReplyCode::Success(_)) => Ok(entry.payload().to_vec()), - _ => Err(RtlError::ReplyIsMissing.into()), - }; - _ = sender.send(reply); - } - } - } - - fn send_message( - &self, - target: ActorId, - payload: impl AsRef<[u8]>, - #[cfg(not(feature = "ethexe"))] gas_limit: Option, - value: ValueUnit, - args: GTestArgs, - ) -> Result { - #[cfg(not(feature = "ethexe"))] - let gas_limit = gas_limit.unwrap_or(GAS_LIMIT_DEFAULT); - #[cfg(feature = "ethexe")] - let gas_limit = GAS_LIMIT_DEFAULT; - let program = self - .system - .get_program(target) - .ok_or(RtlError::ProgramIsNotFound)?; - let actor_id = args.actor_id.unwrap_or(self.actor_id); - let message_id = program.send_bytes_with_gas(actor_id, payload.as_ref(), gas_limit, value); - log::debug!("Send message id: {message_id}, to: {target}"); - Ok(message_id) - } - - fn message_reply_from_next_blocks( - &self, - message_id: MessageId, - ) -> impl Future>> + use<> { - let (tx, rx) = channel::oneshot::channel::>>(); - self.block_reply_senders.borrow_mut().insert(message_id, tx); - - match self.block_run_mode { - BlockRunMode::Auto => { - self.run_until_extract_replies(); - } - BlockRunMode::Next => { - self.run_next_block_and_extract(); - self.drain_reply_senders(); - } - BlockRunMode::Manual => (), - }; - - rx.unwrap_or_else(|_| Err(RtlError::ReplyIsMissing.into())) - } - - fn run_next_block_and_extract(&self) -> BlockRunResult { - let run_result = self.system.run_next_block(); - self.extract_events_and_replies(&run_result); - run_result - } - - fn run_until_extract_replies(&self) { - while !self.block_reply_senders.borrow().is_empty() { - self.run_next_block_and_extract(); - } - } - - fn drain_reply_senders(&self) { - let mut reply_senders = self.block_reply_senders.borrow_mut(); - // drain reply senders that not founded in block - for (message_id, sender) in reply_senders.drain() { - log::debug!("Reply is missing in block for message {message_id}"); - _ = sender.send(Err(RtlError::ReplyIsMissing.into())); - } - } - - fn query_calculate_reply( - &self, - target: ActorId, - payload: impl AsRef<[u8]>, - gas_limit: GasUnit, - value: ValueUnit, - args: GTestArgs, - ) -> Result> { - let actor_id = args.actor_id.unwrap_or(self.actor_id); - let reply_info = self - .system - .calculate_reply_for_handle(actor_id, target, payload.as_ref(), gas_limit, value) - .map_err(|_| RtlError::ReplyIsMissing)?; - - match reply_info.code { - ReplyCode::Success(_) => Ok(reply_info.payload), - ReplyCode::Error(err) => Err(RtlError::ReplyHasError(err, reply_info.payload).into()), - _ => { - log::trace!("Unexpected reply code: {:?}", reply_info.code); - Err(RtlError::ReplyIsMissing.into()) - } - } - } -} - -impl Remoting for GTestRemoting { - type Args = GTestArgs; - - async fn activate( - self, - code_id: CodeId, - salt: impl AsRef<[u8]>, - payload: impl AsRef<[u8]>, - #[cfg(not(feature = "ethexe"))] gas_limit: Option, - value: ValueUnit, - args: GTestArgs, - ) -> Result)>>> { - #[cfg(not(feature = "ethexe"))] - let gas_limit = gas_limit.unwrap_or(GAS_LIMIT_DEFAULT); - #[cfg(feature = "ethexe")] - let gas_limit = GAS_LIMIT_DEFAULT; - let code = self - .system - .submitted_code(code_id) - .ok_or(RtlError::ProgramCodeIsNotFound)?; - let program_id = gtest::calculate_program_id(code_id, salt.as_ref(), None); - let program = Program::from_binary_with_id(&self.system, program_id, code); - let actor_id = args.actor_id.unwrap_or(self.actor_id); - let message_id = program.send_bytes_with_gas(actor_id, payload.as_ref(), gas_limit, value); - log::debug!("Send activation id: {message_id}, to program: {program_id}"); - Ok(self - .message_reply_from_next_blocks(message_id) - .map(move |result| result.map(|reply| (program_id, reply)))) - } - - async fn message( - self, - target: ActorId, - payload: impl AsRef<[u8]>, - #[cfg(not(feature = "ethexe"))] gas_limit: Option, - value: ValueUnit, - args: GTestArgs, - ) -> Result>>> { - let message_id = self.send_message( - target, - payload, - #[cfg(not(feature = "ethexe"))] - gas_limit, - value, - args, - )?; - Ok(self.message_reply_from_next_blocks(message_id)) - } - - async fn query( - self, - target: ActorId, - payload: impl AsRef<[u8]>, - #[cfg(not(feature = "ethexe"))] gas_limit: Option, - value: ValueUnit, - args: GTestArgs, - ) -> Result> { - if args.query_with_message() { - let message_id = self.send_message( - target, - payload, - #[cfg(not(feature = "ethexe"))] - gas_limit, - value, - args, - )?; - - self.message_reply_from_next_blocks(message_id).await - } else { - #[cfg(not(feature = "ethexe"))] - let gas_limit = gas_limit.unwrap_or(GAS_LIMIT_DEFAULT); - #[cfg(feature = "ethexe")] - let gas_limit = GAS_LIMIT_DEFAULT; - self.query_calculate_reply(target, payload, gas_limit, value, args) - } - } -} - -impl Listener> for GTestRemoting { - type Error = Error; - - async fn listen(&mut self) -> Result)>> { - let (tx, rx) = channel::mpsc::unbounded::<(ActorId, Vec)>(); - self.event_senders.borrow_mut().push(tx); - Ok(rx) - } -} - -pub trait WithArgs { - fn with_actor_id(self, actor_id: ActorId) -> Self; -} - -impl WithArgs for T -where - T: Action, -{ - fn with_actor_id(self, actor_id: ActorId) -> Self { - self.with_args(|args| args.with_actor_id(actor_id)) - } -} - -pub trait QueryExtGTest { - fn query_with_message(self, flag: bool) -> Self; -} - -impl QueryExtGTest for T -where - T: Query, -{ - fn query_with_message(self, flag: bool) -> Self { - self.with_args(|args| args.with_query_with_message(flag)) - } -} diff --git a/rs/src/gtest/mod.rs b/rs/src/gtest/mod.rs deleted file mode 100644 index 9c5a46586..000000000 --- a/rs/src/gtest/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub use gtest::{BlockRunResult, Program, System, TestError, constants::*}; - -// pub mod calls; diff --git a/rs/src/lib.rs b/rs/src/lib.rs index b33856c82..82e4b86f3 100644 --- a/rs/src/lib.rs +++ b/rs/src/lib.rs @@ -19,11 +19,8 @@ pub use spin::{self}; #[cfg(feature = "client-builder")] mod builder; -// pub mod calls; pub mod client; pub mod errors; -// #[cfg(not(target_arch = "wasm32"))] -// pub mod events; #[cfg(feature = "gclient")] #[cfg(not(target_arch = "wasm32"))] pub mod gclient; diff --git a/rs/src/mockall.rs b/rs/src/mockall.rs deleted file mode 100644 index f6551dcc8..000000000 --- a/rs/src/mockall.rs +++ /dev/null @@ -1,95 +0,0 @@ -use crate::{calls::*, errors::Result, prelude::*}; - -pub use mockall::*; - -mock! { - pub Activation {} - - impl Action for Activation { - type Args = A; - - #[cfg(not(feature = "ethexe"))] - fn with_gas_limit(self, gas_limit: GasUnit) -> Self; - fn with_value(self, value: ValueUnit) -> Self; - #[mockall::concretize] - fn with_args A>(self, args_fn: F) -> Self; - - #[cfg(not(feature = "ethexe"))] - fn gas_limit(&self) -> Option; - fn value(&self) -> ValueUnit; - fn args(&self) -> &A; - } - - impl Activation for Activation - { - #[allow(refining_impl_trait)] - #[mockall::concretize] - async fn send>(self, code_id: CodeId, salt: S) -> Result>; - #[mockall::concretize] - async fn send_recv>(self, d: CodeId, salt: S) -> Result; - } -} - -mock! { - pub Call {} - - impl Action for Call { - type Args = A; - - #[cfg(not(feature = "ethexe"))] - fn with_gas_limit(self, gas_limit: GasUnit) -> Self; - fn with_value(self, value: ValueUnit) -> Self; - #[mockall::concretize] - fn with_args A>(self, args_fn: F) -> Self; - - #[cfg(not(feature = "ethexe"))] - fn gas_limit(&self) -> Option; - fn value(&self) -> ValueUnit; - fn args(&self) -> &A; - } - - impl Call for Call - { - type Output = O; - - #[allow(refining_impl_trait)] - async fn send(self, target: ActorId) -> Result>; - async fn send_recv(self, target: ActorId) -> Result; - } -} - -mock! { - pub Query {} - - impl Action for Query { - type Args = A; - - #[cfg(not(feature = "ethexe"))] - fn with_gas_limit(self, gas_limit: GasUnit) -> Self; - fn with_value(self, value: ValueUnit) -> Self; - #[mockall::concretize] - fn with_args A>(self, args_fn: F) -> Self; - - #[cfg(not(feature = "ethexe"))] - fn gas_limit(&self) -> Option; - fn value(&self) -> ValueUnit; - fn args(&self) -> &A; - } - - impl Query for Query { - type Output = O; - - async fn recv(self, target: ActorId) -> Result; - } -} - -mock! { - pub Reply {} - - impl Reply for Reply - { - type Output = O; - - async fn recv(self) -> Result; - } -} From 94ca161caae288b5889730ac868acc34be4558f8 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Fri, 12 Sep 2025 13:07:04 +0200 Subject: [PATCH 26/66] wip: gclient, gtest funcs, remove old code --- examples/demo/app/tests/gclient.rs | 2 +- examples/demo/app/tests/gtest.rs | 4 +- .../rmrk/resource/wasm/tests/resources.rs | 14 +- rs/src/client/gclient_env.rs | 331 +++++++++++------- rs/src/client/gstd_env.rs | 58 +-- rs/src/client/gtest_env.rs | 32 +- rs/src/gclient/calls.rs | 279 --------------- rs/src/gclient/mod.rs | 1 - rs/src/gstd/calls.rs | 285 --------------- rs/src/gstd/message_future.rs | 175 --------- rs/src/gstd/mod.rs | 2 - rs/src/lib.rs | 10 +- 12 files changed, 263 insertions(+), 930 deletions(-) delete mode 100644 rs/src/gclient/calls.rs delete mode 100644 rs/src/gclient/mod.rs delete mode 100644 rs/src/gstd/calls.rs delete mode 100644 rs/src/gstd/message_future.rs diff --git a/examples/demo/app/tests/gclient.rs b/examples/demo/app/tests/gclient.rs index 597d4a4c7..70e153368 100644 --- a/examples/demo/app/tests/gclient.rs +++ b/examples/demo/app/tests/gclient.rs @@ -116,7 +116,7 @@ async fn ping_pong_works() { // Act let ping_reply_payload = env - .send_message( + .send_for_reply( demo_program.id(), ping_call_payload, GclientParams::default().with_gas_limit(gas_limit), diff --git a/examples/demo/app/tests/gtest.rs b/examples/demo/app/tests/gtest.rs index f1f349265..390ded54e 100644 --- a/examples/demo/app/tests/gtest.rs +++ b/examples/demo/app/tests/gtest.rs @@ -412,8 +412,8 @@ async fn counter_add_works_via_manual_mode() { let mut counter_events = counter_listener.listen().await.unwrap(); // Use generated client code for calling Counter service - let call_add = counter_client.add(10).send_message().unwrap(); - let call_sub = counter_client.sub(20).send_message().unwrap(); + let call_add = counter_client.add(10).send_for_reply().unwrap(); + let call_sub = counter_client.sub(20).send_for_reply().unwrap(); // Run next Block env.run_next_block(); diff --git a/examples/rmrk/resource/wasm/tests/resources.rs b/examples/rmrk/resource/wasm/tests/resources.rs index fed8d8033..1352ebabf 100644 --- a/examples/rmrk/resource/wasm/tests/resources.rs +++ b/examples/rmrk/resource/wasm/tests/resources.rs @@ -452,15 +452,13 @@ impl Fixture { resource.encode(), ] .concat(); - let env = self.env.clone(); - let message_id = env.send_message( - self.resource_program_id, - encoded_request, - GtestParams::default().with_actor_id(actor_id.into()), - )?; - env.message_reply_from_next_blocks(message_id) + self.env + .send_for_reply( + self.resource_program_id, + encoded_request, + GtestParams::default().with_actor_id(actor_id.into()), + ) .await - .unwrap() } async fn add_resource_via_client( diff --git a/rs/src/client/gclient_env.rs b/rs/src/client/gclient_env.rs index a69558c9b..5257e8517 100644 --- a/rs/src/client/gclient_env.rs +++ b/rs/src/client/gclient_env.rs @@ -1,4 +1,5 @@ use super::*; +use ::gclient::metadata::runtime_types::pallet_gear_voucher::internal::VoucherId; use ::gclient::{EventListener, EventProcessor as _, GearApi}; use core::task::ready; use futures::{Stream, StreamExt, stream}; @@ -9,8 +10,8 @@ pub enum GclientError { Env(#[from] gclient::Error), #[error("reply error: {0}")] ReplyHasError(ErrorReplyReason, crate::Vec), - #[error("unsupported")] - Unsupported, + #[error("reply is missing")] + ReplyIsMissing, } #[derive(Clone)] @@ -25,6 +26,7 @@ crate::params_struct_impl!( gas_limit: GasUnit, value: ValueUnit, at_block: H256, + voucher: (VoucherId, bool), } ); @@ -48,64 +50,35 @@ impl GclientEnv { let api = self.api.clone(); create_program(api, code_id, salt, payload, params).await } - pub async fn send_message( + + pub async fn send_for_reply( &self, program_id: ActorId, payload: Vec, params: GclientParams, ) -> Result, GclientError> { let api = self.api.clone(); - send_message(api, program_id, payload, params) + send_for_reply(api, program_id, payload, params) .await .map(|(_program_id, payload)| payload) } - pub async fn query_calculate_reply( + pub async fn send_one_way( &self, - target: ActorId, - payload: impl AsRef<[u8]>, + program_id: ActorId, + payload: Vec, params: GclientParams, - ) -> Result, GclientError> { - let api = self.api.clone(); - query_calculate_reply(api, target, payload, params).await + ) -> Result { + send_one_way(&self.api, program_id, payload, params).await } -} - -async fn query_calculate_reply( - api: GearApi, - target: ActorId, - payload: impl AsRef<[u8]>, - params: GclientParams, -) -> Result, GclientError> { - // Get Max gas amount if it is not explicitly set - #[cfg(not(feature = "ethexe"))] - let gas_limit = if let Some(gas_limit) = params.gas_limit { - gas_limit - } else { - api.block_gas_limit()? - }; - #[cfg(feature = "ethexe")] - let gas_limit = 0; - let value = params.value.unwrap_or(0); - let origin = H256::from_slice(api.account_id().as_ref()); - let payload = payload.as_ref().to_vec(); - - let reply_info = api - .calculate_reply_for_handle_at( - Some(origin), - target, - payload, - gas_limit, - value, - params.at_block, - ) - .await?; - match reply_info.code { - ReplyCode::Success(_) => Ok(reply_info.payload), - ReplyCode::Error(reason) => Err(GclientError::ReplyHasError(reason, reply_info.payload)), - // TODO - ReplyCode::Unsupported => Err(GclientError::Unsupported), + pub async fn query( + &self, + destination: ActorId, + payload: impl AsRef<[u8]>, + params: GclientParams, + ) -> Result, GclientError> { + query_calculate_reply(&self.api, destination, payload, params).await } } @@ -115,99 +88,32 @@ impl GearEnv for GclientEnv { type MessageState = Pin), GclientError>>>>; } -async fn create_program( - api: GearApi, - code_id: CodeId, - salt: impl AsRef<[u8]>, - payload: impl AsRef<[u8]>, - params: GclientParams, -) -> Result<(ActorId, Vec), GclientError> { - let value = params.value.unwrap_or(0); - // Calculate gas amount if it is not explicitly set - #[cfg(not(feature = "ethexe"))] - let gas_limit = if let Some(gas_limit) = params.gas_limit { - gas_limit - } else { - // Calculate gas amount needed for initialization - let gas_info = api - .calculate_create_gas(None, code_id, Vec::from(payload.as_ref()), value, true) - .await?; - gas_info.min_limit - }; - #[cfg(feature = "ethexe")] - let gas_limit = 0; - - let mut listener = api.subscribe().await?; - let (message_id, program_id, ..) = api - .create_program_bytes(code_id, salt, payload, gas_limit, value) - .await?; - let (_, reply_code, payload, _) = wait_for_reply(&mut listener, message_id).await?; - match reply_code { - ReplyCode::Success(_) => Ok((program_id, payload)), - ReplyCode::Error(reason) => Err(GclientError::ReplyHasError(reason, payload)), - ReplyCode::Unsupported => Err(GclientError::Unsupported), - } -} - -async fn send_message( - api: GearApi, - program_id: ActorId, - payload: Vec, - params: GclientParams, -) -> Result<(ActorId, Vec), GclientError> { - let value = params.value.unwrap_or(0); - #[cfg(not(feature = "ethexe"))] - let gas_limit = if let Some(gas_limit) = params.gas_limit { - gas_limit - } else { - // Calculate gas amount needed for handling the message - let gas_info = api - .calculate_handle_gas(None, program_id, payload.clone(), value, true) - .await?; - gas_info.min_limit - }; - #[cfg(feature = "ethexe")] - let gas_limit = 0; +impl PendingCall { + pub async fn send_one_way(&mut self) -> Result { + let params = self.params.take().unwrap_or_default(); + let args = self + .args + .take() + .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); + let payload = T::encode_params_with_prefix(self.route, &args); - let mut listener = api.subscribe().await?; - let (message_id, ..) = api - .send_message_bytes(program_id, payload, gas_limit, value) - .await?; - let (_, reply_code, payload, _) = wait_for_reply(&mut listener, message_id).await?; - match reply_code { - ReplyCode::Success(_) => Ok((program_id, payload)), - ReplyCode::Error(reason) => Err(GclientError::ReplyHasError(reason, payload)), - ReplyCode::Unsupported => Err(GclientError::Unsupported), + self.env + .send_one_way(self.destination, payload, params) + .await } -} -impl PendingCall { - pub async fn send(mut self) -> Result { - let api = &self.env.api; - let params = self.params.unwrap_or_default(); + pub async fn send_for_reply(mut self) -> Result { + let params = self.params.take().unwrap_or_default(); let args = self .args .take() .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); let payload = T::encode_params_with_prefix(self.route, &args); - let value = params.value.unwrap_or(0); - #[cfg(not(feature = "ethexe"))] - let gas_limit = if let Some(gas_limit) = params.gas_limit { - gas_limit - } else { - // Calculate gas amount needed for handling the message - let gas_info = api - .calculate_handle_gas(None, self.destination, payload.clone(), value, true) - .await?; - gas_info.min_limit - }; - #[cfg(feature = "ethexe")] - let gas_limit = 0; - - let (message_id, ..) = api - .send_message_bytes(self.destination, payload, gas_limit, value) - .await?; - Ok(message_id) + + // send for reply + let send_future = send_for_reply(self.env.api.clone(), self.destination, payload, params); + self.state = Some(Box::pin(send_future)); + Ok(self) } pub async fn query(mut self) -> Result { @@ -219,10 +125,8 @@ impl PendingCall { let payload = T::encode_params_with_prefix(self.route, &args); // Calculate reply - let reply_bytes = self - .env - .query_calculate_reply(self.destination, payload, params) - .await?; + let reply_bytes = + query_calculate_reply(&self.env.api, self.destination, payload, params).await?; // Decode reply T::decode_reply_with_prefix(self.route, reply_bytes) @@ -243,7 +147,8 @@ impl Future for PendingCall { .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); let payload = T::encode_params_with_prefix(self.route, &args); - let send_future = send_message(self.env.api.clone(), self.destination, payload, params); + let send_future = + send_for_reply(self.env.api.clone(), self.destination, payload, params); self.state = Some(Box::pin(send_future)); } @@ -316,6 +221,160 @@ impl Listener for GclientEnv { } } +async fn create_program( + api: GearApi, + code_id: CodeId, + salt: impl AsRef<[u8]>, + payload: impl AsRef<[u8]>, + params: GclientParams, +) -> Result<(ActorId, Vec), GclientError> { + let value = params.value.unwrap_or(0); + // Calculate gas amount if it is not explicitly set + #[cfg(not(feature = "ethexe"))] + let gas_limit = if let Some(gas_limit) = params.gas_limit { + gas_limit + } else { + // Calculate gas amount needed for initialization + let gas_info = api + .calculate_create_gas(None, code_id, Vec::from(payload.as_ref()), value, true) + .await?; + gas_info.min_limit + }; + #[cfg(feature = "ethexe")] + let gas_limit = 0; + + let mut listener = api.subscribe().await?; + let (message_id, program_id, ..) = api + .create_program_bytes(code_id, salt, payload, gas_limit, value) + .await?; + let (_, reply_code, payload, _) = wait_for_reply(&mut listener, message_id).await?; + match reply_code { + ReplyCode::Success(_) => Ok((program_id, payload)), + ReplyCode::Error(reason) => Err(GclientError::ReplyHasError(reason, payload)), + ReplyCode::Unsupported => Err(GclientError::ReplyIsMissing), + } +} + +async fn send_for_reply( + api: GearApi, + program_id: ActorId, + payload: Vec, + params: GclientParams, +) -> Result<(ActorId, Vec), GclientError> { + let value = params.value.unwrap_or(0); + let gas_limit = + calculate_gas_limit(&api, program_id, &payload, params.gas_limit, value).await?; + + let mut listener = api.subscribe().await?; + let message_id = send_message_with_voucher_if_some( + &api, + program_id, + payload, + gas_limit, + value, + params.voucher, + ) + .await?; + let (_, reply_code, payload, _) = wait_for_reply(&mut listener, message_id).await?; + match reply_code { + ReplyCode::Success(_) => Ok((program_id, payload)), + ReplyCode::Error(reason) => Err(GclientError::ReplyHasError(reason, payload)), + ReplyCode::Unsupported => Err(GclientError::ReplyIsMissing), + } +} + +async fn send_one_way( + api: &GearApi, + program_id: ActorId, + payload: Vec, + params: GclientParams, +) -> Result { + let value = params.value.unwrap_or(0); + let gas_limit = calculate_gas_limit(api, program_id, &payload, params.gas_limit, value).await?; + + send_message_with_voucher_if_some(api, program_id, payload, gas_limit, value, params.voucher) + .await +} + +async fn send_message_with_voucher_if_some( + api: &GearApi, + program_id: ActorId, + payload: impl AsRef<[u8]>, + gas_limit: GasUnit, + value: ValueUnit, + voucher: Option<(VoucherId, bool)>, +) -> Result { + let (message_id, ..) = if let Some((voucher_id, keep_alive)) = voucher { + api.send_message_bytes_with_voucher( + voucher_id, program_id, payload, gas_limit, value, keep_alive, + ) + .await? + } else { + api.send_message_bytes(program_id, payload, gas_limit, value) + .await? + }; + Ok(message_id) +} + +async fn calculate_gas_limit( + api: &GearApi, + program_id: ActorId, + payload: impl AsRef<[u8]>, + gas_limit: Option, + value: ValueUnit, +) -> Result { + #[cfg(not(feature = "ethexe"))] + let gas_limit = if let Some(gas_limit) = gas_limit { + gas_limit + } else { + // Calculate gas amount needed for handling the message + let gas_info = api + .calculate_handle_gas(None, program_id, payload.as_ref().to_vec(), value, true) + .await?; + gas_info.min_limit + }; + #[cfg(feature = "ethexe")] + let gas_limit = 0; + Ok(gas_limit) +} + +async fn query_calculate_reply( + api: &GearApi, + destination: ActorId, + payload: impl AsRef<[u8]>, + params: GclientParams, +) -> Result, GclientError> { + // Get Max gas amount if it is not explicitly set + #[cfg(not(feature = "ethexe"))] + let gas_limit = if let Some(gas_limit) = params.gas_limit { + gas_limit + } else { + api.block_gas_limit()? + }; + #[cfg(feature = "ethexe")] + let gas_limit = 0; + let value = params.value.unwrap_or(0); + let origin = H256::from_slice(api.account_id().as_ref()); + let payload = payload.as_ref().to_vec(); + + let reply_info = api + .calculate_reply_for_handle_at( + Some(origin), + destination, + payload, + gas_limit, + value, + params.at_block, + ) + .await?; + + match reply_info.code { + ReplyCode::Success(_) => Ok(reply_info.payload), + ReplyCode::Error(reason) => Err(GclientError::ReplyHasError(reason, reply_info.payload)), + ReplyCode::Unsupported => Err(GclientError::ReplyIsMissing), + } +} + async fn wait_for_reply( listener: &mut EventListener, message_id: MessageId, diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index 2626d9764..24fb3062a 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -75,6 +75,25 @@ impl GearEnv for GstdEnv { type MessageState = core::future::Ready, Self::Error>>; } +impl GstdEnv { + pub fn send_one_way( + &self, + destination: ActorId, + payload: impl AsRef<[u8]>, + params: GstdParams, + ) -> Result { + let value = params.value.unwrap_or(0); + + #[cfg(not(feature = "ethexe"))] + if let Some(gas_limit) = params.gas_limit { + return ::gcore::msg::send_with_gas(destination, payload.as_ref(), gas_limit, value) + .map_err(Error::Core); + } + + ::gcore::msg::send(destination, payload.as_ref(), value).map_err(Error::Core) + } +} + impl PendingCall { pub fn send_one_way(&mut self) -> Result { let args = self @@ -84,20 +103,7 @@ impl PendingCall { let payload = T::encode_params_with_prefix(self.route, &args); let params = self.params.take().unwrap_or_default(); - let value = params.value.unwrap_or(0); - - #[cfg(not(feature = "ethexe"))] - if let Some(gas_limit) = params.gas_limit { - return ::gcore::msg::send_with_gas( - self.destination, - payload.as_slice(), - gas_limit, - value, - ) - .map_err(Error::Core); - } - - ::gcore::msg::send(self.destination, payload.as_slice(), value).map_err(Error::Core) + self.env.send_one_way(self.destination, payload, params) } } @@ -107,8 +113,8 @@ const _: () = { #[cfg(not(feature = "ethexe"))] #[inline] - pub(crate) fn send_for_reply_future( - target: ActorId, + fn send_for_reply_future( + destination: ActorId, payload: &[u8], params: &mut GstdParams, ) -> Result { @@ -116,7 +122,7 @@ const _: () = { // here can be a redirect target let mut message_future = if let Some(gas_limit) = params.gas_limit { ::gstd::msg::send_bytes_with_gas_for_reply( - target, + destination, payload, gas_limit, value, @@ -124,7 +130,7 @@ const _: () = { )? } else { ::gstd::msg::send_bytes_for_reply( - target, + destination, payload, value, params.reply_deposit.unwrap_or_default(), @@ -142,13 +148,13 @@ const _: () = { #[cfg(feature = "ethexe")] #[inline] fn send_for_reply_future( - target: ActorId, + destination: ActorId, payload: &[u8], params: &mut GstdParams, ) -> Result { let value = params.value.unwrap_or(0); // here can be a redirect target - let mut message_future = ::gstd::msg::send_bytes_for_reply(target, payload, value)?; + let mut message_future = ::gstd::msg::send_bytes_for_reply(destination, payload, value)?; message_future = message_future.up_to(params.wait_up_to)?; @@ -157,12 +163,12 @@ const _: () = { #[inline] fn send_for_reply( - target: ActorId, + destination: ActorId, payload: Vec, mut params: GstdParams, ) -> Result { // send message - let future = send_for_reply_future(target, payload.as_ref(), &mut params)?; + let future = send_for_reply_future(destination, payload.as_ref(), &mut params)?; if params.redirect_on_exit { let created_block = params.wait_up_to.map(|_| gstd::exec::block_height()); Ok(GtsdFuture::MessageWithRedirect { @@ -170,7 +176,7 @@ const _: () = { future, params, payload, - target, + destination, }) } else { Ok(GtsdFuture::Message { future }) @@ -222,7 +228,7 @@ const _: () = { ErrorReplyReason::UnavailableActor(SimpleUnavailableActorError::ProgramExited), )) => { if let Replace::MessageWithRedirect { - target: _target, + destination: _destination, payload, mut params, created_block, @@ -231,7 +237,7 @@ const _: () = { && params.redirect_on_exit && let Ok(new_target) = ActorId::try_from(error_payload.0.as_ref()) { - gstd::debug!("Redirecting message from {_target} to {new_target}"); + gstd::debug!("Redirecting message from {_destination} to {new_target}"); // Calculate updated `wait_up_to` if provided // wait_up_to = wait_up_to - (current_block - created_block) @@ -345,7 +351,7 @@ pin_project_lite::pin_project! { MessageWithRedirect { #[pin] future: MessageFuture, - target: ActorId, + destination: ActorId, payload: Vec, params: GstdParams, created_block: Option, diff --git a/rs/src/client/gtest_env.rs b/rs/src/client/gtest_env.rs index f9a2c1abe..942c3a71b 100644 --- a/rs/src/client/gtest_env.rs +++ b/rs/src/client/gtest_env.rs @@ -165,9 +165,9 @@ impl GtestEnv { Ok((program_id, message_id)) } - pub fn send_message( + pub fn send_one_way( &self, - target: ActorId, + destination: ActorId, payload: impl AsRef<[u8]>, params: GtestParams, ) -> Result { @@ -178,17 +178,29 @@ impl GtestEnv { let gas_limit = GAS_LIMIT_DEFAULT; let program = self .system - .get_program(target) - .ok_or(TestError::ActorNotFound(target))?; + .get_program(destination) + .ok_or(TestError::ActorNotFound(destination))?; let actor_id = params.actor_id.unwrap_or(self.actor_id); let message_id = program.send_bytes_with_gas(actor_id, payload.as_ref(), gas_limit, value); log::debug!( - "Send message id: {message_id}, to: {target}, payload: {}", + "Send message id: {message_id}, to: {destination}, payload: {}", hex::encode(payload.as_ref()) ); Ok(message_id) } + pub async fn send_for_reply( + &self, + destination: ActorId, + payload: impl AsRef<[u8]>, + params: GtestParams, + ) -> Result, GtestError> { + let message_id = self.send_one_way(destination, payload, params)?; + self.message_reply_from_next_blocks(message_id) + .await + .unwrap_or(Err(GtestError::ReplyIsMissing)) + } + pub fn message_reply_from_next_blocks(&self, message_id: MessageId) -> ReplyReceiver { let (tx, rx) = oneshot::channel::, GtestError>>(); self.block_reply_senders.borrow_mut().insert(message_id, tx); @@ -208,7 +220,7 @@ impl GtestEnv { pub fn query( &self, - target: ActorId, + destination: ActorId, payload: impl AsRef<[u8]>, params: GtestParams, ) -> Result, GtestError> { @@ -221,7 +233,7 @@ impl GtestEnv { let actor_id = params.actor_id.unwrap_or(self.actor_id); let reply_info = self .system - .calculate_reply_for_handle(actor_id, target, payload.as_ref(), gas_limit, value) + .calculate_reply_for_handle(actor_id, destination, payload.as_ref(), gas_limit, value) .map_err(|_s| GtestError::ReplyIsMissing)?; match reply_info.code { @@ -275,12 +287,12 @@ impl PendingCall { let payload = T::encode_params_with_prefix(self.route, &args); let params = self.params.take().unwrap_or_default(); - let message_id = self.env.send_message(self.destination, payload, params)?; + let message_id = self.env.send_one_way(self.destination, payload, params)?; log::debug!("PendingCall: send message {message_id:?}"); Ok(message_id) } - pub fn send_message(mut self) -> Result { + pub fn send_for_reply(mut self) -> Result { let message_id = self.send_one_way()?; self.state = Some(self.env.message_reply_from_next_blocks(message_id)); Ok(self) @@ -316,7 +328,7 @@ impl Future for PendingCall { let payload = T::encode_params_with_prefix(self.route, &args); let params = self.params.take().unwrap_or_default(); - let send_res = self.env.send_message(self.destination, payload, params); + let send_res = self.env.send_one_way(self.destination, payload, params); match send_res { Ok(message_id) => { log::debug!("PendingCall: send message {message_id:?}"); diff --git a/rs/src/gclient/calls.rs b/rs/src/gclient/calls.rs deleted file mode 100644 index b4d821c2a..000000000 --- a/rs/src/gclient/calls.rs +++ /dev/null @@ -1,279 +0,0 @@ -use crate::{ - calls::{Query, Remoting}, - errors::{Error, Result, RtlError}, - events::Listener, - futures::{Stream, StreamExt, stream}, - prelude::*, -}; -use core::future::Future; -use gclient::metadata::runtime_types::{ - gear_core::message::user::UserMessage as GenUserMessage, - pallet_gear_voucher::internal::VoucherId, -}; -use gclient::{EventProcessor, GearApi, ext::sp_core::ByteArray}; -use gear_core_errors::ReplyCode; - -#[derive(Debug, Default)] -pub struct GClientArgs { - voucher: Option<(VoucherId, bool)>, - at_block: Option, - query_with_message: bool, -} - -impl GClientArgs { - pub fn with_voucher(self, voucher_id: VoucherId, keep_alive: bool) -> Self { - Self { - voucher: Some((voucher_id, keep_alive)), - ..self - } - } - - fn at_block(self, hash: H256) -> Self { - Self { - at_block: Some(hash), - ..self - } - } - - fn query_with_message(self, query_with_message: bool) -> Self { - Self { - query_with_message, - ..self - } - } -} - -#[derive(Clone)] -pub struct GClientRemoting { - api: GearApi, -} - -impl GClientRemoting { - pub fn new(api: GearApi) -> Self { - Self { api } - } - - pub fn with_suri(self, suri: impl AsRef) -> Self { - let api = self.api.with(suri).unwrap(); - Self { api } - } - - async fn query_calculate_reply( - self, - target: ActorId, - payload: impl AsRef<[u8]>, - #[cfg(not(feature = "ethexe"))] gas_limit: Option, - value: ValueUnit, - args: GClientArgs, - ) -> Result> { - let api = self.api; - // Get Max gas amount if it is not explicitly set - #[cfg(not(feature = "ethexe"))] - let gas_limit = if let Some(gas_limit) = gas_limit { - gas_limit - } else { - api.block_gas_limit()? - }; - #[cfg(feature = "ethexe")] - let gas_limit = 0; - let origin = H256::from_slice(api.account_id().as_slice()); - let payload = payload.as_ref().to_vec(); - - let reply_info = api - .calculate_reply_for_handle_at( - Some(origin), - target, - payload, - gas_limit, - value, - args.at_block, - ) - .await?; - - match reply_info.code { - ReplyCode::Success(_) => Ok(reply_info.payload), - ReplyCode::Error(reason) => Err(RtlError::ReplyHasError(reason, reply_info.payload))?, - ReplyCode::Unsupported => Err(RtlError::ReplyIsMissing)?, - } - } -} - -impl Remoting for GClientRemoting { - type Args = GClientArgs; - - async fn activate( - self, - code_id: CodeId, - salt: impl AsRef<[u8]>, - payload: impl AsRef<[u8]>, - #[cfg(not(feature = "ethexe"))] gas_limit: Option, - value: ValueUnit, - _args: GClientArgs, - ) -> Result)>>> { - let api = self.api; - // Calculate gas amount if it is not explicitly set - #[cfg(not(feature = "ethexe"))] - let gas_limit = if let Some(gas_limit) = gas_limit { - gas_limit - } else { - // Calculate gas amount needed for initialization - let gas_info = api - .calculate_create_gas(None, code_id, Vec::from(payload.as_ref()), value, true) - .await?; - gas_info.min_limit - }; - #[cfg(feature = "ethexe")] - let gas_limit = 0; - - let mut listener = api.subscribe().await?; - let (message_id, program_id, ..) = api - .create_program_bytes(code_id, salt, payload, gas_limit, value) - .await?; - - Ok(async move { - let (_, result, _) = listener.reply_bytes_on(message_id).await?; - let reply = result.map_err(RtlError::ReplyHasErrorString)?; - Ok((program_id, reply)) - }) - } - - async fn message( - self, - target: ActorId, - payload: impl AsRef<[u8]>, - #[cfg(not(feature = "ethexe"))] gas_limit: Option, - value: ValueUnit, - args: GClientArgs, - ) -> Result>>> { - let api = self.api; - // Calculate gas amount if it is not explicitly set - #[cfg(not(feature = "ethexe"))] - let gas_limit = if let Some(gas_limit) = gas_limit { - gas_limit - } else { - // Calculate gas amount needed for handling the message - let gas_info = api - .calculate_handle_gas(None, target, Vec::from(payload.as_ref()), value, true) - .await?; - gas_info.min_limit - }; - #[cfg(feature = "ethexe")] - let gas_limit = 0; - - let mut listener = api.subscribe().await?; - let (message_id, ..) = if let Some((voucher_id, keep_alive)) = args.voucher { - api.send_message_bytes_with_voucher( - voucher_id, target, payload, gas_limit, value, keep_alive, - ) - .await? - } else { - api.send_message_bytes(target, payload, gas_limit, value) - .await? - }; - - Ok(async move { - let (_, result, _) = listener.reply_bytes_on(message_id).await?; - let reply = result.map_err(RtlError::ReplyHasErrorString)?; - Ok(reply) - }) - } - - async fn query( - self, - target: ActorId, - payload: impl AsRef<[u8]>, - #[cfg(not(feature = "ethexe"))] gas_limit: Option, - value: ValueUnit, - args: GClientArgs, - ) -> Result> { - if args.query_with_message { - // first await - sending a message, second await - receiving a reply - self.message(target, payload, gas_limit, value, args) - .await? - .await - } else { - self.query_calculate_reply(target, payload, gas_limit, value, args) - .await - } - } -} - -impl Listener> for GClientRemoting { - type Error = Error; - - async fn listen(&mut self) -> Result)> + Unpin> { - let listener = self.api.subscribe().await?; - let stream = stream::unfold(listener, |mut l| async move { - let vec = get_events_from_block(&mut l).await.ok(); - vec.map(|v| (v, l)) - }) - .flat_map(stream::iter); - Ok(Box::pin(stream)) - } -} - -async fn get_events_from_block( - listener: &mut gclient::EventListener, -) -> Result)>> { - let vec = listener - .proc_many( - |e| { - if let gclient::Event::Gear(gclient::GearEvent::UserMessageSent { - message: - GenUserMessage { - id: _, - source, - destination, - payload, - .. - }, - .. - }) = e - { - let source = ActorId::from(source); - if ActorId::from(destination) == ActorId::zero() { - Some((source, payload.0)) - } else { - None - } - } else { - None - } - }, - |v| (v, true), - ) - .await?; - Ok(vec) -} - -pub trait QueryExtGClient { - /// Query at a specific block. - /// - /// See [`GearApi::calculate_reply_for_handle_at`]. - fn at_block(self, hash: H256) -> Self; - - /// Query with sending message. - /// - /// By default, the `query_with_message` flag is set to `false`. - /// The query sends the `gear_calculateReplyForHandle` RPC to the node, - /// which is used to determine the reply when `Gear::send_message(...)` is called. - /// - /// If set the `query_with_message` flag to `true`, - /// the query will actually send the message and wait for a reply. - /// - /// See [`GearApi::calculate_reply_for_handle`]. - fn query_with_message(self, query_with_message: bool) -> Self; -} - -impl QueryExtGClient for T -where - T: Query, -{ - fn at_block(self, hash: H256) -> Self { - self.with_args(|args| args.at_block(hash)) - } - - fn query_with_message(self, query_with_message: bool) -> Self { - self.with_args(|args| args.query_with_message(query_with_message)) - } -} diff --git a/rs/src/gclient/mod.rs b/rs/src/gclient/mod.rs deleted file mode 100644 index 9c5113d56..000000000 --- a/rs/src/gclient/mod.rs +++ /dev/null @@ -1 +0,0 @@ -// pub mod calls; diff --git a/rs/src/gstd/calls.rs b/rs/src/gstd/calls.rs deleted file mode 100644 index 744544920..000000000 --- a/rs/src/gstd/calls.rs +++ /dev/null @@ -1,285 +0,0 @@ -use super::message_future::MessageFutureExtended; -use crate::{ - calls::{Action, Remoting}, - errors::Result, - futures::FutureExt, - prelude::*, -}; -use core::future::Future; -use gstd::{msg, prog}; - -#[derive(Default)] -pub struct GStdArgs { - wait_up_to: Option, - #[cfg(not(feature = "ethexe"))] - reply_deposit: Option, - #[cfg(not(feature = "ethexe"))] - reply_hook: Option>, - redirect_on_exit: bool, -} - -impl GStdArgs { - pub fn with_wait_up_to(self, wait_up_to: Option) -> Self { - Self { wait_up_to, ..self } - } - - pub fn with_redirect_on_exit(self, redirect_on_exit: bool) -> Self { - Self { - redirect_on_exit, - ..self - } - } - - pub fn wait_up_to(&self) -> Option { - self.wait_up_to - } - - pub fn redirect_on_exit(&self) -> bool { - self.redirect_on_exit - } -} - -#[cfg(not(feature = "ethexe"))] -impl GStdArgs { - pub fn with_reply_deposit(self, reply_deposit: Option) -> Self { - Self { - reply_deposit, - ..self - } - } - - pub fn with_reply_hook(self, f: F) -> Self { - Self { - reply_hook: Some(Box::new(f)), - ..self - } - } - - pub fn reply_deposit(&self) -> Option { - self.reply_deposit - } -} - -#[derive(Debug, Default, Clone)] -pub struct GStdRemoting; - -impl GStdRemoting { - pub fn new() -> Self { - Self - } -} - -impl Remoting for GStdRemoting { - type Args = GStdArgs; - - async fn activate( - self, - code_id: CodeId, - salt: impl AsRef<[u8]>, - payload: impl AsRef<[u8]>, - #[cfg(not(feature = "ethexe"))] gas_limit: Option, - value: ValueUnit, - #[allow(unused_variables)] args: GStdArgs, - ) -> Result)>>> { - #[cfg(not(feature = "ethexe"))] - let mut reply_future = if let Some(gas_limit) = gas_limit { - prog::create_program_bytes_with_gas_for_reply( - code_id, - salt, - payload, - gas_limit, - value, - args.reply_deposit.unwrap_or_default(), - )? - } else { - prog::create_program_bytes_for_reply( - code_id, - salt, - payload, - value, - args.reply_deposit.unwrap_or_default(), - )? - }; - #[cfg(feature = "ethexe")] - let mut reply_future = prog::create_program_bytes_for_reply(code_id, salt, payload, value)?; - - reply_future = reply_future.up_to(args.wait_up_to)?; - - #[cfg(not(feature = "ethexe"))] - if let Some(reply_hook) = args.reply_hook { - reply_future = reply_future.handle_reply(reply_hook)?; - } - - let reply_future = reply_future.map(|result| result.map_err(Into::into)); - Ok(reply_future) - } - - async fn message( - self, - target: ActorId, - payload: impl AsRef<[u8]>, - #[cfg(not(feature = "ethexe"))] gas_limit: Option, - value: ValueUnit, - args: GStdArgs, - ) -> Result>>> { - let reply_future = send_for_reply( - target, - payload, - #[cfg(not(feature = "ethexe"))] - gas_limit, - value, - args, - )?; - Ok(reply_future) - } - - async fn query( - self, - target: ActorId, - payload: impl AsRef<[u8]>, - #[cfg(not(feature = "ethexe"))] gas_limit: Option, - value: ValueUnit, - args: GStdArgs, - ) -> Result> { - let reply_future = send_for_reply( - target, - payload, - #[cfg(not(feature = "ethexe"))] - gas_limit, - value, - args, - )?; - let reply = reply_future.await?; - Ok(reply) - } -} - -#[cfg(not(feature = "ethexe"))] -pub(crate) fn send_for_reply_future( - target: ActorId, - payload: &[u8], - gas_limit: Option, - value: ValueUnit, - args: GStdArgs, -) -> Result { - // here can be a redirect target - let mut message_future = if let Some(gas_limit) = gas_limit { - msg::send_bytes_with_gas_for_reply( - target, - payload, - gas_limit, - value, - args.reply_deposit.unwrap_or_default(), - )? - } else { - msg::send_bytes_for_reply( - target, - payload, - value, - args.reply_deposit.unwrap_or_default(), - )? - }; - - message_future = message_future.up_to(args.wait_up_to)?; - - if let Some(reply_hook) = args.reply_hook { - message_future = message_future.handle_reply(reply_hook)?; - } - Ok(message_future) -} - -#[cfg(feature = "ethexe")] -pub(crate) fn send_for_reply_future( - target: ActorId, - payload: &[u8], - value: ValueUnit, - args: GStdArgs, -) -> Result { - // here can be a redirect target - let mut message_future = msg::send_bytes_for_reply(target, payload, value)?; - - message_future = message_future.up_to(args.wait_up_to)?; - - Ok(message_future) -} - -pub(crate) fn send_for_reply>( - target: ActorId, - payload: T, - #[cfg(not(feature = "ethexe"))] gas_limit: Option, - value: ValueUnit, - args: GStdArgs, -) -> Result> { - #[cfg(not(feature = "ethexe"))] - let reply_deposit = args.reply_deposit; - let wait_up_to = args.wait_up_to; - let redirect_on_exit = args.redirect_on_exit; - let message_future = send_for_reply_future( - target, - payload.as_ref(), - #[cfg(not(feature = "ethexe"))] - gas_limit, - value, - args, - )?; - - if redirect_on_exit { - Ok(MessageFutureExtended::with_redirect( - message_future, - target, - payload, - #[cfg(not(feature = "ethexe"))] - gas_limit, - value, - #[cfg(not(feature = "ethexe"))] - reply_deposit, - wait_up_to, - )) - } else { - Ok(MessageFutureExtended::without_redirect(message_future)) - } -} - -pub trait WithArgs { - fn with_wait_up_to(self, wait_up_to: Option) -> Self; - - #[cfg(not(feature = "ethexe"))] - fn with_reply_deposit(self, reply_deposit: Option) -> Self; - - #[cfg(not(feature = "ethexe"))] - fn with_reply_hook(self, f: F) -> Self; - - fn with_redirect_on_exit(self, redirect_on_exit: bool) -> Self; -} - -impl WithArgs for T -where - T: Action, -{ - fn with_wait_up_to(self, wait_up_to: Option) -> Self { - self.with_args(|args| args.with_wait_up_to(wait_up_to)) - } - - #[cfg(not(feature = "ethexe"))] - fn with_reply_deposit(self, reply_deposit: Option) -> Self { - self.with_args(|args| args.with_reply_deposit(reply_deposit)) - } - - #[cfg(not(feature = "ethexe"))] - fn with_reply_hook(self, f: F) -> Self { - self.with_args(|args| args.with_reply_hook(f)) - } - - /// Set `redirect_on_exit` flag to `true`` - /// - /// This flag is used to redirect a message to a new program when the target program exits - /// with an inheritor. - /// - /// WARNING: When this flag is set, the message future captures the payload and other arguments, - /// potentially resulting in multiple messages being sent. This can lead to increased gas consumption. - /// - /// This flag is set to `false`` by default. - fn with_redirect_on_exit(self, redirect_on_exit: bool) -> Self { - self.with_args(|args| args.with_redirect_on_exit(redirect_on_exit)) - } -} diff --git a/rs/src/gstd/message_future.rs b/rs/src/gstd/message_future.rs deleted file mode 100644 index 934b06bf8..000000000 --- a/rs/src/gstd/message_future.rs +++ /dev/null @@ -1,175 +0,0 @@ -use super::calls::GStdArgs; -use crate::{errors::Result, prelude::*}; -use core::{ - future::Future, - pin::Pin, - task::{Context, Poll, ready}, -}; -use gstd::msg; -use pin_project_lite::pin_project; - -pin_project! { - #[project = Projection] - #[project_replace = Replace] - #[must_use = "futures do nothing unless you `.await` or poll them"] - pub(crate) enum MessageFutureExtended> { - NonRedirect { - #[pin] - message_future: msg::MessageFuture, - }, - Redirect { - #[pin] - message_future: msg::MessageFuture, - target: ActorId, - payload: T, - gas_limit: Option, - value: ValueUnit, - reply_deposit: Option, - wait_up_to: Option, - created_block: Option, - }, - Dummy, - } -} - -impl> MessageFutureExtended { - pub(crate) fn without_redirect(message_future: msg::MessageFuture) -> Self { - Self::NonRedirect { message_future } - } - - pub(crate) fn with_redirect( - message_future: msg::MessageFuture, - target: ActorId, - payload: T, - #[cfg(not(feature = "ethexe"))] gas_limit: Option, - value: ValueUnit, - #[cfg(not(feature = "ethexe"))] reply_deposit: Option, - wait_up_to: Option, - ) -> Self { - let created_block = wait_up_to.map(|_| gstd::exec::block_height()); - Self::Redirect { - message_future, - target, - payload, - #[cfg(not(feature = "ethexe"))] - gas_limit, - #[cfg(feature = "ethexe")] - gas_limit: None, - value, - #[cfg(not(feature = "ethexe"))] - reply_deposit, - #[cfg(feature = "ethexe")] - reply_deposit: None, - wait_up_to, - created_block, - } - } -} - -impl> futures::future::FusedFuture for MessageFutureExtended { - fn is_terminated(&self) -> bool { - match self { - Self::NonRedirect { message_future } => message_future.is_terminated(), - Self::Redirect { message_future, .. } => message_future.is_terminated(), - Self::Dummy => true, - } - } -} - -impl> Future for MessageFutureExtended { - type Output = Result>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut this = self; - - let output = match this.as_mut().project() { - Projection::NonRedirect { message_future } => { - // Return if not redirecting on exit - let output = ready!(message_future.poll(cx)); - return Poll::Ready(output.map_err(Into::into)); - } - Projection::Redirect { message_future, .. } => { - ready!(message_future.poll(cx)) - } - Projection::Dummy => { - unreachable!("polled after completion or invalid state") - } - }; - match output { - Ok(res) => Poll::Ready(Ok(res)), - Err(err) => match err { - gstd::errors::Error::ErrorReply( - error_payload, - ErrorReplyReason::UnavailableActor(SimpleUnavailableActorError::ProgramExited), - ) => { - if let Ok(new_target) = ActorId::try_from(error_payload.0.as_ref()) { - // Safely extract values by replacing with Dummy - let Replace::Redirect { - target: _target, - payload, - #[cfg(not(feature = "ethexe"))] - gas_limit, - value, - #[cfg(not(feature = "ethexe"))] - reply_deposit, - wait_up_to, - created_block, - .. - } = this.as_mut().project_replace(MessageFutureExtended::Dummy) - else { - unreachable!("Invalid state during replacement") - }; - gstd::debug!("Redirecting message from {_target} to {new_target}"); - // here can insert new target into redirects - - // Calculate updated `wait_up_to` if provided - // wait_up_to = wait_up_to - (current_block - created_block) - let wait_up_to = wait_up_to.and_then(|wait_up_to| { - created_block.map(|created_block| { - let current_block = gstd::exec::block_height(); - wait_up_to - .saturating_sub(current_block.saturating_sub(created_block)) - }) - }); - #[cfg(not(feature = "ethexe"))] - let args = GStdArgs::default() - .with_reply_deposit(reply_deposit) - .with_wait_up_to(wait_up_to) - .with_redirect_on_exit(true); - #[cfg(feature = "ethexe")] - let args = GStdArgs::default() - .with_wait_up_to(wait_up_to) - .with_redirect_on_exit(true); - // Get new future - let future_res = super::calls::send_for_reply( - new_target, - payload, - #[cfg(not(feature = "ethexe"))] - gas_limit, - value, - args, - ); - match future_res { - Ok(future) => { - // Replace the future with a new one - _ = this.as_mut().project_replace(future); - // Return Pending to allow the new future to be polled - Poll::Pending - } - Err(err) => Poll::Ready(Err(err)), - } - } else { - Poll::Ready(Err(gstd::errors::Error::ErrorReply( - error_payload, - ErrorReplyReason::UnavailableActor( - SimpleUnavailableActorError::ProgramExited, - ), - ) - .into())) - } - } - _ => Poll::Ready(Err(err.into())), - }, - } - } -} diff --git a/rs/src/gstd/mod.rs b/rs/src/gstd/mod.rs index 4a2ea488a..14c2cc64c 100644 --- a/rs/src/gstd/mod.rs +++ b/rs/src/gstd/mod.rs @@ -20,11 +20,9 @@ use crate::{ }; use gcore::stack_buffer; -// pub mod calls; #[cfg(feature = "ethexe")] mod ethexe; mod events; -// mod message_future; pub mod services; mod syscalls; diff --git a/rs/src/lib.rs b/rs/src/lib.rs index 82e4b86f3..c338f30eb 100644 --- a/rs/src/lib.rs +++ b/rs/src/lib.rs @@ -9,13 +9,13 @@ extern crate std; pub use builder::{ClientBuilder, build_client, build_client_as_lib}; #[cfg(feature = "wasm-builder")] pub use gwasm_builder::build as build_wasm; -pub use hex::{self}; +pub use hex; pub use prelude::*; #[cfg(feature = "idl-gen")] #[cfg(not(target_arch = "wasm32"))] pub use sails_idl_gen::{generate_idl, generate_idl_to_file}; pub use sails_idl_meta::{self as meta}; -pub use spin::{self}; +pub use spin; #[cfg(feature = "client-builder")] mod builder; @@ -23,15 +23,15 @@ pub mod client; pub mod errors; #[cfg(feature = "gclient")] #[cfg(not(target_arch = "wasm32"))] -pub mod gclient; +pub use gclient; #[cfg(feature = "gstd")] pub mod gstd; #[cfg(feature = "gtest")] #[cfg(not(target_arch = "wasm32"))] -pub use gtest::{self}; +pub use gtest; #[cfg(feature = "mockall")] #[cfg(not(target_arch = "wasm32"))] -pub use mockall::{self}; +pub use mockall; pub mod prelude; #[cfg(feature = "ethexe")] pub mod solidity; From f74a5056bca87e9363f49537ab52a95dea5b06da Mon Sep 17 00:00:00 2001 From: vobradovich Date: Fri, 12 Sep 2025 13:33:48 +0200 Subject: [PATCH 27/66] chore: refactor --- rs/ethexe/ethapp/tests/gtest.rs | 12 ++------ rs/ethexe/ethapp_with_events/tests/gtest.rs | 12 ++------ rs/src/client/gclient_env.rs | 31 +++------------------ rs/src/client/gstd_env.rs | 8 +----- rs/src/client/gtest_env.rs | 30 ++++---------------- rs/src/client/mod.rs | 11 ++++++++ 6 files changed, 26 insertions(+), 78 deletions(-) diff --git a/rs/ethexe/ethapp/tests/gtest.rs b/rs/ethexe/ethapp/tests/gtest.rs index 0215adb92..80644af9c 100644 --- a/rs/ethexe/ethapp/tests/gtest.rs +++ b/rs/ethexe/ethapp/tests/gtest.rs @@ -88,13 +88,9 @@ async fn ethapp_remoting_works() { let do_this_params = (0u128, false, 42, "hello").abi_encode_sequence(); let payload = [do_this_sig.as_slice(), do_this_params.as_slice()].concat(); - let message_id = env - .send_message(program_id, payload, Default::default()) - .unwrap(); let reply_payload = env - .message_reply_from_next_blocks(message_id) + .send_for_reply(program_id, payload, Default::default()) .await - .unwrap() .unwrap(); let reply = u32::abi_decode(reply_payload.as_slice(), true); @@ -134,13 +130,9 @@ async fn ethapp_remoting_encode_reply_works() { let payload = [do_this_sig.as_slice(), do_this_params.as_slice()].concat(); // act - let message_id = env - .send_message(program_id, payload, Default::default()) - .unwrap(); let reply_payload = env - .message_reply_from_next_blocks(message_id) + .send_for_reply(program_id, payload, Default::default()) .await - .unwrap() .unwrap(); // assert diff --git a/rs/ethexe/ethapp_with_events/tests/gtest.rs b/rs/ethexe/ethapp_with_events/tests/gtest.rs index e002ff917..472bb37f2 100644 --- a/rs/ethexe/ethapp_with_events/tests/gtest.rs +++ b/rs/ethexe/ethapp_with_events/tests/gtest.rs @@ -125,13 +125,9 @@ async fn ethapp_with_events_remoting_works() { let do_this_params = (0u128, false, 42, "hello").abi_encode_sequence(); let payload = [do_this_sig.as_slice(), do_this_params.as_slice()].concat(); - let message_id = env - .send_message(program_id, payload, Default::default()) - .unwrap(); let reply_payload = env - .message_reply_from_next_blocks(message_id) + .send_for_reply(program_id, payload, Default::default()) .await - .unwrap() .unwrap(); let reply = u32::abi_decode(reply_payload.as_slice(), true); @@ -177,13 +173,9 @@ async fn ethapp_with_events_exposure_emit_works() { let do_this_params = (0u128, false, 42, "hello").abi_encode_sequence(); let payload = [do_this_sig.as_slice(), do_this_params.as_slice()].concat(); - let message_id = env - .send_message(program_id, payload, Default::default()) - .unwrap(); let reply_payload = env - .message_reply_from_next_blocks(message_id) + .send_for_reply(program_id, payload, Default::default()) .await - .unwrap() .unwrap(); let reply = u32::abi_decode(reply_payload.as_slice(), true); diff --git a/rs/src/client/gclient_env.rs b/rs/src/client/gclient_env.rs index 5257e8517..7fb6b0cdf 100644 --- a/rs/src/client/gclient_env.rs +++ b/rs/src/client/gclient_env.rs @@ -90,26 +90,14 @@ impl GearEnv for GclientEnv { impl PendingCall { pub async fn send_one_way(&mut self) -> Result { - let params = self.params.take().unwrap_or_default(); - let args = self - .args - .take() - .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(self.route, &args); - + let (payload, params) = self.take_encoded_args_and_params(); self.env .send_one_way(self.destination, payload, params) .await } pub async fn send_for_reply(mut self) -> Result { - let params = self.params.take().unwrap_or_default(); - let args = self - .args - .take() - .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(self.route, &args); - + let (payload, params) = self.take_encoded_args_and_params(); // send for reply let send_future = send_for_reply(self.env.api.clone(), self.destination, payload, params); self.state = Some(Box::pin(send_future)); @@ -117,12 +105,7 @@ impl PendingCall { } pub async fn query(mut self) -> Result { - let params = self.params.unwrap_or_default(); - let args = self - .args - .take() - .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(self.route, &args); + let (payload, params) = self.take_encoded_args_and_params(); // Calculate reply let reply_bytes = @@ -139,14 +122,8 @@ impl Future for PendingCall { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if self.state.is_none() { + let (payload, params) = self.take_encoded_args_and_params(); // Send message - let params = self.params.take().unwrap_or_default(); - let args = self - .args - .take() - .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(self.route, &args); - let send_future = send_for_reply(self.env.api.clone(), self.destination, payload, params); self.state = Some(Box::pin(send_future)); diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index 24fb3062a..d132e6b64 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -96,13 +96,7 @@ impl GstdEnv { impl PendingCall { pub fn send_one_way(&mut self) -> Result { - let args = self - .args - .take() - .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(self.route, &args); - let params = self.params.take().unwrap_or_default(); - + let (payload, params) = self.take_encoded_args_and_params(); self.env.send_one_way(self.destination, payload, params) } } diff --git a/rs/src/client/gtest_env.rs b/rs/src/client/gtest_env.rs index 942c3a71b..93c99d2b7 100644 --- a/rs/src/client/gtest_env.rs +++ b/rs/src/client/gtest_env.rs @@ -69,9 +69,9 @@ impl GtestEnv { } } - // Avoid calling methods of `System` related to block execution. - // Use `GTestRemoting::run_next_block` instead. This method can be used - // for obtaining reference data like balance, timestamp, etc. + /// Avoid calling methods of `System` related to block execution. + /// Use `GtestEnv::run_next_block` instead. This method can be used + /// for obtaining reference data like balance, timestamp, etc. pub fn system(&self) -> &System { &self.system } @@ -279,14 +279,8 @@ impl PendingCall { if self.state.is_some() { panic!("{PENDING_CALL_INVALID_STATE}"); } + let (payload, params) = self.take_encoded_args_and_params(); // Send message - let args = self - .args - .take() - .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(self.route, &args); - let params = self.params.take().unwrap_or_default(); - let message_id = self.env.send_one_way(self.destination, payload, params)?; log::debug!("PendingCall: send message {message_id:?}"); Ok(message_id) @@ -299,13 +293,7 @@ impl PendingCall { } pub fn query(mut self) -> Result { - let params = self.params.unwrap_or_default(); - let args = self - .args - .take() - .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(self.route, &args); - + let (payload, params) = self.take_encoded_args_and_params(); // Calculate reply let reply_bytes = self.env.query(self.destination, payload, params)?; @@ -320,14 +308,8 @@ impl Future for PendingCall { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if self.state.is_none() { + let (payload, params) = self.take_encoded_args_and_params(); // Send message - let args = self - .args - .take() - .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(self.route, &args); - let params = self.params.take().unwrap_or_default(); - let send_res = self.env.send_one_way(self.destination, payload, params); match send_res { Ok(message_id) => { diff --git a/rs/src/client/mod.rs b/rs/src/client/mod.rs index 7ca740524..350bc6d72 100644 --- a/rs/src/client/mod.rs +++ b/rs/src/client/mod.rs @@ -253,6 +253,17 @@ impl PendingCall { self.params = Some(f(self.params.unwrap_or_default())); self } + + #[inline] + fn take_encoded_args_and_params(&mut self) -> (Vec, E::Params) { + let args = self + .args + .take() + .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); + let payload = T::encode_params_with_prefix(self.route, &args); + let params = self.params.take().unwrap_or_default(); + (payload, params) + } } pin_project_lite::pin_project! { From f1d852f004f10d40f454d930338290f0a2629f22 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Tue, 16 Sep 2025 16:35:16 +0200 Subject: [PATCH 28/66] perf: remove params & payload from GtsdFuture::MessageWithRedirect --- benchmarks/bench_data.json | 4 ++-- rs/src/client/gstd_env.rs | 44 +++++++++++++++++++------------------- rs/src/client/mod.rs | 15 +++++++++++++ 3 files changed, 39 insertions(+), 24 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index e25a730ac..0aac9f084 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -17,9 +17,9 @@ "sync_call": 678860419 }, "cross_program": { - "median": 2461550210 + "median": 2450755693 }, "redirect": { - "median": 3514376024 + "median": 3485542442 } } \ No newline at end of file diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index d132e6b64..ae0572f8a 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -158,18 +158,16 @@ const _: () = { #[inline] fn send_for_reply( destination: ActorId, - payload: Vec, - mut params: GstdParams, + payload: &[u8], + params: &mut GstdParams, ) -> Result { // send message - let future = send_for_reply_future(destination, payload.as_ref(), &mut params)?; + let future = send_for_reply_future(destination, payload, params)?; if params.redirect_on_exit { let created_block = params.wait_up_to.map(|_| gstd::exec::block_height()); Ok(GtsdFuture::MessageWithRedirect { created_block, future, - params, - payload, destination, }) } else { @@ -182,15 +180,15 @@ const _: () = { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if self.state.is_none() { - // Send message let args = self .args - .take() + .as_ref() .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(self.route, &args); - let params = self.params.take().unwrap_or_default(); - - let send_res = send_for_reply(self.destination, payload, params); + let payload = T::encode_params_with_prefix(self.route, args); + let destination = self.destination; + let params = self.params.get_or_insert_default(); + // Send message + let send_res = send_for_reply(destination, payload.as_slice(), params); match send_res { Ok(future) => { self.state = Some(future); @@ -221,10 +219,9 @@ const _: () = { error_payload, ErrorReplyReason::UnavailableActor(SimpleUnavailableActorError::ProgramExited), )) => { + let params = this.params.get_or_insert_default(); if let Replace::MessageWithRedirect { destination: _destination, - payload, - mut params, created_block, .. } = state.as_mut().project_replace(GtsdFuture::Dummy) @@ -243,9 +240,14 @@ const _: () = { }) }); + let args = this + .args + .as_ref() + .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); + let payload = T::encode_params_with_prefix(this.route, args); // send message to new target - let future_res = send_for_reply(new_target, payload, params); - match future_res { + let send_res = send_for_reply(new_target, payload.as_slice(), params); + match send_res { Ok(future) => { // Replace the future with a new one _ = state.as_mut().project_replace(future); @@ -275,16 +277,16 @@ const _: () = { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if self.state.is_none() { - // Send message + let params = self.params.take().unwrap_or_default(); + let value = params.value.unwrap_or(0); + let salt = self.salt.take().unwrap(); + let args = self .args .take() .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); let payload = T::encode_params(&args); - let params = self.params.take().unwrap_or_default(); - let value = params.value.unwrap_or(0); - let salt = self.salt.take().unwrap(); - + // Send message #[cfg(not(feature = "ethexe"))] let program_future = if let Some(gas_limit) = params.gas_limit { ::gstd::prog::create_program_bytes_with_gas_for_reply( @@ -346,8 +348,6 @@ pin_project_lite::pin_project! { #[pin] future: MessageFuture, destination: ActorId, - payload: Vec, - params: GstdParams, created_block: Option, }, Dummy, diff --git a/rs/src/client/mod.rs b/rs/src/client/mod.rs index 350bc6d72..86ae6747f 100644 --- a/rs/src/client/mod.rs +++ b/rs/src/client/mod.rs @@ -354,6 +354,21 @@ pub trait CallEncodeDecode { Decode::decode(&mut value) } + fn with_optimized_encode( + prefix: Route, + value: &Self::Params, + f: impl FnOnce(&[u8]) -> R, + ) -> R { + let size = 2 + prefix.len() + Self::ROUTE.len() + Encode::encoded_size(value); + gcore::stack_buffer::with_byte_buffer(size, |buffer| { + let mut buffer_writer = crate::utils::MaybeUninitBufferWriter::new(buffer); + Encode::encode_to(prefix, &mut buffer_writer); + Encode::encode_to(Self::ROUTE, &mut buffer_writer); + Encode::encode_to(value, &mut buffer_writer); + buffer_writer.with_buffer(f) + }) + } + fn is_empty_tuple() -> bool { TypeId::of::() == TypeId::of::<()>() } From 223e13a2bd465e614b1f15b716f40071f4891359 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Thu, 18 Sep 2025 16:21:07 +0200 Subject: [PATCH 29/66] fix: typo GstdFuture --- rs/src/client/gstd_env.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index ae0572f8a..898620d80 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -70,7 +70,7 @@ impl GearEnv for GstdEnv { type Params = GstdParams; type Error = Error; #[cfg(target_arch = "wasm32")] - type MessageState = GtsdFuture; + type MessageState = GstdFuture; #[cfg(not(target_arch = "wasm32"))] type MessageState = core::future::Ready, Self::Error>>; } @@ -160,18 +160,18 @@ const _: () = { destination: ActorId, payload: &[u8], params: &mut GstdParams, - ) -> Result { + ) -> Result { // send message let future = send_for_reply_future(destination, payload, params)?; if params.redirect_on_exit { let created_block = params.wait_up_to.map(|_| gstd::exec::block_height()); - Ok(GtsdFuture::MessageWithRedirect { + Ok(GstdFuture::MessageWithRedirect { created_block, future, destination, }) } else { - Ok(GtsdFuture::Message { future }) + Ok(GstdFuture::Message { future }) } } @@ -224,7 +224,7 @@ const _: () = { destination: _destination, created_block, .. - } = state.as_mut().project_replace(GtsdFuture::Dummy) + } = state.as_mut().project_replace(GstdFuture::Dummy) && params.redirect_on_exit && let Ok(new_target) = ActorId::try_from(error_payload.0.as_ref()) { @@ -315,7 +315,7 @@ const _: () = { )?; // self.program_id = Some(program_future.program_id); - self.state = Some(GtsdFuture::CreateProgram { + self.state = Some(GstdFuture::CreateProgram { future: program_future, }); } @@ -341,7 +341,7 @@ const _: () = { pin_project_lite::pin_project! { #[project = Projection] #[project_replace = Replace] - pub enum GtsdFuture { + pub enum GstdFuture { CreateProgram { #[pin] future: CreateProgramFuture }, Message { #[pin] future: MessageFuture }, MessageWithRedirect { From 0c92d8b7d46db826889e899a202b5c05decd12a5 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Fri, 19 Sep 2025 12:03:40 +0200 Subject: [PATCH 30/66] perf: return encoded payload to GstdFuture::MessageWithRedirect --- benchmarks/bench_data.json | 6 ++--- rs/src/client/gstd_env.rs | 47 +++++++++++++++++--------------------- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 0aac9f084..ed0c82566 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -1,6 +1,6 @@ { "compute": { - "median": 450514036959 + "median": 450514040279 }, "alloc": { "0": 564376739, @@ -17,9 +17,9 @@ "sync_call": 678860419 }, "cross_program": { - "median": 2450755693 + "median": 2451627696 }, "redirect": { - "median": 3485542442 + "median": 3485056908 } } \ No newline at end of file diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index 898620d80..8258ac136 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -82,15 +82,16 @@ impl GstdEnv { payload: impl AsRef<[u8]>, params: GstdParams, ) -> Result { - let value = params.value.unwrap_or(0); + let value = params.value.unwrap_or_default(); + let payload_bytes = payload.as_ref(); #[cfg(not(feature = "ethexe"))] if let Some(gas_limit) = params.gas_limit { - return ::gcore::msg::send_with_gas(destination, payload.as_ref(), gas_limit, value) + return ::gcore::msg::send_with_gas(destination, payload_bytes, gas_limit, value) .map_err(Error::Core); } - ::gcore::msg::send(destination, payload.as_ref(), value).map_err(Error::Core) + ::gcore::msg::send(destination, payload_bytes, value).map_err(Error::Core) } } @@ -112,7 +113,8 @@ const _: () = { payload: &[u8], params: &mut GstdParams, ) -> Result { - let value = params.value.unwrap_or(0); + let value = params.value.unwrap_or_default(); + let reply_deposit = params.reply_deposit.unwrap_or_default(); // here can be a redirect target let mut message_future = if let Some(gas_limit) = params.gas_limit { ::gstd::msg::send_bytes_with_gas_for_reply( @@ -120,15 +122,10 @@ const _: () = { payload, gas_limit, value, - params.reply_deposit.unwrap_or_default(), + reply_deposit, )? } else { - ::gstd::msg::send_bytes_for_reply( - destination, - payload, - value, - params.reply_deposit.unwrap_or_default(), - )? + ::gstd::msg::send_bytes_for_reply(destination, payload, value, reply_deposit)? }; message_future = message_future.up_to(params.wait_up_to)?; @@ -146,7 +143,7 @@ const _: () = { payload: &[u8], params: &mut GstdParams, ) -> Result { - let value = params.value.unwrap_or(0); + let value = params.value.unwrap_or_default(); // here can be a redirect target let mut message_future = ::gstd::msg::send_bytes_for_reply(destination, payload, value)?; @@ -158,17 +155,18 @@ const _: () = { #[inline] fn send_for_reply( destination: ActorId, - payload: &[u8], + payload: Vec, params: &mut GstdParams, ) -> Result { // send message - let future = send_for_reply_future(destination, payload, params)?; + let future = send_for_reply_future(destination, payload.as_ref(), params)?; if params.redirect_on_exit { let created_block = params.wait_up_to.map(|_| gstd::exec::block_height()); Ok(GstdFuture::MessageWithRedirect { created_block, future, destination, + payload, }) } else { Ok(GstdFuture::Message { future }) @@ -184,11 +182,11 @@ const _: () = { .args .as_ref() .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(self.route, args); + let payload = T::encode_params_with_prefix(self.route, &args); let destination = self.destination; let params = self.params.get_or_insert_default(); // Send message - let send_res = send_for_reply(destination, payload.as_slice(), params); + let send_res = send_for_reply(destination, payload, params); match send_res { Ok(future) => { self.state = Some(future); @@ -223,6 +221,7 @@ const _: () = { if let Replace::MessageWithRedirect { destination: _destination, created_block, + payload, .. } = state.as_mut().project_replace(GstdFuture::Dummy) && params.redirect_on_exit @@ -240,13 +239,8 @@ const _: () = { }) }); - let args = this - .args - .as_ref() - .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params_with_prefix(this.route, args); // send message to new target - let send_res = send_for_reply(new_target, payload.as_slice(), params); + let send_res = send_for_reply(new_target, payload, params); match send_res { Ok(future) => { // Replace the future with a new one @@ -278,14 +272,14 @@ const _: () = { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if self.state.is_none() { let params = self.params.take().unwrap_or_default(); - let value = params.value.unwrap_or(0); + let value = params.value.unwrap_or_default(); let salt = self.salt.take().unwrap(); let args = self .args - .take() + .as_ref() .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}")); - let payload = T::encode_params(&args); + let payload = T::encode_params(args); // Send message #[cfg(not(feature = "ethexe"))] let program_future = if let Some(gas_limit) = params.gas_limit { @@ -349,6 +343,7 @@ pin_project_lite::pin_project! { future: MessageFuture, destination: ActorId, created_block: Option, + payload: Vec, // reuse encoded payload when redirecting }, Dummy, } @@ -396,7 +391,7 @@ const _: () = { match self.state.take() { Some(ready) => { let res = ready.into_inner(); - Poll::Ready(res.map(|v| T::Reply::decode(&mut v.as_slice()).unwrap())) + Poll::Ready(res.map(|v| T::Reply::decode(&mut v.as_ref()).unwrap())) } None => panic!("{PENDING_CALL_INVALID_STATE}"), } From a308153ff959312c35c80267cac79251cf1f2d8c Mon Sep 17 00:00:00 2001 From: vobradovich Date: Tue, 23 Sep 2025 17:42:49 +0200 Subject: [PATCH 31/66] fix: comments, send_one_way --- benchmarks/bench_data.json | 8 ++++---- rs/src/client/gstd_env.rs | 16 ++++++++++++---- rs/src/client/mod.rs | 13 +++++++++---- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index ed0c82566..d787941d7 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -13,13 +13,13 @@ "317810": 43147409792 }, "counter": { - "async_call": 851480964, - "sync_call": 678860419 + "async_call": 851480134, + "sync_call": 678859589 }, "cross_program": { - "median": 2451627696 + "median": 2452750056 }, "redirect": { - "median": 3485056908 + "median": 3485554141 } } \ No newline at end of file diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index 8258ac136..43b9000e7 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -86,12 +86,20 @@ impl GstdEnv { let payload_bytes = payload.as_ref(); #[cfg(not(feature = "ethexe"))] - if let Some(gas_limit) = params.gas_limit { - return ::gcore::msg::send_with_gas(destination, payload_bytes, gas_limit, value) - .map_err(Error::Core); + let waiting_reply_to = if let Some(gas_limit) = params.gas_limit { + ::gcore::msg::send_with_gas(destination, payload_bytes, gas_limit, value)? + } else { + ::gcore::msg::send(destination, payload_bytes, value)? + }; + #[cfg(feature = "ethexe")] + let waiting_reply_to = ::gcore::msg::send(destination, payload_bytes, value)?; + + #[cfg(not(feature = "ethexe"))] + if let Some(reply_deposit) = params.reply_deposit { + ::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit)?; } - ::gcore::msg::send(destination, payload_bytes, value).map_err(Error::Core) + Ok(waiting_reply_to) } } diff --git a/rs/src/client/mod.rs b/rs/src/client/mod.rs index 86ae6747f..2dd5448f6 100644 --- a/rs/src/client/mod.rs +++ b/rs/src/client/mod.rs @@ -306,15 +306,18 @@ pub trait CallEncodeDecode { type Reply: Decode + 'static; fn encode_params(value: &Self::Params) -> Vec { - let mut result = Vec::with_capacity(1 + Self::ROUTE.len() + Encode::encoded_size(value)); + let size = Encode::encoded_size(Self::ROUTE) + Encode::encoded_size(value); + let mut result = Vec::with_capacity(size); Encode::encode_to(Self::ROUTE, &mut result); Encode::encode_to(value, &mut result); result } fn encode_params_with_prefix(prefix: Route, value: &Self::Params) -> Vec { - let mut result = - Vec::with_capacity(2 + prefix.len() + Self::ROUTE.len() + Encode::encoded_size(value)); + let size = Encode::encoded_size(prefix) + + Encode::encoded_size(Self::ROUTE) + + Encode::encoded_size(value); + let mut result = Vec::with_capacity(size); Encode::encode_to(prefix, &mut result); Encode::encode_to(Self::ROUTE, &mut result); Encode::encode_to(value, &mut result); @@ -359,7 +362,9 @@ pub trait CallEncodeDecode { value: &Self::Params, f: impl FnOnce(&[u8]) -> R, ) -> R { - let size = 2 + prefix.len() + Self::ROUTE.len() + Encode::encoded_size(value); + let size = Encode::encoded_size(prefix) + + Encode::encoded_size(Self::ROUTE) + + Encode::encoded_size(value); gcore::stack_buffer::with_byte_buffer(size, |buffer| { let mut buffer_writer = crate::utils::MaybeUninitBufferWriter::new(buffer); Encode::encode_to(prefix, &mut buffer_writer); From 6ecacc20e9486bc91c5d093a2faaf887c4824173 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 29 Sep 2025 12:19:12 +0200 Subject: [PATCH 32/66] wip: gstd_env PendingCall::poll opt --- benchmarks/bench_data.json | 4 ++-- rs/src/client/gstd_env.rs | 23 ++++++++++++----------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index d787941d7..6235d6829 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -17,9 +17,9 @@ "sync_call": 678859589 }, "cross_program": { - "median": 2452750056 + "median": 2432696478 }, "redirect": { - "median": 3485554141 + "median": 3464725840 } } \ No newline at end of file diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index 43b9000e7..76ac15719 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -198,6 +198,8 @@ const _: () = { match send_res { Ok(future) => { self.state = Some(future); + // No need to poll the future + return Poll::Pending; } Err(err) => { return Poll::Ready(Err(err)); @@ -205,9 +207,8 @@ const _: () = { } } let this = self.as_mut().project(); - let Some(mut state) = this.state.as_pin_mut() else { - panic!("{PENDING_CALL_INVALID_STATE}"); - }; + // SAFETY: checked in the code above. + let mut state = unsafe { this.state.as_pin_mut().unwrap_unchecked() }; // Poll message future let output = match state.as_mut().project() { Projection::Message { future } => ready!(future.poll(cx)), @@ -290,7 +291,7 @@ const _: () = { let payload = T::encode_params(args); // Send message #[cfg(not(feature = "ethexe"))] - let program_future = if let Some(gas_limit) = params.gas_limit { + let future = if let Some(gas_limit) = params.gas_limit { ::gstd::prog::create_program_bytes_with_gas_for_reply( self.code_id, salt, @@ -309,7 +310,7 @@ const _: () = { )? }; #[cfg(feature = "ethexe")] - let program_future = ::gstd::prog::create_program_bytes_for_reply( + let future = ::gstd::prog::create_program_bytes_for_reply( self.code_id, salt, payload, @@ -317,14 +318,14 @@ const _: () = { )?; // self.program_id = Some(program_future.program_id); - self.state = Some(GstdFuture::CreateProgram { - future: program_future, - }); + self.state = Some(GstdFuture::CreateProgram { future }); + // No need to poll the future + return Poll::Pending; } let this = self.as_mut().project(); - if let Some(state) = this.state.as_pin_mut() - && let Projection::CreateProgram { future } = state.project() - { + // SAFETY: checked in the code above. + let mut state = unsafe { this.state.as_pin_mut().unwrap_unchecked() }; + if let Projection::CreateProgram { future } = state.project() { // Poll create program future match ready!(future.poll(cx)) { Ok((program_id, _payload)) => { From 0abcc4cdcc56a522cedff77b22fb93af986ab68c Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 29 Sep 2025 12:53:53 +0200 Subject: [PATCH 33/66] chore: improve readability, but at the cost of gas Control flow '?' costs 1.8M (?!) --- benchmarks/bench_data.json | 4 ++-- rs/src/client/gstd_env.rs | 39 ++++++++++++++------------------------ 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 6235d6829..d28ea4a25 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -17,9 +17,9 @@ "sync_call": 678859589 }, "cross_program": { - "median": 2432696478 + "median": 2434540652 }, "redirect": { - "median": 3464725840 + "median": 3466787299 } } \ No newline at end of file diff --git a/rs/src/client/gstd_env.rs b/rs/src/client/gstd_env.rs index 76ac15719..1cfc545a8 100644 --- a/rs/src/client/gstd_env.rs +++ b/rs/src/client/gstd_env.rs @@ -194,17 +194,10 @@ const _: () = { let destination = self.destination; let params = self.params.get_or_insert_default(); // Send message - let send_res = send_for_reply(destination, payload, params); - match send_res { - Ok(future) => { - self.state = Some(future); - // No need to poll the future - return Poll::Pending; - } - Err(err) => { - return Poll::Ready(Err(err)); - } - } + let future = send_for_reply(destination, payload, params)?; + self.state = Some(future); + // No need to poll the future + return Poll::Pending; } let this = self.as_mut().project(); // SAFETY: checked in the code above. @@ -217,10 +210,11 @@ const _: () = { }; match output { // ok reply - Ok(payload) => match T::decode_reply_with_prefix(self.route, payload) { - Ok(reply) => Poll::Ready(Ok(reply)), - Err(err) => Poll::Ready(Err(Error::Decode(err))), - }, + Ok(payload) => { + let res = + T::decode_reply_with_prefix(self.route, payload).map_err(Error::Decode)?; + Poll::Ready(Ok(res)) + } // reply with ProgramExited Err(gstd::errors::Error::ErrorReply( error_payload, @@ -249,16 +243,11 @@ const _: () = { }); // send message to new target - let send_res = send_for_reply(new_target, payload, params); - match send_res { - Ok(future) => { - // Replace the future with a new one - _ = state.as_mut().project_replace(future); - // Return Pending to allow the new future to be polled - Poll::Pending - } - Err(err) => Poll::Ready(Err(err)), - } + let future = send_for_reply(new_target, payload, params)?; + // Replace the future with a new one + _ = state.as_mut().project_replace(future); + // Return Pending to allow the new future to be polled + Poll::Pending } else { Poll::Ready(Err(gstd::errors::Error::ErrorReply( error_payload, From ac3c9cb4005988ca50ae462e331dc7b9a429ed4b Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 17 Sep 2025 16:29:33 +0200 Subject: [PATCH 34/66] feat: add ping-pong example w/ message stacking feat: add message stack benchmark --- Cargo.lock | 9 +++ Cargo.toml | 1 + benchmarks/Cargo.toml | 1 + benchmarks/bench_data.json | 9 ++- benchmarks/src/benchmarks.rs | 47 +++++++++++ benchmarks/src/entities.rs | 24 ++++++ benchmarks/src/lib.rs | 2 + examples/ping-pong-stack/Cargo.toml | 14 ++++ examples/ping-pong-stack/README.md | 4 + examples/ping-pong-stack/build.rs | 10 +++ examples/ping-pong-stack/ping_pong_stack.idl | 8 ++ examples/ping-pong-stack/src/lib.rs | 67 ++++++++++++++++ .../ping-pong-stack/src/ping_pong_stack.rs | 77 +++++++++++++++++++ examples/ping-pong-stack/tests/gtest.rs | 58 ++++++++++++++ rs/src/builder.rs | 2 +- rs/src/client/gtest_env.rs | 6 ++ rs/src/lib.rs | 2 +- 17 files changed, 338 insertions(+), 3 deletions(-) create mode 100644 examples/ping-pong-stack/Cargo.toml create mode 100644 examples/ping-pong-stack/README.md create mode 100644 examples/ping-pong-stack/build.rs create mode 100644 examples/ping-pong-stack/ping_pong_stack.idl create mode 100644 examples/ping-pong-stack/src/lib.rs create mode 100644 examples/ping-pong-stack/src/ping_pong_stack.rs create mode 100644 examples/ping-pong-stack/tests/gtest.rs diff --git a/Cargo.lock b/Cargo.lock index 721163e33..5acd25217 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -838,6 +838,7 @@ dependencies = [ "gtest", "itertools 0.14.0", "ping-pong-bench-app", + "ping-pong-stack", "redirect-app", "redirect-client", "redirect-proxy", @@ -6184,6 +6185,14 @@ dependencies = [ "sails-idl-gen", ] +[[package]] +name = "ping-pong-stack" +version = "0.1.0" +dependencies = [ + "sails-rs", + "tokio", +] + [[package]] name = "piper" version = "0.2.4" diff --git a/Cargo.toml b/Cargo.toml index 39db585cc..3663ee425 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "examples/redirect/proxy-client", "examples/event-routes", "examples/event-routes/app", + "examples/ping-pong-stack", "rs", "rs/cli", "rs/client-gen", diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml index f5c3f320d..d6907eb35 100644 --- a/benchmarks/Cargo.toml +++ b/benchmarks/Cargo.toml @@ -33,6 +33,7 @@ alloc-stress = { path = "alloc-stress" } compute-stress = { path = "compute-stress" } counter-bench = { path = "counter-bench" } ping-pong-bench-app = { path = "ping-pong/app" } +ping-pong-stack = { path = "../examples/ping-pong-stack" } redirect-app = { path = "../examples/redirect/app" } redirect-client = { path = "../examples/redirect/client" } redirect-proxy = { path = "../examples/redirect/proxy" } diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index d28ea4a25..76d8d378a 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -20,6 +20,13 @@ "median": 2434540652 }, "redirect": { - "median": 3466787299 + "median": 3486681261 + }, + "message_stack": { + "0": 72470834200, + "1": 351985066300, + "5": 1470567201600, + "10": 2753969804100, + "20": 5958017900500 } } \ No newline at end of file diff --git a/benchmarks/src/benchmarks.rs b/benchmarks/src/benchmarks.rs index 76b59a321..af8a8c13f 100644 --- a/benchmarks/src/benchmarks.rs +++ b/benchmarks/src/benchmarks.rs @@ -245,6 +245,27 @@ async fn redirect_bench() { .unwrap(); } +#[tokio::test] +async fn message_stack_bench() { + let mut benches: BTreeMap> = Default::default(); + let limits = [0u32, 1, 5, 10, 20]; + + for _ in 0..100 { + for &limit in limits.iter() { + let gas = message_stack_test(limit).await; + + benches.entry(limit).or_default().push(gas); + } + } + + for (len, gas_benches) in benches { + crate::store_bench_data(|bench_data| { + bench_data.update_message_stack_bench(len, median(gas_benches)); + }) + .unwrap(); + } +} + async fn alloc_stress_test(n: u32) -> (usize, u64) { // Path taken from the .binpath file let wasm_path = "../target/wasm32-gear/release/alloc_stress.opt.wasm"; @@ -269,6 +290,32 @@ async fn alloc_stress_test(n: u32) -> (usize, u64) { (expected_len, gas) } +async fn message_stack_test(limit: u32) -> u64 { + use ping_pong_stack::client::{PingPongStack as _, ping_pong_stack::PingPongStack as _}; + // Path taken from the .binpath file + let wasm_path = "../target/wasm32-gear/release/ping_pong_stack.opt.wasm"; + let env = create_env(); + let program_1 = deploy_for_bench(&env, wasm_path, |d| { + ping_pong_stack::client::PingPongStackCtors::new_for_bench(d) + }) + .await; + let program_2 = deploy_for_bench(&env, wasm_path, |d| { + ping_pong_stack::client::PingPongStackCtors::new_for_bench(d) + }) + .await; + + let initial_balance = env.system().balance_of(DEFAULT_USER_ALICE); + + program_1 + .ping_pong_stack() + .start(program_2.id(), limit) + .await + .unwrap(); + + let balance = env.system().balance_of(DEFAULT_USER_ALICE); + (initial_balance - balance).try_into().unwrap() +} + fn create_env() -> GtestEnv { let system = System::new(); system.mint_to(DEFAULT_USER_ALICE, 1_000_000_000_000_000); diff --git a/benchmarks/src/entities.rs b/benchmarks/src/entities.rs index 8fa23eac1..76bf8328f 100644 --- a/benchmarks/src/entities.rs +++ b/benchmarks/src/entities.rs @@ -24,6 +24,9 @@ impl BenchData { map.insert(BenchCategory::CounterAsync, data.counter.async_call); map.insert(BenchCategory::CrossProgram, data.cross_program.median); map.insert(BenchCategory::Redirect, data.redirect.median); + for (key, value) in data.message_stack.0 { + map.insert(BenchCategory::MessageStack(key), value); + } Ok(Self(map)) } @@ -57,6 +60,11 @@ impl BenchData { self.0.insert(BenchCategory::Redirect, value); } + /// Update message stack benchmark category value. + pub fn update_message_stack_bench(&mut self, limit: u32, value: u64) { + self.0.insert(BenchCategory::MessageStack(limit), value); + } + /// Convert the benchmark data into a JSON string. pub fn into_json_string(self) -> Result { let mut bench_data = BenchDataSerde::default(); @@ -71,6 +79,9 @@ impl BenchData { BenchCategory::CounterAsync => bench_data.counter.async_call = value, BenchCategory::CrossProgram => bench_data.cross_program.median = value, BenchCategory::Redirect => bench_data.redirect.median = value, + BenchCategory::MessageStack(limit) => { + bench_data.message_stack.0.insert(limit, value); + } } } @@ -93,11 +104,18 @@ impl IntoIterator for BenchData { /// This struct is used to serialize and deserialize benchmark data #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] pub struct BenchDataSerde { + #[serde(default)] pub compute: ComputeBenchDataSerde, + #[serde(default)] pub alloc: AllocBenchDataSerde, + #[serde(default)] pub counter: CounterBenchDataSerde, + #[serde(default)] pub cross_program: CrossProgramBenchDataSerde, + #[serde(default)] pub redirect: RedirectBenchDataSerde, + #[serde(default)] + pub message_stack: MessageStackDataSerde, } /// Compute benchmark data stored in the benchmarks file. @@ -131,6 +149,10 @@ pub struct CounterBenchDataSerde { pub sync_call: u64, } +/// Message stack benchmark data stored in the benchmarks file. +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] +pub struct MessageStackDataSerde(pub BTreeMap); + /// Benchmark category that can be read (written) from (to) the benchmarks file. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum BenchCategory { @@ -140,6 +162,7 @@ pub enum BenchCategory { CounterAsync, CrossProgram, Redirect, + MessageStack(u32), } impl Display for BenchCategory { @@ -151,6 +174,7 @@ impl Display for BenchCategory { BenchCategory::CounterAsync => write!(f, "counter_async"), BenchCategory::CrossProgram => write!(f, "cross_program"), BenchCategory::Redirect => write!(f, "redirect"), + BenchCategory::MessageStack(limit) => write!(f, "stack_{limit}"), } } } diff --git a/benchmarks/src/lib.rs b/benchmarks/src/lib.rs index 3812d9578..281a49d6d 100644 --- a/benchmarks/src/lib.rs +++ b/benchmarks/src/lib.rs @@ -79,6 +79,7 @@ mod tests { }, cross_program: CrossProgramBenchDataSerde { median: 42 }, redirect: RedirectBenchDataSerde { median: 4242 }, + message_stack: Default::default(), }; // Create a temporary file. @@ -140,6 +141,7 @@ mod tests { }, cross_program: CrossProgramBenchDataSerde { median: 0 }, redirect: RedirectBenchDataSerde { median: 4343 }, + message_stack: Default::default(), }, ) } diff --git a/examples/ping-pong-stack/Cargo.toml b/examples/ping-pong-stack/Cargo.toml new file mode 100644 index 000000000..aecf51bde --- /dev/null +++ b/examples/ping-pong-stack/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "ping-pong-stack" +version = "0.1.0" +edition = "2024" + +[dependencies] +sails-rs.workspace = true + +[build-dependencies] +sails-rs = { workspace = true, features = ["build"] } + +[dev-dependencies] +sails-rs = { workspace = true, features = ["gtest"] } +tokio = { workspace = true, features = ["rt", "macros"] } diff --git a/examples/ping-pong-stack/README.md b/examples/ping-pong-stack/README.md new file mode 100644 index 000000000..df0b92049 --- /dev/null +++ b/examples/ping-pong-stack/README.md @@ -0,0 +1,4 @@ +# Ping Pong Stack + +This example demonstrates how to use generated client to call another program with same contract. +Message futures between programs are stacked until the countdown resets. They are then resolved in reverse order. diff --git a/examples/ping-pong-stack/build.rs b/examples/ping-pong-stack/build.rs new file mode 100644 index 000000000..51cf2972a --- /dev/null +++ b/examples/ping-pong-stack/build.rs @@ -0,0 +1,10 @@ +fn main() { + sails_rs::build_wasm(); + + let base_path = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + let idl_path = base_path.join("ping_pong_stack.idl"); + let out_path = base_path.join("src/ping_pong_stack.rs"); + sails_rs::ClientGenerator::from_idl_path(&idl_path) + .generate_to(out_path) + .unwrap(); +} diff --git a/examples/ping-pong-stack/ping_pong_stack.idl b/examples/ping-pong-stack/ping_pong_stack.idl new file mode 100644 index 000000000..bcb42c970 --- /dev/null +++ b/examples/ping-pong-stack/ping_pong_stack.idl @@ -0,0 +1,8 @@ +constructor { + NewForBench : (); +}; + +service PingPongStack { + Start : (actor_id: actor_id, limit: u32) -> null; + Ping : (countdown: u32) -> null; +}; diff --git a/examples/ping-pong-stack/src/lib.rs b/examples/ping-pong-stack/src/lib.rs new file mode 100644 index 000000000..474960552 --- /dev/null +++ b/examples/ping-pong-stack/src/lib.rs @@ -0,0 +1,67 @@ +#![no_std] + +use client::{PingPongStack as _, ping_pong_stack::PingPongStack as _}; +use sails_rs::{client::Program as _, gstd::*, prelude::*}; + +struct PingPongStack; + +impl PingPongStack { + pub fn new() -> Self { + Self + } +} + +#[sails_rs::service] +impl PingPongStack { + #[export] + pub async fn start(&mut self, actor_id: ActorId, limit: u32) { + self.call(actor_id, limit).await; + } + + #[export] + pub async fn ping(&mut self, countdown: u32) { + let source = msg::source(); + self.call(source, countdown - 1).await; + } + + #[inline] + async fn call(&mut self, actor_id: ActorId, countdown: u32) -> bool { + sails_rs::gstd::debug!("Ping: {countdown}, actor_id: {actor_id}"); + if countdown > 0 { + let mut api = client::PingPongStackProgram::client(actor_id).ping_pong_stack(); + let _res = api.ping(countdown).with_reply_deposit(10_000_000_000).await; + sails_rs::gstd::debug!("Result: {_res:?}"); + debug_assert!(_res.is_ok()); + true + } else { + false + } + } +} + +#[derive(Default)] +pub struct Program; + +#[sails_rs::program] +impl Program { + pub fn new_for_bench() -> Self { + Self + } + + // Exposed service + pub fn ping_pong_stack(&self) -> PingPongStack { + PingPongStack::new() + } +} + +pub mod client { + include!("./ping_pong_stack.rs"); +} + +#[cfg(not(target_arch = "wasm32"))] +pub use code::WASM_BINARY_OPT as WASM_BINARY; + +#[cfg(not(target_arch = "wasm32"))] +mod code { + include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +} diff --git a/examples/ping-pong-stack/src/ping_pong_stack.rs b/examples/ping-pong-stack/src/ping_pong_stack.rs new file mode 100644 index 000000000..6d3980fc3 --- /dev/null +++ b/examples/ping-pong-stack/src/ping_pong_stack.rs @@ -0,0 +1,77 @@ +// Code generated by sails-client-gen. DO NOT EDIT. +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; +pub struct PingPongStackProgram; +impl sails_rs::client::Program for PingPongStackProgram {} +pub trait PingPongStack { + type Env: sails_rs::client::GearEnv; + fn ping_pong_stack( + &self, + ) -> sails_rs::client::Service; +} +impl PingPongStack + for sails_rs::client::Actor +{ + type Env = E; + fn ping_pong_stack( + &self, + ) -> sails_rs::client::Service { + self.service(stringify!(PingPongStack)) + } +} +pub trait PingPongStackCtors { + type Env: sails_rs::client::GearEnv; + fn new_for_bench( + self, + ) -> sails_rs::client::PendingCtor; +} +impl PingPongStackCtors + for sails_rs::client::Deployment +{ + type Env = E; + fn new_for_bench( + self, + ) -> sails_rs::client::PendingCtor { + self.pending_ctor(()) + } +} + +pub mod io { + use super::*; + sails_rs::io_struct_impl!(NewForBench () -> ()); +} + +pub mod ping_pong_stack { + use super::*; + pub trait PingPongStack { + type Env: sails_rs::client::GearEnv; + fn start( + &mut self, + actor_id: ActorId, + limit: u32, + ) -> sails_rs::client::PendingCall; + fn ping(&mut self, countdown: u32) -> sails_rs::client::PendingCall; + } + pub struct PingPongStackImpl; + impl PingPongStack + for sails_rs::client::Service + { + type Env = E; + fn start( + &mut self, + actor_id: ActorId, + limit: u32, + ) -> sails_rs::client::PendingCall { + self.pending_call((actor_id, limit)) + } + fn ping(&mut self, countdown: u32) -> sails_rs::client::PendingCall { + self.pending_call((countdown,)) + } + } + + pub mod io { + use super::*; + sails_rs::io_struct_impl!(Start (actor_id: ActorId, limit: u32) -> ()); + sails_rs::io_struct_impl!(Ping (countdown: u32) -> ()); + } +} diff --git a/examples/ping-pong-stack/tests/gtest.rs b/examples/ping-pong-stack/tests/gtest.rs new file mode 100644 index 000000000..438c7e55c --- /dev/null +++ b/examples/ping-pong-stack/tests/gtest.rs @@ -0,0 +1,58 @@ +use ping_pong_stack::client::{ + PingPongStack, PingPongStackCtors, PingPongStackProgram, ping_pong_stack::PingPongStack as _, +}; +use sails_rs::{CodeId, GasUnit, client::*}; + +const ACTOR_ID: u64 = 42; + +#[tokio::test] +async fn ping_pong_stack_works() { + let (env, code_id, _gas_limit) = create_env(); + + let program_1 = env + .deploy::(code_id, vec![1]) + .new_for_bench() + .await + .unwrap(); + + let program_2 = env + .deploy::(code_id, vec![2]) + .new_for_bench() + .await + .unwrap(); + + let limit = 10; + let initial_balance = env.system().balance_of(ACTOR_ID); + + program_1 + .ping_pong_stack() + .start(program_2.id(), limit) + .await + .unwrap(); + + let balance = env.system().balance_of(ACTOR_ID); + + println!( + "[ping_pong_stack_works] limit: {:02}, burned: {:>14}", + limit, + initial_balance - balance, + ); +} + +fn create_env() -> (GtestEnv, CodeId, GasUnit) { + use sails_rs::gtest::{System, constants::MAX_USER_GAS_LIMIT}; + + let system = System::new(); + system.init_logger_with_default_filter( + "gwasm=debug,gtest=info,sails_rs=debug,ping_pong_stack=debug", + ); + system.mint_to(ACTOR_ID, 1_000_000_000_000_000); + // Submit program code into the system + let code_id = system.submit_code(ping_pong_stack::WASM_BINARY); + + // Create a remoting instance for the system + // and set the block run mode to Next, + // cause we don't receive any reply on `Exit` call + let env = GtestEnv::new(system, ACTOR_ID.into()); + (env, code_id, MAX_USER_GAS_LIMIT) +} diff --git a/rs/src/builder.rs b/rs/src/builder.rs index c230d395e..fcedfa605 100644 --- a/rs/src/builder.rs +++ b/rs/src/builder.rs @@ -1,6 +1,6 @@ use convert_case::{Case, Casing}; use core::marker::PhantomData; -use sails_client_gen::{ClientGenerator, IdlPath}; +pub use sails_client_gen::{ClientGenerator, IdlPath}; use sails_idl_meta::ProgramMeta; use std::{ env, diff --git a/rs/src/client/gtest_env.rs b/rs/src/client/gtest_env.rs index 93c99d2b7..2935e2bd5 100644 --- a/rs/src/client/gtest_env.rs +++ b/rs/src/client/gtest_env.rs @@ -435,3 +435,9 @@ impl Listener for GtestEnv { Ok(rx.filter_map(f)) } } + +impl Actor { + pub fn balance(&self) -> ValueUnit { + self.env.system().balance_of(self.id) + } +} diff --git a/rs/src/lib.rs b/rs/src/lib.rs index c338f30eb..d03913294 100644 --- a/rs/src/lib.rs +++ b/rs/src/lib.rs @@ -6,7 +6,7 @@ extern crate std; #[cfg(feature = "client-builder")] -pub use builder::{ClientBuilder, build_client, build_client_as_lib}; +pub use builder::{ClientBuilder, ClientGenerator, IdlPath, build_client, build_client_as_lib}; #[cfg(feature = "wasm-builder")] pub use gwasm_builder::build as build_wasm; pub use hex; From a8da782c09e5095293a84d08a72f5f1e74a9d358 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 29 Sep 2025 19:31:38 +0200 Subject: [PATCH 35/66] feat: create second Ping program from first one feat: create Pong acrtor from Ping ctor --- benchmarks/bench_data.json | 14 +++---- benchmarks/src/benchmarks.rs | 32 +++++++++------ examples/ping-pong-stack/ping_pong_stack.idl | 5 ++- examples/ping-pong-stack/src/lib.rs | 40 +++++++++++++------ .../ping-pong-stack/src/ping_pong_stack.rs | 37 +++++++++-------- examples/ping-pong-stack/tests/gtest.rs | 17 ++------ 6 files changed, 82 insertions(+), 63 deletions(-) diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 76d8d378a..56186fb3f 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -17,16 +17,16 @@ "sync_call": 678859589 }, "cross_program": { - "median": 2434540652 + "median": 2436719419 }, "redirect": { - "median": 3486681261 + "median": 3473474864 }, "message_stack": { - "0": 72470834200, - "1": 351985066300, - "5": 1470567201600, - "10": 2753969804100, - "20": 5958017900500 + "0": 79911236500, + "1": 376957078300, + "5": 1569303378600, + "10": 2959197465500, + "20": 6398916489100 } } \ No newline at end of file diff --git a/benchmarks/src/benchmarks.rs b/benchmarks/src/benchmarks.rs index af8a8c13f..90ca5826f 100644 --- a/benchmarks/src/benchmarks.rs +++ b/benchmarks/src/benchmarks.rs @@ -295,22 +295,15 @@ async fn message_stack_test(limit: u32) -> u64 { // Path taken from the .binpath file let wasm_path = "../target/wasm32-gear/release/ping_pong_stack.opt.wasm"; let env = create_env(); - let program_1 = deploy_for_bench(&env, wasm_path, |d| { - ping_pong_stack::client::PingPongStackCtors::new_for_bench(d) - }) - .await; - let program_2 = deploy_for_bench(&env, wasm_path, |d| { - ping_pong_stack::client::PingPongStackCtors::new_for_bench(d) + let code_id = env.system().submit_local_code_file(wasm_path); + let program = deploy_code_for_bench(&env, code_id, |d| { + ping_pong_stack::client::PingPongStackCtors::create_ping(d, code_id) }) .await; let initial_balance = env.system().balance_of(DEFAULT_USER_ALICE); - program_1 - .ping_pong_stack() - .start(program_2.id(), limit) - .await - .unwrap(); + program.ping_pong_stack().start(limit).await.unwrap(); let balance = env.system().balance_of(DEFAULT_USER_ALICE); (initial_balance - balance).try_into().unwrap() @@ -332,13 +325,28 @@ async fn deploy_for_bench< f: F, ) -> Actor { let code_id = env.system().submit_local_code_file(wasm_path); + deploy_code_for_bench(env, code_id, f).await +} + +async fn deploy_code_for_bench< + P: Program, + IO: CallEncodeDecode, + F: FnOnce(Deployment) -> PendingCtor, +>( + env: &GtestEnv, + code_id: CodeId, + f: F, +) -> Actor { let salt = COUNTER_SALT .fetch_add(1, std::sync::atomic::Ordering::SeqCst) .to_le_bytes() .to_vec(); let deployment = env.deploy::