Arbitrum Stylus logo

Stylus by Example

Primitive Data Types

The Stylus SDK makes use of the popular Alloy library (from the developers of ethers-rs and Foundry) to represent various native Solidity types as Rust types and to seamlessly convert between them when needed. These are needed since there are a number of custom types (like address) and large integers that are not natively supported in Rust.

In this section, we'll focus on the following types:

  • U256
  • I256
  • Address
  • bool

More in-depth documentation about the available methods and types in the Alloy library can be found in their docs. It also helps to cross-reference with Solidity docs if you don't already have a solid understanding of those types.

Learn More

Integers

Alloy defines a set of convenient Rust types to represent the typically sized integers used in Solidity. The type U256 represents a 256-bit unsigned integer, meaning it cannot be negative. The range for a U256 number is 0 to 2^256 - 1.

Negative numbers are allowed for I types, such as I256. These represent signed integers.

  • U256 maps to uint256 ... I256 maps to int256
  • U128 maps to uint128 ... I128 maps to int128
  • ...
  • U8 maps to uint8 ... I8 maps to int8

Integer Usage

1// Unsigned
2let eight_bit: U8 = U8::from(1);
3let two_fifty_six_bit: U256 = U256::from(0xff_u64);
4
5// Out: Stylus says: '8-bit: 1 | 256-bit: 255'
6console!("8-bit: {} | 256-bit: {}", eight_bit, two_fifty_six_bit);
7
8// Signed
9let eight_bit: I8 = I8::unchecked_from(-1);
10let two_fifty_six_bit: I256 = I256::unchecked_from(0xff_u64);
11
12// Out: Stylus says: '8-bit: -1 | 256-bit: 255'
13console!("8-bit: {} | 256-bit: {}", eight_bit, two_fifty_six_bit);
1// Unsigned
2let eight_bit: U8 = U8::from(1);
3let two_fifty_six_bit: U256 = U256::from(0xff_u64);
4
5// Out: Stylus says: '8-bit: 1 | 256-bit: 255'
6console!("8-bit: {} | 256-bit: {}", eight_bit, two_fifty_six_bit);
7
8// Signed
9let eight_bit: I8 = I8::unchecked_from(-1);
10let two_fifty_six_bit: I256 = I256::unchecked_from(0xff_u64);
11
12// Out: Stylus says: '8-bit: -1 | 256-bit: 255'
13console!("8-bit: {} | 256-bit: {}", eight_bit, two_fifty_six_bit);

Expanded Integer Usage

1// Use `try_from` if you're not sure it'll fit
2let a = I256::try_from(20003000).unwrap();
3// Or parse from a string
4let b = "100".parse::<I256>().unwrap();
5// With hex characters
6let c = "-0x138f".parse::<I256>().unwrap();
7// Underscores are ignored
8let d = "1_000_000".parse::<I256>().unwrap();
9
10// Math works great
11let e = a * b + c - d;
12// Out: Stylus says: '20003000 * 100 + -5007 - 1000000 = 1999294993'
13console!("{} * {} + {} - {} = {}", a, b, c, d, e);
14
15// Useful constants
16let f = I256::MAX;
17let g = I256::MIN;
18let h = I256::ZERO;
19let i = I256::MINUS_ONE;
20
21// Stylus says: '5789...9967, -5789...9968, 0, -1'
22console!("{f}, {g}, {h}, {i}");
23// As hex: Stylus says: '0x7fff...ffff, 0x8000...0000, 0x0, 0xffff...ffff'
24console!("{:#x}, {:#x}, {:#x}, {:#x}", f, g, h, i);
1// Use `try_from` if you're not sure it'll fit
2let a = I256::try_from(20003000).unwrap();
3// Or parse from a string
4let b = "100".parse::<I256>().unwrap();
5// With hex characters
6let c = "-0x138f".parse::<I256>().unwrap();
7// Underscores are ignored
8let d = "1_000_000".parse::<I256>().unwrap();
9
10// Math works great
11let e = a * b + c - d;
12// Out: Stylus says: '20003000 * 100 + -5007 - 1000000 = 1999294993'
13console!("{} * {} + {} - {} = {}", a, b, c, d, e);
14
15// Useful constants
16let f = I256::MAX;
17let g = I256::MIN;
18let h = I256::ZERO;
19let i = I256::MINUS_ONE;
20
21// Stylus says: '5789...9967, -5789...9968, 0, -1'
22console!("{f}, {g}, {h}, {i}");
23// As hex: Stylus says: '0x7fff...ffff, 0x8000...0000, 0x0, 0xffff...ffff'
24console!("{:#x}, {:#x}, {:#x}, {:#x}", f, g, h, i);

