Skip to content

Commit 3282a3a

Browse files
wip
1 parent 90ed205 commit 3282a3a

File tree

5 files changed

+231
-37
lines changed

5 files changed

+231
-37
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

soroban-sdk-macros/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ proc-macro2 = "1.0"
2929
itertools = "0.10.5"
3030
darling = "0.20.0"
3131
sha2 = "0.10.7"
32+
heck = "0.5.0"
3233

3334
[features]
3435
testutils = []

soroban-sdk-macros/src/derive_event.rs

Lines changed: 62 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use darling::{ast::NestedMeta, Error, FromMeta};
2+
use heck::ToSnakeCase;
23
use proc_macro2::TokenStream as TokenStream2;
34
use quote::{format_ident, quote};
45
use syn::Index;
@@ -24,12 +25,15 @@ struct ContractEventArgs {
2425
crate_path: Path,
2526
lib: Option<String>,
2627
data_format: String, // single-value, vec, map
27-
prefix_topics: Vec<LitStr>,
28+
prefix_topics: Option<Vec<LitStr>>,
2829
}
2930

3031
impl ContractEventArgs {
3132
pub fn prefix_topics(&self) -> impl Iterator<Item = String> + use<'_> {
32-
self.prefix_topics.iter().map(|s| s.value())
33+
self.prefix_topics
34+
.iter()
35+
.flatten()
36+
.map(|s: &LitStr| s.value())
3337
}
3438
}
3539

@@ -110,7 +114,7 @@ fn derive_event_inner<'a>(
110114
};
111115

112116
let prefix_topics = match prefix_topics[..] {
113-
[] => vec![ident.to_string()],
117+
[] => vec![ident.to_string().to_snake_case()],
114118
_ => prefix_topics,
115119
};
116120

