Skip to content

Commit

Permalink
feat: Add option to deserialize payload (#439)
Browse files Browse the repository at this point in the history
  • Loading branch information
jawoznia committed Oct 14, 2024
1 parent 96d9ec3 commit fb86aaa
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 37 deletions.
36 changes: 18 additions & 18 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
64 changes: 59 additions & 5 deletions sylvia-derive/src/contract/communication/reply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
)
Expand Down Expand Up @@ -315,19 +317,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 )
}
}
}
Expand Down Expand Up @@ -358,17 +370,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 )
}
}
}
Expand All @@ -383,6 +405,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> {
Expand All @@ -399,6 +423,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.
Expand Down
9 changes: 9 additions & 0 deletions sylvia-derive/src/parser/attributes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -29,6 +30,7 @@ pub enum SylviaAttribute {
OverrideEntryPoint,
VariantAttrs,
MsgAttrs,
Payload,
}

impl SylviaAttribute {
Expand All @@ -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,
}
}
Expand Down Expand Up @@ -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.";
);
}
}
}
}
7 changes: 7 additions & 0 deletions sylvia-derive/src/types/msg_field.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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))
}
}
37 changes: 29 additions & 8 deletions sylvia/tests/reply.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -56,6 +57,11 @@ mod noop_contract {
}
}

#[cw_serde]
pub struct InstantiatePayload {
pub sender: Addr,
}

#[derive(Error, Debug, PartialEq)]
pub enum ContractError {
#[error("{0}")]
Expand Down Expand Up @@ -91,13 +97,19 @@ where
#[sv::msg(instantiate)]
pub fn instantiate(
&self,
_ctx: InstantiateCtx<Q>,
ctx: InstantiateCtx<Q>,
remote_code_id: u64,
) -> Result<Response<M>, 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))
}

Expand Down Expand Up @@ -158,13 +170,17 @@ where
ctx: ExecCtx<Q>,
should_fail: bool,
) -> Result<Response<M>, 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))
}
Expand Down Expand Up @@ -198,7 +214,10 @@ where
&self,
ctx: ReplyCtx<Q>,
data: Option<Binary>,
_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<Response<M>, ContractError> {
self.last_reply
.save(ctx.deps.storage, &REMOTE_INSTANTIATED_REPLY_ID)?;
Expand All @@ -216,7 +235,7 @@ where
&self,
ctx: ReplyCtx<Q>,
_data: Option<Binary>,
_payload: Binary,
#[sv::payload] _payload: Binary,
) -> Result<Response<M>, ContractError> {
self.last_reply.save(ctx.deps.storage, &SUCCESS_REPLY_ID)?;

Expand All @@ -228,7 +247,7 @@ where
&self,
ctx: ReplyCtx<Q>,
_error: String,
_payload: Binary,
#[sv::payload] _payload: Binary,
) -> Result<Response<M>, ContractError> {
self.last_reply.save(ctx.deps.storage, &FAILURE_REPLY_ID)?;

Expand All @@ -240,7 +259,9 @@ where
&self,
ctx: ReplyCtx<Q>,
_result: SubMsgResult,
_payload: Binary,
#[sv::payload] _payload: Binary,
// _first_part_payload: u32,
// _second_part_payload: String,
) -> Result<Response<M>, ContractError> {
self.last_reply.save(ctx.deps.storage, &ALWAYS_REPLY_ID)?;

Expand Down
Loading

0 comments on commit fb86aaa

Please sign in to comment.