Skip to content

Commit

Permalink
feat(dapp-staking): Implement stake movement functionality (AstarNetw…
Browse files Browse the repository at this point in the history
…ork#1379)

Implements basic stake movement functionality with:
- Add move_stake extrinsic with appropriate weight handling
- Implement registration checks via is_registered helper
- Add appropriate error handling for move operations
- Add StakeMoved and MovesReset events
- Add tests for registration validation and move mechanics
- Add move_stake weights to all runtimes

The implementation provides the foundation for the complete
bonus moves system, with:
- Contract validation
- Stake transfer logic
- Move counter initialization
- Event emission

Next steps:
- Implement move counter decrement logic
- Add period transition handling
- Enhance documentation

Part of AstarNetwork#1379
  • Loading branch information
sylvaincormier committed Nov 18, 2024
1 parent 154ab84 commit 60bd9a5
Show file tree
Hide file tree
Showing 6 changed files with 341 additions and 117 deletions.
201 changes: 109 additions & 92 deletions pallets/dapp-staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,11 +318,20 @@ pub mod pallet {
ExpiredEntriesRemoved { account: T::AccountId, count: u16 },
/// Privileged origin has forced a new era and possibly a subperiod to start from next block.
Force { forcing_type: ForcingType },
/// Stake was moved between contracts
StakeMoved {
staker: T::AccountId,
from_contract: T::SmartContract,
to_contract: T::SmartContract,
amount: Balance,
remaining_moves: u8,
},

/// Move counter was reset at period boundary
MovesReset {
staker: T::AccountId,
contract: T::SmartContract,
new_count: u8,
},
}

Expand Down Expand Up @@ -403,6 +412,17 @@ pub mod pallet {
InvalidTargetContract,
InvalidAmount,
NoStakeFound,
/// No safe moves remaining for this period
NoSafeMovesRemaining,

/// Cannot move stake between same contract
SameContract,

/// Cannot move zero amount
ZeroMoveAmount,

/// Cannot move more than currently staked
InsufficientStakedAmount,
}

/// General information about dApp staking protocol state.
Expand Down Expand Up @@ -1518,106 +1538,103 @@ pub mod pallet {

/// Used to claim bonus reward for a smart contract on behalf of the specified account, if eligible.
#[pallet::call_index(21)]
#[pallet::weight(10_000)]
pub fn move_stake(
origin: OriginFor<T>,
from_smart_contract: T::SmartContract,
to_smart_contract: T::SmartContract,
amount: Balance,
) -> DispatchResult {
let who = ensure_signed(origin)?;

// Ensure contracts are different and registered
ensure!(
from_smart_contract != to_smart_contract,
Error::<T>::InvalidTargetContract
);
ensure!(
Self::is_registered(&from_smart_contract),
Error::<T>::ContractNotFound
);
ensure!(
Self::is_registered(&to_smart_contract),
Error::<T>::ContractNotFound
);

// Amount validation
ensure!(!amount.is_zero(), Error::<T>::InvalidAmount);

let protocol_state = ActiveProtocolState::<T>::get();
let current_era = protocol_state.era;
let period_info = protocol_state.period_info;

// Reduce stake on source contract
StakerInfo::<T>::try_mutate_exists(
&who,
&from_smart_contract,
|maybe_staker_info| -> DispatchResult {
let mut staker_info =
maybe_staker_info.take().ok_or(Error::<T>::NoStakeFound)?;
ensure!(
staker_info.staked.total() >= amount,
Error::<T>::InsufficientStakeAmount
);
#[pallet::weight(T::WeightInfo::move_stake())]
pub fn move_stake(
origin: OriginFor<T>,
from_smart_contract: T::SmartContract,
to_smart_contract: T::SmartContract,
amount: Balance,
) -> DispatchResult {
let who = ensure_signed(origin)?;

ensure!(
from_smart_contract != to_smart_contract,
Error::<T>::InvalidTargetContract
);
ensure!(
Self::is_registered(&from_smart_contract),
Error::<T>::ContractNotFound
);
ensure!(
Self::is_registered(&to_smart_contract),
Error::<T>::ContractNotFound
);
ensure!(!amount.is_zero(), Error::<T>::InvalidAmount);

let protocol_state = ActiveProtocolState::<T>::get();
let current_era = protocol_state.era;
let period_info = protocol_state.period_info;

// Reduce stake on source contract
StakerInfo::<T>::try_mutate_exists(
&who,
&from_smart_contract,
|maybe_staker_info| -> DispatchResult {
let mut staker_info =
maybe_staker_info.take().ok_or(Error::<T>::NoStakeFound)?;
ensure!(
staker_info.staked.total() >= amount,
Error::<T>::InsufficientStakeAmount
);

staker_info.staked.subtract(amount);
staker_info.staked.subtract(amount);

// Update or remove the staking info
if staker_info.staked.total().is_zero() {
*maybe_staker_info = None;
} else {
*maybe_staker_info = Some(staker_info);
// Update or remove the staking info
if staker_info.staked.total().is_zero() {
*maybe_staker_info = None;
} else {
*maybe_staker_info = Some(staker_info);
}
Ok(())
},
)?;

// Apply stake to target contract
StakerInfo::<T>::try_mutate(
&who,
&to_smart_contract,
|maybe_staker_info| -> DispatchResult {
match maybe_staker_info {
Some(info) => {
// Add to existing stake
info.staked.add(amount, protocol_state.subperiod());
info.bonus_status = BonusStatus::SafeMovesRemaining(
T::MaxBonusMovesPerPeriod::get().into()
);
}
Ok(())
},
)?;

// Apply stake to target contract
StakerInfo::<T>::try_mutate(
&who,
&to_smart_contract,
|maybe_staker_info| -> DispatchResult {
match maybe_staker_info {
Some(info) => {
// Add to existing stake
info.staked.add(amount, protocol_state.subperiod());
info.bonus_status = BonusStatus::SafeMovesRemaining(
T::MaxBonusMovesPerPeriod::get().into(),
);
}
None => {
// Create new stake entry
let mut new_info = SingularStakingInfo::new(
period_info.number,
protocol_state.subperiod(),
);
new_info.staked.add(amount, protocol_state.subperiod());
new_info.bonus_status = BonusStatus::SafeMovesRemaining(
T::MaxBonusMovesPerPeriod::get().into(),
);
*maybe_staker_info = Some(new_info);
}
None => {
// Create new stake entry
let mut new_info = SingularStakingInfo::new(
period_info.number,
protocol_state.subperiod(),
);
new_info.staked.add(amount, protocol_state.subperiod());
new_info.bonus_status = BonusStatus::SafeMovesRemaining(
T::MaxBonusMovesPerPeriod::get().into()
);
*maybe_staker_info = Some(new_info);
}
Ok(())
},
)?;

// Emit event
Self::deposit_event(Event::StakeMoved {
staker: who,
from_contract: from_smart_contract,
to_contract: to_smart_contract,
amount,
});

Ok(())
}
}
Ok(())
},
)?;

// Emit event
Self::deposit_event(Event::StakeMoved {
staker: who,
from_contract: from_smart_contract,
to_contract: to_smart_contract,
amount,
remaining_moves: T::MaxBonusMovesPerPeriod::get(),
});

Ok(())
}
}

impl<T: Config> Pallet<T> {
pub fn is_registered(contract: &T::SmartContract) -> bool {
//TODO: Implement this
true
IntegratedDApps::<T>::contains_key(contract)
}
/// `true` if the account is a staker, `false` otherwise.
pub fn is_staker(account: &T::AccountId) -> bool {
Expand Down
Loading

0 comments on commit 60bd9a5

Please sign in to comment.