Skip to content

Commit

Permalink
Salp revamp (#397)
Browse files Browse the repository at this point in the history
* Add multisig account for salp lite

* Revamp salp flow for unlock&refund
  • Loading branch information
yrong authored Nov 10, 2021
1 parent 9acbcb0 commit 9d7127a
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 331 deletions.
80 changes: 26 additions & 54 deletions pallets/salp-lite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -456,16 +456,8 @@ pub mod pallet {
#[pallet::compact] index: ParaId,
) -> DispatchResult {
let fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
ensure!(
fund.status == FundStatus::Success ||
fund.status == FundStatus::Retired ||
fund.status == FundStatus::RedeemWithdrew ||
fund.status == FundStatus::End,
Error::<T>::InvalidFundStatus
);

let (contributed, status) = Self::contribution(fund.trie_index, &who);
ensure!(status == ContributionStatus::Idle, Error::<T>::InvalidContributionStatus);
let (contributed, _) = Self::contribution(fund.trie_index, &who);

#[allow(non_snake_case)]
let (vsToken, vsBond) = Self::vsAssets(index, fund.first_slot, fund.last_slot);
Expand Down Expand Up @@ -497,13 +489,6 @@ pub mod pallet {
ensure_signed(origin)?;

let fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
ensure!(
fund.status == FundStatus::Success ||
fund.status == FundStatus::Retired ||
fund.status == FundStatus::RedeemWithdrew ||
fund.status == FundStatus::End,
Error::<T>::InvalidFundStatus
);

let mut unlock_count = 0u32;
let contributions = Self::contribution_iterator(fund.trie_index);
Expand All @@ -519,10 +504,8 @@ pub mod pallet {
if status != ContributionStatus::Unlocked {
#[allow(non_snake_case)]
let (vsToken, vsBond) = Self::vsAssets(index, fund.first_slot, fund.last_slot);
let balance = T::MultiCurrency::unreserve(vsToken, &who, contributed);
ensure!(balance == Zero::zero(), Error::<T>::NotEnoughBalanceToUnlock);
let balance = T::MultiCurrency::unreserve(vsBond, &who, contributed);
ensure!(balance == Zero::zero(), Error::<T>::NotEnoughBalanceToUnlock);
T::MultiCurrency::unreserve(vsToken, &who, contributed);
T::MultiCurrency::unreserve(vsBond, &who, contributed);

Self::put_contribution(
fund.trie_index,
Expand Down Expand Up @@ -676,10 +659,6 @@ pub mod pallet {
let who = ensure_signed(origin.clone())?;

let mut fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
ensure!(
fund.status == FundStatus::Failed || fund.status == FundStatus::RefundWithdrew,
Error::<T>::InvalidFundStatus
);

let (contributed, status) = Self::contribution(fund.trie_index, &who);
ensure!(contributed > Zero::zero(), Error::<T>::ZeroContribution);
Expand All @@ -689,21 +668,11 @@ pub mod pallet {

#[allow(non_snake_case)]
let (vsToken, vsBond) = Self::vsAssets(index, fund.first_slot, fund.last_slot);
ensure!(
T::MultiCurrency::reserved_balance(vsToken, &who) >= contributed,
Error::<T>::NotEnoughReservedAssetsToRefund
);
ensure!(
T::MultiCurrency::reserved_balance(vsBond, &who) >= contributed,
Error::<T>::NotEnoughReservedAssetsToRefund
);

fund.raised = fund.raised.saturating_sub(contributed);

let balance = T::MultiCurrency::slash_reserved(vsToken, &who, contributed);
ensure!(balance == Zero::zero(), Error::<T>::NotEnoughReservedAssetsToRefund);
let balance = T::MultiCurrency::slash_reserved(vsBond, &who, contributed);
ensure!(balance == Zero::zero(), Error::<T>::NotEnoughReservedAssetsToRefund);
T::MultiCurrency::slash_reserved(vsToken, &who, contributed);
T::MultiCurrency::slash_reserved(vsBond, &who, contributed);

Self::kill_contribution(fund.trie_index, &who);

Expand All @@ -722,32 +691,35 @@ pub mod pallet {
let who = ensure_signed(origin.clone())?;

let mut fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
ensure!(fund.status == FundStatus::RedeemWithdrew, Error::<T>::InvalidFundStatus);
ensure!(fund.raised >= value, Error::<T>::NotEnoughBalanceInRedeemPool);

let (contributed, status) = Self::contribution(fund.trie_index, &who);
ensure!(
status == ContributionStatus::Idle ||
status == ContributionStatus::Unlocked ||
status == ContributionStatus::Redeemed,
Error::<T>::InvalidContributionStatus
fund.status == FundStatus::RefundWithdrew ||
fund.status == FundStatus::RedeemWithdrew,
Error::<T>::InvalidFundStatus
);
ensure!(fund.raised >= value, Error::<T>::NotEnoughBalanceInRedeemPool);

ensure!(Self::redeem_pool() >= value, Error::<T>::NotEnoughBalanceInRedeemPool);

let (contributed, _) = Self::contribution(fund.trie_index, &who);
#[allow(non_snake_case)]
let (vsToken, vsBond) = Self::vsAssets(index, fund.first_slot, fund.last_slot);

T::MultiCurrency::ensure_can_withdraw(vsToken, &who, value)
.map_err(|_e| Error::<T>::NotEnoughFreeAssetsToRedeem)?;
T::MultiCurrency::ensure_can_withdraw(vsBond, &who, value)
.map_err(|_e| Error::<T>::NotEnoughFreeAssetsToRedeem)?;
T::MultiCurrency::withdraw(vsToken, &who, value)?;
T::MultiCurrency::withdraw(vsBond, &who, value)?;
if fund.status == FundStatus::RedeemWithdrew {
ensure!(Self::redeem_pool() >= value, Error::<T>::NotEnoughBalanceInRedeemPool);
T::MultiCurrency::ensure_can_withdraw(vsToken, &who, value)
.map_err(|_e| Error::<T>::NotEnoughFreeAssetsToRedeem)?;
T::MultiCurrency::ensure_can_withdraw(vsBond, &who, value)
.map_err(|_e| Error::<T>::NotEnoughFreeAssetsToRedeem)?;
}

fund.raised = fund.raised.saturating_sub(value);
RedeemPool::<T>::set(Self::redeem_pool().saturating_sub(value));
if fund.status == FundStatus::RedeemWithdrew {
T::MultiCurrency::withdraw(vsToken, &who, value)?;
T::MultiCurrency::withdraw(vsBond, &who, value)?;
RedeemPool::<T>::set(Self::redeem_pool().saturating_sub(value));
} else if fund.status == FundStatus::RefundWithdrew {
T::MultiCurrency::slash_reserved(vsToken, &who, contributed);
T::MultiCurrency::slash_reserved(vsBond, &who, contributed);
}

fund.raised = fund.raised.saturating_sub(value);
let contributed_new = contributed.saturating_sub(value);
Self::put_contribution(
fund.trie_index,
Expand Down
146 changes: 77 additions & 69 deletions pallets/salp-lite/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

// Ensure we're `no_std` when compiling for Wasm.

use frame_support::{assert_noop, assert_ok, dispatch::DispatchError, traits::BalanceStatus as BS};
use frame_support::{assert_noop, assert_ok, dispatch::DispatchError};
use node_primitives::ContributionStatus;
use orml_traits::{MultiCurrency, MultiReservableCurrency};

Expand Down Expand Up @@ -263,35 +263,6 @@ fn unlock_should_work() {
});
}

#[test]
fn unlock_with_wrong_fund_status_should_fail() {
new_test_ext().execute_with(|| {
assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get()));
assert_ok!(Salp::issue(Some(ALICE).into(), BRUCE, 3_000, 100, CONTRIBUTON_INDEX,));
assert_ok!(Salp::fund_fail(Some(ALICE).into(), 3_000));

assert_noop!(
Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000),
Error::<Test>::InvalidFundStatus
);
});
}

#[test]
fn unlock_when_already_unlocked_should_fail() {
new_test_ext().execute_with(|| {
assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get()));
assert_ok!(Salp::issue(Some(ALICE).into(), BRUCE, 3_000, 100, CONTRIBUTON_INDEX,));
assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000));
assert_ok!(Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000));

assert_noop!(
Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000),
Error::<Test>::InvalidContributionStatus
);
});
}

#[test]
fn unlock_without_enough_reserved_vsassets_should_fail() {
new_test_ext().execute_with(|| {
Expand Down Expand Up @@ -554,26 +525,6 @@ fn refund_should_work() {
});
}

#[test]
fn refund_without_enough_vsassets_should_fail() {
new_test_ext().execute_with(|| {
assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get()));
assert_ok!(Salp::issue(Some(ALICE).into(), BRUCE, 3_000, 100, CONTRIBUTON_INDEX));
assert_ok!(Salp::fund_fail(Some(ALICE).into(), 3_000));
assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000));

#[allow(non_snake_case)]
let (vsToken, vsBond) = Salp::vsAssets(3_000, 1, SlotLength::get());
assert_ok!(Tokens::repatriate_reserved(vsToken, &BRUCE, &ALICE, 50, BS::Reserved));
assert_ok!(Tokens::repatriate_reserved(vsBond, &BRUCE, &ALICE, 50, BS::Reserved));

assert_noop!(
Salp::refund(Some(BRUCE).into(), 3_000),
Error::<Test>::NotEnoughReservedAssetsToRefund
);
});
}

#[test]
fn refund_with_zero_contribution_should_fail() {
new_test_ext().execute_with(|| {
Expand Down Expand Up @@ -611,25 +562,6 @@ fn refund_with_wrong_para_id_should_fail() {
});
}

#[test]
fn refund_with_wrong_fund_status_should_fail() {
new_test_ext().execute_with(|| {
assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get()));
assert_ok!(Salp::fund_fail(Some(ALICE).into(), 3_000));
assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000));
assert_ok!(Salp::fund_end(Some(ALICE).into(), 3_000));

