diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index a42d26c..386cd5f 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -15,4 +15,5 @@ - [SRC-13: Soulbound Address](./src-13-soulbound-address.md) - [SRC-14: Simple Upgradeable Contract](./src-14-simple-upgradeable-proxies.md) - [SRC-15: Offchain Asset Metadata](./src-15-offchain-asset-metadata.md) +- [SRC-16: Typed Structured Data](./src-16-typed-structured-data.md) - [SRC-20: Native Asset](./src-20-native-asset.md) diff --git a/docs/src/index.md b/docs/src/index.md index 742d95d..5f3d3b5 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -59,6 +59,10 @@ use standards::src20::SRC20; - [SRC-8; Bridged Asset](./src-8-bridged-asset.md) defines the metadata required for an asset bridged to the Fuel Network. - [SRC-10; Native Bridge Standard](./src-10-native-bridge.md) defines the standard API for the Native Bridge between the Fuel Chain and the canonical base chain. +### Encoding and hashing + +- [SRC-16; Typed Structured Data](./src-16-typed-structured-data.md) defines standard encoding and hashing of typed structured data. + ### Documentation - [SRC-2; Inline Documentation](./src-2-inline-documentation.md) defines how to document your Sway files. diff --git a/docs/src/src-16-typed-structured-data.md b/docs/src/src-16-typed-structured-data.md new file mode 100644 index 0000000..d498adf --- /dev/null +++ b/docs/src/src-16-typed-structured-data.md @@ -0,0 +1,257 @@ +# SRC-16: Typed Structured Data + +The following standard sets out to standardize encoding and hashing of typed structured data. This enables secure off-chain message signing with human-readable data structures. + + +## Motivation + +As the Fuel ecosystem expands, there's an increasing need for applications to handle complex, human-readable data structures rather than raw bytes. When users sign messages or transactions, they should be able to clearly understand what they're signing, whether it's a simple asset transfer, or a complex DeFi interaction. Without a standard method for hashing structured data, developers risk implementing their own solutions, which could lead to confusion or compromise security. This standard provides a secure and consistent way to handle encoding and hashing of structured data, ensuring both safety and usability within ecosystem. + + +This standard aims to: + +* Provide a secure, standardized method for hashing structured data +* Enable clear presentation of structured data for user verification during signing +* Support complex data types that mirror Sway structs +* Enable domain separation to prevent cross-protocol replay attacks +* Define a consistent encoding scheme for structured data types +* Remain stateless, not requiring any storage attributes to enable use across all Fuel program types. + + +## Prior Art + +This standard uses ideas from [Ethereum's EIP-712 standard](https://eips.ethereum.org/EIPS/eip-712), adapting its concepts for the Fuel ecosystem. EIP-712 has proven successful in enabling secure structured data signing for applications like the various browser based wallets and signers that are utilized throughout various DeFi protocols. + +## Specification + +### Definition of Typed Structured Data 𝕊: + +The set of structured data 𝕊 consists of all instances of struct types that can be composed from the following types: + +Atomic Types: +```sway +u8 to u256 +bool +b256 (hash) +``` + +Dynamic Types: +```sway +Bytes // Variable-length byte sequences +String // Variable-length strings +``` + +Reference Types: + +Arrays (both fixed size and dynamic) +Structs (reference to other struct types) + + +Example struct definition: + +```sway +struct Mail { + from: Address, + to: Address, + contents: String, +} +``` + +### Domain Separator Encoding + +The domain separator provides context for the signing operation, preventing cross-protocol replay attacks. It is computed as hashStruct(domain) where domain is defined as: + +```sway +pub struct SRC16Domain { + name: String, // The protocol name (e.g., "MyProtocol") + version: String, // The protocol version (e.g., "1") + chain_id: u64, // The Fuel chain ID + verifying_contract: ContractId, // The contract id that will verify the signature +} +``` + +The encoding follows this scheme: + +* Add SRC16_DOMAIN_TYPE_HASH +* Add Keccak256 hash of name string +* Add Keccak256 hash of version string +* Add chain ID as 32-byte big-endian +* Add verifying contract id as 32 bytes + + +## Type Encoding + +Each struct type is encoded as name ‖ "(" ‖ member₁ ‖ "," ‖ member₂ ‖ "," ‖ … ‖ memberₙ ")" where each member is written as type ‖ " " ‖ name. + +Example: + +``` +Mail(address from,address to,string contents) +``` + +## Data Encoding + +### Definition of hashStruct + +The hashStruct function is defined as: + +hashStruct(s : 𝕊) = keccak256(typeHash ‖ encodeData(s)) +where: + +* typeHash = keccak256(encodeType(typeOf(s))) +* ‖ represents byte concatenation +* encodeType and encodeData are defined below + + +### Definition of encodeData + +The encoding of a struct instance is enc(value₁) ‖ enc(value₂) ‖ … ‖ enc(valueₙ), the concatenation of the encoded member values in the order they appear in the type. Each encoded member value is exactly 32 bytes long. + +The values are encoded as follows: + +Atomic Values: +* Boolean false and true are encoded as u64 values 0 and 1, padded to 32 bytes +* Addresses, ContractId, Identity, and b256 are encoded directly as 32 bytes +* Unsigned Integer values (u8 to u256) are encoded as big-endian bytes, padded to 32 bytes + +Dynamic Types: +* Bytes and String are encoded as their Keccak256 hash + +Reference Types: +* Arrays (both fixed and dynamic) are encoded as the Keccak256 hash of their concatenated encodings +* Struct values are encoded recursively as hashStruct(value) + +The implementation of `TypedDataHash` for `𝕊` SHALL utilize the `DataEncoder` for encoding each element of the struct based on its type. + +## Final Message Encoding + +The encoding of structured data follows this pattern: + +encode(domainSeparator : 𝔹²⁵⁶, message : 𝕊) = "\x19\x01" ‖ domainSeparator ‖ hashStruct(message) + +where: + +* \x19\x01 is a constant prefix +* ‖ represents byte concatenation +* domainSeparator is the 32-byte hash of the domain parameters +* hashStruct(message) is the 32-byte hash of the structured data + + + +## Example implementation: + +```sway +const MAIL_TYPE_HASH: b256 = 0x536e54c54e6699204b424f41f6dea846ee38ac369afec3e7c141d2c92c65e67f; + +impl TypedDataHash for Mail { + + fn type_hash() -> b256 { + MAIL_TYPE_HASH + } + + fn struct_hash(self) -> b256 { + let mut encoded = Bytes::new(); + encoded.append( + MAIL_TYPE_HASH.to_be_bytes() + ); + encoded.append( + DataEncoder::encode_address(self.from).to_be_bytes() + ); + encoded.append( + DataEncoder::encode_address(self.to).to_be_bytes() + ); + encoded.append( + DataEncoder::encode_string(self.contents).to_be_bytes() + ); + + keccak256(encoded) + } +} +``` + + +## Rationale + +* Domain separators provides protocol-specific context to prevent signature replay across different protocols and chains. +* Type hashes ensure type safety and prevent collisions between different data structures +* The encoding scheme is designed to be deterministic and injective +* The standard maintains compatibility with existing Sway types and practices + + +## Backwards Compatibility + +This standard is compatible with existing Sway data structures and can be implemented alongside other Fuel standards. It does not conflict with existing signature verification methods. + +### Type System Compatibility Notes + +When implementing SRC16 in relation to EIP712, the following type mappings and considerations apply: + +#### String Encoding +- Both standards use the same String type and encoding +- SRC16 specifically uses String type only (not Sway's `str` or `str[]`) +- String values are encoded identically in both standards using keccak256 hash + +#### Fixed Bytes +- EIP712's `bytes32` maps directly to Sway's `b256` +- Encoded using `encode_b256` in the DataEncoder +- Both standards handle 32-byte values identically +- Smaller fixed byte arrays (bytes1 to bytes31) are not supported in SRC16 + +#### Address Types +- EIP712 uses 20-byte Ethereum addresses +- When encoding an EIP712 address, SRC16: + - Takes only rightmost 20 bytes from a 32-byte Fuel Address + - Pads with zeros on the left for EIP712 compatibility + - Example: Fuel Address of 32 bytes becomes rightmost 20 bytes in EIP712 encoding + +#### ContractId Handling +- ContractId is unique to Fuel/SRC16 (no equivalent in EIP712) +- When encoding for EIP712 compatibility: + - Uses rightmost 20 bytes of ContractId + - Particularly important in domain separators where EIP712 expects a 20-byte address + +#### Domain Separator Compatibility +```rust +// SRC16 Domain (Fuel native) +pub struct SRC16Domain { + name: String, // Same as EIP712 + version: String, // Same as EIP712 + chain_id: u64, // Fuel chain ID + verifying_contract: ContractId, // Full 32-byte ContractId +} + +// EIP712 Domain (Ethereum compatible) +pub struct EIP712Domain { + name: String, + version: String, + chain_id: u256, + verifying_contract: b256, // Only rightmost 20 bytes used +} +``` + +Note: When implementing EIP712 compatibility within SRC16, the verifying_contract address in the EIP712Domain must be constructed by taking only the rightmost 20 bytes from either a Fuel ContractId or Address. This ensures proper compatibility with Ethereum's 20-byte addressing scheme in the domain separator. + +## Security Considerations + +### Replay Attacks: + +Implementers must ensure signatures cannot be replayed across: + +Different chains (prevented by chain_id) +Different protocols (prevented by domain separator) +Different contracts (prevented by verifying_contract) + +### Type Safety: + +Implementations must validate all type information and enforce strict encoding rules to prevent type confusion attacks. + + +## Example Implementation + +Example of the SRC-16 implementation where a contract utilizes the encoding scheme to produce a typed structured data hash of the Mail type. + +```sway +{{#include ../examples/src16-typed-data/fuel_example/src/main.sw}} + +{{#include ../examples/src16-typed-data/ethereum_example/src/main.sw}} +``` diff --git a/examples/src16-typed-data/Forc.toml b/examples/src16-typed-data/Forc.toml new file mode 100644 index 0000000..311d022 --- /dev/null +++ b/examples/src16-typed-data/Forc.toml @@ -0,0 +1,5 @@ +[workspace] +members = [ + "fuel_example", + "ethereum_example", +] \ No newline at end of file diff --git a/examples/src16-typed-data/ethereum_example/Forc.toml b/examples/src16-typed-data/ethereum_example/Forc.toml new file mode 100644 index 0000000..d2ff821 --- /dev/null +++ b/examples/src16-typed-data/ethereum_example/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Antony "] +entry = "main.sw" +license = "Apache-2.0" +name = "src16_ethereum_typed_data" + +[dependencies] +standards = { path = "../../../standards" } \ No newline at end of file diff --git a/examples/src16-typed-data/ethereum_example/src/main.sw b/examples/src16-typed-data/ethereum_example/src/main.sw new file mode 100644 index 0000000..c20412b --- /dev/null +++ b/examples/src16-typed-data/ethereum_example/src/main.sw @@ -0,0 +1,186 @@ +contract; + +use standards::src16::{ + SRC16Base, + EIP712, + EIP712Domain, + DomainHash, + TypedDataHash, + DataEncoder, + SRC16Payload, + SRC16Encode, +}; +use std::{ + bytes::Bytes, + string::String, + hash::*, + contract_id::*, +}; + + +configurable { + /// The name of the signing domain. + DOMAIN: str[8] = __to_str_array("MyDomain"), + /// The current major version for the signing domain. + VERSION: str[1] = __to_str_array("1"), + /// The active chain ID where the signing is intended to be used. Cast to u256 in domain_hash + CHAIN_ID: u64 = 9889u64, +} + + +/// A demo struct representing a mail message +pub struct Mail { + /// The sender's address + pub from: b256, + /// The recipient's address + pub to: b256, + /// The message contents + pub contents: String, +} + +/// The Keccak256 hash of the type Mail as UTF8 encoded bytes. +/// +/// "Mail(bytes32 from,bytes32 to,string contents)" +/// +/// cfc972d321844e0304c5a752957425d5df13c3b09c563624a806b517155d7056 +/// +const MAIL_TYPE_HASH: b256 = 0xcfc972d321844e0304c5a752957425d5df13c3b09c563624a806b517155d7056; + +impl TypedDataHash for Mail { + + fn type_hash() -> b256 { + MAIL_TYPE_HASH + } + + fn struct_hash(self) -> b256 { + let mut encoded = Bytes::new(); + + // Add the Mail type hash. + encoded.append( + MAIL_TYPE_HASH.to_be_bytes() + ); + // Use the DataEncoder to encode each field for known types + encoded.append( + DataEncoder::encode_b256(self.from).to_be_bytes() + ); + encoded.append( + DataEncoder::encode_b256(self.to).to_be_bytes() + ); + encoded.append( + DataEncoder::encode_string(self.contents).to_be_bytes() + ); + + keccak256(encoded) + } +} + +/// Implement the encode function for Mail using SRC16Payload +/// +/// # Additional Information +/// +/// 1. Get the encodeData hash of the Mail typed data using +/// ..struct_hash(); +/// 2. Obtain the payload to by populating the SRC16Payload struct +/// with the domain separator and data_hash from the previous step. +/// 3. Obtain the final_hash [Some(b256)] or None using the function +/// SRC16Payload::encode_hash() +/// +impl SRC16Encode for Mail { + + fn encode(s: Mail) -> b256 { + + // encodeData hash + let data_hash = s.struct_hash(); + // setup payload + let payload = SRC16Payload { + domain: _get_domain_separator(), + data_hash: data_hash, + }; + + // Get the final encoded hash + match payload.encode_hash() { + Some(hash) => hash, + None => revert(0), + } + } +} + + +impl SRC16Base for Contract { + + fn domain_separator_hash() -> b256 { + _get_domain_separator().domain_hash() + } + + fn data_type_hash() -> b256 { + MAIL_TYPE_HASH + } +} + +impl EIP712 for Contract { + + fn domain_separator() -> EIP712Domain { + _get_domain_separator() + } + +} + + +abi MailMe { + fn send_mail_get_hash( + from_addr: b256, + to_addr: b256, + contents: String, + ) -> b256; +} + +impl MailMe for Contract { + + /// Sends a some mail and returns its encoded hash + /// + /// # Arguments + /// + /// * `from_addr`: [b256] - The sender's address + /// * `to_addr`: [b256] - The recipient's address + /// * `contents`: [String] - The message contents + /// + /// # Returns + /// + /// * [b256] - The encoded hash of the mail data + /// + fn send_mail_get_hash( + from_addr: b256, + to_addr: b256, + contents: String, + ) -> b256 { + // Create the mail struct from data passed in call + let some_mail = Mail { + from: from_addr, + to: to_addr, + contents: contents, + }; + + Mail::encode(some_mail) + } + +} + +/// A program specific implementation to get the Ethereum EIP712Domain +/// +/// In a Contract the ContractID can be obtain with ContractId::this() +/// +/// In a Predicate or Script it is at the implementors discretion to +/// use the code root if they wish to contrain the validation to a +/// specifc program. +/// +fn _get_domain_separator() -> EIP712Domain { + EIP712Domain::new( + String::from_ascii_str(from_str_array(DOMAIN)), + String::from_ascii_str(from_str_array(VERSION)), + (asm(r1: (0, 0, 0, CHAIN_ID)) { r1: u256 }), + ContractId::this() + ) +} + + + diff --git a/examples/src16-typed-data/fuel_example/Forc.toml b/examples/src16-typed-data/fuel_example/Forc.toml new file mode 100644 index 0000000..40aa2e7 --- /dev/null +++ b/examples/src16-typed-data/fuel_example/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Antony "] +entry = "main.sw" +license = "Apache-2.0" +name = "src16_fuel_typed_data" + +[dependencies] +standards = { path = "../../../standards" } \ No newline at end of file diff --git a/examples/src16-typed-data/fuel_example/src/main.sw b/examples/src16-typed-data/fuel_example/src/main.sw new file mode 100644 index 0000000..2fb4d74 --- /dev/null +++ b/examples/src16-typed-data/fuel_example/src/main.sw @@ -0,0 +1,184 @@ +contract; + +use standards::src16::{ + SRC16Base, + SRC16, + SRC16Domain, + DomainHash, + TypedDataHash, + DataEncoder, + SRC16Payload, + SRC16Encode, +}; +use std::{ + bytes::Bytes, + string::String, + hash::*, + contract_id::*, +}; + + +configurable { + /// The name of the signing domain. + DOMAIN: str[8] = __to_str_array("MyDomain"), + /// The current major version for the signing domain. + VERSION: str[1] = __to_str_array("1"), + /// The active chain ID where the signing is intended to be used. Cast to u256 in domain_hash + CHAIN_ID: u64 = 9889u64, +} + + +/// A demo struct representing a mail message +pub struct Mail { + /// The sender's address + pub from: Address, + /// The recipient's address + pub to: Address, + /// The message contents + pub contents: String, +} + +/// The Keccak256 hash of the type Mail as UTF8 encoded bytes. +/// +/// "Mail(address from,address to,string contents)" +/// +/// 536e54c54e6699204b424f41f6dea846ee38ac369afec3e7c141d2c92c65e67f +/// +const MAIL_TYPE_HASH: b256 = 0x536e54c54e6699204b424f41f6dea846ee38ac369afec3e7c141d2c92c65e67f; + +impl TypedDataHash for Mail { + + fn type_hash() -> b256 { + MAIL_TYPE_HASH + } + + fn struct_hash(self) -> b256 { + let mut encoded = Bytes::new(); + // Add the Mail type hash. + encoded.append( + MAIL_TYPE_HASH.to_be_bytes() + ); + // Use the DataEncoder to encode each field for known types + encoded.append( + DataEncoder::encode_address(self.from).to_be_bytes() + ); + encoded.append( + DataEncoder::encode_address(self.to).to_be_bytes() + ); + encoded.append( + DataEncoder::encode_string(self.contents).to_be_bytes() + ); + + keccak256(encoded) + } +} + +/// Implement the encode function for Mail using SRC16Payload +/// +/// # Additional Information +/// +/// 1. Get the encodeData hash of the Mail typed data using +/// ..struct_hash(); +/// 2. Obtain the payload to by populating the SRC16Payload struct +/// with the domain separator and data_hash from the previous step. +/// 3. Obtain the final_hash [Some(b256)] or None using the function +/// SRC16Payload::encode_hash() +/// +impl SRC16Encode for Mail { + + fn encode(s: Mail) -> b256 { + + // encodeData hash + let data_hash = s.struct_hash(); + // setup payload + let payload = SRC16Payload { + domain: _get_domain_separator(), + data_hash: data_hash, + }; + + // Get the final encoded hash + match payload.encode_hash() { + Some(hash) => hash, + None => revert(0), + } + } +} + + +impl SRC16Base for Contract { + + fn domain_separator_hash() -> b256 { + _get_domain_separator().domain_hash() + } + + fn data_type_hash() -> b256 { + MAIL_TYPE_HASH + } +} + +impl SRC16 for Contract { + + fn domain_separator() -> SRC16Domain { + _get_domain_separator() + } + +} + + +abi MailMe { + fn send_mail_get_hash( + from_addr: Address, + to_addr: Address, + contents: String, + ) -> b256; +} + +impl MailMe for Contract { + + /// Sends a some mail and returns its encoded hash + /// + /// # Arguments + /// + /// * `from_addr`: [Address] - The sender's address + /// * `to_addr`: [Address] - The recipient's address + /// * `contents`: [String] - The message contents + /// + /// # Returns + /// + /// * [b256] - The encoded hash of the mail data + /// + fn send_mail_get_hash( + from_addr: Address, + to_addr: Address, + contents: String, + ) -> b256 { + // Create the mail struct from data passed in call + let some_mail = Mail { + from: from_addr, + to: to_addr, + contents: contents, + }; + + Mail::encode(some_mail) + } + +} + +/// A program specific implementation to get the Fuel SRC16Domain +/// +/// In a Contract the ContractID can be obtain with ContractId::this() +/// +/// In a Predicate or Script it is at the implementors discretion to +/// use the code root if they wish to contrain the validation to a +/// specifc program. +/// +fn _get_domain_separator() -> SRC16Domain { + SRC16Domain::new( + String::from_ascii_str(from_str_array(DOMAIN)), + String::from_ascii_str(from_str_array(VERSION)), + CHAIN_ID, + ContractId::this() + ) +} + + diff --git a/standards/src/src16.sw b/standards/src/src16.sw new file mode 100644 index 0000000..c19d2ee --- /dev/null +++ b/standards/src/src16.sw @@ -0,0 +1,1002 @@ +library; + +use std::{ + bytes::Bytes, + string::String, + hash::*, +}; +use std::bytes_conversions::{b256::*, u256::*, u64::*}; +use std::core::codec::{AbiEncode, encode}; + + +/// This base ABI provides the common hashing functionality that is +/// shared between the Fuel (SRC16) and Ethereum (EIP712) implementations. +abi SRC16Base { + /// Returns the Keccak256 hash of the encoded domain separator + /// + /// # Returns + /// + /// * [b256] - The domain separator hash computed from the domain parameters + fn domain_separator_hash() -> b256; + + /// Returns the Keccak256 hash of the structured data type + /// + /// # Returns + /// + /// * [b256] - The type hash specific to the implementing contract's data structure + fn data_type_hash() -> b256; +} + +/// Fuel-specific implementation of structured data signing +/// +/// # Additional Information +/// +/// Extends SRC16Base with Fuel-specific domain separator handling using +/// +abi SRC16 : SRC16Base { + /// Returns the domain separator struct for Fuel + /// + /// # Returns + /// + /// * [SRC16Domain] - The domain separator containing Fuel-specific parameters + fn domain_separator() -> SRC16Domain; +} + +/// Ethereum-compatible implementation of structured data signing +/// +/// # Additional Information +/// +/// Extends SRC16Base with Ethereum-compatible domain separator handling using +/// +abi EIP712 : SRC16Base { + /// Returns the domain separator struct for Ethereum compatibility + /// + /// # Returns + /// + /// * [EIP712Domain] - The domain separator containing Ethereum-compatible parameters + fn domain_separator() -> EIP712Domain; +} + + +/// Contains the core parameters that uniquely identify a domain for typed +/// data signing on Fuel. +pub struct SRC16Domain { + /// The name of the signing domain + name: String, + /// The current major version of the signing domain + version: String, + /// The active chain ID where the signing is intended to be used. + chain_id: u64, + /// The contract id of the contract that will verify the signature + verifying_contract: ContractId, +} + +/// The type hash constant for the SRC16Domain domain separator +/// +/// # Additional Information +/// +/// This is the Keccak256 hash of "SRC16Domain(string name,string version,uint256 chainId,contractId verifyingContract)" +pub const SRC16_DOMAIN_TYPE_HASH: b256 = 0x10f132d1adc99105bb9ad0d98956a93f35bda5c77713ac13adc489609c39336f; + +/// Encodes the SRC16Domain domain parameters using AbiEncode. +/// +/// # Additional Information +/// +/// The encoding follows the following scheme: +/// 1. add SRC16_DOMAIN_TYPE_HASH +/// 2. add Keccak256 hash of name string +/// 3. add Keccak256 hash of version string +/// 4. add Chain ID as 32-byte big-endian +/// 5. add Verifying contract address as 32-bytes +/// +impl AbiEncode for SRC16Domain { + fn abi_encode(self, buffer: Buffer) -> Buffer { + let buffer = SRC16_DOMAIN_TYPE_HASH.abi_encode(buffer); + let buffer = keccak256(Bytes::from(self.name)).abi_encode(buffer); + let buffer = keccak256(Bytes::from(self.version)).abi_encode(buffer); + let buffer = (asm(r1: (0, 0, 0, self.chain_id)) { r1: b256 }).abi_encode(buffer); + let buffer = self.verifying_contract.abi_encode(buffer); + buffer + } +} + +impl SRC16Domain { + + /// Creates a new SRC16Domain instance with the provided parameters + /// + /// # Arguments + /// + /// * `domain_name`: [String] - The name of the signing domain + /// * `version`: [String] - The version of the signing domain + /// * `chain_id`: [u64] - The chain ID where the contract is deployed + /// * `verifying_contract`: [b256] - The address of the contract that will verify the signature + /// + /// # Returns + /// + /// * [SRC16Domain] - A new instance of SRC16Domain with the provided parameters + /// + pub fn new( + domain_name: String, + version: String, + chain_id: u64, + verifying_contract: ContractId, + ) -> SRC16Domain { + SRC16Domain { + name: domain_name, + version: version, + chain_id: chain_id, + verifying_contract: verifying_contract, + } + } + + /// Computes the Keccak256 hash of the encoded domain parameters + /// + /// # Additional Information + /// + /// Utilizes AbiEncode for SRC16Domain + /// + /// # Returns + /// + /// * [b256] - The Keccak256 hash of the encoded domain parameters + /// + pub fn domain_hash(self) -> b256 { + let encoded = encode(self); + let bytes = Bytes::from(encoded); + keccak256(bytes) + } + +} + +/// The EIP712 Domain struct matching Ethereum's implementation +pub struct EIP712Domain { + /// The name of the signing domain + name: String, + /// The current major version of the signing domain + version: String, + /// The Ethereum chain ID + chain_id: u256, + /// The address of the contract that will verify the signature + verifying_contract: b256, +} + +/// The type hash constant for the EIP712Domain domain separator +/// +/// # Additional Information +/// +/// This is the Keccak256 hash of "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" +pub const EIP712_DOMAIN_TYPE_HASH: b256 = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; + +/// Encodes the EIP712Domain domain parameters using AbiEncode. +/// +/// # Additional Information +/// +/// The encoding follows the following scheme: +/// 1. add EIP712_DOMAIN_TYPE_HASH +/// 2. add Keccak256 hash of name string +/// 3. add Keccak256 hash of version string +/// 4. add Chain ID as 32-byte big-endian +/// 5. add Verifying contract address as 32-bytes +/// +impl AbiEncode for EIP712Domain { + fn abi_encode(self, buffer: Buffer) -> Buffer { + let buffer = EIP712_DOMAIN_TYPE_HASH.abi_encode(buffer); + let buffer = keccak256(Bytes::from(self.name)).abi_encode(buffer); + let buffer = keccak256(Bytes::from(self.version)).abi_encode(buffer); + let buffer = (self.chain_id).abi_encode(buffer); + let buffer = self.verifying_contract.abi_encode(buffer); + buffer + } +} + +impl EIP712Domain { + + /// Creates a new EIP712Domain instance with the provided parameters + /// + /// # Arguments + /// + /// * `domain_name`: [String] - The name of the signing domain + /// * `version`: [String] - The version of the signing domain + /// * `chain_id`: [u256] - The chain ID where the contract is deployed + /// * `verifying_contract`: [b256] - The address (last 20-Bytes) of the contract that will verify the signature + /// + /// # Returns + /// + /// * [EIP712Domain] - A new instance of EIP712Domain with the provided parameters + /// + pub fn new( + domain_name: String, + version: String, + chain_id: u256, + verifying_contract: ContractId, + ) -> EIP712Domain { + + // Take only the rightmost 20 bytes from a 32-byte Fuel ContractId + let mut verifying_contract_last_20 = Bytes::with_capacity(32); + let mut i = 0; + while i < 12 { + verifying_contract_last_20.push(0); + i += 1; + } + let (_, last_20): (Bytes, Bytes) = verifying_contract.bits().to_be_bytes().split_at(12); + verifying_contract_last_20.append(last_20); + + EIP712Domain { + name: domain_name, + version: version, + chain_id: chain_id, + verifying_contract: verifying_contract_last_20.into(), + } + } + + /// Computes the Keccak256 hash of the encoded domain parameters + /// + /// # Additional Information + /// + /// Utilizes AbiEncode for EIP712Domain + /// + /// # Returns + /// + /// * [b256] - The Keccak256 hash of the encoded domain parameters + /// + pub fn domain_hash(self) -> b256 { + let encoded = encode(self); + let bytes = Bytes::from(encoded); + keccak256(bytes) + } + +} + + +/// Trait for types that can be hashed in a structured way +/// +pub trait TypedDataHash { + + /// The type hash constant for this struct + fn type_hash() -> b256; + + /// Return the Keccak256 hash of the encoded typed structured data. + /// + /// # Arguments + /// + /// * `self` : [] - A custom data structure used by the SRC16 validator. + /// # Returns + /// + /// * [b256] - The Keccak256 hash of the encoded structured data + /// + /// # Additional Information + /// + /// This is a per-program implementation. This function should be implemented + /// for the . The DataEncoder can be used to encoded known data types. + /// + /// Implementors should ensure their hash computation follows the SRC16 specification. + /// + /// # Example + /// + /// ```sway + /// use standards::src16::{SRC16, TypedDataHash}; + /// + /// impl TypedDataHash for { + /// fn struct_hash(self) -> b256 { + /// let mut encoded = Bytes::new(); + /// + /// ... implement encodeData for S using DataEncoder ... + /// + /// keccak256(encoded) + /// } + /// ``` + /// + fn struct_hash(self) -> b256; +} + +/// Holds the signing domain and types data hash used by DomainHash. +pub struct SRC16Payload { + pub domain: D, + pub data_hash: b256, +} + +impl SRC16Payload { + + /// Computes the final encoded hash according to SRC16/EIP712 specification + /// + /// # Additional Information + /// + /// The encoding follows this scheme: + /// 1. Add prefix bytes \x19\x01 + /// 2. Add domain separator hash from either SRC16Domain or EIP712Domain + /// 3. Add struct data hash + /// 4. Compute final Keccak256 hash + /// + /// # Arguments + /// + /// * `self`: [SRC16Payload] - The payload containing domain parameters and data hash + /// + /// # Returns + /// + /// * [Option] - The final encoded hash, or None if encoding fails + /// + /// # Examples + /// + /// ```sway + /// fn encode_mail_data(mail: Mail, domain: SRC16Domain) { + /// let payload = SRC16Payload { + /// domain: domain, + /// data_hash: mail.struct_hash(), + /// }; + /// let final_hash = payload.encode_hash(); + /// } + /// ``` + pub fn encode_hash(self) -> Option + where D: DomainHash + { + let domain_separator_bytes = self.domain.domain_hash().to_be_bytes(); + let data_hash_bytes = self.data_hash.to_be_bytes(); + let mut digest_input = Bytes::with_capacity(66); + digest_input.push(0x19); + digest_input.push(0x01); + digest_input.append(domain_separator_bytes); + digest_input.append(data_hash_bytes); + let final_hash = keccak256(digest_input); + + Some(final_hash) + } + +} + +/// Trait for domain types that can be hashed +pub trait DomainHash { + fn domain_hash(self) -> b256; +} + +/// Implementation of domain hashing for both SRC16 and EIP712 domains +/// +/// # Additional Information +/// +/// Both domain types provide their own domain_hash() implementation following their +/// respective encoding schemes: +/// * SRC16Domain uses "SRC16Domain(string name,string version,uint256 chainId,address verifyingContract)" +/// * EIP712Domain uses "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" +/// +/// # Returns +/// +/// * [b256] - The Keccak256 hash of the encoded domain parameters +/// +impl DomainHash for SRC16Domain { + fn domain_hash(self) -> b256 { + self.domain_hash() + } +} + +impl DomainHash for EIP712Domain { + fn domain_hash(self) -> b256 { + self.domain_hash() + } +} + + +pub trait SRC16Encode { + + /// Returns the combined typed data hash according to SRC16 specification. + /// + /// # Arguments + /// + /// * `s`: [T] - A generic structured data type defined in the implementing program. + /// + /// # Additional Information + /// + /// This function produces a domain-bound hash by combining: + /// 1. The prefix bytes (\x19\x01) + /// 2. The domain separator hash + /// 3. The structured data hash + /// + /// # Arguments + /// + /// * `data_hash`: [b256] - The Keccak256 hash of the encoded structured data + /// + /// # Returns + /// + /// * [b256] - The combined typed data hash. + /// + fn encode(s: T) -> b256; +} + + +/// This trait provides common encoding methods for different data types. +/// +/// # Additional Information +/// +/// This trait standardizes the encoding of common data types used in structured data. +/// All unsigned integers are encoded to 32 byte big-endian format. +/// +pub trait TypedDataEncoder { + fn encode_string(value: String) -> b256; + fn encode_u8(value: u8) -> b256; + fn encode_u16(value: u16) -> b256; + fn encode_u32(value: u32) -> b256; + fn encode_u64(value: u64) -> b256; + fn encode_u256(value: u256) -> b256; + fn encode_b256(value: b256) -> b256; + fn encode_bool(value: bool) -> b256; + fn dynamic_u8_array(array: Vec) -> b256; + fn dynamic_u16_array(array: Vec) -> b256; + fn dynamic_u32_array(array: Vec) -> b256; + fn dynamic_u64_array(array: Vec) -> b256; + fn dynamic_u256_array(array: Vec) -> b256; + fn dynamic_b256_array(array: Vec) -> b256; + fn encode_address(value: Address) -> b256; + fn encode_contract_id(value: ContractId) -> b256; + fn encode_identity(value: Identity) -> b256; +} + +/// Standard implementation of typed data encoding methods +pub struct DataEncoder {} + +/// Implementation of typed data encoding methods according to the SRC16 specification. +/// +/// # Additional Information +/// +/// This implementation provides encoding methods for both primitive types and dynamic arrays. +/// All encoded values are 32 bytes (b256) to maintain backwards compatibility with the EIP712 +/// specification. +/// +impl TypedDataEncoder for DataEncoder { + + /// Encodes a String value by taking the Keccak256 hash of its bytes. + /// + /// # Arguments + /// + /// * `value`: [String] - The string value to encode + /// + /// # Returns + /// + /// * [b256] - The keccak256 hash of the string's bytes + fn encode_string(value: String) -> b256 { + keccak256(Bytes::from(value)) + } + + /// Encodes a u8 value into a 32-byte value (big-endian, padded with zeros). + /// + /// # Arguments + /// + /// * `value`: [u8] - The u8 value to encode + /// + /// # Returns + /// + /// * [b256] - The value padded to 32 bytes + fn encode_u8(value: u8) -> b256 { + asm(r1: (0, 0, 0, value.as_u64())) { r1: b256 } + } + + /// Encodes a u16 value into a 32-byte value (big-endian, padded with zeros). + /// + /// # Arguments + /// + /// * `value`: [u16] - The u16 value to encode + /// + /// # Returns + /// + /// * [b256] - The value padded to 32 bytes + fn encode_u16(value: u16) -> b256 { + asm(r1: (0, 0, 0, value.as_u64())) { r1: b256 } + } + + /// Encodes a u32 value into a 32-byte value (big-endian, padded with zeros). + /// + /// # Arguments + /// + /// * `value`: [u32] - The u32 value to encode + /// + /// # Returns + /// + /// * [b256] - The value padded to 32 bytes + fn encode_u32(value: u32) -> b256 { + asm(r1: (0, 0, 0, value.as_u64())) { r1: b256 } + } + + /// Encodes a u64 value into a 32-byte value (big-endian, padded with zeros). + /// + /// # Arguments + /// + /// * `value`: [u64] - The u64 value to encode + /// + /// # Returns + /// + /// * [b256] - The value padded to 32 bytes + fn encode_u64(value: u64) -> b256 { + asm(r1: (0, 0, 0, value)) { r1: b256 } + } + + /// Encodes a u256 value into a 32-byte value (big-endian, padded with zeros). + /// + /// # Arguments + /// + /// * `value`: [u256] - The u256 value to encode + /// + /// # Returns + /// + /// * [b256] - The value padded to 32 bytes + fn encode_u256(value: u256) -> b256 { + value.as_b256() + } + + /// Encodes as it original b256 value. + /// + /// # Arguments + /// + /// * `value`: [b256] - The b256 value to encode + /// + /// # Returns + /// + /// * [b256] - The value padded to 32 bytes + fn encode_b256(value: b256) -> b256 { + value + } + + /// Encodes a boolean value into a 32-byte value + /// + /// # Additional Information + /// + /// Encodes bool values as follows in b256 big-endian format: + /// * false = 0 + /// * true = 1 + /// + /// # Arguments + /// + /// * `value`: [bool] - The boolean to encode + /// + /// # Returns + /// + /// * [b256] - The encoded value + fn encode_bool(value: bool) -> b256 { + let value_as_uint = if value { 1u64 } else { 0u64 }; + asm(r1: (0, 0, 0, value_as_uint)) { r1: b256 } + } + + /// Encodes a dynamic array of u8 values into a single 32-byte hash. + /// + /// # Additional Information + /// + /// The encoding follows this scheme: + /// 1. Each u8 value in the array is encoded to a b256 + /// 2. The encoded values are concatenated in order + /// 3. The concatenated bytes are hashed with Keccak256 + /// + /// # Arguments + /// + /// * `array`: [Vec] - The array of u8 values to encode + /// + /// # Returns + /// + /// * [b256] - The Keccak256 hash of the concatenated encoded values + fn dynamic_u8_array(array: Vec) -> b256 { + let mut encoded = Bytes::new(); + for v in array.iter() { + encoded.append( + (asm(r1: (0, 0, 0, v.as_u64())) { r1: b256 }).to_be_bytes() + ); + } + keccak256(encoded) + + } + + /// Encodes a dynamic array of u16 values into a single 32-byte hash. + /// + /// # Additional Information + /// + /// The encoding follows this scheme: + /// 1. Each u16 value in the array is encoded to a b256 + /// 2. The encoded values are concatenated in order + /// 3. The concatenated bytes are hashed with Keccak256 + /// + /// # Arguments + /// + /// * `array`: [Vec] - The array of u16 values to encode + /// + /// # Returns + /// + /// * [b256] - The Keccak256 hash of the concatenated encoded values + fn dynamic_u16_array(array: Vec) -> b256 { + let mut encoded = Bytes::new(); + for v in array.iter() { + encoded.append( + (asm(r1: (0, 0, 0, v.as_u64())) { r1: b256 }).to_be_bytes() + ); + } + keccak256(encoded) + } + + /// Encodes a dynamic array of u32 values into a single 32-byte hash. + /// + /// # Additional Information + /// + /// The encoding follows this scheme: + /// 1. Each u32 value in the array is encoded to a b256 + /// 2. The encoded values are concatenated in order + /// 3. The concatenated bytes are hashed with Keccak256 + /// + /// # Arguments + /// + /// * `array`: [Vec] - The array of u32 values to encode + /// + /// # Returns + /// + /// * [b256] - The Keccak256 hash of the concatenated encoded values + fn dynamic_u32_array(array: Vec) -> b256 { + let mut encoded = Bytes::new(); + for v in array.iter() { + encoded.append( + (asm(r1: (0, 0, 0, v.as_u64())) { r1: b256 }).to_be_bytes() + ); + } + keccak256(encoded) + } + + /// Encodes a dynamic array of u64 values into a single 32-byte hash. + /// + /// # Additional Information + /// + /// The encoding follows this scheme: + /// 1. Each u64 value in the array is encoded to a b256 + /// 2. The encoded values are concatenated in order + /// 3. The concatenated bytes are hashed with Keccak256 + /// + /// # Arguments + /// + /// * `array`: [Vec] - The array of u64 values to encode + /// + /// # Returns + /// + /// * [b256] - The Keccak256 hash of the concatenated encoded values + fn dynamic_u64_array(array: Vec) -> b256 { + let mut encoded = Bytes::new(); + for v in array.iter() { + encoded.append( + (asm(r1: (0, 0, 0, v)) { r1: b256 }).to_be_bytes() + ); + } + keccak256(encoded) + } + + /// Encodes a dynamic array of u256 values into a single 32-byte hash. + /// + /// # Additional Information + /// + /// The encoding follows this scheme: + /// 1. Each u256 value in the array is encoded to a b256 + /// 2. The encoded values are concatenated in order + /// 3. The concatenated bytes are hashed with Keccak256 + /// + /// # Arguments + /// + /// * `array`: [Vec] - The array of u256 values to encode + /// + /// # Returns + /// + /// * [b256] - The Keccak256 hash of the concatenated encoded values + fn dynamic_u256_array(array: Vec) -> b256 { + let mut encoded = Bytes::new(); + for v in array.iter() { + encoded.append( + v.to_be_bytes() + ); + } + keccak256(encoded) + } + + /// Encodes a dynamic array of b256 values into a single 32-byte hash. + /// + /// # Additional Information + /// + /// The encoding follows this scheme: + /// 1. Each b256 value in the array is encoded to a b256 + /// 2. The encoded values are concatenated in order + /// 3. The concatenated bytes are hashed with Keccak256 + /// + /// # Arguments + /// + /// * `array`: [Vec] - The array of b256 values to encode + /// + /// # Returns + /// + /// * [b256] - The Keccak256 hash of the concatenated encoded values + fn dynamic_b256_array(array: Vec) -> b256 { + let mut encoded = Bytes::new(); + for v in array.iter() { + encoded.append( + v.to_be_bytes() + ); + } + keccak256(encoded) + } + + /// Encodes an Address into a 32-byte value. + /// + /// # Arguments + /// + /// * `value`: [Address] - The address to encode + /// + /// # Returns + /// + /// * [b256] - The address as a b256 + fn encode_address(value: Address) -> b256 { + value.into() + } + + /// Encodes a ContractId into a 32-byte value. + /// + /// # Arguments + /// + /// * `value`: [ContractId] - The contract ID to encode + /// + /// # Returns + /// + /// * [b256] - The contract ID as a b256 + fn encode_contract_id(value: ContractId) -> b256 { + value.into() + } + + /// Encodes an Identity enum into a 32-byte value. + /// + /// # Additional Information + /// + /// Handles both Address and ContractId variants by accessing their underlying b256 representation. + /// + /// # Arguments + /// + /// * `value`: [Identity] - The identity to encode + /// + /// # Returns + /// + /// * [b256] - The encoded identity value + fn encode_identity(value: Identity) -> b256 { + match value { + Identity::Address(addr) => addr.bits(), + Identity::ContractId(contract_id) => contract_id.bits(), + } + } + +} + + +/// This enum determines the encoder type for fixed-length array encoding. +pub enum EncoderType { + String: (), + U8: (), + U16: (), + U32: (), + U64: (), + U256: (), + B256: (), + Address: (), + ContractId: (), + Identity: () +} + +/// This trait provides standard methods for encoding a fixed array of typed +/// values into a single 32-byte hash. +/// +/// # Additional Information +/// +/// The encoding follows this scheme: +/// 1. Each typed value in the array is encoded to a b256 using +/// the respective DataEncoder method. +/// 2. The encoded values are concatenated in order +/// 3. The concatenated bytes are hashed with Keccak256 +/// +/// # Arguments +/// +/// * `slice`: [raw_slice] - The raw slice containing the typed array +/// +/// # Returns +/// +/// * [b256] - The Keccak256 hash of the concatenated encoded values +/// +pub trait FixedDataEncoder { + + fn encode_fixed_string_array(slice: raw_slice) -> b256; + fn encode_fixed_u8_array(slice: raw_slice) -> b256; + fn encode_fixed_u16_array(slice: raw_slice) -> b256; + fn encode_fixed_u32_array(slice: raw_slice) -> b256; + fn encode_fixed_u64_array(slice: raw_slice) -> b256; + fn encode_fixed_u256_array(slice: raw_slice) -> b256; + fn encode_fixed_b256_array(slice: raw_slice) -> b256; + fn encode_fixed_address_array(slice: raw_slice) -> b256; + fn encode_fixed_contract_id_array(slice: raw_slice) -> b256; + fn encode_fixed_identity_array(slice: raw_slice) -> b256; + +} + +impl FixedDataEncoder for DataEncoder { + + /// Encodes a fixed array of String values into a single 32-byte hash. + fn encode_fixed_string_array(slice: raw_slice) -> b256 { + let mut encoded = Bytes::new(); + let len = slice.len::(); + let ptr = slice.ptr(); + + let mut i = 0; + while i < len { + let item = ptr.add::(i).read::(); + encoded.append(DataEncoder::encode_string(item).to_be_bytes()); + i += 1; + } + keccak256(encoded) + } + + /// Encodes a fixed array of u8 values into a single 32-byte hash. + fn encode_fixed_u8_array(slice: raw_slice) -> b256 { + let mut encoded = Bytes::new(); + let len = slice.len::(); + let ptr = slice.ptr(); + + let mut i = 0; + while i < len { + let item = ptr.add::(i).read::(); + encoded.append(DataEncoder::encode_u8(item).to_be_bytes()); + i += 1; + } + keccak256(encoded) + } + + /// Encodes a fixed array of u16 values into a single 32-byte hash. + fn encode_fixed_u16_array(slice: raw_slice) -> b256 { + let mut encoded = Bytes::new(); + let len = slice.len::(); + let ptr = slice.ptr(); + + let mut i = 0; + while i < len { + let item = ptr.add::(i).read::(); + encoded.append(DataEncoder::encode_u16(item).to_be_bytes()); + i += 1; + } + keccak256(encoded) + } + + /// Encodes a fixed array of u32 values into a single 32-byte hash. + fn encode_fixed_u32_array(slice: raw_slice) -> b256 { + let mut encoded = Bytes::new(); + let len = slice.len::(); + let ptr = slice.ptr(); + + let mut i = 0; + while i < len { + let item = ptr.add::(i).read::(); + encoded.append(DataEncoder::encode_u32(item).to_be_bytes()); + i += 1; + } + keccak256(encoded) + } + + /// Encodes a fixed array of u64 values into a single 32-byte hash. + fn encode_fixed_u64_array(slice: raw_slice) -> b256 { + let mut encoded = Bytes::new(); + let len = slice.len::(); + let ptr = slice.ptr(); + + let mut i = 0; + while i < len { + let item = ptr.add::(i).read::(); + encoded.append(DataEncoder::encode_u64(item).to_be_bytes()); + i += 1; + } + keccak256(encoded) + } + + /// Encodes a fixed array of u256 values into a single 32-byte hash. + fn encode_fixed_u256_array(slice: raw_slice) -> b256 { + let mut encoded = Bytes::new(); + let len = slice.len::(); + let ptr = slice.ptr(); + + let mut i = 0; + while i < len { + let item = ptr.add::(i).read::(); + encoded.append(DataEncoder::encode_u256(item).to_be_bytes()); + i += 1; + } + keccak256(encoded) + } + + /// Encodes a fixed array of b256 values into a single 32-byte hash. + fn encode_fixed_b256_array(slice: raw_slice) -> b256 { + let mut encoded = Bytes::new(); + let len = slice.len::(); + let ptr = slice.ptr(); + + let mut i = 0; + while i < len { + let item = ptr.add::(i).read::(); + encoded.append(DataEncoder::encode_b256(item).to_be_bytes()); + i += 1; + } + keccak256(encoded) + } + + /// Encodes a fixed array of Address values into a single 32-byte hash. + fn encode_fixed_address_array(slice: raw_slice) -> b256 { + let mut encoded = Bytes::new(); + let len = slice.len::
(); + let ptr = slice.ptr(); + + let mut i = 0; + while i < len { + let item = ptr.add::
(i).read::
(); + encoded.append(DataEncoder::encode_address(item).to_be_bytes()); + i += 1; + } + keccak256(encoded) + } + + /// Encodes a fixed array of ContractId values into a single 32-byte hash. + fn encode_fixed_contract_id_array(slice: raw_slice) -> b256 { + let mut encoded = Bytes::new(); + let len = slice.len::(); + let ptr = slice.ptr(); + + let mut i = 0; + while i < len { + let item = ptr.add::(i).read::(); + encoded.append(DataEncoder::encode_contract_id(item).to_be_bytes()); + i += 1; + } + keccak256(encoded) + } + + /// Encodes a fixed array of Identity values into a single 32-byte hash + fn encode_fixed_identity_array(slice: raw_slice) -> b256 { + let mut encoded = Bytes::new(); + let len = slice.len::(); + let ptr = slice.ptr(); + + let mut i = 0; + while i < len { + let item = ptr.add::(i).read::(); + encoded.append(DataEncoder::encode_identity(item).to_be_bytes()); + i += 1; + } + keccak256(encoded) + } + +} + + +/// This trait provides encoding functionality for fixed-length arrays of different types. +pub trait FixedArrayEncoder { + /// Encodes a fixed-length array into a 32-byte hash based on the specified encoder type. + /// + /// # Additional Information + /// + /// This function acts as a dispatcher that routes to specific encoding implementations + /// based on the provided EncoderType. The encoding follows this scheme: + /// 1. Each element is encoded according to its type-specific rules + /// 2. The encoded values are concatenated in order + /// 3. The concatenated bytes are hashed with Keccak256 + /// + /// # Arguments + /// + /// * `slice`: [raw_slice] - The raw slice containing the array data + /// * `encoder_type`: [EncoderType] - The type of encoder to use for the array elements + /// + /// # Returns + /// + /// * [b256] - The Keccak256 hash of the concatenated encoded values + fn encode_fixed_array(slice: raw_slice, encoder_type: EncoderType) -> b256; +} + +impl FixedArrayEncoder for DataEncoder { + + fn encode_fixed_array(slice: raw_slice, encoder_type: EncoderType) -> b256 { + match encoder_type { + EncoderType::String => Self::encode_fixed_string_array(slice), + EncoderType::U8 => Self::encode_fixed_u8_array(slice), + EncoderType::U16 => Self::encode_fixed_u16_array(slice), + EncoderType::U32 => Self::encode_fixed_u32_array(slice), + EncoderType::U64 => Self::encode_fixed_u64_array(slice), + EncoderType::U256 => Self::encode_fixed_u256_array(slice), + EncoderType::B256 => Self::encode_fixed_b256_array(slice), + EncoderType::Address => Self::encode_fixed_address_array(slice), + EncoderType::ContractId => Self::encode_fixed_contract_id_array(slice), + EncoderType::Identity => Self::encode_fixed_identity_array(slice), + } + } +} diff --git a/standards/src/standards.sw b/standards/src/standards.sw index 67698b1..f71f644 100644 --- a/standards/src/standards.sw +++ b/standards/src/standards.sw @@ -9,4 +9,5 @@ pub mod src11; pub mod src12; pub mod src14; pub mod src15; +pub mod src16; pub mod src20;