// UNSAFE - Can panic if index is out of bounds
let item = vec.get(i).unwrap();Use when the operation must succeed or the transaction should fail:
// Returns Error::IndexOutOfBounds if index is invalid
let item = vec.get(i).ok_or(Error::IndexOutOfBounds)?;Use cases:
- Batch operations
- Data validation
- Required data processing
- State modifications
Example:
pub fn process_batch(items: &Vec<Item>) -> Result<(), Error> {
for i in 0..items.len() {
let item = items.get(i).ok_or(Error::IndexOutOfBounds)?;
// Process item...
}
Ok(())
}Use when missing items can be safely skipped:
// Safely skips if index is invalid
if let Some(item) = vec.get(i) {
// Process item...
}Use cases:
- Query functions
- Filtering operations
- Optional data retrieval
- Read-only operations
Example:
pub fn get_filtered_items(ids: &Vec<Symbol>) -> Vec<Item> {
let mut results = Vec::new(env);
for i in 0..ids.len() {
if let Some(id) = ids.get(i) {
if let Ok(item) = storage::get_item(env, &id) {
results.push_back(item);
}
}
}
results
}// ❌ UNSAFE
for i in 0..vec.len() {
let item = vec.get(i).unwrap();
process(item);
}
// ✅ SAFE (Critical)
for i in 0..vec.len() {
let item = vec.get(i).ok_or(Error::IndexOutOfBounds)?;
process(item)?;
}
// ✅ SAFE (Query)
for i in 0..vec.len() {
if let Some(item) = vec.get(i) {
process(item);
}
}
// ✅ BEST (No indexing needed)
for item in vec.iter() {
process(item);
}// ❌ UNSAFE
let first = vec.get(0).unwrap();
// ✅ SAFE
let first = vec.get(0).ok_or(Error::IndexOutOfBounds)?;
// ✅ ALTERNATIVE (for first/last)
let first = vec.first_opt().ok_or(Error::IndexOutOfBounds)?;// ❌ UNSAFE
let tag = metadata.tags.get(i).unwrap();
// ✅ SAFE
let tag = metadata.tags.get(i).ok_or(Error::IndexOutOfBounds)?;
validate_tag(&tag)?;#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum Error {
// ... other errors
IndexOutOfBounds = 90,
}pub fn safe_function(items: &Vec<Item>) -> Result<(), Error> {
for i in 0..items.len() {
let item = items.get(i).ok_or(Error::IndexOutOfBounds)?;
// Process item
}
Ok(())
}#[test]
fn test_valid_bounds() {
let env = Env::default();
let mut vec = Vec::new(&env);
vec.push_back(1);
vec.push_back(2);
// Should succeed
let item = vec.get(0).ok_or(Error::IndexOutOfBounds);
assert!(item.is_ok());
}#[test]
fn test_invalid_bounds() {
let env = Env::default();
let vec = Vec::new(&env);
// Should return error, not panic
let item = vec.get(0).ok_or(Error::IndexOutOfBounds);
assert!(item.is_err());
}#[test]
fn test_edge_cases() {
let env = Env::default();
// Empty vector
let empty = Vec::new(&env);
assert!(empty.get(0).is_none());
// Large offset
let mut vec = Vec::new(&env);
vec.push_back(1);
assert!(vec.get(100).is_none());
}.get()already performs bounds checking internally.ok_or()adds minimal overhead- Much cheaper than recovering from a panic
-
Cache vector length:
let len = vec.len(); for i in 0..len { if let Some(item) = vec.get(i) { // Process } }
-
Use iterators when possible:
// Better than indexing for item in vec.iter() { process(item); }
-
Batch bounds checks:
// Check all indices first for i in 0..len { items.get(i).ok_or(Error::IndexOutOfBounds)?; } // Then process for i in 0..len { let item = items.get(i).unwrap(); // Safe now process(item); }
- No
.unwrap()calls on.get()operations - All array accesses use
.ok_or()orif let Some() - Appropriate pattern chosen (fail-fast vs graceful skip)
- Error type defined and documented
- Tests cover valid bounds
- Tests cover invalid bounds
- Tests cover edge cases (empty, single item, large offset)
pub fn register_quests_batch(
env: &Env,
creator: &Address,
quests: &Vec<BatchQuestInput>,
) -> Result<(), Error> {
let len = quests.len();
validation::validate_batch_quest_size(len)?;
for i in 0u32..len {
// ✅ Safe bounds checking
let q = quests.get(i).ok_or(Error::IndexOutOfBounds)?;
register_quest(env, &q.id, creator, &q.reward_asset,
q.reward_amount, &q.verifier, q.deadline)?;
}
Ok(())
}pub fn get_quests_by_status(
env: &Env,
status: &QuestStatus,
offset: u32,
limit: u32,
) -> Vec<Quest> {
let ids = storage::get_quest_ids(env);
let mut results = Vec::new(env);
for i in 0..ids.len() {
// ✅ Safe bounds checking with graceful skip
if let Some(id) = ids.get(i) {
if let Ok(quest) = storage::get_quest(env, &id) {
if &quest.status == status {
results.push_back(quest);
}
}
}
}
results
}If you're unsure which pattern to use:
- Will missing data break the operation? → Use
.ok_or() - Can missing data be safely skipped? → Use
if let Some() - Can you avoid indexing entirely? → Use iterators
When in doubt, use .ok_or() for safety.
Last updated: April 25, 2026
Part of the Array Bounds Checking Security Fix