{
+ let secret_key = SecretKey::new_from_mnemonic_phrase_with_path(phrase, path)?;
+
+ Ok(Self::new_from_private_key(secret_key, provider))
+ }
+
+ /// Creates a new wallet and stores its encrypted version in the given path.
+ pub fn new_from_keystore(
+ dir: P,
+ rng: &mut R,
+ password: S,
+ provider: Option,
+ ) -> WalletResult<(Self, String)>
+ where
+ P: AsRef,
+ R: Rng + CryptoRng + rand_core::CryptoRng,
+ S: AsRef<[u8]>,
+ {
+ let (secret, uuid) = eth_keystore::new(dir, rng, password, None)?;
+
+ let secret_key =
+ SecretKey::try_from(secret.as_slice()).expect("A new secret should be correct size");
+
+ let wallet = Self::new_from_private_key(secret_key, provider);
+
+ Ok((wallet, uuid))
+ }
+
+ /// Encrypts the wallet's private key with the given password and saves it
+ /// to the given path.
+ pub fn encrypt(&self, dir: P, password: S) -> WalletResult
+ where
+ P: AsRef,
+ S: AsRef<[u8]>,
+ {
+ let mut rng = rand::thread_rng();
+
+ Ok(eth_keystore::encrypt_key(
+ dir,
+ &mut rng,
+ *self.private_key,
+ password,
+ None,
+ )?)
+ }
+
+ /// Recreates a wallet from an encrypted JSON wallet given the provided path and password.
+ pub fn load_keystore(
+ keypath: P,
+ password: S,
+ provider: Option,
+ ) -> WalletResult
+ where
+ P: AsRef,
+ S: AsRef<[u8]>,
+ {
+ let secret = eth_keystore::decrypt_key(keypath, password)?;
+ let secret_key = SecretKey::try_from(secret.as_slice())
+ .expect("Decrypted key should have a correct size");
+ Ok(Self::new_from_private_key(secret_key, provider))
+ }
+}
+
+impl ViewOnlyAccount for WalletUnlocked {
+ fn address(&self) -> &Bech32Address {
+ self.wallet.address()
+ }
+
+ fn try_provider(&self) -> AccountResult<&Provider> {
+ self.provider.as_ref().ok_or(AccountError::no_provider())
+ }
+}
+
+#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
+impl Account for WalletUnlocked {
+ /// Returns a vector consisting of `Input::Coin`s and `Input::Message`s for the given
+ /// asset ID and amount. The `witness_index` is the position of the witness (signature)
+ /// in the transaction's list of witnesses. In the validation process, the node will
+ /// use the witness at this index to validate the coins returned by this method.
+ async fn get_asset_inputs_for_amount(
+ &self,
+ asset_id: AssetId,
+ amount: u64,
+ ) -> Result> {
+ Ok(self
+ .get_spendable_resources(asset_id, amount)
+ .await?
+ .into_iter()
+ .map(Input::resource_signed)
+ .collect::>())
+ }
+
+ async fn add_fee_resources(
+ &self,
+ mut tb: Tb,
+ previous_base_amount: u64,
+ ) -> Result {
+ let consensus_parameters = self.try_provider()?.consensus_parameters();
+ tb = tb.with_consensus_parameters(consensus_parameters);
+
+ self.sign_transaction(&mut tb);
+
+ let new_base_amount =
+ calculate_base_amount_with_fee(&tb, &consensus_parameters, previous_base_amount)?;
+
+ let new_base_inputs = self
+ .get_asset_inputs_for_amount(BASE_ASSET_ID, new_base_amount)
+ .await?;
+
+ adjust_inputs(&mut tb, new_base_inputs);
+ adjust_outputs(&mut tb, self.address(), new_base_amount);
+
+ tb.build()
+ }
+}
+
+#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
+#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
+impl Signer for WalletUnlocked {
+ type Error = WalletError;
+ async fn sign_message>(
+ &self,
+ message: S,
+ ) -> WalletResult {
+ let message = Message::new(message);
+ let sig = Signature::sign(&self.private_key, &message);
+ Ok(sig)
+ }
+
+ fn sign_transaction(&self, tb: &mut impl TransactionBuilder) {
+ tb.add_unresolved_signature(self.address().clone(), self.private_key);
+ }
+}
+
+impl fmt::Debug for Wallet {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("Wallet")
+ .field("address", &self.address)
+ .finish()
+ }
+}
+
+impl ops::Deref for WalletUnlocked {
+ type Target = Wallet;
+ fn deref(&self) -> &Self::Target {
+ &self.wallet
+ }
+}
+
+/// Generates a random mnemonic phrase given a random number generator and the number of words to
+/// generate, `count`.
+pub fn generate_mnemonic_phrase(rng: &mut R, count: usize) -> WalletResult {
+ Ok(fuel_crypto::generate_mnemonic_phrase(rng, count)?)
+}
+
+#[cfg(test)]
+mod tests {
+ use tempfile::tempdir;
+
+ use super::*;
+
+ #[tokio::test]
+ async fn encrypted_json_keystore() -> Result<()> {
+ let dir = tempdir()?;
+ let mut rng = rand::thread_rng();
+
+ // Create a wallet to be stored in the keystore.
+ let (wallet, uuid) = WalletUnlocked::new_from_keystore(&dir, &mut rng, "password", None)?;
+
+ // sign a message using the above key.
+ let message = "Hello there!";
+ let signature = wallet.sign_message(message).await?;
+
+ // Read from the encrypted JSON keystore and decrypt it.
+ let path = Path::new(dir.path()).join(uuid);
+ let recovered_wallet = WalletUnlocked::load_keystore(path.clone(), "password", None)?;
+
+ // Sign the same message as before and assert that the signature is the same.
+ let signature2 = recovered_wallet.sign_message(message).await?;
+ assert_eq!(signature, signature2);
+
+ // Remove tempdir.
+ assert!(std::fs::remove_file(&path).is_ok());
+ Ok(())
+ }
+
+ #[tokio::test]
+ async fn mnemonic_generation() -> Result<()> {
+ let mnemonic = generate_mnemonic_phrase(&mut rand::thread_rng(), 12)?;
+
+ let _wallet = WalletUnlocked::new_from_mnemonic_phrase(&mnemonic, None)?;
+ Ok(())
+ }
+
+ #[tokio::test]
+ async fn wallet_from_mnemonic_phrase() -> Result<()> {
+ let phrase =
+ "oblige salon price punch saddle immune slogan rare snap desert retire surprise";
+
+ // Create first account from mnemonic phrase.
+ let wallet =
+ WalletUnlocked::new_from_mnemonic_phrase_with_path(phrase, None, "m/44'/60'/0'/0/0")?;
+
+ let expected_plain_address =
+ "df9d0e6c6c5f5da6e82e5e1a77974af6642bdb450a10c43f0c6910a212600185";
+ let expected_address = "fuel1m7wsumrvtaw6d6pwtcd809627ejzhk69pggvg0cvdyg2yynqqxzseuzply";
+
+ assert_eq!(wallet.address().hash().to_string(), expected_plain_address);
+ assert_eq!(wallet.address().to_string(), expected_address);
+
+ // Create a second account from the same phrase.
+ let wallet2 =
+ WalletUnlocked::new_from_mnemonic_phrase_with_path(phrase, None, "m/44'/60'/1'/0/0")?;
+
+ let expected_second_plain_address =
+ "261191b0164a24fd0fd51566ec5e5b0b9ba8fb2d42dc9cf7dbbd6f23d2742759";
+ let expected_second_address =
+ "fuel1ycgervqkfgj06r74z4nwchjmpwd637edgtwfea7mh4hj85n5yavszjk4cc";
+
+ assert_eq!(
+ wallet2.address().hash().to_string(),
+ expected_second_plain_address
+ );
+ assert_eq!(wallet2.address().to_string(), expected_second_address);
+
+ Ok(())
+ }
+
+ #[tokio::test]
+ async fn encrypt_and_store_wallet_from_mnemonic() -> Result<()> {
+ let dir = tempdir()?;
+
+ let phrase =
+ "oblige salon price punch saddle immune slogan rare snap desert retire surprise";
+
+ // Create first account from mnemonic phrase.
+ let wallet =
+ WalletUnlocked::new_from_mnemonic_phrase_with_path(phrase, None, "m/44'/60'/0'/0/0")?;
+
+ let uuid = wallet.encrypt(&dir, "password")?;
+
+ let path = Path::new(dir.path()).join(uuid);
+
+ let recovered_wallet = WalletUnlocked::load_keystore(&path, "password", None)?;
+
+ assert_eq!(wallet.address(), recovered_wallet.address());
+
+ // Remove tempdir.
+ assert!(std::fs::remove_file(&path).is_ok());
+ Ok(())
+ }
+}
diff --git a/docs/beta-4/fuels-rs/packages/fuels-core/Cargo.toml b/docs/beta-4/fuels-rs/packages/fuels-core/Cargo.toml
new file mode 100644
index 00000000..f2e759eb
--- /dev/null
+++ b/docs/beta-4/fuels-rs/packages/fuels-core/Cargo.toml
@@ -0,0 +1,39 @@
+[package]
+name = "fuels-core"
+version = { workspace = true }
+authors = { workspace = true }
+edition = { workspace = true }
+homepage = { workspace = true }
+license = { workspace = true }
+repository = { workspace = true }
+rust-version = { workspace = true }
+description = "Fuel Rust SDK core."
+
+[dependencies]
+bech32 = { workspace = true }
+chrono = { workspace = true }
+fuel-abi-types = { workspace = true }
+fuel-asm = { workspace = true }
+fuel-core = { workspace = true, default-features = false, optional = true }
+fuel-core-chain-config = { workspace = true }
+fuel-core-client = { workspace = true, optional = true }
+fuel-crypto = { workspace = true }
+fuel-tx = { workspace = true }
+fuel-types = { workspace = true, features = ["default"] }
+fuel-vm = { workspace = true }
+fuels-macros = { workspace = true }
+hex = { workspace = true, features = ["std"] }
+itertools = { workspace = true }
+proc-macro2 = { workspace = true }
+regex = { workspace = true }
+serde = { workspace = true, features = ["derive"] }
+serde_json = { workspace = true, default-features = true }
+sha2 = { workspace = true }
+strum = { workspace = true }
+strum_macros = { workspace = true }
+thiserror = { workspace = true, default-features = false }
+uint = { version = "0.9.5", default-features = false }
+
+[features]
+default = ["std"]
+std = ["dep:fuel-core-client"]
diff --git a/docs/beta-4/fuels-rs/packages/fuels-core/src/codec.rs b/docs/beta-4/fuels-rs/packages/fuels-core/src/codec.rs
new file mode 100644
index 00000000..c70f4496
--- /dev/null
+++ b/docs/beta-4/fuels-rs/packages/fuels-core/src/codec.rs
@@ -0,0 +1,73 @@
+mod abi_decoder;
+mod abi_encoder;
+mod function_selector;
+
+pub use abi_decoder::*;
+pub use abi_encoder::*;
+pub use function_selector::*;
+
+use crate::{
+ traits::{Parameterize, Tokenizable},
+ types::errors::Result,
+};
+
+pub fn try_from_bytes(bytes: &[u8]) -> Result
+where
+ T: Parameterize + Tokenizable,
+{
+ let token = ABIDecoder::decode_single(&T::param_type(), bytes)?;
+
+ T::from_token(token)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{
+ constants::WORD_SIZE,
+ types::{Address, AssetId, ContractId},
+ };
+
+ #[test]
+ fn can_convert_bytes_into_tuple() -> Result<()> {
+ let tuple_in_bytes: Vec = vec![0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2];
+
+ let the_tuple: (u64, u32) = try_from_bytes(&tuple_in_bytes)?;
+
+ assert_eq!(the_tuple, (1, 2));
+
+ Ok(())
+ }
+
+ #[test]
+ fn can_convert_all_from_bool_to_u64() -> Result<()> {
+ let bytes: Vec = vec![0xFF; WORD_SIZE];
+
+ assert!(try_from_bytes::(&bytes)?);
+ assert_eq!(try_from_bytes::(&bytes)?, u8::MAX);
+ assert_eq!(try_from_bytes::(&bytes)?, u16::MAX);
+ assert_eq!(try_from_bytes::(&bytes)?, u32::MAX);
+ assert_eq!(try_from_bytes::(&bytes)?, u64::MAX);
+
+ Ok(())
+ }
+
+ #[test]
+ fn can_convert_native_types() -> Result<()> {
+ let bytes = [0xFF; 32];
+
+ assert_eq!(
+ try_from_bytes::(&bytes)?,
+ Address::new(bytes.as_slice().try_into()?)
+ );
+ assert_eq!(
+ try_from_bytes::(&bytes)?,
+ ContractId::new(bytes.as_slice().try_into()?)
+ );
+ assert_eq!(
+ try_from_bytes::(&bytes)?,
+ AssetId::new(bytes.as_slice().try_into()?)
+ );
+ Ok(())
+ }
+}
diff --git a/docs/beta-4/fuels-rs/packages/fuels-core/src/codec/abi_decoder.rs b/docs/beta-4/fuels-rs/packages/fuels-core/src/codec/abi_decoder.rs
new file mode 100644
index 00000000..ecacff9e
--- /dev/null
+++ b/docs/beta-4/fuels-rs/packages/fuels-core/src/codec/abi_decoder.rs
@@ -0,0 +1,810 @@
+use std::{convert::TryInto, str};
+
+use fuel_types::bytes::padded_len_usize;
+
+use crate::{
+ constants::WORD_SIZE,
+ traits::Tokenizable,
+ types::{
+ enum_variants::EnumVariants,
+ errors::{error, Error, Result},
+ param_types::ParamType,
+ StaticStringToken, Token, U256,
+ },
+};
+
+const U128_BYTES_SIZE: usize = 2 * WORD_SIZE;
+const U256_BYTES_SIZE: usize = 4 * WORD_SIZE;
+const B256_BYTES_SIZE: usize = 4 * WORD_SIZE;
+
+#[derive(Debug, Clone)]
+struct DecodeResult {
+ token: Token,
+ bytes_read: usize,
+}
+
+pub struct ABIDecoder;
+
+impl ABIDecoder {
+ /// Decodes types described by `param_types` into their respective `Token`s
+ /// using the data in `bytes` and `receipts`.
+ ///
+ /// # Arguments
+ ///
+ /// * `param_types`: The ParamType's of the types we expect are encoded
+ /// inside `bytes` and `receipts`.
+ /// * `bytes`: The bytes to be used in the decoding process.
+ /// # Examples
+ ///
+ /// ```
+ /// use fuels_core::codec::ABIDecoder;
+ /// use fuels_core::types::{param_types::ParamType, Token};
+ ///
+ /// let tokens = ABIDecoder::decode(&[ParamType::U8, ParamType::U8], &[0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,2]).unwrap();
+ ///
+ /// assert_eq!(tokens, vec![Token::U8(1), Token::U8(2)])
+ /// ```
+ pub fn decode(param_types: &[ParamType], bytes: &[u8]) -> Result> {
+ let (tokens, _) = Self::decode_multiple(param_types, bytes)?;
+
+ Ok(tokens)
+ }
+
+ /// The same as `decode` just for a single type. Used in most cases since
+ /// contract functions can only return one type.
+ pub fn decode_single(param_type: &ParamType, bytes: &[u8]) -> Result {
+ Ok(Self::decode_param(param_type, bytes)?.token)
+ }
+
+ fn decode_param(param_type: &ParamType, bytes: &[u8]) -> Result {
+ if param_type.contains_nested_heap_types() {
+ return Err(error!(
+ InvalidData,
+ "Type {param_type:?} contains nested heap types (`Vec` or `Bytes`), this is not supported."
+ ));
+ }
+ match param_type {
+ ParamType::Unit => Self::decode_unit(bytes),
+ ParamType::U8 => Self::decode_u8(bytes),
+ ParamType::U16 => Self::decode_u16(bytes),
+ ParamType::U32 => Self::decode_u32(bytes),
+ ParamType::U64 => Self::decode_u64(bytes),
+ ParamType::U128 => Self::decode_u128(bytes),
+ ParamType::U256 => Self::decode_u256(bytes),
+ ParamType::Bool => Self::decode_bool(bytes),
+ ParamType::B256 => Self::decode_b256(bytes),
+ ParamType::RawSlice => Self::decode_raw_slice(bytes),
+ ParamType::StringSlice => Self::decode_string_slice(bytes),
+ ParamType::StringArray(len) => Self::decode_string_array(bytes, *len),
+ ParamType::Array(ref t, length) => Self::decode_array(t, bytes, *length),
+ ParamType::Struct { fields, .. } => Self::decode_struct(fields, bytes),
+ ParamType::Enum { variants, .. } => Self::decode_enum(bytes, variants),
+ ParamType::Tuple(types) => Self::decode_tuple(types, bytes),
+ ParamType::Vector(param_type) => Self::decode_vector(param_type, bytes),
+ ParamType::Bytes => Self::decode_bytes(bytes),
+ ParamType::String => Self::decode_std_string(bytes),
+ }
+ }
+
+ fn decode_bytes(bytes: &[u8]) -> Result {
+ Ok(DecodeResult {
+ token: Token::Bytes(bytes.to_vec()),
+ bytes_read: bytes.len(),
+ })
+ }
+
+ fn decode_std_string(bytes: &[u8]) -> Result {
+ Ok(DecodeResult {
+ token: Token::String(str::from_utf8(bytes)?.to_string()),
+ bytes_read: bytes.len(),
+ })
+ }
+
+ fn decode_vector(param_type: &ParamType, bytes: &[u8]) -> Result {
+ let num_of_elements = ParamType::calculate_num_of_elements(param_type, bytes.len())?;
+ let (tokens, bytes_read) = Self::decode_multiple(vec![param_type; num_of_elements], bytes)?;
+
+ Ok(DecodeResult {
+ token: Token::Vector(tokens),
+ bytes_read,
+ })
+ }
+
+ fn decode_tuple(param_types: &[ParamType], bytes: &[u8]) -> Result {
+ let (tokens, bytes_read) = Self::decode_multiple(param_types, bytes)?;
+
+ Ok(DecodeResult {
+ token: Token::Tuple(tokens),
+ bytes_read,
+ })
+ }
+
+ fn decode_struct(param_types: &[ParamType], bytes: &[u8]) -> Result {
+ let (tokens, bytes_read) = Self::decode_multiple(param_types, bytes)?;
+
+ Ok(DecodeResult {
+ token: Token::Struct(tokens),
+ bytes_read,
+ })
+ }
+
+ fn decode_multiple<'a>(
+ param_types: impl IntoIterator- ,
+ bytes: &[u8],
+ ) -> Result<(Vec, usize)> {
+ let mut results = vec![];
+
+ let mut bytes_read = 0;
+
+ for param_type in param_types {
+ let res = Self::decode_param(param_type, skip(bytes, bytes_read)?)?;
+ bytes_read += res.bytes_read;
+ results.push(res.token);
+ }
+
+ Ok((results, bytes_read))
+ }
+
+ fn decode_array(param_type: &ParamType, bytes: &[u8], length: usize) -> Result {
+ let (tokens, bytes_read) = Self::decode_multiple(&vec![param_type.clone(); length], bytes)?;
+
+ Ok(DecodeResult {
+ token: Token::Array(tokens),
+ bytes_read,
+ })
+ }
+
+ fn decode_raw_slice(bytes: &[u8]) -> Result {
+ let raw_slice_element = ParamType::U64;
+ let num_of_elements =
+ ParamType::calculate_num_of_elements(&raw_slice_element, bytes.len())?;
+ let (tokens, bytes_read) =
+ Self::decode_multiple(&vec![ParamType::U64; num_of_elements], bytes)?;
+ let elements = tokens
+ .into_iter()
+ .map(u64::from_token)
+ .collect::>>()
+ .map_err(|e| error!(InvalidData, "{e}"))?;
+
+ Ok(DecodeResult {
+ token: Token::RawSlice(elements),
+ bytes_read,
+ })
+ }
+
+ fn decode_string_slice(bytes: &[u8]) -> Result {
+ let decoded = str::from_utf8(bytes)?;
+
+ Ok(DecodeResult {
+ token: Token::StringSlice(StaticStringToken::new(decoded.into(), None)),
+ bytes_read: decoded.len(),
+ })
+ }
+
+ fn decode_string_array(bytes: &[u8], length: usize) -> Result {
+ let encoded_len = padded_len_usize(length);
+ let encoded_str = peek(bytes, encoded_len)?;
+
+ let decoded = str::from_utf8(&encoded_str[..length])?;
+ let result = DecodeResult {
+ token: Token::StringArray(StaticStringToken::new(decoded.into(), Some(length))),
+ bytes_read: encoded_len,
+ };
+ Ok(result)
+ }
+
+ fn decode_b256(bytes: &[u8]) -> Result {
+ Ok(DecodeResult {
+ token: Token::B256(*peek_fixed::<32>(bytes)?),
+ bytes_read: B256_BYTES_SIZE,
+ })
+ }
+
+ fn decode_bool(bytes: &[u8]) -> Result {
+ // Grab last byte of the word and compare it to 0x00
+ let b = peek_u8(bytes)? != 0u8;
+
+ let result = DecodeResult {
+ token: Token::Bool(b),
+ bytes_read: WORD_SIZE,
+ };
+
+ Ok(result)
+ }
+
+ fn decode_u128(bytes: &[u8]) -> Result {
+ Ok(DecodeResult {
+ token: Token::U128(peek_u128(bytes)?),
+ bytes_read: U128_BYTES_SIZE,
+ })
+ }
+
+ fn decode_u256(bytes: &[u8]) -> Result {
+ Ok(DecodeResult {
+ token: Token::U256(peek_u256(bytes)?),
+ bytes_read: U256_BYTES_SIZE,
+ })
+ }
+
+ fn decode_u64(bytes: &[u8]) -> Result {
+ Ok(DecodeResult {
+ token: Token::U64(peek_u64(bytes)?),
+ bytes_read: WORD_SIZE,
+ })
+ }
+
+ fn decode_u32(bytes: &[u8]) -> Result {
+ Ok(DecodeResult {
+ token: Token::U32(peek_u32(bytes)?),
+ bytes_read: WORD_SIZE,
+ })
+ }
+
+ fn decode_u16(bytes: &[u8]) -> Result {
+ Ok(DecodeResult {
+ token: Token::U16(peek_u16(bytes)?),
+ bytes_read: WORD_SIZE,
+ })
+ }
+
+ fn decode_u8(bytes: &[u8]) -> Result {
+ Ok(DecodeResult {
+ token: Token::U8(peek_u8(bytes)?),
+ bytes_read: WORD_SIZE,
+ })
+ }
+
+ fn decode_unit(bytes: &[u8]) -> Result {
+ // We don't need the data, we're doing this purely as a bounds
+ // check.
+ peek_fixed::(bytes)?;
+ Ok(DecodeResult {
+ token: Token::Unit,
+ bytes_read: WORD_SIZE,
+ })
+ }
+
+ /// The encoding follows the ABI specs defined
+ /// [here](https://github.com/FuelLabs/fuel-specs/blob/1be31f70c757d8390f74b9e1b3beb096620553eb/specs/protocol/abi.md)
+ ///
+ /// # Arguments
+ ///
+ /// * `data`: slice of encoded data on whose beginning we're expecting an encoded enum
+ /// * `variants`: all types that this particular enum type could hold
+ fn decode_enum(bytes: &[u8], variants: &EnumVariants) -> Result {
+ let enum_width = variants.compute_encoding_width_of_enum();
+
+ let discriminant = peek_u32(bytes)? as u8;
+ let selected_variant = variants.param_type_of_variant(discriminant)?;
+
+ let words_to_skip = enum_width - selected_variant.compute_encoding_width();
+ let enum_content_bytes = skip(bytes, words_to_skip * WORD_SIZE)?;
+ let result = Self::decode_token_in_enum(enum_content_bytes, variants, selected_variant)?;
+
+ let selector = Box::new((discriminant, result.token, variants.clone()));
+ Ok(DecodeResult {
+ token: Token::Enum(selector),
+ bytes_read: enum_width * WORD_SIZE,
+ })
+ }
+
+ fn decode_token_in_enum(
+ bytes: &[u8],
+ variants: &EnumVariants,
+ selected_variant: &ParamType,
+ ) -> Result {
+ // Enums that contain only Units as variants have only their discriminant encoded.
+ // Because of this we construct the Token::Unit rather than calling `decode_param`
+ if variants.only_units_inside() {
+ Ok(DecodeResult {
+ token: Token::Unit,
+ bytes_read: 0,
+ })
+ } else {
+ Self::decode_param(selected_variant, bytes)
+ }
+ }
+}
+
+fn peek_u128(bytes: &[u8]) -> Result {
+ let slice = peek_fixed::(bytes)?;
+ Ok(u128::from_be_bytes(*slice))
+}
+
+fn peek_u256(bytes: &[u8]) -> Result {
+ let slice = peek_fixed::(bytes)?;
+ Ok(U256::from(*slice))
+}
+
+fn peek_u64(bytes: &[u8]) -> Result {
+ let slice = peek_fixed::(bytes)?;
+ Ok(u64::from_be_bytes(*slice))
+}
+
+fn peek_u32(bytes: &[u8]) -> Result {
+ const BYTES: usize = std::mem::size_of::();
+
+ let slice = peek_fixed::(bytes)?;
+ let bytes = slice[WORD_SIZE - BYTES..]
+ .try_into()
+ .expect("peek_u32: You must use a slice containing exactly 4B.");
+ Ok(u32::from_be_bytes(bytes))
+}
+
+fn peek_u16(bytes: &[u8]) -> Result {
+ const BYTES: usize = std::mem::size_of::();
+
+ let slice = peek_fixed::(bytes)?;
+ let bytes = slice[WORD_SIZE - BYTES..]
+ .try_into()
+ .expect("peek_u16: You must use a slice containing exactly 2B.");
+ Ok(u16::from_be_bytes(bytes))
+}
+
+fn peek_u8(bytes: &[u8]) -> Result {
+ const BYTES: usize = std::mem::size_of::();
+
+ let slice = peek_fixed::(bytes)?;
+ let bytes = slice[WORD_SIZE - BYTES..]
+ .try_into()
+ .expect("peek_u8: You must use a slice containing exactly 1B.");
+ Ok(u8::from_be_bytes(bytes))
+}
+
+fn peek_fixed(data: &[u8]) -> Result<&[u8; LEN]> {
+ let slice_w_correct_length = peek(data, LEN)?;
+ Ok(<&[u8; LEN]>::try_from(slice_w_correct_length)
+ .expect("peek(data,len) must return a slice of length `len` or error out"))
+}
+
+fn peek(data: &[u8], len: usize) -> Result<&[u8]> {
+ if len > data.len() {
+ Err(error!(
+ InvalidData,
+ "tried to read {len} bytes from response but only had {} remaining!",
+ data.len()
+ ))
+ } else {
+ Ok(&data[..len])
+ }
+}
+
+fn skip(slice: &[u8], num_bytes: usize) -> Result<&[u8]> {
+ if num_bytes > slice.len() {
+ Err(error!(
+ InvalidData,
+ "tried to consume {num_bytes} bytes from response but only had {} remaining!",
+ slice.len()
+ ))
+ } else {
+ Ok(&slice[num_bytes..])
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::vec;
+
+ use super::*;
+
+ #[test]
+ fn decode_int() -> Result<()> {
+ let data = [0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff];
+
+ let decoded = ABIDecoder::decode_single(&ParamType::U32, &data)?;
+
+ assert_eq!(decoded, Token::U32(u32::MAX));
+ Ok(())
+ }
+
+ #[test]
+ fn decode_multiple_int() -> Result<()> {
+ let types = vec![
+ ParamType::U32,
+ ParamType::U8,
+ ParamType::U16,
+ ParamType::U64,
+ ];
+ let data = [
+ 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff,
+ ];
+
+ let decoded = ABIDecoder::decode(&types, &data)?;
+
+ let expected = vec![
+ Token::U32(u32::MAX),
+ Token::U8(u8::MAX),
+ Token::U16(u16::MAX),
+ Token::U64(u64::MAX),
+ ];
+ assert_eq!(decoded, expected);
+ Ok(())
+ }
+
+ #[test]
+ fn decode_bool() -> Result<()> {
+ let types = vec![ParamType::Bool, ParamType::Bool];
+ let data = [
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x00,
+ ];
+
+ let decoded = ABIDecoder::decode(&types, &data)?;
+
+ let expected = vec![Token::Bool(true), Token::Bool(false)];
+
+ assert_eq!(decoded, expected);
+ Ok(())
+ }
+
+ #[test]
+ fn decode_b256() -> Result<()> {
+ let data = [
+ 0xd5, 0x57, 0x9c, 0x46, 0xdf, 0xcc, 0x7f, 0x18, 0x20, 0x70, 0x13, 0xe6, 0x5b, 0x44,
+ 0xe4, 0xcb, 0x4e, 0x2c, 0x22, 0x98, 0xf4, 0xac, 0x45, 0x7b, 0xa8, 0xf8, 0x27, 0x43,
+ 0xf3, 0x1e, 0x93, 0xb,
+ ];
+
+ let decoded = ABIDecoder::decode_single(&ParamType::B256, &data)?;
+
+ assert_eq!(decoded, Token::B256(data));
+ Ok(())
+ }
+
+ #[test]
+ fn decode_string_array() -> Result<()> {
+ let types = vec![ParamType::StringArray(23), ParamType::StringArray(5)];
+ let data = [
+ 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, // This is
+ 0x61, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x73, // a full s
+ 0x65, 0x6e, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x00, // entence
+ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00, 0x00, // Hello
+ ];
+
+ let decoded = ABIDecoder::decode(&types, &data)?;
+
+ let expected = vec![
+ Token::StringArray(StaticStringToken::new(
+ "This is a full sentence".into(),
+ Some(23),
+ )),
+ Token::StringArray(StaticStringToken::new("Hello".into(), Some(5))),
+ ];
+
+ assert_eq!(decoded, expected);
+ Ok(())
+ }
+
+ #[test]
+ fn decode_string_slice() -> Result<()> {
+ let types = vec![ParamType::StringSlice];
+ let data = [
+ 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, // This is
+ 0x61, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x73, // a full s
+ 0x65, 0x6e, 0x74, 0x65, 0x6e, 0x63, 0x65, // entence
+ ];
+
+ let decoded = ABIDecoder::decode(&types, &data)?;
+
+ let expected = vec![Token::StringSlice(StaticStringToken::new(
+ "This is a full sentence".into(),
+ None,
+ ))];
+
+ assert_eq!(decoded, expected);
+ Ok(())
+ }
+
+ #[test]
+ fn decode_array() -> Result<()> {
+ // Create a parameter type for u8[2].
+ let types = vec![ParamType::Array(Box::new(ParamType::U8), 2)];
+ let data = [
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2a,
+ ];
+
+ let decoded = ABIDecoder::decode(&types, &data)?;
+
+ let expected = vec![Token::Array(vec![Token::U8(255), Token::U8(42)])];
+ assert_eq!(decoded, expected);
+ Ok(())
+ }
+
+ #[test]
+ fn decode_struct() -> Result<()> {
+ // struct MyStruct {
+ // foo: u8,
+ // bar: bool,
+ // }
+
+ let data = [
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
+ ];
+ let param_type = ParamType::Struct {
+ fields: vec![ParamType::U8, ParamType::Bool],
+ generics: vec![],
+ };
+
+ let decoded = ABIDecoder::decode_single(¶m_type, &data)?;
+
+ let expected = Token::Struct(vec![Token::U8(1), Token::Bool(true)]);
+
+ assert_eq!(decoded, expected);
+ Ok(())
+ }
+
+ #[test]
+ fn decode_bytes() -> Result<()> {
+ let data = [0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05];
+ let decoded = ABIDecoder::decode_single(&ParamType::Bytes, &data)?;
+
+ let expected = Token::Bytes(data.to_vec());
+
+ assert_eq!(decoded, expected);
+ Ok(())
+ }
+
+ #[test]
+ fn decode_enum() -> Result<()> {
+ // enum MyEnum {
+ // x: u32,
+ // y: bool,
+ // }
+
+ let types = vec![ParamType::U32, ParamType::Bool];
+ let inner_enum_types = EnumVariants::new(types)?;
+ let types = vec![ParamType::Enum {
+ variants: inner_enum_types.clone(),
+ generics: vec![],
+ }];
+
+ // "0" discriminant and 42 enum value
+ let data = [
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2a,
+ ];
+
+ let decoded = ABIDecoder::decode(&types, &data)?;
+
+ let expected = vec![Token::Enum(Box::new((0, Token::U32(42), inner_enum_types)))];
+ assert_eq!(decoded, expected);
+ Ok(())
+ }
+
+ #[test]
+ fn decoder_will_skip_enum_padding_and_decode_next_arg() -> Result<()> {
+ // struct MyStruct {
+ // par1: MyEnum,
+ // par2: u32
+ // }
+
+ // enum MyEnum {
+ // x: b256,
+ // y: u32,
+ // }
+
+ let types = vec![ParamType::B256, ParamType::U32];
+ let inner_enum_types = EnumVariants::new(types)?;
+
+ let fields = vec![
+ ParamType::Enum {
+ variants: inner_enum_types.clone(),
+ generics: vec![],
+ },
+ ParamType::U32,
+ ];
+ let struct_type = ParamType::Struct {
+ fields,
+ generics: vec![],
+ };
+
+ let enum_discriminant_enc = vec![0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1];
+ let enum_data_enc = vec![0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x30, 0x39];
+ // this padding is due to the biggest variant of MyEnum being 3 WORDs bigger than the chosen variant
+ let enum_padding_enc = vec![0x0; 3 * WORD_SIZE];
+ let struct_par2_enc = vec![0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD4, 0x31];
+ let data: Vec = vec![
+ enum_discriminant_enc,
+ enum_padding_enc,
+ enum_data_enc,
+ struct_par2_enc,
+ ]
+ .into_iter()
+ .flatten()
+ .collect();
+
+ let decoded = ABIDecoder::decode_single(&struct_type, &data)?;
+
+ let expected = Token::Struct(vec![
+ Token::Enum(Box::new((1, Token::U32(12345), inner_enum_types))),
+ Token::U32(54321),
+ ]);
+ assert_eq!(decoded, expected);
+ Ok(())
+ }
+
+ #[test]
+ fn decode_nested_struct() -> Result<()> {
+ // struct Foo {
+ // x: u16,
+ // y: Bar,
+ // }
+ //
+ // struct Bar {
+ // a: bool,
+ // b: u8[2],
+ // }
+
+ let fields = vec![
+ ParamType::U16,
+ ParamType::Struct {
+ fields: vec![
+ ParamType::Bool,
+ ParamType::Array(Box::new(ParamType::U8), 2),
+ ],
+ generics: vec![],
+ },
+ ];
+ let nested_struct = ParamType::Struct {
+ fields,
+ generics: vec![],
+ };
+
+ let data = [
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2,
+ ];
+
+ let decoded = ABIDecoder::decode_single(&nested_struct, &data)?;
+
+ let my_nested_struct = vec![
+ Token::U16(10),
+ Token::Struct(vec![
+ Token::Bool(true),
+ Token::Array(vec![Token::U8(1), Token::U8(2)]),
+ ]),
+ ];
+
+ assert_eq!(decoded, Token::Struct(my_nested_struct));
+ Ok(())
+ }
+
+ #[test]
+ fn decode_comprehensive() -> Result<()> {
+ // struct Foo {
+ // x: u16,
+ // y: Bar,
+ // }
+ //
+ // struct Bar {
+ // a: bool,
+ // b: u8[2],
+ // }
+
+ // fn: long_function(Foo,u8[2],b256,str[3],str)
+
+ // Parameters
+ let fields = vec![
+ ParamType::U16,
+ ParamType::Struct {
+ fields: vec![
+ ParamType::Bool,
+ ParamType::Array(Box::new(ParamType::U8), 2),
+ ],
+ generics: vec![],
+ },
+ ];
+ let nested_struct = ParamType::Struct {
+ fields,
+ generics: vec![],
+ };
+
+ let u8_arr = ParamType::Array(Box::new(ParamType::U8), 2);
+ let b256 = ParamType::B256;
+ let s = ParamType::StringArray(3);
+ let ss = ParamType::StringSlice;
+
+ let types = [nested_struct, u8_arr, b256, s, ss];
+
+ let bytes = [
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, // foo.x == 10u16
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // foo.y.a == true
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // foo.b.0 == 1u8
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, // foo.b.1 == 2u8
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // u8[2].0 == 1u8
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, // u8[2].0 == 2u8
+ 0xd5, 0x57, 0x9c, 0x46, 0xdf, 0xcc, 0x7f, 0x18, // b256
+ 0x20, 0x70, 0x13, 0xe6, 0x5b, 0x44, 0xe4, 0xcb, // b256
+ 0x4e, 0x2c, 0x22, 0x98, 0xf4, 0xac, 0x45, 0x7b, // b256
+ 0xa8, 0xf8, 0x27, 0x43, 0xf3, 0x1e, 0x93, 0xb, // b256
+ 0x66, 0x6f, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, // str[3]
+ 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, // str data
+ 0x61, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x73, // str data
+ 0x65, 0x6e, 0x74, 0x65, 0x6e, 0x63, 0x65, // str data
+ ];
+
+ let decoded = ABIDecoder::decode(&types, &bytes)?;
+
+ // Expected tokens
+ let foo = Token::Struct(vec![
+ Token::U16(10),
+ Token::Struct(vec![
+ Token::Bool(true),
+ Token::Array(vec![Token::U8(1), Token::U8(2)]),
+ ]),
+ ]);
+
+ let u8_arr = Token::Array(vec![Token::U8(1), Token::U8(2)]);
+
+ let b256 = Token::B256([
+ 0xd5, 0x57, 0x9c, 0x46, 0xdf, 0xcc, 0x7f, 0x18, 0x20, 0x70, 0x13, 0xe6, 0x5b, 0x44,
+ 0xe4, 0xcb, 0x4e, 0x2c, 0x22, 0x98, 0xf4, 0xac, 0x45, 0x7b, 0xa8, 0xf8, 0x27, 0x43,
+ 0xf3, 0x1e, 0x93, 0xb,
+ ]);
+
+ let ss = Token::StringSlice(StaticStringToken::new(
+ "This is a full sentence".into(),
+ None,
+ ));
+
+ let s = Token::StringArray(StaticStringToken::new("foo".into(), Some(3)));
+
+ let expected: Vec = vec![foo, u8_arr, b256, s, ss];
+
+ assert_eq!(decoded, expected);
+ Ok(())
+ }
+
+ #[test]
+ fn units_in_structs_are_decoded_as_one_word() -> Result<()> {
+ let data = [
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ ];
+ let struct_type = ParamType::Struct {
+ fields: vec![ParamType::Unit, ParamType::U64],
+ generics: vec![],
+ };
+
+ let actual = ABIDecoder::decode_single(&struct_type, &data)?;
+
+ let expected = Token::Struct(vec![Token::Unit, Token::U64(u64::MAX)]);
+ assert_eq!(actual, expected);
+ Ok(())
+ }
+
+ #[test]
+ fn enums_with_all_unit_variants_are_decoded_from_one_word() -> Result<()> {
+ let data = [0, 0, 0, 0, 0, 0, 0, 1];
+ let types = vec![ParamType::Unit, ParamType::Unit];
+ let variants = EnumVariants::new(types)?;
+ let enum_w_only_units = ParamType::Enum {
+ variants: variants.clone(),
+ generics: vec![],
+ };
+
+ let result = ABIDecoder::decode_single(&enum_w_only_units, &data)?;
+
+ let expected_enum = Token::Enum(Box::new((1, Token::Unit, variants)));
+ assert_eq!(result, expected_enum);
+ Ok(())
+ }
+
+ #[test]
+ fn out_of_bounds_discriminant_is_detected() -> Result<()> {
+ let data = [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2];
+ let types = vec![ParamType::U32];
+ let variants = EnumVariants::new(types)?;
+ let enum_type = ParamType::Enum {
+ variants,
+ generics: vec![],
+ };
+
+ let result = ABIDecoder::decode_single(&enum_type, &data);
+
+ let error = result.expect_err("Should have resulted in an error");
+
+ let expected_msg = "Discriminant '1' doesn't point to any variant: ";
+ assert!(matches!(error, Error::InvalidData(str) if str.starts_with(expected_msg)));
+ Ok(())
+ }
+}
diff --git a/docs/beta-4/fuels-rs/packages/fuels-core/src/codec/abi_encoder.rs b/docs/beta-4/fuels-rs/packages/fuels-core/src/codec/abi_encoder.rs
new file mode 100644
index 00000000..6d53e049
--- /dev/null
+++ b/docs/beta-4/fuels-rs/packages/fuels-core/src/codec/abi_encoder.rs
@@ -0,0 +1,1203 @@
+use fuel_types::bytes::padded_len_usize;
+use itertools::Itertools;
+
+use crate::{
+ constants::WORD_SIZE,
+ types::{
+ errors::Result,
+ pad_string, pad_u16, pad_u32, pad_u8,
+ unresolved_bytes::{Data, UnresolvedBytes},
+ EnumSelector, StaticStringToken, Token, U256,
+ },
+};
+
+pub struct ABIEncoder;
+
+impl ABIEncoder {
+ /// Encodes `Token`s in `args` following the ABI specs defined
+ /// [here](https://github.com/FuelLabs/fuel-specs/blob/master/specs/protocol/abi.md)
+ pub fn encode(args: &[Token]) -> Result {
+ let data = Self::encode_tokens(args)?;
+
+ Ok(UnresolvedBytes::new(data))
+ }
+
+ fn encode_tokens(tokens: &[Token]) -> Result> {
+ tokens
+ .iter()
+ .map(Self::encode_token)
+ .flatten_ok()
+ .collect::>>()
+ }
+
+ fn encode_token(arg: &Token) -> Result> {
+ let encoded_token = match arg {
+ Token::U8(arg_u8) => vec![Self::encode_u8(*arg_u8)],
+ Token::U16(arg_u16) => vec![Self::encode_u16(*arg_u16)],
+ Token::U32(arg_u32) => vec![Self::encode_u32(*arg_u32)],
+ Token::U64(arg_u64) => vec![Self::encode_u64(*arg_u64)],
+ Token::U128(arg_u128) => vec![Self::encode_u128(*arg_u128)],
+ Token::U256(arg_u256) => vec![Self::encode_u256(*arg_u256)],
+ Token::Bool(arg_bool) => vec![Self::encode_bool(*arg_bool)],
+ Token::B256(arg_bits256) => vec![Self::encode_b256(arg_bits256)],
+ Token::Array(arg_array) => Self::encode_array(arg_array)?,
+ Token::Vector(data) => Self::encode_vector(data)?,
+ Token::StringSlice(arg_string) => Self::encode_string_slice(arg_string)?,
+ Token::StringArray(arg_string) => vec![Self::encode_string_array(arg_string)?],
+ Token::Struct(arg_struct) => Self::encode_struct(arg_struct)?,
+ Token::Enum(arg_enum) => Self::encode_enum(arg_enum)?,
+ Token::Tuple(arg_tuple) => Self::encode_tuple(arg_tuple)?,
+ Token::Unit => vec![Self::encode_unit()],
+ Token::RawSlice(data) => Self::encode_raw_slice(data)?,
+ Token::Bytes(data) => Self::encode_bytes(data.to_vec())?,
+ // `String` in Sway has the same memory layout as the bytes type
+ Token::String(string) => Self::encode_bytes(string.clone().into_bytes())?,
+ };
+
+ Ok(encoded_token)
+ }
+
+ fn encode_unit() -> Data {
+ Data::Inline(vec![0; WORD_SIZE])
+ }
+
+ fn encode_tuple(arg_tuple: &[Token]) -> Result> {
+ Self::encode_tokens(arg_tuple)
+ }
+
+ fn encode_struct(subcomponents: &[Token]) -> Result> {
+ Self::encode_tokens(subcomponents)
+ }
+
+ fn encode_array(arg_array: &[Token]) -> Result> {
+ Self::encode_tokens(arg_array)
+ }
+
+ fn encode_b256(arg_bits256: &[u8; 32]) -> Data {
+ Data::Inline(arg_bits256.to_vec())
+ }
+
+ fn encode_bool(arg_bool: bool) -> Data {
+ Data::Inline(pad_u8(u8::from(arg_bool)).to_vec())
+ }
+
+ fn encode_u128(arg_u128: u128) -> Data {
+ Data::Inline(arg_u128.to_be_bytes().to_vec())
+ }
+
+ fn encode_u256(arg_u256: U256) -> Data {
+ let mut bytes = [0u8; 32];
+ arg_u256.to_big_endian(&mut bytes);
+ Data::Inline(bytes.to_vec())
+ }
+
+ fn encode_u64(arg_u64: u64) -> Data {
+ Data::Inline(arg_u64.to_be_bytes().to_vec())
+ }
+
+ fn encode_u32(arg_u32: u32) -> Data {
+ Data::Inline(pad_u32(arg_u32).to_vec())
+ }
+
+ fn encode_u16(arg_u16: u16) -> Data {
+ Data::Inline(pad_u16(arg_u16).to_vec())
+ }
+
+ fn encode_u8(arg_u8: u8) -> Data {
+ Data::Inline(pad_u8(arg_u8).to_vec())
+ }
+
+ fn encode_enum(selector: &EnumSelector) -> Result> {
+ let (discriminant, token_within_enum, variants) = selector;
+
+ let mut encoded_enum = vec![Self::encode_discriminant(*discriminant)];
+
+ // Enums that contain only Units as variants have only their discriminant encoded.
+ if !variants.only_units_inside() {
+ let variant_param_type = variants.param_type_of_variant(*discriminant)?;
+ let padding_amount = variants.compute_padding_amount(variant_param_type);
+
+ encoded_enum.push(Data::Inline(vec![0; padding_amount]));
+
+ let token_data = Self::encode_token(token_within_enum)?;
+ encoded_enum.extend(token_data);
+ }
+
+ Ok(encoded_enum)
+ }
+
+ fn encode_discriminant(discriminant: u8) -> Data {
+ Self::encode_u8(discriminant)
+ }
+
+ fn encode_vector(data: &[Token]) -> Result> {
+ let encoded_data = Self::encode_tokens(data)?;
+ let cap = data.len() as u64;
+ let len = data.len() as u64;
+
+ // A vector is expected to be encoded as 3 WORDs -- a ptr, a cap and a
+ // len. This means that we must place the encoded vector elements
+ // somewhere else. Hence the use of Data::Dynamic which will, when
+ // resolved, leave behind in its place only a pointer to the actual
+ // data.
+ Ok(vec![
+ Data::Dynamic(encoded_data),
+ Self::encode_u64(cap),
+ Self::encode_u64(len),
+ ])
+ }
+
+ fn encode_raw_slice(data: &[u64]) -> Result> {
+ let encoded_data = data
+ .iter()
+ .map(|&word| Self::encode_u64(word))
+ .collect::>();
+
+ let num_bytes = data.len() * WORD_SIZE;
+
+ let len = Self::encode_u64(num_bytes as u64);
+ Ok(vec![Data::Dynamic(encoded_data), len])
+ }
+
+ fn encode_string_slice(arg_string: &StaticStringToken) -> Result> {
+ let encoded_data = Data::Inline(arg_string.get_encodable_str()?.as_bytes().to_vec());
+
+ let num_bytes = arg_string.get_encodable_str()?.len();
+ let len = Self::encode_u64(num_bytes as u64);
+ Ok(vec![Data::Dynamic(vec![encoded_data]), len])
+ }
+
+ fn encode_string_array(arg_string: &StaticStringToken) -> Result {
+ Ok(Data::Inline(pad_string(arg_string.get_encodable_str()?)))
+ }
+
+ fn encode_bytes(mut data: Vec) -> Result> {
+ let len = data.len();
+
+ zeropad_to_word_alignment(&mut data);
+
+ let cap = data.len() as u64;
+ let encoded_data = vec![Data::Inline(data)];
+
+ Ok(vec![
+ Data::Dynamic(encoded_data),
+ Self::encode_u64(cap),
+ Self::encode_u64(len as u64),
+ ])
+ }
+}
+
+fn zeropad_to_word_alignment(data: &mut Vec) {
+ let padded_length = padded_len_usize(data.len());
+ data.resize(padded_length, 0);
+}
+
+#[cfg(test)]
+mod tests {
+ use std::slice;
+
+ use itertools::chain;
+ use sha2::{Digest, Sha256};
+
+ use super::*;
+ use crate::{
+ codec::first_four_bytes_of_sha256_hash,
+ types::{enum_variants::EnumVariants, param_types::ParamType},
+ };
+
+ const VEC_METADATA_SIZE: usize = 3 * WORD_SIZE;
+ const DISCRIMINANT_SIZE: usize = WORD_SIZE;
+
+ #[test]
+ fn encode_function_signature() {
+ let fn_signature = "entry_one(u64)";
+
+ let result = first_four_bytes_of_sha256_hash(fn_signature);
+
+ println!("Encoded function selector for ({fn_signature}): {result:#0x?}");
+
+ assert_eq!(result, [0x0, 0x0, 0x0, 0x0, 0x0c, 0x36, 0xcb, 0x9c]);
+ }
+
+ #[test]
+ fn encode_function_with_u32_type() -> Result<()> {
+ // @todo eventually we must update the json abi examples in here.
+ // They're in the old format.
+ //
+ // let json_abi =
+ // r#"
+ // [
+ // {
+ // "type":"function",
+ // "inputs": [{"name":"arg","type":"u32"}],
+ // "name":"entry_one",
+ // "outputs": []
+ // }
+ // ]
+ // "#;
+
+ let fn_signature = "entry_one(u32)";
+ let arg = Token::U32(u32::MAX);
+
+ let args: Vec = vec![arg];
+
+ let expected_encoded_abi = [0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff];
+
+ let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0xb7, 0x9e, 0xf7, 0x43];
+
+ let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature);
+
+ let encoded = ABIEncoder::encode(&args)?.resolve(0);
+
+ println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}");
+
+ assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded));
+ assert_eq!(encoded_function_selector, expected_function_selector);
+ Ok(())
+ }
+
+ #[test]
+ fn encode_function_with_u32_type_multiple_args() -> Result<()> {
+ // let json_abi =
+ // r#"
+ // [
+ // {
+ // "type":"function",
+ // "inputs": [{"name":"first","type":"u32"},{"name":"second","type":"u32"}],
+ // "name":"takes_two",
+ // "outputs": []
+ // }
+ // ]
+ // "#;
+
+ let fn_signature = "takes_two(u32,u32)";
+ let first = Token::U32(u32::MAX);
+ let second = Token::U32(u32::MAX);
+
+ let args: Vec = vec![first, second];
+
+ let expected_encoded_abi = [
+ 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff,
+ ];
+
+ let expected_fn_selector = [0x0, 0x0, 0x0, 0x0, 0xa7, 0x07, 0xb0, 0x8e];
+
+ let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature);
+ let encoded = ABIEncoder::encode(&args)?.resolve(0);
+
+ println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}");
+
+ assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded));
+ assert_eq!(encoded_function_selector, expected_fn_selector);
+ Ok(())
+ }
+
+ #[test]
+ fn encode_function_with_u64_type() -> Result<()> {
+ // let json_abi =
+ // r#"
+ // [
+ // {
+ // "type":"function",
+ // "inputs": [{"name":"arg","type":"u64"}],
+ // "name":"entry_one",
+ // "outputs": []
+ // }
+ // ]
+ // "#;
+
+ let fn_signature = "entry_one(u64)";
+ let arg = Token::U64(u64::MAX);
+
+ let args: Vec = vec![arg];
+
+ let expected_encoded_abi = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff];
+
+ let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0x0c, 0x36, 0xcb, 0x9c];
+
+ let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature);
+
+ let encoded = ABIEncoder::encode(&args)?.resolve(0);
+
+ println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}");
+
+ assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded));
+ assert_eq!(encoded_function_selector, expected_function_selector);
+ Ok(())
+ }
+
+ #[test]
+ fn encode_function_with_bool_type() -> Result<()> {
+ // let json_abi =
+ // r#"
+ // [
+ // {
+ // "type":"function",
+ // "inputs": [{"name":"arg","type":"bool"}],
+ // "name":"bool_check",
+ // "outputs": []
+ // }
+ // ]
+ // "#;
+
+ let fn_signature = "bool_check(bool)";
+ let arg = Token::Bool(true);
+
+ let args: Vec = vec![arg];
+
+ let expected_encoded_abi = [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1];
+
+ let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0x66, 0x8f, 0xff, 0x58];
+
+ let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature);
+
+ let encoded = ABIEncoder::encode(&args)?.resolve(0);
+
+ println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}");
+
+ assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded));
+ assert_eq!(encoded_function_selector, expected_function_selector);
+ Ok(())
+ }
+
+ #[test]
+ fn encode_function_with_two_different_type() -> Result<()> {
+ // let json_abi =
+ // r#"
+ // [
+ // {
+ // "type":"function",
+ // "inputs": [{"name":"first","type":"u32"},{"name":"second","type":"bool"}],
+ // "name":"takes_two_types",
+ // "outputs": []
+ // }
+ // ]
+ // "#;
+
+ let fn_signature = "takes_two_types(u32,bool)";
+ let first = Token::U32(u32::MAX);
+ let second = Token::Bool(true);
+
+ let args: Vec = vec![first, second];
+
+ let expected_encoded_abi = [
+ 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
+ ];
+
+ let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0xf5, 0x40, 0x73, 0x2b];
+
+ let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature);
+
+ let encoded = ABIEncoder::encode(&args)?.resolve(0);
+
+ println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}");
+
+ assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded));
+ assert_eq!(encoded_function_selector, expected_function_selector);
+ Ok(())
+ }
+
+ #[test]
+ fn encode_function_with_bits256_type() -> Result<()> {
+ // let json_abi =
+ // r#"
+ // [
+ // {
+ // "type":"function",
+ // "inputs": [{"name":"arg","type":"b256"}],
+ // "name":"takes_bits256",
+ // "outputs": []
+ // }
+ // ]
+ // "#;
+
+ let fn_signature = "takes_bits256(b256)";
+
+ let mut hasher = Sha256::new();
+ hasher.update("test string".as_bytes());
+
+ let arg = hasher.finalize();
+
+ let arg = Token::B256(arg.into());
+
+ let args: Vec = vec![arg];
+
+ let expected_encoded_abi = [
+ 0xd5, 0x57, 0x9c, 0x46, 0xdf, 0xcc, 0x7f, 0x18, 0x20, 0x70, 0x13, 0xe6, 0x5b, 0x44,
+ 0xe4, 0xcb, 0x4e, 0x2c, 0x22, 0x98, 0xf4, 0xac, 0x45, 0x7b, 0xa8, 0xf8, 0x27, 0x43,
+ 0xf3, 0x1e, 0x93, 0xb,
+ ];
+
+ let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0x01, 0x49, 0x42, 0x96];
+
+ let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature);
+
+ let encoded = ABIEncoder::encode(&args)?.resolve(0);
+
+ println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}");
+
+ assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded));
+ assert_eq!(encoded_function_selector, expected_function_selector);
+ Ok(())
+ }
+
+ #[test]
+ fn encode_function_with_array_type() -> Result<()> {
+ // let json_abi =
+ // r#"
+ // [
+ // {
+ // "type":"function",
+ // "inputs": [{"name":"arg","type":"u8[3]"}],
+ // "name":"takes_integer_array",
+ // "outputs": []
+ // }
+ // ]
+ // "#;
+
+ let fn_signature = "takes_integer_array(u8[3])";
+
+ // Keeping the construction of the arguments array separate for better readability.
+ let first = Token::U8(1);
+ let second = Token::U8(2);
+ let third = Token::U8(3);
+
+ let arg = vec![first, second, third];
+ let arg_array = Token::Array(arg);
+
+ let args: Vec = vec![arg_array];
+
+ let expected_encoded_abi = [
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3,
+ ];
+
+ let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0x2c, 0x5a, 0x10, 0x2e];
+
+ let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature);
+
+ let encoded = ABIEncoder::encode(&args)?.resolve(0);
+
+ println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}");
+
+ assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded));
+ assert_eq!(encoded_function_selector, expected_function_selector);
+ Ok(())
+ }
+
+ #[test]
+ fn encode_function_with_string_array_type() -> Result<()> {
+ // let json_abi =
+ // r#"
+ // [
+ // {
+ // "type":"function",
+ // "inputs": [{"name":"arg","type":"str[23]"}],
+ // "name":"takes_string",
+ // "outputs": []
+ // }
+ // ]
+ // "#;
+
+ let fn_signature = "takes_string(str[23])";
+
+ let args: Vec = vec![Token::StringArray(StaticStringToken::new(
+ "This is a full sentence".into(),
+ Some(23),
+ ))];
+
+ let expected_encoded_abi = [
+ 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x66, 0x75, 0x6c, 0x6c,
+ 0x20, 0x73, 0x65, 0x6e, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x00,
+ ];
+
+ let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0xd5, 0x6e, 0x76, 0x51];
+
+ let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature);
+
+ let encoded = ABIEncoder::encode(&args)?.resolve(0);
+
+ println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}");
+
+ assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded));
+ assert_eq!(encoded_function_selector, expected_function_selector);
+ Ok(())
+ }
+
+ #[test]
+ fn encode_function_with_string_slice_type() -> Result<()> {
+ // let json_abi =
+ // r#"
+ // [
+ // {
+ // "type":"function",
+ // "inputs": [{"name":"arg","type":"str"}],
+ // "name":"takes_string",
+ // "outputs": []
+ // }
+ // ]
+ // "#;
+
+ let fn_signature = "takes_string(str)";
+
+ let args: Vec = vec![Token::StringSlice(StaticStringToken::new(
+ "This is a full sentence".into(),
+ None,
+ ))];
+
+ let expected_encoded_abi = [
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, // str at data index 16
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, // str of lenght 23
+ 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, //
+ 0x61, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x73, //
+ 0x65, 0x6e, 0x74, 0x65, 0x6e, 0x63, 0x65, //
+ ];
+
+ let expected_function_selector = [0, 0, 0, 0, 239, 77, 222, 230];
+
+ let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature);
+
+ let encoded = ABIEncoder::encode(&args)?.resolve(0);
+
+ println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}");
+
+ assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded));
+ assert_eq!(encoded_function_selector, expected_function_selector);
+ Ok(())
+ }
+
+ #[test]
+ fn encode_function_with_struct() -> Result<()> {
+ // let json_abi =
+ // r#"
+ // [
+ // {
+ // "type":"function",
+ // "inputs": [{"name":"arg","type":"MyStruct"}],
+ // "name":"takes_my_struct",
+ // "outputs": []
+ // }
+ // ]
+ // "#;
+
+ let fn_signature = "takes_my_struct(MyStruct)";
+
+ // struct MyStruct {
+ // foo: u8,
+ // bar: bool,
+ // }
+
+ let foo = Token::U8(1);
+ let bar = Token::Bool(true);
+
+ // Create the custom struct token using the array of tuples above
+ let arg = Token::Struct(vec![foo, bar]);
+
+ let args: Vec = vec![arg];
+
+ let expected_encoded_abi = [
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
+ ];
+
+ let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0xa8, 0x1e, 0x8d, 0xd7];
+
+ let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature);
+
+ let encoded = ABIEncoder::encode(&args)?.resolve(0);
+
+ println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}");
+
+ assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded));
+ assert_eq!(encoded_function_selector, expected_function_selector);
+ Ok(())
+ }
+
+ #[test]
+ fn encode_function_with_enum() -> Result<()> {
+ // let json_abi =
+ // r#"
+ // [
+ // {
+ // "type":"function",
+ // "inputs": [{"name":"arg","type":"MyEnum"}],
+ // "name":"takes_my_enum",
+ // "outputs": []
+ // }
+ // ]
+ // "#;
+
+ let fn_signature = "takes_my_enum(MyEnum)";
+
+ // enum MyEnum {
+ // x: u32,
+ // y: bool,
+ // }
+ let types = vec![ParamType::U32, ParamType::Bool];
+ let params = EnumVariants::new(types)?;
+
+ // An `EnumSelector` indicating that we've chosen the first Enum variant,
+ // whose value is 42 of the type ParamType::U32 and that the Enum could
+ // have held any of the other types present in `params`.
+
+ let enum_selector = Box::new((0, Token::U32(42), params));
+
+ let arg = Token::Enum(enum_selector);
+
+ let args: Vec = vec![arg];
+
+ let expected_encoded_abi = [
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2a,
+ ];
+
+ let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0x35, 0x5c, 0xa6, 0xfa];
+
+ let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature);
+
+ let encoded = ABIEncoder::encode(&args)?.resolve(0);
+
+ assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded));
+ assert_eq!(encoded_function_selector, expected_function_selector);
+ Ok(())
+ }
+
+ // The encoding follows the ABI specs defined [here](https://github.com/FuelLabs/fuel-specs/blob/master/specs/protocol/abi.md)
+ #[test]
+ fn enums_are_sized_to_fit_the_biggest_variant() -> Result<()> {
+ // Our enum has two variants: B256, and U64. So the enum will set aside
+ // 256b of space or 4 WORDS because that is the space needed to fit the
+ // largest variant(B256).
+ let types = vec![ParamType::B256, ParamType::U64];
+ let enum_variants = EnumVariants::new(types)?;
+ let enum_selector = Box::new((1, Token::U64(42), enum_variants));
+
+ let encoded = ABIEncoder::encode(slice::from_ref(&Token::Enum(enum_selector)))?.resolve(0);
+
+ let enum_discriminant_enc = vec![0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1];
+ let u64_enc = vec![0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2a];
+ let enum_padding = vec![0x0; 24];
+
+ // notice the ordering, first the discriminant, then the necessary
+ // padding and then the value itself.
+ let expected: Vec = [enum_discriminant_enc, enum_padding, u64_enc]
+ .into_iter()
+ .flatten()
+ .collect();
+
+ assert_eq!(hex::encode(expected), hex::encode(encoded));
+ Ok(())
+ }
+
+ #[test]
+ fn encoding_enums_with_deeply_nested_types() -> Result<()> {
+ /*
+ enum DeeperEnum {
+ v1: bool,
+ v2: str[10]
+ }
+ */
+ let types = vec![ParamType::Bool, ParamType::StringArray(10)];
+ let deeper_enum_variants = EnumVariants::new(types)?;
+ let deeper_enum_token =
+ Token::StringArray(StaticStringToken::new("0123456789".into(), Some(10)));
+
+ let str_enc = vec![
+ b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0,
+ ];
+ let deeper_enum_discriminant_enc = vec![0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1];
+
+ /*
+ struct StructA {
+ some_enum: DeeperEnum
+ some_number: u32
+ }
+ */
+
+ let fields = vec![
+ ParamType::Enum {
+ variants: deeper_enum_variants.clone(),
+ generics: vec![],
+ },
+ ParamType::Bool,
+ ];
+ let struct_a_type = ParamType::Struct {
+ fields,
+ generics: vec![],
+ };
+
+ let struct_a_token = Token::Struct(vec![
+ Token::Enum(Box::new((1, deeper_enum_token, deeper_enum_variants))),
+ Token::U32(11332),
+ ]);
+ let some_number_enc = vec![0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2c, 0x44];
+
+ /*
+ enum TopLevelEnum {
+ v1: StructA,
+ v2: bool,
+ v3: u64
+ }
+ */
+
+ let types = vec![struct_a_type, ParamType::Bool, ParamType::U64];
+ let top_level_enum_variants = EnumVariants::new(types)?;
+ let top_level_enum_token =
+ Token::Enum(Box::new((0, struct_a_token, top_level_enum_variants)));
+ let top_lvl_discriminant_enc = vec![0x0; 8];
+
+ let encoded = ABIEncoder::encode(slice::from_ref(&top_level_enum_token))?.resolve(0);
+
+ let correct_encoding: Vec = [
+ top_lvl_discriminant_enc,
+ deeper_enum_discriminant_enc,
+ str_enc,
+ some_number_enc,
+ ]
+ .into_iter()
+ .flatten()
+ .collect();
+
+ assert_eq!(hex::encode(correct_encoding), hex::encode(encoded));
+ Ok(())
+ }
+
+ #[test]
+ fn encode_function_with_nested_structs() -> Result<()> {
+ // let json_abi =
+ // r#"
+ // [
+ // {
+ // "type":"function",
+ // "inputs": [{"name":"arg","type":"Foo"}],
+ // "name":"takes_my_nested_struct",
+ // "outputs": []
+ // }
+ // ]
+ // "#;
+
+ // struct Foo {
+ // x: u16,
+ // y: Bar,
+ // }
+ //
+ // struct Bar {
+ // a: bool,
+ // b: u8[2],
+ // }
+
+ let fn_signature = "takes_my_nested_struct(Foo)";
+
+ let args: Vec = vec![Token::Struct(vec![
+ Token::U16(10),
+ Token::Struct(vec![
+ Token::Bool(true),
+ Token::Array(vec![Token::U8(1), Token::U8(2)]),
+ ]),
+ ])];
+
+ let expected_encoded_abi = [
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2,
+ ];
+
+ let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0xea, 0x0a, 0xfd, 0x23];
+
+ let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature);
+
+ let encoded = ABIEncoder::encode(&args)?.resolve(0);
+
+ println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}");
+
+ assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded));
+ assert_eq!(encoded_function_selector, expected_function_selector);
+ Ok(())
+ }
+
+ #[test]
+ fn encode_comprehensive_function() -> Result<()> {
+ // let json_abi =
+ // r#"
+ // [
+ // {
+ // "type": "contract",
+ // "inputs": [
+ // {
+ // "name": "arg",
+ // "type": "Foo"
+ // },
+ // {
+ // "name": "arg2",
+ // "type": "u8[2]"
+ // },
+ // {
+ // "name": "arg3",
+ // "type": "b256"
+ // },
+ // {
+ // "name": "arg",
+ // "type": "str[23]"
+ // }
+ // ],
+ // "name": "long_function",
+ // "outputs": []
+ // }
+ // ]
+ // "#;
+
+ // struct Foo {
+ // x: u16,
+ // y: Bar,
+ // }
+ //
+ // struct Bar {
+ // a: bool,
+ // b: u8[2],
+ // }
+
+ let fn_signature = "long_function(Foo,u8[2],b256,str[23])";
+
+ let foo = Token::Struct(vec![
+ Token::U16(10),
+ Token::Struct(vec![
+ Token::Bool(true),
+ Token::Array(vec![Token::U8(1), Token::U8(2)]),
+ ]),
+ ]);
+
+ let u8_arr = Token::Array(vec![Token::U8(1), Token::U8(2)]);
+
+ let mut hasher = Sha256::new();
+ hasher.update("test string".as_bytes());
+
+ let b256 = Token::B256(hasher.finalize().into());
+
+ let s = Token::StringArray(StaticStringToken::new(
+ "This is a full sentence".into(),
+ Some(23),
+ ));
+
+ let args: Vec = vec![foo, u8_arr, b256, s];
+
+ let expected_encoded_abi = [
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, // foo.x == 10u16
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // foo.y.a == true
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // foo.b.0 == 1u8
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, // foo.b.1 == 2u8
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // u8[2].0 == 1u8
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, // u8[2].0 == 2u8
+ 0xd5, 0x57, 0x9c, 0x46, 0xdf, 0xcc, 0x7f, 0x18, // b256
+ 0x20, 0x70, 0x13, 0xe6, 0x5b, 0x44, 0xe4, 0xcb, // b256
+ 0x4e, 0x2c, 0x22, 0x98, 0xf4, 0xac, 0x45, 0x7b, // b256
+ 0xa8, 0xf8, 0x27, 0x43, 0xf3, 0x1e, 0x93, 0xb, // b256
+ 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, // str[23]
+ 0x61, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x73, // str[23]
+ 0x65, 0x6e, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x0, // str[23]
+ ];
+
+ let expected_function_selector = [0x0, 0x0, 0x0, 0x0, 0x10, 0x93, 0xb2, 0x12];
+
+ let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature);
+
+ let encoded = ABIEncoder::encode(&args)?.resolve(0);
+
+ assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded));
+ assert_eq!(encoded_function_selector, expected_function_selector);
+ Ok(())
+ }
+
+ #[test]
+ fn enums_with_only_unit_variants_are_encoded_in_one_word() -> Result<()> {
+ let expected = [0, 0, 0, 0, 0, 0, 0, 1];
+
+ let types = vec![ParamType::Unit, ParamType::Unit];
+ let enum_selector = Box::new((1, Token::Unit, EnumVariants::new(types)?));
+
+ let actual = ABIEncoder::encode(&[Token::Enum(enum_selector)])?.resolve(0);
+
+ assert_eq!(actual, expected);
+ Ok(())
+ }
+
+ #[test]
+ fn units_in_composite_types_are_encoded_in_one_word() -> Result<()> {
+ let expected = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5];
+
+ let actual =
+ ABIEncoder::encode(&[Token::Struct(vec![Token::Unit, Token::U32(5)])])?.resolve(0);
+
+ assert_eq!(actual, expected);
+ Ok(())
+ }
+
+ #[test]
+ fn enums_with_units_are_correctly_padded() -> Result<()> {
+ let discriminant = vec![0, 0, 0, 0, 0, 0, 0, 1];
+ let padding = vec![0; 32];
+ let expected: Vec = [discriminant, padding].into_iter().flatten().collect();
+
+ let types = vec![ParamType::B256, ParamType::Unit];
+ let enum_selector = Box::new((1, Token::Unit, EnumVariants::new(types)?));
+
+ let actual = ABIEncoder::encode(&[Token::Enum(enum_selector)])?.resolve(0);
+
+ assert_eq!(actual, expected);
+ Ok(())
+ }
+
+ #[test]
+ fn vector_has_ptr_cap_len_and_then_data() -> Result<()> {
+ // arrange
+ let offset: u8 = 150;
+ let token = Token::Vector(vec![Token::U64(5)]);
+
+ // act
+ let result = ABIEncoder::encode(&[token])?.resolve(offset as u64);
+
+ // assert
+ let ptr = [0, 0, 0, 0, 0, 0, 0, 3 * WORD_SIZE as u8 + offset];
+ let cap = [0, 0, 0, 0, 0, 0, 0, 1];
+ let len = [0, 0, 0, 0, 0, 0, 0, 1];
+ let data = [0, 0, 0, 0, 0, 0, 0, 5];
+
+ let expected = chain!(ptr, cap, len, data).collect::>();
+
+ assert_eq!(result, expected);
+
+ Ok(())
+ }
+
+ #[test]
+ fn data_from_two_vectors_aggregated_at_the_end() -> Result<()> {
+ // arrange
+ let offset: u8 = 40;
+ let vec_1 = Token::Vector(vec![Token::U64(5)]);
+ let vec_2 = Token::Vector(vec![Token::U64(6)]);
+
+ // act
+ let result = ABIEncoder::encode(&[vec_1, vec_2])?.resolve(offset as u64);
+
+ // assert
+ let vec1_data_offset = 6 * WORD_SIZE as u8 + offset;
+ let vec1_ptr = [0, 0, 0, 0, 0, 0, 0, vec1_data_offset];
+ let vec1_cap = [0, 0, 0, 0, 0, 0, 0, 1];
+ let vec1_len = [0, 0, 0, 0, 0, 0, 0, 1];
+ let vec1_data = [0, 0, 0, 0, 0, 0, 0, 5];
+
+ let vec2_data_offset = vec1_data_offset + vec1_data.len() as u8;
+ let vec2_ptr = [0, 0, 0, 0, 0, 0, 0, vec2_data_offset];
+ let vec2_cap = [0, 0, 0, 0, 0, 0, 0, 1];
+ let vec2_len = [0, 0, 0, 0, 0, 0, 0, 1];
+ let vec2_data = [0, 0, 0, 0, 0, 0, 0, 6];
+
+ let expected = chain!(
+ vec1_ptr, vec1_cap, vec1_len, vec2_ptr, vec2_cap, vec2_len, vec1_data, vec2_data,
+ )
+ .collect::>();
+
+ assert_eq!(result, expected);
+
+ Ok(())
+ }
+
+ #[test]
+ fn a_vec_in_an_enum() -> Result<()> {
+ // arrange
+ let offset = 40;
+ let types = vec![ParamType::B256, ParamType::Vector(Box::new(ParamType::U64))];
+ let variants = EnumVariants::new(types)?;
+ let selector = (1, Token::Vector(vec![Token::U64(5)]), variants);
+ let token = Token::Enum(Box::new(selector));
+
+ // act
+ let result = ABIEncoder::encode(&[token])?.resolve(offset as u64);
+
+ // assert
+ let discriminant = vec![0, 0, 0, 0, 0, 0, 0, 1];
+
+ const PADDING: usize = std::mem::size_of::<[u8; 32]>() - VEC_METADATA_SIZE;
+
+ let vec1_ptr = ((DISCRIMINANT_SIZE + PADDING + VEC_METADATA_SIZE + offset) as u64)
+ .to_be_bytes()
+ .to_vec();
+ let vec1_cap = [0, 0, 0, 0, 0, 0, 0, 1];
+ let vec1_len = [0, 0, 0, 0, 0, 0, 0, 1];
+ let vec1_data = [0, 0, 0, 0, 0, 0, 0, 5];
+
+ let expected = chain!(
+ discriminant,
+ vec![0; PADDING],
+ vec1_ptr,
+ vec1_cap,
+ vec1_len,
+ vec1_data
+ )
+ .collect::>();
+
+ assert_eq!(result, expected);
+
+ Ok(())
+ }
+
+ #[test]
+ fn an_enum_in_a_vec() -> Result<()> {
+ // arrange
+ let offset = 40;
+ let types = vec![ParamType::B256, ParamType::U8];
+ let variants = EnumVariants::new(types)?;
+ let selector = (1, Token::U8(8), variants);
+ let enum_token = Token::Enum(Box::new(selector));
+
+ let vec_token = Token::Vector(vec![enum_token]);
+
+ // act
+ let result = ABIEncoder::encode(&[vec_token])?.resolve(offset as u64);
+
+ // assert
+ const PADDING: usize = std::mem::size_of::<[u8; 32]>() - WORD_SIZE;
+
+ let vec1_ptr = ((VEC_METADATA_SIZE + offset) as u64).to_be_bytes().to_vec();
+ let vec1_cap = [0, 0, 0, 0, 0, 0, 0, 1];
+ let vec1_len = [0, 0, 0, 0, 0, 0, 0, 1];
+ let discriminant = 1u64.to_be_bytes();
+ let vec1_data = chain!(discriminant, [0; PADDING], 8u64.to_be_bytes()).collect::>();
+
+ let expected = chain!(vec1_ptr, vec1_cap, vec1_len, vec1_data).collect::>();
+
+ assert_eq!(result, expected);
+
+ Ok(())
+ }
+
+ #[test]
+ fn a_vec_in_a_struct() -> Result<()> {
+ // arrange
+ let offset = 40;
+ let token = Token::Struct(vec![Token::Vector(vec![Token::U64(5)]), Token::U8(9)]);
+
+ // act
+ let result = ABIEncoder::encode(&[token])?.resolve(offset as u64);
+
+ // assert
+ let vec1_ptr = ((VEC_METADATA_SIZE + WORD_SIZE + offset) as u64)
+ .to_be_bytes()
+ .to_vec();
+ let vec1_cap = [0, 0, 0, 0, 0, 0, 0, 1];
+ let vec1_len = [0, 0, 0, 0, 0, 0, 0, 1];
+ let vec1_data = [0, 0, 0, 0, 0, 0, 0, 5];
+
+ let expected = chain!(
+ vec1_ptr,
+ vec1_cap,
+ vec1_len,
+ [0, 0, 0, 0, 0, 0, 0, 9],
+ vec1_data
+ )
+ .collect::>();
+
+ assert_eq!(result, expected);
+
+ Ok(())
+ }
+
+ #[test]
+ fn a_vec_in_a_vec() -> Result<()> {
+ // arrange
+ let offset = 40;
+ let token = Token::Vector(vec![Token::Vector(vec![Token::U8(5), Token::U8(6)])]);
+
+ // act
+ let result = ABIEncoder::encode(&[token])?.resolve(offset as u64);
+
+ // assert
+ let vec1_data_offset = (VEC_METADATA_SIZE + offset) as u64;
+ let vec1_ptr = vec1_data_offset.to_be_bytes().to_vec();
+ let vec1_cap = [0, 0, 0, 0, 0, 0, 0, 1];
+ let vec1_len = [0, 0, 0, 0, 0, 0, 0, 1];
+
+ let vec2_ptr = (vec1_data_offset + VEC_METADATA_SIZE as u64)
+ .to_be_bytes()
+ .to_vec();
+ let vec2_cap = [0, 0, 0, 0, 0, 0, 0, 2];
+ let vec2_len = [0, 0, 0, 0, 0, 0, 0, 2];
+ let vec2_data = [0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 6];
+
+ let vec1_data = chain!(vec2_ptr, vec2_cap, vec2_len, vec2_data).collect::>();
+
+ let expected = chain!(vec1_ptr, vec1_cap, vec1_len, vec1_data).collect::>();
+
+ assert_eq!(result, expected);
+
+ Ok(())
+ }
+
+ #[test]
+ fn encoding_bytes() -> Result<()> {
+ // arrange
+ let token = Token::Bytes(vec![1, 2, 3]);
+ let offset = 40;
+
+ // act
+ let encoded_bytes = ABIEncoder::encode(&[token])?.resolve(offset);
+
+ // assert
+ let ptr = [0, 0, 0, 0, 0, 0, 0, 64];
+ let cap = [0, 0, 0, 0, 0, 0, 0, 8];
+ let len = [0, 0, 0, 0, 0, 0, 0, 3];
+ let data = [1, 2, 3, 0, 0, 0, 0, 0];
+
+ let expected_encoded_bytes = [ptr, cap, len, data].concat();
+
+ assert_eq!(expected_encoded_bytes, encoded_bytes);
+
+ Ok(())
+ }
+
+ #[test]
+ fn encoding_raw_slices() -> Result<()> {
+ // arrange
+ let token = Token::RawSlice(vec![1, 2, 3]);
+ let offset = 40;
+
+ // act
+ let encoded_bytes = ABIEncoder::encode(&[token])?.resolve(offset);
+
+ // assert
+ let ptr = vec![0, 0, 0, 0, 0, 0, 0, 56];
+ let len = vec![0, 0, 0, 0, 0, 0, 0, 24];
+ let data = [
+ [0, 0, 0, 0, 0, 0, 0, 1],
+ [0, 0, 0, 0, 0, 0, 0, 2],
+ [0, 0, 0, 0, 0, 0, 0, 3],
+ ]
+ .concat();
+
+ let expected_encoded_bytes = [ptr, len, data].concat();
+
+ assert_eq!(expected_encoded_bytes, encoded_bytes);
+
+ Ok(())
+ }
+
+ #[test]
+ fn encoding_std_string() -> Result<()> {
+ // arrange
+ let string = String::from("This ");
+ let token = Token::String(string);
+ let offset = 40;
+
+ // act
+ let encoded_std_string = ABIEncoder::encode(&[token])?.resolve(offset);
+
+ // assert
+ let ptr = [0, 0, 0, 0, 0, 0, 0, 64];
+ let cap = [0, 0, 0, 0, 0, 0, 0, 8];
+ let len = [0, 0, 0, 0, 0, 0, 0, 5];
+ let data = [0x54, 0x68, 0x69, 0x73, 0x20, 0, 0, 0];
+
+ let expected_encoded_std_string = [ptr, cap, len, data].concat();
+
+ assert_eq!(expected_encoded_std_string, encoded_std_string);
+
+ Ok(())
+ }
+}
diff --git a/docs/beta-4/fuels-rs/packages/fuels-core/src/codec/function_selector.rs b/docs/beta-4/fuels-rs/packages/fuels-core/src/codec/function_selector.rs
new file mode 100644
index 00000000..2e04683e
--- /dev/null
+++ b/docs/beta-4/fuels-rs/packages/fuels-core/src/codec/function_selector.rs
@@ -0,0 +1,264 @@
+use sha2::{Digest, Sha256};
+
+use crate::types::{param_types::ParamType, ByteArray};
+
+/// Given a function name and its inputs will return a ByteArray representing
+/// the function selector as specified in the Fuel specs.
+pub fn resolve_fn_selector(name: &str, inputs: &[ParamType]) -> ByteArray {
+ let fn_signature = resolve_fn_signature(name, inputs);
+
+ first_four_bytes_of_sha256_hash(&fn_signature)
+}
+
+fn resolve_fn_signature(name: &str, inputs: &[ParamType]) -> String {
+ let fn_args = resolve_args(inputs);
+
+ format!("{name}({fn_args})")
+}
+
+fn resolve_args(arg: &[ParamType]) -> String {
+ arg.iter().map(resolve_arg).collect::>().join(",")
+}
+
+fn resolve_arg(arg: &ParamType) -> String {
+ match &arg {
+ ParamType::U8 => "u8".to_owned(),
+ ParamType::U16 => "u16".to_owned(),
+ ParamType::U32 => "u32".to_owned(),
+ ParamType::U64 => "u64".to_owned(),
+ ParamType::U128 => "s(u64,u64)".to_owned(),
+ ParamType::U256 => "s(u64,u64,u64,u64)".to_owned(),
+ ParamType::Bool => "bool".to_owned(),
+ ParamType::B256 => "b256".to_owned(),
+ ParamType::Unit => "()".to_owned(),
+ ParamType::StringSlice => "str".to_owned(),
+ ParamType::StringArray(len) => {
+ format!("str[{len}]")
+ }
+ ParamType::Array(internal_type, len) => {
+ let inner = resolve_arg(internal_type);
+ format!("a[{inner};{len}]")
+ }
+ ParamType::Struct {
+ fields, generics, ..
+ } => {
+ let gen_params = resolve_args(generics);
+ let field_params = resolve_args(fields);
+ let gen_params = if !gen_params.is_empty() {
+ format!("<{gen_params}>")
+ } else {
+ gen_params
+ };
+ format!("s{gen_params}({field_params})")
+ }
+ ParamType::Enum {
+ variants: fields,
+ generics,
+ ..
+ } => {
+ let gen_params = resolve_args(generics);
+ let field_params = resolve_args(fields.param_types());
+ let gen_params = if !gen_params.is_empty() {
+ format!("<{gen_params}>")
+ } else {
+ gen_params
+ };
+ format!("e{gen_params}({field_params})")
+ }
+ ParamType::Tuple(inner) => {
+ let inner = resolve_args(inner);
+ format!("({inner})")
+ }
+ ParamType::Vector(el_type) => {
+ let inner = resolve_arg(el_type);
+ format!("s<{inner}>(s<{inner}>(rawptr,u64),u64)")
+ }
+ ParamType::RawSlice => "rawslice".to_string(),
+ ParamType::Bytes => "s(s(rawptr,u64),u64)".to_string(),
+ ParamType::String => "s(s(s(rawptr,u64),u64))".to_string(),
+ }
+}
+
+/// Hashes an encoded function selector using SHA256 and returns the first 4 bytes.
+/// The function selector has to have been already encoded following the ABI specs defined
+/// [here](https://github.com/FuelLabs/fuel-specs/blob/1be31f70c757d8390f74b9e1b3beb096620553eb/specs/protocol/abi.md)
+pub(crate) fn first_four_bytes_of_sha256_hash(string: &str) -> ByteArray {
+ let string_as_bytes = string.as_bytes();
+ let mut hasher = Sha256::new();
+ hasher.update(string_as_bytes);
+ let result = hasher.finalize();
+ let mut output = ByteArray::default();
+ output[4..].copy_from_slice(&result[..4]);
+ output
+}
+
+#[macro_export]
+macro_rules! fn_selector {
+ ( $fn_name: ident ( $($fn_arg: ty),* ) ) => {
+ ::fuels::core::codec::resolve_fn_selector(
+ stringify!($fn_name),
+ &[$( <$fn_arg as ::fuels::core::traits::Parameterize>::param_type() ),*]
+ )
+ .to_vec()
+ }
+}
+
+pub use fn_selector;
+
+#[macro_export]
+macro_rules! calldata {
+ ( $($arg: expr),* ) => {
+ ::fuels::core::codec::ABIEncoder::encode(&[$(::fuels::core::traits::Tokenizable::into_token($arg)),*])
+ .map(|ub| ub.resolve(0))
+ }
+}
+
+pub use calldata;
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::types::enum_variants::EnumVariants;
+
+ #[test]
+ fn handles_primitive_types() {
+ let check_selector_for_type = |primitive_type: ParamType, expected_selector: &str| {
+ let selector = resolve_fn_signature("some_fun", &[primitive_type]);
+
+ assert_eq!(selector, format!("some_fun({expected_selector})"));
+ };
+
+ for (param_type, expected_signature) in [
+ (ParamType::U8, "u8"),
+ (ParamType::U16, "u16"),
+ (ParamType::U32, "u32"),
+ (ParamType::U64, "u64"),
+ (ParamType::Bool, "bool"),
+ (ParamType::B256, "b256"),
+ (ParamType::Unit, "()"),
+ (ParamType::StringArray(15), "str[15]"),
+ (ParamType::StringSlice, "str"),
+ ] {
+ check_selector_for_type(param_type, expected_signature);
+ }
+ }
+
+ #[test]
+ fn handles_std_strings() {
+ let inputs = [ParamType::String];
+
+ let signature = resolve_fn_signature("some_fn", &inputs);
+
+ assert_eq!(signature, "some_fn(s(s(s(rawptr,u64),u64)))");
+ }
+
+ #[test]
+ fn handles_arrays() {
+ let inputs = [ParamType::Array(Box::new(ParamType::U8), 1)];
+
+ let signature = resolve_fn_signature("some_fun", &inputs);
+
+ assert_eq!(signature, format!("some_fun(a[u8;1])"));
+ }
+
+ #[test]
+ fn handles_tuples() {
+ let inputs = [ParamType::Tuple(vec![ParamType::U8, ParamType::U8])];
+
+ let selector = resolve_fn_signature("some_fun", &inputs);
+
+ assert_eq!(selector, format!("some_fun((u8,u8))"));
+ }
+
+ #[test]
+ fn handles_structs() {
+ let fields = vec![ParamType::U64, ParamType::U32];
+ let generics = vec![ParamType::U32];
+ let inputs = [ParamType::Struct { fields, generics }];
+
+ let selector = resolve_fn_signature("some_fun", &inputs);
+
+ assert_eq!(selector, format!("some_fun(s(u64,u32))"));
+ }
+
+ #[test]
+ fn handles_vectors() {
+ let inputs = [ParamType::Vector(Box::new(ParamType::U32))];
+
+ let selector = resolve_fn_signature("some_fun", &inputs);
+
+ assert_eq!(selector, "some_fun(s(s(rawptr,u64),u64))")
+ }
+
+ #[test]
+ fn handles_bytes() {
+ let inputs = [ParamType::Bytes];
+
+ let selector = resolve_fn_signature("some_fun", &inputs);
+
+ assert_eq!(selector, "some_fun(s(s(rawptr,u64),u64))")
+ }
+
+ #[test]
+ fn handles_enums() {
+ let types = vec![ParamType::U64, ParamType::U32];
+ let variants = EnumVariants::new(types).unwrap();
+ let generics = vec![ParamType::U32];
+ let inputs = [ParamType::Enum { variants, generics }];
+
+ let selector = resolve_fn_signature("some_fun", &inputs);
+
+ assert_eq!(selector, format!("some_fun(e(u64,u32))"));
+ }
+
+ #[test]
+ fn ultimate_test() {
+ let fields = vec![ParamType::Struct {
+ fields: vec![ParamType::StringArray(2)],
+ generics: vec![ParamType::StringArray(2)],
+ }];
+ let struct_a = ParamType::Struct {
+ fields,
+ generics: vec![ParamType::StringArray(2)],
+ };
+
+ let fields = vec![ParamType::Array(Box::new(struct_a.clone()), 2)];
+ let struct_b = ParamType::Struct {
+ fields,
+ generics: vec![struct_a],
+ };
+
+ let fields = vec![ParamType::Tuple(vec![struct_b.clone(), struct_b.clone()])];
+ let struct_c = ParamType::Struct {
+ fields,
+ generics: vec![struct_b],
+ };
+
+ let types = vec![ParamType::U64, struct_c.clone()];
+ let fields = vec![
+ ParamType::Tuple(vec![
+ ParamType::Array(Box::new(ParamType::B256), 2),
+ ParamType::StringArray(2),
+ ]),
+ ParamType::Tuple(vec![
+ ParamType::Array(
+ Box::new(ParamType::Enum {
+ variants: EnumVariants::new(types).unwrap(),
+ generics: vec![struct_c],
+ }),
+ 1,
+ ),
+ ParamType::U32,
+ ]),
+ ];
+
+ let inputs = [ParamType::Struct {
+ fields,
+ generics: vec![ParamType::StringArray(2), ParamType::B256],
+ }];
+
+ let selector = resolve_fn_signature("complex_test", &inputs);
+
+ assert_eq!(selector, "complex_test(s((a[b256;2],str[2]),(a[e
(s(str[2]))>(a[s(s(str[2]));2])>((s(s(str[2]))>(a[s(s(str[2]));2]),s(s(str[2]))>(a[s(s(str[2]));2])))>(u64,s(s(str[2]))>(a[s(s(str[2]));2])>((s(s(str[2]))>(a[s(s(str[2]));2]),s(s(str[2]))>(a[s(s(str[2]));2]))));1],u32)))");
+ }
+}
diff --git a/docs/beta-4/fuels-rs/packages/fuels-core/src/lib.rs b/docs/beta-4/fuels-rs/packages/fuels-core/src/lib.rs
new file mode 100644
index 00000000..4fe809b0
--- /dev/null
+++ b/docs/beta-4/fuels-rs/packages/fuels-core/src/lib.rs
@@ -0,0 +1,24 @@
+pub mod codec;
+pub mod traits;
+pub mod types;
+mod utils;
+
+pub use utils::*;
+
+#[derive(Debug, Clone, Default)]
+pub struct Configurables {
+ offsets_with_data: Vec<(u64, Vec