Skip to content
Snippets Groups Projects
Commit 6100fb14 authored by Marcio Diaz's avatar Marcio Diaz Committed by GitHub
Browse files

Introduce rebond (#4374)

* Implement rebond: allowing to re-bond stake unbonded.
parent 69245901
No related merge requests found
......@@ -78,8 +78,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
// and set impl_version to equal spec_version. If only runtime
// implementation changes and behavior does not, then leave spec_version as
// is and increment impl_version.
spec_version: 199,
impl_version: 199,
spec_version: 200,
impl_version: 200,
apis: RUNTIME_API_VERSIONS,
};
......
......@@ -395,7 +395,7 @@ pub struct StakingLedger<AccountId, Balance: HasCompact> {
impl<
AccountId,
Balance: HasCompact + Copy + Saturating,
Balance: HasCompact + Copy + Saturating + SimpleArithmetic,
> StakingLedger<AccountId, Balance> {
/// Remove entries from `unlocking` that are sufficiently old and reduce the
/// total by the sum of their balances.
......@@ -412,6 +412,30 @@ impl<
Self { total, active: self.active, stash: self.stash, unlocking }
}
/// Re-bond funds that were scheduled for unlocking.
fn rebond(mut self, value: Balance) -> Self {
let mut unlocking_balance: Balance = Zero::zero();
while let Some(last) = self.unlocking.last_mut() {
if unlocking_balance + last.value <= value {
unlocking_balance += last.value;
self.active += last.value;
self.unlocking.pop();
} else {
let diff = value - unlocking_balance;
unlocking_balance += diff;
self.active += diff;
last.value -= diff;
}
if unlocking_balance >= value {
break
}
}
self
}
}
impl<AccountId, Balance> StakingLedger<AccountId, Balance> where
......@@ -804,6 +828,8 @@ decl_error! {
InsufficientValue,
/// Can not schedule more unlock chunks.
NoMoreChunks,
/// Can not rebond without unlocking chunks.
NoUnlockChunk,
}
}
......@@ -959,6 +985,26 @@ decl_module! {
}
}
/// Rebond a portion of the stash scheduled to be unlocked.
///
/// # <weight>
/// - Time complexity: O(1). Bounded by `MAX_UNLOCKING_CHUNKS`.
/// - Storage changes: Can't increase storage, only decrease it.
/// # </weight>
#[weight = SimpleDispatchInfo::FixedNormal(500_000)]
fn rebond(origin, #[compact] value: BalanceOf<T>) {
let controller = ensure_signed(origin)?;
let ledger = Self::ledger(&controller).ok_or(Error::<T>::NotController)?;
ensure!(
ledger.unlocking.len() > 0,
Error::<T>::NoUnlockChunk,
);
let ledger = ledger.rebond(value);
Self::update_ledger(&controller, &ledger);
}
/// Remove any unlocked chunks from the `unlocking` queue from our management.
///
/// This essentially frees up that balance to be used by the stash account to do
......
......@@ -1196,6 +1196,146 @@ fn too_many_unbond_calls_should_not_work() {
})
}
#[test]
fn rebond_works() {
// * Should test
// * Given an account being bonded [and chosen as a validator](not mandatory)
// * it can unbond a portion of its funds from the stash account.
// * it can re-bond a portion of the funds scheduled to unlock.
ExtBuilder::default()
.nominate(false)
.build()
.execute_with(|| {
// Set payee to controller. avoids confusion
assert_ok!(Staking::set_payee(
Origin::signed(10),
RewardDestination::Controller
));
// Give account 11 some large free balance greater than total
let _ = Balances::make_free_balance_be(&11, 1000000);
// confirm that 10 is a normal validator and gets paid at the end of the era.
start_era(1);
// Initial state of 10
assert_eq!(
Staking::ledger(&10),
Some(StakingLedger {
stash: 11,
total: 1000,
active: 1000,
unlocking: vec![],
})
);
start_era(2);
assert_eq!(Staking::current_era(), 2);
// Try to rebond some funds. We get an error since no fund is unbonded.
assert_noop!(
Staking::rebond(Origin::signed(10), 500),
Error::<Test>::NoUnlockChunk,
);
// Unbond almost all of the funds in stash.
Staking::unbond(Origin::signed(10), 900).unwrap();
assert_eq!(
Staking::ledger(&10),
Some(StakingLedger {
stash: 11,
total: 1000,
active: 100,
unlocking: vec![UnlockChunk {
value: 900,
era: 2 + 3
},]
})
);
// Re-bond all the funds unbonded.
Staking::rebond(Origin::signed(10), 900).unwrap();
assert_eq!(
Staking::ledger(&10),
Some(StakingLedger {
stash: 11,
total: 1000,
active: 1000,
unlocking: vec![],
})
);
// Unbond almost all of the funds in stash.
Staking::unbond(Origin::signed(10), 900).unwrap();
assert_eq!(
Staking::ledger(&10),
Some(StakingLedger {
stash: 11,
total: 1000,
active: 100,
unlocking: vec![UnlockChunk { value: 900, era: 5 }],
})
);
// Re-bond part of the funds unbonded.
Staking::rebond(Origin::signed(10), 500).unwrap();
assert_eq!(
Staking::ledger(&10),
Some(StakingLedger {
stash: 11,
total: 1000,
active: 600,
unlocking: vec![UnlockChunk { value: 400, era: 5 }],
})
);
// Re-bond the remainder of the funds unbonded.
Staking::rebond(Origin::signed(10), 500).unwrap();
assert_eq!(
Staking::ledger(&10),
Some(StakingLedger {
stash: 11,
total: 1000,
active: 1000,
unlocking: vec![]
})
);
// Unbond parts of the funds in stash.
Staking::unbond(Origin::signed(10), 300).unwrap();
Staking::unbond(Origin::signed(10), 300).unwrap();
Staking::unbond(Origin::signed(10), 300).unwrap();
assert_eq!(
Staking::ledger(&10),
Some(StakingLedger {
stash: 11,
total: 1000,
active: 100,
unlocking: vec![
UnlockChunk { value: 300, era: 5 },
UnlockChunk { value: 300, era: 5 },
UnlockChunk { value: 300, era: 5 },
]
})
);
// Re-bond part of the funds unbonded.
Staking::rebond(Origin::signed(10), 500).unwrap();
assert_eq!(
Staking::ledger(&10),
Some(StakingLedger {
stash: 11,
total: 1000,
active: 600,
unlocking: vec![
UnlockChunk { value: 300, era: 5 },
UnlockChunk { value: 100, era: 5 },
]
})
);
})
}
#[test]
fn slot_stake_is_least_staked_validator_and_exposure_defines_maximum_punishment() {
// Test that slot_stake is determined by the least staked validator
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment