Skip to content

[DOCS] Critical Architectural Ambiguity: Sync WASM Contracts vs. Async Integration Testing in Stylus SDK #3254

Description

@Cripto5588

The Problem
There is a critical conceptual gap in the current Stylus documentation regarding the execution context of Rust code. The documentation fails to distinguish between the On-chain Context (Contract Logic) and the Off-chain Context (Integration Tests and Tooling), leading to silent failures in test suites.

Senior Diagnostic: The Contrast
On-chain (Synchronous WASM): Within the #[entrypoint], execution is strictly synchronous and deterministic. The WASM runtime lacks an executor (like tokio), making async/await impossible.

Off-chain (Asynchronous Testing): When developers write integration tests or deployment scripts using the stylus-sdk, they are interacting with the blockchain via RPC (often through ethers-rs). This environment is asynchronous.

Irrefutable Proof (Code Inconsistency)
Developers following the current guide often fall into this trap where types appear correct but logic never executes:
// ✅ WHAT DOCS SHOW (Correct for on-chain contracts)
#[entrypoint]
fn set_number(&mut self, x: U256) -> Result<(), Vec> {
self.number.set(x); // Sync - no async/await
Ok(())
}

// ❌ WHAT DEVELOPERS TRY (Fails silently in tests)
#[test]
fn test_counter() {
let contract = Counter::new(addr, provider);
let value = contract.number().call(); // Returns a Future, NOT U256
assert_eq!(value, U256::from(42)); // ❌ Compilation error or invalid comparison
}

Real-World Impact
This ambiguity causes significant friction for teams onboarding to Stylus. A lack of clarity on the requirement for an async runtime in tests leads to hours of debugging "silent failures" where assertions are never polled. This represents a measurable productivity loss for protocol developers migrating from EVM-native tools.

Proposed Resolution (MVP)
I propose adding a dedicated section to /stylus/reference/rust-sdk-guide explaining the Sync/Async boundary and providing the standard boilerplate for integration tests:

// ✅ Correct Integration Test (Off-chain)
#[cfg(test)]
mod integration_tests {
use stylus_sdk::prelude::;
use ethers::prelude::
;

#[tokio::test]  // ⚠️ CRITICAL: Tokio runtime required for off-chain calls
async fn test_async_interaction() -> Result<(), Box<dyn std::error::Error>> {
    let provider = Provider::<Http>::try_from("http://localhost:8547")?;
    let client = std::sync::Arc::new(provider);
    let contract = Counter::new(address, client);

    let current = contract.number().call().await?; // Use .await here
    assert!(current >= U256::zero());
    Ok(())
}

}

The Rule of Thumb:

Contract methods: fn (Sync)

Test/Script methods: async fn + #[tokio::test] (Async)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions