// Copyright 2018-2022 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::{ Call, CallParams, CreateParams, DelegateCall, }, hash::{ Blake2x128, Blake2x256, CryptoHash, HashOutput, Keccak256, Sha2x256, }, topics::{ Topics, TopicsBuilderBackend, }, Clear, EnvBackend, Environment, Error, Result, ReturnFlags, TypedEnvBackend, }; use ink_engine::{ ext, ext::Engine, }; use ink_storage_traits::Storable; /// 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; // 16 kB 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::Unknown => Self::Unknown, 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::_EndowmentTooLow => Self::_EndowmentTooLow, ext::Error::CodeNotFound => Self::CodeNotFound, ext::Error::NotCallable => Self::NotCallable, ext::Error::LoggingDisabled => Self::LoggingDisabled, ext::Error::EcdsaRecoveryFailed => Self::EcdsaRecoveryFailed, } } } #[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: &K, value: &V) -> Option where K: scale::Encode, V: Storable, { let mut v = vec![]; Storable::encode(value, &mut v); self.engine.set_storage(&key.encode(), &v[..]) } fn get_contract_storage(&mut self, key: &K) -> Result> where K: scale::Encode, R: Storable, { let mut output: [u8; 9600] = [0; 9600]; match self.engine.get_storage(&key.encode(), &mut &mut output[..]) { Ok(_) => (), Err(ext::Error::KeyNotFound) => return Ok(None), Err(_) => panic!("encountered unexpected error"), } let decoded = Storable::decode(&mut &output[..])?; Ok(Some(decoded)) } fn take_contract_storage(&mut self, key: &K) -> Result> where K: scale::Encode, R: Storable, { let mut output: [u8; 9600] = [0; 9600]; match self .engine .take_storage(&key.encode(), &mut &mut output[..]) { Ok(_) => (), Err(ext::Error::KeyNotFound) => return Ok(None), Err(_) => panic!("encountered unexpected error"), } let decoded = Storable::decode(&mut &output[..])?; Ok(Some(decoded)) } fn contains_contract_storage(&mut self, key: &K) -> Option where K: scale::Encode, { self.engine.contains_storage(&key.encode()) } fn clear_contract_storage(&mut self, key: &K) -> Option where K: scale::Encode, { self.engine.clear_storage(&key.encode()) } fn decode_input(&mut self) -> Result where T: scale::Decode, { unimplemented!("the off-chain env does not implement `input`") } fn return_value(&mut self, _flags: ReturnFlags, _return_value: &R) -> ! where R: scale::Encode, { unimplemented!("the off-chain env does not implement `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 ecdsa_recover( &mut self, signature: &[u8; 65], message_hash: &[u8; 32], output: &mut [u8; 33], ) -> Result<()> { use secp256k1::{ ecdsa::{ RecoverableSignature, RecoveryId, }, Message, SECP256K1, }; // In most implementations, the v is just 0 or 1 internally, but 27 was added // as an arbitrary number for signing Bitcoin messages and Ethereum adopted that as well. let recovery_byte = if signature[64] > 26 { signature[64] - 27 } else { signature[64] }; let recovery_id = RecoveryId::from_i32(recovery_byte as i32) .unwrap_or_else(|error| panic!("Unable to parse the recovery id: {}", error)); let message = Message::from_slice(message_hash).unwrap_or_else(|error| { panic!("Unable to create the message from hash: {}", error) }); let signature = RecoverableSignature::from_compact(&signature[0..64], recovery_id) .unwrap_or_else(|error| { panic!("Unable to parse the signature: {}", error) }); let pub_key = SECP256K1.recover_ecdsa(&message, &signature); match pub_key { Ok(pub_key) => { *output = pub_key.serialize(); Ok(()) } Err(_) => Err(Error::EcdsaRecoveryFailed), } } fn ecdsa_to_eth_address( &mut self, pubkey: &[u8; 33], output: &mut [u8; 20], ) -> Result<()> { let pk = secp256k1::PublicKey::from_slice(pubkey) .map_err(|_| Error::EcdsaRecoveryFailed)?; let uncompressed = pk.serialize_uncompressed(); let mut hash = ::Type::default(); ::hash(&uncompressed[1..], &mut hash); output.as_mut().copy_from_slice(&hash[12..]); Ok(()) } 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]; self.engine .call_chain_extension(func_id, enc_input, &mut &mut output[..]); let (status, out): (u32, Vec) = scale::Decode::decode(&mut &output[..]) .unwrap_or_else(|error| { panic!( "could not decode `call_chain_extension` output: {:?}", error ) }); status_to_result(status)?; let decoded = decode_to_result(&out[..])?; Ok(decoded) } fn set_code_hash(&mut self, _code_hash: &[u8]) -> Result<()> { unimplemented!("off-chain environment does not support `set_code_hash`") } } impl TypedEnvBackend for EnvInstance { fn caller(&mut self) -> E::AccountId { self.get_property::(Engine::caller) .unwrap_or_else(|error| { panic!("could not read `caller` property: {:?}", error) }) } fn transferred_value(&mut self) -> E::Balance { self.get_property::(Engine::value_transferred) .unwrap_or_else(|error| { panic!("could not read `transferred_value` property: {:?}", error) }) } fn gas_left(&mut self) -> u64 { self.get_property::(Engine::gas_left) .unwrap_or_else(|error| { panic!("could not read `gas_left` property: {:?}", error) }) } fn block_timestamp(&mut self) -> E::Timestamp { self.get_property::(Engine::block_timestamp) .unwrap_or_else(|error| { panic!("could not read `block_timestamp` property: {:?}", error) }) } fn account_id(&mut self) -> E::AccountId { self.get_property::(Engine::address) .unwrap_or_else(|error| { panic!("could not read `account_id` property: {:?}", error) }) } fn balance(&mut self) -> E::Balance { self.get_property::(Engine::balance) .unwrap_or_else(|error| { panic!("could not read `balance` property: {:?}", error) }) } fn block_number(&mut self) -> E::BlockNumber { self.get_property::(Engine::block_number) .unwrap_or_else(|error| { panic!("could not read `block_number` property: {:?}", error) }) } fn minimum_balance(&mut self) -> E::Balance { self.get_property::(Engine::minimum_balance) .unwrap_or_else(|error| { panic!("could not read `minimum_balance` property: {:?}", error) }) } fn emit_event(&mut self, event: Event) where 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 invoke_contract( &mut self, params: &CallParams, Args, R>, ) -> Result where E: Environment, Args: scale::Encode, R: scale::Decode, { let _gas_limit = params.gas_limit(); let _callee = params.callee(); let _call_flags = params.call_flags().into_u32(); let _transferred_value = params.transferred_value(); let _input = params.exec_input(); unimplemented!("off-chain environment does not support contract invocation") } fn invoke_contract_delegate( &mut self, params: &CallParams, Args, R>, ) -> Result where E: Environment, Args: scale::Encode, R: scale::Decode, { let _code_hash = params.code_hash(); unimplemented!( "off-chain environment does not support delegated contract invocation" ) } fn instantiate_contract( &mut self, params: &CreateParams, ) -> Result where E: Environment, Args: scale::Encode, Salt: AsRef<[u8]>, { let _code_hash = params.code_hash(); let _gas_limit = params.gas_limit(); let _endowment = params.endowment(); let _input = params.exec_input(); let _salt_bytes = params.salt_bytes(); unimplemented!("off-chain environment does not support contract instantiation") } fn terminate_contract(&mut self, beneficiary: E::AccountId) -> ! where E: Environment, { let buffer = scale::Encode::encode(&beneficiary); self.engine.terminate(&buffer[..]) } fn transfer(&mut self, destination: E::AccountId, value: E::Balance) -> Result<()> where E: 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) -> E::Balance { let mut output: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; self.engine.weight_to_fee(gas, &mut &mut output[..]); scale::Decode::decode(&mut &output[..]).unwrap_or_else(|error| { panic!("could not read `weight_to_fee` property: {:?}", error) }) } fn is_contract(&mut self, _account: &E::AccountId) -> bool where E: Environment, { unimplemented!("off-chain environment does not support contract instantiation") } fn caller_is_origin(&mut self) -> bool where E: Environment, { unimplemented!("off-chain environment does not support cross-contract calls") } fn code_hash(&mut self, _account: &E::AccountId) -> Result where E: Environment, { unimplemented!("off-chain environment does not support `code_hash`") } fn own_code_hash(&mut self) -> Result where E: Environment, { unimplemented!("off-chain environment does not support `own_code_hash`") } }