diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ab59ecb0..e61e88e6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.3 - name: Run tests - run: cargo test --features package-loader/build-time-blueprints + run: cargo test --workspace --features package-loader/build-time-blueprints --exclude stateful-tests env: # Enable sccache SCCACHE_GHA_ENABLED: "true" diff --git a/Cargo.lock b/Cargo.lock index c7e03397..07058cec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2987,6 +2987,35 @@ dependencies = [ "utils", ] +[[package]] +name = "stateful-tests" +version = "0.1.0" +dependencies = [ + "caviarnine-v1-adapter-v1", + "common", + "defiplaza-v2-adapter-v1", + "extend", + "gateway-client", + "ignition", + "lazy_static", + "macro_rules_attribute", + "ociswap-v1-adapter-v1", + "ociswap-v2-adapter-v1", + "package-loader", + "paste", + "ports-interface", + "publishing-tool", + "radix-engine", + "radix-engine-common", + "radix-engine-interface", + "sbor", + "scrypto-test", + "scrypto-unit", + "simple-oracle", + "state-manager", + "transaction", +] + [[package]] name = "static_assertions" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 1b59d8db..fd9163db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,8 @@ members = [ # Tools "tools/publishing-tool", # Tests - "tests" + "testing/tests", + "testing/stateful-tests" ] [workspace.package] @@ -43,6 +44,8 @@ radix-engine-store-interface = { git = "https://github.com/radixdlt/radixdlt-scr scrypto-unit = { git = "https://github.com/radixdlt/radixdlt-scrypto", rev = "4887c5e4be2603433592ed290b70b1a0c03cced3" } scrypto-test = { git = "https://github.com/radixdlt/radixdlt-scrypto", rev = "4887c5e4be2603433592ed290b70b1a0c03cced3" } +state-manager = { git = "https://github.com/radixdlt/babylon-node", rev = "63a8267196995fef0830e4fbf0271bea65c90ab1" } + [profile.release] opt-level = 'z' lto = true @@ -68,4 +71,4 @@ radix-engine-queries = { git = "https://www.github.com/radixdlt/radixdlt-scrypto radix-engine-interface = { git = "https://www.github.com/radixdlt/radixdlt-scrypto.git", rev = "4887c5e4be2603433592ed290b70b1a0c03cced3" } radix-engine-store-interface = { git = "https://www.github.com/radixdlt/radixdlt-scrypto.git", rev = "4887c5e4be2603433592ed290b70b1a0c03cced3" } scrypto-unit = { git = "https://www.github.com/radixdlt/radixdlt-scrypto.git", rev = "4887c5e4be2603433592ed290b70b1a0c03cced3" } -scrypto-test = { git = "https://www.github.com/radixdlt/radixdlt-scrypto.git", rev = "4887c5e4be2603433592ed290b70b1a0c03cced3" } \ No newline at end of file +scrypto-test = { git = "https://www.github.com/radixdlt/radixdlt-scrypto.git", rev = "4887c5e4be2603433592ed290b70b1a0c03cced3" } diff --git a/packages/ignition/src/blueprint.rs b/packages/ignition/src/blueprint.rs index 47845e4a..a4a6032a 100644 --- a/packages/ignition/src/blueprint.rs +++ b/packages/ignition/src/blueprint.rs @@ -122,7 +122,7 @@ mod ignition { protocol_owner, protocol_manager ]; - set_maximum_allowed_price_staleness => restrict_to: [ + set_maximum_allowed_price_staleness_in_seconds => restrict_to: [ protocol_owner, protocol_manager ]; @@ -289,7 +289,7 @@ mod ignition { /// The maximum allowed staleness of prices in seconds. If a price is /// found to be older than this then it is deemed to be invalid. - maximum_allowed_price_staleness: i64, + maximum_allowed_price_staleness_in_seconds: i64, /// The maximum percentage of price difference the protocol is willing /// to accept before deeming the price difference to be too much. This @@ -310,7 +310,7 @@ mod ignition { /* Initial Configuration */ protocol_resource: ResourceManager, oracle_adapter: ComponentAddress, - maximum_allowed_price_staleness: i64, + maximum_allowed_price_staleness_in_seconds: i64, maximum_allowed_price_difference_percentage: Decimal, /* Initializers */ initialization_parameters: InitializationParameters, @@ -344,7 +344,7 @@ mod ignition { reward_rates: KeyValueStore::new_with_registered_type(), is_open_position_enabled: false, is_close_position_enabled: false, - maximum_allowed_price_staleness, + maximum_allowed_price_staleness_in_seconds, maximum_allowed_price_difference_percentage, user_resource_volatility: KeyValueStore::new_with_registered_type(), @@ -1546,9 +1546,12 @@ mod ignition { /// /// * `value`: [`i64`] - The maximum allowed staleness period in /// seconds. - pub fn set_maximum_allowed_price_staleness(&mut self, value: i64) { + pub fn set_maximum_allowed_price_staleness_in_seconds( + &mut self, + value: i64, + ) { assert!(value >= 0, "{}", INVALID_MAXIMUM_PRICE_STALENESS); - self.maximum_allowed_price_staleness = value + self.maximum_allowed_price_staleness_in_seconds = value } /// Adds a rewards rate to the protocol. @@ -1801,7 +1804,7 @@ mod ignition { let (price, last_update) = self.oracle_adapter.get_price(base, quote); let final_price_validity = last_update - .add_seconds(self.maximum_allowed_price_staleness) + .add_seconds(self.maximum_allowed_price_staleness_in_seconds) .unwrap_or(Instant::new(i64::MAX)); // Check for staleness diff --git a/testing/stateful-tests/Cargo.toml b/testing/stateful-tests/Cargo.toml new file mode 100644 index 00000000..9c6959dd --- /dev/null +++ b/testing/stateful-tests/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "stateful-tests" +version.workspace = true +edition.workspace = true +description = "A crate that tests Ignition against the current mainnet state" + +[dependencies] +sbor = { workspace = true } +transaction = { workspace = true } +scrypto-test = { workspace = true } +scrypto-unit = { workspace = true } +radix-engine = { workspace = true } +radix-engine-common = { workspace = true } +radix-engine-interface = { workspace = true } + +state-manager = { workspace = true } + +common = { path = "../../libraries/common" } +ignition = { path = "../../packages/ignition", features = ["test"] } +simple-oracle = { path = "../../packages/simple-oracle", features = ["test"] } +ports-interface = { path = "../../libraries/ports-interface" } +ociswap-v1-adapter-v1 = { path = "../../packages/ociswap-v1-adapter-v1", features = [ + "test", + "manifest-builder-stubs" +] } +ociswap-v2-adapter-v1 = { path = "../../packages/ociswap-v2-adapter-v1", features = [ + "test", + "manifest-builder-stubs" +] } +defiplaza-v2-adapter-v1 = { path = "../../packages/defiplaza-v2-adapter-v1", features = [ + "test", + "manifest-builder-stubs" +] } +caviarnine-v1-adapter-v1 = { path = "../../packages/caviarnine-v1-adapter-v1", features = [ + "test", + "manifest-builder-stubs" +] } + +package-loader = { path = "../../libraries/package-loader" } +gateway-client = { path = "../../libraries/gateway-client" } +publishing-tool = { path = "../../tools/publishing-tool" } + +paste = { version = "1.0.14" } +extend = { version = "1.2.0" } + +macro_rules_attribute = { version = "0.2.0" } +lazy_static = "1.4.0" + +[lints] +workspace = true diff --git a/testing/stateful-tests/src/lib.rs b/testing/stateful-tests/src/lib.rs new file mode 100644 index 00000000..7fe45542 --- /dev/null +++ b/testing/stateful-tests/src/lib.rs @@ -0,0 +1,336 @@ +use common::prelude::*; +use extend::*; +use publishing_tool::component_address; +use publishing_tool::configuration_selector::*; +use publishing_tool::database_overlay::*; +use publishing_tool::network_connection_provider::*; +use publishing_tool::publishing::*; +use radix_engine::system::system_modules::*; +use radix_engine::transaction::*; +use radix_engine::vm::*; +use radix_engine_interface::blueprints::account::*; +use scrypto_unit::*; +use state_manager::RocksDBStore; +use std::ops::*; +use transaction::prelude::*; + +lazy_static::lazy_static! { + /// The substate manager database is a lazy-static since it takes a lot of + /// time to be opened for read-only and this had a very negative impact on + /// tests. Keep in mind that this now means that we should keep all of the + /// tests to one module and that we should use `cargo test` and not nextest. + static ref SUBSTATE_MANAGER_DATABASE: RocksDBStore = { + const STATE_MANAGER_DATABASE_PATH_ENVIRONMENT_VARIABLE: &str = + "STATE_MANAGER_DATABASE_PATH"; + let Ok(state_manager_database_path) = + std::env::var(STATE_MANAGER_DATABASE_PATH_ENVIRONMENT_VARIABLE) + .map(std::path::PathBuf::from) + else { + panic!( + "The `{}` environment variable is not set", + STATE_MANAGER_DATABASE_PATH_ENVIRONMENT_VARIABLE + ); + }; + RocksDBStore::new_read_only(state_manager_database_path).expect( + "Failed to create a new instance of the state manager database", + ) + }; +} + +pub type StatefulTestRunner<'a> = TestRunner< + NoExtension, + UnmergeableSubstateDatabaseOverlay<'a, RocksDBStore>, +>; + +pub fn execute_test_within_environment(test_function: F) -> O +where + F: Fn( + AccountAndControllingKey, + PublishingConfiguration, + PublishingReceipt, + ComponentAddress, + &mut StatefulTestRunner<'_>, + ) -> O, +{ + // Creating the database and the necessary overlays to run the tests. + + let overlayed_state_manager_database = + UnmergeableSubstateDatabaseOverlay::new_unmergeable( + SUBSTATE_MANAGER_DATABASE.deref(), + ); + + // Creating a test runner from the overlayed state manager database + let mut test_runner = TestRunnerBuilder::new() + .with_custom_database(overlayed_state_manager_database) + .without_trace() + .build_without_bootstrapping(); + + // Creating a new account which we will be using as the notary and funding + // it with XRD. Since there is no faucet on mainnet, the only way we can + // fund this account is by disabling the auth module and minting XRD. + let notary_private_key = + PrivateKey::Ed25519(Ed25519PrivateKey::from_u64(1).unwrap()); + let notary_account_address = + ComponentAddress::virtual_account_from_public_key( + ¬ary_private_key.public_key(), + ); + test_runner + .execute_manifest_with_enabled_modules( + ManifestBuilder::new() + .mint_fungible(XRD, dec!(100_000_000_000)) + .deposit_batch(notary_account_address) + .build(), + EnabledModules::for_notarized_transaction() + & !EnabledModules::COSTING + & !EnabledModules::AUTH, + ) + .expect_commit_success(); + + // The notary account address now has the fees required to be able to pay + // for the deployment of Ignition. We now get the configuration and run the + // deployment. + let mut simulator_network_connection_provider = + SimulatorNetworkConnector::new_with_test_runner( + test_runner, + NetworkDefinition::mainnet(), + ); + let publishing_configuration = ConfigurationSelector::MainnetProduction + .configuration(¬ary_private_key); + let publishing_receipt = publish( + &publishing_configuration, + &mut simulator_network_connection_provider, + ) + .expect("Publishing of Ignition must succeed!"); + let mut test_runner = + simulator_network_connection_provider.into_test_runner(); + + // Modifying the Ignition component state so that we can use it in tests. + // What we will modify is the Oracle to use where we will be using an actual + // oracle that is live on mainnet that prices are being submitted to. We + // also need to allow for opening and closing of liquidity positions. + // Additionally, we fund Ignition with XRD. + let oracle = component_address!( + "component_rdx1crty68w9d6ud4ecreewvpsvgyq0u9ta8syqrmuzelem593putyu79e" + ); + test_runner + .execute_manifest_with_enabled_modules( + ManifestBuilder::new() + .call_method( + publishing_receipt.components.protocol_entities.ignition, + "set_oracle_adapter", + (oracle,), + ) + .call_method( + publishing_receipt.components.protocol_entities.ignition, + "set_is_open_position_enabled", + (true,), + ) + .call_method( + publishing_receipt.components.protocol_entities.ignition, + "set_is_close_position_enabled", + (true,), + ) + .mint_fungible(XRD, dec!(200_000_000_000_000)) + .take_from_worktop(XRD, dec!(100_000_000_000_000), "volatile") + .take_from_worktop( + XRD, + dec!(100_000_000_000_000), + "non_volatile", + ) + .with_name_lookup(|builder, _| { + let volatile = builder.bucket("volatile"); + let non_volatile = builder.bucket("non_volatile"); + + builder + .call_method( + publishing_receipt + .components + .protocol_entities + .ignition, + "deposit_protocol_resources", + (volatile, Volatility::Volatile), + ) + .call_method( + publishing_receipt + .components + .protocol_entities + .ignition, + "deposit_protocol_resources", + (non_volatile, Volatility::NonVolatile), + ) + }) + .build(), + EnabledModules::for_notarized_transaction() + & !EnabledModules::COSTING + & !EnabledModules::AUTH, + ) + .expect_commit_success(); + + // Creating an account to use for the testing that has some of each of the + // resources + let test_account_private_key = + PrivateKey::Ed25519(Ed25519PrivateKey::from_u64(2).unwrap()); + let test_account_address = + ComponentAddress::virtual_account_from_public_key( + &test_account_private_key.public_key(), + ); + test_runner + .execute_manifest_with_enabled_modules( + TransactionManifestV1 { + instructions: std::iter::once(XRD) + .chain(publishing_receipt.user_resources.iter().copied()) + .map(|resource_address| InstructionV1::CallMethod { + address: resource_address.into(), + method_name: FUNGIBLE_RESOURCE_MANAGER_MINT_IDENT + .to_owned(), + args: to_manifest_value( + &FungibleResourceManagerMintInput { + amount: dec!(100_000_000_000), + }, + ) + .unwrap(), + }) + .chain(std::iter::once(InstructionV1::CallMethod { + address: test_account_address.into(), + method_name: ACCOUNT_DEPOSIT_BATCH_IDENT.into(), + args: to_manifest_value(&( + ManifestExpression::EntireWorktop, + )) + .unwrap(), + })) + .collect(), + blobs: Default::default(), + }, + EnabledModules::for_notarized_transaction() + & !EnabledModules::COSTING + & !EnabledModules::AUTH, + ) + .expect_commit_success(); + let test_account = + AccountAndControllingKey::new_virtual_account(test_account_private_key); + + // We are now ready to execute the function callback and return its output + // back + test_function( + test_account, + publishing_configuration, + publishing_receipt, + oracle, + &mut test_runner, + ) +} + +/// This macro can be applied to any function to turn it into a test function +/// that takes arguments. The arguments given is the mainnet state after the +/// publishing of Ignition to the network. The following is an example: +/// +/// ```norun +/// use macro_rules_attribute::apply; +/// +/// #[apply(mainnet_test)] +/// fn example( +/// _test_account: AccountAndControllingKey, +/// _configuration: PublishingConfiguration, +/// _receipt: PublishingReceipt, +/// _test_runner: &mut StatefulTestRunner<'_>, +/// ) -> Result<(), RuntimeError> { +/// assert!(false); +/// Ok(()) +/// } +/// ``` +/// +/// The above function will be treated as a test and will be discoverable by +/// the testing harness. +#[macro_export] +macro_rules! mainnet_test { + ( + $(#[$meta: meta])* + $fn_vis: vis fn $fn_ident: ident ( + $($tokens: tt)* + ) $(-> $return_type: ty)? $block: block + ) => { + #[test] + $(#[$meta])* + $fn_vis fn $fn_ident () $(-> $return_type)? { + $crate::execute_test_within_environment(| + $($tokens)* + | -> $crate::resolve_return_type!($(-> $return_type)?) { + $block + }) + } + }; +} + +#[macro_export] +macro_rules! resolve_return_type { + () => { + () + }; + (-> $type: ty) => { + $type + }; +} + +#[ext] +pub impl<'a> StatefulTestRunner<'a> { + fn execute_manifest_without_auth( + &mut self, + manifest: TransactionManifestV1, + ) -> TransactionReceiptV1 { + self.execute_manifest_with_enabled_modules( + manifest, + EnabledModules::for_notarized_transaction() & !EnabledModules::AUTH, + ) + } + + fn execute_manifest_with_enabled_modules( + &mut self, + manifest: TransactionManifestV1, + enabled_modules: EnabledModules, + ) -> TransactionReceiptV1 { + let mut execution_config = ExecutionConfig::for_notarized_transaction( + NetworkDefinition::mainnet(), + ); + execution_config.enabled_modules = enabled_modules; + + let nonce = self.next_transaction_nonce(); + let test_transaction = TestTransaction::new_from_nonce(manifest, nonce); + let prepared_transaction = test_transaction.prepare().unwrap(); + let executable = + prepared_transaction.get_executable(Default::default()); + self.execute_transaction( + executable, + Default::default(), + execution_config, + ) + } + + /// Constructs a notarized transaction and executes it. This is primarily + /// used in the testing of fees to make sure that they're approximated in + /// the best way. + fn construct_and_execute_notarized_transaction( + &mut self, + manifest: TransactionManifestV1, + notary_private_key: &PrivateKey, + ) -> TransactionReceiptV1 { + let network_definition = NetworkDefinition::simulator(); + let current_epoch = self.get_current_epoch(); + let transaction = TransactionBuilder::new() + .header(TransactionHeaderV1 { + network_id: network_definition.id, + start_epoch_inclusive: current_epoch, + end_epoch_exclusive: current_epoch.after(10).unwrap(), + nonce: self.next_transaction_nonce(), + notary_public_key: notary_private_key.public_key(), + notary_is_signatory: true, + tip_percentage: 0, + }) + .manifest(manifest) + .notarize(notary_private_key) + .build(); + self.execute_raw_transaction( + &network_definition, + &transaction.to_raw().unwrap(), + ) + } +} diff --git a/testing/stateful-tests/tests/lib.rs b/testing/stateful-tests/tests/lib.rs new file mode 100644 index 00000000..18fb4652 --- /dev/null +++ b/testing/stateful-tests/tests/lib.rs @@ -0,0 +1,438 @@ +#![allow(clippy::arithmetic_side_effects)] + +use common::prelude::*; +use macro_rules_attribute::apply; +use publishing_tool::publishing::*; +use radix_engine::blueprints::consensus_manager::*; +use radix_engine::blueprints::models::*; +use radix_engine::system::system_db_reader::*; +use radix_engine::system::system_modules::EnabledModules; +use radix_engine::types::*; +use radix_engine_interface::blueprints::consensus_manager::*; +use stateful_tests::*; +use transaction::prelude::*; + +#[apply(mainnet_test)] +fn all_ignition_entities_are_linked_to_the_dapp_definition_in_accordance_with_the_metadata_standard( + _: AccountAndControllingKey, + _: PublishingConfiguration, + receipt: PublishingReceipt, + _: ComponentAddress, + test_runner: &mut StatefulTestRunner<'_>, +) { + // Collecting all of the entities into an array + let ignition_entities = receipt + .badges + .iter() + .copied() + .map(GlobalAddress::from) + .chain( + receipt + .packages + .protocol_entities + .iter() + .copied() + .map(GlobalAddress::from), + ) + .chain( + receipt + .packages + .exchange_adapter_entities + .iter() + .copied() + .map(GlobalAddress::from), + ) + .chain( + receipt + .components + .protocol_entities + .iter() + .copied() + .map(GlobalAddress::from), + ) + .chain( + receipt + .components + .exchange_adapter_entities + .iter() + .copied() + .map(GlobalAddress::from), + ) + .chain( + receipt + .exchange_information + .iter() + .filter_map(|information| { + information + .as_ref() + .map(|information| information.liquidity_receipt) + }) + .map(GlobalAddress::from), + ) + .collect::>(); + + // Validating that the dapp definition account has the correct fields and + // metadata values. + let dapp_definition_account = receipt.dapp_definition_account; + let Some(MetadataValue::String(dapp_definition_account_type)) = test_runner + .get_metadata(dapp_definition_account.into(), "account_type") + else { + panic!("Dapp definition account type either does not exist or isn't a string") + }; + let Some(MetadataValue::GlobalAddressArray( + dapp_definition_claimed_entities, + )) = test_runner + .get_metadata(dapp_definition_account.into(), "claimed_entities") + else { + panic!("Dapp definition claimed entities either does not exist or is not an array") + }; + assert_eq!(dapp_definition_account_type, "dapp definition"); + assert_eq!( + dapp_definition_claimed_entities + .into_iter() + .collect::>(), + ignition_entities.iter().copied().collect::>() + ); + + // Validating the dapp definition of components and packages. They have the + // metadata field "dapp_definition" (not plural) and its just an address and + // not an array. + for entity_address in ignition_entities.iter().filter(|address| { + PackageAddress::try_from(**address).is_ok() + || ComponentAddress::try_from(**address).is_ok() + }) { + let Some(MetadataValue::GlobalAddress(linked_dapp_definition_account)) = + test_runner.get_metadata(*entity_address, "dapp_definition") + else { + panic!("Dapp definition key does not exist on package or component") + }; + assert_eq!( + linked_dapp_definition_account, + dapp_definition_account.into() + ) + } + + // Validating the dapp definition of resources. They have the metadata field + // "dapp_definitions" (plural) and its an array of dapp definitions. + for entity_address in ignition_entities + .iter() + .filter(|address| ResourceAddress::try_from(**address).is_ok()) + { + let Some(MetadataValue::GlobalAddressArray( + linked_dapp_definition_account, + )) = test_runner.get_metadata(*entity_address, "dapp_definitions") + else { + panic!( + "Dapp definition key does not exist on resource: {}", + entity_address.to_hex() + ) + }; + assert_eq!( + linked_dapp_definition_account.first().copied().unwrap(), + dapp_definition_account.into() + ) + } +} + +macro_rules! define_open_and_close_liquidity_position_tests { + ( + $( + $exchange_ident: ident => [ + $( + $resource_ident: ident + ),* $(,)? + ] + ),* $(,)? + ) => { + $( + $( + define_open_and_close_liquidity_position_tests!($exchange_ident, $resource_ident, 9); + define_open_and_close_liquidity_position_tests!($exchange_ident, $resource_ident, 10); + define_open_and_close_liquidity_position_tests!($exchange_ident, $resource_ident, 11); + define_open_and_close_liquidity_position_tests!($exchange_ident, $resource_ident, 12); + )* + )* + }; + ( + $exchange_ident: ident, + $resource_ident: ident, + $lockup_period: expr + ) => { + paste::paste! { + #[apply(mainnet_test)] + fn [< can_open_an_ignition_position_in_ $exchange_ident _ $resource_ident _pool_with_ $lockup_period _months_in_lock_up >]( + AccountAndControllingKey { + account_address: test_account, + controlling_key: test_account_private_key, + }: AccountAndControllingKey, + _: PublishingConfiguration, + receipt: PublishingReceipt, + _: ComponentAddress, + test_runner: &mut StatefulTestRunner<'_>, + ) { + // Arrange + let Some(ExchangeInformation { pools, .. }) = + receipt.exchange_information.$exchange_ident + else { + panic!("No {} pools", stringify!($exchange_ident)); + }; + let pool = pools.$resource_ident; + let user_resource = receipt.user_resources.$resource_ident; + + let current_epoch = test_runner.get_current_epoch(); + + // Act + let transaction = TransactionBuilder::new() + .header(TransactionHeaderV1 { + network_id: 1, + start_epoch_inclusive: current_epoch, + end_epoch_exclusive: current_epoch.after(10).unwrap(), + nonce: test_runner.next_transaction_nonce(), + notary_public_key: test_account_private_key.public_key(), + notary_is_signatory: true, + tip_percentage: 0, + }) + .manifest( + ManifestBuilder::new() + .lock_fee(test_account, dec!(10)) + .withdraw_from_account(test_account, user_resource, dec!(1000)) + .take_all_from_worktop(user_resource, "bucket") + .with_bucket("bucket", |builder, bucket| { + builder.call_method( + receipt.components.protocol_entities.ignition, + "open_liquidity_position", + (bucket, pool, LockupPeriod::from_months($lockup_period).unwrap()), + ) + }) + .deposit_batch(test_account) + .build(), + ) + .notarize(&test_account_private_key) + .build(); + let receipt = test_runner.execute_raw_transaction( + &NetworkDefinition::mainnet(), + &transaction.to_raw().unwrap(), + ); + + // Assert + receipt.expect_commit_success(); + println!( + "Opening a position in {} {} pool costs {} XRD in total with {} XRD in execution", + stringify!($exchange_ident), + stringify!($resource_ident), + receipt.fee_summary.total_cost(), + receipt.fee_summary.total_execution_cost_in_xrd + ); + + } + + #[apply(mainnet_test)] + fn [< can_open_and_close_an_ignition_position_in_ $exchange_ident _ $resource_ident _pool_with_ $lockup_period _months_in_lock_up >]( + AccountAndControllingKey { + account_address: test_account, + controlling_key: test_account_private_key, + }: AccountAndControllingKey, + _: PublishingConfiguration, + receipt: PublishingReceipt, + oracle: ComponentAddress, + test_runner: &mut StatefulTestRunner<'_>, + ) { + // Arrange + let Some(ExchangeInformation { pools, liquidity_receipt, .. }) = + receipt.exchange_information.$exchange_ident + else { + panic!("No {} pools", stringify!($exchange_ident)); + }; + let pool = pools.$resource_ident; + let user_resource = receipt.user_resources.$resource_ident; + + let current_epoch = test_runner.get_current_epoch(); + + let transaction = TransactionBuilder::new() + .header(TransactionHeaderV1 { + network_id: 1, + start_epoch_inclusive: current_epoch, + end_epoch_exclusive: current_epoch.after(10).unwrap(), + nonce: test_runner.next_transaction_nonce(), + notary_public_key: test_account_private_key.public_key(), + notary_is_signatory: true, + tip_percentage: 0, + }) + .manifest( + ManifestBuilder::new() + .lock_fee(test_account, dec!(10)) + .withdraw_from_account(test_account, user_resource, dec!(1000)) + .take_all_from_worktop(user_resource, "bucket") + .with_bucket("bucket", |builder, bucket| { + builder.call_method( + receipt.components.protocol_entities.ignition, + "open_liquidity_position", + (bucket, pool, LockupPeriod::from_months($lockup_period).unwrap()), + ) + }) + .deposit_batch(test_account) + .build(), + ) + .notarize(&test_account_private_key) + .build(); + let transaction_receipt = test_runner.execute_raw_transaction( + &NetworkDefinition::mainnet(), + &transaction.to_raw().unwrap(), + ); + + transaction_receipt.expect_commit_success(); + + // Set the current time to be 6 months from now. + { + let current_time = + test_runner.get_current_time(TimePrecisionV2::Minute); + let maturity_instant = current_time + .add_seconds( + *LockupPeriod::from_months($lockup_period).unwrap().seconds() as i64 + ) + .unwrap(); + let db = test_runner.substate_db_mut(); + let mut writer = SystemDatabaseWriter::new(db); + + writer + .write_typed_object_field( + CONSENSUS_MANAGER.as_node_id(), + ModuleId::Main, + ConsensusManagerField::ProposerMilliTimestamp.field_index(), + ConsensusManagerProposerMilliTimestampFieldPayload::from_content_source( + ProposerMilliTimestampSubstate { + epoch_milli: maturity_instant.seconds_since_unix_epoch * 1000, + }, + ), + ) + .unwrap(); + + writer + .write_typed_object_field( + CONSENSUS_MANAGER.as_node_id(), + ModuleId::Main, + ConsensusManagerField::ProposerMinuteTimestamp.field_index(), + ConsensusManagerProposerMinuteTimestampFieldPayload::from_content_source( + ProposerMinuteTimestampSubstate { + epoch_minute: i32::try_from( + maturity_instant.seconds_since_unix_epoch / 60, + ) + .unwrap(), + }, + ), + ) + .unwrap(); + } + + { + let (price, _) = test_runner + .execute_manifest_with_enabled_modules( + ManifestBuilder::new() + .call_method( + oracle, + "get_price", + (user_resource, XRD), + ) + .build(), + EnabledModules::for_notarized_transaction() + & !EnabledModules::AUTH + & !EnabledModules::COSTING, + ) + .expect_commit_success() + .output::<(Decimal, Instant)>(0); + test_runner + .execute_manifest_with_enabled_modules( + ManifestBuilder::new() + .call_method( + oracle, + "set_price", + (user_resource, XRD, price), + ) + .build(), + EnabledModules::for_notarized_transaction() + & !EnabledModules::AUTH + & !EnabledModules::COSTING, + ) + .expect_commit_success(); + } + + let current_epoch = test_runner.get_current_epoch(); + + // Act + let transaction = TransactionBuilder::new() + .header(TransactionHeaderV1 { + network_id: 1, + start_epoch_inclusive: current_epoch, + end_epoch_exclusive: current_epoch.after(10).unwrap(), + nonce: test_runner.next_transaction_nonce(), + notary_public_key: test_account_private_key.public_key().into(), + notary_is_signatory: true, + tip_percentage: 0, + }) + .manifest( + ManifestBuilder::new() + .lock_fee(test_account, dec!(10)) + .withdraw_from_account( + test_account, + liquidity_receipt, + dec!(1), + ) + .take_all_from_worktop( + liquidity_receipt, + "bucket", + ) + .with_bucket("bucket", |builder, bucket| { + builder.call_method( + receipt.components.protocol_entities.ignition, + "close_liquidity_position", + (bucket,), + ) + }) + .deposit_batch(test_account) + .build(), + ) + .notarize(&test_account_private_key) + .build(); + let receipt = test_runner.execute_raw_transaction( + &NetworkDefinition::mainnet(), + &transaction.to_raw().unwrap(), + ); + + // Assert + receipt.expect_commit_success(); + println!( + "Closing a position in {} {} pool costs {} XRD in total with {} XRD in execution", + stringify!($exchange_ident), + stringify!($resource_ident), + receipt.fee_summary.total_cost(), + receipt.fee_summary.total_execution_cost_in_xrd + ); + } + } + }; +} + +define_open_and_close_liquidity_position_tests! { + caviarnine_v1 => [ + bitcoin, + ethereum, + usdc, + usdt + ], + // TODO: Enable once Defiplaza's prices are inline with what the oracle is + // reporting. + // defiplaza_v2 => [ + // bitcoin, + // ethereum, + // usdc, + // usdt + // ], + // TODO: Enable once Ociswap v2 is live on mainnet and once they have their + // pools inline with the oracle prices. + // ociswap_v2 => [ + // bitcoin, + // ethereum, + // usdc, + // usdt + // ] +} diff --git a/testing/tests/Cargo.toml b/testing/tests/Cargo.toml new file mode 100644 index 00000000..ca1b49d0 --- /dev/null +++ b/testing/tests/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "tests" +version.workspace = true +edition.workspace = true +description = "A crate with unit and integration tests for Ignition" +build = "build.rs" + +[dependencies] +sbor = { workspace = true } +transaction = { workspace = true } +scrypto-test = { workspace = true } +scrypto-unit = { workspace = true } +radix-engine = { workspace = true } +radix-engine-common = { workspace = true } +radix-engine-interface = { workspace = true } + +common = { path = "../../libraries/common" } +ignition = { path = "../../packages/ignition", features = ["test"] } +simple-oracle = { path = "../../packages/simple-oracle", features = ["test"] } +ports-interface = { path = "../../libraries/ports-interface" } +ociswap-v1-adapter-v1 = { path = "../../packages/ociswap-v1-adapter-v1", features = [ + "test", + "manifest-builder-stubs" +] } +ociswap-v2-adapter-v1 = { path = "../../packages/ociswap-v2-adapter-v1", features = [ + "test", + "manifest-builder-stubs" +] } +defiplaza-v2-adapter-v1 = { path = "../../packages/defiplaza-v2-adapter-v1", features = [ + "test", + "manifest-builder-stubs" +] } +caviarnine-v1-adapter-v1 = { path = "../../packages/caviarnine-v1-adapter-v1", features = [ + "test", + "manifest-builder-stubs" +] } + +package-loader = { path = "../../libraries/package-loader" } +gateway-client = { path = "../../libraries/gateway-client" } + +paste = { version = "1.0.14" } +extend = { version = "1.2.0" } +lazy_static = "1.4.0" + +[build-dependencies] +flate2 = { version = "1.0.28" } + +[lints] +workspace = true \ No newline at end of file diff --git a/tests/assets/defiplaza_v2.rpd b/testing/tests/assets/defiplaza_v2.rpd similarity index 100% rename from tests/assets/defiplaza_v2.rpd rename to testing/tests/assets/defiplaza_v2.rpd diff --git a/tests/assets/defiplaza_v2.wasm b/testing/tests/assets/defiplaza_v2.wasm similarity index 100% rename from tests/assets/defiplaza_v2.wasm rename to testing/tests/assets/defiplaza_v2.wasm diff --git a/tests/assets/ociswap_v2_pool.rpd b/testing/tests/assets/ociswap_v2_pool.rpd similarity index 100% rename from tests/assets/ociswap_v2_pool.rpd rename to testing/tests/assets/ociswap_v2_pool.rpd diff --git a/tests/assets/ociswap_v2_pool.wasm b/testing/tests/assets/ociswap_v2_pool.wasm similarity index 100% rename from tests/assets/ociswap_v2_pool.wasm rename to testing/tests/assets/ociswap_v2_pool.wasm diff --git a/tests/assets/ociswap_v2_registry.rpd b/testing/tests/assets/ociswap_v2_registry.rpd similarity index 100% rename from tests/assets/ociswap_v2_registry.rpd rename to testing/tests/assets/ociswap_v2_registry.rpd diff --git a/tests/assets/ociswap_v2_registry.wasm b/testing/tests/assets/ociswap_v2_registry.wasm similarity index 100% rename from tests/assets/ociswap_v2_registry.wasm rename to testing/tests/assets/ociswap_v2_registry.wasm diff --git a/tests/assets/state b/testing/tests/assets/state similarity index 100% rename from tests/assets/state rename to testing/tests/assets/state diff --git a/tests/build.rs b/testing/tests/build.rs similarity index 100% rename from tests/build.rs rename to testing/tests/build.rs diff --git a/tests/src/environment.rs b/testing/tests/src/environment.rs similarity index 99% rename from tests/src/environment.rs rename to testing/tests/src/environment.rs index 438cf334..3515c509 100644 --- a/tests/src/environment.rs +++ b/testing/tests/src/environment.rs @@ -489,7 +489,7 @@ impl ScryptoTestEnv { protocol_manager_rule, XRD.into(), simple_oracle.try_into().unwrap(), - configuration.maximum_allowed_price_staleness_seconds, + configuration.maximum_allowed_price_staleness_in_seconds_seconds, configuration.maximum_allowed_relative_price_difference, InitializationParameters::default(), None, @@ -1245,7 +1245,7 @@ impl ScryptoUnitEnv { XRD, simple_oracle, configuration - .maximum_allowed_price_staleness_seconds, + .maximum_allowed_price_staleness_in_seconds_seconds, configuration .maximum_allowed_relative_price_difference, InitializationParametersManifest::default(), @@ -1617,7 +1617,7 @@ impl ResourceInformation { #[derive(Clone, Debug)] pub struct Configuration { pub fees: Decimal, - pub maximum_allowed_price_staleness_seconds: i64, + pub maximum_allowed_price_staleness_in_seconds_seconds: i64, pub maximum_allowed_relative_price_difference: Decimal, } @@ -1627,7 +1627,7 @@ impl Default for Configuration { // 1% fees: dec!(0.01), // 5 Minutes - maximum_allowed_price_staleness_seconds: 300i64, + maximum_allowed_price_staleness_in_seconds_seconds: 300i64, // 1% maximum_allowed_relative_price_difference: dec!(0.01), } diff --git a/tests/src/errors.rs b/testing/tests/src/errors.rs similarity index 100% rename from tests/src/errors.rs rename to testing/tests/src/errors.rs diff --git a/tests/src/extensions.rs b/testing/tests/src/extensions.rs similarity index 100% rename from tests/src/extensions.rs rename to testing/tests/src/extensions.rs diff --git a/tests/src/lib.rs b/testing/tests/src/lib.rs similarity index 100% rename from tests/src/lib.rs rename to testing/tests/src/lib.rs diff --git a/tests/src/prelude.rs b/testing/tests/src/prelude.rs similarity index 100% rename from tests/src/prelude.rs rename to testing/tests/src/prelude.rs diff --git a/tests/tests/caviarnine_v1.rs b/testing/tests/tests/caviarnine_v1.rs similarity index 100% rename from tests/tests/caviarnine_v1.rs rename to testing/tests/tests/caviarnine_v1.rs diff --git a/tests/tests/caviarnine_v1_simulation.rs b/testing/tests/tests/caviarnine_v1_simulation.rs similarity index 100% rename from tests/tests/caviarnine_v1_simulation.rs rename to testing/tests/tests/caviarnine_v1_simulation.rs diff --git a/tests/tests/defiplaza_v2.rs b/testing/tests/tests/defiplaza_v2.rs similarity index 100% rename from tests/tests/defiplaza_v2.rs rename to testing/tests/tests/defiplaza_v2.rs diff --git a/tests/tests/ociswap_v1.rs b/testing/tests/tests/ociswap_v1.rs similarity index 100% rename from tests/tests/ociswap_v1.rs rename to testing/tests/tests/ociswap_v1.rs diff --git a/tests/tests/ociswap_v2.rs b/testing/tests/tests/ociswap_v2.rs similarity index 100% rename from tests/tests/ociswap_v2.rs rename to testing/tests/tests/ociswap_v2.rs diff --git a/tests/tests/protocol.rs b/testing/tests/tests/protocol.rs similarity index 98% rename from tests/tests/protocol.rs rename to testing/tests/tests/protocol.rs index 6ab80614..af0c86eb 100644 --- a/tests/tests/protocol.rs +++ b/testing/tests/tests/protocol.rs @@ -105,7 +105,7 @@ fn can_add_a_positive_upfront_reward_percentage() -> Result<(), RuntimeError> { } #[test] -fn cant_set_the_maximum_allowed_price_staleness_to_a_negative_number( +fn cant_set_the_maximum_allowed_price_staleness_in_seconds_to_a_negative_number( ) -> Result<(), RuntimeError> { // Arrange let Environment { @@ -117,7 +117,7 @@ fn cant_set_the_maximum_allowed_price_staleness_to_a_negative_number( // Act let rtn = protocol .ignition - .set_maximum_allowed_price_staleness(-1, env); + .set_maximum_allowed_price_staleness_in_seconds(-1, env); // Assert assert_is_ignition_invalid_maximum_price_staleness(&rtn); @@ -126,7 +126,7 @@ fn cant_set_the_maximum_allowed_price_staleness_to_a_negative_number( } #[test] -fn can_set_the_maximum_allowed_price_staleness_to_zero( +fn can_set_the_maximum_allowed_price_staleness_in_seconds_to_zero( ) -> Result<(), RuntimeError> { // Arrange let Environment { @@ -138,7 +138,7 @@ fn can_set_the_maximum_allowed_price_staleness_to_zero( // Act let rtn = protocol .ignition - .set_maximum_allowed_price_staleness(0, env); + .set_maximum_allowed_price_staleness_in_seconds(0, env); // Assert assert!(rtn.is_ok()); @@ -147,7 +147,7 @@ fn can_set_the_maximum_allowed_price_staleness_to_zero( } #[test] -fn can_set_the_maximum_allowed_price_staleness_to_a_positive_number( +fn can_set_the_maximum_allowed_price_staleness_in_seconds_to_a_positive_number( ) -> Result<(), RuntimeError> { // Arrange let Environment { @@ -159,7 +159,7 @@ fn can_set_the_maximum_allowed_price_staleness_to_a_positive_number( // Act let rtn = protocol .ignition - .set_maximum_allowed_price_staleness(1, env); + .set_maximum_allowed_price_staleness_in_seconds(1, env); // Assert assert!(rtn.is_ok()); @@ -337,7 +337,7 @@ fn can_open_a_liquidity_position_before_the_price_is_stale( resources, .. } = ScryptoTestEnv::new_with_configuration(Configuration { - maximum_allowed_price_staleness_seconds: 5 * 60, + maximum_allowed_price_staleness_in_seconds_seconds: 5 * 60, ..Default::default() })?; @@ -370,7 +370,7 @@ fn can_open_a_liquidity_position_right_before_price_goes_stale( resources, .. } = ScryptoTestEnv::new_with_configuration(Configuration { - maximum_allowed_price_staleness_seconds: 5 * 60, + maximum_allowed_price_staleness_in_seconds_seconds: 5 * 60, ..Default::default() })?; @@ -406,7 +406,7 @@ fn cant_open_a_liquidity_position_right_after_price_goes_stale( resources, .. } = ScryptoTestEnv::new_with_configuration(Configuration { - maximum_allowed_price_staleness_seconds: 5 * 60, + maximum_allowed_price_staleness_in_seconds_seconds: 5 * 60, ..Default::default() })?; @@ -1718,7 +1718,7 @@ fn protocol_owner_can_perform_forced_liquidation() -> Result<(), RuntimeError> { resources, .. } = ScryptoTestEnv::new_with_configuration(Configuration { - maximum_allowed_price_staleness_seconds: i64::MAX, + maximum_allowed_price_staleness_in_seconds_seconds: i64::MAX, ..Default::default() })?; env.enable_auth_module(); @@ -1769,7 +1769,7 @@ fn protocol_owner_can_perform_forced_liquidation_even_when_liquidation_is_closed resources, .. } = ScryptoTestEnv::new_with_configuration(Configuration { - maximum_allowed_price_staleness_seconds: i64::MAX, + maximum_allowed_price_staleness_in_seconds_seconds: i64::MAX, ..Default::default() })?; env.enable_auth_module(); @@ -1824,7 +1824,7 @@ fn protocol_owner_cant_perform_forced_liquidation_before_maturity_date( resources, .. } = ScryptoTestEnv::new_with_configuration(Configuration { - maximum_allowed_price_staleness_seconds: i64::MAX, + maximum_allowed_price_staleness_in_seconds_seconds: i64::MAX, ..Default::default() })?; env.enable_auth_module(); @@ -1872,7 +1872,7 @@ fn forcefully_liquidated_resources_can_be_claimed_when_closing_liquidity_positio resources, .. } = ScryptoTestEnv::new_with_configuration(Configuration { - maximum_allowed_price_staleness_seconds: i64::MAX, + maximum_allowed_price_staleness_in_seconds_seconds: i64::MAX, ..Default::default() })?; @@ -1923,7 +1923,7 @@ fn forcefully_liquidated_resources_can_be_claimed_when_closing_liquidity_positio resources, .. } = ScryptoTestEnv::new_with_configuration(Configuration { - maximum_allowed_price_staleness_seconds: i64::MAX, + maximum_allowed_price_staleness_in_seconds_seconds: i64::MAX, ..Default::default() })?; diff --git a/tests/Cargo.toml b/tests/Cargo.toml deleted file mode 100644 index 929462f2..00000000 --- a/tests/Cargo.toml +++ /dev/null @@ -1,49 +0,0 @@ -[package] -name = "tests" -version.workspace = true -edition.workspace = true -description.workspace = true -build = "build.rs" - -[dependencies] -sbor = { workspace = true } -transaction = { workspace = true } -scrypto-test = { workspace = true } -scrypto-unit = { workspace = true } -radix-engine = { workspace = true } -radix-engine-common = { workspace = true } -radix-engine-interface = { workspace = true } - -common = { path = "../libraries/common" } -ignition = { path = "../packages/ignition", features = ["test"] } -simple-oracle = { path = "../packages/simple-oracle", features = ["test"] } -ports-interface = { path = "../libraries/ports-interface" } -ociswap-v1-adapter-v1 = { path = "../packages/ociswap-v1-adapter-v1", features = [ - "test", - "manifest-builder-stubs" -] } -ociswap-v2-adapter-v1 = { path = "../packages/ociswap-v2-adapter-v1", features = [ - "test", - "manifest-builder-stubs" -] } -defiplaza-v2-adapter-v1 = { path = "../packages/defiplaza-v2-adapter-v1", features = [ - "test", - "manifest-builder-stubs" -] } -caviarnine-v1-adapter-v1 = { path = "../packages/caviarnine-v1-adapter-v1", features = [ - "test", - "manifest-builder-stubs" -] } - -package-loader = { path = "../libraries/package-loader" } -gateway-client = { path = "../libraries/gateway-client" } - -paste = { version = "1.0.14" } -extend = { version = "1.2.0" } -lazy_static = "1.4.0" - -[build-dependencies] -flate2 = { version = "1.0.28" } - -[lints] -workspace = true \ No newline at end of file diff --git a/tools/publishing-tool/Cargo.toml b/tools/publishing-tool/Cargo.toml index 70229bb8..ca560630 100644 --- a/tools/publishing-tool/Cargo.toml +++ b/tools/publishing-tool/Cargo.toml @@ -13,6 +13,8 @@ radix-engine-common = { workspace = true } radix-engine-interface = { workspace = true } radix-engine-store-interface = { workspace = true } +state-manager = { workspace = true } + common = { path = "../../libraries/common" } ignition = { path = "../../packages/ignition" } package-loader = { path = "../../libraries/package-loader" } @@ -31,7 +33,6 @@ caviarnine-v1-adapter-v1 = { path = "../../packages/caviarnine-v1-adapter-v1", f "manifest-builder-stubs", ] } -state-manager = { git = "https://github.com/radixdlt/babylon-node", rev = "63a8267196995fef0830e4fbf0271bea65c90ab1" } sbor-json = { git = "https://github.com/radixdlt/radix-engine-toolkit", rev = "1cfe879c7370cfa497857ada7a8973f8a3388abc" } hex = { version = "0.4.3" } diff --git a/tools/publishing-tool/src/cli/bin.rs b/tools/publishing-tool/src/cli/bin.rs index 083e5238..3337c79a 100644 --- a/tools/publishing-tool/src/cli/bin.rs +++ b/tools/publishing-tool/src/cli/bin.rs @@ -1,6 +1,5 @@ #![allow(dead_code, clippy::enum_variant_names, clippy::wrong_self_convention)] -mod default_configurations; mod publish; use clap::Parser; diff --git a/tools/publishing-tool/src/cli/publish.rs b/tools/publishing-tool/src/cli/publish.rs index 51faba50..968ba830 100644 --- a/tools/publishing-tool/src/cli/publish.rs +++ b/tools/publishing-tool/src/cli/publish.rs @@ -1,6 +1,6 @@ -use crate::default_configurations::*; use crate::*; use clap::Parser; +use publishing_tool::configuration_selector::*; use publishing_tool::network_connection_provider::*; use publishing_tool::publishing::*; use publishing_tool::utils::*; diff --git a/tools/publishing-tool/src/configuration_selector/mainnet_production.rs b/tools/publishing-tool/src/configuration_selector/mainnet_production.rs new file mode 100644 index 00000000..44ad446c --- /dev/null +++ b/tools/publishing-tool/src/configuration_selector/mainnet_production.rs @@ -0,0 +1,318 @@ +use crate::publishing::*; +use crate::utils::*; +use crate::*; +use common::prelude::*; +use radix_engine_interface::prelude::*; +use transaction::prelude::*; + +pub fn mainnet_production( + notary_private_key: &PrivateKey, +) -> PublishingConfiguration { + // cSpell:disable + PublishingConfiguration { + protocol_configuration: ProtocolConfiguration { + // The protocol resource to use is XRD. + protocol_resource: XRD, + user_resource_volatility: UserResourceIndexedData { + bitcoin: Volatility::Volatile, + ethereum: Volatility::Volatile, + usdc: Volatility::NonVolatile, + usdt: Volatility::NonVolatile, + }, + reward_rates: indexmap! { + LockupPeriod::from_months(9).unwrap() => dec!(0.125), // 12.5% + LockupPeriod::from_months(10).unwrap() => dec!(0.145), // 14.5% + LockupPeriod::from_months(11).unwrap() => dec!(0.17), // 17.0% + LockupPeriod::from_months(12).unwrap() => dec!(0.2), // 20.0% + }, + // When Ignition is first deployed nobody is allowed to open or + // close positions. + allow_opening_liquidity_positions: false, + allow_closing_liquidity_positions: false, + // The maximum allowed price staleness is 60 seconds + maximum_allowed_price_staleness_in_seconds: 60, + // The maximum allowed price difference percentage is 5% from the + // oracle price. + maximum_allowed_price_difference_percentage: dec!(0.05), + entities_metadata: Entities { + protocol_entities: ProtocolIndexedData { + ignition: metadata_init! { + "name" => "Ignition", updatable; + "description" => "The main entrypoint into the Ignition liquidity incentive program.", updatable; + // Dapp definition will be automatically added by the + // publisher accordingly. + }, + simple_oracle: metadata_init! { + "name" => "Ignition Oracle", updatable; + "description" => "The oracle used by the Ignition protocol.", updatable; + "tags" => vec!["oracle"], updatable; + // Dapp definition will be automatically added by the + // publisher accordingly. + }, + }, + exchange_adapter_entities: ExchangeIndexedData { + ociswap_v2: metadata_init! { + "name" => "Ignition Ociswap v2 Adapter", updatable; + "description" => "An adapter used by the Ignition protocol for Ociswap v2 interactions.", updatable; + // Dapp definition will be automatically added by the + // publisher accordingly. + }, + defiplaza_v2: metadata_init! { + "name" => "Ignition DefiPlaza v2 Adapter", updatable; + "description" => "An adapter used by the Ignition protocol for DefiPlaza v2 interactions.", updatable; + // Dapp definition will be automatically added by the + // publisher accordingly. + }, + caviarnine_v1: metadata_init! { + "name" => "Ignition Caviarnine v1 Adapter", updatable; + "description" => "An adapter used by the Ignition protocol for Caviarnine v1 interactions.", updatable; + // Dapp definition will be automatically added by the + // publisher accordingly. + }, + }, + }, + }, + dapp_definition_metadata: indexmap! { + "name".to_owned() => MetadataValue::String("Project Ignition".to_owned()), + "description".to_owned() => MetadataValue::String("A Radix liquidity incentives program, offered in partnership with select decentralized exchange dApps in the Radix ecosystem.".to_owned()), + "icon_url".to_owned() => MetadataValue::Url(UncheckedUrl::of("https://assets.radixdlt.com/icons/icon-Ignition-LP.png")) + }, + transaction_configuration: TransactionConfiguration { + // Whoever notarizes this transaction will also be handling the + // payment of fees for it. + notary: clone_private_key(notary_private_key), + fee_payer_information: AccountAndControllingKey::new_virtual_account( + clone_private_key(notary_private_key), + ), + }, + badges: BadgeIndexedData { + oracle_manager_badge: BadgeHandling::CreateAndSend { + // TODO: Confirm this address with Devops + // This is the account of devops that runs the oracle software + account_address: component_address!( + "account_rdx168nr5dwmll4k2x5apegw5dhrpejf3xac7khjhgjqyg4qddj9tg9v4d" + ), + metadata_init: metadata_init! { + "name" => "Ignition Oracle Manager", updatable; + "symbol" => "IGNOM", updatable; + "description" => "A badge with the authority to update the Oracle prices of the Ignition oracle.", updatable; + "tags" => vec!["badge"], updatable; + // Dapp definition will be automatically added by the + // publisher accordingly. + }, + }, + protocol_manager_badge: BadgeHandling::CreateAndSend { + // TODO: This is currently RTJL20 which might be incorrect + account_address: component_address!( + "account_rdx16ykaehfl0suwzy9tvtlhgds7td8ynwx4jk3q4czaucpf6m4pps9yr4" + ), + metadata_init: metadata_init! { + "name" => "Ignition Protocol Manager", updatable; + "symbol" => "IGNPM", updatable; + "description" => "A badge with the authority to manage the Ignition protocol.", updatable; + "tags" => vec!["badge"], updatable; + // Dapp definition will be automatically added by the + // publisher accordingly. + }, + }, + protocol_owner_badge: BadgeHandling::CreateAndSend { + // Thee RTJL20 account + account_address: component_address!( + "account_rdx16ykaehfl0suwzy9tvtlhgds7td8ynwx4jk3q4czaucpf6m4pps9yr4" + ), + metadata_init: metadata_init! { + "name" => "Ignition Protocol Owner", updatable; + "symbol" => "IGNPO", updatable; + "description" => "A badge with owner authority over the Ignition protocol.", updatable; + "tags" => vec!["badge"], updatable; + // Dapp definition will be automatically added by the + // publisher accordingly. + }, + }, + }, + user_resources: UserResourceIndexedData { + bitcoin: UserResourceHandling::UseExisting { + resource_address: resource_address!( + "resource_rdx1t580qxc7upat7lww4l2c4jckacafjeudxj5wpjrrct0p3e82sq4y75" + ), + }, + ethereum: UserResourceHandling::UseExisting { + resource_address: resource_address!( + "resource_rdx1th88qcj5syl9ghka2g9l7tw497vy5x6zaatyvgfkwcfe8n9jt2npww" + ), + }, + usdc: UserResourceHandling::UseExisting { + resource_address: resource_address!( + "resource_rdx1t4upr78guuapv5ept7d7ptekk9mqhy605zgms33mcszen8l9fac8vf" + ), + }, + usdt: UserResourceHandling::UseExisting { + resource_address: resource_address!( + "resource_rdx1thrvr3xfs2tarm2dl9emvs26vjqxu6mqvfgvqjne940jv0lnrrg7rw" + ), + }, + }, + packages: Entities { + protocol_entities: ProtocolIndexedData { + ignition: PackageHandling::LoadAndPublish { + crate_package_name: "ignition".to_owned(), + metadata: metadata_init! { + "name" => "Ignition Package", updatable; + "description" => "The implementation of the Ignition protocol.", updatable; + "tags" => Vec::::new(), updatable; + // Dapp definition will be automatically added by the + // publisher accordingly. + }, + blueprint_name: "Ignition".to_owned(), + }, + simple_oracle: PackageHandling::LoadAndPublish { + crate_package_name: "simple-oracle".to_owned(), + metadata: metadata_init! { + "name" => "Ignition Simple Oracle Package", updatable; + "description" => "The implementation of the Oracle used by the Ignition protocol.", updatable; + "tags" => vec!["oracle"], updatable; + // Dapp definition will be automatically added by the + // publisher accordingly. + }, + blueprint_name: "SimpleOracle".to_owned(), + }, + }, + exchange_adapter_entities: ExchangeIndexedData { + ociswap_v2: PackageHandling::LoadAndPublish { + crate_package_name: "ociswap-v2-adapter-v1".to_owned(), + metadata: metadata_init! { + "name" => "Ignition Ociswap v2 Adapter Package", updatable; + "description" => "The implementation of an adapter for Ociswap v2 for the Ignition protocol.", updatable; + "tags" => vec!["adapter"], updatable; + // Dapp definition will be automatically added by the + // publisher accordingly. + }, + blueprint_name: "OciswapV2Adapter".to_owned(), + }, + defiplaza_v2: PackageHandling::LoadAndPublish { + crate_package_name: "defiplaza-v2-adapter-v1".to_owned(), + metadata: metadata_init! { + "name" => "Ignition DefiPlaza v2 Adapter Package", updatable; + "description" => "The implementation of an adapter for DefiPlaza v1 for the Ignition protocol.", updatable; + "tags" => vec!["adapter"], updatable; + // Dapp definition will be automatically added by the + // publisher accordingly. + }, + blueprint_name: "DefiPlazaV2Adapter".to_owned(), + }, + caviarnine_v1: PackageHandling::LoadAndPublish { + crate_package_name: "caviarnine-v1-adapter-v1".to_owned(), + metadata: metadata_init! { + "name" => "Ignition Caviarnine v1 Adapter Package", updatable; + "description" => "The implementation of an adapter for Caviarnine v1 for the Ignition protocol.", updatable; + "tags" => vec!["adapter"], updatable; + // Dapp definition will be automatically added by the + // publisher accordingly. + }, + blueprint_name: "CaviarnineV1Adapter".to_owned(), + }, + }, + }, + exchange_information: ExchangeIndexedData { + // No ociswap v2 currently on mainnet. + ociswap_v2: None, + defiplaza_v2: Some(ExchangeInformation { + blueprint_id: BlueprintId { + package_address: package_address!( + "package_rdx1p4dhfl7qwthqqu6p2267m5nedlqnzdvfxdl6q7h8g85dflx8n06p93" + ), + blueprint_name: "PlazaPair".to_owned(), + }, + pools: UserResourceIndexedData { + bitcoin: PoolHandling::UseExisting { + pool_address: component_address!( + "component_rdx1cpv5g5a86qezw0g46w2ph8ydlu2m7jnzxw9p4lx6593qn9fmnwerta" + ), + }, + ethereum: PoolHandling::UseExisting { + pool_address: component_address!( + "component_rdx1crwdzvlv7djtkug9gmvp9ejun0gm0w6cvkpfqycw8fcp4gg82eftjc" + ), + }, + usdc: PoolHandling::UseExisting { + pool_address: component_address!( + "component_rdx1cpw85pmjl8ujjq7kp50lgh3ej5hz3ky9x65q2cjqvg4efnhcmfpz27" + ), + }, + usdt: PoolHandling::UseExisting { + pool_address: component_address!( + "component_rdx1czr2hzfv2xnxdsts4a02dglkn05clv3a2t9uk04709utehau8gjv8h" + ), + }, + }, + liquidity_receipt: LiquidityReceiptHandling::CreateNew { + non_fungible_schema: + NonFungibleDataSchema::new_local_without_self_package_replacement::< + LiquidityReceipt, + >(), + metadata: metadata_init! { + "name" => "Ignition LP: DefiPlaza", updatable; + "description" => "Represents a particular contribution of liquidity to DefiPlaza through the Ignition liquidity incentives program. See the redeem_url metadata for where to redeem these NFTs.", updatable; + "tags" => vec!["lp token"], updatable; + "icon_url" => UncheckedUrl::of("https://assets.radixdlt.com/icons/icon-Ignition-LP.png"), updatable; + "DEX" => "DefiPlaza", updatable; + // I have confirmed this with DefiPlaza to be the + // correct link. + "redeem_url" => UncheckedUrl::of("https://radix.defiplaza.net/ignition"), updatable; + }, + }, + }), + caviarnine_v1: Some(ExchangeInformation { + blueprint_id: BlueprintId { + package_address: package_address!( + "package_rdx1p4r9rkp0cq67wmlve544zgy0l45mswn6h798qdqm47x4762h383wa3" + ), + blueprint_name: "QuantaSwap".to_owned(), + }, + pools: UserResourceIndexedData { + bitcoin: PoolHandling::UseExisting { + pool_address: component_address!( + "component_rdx1cp9w8443uyz2jtlaxnkcq84q5a5ndqpg05wgckzrnd3lgggpa080ed" + ), + }, + ethereum: PoolHandling::UseExisting { + pool_address: component_address!( + "component_rdx1cpsvw207842gafeyvf6tc0gdnq47u3mn74kvzszqlhc03lrns52v82" + ), + }, + usdc: PoolHandling::UseExisting { + pool_address: component_address!( + "component_rdx1cr6lxkr83gzhmyg4uxg49wkug5s4wwc3c7cgmhxuczxraa09a97wcu" + ), + }, + usdt: PoolHandling::UseExisting { + pool_address: component_address!( + "component_rdx1cqs338cyje65rk44zgmjvvy42qcszrhk9ewznedtkqd8l3crtgnmh5" + ), + }, + }, + liquidity_receipt: LiquidityReceiptHandling::CreateNew { + non_fungible_schema: + NonFungibleDataSchema::new_local_without_self_package_replacement::< + LiquidityReceipt, + >(), + metadata: metadata_init! { + "name" => "Ignition LP: Caviarnine", updatable; + "description" => "Represents a particular contribution of liquidity to Caviarnine through the Ignition liquidity incentives program. See the redeem_url metadata for where to redeem these NFTs.", updatable; + "tags" => vec!["lp token"], updatable; + "icon_url" => UncheckedUrl::of("https://assets.radixdlt.com/icons/icon-Ignition-LP.png"), updatable; + "DEX" => "Caviarnine", updatable; + // I have confirmed this with Caviarnine to be the + // correct link. + "redeem_url" => UncheckedUrl::of("https://www.caviarnine.com/ignition"), updatable; + }, + }, + }), + }, + additional_information: AdditionalInformation { + ociswap_v2_registry_component_and_dapp_definition: None, + }, + additional_operation_flags: AdditionalOperationFlags::empty(), + } + // cSpell:enable +} diff --git a/tools/publishing-tool/src/cli/default_configurations/mainnet_testing.rs b/tools/publishing-tool/src/configuration_selector/mainnet_testing.rs similarity index 99% rename from tools/publishing-tool/src/cli/default_configurations/mainnet_testing.rs rename to tools/publishing-tool/src/configuration_selector/mainnet_testing.rs index e03ae788..e23d1b07 100644 --- a/tools/publishing-tool/src/cli/default_configurations/mainnet_testing.rs +++ b/tools/publishing-tool/src/configuration_selector/mainnet_testing.rs @@ -1,7 +1,7 @@ +use crate::publishing::*; +use crate::utils::*; +use crate::*; use common::prelude::*; -use publishing_tool::publishing::*; -use publishing_tool::utils::*; -use publishing_tool::*; use radix_engine_interface::prelude::*; use transaction::prelude::*; @@ -31,7 +31,7 @@ pub fn mainnet_testing( }, allow_opening_liquidity_positions: true, allow_closing_liquidity_positions: true, - maximum_allowed_price_staleness: i64::MAX, + maximum_allowed_price_staleness_in_seconds: i64::MAX, maximum_allowed_price_difference_percentage: Decimal::MAX, entities_metadata: Entities { protocol_entities: ProtocolIndexedData { diff --git a/tools/publishing-tool/src/cli/default_configurations/mod.rs b/tools/publishing-tool/src/configuration_selector/mod.rs similarity index 67% rename from tools/publishing-tool/src/cli/default_configurations/mod.rs rename to tools/publishing-tool/src/configuration_selector/mod.rs index d5f6e946..e6b2fb4d 100644 --- a/tools/publishing-tool/src/cli/default_configurations/mod.rs +++ b/tools/publishing-tool/src/configuration_selector/mod.rs @@ -1,13 +1,15 @@ +mod mainnet_production; mod mainnet_testing; mod stokenet_testing; +use crate::publishing::*; use clap::*; -use publishing_tool::publishing::*; use transaction::prelude::*; #[derive(ValueEnum, Clone, Copy, Debug)] pub enum ConfigurationSelector { MainnetTesting, + MainnetProduction, StokenetTesting, } @@ -17,6 +19,9 @@ impl ConfigurationSelector { notary_private_key: &PrivateKey, ) -> PublishingConfiguration { match self { + Self::MainnetProduction => { + mainnet_production::mainnet_production(notary_private_key) + } Self::MainnetTesting => { mainnet_testing::mainnet_testing(notary_private_key) } @@ -28,14 +33,18 @@ impl ConfigurationSelector { pub fn gateway_base_url(self) -> String { match self { - Self::MainnetTesting => "https://mainnet.radixdlt.com".to_owned(), + Self::MainnetProduction | Self::MainnetTesting => { + "https://mainnet.radixdlt.com".to_owned() + } Self::StokenetTesting => "https://stokenet.radixdlt.com".to_owned(), } } pub fn network_definition(self) -> NetworkDefinition { match self { - Self::MainnetTesting => NetworkDefinition::mainnet(), + Self::MainnetProduction | Self::MainnetTesting => { + NetworkDefinition::mainnet() + } Self::StokenetTesting => NetworkDefinition::stokenet(), } } diff --git a/tools/publishing-tool/src/cli/default_configurations/stokenet_testing.rs b/tools/publishing-tool/src/configuration_selector/stokenet_testing.rs similarity index 98% rename from tools/publishing-tool/src/cli/default_configurations/stokenet_testing.rs rename to tools/publishing-tool/src/configuration_selector/stokenet_testing.rs index eee3d1f6..8bd6ccf8 100644 --- a/tools/publishing-tool/src/cli/default_configurations/stokenet_testing.rs +++ b/tools/publishing-tool/src/configuration_selector/stokenet_testing.rs @@ -1,7 +1,7 @@ +use crate::publishing::*; +use crate::utils::*; +use crate::*; use common::prelude::*; -use publishing_tool::publishing::*; -use publishing_tool::utils::*; -use publishing_tool::*; use radix_engine_interface::prelude::*; use transaction::prelude::*; @@ -29,7 +29,7 @@ pub fn stokenet_testing( }, allow_opening_liquidity_positions: true, allow_closing_liquidity_positions: true, - maximum_allowed_price_staleness: i64::MAX, + maximum_allowed_price_staleness_in_seconds: i64::MAX, maximum_allowed_price_difference_percentage: Decimal::MAX, entities_metadata: Entities { protocol_entities: ProtocolIndexedData { diff --git a/tools/publishing-tool/src/lib.rs b/tools/publishing-tool/src/lib.rs index 57f31540..016a34d9 100644 --- a/tools/publishing-tool/src/lib.rs +++ b/tools/publishing-tool/src/lib.rs @@ -1,3 +1,4 @@ +pub mod configuration_selector; pub mod database_overlay; pub mod error; pub mod macros; diff --git a/tools/publishing-tool/src/network_connection_provider/mainnet_simulator_connector.rs b/tools/publishing-tool/src/network_connection_provider/mainnet_simulator_connector.rs index 4cf6f3d5..f111df66 100644 --- a/tools/publishing-tool/src/network_connection_provider/mainnet_simulator_connector.rs +++ b/tools/publishing-tool/src/network_connection_provider/mainnet_simulator_connector.rs @@ -39,6 +39,28 @@ impl<'s> SimulatorNetworkConnector<'s> { network_definition, } } + + pub fn new_with_test_runner( + ledger_simulator: TestRunner< + NoExtension, + UnmergeableSubstateDatabaseOverlay<'s, RocksDBStore>, + >, + network_definition: NetworkDefinition, + ) -> Self { + Self { + ledger_simulator, + network_definition, + } + } + + pub fn into_test_runner( + self, + ) -> TestRunner< + NoExtension, + UnmergeableSubstateDatabaseOverlay<'s, RocksDBStore>, + > { + self.ledger_simulator + } } impl<'s> NetworkConnectionProvider for SimulatorNetworkConnector<'s> { diff --git a/tools/publishing-tool/src/publishing/configuration.rs b/tools/publishing-tool/src/publishing/configuration.rs index 1defbba4..ce655337 100644 --- a/tools/publishing-tool/src/publishing/configuration.rs +++ b/tools/publishing-tool/src/publishing/configuration.rs @@ -52,12 +52,14 @@ pub struct PublishingConfiguration { #[derive(Debug, Clone, ScryptoSbor)] pub struct PublishingReceipt { + pub dapp_definition_account: ComponentAddress, pub packages: Entities, pub components: Entities, pub exchange_information: ExchangeIndexedData< Option>, >, pub protocol_configuration: ProtocolConfigurationReceipt, + pub user_resources: UserResourceIndexedData, pub badges: BadgeIndexedData, } @@ -91,7 +93,7 @@ pub struct ProtocolConfigurationReceipt { pub reward_rates: IndexMap, pub allow_opening_liquidity_positions: bool, pub allow_closing_liquidity_positions: bool, - pub maximum_allowed_price_staleness: i64, + pub maximum_allowed_price_staleness_in_seconds: i64, pub maximum_allowed_price_difference_percentage: Decimal, pub user_resources: UserResourceIndexedData, pub registered_pools: @@ -109,7 +111,7 @@ pub struct ProtocolConfiguration { pub reward_rates: IndexMap, pub allow_opening_liquidity_positions: bool, pub allow_closing_liquidity_positions: bool, - pub maximum_allowed_price_staleness: i64, + pub maximum_allowed_price_staleness_in_seconds: i64, pub maximum_allowed_price_difference_percentage: Decimal, pub entities_metadata: Entities, } diff --git a/tools/publishing-tool/src/publishing/handler.rs b/tools/publishing-tool/src/publishing/handler.rs index 98c170c6..d3b129f5 100644 --- a/tools/publishing-tool/src/publishing/handler.rs +++ b/tools/publishing-tool/src/publishing/handler.rs @@ -784,7 +784,7 @@ pub fn publish( oracle_component_address, configuration .protocol_configuration - .maximum_allowed_price_staleness, + .maximum_allowed_price_staleness_in_seconds, configuration .protocol_configuration .maximum_allowed_price_difference_percentage, @@ -839,7 +839,7 @@ pub fn publish( ) .call_method( resolved_adapter_component_addresses.defiplaza_v2, - "add_pair_config", + "add_pair_configs", (pair_config_map,), ) .build(); @@ -1118,6 +1118,7 @@ pub fn publish( } Ok(PublishingReceipt { + dapp_definition_account, packages: Entities { protocol_entities: resolved_blueprint_ids .protocol_entities @@ -1145,9 +1146,9 @@ pub fn publish( allow_closing_liquidity_positions: configuration .protocol_configuration .allow_closing_liquidity_positions, - maximum_allowed_price_staleness: configuration + maximum_allowed_price_staleness_in_seconds: configuration .protocol_configuration - .maximum_allowed_price_staleness, + .maximum_allowed_price_staleness_in_seconds, maximum_allowed_price_difference_percentage: configuration .protocol_configuration .maximum_allowed_price_difference_percentage, @@ -1156,6 +1157,7 @@ pub fn publish( information.as_ref().map(|information| information.pools) }), }, + user_resources: resolved_user_resources, badges: resolved_badges.map(|(_, address)| *address), }) }