Address

Ethereum addresses are 20 bytes in length, or 160 bits. Alloy provides a number of helper utilities for converting to addresses from strings, bytes, numbers, and addresses.

Address Usage

1// From a 20 byte slice, all 1s
2let addr1 = Address::from([0x11; 20]);
3// Out: Stylus says: '0x1111111111111111111111111111111111111111'
4console!("{addr1}");
5
6// Use the address! macro to parse a string as a checksummed address
7let addr2 = address!("d8da6bf26964af9d7eed9e03e53415d37aa96045");
8// Out: Stylus says: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'
9console!("{addr2}");
10
11// Format compressed addresses for output
12// Out: Stylus says: '0xd8dA…6045'
13console!("{addr2:#}");
1// From a 20 byte slice, all 1s
2let addr1 = Address::from([0x11; 20]);
3// Out: Stylus says: '0x1111111111111111111111111111111111111111'
4console!("{addr1}");
5
6// Use the address! macro to parse a string as a checksummed address
7let addr2 = address!("d8da6bf26964af9d7eed9e03e53415d37aa96045");
8// Out: Stylus says: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'
9console!("{addr2}");
10
11// Format compressed addresses for output
12// Out: Stylus says: '0xd8dA…6045'
13console!("{addr2:#}");

Boolean

Use native Rust primitives where it makes sense and where no equivalent Alloy primitive exists.

Boolean Usage

1let frightened: bool = true;
2// Out: Stylus says: 'Boo! Did I scare you?'
3console!("Boo! Did I scare you?");
4
5let response = match frightened {
6    true => "Yes!".to_string(),
7    false => "No!".to_string(),
8};
9
10// Out: Stylus says: 'Yes!'
11console!("{response}");
1let frightened: bool = true;
2// Out: Stylus says: 'Boo! Did I scare you?'
3console!("Boo! Did I scare you?");
4
5let response = match frightened {
6    true => "Yes!".to_string(),
7    false => "No!".to_string(),
8};
9
10// Out: Stylus says: 'Yes!'
11console!("{response}");

Boilerplate

src/lib.rs