assert_noop!(Salp::refund(Some(BRUCE).into(), 3_000), Error::<Test>::InvalidFundStatus);

assert_ok!(Salp::create(Some(ALICE).into(), 4_000, 1_000, 1, SlotLength::get()));
assert_ok!(Salp::fund_success(Some(ALICE).into(), 4_000));
assert_ok!(Salp::fund_retire(Some(ALICE).into(), 4_000));
assert_ok!(Salp::withdraw(Some(ALICE).into(), 4_000));

assert_noop!(Salp::refund(Some(BRUCE).into(), 4_000), Error::<Test>::InvalidFundStatus);
});
}

#[test]
fn refund_with_when_ump_wrong_should_fail() {
// TODO: Require an solution to settle with parallel test workflow
Expand Down Expand Up @@ -972,3 +904,79 @@ fn batch_migrate_should_work() {
assert_eq!(Tokens::accounts(BRUCE, vs_bond_new).reserved, 100);
})
}

#[test]
fn unlock_when_fund_ongoing_should_work() {
new_test_ext().execute_with(|| {
assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get()));
assert_ok!(Salp::issue(Some(ALICE).into(), BRUCE, 3_000, 100, CONTRIBUTON_INDEX));
assert_ok!(Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000));

#[allow(non_snake_case)]
let (vsToken, vsBond) = Salp::vsAssets(3_000, 1, SlotLength::get());

