cosmwasm_vm/
static_analysis.rs1use std::collections::HashSet;
2
3use strum::{AsRefStr, Display, EnumString};
4use wasmer::wasmparser::ExternalKind;
5
6use crate::parsed_wasm::ParsedWasm;
7
8#[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
50impl 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
71pub trait ExportInfo {
74 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}