Skip to content

Commit

Permalink
Ferries (#812)
Browse files Browse the repository at this point in the history
  • Loading branch information
mateuszaaa authored Sep 23, 2024
2 parents 9c10518 + e2cb076 commit 73344bd
Show file tree
Hide file tree
Showing 6 changed files with 774 additions and 155 deletions.
1 change: 0 additions & 1 deletion pallets/rolldown/rpc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// Copyright (C) 2021 Mangata team

use jsonrpsee::{
core::{async_trait, Error as JsonRpseeError, RpcResult},
proc_macros::rpc,
Expand Down
220 changes: 193 additions & 27 deletions pallets/rolldown/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = <<T as Config>::Tokens as MultiTokenCurrency<
Expand Down Expand Up @@ -66,6 +66,14 @@ impl Convert<[u8; 20], sp_runtime::AccountId20>
}
}

impl ConvertBack<[u8; 20], sp_runtime::AccountId20>
for EthereumAddressConverter<sp_runtime::AccountId20>
{
fn convert_back(eth_addr: sp_runtime::AccountId20) -> [u8; 20] {
eth_addr.into()
}
}

#[derive(Clone)]
pub struct Keccak256Hasher {}

Expand All @@ -80,6 +88,23 @@ impl Hasher for Keccak256Hasher {
}
}

#[derive(PartialEq, RuntimeDebug, Clone, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub enum L1DepositProcessingError {
Overflow,
AssetRegistrationProblem,
MintError,
}

impl<T> From<L1DepositProcessingError> for Error<T> {
fn from(e: L1DepositProcessingError) -> Self {
match e {
L1DepositProcessingError::Overflow => Error::<T>::BalanceOverflow,
L1DepositProcessingError::AssetRegistrationProblem => Error::<T>::L1AssetCreationFailed,
L1DepositProcessingError::MintError => Error::<T>::MintError,
}
}
}

#[derive(PartialEq, RuntimeDebug, Clone, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub enum L1RequestProcessingError {
Overflow,
Expand All @@ -90,6 +115,16 @@ pub enum L1RequestProcessingError {
SequencerNotSlashed,
}

impl From<L1DepositProcessingError> 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;

Expand Down Expand Up @@ -154,10 +189,14 @@ pub mod pallet {
Submitter,
}

#[pallet::storage]
pub type FerriedDeposits<T: Config> =
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<T: Config> =
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)]
Expand Down Expand Up @@ -313,11 +352,13 @@ pub mod pallet {
token_address: [u8; 20],
amount: u128,
hash: H256,
ferry_tip: u128,
},
ManualBatchExtraFeeSet(BalanceOf<T>),
DepositRefundCreated {
chain: ChainIdOf<T>,
refunded_request_id: RequestId,
ferry: Option<AccountIdOf<T>>,
},
L1ReadScheduledForExecution {
chain: T::ChainId,
Expand All @@ -327,6 +368,11 @@ pub mod pallet {
chain: T::ChainId,
hash: H256,
},
DepositFerried {
chain: T::ChainId,
deposit: messages::Deposit,
deposit_hash: H256,
},
}

#[pallet::error]
Expand Down Expand Up @@ -356,10 +402,14 @@ pub mod pallet {
InvalidRange,
NonExistingRequestId,
UnknownAliasAccount,
FailedDepositDoesExists,
FailedDepositDoesNotExist,
EmptyBatch,
TokenDoesNotExist,
NotDepositRecipient,
NotEligibleForRefund,
FerryHashMismatch,
MintError,
AssetRegistrationProblem,
UpdateHashMishmatch,
}

