Skip to content

Commit

Permalink
slash behavior on pallet vesting
Browse files Browse the repository at this point in the history
  • Loading branch information
JuaniRios committed Sep 10, 2024
1 parent 61185bf commit 5e2c3a4
Show file tree
Hide file tree
Showing 17 changed files with 580 additions and 14 deletions.
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ pallet-sandbox = { path = "pallets/sandbox", default-features = false }
pallet-parachain-staking = { path = "pallets/parachain-staking", default-features = false }
pallet-linear-release = { path = "pallets/linear-release", default-features = false }
polimec-receiver = { path = "pallets/polimec-receiver", default-features = false }
on-slash-vesting = { path = "pallets/on-slash-vesting", default-features = false }

# Internal macros
macros = { path = "macros" }
Expand Down Expand Up @@ -109,6 +110,7 @@ color-print = "0.3.5"
xcm-emulator = { version = "0.12.0", default-features = false }

# Substrate (with default disabled)
impl-trait-for-tuples = { version = "0.2.2", default-features = false }
frame-benchmarking = { version = "35.0.0", default-features = false }
frame-benchmarking-cli = { version = "39.0.0" }
frame-executive = { version = "35.0.0", default-features = false }
Expand Down
20 changes: 15 additions & 5 deletions integration-tests/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,12 +378,22 @@ pub mod polimec {
const GENESIS_NUM_SELECTED_CANDIDATES: u32 = 5;

#[allow(unused)]
pub fn set_prices() {
pub fn set_prices(
dot: Option<FixedU128>,
usdc: Option<FixedU128>,
usdt: Option<FixedU128>,
plmc: Option<FixedU128>,
) {
PolimecNet::execute_with(|| {
let dot = (AcceptedFundingAsset::DOT.id(), FixedU128::from_rational(69, 1));
let usdc = (AcceptedFundingAsset::USDC.id(), FixedU128::from_rational(1, 1));
let usdt = (AcceptedFundingAsset::USDT.id(), FixedU128::from_rational(1, 1));
let plmc = (pallet_funding::PLMC_FOREIGN_ID, FixedU128::from_rational(840, 100));
let dot_price = dot.unwrap_or(FixedU128::from_rational(69, 1));
let usdc_price = usdc.unwrap_or(FixedU128::from_rational(1, 1));
let usdt_price = usdt.unwrap_or(FixedU128::from_rational(1, 1));
let plmc_price = plmc.unwrap_or(FixedU128::from_rational(840, 100));

let dot = (AcceptedFundingAsset::DOT.id(), dot_price);
let usdc = (AcceptedFundingAsset::USDC.id(), usdc_price);
let usdt = (AcceptedFundingAsset::USDT.id(), usdt_price);
let plmc = (pallet_funding::PLMC_FOREIGN_ID, plmc_price);

let values: BoundedVec<(u32, FixedU128), <PolimecRuntime as orml_oracle::Config>::MaxFeedValues> =
vec![dot, usdc, usdt, plmc].try_into().expect("benchmarks can panic");
Expand Down
4 changes: 2 additions & 2 deletions integration-tests/src/tests/ct_migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ fn create_settled_project() -> (ProjectId, Vec<AccountId>) {

#[test]
fn full_pallet_migration_test() {
polimec::set_prices();
polimec::set_prices(None, None, None, None);
let (project_id, participants) = create_settled_project();
let _project_status =
PolimecNet::execute_with(|| pallet_funding::ProjectsDetails::<PolimecRuntime>::get(project_id).unwrap().status);
Expand Down Expand Up @@ -296,7 +296,7 @@ fn create_project_with_unsettled_participation(participation_type: Participation

#[test]
fn cannot_start_pallet_migration_with_unsettled_participations() {
polimec::set_prices();
polimec::set_prices(None, None, None, None);

let tup_1 = create_project_with_unsettled_participation(ParticipationType::Evaluation);
let tup_2 = create_project_with_unsettled_participation(ParticipationType::Bid);
Expand Down
14 changes: 7 additions & 7 deletions integration-tests/src/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ fn excel_ct_amounts() -> UserToCTBalance {

#[test]
fn evaluation_round_completed() {
polimec::set_prices();
polimec::set_prices(None, None, None, None);

let mut inst = IntegrationInstantiator::new(None);

Expand All @@ -286,7 +286,7 @@ fn evaluation_round_completed() {

#[test]
fn auction_round_completed() {
polimec::set_prices();
polimec::set_prices(None, None, None, None);

let mut inst = IntegrationInstantiator::new(None);

Expand Down Expand Up @@ -326,7 +326,7 @@ fn auction_round_completed() {

#[test]
fn community_round_completed() {
polimec::set_prices();
polimec::set_prices(None, None, None, None);

let mut inst = IntegrationInstantiator::new(None);

Expand All @@ -353,7 +353,7 @@ fn community_round_completed() {

#[test]
fn remainder_round_completed() {
polimec::set_prices();
polimec::set_prices(None, None, None, None);

let mut inst = IntegrationInstantiator::new(None);

Expand Down Expand Up @@ -386,7 +386,7 @@ fn remainder_round_completed() {

#[test]
fn funds_raised() {
polimec::set_prices();
polimec::set_prices(None, None, None, None);

let mut inst = IntegrationInstantiator::new(None);

Expand Down Expand Up @@ -418,7 +418,7 @@ fn funds_raised() {

#[test]
fn ct_minted() {
polimec::set_prices();
polimec::set_prices(None, None, None, None);

let mut inst = IntegrationInstantiator::new(None);

Expand All @@ -445,7 +445,7 @@ fn ct_minted() {

#[test]
fn ct_migrated() {
polimec::set_prices();
polimec::set_prices(None, None, None, None);

let mut inst = IntegrationInstantiator::new(None);

Expand Down
226 changes: 226 additions & 0 deletions integration-tests/src/tests/evaluator_slash_sideffects.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
use crate::{tests::defaults::*, *};
use frame_support::{
traits::{
fungible::Mutate,
},
};
use frame_system::{pallet_prelude::BlockNumberFor, Account};
use macros::generate_accounts;
use pallet_balances::AccountData;
use pallet_funding::*;
use pallet_vesting::VestingInfo;
use polimec_common::{USD_UNIT};
use polimec_runtime::PLMC;
use sp_arithmetic::Perquintill;
use sp_runtime::{
FixedU128,
MultiAddress::Id,
};

generate_accounts!(STASH, ALICE, BOB, CHARLIE, DAVE, ISSUER);

#[test]
fn evaluator_slash_reduces_vesting_schedules() {
// set plmc price to 1 usd
polimec::set_prices(None, None, None, Some(FixedU128::from_float(1.0)));

let mut inst = IntegrationInstantiator::new(None);
let alice: PolimecAccountId = ALICE.into();
let bob: PolimecAccountId = BOB.into();

PolimecNet::execute_with(|| {
// Account that does the vested transfers (i.e. treasury)
PolimecBalances::set_balance(&STASH.into(), 1_000_000 * PLMC);
// For alice, we try to slash 4 schedules, each with a different duration and amount.
// One schedule should dissapear to due being fully vested before the slash, the other ones should be modified
PolimecBalances::set_balance(&alice.clone(), 0);
// For bob, we try to slash 1 schedule, which makes it dissapear since the slash amount is higher than the amount remaining for vesting
PolimecBalances::set_balance(&bob.clone(), BOB_STARTING_BALANCE);

let slash_percent = <PolimecRuntime as pallet_funding::Config>::EvaluatorSlash::get();

const BOB_STARTING_BALANCE: u128 = 100_000 * PLMC;
const LOCK_1: u128 = 10_000 * PLMC;
const LOCK_2: u128 = 13_000 * PLMC;
const LOCK_3: u128 = 7_500 * PLMC;
const LOCK_4: u128 = 20_000 * PLMC;
const PER_BLOCK_1: u128 = 10 * PLMC;
const PER_BLOCK_2: u128 = 650 * PLMC;
const PER_BLOCK_3: u128 = 5 * PLMC;
const PER_BLOCK_4: u128 = 200 * PLMC;
const DURATION_1: u128 = LOCK_1 / PER_BLOCK_1;
const DURATION_3: u128 = LOCK_3 / PER_BLOCK_3;
const DURATION_4: u128 = LOCK_4 / PER_BLOCK_4;

// Duration 1000 blocks
let vesting_info_1 = VestingInfo::new(LOCK_1, PER_BLOCK_1, 5);
// Duration 20 blocks
let vesting_info_2 = VestingInfo::new(LOCK_2, PER_BLOCK_2, 5);
// Duration 1500 blocks
let vesting_info_3 = VestingInfo::new(LOCK_3, PER_BLOCK_3, 5);
// Duration 100 blocks
let vesting_info_4 = VestingInfo::new(LOCK_4, PER_BLOCK_4, 5);

let total_alice_transferred = LOCK_1 + LOCK_2 + LOCK_3 + LOCK_4;
assert_ok!(PolimecVesting::vested_transfer(
PolimecOrigin::signed(STASH.into()),
Id(alice.clone()),
vesting_info_1
));
assert_ok!(PolimecVesting::vested_transfer(
PolimecOrigin::signed(STASH.into()),
Id(alice.clone()),
vesting_info_2
));
assert_ok!(PolimecVesting::vested_transfer(
PolimecOrigin::signed(STASH.into()),
Id(alice.clone()),
vesting_info_3
));
assert_ok!(PolimecVesting::vested_transfer(
PolimecOrigin::signed(STASH.into()),
Id(alice.clone()),
vesting_info_4
));

let alice_evaluation = UserToUSDBalance::<PolimecRuntime>::new(alice.clone(), 35_000 * USD_UNIT);
let alice_plmc_evaluated =
inst.calculate_evaluation_plmc_spent(vec![alice_evaluation.clone()], false)[0].plmc_amount;
let alice_slashed = slash_percent * alice_plmc_evaluated;

const BOB_EVALUATION: u128 = 60_000;
// We want the amount to be slashed to be higher than the amount remaining for vesting, after unlocking some tokens
let lock_5: u128 = ((slash_percent * BOB_EVALUATION) * PLMC) + PER_BLOCK_5 * 10;
const PER_BLOCK_5: u128 = 100 * PLMC;
let vesting_info_5 = VestingInfo::new(lock_5, PER_BLOCK_5, 5);

assert_ok!(PolimecVesting::vested_transfer(
PolimecOrigin::signed(STASH.into()),
Id(bob.clone()),
vesting_info_5
));
let bob_evaluation = UserToUSDBalance::<PolimecRuntime>::new(bob.clone(), BOB_EVALUATION * USD_UNIT);
let bob_plmc_evaluated =
inst.calculate_evaluation_plmc_spent(vec![bob_evaluation.clone()], false)[0].plmc_amount;
let bob_slashed = slash_percent * bob_plmc_evaluated;

// Set metadata so 50k USD succeeds the evaluation round
let mut project_metadata = default_project_metadata(ISSUER.into());
project_metadata.total_allocation_size = 5_000 * CT_UNIT;

// Create a project where alice and bob evaluated making the round successful, but then the project fails funding at block 25.
let project_id = inst.create_evaluating_project(project_metadata, ISSUER.into(), None);
assert_ok!(inst.evaluate_for_users(project_id, vec![alice_evaluation.clone(), bob_evaluation.clone()]));
assert_eq!(ProjectStatus::AuctionRound, inst.go_to_next_state(project_id));
assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(_)));
assert_eq!(ProjectStatus::FundingFailed, inst.go_to_next_state(project_id));
assert_eq!(ProjectStatus::SettlementStarted(FundingOutcome::Failure), inst.go_to_next_state(project_id));
assert_eq!(inst.current_block(), BlockNumberFor::<PolimecRuntime>::from(25u32));

// All schedules start at block 5, and funding ended at block 25
const TIME_PASSED: u128 = 20u128;

let alice_account_data = Account::<PolimecRuntime>::get(&alice.clone()).data;
assert_eq!(
alice_account_data,
AccountData {
free: total_alice_transferred - alice_plmc_evaluated,
reserved: alice_plmc_evaluated,
frozen: total_alice_transferred,
flags: Default::default(),
}
);
assert_eq!(PolimecBalances::usable_balance(alice.clone()), 0);

// vest schedule 2 was fully vested
assert_ok!(PolimecVesting::vest(PolimecOrigin::signed(alice.clone())));
let alice_account_data = Account::<PolimecRuntime>::get(&alice.clone()).data;
let vested = (PER_BLOCK_1 + PER_BLOCK_2 + PER_BLOCK_3 + PER_BLOCK_4) * TIME_PASSED;

let free = total_alice_transferred - alice_plmc_evaluated;
let reserved = alice_plmc_evaluated;
let frozen = total_alice_transferred - vested;

// `untouchable` is the amount we need to substract from the free balance to get the usable balance.
// When the reserved amount is higher than the frozen amount, it means that the frozen balance restriction is fully covered by the already reserved tokens
// When the frozen amount is higher, it means we need to use some free tokens to cover this restriction.
// This amount can never be below the existential deposit.
let untouchable = frozen.saturating_sub(reserved).max(inst.get_ed());

assert_eq!(alice_account_data, AccountData { free, reserved, frozen, flags: Default::default() });
assert_eq!(PolimecBalances::usable_balance(alice.clone()), free - untouchable);

assert_ok!(PolimecFunding::settle_evaluation(
PolimecOrigin::signed(alice.clone()),
project_id,
alice.clone(),
0
));

let alice_schedules = <pallet_vesting::Vesting<PolimecRuntime>>::get(alice.clone()).unwrap().to_vec();
let new_lock_1 = LOCK_1 - (PER_BLOCK_1 * TIME_PASSED) - alice_slashed;
let new_lock_3 = LOCK_3 - (PER_BLOCK_3 * TIME_PASSED) - alice_slashed;
let new_lock_4 = LOCK_4 - (PER_BLOCK_4 * TIME_PASSED) - alice_slashed;
const TIME_REMAINING_1: u128 = DURATION_1 - TIME_PASSED;
const TIME_REMAINING_3: u128 = DURATION_3 - TIME_PASSED;
const TIME_REMAINING_4: u128 = DURATION_4 - TIME_PASSED;
let new_per_block_1 = new_lock_1 / TIME_REMAINING_1;
let new_per_block_3 = new_lock_3 / TIME_REMAINING_3;
let new_per_block_4 = new_lock_4 / TIME_REMAINING_4;

let alice_account_data = Account::<PolimecRuntime>::get(&alice.clone()).data;
let free = free + alice_plmc_evaluated - alice_slashed;
let frozen = new_lock_1 + new_lock_3 + new_lock_4;
let reserved = 0u128;
let untouchable = frozen.saturating_sub(reserved).max(inst.get_ed());

assert_eq!(
alice_schedules,
vec![
VestingInfo::new(new_lock_1, new_per_block_1, 25),
VestingInfo::new(new_lock_3, new_per_block_3, 25),
VestingInfo::new(new_lock_4, new_per_block_4, 25),
]
);
assert_eq!(alice_account_data, AccountData { free, reserved, frozen, flags: Default::default() });
assert_eq!(PolimecBalances::usable_balance(alice.clone()), free - untouchable);

let bob_account_data = Account::<PolimecRuntime>::get(&bob.clone()).data;
let free = BOB_STARTING_BALANCE + lock_5 - bob_plmc_evaluated;
let reserved = bob_plmc_evaluated;
let frozen = lock_5;
assert_eq!(bob_account_data, AccountData { free, reserved, frozen, flags: Default::default() });
let untouchable = frozen.saturating_sub(reserved).max(inst.get_ed());
assert_eq!(PolimecBalances::usable_balance(bob.clone()), free - untouchable);

// Schedule has some tokens vested, and the remaining locked amount is lower than the slash about to occur
assert_ok!(PolimecVesting::vest(PolimecOrigin::signed(bob.clone())));
let bob_account_data = Account::<PolimecRuntime>::get(&bob.clone()).data;
let vested = PER_BLOCK_5 * TIME_PASSED;

let free = BOB_STARTING_BALANCE + lock_5 - bob_plmc_evaluated;
let reserved = bob_plmc_evaluated;
let frozen = lock_5 - vested;
let untouchable = frozen.saturating_sub(reserved).max(inst.get_ed());
assert_eq!(bob_account_data, AccountData { free, reserved, frozen, flags: Default::default() });
assert_eq!(PolimecBalances::usable_balance(bob.clone()), free - untouchable);

// Here the slash amount is higher than the amount remaining for vesting, so the schedule should dissapear
assert_ok!(PolimecFunding::settle_evaluation(PolimecOrigin::signed(bob.clone()), project_id, bob.clone(), 1));
assert!(bob_slashed > lock_5 - vested && bob_slashed < lock_5);

assert!(<pallet_vesting::Vesting<PolimecRuntime>>::get(bob.clone()).is_none());
let bob_account_data = Account::<PolimecRuntime>::get(&bob.clone()).data;
let free = free + bob_plmc_evaluated - bob_slashed;
let frozen = 0u128;
let reserved = 0u128;
let untouchable = frozen.saturating_sub(reserved).max(inst.get_ed());

assert_eq!(bob_account_data, AccountData { free, reserved, frozen, flags: Default::default() });
assert_close_enough!(
PolimecBalances::usable_balance(bob.clone()),
free - untouchable,
Perquintill::from_float(0.999)
);
});
}
1 change: 1 addition & 0 deletions integration-tests/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod credentials;
mod ct_migration;
mod defaults;
mod e2e;
mod evaluator_slash_sideffects;
mod governance;
mod oracle;
mod reserve_backed_transfers;
Expand Down
Loading

0 comments on commit 5e2c3a4

Please sign in to comment.