Arbitrum Stylus logo

Stylus by Example

ABI Encode

The ABI Encode has 2 types which are encode and encode_packed.

  • encode will concatenate all values and add padding to fit into 32 bytes for each values.
  • encode_packed will concatenate all values in the exact byte representations without padding. (For example, encode_packed("a", "bc") == encode_packed("ab", "c"))

Suppose we have a tuple of values: (target, value, func, data, timestamp) to encode, and their alloy primitives type are (Address, U256, String, Bytes, U256).

Firstly we need to import those types we need from alloy_primitives, stylus_sdk::abi and alloc::string:

1// Import items from the SDK. The prelude contains common traits and macros.
2use stylus_sdk::{alloy_primitives::{U256, Address, FixedBytes}, abi::Bytes, prelude::*};
3// Import String from alloc
4use alloc::string::String;
1// Import items from the SDK. The prelude contains common traits and macros.
2use stylus_sdk::{alloy_primitives::{U256, Address, FixedBytes}, abi::Bytes, prelude::*};
3// Import String from alloc
4use alloc::string::String;

Secondly because we will use the method abi_encode_sequence and abi_encode_packed under alloy_sol_types to encode data, we also need to import the types from alloy_sol_types:

1// Becauce the naming of alloy_primitives and alloy_sol_types is the same, so we need to re-name the types in alloy_sol_types
2use alloy_sol_types::{sol_data::{Address as SOLAddress, String as SOLString, Bytes as SOLBytes, *}, SolType};
1// Becauce the naming of alloy_primitives and alloy_sol_types is the same, so we need to re-name the types in alloy_sol_types
2use alloy_sol_types::{sol_data::{Address as SOLAddress, String as SOLString, Bytes as SOLBytes, *}, SolType};

encode

Then encode them:

1// define sol types tuple
2type TxIdHashType = (SOLAddress, Uint<256>, SOLString, SOLBytes, Uint<256>);
3// set the tuple
4let tx_hash_data = (target, value, func, data, timestamp);
5// encode the tuple
6let tx_hash_bytes = TxIdHashType::abi_encode_sequence(&tx_hash_data);
1// define sol types tuple
2type TxIdHashType = (SOLAddress, Uint<256>, SOLString, SOLBytes, Uint<256>);
3// set the tuple
4let tx_hash_data = (target, value, func, data, timestamp);
5// encode the tuple
6let tx_hash_bytes = TxIdHashType::abi_encode_sequence(&tx_hash_data);

encode_packed

There are 2 methods to encode_packed data:

  1. encode_packed them:
1// define sol types tuple
2type TxIdHashType = (SOLAddress, Uint<256>, SOLString, SOLBytes, Uint<256>);
3// set the tuple
4let tx_hash_data = (target, value, func, data, timestamp);
5// encode the tuple
6let tx_hash_data_encode_packed = TxIdHashType::abi_encode_packed(&tx_hash_data);
1// define sol types tuple
2type TxIdHashType = (SOLAddress, Uint<256>, SOLString, SOLBytes, Uint<256>);
3// set the tuple
4let tx_hash_data = (target, value, func, data, timestamp);
5// encode the tuple
6let tx_hash_data_encode_packed = TxIdHashType::abi_encode_packed(&tx_hash_data);
  1. We can also use the following method to encode_packed them:
1let tx_hash_data_encode_packed = [&target.to_vec(), &value.to_be_bytes_vec(), func.as_bytes(), &data.to_vec(), &timestamp.to_be_bytes_vec()].concat();
1let tx_hash_data_encode_packed = [&target.to_vec(), &value.to_be_bytes_vec(), func.as_bytes(), &data.to_vec(), &timestamp.to_be_bytes_vec()].concat();

Full Example code:

src/main.rs

