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}