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

feat(dAppStaking): Move actions mechanisms for bonus rewards #1402

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

ipapandinas
Copy link
Contributor

@ipapandinas ipapandinas commented Jan 3, 2025

⚠️ A fix for the code coverage job is proposed here: #1408

Pull Request Summary

This PR introduces enhancements to the bonus reward eligibility mechanism and implements robust handling for stake movement, particularly during the build&earn subperiod.

Bonus Reward Eligibility

The previously used loyal_staker flag is replaced with and decoded into a bonus_status enum:

enum BonusStatus {
    /// Bonus rewards are forfeited.
    BonusForfeited,
    /// Bonus rewards are preserved with a value which is the number of remaining 'safe moves' allowed in the ongoing B&E subperiod.
    SafeMovesRemaining(u8),
}

This update:

  • Introduces tracking of bonus status to ensure eligibility based on the number of allowed move actions (MaxBonusMovesPerPeriod).
  • Enforces forfeiture of bonus eligibility if move actions exceed the allowable threshold.

Move action

A move action is defined as either:

  • Partial unstaking that reduces the 'voting stake'.
  • Transfers of 'voting stake' between contracts.

If a move action is performed during the build&earn subperiod, the bonus_status attached to the relevant singular staking info is reduced until it is forfeited.

Stake Movement Enhancements

A new move_stake extrinsic is implemented. It is similar to an 'unstake' followed by two 'stakes' for each subperiod. Existing safeguards are extended additional checks to:

  • Ensure the source and destination contracts are different.
  • Validate the amount to be moved.

When moving stake from unregistered contracts, the bonus status is always preserved.

Migration

A multi-block migration is proposed to convert all existing loyal_staker flags to their respective BonusStatus variants:

  • BonusStatus::SafeMovesRemaining(0) for true.
  • BonusStatus::BonusForfeited for false.

Closes #1379

Check list

  • added or updated unit tests
  • updated Astar official documentation
  • added OnRuntimeUpgrade hook for precompile revert code registration
  • added benchmarks & weights for any modified runtime logics.
  • add E2E tests
  • prepare follow-up issue for Astar Portal frontend & forum post

@ipapandinas ipapandinas added enhancement New feature or request dapps-staking Dapps Staking labels Jan 3, 2025
@ipapandinas ipapandinas marked this pull request as ready for review January 13, 2025 18:19
@ipapandinas
Copy link
Contributor Author

/bench shibuya-dev pallet_dapp_staking

Copy link

Benchmarks job is scheduled at https://github.com/AstarNetwork/Astar/actions/runs/12753557202.
Please wait for a while.
Branch: feat/move_bonus_dapp_staking
SHA: d94b330

@ipapandinas ipapandinas changed the title WIP - feat(dAppStaking): Move actions mechanisms for bonus rewards feat(dAppStaking): Move actions mechanisms for bonus rewards Jan 13, 2025
Copy link

Benchmarks have been finished.
You can download artifacts if exists https://github.com/AstarNetwork/Astar/actions/runs/12753557202.

@ipapandinas ipapandinas added shibuya related to shibuya runtime This PR/Issue is related to the topic “runtime”. and removed shibuya related to shibuya labels Jan 13, 2025
Copy link
Member

@Dinonard Dinonard left a comment

Choose a reason for hiding this comment

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

Did a high level review of the most important components.

Overall impression is that we should simplify the solution.
This adds a lot of new code, that is a functional duplicate of what we already have.

@@ -157,7 +160,23 @@ If unstake would reduce the staked amount below `MinimumStakeAmount`, everything

Once period finishes, all stakes are reset back to zero. This means that no unstake operation is needed after period ends to _unstake_ funds - it's done automatically.

If dApp has been unregistered, a special operation to unstake from unregistered contract must be used.
During the `build&earn` subperiod, if unstaking reduces the voting stake, the bonus status will be updated, and the number of allowed _move actions_ for the ongoing period will be reduced.
Copy link
Member

Choose a reason for hiding this comment

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

Minor comment - for the sake of consistency, let's go with Build&Earn.


- The destination contract must be different from the source contract.
- The user must ensure that unclaimed rewards are claimed before initiating a stake move.
- Only a limited number of move actions (defined by `MaxBonusMovesPerPeriod`) are allowed during the `build&earn` subperiod to preserve bonus reward eligibility (check "Claiming Bonus Reward" section below).
Copy link
Member

Choose a reason for hiding this comment

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

Suggestion - "allowed during a single period"

/// retaining eligibility for bonus rewards. Exceeding this limit will result in the
/// forfeiture of the bonus rewards for the affected stake.
#[pallet::constant]
type MaxBonusMovesPerPeriod: Get<u8> + Default + Debug + Clone;
Copy link
Member

