cosmwasm_vm/
static_analysis.rs

1use std::collections::HashSet;
2
3use strum::{AsRefStr, Display, EnumString};
4use wasmer::wasmparser::ExternalKind;
5
6use crate::parsed_wasm::ParsedWasm;
7
8/// An enum containing all available contract entrypoints.
9/// This also provides conversions to and from strings.
10#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash, EnumString, Display, AsRefStr)]
11pub enum Entrypoint {
12    #[strum(serialize = "instantiate")]
13    Instantiate,
14    #[strum(serialize = "execute")]
15    Execute,
16    #[strum(serialize = "migrate")]
17    Migrate,
18    #[strum(serialize = "sudo")]
19    Sudo,
20    #[strum(serialize = "reply")]
21    Reply,
22    #[strum(serialize = "query")]
23    Query,
24    #[strum(serialize = "ibc_channel_open")]
25    IbcChannelOpen,
26    #[strum(serialize = "ibc_channel_connect")]
27    IbcChannelConnect,
28    #[strum(serialize = "ibc_channel_close")]
29    IbcChannelClose,
30    #[strum(serialize = "ibc_packet_receive")]
31    IbcPacketReceive,
32    #[strum(serialize = "ibc_packet_ack")]
33    IbcPacketAck,
34    #[strum(serialize = "ibc_packet_timeout")]
35    IbcPacketTimeout,
36    #[strum(serialize = "ibc_source_callback")]
37    IbcSourceCallback,
38    #[strum(serialize = "ibc_destination_callback")]
39    IbcDestinationCallback,
40    #[strum(serialize = "ibc2_packet_receive")]
41    Ibc2PacketReceive,
42    #[strum(serialize = "ibc2_packet_timeout")]
43    Ibc2PacketTimeout,
44    #[strum(serialize = "ibc2_packet_ack")]
45    Ibc2PacketAck,
46    #[strum(serialize = "ibc2_packet_send")]
47    Ibc2PacketSend,
48}
49
50// sort entrypoints by their &str representation
51impl PartialOrd for Entrypoint {
52    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
53        Some(self.cmp(other))
54    }
55}
56impl Ord for Entrypoint {
57    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
58        self.as_ref().cmp(other.as_ref())
59    }
60}
61
62pub const REQUIRED_IBC_EXPORTS: &[Entrypoint] = &[
63    Entrypoint::IbcChannelOpen,
64    Entrypoint::IbcChannelConnect,
65    Entrypoint::IbcChannelClose,
66    Entrypoint::IbcPacketReceive,
67    Entrypoint::IbcPacketAck,
68    Entrypoint::IbcPacketTimeout,
69];
70
71/// A trait that allows accessing shared functionality of `parity_wasm::elements::Module`
72/// and `wasmer::Module` in a shared fashion.
73pub trait ExportInfo {
74    /// Returns all exported function names with the given prefix
75    fn exported_function_names(self, prefix: Option<&str>) -> HashSet<String>;
76}
77
78impl ExportInfo for &ParsedWasm<'_> {
79    fn exported_function_names(self, prefix: Option<&str>) -> HashSet<String> {
80        self.exports
81            .iter()
82            .filter_map(|export| match export.kind {
83                ExternalKind::Func => Some(export.name),
84                _ => None,
85            })
86            .filter(|name| {
87                if let Some(required_prefix) = prefix {
88                    name.starts_with(required_prefix)
89                } else {
90                    true
91                }
92            })
93            .map(|name| name.to_string())
94            .collect()
95    }
96}
97
98impl ExportInfo for &wasmer::Module {
99    fn exported_function_names(self, prefix: Option<&str>) -> HashSet<String> {
100        self.exports()
101            .functions()
102            .filter_map(|function_export| {
103                let name = function_export.name();
104                if let Some(required_prefix) = prefix {
105                    if name.starts_with(required_prefix) {
106                        Some(name.to_string())
107                    } else {
108                        None
109                    }
110                } else {
111                    Some(name.to_string())
112                }
113            })
114            .collect()
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use std::str::FromStr;
121
122    use crate::wasm_backend::make_compiler_config;
123    use crate::VmError;
124
125    use super::*;
126    use wasmer::Store;
127
128    static HACKATOM: &[u8] = include_bytes!("../testdata/hackatom.wasm");
129    static CORRUPTED: &[u8] = include_bytes!("../testdata/corrupted.wasm");
130
131    #[test]
132    fn deserialize_exports_works() {
133        let module = ParsedWasm::parse(HACKATOM).unwrap();
134        assert_eq!(module.version, 1);
135
136        let exported_functions = module
137            .exports
138            .iter()
139            .filter(|entry| matches!(entry.kind, ExternalKind::Func));
140        assert_eq!(exported_functions.count(), 15);
141
142        let exported_memories = module
143            .exports
144            .iter()
145            .filter(|entry| matches!(entry.kind, ExternalKind::Memory));
146        assert_eq!(exported_memories.count(), 1);
147    }
148
149    #[test]
150    fn deserialize_wasm_corrupted_data() {
151        match ParsedWasm::parse(CORRUPTED)
152            .and_then(|mut parsed| parsed.validate_funcs())
153            .unwrap_err()
154        {
155            VmError::StaticValidationErr { msg, .. } => {
156                assert!(msg.starts_with("Wasm bytecode could not be deserialized."))
157            }
158            err => panic!("Unexpected error: {err:?}"),
159        }
160    }
161
162    #[test]
163    fn exported_function_names_works_for_parity_with_no_prefix() {
164        let wasm = wat::parse_str(r#"(module)"#).unwrap();
165        let module = ParsedWasm::parse(&wasm).unwrap();
166        let exports = module.exported_function_names(None);
167        assert_eq!(exports, HashSet::new());
168
169        let wasm = wat::parse_str(
170            r#"(module
171                (memory 3)
172                (export "memory" (memory 0))
173
174                (type (func))
175                (func (type 0) nop)
176                (export "foo" (func 0))
177                (export "bar" (func 0))
178            )"#,
179        )
180        .unwrap();
181        let module = ParsedWasm::parse(&wasm).unwrap();
182        let exports = module.exported_function_names(None);
183        assert_eq!(
184            exports,
185            HashSet::from_iter(vec!["foo".to_string(), "bar".to_string()])
186        );
187    }
188
189    #[test]
190    fn exported_function_names_works_for_parity_with_prefix() {
191        let wasm = wat::parse_str(r#"(module)"#).unwrap();
192        let module = ParsedWasm::parse(&wasm).unwrap();
193        let exports = module.exported_function_names(Some("b"));
194        assert_eq!(exports, HashSet::new());
195
196        let wasm = wat::parse_str(
197            r#"(module
198                (memory 3)
199                (export "memory" (memory 0))
200
201                (type (func))
202                (func (type 0) nop)
203                (export "foo" (func 0))
204                (export "bar" (func 0))
205                (export "baz" (func 0))
206            )"#,
207        )
208        .unwrap();
209        let module = ParsedWasm::parse(&wasm).unwrap();
210        let exports = module.exported_function_names(Some("b"));
211        assert_eq!(
212            exports,
213            HashSet::from_iter(vec!["bar".to_string(), "baz".to_string()])
214        );
215    }
216
217    #[test]
218    fn exported_function_names_works_for_wasmer_with_no_prefix() {
219        let wasm = wat::parse_str(r#"(module)"#).unwrap();
220        let compiler = make_compiler_config();
221        let store = Store::new(compiler);
222        let module = wasmer::Module::new(&store, wasm).unwrap();
223        let exports = module.exported_function_names(None);
224        assert_eq!(exports, HashSet::new());
225
226        let wasm = wat::parse_str(
227            r#"(module
228                (memory 3)
229                (export "memory" (memory 0))
230
231                (type (func))
232                (func (type 0) nop)
233                (export "foo" (func 0))
234                (export "bar" (func 0))
235            )"#,
236        )
237        .unwrap();
238        let compiler = make_compiler_config();
239        let store = Store::new(compiler);
240        let module = wasmer::Module::new(&store, wasm).unwrap();
241        let exports = module.exported_function_names(None);
242        assert_eq!(
243            exports,
244            HashSet::from_iter(vec!["foo".to_string(), "bar".to_string()])
245        );
246    }
247
248    #[test]
249    fn exported_function_names_works_for_wasmer_with_prefix() {
250        let wasm = wat::parse_str(r#"(module)"#).unwrap();
251        let compiler = make_compiler_config();
252        let store = Store::new(compiler);
253        let module = wasmer::Module::new(&store, wasm).unwrap();
254        let exports = module.exported_function_names(Some("b"));
255        assert_eq!(exports, HashSet::new());
256
257        let wasm = wat::parse_str(
258            r#"(module
259                (memory 3)
260                (export "memory" (memory 0))
261
262                (type (func))
263                (func (type 0) nop)
264                (export "foo" (func 0))
265                (export "bar" (func 0))
266                (export "baz" (func 0))
267            )"#,
268        )
269        .unwrap();
270        let compiler = make_compiler_config();
271        let store = Store::new(compiler);
272        let module = wasmer::Module::new(&store, wasm).unwrap();
273        let exports = module.exported_function_names(Some("b"));
274        assert_eq!(
275            exports,
276            HashSet::from_iter(vec!["bar".to_string(), "baz".to_string()])
277        );
278    }
279
280    #[test]
281    fn entrypoint_from_string_works() {
282        assert_eq!(
283            Entrypoint::from_str("ibc_channel_open").unwrap(),
284            Entrypoint::IbcChannelOpen
285        );
286
287        assert!(Entrypoint::from_str("IbcChannelConnect").is_err());
288    }
289
290    #[test]
291    fn entrypoint_to_string_works() {
292        assert_eq!(
293            Entrypoint::IbcPacketTimeout.to_string(),
294            "ibc_packet_timeout"
295        );
296
297        let static_str: &'static str = Entrypoint::IbcPacketReceive.as_ref();
298        assert_eq!(static_str, "ibc_packet_receive");
299    }
300}