diff --git a/pallets/rolldown/rpc/src/lib.rs b/pallets/rolldown/rpc/src/lib.rs index d1a039c45..91b25b8bc 100644 --- a/pallets/rolldown/rpc/src/lib.rs +++ b/pallets/rolldown/rpc/src/lib.rs @@ -1,5 +1,4 @@ // Copyright (C) 2021 Mangata team - use jsonrpsee::{ core::{async_trait, Error as JsonRpseeError, RpcResult}, proc_macros::rpc, diff --git a/pallets/rolldown/src/lib.rs b/pallets/rolldown/src/lib.rs index 53fd2ceb9..25dc73ce5 100644 --- a/pallets/rolldown/src/lib.rs +++ b/pallets/rolldown/src/lib.rs @@ -27,7 +27,7 @@ use mangata_types::assets::L1Asset; use orml_tokens::{MultiTokenCurrencyExtended, MultiTokenReservableCurrency}; use sha3::{Digest, Keccak256}; use sp_core::{H256, U256}; -use sp_runtime::traits::{AccountIdConversion, Convert, Zero}; +use sp_runtime::traits::{AccountIdConversion, Convert, ConvertBack, Zero}; use sp_std::{collections::btree_set::BTreeSet, convert::TryInto, prelude::*, vec::Vec}; pub type CurrencyIdOf = <::Tokens as MultiTokenCurrency< @@ -66,6 +66,14 @@ impl Convert<[u8; 20], sp_runtime::AccountId20> } } +impl ConvertBack<[u8; 20], sp_runtime::AccountId20> + for EthereumAddressConverter +{ + fn convert_back(eth_addr: sp_runtime::AccountId20) -> [u8; 20] { + eth_addr.into() + } +} + #[derive(Clone)] pub struct Keccak256Hasher {} @@ -80,6 +88,23 @@ impl Hasher for Keccak256Hasher { } } +#[derive(PartialEq, RuntimeDebug, Clone, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub enum L1DepositProcessingError { + Overflow, + AssetRegistrationProblem, + MintError, +} + +impl From for Error { + fn from(e: L1DepositProcessingError) -> Self { + match e { + L1DepositProcessingError::Overflow => Error::::BalanceOverflow, + L1DepositProcessingError::AssetRegistrationProblem => Error::::L1AssetCreationFailed, + L1DepositProcessingError::MintError => Error::::MintError, + } + } +} + #[derive(PartialEq, RuntimeDebug, Clone, Encode, Decode, MaxEncodedLen, TypeInfo)] pub enum L1RequestProcessingError { Overflow, @@ -90,6 +115,16 @@ pub enum L1RequestProcessingError { SequencerNotSlashed, } +impl From for L1RequestProcessingError { + fn from(err: L1DepositProcessingError) -> Self { + match err { + L1DepositProcessingError::Overflow => Self::Overflow, + L1DepositProcessingError::AssetRegistrationProblem => Self::AssetRegistrationProblem, + L1DepositProcessingError::MintError => Self::MintError, + } + } +} + #[cfg(test)] mod tests; @@ -154,10 +189,14 @@ pub mod pallet { Submitter, } + #[pallet::storage] + pub type FerriedDeposits = + StorageMap<_, Blake2_128Concat, (T::ChainId, H256), T::AccountId, OptionQuery>; + #[pallet::storage] /// stores id of the failed depoisit, so it can be refunded using [`Pallet::refund_failed_deposit`] pub type FailedL1Deposits = - StorageMap<_, Blake2_128Concat, (T::AccountId, T::ChainId, u128), (), OptionQuery>; + StorageMap<_, Blake2_128Concat, (T::ChainId, u128), (T::AccountId, H256), OptionQuery>; #[pallet::storage] #[pallet::getter(fn get_last_processed_request_on_l2)] @@ -313,11 +352,13 @@ pub mod pallet { token_address: [u8; 20], amount: u128, hash: H256, + ferry_tip: u128, }, ManualBatchExtraFeeSet(BalanceOf), DepositRefundCreated { chain: ChainIdOf, refunded_request_id: RequestId, + ferry: Option>, }, L1ReadScheduledForExecution { chain: T::ChainId, @@ -327,6 +368,11 @@ pub mod pallet { chain: T::ChainId, hash: H256, }, + DepositFerried { + chain: T::ChainId, + deposit: messages::Deposit, + deposit_hash: H256, + }, } #[pallet::error] @@ -356,10 +402,14 @@ pub mod pallet { InvalidRange, NonExistingRequestId, UnknownAliasAccount, - FailedDepositDoesExists, + FailedDepositDoesNotExist, EmptyBatch, TokenDoesNotExist, - NotDepositRecipient, + NotEligibleForRefund, + FerryHashMismatch, + MintError, + AssetRegistrationProblem, + UpdateHashMishmatch, } #[pallet::config] @@ -371,7 +421,7 @@ pub mod pallet { ChainIdOf, >; type SequencerStakingRewards: SequencerStakingRewardsTrait; - type AddressConverter: Convert<[u8; 20], Self::AccountId>; + type AddressConverter: ConvertBack<[u8; 20], Self::AccountId>; // Dummy so that we can have the BalanceOf type here for the SequencerStakingProviderTrait type Tokens: MultiTokenCurrency + MultiTokenReservableCurrency @@ -435,8 +485,13 @@ pub mod pallet { pub fn update_l2_from_l1( origin: OriginFor, requests: messages::L1Update, + update_hash: H256, ) -> DispatchResult { let sequencer = ensure_signed(origin)?; + + let hash = requests.abi_encode_hash(); + ensure!(update_hash == hash, Error::::UpdateHashMishmatch); + Self::update_impl(sequencer, requests) } @@ -529,6 +584,7 @@ pub mod pallet { recipient: [u8; 20], token_address: [u8; 20], amount: u128, + ferry_tip: Option, ) -> DispatchResultWithPostInfo { let account = ensure_signed(origin)?; @@ -586,6 +642,7 @@ pub mod pallet { withdrawalRecipient: recipient.clone(), tokenAddress: token_address.clone(), amount: U256::from(amount), + ferryTip: U256::from(ferry_tip.unwrap_or_default()), }; // add cancel request to pending updates L2Requests::::insert( @@ -604,6 +661,7 @@ pub mod pallet { token_address, amount, hash: withdrawal_update.abi_encode_hash(), + ferry_tip: ferry_tip.unwrap_or_default(), }); TotalNumberOfWithdrawals::::mutate(|v| *v = v.saturating_add(One::one())); @@ -693,14 +751,19 @@ pub mod pallet { let sender = ensure_signed(origin)?; // NOTE: failed deposits are not reachable at this point - let _ = FailedL1Deposits::::take((sender, chain, request_id)) - .ok_or(Error::::FailedDepositDoesExists)?; + let (author, deposit_hash) = FailedL1Deposits::::take((chain, request_id)) + .ok_or(Error::::FailedDepositDoesNotExist)?; + + let ferry = FerriedDeposits::::get((chain, deposit_hash)); + let eligible_for_refund = ferry.clone().unwrap_or(author.clone()); + ensure!(eligible_for_refund == sender, Error::::NotEligibleForRefund); let l2_request_id = Self::acquire_l2_request_id(chain); let failed_deposit_resolution = FailedDepositResolution { requestId: RequestId { origin: Origin::L2, id: l2_request_id }, originRequestId: request_id, + ferry: ferry.clone().map(T::AddressConverter::convert_back).unwrap_or([0u8; 20]), }; L2Requests::::insert( @@ -715,6 +778,7 @@ pub mod pallet { Self::deposit_event(Event::DepositRefundCreated { refunded_request_id: RequestId { origin: Origin::L1, id: request_id }, chain, + ferry, }); Ok(().into()) @@ -750,6 +814,74 @@ pub mod pallet { Self::persist_batch_and_deposit_event(chain, range, sequencer_account); Ok(().into()) } + + #[pallet::call_index(10)] + #[pallet::weight(T::DbWeight::get().reads_writes(1, 1).saturating_add(Weight::from_parts(40_000_000, 0)))] + pub fn ferry_deposit( + origin: OriginFor, + chain: T::ChainId, + request_id: RequestId, + deposit_recipient: [u8; 20], + token_address: [u8; 20], + amount: u128, + timestamp: u128, + ferry_tip: u128, + deposit_hash: H256, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let deposit = messages::Deposit { + depositRecipient: deposit_recipient, + requestId: request_id, + tokenAddress: token_address, + amount: amount.into(), + timeStamp: timestamp.into(), + ferryTip: ferry_tip.into(), + }; + + ensure!(deposit.abi_encode_hash() == deposit_hash, Error::::FerryHashMismatch); + Self::ferry_desposit_impl(sender, chain, deposit)?; + + Ok(().into()) + } + + #[pallet::call_index(11)] + #[pallet::weight(T::DbWeight::get().reads_writes(1, 1).saturating_add(Weight::from_parts(40_000_000, 0)))] + pub fn ferry_deposit_unsafe( + origin: OriginFor, + chain: T::ChainId, + request_id: RequestId, + deposit_recipient: [u8; 20], + token_address: [u8; 20], + amount: u128, + timestamp: u128, + ferry_tip: u128, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let deposit = messages::Deposit { + depositRecipient: deposit_recipient, + requestId: request_id, + tokenAddress: token_address, + amount: amount.into(), + timeStamp: timestamp.into(), + ferryTip: ferry_tip.into(), + }; + + Self::ferry_desposit_impl(sender, chain, deposit)?; + + Ok(().into()) + } + + #[pallet::call_index(12)] + #[pallet::weight(T::DbWeight::get().reads_writes(3, 3).saturating_add(Weight::from_parts(40_000_000, 0)))] + pub fn update_l2_from_l1_unsafe( + origin: OriginFor, + requests: messages::L1Update, + ) -> DispatchResult { + let sequencer = ensure_signed(origin)?; + Self::update_impl(sequencer, requests) + } } } @@ -882,12 +1014,18 @@ impl Pallet { } let status = match request.clone() { - messages::L1UpdateRequest::Deposit(deposit) => Self::process_deposit(l1, &deposit) - .or_else(|err| { + messages::L1UpdateRequest::Deposit(deposit) => { + let deposit_status = Self::process_deposit(l1, &deposit); + TotalNumberOfDeposits::::mutate(|v| *v = v.saturating_add(One::one())); + deposit_status.or_else(|err| { let who: T::AccountId = T::AddressConverter::convert(deposit.depositRecipient); - FailedL1Deposits::::insert((who, l1, deposit.requestId.id), ()); - Err(err) - }), + FailedL1Deposits::::insert( + (l1, deposit.requestId.id), + (who, deposit.abi_encode_hash()), + ); + Err(err.into()) + }) + }, messages::L1UpdateRequest::CancelResolution(cancel) => Self::process_cancel_resolution(l1, &cancel).or_else(|err| { T::MaintenanceStatusProvider::trigger_maintanance_mode(); @@ -985,30 +1123,26 @@ impl Pallet { /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! fn process_deposit( l1: T::ChainId, - deposit_request_details: &messages::Deposit, - ) -> Result<(), L1RequestProcessingError> { - let account: T::AccountId = - T::AddressConverter::convert(deposit_request_details.depositRecipient); - - let amount = TryInto::::try_into(deposit_request_details.amount) - .map_err(|_| L1RequestProcessingError::Overflow)? + deposit: &messages::Deposit, + ) -> Result<(), L1DepositProcessingError> { + let amount = TryInto::::try_into(deposit.amount) + .map_err(|_| L1DepositProcessingError::Overflow)? .try_into() - .map_err(|_| L1RequestProcessingError::Overflow)?; + .map_err(|_| L1DepositProcessingError::Overflow)?; - let eth_asset = - T::AssetAddressConverter::convert((l1, deposit_request_details.tokenAddress)); + let eth_asset = T::AssetAddressConverter::convert((l1, deposit.tokenAddress)); let asset_id = match T::AssetRegistryProvider::get_l1_asset_id(eth_asset.clone()) { Some(id) => id, None => T::AssetRegistryProvider::create_l1_asset(eth_asset) - .map_err(|_| L1RequestProcessingError::AssetRegistrationProblem)?, + .map_err(|_| L1DepositProcessingError::AssetRegistrationProblem)?, }; - T::Tokens::mint(asset_id, &account, amount) - .map_err(|_| L1RequestProcessingError::MintError)?; + let account = FerriedDeposits::::get((l1, deposit.abi_encode_hash())) + .unwrap_or(T::AddressConverter::convert(deposit.depositRecipient)); - TotalNumberOfDeposits::::mutate(|v| *v = v.saturating_add(One::one())); - log!(debug, "Deposit processed successfully: {:?}", deposit_request_details); + T::Tokens::mint(asset_id, &account, amount) + .map_err(|_| L1DepositProcessingError::MintError)?; Ok(()) } @@ -1489,6 +1623,38 @@ impl Pallet { Ok(sender.clone()) } } + + fn ferry_desposit_impl( + sender: T::AccountId, + chain: T::ChainId, + deposit: messages::Deposit, + ) -> Result<(), Error> { + let deposit_hash = deposit.abi_encode_hash(); + + let amount = deposit + .amount + .checked_sub(deposit.ferryTip) + .and_then(|v| TryInto::::try_into(v).ok()) + .and_then(|v| TryInto::>::try_into(v).ok()) + .ok_or(Error::::MathOverflow)?; + + let eth_asset = T::AssetAddressConverter::convert((chain, deposit.tokenAddress)); + let asset_id = match T::AssetRegistryProvider::get_l1_asset_id(eth_asset.clone()) { + Some(id) => id, + None => T::AssetRegistryProvider::create_l1_asset(eth_asset) + .map_err(|_| Error::::AssetRegistrationProblem)?, + }; + + let account = T::AddressConverter::convert(deposit.depositRecipient); + + T::Tokens::transfer(asset_id, &sender, &account, amount, ExistenceRequirement::KeepAlive) + .map_err(|_| Error::::NotEnoughAssets)?; + FerriedDeposits::::insert((chain, deposit_hash), sender); + + Self::deposit_event(Event::DepositFerried { chain, deposit, deposit_hash }); + + Ok(().into()) + } } impl RolldownProviderTrait, AccountIdOf> for Pallet { diff --git a/pallets/rolldown/src/messages/eth_abi.rs b/pallets/rolldown/src/messages/eth_abi.rs index 6893869be..e80fcd4f1 100644 --- a/pallets/rolldown/src/messages/eth_abi.rs +++ b/pallets/rolldown/src/messages/eth_abi.rs @@ -34,6 +34,7 @@ impl From for FailedDepositResolution { originRequestId: crate::messages::to_eth_u256( failed_deposit_resolution.originRequestId.into(), ), + ferry: failed_deposit_resolution.ferry.into(), } } } @@ -45,6 +46,7 @@ impl From for Withdrawal { withdrawalRecipient: withdrawal.withdrawalRecipient.into(), tokenAddress: withdrawal.tokenAddress.into(), amount: crate::messages::to_eth_u256(withdrawal.amount.into()), + ferryTip: crate::messages::to_eth_u256(withdrawal.ferryTip.into()), } } } @@ -112,6 +114,7 @@ impl From for Deposit { tokenAddress: deposit.tokenAddress.into(), amount: to_eth_u256(deposit.amount), timeStamp: to_eth_u256(deposit.timeStamp), + ferryTip: to_eth_u256(deposit.ferryTip), } } } @@ -125,6 +128,7 @@ sol! { address tokenAddress; uint256 amount; uint256 timeStamp; + uint256 ferryTip; } @@ -159,6 +163,7 @@ sol! { struct FailedDepositResolution { RequestId requestId; uint256 originRequestId; + address ferry; } #[derive(Debug, PartialEq)] @@ -167,6 +172,7 @@ sol! { address withdrawalRecipient; address tokenAddress; uint256 amount; + uint256 ferryTip; } #[derive(Debug, PartialEq)] @@ -184,15 +190,118 @@ sol! { } -#[test] -fn test_conversion_u256__small() { - let val = sp_core::U256::from(1u8); - let eth_val = alloy_primitives::U256::from(1u8); - assert_eq!(to_eth_u256(val), eth_val); -} +#[cfg(test)] +mod test { + use super::*; + use alloy_sol_types::SolValue; + use hex_literal::hex; + use serial_test::serial; + use sha3::{Digest, Keccak256}; + + pub trait Keccak256Hash { + fn keccak256_hash(&self) -> [u8; 32]; + } + + impl Keccak256Hash for T + where + T: SolValue, + { + fn keccak256_hash(&self) -> [u8; 32] { + Into::<[u8; 32]>::into(Keccak256::digest(&self.abi_encode()[..])) + } + } + + #[test] + fn test_conversion_u256__small() { + let val = sp_core::U256::from(1u8); + let eth_val = alloy_primitives::U256::from(1u8); + assert_eq!(to_eth_u256(val), eth_val); + } + + #[test] + fn test_conversion_u256__big() { + let val = sp_core::U256::from([u8::MAX; 32]); + assert_eq!(from_eth_u256(to_eth_u256(val)), val); + } + + /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + /// NOTE: Below hash values should not be ever chaned, there are comaptible test implemented in eigen monorepo + /// to ensure abi compatibility between L1 & L2 + /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + #[test] + #[serial] + // this test ensures that the hash calculated on rust side matches hash calculated in contract + fn test_l1_update_hash_compare_with_solidty() { + assert_eq!( + L1Update { + chain: Chain::Ethereum, + pendingDeposits: vec![Deposit { + requestId: RequestId { + origin: Origin::L1, + id: alloy_primitives::U256::from(1) + }, + depositRecipient: hex!("1111111111111111111111111111111111111111").into(), + tokenAddress: hex!("2222222222222222222222222222222222222222").into(), + amount: alloy_primitives::U256::from(123456), + timeStamp: alloy_primitives::U256::from(987), + ferryTip: alloy_primitives::U256::from(321987) + }], + pendingCancelResolutions: vec![CancelResolution { + requestId: RequestId { + origin: Origin::L1, + id: alloy_primitives::U256::from(123) + }, + l2RequestId: alloy_primitives::U256::from(123456), + cancelJustified: true, + timeStamp: alloy_primitives::U256::from(987) + }], + } + .keccak256_hash(), + hex!("663fa3ddfe64659f67b2728637936fa8d21f18ef96c07dec110cdd8f45be6fee"), + ); + } -#[test] -fn test_conversion_u256__big() { - let val = sp_core::U256::from([u8::MAX; 32]); - assert_eq!(from_eth_u256(to_eth_u256(val)), val); + #[test] + #[serial] + fn test_calculate_chain_hash() { + assert_eq!( + Chain::Ethereum.keccak256_hash(), + hex!("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563"), + ); + + assert_eq!( + Chain::Arbitrum.keccak256_hash(), + hex!("b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6"), + ); + } + + #[test] + #[serial] + fn test_calculate_withdrawal_hash() { + assert_eq!( + Withdrawal { + requestId: RequestId { origin: Origin::L2, id: alloy_primitives::U256::from(123) }, + withdrawalRecipient: hex!("ffffffffffffffffffffffffffffffffffffffff").into(), + tokenAddress: hex!("1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f").into(), + amount: alloy_primitives::U256::from(123456), + ferryTip: alloy_primitives::U256::from(465789) + } + .keccak256_hash(), + hex!("a931da68c445f23b06a72768d07a3513f85c0118ff80f6e284117a221869ae8b"), + ); + } + + #[test] + #[serial] + fn test_calculate_failed_deposit_resolution_hash() { + assert_eq!( + FailedDepositResolution { + requestId: RequestId { origin: Origin::L1, id: alloy_primitives::U256::from(123) }, + originRequestId: alloy_primitives::U256::from(1234), + ferry: hex!("b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5").into() + } + .keccak256_hash(), + hex!("d3def31efb42dd99500c389f59115f0eef5e008db0ee0a81562ef3acbe02eece"), + ); + } } diff --git a/pallets/rolldown/src/messages/mod.rs b/pallets/rolldown/src/messages/mod.rs index 18cefae01..af7d4aca1 100644 --- a/pallets/rolldown/src/messages/mod.rs +++ b/pallets/rolldown/src/messages/mod.rs @@ -136,6 +136,7 @@ impl From<(Origin, u128)> for RequestId { pub struct FailedDepositResolution { pub requestId: RequestId, pub originRequestId: u128, + pub ferry: [u8; 20], } #[derive( @@ -146,6 +147,7 @@ pub struct Withdrawal { pub withdrawalRecipient: [u8; 20], pub tokenAddress: [u8; 20], pub amount: U256, + pub ferryTip: U256, } impl NativeToEthMapping for Withdrawal { @@ -187,8 +189,13 @@ pub struct Deposit { pub requestId: RequestId, pub depositRecipient: [u8; 20], pub tokenAddress: [u8; 20], - pub amount: sp_core::U256, - pub timeStamp: sp_core::U256, + pub amount: U256, + pub timeStamp: U256, + pub ferryTip: U256, +} + +impl NativeToEthMapping for Deposit { + type EthType = eth_abi::Deposit; } #[derive(Eq, PartialEq, RuntimeDebug, Clone, Encode, Decode, TypeInfo, Serialize)] @@ -210,6 +217,13 @@ pub struct L1Update { pub pendingCancelResolutions: Vec, } +impl NativeToEthMapping for L1Update +where + Self: Clone, +{ + type EthType = eth_abi::L1Update; +} + #[derive(Eq, PartialEq, RuntimeDebug, Clone, Encode, Decode, TypeInfo, Serialize)] pub enum L1UpdateRequest { Deposit(Deposit), @@ -239,6 +253,18 @@ impl L1UpdateRequest { } } +impl From for L1UpdateRequest { + fn from(deposit: Deposit) -> L1UpdateRequest { + L1UpdateRequest::Deposit(deposit) + } +} + +impl From for L1UpdateRequest { + fn from(cancel: CancelResolution) -> L1UpdateRequest { + L1UpdateRequest::CancelResolution(cancel) + } +} + impl L1Update { pub fn range(&self) -> Option { let first = [ @@ -322,6 +348,7 @@ impl TryFrom for Deposit { tokenAddress, amount: from_eth_u256(deposit.amount), timeStamp: from_eth_u256(deposit.timeStamp), + ferryTip: from_eth_u256(deposit.ferryTip), }) } } diff --git a/pallets/rolldown/src/mock.rs b/pallets/rolldown/src/mock.rs index 8e68213aa..1d54bcaef 100644 --- a/pallets/rolldown/src/mock.rs +++ b/pallets/rolldown/src/mock.rs @@ -21,6 +21,7 @@ pub(crate) type TokenId = u32; pub mod consts { pub const MILLION: u128 = 1_000_000; + pub const THOUSAND: u128 = 1_000; pub const ALICE: u64 = 2; pub const BOB: u64 = 3; pub const CHARLIE: u64 = 4; diff --git a/pallets/rolldown/src/tests.rs b/pallets/rolldown/src/tests.rs index a9125f3fe..2dd0eda7d 100644 --- a/pallets/rolldown/src/tests.rs +++ b/pallets/rolldown/src/tests.rs @@ -20,23 +20,24 @@ pub const ETH_RECIPIENT_ACCOUNT_MGX: AccountId = CHARLIE; pub type TokensOf = ::Tokens; -struct L1UpdateBuilder(Option, Vec); +pub(crate) struct L1UpdateBuilder(Option, Vec); + impl L1UpdateBuilder { - fn new() -> Self { + pub fn new() -> Self { Self(None, Default::default()) } - fn with_offset(mut self, offset: u128) -> Self { + pub fn with_offset(mut self, offset: u128) -> Self { self.0 = Some(offset); self } - fn with_requests(mut self, requests: Vec) -> Self { + pub fn with_requests(mut self, requests: Vec) -> Self { self.1 = requests; self } - fn build(self) -> messages::L1Update { + pub fn build(self) -> messages::L1Update { let mut update = messages::L1Update::default(); for (id, r) in self.1.into_iter().enumerate() { @@ -69,7 +70,7 @@ fn error_on_empty_update() { forward_to_block::(36); let update = L1UpdateBuilder::default().build(); assert_err!( - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), update), + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update), Error::::EmptyUpdate ); }); @@ -97,18 +98,34 @@ fn process_single_deposit() { let update = L1UpdateBuilder::default() .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); assert_event_emitted!(Event::L1ReadStored { chain: messages::Chain::Ethereum, sequencer: ALICE, dispute_period_end: current_block_number + dispute_period, range: (1u128, 1u128).into(), - hash: hex!("2bc9e0914fd9ecb6db43aa2db62e53cdc70fdcbf0d232e840d61f01fecfa5f19").into() + hash: hex!("75207958ce929568193284a176e012a8cf5058dc19d73dafee61a419eb667398").into() }); }); } +#[test] +#[serial] +fn test_reject_update_with_wrong_hash() { + ExtBuilder::new().execute_with_default_mocks(|| { + forward_to_block::(36); + let update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) + .build(); + + assert_err!( + Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), update, H256::zero()), + Error::::UpdateHashMishmatch + ); + }); +} + #[test] #[serial] fn l2_counter_updates_when_requests_are_processed() { @@ -126,10 +143,10 @@ fn l2_counter_updates_when_requests_are_processed() { forward_to_block::(10); assert_eq!(Rolldown::get_last_processed_request_on_l2(Chain::Ethereum), 0_u128.into()); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), update1).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update1).unwrap(); forward_to_block::(11); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(BOB), update2).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), update2).unwrap(); forward_to_block::(15); forward_to_next_block::(); @@ -154,10 +171,11 @@ fn deposit_executed_after_dispute_period() { tokenAddress: ETH_TOKEN_ADDRESS, amount: sp_core::U256::from(MILLION), timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), })]) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); forward_to_block::(14); assert!(!L2Requests::::contains_key( Chain::Ethereum, @@ -185,10 +203,11 @@ fn deposit_fail_creates_update_with_status_false() { tokenAddress: ETH_TOKEN_ADDRESS, amount: sp_core::U256::from("3402823669209384634633746074317682114560"), timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), })]) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); forward_to_block::(14); assert!(!L2Requests::::contains_key( Chain::Ethereum, @@ -196,7 +215,7 @@ fn deposit_fail_creates_update_with_status_false() { )); assert_eq!(TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE), 0_u128); - assert!(!FailedL1Deposits::::contains_key((CHARLIE, consts::CHAIN, 1u128))); + assert!(!FailedL1Deposits::::contains_key((consts::CHAIN, 1u128))); forward_to_block::(20); @@ -206,7 +225,7 @@ fn deposit_fail_creates_update_with_status_false() { status: Err(L1RequestProcessingError::Overflow), }); - assert!(FailedL1Deposits::::contains_key((CHARLIE, consts::CHAIN, 1u128))); + assert!(FailedL1Deposits::::contains_key((consts::CHAIN, 1u128))); assert_eq!(TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE), 0_u128); }); } @@ -225,10 +244,11 @@ fn test_refund_of_failed_withdrawal() { tokenAddress: ETH_TOKEN_ADDRESS, amount: sp_core::U256::from("3402823669209384634633746074317682114560"), timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), })]) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); forward_to_block::(20); assert_event_emitted!(Event::RequestProcessedOnL2 { chain: messages::Chain::Ethereum, @@ -241,7 +261,8 @@ fn test_refund_of_failed_withdrawal() { assert_event_emitted!(Event::DepositRefundCreated { refunded_request_id: RequestId::new(Origin::L1, 1u128), - chain: Chain::Ethereum + chain: Chain::Ethereum, + ferry: None }); }); } @@ -260,10 +281,11 @@ fn test_withdrawal_can_be_refunded_only_once() { tokenAddress: ETH_TOKEN_ADDRESS, amount: sp_core::U256::from("3402823669209384634633746074317682114560"), timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), })]) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); forward_to_block::(20); Rolldown::refund_failed_deposit(RuntimeOrigin::signed(CHARLIE), consts::CHAIN, 1u128) .unwrap(); @@ -273,7 +295,7 @@ fn test_withdrawal_can_be_refunded_only_once() { consts::CHAIN, 1u128 ), - Error::::FailedDepositDoesExists + Error::::FailedDepositDoesNotExist ); }); } @@ -292,15 +314,16 @@ fn test_withdrawal_can_be_refunded_only_by_account_deposit_recipient() { tokenAddress: ETH_TOKEN_ADDRESS, amount: sp_core::U256::from("3402823669209384634633746074317682114560"), timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), })]) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); forward_to_block::(20); assert_err!( Rolldown::refund_failed_deposit(RuntimeOrigin::signed(ALICE), consts::CHAIN, 1u128), - Error::::FailedDepositDoesExists + Error::::NotEligibleForRefund ); Rolldown::refund_failed_deposit(RuntimeOrigin::signed(CHARLIE), consts::CHAIN, 1u128) @@ -322,6 +345,7 @@ fn l1_upate_executed_immaidately_if_force_submitted() { tokenAddress: ETH_TOKEN_ADDRESS, amount: sp_core::U256::from(MILLION), timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), })]) .build(); @@ -354,12 +378,14 @@ fn each_request_executed_only_once() { tokenAddress: ETH_TOKEN_ADDRESS, amount: sp_core::U256::from(MILLION), timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), })]) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), update.clone()).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update.clone()) + .unwrap(); forward_to_block::(11); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(BOB), update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), update).unwrap(); forward_to_block::(14); assert!(!L2Requests::::contains_key( @@ -398,7 +424,8 @@ fn test_cancel_removes_pending_requests() { assert!(!PendingSequencerUpdates::::contains_key(15u128, Chain::Ethereum)); // Act - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); assert!(PendingSequencerUpdates::::contains_key(15u128, Chain::Ethereum)); Rolldown::cancel_requests_from_l1( RuntimeOrigin::signed(BOB), @@ -426,7 +453,8 @@ fn test_cancel_produce_update_with_correct_hash() { .build(); // Act - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); Rolldown::cancel_requests_from_l1( RuntimeOrigin::signed(BOB), @@ -444,7 +472,7 @@ fn test_cancel_produce_update_with_correct_hash() { updater: ALICE, canceler: BOB, range: (1u128, 1u128).into(), - hash: hex!("2bc9e0914fd9ecb6db43aa2db62e53cdc70fdcbf0d232e840d61f01fecfa5f19") + hash: hex!("75207958ce929568193284a176e012a8cf5058dc19d73dafee61a419eb667398") .into() } .into() @@ -489,7 +517,8 @@ fn test_malicious_sequencer_is_slashed_when_honest_sequencer_cancels_malicious_r ); // Act - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); assert_eq!( *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), SequencerRights { read_rights: 0u128, cancel_rights: 2u128 } @@ -505,7 +534,8 @@ fn test_malicious_sequencer_is_slashed_when_honest_sequencer_cancels_malicious_r ); forward_to_block::(12); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(BOB), cancel_resolution).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), cancel_resolution) + .unwrap(); assert_eq!( *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), SequencerRights { read_rights: 0u128, cancel_rights: 1u128 } @@ -568,7 +598,8 @@ fn test_malicious_canceler_is_slashed_when_honest_read_is_canceled() { ); // Act - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); assert_eq!( *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), @@ -584,7 +615,8 @@ fn test_malicious_canceler_is_slashed_when_honest_read_is_canceled() { ); forward_to_block::(12); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(BOB), cancel_resolution).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), cancel_resolution) + .unwrap(); forward_to_block::(16); let slash_sequencer_mock = MockSequencerStakingProviderApi::slash_sequencer_context(); @@ -632,7 +664,8 @@ fn test_trigger_maintanance_mode_when_processing_cancel_resolution_triggers_an_e .with_offset(1u128) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(BOB), cancel_resolution).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), cancel_resolution) + .unwrap(); forward_to_block::(15); forward_to_next_block::(); @@ -700,7 +733,8 @@ fn test_cancel_removes_cancel_right() { SequencerRights { read_rights: 1u128, cancel_rights: 2u128 } ); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); assert_eq!( *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), @@ -729,7 +763,8 @@ fn test_cancel_removes_cancel_right() { ); forward_to_block::(11); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(BOB), cancel_resolution).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), cancel_resolution) + .unwrap(); assert_eq!( *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), SequencerRights { read_rights: 0u128, cancel_rights: 1u128 } @@ -748,36 +783,6 @@ fn test_cancel_removes_cancel_right() { }); } -#[test] -#[serial] -// this test ensures that the hash calculated on rust side matches hash calculated in contract -fn test_l1_update_hash_compare_with_solidty() { - ExtBuilder::new().execute_with_default_mocks(|| { - let update = L1UpdateBuilder::new() - .with_requests(vec![ - L1UpdateRequest::Deposit(messages::Deposit { - requestId: RequestId::new(Origin::L1, 1u128), - depositRecipient: hex!("0000000000000000000000000000000000000002"), - tokenAddress: hex!("0000000000000000000000000000000000000003"), - amount: 4u128.into(), - timeStamp: sp_core::U256::from(1), - }), - L1UpdateRequest::CancelResolution(messages::CancelResolution { - requestId: RequestId::new(Origin::L1, 6u128), - l2RequestId: 7u128, - cancelJustified: true, - timeStamp: sp_core::U256::from(2), - }), - ]) - .build(); - let hash = Rolldown::calculate_hash_of_sequencer_update(update); - assert_eq!( - hash, - hex!("af1c7908d0762a131c827a13d9a6afde3e6f1a4a842d96708935d57fc2a0af7a").into() - ); - }); -} - #[test] #[serial] fn cancel_request_as_council_executed_immadiately() { @@ -797,7 +802,8 @@ fn cancel_request_as_council_executed_immadiately() { *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), SequencerRights { read_rights: 1u128, cancel_rights: 2u128 } ); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); assert_eq!( *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), SequencerRights { read_rights: 0u128, cancel_rights: 2u128 } @@ -826,7 +832,7 @@ fn execute_a_lot_of_requests_in_following_blocks() { let requests = vec![L1UpdateRequest::Deposit(messages::Deposit::default()); requests_count]; let deposit_update = L1UpdateBuilder::default().with_requests(requests).build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update).unwrap(); forward_to_block::(14); assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); @@ -864,10 +870,10 @@ fn ignore_duplicated_requests_when_already_executed() { L1UpdateBuilder::default().with_requests(vec![dummy_request; 6]).build(); forward_to_block::(10); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), first_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), first_update).unwrap(); forward_to_block::(11); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(BOB), second_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), second_update).unwrap(); forward_to_block::(14); assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); @@ -893,10 +899,10 @@ fn process_l1_reads_in_order() { L1UpdateBuilder::default().with_requests(vec![dummy_request; 20]).build(); forward_to_block::(10); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), first_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), first_update).unwrap(); forward_to_block::(11); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(BOB), second_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), second_update).unwrap(); forward_to_block::(14); assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); @@ -917,7 +923,7 @@ fn check_request_ids_starts_from_one() { let requests = vec![L1UpdateRequest::Deposit(Default::default())]; assert_err!( - Rolldown::update_l2_from_l1( + Rolldown::update_l2_from_l1_unsafe( RuntimeOrigin::signed(ALICE), L1UpdateBuilder::new() .with_requests(requests.clone()) @@ -928,7 +934,7 @@ fn check_request_ids_starts_from_one() { ); assert_err!( - Rolldown::update_l2_from_l1( + Rolldown::update_l2_from_l1_unsafe( RuntimeOrigin::signed(ALICE), L1UpdateBuilder::new().with_requests(requests).with_offset(2u128).build() ), @@ -949,7 +955,7 @@ fn reject_consecutive_update_with_invalid_counters() { .build(); assert_err!( - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update), + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update), Error::::WrongRequestId ); }); @@ -967,7 +973,7 @@ fn reject_update_with_invalid_too_high_request_id() { .build(); assert_err!( - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update), + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update), Error::::WrongRequestId ); }); @@ -982,9 +988,10 @@ fn reject_second_update_in_the_same_block() { .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update.clone()).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update.clone()) + .unwrap(); assert_err!( - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(BOB), deposit_update), + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), deposit_update), Error::::MultipleUpdatesInSingleBlock ) }); @@ -997,7 +1004,7 @@ fn accept_consecutive_update_split_into_two() { forward_to_block::(10); // imagine that there are 20 request on L1 waiting to be processed - // they need to be split into 2 update_l2_from_l1 calls + // they need to be split into 2 update_l2_from_l1_unsafe calls let dummy_update = L1UpdateRequest::Deposit(Default::default()); @@ -1009,7 +1016,7 @@ fn accept_consecutive_update_split_into_two() { .with_offset(1u128) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), first_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), first_update).unwrap(); forward_to_next_block::(); assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0); @@ -1041,6 +1048,7 @@ fn execute_two_consecutive_incremental_reqeusts() { tokenAddress: ETH_TOKEN_ADDRESS, amount: sp_core::U256::from(MILLION), timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), }); let first_update = L1UpdateBuilder::default() @@ -1054,10 +1062,10 @@ fn execute_two_consecutive_incremental_reqeusts() { .build(); forward_to_block::(10); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), first_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), first_update).unwrap(); forward_to_block::(11); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(BOB), second_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), second_update).unwrap(); forward_to_block::(14); assert_eq!(TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE), 0_u128); @@ -1100,6 +1108,7 @@ fn test_withdraw() { ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1_000_000u128, + 0u128.into(), ) .unwrap(); @@ -1108,6 +1117,7 @@ fn test_withdraw() { withdrawalRecipient: ETH_RECIPIENT_ACCOUNT, tokenAddress: ETH_TOKEN_ADDRESS, amount: U256::from(1_000_000u128), + ferryTip: U256::from(0), }; // check iftokens were burned assert_eq!(TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &ALICE), 0_u128); @@ -1152,6 +1162,7 @@ fn test_withdraw_of_non_existing_token_returns_token_does_not_exist_error() { ETH_RECIPIENT_ACCOUNT, hex!("0123456789012345678901234567890123456789"), 1_000_000u128, + 0u128.into(), ), Error::::TokenDoesNotExist ); @@ -1170,7 +1181,8 @@ fn error_on_withdraw_too_much() { consts::CHAIN, ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, - 10_000_000u128 + 10_000_000u128, + 0u128.into(), ), Error::::NotEnoughAssets ); @@ -1192,6 +1204,7 @@ fn test_reproduce_bug_with_incremental_updates() { tokenAddress: ETH_TOKEN_ADDRESS, amount: sp_core::U256::from(MILLION), timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), }), L1UpdateRequest::Deposit(messages::Deposit { requestId: RequestId::new(Origin::L1, 2u128), @@ -1199,6 +1212,7 @@ fn test_reproduce_bug_with_incremental_updates() { tokenAddress: ETH_TOKEN_ADDRESS, amount: sp_core::U256::from(MILLION), timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), }), ]) .with_offset(1u128) @@ -1211,11 +1225,12 @@ fn test_reproduce_bug_with_incremental_updates() { tokenAddress: ETH_TOKEN_ADDRESS, amount: sp_core::U256::from(MILLION), timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), })]) .build(); forward_to_block::(10); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), first_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), first_update).unwrap(); forward_to_block::(20); assert!(!L2Requests::::contains_key( @@ -1228,6 +1243,7 @@ fn test_reproduce_bug_with_incremental_updates() { ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 10u128, + 0u128.into(), ) .unwrap(); assert_eq!(Rolldown::get_last_processed_request_on_l2(Chain::Ethereum), 2_u128.into()); @@ -1235,7 +1251,8 @@ fn test_reproduce_bug_with_incremental_updates() { L2Requests::::get(Chain::Ethereum, RequestId::new(Origin::L2, 1u128)); assert!(matches!(withdrawal_update, Some((L2Request::Withdrawal(_), _)))); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), second_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), second_update) + .unwrap(); forward_to_block::(40); assert!(!L2Requests::::contains_key( @@ -1316,7 +1333,7 @@ fn test_last_update_by_sequencer_is_updated() { let update = L1UpdateBuilder::default() .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); assert_eq!(LastUpdateBySequencer::::get((consts::CHAIN, ALICE)), block.into()); }); @@ -1340,8 +1357,11 @@ fn test_cancel_updates_awaiting_cancel_resolution() { assert!(!PendingSequencerUpdates::::contains_key(15u128, Chain::Ethereum)); // Act - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update.clone()) - .unwrap(); + Rolldown::update_l2_from_l1_unsafe( + RuntimeOrigin::signed(ALICE), + deposit_update.clone(), + ) + .unwrap(); assert!(PendingSequencerUpdates::::contains_key(15u128, Chain::Ethereum)); let l2_request_id = Rolldown::get_next_l2_request_id(Chain::Ethereum); @@ -1399,12 +1419,14 @@ fn test_cancel_resolution_updates_awaiting_cancel_resolution() { .build(); // Act - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); forward_to_block::(11); Rolldown::cancel_requests_from_l1(RuntimeOrigin::signed(BOB), consts::CHAIN, 15u128) .unwrap(); forward_to_block::(12); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(BOB), cancel_resolution).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), cancel_resolution) + .unwrap(); assert_eq!( AwaitingCancelResolution::::get((consts::CHAIN, ALICE)), BTreeSet::from([(1, DisputeRole::Submitter)]) @@ -1479,7 +1501,7 @@ fn test_maintenance_mode_blocks_extrinsics() { is_maintenance_mock.expect().return_const(true); assert_err!( - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), Default::default()), + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), Default::default()), Error::::BlockedByMaintenanceMode ); assert_err!( @@ -1500,7 +1522,8 @@ fn test_maintenance_mode_blocks_extrinsics() { consts::CHAIN, Default::default(), Default::default(), - Default::default() + Default::default(), + Default::default(), ), Error::::BlockedByMaintenanceMode ); @@ -1541,7 +1564,7 @@ fn test_single_sequencer_cannot_cancel_request_without_cancel_rights_in_same_blo ); // Act - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(BOB), deposit_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), deposit_update).unwrap(); assert_eq!( *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), @@ -1585,7 +1608,7 @@ fn test_single_sequencer_cannot_cancel_request_without_cancel_rights_in_next_blo ); // Act - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(BOB), deposit_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), deposit_update).unwrap(); assert_eq!( *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), @@ -1625,7 +1648,8 @@ fn consider_awaiting_cancel_resolutions_and_cancel_disputes_when_assigning_initi .build(); forward_to_block::(10); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(BOB), honest_update.clone()).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), honest_update.clone()) + .unwrap(); Rolldown::cancel_requests_from_l1( RuntimeOrigin::signed(ALICE), consts::CHAIN, @@ -1634,8 +1658,11 @@ fn consider_awaiting_cancel_resolutions_and_cancel_disputes_when_assigning_initi .unwrap(); forward_to_block::(11); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(CHARLIE), honest_update.clone()) - .unwrap(); + Rolldown::update_l2_from_l1_unsafe( + RuntimeOrigin::signed(CHARLIE), + honest_update.clone(), + ) + .unwrap(); Rolldown::cancel_requests_from_l1( RuntimeOrigin::signed(ALICE), consts::CHAIN, @@ -1699,7 +1726,8 @@ fn consider_awaiting_l1_sequencer_update_in_dispute_period_when_assigning_initia .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) .build(); forward_to_block::(10); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(BOB), honest_update.clone()).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), honest_update.clone()) + .unwrap(); // accidently canceling honest update forward_to_block::(11); @@ -1718,7 +1746,8 @@ fn consider_awaiting_l1_sequencer_update_in_dispute_period_when_assigning_initia }), ]) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(CHARLIE), honest_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(CHARLIE), honest_update) + .unwrap(); forward_to_block::(15); let honest_update = L1UpdateBuilder::default() @@ -1733,7 +1762,8 @@ fn consider_awaiting_l1_sequencer_update_in_dispute_period_when_assigning_initia L1UpdateRequest::Deposit(Default::default()), ]) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), honest_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), honest_update) + .unwrap(); forward_to_block::(17); // at this point alice will be slashed by cancel resolution provided by CHALIE in block 12 @@ -1772,12 +1802,13 @@ fn consider_awaiting_cancel_resolutions_and_cancel_disputes_when_assigning_initi .build(); forward_to_block::(10); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(BOB), honest_update.clone()).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), honest_update.clone()) + .unwrap(); Rolldown::cancel_requests_from_l1(RuntimeOrigin::signed(ALICE), consts::CHAIN, 15u128) .unwrap(); forward_to_block::(15); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), honest_update.clone()) + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), honest_update.clone()) .unwrap(); // lets assume single person controls multiple sequencers (alice&charlie) and charlie intentionally cancels honest update Rolldown::cancel_requests_from_l1( @@ -1788,7 +1819,7 @@ fn consider_awaiting_cancel_resolutions_and_cancel_disputes_when_assigning_initi .unwrap(); // and then CHARLIE provbides honest update - as a result ALICE will be slashed - Rolldown::update_l2_from_l1( + Rolldown::update_l2_from_l1_unsafe( RuntimeOrigin::signed(CHARLIE), L1UpdateBuilder::default() .with_requests(vec![ @@ -1809,7 +1840,7 @@ fn consider_awaiting_cancel_resolutions_and_cancel_disputes_when_assigning_initi // alice is slashed for her first malicious cancel but then she got slashed with honest update but that has not been yet processed Rolldown::handle_sequencer_deactivations(consts::CHAIN, vec![ALICE]); - Rolldown::update_l2_from_l1( + Rolldown::update_l2_from_l1_unsafe( RuntimeOrigin::signed(CHARLIE), L1UpdateBuilder::default() .with_requests(vec![L1UpdateRequest::CancelResolution( @@ -1853,6 +1884,7 @@ fn test_merkle_proof_works() { ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, i as u128, + 0u128.into(), ) .unwrap(); } @@ -1899,6 +1931,7 @@ fn test_batch_is_created_automatically_when_l2requests_count_exceeds_merkle_root ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1000u128, + 0u128.into(), ) .unwrap(); } @@ -1911,6 +1944,7 @@ fn test_batch_is_created_automatically_when_l2requests_count_exceeds_merkle_root ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1000u128, + 0u128.into(), ) .unwrap(); assert_eq!(L2RequestsBatchLast::::get().get(&consts::CHAIN), None); @@ -1928,6 +1962,7 @@ fn test_batch_is_created_automatically_when_l2requests_count_exceeds_merkle_root ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1000u128, + 0u128.into(), ) .unwrap(); } @@ -1944,6 +1979,7 @@ fn test_batch_is_created_automatically_when_l2requests_count_exceeds_merkle_root ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1000u128, + 0u128.into(), ) .unwrap(); @@ -1984,6 +2020,7 @@ fn test_batch_is_created_automatically_when_merkle_root_automatic_batch_period_p ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1000u128, + 0u128.into(), ) .unwrap(); @@ -2001,6 +2038,7 @@ fn test_batch_is_created_automatically_when_merkle_root_automatic_batch_period_p ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1000u128, + 0u128.into(), ) .unwrap(); @@ -2055,6 +2093,7 @@ fn test_batch_is_created_automatically_whenever_new_request_is_created_and_time_ ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1000u128, + 0u128.into(), ) .unwrap(); @@ -2101,6 +2140,7 @@ fn test_period_based_batch_respects_sized_batches() { ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1000u128, + 0u128.into(), ) .unwrap(); } @@ -2115,6 +2155,7 @@ fn test_period_based_batch_respects_sized_batches() { ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1000u128, + 0u128.into(), ) .unwrap(); @@ -2147,6 +2188,7 @@ fn test_create_manual_batch_works() { ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1_000u128, + 0u128.into(), ) .unwrap(); assert_ok!(Rolldown::create_batch(RuntimeOrigin::signed(ALICE), consts::CHAIN, None)); @@ -2164,6 +2206,7 @@ fn test_create_manual_batch_works() { ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1_000u128, + 0u128.into(), ) .unwrap(); @@ -2198,6 +2241,7 @@ fn test_create_manual_batch_fails_for_invalid_alias_account() { ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1_000u128, + 0u128.into(), ) .unwrap(); @@ -2228,6 +2272,7 @@ fn test_create_manual_batch_work_for_alias_account() { ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1_000u128, + 0u128.into(), ) .unwrap(); @@ -2259,6 +2304,7 @@ fn test_merkle_proof_for_single_element_tree_is_empty() { ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1, + 0u128.into(), ) .unwrap(); @@ -2329,6 +2375,7 @@ fn do_not_allow_for_batches_when_there_are_no_pending_requests2() { ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1_000u128, + 0u128.into(), ) .unwrap(); } @@ -2360,6 +2407,7 @@ fn manual_batches_not_allowed_in_maintanance_mode() { ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1_000u128, + 0u128.into(), ) .unwrap(); is_maintenance_mock.checkpoint(); @@ -2393,6 +2441,7 @@ fn automatic_batches_triggered_by_period_blocked_maintenance_mode() { ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1_000u128, + 0u128.into(), ) .unwrap(); is_maintenance_mock.checkpoint(); @@ -2425,6 +2474,7 @@ fn automatic_batches_triggered_by_pending_requests_blocked_maintenance_mode() { ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1_000u128, + 0u128.into(), ) .unwrap(); } @@ -2455,6 +2505,7 @@ fn test_withdrawals_are_not_allowed_in_maintanance_mode() { ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1_000u128, + 0u128.into(), ), Error::::BlockedByMaintenanceMode ); @@ -2477,7 +2528,8 @@ fn test_cancels_are_not_allowed_in_maintanance_mode() { L1UpdateRequest::Deposit(Default::default()), ]) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); is_maintenance_mock.checkpoint(); let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); @@ -2510,7 +2562,7 @@ fn test_updates_are_not_allowed_in_maintanance_mode() { ]) .build(); assert_err!( - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update), + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update), Error::::BlockedByMaintenanceMode ); }) @@ -2537,10 +2589,12 @@ fn test_sequencer_updates_are_ignored_and_removed_in_maintanance_mode() { tokenAddress: ETH_TOKEN_ADDRESS, amount: sp_core::U256::from(MILLION), timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), })]) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); is_maintenance_mock.checkpoint(); let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); @@ -2552,7 +2606,7 @@ fn test_sequencer_updates_are_ignored_and_removed_in_maintanance_mode() { assert_event_emitted!(Event::L1ReadIgnoredBecauseOfMaintenanceMode { chain: consts::CHAIN, hash: H256::from(hex!( - "81edcec3dc1c825d51e584bc1026167892d961b26a60ac745a97fb197473ab6f" + "6b5cabff8b0f12e9fac3708cad00edba4cdb2b3b8a3ab935073a303c53333c2b" )), }); }) @@ -2570,7 +2624,8 @@ fn test_reqeust_scheduled_for_execution_are_not_execute_in_the_same_block() { let deposit_update = L1UpdateBuilder::default() .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit::default())]) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); forward_to_block::(15); @@ -2578,7 +2633,7 @@ fn test_reqeust_scheduled_for_execution_are_not_execute_in_the_same_block() { assert_event_emitted!(Event::L1ReadScheduledForExecution { chain: consts::CHAIN, hash: H256::from(hex!( - "2bc9e0914fd9ecb6db43aa2db62e53cdc70fdcbf0d232e840d61f01fecfa5f19" + "75207958ce929568193284a176e012a8cf5058dc19d73dafee61a419eb667398" )), }); @@ -2606,7 +2661,8 @@ fn test_sequencer_updates_that_went_though_dispute_period_are_not_executed_in_ma let deposit_update = L1UpdateBuilder::default() .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit::default())]) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); forward_to_block::(15); @@ -2614,7 +2670,7 @@ fn test_sequencer_updates_that_went_though_dispute_period_are_not_executed_in_ma assert_event_emitted!(Event::L1ReadScheduledForExecution { chain: consts::CHAIN, hash: H256::from(hex!( - "2bc9e0914fd9ecb6db43aa2db62e53cdc70fdcbf0d232e840d61f01fecfa5f19" + "75207958ce929568193284a176e012a8cf5058dc19d73dafee61a419eb667398" )), }); is_maintenance_mock.checkpoint(); @@ -2647,7 +2703,8 @@ fn test_sequencer_updates_that_went_though_dispute_period_are_not_scheduled_for_ let deposit_update = L1UpdateBuilder::default() .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit::default())]) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); forward_to_block::(14); is_maintenance_mock.checkpoint(); @@ -2659,7 +2716,7 @@ fn test_sequencer_updates_that_went_though_dispute_period_are_not_scheduled_for_ assert_event_emitted!(Event::L1ReadIgnoredBecauseOfMaintenanceMode { chain: consts::CHAIN, hash: H256::from(hex!( - "2bc9e0914fd9ecb6db43aa2db62e53cdc70fdcbf0d232e840d61f01fecfa5f19" + "75207958ce929568193284a176e012a8cf5058dc19d73dafee61a419eb667398" )), }); @@ -2682,14 +2739,15 @@ fn test_sequencer_can_submit_same_update_again_after_maintenance_mode() { let deposit_update = L1UpdateBuilder::default() .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit::default())]) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); forward_to_block::(15); assert_event_emitted!(Event::L1ReadScheduledForExecution { chain: consts::CHAIN, hash: H256::from(hex!( - "2bc9e0914fd9ecb6db43aa2db62e53cdc70fdcbf0d232e840d61f01fecfa5f19" + "75207958ce929568193284a176e012a8cf5058dc19d73dafee61a419eb667398" )), }); is_maintenance_mock.checkpoint(); @@ -2707,7 +2765,8 @@ fn test_sequencer_can_submit_same_update_again_after_maintenance_mode() { let deposit_update = L1UpdateBuilder::default() .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit::default())]) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); forward_to_block::(26); assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 1u128.into()); @@ -2729,7 +2788,8 @@ fn test_sequencer_rights_are_returned_when_maintanance_mode_is_triggered_and_pen let deposit_update = L1UpdateBuilder::default() .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit::default())]) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); is_maintenance_mock.checkpoint(); let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); @@ -2766,7 +2826,8 @@ fn test_sequencer_rights_are_returned_when_maintanance_mode_is_turned_off_before let deposit_update = L1UpdateBuilder::default() .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit::default())]) .build(); - Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), deposit_update).unwrap(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); is_maintenance_mock.checkpoint(); let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); @@ -2832,6 +2893,7 @@ fn test_force_create_batch_fails_for_invalid_range() { ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1_000u128, + 0u128.into(), ) .unwrap(); @@ -2862,6 +2924,7 @@ fn test_force_create_batch_succeeds_for_valid_range() { ETH_RECIPIENT_ACCOUNT, ETH_TOKEN_ADDRESS, 1_000u128, + 0u128.into(), ) .unwrap(); @@ -2877,3 +2940,257 @@ fn test_force_create_batch_succeeds_for_valid_range() { }); }) } + +#[test] +#[serial] +fn test_deposit_ferry_with_wrong_hash_fails() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + let deposit = messages::Deposit { + requestId: Default::default(), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(MILLION), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + }; + + assert_err!( + Rolldown::ferry_deposit( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + deposit.requestId, + deposit.depositRecipient, + deposit.tokenAddress, + deposit.amount.try_into().unwrap(), + deposit.timeStamp.try_into().unwrap(), + deposit.ferryTip.try_into().unwrap(), + H256::zero() + ), + Error::::FerryHashMismatch + ); + + forward_to_block::(14); + }) +} + +#[test] +#[serial] +fn test_deposit_ferry_without_tip() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + let deposit = messages::Deposit { + requestId: RequestId::new(Origin::L1, 1u128), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(MILLION), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + }; + + let alice_balance_before = + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &ALICE); + let charlie_balance_before = + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE); + + let update = + L1UpdateBuilder::default().with_requests(vec![deposit.clone().into()]).build(); + + Rolldown::ferry_deposit( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + deposit.requestId, + deposit.depositRecipient, + deposit.tokenAddress, + deposit.amount.saturated_into::(), + deposit.timeStamp.saturated_into::(), + deposit.ferryTip.saturated_into::(), + deposit.abi_encode_hash(), + ) + .unwrap(); + + let alice_balance_after = TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &ALICE); + let charlie_balance_after = + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE); + let ferried_amount = deposit.amount - deposit.ferryTip; + + assert_eq!( + alice_balance_before - alice_balance_after, + ferried_amount.try_into().unwrap() + ); + assert_eq!( + charlie_balance_after - charlie_balance_before, + ferried_amount.try_into().unwrap() + ); + + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); + + forward_to_block::(16); + assert_eq!( + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE), + charlie_balance_after + ); + assert_eq!( + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &ALICE), + alice_balance_before + deposit.ferryTip.saturated_into::() + ); + }) +} + +#[test] +#[serial] +fn test_deposit_ferry_with_tip() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + let deposit = messages::Deposit { + requestId: RequestId::new(Origin::L1, 1u128), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(MILLION), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(10 * THOUSAND), + }; + + let alice_balance_before = + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &ALICE); + let charlie_balance_before = + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE); + + let update = + L1UpdateBuilder::default().with_requests(vec![deposit.clone().into()]).build(); + + Rolldown::ferry_deposit( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + deposit.requestId, + deposit.depositRecipient, + deposit.tokenAddress, + deposit.amount.saturated_into::(), + deposit.timeStamp.saturated_into::(), + deposit.ferryTip.saturated_into::(), + deposit.abi_encode_hash(), + ) + .unwrap(); + + let alice_balance_after = TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &ALICE); + let charlie_balance_after = + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE); + let ferried_amount = deposit.amount - deposit.ferryTip; + + assert_eq!( + alice_balance_before - alice_balance_after, + ferried_amount.try_into().unwrap() + ); + assert_eq!( + charlie_balance_after - charlie_balance_before, + ferried_amount.try_into().unwrap() + ); + + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); + + forward_to_block::(16); + assert_eq!( + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE), + charlie_balance_after + ); + assert_eq!( + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &ALICE), + alice_balance_before + deposit.ferryTip.saturated_into::() + ); + }) +} + +#[test] +#[serial] +fn test_ferry_deposit_that_fails() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, u128::MAX) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + let deposit = messages::Deposit { + requestId: RequestId::new(Origin::L1, 1u128), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(u128::MAX), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(1u128), + }; + + let alice_balance_before = + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &ALICE); + let charlie_balance_before = + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE); + + let update = + L1UpdateBuilder::default().with_requests(vec![deposit.clone().into()]).build(); + + Rolldown::ferry_deposit( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + deposit.requestId, + deposit.depositRecipient, + deposit.tokenAddress, + deposit.amount.saturated_into::(), + deposit.timeStamp.saturated_into::(), + deposit.ferryTip.saturated_into::(), + deposit.abi_encode_hash(), + ) + .unwrap(); + + let alice_balance_after = TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &ALICE); + let charlie_balance_after = + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE); + let ferried_amount = deposit.amount - deposit.ferryTip; + + assert_eq!( + alice_balance_before - alice_balance_after, + ferried_amount.try_into().unwrap() + ); + assert_eq!( + charlie_balance_after - charlie_balance_before, + ferried_amount.try_into().unwrap() + ); + + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); + + forward_to_block::(16); + + assert_event_emitted!(Event::RequestProcessedOnL2 { + chain: messages::Chain::Ethereum, + request_id: 1u128, + status: Err(L1RequestProcessingError::MintError), + }); + + assert_err!( + Rolldown::refund_failed_deposit( + RuntimeOrigin::signed(CHARLIE), + consts::CHAIN, + deposit.requestId.id + ), + Error::::NotEligibleForRefund + ); + + Rolldown::refund_failed_deposit( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + deposit.requestId.id, + ) + .unwrap(); + + assert_event_emitted!(Event::DepositRefundCreated { + refunded_request_id: RequestId::new(Origin::L1, 1u128), + chain: Chain::Ethereum, + ferry: Some(ALICE), + }); + }) +}