#![allow(clippy::test_attr_in_doctest)]
use super::*;
use crate::prelude::*;
pub struct TestEnvironment<D>(pub(super) EncapsulatedRadixEngine<D>)
where
D: SubstateDatabase + CommittableSubstateDatabase + 'static;
pub type DefaultTestEnvironment = TestEnvironment<InMemorySubstateDatabase>;
impl DefaultTestEnvironment {
pub fn new() -> Self {
TestEnvironmentBuilder::new().build()
}
}
impl<D> TestEnvironment<D>
where
D: SubstateDatabase + CommittableSubstateDatabase + 'static,
{
pub fn call_function_typed<I, O>(
&mut self,
package_address: PackageAddress,
blueprint_name: &str,
function_name: &str,
args: &I,
) -> Result<O, RuntimeError>
where
I: ScryptoEncode,
O: ScryptoDecode,
{
let args = scrypto_encode(args).expect("Scrypto encoding of args failed");
self.call_function(package_address, blueprint_name, function_name, args)
.map(|rtn| scrypto_decode(&rtn).expect("Scrypto decoding of returns failed"))
}
pub fn call_method_typed<N, I, O>(
&mut self,
node_id: N,
method_name: &str,
args: &I,
) -> Result<O, RuntimeError>
where
N: Into<NodeId>,
I: ScryptoEncode,
O: ScryptoDecode,
{
let args = scrypto_encode(args).expect("Scrypto encoding of args failed");
self.call_method(&node_id.into(), method_name, args)
.map(|rtn| scrypto_decode(&rtn).expect("Scrypto decoding of returns failed"))
}
pub fn call_direct_access_method_typed<N, I, O>(
&mut self,
node_id: N,
method_name: &str,
args: &I,
) -> Result<O, RuntimeError>
where
N: Into<NodeId>,
I: ScryptoEncode,
O: ScryptoDecode,
{
let args = scrypto_encode(args).expect("Scrypto encoding of args failed");
self.call_direct_access_method(&node_id.into(), method_name, args)
.map(|rtn| scrypto_decode(&rtn).expect("Scrypto decoding of returns failed"))
}
pub fn call_module_method_typed<N, I, O>(
&mut self,
node_id: N,
module: AttachedModuleId,
method_name: &str,
args: &I,
) -> Result<O, RuntimeError>
where
N: Into<NodeId>,
I: ScryptoEncode,
O: ScryptoDecode,
{
let args = scrypto_encode(args).expect("Scrypto encoding of args failed");
self.call_module_method(&node_id.into(), module, method_name, args)
.map(|rtn| scrypto_decode(&rtn).expect("Scrypto decoding of returns failed"))
}
pub fn with_kernel<F, O>(&mut self, callback: F) -> O
where
F: FnOnce(&TestKernel<'_, D>) -> O,
{
self.0.with_kernel(callback)
}
pub fn with_kernel_mut<F, O>(&mut self, callback: F) -> O
where
F: FnOnce(&mut TestKernel<'_, D>) -> O,
{
self.0.with_kernel_mut(callback)
}
pub fn enable_kernel_trace_module(&mut self) {
self.enable_module(EnabledModules::KERNEL_TRACE)
}
pub fn enable_limits_module(&mut self) {
self.enable_module(EnabledModules::LIMITS)
}
pub fn enable_costing_module(&mut self) {
self.enable_module(EnabledModules::COSTING)
}
pub fn enable_auth_module(&mut self) {
self.enable_module(EnabledModules::AUTH)
}
pub fn enable_transaction_runtime_module(&mut self) {
self.enable_module(EnabledModules::TRANSACTION_RUNTIME)
}
pub fn enable_execution_trace_module(&mut self) {
self.enable_module(EnabledModules::EXECUTION_TRACE)
}
pub fn disable_kernel_trace_module(&mut self) {
self.disable_module(EnabledModules::KERNEL_TRACE)
}
pub fn disable_limits_module(&mut self) {
self.disable_module(EnabledModules::LIMITS)
}
pub fn disable_costing_module(&mut self) {
self.disable_module(EnabledModules::COSTING)
}
pub fn disable_auth_module(&mut self) {
self.disable_module(EnabledModules::AUTH)
}
pub fn disable_transaction_runtime_module(&mut self) {
self.disable_module(EnabledModules::TRANSACTION_RUNTIME)
}
pub fn disable_execution_trace_module(&mut self) {
self.disable_module(EnabledModules::EXECUTION_TRACE)
}
pub fn with_kernel_trace_module_enabled<F, O>(&mut self, callback: F) -> O
where
F: FnOnce(&mut Self) -> O,
{
let enabled_modules = self.enabled_modules();
self.enable_kernel_trace_module();
let rtn = callback(self);
self.set_enabled_modules(enabled_modules);
rtn
}
pub fn with_limits_module_enabled<F, O>(&mut self, callback: F) -> O
where
F: FnOnce(&mut Self) -> O,
{
let enabled_modules = self.enabled_modules();
self.enable_limits_module();
let rtn = callback(self);
self.set_enabled_modules(enabled_modules);
rtn
}
pub fn with_costing_module_enabled<F, O>(&mut self, callback: F) -> O
where
F: FnOnce(&mut Self) -> O,
{
let enabled_modules = self.enabled_modules();
self.enable_costing_module();
let rtn = callback(self);
self.set_enabled_modules(enabled_modules);
rtn
}
pub fn with_auth_module_enabled<F, O>(&mut self, callback: F) -> O
where
F: FnOnce(&mut Self) -> O,
{
let enabled_modules = self.enabled_modules();
self.enable_auth_module();
let rtn = callback(self);
self.set_enabled_modules(enabled_modules);
rtn
}
pub fn with_transaction_runtime_module_enabled<F, O>(&mut self, callback: F) -> O
where
F: FnOnce(&mut Self) -> O,
{
let enabled_modules = self.enabled_modules();
self.enable_transaction_runtime_module();
let rtn = callback(self);
self.set_enabled_modules(enabled_modules);
rtn
}
pub fn with_execution_trace_module_enabled<F, O>(&mut self, callback: F) -> O
where
F: FnOnce(&mut Self) -> O,
{
let enabled_modules = self.enabled_modules();
self.enable_execution_trace_module();
let rtn = callback(self);
self.set_enabled_modules(enabled_modules);
rtn
}
pub fn with_kernel_trace_module_disabled<F, O>(&mut self, callback: F) -> O
where
F: FnOnce(&mut Self) -> O,
{
let enabled_modules = self.enabled_modules();
self.disable_kernel_trace_module();
let rtn = callback(self);
self.set_enabled_modules(enabled_modules);
rtn
}
pub fn with_limits_module_disabled<F, O>(&mut self, callback: F) -> O
where
F: FnOnce(&mut Self) -> O,
{
let enabled_modules = self.enabled_modules();
self.disable_limits_module();
let rtn = callback(self);
self.set_enabled_modules(enabled_modules);
rtn
}
pub fn with_costing_module_disabled<F, O>(&mut self, callback: F) -> O
where
F: FnOnce(&mut Self) -> O,
{
let enabled_modules = self.enabled_modules();
self.disable_costing_module();
let rtn = callback(self);
self.set_enabled_modules(enabled_modules);
rtn
}
pub fn with_auth_module_disabled<F, O>(&mut self, callback: F) -> O
where
F: FnOnce(&mut Self) -> O,
{
let enabled_modules = self.enabled_modules();
self.disable_auth_module();
let rtn = callback(self);
self.set_enabled_modules(enabled_modules);
rtn
}
pub fn with_transaction_runtime_module_disabled<F, O>(&mut self, callback: F) -> O
where
F: FnOnce(&mut Self) -> O,
{
let enabled_modules = self.enabled_modules();
self.disable_transaction_runtime_module();
let rtn = callback(self);
self.set_enabled_modules(enabled_modules);
rtn
}
pub fn with_execution_trace_module_disabled<F, O>(&mut self, callback: F) -> O
where
F: FnOnce(&mut Self) -> O,
{
let enabled_modules = self.enabled_modules();
self.disable_execution_trace_module();
let rtn = callback(self);
self.set_enabled_modules(enabled_modules);
rtn
}
pub fn enabled_modules(&self) -> EnabledModules {
self.0
.with_kernel(|kernel| kernel.kernel_callback().modules.enabled_modules)
}
pub fn set_enabled_modules(&mut self, enabled_modules: EnabledModules) {
self.0.with_kernel_mut(|kernel| {
kernel.kernel_callback_mut().modules.enabled_modules = enabled_modules
})
}
pub fn enable_module(&mut self, module: EnabledModules) {
self.0.with_kernel_mut(|kernel| {
kernel.kernel_callback_mut().modules.enabled_modules |= module
})
}
pub fn disable_module(&mut self, module: EnabledModules) {
self.0.with_kernel_mut(|kernel| {
kernel.kernel_callback_mut().modules.enabled_modules &= !module
})
}
pub fn with_component_state<S, N, F, O>(
&mut self,
node_id: N,
mut callback: F,
) -> Result<O, RuntimeError>
where
S: ScryptoDecode,
N: Into<NodeId>,
F: FnMut(&mut S, &mut Self) -> O,
{
let (handle, state) = self.0.with_kernel_mut(|kernel| {
let handle = kernel.kernel_open_substate(
&node_id.into(),
MAIN_BASE_PARTITION,
&SubstateKey::Field(ComponentField::State0.into()),
LockFlags::read_only(),
SystemLockData::Field(FieldLockData::Read),
)?;
let state = kernel.kernel_read_substate(handle).map(|v| {
let FieldSubstate::<ScryptoValue>::V1(FieldSubstateV1 { payload, .. }) =
v.as_typed().unwrap();
scrypto_encode(&payload).unwrap()
})?;
Ok::<_, RuntimeError>((handle, state))
})?;
let mut state = scrypto_decode::<S>(&state).unwrap();
let rtn = callback(&mut state, self);
self.0
.with_kernel_mut(|kernel| kernel.kernel_close_substate(handle))?;
Ok(rtn)
}
pub fn get_current_epoch(&mut self) -> Epoch {
Runtime::current_epoch(self).unwrap()
}
pub fn set_current_epoch(&mut self, epoch: Epoch) {
self.as_method_actor(
CONSENSUS_MANAGER,
ModuleId::Main,
CONSENSUS_MANAGER_NEXT_ROUND_IDENT,
|env| -> Result<(), RuntimeError> {
let manager_handle = env
.actor_open_field(
ACTOR_STATE_SELF,
ConsensusManagerField::State.into(),
LockFlags::MUTABLE,
)
.unwrap();
let mut manager_substate =
env.field_read_typed::<VersionedConsensusManagerState>(manager_handle)?;
manager_substate.as_unique_version_mut().epoch = epoch;
env.field_write_typed(manager_handle, &manager_substate)?;
env.field_close(manager_handle)?;
Ok(())
},
)
.unwrap()
.unwrap();
}
pub fn get_current_time(&mut self) -> Instant {
Runtime::current_time(TimePrecision::Second, self).unwrap()
}
pub fn set_current_time(&mut self, instant: Instant) {
self.as_method_actor(
CONSENSUS_MANAGER,
ModuleId::Main,
CONSENSUS_MANAGER_NEXT_ROUND_IDENT,
|env| -> Result<(), RuntimeError> {
let handle = env.actor_open_field(
ACTOR_STATE_SELF,
ConsensusManagerField::ProposerMinuteTimestamp.into(),
LockFlags::MUTABLE,
)?;
let mut proposer_minute_timestamp =
env.field_read_typed::<ConsensusManagerProposerMinuteTimestampFieldPayload>(
handle,
)?;
proposer_minute_timestamp
.as_unique_version_mut()
.epoch_minute =
i32::try_from(instant.seconds_since_unix_epoch / 60).map_err(|_| {
RuntimeError::ApplicationError(ApplicationError::ConsensusManagerError(
ConsensusManagerError::InvalidProposerTimestampUpdate {
from_millis: env.get_current_time().seconds_since_unix_epoch * 1000,
to_millis: instant.seconds_since_unix_epoch * 1000,
},
))
})?;
env.field_write_typed(handle, &proposer_minute_timestamp)?;
env.field_close(handle)?;
let handle = env.actor_open_field(
ACTOR_STATE_SELF,
ConsensusManagerField::ProposerMilliTimestamp.into(),
LockFlags::MUTABLE,
)?;
let mut proposer_milli_timestamp =
env.field_read_typed::<ConsensusManagerProposerMilliTimestampFieldPayload>(
handle,
)?;
proposer_milli_timestamp.as_unique_version_mut().epoch_milli =
instant.seconds_since_unix_epoch * 1000;
env.field_write_typed(handle, &proposer_milli_timestamp)?;
env.field_close(handle)?;
Ok(())
},
)
.unwrap()
.unwrap();
}
pub(crate) fn as_method_actor<N, F, O>(
&mut self,
node_id: N,
module_id: ModuleId,
method_ident: &str,
callback: F,
) -> Result<O, RuntimeError>
where
N: Into<NodeId> + Copy,
F: FnOnce(&mut Self) -> O,
O: ScryptoEncode,
{
let object_info = self.0.with_kernel_mut(|kernel| {
SystemService::new(kernel).get_object_info(&node_id.into())
})?;
let auth_zone = self.0.with_kernel_mut(|kernel| {
let mut system_service = SystemService::new(kernel);
AuthModule::on_call_fn_mock(
&mut system_service,
Some((&node_id.into(), false)),
Default::default(),
Default::default(),
)
})?;
let actor = Actor::Method(MethodActor {
method_type: match module_id {
ModuleId::Main => MethodType::Main,
ModuleId::Royalty => MethodType::Module(AttachedModuleId::Royalty),
ModuleId::Metadata => MethodType::Module(AttachedModuleId::Metadata),
ModuleId::RoleAssignment => MethodType::Module(AttachedModuleId::RoleAssignment),
},
ident: method_ident.to_owned(),
node_id: node_id.into(),
auth_zone,
object_info,
});
self.as_actor(actor, callback)
}
pub(crate) fn as_actor<F, O>(&mut self, actor: Actor, callback: F) -> Result<O, RuntimeError>
where
F: FnOnce(&mut Self) -> O,
O: ScryptoEncode,
{
let mut message =
CallFrameMessage::from_input(&IndexedScryptoValue::from_typed(&()), &actor);
self.0.with_kernel_mut(|kernel| {
let (substate_io, current_frame) = kernel.kernel_current_frame_mut();
message
.copy_global_references
.extend(current_frame.stable_references().keys());
let new_frame =
CallFrame::new_child_from_parent(substate_io, current_frame, actor, message)
.unwrap();
let old = core::mem::replace(current_frame, new_frame);
kernel.kernel_prev_frame_stack_mut().push(old);
});
let rtn = callback(self);
let message = {
let indexed_scrypto_value = IndexedScryptoValue::from_typed(&rtn);
CallFrameMessage {
move_nodes: indexed_scrypto_value.owned_nodes().clone(),
copy_global_references: indexed_scrypto_value.references().clone(),
..Default::default()
}
};
self.0
.with_kernel_mut(|kernel| -> Result<(), RuntimeError> {
let mut previous_frame = kernel.kernel_prev_frame_stack_mut().pop().unwrap();
let (substate_io, current_frame) = kernel.kernel_current_frame_mut();
CallFrame::pass_message(
substate_io,
current_frame,
&mut previous_frame,
message.clone(),
)
.map_err(CallFrameError::PassMessageError)
.map_err(KernelError::CallFrameError)?;
*current_frame = previous_frame;
Ok(())
})?;
Ok(rtn)
}
}
impl Default for TestEnvironment<InMemorySubstateDatabase> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use crate::prelude::*;
#[test]
pub fn test_env_can_be_created() {
let _ = TestEnvironment::new();
}
#[test]
pub fn test_time_set() {
let mut env = TestEnvironment::new();
let mut current_time = env.get_current_time();
current_time.seconds_since_unix_epoch += 60;
env.set_current_time(current_time);
let t1 = Runtime::current_time(TimePrecision::Second, &mut env).unwrap();
let t2 = Runtime::current_time(TimePrecision::Minute, &mut env).unwrap();
assert_eq!(t1, t2)
}
}