In Cairo, there are two methods for the less than or equal to comparison operator: assert_le
and assert_nn_le
:
assert_le
asserts that a numbera
is less than or equal tob
, regardless of the size ofa
assert_nn_le
additionally asserts thata
is non-negative, i.e. not greater than or equal to theRANGE_CHECK_BOUND
value of2^128
.
assert_nn_le
works to compare unsigned integers but with a value less than 2^128
(e.g. an Uint256 field). To compare felts as unsigned integer over the entire range (0, P], assert_le_felt
should be used. Note these functions exist also with the is_
prefix where they return 1 (TRUE) or 0 (FALSE).
Due to the complexity of these assertions, a common mistake is to use assert_le
when assert_nn_le
should be used.
Suppose that a codebase uses the following checks regarding a hypothetical ERC20 token. In the first function, it may be possible that value
is in fact greater than max_supply
, yet because the function does not verify value >= 0
the assertion will incorrectly pass. The second function, however, asserts that 0 <= value <= max_supply
, which will correctly not let an incorrect value
go through the assertion.
@storage_var
func max_supply() -> (res: felt) {
}
@external
func bad_comparison{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}() {
let (value: felt) = ERC20.total_supply();
assert_le{range_check_ptr=range_check_ptr}(value, max_supply.read());
// do something...
return ();
}
@external
func better_comparison{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}() {
let (value: felt) = ERC20.total_supply();
assert_nn_le{range_check_ptr=range_check_ptr}(value, max_supply.read());
// do something...
return ();
}
- Review all felt comparisons closely.
- Determine what sort of behavior the comparison should have, and if
assert_nn_le
orassert_le_felt
is more appropriate. - Use
assert_le
if you explicitly want to make comparison between signed integers - otherwise explicitely document why it is used overassert_nn_le