diff --git a/Cargo.lock b/Cargo.lock index 2c27b17d..f0cd58e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,9 +22,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "anyhow" -version = "1.0.87" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "ark-bls12-381" @@ -227,15 +227,15 @@ dependencies = [ [[package]] name = "cosmwasm-core" -version = "2.1.3" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d905990ef3afb5753bb709dc7de88e9e370aa32bcc2f31731d4b533b63e82490" +checksum = "5f6ceb8624260d0d3a67c4e1a1d43fc7e9406720afbcb124521501dd138f90aa" [[package]] name = "cosmwasm-crypto" -version = "2.1.3" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b2a7bd9c1dd9a377a4dc0f4ad97d24b03c33798cd5a6d7ceb8869b41c5d2f2d" +checksum = "4125381e5fd7fefe9f614640049648088015eca2b60d861465329a5d87dfa538" dependencies = [ "ark-bls12-381", "ark-ec", @@ -256,9 +256,9 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "2.1.3" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029910b409398fdf81955d7301b906caf81f2c42b013ea074fbd89720229c424" +checksum = "1b5658b1dc64e10b56ae7a449f678f96932a96f6cfad1769d608d1d1d656480a" dependencies = [ "proc-macro2", "quote", @@ -291,9 +291,9 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "2.1.3" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51dec99a2e478715c0a4277f0dbeadbb8466500eb7dec873d0924edd086e77f1" +checksum = "70eb7ab0c1e99dd6207496963ba2a457c4128ac9ad9c72a83f8d9808542b849b" dependencies = [ "base64", "bech32", @@ -854,9 +854,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" +checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" dependencies = [ "bytes", "prost-derive", @@ -864,9 +864,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" +checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" dependencies = [ "anyhow", "itertools 0.13.0", @@ -1192,18 +1192,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 0b76ff15..c7f2ff5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ sylvia-derive = { version = "1.2.1", path = "sylvia-derive" } anyhow = "1.0.86" cosmwasm-schema = "2.1.1" cosmwasm-std = "2.1.1" -cw-multi-test = "2.1.0" +cw-multi-test = "2.1.1" cw-storage-plus = "2.0.0" schemars = "0.8.21" cw-utils = "2.0.0" diff --git a/sylvia-derive/src/contract/communication/reply.rs b/sylvia-derive/src/contract/communication/reply.rs index ae4da79c..938df45f 100644 --- a/sylvia-derive/src/contract/communication/reply.rs +++ b/sylvia-derive/src/contract/communication/reply.rs @@ -6,7 +6,8 @@ use syn::{parse_quote, GenericParam, Ident, ItemImpl, Type}; use crate::crate_module; use crate::parser::attributes::msg::ReplyOn; -use crate::parser::MsgType; +use crate::parser::{MsgType, SylviaAttribute}; +use crate::types::msg_field::MsgField; use crate::types::msg_variant::{MsgVariant, MsgVariants}; use crate::utils::emit_turbofish; @@ -173,6 +174,7 @@ impl<'a> ReplyVariants<'a> for MsgVariants<'a, GenericParam> { let existing_reply_id = &existing_data.reply_id; let existing_reply_on = existing_handler.msg_attr().reply_on(); let existing_function_name= existing_handler.function_name(); + emit_error!(reply_id.span(), "Duplicated reply handler."; note = existing_data.reply_id.span() => format!("Previous definition of handler={} for reply_on={} defined on `fn {}()`", existing_reply_id, existing_reply_on, existing_function_name); ) @@ -313,19 +315,29 @@ fn emit_success_match_arm(handlers: &[&MsgVariant], contract_turbofish: &Type) - }) { Some(handler) if handler.msg_attr().reply_on() == ReplyOn::Success => { let function_name = handler.function_name(); + let payload = handler.emit_payload_parameters(); + let payload_deserialization = handler.emit_payload_deserialization(); + quote! { #sylvia ::cw_std::SubMsgResult::Ok(sub_msg_resp) => { #[allow(deprecated)] let #sylvia ::cw_std::SubMsgResponse { events, data, msg_responses} = sub_msg_resp; - #contract_turbofish ::new(). #function_name ((deps, env, gas_used, events, msg_responses).into(), data, payload) + #payload_deserialization + + #contract_turbofish ::new(). #function_name ((deps, env, gas_used, events, msg_responses).into(), data, #payload ) } } } Some(handler) if handler.msg_attr().reply_on() == ReplyOn::Always => { let function_name = handler.function_name(); + let payload = handler.emit_payload_parameters(); + let payload_deserialization = handler.emit_payload_deserialization(); + quote! { #sylvia ::cw_std::SubMsgResult::Ok(_) => { - #contract_turbofish ::new(). #function_name ((deps, env, gas_used, vec![], vec![]).into(), result, payload) + #payload_deserialization + + #contract_turbofish ::new(). #function_name ((deps, env, gas_used, vec![], vec![]).into(), result, #payload ) } } } @@ -356,17 +368,27 @@ fn emit_failure_match_arm(handlers: &[&MsgVariant], contract_turbofish: &Type) - }) { Some(handler) if handler.msg_attr().reply_on() == ReplyOn::Failure => { let function_name = handler.function_name(); + let payload = handler.emit_payload_parameters(); + let payload_deserialization = handler.emit_payload_deserialization(); + quote! { #sylvia ::cw_std::SubMsgResult::Err(error) => { - #contract_turbofish ::new(). #function_name ((deps, env, gas_used, vec![], vec![]).into(), error, payload) + #payload_deserialization + + #contract_turbofish ::new(). #function_name ((deps, env, gas_used, vec![], vec![]).into(), error, #payload ) } } } Some(handler) if handler.msg_attr().reply_on() == ReplyOn::Always => { let function_name = handler.function_name(); + let payload = handler.emit_payload_parameters(); + let payload_deserialization = handler.emit_payload_deserialization(); + quote! { #sylvia ::cw_std::SubMsgResult::Err(_) => { - #contract_turbofish ::new(). #function_name ((deps, env, gas_used, vec![], vec![]).into(), result, payload) + #payload_deserialization + + #contract_turbofish ::new(). #function_name ((deps, env, gas_used, vec![], vec![]).into(), result, #payload ) } } } @@ -381,6 +403,8 @@ fn emit_failure_match_arm(handlers: &[&MsgVariant], contract_turbofish: &Type) - trait ReplyVariant<'a> { fn as_handlers(&'a self) -> Vec<&'a Ident>; fn as_reply_data(&self) -> Vec<(Ident, &Ident, &MsgVariant)>; + fn emit_payload_parameters(&self) -> TokenStream; + fn emit_payload_deserialization(&self) -> TokenStream; } impl<'a> ReplyVariant<'a> for MsgVariant<'a> { @@ -397,6 +421,36 @@ impl<'a> ReplyVariant<'a> for MsgVariant<'a> { .map(|&handler| (handler.as_reply_id(), handler, self)) .collect() } + + fn emit_payload_parameters(&self) -> TokenStream { + if self + .fields() + .iter() + .any(|field| field.contains_attribute(SylviaAttribute::Payload)) + { + quote! { payload } + } else { + let deserialized_payload = self.fields().iter().skip(1).map(MsgField::name); + quote! { #(#deserialized_payload),* } + } + } + + fn emit_payload_deserialization(&self) -> TokenStream { + let sylvia = crate_module(); + + if self + .fields() + .iter() + .any(|field| field.contains_attribute(SylviaAttribute::Payload)) + { + return quote! {}; + } + + let deserialized_names = self.fields().iter().skip(1).map(MsgField::name); + quote! { + let ( #(#deserialized_names),* ) = #sylvia ::cw_std::from_json(&payload)?; + } + } } /// Maps self to an [Ident] reply id. diff --git a/sylvia-derive/src/parser/attributes/mod.rs b/sylvia-derive/src/parser/attributes/mod.rs index c3b3cb5f..9390dac6 100644 --- a/sylvia-derive/src/parser/attributes/mod.rs +++ b/sylvia-derive/src/parser/attributes/mod.rs @@ -21,6 +21,7 @@ pub use override_entry_point::{FilteredOverrideEntryPoints, OverrideEntryPoint}; /// This struct represents all possible attributes that /// are parsed and utilized by sylvia. +#[derive(Clone, Copy, Debug, PartialEq)] pub enum SylviaAttribute { Custom, Error, @@ -29,6 +30,7 @@ pub enum SylviaAttribute { OverrideEntryPoint, VariantAttrs, MsgAttrs, + Payload, } impl SylviaAttribute { @@ -50,6 +52,7 @@ impl SylviaAttribute { "override_entry_point" => Some(Self::OverrideEntryPoint), "attr" => Some(Self::VariantAttrs), "msg_attr" => Some(Self::MsgAttrs), + "payload" => Some(Self::Payload), _ => None, } } @@ -158,6 +161,12 @@ impl ParsedSylviaAttributes { self.msg_attrs_forward.push(message_attrs); } } + SylviaAttribute::Payload => { + emit_error!( + attr, "The attribute `sv::payload` used in wrong context"; + note = attr.span() => "The `sv::payload` should be used as a prefix for `Binary` payload."; + ); + } } } } diff --git a/sylvia-derive/src/types/msg_field.rs b/sylvia-derive/src/types/msg_field.rs index 14f26de8..227732d3 100644 --- a/sylvia-derive/src/types/msg_field.rs +++ b/sylvia-derive/src/types/msg_field.rs @@ -1,5 +1,6 @@ use crate::fold::StripSelfPath; use crate::parser::check_generics::{CheckGenerics, GetPath}; +use crate::parser::SylviaAttribute; use proc_macro2::TokenStream; use proc_macro_error::emit_error; use quote::quote; @@ -115,4 +116,10 @@ impl<'a> MsgField<'a> { pub fn name(&self) -> &'a Ident { self.name } + + pub fn contains_attribute(&self, sv_attr: SylviaAttribute) -> bool { + self.attrs + .iter() + .any(|attr| SylviaAttribute::new(attr) == Some(sv_attr)) + } } diff --git a/sylvia/tests/reply.rs b/sylvia/tests/reply.rs index db94581b..b4ff2b01 100644 --- a/sylvia/tests/reply.rs +++ b/sylvia/tests/reply.rs @@ -1,6 +1,7 @@ #![cfg(feature = "sv_replies")] -use cosmwasm_std::{BankMsg, CosmosMsg, Empty, SubMsgResult}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{to_json_binary, BankMsg, CosmosMsg, Empty, SubMsgResult}; use cw_storage_plus::Item; use cw_utils::{parse_instantiate_response_data, ParseReplyError}; use noop_contract::sv::{Executor, NoopContractInstantiateBuilder}; @@ -56,6 +57,11 @@ mod noop_contract { } } +#[cw_serde] +pub struct InstantiatePayload { + pub sender: Addr, +} + #[derive(Error, Debug, PartialEq)] pub enum ContractError { #[error("{0}")] @@ -91,13 +97,19 @@ where #[sv::msg(instantiate)] pub fn instantiate( &self, - _ctx: InstantiateCtx, + ctx: InstantiateCtx, remote_code_id: u64, ) -> Result, ContractError> { + // Custom type can be used as a payload. + let payload = InstantiatePayload { + sender: ctx.info.sender, + }; let sub_msg = InstantiateBuilder::noop_contract(remote_code_id)? .with_label("noop") .build() - .remote_instantiated(); + .remote_instantiated() + .with_payload(to_json_binary(&payload)?); + Ok(Response::new().add_submessage(sub_msg)) } @@ -158,13 +170,17 @@ where ctx: ExecCtx, should_fail: bool, ) -> Result, ContractError> { + // Tuple can be used as a payload. + let payload = to_json_binary(&(42_u32, "Hello, world!".to_string()))?; + let msg = self .remote .load(ctx.deps.storage)? .executor() .noop(should_fail)? .build() - .always(); + .always() + .with_payload(payload); Ok(Response::new().add_submessage(msg)) } @@ -198,7 +214,10 @@ where &self, ctx: ReplyCtx, data: Option, - _payload: Binary, + // Blocked by https://github.com/CosmWasm/cw-multi-test/pull/216. + // Payload is not currently forwarded in the MultiTest. + // _instantiate_payload: InstantiatePayload, + #[sv::payload] _payload: Binary, ) -> Result, ContractError> { self.last_reply .save(ctx.deps.storage, &REMOTE_INSTANTIATED_REPLY_ID)?; @@ -216,7 +235,7 @@ where &self, ctx: ReplyCtx, _data: Option, - _payload: Binary, + #[sv::payload] _payload: Binary, ) -> Result, ContractError> { self.last_reply.save(ctx.deps.storage, &SUCCESS_REPLY_ID)?; @@ -228,7 +247,7 @@ where &self, ctx: ReplyCtx, _error: String, - _payload: Binary, + #[sv::payload] _payload: Binary, ) -> Result, ContractError> { self.last_reply.save(ctx.deps.storage, &FAILURE_REPLY_ID)?; @@ -240,7 +259,9 @@ where &self, ctx: ReplyCtx, _result: SubMsgResult, - _payload: Binary, + #[sv::payload] _payload: Binary, + // _first_part_payload: u32, + // _second_part_payload: String, ) -> Result, ContractError> { self.last_reply.save(ctx.deps.storage, &ALWAYS_REPLY_ID)?; diff --git a/sylvia/tests/reply_generation.rs b/sylvia/tests/reply_generation.rs index 171fa6b1..bf320208 100644 --- a/sylvia/tests/reply_generation.rs +++ b/sylvia/tests/reply_generation.rs @@ -25,7 +25,7 @@ impl Contract { &self, _ctx: ReplyCtx, _result: SubMsgResult, - _payload: Binary, + #[sv::payload] _payload: Binary, ) -> StdResult { Ok(Response::new()) } @@ -36,7 +36,7 @@ impl Contract { &self, _ctx: ReplyCtx, _result: SubMsgResult, - _payload: Binary, + #[sv::payload] _payload: Binary, ) -> StdResult { Ok(Response::new()) } @@ -47,7 +47,7 @@ impl Contract { &self, _ctx: ReplyCtx, _data: Option, - _payload: Binary, + #[sv::payload] _payload: Binary, ) -> StdResult { Ok(Response::new()) } @@ -58,7 +58,7 @@ impl Contract { &self, _ctx: ReplyCtx, _result: SubMsgResult, - _payload: Binary, + #[sv::payload] _payload: Binary, ) -> StdResult { Ok(Response::new()) } @@ -69,7 +69,7 @@ impl Contract { &self, _ctx: ReplyCtx, _error: String, - _payload: Binary, + #[sv::payload] _payload: Binary, ) -> StdResult { Ok(Response::new()) }