assert_eq!(Tokens::accounts(BRUCE, vsToken).free, 100);
assert_eq!(Tokens::accounts(BRUCE, vsToken).frozen, 0);
assert_eq!(Tokens::accounts(BRUCE, vsToken).reserved, 0);
assert_eq!(Tokens::accounts(BRUCE, vsBond).free, 100);
assert_eq!(Tokens::accounts(BRUCE, vsBond).frozen, 0);
assert_eq!(Tokens::accounts(BRUCE, vsBond).reserved, 0);
});
}

#[test]
fn refund_when_fund_ongoing_should_work() {
new_test_ext().execute_with(|| {
assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get()));
assert_ok!(Salp::issue(Some(ALICE).into(), BRUCE, 3_000, 100, CONTRIBUTON_INDEX));
assert_ok!(Salp::refund(Some(BRUCE).into(), 3_000));

#[allow(non_snake_case)]
let (vsToken, vsBond) = Salp::vsAssets(3_000, 1, SlotLength::get());
assert_eq!(Tokens::accounts(BRUCE, vsToken).free, 0);
assert_eq!(Tokens::accounts(BRUCE, vsToken).frozen, 0);
assert_eq!(Tokens::accounts(BRUCE, vsToken).reserved, 0);
assert_eq!(Tokens::accounts(BRUCE, vsBond).free, 0);
assert_eq!(Tokens::accounts(BRUCE, vsBond).frozen, 0);
assert_eq!(Tokens::accounts(BRUCE, vsBond).reserved, 0);
assert_eq!(Tokens::accounts(BRUCE, RelayCurrencyId::get()).free, INIT_BALANCE);
assert_eq!(Tokens::accounts(BRUCE, RelayCurrencyId::get()).frozen, 0);
assert_eq!(Tokens::accounts(BRUCE, RelayCurrencyId::get()).reserved, 0);
});
}

#[test]
fn redeem_when_fund_failed_should_work() {
new_test_ext().execute_with(|| {
assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get()));
assert_ok!(Salp::issue(Some(ALICE).into(), BRUCE, 3_000, 100, CONTRIBUTON_INDEX));

assert_ok!(Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000));
assert_ok!(Salp::fund_fail(Some(ALICE).into(), 3_000));
assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000));

#[allow(non_snake_case)]
let (vsToken, vsBond) = Salp::vsAssets(3_000, 1, SlotLength::get());

assert_ok!(<Tokens as MultiCurrency<AccountId>>::transfer(vsToken, &BRUCE, &CATHI, 50));
assert_ok!(<Tokens as MultiCurrency<AccountId>>::transfer(vsBond, &BRUCE, &CATHI, 50));

assert_ok!(Salp::redeem(Some(BRUCE).into(), 3_000, 50));

assert_eq!(Tokens::accounts(BRUCE, vsToken).free, 50);
assert_eq!(Tokens::accounts(BRUCE, vsToken).frozen, 0);
assert_eq!(Tokens::accounts(BRUCE, vsToken).reserved, 0);
assert_eq!(Tokens::accounts(BRUCE, vsBond).free, 50);
assert_eq!(Tokens::accounts(BRUCE, vsBond).frozen, 0);
assert_eq!(Tokens::accounts(BRUCE, vsBond).reserved, 0);

assert_ok!(Salp::redeem(Some(CATHI).into(), 3_000, 50));

assert_eq!(Tokens::accounts(CATHI, vsToken).free, 50);
assert_eq!(Tokens::accounts(CATHI, vsToken).frozen, 0);
assert_eq!(Tokens::accounts(CATHI, vsToken).reserved, 0);
assert_eq!(Tokens::accounts(CATHI, vsBond).free, 50);
assert_eq!(Tokens::accounts(CATHI, vsBond).frozen, 0);
assert_eq!(Tokens::accounts(CATHI, vsBond).reserved, 0);
});
}
Loading

0 comments on commit 9d7127a

Please sign in to comment.