Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
resolver = "2"

members = [
"shared",
"examples/basics/*",
"examples/intermediate/*",
"examples/advanced/*",
Expand Down
31 changes: 28 additions & 3 deletions docs/common-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,34 @@ pub fn transfer(env: Env, from: Address, to: Address, amount: i128)
return Err(AuthError::InsufficientBalance);
}

let bal: i128 = env.storage().persistent()
.get(&DataKey::Balance(from.clone())).unwrap_or(0);
if bal < amount {
---

## 3. Shared Validation Helpers

**Source:** [`shared/`](../shared/src/lib.rs), [`06-validation-patterns`](../examples/basics/06-validation-patterns/src/lib.rs)

Use shared validation utilities for common validation patterns. These provide
consistent error handling and reduce code duplication across contracts.

```rust
use soroban_validation::*;

// Parameter validation
validate_amount(amount, 1, 1000000)?;
validate_address(address)?;

// State validation
require_sufficient_balance(balance, required_amount)?;
require_cooldown_expired(&env, last_action, 3600)?;

// Authorization validation
require_owner(stored_owner, caller)?;
require_admin(stored_admin, caller)?;
```

**When to use:** For any input validation, state checking, or authorization logic.
Import `soroban_validation::*` and use the appropriate validation functions.
Always handle validation errors appropriately in your contract logic.
return Err(AuthError::InsufficientBalance);
}

Expand Down
1 change: 1 addition & 0 deletions examples/basics/03-authentication/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ crate-type = ["cdylib", "rlib"]

[dependencies]
soroban-sdk = { workspace = true }
soroban-validation = { path = "../../../shared" }

[dev-dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }
10 changes: 7 additions & 3 deletions examples/basics/03-authentication/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use soroban_sdk::{
contract, contracterror, contractimpl, contracttype, symbol_short, vec, Address, Env, Symbol,
Vec,
};
use soroban_validation::*;

// ---------------------------------------------------------------------------
// Role definitions
Expand Down Expand Up @@ -192,7 +193,8 @@ impl AuthContract {
.get(&DataKey::Admin)
.ok_or(AuthError::NotAdmin)?;

if admin != stored_admin {
// Use shared validation pattern
if require_admin(stored_admin, admin.clone()).is_err() {
return Err(AuthError::NotAdmin);
}

Expand Down Expand Up @@ -222,7 +224,8 @@ impl AuthContract {
.get(&DataKey::Admin)
.ok_or(AuthError::NotAdmin)?;

if admin != stored_admin {
// Use shared validation pattern
if require_admin(stored_admin, admin.clone()).is_err() {
return Err(AuthError::NotAdmin);
}

Expand Down Expand Up @@ -261,7 +264,8 @@ impl AuthContract {
.get(&DataKey::Balance(from.clone()))
.unwrap_or(0);

if amount <= 0 || from_balance < amount {
// Use shared validation pattern
if require_sufficient_balance(from_balance, amount).is_err() {
return Err(AuthError::InsufficientBalance);
}

Expand Down
1 change: 1 addition & 0 deletions examples/basics/06-validation-patterns/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ crate-type = ["cdylib", "rlib"]

[dependencies]
soroban-sdk = { workspace = true }
soroban-validation = { path = "../../../shared" }

[dev-dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }
69 changes: 51 additions & 18 deletions examples/basics/06-validation-patterns/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,55 +84,54 @@ ValidationError::Blacklisted = 309,

```rust
// Validate amount with min/max bounds
validate_amount_parameters(amount, min_amount, max_amount)
soroban_validation::validate_amount(amount, min_amount, max_amount)

// Validate string length and content
validate_string_parameters(text, min_length, max_length)
soroban_validation::validate_string(text, min_length, max_length)

// Validate address format
validate_address(address)
soroban_validation::validate_address(address)

// Validate array size
validate_array_parameters(array, min_size, max_size)
soroban_validation::validate_array(array, min_size, max_size)

// Validate timestamp range
validate_timestamp_parameters(env, timestamp, allow_past, max_future_seconds)
soroban_validation::validate_timestamp(env, timestamp, allow_past, max_future_seconds)
```

### State Validation Functions

```rust
// Validate contract is in required state
validate_contract_state(env, required_state)

// Validate sufficient balance
validate_balance(env, address, required_amount)

// Validate sufficient allowance
validate_allowance(env, owner, spender, required_amount)
soroban_validation::require_sufficient_balance(current_balance, required_amount)

// Validate cooldown period
validate_cooldown(env, address, cooldown_seconds)
soroban_validation::require_cooldown_expired(env, last_action_time, cooldown_seconds)
```

### Authorization Validation Functions

```rust
// Validate user has sufficient role
validate_role(env, address, required_role)

// Validate ownership
validate_ownership(env, address)
soroban_validation::require_owner(stored_owner, claimed_owner)

// Validate admin permissions
validate_admin(env, address)
soroban_validation::require_admin(stored_admin, claimed_admin)

// Validate role hierarchy
soroban_validation::require_role(user_role, required_role)

// Validate blacklist status
soroban_validation::require_not_blacklisted(is_blacklisted)
```

## Usage Examples

### Basic Transfer with Full Validation

```rust
use soroban_validation::*;

let result = client.validated_transfer(
&from_address,
&to_address,
Expand All @@ -148,6 +147,40 @@ match result {
}
```

### Using Shared Validators in Contract Functions

```rust
use soroban_sdk::*;
use soroban_validation::*;

#[contractimpl]
impl MyContract {
pub fn transfer_with_validation(
env: Env,
from: Address,
to: Address,
amount: i128
) -> Result<(), ValidationError> {
from.require_auth();

// Parameter validation
validate_amount(amount, 1, 1000000)?;
validate_address(from.clone())?;
validate_address(to.clone())?;

// State validation
let balance = get_balance(&env, from.clone());
require_sufficient_balance(balance, amount)?;

// Authorization validation (if needed)
// require_owner(stored_owner, from.clone())?;

// Execute transfer logic...
Ok(())
}
}
```

### Admin Operations with Authorization

```rust
Expand Down
Loading