# The rs-soroban-sdk #[contractimpl] macro calls inherent function instead of trait function when names collide

rs-soroban-sdk #[contractimpl] macro generated MyContract::value() calls for trait impls, so a same-named inherent function was exported instead, bypassing trait auth checks.

- Payout: Research
- Severity: high
- Program: rs-soroban-sdk
- Found by: nan-zellic, dmkozh
- Disclosed: 2026-02-17
- CVE: CVE-2026-26267
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-4chv-4c6w-w254
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-4chv-4c6w-w254
- Tags: area:web3 domain:web3 impact:integrity kind:research platform:ghsa sev:high tech:rust tech:solidity technique:source-review vuln:access-control vuln:auth-bypass

**Target:** #[contractimpl] proc macro / Wasm entry point generation
**Root cause:** The macro emitted `<Type>::func()` instead of `<Type as Trait>::func()` when wiring trait implementations, so Rust name resolution preferred an identically named inherent function.
**Steps:**
1. Define `#[contract] pub struct Contract;` with an inherent `impl Contract { pub fn value() -> u32 { 1 } }` (no #[contractimpl]).
2. Define `pub trait Trait { fn value(env: Env) -> u32; }` and `#[contractimpl] impl Trait for MyContract { fn value() -> u32 { 2 } }`.
3. Compile with soroban-sdk-macros 25.0.0-25.1.0; the generated Wasm export calls the inherent function returning 1 instead of the trait function returning 2.
4. Invoke the contract entry point and observe the wrong function executes, skipping any authorization checks present only in the trait impl.
**How found:** Not stated; advisory only documents the macro's generated call form and the Rust resolution rule that makes inherent functions win over trait functions.
**Exploitation:** Any caller of the contract's public entry point triggers the inherent function rather than the trait implementation; if the trait version held caller-authorization checks absent from the inherent one, those checks are silently bypassed.
**Insight:** In proc-macro codegen, always emit fully-qualified `<Type as Trait>::func()` paths; unqualified `<Type>::func()` silently rebinds to inherent items and can drop security checks in generated entry points.

## Proof of concept (copied exactly from the original report)

**Colliding inherent vs trait function example**

```
#[contract]
pub struct Contract;

impl Contract {
    /// Inherent function — returns 1.
    /// Bug: The macro-generated WASM export is wired up to call this function.
    pub fn value() -> u32 {
        1
    }
}

pub trait Trait {
    fn value(env: Env) -> u32;
}

#[contractimpl]
impl Trait for MyContract {
    /// Trait implementation — returns 2.
    /// Fix: The macro-generated WASM export should call this function.
    fn value() -> u32 {
        2
    }
}
```

