ink_ir/ir/
selector.rs

1// Copyright (C) Use Ink (UK) Ltd.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use super::blake2::blake2b_256;
16use crate::literal::HexLiteral;
17use proc_macro2::TokenStream as TokenStream2;
18use quote::ToTokens;
19use std::marker::PhantomData;
20use syn::{
21    parse::Parser,
22    spanned::Spanned as _,
23};
24
25/// The ABI type used for selector computation.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum SelectorAbi {
28    /// ink! ABI (uses BLAKE-2 256-bit hash)
29    Ink,
30    /// Solidity ABI (uses Keccak-256 hash)
31    Sol,
32}
33
34/// Computes the Keccak-256 hash of the given input.
35fn keccak_256(input: &[u8], output: &mut [u8; 32]) {
36    use sha3::{
37        Digest,
38        Keccak256,
39    };
40    let mut hasher = Keccak256::new();
41    hasher.update(input);
42    output.copy_from_slice(&hasher.finalize());
43}
44
45/// The selector of an ink! dispatchable.
46///
47/// # Note
48///
49/// This is equal to the first four bytes of the BLAKE-2 256 hash of a function's name.
50#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
51pub struct Selector {
52    bytes: [u8; 4],
53}
54
55/// The trait prefix to compute a composed selector for trait implementation blocks.
56#[derive(Debug, Copy, Clone)]
57pub struct TraitPrefix<'a> {
58    /// The namespace of the ink! trait definition.
59    ///
60    /// By default this is equal to the `module_path!` at the ink! trait definition site.
61    /// It can be customized by the ink! trait definition author using `#[ink(namespace =
62    /// N)]` ink! attribute.
63    namespace: Option<&'a syn::LitStr>,
64    /// The Rust identifier of the ink! trait definition.
65    trait_ident: &'a syn::Ident,
66}
67
68impl<'a> TraitPrefix<'a> {
69    /// Creates a new trait prefix.
70    pub fn new(trait_ident: &'a syn::Ident, namespace: Option<&'a syn::LitStr>) -> Self {
71        Self {
72            namespace,
73            trait_ident,
74        }
75    }
76
77    /// Returns a vector over the bytes of the namespace.
78    pub fn namespace_bytes(&self) -> Vec<u8> {
79        self.namespace
80            .map(|namespace| namespace.value().into_bytes())
81            .unwrap_or_default()
82    }
83
84    /// Returns a shared reference to the Rust identifier of the trait.
85    pub fn trait_ident(&self) -> &'a syn::Ident {
86        self.trait_ident
87    }
88}
89
90impl Selector {
91    /// Computes the selector from the given input bytes using the specified ABI.
92    ///
93    /// - For `SelectorAbi::Ink`: uses BLAKE-2 256-bit hash
94    /// - For `SelectorAbi::Sol`: uses Keccak-256 hash
95    pub fn compute(input: &[u8], abi: SelectorAbi) -> Self {
96        let mut output = [0; 32];
97        match abi {
98            SelectorAbi::Ink => blake2b_256(input, &mut output),
99            SelectorAbi::Sol => keccak_256(input, &mut output),
100        }
101        Self::from([output[0], output[1], output[2], output[3]])
102    }
103
104    /// # Note
105    ///
106    /// - `trait_prefix` is `None` when computing the selector of ink! constructors and
107    ///   messages in inherent implementation blocks.
108    /// - `trait_prefix` is `Some` when computing the selector of ink! constructors and
109    ///   messages in trait implementation blocks. In this case the `namespace` is either
110    ///   the full path of the trait definition gained by Rust's `module_path!` macro by
111    ///   default or it is customized by manual application of the `#[ink(namespace =
112    ///   "my_namespace")]` ink! attribute. In the example `my_namespace` concatenated
113    ///   with `::` and the identifier of the trait definition would then be part of the
114    ///   provided `trait_prefix` parameter.
115    /// - `fn_ident` refers to the ink! constructor or message identifier.
116    ///
117    /// # Inherent Implementation Blocks
118    ///
119    /// For inherent implementation blocks, when `trait_prefix` is `None` the composed
120    /// selector is computed as follows:
121    ///
122    /// 1. Apply `BLAKE2` 256-bit hash `H` on the bytes of the ASCII representation of the
123    ///    `fn_ident` identifier.
124    /// 1. The first 4 bytes of `H` make up the selector.
125    ///
126    /// # Trait Implementation Blocks
127    ///
128    /// For trait implementation blocks, when `trait_prefix` is
129    /// `Some((namespace, trait_ident))` the composed selector is computed as follows:
130    ///
131    /// 1. Compute the ASCII byte representation of `fn_ident` and call it `F`.
132    /// 1. Compute the ASCII byte representation of `namespace` and call it `N`.
133    /// 1. Compute the ASCII byte representation of `trait_ident` and call it `T`.
134    /// 1. Concatenate `N`, `T` and `F` using `::` as separator and call it `C`.
135    /// 1. Apply the `BLAKE2` 256-bit hash `H` of `C` (or Keccak-256 for Solidity ABI).
136    /// 1. The first 4 bytes of `H` make up the selector.
137    pub fn compose<'a, T>(trait_prefix: T, fn_name: String, abi: SelectorAbi) -> Self
138    where
139        T: Into<Option<TraitPrefix<'a>>>,
140    {
141        let fn_ident = fn_name.into_bytes();
142        let input_bytes: Vec<u8> = match trait_prefix.into() {
143            Some(trait_prefix) => {
144                let namespace = trait_prefix.namespace_bytes();
145                let trait_ident = trait_prefix.trait_ident().to_string().into_bytes();
146                let separator = &b"::"[..];
147                if namespace.is_empty() {
148                    [&trait_ident[..], &fn_ident[..]].join(separator)
149                } else {
150                    [&namespace[..], &trait_ident[..], &fn_ident[..]].join(separator)
151                }
152            }
153            None => fn_ident.to_vec(),
154        };
155        Self::compute(&input_bytes, abi)
156    }
157
158    /// Returns the underlying four bytes.
159    pub fn to_bytes(&self) -> [u8; 4] {
160        self.bytes
161    }
162
163    /// Returns the big-endian `u32` representation of the selector bytes.
164    pub fn into_be_u32(self) -> u32 {
165        u32::from_be_bytes(self.bytes)
166    }
167
168    /// Returns the 4 bytes that make up the selector as hex encoded bytes.
169    pub fn hex_lits(self) -> [syn::LitInt; 4] {
170        self.bytes.map(<u8 as HexLiteral>::hex_padded_suffixed)
171    }
172}
173
174impl From<[u8; 4]> for Selector {
175    fn from(bytes: [u8; 4]) -> Self {
176        Self { bytes }
177    }
178}
179
180/// Used as generic parameter for the `selector_id!` macro.
181pub enum SelectorId {}
182
183/// Used as generic parameter for the `selector_bytes!` macro.
184pub enum SelectorBytes {}
185
186/// The selector ID of an ink! dispatchable.
187///
188/// # Note
189///
190/// This is mainly used for analysis and codegen of the `selector_id!` macro.
191#[derive(Debug)]
192pub struct SelectorMacro<T> {
193    selector: Selector,
194    input: syn::Lit,
195    abi: SelectorAbi,
196    _marker: PhantomData<fn() -> T>,
197}
198
199impl<T> SelectorMacro<T> {
200    /// Returns the underlying selector.
201    pub fn selector(&self) -> Selector {
202        self.selector
203    }
204
205    /// Returns the literal input of the selector ID.
206    pub fn input(&self) -> &syn::Lit {
207        &self.input
208    }
209
210    /// Returns the ABI used for selector computation.
211    pub fn abi(&self) -> SelectorAbi {
212        self.abi
213    }
214}
215
216impl<T> TryFrom<TokenStream2> for SelectorMacro<T> {
217    type Error = syn::Error;
218
219    fn try_from(input: TokenStream2) -> Result<Self, Self::Error> {
220        let input_span = input.span();
221
222        // Parse as a punctuated list, we require exactly 2 arguments
223        let parser =
224            syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated;
225        let exprs = parser.parse2(input.clone()).map_err(|error| {
226            format_err!(
227                input_span,
228                "expected Abi enum and string literal: {}",
229                error
230            )
231        })?;
232
233        // We require exactly 2 arguments: Abi enum and string literal
234        if exprs.len() != 2 {
235            return Err(format_err!(
236                input_span,
237                "expected exactly 2 arguments (Abi enum, string literal), found {}",
238                exprs.len()
239            ))
240        }
241
242        // Parse the ABI enum (first argument)
243        let abi = match &exprs[0] {
244            syn::Expr::Path(expr_path) => {
245                let path_str = expr_path
246                    .path
247                    .segments
248                    .iter()
249                    .map(|seg| seg.ident.to_string())
250                    .collect::<Vec<_>>()
251                    .join("::");
252
253                if path_str == "Abi::Ink" || path_str == "Ink" {
254                    SelectorAbi::Ink
255                } else if path_str == "Abi::Sol" || path_str == "Sol" {
256                    SelectorAbi::Sol
257                } else {
258                    return Err(format_err!(
259                        expr_path.span(),
260                        "expected Abi::Ink or Abi::Sol, found {}",
261                        path_str
262                    ))
263                }
264            }
265            invalid => {
266                return Err(format_err!(
267                    invalid.span(),
268                    "expected Abi enum (Abi::Ink or Abi::Sol) as first argument",
269                ))
270            }
271        };
272
273        // Parse the literal (second argument)
274        // We need to handle both direct literals and literals passed through declarative
275        // macros. When a literal is captured by a declarative macro and passed through,
276        // it may not be recognized as syn::Expr::Lit. In such cases, we try to parse
277        // the expression's token stream directly as a literal.
278        let lit = match &exprs[1] {
279            syn::Expr::Lit(expr_lit) => expr_lit.lit.clone(),
280            other_expr => {
281                // Try to parse the expression's tokens directly as a literal
282                // This handles cases where literals are passed through declarative macros
283                let tokens = other_expr.to_token_stream();
284                match syn::parse2::<syn::Lit>(tokens) {
285                    Ok(lit) => lit,
286                    Err(_) => {
287                        return Err(format_err!(
288                            other_expr.span(),
289                            "expected string or byte string literal as second argument",
290                        ))
291                    }
292                }
293            }
294        };
295
296        let input_bytes = match lit {
297            syn::Lit::Str(ref lit_str) => lit_str.value().into_bytes(),
298            syn::Lit::ByteStr(ref byte_str) => byte_str.value(),
299            ref invalid => {
300                return Err(format_err!(
301                    invalid.span(),
302                    "expected string or byte string literal as second argument. found {:?}",
303                    invalid,
304                ))
305            }
306        };
307
308        let selector = Selector::compute(&input_bytes, abi);
309        Ok(Self {
310            selector,
311            input: lit,
312            abi,
313            _marker: PhantomData,
314        })
315    }
316}
317
318#[cfg(test)]
319mod tests {
320    use super::*;
321
322    #[test]
323    fn hex_lits_works() {
324        let hex_lits = Selector::from([0xC0, 0xDE, 0xCA, 0xFE]).hex_lits();
325        assert_eq!(
326            hex_lits,
327            [
328                syn::parse_quote! { 0xC0_u8 },
329                syn::parse_quote! { 0xDE_u8 },
330                syn::parse_quote! { 0xCA_u8 },
331                syn::parse_quote! { 0xFE_u8 },
332            ]
333        )
334    }
335
336    #[test]
337    fn selector_sol_works() {
338        use quote::quote;
339        let sel = SelectorMacro::<crate::marker::SelectorBytes>::try_from(quote! {
340            Abi::Sol, "ownCodeHash()"
341        })
342        .unwrap();
343        assert_eq!(sel.selector.bytes, [219u8, 107, 220, 138]);
344    }
345
346    #[test]
347    fn selector_ink_works() {
348        use quote::quote;
349        let sel = SelectorMacro::<crate::marker::SelectorBytes>::try_from(quote! {
350            Abi::Ink, "flip"
351        })
352        .unwrap();
353        assert_eq!(sel.selector.bytes, hex_literal::hex!("633aa551"));
354    }
355}