1// Allow `cargo stylus export-abi` to generate a main function.
2#![cfg_attr(not(feature = "export-abi"), no_main)]
3extern crate alloc;
4
5/// Use an efficient WASM allocator.
6#[global_allocator]
7static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT;
8
9
10/// Import items from the SDK. The prelude contains common traits and macros.
11use stylus_sdk::{alloy_primitives::{U256, Address, FixedBytes}, abi::Bytes, prelude::*};
12use alloc::string::String;
13// Becauce the naming of alloy_primitives and alloy_sol_types is the same, so we need to re-name the types in alloy_sol_types
14use alloy_sol_types::{sol_data::{Address as SOLAddress, String as SOLString, Bytes as SOLBytes, *}, SolType};
15use sha3::{Digest, Keccak256};
16
17// Define some persistent storage using the Solidity ABI.
18// `Encoder` will be the entrypoint.
19#[solidity_storage]
20#[entrypoint]
21pub struct Encoder;
22
23impl Encoder {
24    fn keccak256(&self, data: Bytes) -> FixedBytes<32> {
25        // prepare hasher
26        let mut hasher = Keccak256::new();
27        // populate the data
28        hasher.update(data);
29        // hashing with keccack256
30        let result = hasher.finalize();
31        // convert the result hash to FixedBytes<32>
32        let result_vec = result.to_vec();
33        FixedBytes::<32>::from_slice(&result_vec)   
34    }
35}
36
37/// Declare that `Encoder` is a contract with the following external methods.
38#[external]
39impl Encoder {
40
41     // Encode the data and hash it
42     pub fn encode(
43        &self, 
44        target: Address,
45        value: U256,
46        func: String,
47        data: Bytes,
48        timestamp: U256
49    ) -> Vec<u8> {
50        // define sol types tuple
51        type TxIdHashType = (SOLAddress, Uint<256>, SOLString, SOLBytes, Uint<256>);
52        // set the tuple
53        let tx_hash_data = (target, value, func, data, timestamp);
54        // encode the tuple
55        let tx_hash_data_encode = TxIdHashType::abi_encode_sequence(&tx_hash_data);
56        tx_hash_data_encode
57    }
58
59    // Packed encode the data and hash it, the same result with the following one
60    pub fn packed_encode(
61        &self, 
62        target: Address,
63        value: U256,
64        func: String,
65        data: Bytes,
66        timestamp: U256
67    )-> Vec<u8> {
68        // define sol types tuple
69        type TxIdHashType = (SOLAddress, Uint<256>, SOLString, SOLBytes, Uint<256>);
70        // set the tuple
71        let tx_hash_data = (target, value, func, data, timestamp);
72        // encode the tuple
73        let tx_hash_data_encode_packed = TxIdHashType::abi_encode_packed(&tx_hash_data);
74        tx_hash_data_encode_packed
75    }
76
77    // Packed encode the data and hash it, the same result with the above one
78    pub fn packed_encode_2(
79        &self, 
80        target: Address,
81        value: U256,
82        func: String,
83        data: Bytes,
84        timestamp: U256
85    )-> Vec<u8> {
86        // set the data to arrary and concat it directly
87        let tx_hash_data_encode_packed = [&target.to_vec(), &value.to_be_bytes_vec(), func.as_bytes(), &data.to_vec(), &timestamp.to_be_bytes_vec()].concat();
88        tx_hash_data_encode_packed
89    }
90
91
92    // The func example: "transfer(address,uint256)"
93    pub fn encode_with_signature(
94        &self, 
95        func: String, 
96        address: Address, 
97        amount: U256
98    ) -> Vec<u8> {
99        type TransferType = (SOLAddress, Uint<256>);
100        let tx_data = (address, amount);
101        let data = TransferType::abi_encode_sequence(&tx_data);
102        // Get function selector
103        let hashed_function_selector = self.keccak256(func.as_bytes().to_vec().into());
104        // Combine function selector and input data (use abi_packed way)
105        let calldata = [&hashed_function_selector[..4], &data].concat();
106        calldata
107    }
108
109}
1// Allow `cargo stylus export-abi` to generate a main function.
2#![cfg_attr(not(feature = "export-abi"), no_main)]
3extern crate alloc;
4
5/// Use an efficient WASM allocator.
6#[global_allocator]
7static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT;
8
9
10/// Import items from the SDK. The prelude contains common traits and macros.
11use stylus_sdk::{alloy_primitives::{U256, Address, FixedBytes}, abi::Bytes, prelude::*};
12use alloc::string::String;
13// Becauce the naming of alloy_primitives and alloy_sol_types is the same, so we need to re-name the types in alloy_sol_types
14use alloy_sol_types::{sol_data::{Address as SOLAddress, String as SOLString, Bytes as SOLBytes, *}, SolType};
15use sha3::{Digest, Keccak256};
16
17// Define some persistent storage using the Solidity ABI.
18// `Encoder` will be the entrypoint.
19#[solidity_storage]
20#[entrypoint]
21pub struct Encoder;
22
23impl Encoder {
24    fn keccak256(&self, data: Bytes) -> FixedBytes<32> {
25        // prepare hasher
26        let mut hasher = Keccak256::new();
27        // populate the data
28        hasher.update(data);
29        // hashing with keccack256
30        let result = hasher.finalize();
31        // convert the result hash to FixedBytes<32>
32        let result_vec = result.to_vec();
33        FixedBytes::<32>::from_slice(&result_vec)   
34    }
35}
36
37/// Declare that `Encoder` is a contract with the following external methods.
38#[external]
39impl Encoder {
40
41     // Encode the data and hash it
42     pub fn encode(
43        &self, 
44        target: Address,
45        value: U256,
46        func: String,
47        data: Bytes,
48        timestamp: U256
49    ) -> Vec<u8> {
50        // define sol types tuple
51        type TxIdHashType = (SOLAddress, Uint<256>, SOLString, SOLBytes, Uint<256>);
52        // set the tuple
53        let tx_hash_data = (target, value, func, data, timestamp);
54        // encode the tuple
55        let tx_hash_data_encode = TxIdHashType::abi_encode_sequence(&tx_hash_data);
56        tx_hash_data_encode
57    }
58
59    // Packed encode the data and hash it, the same result with the following one
60    pub fn packed_encode(
61        &self, 
62        target: Address,
63        value: U256,
64        func: String,
65        data: Bytes,
66        timestamp: U256
67    )-> Vec<u8> {
68        // define sol types tuple
69        type TxIdHashType = (SOLAddress, Uint<256>, SOLString, SOLBytes, Uint<256>);
70        // set the tuple
71        let tx_hash_data = (target, value, func, data, timestamp);
72        // encode the tuple
73        let tx_hash_data_encode_packed = TxIdHashType::abi_encode_packed(&tx_hash_data);
74        tx_hash_data_encode_packed
75    }
76
77    // Packed encode the data and hash it, the same result with the above one
78    pub fn packed_encode_2(
79        &self, 
80        target: Address,
81        value: U256,
82        func: String,
83        data: Bytes,
84        timestamp: U256
85    )-> Vec<u8> {
86        // set the data to arrary and concat it directly
87        let tx_hash_data_encode_packed = [&target.to_vec(), &value.to_be_bytes_vec(), func.as_bytes(), &data.to_vec(), &timestamp.to_be_bytes_vec()].concat();
88        tx_hash_data_encode_packed
89    }
90
91
92    // The func example: "transfer(address,uint256)"
93    pub fn encode_with_signature(
94        &self, 
95        func: String, 
96        address: Address, 
97        amount: U256
98    ) -> Vec<u8> {
99        type TransferType = (SOLAddress, Uint<256>);
100        let tx_data = (address, amount);
101        let data = TransferType::abi_encode_sequence(&tx_data);
102        // Get function selector
103        let hashed_function_selector = self.keccak256(func.as_bytes().to_vec().into());
104        // Combine function selector and input data (use abi_packed way)
105        let calldata = [&hashed_function_selector[..4], &data].concat();
106        calldata
107    }
108
109}

