Skip to content

Commit

Permalink
[Ignition]: Update Ignition to support matching factors
Browse files Browse the repository at this point in the history
  • Loading branch information
0xOmarA committed Jun 17, 2024
1 parent be67e18 commit e4e720c
Show file tree
Hide file tree
Showing 16 changed files with 280 additions and 9 deletions.
85 changes: 76 additions & 9 deletions packages/ignition/src/blueprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ type OracleAdapter = OracleAdapterInterfaceScryptoStub;
#[types(
Decimal,
ResourceAddress,
ComponentAddress,
NonFungibleGlobalId,
BlueprintId,
Vault,
Expand Down Expand Up @@ -167,6 +168,7 @@ mod ignition {
protocol_owner,
protocol_manager
];
upsert_matching_factor => restrict_to: [protocol_owner];
deposit_protocol_resources => restrict_to: [protocol_owner];
withdraw_protocol_resources => restrict_to: [protocol_owner];
deposit_user_resources => restrict_to: [protocol_owner];
Expand Down Expand Up @@ -289,6 +291,14 @@ mod ignition {
forced_liquidation_claims:
KeyValueStore<NonFungibleGlobalId, Vec<Vault>>,

/// A map that stores the _matching factor_ for each pool which is a
/// [`Decimal`] between 0 and 1 that controls how much of the user's
/// contribution is matched by Ignition. For a given pool, if the user
/// provides X worth of a user resource and if the pool has a Y%
/// matching factor then the amount of protocol resources provided is
/// X • Y%.
matching_factor: KeyValueStore<ComponentAddress, Decimal>,

/* Configuration */
/// The upfront reward rates supported by the protocol. This is a map
/// of the lockup period to the reward rate ratio. In this
Expand Down Expand Up @@ -349,6 +359,7 @@ mod ignition {
initial_non_volatile_protocol_resources,
initial_is_open_position_enabled,
initial_is_close_position_enabled,
initial_matching_factors,
} = initialization_parameters;

let mut ignition = Self {
Expand All @@ -370,6 +381,7 @@ mod ignition {
),
forced_liquidation_claims:
KeyValueStore::new_with_registered_type(),
matching_factor: KeyValueStore::new_with_registered_type(),
};

if let Some(resource_volatility) =
Expand Down Expand Up @@ -418,6 +430,15 @@ mod ignition {
)
}

if let Some(matching_factors) = initial_matching_factors {
for (address, matching_factor) in
matching_factors.into_iter()
{
ignition
.upsert_matching_factor(address, matching_factor)
}
}

ignition.is_open_position_enabled =
initial_is_open_position_enabled.unwrap_or(false);
ignition.is_close_position_enabled =
Expand Down Expand Up @@ -565,11 +586,15 @@ mod ignition {
(oracle_reported_price, pool_reported_price)
};

let pool_reported_value_of_user_resource_in_protocol_resource =
pool_reported_price
.exchange(user_resource_address, user_resource_amount)
.expect(UNEXPECTED_ERROR)
.1;
let matching_factor = *self
.matching_factor
.get(&pool_address)
.expect(NO_MATCHING_FACTOR_FOUND_FOR_POOL);

let matching_amount_of_protocol_resource = pool_reported_price
.exchange(user_resource_address, user_resource_amount)
.and_then(|(_, value)| value.checked_mul(matching_factor))
.expect(UNEXPECTED_ERROR);

// An assertion added for safety - the pool reported value of the
// resources must be less than (1 + padding_percentage) * oracle
Expand All @@ -589,6 +614,7 @@ mod ignition {
.1
.checked_mul(padding)
})
.and_then(|value| value.checked_mul(matching_factor))
.and_then(|value| {
// 17 decimal places so that 9.99 (with 18 nines) rounds
// to 10. Essentially fixing for any small loss of
Expand All @@ -598,17 +624,17 @@ mod ignition {
})
.unwrap_or(Decimal::MAX);
assert!(
pool_reported_value_of_user_resource_in_protocol_resource <= maximum_amount,
matching_amount_of_protocol_resource <= maximum_amount,
"Amount provided by Ignition exceeds the maximum allowed at the current price. Provided: {}, Maximum allowed: {}",
pool_reported_value_of_user_resource_in_protocol_resource,
matching_amount_of_protocol_resource,
maximum_amount
);
}

