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
Boolean
Bytes
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.
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
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);
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);
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.
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:#}");
Use native Rust primitives where it makes sense and where no equivalent Alloy primitive exists.
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}");
The Stylus SDK provides this wrapper type around Vec<u8>
to represent a bytes
value in Solidity.
1let vec = vec![108, 27, 56, 87];
2let b = Bytes::from(vec);
3// Out: Stylus says: '0x6c1b3857'
4console!(String::from_utf8_lossy(b.as_slice()));
5
6let b = Bytes::from(b"Hello!".to_vec());
7// Out: Stylus says: 'Hello!'
8console!(String::from_utf8_lossy(b.as_slice()));
1let vec = vec![108, 27, 56, 87];
2let b = Bytes::from(vec);
3// Out: Stylus says: '0x6c1b3857'
4console!(String::from_utf8_lossy(b.as_slice()));
5
6let b = Bytes::from(b"Hello!".to_vec());
7// Out: Stylus says: 'Hello!'
8console!(String::from_utf8_lossy(b.as_slice()));
Note: Return the Bytes
type on your Rust function if you want to return the ABI bytes memory
type.
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}
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"