use crate::blueprints::pool::v1::constants::*;
use crate::blueprints::pool::v1::errors::one_resource_pool::*;
use crate::blueprints::pool::v1::events::one_resource_pool::*;
use crate::blueprints::pool::v1::substates::one_resource_pool::*;
use crate::internal_prelude::*;
use radix_engine_interface::blueprints::component::*;
use radix_engine_interface::blueprints::pool::*;
use radix_engine_interface::prelude::*;
use radix_engine_interface::*;
use radix_native_sdk::modules::metadata::*;
use radix_native_sdk::modules::role_assignment::*;
use radix_native_sdk::modules::royalty::*;
use radix_native_sdk::resource::*;
use radix_native_sdk::runtime::*;
pub struct OneResourcePoolBlueprint;
impl OneResourcePoolBlueprint {
pub fn instantiate<Y: SystemApi<RuntimeError>>(
resource_address: ResourceAddress,
owner_role: OwnerRole,
pool_manager_rule: AccessRule,
address_reservation: Option<GlobalAddressReservation>,
api: &mut Y,
) -> Result<OneResourcePoolInstantiateOutput, RuntimeError> {
let resource_manager = ResourceManager(resource_address);
if let ResourceType::NonFungible { .. } = resource_manager.resource_type(api)? {
Err(Error::NonFungibleResourcesAreNotAccepted { resource_address })?
}
let (address_reservation, address) = {
if let Some(address_reservation) = address_reservation {
let address = api.get_reservation_address(address_reservation.0.as_node_id())?;
(address_reservation, address)
} else {
api.allocate_global_address(BlueprintId {
package_address: POOL_PACKAGE,
blueprint_name: ONE_RESOURCE_POOL_BLUEPRINT_IDENT.to_string(),
})?
}
};
let pool_unit_resource_manager = {
let component_caller_badge = NonFungibleGlobalId::global_caller_badge(address);
ResourceManager::new_fungible(
owner_role.clone(),
true,
18,
FungibleResourceRoles {
mint_roles: mint_roles! {
minter => rule!(require(component_caller_badge.clone()));
minter_updater => rule!(deny_all);
},
burn_roles: burn_roles! {
burner => rule!(require(component_caller_badge.clone()));
burner_updater => rule!(deny_all);
},
..Default::default()
},
metadata_init! {
"pool" => address, locked;
},
None,
api,
)?
};
let role_assignment = RoleAssignment::create(
owner_role,
indexmap! {
ModuleId::Main => roles_init! {
RoleKey { key: POOL_MANAGER_ROLE.to_owned() } => pool_manager_rule;
}
},
api,
)?
.0;
let metadata = Metadata::create_with_data(
metadata_init! {
"pool_vault_number" => 1u8, locked;
"pool_resources" => vec![GlobalAddress::from(resource_address)], locked;
"pool_unit" => GlobalAddress::from(pool_unit_resource_manager.0), locked;
},
api,
)?;
let royalty = ComponentRoyalty::create(ComponentRoyaltyConfig::default(), api)?;
let object_id = {
let vault = Vault::create(resource_address, api)?;
let substate = Substate {
vault,
pool_unit_resource_manager,
};
api.new_simple_object(
ONE_RESOURCE_POOL_BLUEPRINT_IDENT,
indexmap! {
OneResourcePoolField::State.field_index() => FieldValue::immutable(OneResourcePoolStateFieldPayload::from_content_source(substate)),
},
)?
};
api.globalize(
object_id,
indexmap!(
AttachedModuleId::RoleAssignment => role_assignment.0,
AttachedModuleId::Metadata => metadata.0,
AttachedModuleId::Royalty => royalty.0,
),
Some(address_reservation),
)?;
Ok(Global::new(ComponentAddress::new_or_panic(
address.as_node_id().0,
)))
}
pub fn contribute<Y: SystemApi<RuntimeError>>(
bucket: Bucket,
api: &mut Y,
) -> Result<OneResourcePoolContributeOutput, RuntimeError> {
if bucket.is_empty(api)? {
return Err(Error::ContributionOfEmptyBucketError.into());
}
Self::with_state(api, |mut substate, api| {
{
let input_resource_address = bucket.resource_address(api)?;
let pool_reserves_resource_address = substate.vault.resource_address(api)?;
if input_resource_address != pool_reserves_resource_address {
return Err(Error::ResourceDoesNotBelongToPool {
resource_address: input_resource_address,
}
.into());
}
}
let initial_reserves_decimal = substate.vault.amount(api)?;
let initial_pool_unit_total_supply_decimal = substate
.pool_unit_resource_manager
.total_supply(api)?
.expect("Total supply is always enabled for pool unit resource.");
let amount_of_contributed_resources_decimal = bucket.amount(api)?;
let initial_reserves = PreciseDecimal::from(initial_reserves_decimal);
let initial_pool_unit_total_supply =
PreciseDecimal::from(initial_pool_unit_total_supply_decimal);
let amount_of_contributed_resources =
PreciseDecimal::from(amount_of_contributed_resources_decimal);
let pool_units_to_mint = match (
initial_pool_unit_total_supply > PreciseDecimal::ZERO,
initial_reserves > PreciseDecimal::ZERO,
) {
(false, false) => Ok(amount_of_contributed_resources),
(false, true) => amount_of_contributed_resources
.checked_add(initial_reserves)
.ok_or(Error::DecimalOverflowError),
(true, false) => Err(Error::NonZeroPoolUnitSupplyButZeroReserves),
(true, true) => amount_of_contributed_resources
.checked_div(initial_reserves)
.and_then(|d| d.checked_mul(initial_pool_unit_total_supply))
.ok_or(Error::DecimalOverflowError),
}?;
let pool_units_to_mint =
Decimal::try_from(pool_units_to_mint).map_err(|_| Error::DecimalOverflowError)?;
if pool_units_to_mint == Decimal::ZERO {
return Err(Error::ZeroPoolUnitsMinted.into());
}
Runtime::emit_event(
api,
ContributionEvent {
amount_of_resources_contributed: amount_of_contributed_resources_decimal,
pool_units_minted: pool_units_to_mint,
},
)?;
substate.vault.put(bucket, api)?;
let pool_units = substate
.pool_unit_resource_manager
.mint_fungible(pool_units_to_mint, api)?;
Ok(pool_units.into())
})
}
pub fn redeem<Y: SystemApi<RuntimeError>>(
bucket: Bucket,
api: &mut Y,
) -> Result<OneResourcePoolRedeemOutput, RuntimeError> {
Self::with_state(api, |mut substate, api| {
let bucket_resource_address = bucket.resource_address(api)?;
if bucket_resource_address != substate.pool_unit_resource_manager.0 {
return Err(Error::InvalidPoolUnitResource {
expected: substate.pool_unit_resource_manager.0,
actual: bucket_resource_address,
}
.into());
}
let pool_units_to_redeem = bucket.amount(api)?;
let initial_pool_units_total_supply = substate
.pool_unit_resource_manager
.total_supply(api)?
.expect("Total supply is always enabled for pool unit resource.");
let initial_pool_resource_reserves = substate.vault.amount(api)?;
let reserves_divisibility = substate.vault
.resource_address(api)
.and_then(|resource_address| ResourceManager(resource_address).resource_type(api))
.map(|resource_type| {
if let ResourceType::Fungible { divisibility } = resource_type {
divisibility
} else {
panic!("Impossible case, we check for this in the constructor and have a test for this.")
}
})?;
let amount_owed = Self::calculate_amount_owed(
pool_units_to_redeem,
initial_pool_units_total_supply,
initial_pool_resource_reserves,
reserves_divisibility,
)?;
if amount_owed == Decimal::ZERO {
return Err(Error::RedeemedZeroTokens.into());
}
Runtime::emit_event(
api,
RedemptionEvent {
pool_unit_tokens_redeemed: pool_units_to_redeem,
redeemed_amount: amount_owed,
},
)?;
bucket.burn(api)?;
substate.vault.take(amount_owed, api)
})
}
pub fn protected_deposit<Y: SystemApi<RuntimeError>>(
bucket: Bucket,
api: &mut Y,
) -> Result<OneResourcePoolProtectedDepositOutput, RuntimeError> {
let bucket_amount = bucket.amount(api)?;
Self::with_state(api, |mut substate, api| substate.vault.put(bucket, api))?;
Runtime::emit_event(
api,
DepositEvent {
amount: bucket_amount,
},
)?;
Ok(())
}
pub fn protected_withdraw<Y: SystemApi<RuntimeError>>(
amount: Decimal,
withdraw_strategy: WithdrawStrategy,
api: &mut Y,
) -> Result<OneResourcePoolProtectedWithdrawOutput, RuntimeError> {
let bucket = Self::with_state(api, |mut substate, api| {
substate.vault.take_advanced(amount, withdraw_strategy, api)
})?;
let withdrawn_amount = bucket.amount(api)?;
Runtime::emit_event(
api,
WithdrawEvent {
amount: withdrawn_amount,
},
)?;
Ok(bucket)
}
pub fn get_redemption_value<Y: SystemApi<RuntimeError>>(
amount_of_pool_units: Decimal,
api: &mut Y,
) -> Result<OneResourcePoolGetRedemptionValueOutput, RuntimeError> {
Self::with_state(api, |substate, api| {
let pool_units_to_redeem = amount_of_pool_units;
let pool_units_total_supply = substate
.pool_unit_resource_manager
.total_supply(api)?
.expect("Total supply is always enabled for pool unit resource.");
if amount_of_pool_units.is_negative()
|| amount_of_pool_units.is_zero()
|| amount_of_pool_units > pool_units_total_supply
{
return Err(Error::InvalidGetRedemptionAmount.into());
}
let pool_resource_reserves = substate.vault.amount(api)?;
let pool_resource_divisibility = substate.vault
.resource_address(api)
.and_then(|resource_address| ResourceManager(resource_address).resource_type(api))
.map(|resource_type| {
if let ResourceType::Fungible { divisibility } = resource_type {
divisibility
} else {
panic!("Impossible case, we check for this in the constructor and have a test for this.")
}
})?;
Self::calculate_amount_owed(
pool_units_to_redeem,
pool_units_total_supply,
pool_resource_reserves,
pool_resource_divisibility,
)
})
}
pub fn get_vault_amount<Y: SystemApi<RuntimeError>>(
api: &mut Y,
) -> Result<OneResourcePoolGetVaultAmountOutput, RuntimeError> {
Self::with_state(api, |substate, api| substate.vault.amount(api))
}
fn calculate_amount_owed(
pool_units_to_redeem: Decimal,
pool_units_total_supply: Decimal,
reserves_amount: Decimal,
reserves_divisibility: u8,
) -> Result<Decimal, RuntimeError> {
let pool_units_to_redeem = PreciseDecimal::from(pool_units_to_redeem);
let pool_units_total_supply = PreciseDecimal::from(pool_units_total_supply);
let reserves_amount = PreciseDecimal::from(reserves_amount);
let amount_owed = pool_units_to_redeem
.checked_div(pool_units_total_supply)
.and_then(|d| d.checked_mul(reserves_amount))
.ok_or(Error::DecimalOverflowError)?;
Decimal::try_from(amount_owed)
.ok()
.and_then(|value| {
value.checked_round(reserves_divisibility, RoundingMode::ToNegativeInfinity)
})
.ok_or(Error::DecimalOverflowError.into())
}
fn with_state<Y: SystemApi<RuntimeError>, O>(
api: &mut Y,
callback: impl FnOnce(Substate, &mut Y) -> Result<O, RuntimeError>,
) -> Result<O, RuntimeError> {
let substate_key = OneResourcePoolField::State.into();
let handle =
api.actor_open_field(ACTOR_STATE_SELF, substate_key, LockFlags::read_only())?;
let substate = api
.field_read_typed::<VersionedOneResourcePoolState>(handle)?
.fully_update_and_into_latest_version();
let rtn = callback(substate, api);
if rtn.is_ok() {
api.field_close(handle)?;
}
rtn
}
}