Skip to content

Commit d2b6706

Browse files
Merge pull request #1472 from galacticcouncil/gigahdx-vote-locks
feat: gigahdx - vote locks
2 parents 3917ff5 + 6f4180d commit d2b6706

13 files changed

Lines changed: 754 additions & 47 deletions

File tree

Cargo.lock

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

integration-tests/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "runtime-integration-tests"
3-
version = "1.90.0"
3+
version = "1.99.0"
44
description = "Integration tests"
55
authors = ["GalacticCouncil"]
66
edition = "2021"

integration-tests/src/gigahdx.rs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1907,6 +1907,28 @@ fn init_legacy_staking() {
19071907
assert_ok!(Staking::initialize_staking(RawOrigin::Root.into()));
19081908
}
19091909

1910+
/// Submit a root-track referendum (trivial remark proposal) and place its
1911+
/// decision deposit. Returns the referendum index.
1912+
fn submit_remark_referendum(submitter: &AccountId, depositor: &AccountId) -> u32 {
1913+
use frame_support::traits::{schedule::DispatchTime, Bounded, StorePreimage};
1914+
use hydradx_runtime::Preimage;
1915+
let call = hydradx_runtime::RuntimeCall::System(frame_system::Call::remark { remark: vec![1, 2, 3] });
1916+
let bounded: Bounded<_, <Runtime as frame_system::Config>::Hashing> = Preimage::bound(call).unwrap();
1917+
let now = System::block_number();
1918+
let ref_index = pallet_referenda::ReferendumCount::<Runtime>::get();
1919+
assert_ok!(Referenda::submit(
1920+
RuntimeOrigin::signed(submitter.clone()),
1921+
Box::new(RawOrigin::Root.into()),
1922+
bounded,
1923+
DispatchTime::At(now + 100),
1924+
));
1925+
assert_ok!(Referenda::place_decision_deposit(
1926+
RuntimeOrigin::signed(depositor.clone()),
1927+
ref_index,
1928+
));
1929+
ref_index
1930+
}
1931+
19101932
#[test]
19111933
fn migrate_should_move_legacy_position_into_gigahdx_when_called() {
19121934
TestNet::reset();
@@ -1971,6 +1993,128 @@ fn migrate_should_refuse_when_no_legacy_position() {
19711993
});
19721994
}
19731995

