1use std::convert::TryInto;
3
4use oasis_contract_sdk_types::storage::StoreKind;
5use oasis_runtime_sdk::{context::Context, storage::Store};
6
7use super::{memory::Region, OasisV1};
8use crate::{
9 abi::{gas, ExecutionContext},
10 store, Config, Error,
11};
12
13impl<Cfg: Config> OasisV1<Cfg> {
14 pub fn link_storage<C: Context>(
16 instance: &mut wasm3::Instance<'_, '_, ExecutionContext<'_, C>>,
17 ) -> Result<(), Error> {
18 let _ = instance.link_function(
20 "storage",
21 "get",
22 |ctx, (store, key): (u32, (u32, u32))| -> Result<u32, wasm3::Trap> {
23 let ec = ctx.context.ok_or(wasm3::Trap::Abort)?;
25 let store_kind: StoreKind = store.try_into().map_err(|_| wasm3::Trap::Abort)?;
26
27 ensure_key_size(ec, key.1)?;
28
29 let total_gas = (|| {
31 let (base, key_base) = match store_kind {
32 StoreKind::Public => (
33 ec.params.gas_costs.wasm_public_storage_get_base,
34 ec.params.gas_costs.wasm_public_storage_key_byte,
35 ),
36 StoreKind::Confidential => (
37 ec.params.gas_costs.wasm_confidential_storage_get_base,
38 ec.params.gas_costs.wasm_confidential_storage_key_byte,
39 ),
40 };
41 let key = key_base.checked_mul(key.1.into())?;
42 let total = base.checked_add(key)?;
43 Some(total)
44 })()
45 .ok_or(wasm3::Trap::Abort)?;
46 gas::use_gas(ctx.instance, total_gas)?;
47
48 let value = ctx.instance.runtime().try_with_memory(
50 |memory| -> Result<_, wasm3::Trap> {
51 let key = Region::from_arg(key).as_slice(&memory)?;
52 with_instance_store(ec, store_kind, |store| store.get(key))
53 },
54 )??;
55
56 let value = match value {
57 Some(value) => value,
58 None => return Ok(0),
59 };
60
61 let value_byte_cost = match store_kind {
63 StoreKind::Public => ec.params.gas_costs.wasm_public_storage_value_byte,
64 StoreKind::Confidential => {
65 ec.params.gas_costs.wasm_confidential_storage_value_byte
66 }
67 };
68 gas::use_gas(
69 ctx.instance,
70 value_byte_cost
71 .checked_mul(value.len().try_into()?)
72 .ok_or(wasm3::Trap::Abort)?,
73 )?;
74
75 let value_region = Self::allocate_and_copy(ctx.instance, &value)?;
80
81 Self::allocate_region(ctx.instance, value_region).map_err(|e| e.into())
83 },
84 );
85
86 let _ = instance.link_function(
88 "storage",
89 "insert",
90 |ctx, (store, key, value): (u32, (u32, u32), (u32, u32))| {
91 let ec = ctx.context.ok_or(wasm3::Trap::Abort)?;
93 let store_kind: StoreKind = store.try_into().map_err(|_| wasm3::Trap::Abort)?;
94
95 ensure_key_size(ec, key.1)?;
96 ensure_value_size(ec, value.1)?;
97
98 let total_gas = (|| {
100 let (base, key_base, value_base) = match store_kind {
101 StoreKind::Public => (
102 ec.params.gas_costs.wasm_public_storage_insert_base,
103 ec.params.gas_costs.wasm_public_storage_key_byte,
104 ec.params.gas_costs.wasm_public_storage_value_byte,
105 ),
106 StoreKind::Confidential => (
107 ec.params.gas_costs.wasm_confidential_storage_insert_base,
108 ec.params.gas_costs.wasm_confidential_storage_key_byte,
109 ec.params.gas_costs.wasm_confidential_storage_value_byte,
110 ),
111 };
112 let key = key_base.checked_mul(key.1.into())?;
113 let value = value_base.checked_mul(value.1.into())?;
114 let total = base.checked_add(key)?.checked_add(value)?;
115 Some(total)
116 })()
117 .ok_or(wasm3::Trap::Abort)?;
118 gas::use_gas(ctx.instance, total_gas)?;
119
120 ctx.instance
122 .runtime()
123 .try_with_memory(|memory| -> Result<(), wasm3::Trap> {
124 let key = Region::from_arg(key).as_slice(&memory)?;
125 let value = Region::from_arg(value).as_slice(&memory)?;
126 with_instance_store(ec, store_kind, |store| store.insert(key, value))
127 })??;
128
129 Ok(())
130 },
131 );
132
133 let _ = instance.link_function(
135 "storage",
136 "remove",
137 |ctx, (store, key): (u32, (u32, u32))| {
138 let ec = ctx.context.ok_or(wasm3::Trap::Abort)?;
140 let store_kind: StoreKind = store.try_into().map_err(|_| wasm3::Trap::Abort)?;
141
142 ensure_key_size(ec, key.1)?;
143
144 let total_gas = (|| {
146 let (base, key_base) = match store_kind {
147 StoreKind::Public => (
148 ec.params.gas_costs.wasm_public_storage_remove_base,
149 ec.params.gas_costs.wasm_public_storage_key_byte,
150 ),
151 StoreKind::Confidential => (
152 ec.params.gas_costs.wasm_confidential_storage_remove_base,
153 ec.params.gas_costs.wasm_confidential_storage_key_byte,
154 ),
155 };
156 let key = key_base.checked_mul(key.1.into())?;
157 let total = base.checked_add(key)?;
158 Some(total)
159 })()
160 .ok_or(wasm3::Trap::Abort)?;
161 gas::use_gas(ctx.instance, total_gas)?;
162
163 ctx.instance
165 .runtime()
166 .try_with_memory(|memory| -> Result<(), wasm3::Trap> {
167 let key = Region::from_arg(key).as_slice(&memory)?;
168 with_instance_store(ec, store_kind, |store| store.remove(key))
169 })??;
170
171 Ok(())
172 },
173 );
174
175 Ok(())
176 }
177}
178
179fn with_instance_store<C, F, R>(
181 ec: &mut ExecutionContext<'_, C>,
182 store_kind: StoreKind,
183 f: F,
184) -> Result<R, wasm3::Trap>
185where
186 C: Context,
187 F: FnOnce(&mut dyn Store) -> R,
188{
189 store::with_instance_store(ec.tx_context, ec.instance_info, store_kind, |store| {
190 f(store)
191 })
192 .map_err(|err| {
193 ec.aborted = Some(err);
195 wasm3::Trap::Abort
196 })
197}
198
199fn ensure_key_size<C: Context>(
201 ec: &mut ExecutionContext<'_, C>,
202 size: u32,
203) -> Result<(), wasm3::Trap> {
204 if size > ec.params.max_storage_key_size_bytes {
205 ec.aborted = Some(Error::StorageKeyTooLarge(
206 size,
207 ec.params.max_storage_key_size_bytes,
208 ));
209 return Err(wasm3::Trap::Abort);
210 }
211 Ok(())
212}
213
214fn ensure_value_size<C: Context>(
216 ec: &mut ExecutionContext<'_, C>,
217 size: u32,
218) -> Result<(), wasm3::Trap> {
219 if size > ec.params.max_storage_value_size_bytes {
220 ec.aborted = Some(Error::StorageValueTooLarge(
221 size,
222 ec.params.max_storage_value_size_bytes,
223 ));
224 return Err(wasm3::Trap::Abort);
225 }
226 Ok(())
227}
228
229#[cfg(all(feature = "benchmarks", test))]
230mod bench {
231 extern crate test;
232 use super::*;
233 use std::{cell::RefCell, rc::Rc};
234 use test::Bencher;
235
236 use oasis_runtime_sdk::{context::Context, storage, testing::mock::Mock};
237
238 const BENCH_CODE: &[u8] = include_bytes!(
240 "../../../../../../tests/contracts/bench/target/wasm32-unknown-unknown/release/bench.wasm"
241 );
242
243 fn make_items(num: usize) -> Vec<(Vec<u8>, Vec<u8>)> {
244 let mut items = Vec::new();
245 for i in 0..num {
246 items.push((
247 format!("key{}", i).into_bytes(),
248 format!("value{}", i).into_bytes(),
249 ));
250 }
251 items
252 }
253
254 struct StoreContext<'a> {
255 store: Box<dyn storage::Store + 'a>,
256 }
257
258 #[bench]
259 fn bench_wasm_plain_get(b: &mut Bencher) {
260 let mut mock = Mock::default();
262 let mut ctx = mock.create_ctx();
263 let inner = storage::PrefixStore::new(
264 storage::PrefixStore::new(
265 storage::PrefixStore::new(ctx.runtime_state(), "test module"),
266 "instance prefix",
267 ),
268 "type prefix",
269 );
270 let mut store_ctx = StoreContext {
271 store: Box::new(storage::HashedStore::<_, blake3::Hasher>::new(inner)),
272 };
273
274 let items = make_items(10_000);
275 for i in 0..10_000 {
276 let item = &items[i % items.len()];
277 store_ctx.store.insert(&item.0, &item.1);
278 }
279
280 let env =
282 wasm3::Environment::new().expect("creating a new wasm3 environment should succeed");
283 let module = env
284 .parse_module(BENCH_CODE)
285 .expect("parsing the code should succeed");
286 let rt: wasm3::Runtime<'_, StoreContext<'_>> = env
287 .new_runtime(1 * 1024 * 1024, None)
288 .expect("creating a new wasm3 runtime should succeed");
289 let mut instance = rt
290 .load_module(module)
291 .expect("instance creation should succeed");
292 let _ = instance.link_function(
293 "bench",
294 "plain_get",
295 |ctx, key: (u32, u32)| -> Result<u32, wasm3::Trap> {
296 let key = ctx.instance.runtime().try_with_memory(
297 |memory| -> Result<Vec<u8>, wasm3::Trap> {
298 Ok(Region::from_arg(key)
299 .as_slice(&memory)
300 .map_err(|_| wasm3::Trap::Abort)?
301 .into())
302 },
303 )??;
304 let store_ctx = ctx.context.ok_or(wasm3::Trap::Abort)?;
305 match store_ctx.store.get(&key) {
306 None => Ok(0),
307 Some(value) => {
308 let alloc = ctx
309 .instance
310 .find_function::<u32, u32>("alloc")
311 .expect("finding alloc function should succeed");
312 let val_len = value.len() as u32;
313 let target_offset = alloc
314 .call(val_len + std::mem::size_of::<u32>() as u32)
315 .expect("alloc should succeed")
316 as usize;
317
318 ctx.instance.runtime().try_with_memory(
319 |mut memory| -> Result<_, wasm3::Trap> {
320 let len_bytes = &mut memory.as_slice_mut()
321 [target_offset..target_offset + std::mem::size_of::<u32>()];
322 len_bytes.copy_from_slice(&val_len.to_le_bytes());
323
324 let val_start = target_offset + std::mem::size_of::<u32>();
325 let target =
326 &mut memory.as_slice_mut()[val_start..val_start + value.len()];
327 target.copy_from_slice(&value);
328 Ok(target_offset as u32)
329 },
330 )?
331 }
332 }
333 },
334 );
335 let func = instance
336 .find_function::<(), ()>("bench_storage_get")
337 .expect("finding the entrypoint function should succeed");
338 b.iter(|| {
339 func.call_with_context(&mut store_ctx, ())
340 .expect("function call should succeed");
341 });
342 }
343
344 #[bench]
345 fn bench_wasm_plain_insert(b: &mut Bencher) {
346 let mut mock = Mock::default();
348 let mut ctx = mock.create_ctx();
349 let inner = storage::PrefixStore::new(
350 storage::PrefixStore::new(
351 storage::PrefixStore::new(ctx.runtime_state(), "test module"),
352 "instance prefix",
353 ),
354 "type prefix",
355 );
356 let mut store_ctx = StoreContext {
357 store: Box::new(storage::HashedStore::<_, blake3::Hasher>::new(inner)),
358 };
359
360 let env =
362 wasm3::Environment::new().expect("creating a new wasm3 environment should succeed");
363 let module = env
364 .parse_module(BENCH_CODE)
365 .expect("parsing the code should succeed");
366 let rt: wasm3::Runtime<'_, StoreContext<'_>> = env
367 .new_runtime(1 * 1024 * 1024, None)
368 .expect("creating a new wasm3 runtime should succeed");
369 let mut instance = rt
370 .load_module(module)
371 .expect("instance creation should succeed");
372 let _ = instance.link_function(
373 "bench",
374 "plain_insert",
375 |ctx, (key, value): ((u32, u32), (u32, u32))| -> Result<u32, wasm3::Trap> {
376 let key = ctx.instance.runtime().try_with_memory(
377 |memory| -> Result<Vec<u8>, wasm3::Trap> {
378 Ok(Region::from_arg(key)
379 .as_slice(&memory)
380 .map_err(|_| wasm3::Trap::Abort)?
381 .into())
382 },
383 )??;
384 let value = ctx.instance.runtime().try_with_memory(
385 |memory| -> Result<Vec<u8>, wasm3::Trap> {
386 Ok(Region::from_arg(value)
387 .as_slice(&memory)
388 .map_err(|_| wasm3::Trap::Abort)?
389 .into())
390 },
391 )??;
392 let store_ctx = ctx.context.ok_or(wasm3::Trap::Abort)?;
393 store_ctx.store.insert(&key, &value);
394 Ok(0)
395 },
396 );
397 let func = instance
398 .find_function::<u32, u32>("bench_storage_insert")
399 .expect("finding the entrypoint function should succeed");
400 let mut counter_base: u32 = 0;
401 b.iter(|| {
402 counter_base += func
403 .call_with_context(&mut store_ctx, counter_base)
404 .expect("function call should succeed");
405 });
406 }
407
408 #[bench]
409 fn bench_wasm_plain_remove(b: &mut Bencher) {
410 let mut mock = Mock::default();
412 let mut ctx = mock.create_ctx();
413 let inner = storage::PrefixStore::new(
414 storage::PrefixStore::new(
415 storage::PrefixStore::new(ctx.runtime_state(), "test module"),
416 "instance prefix",
417 ),
418 "type prefix",
419 );
420 let mut store_ctx = StoreContext {
421 store: Box::new(storage::HashedStore::<_, blake3::Hasher>::new(inner)),
422 };
423
424 for i in 0..2_000_000 {
425 store_ctx.store.insert(
426 format!("key{}", i).as_bytes(),
427 format!("value{}", i).as_bytes(),
428 );
429 }
430
431 let env =
433 wasm3::Environment::new().expect("creating a new wasm3 environment should succeed");
434 let module = env
435 .parse_module(BENCH_CODE)
436 .expect("parsing the code should succeed");
437 let rt: wasm3::Runtime<'_, StoreContext<'_>> = env
438 .new_runtime(1 * 1024 * 1024, None)
439 .expect("creating a new wasm3 runtime should succeed");
440 let mut instance = rt
441 .load_module(module)
442 .expect("instance creation should succeed");
443 let _ = instance.link_function(
444 "bench",
445 "plain_remove",
446 |ctx, key: (u32, u32)| -> Result<u32, wasm3::Trap> {
447 let key = ctx.instance.runtime().try_with_memory(
448 |memory| -> Result<Vec<u8>, wasm3::Trap> {
449 Ok(Region::from_arg(key)
450 .as_slice(&memory)
451 .map_err(|_| wasm3::Trap::Abort)?
452 .into())
453 },
454 )??;
455 let store_ctx = ctx.context.ok_or(wasm3::Trap::Abort)?;
456 store_ctx.store.remove(&key);
457 Ok(0)
458 },
459 );
460 let func = instance
461 .find_function::<u32, u32>("bench_storage_remove")
462 .expect("finding the entrypoint function should succeed");
463 let mut counter_base: u32 = 0;
464 b.iter(|| {
465 counter_base += func
466 .call_with_context(&mut store_ctx, counter_base)
467 .expect("function call should succeed");
468 });
469 }
470
471 #[bench]
472 fn bench_nowasm_get(b: &mut Bencher) {
473 let mut mock = Mock::default();
475 let mut ctx = mock.create_ctx();
476 let inner = storage::PrefixStore::new(
477 storage::PrefixStore::new(
478 storage::PrefixStore::new(ctx.runtime_state(), "test module"),
479 "instance prefix",
480 ),
481 "type prefix",
482 );
483 let mut store = Box::new(storage::HashedStore::<_, blake3::Hasher>::new(inner));
484
485 let items = make_items(10_000);
486 for i in 0..10_000 {
487 let item = &items[i % items.len()];
488 store.insert(&item.0, &item.1);
489 }
490 b.iter(move || {
491 for i in 0..5_000 {
492 let key = format!("key{}", i);
493 let exp_value = format!("value{}", i);
494 let value = store.get(key.as_bytes()).unwrap();
495 assert_eq!(exp_value.as_bytes(), value.as_slice());
496 }
497 });
498 }
499
500 #[bench]
501 fn bench_wasm_reach_gas_limit(_b: &mut Bencher) {
502 let mut mock = Mock::default();
504 let mut ctx = mock.create_ctx();
505 let inner = storage::PrefixStore::new(
506 storage::PrefixStore::new(
507 storage::PrefixStore::new(ctx.runtime_state(), "test module"),
508 "instance prefix",
509 ),
510 "type prefix",
511 );
512 let mut store_ctx = StoreContext {
513 store: Box::new(storage::HashedStore::<_, blake3::Hasher>::new(inner)),
514 };
515
516 let params = crate::Parameters::default();
517 let params_cb = params.clone();
518
519 let mut module = walrus::ModuleConfig::new()
521 .generate_producers_section(false)
522 .parse(&BENCH_CODE)
523 .unwrap();
524 gas::transform(&mut module);
525 let instrumented_code = module.emit_wasm();
526 let env =
527 wasm3::Environment::new().expect("creating a new wasm3 environment should succeed");
528 let module = env
529 .parse_module(&instrumented_code)
530 .expect("parsing the code should succeed");
531 let rt: wasm3::Runtime<'_, StoreContext<'_>> = env
532 .new_runtime(1 * 1024 * 1024, None)
533 .expect("creating a new wasm3 runtime should succeed");
534 let mut instance = rt
535 .load_module(module)
536 .expect("instance creation should succeed");
537 let initial_gas: u64 = 1_000_000_000;
538 instance
539 .set_global(gas::EXPORT_GAS_LIMIT, initial_gas)
540 .expect("setting gas limit should succeed");
541
542 let bytes_written: Rc<RefCell<usize>> = Rc::new(RefCell::new(0));
543 let bytes_written_cb = bytes_written.clone();
544
545 let _ = instance.link_function(
546 "bench",
547 "plain_insert",
548 move |ctx, (key, value): ((u32, u32), (u32, u32))| -> Result<u32, wasm3::Trap> {
549 let key = ctx.instance.runtime().try_with_memory(
550 |memory| -> Result<Vec<u8>, wasm3::Trap> {
551 Ok(Region::from_arg(key)
552 .as_slice(&memory)
553 .map_err(|_| wasm3::Trap::Abort)?
554 .into())
555 },
556 )??;
557 let value = ctx.instance.runtime().try_with_memory(
558 |memory| -> Result<Vec<u8>, wasm3::Trap> {
559 Ok(Region::from_arg(value)
560 .as_slice(&memory)
561 .map_err(|_| wasm3::Trap::Abort)?
562 .into())
563 },
564 )??;
565
566 let total_gas = (|| {
567 let key_cost = params_cb
568 .gas_costs
569 .wasm_public_storage_key_byte
570 .checked_mul(key.len() as u64)?;
571 let value_cost = params_cb
572 .gas_costs
573 .wasm_public_storage_value_byte
574 .checked_mul(value.len() as u64)?;
575 let total = params_cb
576 .gas_costs
577 .wasm_public_storage_insert_base
578 .checked_add(key_cost)?
579 .checked_add(value_cost)?;
580 Some(total)
581 })()
582 .ok_or(wasm3::Trap::Abort)?;
583 gas::use_gas(ctx.instance, total_gas)?;
584
585 *bytes_written_cb.borrow_mut() += value.len();
586 let store_ctx = ctx.context.ok_or(wasm3::Trap::Abort)?;
587 store_ctx.store.insert(&key, &value);
588 Ok(0)
589 },
590 );
591 let func = instance
592 .find_function::<u32, u32>("bench_storage_gas_consumer")
593 .expect("finding the entrypoint function should succeed");
594 let _ = func.call_with_context(&mut store_ctx, params.max_storage_value_size_bytes as u32);
595 let gas_limit: u64 = instance
596 .get_global(gas::EXPORT_GAS_LIMIT)
597 .expect("getting gas limit global should succeed");
598 let gas_limit_exhausted: u64 = instance
599 .get_global(gas::EXPORT_GAS_LIMIT_EXHAUSTED)
600 .expect("getting gas limit exhausted global should succeed");
601 println!(
602 " storage waster: gas remaining {} [used: {}, exhausted flag: {}], value bytes written: {}",
603 gas_limit,
604 initial_gas - gas_limit,
605 gas_limit_exhausted,
606 *bytes_written.borrow()
607 );
608 }
609}