@@ -4,14 +4,16 @@ use crate::evm::init_omnipool_with_oracle_for_block_10;
44use crate :: polkadot_test_net:: * ;
55use frame_support:: weights:: Weight ;
66use frame_support:: { assert_err, assert_noop, assert_ok} ;
7- use hydradx_runtime:: { AssetRegistry , CircuitBreaker , RuntimeCall , DOT_ASSET_LOCATION } ;
7+ use hydradx_runtime:: { AssetRegistry , CircuitBreaker , RuntimeCall , DOT_ASSET_LOCATION , EVM } ;
88use orml_traits:: MultiCurrency ;
9+ use pallet_circuit_breaker:: types:: GlobalWithdrawLimitParameters ;
910use pallet_circuit_breaker:: GlobalAssetCategory ;
1011use pallet_transaction_payment:: OnChargeTransaction ;
1112use polkadot_xcm:: v5:: prelude:: * ;
1213use polkadot_xcm:: { VersionedAssetId , VersionedXcm } ;
1314use primitives:: constants:: time:: unix_time:: DAY ;
1415use primitives:: constants:: time:: MILLISECS_PER_BLOCK ;
16+ use sp_core:: U256 ;
1517use sp_runtime:: traits:: Dispatchable ;
1618use sp_runtime:: FixedU128 ;
1719use xcm_emulator:: TestExt ;
@@ -82,11 +84,23 @@ fn set_dot_external_and_get_transfer_call() -> hydradx_runtime::RuntimeCall {
8284 } )
8385}
8486
87+ fn init_global_withdraw_limit_params ( ) {
88+ let params = GlobalWithdrawLimitParameters {
89+ limit : 1_000_000 * UNITS ,
90+ window : DAY ,
91+ } ;
92+ assert_ok ! ( CircuitBreaker :: set_global_withdraw_limit_params(
93+ hydradx_runtime:: RuntimeOrigin :: root( ) ,
94+ params
95+ ) ) ;
96+ }
97+
8598#[ test]
8699fn polkadot_xcm_execute_should_fail_when_lockdown_active_and_asset_is_egress ( ) {
87100 TestNet :: reset ( ) ;
88101 Hydra :: execute_with ( || {
89102 // Arrange
103+ init_global_withdraw_limit_params ( ) ;
90104 let now = CircuitBreaker :: timestamp_now ( ) ;
91105
92106 assert_ok ! ( CircuitBreaker :: set_asset_category(
@@ -152,6 +166,8 @@ fn xtokens_transfer_should_fail_when_lockdown_active_and_asset_is_egress() {
152166 TestNet :: reset ( ) ;
153167 Hydra :: execute_with ( || {
154168 // Arrange
169+ init_global_withdraw_limit_params ( ) ;
170+
155171 let now = CircuitBreaker :: timestamp_now ( ) ;
156172
157173 assert_ok ! ( CircuitBreaker :: set_asset_category(
@@ -195,6 +211,7 @@ fn on_charge_transaction_skips_global_withdraw_accounting_for_native_asset() {
195211 TestNet :: reset ( ) ;
196212 Hydra :: execute_with ( || {
197213 // Arrange
214+ init_global_withdraw_limit_params ( ) ;
198215 let alice: AccountId = ALICE . into ( ) ;
199216
200217 // Ensure HDX is a participating asset for the global-withdraw logic
@@ -254,6 +271,7 @@ fn on_charge_transaction_skips_global_withdraw_accounting_for_external_asset() {
254271 TestNet :: reset ( ) ;
255272 Hydra :: execute_with ( || {
256273 init_omnipool_with_oracle_for_block_10 ( ) ;
274+ init_global_withdraw_limit_params ( ) ;
257275
258276 // Arrange
259277 let alice: AccountId = ALICE . into ( ) ;
@@ -321,12 +339,83 @@ fn on_charge_transaction_skips_global_withdraw_accounting_for_external_asset() {
321339 } ) ;
322340}
323341
342+ #[ test]
343+ fn evm_on_charge_transaction_skips_global_withdraw_accounting ( ) {
344+ TestNet :: reset ( ) ;
345+ Hydra :: execute_with ( || {
346+ init_omnipool_with_oracle_for_block_10 ( ) ;
347+ init_global_withdraw_limit_params ( ) ;
348+
349+ // Arrange
350+ let evm_addr = evm_address ( ) ;
351+ let evm_account = hydradx_runtime:: EVMAccounts :: truncated_account_id ( evm_addr) ;
352+
353+ // Ensure WETH has sufficient balance for the EVM account
354+ assert_ok ! ( Currencies :: deposit( WETH , & evm_account, 100 * UNITS ) ) ;
355+
356+ // Set WETH as External to make it a participating asset
357+ assert_ok ! ( CircuitBreaker :: set_asset_category(
358+ hydradx_runtime:: RuntimeOrigin :: root( ) ,
359+ WETH ,
360+ Some ( GlobalAssetCategory :: External )
361+ ) ) ;
362+
363+ // Activate global lockdown
364+ let now = CircuitBreaker :: timestamp_now ( ) ;
365+ assert_ok ! ( CircuitBreaker :: set_global_withdraw_lockdown(
366+ hydradx_runtime:: RuntimeOrigin :: root( ) ,
367+ now + 1000
368+ ) ) ;
369+
370+ let initial_balance = Currencies :: free_balance ( WETH , & evm_account) ;
371+
372+ // Act - Make an EVM call that will charge fees
373+ // Simple transfer to self with some data
374+ assert_ok ! ( EVM :: call(
375+ evm_signed_origin( evm_addr) ,
376+ evm_addr,
377+ evm_addr,
378+ vec![ 1 , 2 , 3 ] ,
379+ U256 :: from( 0 ) ,
380+ 100000 ,
381+ crate :: evm:: gas_price( ) ,
382+ None ,
383+ Some ( U256 :: zero( ) ) ,
384+ [ ] . into( ) ,
385+ vec![ ] ,
386+ ) ) ;
387+
388+ // Assert
389+ // Fee should be charged from WETH balance
390+ let after_balance = Currencies :: free_balance ( WETH , & evm_account) ;
391+ assert ! ( after_balance < initial_balance, "EVM fee should be charged" ) ;
392+
393+ // Verify global-withdraw accounting was skipped for the EVM fee withdraw
394+ assert_eq ! ( CircuitBreaker :: withdraw_limit_accumulator( ) . 0 , 0 ) ;
395+
396+ // Also assert lockdown is still active
397+ assert ! ( CircuitBreaker :: withdraw_lockdown_until( ) . is_some( ) ) ;
398+
399+ // Negative control: normal (non-fee) operations with participating asset are blocked during lockdown
400+ assert_err ! (
401+ Currencies :: withdraw(
402+ WETH ,
403+ & evm_account,
404+ 1 * UNITS ,
405+ frame_support:: traits:: ExistenceRequirement :: AllowDeath
406+ ) ,
407+ pallet_circuit_breaker:: Error :: <hydradx_runtime:: Runtime >:: WithdrawLockdownActive
408+ ) ;
409+ } ) ;
410+ }
411+
324412#[ test]
325413fn xcm_transfer_assets_blocked_during_lockdown ( ) {
326414 TestNet :: reset ( ) ;
327415 Hydra :: execute_with ( || {
328416 // Arrange
329417 init_omnipool_with_oracle_for_block_10 ( ) ;
418+ init_global_withdraw_limit_params ( ) ;
330419
331420 let now = CircuitBreaker :: timestamp_now ( ) ;
332421 let until = now + MILLISECS_PER_BLOCK * 11 ;
@@ -351,9 +440,11 @@ fn lockdown_expiry_allows_egress() {
351440 TestNet :: reset ( ) ;
352441 Hydra :: execute_with ( || {
353442 // Arrange
443+ init_omnipool_with_oracle_for_block_10 ( ) ;
444+ init_global_withdraw_limit_params ( ) ;
445+
354446 let now = CircuitBreaker :: timestamp_now ( ) ;
355447 let until = now + MILLISECS_PER_BLOCK * 11 ;
356- init_omnipool_with_oracle_for_block_10 ( ) ;
357448
358449 assert_ok ! ( CircuitBreaker :: set_global_withdraw_lockdown(
359450 hydradx_runtime:: RuntimeOrigin :: root( ) ,
@@ -384,6 +475,8 @@ fn lockdown_expiry_allows_egress() {
384475fn withdraw_external_should_be_accounted ( ) {
385476 TestNet :: reset ( ) ;
386477 Hydra :: execute_with ( || {
478+ init_global_withdraw_limit_params ( ) ;
479+
387480 // Set HDX as External to avoid conversion issues
388481 assert_ok ! ( CircuitBreaker :: set_asset_category(
389482 hydradx_runtime:: RuntimeOrigin :: root( ) ,
@@ -440,14 +533,15 @@ fn transfer_to_sink_should_be_accounted_for_participating_assets() {
440533 TestNet :: reset ( ) ;
441534 Hydra :: execute_with ( || {
442535 init_omnipool_with_oracle_for_block_10 ( ) ;
536+ init_global_withdraw_limit_params ( ) ;
443537
444538 let sink: AccountId = [ 99u8 ; 32 ] . into ( ) ;
445539 assert_ok ! ( CircuitBreaker :: add_egress_accounts(
446540 hydradx_runtime:: RuntimeOrigin :: root( ) ,
447541 vec![ sink. clone( ) ]
448542 ) ) ;
449543
450- let amount = 100 * UNITS ;
544+ let amount = 10 * UNITS ;
451545
452546 // 1. External -> Accounted (Override DOT to External)
453547 assert_ok ! ( CircuitBreaker :: set_asset_category(
@@ -495,6 +589,7 @@ fn should_account_withdraw_operation_accounts_external_and_local() {
495589 TestNet :: reset ( ) ;
496590 Hydra :: execute_with ( || {
497591 init_omnipool_with_oracle_for_block_10 ( ) ;
592+ init_global_withdraw_limit_params ( ) ;
498593
499594 let who: AccountId = ALICE . into ( ) ;
500595 let amount = 10 * UNITS ;
@@ -580,6 +675,8 @@ fn ingress_deposit_decrements_accumulator_for_external() {
580675 // External deposits are always accounted as ingress and decrement the accumulator.
581676 TestNet :: reset ( ) ;
582677 Hydra :: execute_with ( || {
678+ init_global_withdraw_limit_params ( ) ;
679+
583680 let who: AccountId = ALICE . into ( ) ;
584681 let amount = 10 * UNITS ;
585682
@@ -620,6 +717,8 @@ fn ingress_deposit_decrements_accumulator_for_local_only_when_source_is_egress()
620717 // Local deposits are accounted only when `maybe_from` is an egress account.
621718 TestNet :: reset ( ) ;
622719 Hydra :: execute_with ( || {
720+ init_global_withdraw_limit_params ( ) ;
721+
623722 let alice: AccountId = ALICE . into ( ) ;
624723 let egress_src: AccountId = [ 9u8 ; 32 ] . into ( ) ;
625724 let non_egress_src: AccountId = [ 10u8 ; 32 ] . into ( ) ;
@@ -684,6 +783,7 @@ fn dot_external_limit_trigger_fails_then_decays_to_zero() {
684783 TestNet :: reset ( ) ;
685784 Hydra :: execute_with ( || {
686785 init_omnipool_with_oracle_for_block_10 ( ) ;
786+ init_global_withdraw_limit_params ( ) ;
687787 assert_ok ! ( CircuitBreaker :: reset_withdraw_lockdown(
688788 hydradx_runtime:: RuntimeOrigin :: root( )
689789 ) ) ;
@@ -718,9 +818,12 @@ fn dot_external_limit_trigger_fails_then_decays_to_zero() {
718818 ) ;
719819
720820 // Set a limit that allows the first withdraw but blocks the next one.
721- assert_ok ! ( CircuitBreaker :: set_global_withdraw_limit (
821+ assert_ok ! ( CircuitBreaker :: set_global_withdraw_limit_params (
722822 hydradx_runtime:: RuntimeOrigin :: root( ) ,
723- acc_after_first + 1
823+ pallet_circuit_breaker:: types:: GlobalWithdrawLimitParameters {
824+ limit: acc_after_first + 1 ,
825+ window: DAY
826+ }
724827 ) ) ;
725828
726829 assert_err ! (
@@ -753,6 +856,7 @@ fn dot_external_lockdown_blocks_withdraw_but_regular_dot_transfer_still_works()
753856 TestNet :: reset ( ) ;
754857 Hydra :: execute_with ( || {
755858 init_omnipool_with_oracle_for_block_10 ( ) ;
859+ init_global_withdraw_limit_params ( ) ;
756860 assert_ok ! ( CircuitBreaker :: reset_withdraw_lockdown(
757861 hydradx_runtime:: RuntimeOrigin :: root( )
758862 ) ) ;
0 commit comments