Choose a reason for hiding this comment

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

Why are Default + Debug + Clone needed?

@@ -290,7 +296,7 @@ pub mod pallet {
era: EraNumber,
amount: Balance,
},
/// Bonus reward has been paid out to a loyal staker.
/// Bonus reward has been paid out to a 'loyal' staker.
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we can change the nomenclature to something like "engaged" or "early".

origin: OriginFor<T>,
source_contract: T::SmartContract,
destination_contract: T::SmartContract,
maybe_amount: Option<Balance>,
Copy link
Member

Choose a reason for hiding this comment

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

My suggestion is to remove this because it's something that can easily be handled by the outside logic.

Reading the staked amount is easy for the frontend part, so no need to have any special logic in runtime to handle this. In case of unregistered contract - the whole amount can simply be ignored and moved to the new contract.

Comment on lines +1010 to +1017
pub enum BonusStatus<MaxBonusMoves: Get<u8>> {
/// Bonus rewards are forfeited.
BonusForfeited,
/// Bonus rewards are preserved with a value which is the number of remaining 'safe moves' allowed in the ongoing B&E subperiod.
SafeMovesRemaining(u8),
#[codec(skip)]
_Phantom(PhantomData<MaxBonusMoves>),
}
Copy link
Member

Choose a reason for hiding this comment

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

At the moment we have issues with MBM migrations, that's why we didn't go with async backing on Astar yet.

If this approach is adopted, we'll be blocked with the feature until an adequate fix is provided for that issue.

However, taking the approach with additional enum value, OneMoveRemaining solves the need for any migration whatsoever. Only the "storage version" needs to be bumped.

EDIT:
Or even better - why not just pure u8 here (ofc, it can be wrapped into a struct)? Call the variable "moves_remaining" and that's it?

Comment on lines +1082 to +1087
// Bonus staking is only possible if stake is first made during the voting subperiod.
let bonus_status = if subperiod == Subperiod::Voting {
BonusStatus::<MaxBonusMoves>::default()
} else {
BonusStatus::<MaxBonusMoves>::BonusForfeited
};
Copy link
Member

Choose a reason for hiding this comment

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

If this has to be decided at this point - it might be better to provide the value from the outside.

Current approach seems hacky & incomplete.

Comment on lines +1208 to +1247
/// Transfers stake between contracts while maintaining subperiod-specific allocations, era consistency, and bonus-safe conditions.
pub fn move_stake(
&mut self,
destination: &mut SingularStakingInfo<MaxBonusMoves>,
amount: Balance,
current_era: EraNumber,
subperiod: Subperiod,
) -> (Vec<(EraNumber, Balance)>, StakeAmountMoved) {
let staked_snapshot = self.staked;
let era_and_amount_pairs = self.unstake(amount, current_era, subperiod);

let voting_stake_moved = staked_snapshot.voting.saturating_sub(self.staked.voting);
let build_earn_stake_moved = staked_snapshot
.build_and_earn
.saturating_sub(self.staked.build_and_earn);

// Similar to a stake on destination but for the 2 subperiods
destination.previous_staked = destination.staked;
destination.previous_staked.era = current_era;
if destination.previous_staked.total().is_zero() {
destination.previous_staked = Default::default();
}
destination
.staked
.add(voting_stake_moved, Subperiod::Voting);
destination
.staked
.add(build_earn_stake_moved, Subperiod::BuildAndEarn);

// Moved stake is only valid from the next era so we keep it consistent here
destination.staked.era = current_era.saturating_add(1);

(
era_and_amount_pairs,
StakeAmountMoved {
voting: voting_stake_moved,
build_and_earn: build_earn_stake_moved,
},
)
}
Copy link
Member

Choose a reason for hiding this comment

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

IMHO, this method should be removed.

The move operation is a combination of unstake and stake.
Nothing more.

We should reuse that code as best we can.
If it means modifying the methods footprint(s) - it's fine.
We have plenty of tests for stake & unstake operations, and these should continue to work as they have before.

});

Ok(())
}
Copy link
Member

Choose a reason for hiding this comment

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

General comment about the extrinsic call - we should reuse code more.
This is essentially unstake and stake.

Logic should be extracted from those calls, and reused here.
Something like this: https://github.com/AstarNetwork/astar-frame/blob/polkadot-v0.9.39/frame/dapps-staking/src/pallet/mod.rs#L652

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dapps-staking Dapps Staking enhancement New feature or request runtime This PR/Issue is related to the topic “runtime”.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

dAppStaking - Bonus rewards mechanism rework
2 participants