diff --git a/runtime-modules/argo-bridge/src/events.rs b/runtime-modules/argo-bridge/src/events.rs index 1d2e7e05a8..e2b2bc5076 100644 --- a/runtime-modules/argo-bridge/src/events.rs +++ b/runtime-modules/argo-bridge/src/events.rs @@ -3,6 +3,7 @@ use frame_support::decl_event; use crate::{RemoteAccount, RemoteTransfer, TransferId}; +use sp_std::vec::Vec; use crate::types::*; @@ -19,6 +20,7 @@ decl_event!( { OutboundTransferRequested(TransferId, AccountId, RemoteAccount, Balance, Balance), InboundTransferFinalized(RemoteTransfer, AccountId, Balance), + OutboundTransferReverted(TransferId, AccountId, Balance, Vec), BridgePaused(AccountId), BridgeThawnStarted(AccountId, BlockNumber), BridgeThawnFinished(), diff --git a/runtime-modules/argo-bridge/src/lib.rs b/runtime-modules/argo-bridge/src/lib.rs index bd8622950a..9ea9cc11a1 100644 --- a/runtime-modules/argo-bridge/src/lib.rs +++ b/runtime-modules/argo-bridge/src/lib.rs @@ -137,6 +137,33 @@ decl_module! { Ok(()) } + // TODO: add weight for revert_outbound_transfer + #[weight = WeightInfoArgo::::finalize_inbound_transfer()] + pub fn revert_outbound_transfer( + origin, + transfer_id: TransferId, + revert_account: T::AccountId, + revert_amount: BalanceOf, + rationale: vec::Vec, + ) -> DispatchResult { + ensure!(Self::operator_account().is_some(), Error::::OperatorAccountNotSet); + let caller = ensure_signed(origin)?; + ensure!(caller == Self::operator_account().unwrap(), Error::::NotOperatorAccount); + + ensure!(Self::status() == BridgeStatus::Active, Error::::BridgeNotActive); + ensure!(revert_amount <= Self::mint_allowance(), Error::::InsufficientBridgeMintAllowance); + + >::put(Self::mint_allowance() - revert_amount); + let _ = balances::Pallet::::deposit_creating( + &revert_account, + revert_amount + ); + + Self::deposit_event(RawEvent::OutboundTransferReverted(transfer_id, revert_account, revert_amount, rationale)); + + Ok(()) + } + #[weight = WeightInfoArgo::::pause_bridge()] pub fn pause_bridge(origin) -> DispatchResult { let caller = ensure_signed(origin)?; diff --git a/runtime-modules/argo-bridge/src/tests/mod.rs b/runtime-modules/argo-bridge/src/tests/mod.rs index 1a2cc5c728..a604912d31 100644 --- a/runtime-modules/argo-bridge/src/tests/mod.rs +++ b/runtime-modules/argo-bridge/src/tests/mod.rs @@ -316,6 +316,109 @@ fn finalize_inbound_transfer_with_insufficient_bridge_mint() { }); } +#[test] +fn revert_outbound_transfer_success() { + with_test_externalities_custom_mint_allowance(joy!(1000), || { + let remote_chains = BoundedVec::try_from(vec![1u32]).unwrap(); + let parameters = BridgeConstraints { + operator_account: Some(account!(1)), + pauser_accounts: None, + bridging_fee: None, + thawn_duration: None, + remote_chains: Some(remote_chains), + }; + ArgoBridge::update_bridge_constrains(RuntimeOrigin::root(), parameters).unwrap(); + + let transfer_id = 1u64; + let revert_amount = joy!(123); + let revert_account = account!(2); + let rationale = "test".as_bytes().to_vec(); + let result = ArgoBridge::revert_outbound_transfer( + RuntimeOrigin::signed(account!(1)), + transfer_id, + revert_account, + revert_amount, + rationale.clone(), + ); + assert_ok!(result); + assert_eq!(Balances::free_balance(revert_account), revert_amount); + last_event_eq!(RawEvent::OutboundTransferReverted( + transfer_id, + revert_account, + revert_amount, + rationale, + )); + }); +} + +#[test] +fn revert_outbound_transfer_with_no_operator_account() { + with_test_externalities(|| { + let result = ArgoBridge::revert_outbound_transfer( + RuntimeOrigin::signed(account!(1)), + 1u64, + account!(2), + joy!(123), + vec![], + ); + assert_err!(result, Error::::OperatorAccountNotSet); + }); +} + +#[test] +fn revert_outbound_transfer_with_unauthorized_account() { + with_test_externalities(|| { + let remote_chains = BoundedVec::try_from(vec![1u32]).unwrap(); + let parameters = BridgeConstraints { + operator_account: Some(account!(1)), + pauser_accounts: None, + bridging_fee: None, + thawn_duration: None, + remote_chains: Some(remote_chains), + }; + assert_ok!(ArgoBridge::update_bridge_constrains( + RuntimeOrigin::root(), + parameters + )); + + let result = ArgoBridge::revert_outbound_transfer( + RuntimeOrigin::signed(account!(2)), + 1u64, + account!(2), + joy!(123), + vec![], + ); + assert_err!(result, Error::::NotOperatorAccount); + }); +} + +#[test] +fn revert_outbound_transfer_with_insufficient_bridge_mint() { + with_test_externalities(|| { + let remote_chains = BoundedVec::try_from(vec![1u32]).unwrap(); + let parameters = BridgeConstraints { + operator_account: Some(account!(1)), + pauser_accounts: None, + bridging_fee: None, + thawn_duration: None, + remote_chains: Some(remote_chains), + }; + assert_ok!(ArgoBridge::update_bridge_constrains( + RuntimeOrigin::root(), + parameters + )); + + let result = ArgoBridge::revert_outbound_transfer( + RuntimeOrigin::signed(account!(1)), + 1u64, + account!(2), + joy!(100), + vec![], + ); + assert_err!(result, Error::::InsufficientBridgeMintAllowance); + }); +} + #[test] fn pause_bridge_success() { with_test_externalities(|| {