Skip to content

Commit 9a1b75b

Browse files
Fix trait method call resolution in contractimpl (#1729)
### What Use fully qualified syntax (`<Type as Trait>::func()`) in macro-generated WASM exports when `#[contractimpl]` is applied to a trait impl block. ### Why Rust's name resolution prefers inherent methods over trait methods when using `<Type>::func()` syntax. If a contract type has both an inherent method and a trait method with the same name, the generated export incorrectly calls the inherent method instead of the trait method the `#[contractimpl]` block is defined for. (cherry picked from commit e92a393) (cherry picked from commit 347f711)
1 parent f10df3b commit 9a1b75b

File tree

5 files changed

+136
-8
lines changed

5 files changed

+136
-8
lines changed

soroban-sdk-macros/src/derive_fn.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub fn derive_pub_fn(
1717
ident: &Ident,
1818
attrs: &[Attribute],
1919
inputs: &Punctuated<FnArg, Comma>,
20-
trait_ident: Option<&Ident>,
20+
_trait_ident: Option<&Ident>,
2121
client_ident: &str,
2222
) -> Result<TokenStream2, TokenStream2> {
2323
// Collect errors as they are encountered and emit them at the end.
@@ -114,11 +114,6 @@ pub fn derive_pub_fn(
114114
};
115115
let slice_args: Vec<TokenStream2> = (0..wrap_args.len()).map(|n| quote! { args[#n] }).collect();
116116
let arg_count = slice_args.len();
117-
let use_trait = if let Some(t) = trait_ident {
118-
quote! { use super::#t }
119-
} else {
120-
quote! {}
121-
};
122117

123118
// If errors have occurred, render them instead.
124119
if !errors.is_empty() {
@@ -159,7 +154,6 @@ pub fn derive_pub_fn(
159154

160155
#[deprecated(note = #deprecated_note)]
161156
pub fn invoke_raw(env: #crate_path::Env, #(#wrap_args),*) -> #crate_path::Val {
162-
#use_trait;
163157
<_ as #crate_path::IntoVal<#crate_path::Env, #crate_path::Val>>::into_val(
164158
#[allow(deprecated)]
165159
&#call(

soroban-sdk-macros/src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,11 @@ pub fn contractimpl(metadata: TokenStream, input: TokenStream) -> TokenStream {
241241
.iter()
242242
.map(|m| {
243243
let ident = &m.sig.ident;
244-
let call = quote! { <super::#ty>::#ident };
244+
let call = if let Some(t) = trait_ident {
245+
quote! { <super::#ty as #t>::#ident }
246+
} else {
247+
quote! { <super::#ty>::#ident }
248+
};
245249
derive_pub_fn(
246250
crate_path,
247251
&call,

soroban-sdk/src/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ mod contract_udt_enum_option;
2424
mod contract_udt_option;
2525
mod contract_udt_struct;
2626
mod contract_udt_struct_tuple;
27+
mod contractimpl_trait_call_resolution;
2728
mod contractimport;
2829
mod contractimport_with_error;
2930
mod cost_estimate;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use crate as soroban_sdk;
2+
use soroban_sdk::{contract, contractimpl, Env};
3+
4+
// ---------------------------------------------------------------------------
5+
// Test: when #[contractimpl] is applied to `impl Trait for Type`, the
6+
// generated WASM export must call the *trait* associated function, not an
7+
// inherent associated function that happens to share the same name.
8+
//
9+
// Rust's name-resolution rules say that `<Type>::func()` resolves to the
10+
// inherent associated function when one exists, even if a trait with the
11+
// same function is in scope. The only way to force the trait version is
12+
// Universal Function Call Syntax: `<Type as Trait>::func()`.
13+
//
14+
// This test creates a contract where both the inherent and trait versions
15+
// of `value` return a different u32. We then check which value was
16+
// returned to determine which function was called.
17+
// ---------------------------------------------------------------------------
18+
19+
#[contract]
20+
pub struct Contract;
21+
22+
/// Inherent function — returns 1.
23+
impl Contract {
24+
pub fn value(_env: Env) -> u32 {
25+
1
26+
}
27+
}
28+
29+
pub trait ContractTrait {
30+
fn value(env: Env) -> u32;
31+
}
32+
33+
/// Trait implementation — returns 2.
34+
/// The macro-generated WASM export for "value" MUST call this version.
35+
#[contractimpl]
36+
impl ContractTrait for Contract {
37+
fn value(_env: Env) -> u32 {
38+
2
39+
}
40+
}
41+
42+
/// The exported `value` entry point must call the trait function, which
43+
/// returns 2.
44+
#[test]
45+
fn calls_trait_fn() {
46+
let e = Env::default();
47+
let contract_id = e.register(Contract, ());
48+
let client = ContractClient::new(&e, &contract_id);
49+
50+
let result = client.value();
51+
52+
assert_eq!(result, 2);
53+
}
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+
}

0 commit comments

Comments
 (0)