// Contribute the resources to the pool.
let user_side_of_liquidity = bucket;
let protocol_side_of_liquidity = self.withdraw_protocol_resources(
pool_reported_value_of_user_resource_in_protocol_resource,
matching_amount_of_protocol_resource,
WithdrawStrategy::Rounded(RoundingMode::ToZero),
volatility,
);
Expand Down Expand Up @@ -637,7 +663,7 @@ mod ignition {
// underflow.
.expect(OVERFLOW_ERROR);
let amount_of_protocol_tokens_contributed =
pool_reported_value_of_user_resource_in_protocol_resource
matching_amount_of_protocol_resource
.checked_sub(
change
.get(&self.protocol_resource.address())
Expand Down Expand Up @@ -1065,6 +1091,41 @@ mod ignition {
bucket_returns
}

/// Updates the matching factor of a pool.
///
/// This method updates the matching factor for a given pool after doing
/// a bounds check on it ensuring that it is in the range [0, 1]. This
/// performs an upsert operation meaning that if an entry already exists
/// then that entry will be overwritten.
///
/// # Example Scenario
///
/// We may want to dynamically control the matching factor of pools such
/// that we can update them at runtime instead of doing a new deployment
/// of Ignition.
///
/// # Access
///
/// Requires the `protocol_owner` roles.
///
/// # Arguments
///
/// * `component_address`: [`ComponentAddress`] - The address of the
/// pool to set the matching factor for.
/// * `matching_factor`: [`Decimal`] - The matching factor of the pool.
pub fn upsert_matching_factor(
&mut self,
component_address: ComponentAddress,
matching_factor: Decimal,
) {
if matching_factor < Decimal::ZERO || matching_factor > Decimal::ONE
{
panic!("{}", INVALID_MATCHING_FACTOR)
}
self.matching_factor
.insert(component_address, matching_factor)
}

/// Updates the oracle adapter used by the protocol to a different
/// adapter.
///
Expand Down Expand Up @@ -1998,6 +2059,9 @@ pub struct InitializationParameters {
/// The initial control of whether the user is allowed to close a liquidity
/// position or not. Defaults to [`false`] if not specified.
pub initial_is_close_position_enabled: Option<bool>,

/// The initial map of matching factors to use in Ignition.
pub initial_matching_factors: Option<IndexMap<ComponentAddress, Decimal>>,
}

#[derive(Debug, PartialEq, Eq, ManifestSbor, Default)]
Expand Down Expand Up @@ -2026,4 +2090,7 @@ pub struct InitializationParametersManifest {
/// The initial control of whether the user is allowed to close a liquidity
/// position or not. Defaults to [`false`] if not specified.
pub initial_is_close_position_enabled: Option<bool>,

/// The initial map of matching factors to use in Ignition.
pub initial_matching_factors: Option<IndexMap<ComponentAddress, Decimal>>,
}
3 changes: 3 additions & 0 deletions packages/ignition/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,7 @@ define_error! {
=> "Price staleness must be a positive or zero integer";
INVALID_UPFRONT_REWARD_PERCENTAGE
=> "Upfront rewards must be positive or zero decimals";
NO_MATCHING_FACTOR_FOUND_FOR_POOL
=> "Pool doesn't have a matching factor";
INVALID_MATCHING_FACTOR => "Invalid matching factor";
}
1 change: 1 addition & 0 deletions testing/stateful-tests/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,7 @@ fn log_reported_price_from_defiplaza_pool(
}

#[apply(mainnet_test)]
#[ignore = "Ignoring this test for now"]
fn lsu_lp_positions_opened_at_current_bin_can_be_closed_at_any_bin(
AccountAndControllingKey {
account_address: test_account,
Expand Down
49 changes: 49 additions & 0 deletions testing/tests/src/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,32 @@ impl ScryptoTestEnv {
&mut env,
)?;

for pool in ociswap_v1_pools
.iter()
.map(|item| ComponentAddress::try_from(item).unwrap())
.chain(
ociswap_v2_pools
.iter()
.map(|item| ComponentAddress::try_from(item).unwrap()),
)
.chain(
caviarnine_v1_pools
.iter()
.map(|item| ComponentAddress::try_from(item).unwrap()),
)
.chain(
defiplaza_v2_pools
.iter()
.map(|item| ComponentAddress::try_from(item).unwrap()),
)
{
ignition.upsert_matching_factor(
pool,
Decimal::ONE,
&mut env,
)?;
}

ignition.insert_pool_information(
CaviarnineV1PoolInterfaceScryptoTestStub::blueprint_id(
caviarnine_v1_package,
Expand Down Expand Up @@ -1293,6 +1319,29 @@ impl ScryptoUnitEnv {
.copied()
.unwrap();

// Submit the matching factor to Ignition
{
let manifest = ociswap_v1_pools
.iter()
.chain(ociswap_v2_pools.iter())
.chain(caviarnine_v1_pools.iter())
.chain(defiplaza_v2_pools.iter())
.fold(
ManifestBuilder::new().lock_fee_from_faucet(),
|builder, address| {
builder.call_method(
ignition,
"upsert_matching_factor",
(address, Decimal::ONE),
)
},
)
.build();
ledger
.execute_manifest_without_auth(manifest)
.expect_commit_success();
}

let [ociswap_v1_adapter_v1, ociswap_v2_adapter_v1, defiplaza_v2_adapter_v1, caviarnine_v1_adapter] =
[
(ociswap_v1_adapter_v1_package, "OciswapV1Adapter"),
Expand Down
13 changes: 13 additions & 0 deletions testing/tests/tests/caviarnine_v1_adapter_v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,19 @@ fn test_effect_of_price_action_on_fees(multiplier: i32, bin_span: u32) {
.copied()
.unwrap();

ledger
.execute_manifest_without_auth(
ManifestBuilder::new()
.lock_fee_from_faucet()
.call_method(
protocol.ignition,
"upsert_matching_factor",
(pool_address, dec!(1)),
)
.build(),
)
.expect_commit_success();

// We're allowed to contribute to 200 bins. So, we will contribute to all of
// them. This ensures that the maximum amount of price range is covered by
// our liquidity.
Expand Down
13 changes: 13 additions & 0 deletions testing/tests/tests/caviarnine_v1_simulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,19 @@ fn can_open_and_close_positions_to_all_mainnet_caviarnine_pools() {
.copied()
.unwrap();

ledger
.execute_manifest_without_auth(
ManifestBuilder::new()
.lock_fee_from_faucet()
.call_method(
protocol.ignition,
"upsert_matching_factor",
(pool_address, dec!(1)),
)
.build(),
)
.expect_commit_success();

// Providing the liquidity to the pools.
let (divisibility_x, divisibility_y) = if resource_x == XRD {
(18, divisibility)
Expand Down
65 changes: 65 additions & 0 deletions testing/tests/tests/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1985,6 +1985,71 @@ fn forcefully_liquidated_resources_can_be_claimed_when_closing_liquidity_positio
Ok(())
}

#[test]
fn protocol_manager_cant_change_matching_factor() -> Result<(), RuntimeError> {
// Arrange
let Environment {
environment: ref mut env,
mut protocol,
ociswap_v1,
..
} = ScryptoTestEnv::new_with_configuration(Configuration {
maximum_allowed_price_staleness_in_seconds_seconds: i64::MAX,
..Default::default()
})?;
env.enable_auth_module();

// Act
LocalAuthZone::push(
protocol.protocol_manager_badge.create_proof_of_all(env)?,
env,
)?;
let rtn = protocol.ignition.upsert_matching_factor(
ociswap_v1.pools.bitcoin.try_into().unwrap(),
Decimal::ONE,
env,
);

// Assert
matches!(
rtn,
Err(RuntimeError::SystemModuleError(
SystemModuleError::AuthError(AuthError::Unauthorized(..))
))
);
Ok(())
}

#[test]
fn protocol_owner_can_change_matching_factor() -> Result<(), RuntimeError> {
// Arrange
let Environment {
environment: ref mut env,
mut protocol,
ociswap_v1,
..
} = ScryptoTestEnv::new_with_configuration(Configuration {
maximum_allowed_price_staleness_in_seconds_seconds: i64::MAX,
..Default::default()
})?;
env.enable_auth_module();

// Act
LocalAuthZone::push(
protocol.protocol_owner_badge.create_proof_of_all(env)?,
env,
)?;
let rtn = protocol.ignition.upsert_matching_factor(
ociswap_v1.pools.bitcoin.try_into().unwrap(),
Decimal::ONE,
env,
);

// Assert
assert!(rtn.is_ok());
Ok(())
}

mod utils {
use super::*;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ pub fn mainnet_production(
},
},
},
matching_factors: UserResourceIndexedData {
lsu_lp_resource: dec!(0.4)
},
},
dapp_definition: DappDefinitionHandling::UseExistingOneWayLink {
component_address: component_address!(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ pub fn mainnet_testing(
},
},
},
matching_factors: UserResourceIndexedData {
lsu_lp_resource: dec!(0.4)
},
},
dapp_definition: DappDefinitionHandling::UseExistingOneWayLink {
component_address: component_address!(
Expand Down
Loading

0 comments on commit e4e720c

Please sign in to comment.