Cairo no ofrece la gama completa de tamaños de enteros que se encuentran en Solidity. Mientras que Solidity proporciona tipos de enteros para cada múltiplo de 8 bits hasta 256, Cairo solo admite los siguientes tipos de enteros:
u8u16u32u64u128u256
Para los lectores familiarizados con Rust, el tipo usize es un u32.
Aquí hay un ejemplo de un contrato en Cairo que suma dos números u256:
#[starknet::interface]
pub trait IAdd<TContractState> {
fn add(self: @TContractState, a: u256, b: u256) -> u256;
}
#[starknet::contract]
mod Add {
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl AddImpl of super::IAdd<ContractState> {
fn add(self: @ContractState, a: u256, b: u256) -> u256 {
a + b
}
}
}
Cairo también admite enteros con signo de los siguientes tipos:
i8i16i32i64i128
Tenga en cuenta que i256 no es compatible.
En este artículo, cubriremos cómo funcionan los enteros en Cairo, destacando las diferencias clave con Solidity. Repasaremos conceptos como el comportamiento ante el desbordamiento (overflow), la conversión (casting) entre tamaños de enteros y el trabajo con valores con y sin signo. También hablaremos sobre felt252, el elemento de campo nativo de Cairo que se encuentra en el núcleo de todas las operaciones numéricas.
Los enteros tienen protección contra desbordamiento (overflow y underflow)
La protección contra desbordamiento está habilitada por defecto en Cairo para los tipos de enteros (con y sin signo). Para ver esto, cree un nuevo proyecto scarb con scarb new integers, luego elimine el contrato predeterminado en ./src/lib.cairo y reemplácelo con el siguiente código:
#[starknet::interface]
pub trait IHelloStarknet<TContractState> {
fn underflow(ref self: TContractState, x: u256, y: u256) -> u256;
}
#[starknet::contract]
mod HelloStarknet {
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
fn underflow(ref self: ContractState, x: u256, y: u256) -> u256 {
x - y
}
}
}
Elimine las pruebas creadas automáticamente y agregue el siguiente código a continuación. Tenga en cuenta que la macro #[should_panic] encima de la función especifica que la prueba se aprueba si la ejecución entra en pánico (panics).
use snforge_std::{ContractClassTrait, DeclareResultTrait, declare};
use integers::{ IHelloStarknetDispatcher, IHelloStarknetDispatcherTrait};
use starknet::ContractAddress;
fn deploy_contract(name: ByteArray) -> ContractAddress {
let contract = declare(name).unwrap().contract_class();
let (contract_address, _) = contract.deploy(@ArrayTrait::new()).unwrap();
contract_address
}
#[test]
#[should_panic]
fn test_flow_protection() {
let contract_address = deploy_contract("HelloStarknet");
let dispatcher = IHelloStarknetDispatcher { contract_address };
dispatcher.underflow(0, 1);
}
Ejecute las pruebas con scarb test y note que la prueba se aprueba.
Sin puntos flotantes
Al igual que Solidity, Cairo no admite puntos flotantes.
Conversión de enteros (Casting)
Hay dos tipos de conversiones:
- conversiones que tienen garantizado su éxito y
- aquellas que podrían fallar.
Por ejemplo, convertir un u8 a un u16 siempre tendrá éxito porque un u16 puede contener cualquier número que un u8 pueda. Sin embargo, la conversión de u16 a u8 podría fallar porque algunos de los bits más significativos podrían truncarse.
La conversión de i16 a u16 puede fallar si el i16 contiene un valor negativo. La conversión de u16 a i16 puede fallar si el número contenido en el u16 es demasiado grande; los enteros sin signo del mismo tamaño de bits que los enteros con signo pueden contener números positivos más grandes que los que puede contener el entero con signo.
Conversiones que siempre tienen éxito
La conversión a un tipo más grande siempre tendrá éxito porque el tipo de destino puede contener cualquier valor del tipo de origen.
Ejemplo de conversión que siempre tiene éxito:
u8→u16,u32,u64,u128,u256u16→u32,u64,u128,u256i8→i16,i32,i64,i128i16→i32,i64,i128
Para realizar una conversión que siempre tenga éxito, use .into()
let small: u8 = 7;
let large: u16 = small.into(); // Always succeeds - u16 can hold any u8 value
Conversiones que pueden fallar
A diferencia de Solidity, que trunca silenciosamente los bits más significativos cuando un número más grande se convierte a un tipo más pequeño, Cairo entrará en pánico si una conversión no se puede realizar de manera segura.
Aquí está el fragmento de código para una conversión que puede fallar:
// may fail if value is too large
let large: u16 = 300;
let small: u8 = large.try_into().unwrap(); // Panics! 300 > 255
El código anterior entra en pánico porque el valor 300 no cabe en un u8, que solo puede almacenar valores de hasta 255. Por ahora, solo recuerde que el método unwrap() extrae el valor de un tipo “envoltorio” (wrapper) (que se analiza en la siguiente subsección). Si el envoltorio contiene un error en lugar de un valor válido, unwrap() entrará en pánico.
Tenga en cuenta que si intenta usar la conversión into() en una situación donde la conversión puede fallar (convirtiendo de un tipo grande a uno pequeño), el código no compilará.
Detectar si una conversión fallará
Al convertir entre tipos de enteros usando try_into(), el resultado puede o no caber en el tipo de destino. Debido a esto, la conversión devuelve un Option (un ejemplo de tipo “envoltorio”), que es la forma en que Cairo dice “esto podría tener éxito, o podría fallar”.
- Si la conversión funciona, obtiene
Option::Some(value). - Si no cabe, obtiene
Option::None.
Esto nos obliga a manejar el posible fallo de forma segura antes de usar el valor. Dos formas comunes de hacer esto son:
- usando el método
.is_some()que devuelve un booleano indicando si la opción tiene algún valor o no. - usando
if let.
Usando el método .is_some():
// Value 300 cannot fit into u8 (max 255), so try_into returns None
let value: u16 = 300;
let result_option: Option<u8> = value.try_into();
if result_option.is_some() {
// cast succeeded
} else {
// cast failed
}
Usando if let:
if let Some(result) = result_option {
// cast succeeded, use `result`
} else {
// cast failed
}
Constantes
Las constantes en Cairo son valores que se conocen en tiempo de compilación y no se pueden cambiar en tiempo de ejecución. Se declaran dentro del bloque mod usando la palabra clave const y deben tener su tipo especificado explícitamente, de esta manera:
const <NAME>: <Type> = <value>;
Así es como se declaran y usan las constantes en Cairo:
#[starknet::contract]
mod HelloStarknet {
// DECLARE CONSTANTS
const num_one: u256 = 1;
const num_two: i8 = -2;
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
fn get_one(self: @ContractState) -> u256 {
// USE CONSTANTS
num_one
}
fn get_two(self: @ContractState) -> i8 {
// USE CONSTANTS
num_two
}
}
}
Constantes vs Inmutables
A diferencia de Solidity, Cairo no tiene una palabra clave immutable separada. Los valores que deben establecerse una vez durante el despliegue del contrato, pero que no se conocen en tiempo de compilación, deben almacenarse en el almacenamiento del contrato y establecerse en el constructor.
Tamaños máximos de enteros
En Solidity, type(uint256).max se usa para obtener el tamaño máximo de un entero. En Cairo, usamos let max_u256: u256 = Bounded::MAX como se muestra a continuación:
#[starknet::contract]
mod HelloStarknet {
use core::num::traits::{Bounded}; // Bounded is how we get the max
#[storage]
struct Storage {} // unusued
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
fn max_demo(ref self: ContractState) -> u256 {
let max_u256: u256 = Bounded::MAX;
max_u256
}
}
}
En el código anterior, el contrato primero importa el trait Bounded, que proporciona acceso a constantes numéricas: MIN y MAX. MAX le devuelve el valor máximo permitido para el tipo. Dentro de la función max_demo(), usamos Bounded::MAX para recuperar el valor máximo del tipo u256.
La mayor parte del tiempo, Cairo es capaz de determinar los tipos por sí solo, pero al usar Bounded::MAX el compilador no sabrá automáticamente para qué tipo de entero necesita el máximo. Por lo tanto, la variable necesita una anotación de tipo explícita, que es el u256 después de : es decir, let max_u256: u256 = Bounded::MAX;.
Tamaños mínimos de enteros
Al igual que podemos obtener el valor máximo para los tipos de enteros, Cairo también proporciona acceso a sus valores mínimos a través del trait Bounded con Bounded::MIN como se muestra a continuación:
#[starknet::contract]
mod HelloStarknet {
use core::num::traits::{Bounded}; // Bounded provides both MIN and MAX
#[storage]
struct Storage {} // unused
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
fn min_demo(ref self: ContractState) -> (u256, i128) {
// This will be 0 for unsigned types
let min_u256: u256 = Bounded::MIN;
// This will be the most negative value
let min_i128: i128 = Bounded::MIN;
(min_u256, min_i128)
}
}
}
Comprendiendo los tamaños mínimos
Para los tipos de enteros sin signo (u8, u16, u32, u64, u128, u256), el valor mínimo es siempre 0:
let min_u8: u8 = Bounded::MIN; // 0
let min_u16: u16 = Bounded::MIN; // 0
let min_u32: u32 = Bounded::MIN; // 0
let min_u64: u64 = Bounded::MIN; // 0
let min_u128: u128 = Bounded::MIN; // 0
let min_u256: u256 = Bounded::MIN; // 0
Para los tipos de enteros con signo (i8, i16, i32, i64, i128), el valor mínimo es el número más negativo que se puede representar:
let min_i8: i8 = Bounded::MIN; // -128
let min_i16: i16 = Bounded::MIN; // -32,768
let min_i32: i32 = Bounded::MIN; // -2,147,483,648
let min_i64: i64 = Bounded::MIN; // -9,223,372,036,854,775,808
let min_i128: i128 = Bounded::MIN; // a very large negative value
Requisito de anotación de tipo
Al igual que con Bounded::MAX, el compilador no puede adivinar automáticamente el tipo al usar Bounded::MIN, por lo que se requieren anotaciones de tipo explícitas:
// This won't compile - ambiguous type ❌
let min_val = Bounded::MIN;
// This will compile - explicit type annotation ✅
let min_val: u64 = Bounded::MIN;
Abreviatura para especificar tipos en literales enteros
Si asignamos un valor fijo a un entero, podemos especificar el tipo del entero explícitamente o permitir que el compilador infiera el tipo.
Especificando el tipo:
// first way
let x: i32 = 10;
// second way
let y = 10_i32;
Permitiendo al compilador inferir el tipo:
Si no especificamos el tipo, el compilador intentará inferirlo del contexto. Por ejemplo, la siguiente función devuelve un u32 por lo que el tipo de 10 es u32:
fn hello_world() -> u32 {
let x = 10;
x
}
Desbordamiento de división de enteros con signo
En Solidity, hay un caso límite (edge case) específico con la división de enteros con signo que puede causar un comportamiento inesperado. Considere este contrato de Solidity:
contract D {
function div(int8 a, int8 b) public pure returns (int8 c) {
c = a / b;
}
}
El problema ocurre cuando se divide el valor más negativo entre -1. Para int8, el rango es de -128 a 127. Cuando se realiza -128 / -1, matemáticamente el resultado debería ser 128, pero 128 no cabe en un int8 (que tiene un valor máximo de 127). Esto provoca un desbordamiento.
En Solidity, esta operación:
- Daría la vuelta (wrap around) a un valor inesperado
- Se revertiría (en versiones más nuevas con protección contra desbordamiento)
Cómo maneja Cairo el desbordamiento de la división de enteros
Al igual que en la versión de Solidity ≥ 0.8, Cairo proporciona protección integrada contra el desbordamiento. Si una operación diera como resultado un desbordamiento, el programa entrará en pánico en tiempo de ejecución, evitando un comportamiento no deseado.
#[starknet::contract]
mod Div {
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl DivImpl of super::IDiv<ContractState> {
fn div(self: @ContractState, a: i8, b: i8) -> i8 {
// This will panic if `a` is -128 and `b` is -1
a / b
}
}
}
Para evitar un pánico por desbordamiento de división con signo, necesitamos comprobar manualmente las condiciones antes de realizar la operación, así:
#[starknet::contract]
mod Div {
use core::num::traits::Bounded;
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl DivImpl of super::IDiv<ContractState> {
fn div(self: @ContractState, a: i8, b: i8) -> i8 {
if b == 0 {
// Division by zero
} else if a == Bounded::<i8>::MIN && b == -1 {
// Overflow case
} else {
a / b
}
}
}
}
Fallo en la conversión ascendente (Casting Up)
Esta función de Solidity parece segura pero puede producir resultados inesperados:
function mul(uint8 a, uint8 b) public pure returns (uint256 c) {
c = a * b;
}
El problema es que la multiplicación a * b ocurre primero en la aritmética de uint8, y luego el resultado se convierte a uint256. Si a * b desborda el rango de uint8 (0-255), la multiplicación da la vuelta antes de ser convertida a un tipo mayor.
Por ejemplo:
mul(200, 200)debería matemáticamente devolver40000- Pero
200 * 200 = 40000desbordauint8(máximo 255) - El resultado tras dar la vuelta en
uint8sería40000 % 256 = 64 - Luego
64se convierte auint256, devolviendo64en lugar de40000, por supuesto, esto sucede en versiones de Solidity inferiores a 0.8
Desbordamiento en Cairo
Cairo maneja el problema del desbordamiento en conversiones ascendentes a través de su protección integrada contra desbordamientos. Es decir, si una operación produce un valor que va más allá del rango permitido, Cairo arrojará un error y detendrá la ejecución en lugar de permitir un comportamiento no deseado.
Por ejemplo, el código a continuación entrará en pánico si a * b > 255:
// This will panic if the multiplication overflows u8
fn mul(self: @ContractState, a: u8, b: u8) -> u256 {
let result_u8 = a * b; // Panic if a * b > 255
result_u8.into() // This line never executes if overflow occurs
}
Conversión ascendente segura
Un enfoque seguro para evitar que nuestro contrato en Cairo entre en pánico debido al desbordamiento es convertir a un tipo mayor antes de las operaciones aritméticas cuando necesitamos el resultado en un tipo más grande. Por ejemplo, podemos convertir de u8 a u256:
// cast up before multiplication
fn safe_mul(self: @ContractState, a: u8, b: u8) -> u256 {
let a_wide: u256 = a.into();
let b_wide: u256 = b.into();
a_wide * b_wide // No overflow possible
}
Exponentes
En Solidity, la sintaxis para los exponentes es b ** e donde b es la base y e es el exponente.
En Cairo, debe importar Pow con use core::num::traits::Pow;. Luego, puede elevar un entero a una potencia usando b.pow(e).
#[starknet::contract]
mod HelloStarknet {
use core::num::traits::Pow; // THIS IMPORT IS REQUIRED
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
fn upcast_demo(ref self: ContractState, x: u256, y: u32) -> u256 {
x.pow(y) // compute exponent
}
}
}
El método .pow() devuelve un valor del mismo tipo que la base, por lo que x.pow(y) aquí produce un valor de tipo (u256).
Importante: El exponente debe ser de tipo u32 (o usize que es un u32 internamente en Cairo). El código no compilará si se usa otro tipo de entero.
Guiones bajos en literales
Al igual que en Solidity, los números grandes en Cairo se pueden separar con guiones bajos para que sean más fáciles de leer:
// valid Cairo
let basis_points = 10_000;
Abreviatura de notación científica
En Solidity, puede escribir números usando notación científica como 10e18, que representa . Cairo no admite esto. Para escribir 10e18 en Cairo, use el trait Pow como se muestra a continuación:
use core::num::traits::Pow;
// ...
let num = 10_u256.pow(18_u32);
Recuerde, el exponente debe ser de tipo u32.
Operaciones bit a bit, Operaciones de desplazamiento y Comparaciones
Operaciones bit a bit
Cairo admite las operaciones estándar bit a bit en los tipos de enteros:
AND bit a bit (&):
let a: u8 = 0b1100; // 12 in decimal
let b: u8 = 0b1010; // 10 in decimal
let result = a & b; // 0b1100 & 0b1010 = 0b1000 => 8
OR bit a bit (|):
let a: u8 = 0b1100; // 12 in decimal
let b: u8 = 0b1010; // 10 in decimal
let result = a | b; // 0b1110 = 14
XOR bit a bit (^):
let a: u8 = 0b1100; // 12 in decimal
let b: u8 = 0b1010; // 10 in decimal
let result = a ^ b; // 0b0110 = 6
NOT bit a bit (~):
let a: u8 = 0b1100; // 12 in decimal
let result = ~a; // 0b11110011 = 243 (inverts all bits)
Operaciones de desplazamiento
Cairo proporciona operaciones de desplazamiento de bits a la izquierda y a la derecha:
Desplazamiento a la izquierda (<<)
Desplaza los bits a la izquierda, rellenando con ceros:
let a: u8 = 0b0001; // 1 in decimal
let result = a << 3; // 0b1000 = 8 (multiplies by 2**3)
Desplazamiento a la derecha (>>)
Desplaza los bits a la derecha:
let a: u8 = 0b1100; // 12 in decimal
let result = a >> 2; // 0b0011 = 3 (divides by 2**2)
Operaciones de comparación
Cairo admite todos los operadores de comparación estándar:
Igualdad (== y !=)
let a: u32 = 10;
let b: u32 = 20;
let equal = a == b; // false
let not_equal = a != b; // true
Orden (<, <=, >, >=)
let a: u32 = 10;
let b: u32 = 20;
let less_than = a < b; // true
let less_or_equal = a <= b; // true
let greater_than = a > b; // false
let greater_or_equal = a >= b; // false
Una nota sobre felt252
Si lee código de producción antiguo de Cairo, verá que el tipo de datos felt252 se usa con frecuencia. De forma similar a cómo la EVM tiene un tamaño de palabra predeterminado de 256 bits, la CairoVM tiene un tamaño de palabra predeterminado de poco menos de 252 bits, o para ser precisos: 3618502788666131213697322783095070105623107215331596699973092056135872020481 o 2²⁵¹+17⋅2¹⁹²+1. El número es ligeramente más pequeño que 2²⁵².
Cairo se refiere a los tipos de números que caen en el rango de [0…2²⁵¹+17⋅2¹⁹²+1] como felt252.
Este número grande es un número primo que está optimizado para las matemáticas de pruebas de conocimiento cero (zero-knowledge proof) en la máquina virtual de Cairo.
El nombre felt252 proviene del término “elemento de campo que cabe en 252 bits” (field element that fits in 252 bits). Un “elemento de campo” es un número que vive en un sistema numérico donde todas las sumas y multiplicaciones se realizan en módulo de algún número primo.
No se recomienda el uso de felt252 en el código de Cairo porque en una fecha posterior, la CairoVM puede cambiar su tamaño de palabra predeterminado a un valor más pequeño para mejorar la velocidad a la que puede probar las transacciones.
El compilador de Cairo manejará de forma transparente la traducción de enteros (u8… u256) a felt252 tras bambalinas por usted. Vale la pena señalar que un u256 no cabe en 252 bits, por lo que internamente, un u256 es en realidad dos elementos felt252. Por lo tanto, es preferible por razones de eficiencia de gas usar u128 o enteros más pequeños cuando sea posible. La única otra ocasión en la que tiene sentido usar felt252 es donde se necesitan optimizaciones extremas. Revisaremos los costos de gas en Starknet en un tutorial posterior. Por ahora, le recomendamos que no use el tipo felt252 y simplemente use enteros.
Sin embargo, debido a que verá felt252 con frecuencia en el código, vale la pena explicar cómo funciona.
felt252 no tiene protección contra overflow y underflow
A diferencia de Solidity 0.8.0 o superior, Cairo no incluye protección integrada contra overflow y underflow para felt252. Para demostrar esto, cree un nuevo proyecto scarb new numbers. Luego reemplace el código generado en lib.cairo con el siguiente código:
#[starknet::interface]
pub trait IHelloStarknet<TContractState> {
fn math_demo(self: @TContractState, x: felt252, y: felt252) -> felt252;
}
#[starknet::contract]
mod HelloStarknet {
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
fn math_demo(self: @ContractState, x: felt252, y: felt252) -> felt252 {
x - y
}
}
}
Reemplace la prueba de la siguiente manera:
use starknet::ContractAddress;
use snforge_std::{declare, ContractClassTrait, DeclareResultTrait};
use numbers::IHelloStarknetDispatcher;
use numbers::IHelloStarknetDispatcherTrait;
fn deploy_contract(name: ByteArray) -> ContractAddress {
let contract = declare(name).unwrap().contract_class();
let (contract_address, _) = contract.deploy(@ArrayTrait::new()).unwrap();
contract_address
}
#[test]
fn test_math_demo() {
let contract_address = deploy_contract("HelloStarknet");
let dispatcher = IHelloStarknetDispatcher { contract_address };
let result = dispatcher.math_demo(0, 1); // 0 - 1
println!("result: {}", result);
}
La consola imprimirá:
result: 3618502788666131213697322783095070105623107215331596699973092056135872020480
En Solidity anterior a 0.8.0, asumiendo aritmética sin signo, 0 - 1 da como resultado un underflow. Luego, el valor da la vuelta hasta el máximo valor posible de uint256. Algo similar ocurre con felt252 en Cairo, ya que no tiene protección contra overflow ni underflow. Toda la aritmética se realiza en módulo del primo del campo (2²⁵¹ + 17 × 2¹⁹² + 1), por lo que restar 1 a 0 devuelve el mayor valor válido de felt252, que parece un número enorme.
felt252_div
Si intenta dividir un felt252 por otro felt252 obtendrá un error de compilación. El siguiente código no compilará:
fn math_demo(self: @ContractState, x: felt252, y: felt252) -> felt252 {
x / y
}
Para comprender completamente por qué Cairo no permite la división de esta manera, vea nuestro video sobre aritmética modular.
La forma correcta de dividir felt252
Para realizar una división con valores felt252, debemos usar felt252_div que es una función integrada en la biblioteca principal de Cairo:
#[starknet::contract]
mod HelloStarknet {
// THIS IS NEW
use core::felt252_div;
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
fn math_demo(self: @ContractState) -> felt252 {
felt252_div(4, 2)
}
}
}
La función felt252_div no realiza una división regular. En cambio:
- Encuentra el inverso modular del divisor
yen módulo del primo del campo - Multiplica
xpor este inverso - Devuelve el resultado en módulo del primo del campo
Matemáticamente:
felt252_div(x, y) = x * y^(-1) mod P
Donde y^(-1) es el inverso modular de y en el campo finito.
División por cero
Intentar felt252_div(x, 0) causará un pánico en tiempo de ejecución:
*// This will panic!*
let result = felt252_div(42, 0);
Siempre valide su divisor antes de realizar una división. Una forma en la que felt252_div se asegura de que el valor de felt252 no pueda ser cero es mediante el uso de NonZero<felt252>.
NonZero
La función felt252_div requiere que su segundo argumento (el divisor) sea del tipo NonZero<felt252> en lugar de un felt252 simple. Esto evita la división por cero en tiempo de compilación.
// BE SURE TO CHANGE THE TRAIT DEFINITION ALSO
#[starknet::contract]
mod HelloStarknet {
use core::felt252_div;
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
// NOTE THE TYPE OF `y`
fn underflow_demo(self: @ContractState, x: felt252, y: NonZero<felt252>) -> felt252 {
felt252_div(x, y)
}
}
}
Resumen
Los enteros en Cairo son seguros. Todos los tipos u* e i* tienen protección contra desbordamientos y entran en pánico ante operaciones inválidas.
La conversión (casting) es estricta: .into() es seguro (siempre tiene éxito al convertir a un tipo más grande), .try_into() comprueba si hay errores (puede entrar en pánico si el tipo de destino no puede contener el valor).
La exponenciación usa .pow() a través de la importación de un trait.
Los operadores bit a bit y de comparación funcionan normalmente en todos los tipos de enteros.
felt252 es el elemento de campo nativo de Cairo sin comprobaciones de desbordamiento como los enteros. La división en felt requiere una función (felt252_div) con comprobación de ceros.
Este artículo es parte de una serie de tutoriales sobre Programación en Cairo en Starknet