Assessment reports>Chainflip Backend>Threat Model>Function: ccm_deposit

Function: ccm_deposit

Processes a CCM by calling the internal on_ccm_deposit function. This function is intended to be called by the witnesser.

The destination address must be a valid address and be on the same chain as the destination asset.

If the CCM destination is not Ethereum, the principal is less than the gas budget, or the CCM intends to swap but the input asset is less than the minimum amount, it confiscates the assets and locks them in the protocol. This logic is implemented in the principal_and_gas_amounts function:

let gas_budget = channel_metadata.gas_budget; let principal_swap_amount = deposit_amount.saturating_sub(gas_budget); if ForeignChain::Ethereum != destination_asset.into() { return Err(CcmFailReason::UnsupportedForTargetChain) } else if deposit_amount < gas_budget { return Err(CcmFailReason::InsufficientDepositAmount) } else if source_asset != destination_asset && !principal_swap_amount.is_zero() && principal_swap_amount < MinimumSwapAmount::::get(source_asset) { // If the CCM's principal requires a swap and is non-zero, // then the principal swap amount must be above minimum swap amount required. return Err(CcmFailReason::PrincipalSwapAmountTooLow) }

Next, the CCM is determined to be either a swap CCM, which may also carry a message, or a messaging-only CCM (i.e., without a swap). If the source and destination assets are the same or the principal swap amount (after subtracting the gas budget) is zero, the CCM is a messaging-only swap. Otherwise, it is a swap CCM, too.

If the destination asset is not the same as the destination asset's chain's gas asset, the CCM requires an additional swap from the principal asset to the gas asset. This happens independently of whether the CCM intends to carry a swap or be messaging only:

let gas_swap_id = if let Some(other_gas_asset) = other_gas_asset { let swap_id = Self::schedule_swap_internal( source_asset, other_gas_asset, gas_budget, SwapType::CcmGas(ccm_id), ); Self::deposit_event(Event::::SwapScheduled { swap_id, source_asset, deposit_amount: gas_budget, destination_asset: other_gas_asset, destination_address: encoded_destination_address.clone(), origin, swap_type: SwapType::CcmGas(ccm_id), broker_commission: None, }); Some(swap_id) } else { swap_output.gas = Some(gas_budget); None };

If a gas swap is not required and the CCM is messaging only, the CCM is executed immediately. Otherwise, it is scheduled to be executed during block finalization (on_finalize):

if let Some((principal, gas)) = swap_output.completed_result() { Self::schedule_ccm_egress(ccm_id, ccm_swap, (principal, gas)); } else { PendingCcms::::insert(ccm_id, ccm_swap); CcmOutputs::::insert(ccm_id, swap_output); }

Note that if any swaps raise an error and revert the entire swapping transaction in on_finalize, no future CCMs will work because the on_finalize function will always revert. We did not identify a way to cause such an error to happen that would permanently cause a denial-of-service issue (lack of liquidity can be quickly fixed by depositing LP).

Zellic © 2024Back to top ↑