use std::{
fmt::Debug,
mem,
rc::Rc,
sync::{Arc, Mutex},
};
pub use contract_transcode;
use contract_transcode::ContractMessageTranscoder;
use frame_support::{traits::fungible::Inspect, weights::Weight};
use pallet_contracts::Determinism;
use parity_scale_codec::Decode;
pub use record::Record;
use crate::{
mock::MockRegistry,
runtime::{
pallet_contracts_debugging::{InterceptingExt, TracingExt},
AccountIdFor, HashFor, RuntimeWithContracts,
},
MockingExtension, Sandbox, DEFAULT_GAS_LIMIT,
};
pub mod error;
pub mod mocking_api;
mod record;
mod transcoding;
use error::SessionError;
use self::mocking_api::MockingApi;
use crate::{
bundle::ContractBundle, errors::MessageResult, runtime::MinimalRuntime,
session::transcoding::TranscoderRegistry,
};
type BalanceOf<R> =
<<R as pallet_contracts::Config>::Currency as Inspect<AccountIdFor<R>>>::Balance;
pub const NO_ARGS: &[String] = &[];
pub const NO_SALT: Vec<u8> = vec![];
pub const NO_ENDOWMENT: Option<BalanceOf<MinimalRuntime>> = None;
pub struct Session<R: RuntimeWithContracts> {
sandbox: Sandbox<R>,
actor: AccountIdFor<R>,
gas_limit: Weight,
determinism: Determinism,
transcoders: TranscoderRegistry<AccountIdFor<R>>,
record: Record<R>,
mocks: Arc<Mutex<MockRegistry<AccountIdFor<R>>>>,
}
impl<R: RuntimeWithContracts> Session<R> {
pub fn new() -> Result<Self, SessionError> {
let mocks = Arc::new(Mutex::new(MockRegistry::new()));
let mut sandbox = Sandbox::new().map_err(SessionError::Drink)?;
sandbox.register_extension(InterceptingExt(Box::new(MockingExtension {
mock_registry: Arc::clone(&mocks),
})));
Ok(Self {
sandbox,
mocks,
actor: R::default_actor(),
gas_limit: DEFAULT_GAS_LIMIT,
determinism: Determinism::Enforced,
transcoders: TranscoderRegistry::new(),
record: Default::default(),
})
}
pub fn with_actor(self, actor: AccountIdFor<R>) -> Self {
Self { actor, ..self }
}
pub fn get_actor(&self) -> AccountIdFor<R> {
self.actor.clone()
}
pub fn set_actor(&mut self, actor: AccountIdFor<R>) -> AccountIdFor<R> {
mem::replace(&mut self.actor, actor)
}
pub fn with_gas_limit(self, gas_limit: Weight) -> Self {
Self { gas_limit, ..self }
}
pub fn set_gas_limit(&mut self, gas_limit: Weight) -> Weight {
mem::replace(&mut self.gas_limit, gas_limit)
}
pub fn get_gas_limit(&self) -> Weight {
self.gas_limit
}
pub fn with_determinism(self, determinism: Determinism) -> Self {
Self {
determinism,
..self
}
}
pub fn set_determinism(&mut self, determinism: Determinism) -> Determinism {
mem::replace(&mut self.determinism, determinism)
}
pub fn with_transcoder(
mut self,
contract_address: AccountIdFor<R>,
transcoder: &Rc<ContractMessageTranscoder>,
) -> Self {
self.set_transcoder(contract_address, transcoder);
self
}
pub fn set_transcoder(
&mut self,
contract_address: AccountIdFor<R>,
transcoder: &Rc<ContractMessageTranscoder>,
) {
self.transcoders.register(contract_address, transcoder);
}
pub fn sandbox(&mut self) -> &mut Sandbox<R> {
&mut self.sandbox
}
pub fn record(&self) -> &Record<R> {
&self.record
}
pub fn mocking_api(&mut self) -> &mut impl MockingApi<R> {
self
}
pub fn deploy_and<S: AsRef<str> + Debug>(
mut self,
contract_bytes: Vec<u8>,
constructor: &str,
args: &[S],
salt: Vec<u8>,
endowment: Option<BalanceOf<R>>,
transcoder: &Rc<ContractMessageTranscoder>,
) -> Result<Self, SessionError> {
self.deploy(
contract_bytes,
constructor,
args,
salt,
endowment,
transcoder,
)
.map(|_| self)
}
pub fn deploy<S: AsRef<str> + Debug>(
&mut self,
contract_bytes: Vec<u8>,
constructor: &str,
args: &[S],
salt: Vec<u8>,
endowment: Option<BalanceOf<R>>,
transcoder: &Rc<ContractMessageTranscoder>,
) -> Result<AccountIdFor<R>, SessionError> {
let data = transcoder
.encode(constructor, args)
.map_err(|err| SessionError::Encoding(err.to_string()))?;
self.record.start_recording_events(&mut self.sandbox);
let result = self.sandbox.deploy_contract(
contract_bytes,
endowment.unwrap_or_default(),
data,
salt,
self.actor.clone(),
self.gas_limit,
None,
);
self.record.stop_recording_events(&mut self.sandbox);
let ret = match &result.result {
Ok(exec_result) if exec_result.result.did_revert() => {
Err(SessionError::DeploymentReverted)
}
Ok(exec_result) => {
let address = exec_result.account_id.clone();
self.record.push_deploy_return(address.clone());
self.transcoders.register(address.clone(), transcoder);
Ok(address)
}
Err(err) => Err(SessionError::DeploymentFailed(*err)),
};
self.record.push_deploy_result(result);
ret
}
pub fn deploy_bundle<S: AsRef<str> + Debug>(
&mut self,
contract_file: ContractBundle,
constructor: &str,
args: &[S],
salt: Vec<u8>,
endowment: Option<BalanceOf<R>>,
) -> Result<AccountIdFor<R>, SessionError> {
self.deploy(
contract_file.wasm,
constructor,
args,
salt,
endowment,
&contract_file.transcoder,
)
}
pub fn deploy_bundle_and<S: AsRef<str> + Debug>(
mut self,
contract_file: ContractBundle,
constructor: &str,
args: &[S],
salt: Vec<u8>,
endowment: Option<BalanceOf<R>>,
) -> Result<Self, SessionError> {
self.deploy_bundle(contract_file, constructor, args, salt, endowment)
.map(|_| self)
}
pub fn upload_and(mut self, contract_bytes: Vec<u8>) -> Result<Self, SessionError> {
self.upload(contract_bytes).map(|_| self)
}
pub fn upload(&mut self, contract_bytes: Vec<u8>) -> Result<HashFor<R>, SessionError> {
let result = self.sandbox.upload_contract(
contract_bytes,
self.actor.clone(),
None,
self.determinism,
);
result
.map(|upload_result| upload_result.code_hash)
.map_err(SessionError::UploadFailed)
}
pub fn upload_bundle_and(self, contract_file: ContractBundle) -> Result<Self, SessionError> {
self.upload_and(contract_file.wasm)
}
pub fn upload_bundle(
&mut self,
contract_file: ContractBundle,
) -> Result<HashFor<R>, SessionError> {
self.upload(contract_file.wasm)
}
pub fn call_and<S: AsRef<str> + Debug>(
mut self,
message: &str,
args: &[S],
endowment: Option<BalanceOf<R>>,
) -> Result<Self, SessionError> {
self.call_internal::<_, ()>(None, message, args, endowment)
.map(|_| self)
}
pub fn call_with_address_and<S: AsRef<str> + Debug>(
mut self,
address: AccountIdFor<R>,
message: &str,
args: &[S],
endowment: Option<BalanceOf<R>>,
) -> Result<Self, SessionError> {
self.call_internal::<_, ()>(Some(address), message, args, endowment)
.map(|_| self)
}
pub fn call<S: AsRef<str> + Debug, T: Decode>(
&mut self,
message: &str,
args: &[S],
endowment: Option<BalanceOf<R>>,
) -> Result<MessageResult<T>, SessionError> {
self.call_internal::<_, T>(None, message, args, endowment)
}
pub fn call_with_address<S: AsRef<str> + Debug, T: Decode>(
&mut self,
address: AccountIdFor<R>,
message: &str,
args: &[S],
endowment: Option<BalanceOf<R>>,
) -> Result<MessageResult<T>, SessionError> {
self.call_internal(Some(address), message, args, endowment)
}
fn call_internal<S: AsRef<str> + Debug, T: Decode>(
&mut self,
address: Option<AccountIdFor<R>>,
message: &str,
args: &[S],
endowment: Option<BalanceOf<R>>,
) -> Result<MessageResult<T>, SessionError> {
let address = match address {
Some(address) => address,
None => self
.record
.deploy_returns()
.last()
.ok_or(SessionError::NoContract)?
.clone(),
};
let data = self
.transcoders
.get(&address)
.as_ref()
.ok_or(SessionError::NoTranscoder)?
.encode(message, args)
.map_err(|err| SessionError::Encoding(err.to_string()))?;
self.record.start_recording_events(&mut self.sandbox);
let result = self.sandbox.call_contract(
address,
endowment.unwrap_or_default(),
data,
self.actor.clone(),
self.gas_limit,
None,
self.determinism,
);
self.record.stop_recording_events(&mut self.sandbox);
let ret = match &result.result {
Ok(exec_result) if exec_result.did_revert() => Err(SessionError::CallReverted),
Ok(exec_result) => {
self.record.push_call_return(exec_result.data.clone());
self.record.last_call_return_decoded::<T>()
}
Err(err) => Err(SessionError::CallFailed(*err)),
};
self.record.push_call_result(result);
ret
}
pub fn set_tracing_extension(&mut self, d: TracingExt) {
self.sandbox.register_extension(d);
}
}