1#![cfg_attr(not(any(feature = "export-abi", test)), no_main)]
2extern crate alloc;
3use alloc::{string::ToString, vec::Vec};
4
5use stylus_sdk::{
6    alloy_primitives::{address, Address, I256, I8, U256, U8}, console, prelude::*, ArbResult
7};
8
9#[storage]
10#[entrypoint]
11pub struct Data {
12    
13}
14
15
16#[public]
17impl Data {
18fn user_main(_input: Vec<u8>) -> ArbResult {
19    // Use native Rust primitives where they make sense
20    // and where no equivalent Alloy primitive exists
21    let frightened: bool = true;
22    // Out: Stylus says: 'Boo! Did I scare you?'
23    console!("Boo! Did I scare you?");
24
25    let _response = match frightened {
26        true => "Yes!".to_string(),
27        false => "No!".to_string(),
28    };
29
30    // Out: Stylus says: 'Yes!'
31    console!("{_response}");
32
33    // U256 stands for a 256-bit *unsigned* integer, meaning it cannot be
34    // negative. The range for a U256 number is 0 to 2^256 - 1. Alloy provides
35    // a set of unsigned integer types to represent the various sizes available
36    // in the EVM.
37    //    U256 maps to uint256
38    //    U128 maps to uint128
39    //    ...
40    //    U8 maps to uint8
41    let _eight_bit: U8 = U8::from(1);
42    let _two_fifty_six_bit: U256 = U256::from(0xff_u64);
43
44    // Out: Stylus says: '8-bit: 1 | 256-bit: 255'
45    console!("8-bit: {} | 256-bit: {}", _eight_bit, _two_fifty_six_bit);
46
47    // Negative numbers are allowed for I types. These represent signed integers.
48    //    I256 maps to int256
49    //    I128 maps to int128
50    //    ...
51    //    I8 maps to int8
52    let _eight_bit: I8 = I8::unchecked_from(-1);
53    let _two_fifty_six_bit: I256 = I256::unchecked_from(0xff_u64);
54
55    // Out: Stylus says: '8-bit: -1 | 256-bit: 255'
56    console!("8-bit: {} | 256-bit: {}", _eight_bit, _two_fifty_six_bit);
57
58    // Additional usage of integers
59
60    // Use `try_from` if you're not sure it'll fit
61    let a = I256::try_from(20003000).unwrap();
62    // Or parse from a string
63    let b = "100".parse::<I256>().unwrap();
64    // With hex characters
65    let c = "-0x138f".parse::<I256>().unwrap();
66    // Underscores are ignored
67    let d = "1_000_000".parse::<I256>().unwrap();
68
69    // Math works great
70    let _e = a * b + c - d;
71    // Out: Stylus says: '20003000 * 100 + -5007 - 1000000 = 1999294993'
72    console!("{} * {} + {} - {} = {}", a, b, c, d, _e);
73
74    // Useful constants
75    let _f = I256::MAX;
76    let _g = I256::MIN;
77    let _h = I256::ZERO;
78    let _i = I256::MINUS_ONE;
79
80    // Stylus says: '5789...9967, -5789...9968, 0, -1'
81    console!("{_f}, {_g}, {_h}, {_i}");
82    // As hex: Stylus says: '0x7fff...ffff, 0x8000...0000, 0x0, 0xffff...ffff'
83    console!("{:#x}, {:#x}, {:#x}, {:#x}", _f, _g, _h, _i);
84
85    // Ethereum addresses are 20 bytes in length, or 160 bits. Alloy provides a number of helper utilities for converting to addresses from strings, bytes, numbers, and addresses
86
87    // From a 20 byte slice, all 1s
88    let _addr1 = Address::from([0x11; 20]);
89    // Out: Stylus says: '0x1111111111111111111111111111111111111111'
90    console!("{_addr1}");
91
92    // Use the address! macro to parse a string as a checksummed address
93    let _addr2 = address!("d8da6bf26964af9d7eed9e03e53415d37aa96045");
94    // Out: Stylus says: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'
95    console!("{_addr2}");
96
97    // Format compressed addresses for output
98    // Out: Stylus says: '0xd8dA…6045'
99    console!("{_addr2:#}");
100
101    Ok(Vec::new())
102}
103}
1#![cfg_attr(not(any(feature = "export-abi", test)), no_main)]
2extern crate alloc;
3use alloc::{string::ToString, vec::Vec};
4
5use stylus_sdk::{
6    alloy_primitives::{address, Address, I256, I8, U256, U8}, console, prelude::*, ArbResult
7};
8
9#[storage]
10#[entrypoint]
11pub struct Data {
12    
13}
14
15
16#[public]
17impl Data {
18fn user_main(_input: Vec<u8>) -> ArbResult {
19    // Use native Rust primitives where they make sense
20    // and where no equivalent Alloy primitive exists
21    let frightened: bool = true;
22    // Out: Stylus says: 'Boo! Did I scare you?'
23    console!("Boo! Did I scare you?");
24
25    let _response = match frightened {
26        true => "Yes!".to_string(),
27        false => "No!".to_string(),
28    };
29
30    // Out: Stylus says: 'Yes!'
31    console!("{_response}");
32
33    // U256 stands for a 256-bit *unsigned* integer, meaning it cannot be
34    // negative. The range for a U256 number is 0 to 2^256 - 1. Alloy provides
35    // a set of unsigned integer types to represent the various sizes available
36    // in the EVM.
37    //    U256 maps to uint256
38    //    U128 maps to uint128
39    //    ...
40    //    U8 maps to uint8
41    let _eight_bit: U8 = U8::from(1);
42    let _two_fifty_six_bit: U256 = U256::from(0xff_u64);
43
44    // Out: Stylus says: '8-bit: 1 | 256-bit: 255'
45    console!("8-bit: {} | 256-bit: {}", _eight_bit, _two_fifty_six_bit);
46
47    // Negative numbers are allowed for I types. These represent signed integers.
48    //    I256 maps to int256
49    //    I128 maps to int128
50    //    ...
51    //    I8 maps to int8
52    let _eight_bit: I8 = I8::unchecked_from(-1);
53    let _two_fifty_six_bit: I256 = I256::unchecked_from(0xff_u64);
54
55    // Out: Stylus says: '8-bit: -1 | 256-bit: 255'
56    console!("8-bit: {} | 256-bit: {}", _eight_bit, _two_fifty_six_bit);
57
58    // Additional usage of integers
59
60    // Use `try_from` if you're not sure it'll fit
61    let a = I256::try_from(20003000).unwrap();
62    // Or parse from a string
63    let b = "100".parse::<I256>().unwrap();
64    // With hex characters
65    let c = "-0x138f".parse::<I256>().unwrap();
66    // Underscores are ignored
67    let d = "1_000_000".parse::<I256>().unwrap();
68
69    // Math works great
70    let _e = a * b + c - d;
71    // Out: Stylus says: '20003000 * 100 + -5007 - 1000000 = 1999294993'
72    console!("{} * {} + {} - {} = {}", a, b, c, d, _e);
73
74    // Useful constants
75    let _f = I256::MAX;
76    let _g = I256::MIN;
77    let _h = I256::ZERO;
78    let _i = I256::MINUS_ONE;
79
80    // Stylus says: '5789...9967, -5789...9968, 0, -1'
81    console!("{_f}, {_g}, {_h}, {_i}");
82    // As hex: Stylus says: '0x7fff...ffff, 0x8000...0000, 0x0, 0xffff...ffff'
83    console!("{:#x}, {:#x}, {:#x}, {:#x}", _f, _g, _h, _i);
84
85    // Ethereum addresses are 20 bytes in length, or 160 bits. Alloy provides a number of helper utilities for converting to addresses from strings, bytes, numbers, and addresses
86
87    // From a 20 byte slice, all 1s
88    let _addr1 = Address::from([0x11; 20]);
89    // Out: Stylus says: '0x1111111111111111111111111111111111111111'
90    console!("{_addr1}");
91
92    // Use the address! macro to parse a string as a checksummed address
93    let _addr2 = address!("d8da6bf26964af9d7eed9e03e53415d37aa96045");
94    // Out: Stylus says: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'
95    console!("{_addr2}");
96
97    // Format compressed addresses for output
98    // Out: Stylus says: '0xd8dA…6045'
99    console!("{_addr2:#}");
100
101    Ok(Vec::new())
102}
103}

