use crate::{
chain_extension::ChainExtensionHandler,
database::Database,
exec_context::ExecContext,
test_api::{
DebugInfo,
EmittedEvent,
},
types::{
AccountId,
Balance,
BlockTimestamp,
},
};
pub use pallet_contracts_uapi::ReturnErrorCode as Error;
use scale::Encode;
use std::panic::panic_any;
pub struct Engine {
pub database: Database,
pub exec_context: ExecContext,
pub(crate) debug_info: DebugInfo,
pub chain_spec: ChainSpec,
pub chain_extension_handler: ChainExtensionHandler,
}
pub struct ChainSpec {
pub gas_price: Balance,
pub minimum_balance: Balance,
pub block_time: BlockTimestamp,
}
impl Default for ChainSpec {
fn default() -> Self {
Self {
gas_price: 100,
minimum_balance: 1000000,
block_time: 6,
}
}
}
impl Engine {
pub fn new() -> Self {
Self {
database: Database::new(),
exec_context: ExecContext::new(),
debug_info: DebugInfo::new(),
chain_spec: ChainSpec::default(),
chain_extension_handler: ChainExtensionHandler::new(),
}
}
}
impl Default for Engine {
fn default() -> Self {
Self::new()
}
}
impl Engine {
pub fn transfer(&mut self, account_id: &[u8], mut value: &[u8]) -> Result<(), Error> {
let increment = <u128 as scale::Decode>::decode(&mut value)
.map_err(|_| Error::TransferFailed)?;
let dest = account_id.to_vec();
let dest_old_balance = self.get_balance(dest.clone()).unwrap_or_default();
let contract = self.get_callee();
let contract_old_balance = self
.get_balance(contract.clone())
.map_err(|_| Error::TransferFailed)?;
self.database
.set_balance(&contract, contract_old_balance - increment);
self.database
.set_balance(&dest, dest_old_balance + increment);
Ok(())
}
pub fn deposit_event(&mut self, topics: &[u8], data: &[u8]) {
let topics_count: scale::Compact<u32> = scale::Decode::decode(&mut &topics[0..1])
.unwrap_or_else(|err| panic!("decoding number of topics failed: {err}"));
let topics_count = topics_count.0 as usize;
let topics_vec = if topics_count > 0 {
let topics = &topics[1..];
let bytes_per_topic = topics.len() / topics_count;
let topics_vec: Vec<Vec<u8>> = topics
.chunks(bytes_per_topic)
.map(|chunk| chunk.to_vec())
.collect();
assert_eq!(topics_count, topics_vec.len());
topics_vec
} else {
Vec::new()
};
self.debug_info.record_event(EmittedEvent {
topics: topics_vec,
data: data.to_vec(),
});
}
pub fn set_storage(&mut self, key: &[u8], encoded_value: &[u8]) -> Option<u32> {
let callee = self.get_callee();
let account_id = AccountId::from_bytes(&callee[..]);
self.debug_info.inc_writes(account_id.clone());
self.debug_info
.record_cell_for_account(account_id, key.to_vec());
self.database
.insert_into_contract_storage(&callee, key, encoded_value.to_vec())
.map(|v| <u32>::try_from(v.len()).expect("usize to u32 conversion failed"))
}
pub fn get_storage(&mut self, key: &[u8]) -> Result<&[u8], Error> {
let callee = self.get_callee();
let account_id = AccountId::from_bytes(&callee[..]);
self.debug_info.inc_reads(account_id);
match self.database.get_from_contract_storage(&callee, key) {
Some(val) => Ok(val),
None => Err(Error::KeyNotFound),
}
}
pub fn take_storage(&mut self, key: &[u8]) -> Result<Vec<u8>, Error> {
let callee = self.get_callee();
let account_id = AccountId::from_bytes(&callee[..]);
self.debug_info.inc_writes(account_id);
match self.database.remove_contract_storage(&callee, key) {
Some(val) => Ok(val),
None => Err(Error::KeyNotFound),
}
}
pub fn contains_storage(&mut self, key: &[u8]) -> Option<u32> {
let callee = self.get_callee();
let account_id = AccountId::from_bytes(&callee[..]);
self.debug_info.inc_reads(account_id);
self.database
.get_from_contract_storage(&callee, key)
.map(|val| val.len() as u32)
}
pub fn clear_storage(&mut self, key: &[u8]) -> Option<u32> {
let callee = self.get_callee();
let account_id = AccountId::from_bytes(&callee[..]);
self.debug_info.inc_writes(account_id.clone());
let _ = self
.debug_info
.remove_cell_for_account(account_id, key.to_vec());
self.database
.remove_contract_storage(&callee, key)
.map(|val| val.len() as u32)
}
pub fn terminate(&mut self, beneficiary: &[u8]) -> ! {
let contract = self.get_callee();
let all = self
.get_balance(contract)
.unwrap_or_else(|err| panic!("could not get balance: {err:?}"));
let value = &scale::Encode::encode(&all)[..];
self.transfer(beneficiary, value)
.unwrap_or_else(|err| panic!("transfer did not work: {err:?}"));
let res = (all, beneficiary.to_vec());
panic_any(scale::Encode::encode(&res));
}
pub fn caller(&self, output: &mut &mut [u8]) {
let caller = self
.exec_context
.caller
.as_ref()
.expect("no caller has been set")
.as_bytes();
set_output(output, caller);
}
pub fn balance(&self, output: &mut &mut [u8]) {
let contract = self
.exec_context
.callee
.as_ref()
.expect("no callee has been set");
let balance_in_storage = self
.database
.get_balance(contract.as_bytes())
.expect("currently executing contract must exist");
let balance = scale::Encode::encode(&balance_in_storage);
set_output(output, &balance[..])
}
pub fn value_transferred(&self, output: &mut &mut [u8]) {
let value_transferred: Vec<u8> =
scale::Encode::encode(&self.exec_context.value_transferred);
set_output(output, &value_transferred[..])
}
pub fn address(&self, output: &mut &mut [u8]) {
let callee = self
.exec_context
.callee
.as_ref()
.expect("no callee has been set")
.as_bytes();
set_output(output, callee)
}
pub fn debug_message(&mut self, message: &str) {
self.debug_info.record_debug_message(String::from(message));
print!("{message}");
}
pub fn hash_blake2_256(input: &[u8], output: &mut [u8; 32]) {
super::hashing::blake2b_256(input, output);
}
pub fn hash_blake2_128(input: &[u8], output: &mut [u8; 16]) {
super::hashing::blake2b_128(input, output);
}
pub fn hash_sha2_256(input: &[u8], output: &mut [u8; 32]) {
super::hashing::sha2_256(input, output);
}
pub fn hash_keccak_256(input: &[u8], output: &mut [u8; 32]) {
super::hashing::keccak_256(input, output);
}
pub fn block_number(&self, output: &mut &mut [u8]) {
let block_number: Vec<u8> =
scale::Encode::encode(&self.exec_context.block_number);
set_output(output, &block_number[..])
}
pub fn block_timestamp(&self, output: &mut &mut [u8]) {
let block_timestamp: Vec<u8> =
scale::Encode::encode(&self.exec_context.block_timestamp);
set_output(output, &block_timestamp[..])
}
pub fn gas_left(&self, _output: &mut &mut [u8]) {
unimplemented!("off-chain environment does not yet support `gas_left`");
}
pub fn minimum_balance(&self, output: &mut &mut [u8]) {
let minimum_balance: Vec<u8> =
scale::Encode::encode(&self.chain_spec.minimum_balance);
set_output(output, &minimum_balance[..])
}
#[allow(clippy::too_many_arguments)]
pub fn instantiate(
&mut self,
_code_hash: &[u8],
_gas_limit: u64,
_endowment: &[u8],
_input: &[u8],
_out_address: &mut &mut [u8],
_out_return_value: &mut &mut [u8],
_salt: &[u8],
) -> Result<(), Error> {
unimplemented!("off-chain environment does not yet support `instantiate`");
}
pub fn call(
&mut self,
_callee: &[u8],
_gas_limit: u64,
_value: &[u8],
_input: &[u8],
_output: &mut &mut [u8],
) -> Result<(), Error> {
unimplemented!("off-chain environment does not yet support `call`");
}
pub fn weight_to_fee(&self, gas: u64, output: &mut &mut [u8]) {
let fee = self.chain_spec.gas_price.saturating_mul(gas.into());
let fee: Vec<u8> = scale::Encode::encode(&fee);
set_output(output, &fee[..])
}
pub fn call_chain_extension(
&mut self,
id: u32,
input: &[u8],
output: &mut &mut [u8],
) {
let encoded_input = input.encode();
let (status_code, out) = self
.chain_extension_handler
.eval(id, &encoded_input)
.unwrap_or_else(|error| {
panic!(
"Encountered unexpected missing chain extension method: {error:?}"
);
});
let res = (status_code, out);
let decoded: Vec<u8> = scale::Encode::encode(&res);
set_output(output, &decoded[..])
}
pub fn ecdsa_recover(
&mut self,
signature: &[u8; 65],
message_hash: &[u8; 32],
output: &mut [u8; 33],
) -> Result<(), Error> {
use secp256k1::{
ecdsa::{
RecoverableSignature,
RecoveryId,
},
Message,
SECP256K1,
};
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_digest_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 set_output(output: &mut &mut [u8], slice: &[u8]) {
assert!(
slice.len() <= output.len(),
"the output buffer is too small! the decoded storage is of size {} bytes, \
but the output buffer has only room for {}.",
slice.len(),
output.len(),
);
output[..slice.len()].copy_from_slice(slice);
}