diff --git a/Makefile.toml b/Makefile.toml index 86799a3f..5cfa1fc5 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -3,7 +3,7 @@ default_to_workspace = false [tasks.test] command = "cargo" -args = ["nextest", "run"] +args = ["nextest", "run", "--features", "package-loader/build-time-blueprints"] [tasks.check] install_crate = "clippy" diff --git a/libraries/package-loader/Cargo.toml b/libraries/package-loader/Cargo.toml index 067e911f..262939d5 100644 --- a/libraries/package-loader/Cargo.toml +++ b/libraries/package-loader/Cargo.toml @@ -15,9 +15,10 @@ getrandom = { version = "0.2.12", features = ["js"] } [build-dependencies] walkdir = { version = "2.3.3", optional = true } +wasm-opt = { version = "0.116.0", optional = true } cargo_toml = { version = "0.18.0", optional = true } -scrypto-unit = { workspace = true, optional = true } +radix-engine = { workspace = true, optional = true } radix-engine-interface = { workspace = true, optional = true } [features] @@ -26,8 +27,9 @@ build-time-blueprints = [ "dep:lazy_static", "dep:walkdir", "dep:cargo_toml", + "dep:radix-engine", "dep:radix-engine-interface", - "dep:scrypto-unit", + "dep:wasm-opt", ] [lib] diff --git a/libraries/package-loader/build.rs b/libraries/package-loader/build.rs index fb3c39ab..2ffabc71 100644 --- a/libraries/package-loader/build.rs +++ b/libraries/package-loader/build.rs @@ -1,68 +1,172 @@ -fn main() { - build_blueprints(); +fn main() -> Result<(), Error> { + build_blueprints()?; + Ok(()) } #[cfg(not(feature = "build-time-blueprints"))] -fn build_blueprints() {} +fn build_blueprints() -> Result<(), Error> { + Ok(()) +} #[cfg(feature = "build-time-blueprints")] -fn build_blueprints() { - use std::env; - use std::path::PathBuf; +fn build_blueprints() -> Result<(), Error> { + use std::env::*; + use std::fs::*; + use std::path::*; + use std::process::*; + use std::*; - use cargo_toml::{Manifest, Package}; + use cargo_toml::Manifest; use radix_engine_interface::prelude::*; - let manifest_dir = PathBuf::from_str(env!("CARGO_MANIFEST_DIR")).unwrap(); - let blueprints_dir = manifest_dir - .parent() - .unwrap() - .parent() - .unwrap() - .join("packages"); - println!("cargo:rerun-if-changed=\"{:?}\"", blueprints_dir); - - let mut scrypto_packages_manifest_paths = vec![]; - for entry in walkdir::WalkDir::new(blueprints_dir) { - let Ok(entry) = entry else { - continue; - }; - let path = entry.path(); - if !path - .file_name() - .map_or(false, |file_name| file_name == "Cargo.toml") - { - continue; + // All of the blueprints are in the `packages` subdirectory of the project. + // So, we get the path to it so that we can start finding the blueprints + // here. + let root_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .ok_or(Error::FailedToFindAncestor { + path: PathBuf::from(env!("CARGO_MANIFEST_DIR")), + ancestor: 2, + })? + .to_owned(); + + let packages_path = root_path.join("packages"); + let target_path = root_path.join("target"); + let builds_target_path = target_path.join("package-loader-target"); + if !builds_target_path.exists() { + create_dir(&builds_target_path)?; + } + + // Getting the name of all of the blueprints found in the packages directory + let package_names = read_dir(&packages_path)? + .filter_map(Result::ok) + .filter_map(|entry| { + if entry.file_type().is_ok_and(|ty| ty.is_dir()) { + Some(entry.path()) + } else { + None + } + }) + .map(|path| { + Manifest::from_path(path.join("Cargo.toml")) + .map(|manifest| manifest.package.map(|package| package.name)) + }) + .filter_map(Result::ok) + .flatten() + .collect::>(); + + // Building each of the packages that have been discovered. + let mut packages = HashMap::new(); + for package_name in package_names { + // Build the package + let status = Command::new("cargo") + .args([ + "build", + "--target", + "wasm32-unknown-unknown", + "--release", + "--target-dir", + builds_target_path.as_path().display().to_string().as_str(), + "--package", + package_name.as_str(), + ]) + .status()?; + if !status.success() { + return Err(Error::CompilationOfPackageFailed(package_name)); } - let manifest = Manifest::from_path(path).unwrap(); - if !manifest - .dependencies - .into_iter() - .any(|(name, _)| name == "scrypto") - { - continue; + // Construct the path to the WASM file. + let wasm_path = builds_target_path + .join("wasm32-unknown-unknown") + .join("release") + .join(format!("{}.wasm", package_name.replace('-', "_"))); + + // Extract the package definition + let package_definition = + radix_engine::utils::extract_definition(&read(&wasm_path)?)?; + + // Build a new WASM build without any of the schema information + let status = Command::new("cargo") + .args([ + "build", + "--target", + "wasm32-unknown-unknown", + "--release", + "--target-dir", + builds_target_path.as_path().display().to_string().as_str(), + "--package", + package_name.as_str(), + "--features", + "scrypto/no-schema", + ]) + .status()?; + if !status.success() { + return Err(Error::CompilationOfPackageFailed(package_name)); } - let Some(Package { name, .. }) = manifest.package else { - continue; - }; + // Optimize the WASM using wasm-opt for size + wasm_opt::OptimizationOptions::new_optimize_for_size_aggressively() + .add_pass(wasm_opt::Pass::StripDebug) + .add_pass(wasm_opt::Pass::StripDwarf) + .add_pass(wasm_opt::Pass::StripProducers) + .run(&wasm_path, &wasm_path)?; - scrypto_packages_manifest_paths - .push((name, path.parent().unwrap().to_owned())); - } + // Read the final wasm. + let wasm = read(wasm_path)?; - let mut packages = HashMap::new(); - for (name, manifest_file_path) in scrypto_packages_manifest_paths { - let (code, definition) = - scrypto_unit::Compile::compile(manifest_file_path); - packages.insert(name, (code, definition)); + packages.insert(package_name, (wasm, package_definition)); } let out_dir = - PathBuf::from_str(env::var("OUT_DIR").unwrap().as_str()).unwrap(); + PathBuf::from(var("OUT_DIR").expect("out dir must be defined!")); let compilation_path = out_dir.join("compiled_packages.bin"); let encoded_packages = scrypto_encode(&packages).unwrap(); - std::fs::write(compilation_path, encoded_packages).unwrap(); + write(compilation_path, encoded_packages).unwrap(); + + Ok(()) +} + +#[derive(Debug)] +pub enum Error { + FailedToFindAncestor { + path: std::path::PathBuf, + ancestor: usize, + }, + IoError(std::io::Error), + #[cfg(feature = "build-time-blueprints")] + ManifestError(cargo_toml::Error), + CompilationOfPackageFailed(String), + #[cfg(feature = "build-time-blueprints")] + ExtractSchemaError(radix_engine::utils::ExtractSchemaError), + #[cfg(feature = "build-time-blueprints")] + OptimizationError(wasm_opt::OptimizationError), +} + +impl From for Error { + fn from(value: std::io::Error) -> Self { + Self::IoError(value) + } +} + +#[cfg(feature = "build-time-blueprints")] +impl From for Error { + fn from(value: cargo_toml::Error) -> Self { + Self::ManifestError(value) + } +} + +#[cfg(feature = "build-time-blueprints")] +impl From for Error { + fn from(value: radix_engine::utils::ExtractSchemaError) -> Self { + Self::ExtractSchemaError(value) + } +} + +#[cfg(feature = "build-time-blueprints")] +impl From for Error { + fn from(value: wasm_opt::OptimizationError) -> Self { + Self::OptimizationError(value) + } } diff --git a/packages/ignition/src/blueprint.rs b/packages/ignition/src/blueprint.rs index 8de3f58b..11b64158 100644 --- a/packages/ignition/src/blueprint.rs +++ b/packages/ignition/src/blueprint.rs @@ -339,6 +339,12 @@ mod ignition { || resource2 == user_resource_address, "{}", USER_ASSET_DOES_NOT_BELONG_TO_POOL_ERROR + ); + + assert!( + user_resource_address != self.protocol_resource.address(), + "{}", + USER_MUST_NOT_PROVIDE_PROTOCOL_ASSET_ERROR ) } diff --git a/packages/ignition/src/errors.rs b/packages/ignition/src/errors.rs index 13512b3e..b0e203c4 100644 --- a/packages/ignition/src/errors.rs +++ b/packages/ignition/src/errors.rs @@ -45,4 +45,6 @@ define_error! { => "Not a valid liquidity receipt resource."; LIQUIDITY_POSITION_HAS_NOT_MATURED_ERROR => "Can't close a liquidity position before it has matured."; + USER_MUST_NOT_PROVIDE_PROTOCOL_ASSET_ERROR + => "The user has provided the protocol asset, which is not allowed"; } diff --git a/packages/ociswap-adapter-v1/src/lib.rs b/packages/ociswap-adapter-v1/src/lib.rs index 16a2125f..5688c478 100644 --- a/packages/ociswap-adapter-v1/src/lib.rs +++ b/packages/ociswap-adapter-v1/src/lib.rs @@ -12,7 +12,7 @@ macro_rules! define_error { )* ) => { $( - const $name: &'static str = concat!("[Ociswap Adapter]", " ", $item); + pub const $name: &'static str = concat!("[Ociswap Adapter]", " ", $item); )* }; } @@ -22,6 +22,8 @@ define_error! { => "Failed to get resource addresses - unexpected error."; FAILED_TO_GET_VAULT_ERROR => "Failed to get vault - unexpected error."; + PRICE_IS_UNDEFINED + => "Price is undefined."; } #[blueprint_with_traits] @@ -118,7 +120,7 @@ pub mod adapter { Price { base: resource_address1, quote: resource_address2, - price: amount2 / amount1, + price: amount2.checked_div(amount1).expect(PRICE_IS_UNDEFINED), } } diff --git a/tests/src/environment.rs b/tests/src/environment.rs index e81fb9fb..fbfbb0d6 100644 --- a/tests/src/environment.rs +++ b/tests/src/environment.rs @@ -32,16 +32,12 @@ impl Environment { include_bytes!(concat!(env!("OUT_DIR"), "/uncompressed_state.bin")); pub fn new() -> Result { - Self::new_with_configuration(Configuration { - fees: Some(dec!(0.01)), - }) + Self::new_with_configuration(Configuration::default()) } pub fn new_with_configuration( configuration: Configuration, ) -> Result { - let fees = configuration.fees(); - // Flash the substates to the ledger so that they can be used in tests. let (addresses, db_flash) = scrypto_decode::<(Vec, DbFlash)>(Self::PACKAGES_BINARY) @@ -133,14 +129,24 @@ impl Environment { // Creating the Ociswap pools of the resources. let ociswap_pools = resource_addresses.try_map(|resource_address| { - OciswapPoolInterfaceScryptoTestStub::instantiate( - *resource_address, - XRD, - fees, - FAUCET, - ociswap_package, - &mut env, - ) + let mut ociswap_pool = + OciswapPoolInterfaceScryptoTestStub::instantiate( + *resource_address, + XRD, + configuration.fees, + FAUCET, + ociswap_package, + &mut env, + )?; + + let resource_x = ResourceManager(*resource_address) + .mint_fungible(dec!(100_000_000), &mut env)?; + let resource_y = ResourceManager(XRD) + .mint_fungible(dec!(100_000_000), &mut env)?; + let _ = + ociswap_pool.add_liquidity(resource_x, resource_y, &mut env)?; + + Ok::<_, RuntimeError>(ociswap_pool) })?; // Creating the Caviarnine pools of the resources. @@ -172,8 +178,8 @@ impl Environment { protocol_manager_rule, XRD.into(), simple_oracle.try_into().unwrap(), - 300i64, - dec!(0.01), + configuration.maximum_allowed_price_staleness_seconds, + configuration.maximum_allowed_relative_price_difference, None, ignition_package, &mut env, @@ -193,7 +199,7 @@ impl Environment { // Submitting some dummy prices to the oracle to get things going. resource_addresses.try_map(|resource_address| { - simple_oracle.set_price(*resource_address, XRD, dec!(100), &mut env) + simple_oracle.set_price(*resource_address, XRD, dec!(1), &mut env) })?; // Initializing ignition with information @@ -201,6 +207,17 @@ impl Environment { ignition.set_is_open_position_enabled(true, &mut env)?; ignition.set_is_close_position_enabled(true, &mut env)?; + ignition.add_reward_rate( + LockupPeriod::from_months(6), + dec!(0.2), + &mut env, + )?; + ignition.add_reward_rate( + LockupPeriod::from_months(12), + dec!(0.4), + &mut env, + )?; + let xrd_bucket = ResourceManager(XRD) .mint_fungible(dec!(100_000_000_000_000), &mut env)?; ignition.deposit_resources(FungibleBucket(xrd_bucket), &mut env)?; @@ -348,11 +365,20 @@ impl ResourceInformation { #[derive(Clone, Debug)] pub struct Configuration { - pub fees: Option, + pub fees: Decimal, + pub maximum_allowed_price_staleness_seconds: i64, + pub maximum_allowed_relative_price_difference: Decimal, } -impl Configuration { - pub fn fees(&self) -> Decimal { - self.fees.unwrap_or(Decimal::ZERO) +impl Default for Configuration { + fn default() -> Self { + Self { + // 1% + fees: dec!(0.01), + // 5 Minutes + maximum_allowed_price_staleness_seconds: 300i64, + // 1% + maximum_allowed_relative_price_difference: dec!(0.01), + } } } diff --git a/tests/src/errors.rs b/tests/src/errors.rs index 79745695..d92e1131 100644 --- a/tests/src/errors.rs +++ b/tests/src/errors.rs @@ -8,7 +8,7 @@ macro_rules! define_error_checking_functions { $error: ident ),* $(,)? ] - )* $(,)? + ),* $(,)? ) => { paste::paste! { $( @@ -64,5 +64,11 @@ define_error_checking_functions! { MORE_THAN_ONE_LIQUIDITY_RECEIPT_NFTS_ERROR, NOT_A_VALID_LIQUIDITY_RECEIPT_ERROR, LIQUIDITY_POSITION_HAS_NOT_MATURED_ERROR, + USER_MUST_NOT_PROVIDE_PROTOCOL_ASSET_ERROR + ], + ociswap_adapter => [ + FAILED_TO_GET_RESOURCE_ADDRESSES_ERROR, + FAILED_TO_GET_VAULT_ERROR, + PRICE_IS_UNDEFINED ] } diff --git a/tests/tests/ociswap.rs b/tests/tests/ociswap.rs new file mode 100644 index 00000000..efc3528e --- /dev/null +++ b/tests/tests/ociswap.rs @@ -0,0 +1,31 @@ +use tests::prelude::*; + +#[test] +pub fn can_open_a_simple_position_against_an_ociswap_pool( +) -> Result<(), RuntimeError> { + // Arrange + let Environment { + environment: ref mut env, + mut protocol, + ociswap, + resources, + .. + } = Environment::new()?; + + // Act + let bitcoin_bucket = + ResourceManager(resources.bitcoin).mint_fungible(dec!(100), env)?; + + // Act + let rtn = protocol.ignition.open_liquidity_position( + FungibleBucket(bitcoin_bucket), + ociswap.pools.bitcoin.try_into().unwrap(), + LockupPeriod::from_months(6), + env, + ); + + // Assert + let _ = rtn.expect("Should succeed!"); + + Ok(()) +} diff --git a/tests/tests/protocol.rs b/tests/tests/protocol.rs new file mode 100644 index 00000000..c625a414 --- /dev/null +++ b/tests/tests/protocol.rs @@ -0,0 +1,394 @@ +use tests::prelude::*; + +#[test] +fn simple_testing_environment_can_be_created() { + Environment::new().expect("Must succeed!"); +} + +#[test] +fn cant_open_a_liquidity_position_when_opening_is_disabled( +) -> Result<(), RuntimeError> { + // Arrange + let Environment { + environment: ref mut env, + resources, + mut protocol, + ociswap, + .. + } = Environment::new()?; + + protocol.ignition.set_is_open_position_enabled(false, env)?; + let bitcoin_bucket = + ResourceManager(resources.bitcoin).mint_fungible(dec!(100), env)?; + + // Act + let rtn = protocol.ignition.open_liquidity_position( + FungibleBucket(bitcoin_bucket), + ociswap.pools.bitcoin.try_into().unwrap(), + LockupPeriod::from_months(6), + env, + ); + + // Assert + assert_is_ignition_opening_liquidity_positions_is_closed_error(&rtn); + + Ok(()) +} + +#[test] +fn cant_open_a_liquidity_position_on_a_pool_that_has_no_adapter( +) -> Result<(), RuntimeError> { + // Arrange + let Environment { + environment: ref mut env, + resources, + mut protocol, + .. + } = Environment::new()?; + + let bitcoin_bucket = + ResourceManager(resources.bitcoin).mint_fungible(dec!(100), env)?; + + // Act + let rtn = protocol.ignition.open_liquidity_position( + FungibleBucket(bitcoin_bucket), + FAUCET, + LockupPeriod::from_months(6), + env, + ); + + // Assert + assert_is_ignition_no_adapter_found_for_pool_error(&rtn); + + Ok(()) +} + +#[test] +fn cant_open_liquidity_position_against_a_pool_outside_of_the_allow_list( +) -> Result<(), RuntimeError> { + // Arrange + let Environment { + environment: ref mut env, + resources, + mut protocol, + ociswap, + .. + } = Environment::new()?; + + let new_pool = OciswapPoolInterfaceScryptoTestStub::instantiate( + resources.bitcoin, + XRD, + dec!(0), + FAUCET, + ociswap.package, + env, + )?; + let bitcoin_bucket = + ResourceManager(resources.bitcoin).mint_fungible(dec!(100), env)?; + + // Act + let rtn = protocol.ignition.open_liquidity_position( + FungibleBucket(bitcoin_bucket), + new_pool.try_into().unwrap(), + LockupPeriod::from_months(6), + env, + ); + + // Assert + assert_is_ignition_pool_is_not_in_allow_list_error(&rtn); + + Ok(()) +} + +#[test] +fn cant_open_a_liquidity_position_in_a_pool_after_it_has_been_removed_from_allowed_list( +) -> Result<(), RuntimeError> { + // Arrange + let Environment { + environment: ref mut env, + resources, + mut protocol, + ociswap, + .. + } = Environment::new()?; + + protocol + .ignition + .remove_allowed_pool(ociswap.pools.bitcoin.try_into().unwrap(), env)?; + let bitcoin_bucket = + ResourceManager(resources.bitcoin).mint_fungible(dec!(100), env)?; + + // Act + let rtn = protocol.ignition.open_liquidity_position( + FungibleBucket(bitcoin_bucket), + ociswap.pools.bitcoin.try_into().unwrap(), + LockupPeriod::from_months(6), + env, + ); + + // Assert + assert_is_ignition_pool_is_not_in_allow_list_error(&rtn); + + Ok(()) +} + +#[test] +fn cant_open_a_liquidity_position_with_some_random_resource( +) -> Result<(), RuntimeError> { + // Arrange + let Environment { + environment: ref mut env, + mut protocol, + ociswap, + .. + } = Environment::new()?; + + let random_resource = ResourceBuilder::new_fungible(OwnerRole::None) + .mint_initial_supply(100, env)?; + + // Act + let rtn = protocol.ignition.open_liquidity_position( + FungibleBucket(random_resource), + ociswap.pools.bitcoin.try_into().unwrap(), + LockupPeriod::from_months(6), + env, + ); + + // Assert + assert_is_ignition_user_asset_does_not_belong_to_pool_error(&rtn); + + Ok(()) +} + +#[test] +fn cant_open_a_liquidity_position_by_providing_the_protocol_resource( +) -> Result<(), RuntimeError> { + // Arrange + let Environment { + environment: ref mut env, + mut protocol, + ociswap, + .. + } = Environment::new()?; + + let protocol_resource = + ResourceManager(XRD).mint_fungible(dec!(100), env)?; + + // Act + let rtn = protocol.ignition.open_liquidity_position( + FungibleBucket(protocol_resource), + ociswap.pools.bitcoin.try_into().unwrap(), + LockupPeriod::from_months(6), + env, + ); + + // Assert + assert_is_ignition_user_must_not_provide_protocol_asset_error(&rtn); + + Ok(()) +} + +#[test] +pub fn can_open_a_liquidity_position_before_the_price_is_stale( +) -> Result<(), RuntimeError> { + // Arrange + let Environment { + environment: ref mut env, + mut protocol, + ociswap, + resources, + .. + } = Environment::new_with_configuration(Configuration { + maximum_allowed_price_staleness_seconds: 5 * 60, + ..Default::default() + })?; + + // Act + let bitcoin_bucket = + ResourceManager(resources.bitcoin).mint_fungible(dec!(100), env)?; + + // Act + let rtn = protocol.ignition.open_liquidity_position( + FungibleBucket(bitcoin_bucket), + ociswap.pools.bitcoin.try_into().unwrap(), + LockupPeriod::from_months(6), + env, + ); + + // Assert + let _ = rtn.expect("Should succeed!"); + + Ok(()) +} + +#[test] +pub fn can_open_a_liquidity_position_right_before_price_goes_stale( +) -> Result<(), RuntimeError> { + // Arrange + let Environment { + environment: ref mut env, + mut protocol, + ociswap, + resources, + .. + } = Environment::new_with_configuration(Configuration { + maximum_allowed_price_staleness_seconds: 5 * 60, + ..Default::default() + })?; + + // Act + let bitcoin_bucket = + ResourceManager(resources.bitcoin).mint_fungible(dec!(100), env)?; + + let current_time = env.get_current_time(); + env.set_current_time(current_time.add_minutes(5).unwrap()); + + // Act + let rtn = protocol.ignition.open_liquidity_position( + FungibleBucket(bitcoin_bucket), + ociswap.pools.bitcoin.try_into().unwrap(), + LockupPeriod::from_months(6), + env, + ); + + // Assert + let _ = rtn.expect("Should succeed!"); + + Ok(()) +} + +#[test] +pub fn cant_open_a_liquidity_position_right_after_price_goes_stale( +) -> Result<(), RuntimeError> { + // Arrange + let Environment { + environment: ref mut env, + mut protocol, + ociswap, + resources, + .. + } = Environment::new_with_configuration(Configuration { + maximum_allowed_price_staleness_seconds: 5 * 60, + ..Default::default() + })?; + + // Act + let bitcoin_bucket = + ResourceManager(resources.bitcoin).mint_fungible(dec!(100), env)?; + + let current_time = env.get_current_time(); + env.set_current_time(current_time.add_minutes(6).unwrap()); + + // Act + let rtn = protocol.ignition.open_liquidity_position( + FungibleBucket(bitcoin_bucket), + ociswap.pools.bitcoin.try_into().unwrap(), + LockupPeriod::from_months(6), + env, + ); + + // Assert + assert_is_ignition_oracle_reported_price_is_stale_error(&rtn); + + Ok(()) +} + +#[test] +pub fn can_open_liquidity_position_when_oracle_price_is_lower_than_pool_but_within_allowed_relative_difference( +) -> Result<(), RuntimeError> { + // Arrange + let Environment { + environment: ref mut env, + mut protocol, + ociswap, + resources, + .. + } = Environment::new_with_configuration(Configuration { + maximum_allowed_relative_price_difference: dec!(0.01), + ..Default::default() + })?; + + // Act + let bitcoin_bucket = + ResourceManager(resources.bitcoin).mint_fungible(dec!(100), env)?; + + let current_time = env.get_current_time(); + env.set_current_time(current_time.add_minutes(6).unwrap()); + + // Act + let rtn = protocol.ignition.open_liquidity_position( + FungibleBucket(bitcoin_bucket), + ociswap.pools.bitcoin.try_into().unwrap(), + LockupPeriod::from_months(6), + env, + ); + + // Assert + assert_is_ignition_oracle_reported_price_is_stale_error(&rtn); + + Ok(()) +} + +#[test] +#[allow(unused_must_use)] +fn oracle_price_cutoffs_for_opening_liquidity_positions_are_implemented_correctly( +) { + test_oracle_price(dec!(1) / dec!(1.01), dec!(0.01)) + .expect("Should succeed!"); + test_oracle_price(dec!(1) / dec!(0.99), dec!(0.01)) + .expect("Should succeed!"); + test_oracle_price( + dec!(1) / dec!(0.99) - dec!(0.000000000000000010), + dec!(0.01), + ) + .expect("Should succeed!"); + test_oracle_price( + dec!(1) / dec!(1.01) + dec!(0.000000000000000010), + dec!(0.01), + ) + .expect("Should succeed!"); + + assert_is_ignition_relative_price_difference_larger_than_allowed_error( + &test_oracle_price( + dbg!(dec!(1) / dec!(0.99) + dec!(0.000000000000000010)), + dec!(0.01), + ), + ); + assert_is_ignition_relative_price_difference_larger_than_allowed_error( + &test_oracle_price( + dec!(1) / dec!(1.01) - dec!(0.000000000000000010), + dec!(0.01), + ), + ); +} + +fn test_oracle_price( + oracle_price: Decimal, + allowed_price_difference: Decimal, +) -> Result<(NonFungibleBucket, FungibleBucket, Vec), RuntimeError> { + let Environment { + environment: ref mut env, + mut protocol, + ociswap, + resources, + .. + } = Environment::new_with_configuration(Configuration { + maximum_allowed_relative_price_difference: allowed_price_difference, + ..Default::default() + })?; + + let bitcoin_bucket = + ResourceManager(resources.bitcoin).mint_fungible(dec!(100), env)?; + + protocol + .oracle + .set_price(resources.bitcoin, XRD, oracle_price, env)?; + + // Act + protocol.ignition.open_liquidity_position( + FungibleBucket(bitcoin_bucket), + ociswap.pools.bitcoin.try_into().unwrap(), + LockupPeriod::from_months(6), + env, + ) +}