diff --git a/pallets/exodus/Cargo.toml b/pallets/exodus/Cargo.toml index a3fee87..a18d257 100644 --- a/pallets/exodus/Cargo.toml +++ b/pallets/exodus/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ghost-exodus" -version = "0.0.12" +version = "0.0.13" description = "Threshold signature generation with DKG included" license.workspace = true authors.workspace = true diff --git a/pallets/exodus/src/benchmarking.rs b/pallets/exodus/src/benchmarking.rs index e303f97..794014b 100644 --- a/pallets/exodus/src/benchmarking.rs +++ b/pallets/exodus/src/benchmarking.rs @@ -506,7 +506,7 @@ mod benchmarks { QualificationDkgState::::insert(&network_curve, dkg_state); ActiveDkgAuthorities::::insert(&network_curve, active_dkg); ActiveAuthorities::::insert(&network_curve, authorities); - ExodusRequests::::insert(network_curve, exodus_session, request); + ExodusRequests::::insert((network_curve, exodus_session), request); let nonce_package = PackageContext::default() .with_network_curve(NetworkCurve::Secp256k1) @@ -584,7 +584,7 @@ mod benchmarks { QualificationDkgState::::insert(&network_curve, dkg_state); ActiveDkgAuthorities::::insert(&network_curve, active_dkg); ActiveAuthorities::::insert(&network_curve, authorities); - ExodusRequests::::insert(network_curve, exodus_session, request); + ExodusRequests::::insert((network_curve, exodus_session), request); let mut roast_state = RoastSessionState::>::default(); roast_state.status = RoastStatus::GroupCommitments; @@ -723,7 +723,7 @@ mod benchmarks { QualificationDkgState::::insert(&network_curve, dkg_state); ActiveDkgAuthorities::::insert(&network_curve, active_dkg); ActiveAuthorities::::insert(&network_curve, authorities); - ExodusRequests::::insert(network_curve, exodus_session, request); + ExodusRequests::::insert((network_curve, exodus_session), request); let mut roast_state = RoastSessionState::>::default(); roast_state.status = RoastStatus::PartialSignatures; @@ -796,7 +796,7 @@ mod benchmarks { assert!(!consensus_exists); assert!(!committers_exists); - assert!(!ExodusRequests::::contains_key(&network_curve, &exodus_session)); + assert!(!ExodusRequests::::contains_key((&network_curve, &exodus_session))); assert!(ExodusSignedGovernance::::contains_key((&exodus_session, network_id), dkg_index)); Ok(()) @@ -808,7 +808,7 @@ mod benchmarks { let (_, network_curve, _, _) = prepare_pallet::(1usize); let network_id = NetworkIdOf::::default(); - let base_amount: BalanceOf = 10_000u32.into(); + let base_amount: BalanceOf = T::BaseEvmAmount::get().unique_saturated_into(); let amount = base_amount.saturating_add(T::Currency::minimum_balance()); let bounty = Perbill::from_percent(5); @@ -840,7 +840,7 @@ mod benchmarks { receiver, ); - assert!(ExodusRequests::::contains_key(&network_curve, current_exodus_session)); + assert!(ExodusRequests::::contains_key((&network_curve, current_exodus_session))); assert_eq!(CurrentExodus::::get(), current_exodus_session + 1); assert_eq!(T::Currency::balance(&caller), initial_balance - amount); assert_eq!(T::Currency::total_issuance(), total_issuance - amount); diff --git a/pallets/exodus/src/lib.rs b/pallets/exodus/src/lib.rs index 30ebd52..47e37d5 100644 --- a/pallets/exodus/src/lib.rs +++ b/pallets/exodus/src/lib.rs @@ -423,6 +423,9 @@ pub mod pallet { #[pallet::constant] type MaxCursorLen: Get; + #[pallet::constant] + type BaseEvmAmount: Get; + type WeightInfo: WeightInfo; } @@ -502,7 +505,7 @@ pub mod pallet { NoActiveAuthorities, InvalidSignatureShare, VerifyingShareNotFound, - BelowExistentialBalance, + BridgeQueueMinimumNotMet, DkgAuthoritiesInProgress, PackagesAlreadyRegistered, DkgAuthoritiesNotInitialized, @@ -551,10 +554,12 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn exodus_requests)] - pub(super) type ExodusRequests = StorageDoubleMap< + pub(super) type ExodusRequests = CountedStorageNMap< _, - Twox64Concat, NetworkCurve, - Twox64Concat, ExodusSession, + ( + NMapKey, + NMapKey, + ), ExodusRequest, BalanceOf>, OptionQuery, >; @@ -1450,7 +1455,7 @@ pub mod pallet { exodus_request.next_roast_session = roast_session .saturating_add(1); - ExodusRequests::::insert(&network_curve, &exodus_session, exodus_request); + ExodusRequests::::insert((&network_curve, &exodus_session), exodus_request); } RoastSessions::::insert(&exodus_session, &authority_index, roast_session); @@ -1508,7 +1513,7 @@ pub mod pallet { let exodus_session = package.session; ensure!( - ExodusRequests::::contains_key(&network_curve, &exodus_session), + ExodusRequests::::contains_key((&network_curve, &exodus_session)), Error::::ExodusRequestNotFound ); @@ -1640,7 +1645,7 @@ pub mod pallet { ); let exodus_request = - ExodusRequests::::get(&network_curve, &exodus_session) + ExodusRequests::::get((&network_curve, &exodus_session)) .ok_or(Error::::ExodusRequestNotFound)?; ensure!( @@ -1795,7 +1800,7 @@ pub mod pallet { return Ok(()); } - ExodusRequests::::remove(&network_curve, &exodus_session); + ExodusRequests::::remove((&network_curve, &exodus_session)); match exodus_request.r#type { ExodusRequestType::EvmRotation { .. } => { @@ -1838,10 +1843,17 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; - ensure!( - amount > T::Currency::minimum_balance(), - Error::::BelowExistentialBalance - ); + let base_evm_amount: BalanceOf = T::BaseEvmAmount::get() + .unique_saturated_into(); + + let pending_requests_count = ExodusRequests::::count() + .unique_saturated_into(); + + let minimum_amount = base_evm_amount + .saturating_mul(pending_requests_count) + .saturating_add(T::Currency::minimum_balance()); + + ensure!(amount >= minimum_amount, Error::::BridgeQueueMinimumNotMet); let actual_amount = T::Currency::burn_from( &who, @@ -1861,7 +1873,7 @@ pub mod pallet { Self::deposit_event(Event::::EvmBridgeOutRegistered { who, network_id, - amount, + amount: actual_amount, bounty, receiver, }); @@ -2151,7 +2163,7 @@ impl Pallet { let exodus_session = CurrentExodus::::get(); let request = request_builder(exodus_session); - ExodusRequests::::insert(&network.curve, &exodus_session, request); + ExodusRequests::::insert((&network.curve, &exodus_session), request); CurrentExodus::::put(exodus_session.saturating_add(1)); Ok(()) @@ -2566,7 +2578,7 @@ impl Pallet { }; weight.saturating_accrue(T::DbWeight::get().writes(1)); - ExodusRequests::::insert(&network_curve, &exodus_session, exodus_request); + ExodusRequests::::insert((&network_curve, &exodus_session), exodus_request); if let None = dkg_qualification .try_push_rotation_session(exodus_session, network_type) { @@ -2607,7 +2619,7 @@ impl Pallet { let r#type = rotation_session.network_type; weight.saturating_accrue(T::DbWeight::get().reads(1)); - if ExodusRequests::::contains_key(&network_curve, &session) { + if ExodusRequests::::contains_key((&network_curve, &session)) { return false }; @@ -3088,7 +3100,7 @@ impl Pallet { } let exodus_request = - ExodusRequests::::get(&network_curve, &package.session) + ExodusRequests::::get((&network_curve, &package.session)) .ok_or(InvalidTransaction::BadProof)?; let roast_session = @@ -3286,7 +3298,7 @@ impl Pallet { let threshold = get_byzantium_quorum_threshold(max_authorities); - let mut exodus_requests = ExodusRequests::::iter_prefix(&network_curve) + let mut exodus_requests = ExodusRequests::::iter_prefix((&network_curve,)) .collect::)>>(); exodus_requests.sort_by_key(|&(session, _)| session); diff --git a/pallets/exodus/src/mock.rs b/pallets/exodus/src/mock.rs index 37f161a..460790b 100644 --- a/pallets/exodus/src/mock.rs +++ b/pallets/exodus/src/mock.rs @@ -3,7 +3,7 @@ use frame_system::EnsureRoot; use frame_support::{ derive_impl, parameter_types, - traits::{ConstU32, ConstU64}, + traits::{ConstU32, ConstU64, ConstU128}, }; use crate as ghost_exodus; @@ -143,6 +143,7 @@ impl ghost_exodus::Config for Runtime { type DkgRoundPeriod = ConstU64<100>; type RemovalLimit = ConstU32<20>; type MaxCursorLen = ConstU32<20>; + type BaseEvmAmount = ConstU128<5>; type WeightInfo = (); } diff --git a/pallets/exodus/src/tests.rs b/pallets/exodus/src/tests.rs index 7e54f9e..c1569b4 100644 --- a/pallets/exodus/src/tests.rs +++ b/pallets/exodus/src/tests.rs @@ -810,7 +810,7 @@ fn run_dkg_session( return Err(TestError::InvalidStorageAfter); } - if ExodusRequests::::contains_key(curve, exodus) { + if ExodusRequests::::contains_key((curve, exodus)) { let roast_state = RoastSessionStates::::get(exodus, roast); if !roast_state.partial_signers.contains(idx) || !roast_state.nonce_committers.contains(idx) @@ -881,7 +881,7 @@ fn run_dkg_session( for auth in prev_authorities.iter() { let request_exists = ext.execute_with(|| { - ExodusRequests::::contains_key(&curve, &exodus_session) + ExodusRequests::::contains_key((&curve, &exodus_session)) }); if !request_exists { break; } @@ -1446,6 +1446,118 @@ fn test_prepare_round5_kick_on_empty_justifications() { assert_eq!(indexes.active_bits_count(), authorities_len as u32); } +#[test] +fn register_evm_bridge_out_dynamic_threshold_works() { + let (mut ext, _) = new_test_ext(); + + ext.execute_with(|| { + let network_curve = NetworkCurve::Secp256k1; + let network_id = 69u64; + let user = 420u64; + + let receiver = EvmAddress::from([2u8; 20]); + let bounty = Perbill::from_percent(10); + + let initial_balance: BalanceOf = 100_000u128.unique_saturated_into(); + let _ = ::Currency::set_balance(&user, initial_balance); + + let base_evm_amount_u128 = <::BaseEvmAmount as Get>::get(); + let base_evm_amount: BalanceOf = base_evm_amount_u128.unique_saturated_into(); + let existential_deposit = ::Currency::minimum_balance(); + + let network_data = NetworkDataBuilder::default() + .with_network_curve(network_curve) + .build(); + + let _ = ::NetworkDataHandler::register(network_id, network_data).unwrap(); + let _ = ::NetworkDataHandler::register_incoming(&network_id, 1337).unwrap(); + + let mut dkg_state = PendingDkgAuthorities::default(); + loop { + if dkg_state.is_dkg_finalized() { break; } + dkg_state.next_phase(); + } + + QualificationDkgState::::insert(&network_curve, dkg_state); + + let amount_1 = existential_deposit; + assert_ok!(Exodus::register_evm_bridge_out_exodus( + RuntimeOrigin::signed(user.clone()), + network_id, + amount_1, + bounty, + receiver, + )); + + assert_eq!(ExodusRequests::::count(), 1); + + System::assert_last_event(RuntimeEvent::Exodus( + Event::EvmBridgeOutRegistered { + who: user.clone(), + network_id, + amount: amount_1, + bounty, + receiver, + } + )); + + assert_err!( + Exodus::register_evm_bridge_out_exodus( + RuntimeOrigin::signed(user.clone()), + network_id, + amount_1, + bounty, + receiver, + ), + Error::::BridgeQueueMinimumNotMet + ); + + let amount_2 = existential_deposit.saturating_add(base_evm_amount); + assert_ok!(Exodus::register_evm_bridge_out_exodus( + RuntimeOrigin::signed(user.clone()), + network_id, + amount_2, + bounty, + receiver, + )); + + assert_err!( + Exodus::register_evm_bridge_out_exodus( + RuntimeOrigin::signed(user.clone()), + network_id, + amount_2, + bounty, + receiver, + ), + Error::::BridgeQueueMinimumNotMet + ); + + assert_eq!(ExodusRequests::::count(), 2); + + let amount_3 = amount_2.saturating_add(base_evm_amount); + assert_ok!(Exodus::register_evm_bridge_out_exodus( + RuntimeOrigin::signed(user.clone()), + network_id, + amount_3, + bounty, + receiver, + )); + + assert_eq!(ExodusRequests::::count(), 3); + + assert_err!( + Exodus::register_evm_bridge_out_exodus( + RuntimeOrigin::signed(user.clone()), + network_id, + amount_3, + bounty, + receiver, + ), + Error::::BridgeQueueMinimumNotMet + ); + }); +} + #[test] fn test_merkle_tree_success_power_of_two_for_shares() { let network_curve = NetworkCurve::Secp256k1;