Skip to content

Commit bb38320

Browse files
Allow &Env in contract fns (#1210)
### What Allow the first param of contract functions to be &Env or Env, instead of requiring that if present it is Env. ### Why To reduce repetitive clones in contract functions. Requiring as input to functions an owned copy of Env is unnecessary, and it has a run on affect that if someone wants to use a contract function also as an internal function called by their code they need to clone the Env each time it is called. This has zero performance implications because the Env is, or should be, erased from most places at build time, but it adds noise to the application. Looking back we should have made the Env a ref everywhere, probably, but in lieu of doing that which would be a breaking change we can make it allowed so both are allowed. This will also remove a compile fail case for devs. Before if they used an &Env they would see an error. Now they won't. This change is not a breaking change.
1 parent eb9f7e2 commit bb38320

6 files changed

Lines changed: 211 additions & 10 deletions

File tree

soroban-sdk-macros/src/derive_client.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use proc_macro2::TokenStream;
22
use quote::{format_ident, quote};
3-
use syn::{spanned::Spanned, Error, FnArg, Path, Type, TypePath};
3+
use syn::{spanned::Spanned, Error, FnArg, Path, Type, TypePath, TypeReference};
44

55
use crate::syn_ext;
66

@@ -141,14 +141,17 @@ pub fn derive_client_impl(crate_path: &Path, name: &str, fns: &[syn_ext::Fn]) ->
141141
// Check for the Env argument.
142142
let env_input = f.inputs.first().and_then(|a| match a {
143143
FnArg::Typed(pat_type) => {
144-
let ty = &*pat_type.ty;
144+
let mut ty = &*pat_type.ty;
145+
if let Type::Reference(TypeReference { elem, .. }) = ty {
146+
ty = elem;
147+
}
145148
if let Type::Path(TypePath {
146149
path: syn::Path { segments, .. },
147150
..
148151
}) = ty
149152
{
150153
if segments.last().map_or(false, |s| s.ident == "Env") {
151-
Some(a)
154+
Some(())
152155
} else {
153156
None
154157
}

soroban-sdk-macros/src/derive_fn.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use syn::{
66
punctuated::Punctuated,
77
spanned::Spanned,
88
token::{Colon, Comma},
9-
Attribute, Error, FnArg, Ident, Pat, PatIdent, PatType, Path, Type, TypePath,
9+
Attribute, Error, FnArg, Ident, Pat, PatIdent, PatType, Path, Type, TypePath, TypeReference,
1010
};
1111

1212
#[allow(clippy::too_many_arguments)]
@@ -25,14 +25,19 @@ pub fn derive_fn(
2525
// Prepare the env input.
2626
let env_input = inputs.first().and_then(|a| match a {
2727
FnArg::Typed(pat_type) => {
28-
let ty = &*pat_type.ty;
28+
let mut is_ref = false;
29+
let mut ty = &*pat_type.ty;
30+
if let Type::Reference(TypeReference { elem, .. }) = ty {
31+
is_ref = true;
32+
ty = elem;
33+
}
2934
if let Type::Path(TypePath {
3035
path: syn::Path { segments, .. },
3136
..
3237
}) = ty
3338
{
3439
if segments.last().map_or(false, |s| s.ident == "Env") {
35-
Some(a)
40+
Some(is_ref)
3641
} else {
3742
None
3843
}
@@ -87,8 +92,12 @@ pub fn derive_fn(
8792
"use `{}::new(&env, &contract_id).{}` instead",
8893
client_ident, &ident
8994
);
90-
let env_call = if env_input.is_some() {
91-
quote! { env.clone(), }
95+
let env_call = if let Some(is_ref) = env_input {
96+
if is_ref {
97+
quote! { &env, }
98+
} else {
99+
quote! { env.clone(), }
100+
}
92101
} else {
93102
quote! {}
94103
};

soroban-sdk-macros/src/derive_spec_fn.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use stellar_xdr::{
55
ScSpecEntry, ScSpecFunctionInputV0, ScSpecFunctionV0, ScSpecTypeDef, ScSymbol, StringM, VecM,
66
WriteXdr, SCSYMBOL_LIMIT,
77
};
8+
use syn::TypeReference;
89
use syn::{
910
punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Error, FnArg, Ident, Pat,
1011
ReturnType, Type, TypePath,
@@ -27,14 +28,17 @@ pub fn derive_fn_spec(
2728
// Prepare the env input.
2829
let env_input = inputs.first().and_then(|a| match a {
2930
FnArg::Typed(pat_type) => {
30-
let ty = &*pat_type.ty;
31+
let mut ty = &*pat_type.ty;
32+
if let Type::Reference(TypeReference { elem, .. }) = ty {
33+
ty = elem;
34+
}
3135
if let Type::Path(TypePath {
3236
path: syn::Path { segments, .. },
3337
..
3438
}) = ty
3539
{
3640
if segments.last().map_or(false, |s| s.ident == "Env") {
37-
Some(a)
41+
Some(())
3842
} else {
3943
None
4044
}

soroban-sdk/src/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod contract_add_i32;
77
mod contract_assert;
88
mod contract_docs;
99
mod contract_duration;
10+
mod contract_fn;
1011
mod contract_invoke;
1112
mod contract_overlapping_type_fn_names;
1213
mod contract_snapshot;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use crate as soroban_sdk;
2+
use soroban_sdk::{contract, contractimpl, Env};
3+
use stellar_xdr::curr as stellar_xdr;
4+
use stellar_xdr::{
5+
Limits, ReadXdr, ScSpecEntry, ScSpecFunctionInputV0, ScSpecFunctionV0, ScSpecTypeDef,
6+
};
7+
8+
#[contract]
9+
pub struct Contract;
10+
11+
#[contractimpl]
12+
impl Contract {
13+
pub fn add(_e: &Env, a: i32, b: i32) -> i32 {
14+
a + b
15+
}
16+
}
17+
18+
#[test]
19+
fn test_functional() {
20+
let e = Env::default();
21+
let contract_id = e.register_contract(None, Contract);
22+
23+
let a = 10i32;
24+
let b = 12i32;
25+
let c = ContractClient::new(&e, &contract_id).add(&a, &b);
26+
assert_eq!(c, 22);
27+
}
28+
29+
#[test]
30+
fn test_spec() {
31+
let entries = ScSpecEntry::from_xdr(__SPEC_XDR_FN_ADD, Limits::none()).unwrap();
32+
let expect = ScSpecEntry::FunctionV0(ScSpecFunctionV0 {
33+
doc: "".try_into().unwrap(),
34+
name: "add".try_into().unwrap(),
35+
inputs: vec![
36+
ScSpecFunctionInputV0 {
37+
doc: "".try_into().unwrap(),
38+
name: "a".try_into().unwrap(),
39+
type_: ScSpecTypeDef::I32,
40+
},
41+
ScSpecFunctionInputV0 {
42+
doc: "".try_into().unwrap(),
43+
name: "b".try_into().unwrap(),
44+
type_: ScSpecTypeDef::I32,
45+
},
46+
]
47+
.try_into()
48+
.unwrap(),
49+
outputs: vec![ScSpecTypeDef::I32].try_into().unwrap(),
50+
});
51+
assert_eq!(entries, expect);
52+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
{
2+
"generators": {
3+
"address": 1,
4+
"nonce": 0
5+
},
6+
"auth": [
7+
[]
8+
],
9+
"ledger": {
10+
"protocol_version": 20,
11+
"sequence_number": 0,
12+
"timestamp": 0,
13+
"network_id": "0000000000000000000000000000000000000000000000000000000000000000",
14+
"base_reserve": 0,
15+
"min_persistent_entry_ttl": 4096,
16+
"min_temp_entry_ttl": 16,
17+
"max_entry_ttl": 6312000,
18+
"ledger_entries": [
19+
[
20+
{
21+
"contract_data": {
22+
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
23+
"key": "ledger_key_contract_instance",
24+
"durability": "persistent"
25+
}
26+
},
27+
[
28+
{
29+
"last_modified_ledger_seq": 0,
30+
"data": {
31+
"contract_data": {
32+
"ext": "v0",
33+
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
34+
"key": "ledger_key_contract_instance",
35+
"durability": "persistent",
36+
"val": {
37+
"contract_instance": {
38+
"executable": {
39+
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
40+
},
41+
"storage": null
42+
}
43+
}
44+
}
45+
},
46+
"ext": "v0"
47+
},
48+
4095
49+
]
50+
],
51+
[
52+
{
53+
"contract_code": {
54+
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
55+
}
56+
},
57+
[
58+
{
59+
"last_modified_ledger_seq": 0,
60+
"data": {
61+
"contract_code": {
62+
"ext": "v0",
63+
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
64+
"code": ""
65+
}
66+
},
67+
"ext": "v0"
68+
},
69+
4095
70+
]
71+
]
72+
]
73+
},
74+
"events": [
75+
{
76+
"event": {
77+
"ext": "v0",
78+
"contract_id": null,
79+
"type_": "diagnostic",
80+
"body": {
81+
"v0": {
82+
"topics": [
83+
{
84+
"symbol": "fn_call"
85+
},
86+
{
87+
"bytes": "0000000000000000000000000000000000000000000000000000000000000001"
88+
},
89+
{
90+
"symbol": "add"
91+
}
92+
],
93+
"data": {
94+
"vec": [
95+
{
96+
"i32": 10
97+
},
98+
{
99+
"i32": 12
100+
}
101+
]
102+
}
103+
}
104+
}
105+
},
106+
"failed_call": false
107+
},
108+
{
109+
"event": {
110+
"ext": "v0",
111+
"contract_id": "0000000000000000000000000000000000000000000000000000000000000001",
112+
"type_": "diagnostic",
113+
"body": {
114+
"v0": {
115+
"topics": [
116+
{
117+
"symbol": "fn_return"
118+
},
119+
{
120+
"symbol": "add"
121+
}
122+
],
123+
"data": {
124+
"i32": 22
125+
}
126+
}
127+
}
128+
},
129+
"failed_call": false
130+
}
131+
]
132+
}

0 commit comments

Comments
 (0)