Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vortex token distribution #726

Merged
merged 7 commits into from
Nov 12, 2023
Merged

vortex token distribution #726

merged 7 commits into from
Nov 12, 2023

Conversation

leo-plugdefi
Copy link
Collaborator

@leo-plugdefi leo-plugdefi commented Oct 27, 2023

  • add multisig in runtime
  • configure runtime
  • rebase on latest main when ready (including upgrade dependencies)
  • code improvement
  • proper benchmark & weight will be triggered in workflow
  • e2e test script

This pallet is for automatic vortex calculation, distribution.
Due to the time restraint staking related calculation is done offchain and reward data has to be fed by privileged account.
Asset price is also set by privileged account.

pub struct Pallet<T>(_);

#[pallet::storage]
#[pallet::getter(fn vtx_dist_statuses)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we try avoid using storage getters due to the fact there is more than 1 way to access storage (and hence makes it more difficult to search for all storage references).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment applies for the storage getters below

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was told these have been changed.

impl<T: Config> Pallet<T> {
#[pallet::weight(<T as pallet::Config>::WeightInfo::list_vtx_dist())]
#[transactional]
pub fn list_vtx_dist(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should probably rename this extrinsic; it's not a list operation - but instead performs state changes.
maybe;

Suggested change
pub fn list_vtx_dist(
pub fn set_vortex_amount(

@ken-futureverse
Copy link
Contributor

Add screenshot to explain the current functionality of the Vortex pallet

Screenshot 2023-11-01 at 14 00 16

Copy link
Contributor

@JasonTulp JasonTulp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Leo, have had a chance to look through and I can't see any major flaws in the logic so far. A few things are missing from this PR which may already be addressed but thought I'd mention them as they weren't mentioned in your list.

  • Doc comments are missing throughout. They should be on every extrinsic, event, error config trait and helper function
  • Missing unit tests, every extrinsic should have a unit test for the best case and all failure cases
  • Input parameters for some extrinsics are unbounded (i.e. using Vec instead of BoundedVec)
  • Events should be declared with named parameters. i.e.
    VtxDistEnabled{ identifier: T::VtxDistIdentifier }
  • Benchmarks should include the verify part where applicable

Once the full changes are in place I will give a more complete review and pick out any smaller issues that may still be present.


#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::without_storage_info]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to quote We need this as there are a couple of storages use which doesn't implement MaxEncodedLen. Most of them are updated to use BoundedVec, but still get one leftover - VtxDistPayoutPivot, needs to find a proper size limitation for the length. With this macro, it'd stop propogate metadata for the pallet storage which would reduce WASM size a bit.

Copy link
Contributor

@surangap surangap Nov 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, the question is more about whether can we remove it. Even though substrate state db raw key length can have varying lengths, we should be able to define a bound for this case considering that it's going to be raw keys from VtxDistOrderbook that get stored in VtxDistPayoutPivot?
If we plan to migrate to a bounded vec once it's deployed, there might be migrations involved. we should do it now if we can

@leo-plugdefi leo-plugdefi force-pushed the feat/vortex-token-distribution branch 2 times, most recently from 2f1f25e to 6f3d254 Compare November 4, 2023 10:57
runtime/src/constants.rs Outdated Show resolved Hide resolved
runtime/src/lib.rs Outdated Show resolved Hide resolved
@leo-plugdefi leo-plugdefi force-pushed the feat/vortex-token-distribution branch from 6f3d254 to cb647ff Compare November 8, 2023 03:48
@leo-plugdefi
Copy link
Collaborator Author

Hey Leo, have had a chance to look through and I can't see any major flaws in the logic so far. A few things are missing from this PR which may already be addressed but thought I'd mention them as they weren't mentioned in your list.

* Doc comments are missing throughout. They should be on every extrinsic, event, error config trait and helper function

* Missing unit tests, every extrinsic should have a unit test for the best case and all failure cases

* Input parameters for some extrinsics are unbounded (i.e. using Vec instead of BoundedVec)

* Events should be declared with named parameters. i.e.
  `VtxDistEnabled{ identifier: T::VtxDistIdentifier }`

* Benchmarks should include the verify part where applicable

Once the full changes are in place I will give a more complete review and pick out any smaller issues that may still be present.

I was told these has been addressed

Comment on lines 349 to 352
// let total_vortex: <T as Config>::CurrencyBalance =
// T::MultiCurrency::total_issuance(T::VtxAssetId::get()); log::warn!("
// payed, vortex_balance is {:?}, total_vortex is {:?}", share,
// total_vortex);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit - commented code

});
});

