Skip to content

Commit

Permalink
compute: settle vault reward when necessary
Browse files Browse the repository at this point in the history
  • Loading branch information
h4x3rotab committed Oct 27, 2023
1 parent ec514b2 commit f862904
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 34 deletions.
92 changes: 58 additions & 34 deletions pallets/phala/src/compute/vault.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,41 +309,12 @@ pub mod pallet {
#[pallet::weight({0})]
pub fn maybe_gain_owner_shares(origin: OriginFor<T>, vault_pid: u64) -> DispatchResult {
let who = ensure_signed(origin)?;
let mut pool_info = ensure_vault::<T>(vault_pid)?;
// Add pool owner's reward if applicable
let pool_info = ensure_vault::<T>(vault_pid)?;
ensure!(
who == pool_info.basepool.owner,
Error::<T>::UnauthorizedPoolOwner
);
let current_price = match pool_info.basepool.share_price() {
Some(price) => BalanceOf::<T>::from_fixed(&price),
None => return Ok(()),
};
if pool_info.last_share_price_checkpoint == Zero::zero() {
pool_info.last_share_price_checkpoint = current_price;
base_pool::pallet::Pools::<T>::insert(vault_pid, PoolProxy::Vault(pool_info));
return Ok(());
}
if current_price <= pool_info.last_share_price_checkpoint {
return Ok(());
}
let delta_price = pool_info.commission.unwrap_or_default()
* (current_price - pool_info.last_share_price_checkpoint);
let new_price = current_price - delta_price;
let adjust_shares = bdiv(pool_info.basepool.total_value, &new_price.to_fixed())
- pool_info.basepool.total_shares;
pool_info.basepool.total_shares += adjust_shares;
pool_info.owner_shares += adjust_shares;
pool_info.last_share_price_checkpoint = new_price;

base_pool::pallet::Pools::<T>::insert(vault_pid, PoolProxy::Vault(pool_info));
Self::deposit_event(Event::<T>::OwnerSharesGained {
pid: vault_pid,
shares: adjust_shares,
checkout_price: new_price,
});

Ok(())
Self::do_gain_owner_share(vault_pid)
}

/// Let any user to launch a vault withdraw. Then check if the vault need to be forced withdraw all its contributions.
Expand Down Expand Up @@ -488,9 +459,7 @@ pub mod pallet {
#[frame_support::transactional]
pub fn contribute(origin: OriginFor<T>, pid: u64, amount: BalanceOf<T>) -> DispatchResult {
let who = ensure_signed(origin)?;
let mut pool_info = ensure_vault::<T>(pid)?;
let a = amount; // Alias to reduce confusion in the code below

ensure!(
a >= T::MinContribution::get(),
Error::<T>::InsufficientContribution
Expand All @@ -502,11 +471,15 @@ pub mod pallet {
.ok_or(Error::<T>::AssetAccountNotExist)?;
ensure!(free >= a, Error::<T>::InsufficientBalance);

// Trigger owner reward share distribution before contribution to ensure no harm to the
// contributor.
Self::do_gain_owner_share(pid)?;
let mut pool_info = ensure_vault::<T>(pid)?;

let shares =
base_pool::Pallet::<T>::contribute(&mut pool_info.basepool, who.clone(), amount)?;

// We have new free stake now, try to handle the waiting withdraw queue

base_pool::Pallet::<T>::try_process_withdraw_queue(&mut pool_info.basepool);

// Persist
Expand Down Expand Up @@ -542,7 +515,12 @@ pub mod pallet {
#[frame_support::transactional]
pub fn withdraw(origin: OriginFor<T>, pid: u64, shares: BalanceOf<T>) -> DispatchResult {
let who = ensure_signed(origin)?;

// Trigger owner reward share distribution before withdrawal to ensure no harm to the
// pool owner.
Self::do_gain_owner_share(pid)?;
let mut pool_info = ensure_vault::<T>(pid)?;

let maybe_nft_id = base_pool::Pallet::<T>::merge_nft_for_staker(
pool_info.basepool.cid,
who.clone(),
Expand Down Expand Up @@ -605,4 +583,50 @@ pub mod pallet {
Self::check_and_maybe_force_withdraw(origin, pid)
}
}

impl<T: Config> Pallet<T>
where
BalanceOf<T>: sp_runtime::traits::AtLeast32BitUnsigned + Copy + FixedPointConvert + Display,
T: pallet_rmrk_core::Config<CollectionId = CollectionId, ItemId = NftId>,
T: pallet_assets::Config<AssetId = u32, Balance = BalanceOf<T>>,
{
/// Triggers owner reward share distribution
///
/// Note 1: This function does mutate the pool info. After calling this function, the caller
/// must read the pool info again if it's accessed.
///
/// Note 2: This function guarantees no-op when it returns error.
fn do_gain_owner_share(vault_pid: u64) -> DispatchResult {
let mut pool_info = ensure_vault::<T>(vault_pid)?;
let current_price = match pool_info.basepool.share_price() {
Some(price) => BalanceOf::<T>::from_fixed(&price),
None => return Ok(()),
};
if pool_info.last_share_price_checkpoint == Zero::zero() {
pool_info.last_share_price_checkpoint = current_price;
base_pool::pallet::Pools::<T>::insert(vault_pid, PoolProxy::Vault(pool_info));
return Ok(());
}
if current_price <= pool_info.last_share_price_checkpoint {
return Ok(());
}
let delta_price = pool_info.commission.unwrap_or_default()
* (current_price - pool_info.last_share_price_checkpoint);
let new_price = current_price - delta_price;
let adjust_shares = bdiv(pool_info.basepool.total_value, &new_price.to_fixed())
- pool_info.basepool.total_shares;
pool_info.basepool.total_shares += adjust_shares;
pool_info.owner_shares += adjust_shares;
pool_info.last_share_price_checkpoint = new_price;

base_pool::pallet::Pools::<T>::insert(vault_pid, PoolProxy::Vault(pool_info));
Self::deposit_event(Event::<T>::OwnerSharesGained {
pid: vault_pid,
shares: adjust_shares,
checkout_price: new_price,
});

Ok(())
}
}
}
79 changes: 79 additions & 0 deletions pallets/phala/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2212,6 +2212,85 @@ fn vault_force_withdraw_after_3x_grace_period() {
});
}

#[test]
fn vault_owner_reward_settle_when_contribute_withdraw() {
use crate::compute::computation::pallet::OnReward;
new_test_ext().execute_with(|| {
mock_asset_id();
assert_ok!(PhalaWrappedBalances::wrap(
RuntimeOrigin::signed(1),
500 * DOLLARS
));
set_block_1();
setup_workers(1);
setup_stake_pool_with_workers(1, &[1]); // pid = 0
let vault1 = setup_vault(99);
assert_ok!(PhalaVault::set_payout_pref(
RuntimeOrigin::signed(99),
vault1,
Some(Permill::from_percent(100)),
));
assert_ok!(PhalaVault::contribute(
RuntimeOrigin::signed(1),
1,
100 * DOLLARS,
));
assert_ok!(PhalaStakePoolv2::contribute(
RuntimeOrigin::signed(99),
0,
100 * DOLLARS,
Some(vault1),
));
// Checkpoint price = 1
assert_ok!(PhalaVault::maybe_gain_owner_shares(
RuntimeOrigin::signed(99),
vault1,
));
let pool0 = ensure_stake_pool::<Test>(0).unwrap();
assert_eq!(pool0.basepool.share_price(), Some(fp!(1)));
let pool1 = ensure_vault::<Test>(1).unwrap();
assert_eq!(pool1.basepool.share_price(), Some(fp!(1)));
assert_eq!(pool1.last_share_price_checkpoint, 1 * DOLLARS);

// Current price = 2
PhalaStakePoolv2::on_reward(&[SettleInfo {
pubkey: worker_pubkey(1),
v: FixedPoint::from_num(1u32).to_bits(),
payout: FixedPoint::from_num(100u32).to_bits(),
treasury: 0,
}]);
let pool1 = ensure_vault::<Test>(1).unwrap();
assert_eq!(pool1.basepool.share_price(), Some(fp!(2)));
// Contribution should bring price back to 1
assert_ok!(PhalaVault::contribute(
RuntimeOrigin::signed(1),
1,
100 * DOLLARS,
));
let pool1 = ensure_vault::<Test>(1).unwrap();
assert_eq!(pool1.basepool.share_price(), Some(fp!(1)));

// Double the stake pool asset by adding 300 reward
// Current price = 2 again
PhalaStakePoolv2::on_reward(&[SettleInfo {
pubkey: worker_pubkey(1),
v: FixedPoint::from_num(1u32).to_bits(),
payout: FixedPoint::from_num(300u32).to_bits(),
treasury: 0,
}]);
let pool1 = ensure_vault::<Test>(1).unwrap();
assert_eq!(pool1.basepool.share_price(), Some(fp!(2)));
// Withdrawal should bring the price back to 1
assert_ok!(PhalaVault::withdraw(
RuntimeOrigin::signed(1),
1,
100 * DOLLARS,
));
let pool1 = ensure_vault::<Test>(1).unwrap();
assert_eq!(pool1.basepool.share_price(), Some(fp!(1)));
});
}

fn mock_asset_id() {
<pallet_assets::pallet::Pallet<Test> as Create<u64>>::create(
<Test as wrapped_balances::Config>::WPhaAssetId::get(),
Expand Down

0 comments on commit f862904

Please sign in to comment.