The SwiftRemit contract implements a centralized validation system that validates all incoming API requests before they reach business logic. This ensures data integrity, prevents invalid operations, and provides structured error responses.
All validation logic is centralized in src/validation.rs. Each public contract function uses validation functions before executing business logic:
API Request → Validation Layer → Business Logic → Response
- Early Error Detection: Invalid requests are rejected before any state changes
- Structured Errors: Consistent error codes and messages across all operations
- Code Reusability: Validation logic is shared across multiple functions
- Maintainability: Single source of truth for validation rules
- Security: Prevents invalid data from reaching critical business logic
- Validates that an address is properly formatted
- Returns:
Result<(), ContractError> - Error:
ContractError::InvalidAddress
- Validates fee is within 0-10000 range (0%-100%)
- Returns:
Result<(), ContractError> - Error:
ContractError::InvalidFeeBps
- Validates amount is positive and non-zero
- Returns:
Result<(), ContractError> - Error:
ContractError::InvalidAmount
- Validates agent is registered in the system
- Returns:
Result<(), ContractError> - Error:
ContractError::AgentNotRegistered
- Validates contract is not in paused state
- Returns:
Result<(), ContractError> - Error:
ContractError::ContractPaused
- Validates remittance exists and returns it
- Returns:
Result<Remittance, ContractError> - Error:
ContractError::RemittanceNotFound
- Validates remittance is in pending status
- Returns:
Result<(), ContractError> - Error:
ContractError::InvalidStatus
- Validates settlement has not expired
- Returns:
Result<(), ContractError> - Error:
ContractError::SettlementExpired
- Validates settlement hasn't been executed before
- Returns:
Result<(), ContractError> - Error:
ContractError::DuplicateSettlement
- Validates there are fees available to withdraw
- Returns:
Result<(), ContractError> - Error:
ContractError::NoFeesToWithdraw
These functions combine multiple basic validators to validate entire API requests:
Validates initialization request:
- Admin address is valid
- Token address is valid
- Fee is within valid range
- Contract not already initialized
- Token is whitelisted
Validates remittance creation:
- Sender address is valid
- Agent address is valid
- Amount is positive
- Agent is registered
Validates payout confirmation:
- Contract is not paused
- Remittance exists
- Remittance is pending
- No duplicate settlement
- Settlement not expired
- Agent address is valid
Returns the validated remittance for use in business logic.
Validates remittance cancellation:
- Remittance exists
- Remittance is pending
- Sender address is valid
Returns the validated remittance for use in business logic.
Validates fee withdrawal:
- Recipient address is valid
- Fees are available to withdraw
Returns the fee amount for use in business logic.
Validates fee update:
- Fee is within valid range (0-10000)
Validates admin operations:
- Caller address is valid
- Target address is valid
- Caller is an admin
pub fn create_remittance(
env: Env,
sender: Address,
agent: Address,
amount: i128,
expiry: Option<u64>,
) -> Result<u64, ContractError> {
// Centralized validation before business logic
validate_create_remittance_request(&env, &sender, &agent, amount)?;
sender.require_auth();
// Business logic continues...
}All validation functions return Result<T, ContractError>. When validation fails:
- The error is immediately returned to the caller
- No state changes occur
- No tokens are transferred
- Structured error code is provided
| Error Code | Error Name | Description |
|---|---|---|
| 1 | AlreadyInitialized | Contract already initialized |
| 2 | NotInitialized | Contract not initialized |
| 3 | InvalidAmount | Amount must be > 0 |
| 4 | InvalidFeeBps | Fee must be 0-10000 |
| 5 | AgentNotRegistered | Agent not registered |
| 6 | RemittanceNotFound | Remittance doesn't exist |
| 7 | InvalidStatus | Invalid remittance status |
| 8 | Overflow | Arithmetic overflow |
| 9 | NoFeesToWithdraw | No fees available |
| 10 | InvalidAddress | Invalid address |
| 11 | SettlementExpired | Settlement expired |
| 12 | DuplicateSettlement | Settlement already executed |
| 13 | ContractPaused | Contract is paused |
| 14 | Unauthorized | Not authorized |
| 15 | AdminAlreadyExists | Admin already exists |
| 16 | AdminNotFound | Admin not found |
| 17 | CannotRemoveLastAdmin | Cannot remove last admin |
| 18 | TokenNotWhitelisted | Token not whitelisted |
| 19 | TokenAlreadyWhitelisted | Token already whitelisted |
1. validate_create_remittance_request()
├─ validate_address(sender)
├─ validate_address(agent)
├─ validate_amount(amount)
└─ validate_agent_registered(agent)
2. sender.require_auth()
3. Calculate fee
4. Transfer tokens
5. Store remittance
6. Emit events
1. validate_confirm_payout_request()
├─ validate_not_paused()
├─ validate_remittance_exists()
├─ validate_remittance_pending()
├─ validate_no_duplicate_settlement()
├─ validate_settlement_not_expired()
└─ validate_address(agent)
2. agent.require_auth()
3. Calculate payout
4. Transfer tokens
5. Update state
6. Mark settlement as executed
7. Emit events
Comprehensive validation tests are located in src/test.rs:
test_validation_prevents_invalid_amount- Tests amount validationtest_validation_prevents_invalid_fee_bps- Tests fee validationtest_validation_prevents_unregistered_agent- Tests agent validationtest_validation_prevents_operations_on_nonexistent_remittance- Tests existence validationtest_validation_prevents_operations_on_completed_remittance- Tests status validationtest_validation_prevents_withdraw_with_no_fees- Tests fee availability validationtest_validation_prevents_paused_operations- Tests pause state validationtest_validation_allows_valid_operations- Tests valid operations passtest_validation_structured_error_for_expired_settlement- Tests expiry validationtest_validation_prevents_duplicate_settlement- Tests duplicate preventiontest_validation_comprehensive_*- Tests complete validation flowstest_validation_edge_case_*- Tests boundary conditions
- Always validate first: Call validation functions before any business logic
- Use comprehensive validators: Prefer
validate_*_request()functions for complete validation - Handle errors gracefully: Always propagate validation errors with
? - Don't bypass validation: Never skip validation for "trusted" inputs
- Test edge cases: Ensure validation handles boundary conditions
- Document validation rules: Keep this document updated with validation changes
To add a new validation:
- Add basic validator function to
src/validation.rs - Add comprehensive request validator if needed
- Update contract function to use validator
- Add tests in
src/test.rs - Update this documentation
- Update error codes if new errors are added
Example:
// 1. Add basic validator
pub fn validate_new_field(field: &Type) -> Result<(), ContractError> {
if !is_valid(field) {
return Err(ContractError::NewError);
}
Ok(())
}
// 2. Add to comprehensive validator
pub fn validate_operation_request(env: &Env, field: &Type) -> Result<(), ContractError> {
validate_new_field(field)?;
// ... other validations
Ok(())
}
// 3. Use in contract
pub fn operation(env: Env, field: Type) -> Result<(), ContractError> {
validate_operation_request(&env, &field)?;
// ... business logic
}
// 4. Add test
#[test]
fn test_validation_new_field() {
// ... test implementation
}- Validation Order: Critical validations (auth, pause state) happen first
- No Side Effects: Validation functions never modify state
- Fail Fast: Return errors immediately on validation failure
- Complete Validation: All fields are validated before any processing
- Consistent Errors: Same validation always returns same error code
- Validation adds minimal overhead (< 1% of total execution time)
- Most validations are simple checks (comparisons, lookups)
- Early rejection prevents expensive operations on invalid data
- Validation is optimized for common success path
When modifying validation:
- Update validation function in
src/validation.rs - Update all callers if signature changes
- Update tests to cover new behavior
- Update this documentation
- Update error reference if needed
- Run full test suite to ensure no regressions