1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
use crate::{EventParam, Param};
use alloc::string::String;

macro_rules! signature {
    ($name:expr, $inputs:expr, $preimage:expr) => {{
        $preimage.push_str($name);

        $preimage.push('(');
        for (i, input) in $inputs.iter().enumerate() {
            if i > 0 {
                $preimage.push(',');
            }
            input.selector_type_raw($preimage);
        }
        $preimage.push(')');
    }};
}

macro_rules! validate_identifier {
    ($name:expr) => {
        if !$name.is_empty() && !alloy_sol_type_parser::is_valid_identifier($name) {
            return Err(serde::de::Error::invalid_value(
                serde::de::Unexpected::Str($name),
                &"a valid solidity identifier in the name field",
            ))
        }
    };
}
pub(crate) use validate_identifier;

macro_rules! validate_ty {
    ($ty:expr) => {
        // dirty hacks to allow `address payable` in the ABI JSON internalType
        // field
        if $ty != "address payable" {
            alloy_sol_type_parser::TypeSpecifier::parse($ty).map_err(|_| {
                serde::de::Error::invalid_value(
                    Unexpected::Str($ty),
                    &"a valid solidity type specifier",
                )
            })?;
        }
    };
}

pub(crate) use validate_ty;

pub(crate) fn signature(name: &str, inputs: &[Param]) -> String {
    let mut preimage = String::with_capacity(name.len() + 2 + inputs.len() * 32);
    signature_raw(name, inputs, &mut preimage);
    preimage
}

pub(crate) fn signature_raw(name: &str, inputs: &[Param], preimage: &mut String) {
    signature!(name, inputs, preimage)
}

pub(crate) fn event_signature(name: &str, inputs: &[EventParam]) -> String {
    let mut preimage = String::with_capacity(name.len() + 2 + inputs.len() * 32);
    event_signature_raw(name, inputs, &mut preimage);
    preimage
}

pub(crate) fn event_signature_raw(name: &str, inputs: &[EventParam], preimage: &mut String) {
    signature!(name, inputs, preimage)
}

/// `keccak256(preimage)[..4]`
pub(crate) fn selector(preimage: &str) -> [u8; 4] {
    // SAFETY: splitting an array
    unsafe {
        alloy_primitives::keccak256(preimage.as_bytes())
            .0
            .get_unchecked(..4)
            .try_into()
            .unwrap_unchecked()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use alloc::string::ToString;

    fn param(kind: &str) -> Param {
        crate::Param {
            name: "param".to_string(),
            ty: kind.to_string(),
            internal_type: None,
            components: vec![],
        }
    }

    fn params(components: impl IntoIterator<Item = &'static str>) -> Param {
        let components = components.into_iter().map(param).collect();
        crate::Param {
            name: "param".to_string(),
            ty: "ty".to_string(),
            internal_type: None,
            components,
        }
    }

    #[test]
    fn test_signature() {
        assert_eq!(signature("foo", &[]), "foo()");
        assert_eq!(signature("foo", &[param("bool")]), "foo(bool)");
        assert_eq!(
            signature("foo", &[param("bool"), param("bool")]),
            "foo(bool,bool)"
        );
        assert_eq!(
            signature("foo", &[param("bool"), params(["bool[]"]), param("bool")]),
            "foo(bool,(bool[]),bool)"
        );
    }
}