Common issues and solutions
This guide covers frequently encountered issues in Stylus development with step-by-step solutions.
Installation and setup
cargo stylus not found
Problem: Running cargo stylus returns "command not found."
Solution:
# Install cargo-stylus
cargo install --force cargo-stylus
# Verify installation
cargo stylus --version
# If still not found, check your PATH includes ~/.cargo/bin
echo $PATH | grep cargo
Alternative: Add cargo bin to your PATH:
# Add to ~/.bashrc or ~/.zshrc
export PATH="$HOME/.cargo/bin:$PATH"
# Reload shell configuration
source ~/.bashrc # or source ~/.zshrc
WASM target not installed
Problem: Build fails with "can't find crate for std" or "target 'wasm32-unknown-unknown' not found."
Solution:
# Add WASM target for your Rust version
rustup target add wasm32-unknown-unknown
# If using specific toolchain
rustup target add wasm32-unknown-unknown --toolchain 1.81
Verify:
rustup target list | grep wasm32
# Should show: wasm32-unknown-unknown (installed)
Docker not running
Problem: cargo stylus check fails with "Cannot connect to Docker daemon."
Solution:
- Start Docker Desktop
- Verify Docker is running:
docker ps
- If still failing, restart Docker:
# macOS/Linux
sudo systemctl restart docker
# Or restart Docker Desktop application
Build errors
Contract exceeds size limit
Problem: "Program activation failed: program too large."
Solution:
- Optimize build settings in
Cargo.toml:
[profile.release]
opt-level = "z" # Optimize for size
lto = true # Link-time optimization
codegen-units = 1 # Better optimization
strip = true # Remove debug symbols
panic = "abort" # Smaller panic handling
- Use wasm-opt:
cargo stylus check --wasm-opt
- Remove unused dependencies:
# Remove unnecessary features
stylus-sdk = { version = "0.8", default-features = false }
- Check binary size:
ls -lh target/wasm32-unknown-unknown/release/*.wasm
See Optimizing binaries guide for more strategies.
Linker errors
Problem: Build fails with linker errors or "undefined reference" messages.
Solution:
# Clean build artifacts
cargo clean
# Rebuild
cargo build --target wasm32-unknown-unknown --release
# If still failing, update Rust
rustup update
Dependency compilation failures
Problem: Dependencies fail to compile for WASM target.
Solution:
-
Check dependency compatibility: Not all crates support WASM. Look for:
no_stdsupport- WASM-compatible versions
- Alternatives designed for blockchain
-
Disable incompatible features:
[dependencies]
# Disable std-only features
serde = { version = "1.0", default-features = false, features = ["derive"] }
- Use Stylus-compatible alternatives: See recommended libraries.
Deployment errors
Insufficient funds
Problem: "Insufficient funds for gas * price + value."
Solution:
# Check your balance
cast balance <YOUR_ADDRESS> --rpc-url <RPC_URL>
# Fund your account (testnet)
# Visit faucet: https://faucet.quicknode.com/arbitrum/sepolia
Activation failed
Problem: "Program activation failed" after deployment.
Solution:
- Check contract size:
cargo stylus check
- Verify WASM validity:
# Ensure binary is valid WASM
wasm-validate target/wasm32-unknown-unknown/release/*.wasm
- Check gas limits:
# Increase gas limit for activation
cargo stylus deploy \
--endpoint=<RPC_URL> \
--private-key=<KEY> \
--gas-limit=10000000
See Activation concepts for more details.
Transaction reverted
Problem: Deployment transaction reverts without clear error.
Solution:
- Enable verbose logging:
RUST_LOG=debug cargo stylus deploy \
--endpoint=<RPC_URL> \
--private-key=<KEY>
- Check network status:
# Verify RPC is responding
cast block-number --rpc-url <RPC_URL>
- Use replay for debugging:
cargo stylus replay \
--endpoint=<RPC_URL> \
<TRANSACTION_HASH>
Runtime errors
Panic in contract code
Problem: Contract panics during execution.
Solution:
- Use
Resultinstead ofpanic!:
// ❌ Bad: Panics
pub fn divide(&self, a: U256, b: U256) -> U256 {
if b.is_zero() {
panic!("Division by zero");
}
a / b
}
// ✅ Good: Returns error
pub fn divide(&self, a: U256, b: U256) -> Result<U256, Vec<u8>> {
ensure!(!b.is_zero(), "Division by zero");
Ok(a / b)
}
- Enable backtrace for debugging:
RUST_BACKTRACE=1 cargo test
Storage slot conflicts
Problem: Storage values overwriting each other unexpectedly.
Solution:
- Use explicit storage layout:
sol_storage! {
pub struct MyContract {
#[borrow] // Use borrow for nested structs
StorageMap<Address, UserData> users;
StorageU256 total_supply;
}
}
-
Avoid manual slot manipulation unless necessary.
-
Check for storage collisions in upgradeable contracts.
Out of gas
Problem: Transactions fail with "out of gas" error.
Solution:
- Optimize loops:
// ❌ Bad: Unbounded loop
pub fn process_all(&self, items: Vec<Address>) -> Result<(), Vec<u8>> {
for item in items {
self.process(item)?; // Could exceed gas limit
}
Ok(())
}
// ✅ Good: Paginated
pub fn process_batch(&self, items: Vec<Address>, max: usize) -> Result<(), Vec<u8>> {
for item in items.iter().take(max) {
self.process(*item)?;
}
Ok(())
}
- Increase gas limit when calling:
cast send <CONTRACT> "function()" \
--gas-limit 1000000 \
--rpc-url <RPC_URL> \
--private-key <KEY>
- Profile gas usage:
cargo stylus trace <TX_HASH> --endpoint <RPC_URL>
See Gas optimization guide for more strategies.
Testing issues
Tests failing with storage errors
Problem: Tests fail with "storage not initialized" or similar errors.
Solution:
#[cfg(test)]
mod tests {
use super::*;
use stylus_sdk::testing::*;
#[test]
fn test_contract() {
// ✅ Always initialize TestVM
let vm = TestVM::default();
let mut contract = MyContract::from(&vm);
// Configure test environment
vm.set_caller(address!("0x0000000000000000000000000000000000000001"));
// Run tests
assert_eq!(contract.get_value().unwrap(), U256::ZERO);
}
}
Tests pass locally but fail on CI
Problem: Tests work on your machine but fail in continuous integration.
Solution:
- Pin Rust version in
rust-toolchain.toml:
[toolchain]
channel = "1.81"
components = ["rustfmt", "clippy"]
targets = ["wasm32-unknown-unknown"]
- Ensure WASM target in CI:
# .github/workflows/test.yml
- name: Add WASM target
run: rustup target add wasm32-unknown-unknown
- Check for environment-specific dependencies.
ABI and integration
ABI export fails
Problem: cargo stylus export-abi returns empty or incorrect ABI.
Solution:
- Ensure methods use
#[external]macro:
#[external]
impl MyContract {
// ✅ This will be exported
pub fn public_method(&self) -> U256 {
U256::from(42)
}
// ❌ This won't be exported (no #[external])
fn internal_method(&self) -> U256 {
U256::from(42)
}
}
- Check for compilation errors:
cargo build --target wasm32-unknown-unknown --release
cargo stylus export-abi
Type conversion errors
Problem: "Type mismatch" when calling from Solidity or TypeScript.
Solution:
- Use explicit type conversions:
use stylus_sdk::abi::AbiType;
// ✅ Ensure types match Solidity ABI
pub fn get_balance(&self, account: Address) -> U256 {
self.balances.get(account)
}
- Check ABI matches expectations:
cargo stylus export-abi > abi.json
# Verify types in abi.json match your frontend
Network and RPC issues
RPC connection timeout
Problem: "Connection timeout" or "Connection refused" errors.
Solution:
- Verify RPC URL:
# Test RPC connection
curl -X POST <RPC_URL> \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
-
Check rate limits: Public RPCs may have rate limits. Consider:
- Using a dedicated RPC provider
- Adding retry logic
- Implementing backoff strategies
-
Network-specific endpoints:
- Arbitrum Sepolia: https://sepolia-rollup.arbitrum.io/rpc
- Arbitrum One: https://arb1.arbitrum.io/rpc
Nonce errors
Problem: "Nonce too low" or "nonce too high" errors.
Solution:
- Let tooling manage nonces (default behavior):
# cargo stylus automatically manages nonces
cargo stylus deploy --endpoint <RPC_URL> --private-key <KEY>
- Reset nonce manually if needed:
# Check current nonce
cast nonce <YOUR_ADDRESS> --rpc-url <RPC_URL>
# Send transaction with specific nonce
cast send <CONTRACT> "function()" \
--nonce <NONCE> \
--rpc-url <RPC_URL> \
--private-key <KEY>
Debugging strategies
Enable verbose logging
# Set log level
export RUST_LOG=debug
# Run with logging
cargo stylus check
cargo stylus deploy --endpoint <RPC_URL> --private-key <KEY>
Use replay debugging
# Replay failed transaction
cargo stylus replay \
--endpoint <RPC_URL> \
<TX_HASH>
# With GDB for advanced debugging
cargo stylus replay \
--endpoint <RPC_URL> \
--use-gdb \
<TX_HASH>
See Debugging guide for detailed walkthrough.
Trace execution
# Trace transaction execution
cargo stylus trace <TX_HASH> --endpoint <RPC_URL>
Check contract state
# Read storage values
cast storage <CONTRACT_ADDRESS> <SLOT> --rpc-url <RPC_URL>
# Call view functions
cast call <CONTRACT_ADDRESS> "getValue()(uint256)" --rpc-url <RPC_URL>
Getting help
If you're still stuck:
- Check documentation: Search Stylus docs
- Search GitHub issues: Stylus SDK Issues
- Ask in Discord: Arbitrum Discord #stylus channel
- Post in forums: Arbitrum Developer Forum
Creating a good bug report
Include:
- Rust version:
rustc --version - cargo-stylus version:
cargo stylus --version - Minimal reproducible example
- Full error message and stack trace
- Steps to reproduce
- Expected vs. actual behavior
Common error messages quick reference
| Error Message | Common Cause | Quick Fix |
|---|---|---|
| "target not found: wasm32-unknown-unknown" | WASM target not installed | rustup target add wasm32-unknown-unknown |
| "Cannot connect to Docker daemon" | Docker not running | Start Docker Desktop |
| "Program too large" | Binary exceeds size limit | Add optimization flags, use --wasm-opt |
| "Insufficient funds" | Not enough ETH for gas | Fund account from faucet |
| "Nonce too low" | Nonce mismatch | Let tooling manage nonces |
| "Out of gas" | Gas limit exceeded | Increase gas limit or optimize code |
| "Storage not initialized" | Missing TestVM setup | Initialize TestVM::default() in tests |
| "Division by zero" | Unchecked arithmetic | Use ensure!() or checked_div() |
Next steps
- Review security best practices
- Study gas optimization
- Learn debugging techniques
- Explore testing strategies