// Copyright 2018-2021 Parity Technologies (UK) Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use super::EnvInstance; use crate::{ call::{ utils::ReturnType, CallParams, CreateParams, }, hash::{ Blake2x128, Blake2x256, CryptoHash, HashOutput, Keccak256, Sha2x256, }, topics::{ Topics, TopicsBuilderBackend, }, Clear, EnvBackend, Environment, RentParams, RentStatus, Result, ReturnFlags, TypedEnvBackend, }; use ink_engine::{ ext, ext::Engine, }; use ink_primitives::Key; /// The capacity of the static buffer. /// This is the same size as the ink! on-chain environment. We chose to use the same size /// to be as close to the on-chain behavior as possible. const BUFFER_SIZE: usize = 1 << 14; // 16kB impl CryptoHash for Blake2x128 { fn hash(input: &[u8], output: &mut ::Type) { type OutputType = [u8; 16]; static_assertions::assert_type_eq_all!( ::Type, OutputType ); let output: &mut OutputType = arrayref::array_mut_ref!(output, 0, 16); Engine::hash_blake2_128(input, output); } } impl CryptoHash for Blake2x256 { fn hash(input: &[u8], output: &mut ::Type) { type OutputType = [u8; 32]; static_assertions::assert_type_eq_all!( ::Type, OutputType ); let output: &mut OutputType = arrayref::array_mut_ref!(output, 0, 32); Engine::hash_blake2_256(input, output); } } impl CryptoHash for Sha2x256 { fn hash(input: &[u8], output: &mut ::Type) { type OutputType = [u8; 32]; static_assertions::assert_type_eq_all!( ::Type, OutputType ); let output: &mut OutputType = arrayref::array_mut_ref!(output, 0, 32); Engine::hash_sha2_256(input, output); } } impl CryptoHash for Keccak256 { fn hash(input: &[u8], output: &mut ::Type) { type OutputType = [u8; 32]; static_assertions::assert_type_eq_all!( ::Type, OutputType ); let output: &mut OutputType = arrayref::array_mut_ref!(output, 0, 32); Engine::hash_keccak_256(input, output); } } impl From for crate::Error { fn from(ext_error: ext::Error) -> Self { match ext_error { ext::Error::UnknownError => Self::UnknownError, ext::Error::CalleeTrapped => Self::CalleeTrapped, ext::Error::CalleeReverted => Self::CalleeReverted, ext::Error::KeyNotFound => Self::KeyNotFound, ext::Error::BelowSubsistenceThreshold => Self::BelowSubsistenceThreshold, ext::Error::TransferFailed => Self::TransferFailed, ext::Error::NewContractNotFunded => Self::NewContractNotFunded, ext::Error::CodeNotFound => Self::CodeNotFound, ext::Error::NotCallable => Self::NotCallable, ext::Error::LoggingDisabled => Self::LoggingDisabled, } } } #[derive(Default)] pub struct TopicsBuilder { pub topics: Vec>, } impl TopicsBuilderBackend for TopicsBuilder where E: Environment, { type Output = Vec; fn expect(&mut self, _expected_topics: usize) {} fn push_topic(&mut self, topic_value: &T) where T: scale::Encode, { let encoded = topic_value.encode(); let len_encoded = encoded.len(); let mut result = ::Hash::clear(); let len_result = result.as_ref().len(); if len_encoded <= len_result { result.as_mut()[..len_encoded].copy_from_slice(&encoded[..]); } else { let mut hash_output = ::Type::default(); ::hash(&encoded[..], &mut hash_output); let copy_len = core::cmp::min(hash_output.len(), len_result); result.as_mut()[0..copy_len].copy_from_slice(&hash_output[0..copy_len]); } let off_hash = result.as_ref(); let off_hash = off_hash.to_vec(); debug_assert!( !self.topics.contains(&off_hash), "duplicate topic hash discovered!" ); self.topics.push(off_hash); } fn output(self) -> Self::Output { let mut all: Vec = Vec::new(); let topics_len_compact = &scale::Compact(self.topics.len() as u32); let topics_encoded = &scale::Encode::encode(&topics_len_compact)[..]; all.append(&mut topics_encoded.to_vec()); self.topics.into_iter().for_each(|mut v| all.append(&mut v)); all } } impl EnvInstance { /// Returns the contract property value. fn get_property( &mut self, ext_fn: fn(engine: &Engine, output: &mut &mut [u8]), ) -> Result where T: scale::Decode, { let mut full_scope: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; let full_scope = &mut &mut full_scope[..]; ext_fn(&self.engine, full_scope); scale::Decode::decode(&mut &full_scope[..]).map_err(Into::into) } } impl EnvBackend for EnvInstance { fn set_contract_storage(&mut self, key: &Key, value: &V) where V: scale::Encode, { let v = scale::Encode::encode(value); self.engine.set_storage(key.as_bytes(), &v[..]); } fn get_contract_storage(&mut self, key: &Key) -> Result> where R: scale::Decode, { let mut output: [u8; 9600] = [0; 9600]; match self .engine .get_storage(key.as_bytes(), &mut &mut output[..]) { Ok(_) => (), Err(ext::Error::KeyNotFound) => return Ok(None), Err(_) => panic!("encountered unexpected error"), } let decoded = scale::Decode::decode(&mut &output[..])?; Ok(Some(decoded)) } fn clear_contract_storage(&mut self, key: &Key) { self.engine.clear_storage(key.as_bytes()) } fn decode_input(&mut self) -> Result where T: scale::Decode, { unimplemented!("the experimental off chain env does not implement `seal_input`") } fn return_value(&mut self, _flags: ReturnFlags, _return_value: &R) -> ! where R: scale::Encode, { unimplemented!( "the experimental off chain env does not implement `seal_return_value`" ) } fn debug_message(&mut self, message: &str) { self.engine.debug_message(message) } fn hash_bytes(&mut self, input: &[u8], output: &mut ::Type) where H: CryptoHash, { ::hash(input, output) } fn hash_encoded(&mut self, input: &T, output: &mut ::Type) where H: CryptoHash, T: scale::Encode, { let enc_input = &scale::Encode::encode(input)[..]; ::hash(enc_input, output) } fn call_chain_extension( &mut self, func_id: u32, input: &I, status_to_result: F, decode_to_result: D, ) -> ::core::result::Result where I: scale::Encode, T: scale::Decode, E: From, F: FnOnce(u32) -> ::core::result::Result<(), ErrorCode>, D: FnOnce(&[u8]) -> ::core::result::Result, { let enc_input = &scale::Encode::encode(input)[..]; let mut output: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; status_to_result(self.engine.call_chain_extension( func_id, enc_input, &mut &mut output[..], ))?; let decoded = decode_to_result(&mut &output[..])?; Ok(decoded) } } impl TypedEnvBackend for EnvInstance { fn caller(&mut self) -> Result { self.get_property::(Engine::caller) } fn transferred_balance(&mut self) -> Result { self.get_property::(Engine::value_transferred) } fn gas_left(&mut self) -> Result { self.get_property::(Engine::gas_left) } fn block_timestamp(&mut self) -> Result { self.get_property::(Engine::now) } fn account_id(&mut self) -> Result { self.get_property::(Engine::address) } fn balance(&mut self) -> Result { self.get_property::(Engine::balance) } fn rent_allowance(&mut self) -> Result { self.get_property::(Engine::rent_allowance) } fn rent_params(&mut self) -> Result> where T: Environment, { unimplemented!("off-chain environment does not support rent params") } fn rent_status( &mut self, _at_refcount: Option, ) -> Result> where T: Environment, { unimplemented!("off-chain environment does not support rent status") } fn block_number(&mut self) -> Result { self.get_property::(Engine::block_number) } fn minimum_balance(&mut self) -> Result { self.get_property::(Engine::minimum_balance) } fn tombstone_deposit(&mut self) -> Result { self.get_property::(Engine::tombstone_deposit) } fn emit_event(&mut self, event: Event) where T: Environment, Event: Topics + scale::Encode, { let builder = TopicsBuilder::default(); let enc_topics = event.topics::(builder.into()); let enc_data = &scale::Encode::encode(&event)[..]; self.engine.deposit_event(&enc_topics[..], enc_data); } fn set_rent_allowance(&mut self, new_value: T::Balance) where T: Environment, { let buffer = &scale::Encode::encode(&new_value)[..]; self.engine.set_rent_allowance(buffer) } fn invoke_contract( &mut self, _call_params: &CallParams, ) -> Result<()> where T: Environment, Args: scale::Encode, { unimplemented!("off-chain environment does not support contract invocation") } fn eval_contract( &mut self, _call_params: &CallParams>, ) -> Result where T: Environment, Args: scale::Encode, R: scale::Decode, { unimplemented!("off-chain environment does not support contract evaluation") } fn instantiate_contract( &mut self, _params: &CreateParams, ) -> Result where T: Environment, Args: scale::Encode, Salt: AsRef<[u8]>, { unimplemented!("off-chain environment does not support contract instantiation") } fn restore_contract( &mut self, account_id: T::AccountId, code_hash: T::Hash, rent_allowance: T::Balance, filtered_keys: &[Key], ) where T: Environment, { let enc_account_id = &scale::Encode::encode(&account_id)[..]; let enc_code_hash = &scale::Encode::encode(&code_hash)[..]; let enc_rent_allowance = &scale::Encode::encode(&rent_allowance)[..]; let filtered: Vec<&[u8]> = filtered_keys.iter().map(|k| &k.as_bytes()[..]).collect(); self.engine.restore_to( enc_account_id, enc_code_hash, enc_rent_allowance, &filtered[..], ); } fn terminate_contract(&mut self, beneficiary: T::AccountId) -> ! where T: Environment, { let buffer = scale::Encode::encode(&beneficiary); self.engine.terminate(&buffer[..]) } fn transfer(&mut self, destination: T::AccountId, value: T::Balance) -> Result<()> where T: Environment, { let enc_destination = &scale::Encode::encode(&destination)[..]; let enc_value = &scale::Encode::encode(&value)[..]; self.engine .transfer(enc_destination, enc_value) .map_err(Into::into) } fn weight_to_fee(&mut self, gas: u64) -> Result { let mut output: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; self.engine.weight_to_fee(gas, &mut &mut output[..]); scale::Decode::decode(&mut &output[..]).map_err(Into::into) } fn random(&mut self, subject: &[u8]) -> Result<(T::Hash, T::BlockNumber)> where T: Environment, { let mut output: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; self.engine.random(subject, &mut &mut output[..]); scale::Decode::decode(&mut &output[..]).map_err(Into::into) } }