diff --git a/porteer/src/benchmarking.rs b/porteer/src/benchmarking.rs index 5e2375b8..9bdad1a2 100644 --- a/porteer/src/benchmarking.rs +++ b/porteer/src/benchmarking.rs @@ -85,7 +85,7 @@ benchmarks! { >::set_balance(&bob, 0u32.into()); let location = ::BenchmarkHelper::get_whitelisted_location(); - }: _(RawOrigin::Root, bob.clone(), mint_amount, Some(location)) + }: _(RawOrigin::Root, bob.clone(), mint_amount, Some(location), 0u32.into()) verify { assert_eq!(>::balance(&bob), mint_amount); } diff --git a/porteer/src/lib.rs b/porteer/src/lib.rs index 0925687c..7042720e 100644 --- a/porteer/src/lib.rs +++ b/porteer/src/lib.rs @@ -36,11 +36,14 @@ mod tests; pub mod weights; -use frame_support::transactional; -use sp_runtime::DispatchError; - pub use crate::weights::WeightInfo; +use frame_support::{transactional, Parameter}; pub use pallet::*; +use parity_scale_codec::MaxEncodedLen; +use sp_runtime::{ + traits::{AtLeast32BitUnsigned, MaybeSerializeDeserialize, Member}, + DispatchError, +}; pub const LOG_TARGET: &str = "integritee::porteer"; @@ -63,6 +66,7 @@ pub mod pallet { pub type AccountIdOf = ::AccountId; pub type BalanceOf = <::Fungible as fungible::Inspect>>::Balance; + pub type PortTokensNonceOf = <::PortTokensToDestination as PortTokens>::Nonce; const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); #[pallet::pallet] @@ -193,7 +197,11 @@ pub mod pallet { /// Ported some tokens to the destination chain. PortedTokens { who: AccountIdOf, amount: BalanceOf }, /// Minted some tokens ported from another chain! - MintedPortedTokens { who: AccountIdOf, amount: BalanceOf }, + MintedPortedTokens { + who: AccountIdOf, + amount: BalanceOf, + source_nonce: PortTokensNonceOf, + }, /// Forwarded some minted tokens to another location. ForwardedPortedTokens { who: AccountIdOf, amount: BalanceOf, location: T::Location }, /// Failed to forward the tokens to the final destination. @@ -231,10 +239,14 @@ pub mod pallet { #[pallet::storage] pub(super) type WatchdogAccount = StorageValue<_, AccountIdOf, OptionQuery>; - /// The block number at which the last heartbeat was received. + /// The timestamp at which the last heartbeat was received. #[pallet::storage] pub(super) type LastHeartBeat = StorageValue<_, T::Moment, ValueQuery>; + /// The timestamp at which the last heartbeat was received. + #[pallet::storage] + pub(super) type PortTokensNonce = StorageValue<_, PortTokensNonceOf, ValueQuery>; + /// Entails the amount of fees needed at the respective hops. #[pallet::storage] pub(super) type XcmFeeConfig = @@ -407,10 +419,16 @@ pub mod pallet { Fortitude::Polite, )?; + let nonce = PortTokensNonce::::mutate(|n| { + *n = n.saturating_add(1u32.into()); + *n + }); + T::PortTokensToDestination::port_tokens( signer.clone(), amount, forward_tokens_to_location, + nonce, ) .map_err(|e| { log::error!(target: LOG_TARGET, "Port tokens error: {:?}", e); @@ -432,6 +450,7 @@ pub mod pallet { beneficiary: AccountIdOf, amount: BalanceOf, forward_tokens_to_location: Option, + source_nonce: PortTokensNonceOf, ) -> DispatchResult { let _signer = T::TokenSenderLocationOrigin::ensure_origin(origin)?; @@ -442,6 +461,7 @@ pub mod pallet { Self::deposit_event(Event::::MintedPortedTokens { who: beneficiary.clone(), amount, + source_nonce, }); // Forward the tokens if desired @@ -473,6 +493,13 @@ pub trait PortTokens { type AccountId; type Balance; + type Nonce: Parameter + + Member + + AtLeast32BitUnsigned + + Default + + Copy + + MaybeSerializeDeserialize + + MaxEncodedLen; type Location; @@ -482,6 +509,7 @@ pub trait PortTokens { who: Self::AccountId, amount: Self::Balance, forward_tokens_to: Option, + nonce: Self::Nonce, ) -> Result<(), Self::Error>; } diff --git a/porteer/src/mock.rs b/porteer/src/mock.rs index 286b24ed..195e388a 100644 --- a/porteer/src/mock.rs +++ b/porteer/src/mock.rs @@ -119,6 +119,7 @@ pub struct MockPortTokens; impl PortTokens for MockPortTokens { type AccountId = AccountId; type Balance = Balance; + type Nonce = u32; type Location = TestLocation; type Error = DispatchError; @@ -126,6 +127,7 @@ impl PortTokens for MockPortTokens { _who: Self::AccountId, _amount: Self::Balance, _forward_tokens_to: Option, + _nonce: Self::Nonce, ) -> Result<(), Self::Error> { Ok(()) } diff --git a/porteer/src/tests.rs b/porteer/src/tests.rs index 641cc281..2960c2a3 100644 --- a/porteer/src/tests.rs +++ b/porteer/src/tests.rs @@ -301,6 +301,7 @@ fn port_tokens_system_test_works() { assert_ok!(Porteer::set_watchdog(RuntimeOrigin::signed(alice.clone()), bob.clone())); + assert_eq!(PortTokensNonce::::get(), 0); assert_eq!(LastHeartBeat::::get(), 0); let now = Timestamp::get(); @@ -322,6 +323,7 @@ fn port_tokens_system_test_works() { porteering_amount, None )); + assert_eq!(PortTokensNonce::::get(), 1); // Test that bridge stays enabled until the HeartbeatTimout Timestamp::set_timestamp(now + HeartBeatTimeout::get()); @@ -330,6 +332,7 @@ fn port_tokens_system_test_works() { porteering_amount, None )); + assert_eq!(PortTokensNonce::::get(), 2); // Bridge Send is disabled after HeartbeatTimeout has passed Timestamp::set_timestamp(now + HeartBeatTimeout::get() + 1); @@ -337,6 +340,8 @@ fn port_tokens_system_test_works() { Porteer::port_tokens(RuntimeOrigin::signed(alice.clone()), porteering_amount, None), Error::::WatchdogHeartbeatIsTooOld ); + // Nonce has not increased + assert_eq!(PortTokensNonce::::get(), 2); }) } @@ -396,12 +401,14 @@ fn minting_ported_tokens_works() { RuntimeOrigin::signed(alice.clone()), bob.clone(), mint_amount, - None + None, + 42 )); let expected_event = RuntimeEvent::Porteer(PorteerEvent::MintedPortedTokens { who: bob.clone(), amount: mint_amount, + source_nonce: 42, }); assert!(System::events().iter().any(|a| a.event == expected_event)); @@ -415,7 +422,7 @@ fn minting_ported_tokens_errs_with_wrong_origin() { let bob = Keyring::Bob.to_account_id(); assert_noop!( - Porteer::mint_ported_tokens(RuntimeOrigin::signed(bob.clone()), bob, 1, None), + Porteer::mint_ported_tokens(RuntimeOrigin::signed(bob.clone()), bob, 1, None, 0), BadOrigin ); }) @@ -430,7 +437,7 @@ fn minting_ported_tokens_errs_when_receiving_disabled() { assert_ok!(Porteer::set_porteer_config(RuntimeOrigin::signed(alice.clone()), config)); assert_noop!( - Porteer::mint_ported_tokens(RuntimeOrigin::signed(alice.clone()), alice, 1, None), + Porteer::mint_ported_tokens(RuntimeOrigin::signed(alice.clone()), alice, 1, None, 0), Error::::PorteerOperationDisabled ); }) @@ -454,7 +461,8 @@ fn minting_ported_tokens_with_forwarding_works() { RuntimeOrigin::signed(alice.clone()), bob.clone(), mint_amount, - Some(WHITELISTED_LOCATION) + Some(WHITELISTED_LOCATION), + 0 )); // We keep the ED during forwarding @@ -478,7 +486,8 @@ fn minting_ported_tokens_with_forwarding_non_whitelisted_location_preserves_bala RuntimeOrigin::signed(alice.clone()), bob.clone(), mint_amount, - Some(WHITELISTED_LOCATION) + Some(WHITELISTED_LOCATION), + 0 )); let expected_event = RuntimeEvent::Porteer(PorteerEvent::IllegalForwardingLocation { @@ -510,7 +519,8 @@ fn minting_ported_tokens_with_forwarding_to_unsupported_location_preserves_balan RuntimeOrigin::signed(alice.clone()), bob.clone(), mint_amount, - Some(WHITELISTED_BUT_UNSUPPORTED_LOCATION) + Some(WHITELISTED_BUT_UNSUPPORTED_LOCATION), + 0 )); let expected_event = RuntimeEvent::Porteer(PorteerEvent::FailedToForwardTokens {