Cargo.toml

1[package]
2name = "stylus-encode-hashing"
3version = "0.1.0"
4edition = "2021"
5
6[dependencies]
7alloy-primitives = "0.7.3"
8alloy-sol-types = "0.7.3"
9mini-alloc = "0.4.2"
10stylus-sdk = "0.5.1"
11sha3 = "0.10.8"
12
13[features]
14export-abi = ["stylus-sdk/export-abi"]
15debug = ["stylus-sdk/debug"]
16
17[lib]
18crate-type = ["lib", "cdylib"]
19
20[profile.release]
21codegen-units = 1
22strip = true
23lto = true
24panic = "abort"
25opt-level = "s"
1[package]
2name = "stylus-encode-hashing"
3version = "0.1.0"
4edition = "2021"
5
6[dependencies]
7alloy-primitives = "0.7.3"
8alloy-sol-types = "0.7.3"
9mini-alloc = "0.4.2"
10stylus-sdk = "0.5.1"
11sha3 = "0.10.8"
12
13[features]
14export-abi = ["stylus-sdk/export-abi"]
15debug = ["stylus-sdk/debug"]
16
17[lib]
18crate-type = ["lib", "cdylib"]
19
20[profile.release]
21codegen-units = 1
22strip = true
23lto = true
24panic = "abort"
25opt-level = "s"