From 33b386585895739bd31bfaf5bf2b49f52b428840 Mon Sep 17 00:00:00 2001 From: Sergei Pepyakin Date: Mon, 18 Nov 2019 13:37:15 +0100 Subject: [PATCH] srml-contracts: Add ability to read substrate's storage (#4108) * Add `ext_get_runtime_storage` * Implement testing for ext_get_runtime_storage * Add integration test. * Update spec version * Fix the doc * Save memory allocation. --- substrate/paint/contracts/src/exec.rs | 14 +- substrate/paint/contracts/src/tests.rs | 106 +++++++++++++++ substrate/paint/contracts/src/wasm/mod.rs | 127 ++++++++++++++++++ substrate/paint/contracts/src/wasm/runtime.rs | 30 ++++- 4 files changed, 274 insertions(+), 3 deletions(-) diff --git a/substrate/paint/contracts/src/exec.rs b/substrate/paint/contracts/src/exec.rs index 08fd8999a4..b71f54b18b 100644 --- a/substrate/paint/contracts/src/exec.rs +++ b/substrate/paint/contracts/src/exec.rs @@ -22,7 +22,10 @@ use crate::rent; use rstd::prelude::*; use sr_primitives::traits::{Bounded, CheckedAdd, CheckedSub, Zero}; -use support::traits::{WithdrawReason, Currency, Time, Randomness}; +use support::{ + storage::unhashed, + traits::{WithdrawReason, Currency, Time, Randomness}, +}; pub type AccountIdOf = ::AccountId; pub type CallOf = ::Call; @@ -175,6 +178,11 @@ pub trait Ext { /// Returns the maximum allowed size of a storage item. fn max_value_size(&self) -> u32; + + /// Returns the value of runtime under the given key. + /// + /// Returns `None` if the value doesn't exist. + fn get_runtime_storage(&self, key: &[u8]) -> Option>; } /// Loader is a companion of the `Vm` trait. It loads an appropriate abstract @@ -785,6 +793,10 @@ where fn max_value_size(&self) -> u32 { self.ctx.config.max_value_size } + + fn get_runtime_storage(&self, key: &[u8]) -> Option> { + unhashed::get_raw(&key) + } } /// These tests exercise the executive layer. diff --git a/substrate/paint/contracts/src/tests.rs b/substrate/paint/contracts/src/tests.rs index 7a13a66de2..28ba77f0bb 100644 --- a/substrate/paint/contracts/src/tests.rs +++ b/substrate/paint/contracts/src/tests.rs @@ -2341,3 +2341,109 @@ fn check_block_gas_limit_works() { assert_eq!(check.validate(&0, &call, info, 0), Ok(Default::default())); }); } + +const CODE_GET_RUNTIME_STORAGE: &str = r#" +(module + (import "env" "ext_get_runtime_storage" + (func $ext_get_runtime_storage (param i32 i32) (result i32)) + ) + (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) + (import "env" "ext_scratch_write" (func $ext_scratch_write (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + (func (export "deploy")) + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func $call (export "call") + ;; Load runtime storage for the first key and assert that it exists. + (call $assert + (i32.eq + (call $ext_get_runtime_storage + (i32.const 16) + (i32.const 4) + ) + (i32.const 0) + ) + ) + + ;; assert $ext_scratch_size == 4 + (call $assert + (i32.eq + (call $ext_scratch_size) + (i32.const 4) + ) + ) + + ;; copy contents of the scratch buffer into the contract's memory. + (call $ext_scratch_read + (i32.const 4) ;; Pointer in memory to the place where to copy. + (i32.const 0) ;; Offset from the start of the scratch buffer. + (i32.const 4) ;; Count of bytes to copy. + ) + + ;; assert that contents of the buffer is equal to the i32 value of 0x14144020. + (call $assert + (i32.eq + (i32.load + (i32.const 4) + ) + (i32.const 0x14144020) + ) + ) + + ;; Load the second key and assert that it doesn't exist. + (call $assert + (i32.eq + (call $ext_get_runtime_storage + (i32.const 20) + (i32.const 4) + ) + (i32.const 1) + ) + ) + ) + + ;; The first key, 4 bytes long. + (data (i32.const 16) "\01\02\03\04") + ;; The second key, 4 bytes long. + (data (i32.const 20) "\02\03\04\05") +) +"#; + +#[test] +fn get_runtime_storage() { + let (wasm, code_hash) = compile_module::(CODE_GET_RUNTIME_STORAGE).unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + Balances::deposit_creating(&ALICE, 1_000_000); + + support::storage::unhashed::put_raw( + &[1, 2, 3, 4], + 0x14144020u32.to_le_bytes().to_vec().as_ref() + ); + + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); + assert_ok!(Contract::instantiate( + Origin::signed(ALICE), + 100, + 100_000, + code_hash.into(), + vec![], + )); + assert_ok!(Contract::call( + Origin::signed(ALICE), + BOB, + 0, + 100_000, + vec![], + )); + }); +} diff --git a/substrate/paint/contracts/src/wasm/mod.rs b/substrate/paint/contracts/src/wasm/mod.rs index 42ee6ee0a2..b1010d7b14 100644 --- a/substrate/paint/contracts/src/wasm/mod.rs +++ b/substrate/paint/contracts/src/wasm/mod.rs @@ -151,6 +151,7 @@ impl<'a, T: Trait> crate::exec::Vm for WasmVm<'a> { mod tests { use super::*; use std::collections::HashMap; + use std::cell::RefCell; use primitives::H256; use crate::exec::{Ext, StorageKey, ExecError, ExecReturnValue, STATUS_SUCCESS}; use crate::gas::{Gas, GasMeter}; @@ -199,6 +200,17 @@ mod tests { // (topics, data) events: Vec<(Vec, Vec)>, next_account_id: u64, + + /// Runtime storage keys works the following way. + /// + /// - If the test code requests a value and it doesn't exist in this storage map then a + /// panic happens. + /// - If the value does exist it is returned and then removed from the map. So a panic + /// happens if the same value is requested for the second time. + /// + /// This behavior is used to prevent mixing up an access to unexpected location and empty + /// cell. + runtime_storage_keys: RefCell, Option>>>, } impl Ext for MockExt { @@ -305,6 +317,19 @@ mod tests { fn block_number(&self) -> u64 { 121 } fn max_value_size(&self) -> u32 { 16_384 } + + fn get_runtime_storage(&self, key: &[u8]) -> Option> { + let opt_value = self.runtime_storage_keys + .borrow_mut() + .remove(key); + opt_value.unwrap_or_else(|| + panic!( + "{:?} doesn't exist. values that do exist {:?}", + key, + self.runtime_storage_keys + ) + ) + } } impl Ext for &mut MockExt { @@ -389,6 +414,9 @@ mod tests { fn max_value_size(&self) -> u32 { (**self).max_value_size() } + fn get_runtime_storage(&self, key: &[u8]) -> Option> { + (**self).get_runtime_storage(key) + } } fn execute( @@ -1634,4 +1662,103 @@ mod tests { assert_eq!(output, ExecReturnValue { status: 17, data: hex!("5566778899").to_vec() }); assert!(!output.is_success()); } + + const CODE_GET_RUNTIME_STORAGE: &str = r#" +(module + (import "env" "ext_get_runtime_storage" + (func $ext_get_runtime_storage (param i32 i32) (result i32)) + ) + (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) + (import "env" "ext_scratch_write" (func $ext_scratch_write (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + (func (export "deploy")) + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func $call (export "call") + ;; Load runtime storage for the first key and assert that it exists. + (call $assert + (i32.eq + (call $ext_get_runtime_storage + (i32.const 16) + (i32.const 4) + ) + (i32.const 0) + ) + ) + + ;; assert $ext_scratch_size == 4 + (call $assert + (i32.eq + (call $ext_scratch_size) + (i32.const 4) + ) + ) + + ;; copy contents of the scratch buffer into the contract's memory. + (call $ext_scratch_read + (i32.const 4) ;; Pointer in memory to the place where to copy. + (i32.const 0) ;; Offset from the start of the scratch buffer. + (i32.const 4) ;; Count of bytes to copy. + ) + + ;; assert that contents of the buffer is equal to the i32 value of 0x14144020. + (call $assert + (i32.eq + (i32.load + (i32.const 4) + ) + (i32.const 0x14144020) + ) + ) + + ;; Load the second key and assert that it doesn't exist. + (call $assert + (i32.eq + (call $ext_get_runtime_storage + (i32.const 20) + (i32.const 4) + ) + (i32.const 1) + ) + ) + ) + + ;; The first key, 4 bytes long. + (data (i32.const 16) "\01\02\03\04") + ;; The second key, 4 bytes long. + (data (i32.const 20) "\02\03\04\05") +) +"#; + + #[test] + fn get_runtime_storage() { + let mut gas_meter = GasMeter::with_limit(50_000, 1); + let mock_ext = MockExt::default(); + + // "\01\02\03\04" - Some(0x14144020) + // "\02\03\04\05" - None + *mock_ext.runtime_storage_keys.borrow_mut() = [ + ([1, 2, 3, 4].to_vec(), Some(0x14144020u32.to_le_bytes().to_vec())), + ([2, 3, 4, 5].to_vec().to_vec(), None), + ] + .iter() + .cloned() + .collect(); + let _ = execute( + CODE_GET_RUNTIME_STORAGE, + vec![], + mock_ext, + &mut gas_meter, + ).unwrap(); + } } diff --git a/substrate/paint/contracts/src/wasm/runtime.rs b/substrate/paint/contracts/src/wasm/runtime.rs index 28117eaa7c..e53cadaf7b 100644 --- a/substrate/paint/contracts/src/wasm/runtime.rs +++ b/substrate/paint/contracts/src/wasm/runtime.rs @@ -333,8 +333,8 @@ define_env!(Env, , Ok(()) }, - // Retrieve the value at the given location from the storage and return 0. - // If there is no entry at the given location then this function will return 1 and + // Retrieve the value under the given key from the storage and return 0. + // If there is no entry under the given key then this function will return 1 and // clear the scratch buffer. // // - key_ptr: pointer into the linear memory where the key @@ -835,6 +835,32 @@ define_env!(Env, , ctx.ext.block_number().encode_to(&mut ctx.scratch_buf); Ok(()) }, + + // Retrieve the value under the given key from the **runtime** storage and return 0. + // If there is no entry under the given key then this function will return 1 and + // clear the scratch buffer. + // + // - key_ptr: the pointer into the linear memory where the requested value is placed. + // - key_len: the length of the key in bytes. + ext_get_runtime_storage(ctx, key_ptr: u32, key_len: u32) -> u32 => { + // Steal the scratch buffer so that we hopefully save an allocation for the `key_buf`. + read_sandbox_memory_into_scratch(ctx, key_ptr, key_len)?; + let key_buf = mem::replace(&mut ctx.scratch_buf, Vec::new()); + + match ctx.ext.get_runtime_storage(&key_buf) { + Some(value_buf) => { + // The given value exists. + ctx.scratch_buf = value_buf; + Ok(0) + } + None => { + // Put back the `key_buf` and allow its allocation to be reused. + ctx.scratch_buf = key_buf; + ctx.scratch_buf.clear(); + Ok(1) + } + } + }, ); /// Finds duplicates in a given vector.