@@ -193,8 +197,13 @@ fn derive_event_inner<'a>(
193197
topic_params.clone().map(|p| (p.ident, p.type_)).unzip();
194198
let prefix_topic_types = (0..prefix_topics.len()).map(|_| quote! { #path::Symbol });
195199
let tuple_index_calls = (0..(prefix_topics.len() + topic_params.count())).map(|i| {
196-
let i = Index::from(i);
197-
quote! { self.#i }
200+
if i < prefix_topics.len() {
201+
let i = Index::from(i);
202+
quote! { &self.#i }
203+
} else {
204+
let i = Index::from(i);
205+
quote! { self.#i }
206+
}
198207
});
199208
let topic_type = quote! {
200209
pub struct #topic_type_ident<'a>(
@@ -210,6 +219,8 @@ fn derive_event_inner<'a>(
210219
}
211220
}
212221
impl<'a> #path::Topics for #topic_type_ident<'a> {}
222+
// TODO: Implement to Vec<Val> for contracttype struct with unnamed fields so that this
223+
// impl is not required here on every type.
213224
impl<'a> #path::IntoVal<#path::Env, #path::Vec<#path::Val>> for #topic_type_ident<'a> {
214225
fn into_val(&self, env: &#path::Env) -> #path::Vec<#path::Val> {
215226
(#(#tuple_index_calls),*).into_val(env)
@@ -219,45 +230,61 @@ fn derive_event_inner<'a>(
219230
// TODO: prefix topics may no be short symbols, handle long symbols too.
220231

221232
// Prepare Data Type.
233+
// TODO: support events with no data.
222234
let data_type_ident = format_ident!("{ident}Data");
223235
let (data_type_params_idents, data_type_params_types): (Vec<_>, Vec<_>) = params
224236
.iter()
225237
.filter(|p| p.location == ParamLocation::Data)
226238
.map(|p| (p.ident, p.type_))
227239
.unzip();
228-
let data_type = match data_format {
229-
ScSpecEventDataFormat::SingleValue => quote! {
230-
pub type #data_type_ident<'a> = &'a #(#data_type_params_types),*;
231-
impl<'a> #data_type_ident<'a> {
232-
pub fn new(#(#data_type_params_idents: &'a #data_type_params_types),*) -> Self {
233-
#(#data_type_params_idents),*
240+
let (data_type, data_type_call) = match data_format {
241+
ScSpecEventDataFormat::SingleValue => (
242+
quote! {
243+
pub type #data_type_ident<'a> = #(#data_type_params_types)*;
244+
},
245+
quote! {
246+
#(self.#data_type_params_idents)*.clone()
247+
},
248+
),
249+
ScSpecEventDataFormat::Vec => (
250+
quote! {
251+
// TODO: Implement optional fields on contracttype.
252+
// TODO: #[#path::contracttype]
253+
pub struct #data_type_ident<'a>(
254+
#(&'a #data_type_params_types,)*
255+
);
256+
impl<'a> #data_type_ident<'a> {
257+
pub fn new(#(#data_type_params_idents: &'a #data_type_params_types),*) -> Self{
258+
#data_type_ident(#(#data_type_params_idents),*)
259+
}
234260
}
235-
}
236-
},
237-
ScSpecEventDataFormat::Vec => quote! {
238-
#[#path::contracttype]
239-
pub struct #data_type_ident<'a>(
240-
#(&'a #data_type_params_types,)*
241-
);
242-
impl<'a> #data_type_ident<'a> {
243-
pub fn new(#(#data_type_params_idents: &'a #data_type_params_types),*) -> Self{
244-
#data_type_ident(#(#data_type_params_idents),*)
261+
},
262+
quote! {
263+
#data_type_ident::<'a>::new(
264+
#(&self.#data_type_params_idents,)*
265+
)
266+
},
267+
),
268+
ScSpecEventDataFormat::Map => (
269+
quote! {
270+
// TODO: #[#path::contracttype]
271+
pub struct #data_type_ident<'a>{
272+
#(#data_type_params_idents: &'a #data_type_params_types,)*
245273
}
246-
}
247-
},
248-
ScSpecEventDataFormat::Map => quote! {
249-
#[#path::contracttype]
250-
pub struct #data_type_ident<'a>{
251-
#(#data_type_params_idents: &'a #data_type_params_types,)*
252-
}
253-
impl<'a> #data_type_ident<'a> {
254-
pub fn new(#(#data_type_params_idents: &'a #data_type_params_types),*) -> Self {
255-
#data_type_ident{
256-
#(#data_type_params_idents,)*
274+
impl<'a> #data_type_ident<'a> {
275+
pub fn new(#(#data_type_params_idents: &'a #data_type_params_types),*) -> Self {
276+
#data_type_ident{
277+
#(#data_type_params_idents,)*
278+
}
257279
}
258280
}
259-
}
260-
},
281+
},
282+
quote! {
283+
#data_type_ident::<'a>::new(
284+
#(&self.#data_type_params_idents,)*
285+
)
286+
},
287+
),
261288
};
262289

263290
// Output.
@@ -275,9 +302,7 @@ fn derive_event_inner<'a>(
275302
)
276303
}
277304
fn data(&'a self) -> Self::Data {
278-
#data_type_ident::<'a>::new(
279-
#(&self.#data_type_params_idents,)*
280-
)
305+
#data_type_call
281306
}
282307
}
283308
};

