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 for (key, value) in &output.storage.current_keys {
50 storage.set(key, value);
51 }
52
53 for key in &output.storage.removed_keys {
55 storage.remove(key);
56 }
57}
58
59const DEFAULT_GAS_LIMIT: u64 = 500_000_000_000_000; #[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 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 let mut instance = instance_from_reused_module(module, backend, options)?;
175
176 let gas_before = instance.get_gas_left();
177
178 let result = execute_function(&mut instance, function)?;
180
181 let gas_after = instance.get_gas_left();
182
183 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 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 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 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 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 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 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 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 Writing,
457 Ready,
459 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 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 if !wasm_cache_enabled {
515 return wasm_code_bytes();
516 }
517
518 let wasm_cache_dir = cargo_target_dir().join(WASM_CACHE_DIR);
519 match fs::metadata(&wasm_cache_dir) {
521 Ok(wasm_cache_metadata) => {
523 if !wasm_cache_metadata.is_dir() {
524 bail!("{WASM_CACHE_DIR} supposed to be directory")
525 }
526 }
527 Err(_) => {
529 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 Ok(_) => {
538 let mut file =
539 fs::File::open(&cached_wasm_file).context("unable to open wasm cache file")?;
540 let mut status = WasmCachingStatus::status(&file);
543 if let WasmCachingStatus::Writing = status {
544 let options = file_lock::FileOptions::new().read(true);
545 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 WasmCachingStatus::Corrupted => {
561 store_new_wasm(wasm_code_bytes, &cached_wasm_file)?
562 }
563 WasmCachingStatus::Writing => {
565 store_new_wasm(wasm_code_bytes, &cached_wasm_file)?
566 }
567 }
568 }
569 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 WasmCachingStatus::Writing.set_status(&file_lock.file);
585
586 match file_lock.file.write_all_at(&wasm, 1) {
587 Ok(()) => {
589 WasmCachingStatus::Ready.set_status(&file_lock.file);
590 Ok(())
591 }
592 Err(e) => {
594 WasmCachingStatus::Corrupted.set_status(&file_lock.file);
595 Err(e)
596 }
597 }
598 }) {
599 log::error!(target: "wasm_caching", "Failed to save wasm cache: {cache_save_err}")
601 }
602 Ok(wasm)
603 }
604}