clone_cw_multi_test/wasm_emulation/
contract.rs

1use crate::wasm_emulation::api::RealApi;
2use crate::wasm_emulation::input::ReplyArgs;
3use crate::wasm_emulation::instance::instance_from_reused_module;
4use crate::wasm_emulation::output::StorageChanges;
5use crate::wasm_emulation::query::MockQuerier;
6use crate::wasm_emulation::storage::DualStorage;
7use cosmwasm_std::Checksum;
8use cosmwasm_std::CustomMsg;
9use cosmwasm_std::StdError;
10use cosmwasm_vm::WasmLimits;
11use cosmwasm_vm::{
12    call_execute, call_instantiate, call_migrate, call_query, call_reply, call_sudo, Backend,
13    BackendApi, Instance, InstanceOptions, Querier,
14};
15use cw_orch::daemon::queriers::CosmWasm;
16
17use cosmwasm_std::Order;
18use cosmwasm_std::Storage;
19
20use serde::de::DeserializeOwned;
21use wasmer::Engine;
22use wasmer::Module;
23
24use crate::wasm_emulation::input::InstanceArguments;
25use crate::wasm_emulation::output::WasmRunnerOutput;
26
27use cosmwasm_vm::internals::check_wasm;
28use std::collections::HashSet;
29
30use crate::Contract;
31
32use cosmwasm_std::{Binary, CustomQuery, Deps, DepsMut, Env, MessageInfo, Reply, Response};
33
34use anyhow::Result as AnyResult;
35
36use super::channel::RemoteChannel;
37use super::input::ExecuteArgs;
38use super::input::InstantiateArgs;
39use super::input::MigrateArgs;
40use super::input::QueryArgs;
41use super::input::SudoArgs;
42use super::input::WasmFunction;
43use super::instance::create_module;
44use super::output::WasmOutput;
45use super::query::mock_querier::ForkState;
46
47fn apply_storage_changes<ExecC>(storage: &mut dyn Storage, output: &WasmRunnerOutput<ExecC>) {
48    // We change all the values with the output
49    for (key, value) in &output.storage.current_keys {
50        storage.set(key, value);
51    }
52
53    // We remove all values that need to be removed from it
54    for key in &output.storage.removed_keys {
55        storage.remove(key);
56    }
57}
58
59/// Taken from cosmwasm_vm::testing
60/// This gas limit is used in integration tests and should be high enough to allow a reasonable
61/// number of contract executions and queries on one instance. For this reason it is significatly
62/// higher than the limit for a single execution that we have in the production setup.
63const DEFAULT_GAS_LIMIT: u64 = 500_000_000_000_000; // ~0.5s
64
65#[derive(Debug, Clone)]
66pub struct DistantCodeId {
67    pub code_id: u64,
68    pub module: (Engine, Module),
69}
70
71#[derive(Clone)]
72pub struct LocalWasmContract {
73    pub code: Vec<u8>,
74    pub module: (Engine, Module),
75}
76
77#[derive(Debug, Clone)]
78pub enum WasmContract {
79    Local(LocalWasmContract),
80    DistantCodeId(DistantCodeId),
81}
82
83impl std::fmt::Debug for LocalWasmContract {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
85        write!(
86            f,
87            "LocalContract {{ checksum: {} }}",
88            Checksum::generate(&self.code),
89        )
90    }
91}
92
93impl WasmContract {
94    pub fn new_local(code: Vec<u8>) -> Self {
95        check_wasm(
96            &code,
97            &HashSet::from([
98                "iterator".to_string(),
99                "staking".to_string(),
100                "stargate".to_string(),
101            ]),
102            &WasmLimits::default(),
103            cosmwasm_vm::internals::Logger::Off,
104        )
105        .unwrap();
106        Self::Local(LocalWasmContract {
107            code: code.clone(),
108            module: create_module(&code).unwrap(),
109        })
110    }
111
112    pub fn new_distant_code_id(code_id: u64, remote: RemoteChannel) -> Self {
113        let code = {
114            let wasm_querier = CosmWasm::new_sync(remote.channel.clone(), &remote.rt);
115
116            let cache_key = format!("{}:{}", remote.chain_id, &code_id);
117
118            let code = wasm_caching::maybe_cached_wasm(cache_key, || {
119                remote
120                    .rt
121                    .block_on(wasm_querier._code_data(code_id))
122                    .map_err(|e| e.into())
123            })
124            .unwrap();
125
126            code
127        };
128        Self::DistantCodeId(DistantCodeId {
129            code_id,
130            module: create_module(&code).unwrap(),
131        })
132    }
133
134    pub fn get_module(&self) -> (Engine, Module) {
135        match self {
136            WasmContract::Local(LocalWasmContract { module, .. }) => module.clone(),
137            WasmContract::DistantCodeId(DistantCodeId { module, .. }) => module.clone(),
138        }
139    }
140
141    pub fn run_contract<
142        QueryC: CustomQuery + DeserializeOwned + 'static,
143        ExecC: CustomMsg + DeserializeOwned,
144    >(
145        &self,
146        args: InstanceArguments,
147        fork_state: ForkState<ExecC, QueryC>,
148    ) -> AnyResult<WasmRunnerOutput<ExecC>> {
149        let InstanceArguments {
150            function,
151            init_storage,
152        } = args;
153        let address = function.get_address();
154        let module = self.get_module();
155
156        let api = RealApi::new(&fork_state.remote.pub_address_prefix);
157
158        // We create the backend here from outside information;
159        let backend = Backend {
160            api,
161            storage: DualStorage::new(
162                fork_state.remote.clone(),
163                address.to_string(),
164                Some(init_storage),
165            )?,
166            querier: MockQuerier::<ExecC, QueryC>::new(fork_state),
167        };
168        let options = InstanceOptions {
169            gas_limit: DEFAULT_GAS_LIMIT,
170        };
171
172        // Then we create the instance
173
174        let mut instance = instance_from_reused_module(module, backend, options)?;
175
176        let gas_before = instance.get_gas_left();
177
178        // Then we call the function that we wanted to call
179        let result = execute_function(&mut instance, function)?;
180
181        let gas_after = instance.get_gas_left();
182
183        // We return the code response + any storage change (or the whole local storage object), with serializing
184        let mut recycled_instance = instance.recycle().unwrap();
185
186        let wasm_result = WasmRunnerOutput {
187            storage: StorageChanges {
188                current_keys: recycled_instance.storage.get_all_storage()?,
189                removed_keys: recycled_instance.storage.removed_keys.into_iter().collect(),
190            },
191            gas_used: gas_before - gas_after,
192            wasm: result,
193        };
194
195        Ok(wasm_result)
196    }
197
198    pub fn after_execution_callback<ExecC>(&self, output: &WasmRunnerOutput<ExecC>) {
199        // We log the gas used
200        let operation = match output.wasm {
201            WasmOutput::Execute(_) => "execution",
202            WasmOutput::Query(_) => "query",
203            WasmOutput::Instantiate(_) => "instantiation",
204            WasmOutput::Migrate(_) => "migration",
205            WasmOutput::Sudo(_) => "sudo",
206            WasmOutput::Reply(_) => "reply",
207        };
208        log::debug!(
209            "Gas used {:?} for {:} on contract {:?}",
210            output.gas_used,
211            operation,
212            self
213        );
214    }
215
216    pub fn code_id(&self) -> u64 {
217        match self {
218            WasmContract::Local(_) => unimplemented!(),
219            WasmContract::DistantCodeId(d) => d.code_id,
220        }
221    }
222}
223
224impl<ExecC, QueryC> Contract<ExecC, QueryC> for WasmContract
225where
226    ExecC: CustomMsg + DeserializeOwned,
227    QueryC: CustomQuery + DeserializeOwned,
228{
229    fn execute(
230        &self,
231        deps: DepsMut<QueryC>,
232        env: Env,
233        info: MessageInfo,
234        msg: Vec<u8>,
235        fork_state: ForkState<ExecC, QueryC>,
236    ) -> AnyResult<Response<ExecC>> {
237        // We start by building the dependencies we will pass through the wasm executer
238        let execute_args = InstanceArguments {
239            function: WasmFunction::Execute(ExecuteArgs { env, info, msg }),
240            init_storage: deps.storage.range(None, None, Order::Ascending).collect(),
241        };
242
243        let decoded_result = self.run_contract(execute_args, fork_state)?;
244
245        apply_storage_changes(deps.storage, &decoded_result);
246        self.after_execution_callback(&decoded_result);
247
248        match decoded_result.wasm {
249            WasmOutput::Execute(x) => Ok(x),
250            _ => panic!("Wrong kind of answer from wasm container"),
251        }
252    }
253
254    fn instantiate(
255        &self,
256        deps: DepsMut<QueryC>,
257        env: Env,
258        info: MessageInfo,
259        msg: Vec<u8>,
260        fork_state: ForkState<ExecC, QueryC>,
261    ) -> AnyResult<Response<ExecC>> {
262        // We start by building the dependencies we will pass through the wasm executer
263        let instantiate_arguments = InstanceArguments {
264            function: WasmFunction::Instantiate(InstantiateArgs { env, info, msg }),
265            init_storage: deps.storage.range(None, None, Order::Ascending).collect(),
266        };
267
268        let decoded_result = self.run_contract(instantiate_arguments, fork_state)?;
269
270        apply_storage_changes(deps.storage, &decoded_result);
271        self.after_execution_callback(&decoded_result);
272
273        match decoded_result.wasm {
274            WasmOutput::Instantiate(x) => Ok(x),
275            _ => panic!("Wrong kind of answer from wasm container"),
276        }
277    }
278
279    fn query(
280        &self,
281        deps: Deps<QueryC>,
282        env: Env,
283        msg: Vec<u8>,
284        fork_state: ForkState<ExecC, QueryC>,
285    ) -> AnyResult<Binary> {
286        // We start by building the dependencies we will pass through the wasm executer
287        let query_arguments = InstanceArguments {
288            function: WasmFunction::Query(QueryArgs { env, msg }),
289            init_storage: deps.storage.range(None, None, Order::Ascending).collect(),
290        };
291
292        let decoded_result: WasmRunnerOutput<ExecC> =
293            self.run_contract(query_arguments, fork_state)?;
294
295        self.after_execution_callback(&decoded_result);
296
297        match decoded_result.wasm {
298            WasmOutput::Query(x) => Ok(x),
299            _ => panic!("Wrong kind of answer from wasm container"),
300        }
301    }
302
303    // this returns an error if the contract doesn't implement sudo
304    fn sudo(
305        &self,
306        deps: DepsMut<QueryC>,
307        env: Env,
308        msg: Vec<u8>,
309        fork_state: ForkState<ExecC, QueryC>,
310    ) -> AnyResult<Response<ExecC>> {
311        let sudo_args = InstanceArguments {
312            function: WasmFunction::Sudo(SudoArgs { env, msg }),
313            init_storage: deps.storage.range(None, None, Order::Ascending).collect(),
314        };
315
316        let decoded_result = self.run_contract(sudo_args, fork_state)?;
317
318        apply_storage_changes(deps.storage, &decoded_result);
319        self.after_execution_callback(&decoded_result);
320
321        match decoded_result.wasm {
322            WasmOutput::Sudo(x) => Ok(x),
323            _ => panic!("Wrong kind of answer from wasm container"),
324        }
325    }
326
327    // this returns an error if the contract doesn't implement reply
328    fn reply(
329        &self,
330        deps: DepsMut<QueryC>,
331        env: Env,
332        reply: Reply,
333        fork_state: ForkState<ExecC, QueryC>,
334    ) -> AnyResult<Response<ExecC>> {
335        let reply_args = InstanceArguments {
336            function: WasmFunction::Reply(ReplyArgs { env, reply }),
337            init_storage: deps.storage.range(None, None, Order::Ascending).collect(),
338        };
339
340        let decoded_result = self.run_contract(reply_args, fork_state)?;
341
342        apply_storage_changes(deps.storage, &decoded_result);
343        self.after_execution_callback(&decoded_result);
344
345        match decoded_result.wasm {
346            WasmOutput::Reply(x) => Ok(x),
347            _ => panic!("Wrong kind of answer from wasm container"),
348        }
349    }
350
351    // this returns an error if the contract doesn't implement migrate
352    fn migrate(
353        &self,
354        deps: DepsMut<QueryC>,
355        env: Env,
356        msg: Vec<u8>,
357        fork_state: ForkState<ExecC, QueryC>,
358    ) -> AnyResult<Response<ExecC>> {
359        let migrate_args = InstanceArguments {
360            function: WasmFunction::Migrate(MigrateArgs { env, msg }),
361            init_storage: deps.storage.range(None, None, Order::Ascending).collect(),
362        };
363
364        let decoded_result = self.run_contract(migrate_args, fork_state)?;
365
366        apply_storage_changes(deps.storage, &decoded_result);
367        self.after_execution_callback(&decoded_result);
368
369        match decoded_result.wasm {
370            WasmOutput::Migrate(x) => Ok(x),
371            _ => panic!("Wrong kind of answer from wasm container"),
372        }
373    }
374}
375
376pub fn execute_function<
377    A: BackendApi + 'static,
378    B: cosmwasm_vm::Storage + 'static,
379    C: Querier + 'static,
380    ExecC: CustomMsg + DeserializeOwned,
381>(
382    instance: &mut Instance<A, B, C>,
383    function: WasmFunction,
384) -> AnyResult<WasmOutput<ExecC>> {
385    match function {
386        WasmFunction::Execute(args) => {
387            let result = call_execute(instance, &args.env, &args.info, &args.msg)?
388                .into_result()
389                .map_err(StdError::generic_err)?;
390            Ok(WasmOutput::Execute(result))
391        }
392        WasmFunction::Query(args) => {
393            let result = call_query(instance, &args.env, &args.msg)?
394                .into_result()
395                .map_err(StdError::generic_err)?;
396            Ok(WasmOutput::Query(result))
397        }
398        WasmFunction::Instantiate(args) => {
399            let result = call_instantiate(instance, &args.env, &args.info, &args.msg)?
400                .into_result()
401                .map_err(StdError::generic_err)?;
402            Ok(WasmOutput::Instantiate(result))
403        }
404        WasmFunction::Reply(args) => {
405            let result = call_reply(instance, &args.env, &args.reply)?
406                .into_result()
407                .map_err(StdError::generic_err)?;
408            Ok(WasmOutput::Reply(result))
409        }
410        WasmFunction::Migrate(args) => {
411            let result = call_migrate(instance, &args.env, &args.msg)?
412                .into_result()
413                .map_err(StdError::generic_err)?;
414            Ok(WasmOutput::Migrate(result))
415        }
416        WasmFunction::Sudo(args) => {
417            let result = call_sudo(instance, &args.env, &args.msg)?
418                .into_result()
419                .map_err(StdError::generic_err)?;
420            Ok(WasmOutput::Sudo(result))
421        }
422    }
423}
424
425mod wasm_caching {
426    use super::*;
427
428    use std::{
429        env, fs,
430        io::{Read, Seek},
431        os::unix::fs::FileExt,
432        path::PathBuf,
433    };
434
435    use anyhow::{bail, Context};
436
437    const WASM_CACHE_DIR: &str = "wasm_cache";
438    const WASM_CACHE_ENV: &str = "WASM_CACHE";
439
440    static CARGO_TARGET_DIR: std::sync::OnceLock<PathBuf> = std::sync::OnceLock::new();
441
442    pub(crate) fn cargo_target_dir() -> &'static PathBuf {
443        CARGO_TARGET_DIR.get_or_init(|| {
444            cargo_metadata::MetadataCommand::new()
445                .no_deps()
446                .exec()
447                .unwrap()
448                .target_directory
449                .into()
450        })
451    }
452
453    #[repr(u8)]
454    enum WasmCachingStatus {
455        /// Currently writing
456        Writing,
457        /// This wasm ready for use
458        Ready,
459        /// Writing to it have failed, it's not usable until valid cache written to it
460        Corrupted,
461    }
462
463    impl From<u8> for WasmCachingStatus {
464        fn from(value: u8) -> Self {
465            match value {
466                0 => WasmCachingStatus::Writing,
467                1 => WasmCachingStatus::Ready,
468                2 => WasmCachingStatus::Corrupted,
469                _ => unimplemented!(),
470            }
471        }
472    }
473
474    impl WasmCachingStatus {
475        pub fn set_status(self, file: &fs::File) {
476            file.write_at(&[self as u8], 0)
477                .expect("Failed to update wasm caching status");
478        }
479
480        pub fn status(file: &fs::File) -> Self {
481            let buf = &mut [0];
482            match file.read_at(buf, 0) {
483                Ok(_) => buf[0].into(),
484                Err(_) => WasmCachingStatus::Corrupted,
485            }
486        }
487    }
488
489    /// Returns wasm bytes for the contract
490    ///
491    /// Will get cached wasm stored in `CARGO_TARGET_DIR` (./target/ from project root by default)
492    /// This feature can be disabled by setting wasm cache environment variable to `false`: `WASM_CACHE=false`
493    ///
494    /// # Arguments
495    ///
496    /// * `key` - A string key that represents unique id of the contract (usually chain-id:code_id)
497    /// * `wasm_code_bytes` - Function that returns non-cached version of the contract
498    ///
499    /// # Scenarios
500    ///
501    /// - Wasm caching disabled: return result of the `wasm_code_bytes` function
502    /// - Wasm file not found in cache location: return result of the `wasm_code_bytes` function, saving cache on on success
503    /// - Wasm stored in cache: return bytes
504    pub(crate) fn maybe_cached_wasm<F: Fn() -> AnyResult<Vec<u8>>>(
505        key: String,
506        wasm_code_bytes: F,
507    ) -> AnyResult<Vec<u8>> {
508        let wasm_cache_enabled = env::var(WASM_CACHE_ENV)
509            .ok()
510            .and_then(|wasm_cache| wasm_cache.parse().ok())
511            .unwrap_or(true);
512
513        // Wasm caching disabled, return function result
514        if !wasm_cache_enabled {
515            return wasm_code_bytes();
516        }
517
518        let wasm_cache_dir = cargo_target_dir().join(WASM_CACHE_DIR);
519        // Prepare cache directory in `./target/`
520        match fs::metadata(&wasm_cache_dir) {
521            // Verify it's dir
522            Ok(wasm_cache_metadata) => {
523                if !wasm_cache_metadata.is_dir() {
524                    bail!("{WASM_CACHE_DIR} supposed to be directory")
525                }
526            }
527            // Error on checking cache dir, try to create it
528            Err(_) => {
529                // We try to create the dir silently, it's ok if it fails, the error will pop-up later if there's an issue
530                let _ = fs::create_dir(&wasm_cache_dir);
531            }
532        }
533
534        let cached_wasm_file = wasm_cache_dir.join(key);
535        let wasm_bytes = match fs::metadata(&cached_wasm_file) {
536            // Cache file exists, try to read it
537            Ok(_) => {
538                let mut file =
539                    fs::File::open(&cached_wasm_file).context("unable to open wasm cache file")?;
540                // If someone is writing to it we need to wait, and then check again
541                // TODO: decide what is the best way to wait for it
542                let mut status = WasmCachingStatus::status(&file);
543                if let WasmCachingStatus::Writing = status {
544                    let options = file_lock::FileOptions::new().read(true);
545                    // Blocking lock until writer unlocks it
546                    let file_lock = file_lock::FileLock::lock(&cached_wasm_file, true, options)?;
547                    status = WasmCachingStatus::status(&file_lock.file);
548                    file_lock.unlock()?
549                }
550                match status {
551                    WasmCachingStatus::Ready => {
552                        let mut buf = vec![];
553                        file.seek(std::io::SeekFrom::Start(1))?;
554                        file.read_to_end(&mut buf)
555                            .context("unable to open wasm cache file")?;
556                        buf
557                    }
558                    // Ready for read
559                    // Corrupted, need to write new wasm
560                    WasmCachingStatus::Corrupted => {
561                        store_new_wasm(wasm_code_bytes, &cached_wasm_file)?
562                    }
563                    // Someone dropped file lock with caching status Writing it means it's corrupted
564                    WasmCachingStatus::Writing => {
565                        store_new_wasm(wasm_code_bytes, &cached_wasm_file)?
566                    }
567                }
568            }
569            // Error on checking cache dir, get wasm bytes and try to cache it
570            Err(_) => store_new_wasm(wasm_code_bytes, &cached_wasm_file)?,
571        };
572        Ok(wasm_bytes)
573    }
574
575    fn store_new_wasm<F: Fn() -> AnyResult<Vec<u8>>>(
576        wasm_code_bytes: F,
577        cached_wasm_file: &PathBuf,
578    ) -> Result<Vec<u8>, anyhow::Error> {
579        let options = file_lock::FileOptions::new().create(true).write(true);
580        let file_lock_status = file_lock::FileLock::lock(cached_wasm_file, false, options);
581        let wasm = wasm_code_bytes()?;
582        if let Err(cache_save_err) = file_lock_status.and_then(|file_lock| {
583            // Set writing status
584            WasmCachingStatus::Writing.set_status(&file_lock.file);
585
586            match file_lock.file.write_all_at(&wasm, 1) {
587                // Done writing, set ready status
588                Ok(()) => {
589                    WasmCachingStatus::Ready.set_status(&file_lock.file);
590                    Ok(())
591                }
592                // Failed to write, set corrupted status
593                Err(e) => {
594                    WasmCachingStatus::Corrupted.set_status(&file_lock.file);
595                    Err(e)
596                }
597            }
598        }) {
599            // It's not critical if it fails, as we already have wasm bytes, so we just log it
600            log::error!(target: "wasm_caching", "Failed to save wasm cache: {cache_save_err}")
601        }
602        Ok(wasm)
603    }
604}