Selendra

Documentation

Security Best Practices

Keep WASM contracts secure

Security critical for smart contracts. Rust prevents memory bugs but logic errors still possible.

Rust Safety Advantages

  • No buffer overflows - Bounds checked automatically
  • No null dereferences - Option type enforced
  • No data races - Borrow checker ensures safety
  • Strong types - Misuse caught at compile time

Still need care: Logic bugs, integer overflow, access control errors.

Common Vulnerabilities

Integer Overflow

// Dangerous - may overflow in release
self.balance = self.balance + amount;

// Safe - explicit check
self.balance = self.balance
    .checked_add(amount)
    .ok_or(Error::Overflow)?;

Use checked_add(), checked_sub(), checked_mul() for financial operations. saturating_add() clamps to max.

Unauthorized Access

// Bad - No check
#[ink(message)]
pub fn mint(&mut self, to: AccountId, amount: Balance) {
    self.balances.insert(to, &amount);
}

// Good - Owner check
#[ink(message)]
pub fn mint(&mut self, to: AccountId, amount: Balance) -> Result<()> {
    if self.env().caller() != self.owner {
        return Err(Error::Unauthorized);
    }
    // Mint logic
    Ok(())
}

Reentrancy

// Safe pattern - Update state before external call
#[ink(message)]
pub fn withdraw(&mut self, amount: Balance) -> Result<()> {
    let caller = self.env().caller();
    let balance = self.balance_of(caller);

    if balance < amount {
        return Err(Error::InsufficientBalance);
    }

    // Update state first
    self.balances.insert(caller, &(balance - amount));

    // Then transfer
    self.env().transfer(caller, amount)
        .map_err(|_| Error::TransferFailed)?;
    Ok(())
}

Input Validation

#[ink(message)]
pub fn transfer(&mut self, to: AccountId, amount: Balance) -> Result<()> {
    if amount == 0 {
        return Err(Error::InvalidAmount);
    }
    if to == AccountId::from([0u8; 32]) {
        return Err(Error::InvalidAddress);
    }

    let from = self.env().caller();
    let from_balance = self.balance_of(from);

    if from_balance < amount {
        return Err(Error::InsufficientBalance);
    }

    self.balances.insert(from, &(from_balance - amount));
    self.balances.insert(to, &(self.balance_of(to) + amount));
    Ok(())
}

Unbounded Operations

// Dangerous - Can run out of gas
for item in self.items.iter() {
    self.process_item(item);
}

// Safe - Batched processing
#[ink(message)]
pub fn process_batch(&mut self, start: u32, count: u32) -> Result<()> {
    let end = (start + count).min(self.items.len() as u32);
    for i in start..end {
        self.process_item(&self.items[i as usize]);
    }
    Ok(())
}

Access Control Patterns

Single Owner

fn ensure_owner(&self) -> Result<()> {
    if self.env().caller() != self.owner {
        return Err(Error::Unauthorized);
    }
    Ok(())
}

#[ink(message)]
pub fn admin_function(&mut self) -> Result<()> {
    self.ensure_owner()?;
    // Protected logic
    Ok(())
}

Role-Based

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Role { Admin, Minter, Pauser }

fn ensure_role(&self, role: Role) -> Result<()> {
    if !self.roles.get((self.env().caller(), role)).unwrap_or(false) {
        return Err(Error::MissingRole);
    }
    Ok(())
}

#[ink(message)]
pub fn mint(&mut self, to: AccountId, amount: Balance) -> Result<()> {
    self.ensure_role(Role::Minter)?;
    // Mint logic
    Ok(())
}

Security Testing

#[ink::test]
fn test_unauthorized_mint() {
    let accounts = ink::env::test::default_accounts::<Environment>();
    let mut token = Token::new(1000);

    ink::env::test::set_caller::<Environment>(accounts.bob);
    assert_eq!(token.mint(accounts.bob, 1000), Err(Error::Unauthorized));
}

#[ink::test]
fn test_overflow() {
    let mut token = Token::new(u128::MAX);
    assert_eq!(token.mint(accounts.alice, 1), Err(Error::Overflow));
}

Security Checklist

Before Deployment

  • All privileged functions protected
  • Input validation everywhere
  • Integer overflow checks
  • State updates before external calls
  • No unbounded loops
  • Complete error handling
  • Access control tested
  • Attack scenarios tested
  • Code reviewed by another developer

Next Steps

Contribute

Found an issue or want to contribute?

Help us improve this documentation by editing this page on GitHub.

Edit this page on GitHub