Skip to content

Commit 5ad52f7

Browse files
Fix bug in contractimpl code generation for impl traits using associated types (#1401)
### What Allow associated types in impl of traits, specifically the CustomAccountInterface. ### Why The use of associated types was allowed until v22 of the SDK, where an assumption in the code generation `contractimpl` macro is causing ambiguous code to be generated when associated types are in use. Close #1400
1 parent 13f8b01 commit 5ad52f7

7 files changed

Lines changed: 201 additions & 30 deletions

File tree

soroban-sdk-macros/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ soroban-spec = { workspace = true }
2323
soroban-spec-rust = { workspace = true }
2424
soroban-env-common = { workspace = true }
2525
stellar-xdr = { workspace = true, features = ["curr", "std"] }
26-
syn = {version="2.0",features=["full"]}
26+
syn = {version="2.0.77",features=["full"]}
2727
quote = "1.0"
2828
proc-macro2 = "1.0"
2929
itertools = "0.10.5"

soroban-sdk-macros/src/syn_ext.rs

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use proc_macro2::TokenStream;
2-
use quote::{quote, ToTokens};
2+
use quote::{format_ident, quote, ToTokens};
3+
use std::collections::HashMap;
34
use syn::{
45
parse::{Parse, ParseStream},
56
punctuated::Punctuated,
@@ -133,9 +134,12 @@ impl Parse for HasFnsItem {
133134
_ = input.parse::<Token![pub]>();
134135
let lookahead = input.lookahead1();
135136
if lookahead.peek(Token![trait]) {
136-
input.parse().map(HasFnsItem::Trait)
137+
let t = input.parse()?;
138+
Ok(HasFnsItem::Trait(t))
137139
} else if lookahead.peek(Token![impl]) {
138-
input.parse().map(HasFnsItem::Impl)
140+
let mut imp = input.parse()?;
141+
flatten_associated_items_in_impl_fns(&mut imp);
142+
Ok(HasFnsItem::Impl(imp))
139143
} else {
140144
Err(lookahead.error())
141145
}
@@ -209,3 +213,45 @@ fn unpack_result(typ: &Type) -> Option<(Type, Type)> {
209213
_ => None,
210214
}
211215
}
216+
217+
fn flatten_associated_items_in_impl_fns(imp: &mut ItemImpl) {
218+
// TODO: Flatten associated consts used in functions.
219+
// Flatten associated types used in functions.
220+
let associated_types = imp
221+
.items
222+
.iter()
223+
.filter_map(|item| match item {
224+
ImplItem::Type(i) => Some((i.ident.clone(), i.ty.clone())),
225+
_ => None,
226+
})
227+
.collect::<HashMap<_, _>>();
228+
let fn_input_types = imp
229+
.items
230+
.iter_mut()
231+
.filter_map(|item| match item {
232+
ImplItem::Fn(f) => Some(f.sig.inputs.iter_mut().filter_map(|input| match input {
233+
FnArg::Typed(t) => Some(&mut t.ty),
234+
_ => None,
235+
})),
236+
_ => None,
237+
})
238+
.flatten();
239+
for t in fn_input_types {
240+
if let Type::Path(TypePath { qself: None, path }) = t.as_mut() {
241+
let segments = &path.segments;
242+
if segments.len() == 2
243+
&& segments.first() == Some(&PathSegment::from(format_ident!("Self")))
244+
{
245+
if let Some(PathSegment {
246+
arguments: PathArguments::None,
247+
ident,
248+
}) = segments.get(1)
249+
{
250+
if let Some(resolved_ty) = associated_types.get(ident) {
251+
*t.as_mut() = resolved_ty.clone();
252+
}
253+
}
254+
}
255+
}
256+
}
257+
}

soroban-sdk/src/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod bytes_alloc_vec;
77
mod bytes_buffer;
88
mod contract_add_i32;
99
mod contract_assert;
10+
mod contract_custom_account_impl;
1011
mod contract_docs;
1112
mod contract_duration;
1213
mod contract_fn;
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use crate::{self as soroban_sdk, BytesN, IntoVal};
2+
use soroban_sdk::{
3+
auth::{Context, CustomAccountInterface},
4+
contract, contracterror, contractimpl,
5+
crypto::Hash,
6+
Env, Vec,
7+
};
8+
9+
#[contract]
10+
pub struct Contract;
11+
12+
#[contracterror]
13+
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
14+
pub enum Error {
15+
Fail = 1,
16+
}
17+
18+
#[contractimpl]
19+
impl CustomAccountInterface for Contract {
20+
type Signature = ();
21+
type Error = Error;
22+
23+
/// Check that the signatures and auth contexts are valid.
24+
fn __check_auth(
25+
_env: Env,
26+
_signature_payload: Hash<32>,
27+
_signatures: Self::Signature,
28+
_auth_contexts: Vec<Context>,
29+
) -> Result<(), Self::Error> {
30+
Ok(())
31+
}
32+
}
33+
34+
#[test]
35+
fn test_functional() {
36+
let e = Env::default();
37+
let contract_id = e.register(Contract, ());
38+
let payload = BytesN::from_array(&e, &[0; 32]);
39+
let signature = ();
40+
let auth_context = Vec::new(&e);
41+
let result = e.try_invoke_contract_check_auth::<Error>(
42+
&contract_id,
43+
&payload,
44+
signature.into_val(&e),
45+
&auth_context,
46+
);
47+
assert_eq!(result, Ok(()));
48+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
{
2+
"generators": {
3+
"address": 1,
4+
"nonce": 0
5+
},
6+
"auth": [
7+
[],
8+
[]
9+
],
10+
"ledger": {
11+
"protocol_version": 22,
12+
"sequence_number": 0,
13+
"timestamp": 0,
14+
"network_id": "0000000000000000000000000000000000000000000000000000000000000000",
15+
"base_reserve": 0,
16+
"min_persistent_entry_ttl": 4096,
17+
"min_temp_entry_ttl": 16,
18+
"max_entry_ttl": 6312000,
19+
"ledger_entries": [
20+
[
21+
{
22+
"contract_data": {
23+
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
24+
"key": "ledger_key_contract_instance",
25+
"durability": "persistent"
26+
}
27+
},
28+
[
29+
{
30+
"last_modified_ledger_seq": 0,
31+
"data": {
32+
"contract_data": {
33+
"ext": "v0",
34+
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
35+
"key": "ledger_key_contract_instance",
36+
"durability": "persistent",
37+
"val": {
38+
"contract_instance": {
39+
"executable": {
40+
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
41+
},
42+
"storage": null
43+
}
44+
}
45+
}
46+
},
47+
"ext": "v0"
48+
},
49+
4095
50+
]
51+
],
52+
[
53+
{
54+
"contract_code": {
55+
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
56+
}
57+
},
58+
[
59+
{
60+
"last_modified_ledger_seq": 0,
61+
"data": {
62+
"contract_code": {
63+
"ext": "v0",
64+
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
65+
"code": ""
66+
}
67+
},
68+
"ext": "v0"
69+
},
70+
4095
71+
]
72+
]
73+
]
74+
},
75+
"events": []
76+
}

tests/account/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ impl CustomAccountInterface for Contract {
2121
fn __check_auth(
2222
_env: Env,
2323
_signature_payload: Hash<32>,
24-
_signatures: (),
24+
_signatures: Self::Signature,
2525
_auth_contexts: Vec<Context>,
2626
) -> Result<(), Error> {
2727
Ok(())

0 commit comments

Comments
 (0)