const finalizeTx = async (signer: KeyringPair, extrinsic: SubmittableExtrinsic<"promise">) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not import the function from utils?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't run this test; but is this test repeatable?
Ideally all tests should be repeatable if possible - otherwise it's really hard to debug broken tests since that would require node restarts :/

@@ -45,7 +45,7 @@ const config: HardhatUserConfig = {
apiKey: process.env.ETHERSCAN_API_KEY,
},
mocha: {
timeout: 120_000, // global tests timeout
timeout: 12_000_000, // global tests timeout
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the unit for this? 12,000s? does this not seem very long?

Comment on lines 203 to 204
assert_eq!(AssetsExt::balance(usdc, &bob), 500_000);
assert_eq!(Balances::free_balance(&bob), 500_000);
Copy link
Contributor

@surangap surangap Nov 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add the calculation or a comment.
also a test/check for root reward?

parameter_types! {
pub const VortexPalletId: PalletId = PalletId(*b"root/vtx");
pub const UnsignedInterval: BlockNumber = MINUTES / 2;
pub const PayoutBatchSize: u32 = 799;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any specific reason for 799 ?

Copy link
Contributor

@JasonTulp JasonTulp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Leo, I've had a thorough look through with the focus on determining which tests need to be added as there are still not nearly enough unit tests to meet the standard we adhere by in our PRs. I am in the process of testing these changes locally so will add more comments if anything else comes up.

One other point outside of lack of unit tests is would we not need a migration for the Vortex AssetId? I see it is created in the genesis config, however when updating the runtime this will not be set. I think we'd need a small migration with all the metadata for the Vortex token and any initial issuance

Below are the tests that are missing from this PR:

create_vst_dist extrinsic:

  • Failure case for if called from not the VtxDistAdmin
  • Isolated success case for ensuring storage is updated and event thrown
  • Failure case for if VtxDistStatuses does not contain the VtxDistIdentifier

disable_vtx_dist extrinsic:

  • Failure case for if called from not the VtxDistAdmin
  • Isolated success case for ensuring storage is updated and event thrown

start_vtx_dist extrinsic:

  • Failure case for if called from not the VtxDistAdmin
  • Isolated success case for ensuring storage is updated and event thrown

pay_unsigned:

  • Failure case for if called by a signed address
  • Ensure payment does not happen if the VtxDistStatus is not == VtxDistStatus::Paying
  • Ensure payment does not happen if there is no vault account
  • Ensure the correct accounts are paid out based on the VtxDistPayoutPivot. This should
    ensure the orderbook is updated accordingly
    • This should also ensure the VtxDistPaidOut event is thrown for every account we
      expect to be paid out and the VtxDistDone event is thrown when complete
    • Should also do a test that spans multiple payout blocks. i.e.e the next_unsigned_at block is then checked and the remainder paid out
    • There should be a separate test for both with and without a start key

set_vtx_dist_eras extrinsic:

  • Failure case for if called from not the VtxDistAdmin
  • Isolated success case for ensuring storage is properly updated (This extrinsic also needs an event to be thrown)
  • Failure case when the start_era is greater then the end_era

set_asset_prices extrinsic:

  • Failure case for if called from not the VtxDistAdmin
  • Isolated success case for ensuring storage is properly updated for all assets (This extrinsic also needs an event to be thrown)

register_rewards extrinsic:

  • Failure case for if called from not the VtxDistAdmin
  • Isolated success case for ensuring storage is updated and RewardRegistered event thrown for each reward
    • Rewards should be the size of MaxRewards
  • Failure case for when VtxDistStatus is not enabled

trigger_vtx_distribution extrinsic:

  • Failure case for if called from not the VtxDistAdmin
  • Failure case for if there is no vault account
  • Isolated success case for ensuring all assets + root are distributed from the fee_vault to the new vault account based on vault balance
    • This should ensure the fee_vault does not have any balance left after the transfer
    • This should be tested with multiple assets set for AssetPrices (Including XRP and custom assets)
    • This extrinsic also needs an event to be thrown

redeem_tokens_from_vault extrinsic:

  • Failure case for if there is no vault account
  • Test case for if the total issuance of the VtxAsset is zero, it looks as if it would attempt to divide by zero in this case which would panic. We should not let this happen
  • Failure case for if the vortex_balance is less than zero
  • Isolated success case that ensures the correct assets are transferred from the vault account to the signer
    • This should also check that the correct amount of the vortex asset is burnt
    • This extrinsic should throw an event that should be asserted here

VtxDistStatuses::<T>::mutate(id, |status| {
*status = VtxDistStatus::NotEnabled;
});
// clean ledger when necessary
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to be a TODO, is the cleaning logic missing?

@@ -644,3 +644,22 @@ export const executeForPreviousEvent = async (
currentInHistory++;
}
};

export const loadTestUsers = (userAmount?: number): KeyringPair[] => {
const content = fs.readFileSync(path.resolve(__dirname, "./generated_users.txt"), "utf-8");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why hardcode the generated user list? Could we not instead create random accounts and store them when the test is run?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed here; shouldn't be load testing in CI

pallet/vortex-distribution/src/lib.rs Show resolved Hide resolved
pallet/vortex-distribution/src/lib.rs Show resolved Hide resolved
pallet/vortex-distribution/src/lib.rs Show resolved Hide resolved
pallet/vortex-distribution/src/lib.rs Show resolved Hide resolved
asset_prices: BoundedVec<(AssetId, BalanceOf<T>), T::MaxAssetPrices>,
id: T::VtxDistIdentifier,
) -> DispatchResultWithPostInfo {
for (asset_id, price) in asset_prices {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if the asset_id is the vortex token asset_id? Should we allow the price to be set or fail?

///
/// `asset_prices` - List of asset prices
/// `id` - The distribution id
#[pallet::weight(<T as pallet::Config>::WeightInfo::set_asset_prices())]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit. ideally the benchmark for setting asset prices should take into account the length of asset_prices

let asset_id = mint_asset::<T>(&fee_vault, &root_vault);
assert_ok!(VortexDistribution::<T>::create_vtx_dist(RawOrigin::Root.into(), vortex_dist_id, amount));
let balance = <T as pallet_staking::Config>::CurrencyBalance::one();
let asset_prices = BoundedVec::try_from(vec![(asset_id, balance.into())]).unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This benchmark only takes into account one asset set in asset_prices. This is an issue as the majority of reads and writes are determined by the length of AssetPrices. Although it's not possible to determine the number of assets in the benchmark, we should mock a higher number of assets in this benchmark (Say 3-5) so that we can more accurately cover the cost of redeeming multiple assets

type UnsignedInterval = UnsignedInterval;
type PayoutBatchSize = PayoutBatchSize;
type VtxDistIdentifier = u32;
type VtxDistAdminOrigin = EnsureRoot<AccountId>; //TODO: change to proper committee later on
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO, should this be the Multisig pallet?

@JasonTulp
Copy link
Contributor

One more thing that is cause for concern is that the pay_unsigned transaction does not take into account the weight of paying out up to 799 accounts. I attempted to multiply the weight by 799 as the worst case but this exhausted block limits which tells me that this limit is too high.
My suggestion would be that we set the PayoutBatchSize to 100 (20% of the block weight) and measure weight as if 100 accounts were being paid out in the benchmark

@surangap
Copy link
Contributor

One more thing that is cause for concern is that the pay_unsigned transaction does not take into account the weight of paying out up to 799 accounts. I attempted to multiply the weight by 799 as the worst case but this exhausted block limits which tells me that this limit is too high. My suggestion would be that we set the PayoutBatchSize to 100 (20% of the block weight) and measure weight as if 100 accounts were being paid out in the benchmark

yes, also the benchmark should take the variability into account. PayoutBatchSize should be taken as the bound when writing benchmarks. A similar benchmark can be found here ->

register_delegate_with_signature {

@leo-plugdefi leo-plugdefi force-pushed the feat/vortex-token-distribution branch 2 times, most recently from 51528cb to e55d8ed Compare November 12, 2023 11:57
@zees-dev
Copy link
Contributor

  • rebase over main required
  • the issues in the CI need to be addressed

@leo-plugdefi leo-plugdefi force-pushed the feat/vortex-token-distribution branch from 017c8b4 to 87caa39 Compare November 12, 2023 20:55
Comment on lines +58 to +71
txs = [api.tx.assetsExt.createAsset("test1", "TEST1", 6, 1, alith.address)];
await finalizeTx(alith, api.tx.utility.batch(txs));

TOKEN_ID_2 = await getNextAssetId(api);
txs = [api.tx.assetsExt.createAsset("test2", "TEST2", 6, 1, alith.address)];
await finalizeTx(alith, api.tx.utility.batch(txs));

TOKEN_ID_3 = await getNextAssetId(api);
txs = [api.tx.assetsExt.createAsset("test3", "TEST3", 6, 1, alith.address)];
await finalizeTx(alith, api.tx.utility.batch(txs));

TOKEN_ID_4 = await getNextAssetId(api);
txs = [api.tx.assetsExt.createAsset("test4", "TEST4", 6, 1, alith.address)];
await finalizeTx(alith, api.tx.utility.batch(txs));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: these are all single transactions that we await for; why batch

// and would redeem afterwards.
// There will be 2 same vortex distributions happening right after.
let txs = [api.tx.assets.mint(VORTEX_ID, alith.address, mintAmount)];
await finalizeTx(alith, api.tx.utility.batch(txs));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here; no need to batch


// create vortex distribution
const vortexDistributionId1 = await api.query.vortexDistribution.nextVortexId();
console.log(`vortexDistributionId1: ${vortexDistributionId1}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: ideally we try to avoid console logs in the tests as it pollutes CI output - making it hard to resolve broken tests in CI

@@ -45,7 +45,7 @@ const config: HardhatUserConfig = {
apiKey: process.env.ETHERSCAN_API_KEY,
},
mocha: {
timeout: 120_000, // global tests timeout
timeout: 6_000_000, // global tests timeout
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the timeout shouldnt be this high; this implies 6000 seconds right - which is 100 minutes.
CI should never be that long.
It would. be best to identify the long test and reduce the timing of it (test on a subset or something).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah agreed here, I believe the root cause of this is because they seem want to do a load test inside this CI, which is not ideal. I will ask them to update this e2e folder later today due to the time difference.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I think the load testing should be separated from the usual CI flow which aims at regression testing.

Copy link
Contributor

@JasonTulp JasonTulp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, thanks for addressing all my concerns from the previous review. There are still some minor things that need fixing in follow up PRs but I'm happy to merge this

register_rewards {
let vortex_dist_id = NextVortexId::<T>::get();
let amount = 1_000_000u32.into();
let rewards = BoundedVec::try_from(vec![(account::<T>("test"), 100u32.into())]).unwrap(); assert_ok!(VortexDistribution::<T>::create_vtx_dist(RawOrigin::Root.into(), amount));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what happened here but looks like these lines have merged into one

Suggested change
let rewards = BoundedVec::try_from(vec![(account::<T>("test"), 100u32.into())]).unwrap(); assert_ok!(VortexDistribution::<T>::create_vtx_dist(RawOrigin::Root.into(), amount));
let rewards = BoundedVec::try_from(vec![(account::<T>("test"), 100u32.into())]).unwrap();
assert_ok!(VortexDistribution::<T>::create_vtx_dist(RawOrigin::Root.into(), amount));

@leo-plugdefi leo-plugdefi changed the title WIP: draft impelementatino of vortex token distribution vortex token distribution Nov 12, 2023
@zees-dev zees-dev mentioned this pull request Nov 12, 2023
4 tasks
Copy link
Contributor

@surangap surangap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good for a merge.

@JasonTulp JasonTulp merged commit 70a69e6 into main Nov 12, 2023
2 checks passed
@JasonTulp JasonTulp deleted the feat/vortex-token-distribution branch November 12, 2023 23:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants