oasis_contract_sdk/
testing.rs

1//! Utilities for testing smart contracts.
2use std::{
3    collections::BTreeMap,
4    sync::{Arc, Mutex},
5};
6
7use rand_core::{RngCore as _, SeedableRng as _};
8use rand_xorshift::XorShiftRng;
9
10use oasis_contract_sdk_crypto as crypto;
11use oasis_runtime_sdk::crypto::signature;
12
13use crate::{
14    context::Context,
15    env::{Crypto, CryptoError, Env},
16    event::Event,
17    storage::{ConfidentialStore, PublicStore, Store},
18    types::{
19        address::Address,
20        env::{QueryRequest, QueryResponse},
21        event::Event as RawEvent,
22        message::Message,
23        token, CallFormat, ExecutionContext, InstanceId,
24    },
25};
26
27/// Mock store.
28#[derive(Clone, Default)]
29pub struct MockStore {
30    inner: BTreeMap<Vec<u8>, Vec<u8>>,
31}
32
33impl MockStore {
34    /// Create a new empty mock store.
35    pub fn new() -> Self {
36        Self {
37            inner: BTreeMap::new(),
38        }
39    }
40}
41
42impl Store for MockStore {
43    fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
44        self.inner.get(key).cloned()
45    }
46
47    fn insert(&mut self, key: &[u8], value: &[u8]) {
48        self.inner.insert(key.to_owned(), value.to_owned());
49    }
50
51    fn remove(&mut self, key: &[u8]) {
52        self.inner.remove(key);
53    }
54}
55
56impl PublicStore for MockStore {}
57impl ConfidentialStore for MockStore {}
58
59/// Mock environment.
60#[derive(Clone)]
61pub struct MockEnv {
62    rng: Arc<Mutex<XorShiftRng>>,
63}
64
65impl MockEnv {
66    /// Create a new mock environment.
67    pub fn new() -> Self {
68        Default::default()
69    }
70}
71
72impl Default for MockEnv {
73    fn default() -> Self {
74        Self {
75            rng: Arc::new(Mutex::new(XorShiftRng::seed_from_u64(0))),
76        }
77    }
78}
79
80impl Env for MockEnv {
81    fn query<Q: Into<QueryRequest>>(&self, query: Q) -> QueryResponse {
82        match query.into() {
83            QueryRequest::BlockInfo => QueryResponse::BlockInfo {
84                round: 42,
85                epoch: 2,
86                timestamp: 100_000,
87            },
88            _ => unimplemented!(),
89        }
90    }
91
92    fn address_for_instance(&self, instance_id: InstanceId) -> Address {
93        let b = [
94            "test_12345678".as_bytes(),
95            &instance_id.as_u64().to_be_bytes(),
96        ]
97        .concat();
98        Address::from_bytes(&b).unwrap()
99    }
100
101    #[cfg(feature = "debug-utils")]
102    fn debug_print(&self, msg: &str) {
103        eprintln!("{msg}");
104    }
105}
106
107impl Crypto for MockEnv {
108    fn ecdsa_recover(&self, input: &[u8]) -> [u8; 65] {
109        crypto::ecdsa::recover(input).unwrap()
110    }
111
112    fn signature_verify_ed25519(&self, key: &[u8], message: &[u8], signature: &[u8]) -> bool {
113        let key = if let Ok(key) = signature::ed25519::PublicKey::from_bytes(key) {
114            key
115        } else {
116            return false;
117        };
118        let sig: signature::Signature = signature.to_vec().into();
119        key.verify_raw(message, &sig).is_ok()
120    }
121
122    fn signature_verify_secp256k1(&self, key: &[u8], message: &[u8], signature: &[u8]) -> bool {
123        let key = if let Ok(key) = signature::secp256k1::PublicKey::from_bytes(key) {
124            key
125        } else {
126            return false;
127        };
128        let sig: signature::Signature = signature.to_vec().into();
129        key.verify_raw(message, &sig).is_ok()
130    }
131
132    fn signature_verify_sr25519(
133        &self,
134        key: &[u8],
135        context: &[u8],
136        message: &[u8],
137        signature: &[u8],
138    ) -> bool {
139        let key = if let Ok(key) = signature::sr25519::PublicKey::from_bytes(key) {
140            key
141        } else {
142            return false;
143        };
144        let sig: signature::Signature = signature.to_vec().into();
145        key.verify(context, message, &sig).is_ok()
146    }
147
148    fn x25519_derive_symmetric(&self, public_key: &[u8], private_key: &[u8]) -> [u8; 32] {
149        crypto::x25519::derive_symmetric(public_key, private_key).unwrap()
150    }
151
152    fn deoxysii_seal(
153        &self,
154        key: &[u8],
155        nonce: &[u8],
156        message: &[u8],
157        additional_data: &[u8],
158    ) -> Result<Vec<u8>, CryptoError> {
159        Ok(crypto::deoxysii::seal(key, nonce, message, additional_data).unwrap())
160    }
161
162    fn deoxysii_open(
163        &self,
164        key: &[u8],
165        nonce: &[u8],
166        message: &[u8],
167        additional_data: &[u8],
168    ) -> Result<Vec<u8>, CryptoError> {
169        crypto::deoxysii::open(key, nonce, message, additional_data).map_err(|e| match e {
170            crypto::deoxysii::Error::DecryptionFailed => CryptoError::DecryptionFailed,
171            _ => panic!("unexpected crypto error"),
172        })
173    }
174
175    fn random_bytes(&self, _pers: &[u8], dst: &mut [u8]) -> usize {
176        self.rng.lock().unwrap().fill_bytes(dst);
177        dst.len()
178    }
179}
180
181/// A mock contract context suitable for testing.
182pub struct MockContext {
183    /// Execution context.
184    pub ec: ExecutionContext,
185
186    /// Public store.
187    pub public_store: MockStore,
188    /// "Confidential" store.
189    pub confidential_store: MockStore,
190    /// Environment.
191    pub env: MockEnv,
192
193    /// Emitted messages.
194    pub messages: Vec<Message>,
195    /// Emitted events.
196    pub events: Vec<RawEvent>,
197}
198
199impl From<ExecutionContext> for MockContext {
200    fn from(ec: ExecutionContext) -> Self {
201        Self {
202            ec,
203            public_store: MockStore::new(),
204            confidential_store: MockStore::new(),
205            env: MockEnv::new(),
206            messages: Vec::new(),
207            events: Vec::new(),
208        }
209    }
210}
211
212impl Context for MockContext {
213    type PublicStore = MockStore;
214    type ConfidentialStore = MockStore;
215    type Env = MockEnv;
216
217    fn instance_id(&self) -> InstanceId {
218        self.ec.instance_id
219    }
220
221    fn instance_address(&self) -> &Address {
222        &self.ec.instance_address
223    }
224
225    fn caller_address(&self) -> &Address {
226        &self.ec.caller_address
227    }
228
229    fn deposited_tokens(&self) -> &[token::BaseUnits] {
230        &self.ec.deposited_tokens
231    }
232
233    fn is_read_only(&self) -> bool {
234        self.ec.read_only
235    }
236
237    fn call_format(&self) -> CallFormat {
238        self.ec.call_format
239    }
240
241    fn emit_message(&mut self, msg: Message) {
242        self.messages.push(msg);
243    }
244
245    fn emit_event<E: Event>(&mut self, event: E) {
246        self.events.push(event.into_raw());
247    }
248
249    fn public_store(&mut self) -> &mut Self::PublicStore {
250        &mut self.public_store
251    }
252
253    fn confidential_store(&mut self) -> &mut Self::ConfidentialStore {
254        &mut self.confidential_store
255    }
256
257    fn env(&self) -> &Self::Env {
258        &self.env
259    }
260}
261
262/// A macro that creates Oasis ABI entry points.
263#[macro_export]
264#[doc(hidden)]
265macro_rules! __create_contract {
266    ($name:ty) => {};
267}