soroban-sdk/src/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod contract_assert;
1010
mod contract_custom_account_impl;
1111
mod contract_docs;
1212
mod contract_duration;
13+
mod contract_event;
1314
mod contract_fn;
1415
mod contract_invoke;
1516
mod contract_invoke_arg_count;
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
use crate::{
2+
self as soroban_sdk, contract, contractevent, symbol_short, testutils::Events as _, vec, Env,
3+
IntoVal as _, Symbol,
4+
};
5+
6+
#[test]
7+
fn test_defaults() {
8+
let env = Env::default();
9+
10+
#[contract]
11+
pub struct Contract;
12+
let id = env.register(Contract, ());
13+
14+
#[contractevent(data_format = "single-value")]
15+
pub struct MyEvent {
16+
#[topic]
17+
name: Symbol,
18+
value: Symbol,
19+
}
20+
21+
env.as_contract(&id, || {
22+
env.events().publish_event(&MyEvent {
23+
name: symbol_short!("hi"),
24+
value: symbol_short!("hello"),
25+
});
26+
});
27+
28+
assert_eq!(
29+
env.events().all(),
30+
vec![
31+
&env,
32+
(
33+
id,
34+
// Expect these event topics.
35+
(symbol_short!("my_event"), symbol_short!("hi")).into_val(&env),
36+
// Expect this event body.
37+
symbol_short!("hello").into_val(&env)
38+
),
39+
],
40+
);
41+
}
42+
43+
#[test]
44+
fn test_prefix_topics() {
45+
let env = Env::default();
46+
47+
#[contract]
48+
pub struct Contract;
49+
let id = env.register(Contract, ());
50+
51+
#[contractevent(prefix_topics = ["topic1", "topic2"], data_format = "single-value")]
52+
pub struct MyEvent {
53+
#[topic]
54+
name: Symbol,
55+
value: Symbol,
56+
}
57+
58+
env.as_contract(&id, || {
59+
env.events().publish_event(&MyEvent {
60+
name: symbol_short!("hi"),
61+
value: symbol_short!("hello"),
62+
});
63+
});
64+
65+
assert_eq!(
66+
env.events().all(),
67+
vec![
68+
&env,
69+
(
70+
id,
71+
// Expect these event topics.
72+
(
73+
symbol_short!("topic1"),
74+
symbol_short!("topic2"),
75+
symbol_short!("hi")
76+
)
77+
.into_val(&env),
78+
// Expect this event body.
79+
symbol_short!("hello").into_val(&env)
80+
),
81+
],
82+
);
83+
}
84+
85+
#[test]
86+
fn test_data_single_value() {
87+
let env = Env::default();
88+
89+
#[contract]
90+
pub struct Contract;
91+
let id = env.register(Contract, ());
92+
93+
#[contractevent(data_format = "single-value")]
94+
pub struct MyEvent {
95+
#[topic]
96+
name: Symbol,
97+
value: Symbol,
98+
}
99+
100+
env.as_contract(&id, || {
101+
env.events().publish_event(&MyEvent {
102+
name: symbol_short!("hi"),
103+
value: symbol_short!("yo"),
104+
});
105+
});
106+
107+
assert_eq!(
108+
env.events().all(),
109+
vec![
110+
&env,
111+
(
112+
id,
113+
// Expect these event topics.
114+
(symbol_short!("my_event"), symbol_short!("hi")).into_val(&env),
115+
// Expect this event body.
116+
symbol_short!("yo").into_val(&env)
117+
),
118+
],
119+
);
120+
}
121+
122+
#[test]
123+
fn test_data_vec() {
124+
let env = Env::default();
125+
126+
#[contract]
127+
pub struct Contract;
128+
let id = env.register(Contract, ());
129+
130+
// TODO: the trait bound `Val: TryFromVal<Env, MyEventData<'a>>` is not satisfied
131+
#[contractevent(data_format = "vec")]
132+
pub struct MyEvent {
133+
#[topic]
134+
name: Symbol,
135+
value: Symbol,
136+
value2: u32,
137+
}
138+
139+
env.as_contract(&id, || {
140+
env.events().publish_event(&MyEvent {
141+
name: symbol_short!("hi"),
142+
value: symbol_short!("yo"),
143+
value2: 2,
144+
});
145+
});
146+
147+
assert_eq!(
148+
env.events().all(),
149+
vec![
150+
&env,
151+
(
152+
id,
153+
// Expect these event topics.
154+
(symbol_short!("my_event"), symbol_short!("hi")).into_val(&env),
155+
// Expect this event body.
156+
symbol_short!("yo").into_val(&env)
157+
),
158+
],
159+
);
160+
}

0 commit comments

Comments
 (0)