Cargo.toml

1[package]
2name = "stylus_data_example"
3version = "0.1.7"
4edition = "2021"
5license = "MIT OR Apache-2.0"
6keywords = ["arbitrum", "ethereum", "stylus", "alloy"]
7
8[dependencies]
9alloy-primitives = "=0.7.6"
10alloy-sol-types = "=0.7.6"
11mini-alloc = "0.4.2"
12stylus-sdk = "0.6.0"
13hex = "0.4.3"
14
15[dev-dependencies]
16tokio = { version = "1.12.0", features = ["full"] }
17ethers = "2.0"
18eyre = "0.6.8"
19
20[features]
21export-abi = ["stylus-sdk/export-abi"]
22
23[lib]
24crate-type = ["lib", "cdylib"]
25
26[profile.release]
27codegen-units = 1
28strip = true
29lto = true
30panic = "abort"
31opt-level = "s"
1[package]
2name = "stylus_data_example"
3version = "0.1.7"
4edition = "2021"
5license = "MIT OR Apache-2.0"
6keywords = ["arbitrum", "ethereum", "stylus", "alloy"]
7
8[dependencies]
9alloy-primitives = "=0.7.6"
10alloy-sol-types = "=0.7.6"
11mini-alloc = "0.4.2"
12stylus-sdk = "0.6.0"
13hex = "0.4.3"
14
15[dev-dependencies]
16tokio = { version = "1.12.0", features = ["full"] }
17ethers = "2.0"
18eyre = "0.6.8"
19
20[features]
21export-abi = ["stylus-sdk/export-abi"]
22
23[lib]
24crate-type = ["lib", "cdylib"]
25
26[profile.release]
27codegen-units = 1
28strip = true
29lto = true
30panic = "abort"
31opt-level = "s"