// 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::{ hashing, Account, EnvInstance, }; use crate::{ call::{ utils::ReturnType, CallParams, CreateParams, }, hash::{ Blake2x128, Blake2x256, CryptoHash, HashOutput, Keccak256, Sha2x256, }, topics::Topics, EnvBackend, Environment, Error, Result, ReturnFlags, TypedEnvBackend, }; use core::convert::TryInto; use ink_primitives::Key; use num_traits::Bounded; const UNITIALIZED_EXEC_CONTEXT: &str = "unitialized execution context: \ a possible source of error could be that you are using `#[test]` instead of `#[ink::test]`."; impl EnvInstance { /// Returns the callee account. fn callee_account(&self) -> &Account { let callee = self .exec_context() .expect(UNITIALIZED_EXEC_CONTEXT) .callee .clone(); self.accounts .get_account_off(&callee) .expect("callee account does not exist") } /// Returns the callee account as mutable reference. fn callee_account_mut(&mut self) -> &mut Account { let callee = self .exec_context() .expect(UNITIALIZED_EXEC_CONTEXT) .callee .clone(); self.accounts .get_account_off_mut(&callee) .expect("callee account does not exist") } } 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); hashing::blake2b_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); hashing::blake2b_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); hashing::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); hashing::keccak_256(input, output); } } impl EnvBackend for EnvInstance { fn set_contract_storage(&mut self, key: &Key, value: &V) where V: scale::Encode, { self.callee_account_mut() .set_storage(*key, value) .expect("callee account is not a smart contract"); } fn get_contract_storage(&mut self, key: &Key) -> Result> where R: scale::Decode, { self.callee_account() .get_storage::(*key) .map_err(Into::into) } fn clear_contract_storage(&mut self, key: &Key) { if !self.clear_storage_disabled { self.callee_account_mut() .clear_storage(*key) .expect("callee account is not a smart contract"); } } fn decode_input(&mut self) -> Result where T: scale::Decode, { self.exec_context() .map(|exec_ctx| &exec_ctx.call_data) .map(|call_data| scale::Encode::encode(call_data)) .map_err(Into::into) .and_then(|encoded| { ::decode(&mut &encoded[..]) .map_err(|_| scale::Error::from("could not decode input call data")) .map_err(Into::into) }) } fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> ! where R: scale::Encode, { let ctx = self.exec_context_mut().expect(UNITIALIZED_EXEC_CONTEXT); ctx.output = Some(return_value.encode()); std::process::exit(flags.into_u32() as i32) } fn println(&mut self, content: &str) { self.console.println(content) } 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 encoded = input.encode(); self.hash_bytes::(&encoded[..], 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 encoded_input = input.encode(); let (status_code, mut output) = self .chain_extension_handler .eval(func_id, &encoded_input) .expect("encountered unexpected missing chain extension method"); status_to_result(status_code)?; let decoded = decode_to_result(&mut output)?; Ok(decoded) } } impl EnvInstance { fn transfer_impl( &mut self, destination: &T::AccountId, value: T::Balance, ) -> Result<()> where T: Environment, { let src_id = self.account_id::()?; let src_value = self .accounts .get_account::(&src_id) .expect("account of executed contract must exist") .balance::()?; if src_value < value { return Err(Error::TransferFailed) } let dst_value = self .accounts .get_or_create_account::(destination) .balance::()?; self.accounts .get_account_mut::(&src_id) .expect("account of executed contract must exist") .set_balance::(src_value - value)?; self.accounts .get_account_mut::(destination) .expect("the account must exist already or has just been created") .set_balance::(dst_value + value)?; Ok(()) } // Remove the calling account and transfer remaining balance. // // This function never returns. Either the termination was successful and the // execution of the destroyed contract is halted. Or it failed during the termination // which is considered fatal. fn terminate_contract_impl(&mut self, beneficiary: T::AccountId) -> ! where T: Environment, { // Send the remaining balance to the beneficiary let all: T::Balance = self.balance::().expect("could not decode balance"); self.transfer_impl::(&beneficiary, all) .expect("transfer did not work "); // Remove account let contract_id = self.account_id::().expect("could not decode account id"); self.accounts.remove_account::(contract_id); // The on-chain implementation would set a tombstone with a code hash here // and remove the contract storage subsequently. Both is not easily achievable // with our current off-chain env, hence we left it out here for the moment. // Encode the result of the termination and panic with it. // This enables testing for the proper result and makes sure this // method returns `Never`. let res = crate::test::ContractTerminationResult:: { beneficiary, transferred: all, }; std::panic::panic_any(scale::Encode::encode(&res)); } } impl TypedEnvBackend for EnvInstance { fn caller(&mut self) -> Result { self.exec_context() .expect(UNITIALIZED_EXEC_CONTEXT) .caller::() .map_err(|_| scale::Error::from("could not decode caller")) .map_err(Into::into) } fn transferred_balance(&mut self) -> Result { self.exec_context() .expect(UNITIALIZED_EXEC_CONTEXT) .transferred_value::() .map_err(|_| scale::Error::from("could not decode transferred balance")) .map_err(Into::into) } /// Emulates gas price calculation fn weight_to_fee(&mut self, gas: u64) -> Result { use crate::arithmetic::Saturating as _; let gas_price = self .chain_spec .gas_price::() .map_err(|_| scale::Error::from("could not decode gas price"))?; Ok(gas_price .saturating_mul(gas.try_into().unwrap_or_else(|_| Bounded::max_value()))) } fn gas_left(&mut self) -> Result { self.exec_context() .expect(UNITIALIZED_EXEC_CONTEXT) .gas::() .map_err(|_| scale::Error::from("could not decode gas left")) .map_err(Into::into) } fn block_timestamp(&mut self) -> Result { self.current_block() .expect(UNITIALIZED_EXEC_CONTEXT) .timestamp::() .map_err(|_| scale::Error::from("could not decode block time")) .map_err(Into::into) } fn account_id(&mut self) -> Result { self.exec_context() .expect(UNITIALIZED_EXEC_CONTEXT) .callee::() .map_err(|_| scale::Error::from("could not decode callee")) .map_err(Into::into) } fn balance(&mut self) -> Result { self.callee_account() .balance::() .map_err(|_| scale::Error::from("could not decode callee balance")) .map_err(Into::into) } fn rent_allowance(&mut self) -> Result { self.callee_account() .rent_allowance::() .map_err(|_| scale::Error::from("could not decode callee rent allowance")) .map_err(Into::into) } fn block_number(&mut self) -> Result { self.current_block() .expect(UNITIALIZED_EXEC_CONTEXT) .number::() .map_err(|_| scale::Error::from("could not decode block number")) .map_err(Into::into) } fn minimum_balance(&mut self) -> Result { self.chain_spec .minimum_balance::() .map_err(|_| scale::Error::from("could not decode minimum balance")) .map_err(Into::into) } fn tombstone_deposit(&mut self) -> Result { self.chain_spec .tombstone_deposit::() .map_err(|_| scale::Error::from("could not decode tombstone deposit")) .map_err(Into::into) } fn emit_event(&mut self, new_event: Event) where T: Environment, Event: Topics + scale::Encode, { self.emitted_events.record::(new_event) } fn set_rent_allowance(&mut self, new_rent_allowance: T::Balance) where T: Environment, { self.callee_account_mut() .set_rent_allowance::(new_rent_allowance) .expect("could not encode rent allowance") } 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, { unimplemented!("off-chain environment does not support contract instantiation") } fn terminate_contract(&mut self, beneficiary: T::AccountId) -> ! where T: Environment, { self.terminate_contract_impl::(beneficiary) } fn restore_contract( &mut self, _account_id: T::AccountId, _code_hash: T::Hash, _rent_allowance: T::Balance, _filtered_keys: &[Key], ) where T: Environment, { unimplemented!("off-chain environment does not support contract restoration") } fn transfer(&mut self, destination: T::AccountId, value: T::Balance) -> Result<()> where T: Environment, { self.transfer_impl::(&destination, value) } fn random(&mut self, subject: &[u8]) -> Result<(T::Hash, T::BlockNumber)> where T: Environment, { let block = self.current_block().expect(UNITIALIZED_EXEC_CONTEXT); Ok((block.random::(subject)?, block.number::()?)) } }