diff --git a/radix-engine-tests/src/lib.rs b/radix-engine-tests/src/lib.rs index 29668f8dfcc..ba2fe935551 100644 --- a/radix-engine-tests/src/lib.rs +++ b/radix-engine-tests/src/lib.rs @@ -1,3 +1,4 @@ // Provides assets paths to benchmarks. pub mod common; pub mod pool_stubs; +pub mod prelude; diff --git a/radix-engine-tests/src/prelude.rs b/radix-engine-tests/src/prelude.rs new file mode 100644 index 00000000000..79918a0264b --- /dev/null +++ b/radix-engine-tests/src/prelude.rs @@ -0,0 +1,2 @@ +pub use crate::common::PackageLoader; +pub use scrypto_test::prelude::*; diff --git a/radix-engine-tests/tests/application_folder.rs b/radix-engine-tests/tests/application_folder.rs index 01e2009c2e5..54939bf5a92 100644 --- a/radix-engine-tests/tests/application_folder.rs +++ b/radix-engine-tests/tests/application_folder.rs @@ -7,3 +7,7 @@ // * Have this X_folder.rs file which gets loaded by the test loader // * Use a mod definition to point to `X/mod.rs` where tests are defined mod application; + +pub mod prelude { + pub use radix_engine_tests::prelude::*; +} diff --git a/radix-engine-tests/tests/blueprints_folder.rs b/radix-engine-tests/tests/blueprints_folder.rs index 29fd27fe72b..a8293eb6a38 100644 --- a/radix-engine-tests/tests/blueprints_folder.rs +++ b/radix-engine-tests/tests/blueprints_folder.rs @@ -7,3 +7,7 @@ // * Have this X_folder.rs file which gets loaded by the test loader // * Use a mod definition to point to `X/mod.rs` where tests are defined mod blueprints; + +pub mod prelude { + pub use radix_engine_tests::prelude::*; +} diff --git a/radix-engine-tests/tests/db_folder.rs b/radix-engine-tests/tests/db_folder.rs index 895829cffa5..27866e59acf 100644 --- a/radix-engine-tests/tests/db_folder.rs +++ b/radix-engine-tests/tests/db_folder.rs @@ -7,3 +7,7 @@ // * Have this X_folder.rs file which gets loaded by the test loader // * Use a mod definition to point to `X/mod.rs` where tests are defined mod db; + +pub mod prelude { + pub use radix_engine_tests::prelude::*; +} diff --git a/radix-engine-tests/tests/flash_folder.rs b/radix-engine-tests/tests/flash_folder.rs index d3600030d84..882453dcf65 100644 --- a/radix-engine-tests/tests/flash_folder.rs +++ b/radix-engine-tests/tests/flash_folder.rs @@ -7,3 +7,7 @@ // * Have this X_folder.rs file which gets loaded by the test loader // * Use a mod definition to point to `X/mod.rs` where tests are defined mod flash; + +pub mod prelude { + pub use radix_engine_tests::prelude::*; +} diff --git a/radix-engine-tests/tests/kernel_folder.rs b/radix-engine-tests/tests/kernel_folder.rs index 5eb937cfe95..640f804e7b6 100644 --- a/radix-engine-tests/tests/kernel_folder.rs +++ b/radix-engine-tests/tests/kernel_folder.rs @@ -7,3 +7,7 @@ // * Have this X_folder.rs file which gets loaded by the test loader // * Use a mod definition to point to `X/mod.rs` where tests are defined mod kernel; + +pub mod prelude { + pub use radix_engine_tests::prelude::*; +} diff --git a/radix-engine-tests/tests/protocol_folder.rs b/radix-engine-tests/tests/protocol_folder.rs index deb4acde2c8..f98e2595178 100644 --- a/radix-engine-tests/tests/protocol_folder.rs +++ b/radix-engine-tests/tests/protocol_folder.rs @@ -7,3 +7,7 @@ // * Have this X_folder.rs file which gets loaded by the test loader // * Use a mod definition to point to `X/mod.rs` where tests are defined mod protocol; + +pub mod prelude { + pub use radix_engine_tests::prelude::*; +} diff --git a/radix-engine-tests/tests/system/subintent_auth.rs b/radix-engine-tests/tests/system/subintent_auth.rs index 7aba0a22df1..8c23780aae5 100644 --- a/radix-engine-tests/tests/system/subintent_auth.rs +++ b/radix-engine-tests/tests/system/subintent_auth.rs @@ -1,7 +1,7 @@ -use scrypto_test::prelude::*; +use crate::prelude::*; #[test] -fn should_not_be_able_to_use_root_auth_in_subintent() { +fn subintent_should_not_be_able_to_use_proofs_from_transaction_intent() { // Arrange let mut ledger = LedgerSimulatorBuilder::new().build(); let (public_key, _, account) = ledger.new_allocated_account(); @@ -38,6 +38,88 @@ fn should_not_be_able_to_use_root_auth_in_subintent() { }); } +#[test] +fn subintent_should_not_be_able_to_use_proofs_from_other_subintents() { + // Arrange + let mut ledger = LedgerSimulatorBuilder::new().build(); + + let assert_access_rule_component_address = { + let package_address = ledger.publish_package_simple(PackageLoader::get("role_assignment")); + + let manifest = ManifestBuilder::new() + .lock_fee_from_faucet() + .call_function(package_address, "AssertAccessRule", "new", manifest_args!()) + .build(); + + let receipt = ledger.execute_manifest(manifest, []); + receipt.expect_commit_success(); + + receipt.expect_commit(true).new_component_addresses()[0] + }; + + // We will create a complex transaction with lots of intents. + // Naming convention: subintent XXX will have children XXXa, XXXb, XXXc, etc. + // Each intent will be signed by its own key. + + let keys = indexmap! { + "aaa" => ledger.new_allocated_account().0, + "aaba" => ledger.new_allocated_account().0, + "aab" => ledger.new_allocated_account().0, + "aac" => ledger.new_allocated_account().0, + "aa" => ledger.new_allocated_account().0, + "ab" => ledger.new_allocated_account().0, + "a" => ledger.new_allocated_account().0, + "ba" => ledger.new_allocated_account().0, + "b" => ledger.new_allocated_account().0, + "c" => ledger.new_allocated_account().0, + "ROOT" => ledger.new_allocated_account().0, + }; + + // We will set all their manifests to be simple/trivial, except the AAB manifest + // which will be set to call "assert access rule" with each of the keys. + // The transaction should only pass if the AAB key is used. + for (key_name_to_assert, key_to_assert) in keys.iter() { + let mut builder = TestTransaction::new_v2_builder(ledger.next_transaction_nonce()); + + let aaa = builder.add_simple_subintent([], [keys["aaa"].signature_proof()]); + let aaba = builder.add_simple_subintent([], [keys["aaba"].signature_proof()]); + let aab = builder.add_tweaked_simple_subintent( + [aaba], + [keys["aab"].signature_proof()], + |builder| { + builder.call_method( + assert_access_rule_component_address, + "assert_access_rule", + (rule!(require(key_to_assert.signature_proof())),), + ) + }, + ); + let aac = builder.add_simple_subintent([], [keys["aac"].signature_proof()]); + let aa = builder.add_simple_subintent([aaa, aab, aac], [keys["aa"].signature_proof()]); + let ab = builder.add_simple_subintent([], [keys["ab"].signature_proof()]); + let a = builder.add_simple_subintent([aa, ab], [keys["a"].signature_proof()]); + let ba = builder.add_simple_subintent([], [keys["ba"].signature_proof()]); + let b = builder.add_simple_subintent([ba], [keys["b"].signature_proof()]); + let c = builder.add_simple_subintent([], [keys["c"].signature_proof()]); + let transaction = + builder.finish_with_simple_root_intent([a, b, c], [keys["ROOT"].signature_proof()]); + + let receipt = ledger.execute_test_transaction(transaction); + + // ASSERT + if *key_name_to_assert == "aab" { + receipt.expect_commit_success(); + } else { + receipt.expect_specific_failure(|e| { + matches!( + e, + RuntimeError::SystemError(SystemError::AssertAccessRuleFailed) + ) + }); + } + } +} + #[test] fn should_be_able_to_use_separate_auth_in_subintent() { // Arrange @@ -72,6 +154,60 @@ fn should_be_able_to_use_separate_auth_in_subintent() { receipt.expect_commit_success(); } +#[test] +fn subintent_processor_uses_transaction_processor_global_caller() { + // Arrange + let mut ledger = LedgerSimulatorBuilder::new().build(); + + let assert_access_rule_component_address = { + let package_address = ledger.publish_package_simple(PackageLoader::get("role_assignment")); + + let manifest = ManifestBuilder::new() + .lock_fee_from_faucet() + .call_function(package_address, "AssertAccessRule", "new", manifest_args!()) + .build(); + + let receipt = ledger.execute_manifest(manifest, []); + receipt.expect_commit_success(); + + receipt.expect_commit(true).new_component_addresses()[0] + }; + + // Act + let mut builder = TestTransaction::new_v2_builder(ledger.next_transaction_nonce()); + + let transaction_processor = BlueprintId { + package_address: TRANSACTION_PROCESSOR_PACKAGE, + blueprint_name: TRANSACTION_PROCESSOR_BLUEPRINT.to_string(), + }; + + let child = builder.add_subintent( + ManifestBuilder::new_subintent_v2() + .call_method( + assert_access_rule_component_address, + "assert_access_rule", + (rule!(require(global_caller(transaction_processor))),), + ) + .yield_to_parent(()) + .build(), + [], + ); + + let transaction = builder.finish_with_root_intent( + ManifestBuilder::new_v2() + .use_child("child", child) + .lock_fee_from_faucet() + .yield_to_child("child", ()) + .build(), + [], + ); + + let receipt = ledger.execute_test_transaction(transaction); + + // Assert + receipt.expect_commit_success(); +} + #[derive(Debug, Eq, PartialEq, ManifestSbor)] pub struct ManifestTransactionProcessorRunInput { pub manifest_encoded_instructions: Vec, diff --git a/radix-engine-tests/tests/system/subintent_verify_parent.rs b/radix-engine-tests/tests/system/subintent_verify_parent.rs index c5518c61d0f..44510fb2797 100644 --- a/radix-engine-tests/tests/system/subintent_verify_parent.rs +++ b/radix-engine-tests/tests/system/subintent_verify_parent.rs @@ -1,12 +1,4 @@ -use radix_common::constants::XRD; -use radix_common::crypto::HasPublicKeyHash; -use radix_engine::errors::{IntentError, RuntimeError, SystemError}; -use radix_engine_interface::macros::dec; -use radix_engine_interface::prelude::{require, require_amount, AccessRule}; -use radix_engine_interface::rule; -use radix_transactions::builder::ManifestBuilder; -use radix_transactions::model::TestTransaction; -use scrypto_test::ledger_simulator::LedgerSimulatorBuilder; +use crate::prelude::*; #[test] fn should_not_be_able_to_use_subintent_when_verify_parent_access_rule_not_met() { @@ -198,3 +190,67 @@ fn should_be_able_to_use_subintent_when_verify_parent_access_rule_is_met_on_seco // Assert receipt.expect_commit_success(); } + +#[test] +fn verify_parent_should_only_work_against_proofs_in_parent_intent() { + // Arrange + let mut ledger = LedgerSimulatorBuilder::new().build(); + + // We will create a complex transaction with lots of intents. + // Naming convention: subintent XXX will have children XXXa, XXXb, XXXc, etc. + // Each intent will be signed by its own key. + + let keys = indexmap! { + "aaa" => ledger.new_allocated_account().0, + "aaba" => ledger.new_allocated_account().0, + "aab" => ledger.new_allocated_account().0, + "aac" => ledger.new_allocated_account().0, + "aa" => ledger.new_allocated_account().0, + "ab" => ledger.new_allocated_account().0, + "a" => ledger.new_allocated_account().0, + "ba" => ledger.new_allocated_account().0, + "b" => ledger.new_allocated_account().0, + "c" => ledger.new_allocated_account().0, + "ROOT" => ledger.new_allocated_account().0, + }; + + // We will set all their manifests to be simple/trivial, except the AAB manifest + // which will be set to call "verify parent" with each of the keys. + // The transaction should only pass if the AA key is used (its parent). + for (key_name_to_assert, key_to_assert) in keys.iter() { + let mut builder = TestTransaction::new_v2_builder(ledger.next_transaction_nonce()); + + let aaa = builder.add_simple_subintent([], [keys["aaa"].signature_proof()]); + let aaba = builder.add_simple_subintent([], [keys["aaba"].signature_proof()]); + let aab = builder.add_tweaked_simple_subintent( + [aaba], + [keys["aab"].signature_proof()], + |builder| builder.verify_parent(rule!(require(key_to_assert.signature_proof()))), + ); + let aac = builder.add_simple_subintent([], [keys["aac"].signature_proof()]); + let aa = builder.add_simple_subintent([aaa, aab, aac], [keys["aa"].signature_proof()]); + let ab = builder.add_simple_subintent([], [keys["ab"].signature_proof()]); + let a = builder.add_simple_subintent([aa, ab], [keys["a"].signature_proof()]); + let ba = builder.add_simple_subintent([], [keys["ba"].signature_proof()]); + let b = builder.add_simple_subintent([ba], [keys["b"].signature_proof()]); + let c = builder.add_simple_subintent([], [keys["c"].signature_proof()]); + let transaction = + builder.finish_with_simple_root_intent([a, b, c], [keys["ROOT"].signature_proof()]); + + let receipt = ledger.execute_test_transaction(transaction); + + // ASSERT + if *key_name_to_assert == "aa" { + receipt.expect_commit_success(); + } else { + receipt.expect_specific_failure(|e| { + matches!( + e, + RuntimeError::SystemError(SystemError::IntentError( + IntentError::VerifyParentFailed + )) + ) + }); + } + } +} diff --git a/radix-engine-tests/tests/system_folder.rs b/radix-engine-tests/tests/system_folder.rs index 679a0c5d2ac..f9da4fc8742 100644 --- a/radix-engine-tests/tests/system_folder.rs +++ b/radix-engine-tests/tests/system_folder.rs @@ -7,3 +7,7 @@ // * Have this X_folder.rs file which gets loaded by the test loader // * Use a mod definition to point to `X/mod.rs` where tests are defined mod system; + +mod prelude { + pub use radix_engine_tests::prelude::*; +} diff --git a/radix-engine-tests/tests/vm_folder.rs b/radix-engine-tests/tests/vm_folder.rs index 0664f38901b..7cea481f86b 100644 --- a/radix-engine-tests/tests/vm_folder.rs +++ b/radix-engine-tests/tests/vm_folder.rs @@ -7,3 +7,7 @@ // * Have this X_folder.rs file which gets loaded by the test loader // * Use a mod definition to point to `X/mod.rs` where tests are defined mod vm; + +pub mod prelude { + pub use radix_engine_tests::prelude::*; +} diff --git a/radix-transactions/src/model/test_transaction.rs b/radix-transactions/src/model/test_transaction.rs index 6684dc6e30c..2938ce892ff 100644 --- a/radix-transactions/src/model/test_transaction.rs +++ b/radix-transactions/src/model/test_transaction.rs @@ -22,6 +22,39 @@ impl TestTransactionV2Builder { } } + /// Yields to each child exactly once with empty arguments. + pub fn add_simple_subintent( + &mut self, + children: impl IntoIterator, + proofs: impl IntoIterator, + ) -> SubintentHash { + let mut manifest_builder = ManifestBuilder::new_subintent_v2(); + for (child_index, child_hash) in children.into_iter().enumerate() { + let child_name = format!("child_{child_index}"); + manifest_builder = manifest_builder.use_child(&child_name, child_hash); + manifest_builder = manifest_builder.yield_to_child(child_name, ()); + } + let manifest = manifest_builder.yield_to_parent(()).build(); + self.add_subintent(manifest, proofs) + } + + pub fn add_tweaked_simple_subintent( + &mut self, + children: impl IntoIterator, + proofs: impl IntoIterator, + addition: impl FnOnce(SubintentManifestV2Builder) -> SubintentManifestV2Builder, + ) -> SubintentHash { + let mut manifest_builder = ManifestBuilder::new_subintent_v2(); + for (child_index, child_hash) in children.into_iter().enumerate() { + let child_name = format!("child_{child_index}"); + manifest_builder = manifest_builder.use_child(&child_name, child_hash); + manifest_builder = manifest_builder.yield_to_child(child_name, ()); + } + manifest_builder = addition(manifest_builder); + let manifest = manifest_builder.yield_to_parent(()).build(); + self.add_subintent(manifest, proofs) + } + pub fn add_subintent( &mut self, manifest: SubintentManifestV2, @@ -34,6 +67,24 @@ impl TestTransactionV2Builder { SubintentHash(hash) } + /// Uses the faucet and yields to each child exactly once with empty arguments. + pub fn finish_with_simple_root_intent( + self, + children: impl IntoIterator, + proofs: impl IntoIterator, + ) -> TestTransaction { + let mut manifest_builder = ManifestBuilder::new_v2(); + manifest_builder = manifest_builder.lock_fee_from_faucet(); + for (child_index, child_hash) in children.into_iter().enumerate() { + let child_name = format!("child_{child_index}"); + // In the manifest builder, we allow USE_CHILD later than in a written manifest + manifest_builder = manifest_builder.use_child(&child_name, child_hash); + manifest_builder = manifest_builder.yield_to_child(child_name, ()); + } + let manifest = manifest_builder.build(); + self.finish_with_root_intent(manifest, proofs) + } + pub fn finish_with_root_intent( self, manifest: TransactionManifestV2,