Skip to content

Commit c477cc6

Browse files
Support ref fields on contractevent structs (#1499)
### What Add support for reference fields in `contractevent` structs, allowing borrowed values to be used in event fields. ### Why When publishing events that are made up of user-defined-types, this gives the developer the option of not copying the value into the event, instead referencing it wherever it is. This may present some optimisation opportunities over being forced to copy the value into the struct. Nothing about this change requires the use of references, it merely makes it allowed rather than disallowed. Close #1491
1 parent 8bd5d97 commit c477cc6

File tree

9 files changed

+201
-31
lines changed

9 files changed

+201
-31
lines changed

soroban-sdk-macros/src/derive_enum.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ fn map_tuple_variant(
352352
let spec_case = {
353353
let field_types = fields
354354
.iter()
355-
.map(|f| match map_type(&f.ty, false) {
355+
.map(|f| match map_type(&f.ty, false, false) {
356356
Ok(t) => t,
357357
Err(e) => {
358358
errors.push(e);

soroban-sdk-macros/src/derive_event.rs

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
8383
let mut errors = Error::accumulator();
8484

8585
let ident = &input.ident;
86+
let (gen_impl, gen_types, gen_where) = input.generics.split_for_impl();
8687
let path = &args.crate_path;
8788

8889
let prefix_topics = if let Some(prefix_topics) = &args.topics {
@@ -133,7 +134,7 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
133134
}))
134135
.unwrap_or_default();
135136
let type_ = errors
136-
.handle_in(|| Ok(map_type(&field.ty, false)?))
137+
.handle_in(|| Ok(map_type(&field.ty, true, false)?))
137138
.unwrap_or_default();
138139
ScSpecEventParamV0 {
139140
location,
@@ -183,7 +184,7 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
183184
#export_gen
184185
pub static #spec_ident: [u8; #spec_xdr_len] = #ident::spec_xdr();
185186

186-
impl #ident {
187+
impl #gen_impl #ident #gen_types #gen_where {
187188
pub const fn spec_xdr() -> [u8; #spec_xdr_len] {
188189
*#spec_xdr_lit
189190
}
@@ -207,7 +208,7 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
207208
use #path::IntoVal;
208209
(
209210
#(&#prefix_topics_symbols,)*
210-
#(&self.#topic_idents,)*
211+
#({ let v: #path::Val = self.#topic_idents.into_val(env); v },)*
211212
).into_val(env)
212213
};
213214

@@ -227,24 +228,22 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
227228
.map(|i| i.to_string())
228229
.collect::<Vec<_>>();
229230
let data_to_val = match args.data_format {
230-
DataFormat::SingleValue if data_params_count == 0 => {
231-
quote! {
232-
#path::Val::VOID.to_val()
233-
}
234-
}
235-
DataFormat::SingleValue => {
236-
quote! {
237-
use #path::IntoVal;
238-
#(self.#data_idents.into_val(env))*
239-
}
240-
}
231+
DataFormat::SingleValue if data_params_count == 0 => quote! {
232+
#path::Val::VOID.to_val()
233+
},
234+
DataFormat::SingleValue => quote! {
235+
use #path::IntoVal;
236+
#(self.#data_idents.into_val(env))*
237+
},
241238
DataFormat::Vec if data_params_count == 0 => quote! {
242239
use #path::IntoVal;
243240
#path::Vec::<#path::Val>::new(env).into_val(env)
244241
},
245242
DataFormat::Vec => quote! {
246243
use #path::IntoVal;
247-
(#(&self.#data_idents,)*).into_val(env)
244+
(
245+
#({ let v: #path::Val = self.#data_idents.into_val(env); v },)*
246+
).into_val(env)
248247
},
249248
DataFormat::Map => quote! {
250249
use #path::{EnvBase,IntoVal,unwrap::UnwrapInfallible};
@@ -260,7 +259,7 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
260259
let output = quote! {
261260
#spec_gen
262261

263-
impl #path::Event for #ident {
262+
impl #gen_impl #path::Event for #ident #gen_types #gen_where {
264263
fn topics(&self, env: &#path::Env) -> #path::Vec<#path::Val> {
265264
#topics_to_vec_val
266265
}
@@ -269,7 +268,7 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
269268
}
270269
}
271270

272-
impl #ident {
271+
impl #gen_impl #ident #gen_types #gen_where {
273272
fn publish(&self, env: &#path::Env) {
274273
<_ as #path::Event>::publish(self, env);
275274
}

soroban-sdk-macros/src/derive_fn.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ pub fn derive_pub_fn(
6161
let allow_hash = ident == "__check_auth" && i == 0;
6262

6363
// Error if the type of the fn is not mappable.
64-
if let Err(e) = map_type(&pat_ty.ty, allow_hash) {
64+
if let Err(e) = map_type(&pat_ty.ty, false, allow_hash) {
6565
errors.push(e);
6666
}
6767

soroban-sdk-macros/src/derive_spec_fn.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ pub fn derive_fn_spec(
6868
// signature_payload of type Bytes (32 size), to be a Hash.
6969
let allow_hash = ident == "__check_auth" && i == 0;
7070

71-
match map_type(&pat_type.ty, allow_hash) {
71+
match map_type(&pat_type.ty, false, allow_hash) {
7272
Ok(type_) => {
7373
let name = name.try_into().unwrap_or_else(|_| {
7474
const MAX: u32 = 30;
@@ -107,7 +107,7 @@ pub fn derive_fn_spec(
107107

108108
// Prepare the output.
109109
let spec_result = match output {
110-
ReturnType::Type(_, ty) => vec![match map_type(ty, true) {
110+
ReturnType::Type(_, ty) => vec![match map_type(ty, false, true) {
111111
Ok(spec) => spec,
112112
Err(e) => {
113113
errors.push(e);

soroban-sdk-macros/src/derive_struct.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub fn derive_type_struct(
4242
errors.push(Error::new(field_ident.span(), format!("struct field name is too long: {}, max is {MAX}", field_name.len())));
4343
StringM::<MAX>::default()
4444
}),
45-
type_: match map_type(&field.ty, false) {
45+
type_: match map_type(&field.ty,false, false) {
4646
Ok(t) => t,
4747
Err(e) => {
4848
errors.push(e);

soroban-sdk-macros/src/derive_struct_tuple.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ pub fn derive_type_struct_tuple(
3636
let field_spec = ScSpecUdtStructFieldV0 {
3737
doc: docs_from_attrs(&field.attrs),
3838
name: field_name.try_into().unwrap_or_else(|_| StringM::default()),
39-
type_: match map_type(&field.ty, false) {
39+
type_: match map_type(&field.ty, false, false) {
4040
Ok(t) => t,
4141
Err(e) => {
4242
errors.push(e);

soroban-sdk-macros/src/map_type.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use stellar_xdr::{
33
ScSpecTypeBytesN, ScSpecTypeDef, ScSpecTypeMap, ScSpecTypeOption, ScSpecTypeResult,
44
ScSpecTypeTuple, ScSpecTypeUdt, ScSpecTypeVec,
55
};
6+
use syn::TypeReference;
67
use syn::{
78
spanned::Spanned, Error, Expr, ExprLit, GenericArgument, Lit, Path, PathArguments, PathSegment,
89
Type, TypePath, TypeTuple,
@@ -16,8 +17,9 @@ pub const G1_SERIALIZED_SIZE: u32 = FP_SERIALIZED_SIZE * 2;
1617
pub const G2_SERIALIZED_SIZE: u32 = FP2_SERIALIZED_SIZE * 2;
1718

1819
#[allow(clippy::too_many_lines)]
19-
pub fn map_type(t: &Type, allow_hash: bool) -> Result<ScSpecTypeDef, Error> {
20+
pub fn map_type(t: &Type, allow_ref: bool, allow_hash: bool) -> Result<ScSpecTypeDef, Error> {
2021
match t {
22+
Type::Reference(TypeReference { elem, .. }) => map_type(elem, allow_ref, allow_hash),
2123
Type::Path(TypePath {
2224
qself: None,
2325
path: Path { segments, .. },
@@ -100,8 +102,8 @@ pub fn map_type(t: &Type, allow_hash: bool) -> Result<ScSpecTypeDef, Error> {
100102
))?,
101103
};
102104
Ok(ScSpecTypeDef::Result(Box::new(ScSpecTypeResult {
103-
ok_type: Box::new(map_type(ok, false)?),
104-
error_type: Box::new(map_type(err, false)?),
105+
ok_type: Box::new(map_type(ok, allow_ref, false)?),
106+
error_type: Box::new(map_type(err, allow_ref, false)?),
105107
})))
106108
}
107109
"Option" => {
@@ -113,7 +115,7 @@ pub fn map_type(t: &Type, allow_hash: bool) -> Result<ScSpecTypeDef, Error> {
113115
))?,
114116
};
115117
Ok(ScSpecTypeDef::Option(Box::new(ScSpecTypeOption {
116-
value_type: Box::new(map_type(t, false)?),
118+
value_type: Box::new(map_type(t, allow_ref, false)?),
117119
})))
118120
}
119121
"Vec" => {
@@ -125,7 +127,7 @@ pub fn map_type(t: &Type, allow_hash: bool) -> Result<ScSpecTypeDef, Error> {
125127
))?,
126128
};
127129
Ok(ScSpecTypeDef::Vec(Box::new(ScSpecTypeVec {
128-
element_type: Box::new(map_type(t, false)?),
130+
element_type: Box::new(map_type(t, allow_ref, false)?),
129131
})))
130132
}
131133
"Map" => {
@@ -137,8 +139,8 @@ pub fn map_type(t: &Type, allow_hash: bool) -> Result<ScSpecTypeDef, Error> {
137139
))?,
138140
};
139141
Ok(ScSpecTypeDef::Map(Box::new(ScSpecTypeMap {
140-
key_type: Box::new(map_type(k, false)?),
141-
value_type: Box::new(map_type(v, false)?),
142+
key_type: Box::new(map_type(k, allow_ref, false)?),
143+
value_type: Box::new(map_type(v, allow_ref, false)?),
142144
})))
143145
}
144146
"BytesN" => {
@@ -179,7 +181,7 @@ pub fn map_type(t: &Type, allow_hash: bool) -> Result<ScSpecTypeDef, Error> {
179181
}
180182
Type::Tuple(TypeTuple { elems, .. }) => {
181183
let map_type_reject_hash =
182-
|t: &Type| -> Result<ScSpecTypeDef, Error> { map_type(t, false) };
184+
|t: &Type| -> Result<ScSpecTypeDef, Error> { map_type(t, allow_ref, false) };
183185
Ok(ScSpecTypeDef::Tuple(Box::new(ScSpecTypeTuple {
184186
value_types: elems
185187
.iter()

soroban-sdk/src/tests/contract_event.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,3 +491,54 @@ fn test_data_map_no_data() {
491491
],
492492
);
493493
}
494+
495+
#[test]
496+
fn test_ref_fields() {
497+
let env = Env::default();
498+
499+
#[contract]
500+
pub struct Contract;
501+
let id = env.register(Contract, ());
502+
503+
#[contractevent]
504+
pub struct MyEvent<'a> {
505+
#[topic]
506+
name: &'a Symbol,
507+
value: &'a Symbol,
508+
value2: u32,
509+
}
510+
511+
env.as_contract(&id, || {
512+
MyEvent {
513+
name: &symbol_short!("hi"),
514+
value: &symbol_short!("yo"),
515+
value2: 2,
516+
}
517+
.publish(&env);
518+
});
519+
520+
assert_eq!(
521+
env.events().all(),
522+
vec![
523+
&env,
524+
(
525+
id,
526+
// Expect these event topics.
527+
(symbol_short!("my_event"), symbol_short!("hi")).into_val(&env),
528+
// Expect this event body.
529+
map![
530+
&env,
531+
(
532+
symbol_short!("value"),
533+
<_ as IntoVal<Env, Val>>::into_val(&symbol_short!("yo"), &env),
534+
),
535+
(
536+
symbol_short!("value2"),
537+
<_ as IntoVal<Env, Val>>::into_val(&2u32, &env),
538+
),
539+
]
540+
.into_val(&env)
541+
),
542+
],
543+
);
544+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
{
2+
"generators": {
3+
"address": 1,
4+
"nonce": 0,
5+
"mux_id": 0
6+
},
7+
"auth": [
8+
[],
9+
[]
10+
],
11+
"ledger": {
12+
"protocol_version": 23,
13+
"sequence_number": 0,
14+
"timestamp": 0,
15+
"network_id": "0000000000000000000000000000000000000000000000000000000000000000",
16+
"base_reserve": 0,
17+
"min_persistent_entry_ttl": 4096,
18+
"min_temp_entry_ttl": 16,
19+
"max_entry_ttl": 6312000,
20+
"ledger_entries": [
21+
[
22+
{
23+
"contract_data": {
24+
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
25+
"key": "ledger_key_contract_instance",
26+
"durability": "persistent"
27+
}
28+
},
29+
[
30+
{
31+
"last_modified_ledger_seq": 0,
32+
"data": {
33+
"contract_data": {
34+
"ext": "v0",
35+
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
36+
"key": "ledger_key_contract_instance",
37+
"durability": "persistent",
38+
"val": {
39+
"contract_instance": {
40+
"executable": {
41+
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
42+
},
43+
"storage": null
44+
}
45+
}
46+
}
47+
},
48+
"ext": "v0"
49+
},
50+
4095
51+
]
52+
],
53+
[
54+
{
55+
"contract_code": {
56+
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
57+
}
58+
},
59+
[
60+
{
61+
"last_modified_ledger_seq": 0,
62+
"data": {
63+
"contract_code": {
64+
"ext": "v0",
65+
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
66+
"code": ""
67+
}
68+
},
69+
"ext": "v0"
70+
},
71+
4095
72+
]
73+
]
74+
]
75+
},
76+
"events": [
77+
{
78+
"event": {
79+
"ext": "v0",
80+
"contract_id": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
81+
"type_": "contract",
82+
"body": {
83+
"v0": {
84+
"topics": [
85+
{
86+
"symbol": "my_event"
87+
},
88+
{
89+
"symbol": "hi"
90+
}
91+
],
92+
"data": {
93+
"map": [
94+
{
95+
"key": {
96+
"symbol": "value"
97+
},
98+
"val": {
99+
"symbol": "yo"
100+
}
101+
},
102+
{
103+
"key": {
104+
"symbol": "value2"
105+
},
106+
"val": {
107+
"u32": 2
108+
}
109+
}
110+
]
111+
}
112+
}
113+
}
114+
},
115+
"failed_call": false
116+
}
117+
]
118+
}

0 commit comments

Comments
 (0)