#[pallet::config]
Expand All @@ -371,7 +421,7 @@ pub mod pallet {
ChainIdOf<Self>,
>;
type SequencerStakingRewards: SequencerStakingRewardsTrait<Self::AccountId, RoundIndex>;
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<Self::AccountId>
+ MultiTokenReservableCurrency<Self::AccountId>
Expand Down Expand Up @@ -435,8 +485,13 @@ pub mod pallet {
pub fn update_l2_from_l1(
origin: OriginFor<T>,
requests: messages::L1Update,
update_hash: H256,
) -> DispatchResult {
let sequencer = ensure_signed(origin)?;

let hash = requests.abi_encode_hash();
ensure!(update_hash == hash, Error::<T>::UpdateHashMishmatch);

Self::update_impl(sequencer, requests)
}

Expand Down Expand Up @@ -529,6 +584,7 @@ pub mod pallet {
recipient: [u8; 20],
token_address: [u8; 20],
amount: u128,
ferry_tip: Option<u128>,
) -> DispatchResultWithPostInfo {
let account = ensure_signed(origin)?;

Expand Down Expand Up @@ -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::<T>::insert(
Expand All @@ -604,6 +661,7 @@ pub mod pallet {
token_address,
amount,
hash: withdrawal_update.abi_encode_hash(),
ferry_tip: ferry_tip.unwrap_or_default(),
});
TotalNumberOfWithdrawals::<T>::mutate(|v| *v = v.saturating_add(One::one()));

Expand Down Expand Up @@ -693,14 +751,19 @@ pub mod pallet {
let sender = ensure_signed(origin)?;

// NOTE: failed deposits are not reachable at this point
let _ = FailedL1Deposits::<T>::take((sender, chain, request_id))
.ok_or(Error::<T>::FailedDepositDoesExists)?;
let (author, deposit_hash) = FailedL1Deposits::<T>::take((chain, request_id))
.ok_or(Error::<T>::FailedDepositDoesNotExist)?;

let ferry = FerriedDeposits::<T>::get((chain, deposit_hash));
let eligible_for_refund = ferry.clone().unwrap_or(author.clone());
ensure!(eligible_for_refund == sender, Error::<T>::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::<T>::insert(
Expand All @@ -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())
Expand Down Expand Up @@ -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<T>,
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::<T>::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<T>,
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<T>,
requests: messages::L1Update,
) -> DispatchResult {
let sequencer = ensure_signed(origin)?;
Self::update_impl(sequencer, requests)
}
}
}

Expand Down Expand Up @@ -882,12 +1014,18 @@ impl<T: Config> Pallet<T> {
}

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::<T>::mutate(|v| *v = v.saturating_add(One::one()));
deposit_status.or_else(|err| {
let who: T::AccountId = T::AddressConverter::convert(deposit.depositRecipient);
FailedL1Deposits::<T>::insert((who, l1, deposit.requestId.id), ());
Err(err)
}),
FailedL1Deposits::<T>::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();
Expand Down Expand Up @@ -985,30 +1123,26 @@ impl<T: Config> Pallet<T> {
/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
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::<u128>::try_into(deposit_request_details.amount)
.map_err(|_| L1RequestProcessingError::Overflow)?
deposit: &messages::Deposit,
) -> Result<(), L1DepositProcessingError> {
let amount = TryInto::<u128>::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::<T>::get((l1, deposit.abi_encode_hash()))
.unwrap_or(T::AddressConverter::convert(deposit.depositRecipient));

TotalNumberOfDeposits::<T>::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(())
}
Expand Down Expand Up @@ -1489,6 +1623,38 @@ impl<T: Config> Pallet<T> {
Ok(sender.clone())
}
}

fn ferry_desposit_impl(
sender: T::AccountId,
chain: T::ChainId,
deposit: messages::Deposit,
) -> Result<(), Error<T>> {
let deposit_hash = deposit.abi_encode_hash();

let amount = deposit
.amount
.checked_sub(deposit.ferryTip)
.and_then(|v| TryInto::<u128>::try_into(v).ok())
.and_then(|v| TryInto::<BalanceOf<T>>::try_into(v).ok())
.ok_or(Error::<T>::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::<T>::AssetRegistrationProblem)?,
};

let account = T::AddressConverter::convert(deposit.depositRecipient);

T::Tokens::transfer(asset_id, &sender, &account, amount, ExistenceRequirement::KeepAlive)
.map_err(|_| Error::<T>::NotEnoughAssets)?;
FerriedDeposits::<T>::insert((chain, deposit_hash), sender);

Self::deposit_event(Event::DepositFerried { chain, deposit, deposit_hash });

Ok(().into())
}
}

impl<T: Config> RolldownProviderTrait<ChainIdOf<T>, AccountIdOf<T>> for Pallet<T> {
Expand Down
Loading

0 comments on commit 73344bd

Please sign in to comment.