Skip to main content

Unsafe expect

Description

In Rust, the expect method is often used for error handling. It returns the contained Ok value for a Result or Some value for an Option. If an error occurs, it calls panic! with a provided error message.

The usage of expect can lead to a panic and crash the program, which is not desired behavior in most cases, especially for a smart contract.

Exploit Scenario

Consider the following ink! contract:

#[ink::contract]
mod unsafe_expect {
use ink::storage::Mapping;

#[ink(storage)]
pub struct UnsafeExpect {
total_supply: Balance,
balances: Mapping<AccountId, Balance>,
}

impl UnsafeExpect {

// ...

/// Returns the balance of a given account.
#[ink(message)]
pub fn balance_of(&self, owner: AccountId) -> Balance {
self.balances.get(owner).expect("could not get balance")
}
}
}

In this contract, the balance_of function uses the expect method to retrieve the balance of an account. If there is no entry for this account in the balances mapping, the contract will panic and halt execution, which could be exploited maliciously to disrupt the contract's operation.

The vulnerable code example can be found here.

Remediation

Instead of using expect, use a safer method for error handling. In this case, if there is no entry for an account in the balances mapping, return a default value (like 0).

#[ink::contract]
mod unsafe_expect {
use ink::storage::Mapping;

#[ink(storage)]
pub struct UnsafeExpect {
total_supply: Balance,
balances: Mapping<AccountId, Balance>,
}

impl UnsafeExpect {

// ...

/// Returns the balance of a given account.
#[ink(message)]
pub fn balance_of(&self, owner: AccountId) -> Balance {
if let Some(balance) = self.balances.get(owner) {
balance
} else {
0
}
}
}
}

The remediated code example can be found here.

References

Rust documentation: expect