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> {
let (substate, handle) = Self::lock_and_read(api, LockFlags::read_only())?;
let mut pool_unit_resource_manager = substate.pool_unit_resource_manager;
let mut vault = substate.vault;
if bucket.is_empty(api)? {
return Err(Error::ContributionOfEmptyBucketError.into());
}
let reserves = vault.amount(api)?;
let pool_unit_total_supply = pool_unit_resource_manager
.total_supply(api)?
.expect("Total supply is always enabled for pool unit resource.");
let amount_of_contributed_resources = bucket.amount(api)?;
let pool_units_to_mint = match (
pool_unit_total_supply > Decimal::ZERO,
reserves > Decimal::ZERO,
) {
(false, false) => Ok(amount_of_contributed_resources),
(false, true) => amount_of_contributed_resources
.checked_add(reserves)
.ok_or(Error::DecimalOverflowError),
(true, false) => Err(Error::NonZeroPoolUnitSupplyButZeroReserves),
(true, true) => amount_of_contributed_resources
.checked_div(reserves)
.and_then(|d| d.checked_mul(pool_unit_total_supply))
.ok_or(Error::DecimalOverflowError),
}?;
vault.put(bucket, api)?;
let pool_units = pool_unit_resource_manager.mint_fungible(pool_units_to_mint, api)?;
api.field_close(handle)?;
Runtime::emit_event(
api,
ContributionEvent {
amount_of_resources_contributed: amount_of_contributed_resources,
pool_units_minted: pool_units_to_mint,
},
)?;
Ok(pool_units.into())
}
pub fn redeem<Y: SystemApi<RuntimeError>>(
bucket: Bucket,
api: &mut Y,
) -> Result<OneResourcePoolRedeemOutput, RuntimeError> {
let (pool_unit_resource_manager, mut vault, handle) = {
let (substate, lock_handle) = Self::lock_and_read(api, LockFlags::read_only())?;
(
substate.pool_unit_resource_manager,
substate.vault,
lock_handle,
)
};
let bucket_resource_address = bucket.resource_address(api)?;
if bucket_resource_address != pool_unit_resource_manager.0 {
return Err(Error::InvalidPoolUnitResource {
expected: pool_unit_resource_manager.0,
actual: bucket_resource_address,
}
.into());
}
let pool_units_to_redeem = bucket.amount(api)?;
let pool_units_total_supply = pool_unit_resource_manager
.total_supply(api)?
.expect("Total supply is always enabled for pool unit resource.");
let pool_resource_reserves = vault.amount(api)?;
let pool_resource_divisibility = 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,
pool_units_total_supply,
pool_resource_reserves,
pool_resource_divisibility,
)?;
bucket.burn(api)?;
let owed_resources = vault.take(amount_owed, api)?;
api.field_close(handle)?;
Runtime::emit_event(
api,
RedemptionEvent {
pool_unit_tokens_redeemed: pool_units_to_redeem,
redeemed_amount: amount_owed,
},
)?;
Ok(owed_resources)
}
pub fn protected_deposit<Y: SystemApi<RuntimeError>>(
bucket: Bucket,
api: &mut Y,
) -> Result<OneResourcePoolProtectedDepositOutput, RuntimeError> {
let (mut substate, handle) = Self::lock_and_read(api, LockFlags::read_only())?;
let event = DepositEvent {
amount: bucket.amount(api)?,
};
substate.vault.put(bucket, api)?;
api.field_close(handle)?;
Runtime::emit_event(api, event)?;
Ok(())
}
pub fn protected_withdraw<Y: SystemApi<RuntimeError>>(
amount: Decimal,
withdraw_strategy: WithdrawStrategy,
api: &mut Y,
) -> Result<OneResourcePoolProtectedWithdrawOutput, RuntimeError> {
let (mut substate, handle) = Self::lock_and_read(api, LockFlags::read_only())?;
let bucket = substate
.vault
.take_advanced(amount, withdraw_strategy, api)?;
api.field_close(handle)?;
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> {
let (pool_unit_resource_manager, vault, handle) = {
let (substate, lock_handle) = Self::lock_and_read(api, LockFlags::read_only())?;
(
substate.pool_unit_resource_manager,
substate.vault,
lock_handle,
)
};
let pool_units_to_redeem = amount_of_pool_units;
let pool_units_total_supply = 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 = vault.amount(api)?;
let pool_resource_divisibility = 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,
pool_units_total_supply,
pool_resource_reserves,
pool_resource_divisibility,
)?;
api.field_close(handle)?;
Ok(amount_owed)
}
pub fn get_vault_amount<Y: SystemApi<RuntimeError>>(
api: &mut Y,
) -> Result<OneResourcePoolGetVaultAmountOutput, RuntimeError> {
let (substate, handle) = Self::lock_and_read(api, LockFlags::read_only())?;
let amount = substate.vault.amount(api)?;
api.field_close(handle)?;
Ok(amount)
}
fn calculate_amount_owed(
pool_units_to_redeem: Decimal,
pool_units_total_supply: Decimal,
pool_resource_reserves: Decimal,
pool_resource_divisibility: u8,
) -> Result<Decimal, RuntimeError> {
let amount_owed = pool_units_to_redeem
.checked_div(pool_units_total_supply)
.and_then(|d| d.checked_mul(pool_resource_reserves))
.ok_or(Error::DecimalOverflowError)?;
let amount_owed = if pool_resource_divisibility == 18 {
amount_owed
} else {
amount_owed
.checked_round(pool_resource_divisibility, RoundingMode::ToNegativeInfinity)
.ok_or(Error::DecimalOverflowError)?
};
Ok(amount_owed)
}
fn lock_and_read<Y: SystemApi<RuntimeError>>(
api: &mut Y,
lock_flags: LockFlags,
) -> Result<(Substate, SubstateHandle), RuntimeError> {
let substate_key = OneResourcePoolField::State.into();
let handle = api.actor_open_field(ACTOR_STATE_SELF, substate_key, lock_flags)?;
let substate = api
.field_read_typed::<VersionedOneResourcePoolState>(handle)?
.fully_update_and_into_latest_version();
Ok((substate, handle))
}
}