1996+
// Migration must not launder away a conviction commitment: `migrate` is refused
1997+
// while any conviction vote is still registered against the legacy position. The
1998+
// caller must `remove_vote` first (which applies the conviction lock — including
1999+
// to losing votes — while the legacy position still backs it), then migrate.
2000+
#[test]
2001+
fn migrate_should_be_refused_while_conviction_vote_active_then_succeed_after_removal() {
2002+
TestNet::reset();
2003+
hydra_live_ext(PATH_TO_SNAPSHOT).execute_with(|| {
2004+
let alice: AccountId = ALICE.into();
2005+
let bob: AccountId = BOB.into();
2006+
assert_ok!(Balances::force_set_balance(
2007+
RawOrigin::Root.into(),
2008+
alice.clone(),
2009+
10_000 * UNITS,
2010+
));
2011+
let _ = EVMAccounts::bind_evm_address(RuntimeOrigin::signed(alice.clone()));
2012+
init_legacy_staking();
2013+
fund_bob_for_decision_deposit();
2014+
2015+
assert_ok!(Staking::stake(RuntimeOrigin::signed(alice.clone()), 1_000 * UNITS));
2016+
2017+
let ref_index = submit_remark_referendum(&alice, &bob);
2018+
assert_ok!(ConvictionVoting::vote(
2019+
RuntimeOrigin::signed(alice.clone()),
2020+
ref_index,
2021+
AccountVote::Standard {
2022+
vote: Vote {
2023+
aye: false,
2024+
conviction: Conviction::Locked1x,
2025+
},
2026+
balance: 1_000 * UNITS,
2027+
},
2028+
));
2029+
2030+
// Refused while the vote is registered (previously this leniently allowed
2031+
// finished votes to survive migration, stranding the losing-vote lock).
2032+
assert_noop!(
2033+
GigaHdx::migrate(RuntimeOrigin::signed(alice.clone())),
2034+
pallet_staking::Error::<Runtime>::ExistingVotes
2035+
);
2036+
assert!(pallet_gigahdx::Stakes::<Runtime>::get(&alice).is_none());
2037+
2038+
// Remove the vote, then migration succeeds.
2039+
assert_ok!(ConvictionVoting::remove_vote(
2040+
RuntimeOrigin::signed(alice.clone()),
2041+
Some(0u16),
2042+
ref_index,
2043+
));
2044+
assert_ok!(GigaHdx::migrate(RuntimeOrigin::signed(alice.clone())));
2045+
assert!(
2046+
pallet_gigahdx::Stakes::<Runtime>::get(&alice).is_some(),
2047+
"gigahdx position opened once the vote is cleared"
2048+
);
2049+
});
2050+
}
2051+
2052+
// The security goal end-to-end: a *losing* legacy vote gets conviction-locked
2053+
// when removed before migration, and that lock carries into the gigahdx position
2054+
// — it cannot be laundered away by migrating.
2055+
#[test]
2056+
fn losing_vote_should_be_conviction_locked_before_legacy_migration() {
2057+
TestNet::reset();
2058+
hydra_live_ext(PATH_TO_SNAPSHOT).execute_with(|| {
2059+
let alice: AccountId = ALICE.into();
2060+
let bob: AccountId = BOB.into();
2061+
assert_ok!(Balances::force_set_balance(
2062+
RawOrigin::Root.into(),
2063+
alice.clone(),
2064+
10_000 * UNITS,
2065+
));
2066+
let _ = EVMAccounts::bind_evm_address(RuntimeOrigin::signed(alice.clone()));
2067+
init_legacy_staking();
2068+
fund_bob_for_decision_deposit();
2069+
2070+
assert_ok!(Staking::stake(RuntimeOrigin::signed(alice.clone()), 1_000 * UNITS));
2071+
2072+
let ref_index = submit_remark_referendum(&alice, &bob);
2073+
// Vote AYE — the losing side once the referendum is rejected.
2074+
assert_ok!(ConvictionVoting::vote(
2075+
RuntimeOrigin::signed(alice.clone()),
2076+
ref_index,
2077+
AccountVote::Standard {
2078+
vote: Vote {
2079+
aye: true,
2080+
conviction: Conviction::Locked1x,
2081+
},
2082+
balance: 1_000 * UNITS,
2083+
},
2084+
));
2085+
2086+
// Force the referendum to Rejected so Alice's AYE is on the losing side.
2087+
// `end = now`, so the conviction-lock window is open at the next remove.
2088+
let now = System::block_number();
2089+
pallet_referenda::ReferendumInfoFor::<Runtime>::insert(
2090+
ref_index,
2091+
pallet_referenda::ReferendumInfo::Rejected(now, None, None),
2092+
);
2093+
2094+
// Remove the losing vote while still a legacy staker → legacy hook applies
2095+
// the conviction lock to the loser.
2096+
assert_ok!(ConvictionVoting::remove_vote(
2097+
RuntimeOrigin::signed(alice.clone()),
2098+
Some(0u16),
2099+
ref_index,
2100+
));
2101+
assert_eq!(
2102+
lock_amount(&alice, *b"pyconvot"),
2103+
1_000 * UNITS,
2104+
"losing legacy vote must be conviction-locked on removal"
2105+
);
2106+
2107+
// Migration succeeds and the conviction lock survives — not laundered away.
2108+
assert_ok!(GigaHdx::migrate(RuntimeOrigin::signed(alice.clone())));
2109+
assert!(pallet_gigahdx::Stakes::<Runtime>::get(&alice).is_some());
2110+
assert_eq!(
2111+
lock_amount(&alice, *b"pyconvot"),
2112+
1_000 * UNITS,
2113+
"conviction lock carries into the migrated gigahdx position"
2114+
);
2115+
});
2116+
}
2117+
19742118
#[test]
19752119
fn legacy_stake_should_refuse_when_gigahdx_lock_present() {
19762120
// Strict policy: HDX already pledged under `ghdxlock` cannot back a legacy

0 commit comments

Comments
 (0)