wasm_contract/
contract.rs

1//! The definition of a WASM contract
2
3use std::collections::HashMap;
4
5#[derive(Debug, Clone, PartialEq, Eq, Default)]
6pub struct Contract {
7    /// Things that the module can import
8    pub imports: HashMap<(String, String), Import>,
9    /// Things that the module must export
10    pub exports: HashMap<String, Export>,
11}
12
13impl Contract {
14    pub fn merge(&self, other: Contract) -> Result<Contract, String> {
15        let mut base = self.clone();
16
17        for (key, val) in other.imports.into_iter() {
18            if base.imports.contains_key(&key) {
19                if val != base.imports[&key] {
20                    return Err(format!("Conflict detected: the import \"{}\" \"{}\" was found but the definitions were different: {:?} {:?}", &key.0, &key.1, base.imports[&key], val));
21                }
22            } else {
23                let res = base.imports.insert(key, val);
24                debug_assert!(res.is_none());
25            }
26        }
27
28        for (key, val) in other.exports.into_iter() {
29            if base.exports.contains_key(&key) {
30                if val != base.exports[&key] {
31                    return Err(format!("Conflict detected: the key {} was found in exports but the definitions were different: {:?} {:?}", key, base.exports[&key], val));
32                }
33            } else {
34                let res = base.exports.insert(key, val);
35                debug_assert!(res.is_none());
36            }
37        }
38        Ok(base)
39    }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum Import {
44    Func {
45        namespace: String,
46        name: String,
47        params: Vec<WasmType>,
48        result: Vec<WasmType>,
49    },
50    Global {
51        namespace: String,
52        name: String,
53        var_type: WasmType,
54    },
55}
56
57impl Import {
58    pub fn format_key(ns: &str, name: &str) -> (String, String) {
59        (ns.to_string(), name.to_string())
60    }
61
62    /// Get the key used to look this import up in the Contract's import hashmap
63    pub fn get_key(&self) -> (String, String) {
64        match self {
65            Import::Func {
66                namespace, name, ..
67            } => Self::format_key(&namespace, &name),
68            Import::Global {
69                namespace, name, ..
70            } => Self::format_key(&namespace, &name),
71        }
72    }
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub enum Export {
77    Func {
78        name: String,
79        params: Vec<WasmType>,
80        result: Vec<WasmType>,
81    },
82    Global {
83        name: String,
84        var_type: WasmType,
85    },
86}
87
88impl Export {
89    pub fn format_key(name: &str) -> String {
90        name.to_string()
91    }
92
93    /// Get the key used to look this export up in the Contract's export hashmap
94    pub fn get_key(&self) -> String {
95        match self {
96            Export::Func { name, .. } => Self::format_key(&name),
97            Export::Global { name, .. } => Self::format_key(&name),
98        }
99    }
100}
101
102/// Primitive wasm type
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub enum WasmType {
105    I32,
106    I64,
107    F32,
108    F64,
109}
110
111impl std::fmt::Display for WasmType {
112    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
113        write!(
114            f,
115            "{}",
116            match self {
117                WasmType::I32 => "i32",
118                WasmType::I64 => "i64",
119                WasmType::F32 => "f32",
120                WasmType::F64 => "f64",
121            }
122        )
123    }
124}
125
126#[cfg(test)]
127mod test {
128    use crate::parser;
129
130    #[test]
131    fn merging_works() {
132        let contract1_src = r#"(assert_import (func "env" "plus_one" (param i32) (result i32)))"#;
133        let contract2_src = r#"(assert_import (func "env" "plus_one" (param i64) (result i64)))"#;
134        let contract3_src = r#"(assert_import (func "env" "times_two" (param i64) (result i64)))"#;
135        let contract4_src =
136            r#"(assert_import (func "env" "times_two" (param i64 i64) (result i64)))"#;
137        let contract5_src = r#"(assert_export (func "empty_bank_account" (param) (result)))"#;
138        let contract6_src = r#"(assert_export (func "empty_bank_account" (param) (result i64)))"#;
139
140        let contract1 = parser::parse_contract(contract1_src).unwrap();
141        let contract2 = parser::parse_contract(contract2_src).unwrap();
142        let contract3 = parser::parse_contract(contract3_src).unwrap();
143        let contract4 = parser::parse_contract(contract4_src).unwrap();
144        let contract5 = parser::parse_contract(contract5_src).unwrap();
145        let contract6 = parser::parse_contract(contract6_src).unwrap();
146
147        assert!(contract1.merge(contract2.clone()).is_err());
148        assert!(contract2.merge(contract1.clone()).is_err());
149        assert!(contract1.merge(contract3.clone()).is_ok());
150        assert!(contract2.merge(contract3.clone()).is_ok());
151        assert!(contract3.merge(contract2.clone()).is_ok());
152        assert!(
153            contract1.merge(contract1.clone()).is_ok(),
154            "exact matches are accepted"
155        );
156        assert!(contract3.merge(contract4.clone()).is_err());
157        assert!(contract5.merge(contract5.clone()).is_ok());
158        assert!(contract5.merge(contract6.clone()).is_err());
159    }
160}